Skip to content

ga-explorer/GeometricAlgebraFulcrumLib

Repository files navigation

Geometric Algebra Fulcrum Library (GA-FuL)

Geometric Algebra Fulcrum Library (GA-FuL for short) is a unified generic C# library for Geometric Algebra computations using any kind of scalars (floating point, symbolic, etc.). GA-FuL can be used for prototyping geometric algorithms based on the powerful mathematics of geometric algebra. The GA-FuL code base can be used to perform numeric computations, symbolic manipulations, and optimized code generation.

Why The Name Geometric Algebra Fulcrum

Geometric Algebra is a powerful mathematical language that unifies many algebraic tools under the same framework of mathematical operations. Such tools include, for example, real vectors, complex numbers, quaternions, octanions, spinors, matrices, among others; along with their algebraic operations. The most important feature of GA is, however, that it unifies the geometric reasoning process among many seemingly diverse fields of application domains. Thus, Geometric Algebra acts as a Fulcrum for geometric reasoning across scientific and engineering domains. We can use this Geometric Algebra Fulcrum to seamlessly balance the ideal needs of geometric reasoning with the concrete tools of algebraic manipulation under a unifying mathematical framework.

On the other side, there is a need for software tools that act as a pivot point, i.e. a Fulcrum, for prototyping and implementing several kinds of computations on multivectors. Commonly required computations include numerical, symbolic, and code generation, among others. Writing and maintaining a separate code base for each kind is highly impractical. GA-FuL is intended to play the role of a Fulcrum for prototyping algorithms and implementing software based on Geometric Algebra.

Design Intentions

Architecture view for GeometricAlgebraFulcrumLib

GA-FuL is intended to:

  1. abstract and unify the representation and manipulation of multivectors as a layer of higher level generic computations on any kind of useful scalars (i.e. numbers).
  2. represent multivectors internally as sparsely as possible using several suitable data structures to reduce memory requirements for high-dimensional geometric algebras (up to 64 dimensions).
  3. provide a simple unified and generic Application Programming Interface (API) for several classes of applications, including, but not limited to, numerical computations, symbolic manipulations, and optimized code generation.
  4. implement an extensible layered approach to allow users of different backgrounds to select the suitable level of coding they can handle, ranging from a very high-level of coordinate-independent prototyping using GA operations to a fully-controlled lower level of direct manipulation of scalars and coordinates.

GA-FuL Usage

The following example shows a geometric construction for computing vertex positions of multidimensional regular simplex, and finding the radii ratio of its circumscribed and inscribed hyper-spheres. in 2-dimensions, the regular simplex is an equilateral triangle. The 2-dimensional vectors from the triangle's centroid to its vertices are actually projections of 3 orthonormal basis vectors from a 3-dimensional space. This basic geometric idea can be generalized to any higher dimensions without change.

Regular Simplex Construction in 3D

The main point here is the way GA-FuL is used to implement the construction in this example. The code begins by selecting a proper processor for the computation. In this example, a processor can be numeric (ScalarAlgebraFloat64Processor), or symbolic (either ScalarAlgebraMathematicaProcessor or ScalarAlgebraAngouriMathProcessor). The computation code itself is almost independent from the selected processor. Additionally, the computations are coordinates-independent, and dimensions-independents as illustrated in the code. The symbolic processors are very useful in finding mathematical patterns and doing more exact math computations with the geometric construction. Additionally, symbolic processors are the base for optimized code generation in GA-FuL.

using System;
using System.Linq;
using GeometricAlgebraFulcrumLib.Mathematica.Processors;
using GeometricAlgebraFulcrumLib.Mathematica.Text;
using GeometricAlgebraFulcrumLib.Processors.ScalarAlgebra;
using GeometricAlgebraFulcrumLib.Utilities.Composers;
using GeometricAlgebraFulcrumLib.Utilities.Extensions;
using GeometricAlgebraFulcrumLib.Utilities.Factories;

