{"id":25872851,"url":"https://github.com/hcschuetz/pegeal","last_synced_at":"2025-08-28T22:11:53.650Z","repository":{"id":257250892,"uuid":"857726086","full_name":"hcschuetz/pegeal","owner":"hcschuetz","description":"Generate code for geometric-algebra expressions by partial evaluation","archived":false,"fork":false,"pushed_at":"2024-10-24T09:59:19.000Z","size":555,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-02T08:33:45.891Z","etag":null,"topics":["code-generation","code-generator","geometric-algebra","partial-evaluation","partial-evaluator"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hcschuetz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-15T12:56:03.000Z","updated_at":"2024-10-24T09:59:22.000Z","dependencies_parsed_at":"2024-09-15T14:45:25.381Z","dependency_job_id":"a453e37e-4475-43e2-b8e5-60101b70d9f8","html_url":"https://github.com/hcschuetz/pegeal","commit_stats":null,"previous_names":["hcschuetz/pegeal"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hcschuetz/pegeal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hcschuetz%2Fpegeal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hcschuetz%2Fpegeal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hcschuetz%2Fpegeal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hcschuetz%2Fpegeal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hcschuetz","download_url":"https://codeload.github.com/hcschuetz/pegeal/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hcschuetz%2Fpegeal/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272566652,"owners_count":24956620,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-28T02:00:10.768Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code-generation","code-generator","geometric-algebra","partial-evaluation","partial-evaluator"],"created_at":"2025-03-02T08:28:51.847Z","updated_at":"2025-08-28T22:11:53.625Z","avatar_url":"https://github.com/hcschuetz.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"PEGEAL:\u003cbr\u003ePartial Evaluation for Geometric Algebra\n===================================================\n\nThis package translates geometric-algebra expressions to lower-level code.\n\nCurrently WebGL and WebAssembly (WASM) can be generated, but implementing\nbackends for other languages is very little effort.\n\n\u003e ## WARNING\n\u003e\n\u003e This is not a ready-made library but still experimental code.\n\u003e\n\u003e Before using it for productive purposes you should\n\u003e - understand the code generator or the generated code,\n\u003e - be able to integrate the generated code with the rest of your application,\n\u003e - not depend on this package being maintained in any way,\n\u003e - agree to the conditions of the MIT license.\n\u003e\n\u003e (As of mid September 2024 I have not even got around to\n\u003e actually execute any of the generated code.\n\u003e But that's on my TODO list.)\n\u003e \n\u003e **Also this README still needs some work.**\n\u003e\n\u003e OTOH, the core partial-evaluation code has reached some maturity.\n\nSince the package is implemented in TypeScript,\nit is not only possible to create code in a build step or in a server.\nCode (for example, WebGL or WebAssembly) can also be generated on demand\nin a web browser.\n\n\n## TL;DR\n\n```sh\ngit clone https://github.com/hcschuetz/pegeal.git\ncd pegeal\nnpm install\n```\nHave a look at [`examples/introduction.ts`](examples/introduction.ts).\n```sh\nnode --import=tsx examples/introduction.ts # or use deno or bun\n```\nHave a look at the output.\n\nThen play with the introduction example and also run other examples.\nThe multi-backend example\n([`examples/multi-backend.ts`](examples/multi-backend.ts)) might be particularly\ninteresting.\n\n## Algebras\n\nMultivectors do not exist in empty space.  Instead each multivector belongs to\nan algebra.\n\nOperations on multivectors are implemented as methods of the algebra,\ntaking the multivectors as parameters.\nMost operations of an algebra taking multivector parameters require that these\nbelong to that same algebra.\nThis avoids accidental cross-algebra operations,\nwhich usually have no well-defined meaning.\n\nAn algebra has a metric\nand uses it in products and other operations on multivectors.\nThis way it is not necessary to explicitly pass the metric as a parameter\nto each operation.\nWe only support Euclidean and pseudo-Euclidean metrics, that is,\nwe assume that the basis vectors of the algebra are pairwise orthogonal.\n\nIf needed, an outermorphism can be used to convert a multivector\nfrom one algebra to another.\nThis can be used to implement an algebra with a more general metric\n(such as a conformal geometric algebra)\non top of another algebra with a (pseudo-)Euclidean metric,\nbut for now such an implementation is not provided.\n\nFurthermore the algebra knows coordinate names for all basis blades.\nThis is used to parse and to print multivectors.\n(The multivectors themselves just identify the basis blades by numeric indices.)\n\nThe caller has to pass these parameters to the `Algebra` constructor:\n\n- Metric factors for the basis vectors.\n  The length of this list also determines the dimensionality `n` of the algebra.\n\n  A (trivial) helper function `euclidean` can be used to make it explicit\n  in the code that a Euclidean metric is being used.\n\n- A back-end that knows how to generate code for a particular target language.\n  (See section \"[Back-End Implementations](#back-end-implementations)\".)\n\n- A list of basis-blade names.\n  This list should have length `2**n` and should not contain duplicates.\n\n  Utility functions `makeLetterNames` and `makeNumberedNames`\n  are available to produce typical component names,\n  but the application programmer is free to choose a different naming scheme.\n  (Component names should however be usable as parts of target-language\n  identifiers names by some backends.)\n\n## Multivectors\n\n### Logical Structure\n\nA basis blade is a set of basis vectors.\nEssentially a multivector is a partial map from basis blades to magnitudes.\nThe latter are either numbers or symbolic values.\n\nThus a component of a multivector for some basis blade\ncan be in one of these states:\n- It can be missing, which is interpreted as magnitude 0.\n- It can be a number providing the magnitude at code-generation time.\n- It can be a symbolic value,\n  that is, a reference to the target-language variable\n  which will hold the magnitude at run time.\n\n### Minimizing Run-Time Data and Computations\n\nThe algebra operations attempt to avoid symbolic values\nand their computation by generated run-time code whenever possible.\n\nBut note that this optimization is only performed per-operation\nand not globally.\nSo Pegeal will not recognize that certain complex expressions such as\n`M + (-M)` or `(a ∧ b) + (b ∧ a)` (with 1-vectors `a` and `b`) are actually 0.\nSimilarly, a sandwich product `a b ~a` (with a versor `a` and a 1-vector `b`)\nmay produce a multivector with a grade-3 component even though that component\nwill always be 0 at run time.\n\nIn such cases the application programmer should either re-formulate\nthe algebraic expressions or explicitly drop some components using the method\n`extract` or `extractGrade`.\n(Multivector products also accept an inclusion condition for pairs of basis\nblades.  See [below](#products).\nAlso the [`sandwich`](#sandwich-products) method helps to avoid creating\nsuperfluous components.)\n\n### Data Structure\n\nTechnically we represent a basis blade as a bitmap,\nthat is, a non-negative integer.\nThe component map is implemented as a sparse JavaScript array where\nthe indices are the bitmaps and\nthe values are the magnitudes (numbers or symbolic).\n\n### Multivector Creation and Initialization\n\nThe `Multivector` constructor takes\n- the algebra to which the multivector will belong,\n- a name (essentially for debugging purposes),\n- and an initialization function.\n\nThat initialization function will be invoked immediately\nfrom within the multivector constructor.\nIt receives a callback function usually named `add`.\n(Since the initialization function is already a callback,\nthe `add` function could be called a \"second-order\" callback.)\nThe `add` callback is used to populate the multivector.\nIt takes two parameters:\n- a basis blade represented as a bitmap telling to which component\n  this addition contributes\n- and a numeric or symbolic value to be added to the component.\n\nNotice that `add` is *not* a setter function for multivector components.\nYou can call it multiple times with the same basis blade\nand the provided values will be added up.\n\nThis API is quite convenient for creating new multivectors in the algebra code:\n- It protects the multivector from being modified inadvertently\n  after construction.\n- There is no need to group the terms by basis blade.\n  This is done automatically behind the scenes.\n- It allows for fluent programming.  Instead of the usual pattern\n  ```js\n  const result = new Multivector(...);\n  for various cases {\n    result.add(...);\n  }\n  return result;\n  ```\n  one simply writes\n  ```js\n  return new Multivector(..., add =\u003e {\n    for various cases {\n      add(...);\n    }\n  });\n  ```\n  without the need for an auxiliary variable `result`.\n\n### Known Squared Norm\n...\n- simplifies/avoids normalization, inversion, and computation of norms.\n- keeps track of the squared norm of a multivector if it is known at compile\n  time (even if the components are not yet known).\n- set automatically in some operations, e.g.:\n  - after normalization\n- can also be set by the user (that is, the application programmer)\n  - e.g. slerp, if we did not have it in the algebra\n- .withSqNorm(...) for fluent programming\n\n...similar idea: versorness flag\n- log a warning (or throw?) if a method expecting a versor gets called with\n  a (possibly) non-versor\n\n## Back-Ends\n\nAn algebra uses a back-end to actually generate lower-level code.\n\nVarious types have a type parameter `T`.  It represents the type that the\nback-end uses to represent \"symbolic values\", that is, scalar variables\nin the target language.\n\nFor example:\n- `Scalar\u003cT\u003e`: A scalar value, either a number known at code-generation time\n  or a symbolic value.\n- `Var\u003cT\u003e`: A scalar variable in the \"source language\",\n  possibly (if the value cannot be determined at code-generation time)\n  backed by a scalar variable of type `T` in the \"target language\".\n- `Multivector\u003cT\u003e`: A multivector whose component magnitudes are stored as\n  `Var\u003cT\u003e`s.\n\n### Variables\n\nEach back-end has its subclass of `Var\u003cT\u003e`.\nAn instance of `Var\u003cT\u003e` represents a source-level scalar variable,\nwhich can be a component magnitude in a multivector\nor an intermediate or final result of some scalar computation.\n\nThe abstract class `Var\u003cT\u003e` provides API methods `add` and `value`.\n- It starts with value 0 upon creation.\n- Then (numeric and/or symbolic) values can be added to the variable.\n- Finally the value can be retrieved.\n  It is a number or a symbolic value of type `T`,\n  that is, a reference to a target-language variable.\n\nAs soon as the value has been retrieved, the variable is \"frozen\", that is,\nno more values may be added.  This is to detect bugs where a variable's value\nis retrieved before it is completely computed.\n\nThe `add` method avoids creating the corresponding target-language\nvariable as long as possible, that is, as long as all the added terms are\nfully numeric and do not contain symbolic values.\n\n`Var\u003cT\u003e` requires its subclasses to provide similar API methods\n(`addValue` and `getValue`).\nThese are only called if and when a symbolic value is actually needed.\n\n### Back-End Implementations\n\n#### Dummy Back-End\n\nIn earlier versions\nthis back-end expected fully numeric input and no symbolic values.\nAccordingly, the type parameter here is `\u003cnever\u003e`,\nindicating that we  _never_ deal with variables.\nAll computations were performed immediately and a numeric result was returned,\n_never_ a symbolic value.\n\nMeanwhile the `Algebra` operations pre-calculate purely numeric expressions\nas far as possible to optimize code generated by any back-end.\nAs a consequence, most methods of this back-end and its variable implementation\nare no more invoked.\nThe methods now simply throw exceptions to verify this assumption.\n\n#### Emitting WebGL\n\nThe `WebGLBackend` is quite straight-forward.\n- It represents symbolic values as strings, which are the WebGL variable names.\n- A counter is used to create unique variable-name suffixes.\n- The generated code is written to a string.\n\n#### Emitting WebAssembly (\"WASM\")\n\nThe `WebAssemblyBackEnd` uses the `binaryen` API for code generation.\n- Symbolic values are of class `LocalRef`, which is just a wrapper around\n  the index of the local variable in the WASM function.\n  (The index cannot be used directly as it would be interpreted as the\n  actual value.)\n- A counter keeps track of used local-variable indices.\n- A list of `ExpressionRef`s is used to collect the created \"WASM statements\".\n\nA usage example even demonstrates how `binaryen` can be used\nas an intermediate representation and optimizer,\nwhich can be translated to yet another language.\n\n## Implementation Details\n\n### Products\n\nAll kinds of products are ultimately impemented by the single method `product2`.\n\nThe method considers each partial product of\nsome component `(A, α)` from the left operand and\nsome component `(B, β)` from the right operand\nfor inclusion in the result.\n(Here `A` and `B` are basis blades and\n`α` and `β` are the corresponding magnitudes.)\n\nThe product of `α` and `β` (and metric factors and a sign, see below)\nwill be used as a term of the result component for basis blade\n`Out := (A \\ B) ∪ (B \\ A)`\nunder the following conditions depending on the product kind:\n\n| product kind      | set-based condition | grade-based condition\n|-------------------|:-------------------:|----------------------\n| geometric         | `true`              | `true`\n| wedge (= outer)   | `A ⋂ B = {}`        | `\\|Out\\| = \\|A\\| + \\|B\\|`\n| left contraction  | `A ⊂ B`             | `\\|Out\\| = \\|B\\| - \\|A\\|`\n| right contraction | `A ⊃ B`             | `\\|Out\\| = \\|A\\| - \\|B\\|`\n| scalar            | `A = B`             | `\\|Out\\| = 0`\n| dot               | `A ⊂ B` or `A ⊃ B`  | `\\|Out\\| = abs(\\|A\\| - \\|B\\|)`\n\nHere `|S|` denotes the cardinality of a set `S`\nand thus the grade of a basis blade.\nTo avoid confusion, we used the different notation `abs(...)`\nfor the absolute value of a number in the \"dot\" line above.\n\nTo emphasize their analogy, the \"scalar\" and \"dot\" cases could be defined\nequivalently as:\n\n| product kind | set-based condition | grade-based condition\n|--------------|:-------------------:|:---------------------\n| scalar       | `A ⊂ B` and `A ⊃ B` | `\\|Out\\| = \\|B\\| - \\|A\\|` and `\\|Out\\| = \\|A\\| - \\|B\\|`\n| dot          | `A ⊂ B` or  `A ⊃ B` | `\\|Out\\| = \\|B\\| - \\|A\\|` or  `\\|Out\\| = \\|A\\| - \\|B\\|`\n\nNotice that in all cases\nthe set-based conditions can be formulated using just the inputs\nwhereas the grade-based conditions also depend on the result basis blade `Out`.\n\nI also consider the set conditions easier to understand.\nThey can also be implemented as bitmap operations easily.\nThe resulting basis blade `Out` is easily implemented\nas an XOR between the bitmaps for `A` and `B`.\n\nIn addition to the conditions described above, application programmers\ncan also provide their own inclusion conditions for pairs of base blades.\n\nThe metric factors of the basis vectors in `A ⋂ B` are included in the\npartial-product term.\nAs an optimization, the term is skipped if any of these factors is 0.\n\n\n### Flip Counting\n\nThe auxiliary function `productFlips(bitmapA, bitmapB)` computes the number\nof adjacent transpositions needed to ensure that the resulting list of basis\nblades is properly ordered.\nThe rest of this number modulo 2 (= the last bit of the binary representation)\ntells if the partial product must be negated or not.\n\n\u003e The time complexity of `productFlips` is `𝒪(Amax)`,\nwhere `Amax` is the highest basis-vector index in `A`,\neven though it does not rely on a constant-time `popcnt` operation.\nAll implementations I have seen so far either depend on `popcnt` or\nhave a time complexity of at least `𝒪(|A| * |B|)`.\n(But actually the performance does not matter much here\nsince this function is invoked at code-generation time, not at run time.)\n\nThere are also two unary helper functions\n- `gradeInvolutionFlips(bitmap)` and\n- `reverseFlips(bitmap)`.\n\nThe need for a sign flip in a grade involution can be determined by looking\nat the last bit of the basis blade's grade,\nthat is, the population count of the bitmap.\n\nThe condition for the reverse operation is very similar:\nJust look at the penultimate bit of the grade.\n\n\u003e It actually came as a bit of surprise to me that `(|A| * (|A|-1) / 2) % 2`\ncan be simplified to `(|A| \u003e\u003e 1) % 2`.\n\n\n### Sandwich Products\n\n...\n\n## Open Issues\n\n- A configuration method telling if a particular optimization step should be\n  performed or not.\n\n... The core task of generation is quite well-understood.\nBut it still needs to be figured out how to integrate this in a\ncomplete application.\n\n- Can be used to generate\n  - CPU code (e.g., code setting up meshes)\n  - GPU code\n    - vertex shaders\n    - fragment shaders\n\n... but where does it make most sense?\n\n... advantage: The same GA code can be converted to different targets.\n\n- A parser for a language supporting terser notation of GA expressions than the\n  API calls in TypeScript/JavaScript.\n\n- Support conformal geometric algebras\n\n- Interface to an existing \"3D engine\"?\n\n## Unsorted\n\nTopics to elaborate on:\n\n- The generated code will typically be processed by another compilation step.\n  It is expected that this compiler is able to perform various straight-forward\n  optimizations so that the algebra methods and the back-end need not care about\n  these lower-level details.\n\n  Nevertheless the algebra does perform pre-calculations\n  because they might lead to zero values,\n  which can be used to do higher-level optimizations in turn.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhcschuetz%2Fpegeal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhcschuetz%2Fpegeal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhcschuetz%2Fpegeal/lists"}