1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
namespace example frame h3d (e1, e2, e3, e0) euclidean structure h3d.Line ( p1 : Multivector, p2 : Multivector ) macro h3d.LineDistance ( L1 : Line, L2 : Line ) : scalar begin let d = h3d.&( (L1.p1 + e0) ^ (L1.p2 + e0) ^ (L2.p1 + e0) ^ (L2.p2 + e0) ) lcp (e1^e2^e3^e0)& return d.#E0# end |
In this guide we will take a tour of GMacDSL and explain the following points with simple examples:
1. Common Syntax Elements
The syntax of GMacDSL is based on some simple common elements:
- Comments: Including the traditional C-like single line comments that start with // until the end of a line, and multi-line comments between /* and */
- String Literals: Any series of characters between pairs of double quotes “ ” or single quotes ‘ ‘ is a string literal, with escape sequences (for example \t, \r, \\, \”, etc.) allowed inside strings. If a string is prefixed by @ it’s a verbatim string that is taken literally with no escape sequences allowed. String literals in GMacDSL are very similar to C# string literals; see this article about C# string literals for more details.
- Identifiers: Used for naming code items in GMacDSL like variables, structures, macros, namespaces, etc. An identifier is any sequence of character letters, digits, or underscore that does not start with a digit.
- Qualified Identifiers: We can concatenate two or more identifiers separated by dots to fully describe a child code item just like member access in common programming languages. For example main.e3d.e1
- Numeric Literals: The standard integers and scientific notation for floating point numbers widely used in many programming languages like C# for example.
- Keywords: We will see GMacDSL keywords throughout this guide like frame, namespace, macro, constant, etc.
- Operators: Many GMacDSL operators exist for computing with Geometric Algebra (GA) and we will see them throughout this guide.
In GMacDSL identifiers, keywords, and operators are case-sensitive.
2. Main Code Items
The main code items of GMacDSL are:
- Namespaces
- Frames
- Structures
- Constants
- Macros.
Namespaces can contain all other code items as child items. Frames can contain child Constants, Structures, and Macros but not child Namespaces or Frames.
3. Namespaces
The main code container in a GMacDSL program is the namespace. A GMacDSL program can have several root namespaces, each namespace can in turn have several child namespaces. Namespaces can be nested to form a full hierarchy of code containers. To create a new namespace or activate an existing namespace we use:
namespace <qname>
Where <qname> is a the qualified identifier of the namespace.
If <qname> is already defined it’s activated else it’s created and activated. An activated namespace is used as a starting point in the code to define other child items like frames, constants, etc.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//Create and activate a //new namespace cga5d.trans namespace cga5d.trans //Define a child constant under //the new namespace cga5d.trans constant One = 1 //Create and activate a //new namespace cga4d namespace cga4d //Add a child constant under //the new namespace cga4d constant Two = 2 //Create and activate a new //namespace twist under cga5d.trans namespace cga5d.trans.twist |
This code will:
- Create two namespaces cga5d and trans under cga5d , and set cga5d.trans as the active namespace.
- Define the constant One under cga5d.trans .
- Create and activate the cga4d namespace.
- Define a constant Two under cga4d .
- Create the twist namespace under cga5d.trans and set it as the active namespace.
If any frame, constant, structure, or macro is now defined after the last line then it’s defined under the active namespace cga5d.trans.twist .
4. Frames
A Frame is the most basic GA definition in GMacDSL, it represents a set of basis vectors with their symmetric bilinear form that the whole GA is constructed from. The basic components to define a frame are a list of basis vectors names, and the signature definition of the basis vectors to define the bilinear form. We can also define subspaces under a frame in addition to constants, structures, and macros. The basic syntax to define a frame is:
frame <qname> (<basis>) <sig> <subspaces>
- <qname> is qualified identifier name of the frame.
- <basis> is a comma separated list of basis vector names.
- <sig> is the frame signature (definition of its bilinear form).
- <subspaces> is an optional set of subspace definitions inside the frame.
1 2 3 4 5 6 7 |
namespace cga5d frame e3d (e1, e2, e3) euclidean subspace quaternions = @G0, G2@ |
- This code will activate the cga5d namespace and define the e3d frame under it.
- The cga5d.e3d frame has three basis vectors named e1 , e2 , and e3 .
- The signature of the frame is Euclidean meaning that the inner product of each basis vector with itself is 1 and with any other is 0.
- The frame has a single subspace quaternions that spans the scalars G0 (grade 0 k-vectors) and bivectors G2 (grade 2 k-vectors). More about defining subspaces later.
4.1. Frame Basis Blades
When we create a frame a set of basis blades for the frame are automatically defined using the basis vectors. Each basis blade has 3 ways for referencing it inside GMacDSL code:
- The Canonical Name: The outer product of its basis vectors ordered by their appearance in the definition of the frame.
- The Indexed Name: The basis blades of a frame having n basis vectors will have 2^{n} basis blades. The indexed name of a basis blade is E<n> where <n> is any integer between 0 and 2^{n}-1.
- The Binary Indexed Name: This is the same as the indexed name but using a binary representation for the index in n-digits. The binary indexed name of a basis blade is B<n> where <n> is any integer between 0 and 2^{n}-1 represented as a full n-bit binary number.
1 2 3 4 5 6 7 |
namespace cga5d frame e3d (e1, e2, e3) euclidean subspace quaternions = @G0, G2@ |
The following are all the basis blades for the shown cga5d.e3d frame:
- Grade 0 basis blade is the scalar basis: E0 (also named B000 ).
- Grade 1 basis blades are the basis vectors: e1 (also named E1 or B001 ), e2 ( E2 or B010 ), and e3 ( E4 or B100 ).
- Grade 2 basis blades are: e1^e2 (also named E3 or B011 ), e1^e3 ( E5 or B101 ), and e2^e3 ( E6 or B110 ).
- Grade 3 basis blade is the space pseudo-scalar basis: e1^e2^e3 (also named E7 or B111 ).
4.2. Frame Signature Definition
The signature of a frame can be defined in any one of the following methods:
- euclidean : This will create a Euclidean frame of any dimension depending on the number of basis vectors.
- orthonormal <sig_string> : This will create an orthonormal frame with a diagonal bilinear form matrix having +1 or -1 items on the diagonal. <sig_string> is a string containing + \ - characters for the basis vectors signatures. It must have the same number of + \ - as the basis vectors of the frame.
- orthogonal <mat_list> : This will create an orthogonal frame with a diagonal bilinear form matrix. The given <mat_list> is any Mathematica expression that results in a list of constants having the same number of elements as the basis vectors of the frame.
- IPM <mat_matrix> : This will create a general frame with a specific bilinear form matrix. The matrix is called the Inner Product Matrix (IPM) and must be square, symmetric, with constant elements, and having a number of rows\columns equal to the number of basis vectors of the frame. The given <mat_matrix> is any Mathematica expression that results in a legal IPM.
- CBM <base_frame> <mat_matrix> : This will create a frame based on another base frame using a Change-of-Basis Matrix (CBM). <base_frame> is the name of the base frame and <mat_matrix> is any Mathematica expression resulting in a legal square CBM with constant elements. The base frame must have the same number of basis vectors as the new frame and number the CBM rows\columns.
- reciprocal <base_frame> : This will create a frame based on another base frame using a Change-of-Basis Matrix (CBM). The new frame will be the reciprocal of the base frame and the CBM is automatically computed. Both frames must have the same number of basis vectors.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
frame e4d (e1, e2, e3, e4) euclidean frame e4d (e1, e2, e3, e4) orthonormal ‘++++’ frame e4d (e1, e2, e3, e4) orthogonal ‘{1, 1, 1, 1}’ frame e4d (e1, e2, e3, e4) IPM ‘{ {1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1} }’ |
All the shown frame definitions are identical and they will create a 4-dimensional Euclidean frame called “e4d” having 4 basis vectors “e1”, “e2”, “e3”, and “e4”.
1 2 3 4 5 6 7 8 9 10 11 |
frame cga5dOrtho (ep, e1, e2, e3, en) IPM 'DiagonalMatrix[{1,1,1,1,-1}]' frame cga5d (no, e1, e2, e3, ni) CBM cga5dOrtho @"{ {1/2, 0, 0, 0, 1/2}, { 0, 1, 0, 0, 0}, { 0, 0, 1, 0, 0}, { 0, 0, 0, 1, 0}, { -1, 0, 0, 0, 1} }" |
This code will define the orthogonal frame for the Conformal Geometric Algebra for 3D space, called here “cga5dOrtho”. Then a change of basis matrix is used to define the easier to use non-orthogonal form of the same space “cga5d”. The relation between the basis vectors of the two frames as described by the CBM are as follows:
1 2 3 4 5 |
cga5d.no = 0.5 (cga5dOrtho.ep + cga5dOrtho.en), cga5d.e1 = cga5dOrtho.e1, cga5d.e2 = cga5dOrtho.e2, cga5d.e3 = cga5dOrtho.e3, cga5d.ni = - cga5dOrtho.ep + cga5dOrtho.en |
4.3. Subspaces
A Subspace of a frame can be defined using:
subspace <name> = @ <basis_blades> @
- <name> is the name of the subspace inside the frame
- <basis_blades> is a comma-separated list of basis blades that the subspace will span.
Each element of <basis_blades> can be any of the following:
- The name of a basis blade of the frame (in any form: canonical, indexed, or binary indexed).
- ga{ <basis_vectors> } where <basis_vectors> is a set of basis vectors of the frame. This form specifies a sub-geometric algebra based on the selected basis vectors of the frame.
- A name of another subspace already defined for the frame.
When a frame is defined, GMac automatically creates several default subspaces each spanning k-vectors of some grade k. These subspaces are named G<k> where <k> is any number between zero and n, the dimension of the frame (the number of its basis vectors). This form specifies all basis blades of grade k and can be used as any user-defined subspaces.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
frame cga5dOrtho (ep, e1, e2, e3, en) IPM 'DiagonalMatrix[{1,1,1,1,-1}]' subspace vectors3D = @ e1, e2, e3 @ subspace bivectors3D = @ e1^e2, e1^e3, e2^e3 @ subspace evenSubspace = @ G0, G2, G4 @ subspace euclidean3D = @ ga{e1, e2, e3} @ subspace quaternions = @ E0, bivectors3D @ |
In the shown code example:
- The subspace cga5dOrtho.vectors3D spans the basis blades e1 , e2 , and e3 (i.e. three of the five basis vectors of the frame).
- The subspace cga5dOrtho.bivectors3D spans the basis blades B00110 , B01010 , and B01100 (i.e. three of the ten basis bivectors of the frame).
- The subspace cga5dOrtho.evenSubspace spans the 16 basis blades with even grades 0, 2, and 4.
- The subspace cga5dOrtho.euclidean3D spans the 8 basis blades E0 , e1 , e2 , e3 , e1^e2 , e1^e3 , e2^e3 , and e1^e2^e3 . This is the full sub-GA with basis vectors e1 , e2 , and e3 .
- The subspace cga5dOrtho.quaternions spans the direct sum of the scalar ( E0 ) and bivectors3D ( e1^e2 , e1^e3 , and e2^e3 ) subspaces.
4.4. Frame Default Code Items
When we define a frame GMac automatically defines several useful child code items. You can inspect these items by compiling a frame and showing its sub-items inside GMacIDE. Here is a list of some of the default items:
- The default multivector type called Multivector . We will talk about GMacDSL’s types shortly.
- Some default constants named E0 , E1 , E2 , etc. holding multivector values of the 2^{n} basis blades of the frame. In addition to the default constant I holding the frame’s pseudoscalar multivector value.
- Some default subspaces like G0 , G1 , G2 , etc. spanning k-vectors of different grades from 0 to n. In addition to the default subspaces pseudoscalars (the pseudo-scalars subspace of the frame), even (the even grade k-vectors of the frame), odd (the odd grade k-vectors of the frame), and ga (the full GA space of the frame).
- Some default structures used to compute outermorphisms and general multivector linear transforms. See the GMac Guide for more details.
- Many default macros to perform common useful operations on the frame’s multivector values useful during code generation from GMac. See the GMac Guide for more details.
5. Types
A type defines a grouping of scalar values into a combinatorial tree of fixed form. Types can be any of the following three kinds:
- In GMacDSL there is one built-in data type called scalar used to represent real numbers. This is a simple type with a single scalar value. The combinatorial tree of a scalar type is a single node.
- When any frame is defined GMac automatically creates a Multivector type for that frame. If the frame has n basis vectors the Multivector type is a combination of 2^{n} scalar values that correspond to the scalar coefficients of the basis blades of the frame. The combinatorial tree of a Multivector type is a root node with 2^{n} leaf scalar nodes.
- Any GMacDSL structure is also a type. The combinatorial tree of a structure type can be of arbitrary form with leaf scalar values. We will discuss Structures later on.
1 2 3 4 5 6 7 8 9 |
frame e3d (e1, e2, e3) euclidean frame e2d (e1, e2) euclidean structure e2d.ScaledRotation ( origin : Multivector, angle : scalar, scale : scalar ) |
This code:
- Creates a frame e3d and automatically defines a e3d.Multivector type.
- Creates a frame e2d and automatically defines a e2d.Multivector type.
- Creates a structure
ScaledRotation under the frame
e2d . The structure will have 3 members:
- origin of type e2d.Multivector
- angle of type scalar
- scale of type scalar .
The combinatorial trees for e3d.Multivector , e2d.Multivector , and e2d.ScaledRotation are as shown, all leafs are scalars.
6. Constants
In GMacDSL a Constant can be defined inside a namespace or a frame using:
constant <qname> = <expr>
- <qname> is the qualified name of the constant.
- <expr> is a GMacDSL expression.
The expression will be evaluated at compile time into a constant value and the value will be automatically substituted whenever the constant‘s name is used as part of any following expression. The type of the new constant is automatically determined as the expression’s type.
In addition to user-defined constants, each frame basis vector is associated with a constant value of type Multivector. These basis vector constants can be used inside expressions exactly like user-defined constants; they are read-only data-stores that are assigned their constant values at compile time.
1 2 3 4 5 6 7 8 9 10 11 12 |
namespace cga5d cosntant One = 1 constant Two = One + 1 constant TwoPi = Two * ‘Pi’ frame h3d (e1, e2, e3, e0) orthonormal ‘+++-’ constant h3d.I3 = e1^e2^e3 |
This code will define three scalar constants One, Two, and TwoPi under the cga5d namespace. It will then define the constant I3 under the frame cga5d.h3d having the type cga5d.h3d.Multivector . In addition, the frame h3d has the four basis vector constants h3d.e1, h3d.e2, h3d.e3, and h3d.e0, and 17 basis blade constants h3d.E0 to h3d.E15 and h3d.I; all of type h3d.Multivector.
7. Structures
In GMacDSL a Structure is a combinatorial type that can be defined inside a namespace or a frame using:
structure <qname> (<members>)
- <qname> is qualified name of the structure.
- <members> is a comma separated list of data members definitions.
The members list can contain one or more data member definitions. Each data member is defined as <name> : <type> where <name> is the name of the member and <type> is its type.
GMacDSL structures are not recursive. A data member of structure S cannot be of type S . In addition, a data member can only be defined using types already defined in previous code not in following code.
Because a data member of a structure can be of a type that is itself another structure, structures can be used to create non-recursive combinatorial trees having leafs of scalar values to represent more complex geometric concepts that a single multivector can’t typically represent; then we can use macros to define GA operations on these structure values. We already showed an example when we talked about GMacDSL types.
1 2 3 4 5 6 7 8 9 10 |
namespace cga5d frame e3d (e1, e2, e3) euclidean structure Ray ( origin : e3d.Multivector, direction : e3d.Multivector, length : scalar ) |
This code will create a structure Ray under the namespace cga5d . The structure will have 3 members: origin and direction of type e3d.Multivector and length of type scalar. The full combinatorial tree is as shown.
8. Macros
In GMacDSL a Macro is an abstraction of a geometric algebra computation that can be defined inside a namespace or a frame using:
macro <qname> (<parameters>) : <out_type> begin <commands> end
- <qname> is the qualified name of the macro
- <parameters> is a comma separated list of macro parameter definitions. The list can contain one or more parameter definitions; each parameter is defined as <name> : <type> where <name> is the name of the parameter and <type> is its type.
- <out_type> is the type of the value returned by the macro.
- <commands> is a list of GMacDSL commands to be executed when the macro is called or used for a GA computation. We will talk about commands and expressions in full details shortly.
In addition to user defined macros there are several useful built-in GMacDSL macros:
- reverse: The Reverse of a multivector.
- grade_inv: The Grade Involution of a multivector.
- cliff_conj: The Clifford Conjugate of a multivector.
- norm2: The squared norm of a multivector. This operation is metric dependent (i.e. it depends on the bilinear form of the frame) and can result in negative scalars having no real root for some frames.
- mag2 : The squared magnitude of a multivector. This operation is metric dependent and will always result in positive scalars.
- mag : The magnitude of a multivector. This operation is metric dependent and will always result in positive scalars computed from the positive square root of mag2.
- emag2 : Same as mag2 and norm2 but assumes a Euclidean metric for the multivector.
- emag : Same as mag and the square root of emag2 but assumes a Euclidean metric for the multivector.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
namespace cga5d frame h3d (e1, e2, e3, e0) orthonormal ‘+++-’ structure h3d.Line ( p1 : Multivector, p2 : Multivector ) macro h3d.LineDistance ( L1 : Line, L2 : Line ) : scalar begin let d = h3d.& ( (L1.p1 + e0) ^ (L1.p2 + e0) ^ (L2.p1 + e0) ^ (L2.p2 + e0) ) lcp (e1^e2^e3^e0)& return d.#E0# end |
Assuming we have the shown definitions for the h3d frame and the Line structure inside it that represents a line passing through points p1 and p2. We will create a macro that will compute the distance between any two lines described by the cga5d.h3d.line structure. The macro is called LineDistance and is created inside the cga5d.h3d frame. The macro takes two parameters L1 of structure type cga5d.h3d.Line and L2 of the same type. The macro returns a value of type scalar and executes two commands: a let and a return command.
9. Macro Templates
In many cases we need to define the same macro under several frames. For example a macro to create the inverse of a versor is common in geometric algebra. Instead of manually duplicating the same macro definition inside several frames we can use a macro template and implement the template in any frames we need easily. The general syntax for a macro template is exactly like that of a macro but prefixed by the template keyword:
template macro <qname> (<parameters>) : <out_type> begin <commands> end
Then we can implement the template using:
implement <qname_list> using <frames_list>
- <qname_list> is a comma separated list of qualified macro templates names to be implemented.
- <frames_list> a comma separated list of qualified frame names where the macros will be created.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
template macro VInv( mv : Multivector ) : Multivector begin let b = reverse( mv ) let a = mv gp b return b / a.#G0# end template macro DivByNorm( mv : Multivector ) : Multivector begin return mv / 'Sqrt[$ norm2( mv ) $]' end implement vinv, DivByNorm using e3d, h3d, cga5d |
This code will implement two macros VInv and DivByNorm under frames e3d, h3d, and cga5d; i.e. a total of 6 new macros will be created.
10. Commands
Inside GMacDSL macros, a command represents the smallest abstract high-level unit of execution. There are few simple commands in GMacDSL:
- The declare command.
- The let command.
- The return command.
These commands are always inside a command block between the begin\ end keywords. Command blocks can be nested like the case in C++ and other similar languages.
10.1. The declare Command
The declare command is used to declare local variables inside a command block:
declare <var> : <type>
- <var> is the name of the local variable.
- <type> is the type of the local variable.
No two local variables in the same command block can have the same name, but we can declare a variable with the same name inside child command blocks. When a new local variable is created all of it’s leaf components are set to zero. So if we use the variable or any part of it in a following expression before assigning a value to it we will get zeros from the part we read. More about full and partial local variable access later on.
10.2. The let Command
The let command has 3 forms:
let <var> : <type> = <expr>
let <var> = <expr>
let <part_var> = <expr>
- <var> is the name of a local variable
- <type> is the type of the local variable
- <expr> is a right-hand-side GMacDSL expression
- <part_var> is a partial access of a local variable. We will talk about expressions and partial access in GMacDSL shortly.
- The first form let <var> : <type> = <expr> can be used to declare a new local variable inside a command block and assign an expression to it. The name must be unique within the command block and not used before in any declare or let commands of the same command block.
- The second form let <var> = <expr> can be used to assign an expression to a local variable. If the local variable is found in this command block or any parent command block its value is changed. If no such variable is found a new local variable is declared and assigned the expression.
- The third form let let <part_var> = <expr> is used to change a part of a compound local variable that is already declared. A compound local variable is a variable of type multivector or structure, not a scalar.
When assigning an expression to a variable or part of a variable the types must be compatible. We can assign a scalar expression to any multivector or scalar variable. We can assign a multivector expression to a variable of the same multivector type. We can assign a structure expression to a variable of the same structure type. All other possibilities are not allowed and all assignments in GMac are strictly by value; there are no references or pointers in GMacDSL like some other languages have.
10.3. The return Command
The return command is used to assign a value to the return variable of a macro:
return <expr>
Where <expr> is an expression assigned to the macro’s return variable.
This command can only be used inside a command block if it’s part of a macro definition; it can’t be used, for example, inside a composite expression used to define a constant.
When any macro is defined, GMac automatically adds a hidden output parameter named result as the first parameter of the macro. The parameter result has the same type as the return type of the macro. When we add a return command we are just assigning the expression to the result parameter. The return command does not stop execution of any following commands, it just alters the value of the result macro output parameter. Normally the return command should be the last in the macro’s command block. The return <expr> command is semantically equivalent to let result = <expr> so the type of the expression must be assignable to the type of the output result parameter as we discussed in the let command.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
namespace main frame h3d (e1, e2, e3, e0) orthogonal '{ 1, 1, 1, 1 }' structure h3d.RaySphereIntersection( q : scalar, z2 : scalar ) macro h3d.RaySphereIntersect ( ray_orgn : Multivector, ray_dir : Multivector, center : Multivector, radius : scalar ) : RaySphereIntersection begin declare rsiStruct : RaySphereIntersection let mvQ = (center lcp ray_dir + ray_orgn ^ ray_dir) gp ray_dir let rsiStruct.q = mvQ.#E0# let rsiStruct.z2 = radius gp radius - norm2(q - center) return rsiStruct end |
11. Expressions
GMacDSL expressions are the reason behind the power of this simple GA-based DSL. Many GA operations are supported and are easy to use as we will see shortly. Expressions are used to define constants in addition to let and return commands. There are several types of GMacDSL expressions:
- Constant number value.
- Full and Partial data-store access.
- Symbolic scalar.
- GA arithmetic.
- Constructor.
- Macro call.
- Composite.
11.1. Constant Number Value
A constant number value is simply a normal integer or floating point number like: -1 , 10.345 , or -12.2345e-9.
11.2. Full and Partial Data-Store Access
A data-store in GMacDSL is either a constant, macro parameter, or local variable. They’re called data-stores because they can be used to store data (i.e. values) either at compile time (a constant), or at computation time (a macro parameter and a local variable). In GMacDSL we can access (i.e. read\write) the full value of a data-store or any part of it. Constants are always read-only, but macro parameters and local variables are read-write. Initialization of data-stores goes as follows:
- The initial value of a constant is determined inside its definition at compile time. This value is never changed after that.
- The initial value of a local variable is a combinatorial tree of zero leaf values if the variable is created using a declare command, and is a user-defined value if initialized using a let command. That value can change using later let commands inside the same command block or any child command block.
- The initial value of a macro parameter is determined from the calling macro and can change inside the called macro’s command block or any of its child command blocks. If we change the value of a macro parameter the calling macro will not be affected as all parameter assignments in GMacDSL macro calls are by value not by reference.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
namespace cga5d frame e3d (e1, e2, e3) euclidean structure Ray ( origin : e3d.Multivector, direction : e3d.Multivector, length : scalar ) //More code here //In a command block here are some //data-store access expressions let r = ray let v = ray.direction let k = ray.length let s = ray.origin.#e2# let w = ray.origin.@ G0,G2 @ let ray.origin.@ G0,G2 @ = v |
If we have a data-store (for example a local variable) called ray of type cga5d.Ray we can access its full value tree or parts of it in many ways. We will now explain the difference between all these access methods.
The first access method, called full data-store access, is simply done by using the name of the data-store:
let r = rayIn this way the whole tree is copied by value into r that will have the exact same type as ray, a cga5d.Ray structure.
The second access method, called partial data-store access, is done by selecting a sub-tree using the member names of the structure:
let v = ray.directionIn this way the sub-tree starting at direction member is copied by value into v that will have the type cga5d.e3d.Multivector.
Another example for a partial data-store access is:
let k = ray.lengthHere we are copying, by value, a single scalar in the length member to k.
We can use partial data-store access on multivectors to read a single basis blade coefficient:
let s = ray.origin.#e2#Here we are copying, by value, a single scalar basis blade coefficient under the origin multivector member to s.
One useful form of partial data-store access on multivectors to read a number of terms in the multivector:
let w = ray.origin.@ G0,G2 @This form creates a multivector from the subspace specified inside the @ @ delimiters, see the part of this guide about subspaces in GMacDSL. In this example, we specified the subspace @G0, G2@ consisting of basis blades E0 , e1^e2 , e1^e3 , and e2^e3 . Only these coefficients are copied from ray.origin into w and all other coefficients are set to zero in w, that has a e3d.Multivector type.
If the partial data-store access is used on the left-hand-side of an assignment, only the selected components are changed and all other components are left without any change:
let ray.origin.@ G0,G2 @ = vThis command changes the coefficients specified by the subspace @G0, G2@ consisting of basis blades E0 , e1^e2 , e1^e3 , and e2^e3 . Only these coefficients are copied from v to ray.origin and all other coefficients are left unchanged in ray.origin.
11.3. Symbolic Scalar Expression
A symbolic scalar expression is a string that can be evaluated by Mathematica to produce a scalar value. For example:
‘-1’
‘2 * Pi’
‘Sin[Rational[3, 4] * Pi]’
One important feature of symbolic scalar expressions is that they can be parametric. For example the expression ‘Sin[$d$]’ is interpreted as “read the value of d and create a Mathematica expression that is the Sin[] of that value”. d can be a constant, parameter, or local variable of type scalar. Actually any legal GMacDSL expression of type scalar can be put inside the $ $ delimiters in a symbolic scalar expression. This is a form of small inline macro call in GMacDSL. The resulting type of a symbolic scalar expression is assumed by GMac to be of type scalar. In this way many powerful symbolic computations can be performed with GMacDSL using Mathematica.
1 2 3 4 5 6 |
macro h3d.DivByNorm ( mv : Multivector ) : Multivector begin return mv / 'Sqrt[$ norm2(mv) $]' end |
This code creates the DivByNorm macro inside the h3d frame. The macro has one input parameter mv of type h3d.Multivector and returns a value of the same type. It contains a single return command with an expression that:
- Finds the squared norm of mv.
- Applies a symbolic expression to the result of step 1 to get the square root of the squared norm as a scalar value.
- Divides the input parameter mv by its scalar norm computed in step 2.
Note the use of $ norm2(mv) $ inside the parametric symbolic scalar expression.
11.4. GA Arithmetic Expression
GMacDSL supports many GA arithmetic operations on scalar and multivector values:
- Unary negation of a scalar or multivector value (ex. -x).
- + \ - (binary addition and subtraction operators): These can be applied to two scalars, two multivectors of the same frame, or a scalar and a multivector.
- * (binary times operator): This can be applied to any two scalars, or a scalar and a multivector.
- / (binary divide operator): This can be applied to any two scalars, or to a multivector and a scalar; the multivector must come first in this case.
- GA bilinear products: These can be applied to two multivectors of the same frame, or to a scalar and a multivector. They generally return a multivector except for the scalar product sp and Euclidean scalar product esp operators that return a scalar.
The GA bilinear products include two major types: The metric-dependent and the metric-independent products. The metric-dependent products use the frame’s bilinear form to compute the product, while the metric-independent products assume a Euclidean metric and disregard the frame’s bilinear form. These products can be applied to two multivector values with the same frame or to a scalar and a multivector. All bilinear products return a multivector value except for the scalar and Euclidean scalar products ( sp and esp) that return a scalar value.
All binary operators are left-associative. The operator precedence for binary operators are as follows:
- The unary negation operator has the highest precedence.
- GA bilinear products, *, and / operators have equal precedence. The first to encounter is computed first with left associativity.
- The binary +/ - operators have equal precedence but lower than the products, also with left associativity.
- Expressions can be grouped by ( ) to enforce precedence just like in common programming languages.
Assuming x, y, z, and w are legal data-stores, the expression:
(x + y gp z / 2 lcp w) * 5.2- Finds the geometric product of y and z.
- Scales the result from step 1 by 0.5 (i.e. divides the resulting multivector by 2).
- Finds the left contraction product of the result of step 2 with w.
- Adds the result of step 3 to x.
- Scales the result of step 4 by 5.2.
11.5. Constructor Expression
In GMacDSL we can directly create a multivector or a structure value by specifying the components of the multivector or structure. This operation is similar to object initialization in OOP languages.
To construct a multivector value we use one of the following:
<qname> ( <coef_list> )
<qname> ( <scalar_expr> )
<qname> { <init_expr> } ( <coef_list> )
- <qname> is the name of the multivector type
- <coef_list> is a comma separated list that assigns an expression to coefficients of basis blades in the created multivector value
- <scalar_expr> is a single expression of type scalar.
- <init_expr> is an expression for initializing the multivector value before the coefficients are assigned using the <coef_list>.
The second form can be used to cast a scalar into the Multivector type of some frame.
1 2 3 4 5 6 7 |
let v1 = e3d.Multivector( #E0# = ‘Pi’, #e1^e3# = -5 ) let v2 = e3d.Multivector {v1 / 5} ( #E0# = 2, #e1^e2# = v1.#e1^e3# + 2 ) |
This code constructs and assignes a multivector value to v1 as shown. v2 is created as follows:
- v2 components are initially copied from v1 / 5.
- The #E0# (i.e. scalar) component is set to 2 overriding the previous value of Pi / 5.
- The #e1^e2# component is set to -3 (i.e. v1.#e1^e3# + 2 = -5 + 2).
- The final value of v2 has 3 non-zero components: #E0#, #e1^e2#, and #e1^e3#.
The exact same effect can be achieved by this series of commands:
1 2 3 4 5 6 7 |
declare v1 : e3d.Multivector let v1.#E0# = ‘Pi’ let v1.#e1^e3# = -5 let v2 = v1 / 5 let v2.#E0# = 2 let v2.#e1^e2# = v1.#e1^e3# + 2 |
In effect the constructor expression is just a convenient shorthand for several declare\ let commands.
We can also use a subspace to create a multivector value using one of the following:
<qname> ( <expr_list> )
<qname> { <init_expr> } ( <expr_list> )
- <qname> is the qualified name of the subspace.
- <expr_list> is a comma separated list of scalar expressions. Each expression is assigned to a basis blade coefficient for a corresponding basis blade in the subspace. The basis blades are ordered by their ID as in their indexed name E<n>
- <init_expr> is an expression for initializing the multivector value before the coefficients are assigned using the <coef_expr>.
1 2 3 4 5 6 7 |
frame e3d(e1, e2, e3) euclidean subspace scalars = @ E0 @ subspace vectors = @ G1 @ subspace bivectors = @ G2 @ subspace quaternions = @ scalars, bivectors @ |
If we have this definition for the e3d frame the following pairs of constructor expressions are equivalent:
1 2 3 4 5 6 |
//Code Piece 1 let a = e3d.vector(2, -1, 4) let b = e3d.Multivector( #e1# = 2, #e2# = -1, #e3# = 4 ) |
1 2 3 4 |
//Code Piece 2 let a = e3d.scalars(‘Pi’) let b = e3d.Multivector(‘Pi’) |
1 2 3 4 5 6 |
//Code Piece 3 let a = e3d.quaternions{mv}(4, 1, 0, -3) let b = e3d.Multivector{mv}( #E0# = 4, #e1^e2# = 1, #e2^e3# = -3 ) |
To construct a structure value we use one of the following:
<qname> ( <member_list> )
<qname> ( <expr_list> )
<qname> { <init_expr> } ( <member_list> )
<qname> { <init_expr> } ( <expr_list> )
- <qname> is the name of the structure type.
- <member_list> is a comma separated list that assigns an expression to members of the created structure value. We can use full or partial data-store access on the members. This form is very useful for readability especially when only a small number of members are to be initialized. No specific order of members is needed here.
- <expr_list> is a comma separated list of expressions. Each expression is automatically assigned to a full structure member according to the order of the member’s declaration inside the structure. This form is useful when most of the members are to be fully initialized.
- <init_expr> is an expression for initializing the structure value before the members are assigned using the <member_list>
1 2 3 4 5 6 7 8 9 10 |
namespace cga5d frame e3d (e1, e2, e3) Euclidean subspace vectors = @G1@ structure Ray ( origin : e3d.Multivector, direction : e3d.Multivector, length : scalar ) |
Like the case with multivector constructors, a structure constructor is just a convenient shorthand for a set of declare\ let commands. If we have a definition as the shown the following 5 pieces of code are identical:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//Code Piece 1 let r = Ray( direction = e3d.Multivector( #e1# = ‘1/3’, #e2# = ‘1/3’, #e3# = ‘-1/3’ ), origin = e3d.Multivector( #e1# = 1, #e2# = -3, #e3# = 5 ), length = 10 ) let s = Ray{r}(length = r.length / 2) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//Code Piece 2 let r = Ray( length = 10, origin.#e1# = 1, direction.#e1# = ‘1/3’, direction.#e2# = ‘1/3’, origin.#e2# = -3, direction.#e3# = ‘-1/3’, origin.#e3# = 5 ) let s = Ray( origin = r.origin, length = r.length / 2, direction = r.direction ) |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//Code Piece 3 declare r : Ray let r.direction.#e1# = ‘1/3’ let r.direction.#e1# = ‘1/3’ let r.direction.#e1# = ‘-1/3’ let r.origin.#e1# = 1 let r.origin.#e2# = -3 let r.origin.#e3# = 5 let r.length = 10 let s = r let s.length = r.length / 2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//Code Piece 4 let r = Ray( e3d.Multivector( #e1# = 1, #e2# = -3, #e3# = 5 ), e3d.Multivector( #e1# = ‘1/3’, #e2# = ‘1/3’, #e3# = ‘-1/3’ ), 10 ) let s = Ray( r.origin, r.direction, r.length / 2 ) |
1 2 3 4 5 6 7 8 |
//Code Piece 5 let r = Ray( e3d.vectors(1, -3, 5), e3d.vectors(‘1/3’, ‘1/3’, ‘-1/3’), 10 ) let s = Ray{r}(length = r.length / 2) |
11.6. Macro Call Expression
To call a macro, that is already defined, we use one of the following:
<qname> ( <param_list> )
<qname> ( <expr_list> )
- <qname> is the name of the macro.
- <param_list> is a comma separated list that assigns a expressions to parameters of the called macro. We can use full or partial data-store access on the parameters.
- <param_list> is a comma separated list that assigns a expressions to parameters of the called macro. We can use full or partial data-store access on the parameters.
All values are assigned to parameters of the called macro by value so that is the called macro changes the value of its parameter it will not affect the calling macro.
In GMacDSL macros are not recursive. We can never call a macro from itself or call a macro that will be defined in following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
namespace cga5d frame e3d (e1, e2, e3) euclidean structure Ray ( origin : e3d.Multivector, direction : e3d.Multivector, length : scalar ) macro GetPointOnRay( r : Ray, t : scalar ) begin return r.origin + t * r.direction end |
If we have a definition as the shown the following 3 pieces of code are identical:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//Code Piece 1 let s = GetPointOnRay( r = Ray( origin = e3d.Multivector( #e1# = 1, #e2# = -3, #e3# = 5 ), direction = e3d.Multivector( #e1# = ‘1/3’, #e2# = ‘1/3’, #e3# = ‘-1/3’ ), length = 10 ), s = 2.5 ) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//Code Piece 2 let ray = Ray( origin = e3d.Multivector( #e1# = 1, #e2# = -3, #e3# = 5 ), direction = e3d.Multivector( #e1# = ‘1/3’, #e2# = ‘1/3’, #e3# = ‘-1/3’ ), length = 10 ) let s = GetPointOnRay( r = ray, s = 2.5 ) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//Code Piece 3 let s = GetPointOnRay( r.origin = e3d.Multivector( #e1# = 1, #e2# = -3, #e3# = 5 ), r.direction = e3d.Multivector( #e1# = ‘1/3’, #e2# = ‘1/3’, #e3# = ‘-1/3’ ), r.length = 10, s = 2.5 ) |
11.7. Composite Expression
A composite expression is command block that has a return value and starts with a declaration of its final output local variable. This is somehow conceptually similar to an inline macro with no parameters. The general form of composite expressions is:
{ output <var> : <type> <commands> }
- <var> is the name of the local variable that will contain the final value of the composite expression. This variable is called the composite expression’s output variable.
- <type> is the type of the output variable.
- <commands> is a list of GMacDSL commands that will eventually change the value of the output variable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
begin let p = ‘Pi’ let x = { output s : scalar let a = 2 * p / 3 let s = ‘Sin[$a$]’ } * 2.4 . . . end |
The shown code is a part of the command block for some macro. The value of x is determined by the product of a composite expression with 2.4. The composite expression can access the value of p, another local variable outside the expression’s scope but inside its parent command block. All of the macro’s parameters are also accessible inside the composite expression.
12. Identifier Referencing Rules
In GMacDSL, namespaces, frames, macros, structures, command blocks, and composite expressions have scopes. A scope is a “personal” code space where child code items can be defined. For example:
- Inside a namespace’s scope we can define child namespaces, frames, structures, macros, and constants.
- Inside a frame’s scope we can define basis vectors, subspaces, the Multivector type of the frame, constants, structures, and macros.
- Inside a macro’s scope we define macro parameters and a single command block that itself has a scope.
- Inside a structure’s scope we define data members.
- Inside a command block or a composite expression we can define other command blocks, composite expressions, and local variables.
In order to use a code item from any scope in GMacDSL we can always use a “full qualification” of the item’s name. Full qualification is accurate but tedious to write. An alternative is by using referencing rules that simplify using code items from other scopes in the code. Instead of explicitly stating the rules we will give examples to show how referencing code items works in GMacDSL.
The fully qualified names of the items in the shown hierarchy are:
- The namespaces: main, main.conformal, and main.conformal.twist
- The frames: main.e3d, main.cga5dOrtho, and main.cga5d
- The structures: main.e3d.Line, main.e3d.Plane, and main.conformal.twist.Twist
- The macros: main.conformal.IntersectLinePlane and main.conformal.twist.ApplyTwist
- The subspaces: main.e3d.bivectors and main.e3d.quaternions
when defining or activating a namespace (like the main, conformal, or twist namespaces) we must use full qualification:
1 2 3 4 5 6 7 8 |
namespace main //Define other code items here namespace main.conformal //Define other code items here namespace main.conformal.twist //Define other code items here |
We can define a child code item under the active namespace easily. For example:
1 2 3 4 5 6 7 8 9 |
namespace main frame e3d (e1, e2, e3) euclidean . . . namespace main.conformal.twist macro ApplyTwist(...)... |
This will define the e3d frame under the main namespace and the ApplyTwist macro under the main.conformal.twist namespace.
If we need to add a child code item under a frame (i.e. a constant, structure, or macro) we have to use relative referencing to reach the frame from the current scope. For example to define the Line structure after activating the twist namespace:
1 2 3 4 5 |
namespace main.conformal.twist . . . structure main.e3d.Line (...) |
Or we can activate the main namespace and use simpler code:
1 2 3 |
namespace main structure e3d.Line (...) |
The other kind of referencing we need to understand happens during resolving identifiers for types and other identifiers inside expressions. We usually need this type of referencing when:
- Selecting the type of a structure data member, a macro parameter, or a local variable inside a command block.
- Reading the value of a constant inside an expression.
- Constructing a multivector or a structure.
- Calling a macro from inside an expression.
When defining the Line structure we automatically enter its scope to define its members. We can use relative referencing to select the types of its members here as follows:
1 2 3 4 5 6 7 8 9 |
namespace main frame e3d (e1, e2, e3) euclidean structure e3d.Line( p1 : Multivector, p2 : Multivector ) |
We can use Multivector instead of e3d.Multivector because the scope of Line (the active scope) is inside the scope of e3d and GMac searches the scope tree from the active scope upwards.
Compare this to defining the twist structure:
1 2 3 4 5 6 7 8 |
namespace main.conformal.twist structure Twist( axis : main.e3d.Multivector, origin : main.e3d.Multivector, angle : scalar, transVector : main.e3d.Multivector ) |
We had to use full qualification because the active scope of main.conformal.twist.Twist is too far from the multivector type we need to use main.e3d.Multivector.
To make it more easy to reference that type we can use the open keyword as follows:
1 2 3 4 5 6 7 8 9 10 |
namespace main.conformal.twist open main.e3d structure Twist( axis : Multivector, origin : Multivector, angle : scalar, transVector : Multivector ) |
Here GMac will not find Multivector as an identifier inside the active scope or its parents; it will then search the opened scopes and their parents until it finds an identifier with the given name. Note that whenever GMacDSL finds a namespace activation it automatically clears all opened scopes.
Assuming the cga5d frame has basis vectors no, e1, e2, e3, and ni we can use their names as constant multivector values inside any expression in our code, we just need to reference them correctly. Assuming we need the outer product of no and e1 inside the ApplyTwist macro, one way is to use the full qualification of their frame:
let x = main.cga5d.no ^ main.cga5d.e1This method is hard to write and read especially when we need an expression depending on many constants or macros defined under a far away frame.
Another alternative is to use the “temporary open frame scope” operation <frame>.& & :
let x = main.cga5d.& no^e1 &This operation temporarily adds the frame’s scope as the top opened scope. When GMac searches the active scope and does not find the no identifier it automatically searches the opened scopes and their ancestors one by one. Since main.cga5d is the top opened scope it will find that no is a constant and get its value. The same will happen with e1 and after that the main.cga5d scope will be removed from the top of the opened scopes. Note that even if main.cga5d is already opened using the open keyword it will only be searched once.