namespace GeometricAlgebraFulcrumLib.Samples.EuclideanGeometry
{
    public static class RegularSimplexSample
    {
        public static void Execute()
        {
            // Select suitable scalar processor for managing computations
            var scalarProcessor = ScalarAlgebraFloat64Processor.DefaultProcessor;

            // Select a suitable text composer for displaying results
            var textComposer = TextFloat64Composer.DefaultComposer;

            // You can also use other kinds of symbolic processors and text composers

            //var scalarProcessor = ScalarAlgebraMathematicaProcessor.DefaultProcessor;
            //var textComposer = LaTeXMathematicaComposer.DefaultComposer;

            //var scalarProcessor = ScalarAlgebraAngouriMathProcessor.DefaultProcessor;
            //var textComposer = TextAngouriMathComposer.DefaultComposer;

            // Make the same construction for dimensions 2, 3, ..., 10
            // Note that there is no explicit use of coordinates, the abstract geometric
            // idea is directly expressed in code
            for (var n = 2U; n < 10U; n++)
            {
                // The dimension of the larger GA space is one more than the dimension
                // of the target space
                var vSpaceDimension = n + 1;

                // Create a Euclidean geometric algebra processor based on the selected
                // scalar processor
                var processor = 
                    scalarProcessor.CreateGeometricAlgebraEuclideanProcessor(vSpaceDimension);
                
                // The ones vector is the core of this geometric construction
                var onesVector = processor
                    .CreateVectorOnes((int) vSpaceDimension);
                    //.MapScalars(expr => Mfs.N[expr].Evaluate());

                // This hyperspace is the orthogonal complement of the all-ones vector
                var hyperSpace = 
                    onesVector.GetDualSubspace();

                // Basis vectors of GA space
                var basisVectors =
                    processor.CreateVectorBasis().ToArray();

                // Simplex centroid to vertex vectors are projections of basis vectors
                // onto hyperSpace
                var simplexVertices =
                    basisVectors.ProjectOn(hyperSpace).ToArray();

                // Take the average of the first n basis vectors of the larger GA space
                var avgVector = 
                    processor.CreateVectorAverageOnes((int) n);

                // Projecting the average vector onto the hyperplane gives a radius of
                // the inner sphere
                var innerSphereRadius = 
                    hyperSpace.Project(avgVector).Norm();

                var outerSphereRadius =
                    simplexVertices[0].Norm();

                // The radius ratio of outer to inner spheres is equal to n
                var sphereRatio = 
                    outerSphereRadius / innerSphereRadius;

                // Find a Euclidean rotor that maps the ones vector into the last
                // basis vector of the GA space
                var rotor = 
                    onesVector.GetEuclideanRotorTo(basisVectors[n]);

                // Rotate the simplex vertices from the GA space to the target space
                // After rotation, the coordinate of the last basis vector is zero,
                // so it can be discarded
                var vertices =
                    simplexVertices.OmMapUsing(rotor).ToArray();


                //Display results
                Console.WriteLine($"n = {n}");
                Console.WriteLine();

                Console.WriteLine($"Inner Sphere Radius = {textComposer.GetScalarText(innerSphereRadius)}");
                Console.WriteLine();

                Console.WriteLine($"Outer Sphere Radius = {textComposer.GetScalarText(outerSphereRadius)}");
                Console.WriteLine();

                Console.WriteLine($"Sphere Ratio = {textComposer.GetScalarText(sphereRatio)}");
                Console.WriteLine();

                Console.WriteLine($"Vertex Positions in {n + 1} dimensions:");
                foreach (var position in simplexVertices)
                    Console.WriteLine($"   {textComposer.GetMultivectorText(position)}");
                Console.WriteLine();

                Console.WriteLine($"Vertex Positions in {n} dimensions:");
                foreach (var position in vertices)
                    Console.WriteLine($"   {textComposer.GetMultivectorText(position)}");
                Console.WriteLine();

                Console.WriteLine();
            }
        }
    }
}

GA-FuL Components

1. Storage Components

The storage components layer is designed to provide a set of generic classes for storing scalars, vectors, matrices, multivectors, and outermorphisms at the coordinates level, as compactly as possible.

1.1 Storing Linear Algebra Vectors and Matrices

Type Dependencies Diagram for ILinArrayStorage

The ILinVectorStorage<T> generic interface provides a unified representation of classical dense and sparse vectors of linear algebra. In GA-FuL, a vector can be viewed as a set of (index, scalar) tuples, where the index implicitly represents a specific basis vector of an arbitrary linear space. In GA-FuL, a linear vector as a whole implicitly represents a linear combination of basis vectors. Which basis is used depends on the usage context of the vector. In the storage layer, no information is retained regarding the specific linear space or the exact kind of scalars used.

The following is a list of important vector interfaces in the GA-FuL storage layer:

Classes derived from the generic ILinMatrixStorage<T> interface represent dense and sparse matrices. A GA-FuL matrix is conceptually a set of (row index, column index, scalar) tuples. As in the case of vectors, no information is stored in this layer about the exact basis used for the matrices, only the index-scalar structure of the matrix is represented internally.

The following is a list of important GA-FuL matrix storage interfaces:

1.2 Storing Geometric Algebra Multivectors and Outermorphisms

Type Dependencies Diagram for IMultivectorStorage

In geometric algebra, the space of multivectors is essentially a graded linear space with additional mathematical structure. A multivector is the sum of k-vectors, each of grade k. Each k-vector space is itself a smaller linear subspace of the larger multivectors linear space.

In GA-FuL, the additional mathematical structure of GA multivectors is encoded in several classes which internally have ILinVectorGradedStorage<T> objects to hold the actual (grade, index, scalar) data:

In GA-FuL there are two kinds of outermorphisms: computed and stored. A stored outermorphism internally contains a graded matrix, with some additional operations. The OutermorphismStorage<T> class represents the graded matrix storage of a stored outermorphism. Computed outermorphisms are described in following sections.

2. Processor Components Layer

Type Dependencies Diagram for IScalarAlgebraProcessor

The second layer of components in GA-FuL is the processors layer. This layer contains a collection of classes with unified interfaces to implement processing operations on scalars, vectors, matrices, multivectors, and outermorphisms. Processing operations include creating storage objects for specific purposes, applying common algebraic operations (addition, subtraction, products, etc.) and converting between storage objects (for example converting a set of (index, scalar) terms into a vector, converting a graded matrix into a stored outermorphism, etc.) There are generally 4 kinds of processors in GA-FuL described in the following subsections.

2.1 Scalar Algebra Processors

A scalar processor implements the generic interface IScalarAlgebraProcessor<T>. This interface implements basic operations on scalars, such as addition, subtraction, product, division, power, exponential, logarithm, and trigonometric functions. Additionally, many extension methods are defined in the GA-FuL utilities layer, described later, for more operations on scalars. The user can define a custom scalar processor by implementing the IScalarAlgebraProcessor<T> interface to any desired scalar type T.

There are some predefined scalar processors in GA-FuL such as:

2.2 Linear Algebra Processors

In GA-FuL, a linear processor is an extended scalar processor which implements either one of the two generic interfaces ILinearAlgebraProcessor<T> or ILinearAlgebraProcessor<TMatrix, TScalar>. A linear processor performs common operations on linear algebra vector and matrix storage objects such as creation of matrix objects, accessing matrix elements, addition and subtraction of matrices and vectors, eigen decomposition of matrices, etc.

The ILinearAlgebraProcessor<TMatrix, TScalar> interface has two predefined class implementations:

The other interface, ILinearAlgebraProcessor<T>, has a built-in implementation in GA-FuL; the LinearAlgebraProcessor<T> generic class. This class is a simpler generic version for processing GA-FuL linear algebra vector and matrix storage objects. Most of the basic linear algebra processing operations can be done using this class.

The other built-in implementation of the ILinearAlgebraProcessor<T> interface is the SymbolicContext class, which is the core symbolic processing class for optimized code generation in GA-FuL.

2.3 Geometric Algebra Processors

A GA processor is a linear processor for manipulating and computing with multivectors within a given GA space. Examples of operations a GA processor performs include addition and subtraction of multivector storage objects, GA products (geometric, outer, contraction, etc.), norm of multivectors, and inverses of blades and versors. The generic interface IGeometricAlgebraProcessor<T> is the base for all GA processors. There are 3 derived classes for specific GA spaces:

2.4 Symbolic Algebra Processors

One of the fundamental objectives of GA-FuL is to allow users to generate optimized code from GA-based algorithms. In the algebra layer of GA-FuL, the ISymbolicExpression interface is the base for symbolic expression trees designed specifically for optimized code generation from arbitrary computations (i.e. symbolic computations defined on scalars, vectors, matrices, multivectors, and outermorphisms). This symbolic algebra sub-layer is explained in later sections. Inside the processors layer, however, there are two important processors for this task:

The best way to learn about this part of GA-FuL is by browsing the code generation samples.

3. Algebra Components Layer

The GA-FuL algebra components layer contains a set of high-level interfaces and classes for simplifying the GA-FuL Application User Interface (API). Each class is a wrapper typically containing a storage member and a processor member. Additionally, each class defines wrappers around common processing operations specific to the class type.

The Scalar<T> class is a simple unified wrapper around a scalar type T and a suitable scalar algebra processor of type IScalarAlgebraProcessor<T>. Common operations on scalars are implemented through a simple API, for example instead of writing:

scalarStorage4 = scalarProcessor.Add(
    scalarStorage1, 
    scalarProcessor.Times(scalarStorage2, scalarStorage3)
)

the user can simply write:

scalar4 = scalar1 + scalar2 * scalar3

The same idea carries on for all classes in the algebra layer. Examples of these classes include:

  • LinVector<T> : This class contains a linear processor object of type ILinearAlgebraProcessor<T>, and a linear vector storage object of type ILinVectorStorage<T> for making computations with linear algebra vectors.
  • LinMatrix<T> : This class contains a linear processor object of type ILinearAlgebraProcessor<T>, and a linear matrix storage object of type ILinMatrixStorage<T> for making computations with linear algebra matrices.
  • LinMatrix<TMatrix, TScalar> : This class contains a linear processor object of type ILinearAlgebraProcessor<TMatrix, TScalar>, and a linear matrix storage object of type TMatrix for making computations with linear algebra matrices.
  • Vector<T> : A wrapper class around a geometric processor and a GA vector storage.
  • Bivector<T> : A wrapper class around a geometric processor and a GA bivector storage.
  • KVector<T> : A wrapper class around a geometric processor and a GA k-vector storage.
  • Multivector<T> : A wrapper class around a geometric processor and a GA multivector storage of any kind.
  • Outermorphism<T> : A wrapper class around a linear processor and an outermorphism storage.
  • OutermorphismsSequence<T> : Represents a composition sequence of arbitrary outermorphisms.
  • PureVersor<T> : Represents a simple versor expressed as a reflection on a hyperspace represented by its dual vector.
  • PureVersorsSequence<T> : Represents a composition sequence of arbitrary pure versors.
  • Versor<T> : Represents a general versor expressed using the geometric product of several vectors; i.e. a single multivector.
  • PureRotor<T> : Represents a simple rotor, which is the exponential of a 2-blade.
  • PureRotorsSequence<T> : Represents a composition sequence of pure rotors.
  • Rotor<T> : Represents a general rotor expressed as the geometric product (a single even multivector) of several simple rotors.
  • Projector<T> : Represents a subspace blade acting as a linear projection operator.

4. Geometry Components Layer

5. Utility Components

The utility components connects all other layers to implement many useful generic functions.

5.1 Basic Structures

The basic structures components are used as data infra-structure for GA-FuL classes; most notably:

  • GaFuLLookupTables : Holds lookup tables for computationally demanding GA operations for spaces up to 13 dimensions, such as the Euclidean geometric product sign of pairs of basis blades, the grades and indices of basis blades given their IDs, etc.
  • Record classes and interfaces : These provide small-data exchange between GA-FuL objects. For example, the IndexScalarRecord<T> class contains an Index and a Scalar member to hold information about a single (index, scalar) element in a vector.

5.2 Static Extension Classes

This is a set of static classes holding many extension methods for GA-FuL objects. Mostly, such extension methods are defined on GA-FuL generic interfaces in order to provide generic implementations of essential functions. For example, the KVectorGpUtils static class contains extension methods for implementing the geometric product of k-vector objects.

5.3 Static Factory Classes

GA-FuL factory classes contain extension methods for properly creating GA-FuL objects using simple inputs. For example, the LinVectorStorageFactory static class contains extension methods for creating linear algebra vector storage objects, using a single method call.

5.4 Object Composer Classes

Often the user needs to construct certain GA-FuL objects with more control over the composition of the object, which required several steps for object construction. In such scenarios, the use of simple factory extension methods is not suitable. GA-FuL contains various composer classes for this purpose. For example, the MatrixSparseStorageComposer<T> class implements many member methods for construction of sparse matrix storage and related objects.

Code Examples

GA-FuL Numeric Computations

The following example shows how to use numerical 64-bits floating point processors. More examples can be found under the GeometricAlgebraFulcrumLib.Samples.Numeric name space.

using System;
using GeometricAlgebraFulcrumLib.Processors.ScalarAlgebra;
using GeometricAlgebraFulcrumLib.Utilities.Composers;
using GeometricAlgebraFulcrumLib.Utilities.Extensions;
using GeometricAlgebraFulcrumLib.Utilities.Factories;

namespace GeometricAlgebraFulcrumLib.Samples.Numeric
{
    public static class Sample1
    {
        public static void Execute()
        {
            // This is a pre-defined scalar processor for the standard
            // 64-bit floating point scalars
            var scalarProcessor = ScalarAlgebraFloat64Processor.DefaultProcessor;

            // Create a 3-dimensional Euclidean geometric algebra processor based on the
            // selected scalar processor
            var geometricProcessor = 
                scalarProcessor.CreateGeometricAlgebraEuclideanProcessor(3);

            // This is a pre-defined text generator for displaying multivectors
            // with 64-bit floating point scalars
            var textComposer = TextFloat64Composer.DefaultComposer;

            // This is a pre-defined LaTeX generator for displaying multivectors
            // with 64-bit floating point scalars
            var latexComposer = LaTeXFloat64Composer.DefaultComposer;

            // Create two GA vectors each having 3 components
            var u = geometricProcessor.CreateVector(1.2, -1, 1.25);
            var v = geometricProcessor.CreateVector(2.1, 0.9, 2.1);

            // Compute their outer product as a bivector
            var bv = u.Op(v);

            // Display a text representation of the vectors and their outer product
            Console.WriteLine($@"u = {textComposer.GetMultivectorText(u)}");
            Console.WriteLine($@"v = {textComposer.GetMultivectorText(v)}");
            Console.WriteLine($@"u op v = {textComposer.GetMultivectorText(bv)}");
            Console.WriteLine();

            // Display a LaTeX representation of the vectors and their outer product
            Console.WriteLine($@"\boldsymbol{{u}} = {latexComposer.GetMultivectorText(u)}");
            Console.WriteLine($@"\boldsymbol{{v}} = {latexComposer.GetMultivectorText(v)}");
            Console.WriteLine($@"\boldsymbol{{u}}\wedge\boldsymbol{{v}} = {latexComposer.GetMultivectorText(bv)}");
            Console.WriteLine();
        }
    }
}

The final output is:

u = '1.2'<1>, '-1'<2>, '1.25'<3>
v = '2.1'<1>, '0.9'<2>, '2.1'<3>
u op v = '3.18'<1, 2>, '-0.105'<1, 3>, '-3.225'<2, 3>

\boldsymbol{u} = \left( 1.2 \right) \boldsymbol{e}_{1} + \left( -1 \right) \boldsymbol{e}_{2} + \left( 1.25 \right) \boldsymbol{e}_{3}
\boldsymbol{v} = \left( 2.1 \right) \boldsymbol{e}_{1} + \left( 0.9 \right) \boldsymbol{e}_{2} + \left( 2.1 \right) \boldsymbol{e}_{3}
\boldsymbol{u}\wedge\boldsymbol{v} = \left( 3.18 \right) \boldsymbol{e}_{1,2} + \left( -0.105 \right) \boldsymbol{e}_{1,3} + \left( -3.225 \right) \boldsymbol{e}_{2,3}

GA-FuL Symbolic Computations

The following two examples shows how to use the predefined symbolic processors. More examples can be found under the GeometricAlgebraFulcrumLib.Samples.Symbolic name space. Note that the code is almost identical to the numeric example above.

The first symbolic processor is the ScalarAlgebraAngouriMathProcessor, which depends on Entity objects of the AngouriMath symbolic algebra library:

using System;
using GeometricAlgebraFulcrumLib.Processors.ScalarAlgebra;
using GeometricAlgebraFulcrumLib.Utilities.Composers;
using GeometricAlgebraFulcrumLib.Utilities.Extensions;
using GeometricAlgebraFulcrumLib.Utilities.Factories;

namespace GeometricAlgebraFulcrumLib.Samples.Symbolic.AngouriMath
{
    public static class Sample2
    {
        public static void Execute()
        {
            // This is a pre-defined scalar processor for the symbolic
            // AngouriMath scalars using Entity objects
            var scalarProcessor = ScalarAlgebraAngouriMathProcessor.DefaultProcessor;
            
            // Create a 3-dimensional Euclidean geometric algebra processor based on the
            // selected scalar processor
            var geometricProcessor = scalarProcessor.CreateGeometricAlgebraEuclideanProcessor(3);

            // This is a pre-defined text generator for displaying multivectors
            // with symbolic AngouriMath scalars using Entity objects
            var textComposer = TextAngouriMathComposer.DefaultComposer;

            // This is a pre-defined LaTeX generator for displaying multivectors
            // with symbolic AngouriMath scalars using Entity objects
            var latexComposer = LaTeXAngouriMathComposer.DefaultComposer;

            // Create two vectors each having 3 components (a 3-dimensional GA)
            var u = geometricProcessor.CreateVectorFromText(3, i => $"u_{i + 1}");
            var v = geometricProcessor.CreateVectorFromText(3, i => $"v_{i + 1}");

            // Compute their outer product as a bivector
            var bv = u.Op(v);

            // Display a text representation of the vectors and their outer product
            Console.WriteLine($@"u = {textComposer.GetMultivectorText(u)}");
            Console.WriteLine($@"v = {textComposer.GetMultivectorText(v)}");
            Console.WriteLine($@"u op v = {textComposer.GetMultivectorText(bv)}");
            Console.WriteLine();

            // Display a LaTeX representation of the vectors and their outer product
            Console.WriteLine($@"\boldsymbol{{u}} = {latexComposer.GetMultivectorText(u)}");
            Console.WriteLine($@"\boldsymbol{{v}} = {latexComposer.GetMultivectorText(v)}");
            Console.WriteLine($@"\boldsymbol{{u}}\wedge\boldsymbol{{v}} = {latexComposer.GetMultivectorText(bv)}");
            Console.WriteLine();
        }
    }
}

The final output is:

u = 'u_1'<1>, 'u_2'<2>, 'u_3'<3>
v = 'v_1'<1>, 'v_2'<2>, 'v_3'<3>
u op v = 'u_1 * v_2 + -u_2 * v_1'<1, 2>, 'u_1 * v_3 + -u_3 * v_1'<1, 3>, 'u_2 * v_3 + -u_3 * v_2'<2, 3>

\boldsymbol{u} = \left( u_{1} \right) \boldsymbol{e}_{1} + \left( u_{2} \right) \boldsymbol{e}_{2} + \left( u_{3} \right) \boldsymbol{e}_{3}
\boldsymbol{v} = \left( v_{1} \right) \boldsymbol{e}_{1} + \left( v_{2} \right) \boldsymbol{e}_{2} + \left( v_{3} \right) \boldsymbol{e}_{3}
\boldsymbol{u}\wedge\boldsymbol{v} = \left( u_{1} v_{2}+\left(-1\right) \cdot u_{2} v_{1} \right) \boldsymbol{e}_{1,2} + \left( u_{1} v_{3}+\left(-1\right) \cdot u_{3} v_{1} \right) \boldsymbol{e}_{1,3} + \left( u_{2} v_{3}+\left(-1\right) \cdot u_{3} v_{2} \right) \boldsymbol{e}_{2,3}

The same GA-FuL computation could be performed using the ScalarAlgebraMathematicaProcessor processor, which depends on Wolfram Mathematica and its Expr object for manipulating general symbolic scalar expressions:

using System;
using GeometricAlgebraFulcrumLib.Mathematica.Processors;
using GeometricAlgebraFulcrumLib.Mathematica.Text;
using GeometricAlgebraFulcrumLib.Utilities.Extensions;
using GeometricAlgebraFulcrumLib.Utilities.Factories;

namespace GeometricAlgebraFulcrumLib.Samples.Symbolic.Mathematica
{
    public static class Sample1
    {
        public static void Execute()
        {
            // This is a pre-defined scalar processor for symbolic
            // Wolfram Mathematica scalars using Expr objects
            var scalarProcessor = ScalarAlgebraMathematicaProcessor.DefaultProcessor;
            
            // Create a 3-dimensional Euclidean geometric algebra processor based on the
            // selected scalar processor
            var geometricProcessor = scalarProcessor.CreateGeometricAlgebraEuclideanProcessor(3);

            // This is a pre-defined text generator for displaying multivectors
            // with symbolic Wolfram Mathematica scalars using Expr objects
            var textComposer = MathematicaTextComposer.DefaultComposer;

            // This is a pre-defined LaTeX generator for displaying multivectors
            // with symbolic Wolfram Mathematica scalars using Expr objects
            var latexComposer = MathematicaLaTeXComposer.DefaultComposer;

            // Create two vectors each having 3 components (a 3-dimensional GA)
            var u = geometricProcessor.CreateVectorFromText(3, i => $"Subscript[u,{i + 1}]");
            var v = geometricProcessor.CreateVectorFromText(3, i => $"Subscript[v,{i + 1}]");

            // Compute their outer product as a bivector
            var bv = u.Op(v);

            // Display a text representation of the vectors and their outer product
            Console.WriteLine($@"u = {textComposer.GetMultivectorText(u)}");
            Console.WriteLine($@"v = {textComposer.GetMultivectorText(v)}");
            Console.WriteLine($@"u op v = {textComposer.GetMultivectorText(bv)}");
            Console.WriteLine();

            // Display a LaTeX representation of the vectors and their outer product
            Console.WriteLine($@"\boldsymbol{{u}} = {latexComposer.GetMultivectorText(u)}");
            Console.WriteLine($@"\boldsymbol{{v}} = {latexComposer.GetMultivectorText(v)}");
            Console.WriteLine($@"\boldsymbol{{u}}\wedge\boldsymbol{{v}} = {latexComposer.GetMultivectorText(bv)}");
            Console.WriteLine();
        }
    }
}

The final output is:

v1 = 'Subscript[u,1]'<1>, 'Subscript[u,2]'<2>, 'Subscript[u,3]'<3>
v2 = 'Subscript[v,1]'<1>, 'Subscript[v,2]'<2>, 'Subscript[v,3]'<3>
v1 op v2 = 'Plus[Times[-1,Subscript[u,2],Subscript[v,1]],Times[Subscript[u,1],Subscript[v,2]]]'<1, 2>, 'Plus[Times[-1,Subscript[u,3],Subscript[v,1]],Times[Subscript[u,1],Subscript[v,3]]]'<1, 3>, 'Plus[Times[-1,Subscript[u,3],Subscript[v,2]],Times[Subscript[u,2],Subscript[v,3]]]'<2, 3>

\boldsymbol{u} = \left( u_1 \right) \boldsymbol{e}_{1} + \left( u_2 \right) \boldsymbol{e}_{2} + \left( u_3 \right) \boldsymbol{e}_{3}
\boldsymbol{v} = \left( v_1 \right) \boldsymbol{e}_{1} + \left( v_2 \right) \boldsymbol{e}_{2} + \left( v_3 \right) \boldsymbol{e}_{3}
\boldsymbol{u}\wedge\boldsymbol{v} = \left( u_1 v_2-u_2 v_1 \right) \boldsymbol{e}_{1,2} + \left( u_1 v_3-u_3 v_1 \right) \boldsymbol{e}_{1,3} + \left( u_2 v_3-u_3 v_2 \right) \boldsymbol{e}_{2,3}

GA-FuL Code Generation

The code generation capabilities of GA-FuL are comprehensive and sophisticated. They range from generating code for a single operation on multivectors to generating a full software library with nested folder\file structure and proper software architecture. The following example illustrates the bare minimum for generating an internal code representation for rotating a vector x using a rotor defined to rotate unit vector u to unit vector v in 3-dimensions. The differences between this code and the previous numeric and symbolic examples is mainly due to the special requirements imposed by code generation. These are not related to GA and its multivectors, but rather to concepts of Automatic Programming.

using System;
using DataStructuresLib.BitManipulation;
using GeometricAlgebraFulcrumLib.CodeComposer.Composers;
using GeometricAlgebraFulcrumLib.CodeComposer.Languages;
using GeometricAlgebraFulcrumLib.Processors.SymbolicAlgebra.Context;
//using GeometricAlgebraFulcrumLib.Mathematica;
using GeometricAlgebraFulcrumLib.Utilities.Extensions;
using GeometricAlgebraFulcrumLib.Utilities.Factories;

namespace GeometricAlgebraFulcrumLib.Samples.CodeComposer
{
    public static class Sample1
    {
        public static void Execute()
        {
            // The number of dimensions
            const int n = 3;

            // Stage 1: Define the symbolic context
            // The symbolic context is a special kind of symbolic linear processor for code generation
            var context = 
                new SymbolicContext()
                {
                    MergeExpressions = false,
                    ContextOptions = { ContextName = "TestCode" }
                };

            // Use this if you want Wolfram Mathematica symbolic processor
            // instead of the default AngouriMath symbolic processor
            //context.AttachMathematicaExpressionEvaluator();

            // Define a Euclidean multivectors processor for the context
            var processor = 
                context.CreateGeometricAlgebraEuclideanProcessor(n);

            // Stage 2: Define the input parameters of the context
            // The input parameters are named variables created as scalar parts of multivectors
            // and used for later processing to compute some outputs

            // Define the first vector with a given set of scalar components u1, u2, ...
            var u =
                context.ParameterVariablesFactory.CreateDenseVector(
                    n, 
                    index => $"u_{index + 1}"
                );

            // Define the second vector with a given set of scalar components v1, v2, ...
            var v =
                context.ParameterVariablesFactory.CreateDenseVector(
                    n, 
                    index => $"v_{index + 1}"
                );

            // Define the 3rd vector with a given set of scalar components x1, x2, ...
            var x =
                context.ParameterVariablesFactory.CreateDenseVector(
                    n, 
                    index => $"x_{index + 1}"
                );

            // Stage 3: Define computations and specify which variables are required outputs
            //Define a Euclidean rotor which takes input unit vector u to input unit vector v
            var rotor = processor.CreateEuclideanRotor(u, v, true);
            
            //Find the rotation of an arbitrary input vector x using this rotor
            var xRotated = rotor.OmMapVector(x);

            // Define the final outputs for the computations for proper code generation
            xRotated.SetIsOutput(true);

            // Stage 4: Optimize symbolic computations in the symbolic context
            context.OptimizeContext();

            // Stage 5: Assign code generated variable names for all variables
            // Define code generated variable names for input variables
            v.SetExternalNamesByTermId(id => $"v.Scalar{id.PatternToString(n)}");
            u.SetExternalNamesByTermId(id => $"u.Scalar{id.PatternToString(n)}");
            x.SetExternalNamesByTermId(id => $"x.Scalar{id.PatternToString(n)}");
            
            // Define code generated variable names for output variables
            xRotated.SetExternalNamesByTermId(id => $"xRotated.Scalar{id.PatternToString(n)}");

            // Define code generated variable names for intermediate variables
            context.SetIntermediateExternalNamesByNameIndex(index => $"temp{index}");

            // Stage 6: Define a C# code composer with AngouriMath symbolic expressions converter
            var contextCodeComposer = context.CreateContextCodeComposer(
                GaFuLLanguageServerBase.CSharp()
            );

            // Stage 7: Generate the final C# code
            var code = contextCodeComposer.Generate();

            Console.WriteLine("Generated Code:");
            Console.WriteLine();
            Console.WriteLine(code);
            Console.WriteLine();
        }
    }
}

The final output is:

Generated Code:

//Begin GA-FuL Symbolic Context Code Generation, 2021-09-17T19:39:20.7483394+02:00
//SymbolicContext: TestCode
//Input Variables: 9 used, 0 not used, 9 total.
//Temp Variables: 78 sub-expressions, 0 generated temps, 78 total.
//Target Temp Variables: 11 total.
//Output Variables: 3 total.
//Computations: 1 average, 81 total.
//Memory Reads: 1.7777777777777777 average, 144 total.
//Memory Writes: 81 total.
//
//SymbolicContext Binding Data:
//   1 = constant: '1'
//   -1 = constant: '-1'
//   2 = constant: '2'
//   Rational[1, 2] = constant: 'Rational[1, 2]'
//   u_1 = parameter: u.Scalar001
//   u_2 = parameter: u.Scalar010
//   u_3 = parameter: u.Scalar100
//   v_1 = parameter: v.Scalar001
//   v_2 = parameter: v.Scalar010
//   v_3 = parameter: v.Scalar100
//   x_1 = parameter: x.Scalar001
//   x_2 = parameter: x.Scalar010
//   x_3 = parameter: x.Scalar100

var temp0 = u.Scalar001 * v.Scalar001;
var temp1 = u.Scalar010 * v.Scalar010;
temp0 = temp0 + temp1;
temp1 = u.Scalar100 * v.Scalar100;
temp0 = temp0 + temp1;
temp1 = -1;
temp1 = temp1 / 2;
temp1 = Math.Pow(temp1, 0.5);
var temp2 = u.Scalar100 * v.Scalar001;
var temp3 = u.Scalar001 * v.Scalar100;
temp3 = -temp3;
temp2 = temp2 + temp3;
temp3 = u.Scalar010 * v.Scalar001;
var temp4 = u.Scalar001 * v.Scalar010;
temp4 = -temp4;
temp3 = temp3 + temp4;
temp4 = temp3 * temp3;
temp4 = -temp4;
var temp5 = temp2 * temp2;
temp4 = -temp4;
temp5 = u.Scalar100 * v.Scalar010;
var temp6 = u.Scalar010 * v.Scalar100;
temp6 = -temp6;
temp5 = temp5 + temp6;
temp6 = temp5 * temp5;
temp4 = -temp4;
temp4 = -temp4;
temp4 = Math.Pow(temp4, 0.5);
temp2 = temp2 / temp4;
temp2 = temp1 * temp2;
temp6 = -temp2;
temp0 = 1 + temp0;
temp0 = temp0 / 2;
temp0 = Math.Pow(temp0, 0.5);
var temp7 = temp0 * x.Scalar001;
temp3 = temp3 / temp4;
temp3 = temp1 * temp3;
var temp8 = temp3 * x.Scalar010;
temp7 = temp7 + temp8;
temp8 = temp2 * x.Scalar100;
temp7 = temp7 + temp8;
temp8 = temp6 * temp7;
temp4 = temp5 / temp4;
temp1 = temp1 * temp4;
temp4 = -temp1;
temp5 = temp0 * x.Scalar010;
var temp9 = temp3 * x.Scalar001;
temp5 = -temp5;
temp9 = temp1 * x.Scalar100;
temp5 = temp5 + temp9;
temp9 = temp4 * temp5;
temp8 = temp8 + temp9;
temp9 = temp0 * x.Scalar100;
var temp10 = temp2 * x.Scalar001;
temp9 = -temp9;
temp10 = temp1 * x.Scalar010;
temp9 = -temp9;
temp10 = temp0 * temp9;
temp8 = temp8 + temp10;
temp10 = -temp3;
temp3 = temp3 * x.Scalar100;
temp2 = temp2 * x.Scalar010;
temp2 = -temp3;
temp1 = temp1 * x.Scalar001;
temp1 = temp2 + temp1;
temp2 = temp10 * temp1;
xRotated.Scalar100 = -temp8;

temp2 = temp6 * temp1;
temp3 = temp7 * temp10;
temp8 = temp0 * temp5;
temp3 = temp3 + temp8;
temp8 = temp4 * temp9;
temp3 = -temp3;
xRotated.Scalar010 = temp2 + temp3;

temp0 = temp0 * temp7;
temp2 = temp5 * temp10;
temp0 = -temp0;
temp2 = temp6 * temp9;
temp0 = -temp0;
temp1 = temp4 * temp1;
xRotated.Scalar001 = -temp0;

//Finish GA-FuL Symbolic Context Code Generation, 2021-09-17T19:39:20.7825738+02:00