GMacAPI Guide

GMacAPI Guide

On this page you can:

  • Read a full explanation of the various components of GMacAPI code composition system.
  • Find a description of the main .NET classes of GMacAPI including their roles and relations to each other.
  • Read a complete code example for using GMacAPI to compose a simple code library from GMacDSL code.

 

This guide explains GMacAPI by covering the following:

  1. An Industrial View of GMacAPI:
    1. The Assembly Line.
    2. Raw Materials.
    3. The Final Product.
    4. Monitoring Production.
    5. Composition by Sub-Processes.
    6. The Core Sub-Process.
  2. GMacAPI Classes:
    1. Code Generation Classes.
    2. Binding Classes.
    3. Code Block Classes.
    4. Target Language Classes.
  3. Creating an Assembly Line Step by Step:
    1. Selecting A Suitable Design.
    2. Preparing the Assembly Line Classes.
    3. Using Parametric Templates.
    4. Generating Computational Code From a Single Macros.
    5. Generating Computational Code From Multiple Related Macros.
    6. Wrapping Up.

 

1. An Industrial View of GMacAPI

A software factory is described in Wikipedia as:

In software engineering and enterprise software architecture, a software factory is a software product line that configures extensive tools, processes, and content using a template based on a schema to automate the development and maintenance of variants of an archetypical product by adapting, assembling, and configuring framework-based components.
Since coding requires a software engineer (or the parallel in traditional manufacturing, a skilled craftsman) it is eliminated from the process at the application layer, and the software is created by assembling predefined components instead of using traditional IDE’s. Traditional coding is left only for creating new components or services. As with traditional manufacturing, the engineering is left to creation of the components and the requirements gathering for the system. The end result of manufacturing in a software factory is a composite application.

GMac can be used as a configurable assembly line in a software factory for creating geometric computing applications and libraries.

In this guide we will explain GMacAPI, the code composition and generation part of GMac, in several ways:

  1. First, we will explain the concepts behind GMacAPI and its base classes in general terms.
  2. Next, we will dive into the details of GMacAPI classes and the processes required to produce the desired result.
  3. Finally, we will give an example of the whole process in code.

In order to fully understand the explanations in this guide you must read the following GMac guides first:

  1. About GMac
  2. GMac System Guide
  3. GMac Math Guide
  4. GMacDSL Guide
  5. GMacAST Guide
  6. TextComposerLib Guide

1.1. The Assembly Line

In many ways the process of parsing text code into an intermediate Abstract Syntax Tree (AST) is the mirror of text code generation from an AST:

  • The input for a typical parser is a set of concrete, structured, human-readable code files organized, typically, inside a set of nested folders. The output of a text code generator is a set of concrete, structured, human-readable code files organized in a set of nested folders.
  • The output of a parser is an abstract syntax tree (or at least a less abstract parse tree) while the main input of a code generator is an abstract syntax tree.
  • The process of parsing is done by abstracting meaning from concrete text code into an AST, while code generation adds concrete information to the abstract semantics encoded in the AST.
  • A well organized parser typically consists of many smaller integrated components and sub-processes, and so should a good code generator be.

A parser is similar to our biological digestive system, extracting bio-chemical nutrants from living food (after some cooking of course) while a code generator is similar to our reproductive system assembling a living body from very basic bio-chemical materials according to a carefully guided construction plan. Because most of the readers wouldn’t be biologists, a more appropriate analogy of the GMac code composer and generator is an assembly line for composing structured textual code from raw materials found in a GMacAST data structure based on a well-thought code design.

The GMacAPI Assembly Line

Components of the GMacAPI Assembly Line

In GMac, the GMacCodeLibraryComposer abstract class plays the role of an assembly line for composing code. This class in found under the GMac.GMacAPI.CodeGen namespace and is inherited from TextComposerLib’s CodeLibraryComposer class. The GMacCodeLibraryComposer abstract class contains several essential integrated components working together to produce the desired results:

  • GMacAST Information: The  Root member, an object of type AstRoot, can be used to access a full GMacAST structure used for code generation. In addition, the  SelectedSymbols member, of type AstSymbolsCollection, holds a set of GMacAST symbols to be used in code generation when a specific part, rather than the whole, of the GMacAST is to be used for code generation.
  • Text Code Composer: The  CodeFilesComposer member of type FilesComposer acts as the container where the code is generated.
  • Parametric Text Templates: The  Templates member, an object of type ParametricComposerCollection, contains a set of parametric text templates to be used in producing the main body of the generated code.
  • Target Language Operations: The  GMacLanguage member, an object of type GMacLanguageServer, is used in generating target language specific code in a correct syntax like comments, declarations, assignments, array access, loops, etc.
  • Code Generation Monitoring: The  Progress member, an object of type ProgressComposer, can be used to monitor the progress of the code generation process.

The designer would create an instance of the assembly line by inheriting from the GMacCodeLibraryComposer class and then use these components in the code generation process. The designer may add more components to the custom assembly line, some of which already exist in GMac and TextComposerLib, for example:

  • Computational Code Generators: The GMacMacroCodeComposer class performs the most important code generation task in GMac: to use some macro for computational code generation using GMacMacroBinding and other related binding classes for configuring the whole process for the selected macro.
  • Sub-Process Code Generators: The GMacCodePartComposer class and derived classes can be used to distribute and organize the full code generation process among several sub-generators. This provides much better control, readability, and extensiblity compared to using many methods inside a single class derived from the GMacCodeLibraryComposer main assembly line class.
  • Text Composers: All the text composers in the TextComposerLib library can be used for the code generation process as needed.
  • Temporary Symbols Compilers: The GMacTempSymbolCompiler class, under the GMac.GMacCompiler namespace, can be used to add temporary symbols, like structures and macros, to the original GMacAST just to be used during some code generation task and removed from the GMacAST after finishing that task.

Other components will be added as more applications are created with GMac. Nevertheless the set of components already present is more than enough to effectively produce geometric computing concrete libraries from GA-based abstract models. We will explain all classes related to the code generation process in the second part of this guide.

1.2. Raw Materials

The main source of information, or raw data, for the GMacCodeLibraryComposer assembly line is a GMacAST data structure compiled from GMacDSL code as explained in the GMacAST Guide. The GMacAST structure is independent of any particular target language, like C#, Python, C++, Java, etc., from any particular external computing libraries, and from any particular software architecture required. The assembly line reformulates the information in the GMacAST structure, accessible from the  Root and SelectedSymbols members of the GMacCodeLibraryComposer base class, into the required target code for a particular target language, by filling the places in a second kind of raw material: the parametric text templates.

The Templates member of the GMacCodeLibraryComposer class is a collection of parametric text templates, each template can be used in two distinct but complementary ways:

  1. To create a small piece of code based on some information in the template’s parameters. For example a basis blade can be used to generate a class member in the generated target language code using a template "Coef#id#" that replaces the #id# parameter with the basis blade ID number provided in the GMacAST.
  2. To create a large chunk of code, even a whole code file, with predefined target language code structure.  Much of the hierarchical information organization of readable text typically found in (well-) written human code can be implemented very elegantly using this technique. Most of the software system architecture can be designed beforehand, just like a web-designer produces the general template for a whole web site, and then the small but critical computational code can be generated from the GMacAST structure to fill the gaps in the large body of the text templates.

The ParametricComposer and ParametricComposerCollection classes described in TextComposerLib Guide are designed specifically to serve these purposes. In this way, the parametric templates are a second form of raw material that are target-language and code-structure specific; in contrast with the language independent abstract GMacAST structure. We can view this as we view as a mold (the parametric text templates) and a liquid metal (the GMacAST structure) commonly used in many manufacturing operations.

1.3. The Final Product

The goal of the GMacCodeLibraryComposer assembly line is to use its components to produce structured textual code. The code structure can be both external, related to how code files are organized inside specific folders, and internal, related to well formatted readable text inside a code file. Using TextComposerLib’s FilesComposer class is particularly suitable for this task. All code is written to the CodeFilesComposer member of the GMacCodeLibraryComposer class using its methods and properties as described in the TextComposerLib Guide.

1.4. Monitoring Production

Generation of code in GMac is, typically, a composite operation having many small steps that may take many minutes to complete. The ProgressComposer class can be used to report detailed progress in the code generation process. The Progress member of the GMacCodeLibraryComposer assembly line is intended for this purpose. Progress reporting can be enabled or disabled at any point and progress history can be stored in the form of text or in the form of ProgressEventArgs objects as explained in the TextComposerLib Guide. The Progress member can be used to raise events for integration with the code generation user interface or be used to read a part of the history to update the UI at regular intervals.

1.5. Composition by Sub-Processes

It’s possible to create a set of private methods inside our GMacCodeLibraryComposer assembly line to generate all required code. It is not recommended to do so, however, because the resulting class would be bulky and hard to maintain. A good alternative is to use any of the following abstract classes as a form of sub-process organization for the whole code composition process:

  • GMacCodePartComposer : This abstract class encapsulates a sub-process of code generation that can access all components of the main  GMacCodeLibraryComposer assembly line class.
  • GMacCodeFileComposer : Inherited from GMacCodePartComposer; this abstract class encapsulates a sub-process of code generation into a single file.
  • GMacCodeStringComposer : Inherited from GMacCodePartComposer; this abstract class encapsulates a sub-process of code generation into a single string.
  • GMacMacroCodeComposer : Inherited from GMacCodeStringComposer; this class provides the ability to generate computational code from a selected GMac macro with configurable parameters bindings to target language variables and constant values. We will discuss this very important sub-process shortly.
  • GMacMacroCodeFileComposer : Inherited from GMacCodeFileComposer; this class can be used to generate a single code file containing the computations generated from a single macro or several related macros. This class is based on a  GMacMacroCodeComposer object internally.

All the sub-process classes are inherited from the GMacCodePartComposer class. The main assembly line components are accessible through the LibraryComposer member and other members. This way the full complicated code composition process can be divided among several sub-code generators based on these classes providing good maintainability of the final assembly line classes.

1.6. The Core Sub-Process

The core code generation sub-process in GMac is to use some abstract, coordinates-free, geometric algebra based macro for creating equivalent optimized, coordinate-dependent computational code in some selected target language like C++, C#, or Python for example. There are two sides to the computational code generation process in GMac:

  1. The Abstract Side: Where a GMac macro represent some clear abstract GA-based computational procedure.
  2. The Concrete Side: Where a set of code instructions written in some target language perform the same computation on a lower, less abstract, but computationally more efficient level.

On the boundary between the two sides, GMac uses data binding patterns to link leaf components of the macro parameters with primitive target language variables and values. Each abstract macro parameter component can be bound to:

  • A target language variable: For example some local variable in a sub-routine or some array item in a class member function.
  • A constant value.

As soon as a macro is selected and a data binding pattern is set, GMac can compute a set of abstract symbolic computations that can be used to calculate the final lowest-level macro outputs from its assigned inputs. Then the GMac code generator substitutes target variables in place of symbolic variables, converts symbolic expressions into target language computational expressions, and emits the required code. This sub-process is called Macro Code Generation and is the core sub-process in our assembly line.

Macro Code Generation Sub-Process

Macro Code Generation Sub-Process

Example40

As an example assume we select the e3d.versorInverse macro, listed below, for code generation. The macro code is compiled into an AstMacro object that can be accessed using the Root member of the GMacCodeLibraryComposer class:

First we set how each macro input and output parameter is bound as a constant or variable. In the shown list of bindings all outputs are variables, the scalar part of the input is a constant one, the bivectors of the input are variables, and all other input components are zero. These bindings are set using methods in the GMacMacroBinding class as we will explain later on:

Then we use the CreateOptimizedCodeBlock() method of the GMacMacroBinding object to create a code block (an instance of the class GMacCodeBlock) containing an equivalent abstract low-level version of the computations provided by the symbolic computer algebra system Mathematica in the background:

After GMac optimizes the computations we get this equivalent sequence of computations in the code block:

Finally we can configure a GMac macro code generator (an object of type GMacMacroCodeComposer) to compose concrete textual code in a selected target language (an object inherited from the GMacTargetLanguage class) from our optimized code block, like this generated C# code for example:

The use of binding patterns and symbolic manipulations can considerably reduce the amount of computations and intermediate storage needed for complex GA-procedures. As shown in the following two diagrams, even a simple operation of geometric product in the 5D CGA frame requires almost half the amount of processing and storage if we apply it to two grade-3 full k-vectors instead of using two full multivectors. The library designer should always use the minimum binding required for a specific computational task and GMac will try to produce the smallest code based on the selected bindings.

MacroGenExample2

The complexity of the geometric product of two full grade 3 k-vectors in a 5d GA frame.

MacroGenExample1

The complexity of the geometric product of two full multivectors in a 5d GA frame.

Several GMacAPI classes can be used to configure and execute this important sub-process, that can be considered a small assembly line by itself. The GMacMacroCodeComposer class is the main class for configuring and implementing such core GMac code generation sub-process. Several other classes are used for configuring and executing this process:

  • The Raw Material: In this case an AstMacro GMacAST symbol is the main source of input data to this sub-process.
  • The Control Panel: Using the GMacMacroBinding and other data binding pattern classes, we can select and configure the nature of each low-level macro input and output component of the macro as either a constant or a target language variable of some kind. We will explain the binding classes later on.
  • The Semi-Finished Assembly: The GMacMacroCodeComposer class creates a GMacCodeBlock object containing an abstract, language independent, and optimized sequence of computations connecting the selected outputs to the selected inputs using the minimum possible number of intermediate computations. More about this important class later.
  • The Final Sub-Product: Using the GMacTargetVariablesNamingGMacMathematicaExpressionConverter and GMacLanguageServer classes we can generate computational code for a specific target language from the GMacCodeBlock object. All target language-specific features can be implemented at this level to produce the desired code.
  • Controlling Code Generation Details: We can control which computations to generate as code and to inject code before or after each computation using the ActionBeforeGenerateSingleComputation and ActionAfterGenerateSingleComputation member delegates and the GMacComputationCodeInfo class. More about this later.
  • Integration with the Main Assembly Line: The main  GMacCodeLibraryComposer assembly line, with all of its components, is available to the  GMacMacroCodeComposer class through its LibraryComposer member. This way the macro code generation sub-process can use other components of the main assembly line, like Templates and Progress objects for example. In addition, a special sub-process class, the GMacMacroCodeFileComposer class, can be used to generate several bindings of the same macro or several related macros into a single code file using an internal hidden  GMacMacroCodeComposer object.

 

2. GMacAPI Classes

The GMacAPI classes can be found under four namespaces:

  1. Code Generation Classes: Under the GMac.GMacAPI.CodeGen namespace. These are the main classes in the code generation process responsible for coordinating and using all other classes for the purpose of structured text code generation from GMacAST symbols.
  2. Binding Classes: Under the GMac.GMacAPI.Binding namespace. These classes are responsible for defining how a macro’s parameters are connected to target language variables in order to generate optimized code accordingly.
  3. Code Block Classes: Under the GMac.GMacAPI.CodeBlock namespace. These classes hold information about low-level symbolic computations required to compute the outputs of a macro from its inputs.
  4. Target Language Classes: Under the GMac.GMacAPI.Target namespace. These classes can be used to convert abstract symbolic expressions into target language code and generate common code constructs like for-loops, comments, assignments, local variable declarations, etc.

The code generation classes are the main classes for implementing the code assembly line. The binding and code block classes are used only for generating computational code from macros, the core sub-process of GMacAPI. The target language classes are used to abstract some of the general operations on target languages useful in both general code generation and macro code generation; they are a good example of the AST-unparser code composition method explained in the TextComposerLib Guide.

2.1. Code Generation Classes

GMacCodeLibraryComposer

This is the base class for our assembly line. To understand this class we first need to show the correct initialization sequence for any derived class so we can use it correctly.

GMacCodeLibraryComposer Class

GMacCodeLibraryComposer Class

Step 1: The first step is to inherit a new class from GMacCodeLibraryComposer and add any additional components useful for our particular code library generator. For example:

Step 2: In the constructor of the inherited class we call the base constructor for the GMacCodeLibraryComposer base class passing a root GMacAST object (of type AstRoot) and a target language server (inherited from GMacLanguageServer). We also create the required MacroGenDefaults member and any other components we need later in the constructor:

After the constructor finishes the following GMacCodeLibraryComposer members are created and ready to be used immediately:

  • Progress : An object of type ProgressComposer that can be used to report the progress of code generation as explained in the TextComposerLib Guide.
  • CodeFilesComposer : An object of type FilesComposer used for placing the generated structured code  as explained in the TextComposerLib Guide.
  • Root : An object of type AstRoot holding main information of a GMacAST as explained in the GMacAST Guide.
  • GMacLanguage : An object of type GMacLanguageServer for performing code generation of common code structures in the selected target language like loops, comments, array and variable declarations and assignments, etc.

The following members are created after the constructor is finished, but still need to be initialized before being used:

  • Templates : An object of type ParametricComposerCollection (explained in the TextComposerLib Guide) used for storing parametric text templates for later use by the code generators.
  • TemplatesReady : This member returns true after initializing the Templates inside the InitializeTemplates() method as explained shortly.
  • SelectedSymbols : An object of type AstSymbolsCollection (explained in the GMacAST Guide) to be initialized by the code library generator user by adding GMacAST symbols used as starting points for code generation.
  • MacroGenDefaults : An object of type GMacMacroCodeGenDefaults, that will be explained shortly, holding the default configuration for computational code generation of macros inside this code library generator.

Step 3: We should at this point implement all the abstract members and methods of the CodeLibraryComposer and GMacCodeLibraryComposer base classes. But before we do that we need to keep in mind the correct sequence in which the code library generator methods are called during the code generation process. There are generally two ways we can use the derived code generator:

The first way is to call the Generate() method of the code library generator and, optionally, give it a delegate that executes the code generation process. If no delegate is passed to this method, the implementation of the ComposeTextFiles() abstract method is passed by default. This method automatically calls other initialization\generation\finalization methods as shown here:

The InitializeGenerator() and FinalizeGenerator() protected methods perform initialization and finalization of the code generator components as shown in their code:

As we can see from the code above the actual sequence of called member methods in the Generate() method is as follows:

  1. VerifyReadyToGenerate()
  2. InitializeTemplates()
  3. InitializeOtherComponents()
  4. ComposeTextFiles()
  5. FinalizeOtherComponents()

The second way is to create a custom “generate” method (perhaps with special parameters or even as a static method in the inherited library code composer class). As an example see this code:

Step 4: Now we are ready to override the abstract members of the base classes and any other virtual members needed. We need to implement the following members as a minimum:

  • Name : Returns a string containing a defining name for this code library composer.
  • Description : Returns a descriptive string for the code library composer.
  • CreateEmptyGenerator() : Create an uninitialized copy of this generator. This can used by a user interface to create a default copy this code library composer for later configuration.
  • GetBaseSymbolsList() : Returns a list of GMacAST symbols that can be used as the base for code generation for this library composer. For example, we may return all AstFrame objects in the Root of this generator. This can be used by the user interface to restrict the possible starting GMacAST symbols to be selected for code generation.
  • VerifyReadyToGenerate() : Implement this method so that it returns true whenever the code library composer is ready for initialization and code generation. For example make sure the user selected at least one frame to be used for code generation in the SelectedSymbols member.
  • InitializeTemplates() : If the code library composer needs parametric templates add whatever templates needed here either as fixed strings or loaded from text files. Use the Templates member to add your template and return true after finishing.
  • InitializeOtherComponents() : For any other components that require initialization. For example set the properties for the MacroGenDefaults member to be used to generate computational code from macros. We can also initialize a temporary GMacAST symbol compiler object of type GMacTempSymbolCompiler to add symbols to the base GMacAST for the duration of out code generation process.
  • ComposeTextFiles() : Here is where all code generation should take place after everything is initialized properly. Here we can call other methods specific to our code generation needs and use sub-process code generators for delegating specific parts of the full code generation process.
  • FinalizeOtherComponents() : After code generation is complete we use this to finalize code generation components if needed. For example we may remove any temporary macros or structures created during code generation using a temporary GMacAST symbol compiler object of type GMacTempSymbolCompiler.
  • GetSymbolTargetName() : Implement this to convert some GMacAST symbol’s name into a suitable target language code object’s name if needed.

We can also override the CreateMacroCodeGenerator() virtual method for creating and initializing a GMacMacroCodeGenerator object to be used for the core sub-process of macro code generation.

Step 5: During code generation we can use any of our assembly line components and any of the following members as we desire:

  • SelectedSymbols : A collection of AstSymbol objects that can be used as starting points for code generation. This is typically set by the user after calling the constructor of our code library generator.
  • HasActiveFile : At any instance of time we can use this member to see if the code library generator’s CodeFilesComposer member has an active code file to be used for composing code.
  • ActiveFileComposer : We can use this member to access the TextFileComposer object of the active file for our code library generator.
  • ActiveFileTextComposer : We can use this member to access the LinearComposer object of the active file for our code library generator.
  • GetUniqueName() : Gets a unique internal name for the given AstSymbol.
  • RequestProgressStop() : An extension method in the TextComposerLib.Logs.Progress.ProgressUtils class. This method puts a request to stop code generation at the first possible coming point.
  • CheckProgressRequestStop() : An extension method in the TextComposerLib.Logs.Progress.ProgressUtils class. When suitable we can put a call to this method to allow for interrupting code generation if the user have already called the RequestProgressStop() method.
  • RequestCancelGeneration() : The user can call this method to request interrupting code generation especially when it takes a long time. If this method is called the Status member will have the value InPregressRequestCancel.
  • GenerateComment() : To add a comment to the active file.

The the TextComposerLib.Logs.Progress.ProgressUtils class contains many other progress reporting extension methods that can be used to monitor code generation as needed.

GMacCodePartComposer

This is the base class for all sub-process code generators. We may inherit a class from this class directly to perform a complex but related set of sub-processes for out code generation process. The constructor of this class takes a reference to a GMacCodeLibraryComposer parent object to be used inside the code generation sub-process. The parent object can be accessed using the LibraryComposer member and many other assembly line components have corresponding members like: Templates, Root, SelectedSymbols, Progress, and GMacLanguage.

GMacCodePartComposer Class Diagram

GMacCodePartComposer Class Diagram

GMacCodeStringComposer

This is an abstract class derived from GMacCodePartComposer and used for generating a single string of code using its Generate() method.

GMacCodeFileComposer

This is another abstract class inherited from GMacCodePartComposer class to be used for generating code into a single file. In addition to the members inherited from its base class, other members include:

  • FileComposer : The TextFileComposer object used for composing the file’s code.
  • TextComposer : The LinearComposer object used for composing the file’s code.
  • Generate() : This abstract method is overridden to generate the desired code into the file.

GMacMacroCodeComposer

The main class for generating computational code from macro bindings for some target language. This class in inherited from the GMacCodeStringComposer class. Because this class implements a complex process, comparable to the GMacCodeLibraryComposer class, we need to understand the sequence of operations inside this important class.

GMacMacroCodeComposer Class Diagram

When creating a GMacMacroCodeComposer object we may either pass a GMacCodeLibraryComposer or a GMacMacroCodeComposerDefaults object as input.  We can also supply a base macro for code generation if we desire. In any case the following members are created:

  • LibraryComposer : A reference to the main code library composer of this macro code composer.
  • SyntaxList : This object is of type  SteSyntaxElementsList explained in the TextComposerLib Guide. The code is generated into this object that acts as a simple target language AST that will be unparsed into the final computational code.
  • UsedDefaults : Holds a copy of the GMacMacroCodeComposerDefaults object used for initializing some properties of this GMacMacroCodeComposer.
  • MacroBinding : This member is only created if we supply a valid AstMacro as an input to the constructor, else it’s null. To create this member at a later time we must use the SetBaseMacro() method as we will explain shortly.
  • BaseMacro : If the MacroBinding member is not null, this member returns its base macro used for code generation.
  • IsMacroBindingReady : This member returns true if the MacroBinding member is not null and some of the macro’s output parameters are already bound to variables so code generation can now start without problems.

After the GMacMacroCodeComposer object is created we can call the SetBaseMacro() method at any time to change the base macro and create a new GMacMacroBinding object, accessible through the MacroBinding member. The next step is to specify the values of some members for controlling the macro code generation sub-process:

  • AllowGenerateMacroCode : If this is set to false the macro code generation process is disabled and no code is generated. This can be used by the UI to generate a “skeleton” code library in a short time as macro code generation typically involves many symbolic computations and takes most of the code generation time.
  • ActionSetMacroParametersBindings : This is a delegate used for initializing the MacroBinding member with suitable macro parameters bindings before code generation can begin.
  • ActionSetTargetVariablesNames : This is a delegate used for assigning target variables names to macro parameters bound with variables after the optimized abstract CodeBlock object is created.
  • ActionBeforeGenerateComputations : This delegate is called before generating computational macro code. It’s typically used for generating comments, temp variables declarations, and other code needed before the actual computations can be performed. In addition, the user can return false from this delegate to prevent code from being generated based on the structure of the CodeBlock member or some other condition.
  • ActionAfterGenerateComputations : This delegate is called after generating computational macro code. It’s typically used for generating comments, temp variables destruction and cleanup, and other code needed after the actual computations is performed.
  • ActionBeforeGenerateSingleComputation : This delegate is called, if not set to null, before each computation in the CodeBlock member is converted into target language code. Here we can inject code before the computation is written and we can prevent the computation from being generated entirely if desired.
  • ActionAfterGenerateSingleComputation : This delegate is called, if not set to null, after each computation is converted into code. Here we can inject code after the computation is written if we need to.

After creating the GMacMacroCodeComposer object and setting its main members and actions we can call its Generate()  method to perform the following tasks in the stated order:

  1. Clear the SyntaxList, MacroBinding, CodeBlock, and TargetVariablesNaming members.
  2. If the AllowGenerateMacroCode is true, it goes ahead with code generation else it returns an empty string.
  3. Execute the ActionSetMacroParametersBindings delegate passing the MacroBinding member as input. If the delegate is set to null a default version (a static method called DefaultActionSetMacroParametersBindings) is called instead.
  4. If the IsMacroBindingReady member returns true it goes ahead with code generation else it returns an empty string.
  5. The CodeBlock member is created by calling the CreateOptimizedCodeBlock() method of the MacroBinding member. This is where all symbolic manipulation and code optimization occur taking most of the code generation time.
  6. The TargetVariablesNaming member is created based on the TargetLanguage and CodeBlock members.
  7. Execute the ActionSetTargetVariablesNames delegate passing the TargetVariablesNaming member as input to allow the user to set target variables names for input and output macro parameters that were bound to variables and to temporary computation variables used in the intermediate computations. If this delegate is null a default version (the DefaultActionSetTargetVariablesNames static method) is called instead.
  8. Execute the ActionBeforeGenerateComputations delegate passing the whole GMacMacroCodeComposer as input. If the delegate is set to null a default version (a static method called DefaultActionBeforeGenerateComputations) is called instead. In any case if the called delegate returns false no code generation is performed and an empty string is returned.
  9. The GenerateProcessingCode() method is called to performs the following tasks per computation of the CodeBlock member:
    1. Use the ExpressionConverter member’s Convert() method to convert the RHS symbolic expression into target language code.
    2. Create a new instance of the GMacComputationCodeInfo class and initialize its members.
    3. Call the delegate ActionBeforeGenerateSingleComputation if not set to null. Here the user can set the EnableCodeGeneration member of the GMacComputationCodeInfo object to false in order to prevent code generation for this particular computation.
    4. Call the virtual method GenerateSingleComputationCode() to generate the code for a single computation based on the GMacComputationCodeInfo object. The GMacLanguage and SyntaxList objects are used to generate the computation’s code. The user may override this method to do whatever code generation task needed for this single computation instead of the default implementation.
    5. Finally, a call to the ActionAfterGenerateSingleComputation delegate is made, if it’s not set to null.
  10. After all computations are generated a call to the ActionAfterGenerateComputations delegate is done. If the delegate is set to null a call to a default version (the static DefaultActionAfterGenerateComputations method) is made instead.
  11. The SyntaxList member is unparsed into the final target code.
  12. Finally, some cleanup is made and the generated code is returned.

If the default behavior of the GMacMacroCodeComposer class is not suitable we can use the action delegates to modify it and we can inherit from the GMacMacroCodeComposer and override its virtual GenerateSingleComputationCode() method. The full code of the Generate(), GenerateProcessingCode(), and GenerateSingleComputationCode() methods is shown below:

We will illustrate this core sub-process in the example at the third part of this guide.

GMacMacroCodeComposerDefaults

This class holds default configuration for macro code composers. The main members include:

  • LibraryComposer : The parent code library generator object.
  • FixOutputComputationsOrder : Prevents the CodeBlock optimization process from changing the order of computing output parameters from input and temp variables. This is especially useful when we need to exit the computational code block after some condition is met on some output variable.
  • AllowGenerateMacroCode : To allow or prevent generation of macro code.
  • ActionBeforeGenerateComputations : The action performed just before starting to generate the optimized macro computations code. This is useful for adding comments and initialization code before the actual computations are generated.
  • ActionAfterGenerateComputations : The action performed just after generating the optimized macro computations code. This is useful for adding comments and cleanup code before the actual computations are generated.
  • Duplicate() : This method returns an exact copy of this object to be stored for later use.

GMacComputationCodeInfo

This class holds some information for a single computation to be generated as target language code. The members include:

  • ComputedVariable : The code block variable holding the abstract symbolic computation to be generated. The computed variable may be a temporary or output variable in the whole computation.
  • TargetVariableName : The name of the target variable corresponding to the computed variable.
  • RhsExpressionCode : The RHS symbolic expression of the computation after converted to a proper target language expression.
  • GMacLanguage : The target language to be used for code generation.
  • EnableCodeGeneration : This flag can be set to disable the generation of this computation’s code.

GMacMacroCodeFileComposer

This abstract class, inherited from the GMacCodeFileComposer class, can be used to generate code from a single base macro or a set of related macros into a single code file.

GMacMacroCodeFileComposer Class Diagram

GMacMacroCodeFileComposer Class Diagram

The constructors of this class can directly create a GMacMacroCodeComposer object based on a given macro, or use a given GMacMacroCodeComposer object as the user requires. Several related bindings of the same macro or related macros can be generated by several calls to the GenerateComputationsCode() method with varying binding and target naming depending on other derived class parameters. The SetBaseMacro() member method can be called to change the base macro before calling the GenerateComputationsCode() method. The main members include:

  • BaseMacro : The current macro used for code generation.
  • InitializeGenerator() : This virtual method can be used to setup any members of the internal GMacMacroCodeComposer object.
  • SetMacroParametersBindings() : This method is automatically selected to initialize macro parameter bindings for the internal GMacMacroCodeComposer object.
  • SetTargetVariablesNames() : This method is automatically selected to initialize macro parameter target variables names for the internal GMacMacroCodeComposer object.
  • SetBaseMacro() : This method can be called to change the base macro from which code will be generated.
  • GenerateComputationsCode() : This method calls the InitializeGenerator() method, then calls the Generate() method of the internal  GMacMacroCodeComposer object to return the generated optimized macro code as a string.

2.2. Binding Classes

There are two kinds of binding classes in GMacAPI: the linear binding classes and the tree binding classes. The most important of all is the GMacMacroBinding class used directly by the GMacMacroCodeComposer class for generating optimized code. All other classes can be used mainly to fill the data in the GMacMacroBinding class. The tree binding classes implement a set of simple interfaces shown.

Tree Binding Interfaces

Any macro parameter is of scalar type, multivector type, or structure type. A multivector macro parameter contains many primitive scalar components corresponding to the multivector’s coefficients. A structure macro parameter consists of data members of other types and ultimately defines a tree having leaf scalar components. So we can view a single macro parameter either as a tree of scalar components, or as a linear list of scalar components resulting from complete branches in the tree.

To illustrate the use of GMacAPI binding classes we will use this sample GMacDSL code:

For example a macro parameter named hitInfo of type e3d.HitInfo can be viewed conceptually either as a tree, as shown, or as a linear list of primitive components:

In addition, we can access or use a partial components tree of the hitInfo parameter using any of the following expressions:

 

GMacMacroParameterBinding

A single object of this class holds information that binds a single primitive scalar component of a macro parameter to either a constant symbolic expression or to a variable. The main members include:

  • ValueAccess : A data-store value access having a root macro parameter and accessing a sub-component of primitive scalar type.
  • ValueAccessName : The full name of the selected macro parameter component.
  • ToScalarBinding : Create a GMacScalarBinding object from this macro parameter binding.
  • ConstantExpr, ConstantSymbolicScalar, and ConstantValue : If the required binding is a constant binding these members hold several forms of the same constant value used in the binding. If the required parameter binding is a variable binding, these members are null.
  • Root : The root GMacAST of this binding’s macro parameter.
  • BaseMacro : The base macro of this binding’s macro parameter.
  • GMacType : The GMac scalar type object of this binding.
  • IsInput and IsOutput : True for input and output parameter bindings respectively.
  • IsVariable, IsConstant, IsNonZero, IsNonZeroConstant, and IsZeroConstant : Some members to classify the kind of binding used for the selected macro parameter component.

GMacMacroBinding Class Diagram

GMacMacroBinding

This class holds a set of GMacMacroParameterBinding objects to fully specify how the primitive components of inputs and outputs of some macro are bound to variables or constant values. The main members include:

  • Static Create() methods: These methods can create an empty macro binding or create and fill the macro binding from a macro tree binding object that will be explained shortly.
  • BindOutputToConstantBehavior : This member defines how the macro binding will respond when the user requests to bind an output parameter to a constant value: to raise an exception, to ignore the binding, or to bind with a variable instead.
  • BaseMacro : The base macro for this macro binding.
  • FixOutputComputationsOrder : When this flag is set to true, the code optimization phase will not change the order of output parameters computations.
  • IsMacroBindingReady : True if any component of the macro output parameter is bound to a variable.
  • Progress : The ProgressComposer  object used for reporting macro code generation progress.
  • Bindings, ConstantBindings, VariableBindings, InputBindings, OutputBindings, VariableInputBindingsVariableInputBindings, and VariableOutputBindings : Some members to return filtered lists of macro parameter bindings.
  • UsedParameters, UsedConstantParameters, UsedVariableParameters, UsedInputs, UsedOutputs, UsedVariableInputs, and UsedVariableOutputs : Some members to return filtered lists of macro parameter components used in this macro binding.
  • UsedParametersNames , UsedConstantParameterNames , UsedVariableParameterNames , UsedInputNames , UsedOutputNames , UsedVariableInputNames , and UsedVariableOutputNames : Some members to return filtered lists of macro parameter components names used in this macro binding.
  • Clear() : Clear all macro parameters bindings.
  • GetBindings() : Get a list of all macro parameter components bindings used in this macro binding that are sub-components of a given macro parameter.
  • GetUsedParameters() : Get a list of all macro parameter primitive scalar components used in this macro binding that are sub-components of a given macro parameter.
  • TryGetBinding() : Search for a macro parameter component binding given a primitive data-store value access on the component.
  • TryGetScalarBinding() : Search for a macro parameter component binding given a primitive data-store value access on the component and return an equivalent scalar binding.
  • UnBind() : Remove the given macro parameter components from the macro binding.
  • BindScalarToConstant() : Overloaded methods for binding a given primitive macro parameter component to a constant value.
  • BindMultivectorUsing()  and BindMultivectorPartToVariables() : Overloaded methods for binding some components of a multivector macro parameter using several binding methods.
  • BindToConstants() , BindToVariables() , BindToTreePattern() , BindUsing() : Overloaded methods for binding some components of a macro parameter using several binding methods.
  • CreateOptimizedCodeBlock() : This is the method responsible for creating an optimized code block, of type GMacCodeBlock , from the macro binding information.

Example40

 

 

GMacScalarBinding

This is the simplest of all tree binding patterns used for the leafs of the tree binding. Conceptually, a tree binding pattern is a tree of components very similar to a type having leafs of GMacScalarBinding objects. The main members of this class are:

  • GMacType : The GMac scalar type of this scalar binding.
  • ConstantExpr, ConstantSymbolicScalar, and ConstantValue : If this scalar binding is with a constant value these members return several forms of the same binding constant value.
  • IsVariable, IsConstant, IsNonZero, IsNonZeroConstant, and IsZeroConstant : Members to query the nature of the scalar binding.
  • ToConstantsFreePattern() : Return a scalar binding with a variable.
  • ToExpr(), ToMathematicaScalar(), and ToValue() : Convert this binding into a symbolic expression in several forms. If it’s a constant binding these return the constant else they use the supplied input to generate a symbolic variable.

GMacScalarBinding Class Diagram

GMacMultivectorBinding

The multivector tree binding pattern can bind the coefficients of a multivector type to variables or constant expressions using one GMacScalarBinding object per selected coefficient. The main members include:

  • Static Create() methods: Used to create multivector binding patterns from several kinds of inputs.
  • GMacType : The frame multivector of this binding pattern as a GMac type object.
  • BaseFrameMultivector : The frame multivector of this binding pattern.
  • BaseFrame : The frame of this binding pattern
  • ScalarType : The primitive scalar type of the coefficients of this binding pattern.
  • BoundIDs , BoundConstantIDs , BoundNonZeroIDs , BoundZeroConstantIDs , BoundNonZeroConstantIDs , BoundVariableIDs , and NotBoundIDs : The IDs of the basis blades used (or not used in the case of NotBoundIDs) in this binding pattern.
  • BoundGrades and NotBoundGrades : The grades of the basis blades used \ not used in this binding pattern.
  • VariablesCount and ConstantsCount : The number of variable or constant scalar binding patterns for the multivector coefficients of this binding pattern.
  • ContainsVariables and ContainsConstants : Tests the use of variable or constant scalar binding patterns for the multivector coefficients of this binding pattern.
  • Constants : The constant scalar binding patterns for the multivector coefficients of this binding pattern returned as a dictionary having basis blade IDs as keys and constant expressions as values.
  • The this[] indexer: Gets the scalar binding pattern associated with the given basis blade ID, or grade and index.
  • Clear() : Clears all scalar bindings for the multivector coefficients of this pattern.
  • HasBoundCoef() : Tests if the given basis blade’s coefficient is bound in this binding pattern.
  • HasBoundCoefOfGrade() : Tests if some basis blade’s coefficient of the given grade is bound in this binding pattern.
  • ContainsVariable() , ContainsConstant() , ContainsScalarPattern() : Test if this binding pattern contains some variable or constant scalar binding for the given basis blade coefficient.
  • MatchesPattern() : Test is this multivector binding pattern exactly matches a given multivector binding pattern.
  • CanStoreRhs() : Test if the given input pattern can be stored in this pattern. Each bound basis blade coefficient in the input pattern is tested for being bound to a variable in this pattern or bound to a constant of the same value.
  • UnBindCoef() , UnBindCoefs() , and UnBindKVector() : Remove the given basis blade coefficients from this binding pattern.
  • Methods for binding a group of coefficients or a single coefficient like BindCoefToPattern() , BindCoefToConstant() , BindCoefToVariable() , and BindUsing().
  • ToConstantsFreePattern() : Create a copy of this pattern but replace any coefficient bound to a constant by a variable scalar binding.
  • PickConstantComponents() : Select the components bound to constants in this pattern into a new binding pattern of the same base multivector type.
  • PickVariableComponents() : Select the components bound to variables in this pattern into a new binding pattern of the same base multivector type.
  • PickConstantComponentsAsVariables() : Select the components bound to constants in this pattern into a new binding pattern of the same base multivector type but having all variable scalar binding patterns.
  • ToConstantsFreePattern() : Return an identical binding pattern to this but with all leaf scalar constant bindings replaced by variable bindings.
  • ToValue() : Convert this binding pattern into a multivector value. For each bound basis blade coefficient if it’s a constant binding the constant expression is used else the supplied input is used to generate a symbolic variable.

GMacMultivectorBinding Class Diagram

GMacStructureBinding

The structure tree binding pattern can be used to bind the components of a GMac structure type with other binding patterns of suitable types. The main members include:

  • The static Create() method: Used for creating a new structure binding pattern.
  • GMacType : The base structure of this binding pattern as a GMac type object.
  • BaseStructure : The base structure of this binding pattern.
  • BoundMembers , NotBoundMembers : A list of structure members used or not used in this binding pattern.
  • BoundMembersNames, NotBoundMembersNames : A list of structure member names used or not used in this binding pattern.
  • Clear() : Clear all members bindings in this binding pattern.
  • HasBoundMember() : Test if the given structure member is bound in this binding pattern.
  • BindMemberToPattern() : Bind the given structure member to some binding pattern of the same base type as the member.
  • PickConstantComponents() : Select the components bound to constants in this pattern into a new binding pattern of the same base structure type.
  • PickVariableComponents() : Select the components bound to variables in this pattern into a new binding pattern of the same base structure type.
  • PickConstantComponentsAsVariables() : Select the components bound to constants in this pattern into a new binding pattern of the same base structure type but having all variable scalar binding patterns for its leaf components.
  • ToConstantsFreePattern() : Return an identical binding pattern to this but with all leaf scalar constant bindings replaced by variable bindings.
  • ToValue() : Convert this binding pattern into a structure value. For each leaf component if it’s a constant binding the constant expression is used else the supplied input is used to generate a symbolic variable.

GMacStructureBinding Class Diagram

GMacMacroTreeBinding

This binding is equivalent to a GMacMacroBinding but holds binding information in tree form not linear form. The main members include:

  • The static Create() method: Used to create a new macro tree binding pattern.
  • BaseMacro : The base macro for this binding pattern.
  • BindOutputToConstantAction : Used to specify the action taken when the user requests to bind an output parameter to a constant value.
  • BoundParameters, NotBoundParameters : A list of macro parameters that are used or not used in this binding pattern.
  • BoundParametersNames, NotBoundParametersNames : A list of macro parameter names that are used or not used in this binding pattern.
  • Clear() : Clear all parameters bindings in this binding pattern.
  • HasBoundParameter() : Test if the given macro parameter is bound in this binding pattern.
  • BindParameterToPattern() : Bind the given macro parameter to some binding pattern of the same base type as the parameter.
  • PickConstantComponents() : Select the components bound to constants in this pattern into a new binding pattern of the same base macro.
  • PickVariableComponents() : Select the components bound to variables in this pattern into a new binding pattern of the same base macro.
  • PickConstantComponentsAsVariables() : Select the components bound to constants in this pattern into a new binding pattern of the same base macro but having all variable scalar binding patterns for its leaf components.
  • ToConstantsFreePattern() : Return an identical binding pattern to this but with all leaf scalar constant bindings replaced by variable bindings.

GMacMacroTreeBinding Class Diagram

2.3. Code Block Classes

Code Block Variables Interfaces and Classes

There are three types of code block variables:

  1. Input variables represented by the GMacCbInputVariable class.
  2. Temporary variables represented by the GMacCbTempVariable class.
  3. Output variables represented by the GMacCbOutputVariable class.

Input variables are read-only and they are independent of all other code block variables. Temporary and output variables are computed from other temporary and input variables. Finally, output variables are write-only and they can not be used for computing any other variables. The base class for all three code block variables classes is the abstract GMacCbVariable class. The GMacCbComputedVariable class, inherited from the  GMacCbVariable class, is the base class for the GMacCbTempVariable and GMacCbOutputVariable classes as shown in the class diagram.

GMacCbVariable Class Diagram

The code block variables classes implement several interfaces depending on their function:

  • All three code block variable classes are inherited from the abstract GMacCbVariable class that implements the IGMacCbVariable interface.
  • The input and temporary variables classes implement the IGMacCbRhsVariable interface because they can be used to compute other code block variables.
  • The input and output variables classes implement the IGMacCbParameterVariable interface because they represent low-level scalar components of macro parameters.
  • The temporary and output variables classes are inherited from the GMacCbComputedVariable class that implements the IGMacCbComputedVariable interface because they are computed from other code block variables.

GMacCodeBlock

This class is essentially a list of computations that relate the outputs of a macro to its inputs on a low-level manner. The information in this class is the final source of target language code to be generated for a single macro. Each code block contains input and output variables (i.e. macro parameters) and may contain temporary variables. A macro parameter inside the code block has an internal symbolic name, like LLDI0023 for example, a data-store value access GMacDSL name, like hit.Ray.origin.#e1#, and eventually will have a target code name, like Ray.Origin.X or Item[1], in the final code. Temporary variables only have symbolic names and target names.

GMacCodeBlock Class Diagram

The following members provide detailed information about the optimized low-level abstract computations required to obtain the outputs from the inputs as determined by macro parameters bindings:

  • BaseMacro : The base macro of the computations in the code block.
  • Variables : ِA list of all variables in the code block.
  • InputVariables : A list of all input variables in the code block.
  • UsedInputVariables : A list of the input variables used in computations in the code block.
  • NotUsedInputVariables : A list of the input variables not used in any later computations in the code block.
  • TempVariables : A list of the temporary variables in the code block.
  • OutputVariables : A list of the output variables in the command block.
  • ConstantOutputVariables : A list of the output variables not depending on any other variables for their final values in the command block.
  • NonConstantOutputVariables : A list of the output variables depending on some other variables for their final values in the command block.
  • ParameterVariables : A list of all input and output variables in the command block.
  • TempSubExpressions : A list of the temporary variables resulting from common sub-expression refactoring optimization stage in the code block.
  • TempNonSubExpressions : A list of the temporary variables other than the ones resulting from common sub-expression refactoring optimization stage in the code block.
  • TargetTempVarsCount : The number of target temporary variables required for the computations of this code block. This is often less from the actual number of temporary variables in the code block because of temporary variable re-use.
  • Some methods to get a variable given its low-level name like: TryGetTempVariable(), TryGetInputVariable(), TryGetOutputVariable(), and TryGetVariable().
  • TryGetParameterVariable() : Gets an input or output variables in the code block given its data-store value access.
  • GetParameters() : Given a data-store value access on a parameter of the base macro this gets all input\output variables belonging to the given value access in the code block.
  • GetParametersValueAccess() : Given a data-store value access on a parameter of the base macro this gets all input\output variables’ value access objects belonging to the given value access in the code block.
  • GetParametersValueAccessNames() : Given a data-store value access on a parameter of the base macro this gets all input\output variables’ value access names belonging to the given value access in the code block.
  • GetDependencyGraph() : Create and populate the dependency graph holding information about the relations between code block variables.
  • GetStatistics() : Gets a list of statistics about the code block.
  • GetStatisticsReport() : Gets a statistics report as a single string about the code block.

GMacCbVariable

This abstract class is the base for all classes representing the code block’s low-level variables. The class contains the following useful members inherited by its sub-classes:

  • MaxComputationLevel : The max number of computation steps needed to compute this variable. For input variables and constant computed variables this is always zero.
  • LowLevelId : A unique ID for the low-level variable.
  • LowLevelName : A unique internal name for the low-level variable.
  • UserVariables : A list of variables that directly use (i.e. depend on) this variable in their RHS computations.
  • TargetVariableName : The final name of the low-level variable in the generated target code.
  • IsInput : True for input variables.
  • IsTemp : True for temporary variables.
  • IsOutput : True for output variables.
  • IsComputed : True for computed variables (temp and output variables).
  • IsUsed : True if the variable is used by other variables.
  • IsParameter : True if this is a macro parameter (i.e. an input or output code block variable).
  • IsRhsVariable : True if this is an input or temporary variable.
  • IsIndependent : True for input variables and constant computed variables.
  • IsTempSubExpression : True if this is a temporary variable resulting from common sub-expression factoring.
  • IsTempNonSubExpression : True if this is a temporary variable not resulting from common sub-expression factoring.

GMacCbInputVariable

This class represents low-level input variables in the code block. Input variables are read-only, never appearing in the LHS of a computation, and they take their target values from macro parameters binding information. The main members include:

  • ValueAccess : This member links the input variable to a primitive data-store value access component of a macro input parameter.
  • ValueAccessName : The full name of the macro input parameter component.
  • UserVariables : A list of the code block variables that use this input variable in their computations.
  • IsUsed : True if this input variable is used in the computations of other code block variables.
  • LastUsingVariable : The last code block variable to use this input variable in its computation.
  • LastUsingComputationOrder : The order of the last computation that uses this input variable.

GMacCbComputedVariable

This abstract class is the base for computed low-level variables in the code block; i.e. temporary and output variables. The class contains the following members inherited by its two sub-classes:

  • RhsExpr : The RHS expression specifying the computation of this variable in the form of a text expression tree. See MathematicaInterface Guide for more information.
  • ComputationOrder : The order of which this variable is computed in the code block.
  • RhsVariables : A list of variables used in the RHS computation of this variable. The list may contain repeated instances of the same variable so if a set is required use the Distinct() Linq method or something similar.
  • RhsInputVariables : A list of input variables used in the RHS computation of this variable. The list may contain repeated instances of the same variable.
  • RhsTempVariables : A list of temporary variables used in the RHS computation of this variable. The list may contain repeated instances of the same variable.
  • RhsSubExpressions : Gets a list of all sub-expressions in the RHS computation of this variable.
  • IsConstant : True if this computed variable does not depend on any other code block variables for its computation.

GMacCbTempVariable

This class represent a temporary variable that performs some intermediate computation. The main members include:

  • SubExpressionUseCount : The number of times this sub-expression temp variable is used in later computations.
  • IsReused : True if this temporary variable is a re-use of another target variable.
  • NameIndex : The target index of this re-used temporary variable.
  • IsFactoredSubExpression : True if this temporary variable was introduced during code block optimization as a factored sub-expression of some computation.
  • UserVariables : A list of the code block variables that use this input variable in their computations.
  • IsUsed : True if this temporary variable is used in the computations of other code block variables.
  • LastUsingVariable : The last code block variable to use this temporary variable in its computation.
  • LastUsingComputationOrder : The order of the last computation that uses this temporary variable.

GMacCbOutputVariable

This class represents a low-level computed output variable in the code block. Output variables are write-only and never appear in the RHS of any computation. The class contains the ValueAccess property that links the output variable to a primitive data-store value access on a macro parameter.

GMacCbDependencyGraph

This class is inherited from the generic DependencyGraph class we talked about in the GMacAST Guide. The class contains information about which variables in the code block depend on a certain variable variable and which are used by the variable.

GMacCbDependencyGraph Class Diagram

 

2.4. Target Language Classes

The purpose of the classes under the GMac.GMacAPI.Target namespace is to allow for code composition for a selected target language by constructing a simple abstract syntax tree (AST) with syntax elements belonging to the target language. Then we can unparse the AST into text code with correct formatting and punctuation. The whole approach is explained in the TextComposerLib Guide.

GMac Target Language Classes

GMac Target Language Classes

GMacLanguageServer

The GMacLanguageServer abstract class is inherited from the TextComposerLib.Code.Languages.LanguageServer  class. This class is constructed with an object of type LanguageSyntaxFactory to be used for constructing the AST, and another of type LanguageCodeGenerator to unparse the AST into code. Each target language in GMac, like C#, C++, Python, etc., has a corresponding language server class inherited from the GMacLanguageServer class. The following are the important members of this class:

  • LanguageInfo : A member holding some target language information like its name and version.
  • SyntaxFactory : A member that can be used for constructing an AST of the target language.
  • CodeGenerator : A member that can be used to unparse an AST into target language text code.
  • ExpressionConverter : This member can convert symbolic Mathematica expressions into target language expressions. For example the symbolic expression Plus[1, Sin[a], b] is converted to the C# code 1 + Math.Sin(a) + b .
  • GenerateCode() : This method uses the ExpressionConverter member if not null to convert the given Mathematica expression into target code.

GMacMathematicaExpressionConverter

This abstract class, inherited from the TextComposerLib.Code.Languages.LanguageExpressionConverter class, is used for converting Mathematica symbolic expressions, typically found in the GMacCodeBlock class, into target language expressions using its Convert() method. The converter may need to assign a GMacCodeBlock object to its ActiveCodeBlock member so it can replace symbolic low-level variables by their target language names correctly. Each target language in GMac, like C#, C++, Python, etc., has a corresponding expression converter class inherited from the GMacMathematicaExpressionConverter class.

GMacTargetVariablesNaming

This class assigns target language variable names to primitive macro parameters that were bound to variables in the macro binding used for generating the optimized code block. The main members include:

  • TargetLanguage : The target language for this object.
  • CodeBlock : The code block used for naming target language variables.
  • BaseMacro : The base macro from which the code block is generated.
  • SetScalarParameter() , SetMultivectorParameters() , and SetParameters() : Sets the names for the given macro input or output parameters.
  • SetVariables() , SetInputVariables() , SetOutputVariables() , and SetTempVariables() : Sets the names for the given code block input, output, or temporary variables.

3. Creating an Assembly Line Step by Step

In the final part of this guide we will explain practical steps for creating a code library for general GA computations based on a 3D Euclidean GA frame. The purpose is to give a practical illustration of the use of GMacAPI classes to generate a structured C# code library from simple GMacDSL code. First we need to represent a multivector using one or more C# classes and then implement our GA operations as methods and properties on the multivector classes. The full code can be found under the GMacSamples.CodeGen.Multivectors namespace in the GMacSamples project of the GMac .NET solution. The class diagram for the assembly line classes is shown below.

MvLibrary Class Diagram

MvLibrary Code Generation Classes

3.1. Selecting A Suitable Design

Before generating the code for the library we need to select its design. To represent the 3D Euclidean multivectors we can use a single class holding 8 real coefficients and create a single method for each of our operations like geometric product, addition, scalar multiplication, etc. This is a very bad idea since we can usually rely on combinations of simpler algebraic entities like vectors, points, and normals that are both more efficient to represent and to compute with. Fortunately GA allows for multivectors to sparsely represent all these simpler entities with uniform and concise geometric interpretations. The role of GMac is to implement the sparse multivectors as concrete code in the selected target language; C# in this example.

We will take another approach of representing multivectors using a base abstract class called e3dMultivector and use a different sub-class per multivector sparse type. We will define our multivector types as the 4 main k-vector types: scalars, vectors, pseudo-vectors (bi-vectors), pseudo-scalars, and every combination of them. Thus we get 16 different multivector classes derived from the base e3dMultivector class. We will call the classes using the naming convention e3dMultivectorX where X is a decimal number from 0 to 15 whose binary pattern has a 1 where the multivector contains k-vectors of that grade. For example the cga5dMultivector5 class contains only a coefficient for the scalar basis blade and the 3 bi-vector basis blades of the GA space, a total of 4 real coefficients. So if we need to perform the geometric product of two e3dMultivector5 objects GMac produces a much more efficient implementation instead of using the full 8 coefficients multivectors. We will give some of the classes special names:

  • e3dZero : The zero multivector containing no coefficients. A single instance of this class is created as a static member in the e3dMultivector class to act as a fast result for operations always resulting in the zero multivector.
  • e3dScalar : The multivector containing a single coefficient for the scalar basis blade 1.
  • e3dVector : The multivector containing 3 coefficients for representing vectors.
  • e3dPseudoVector : The multivector containing 3 coefficients for representing 2-vectors.
  • e3dPseudoScalar : The multivector containing 1 coefficient for representing pseudo-scalars.
  • e3dFull : A full 8 coefficients representation for multivectors.

This design may not be the best for all kinds of GA based models but it’s just an illustration of the flexibility of using GMac as a tool for implementing any desired GA-based code library.

After generating the code for the 17 classes (1 base class + 16 sub-classes) we need to implement operations on multivectors. Here is where things get interesting because we need to implement the geometric product, for example, on 16 x 16 = 256 different combinations of multivector classes! This task is humanly impossible to implement and verify by hand, even for a small 3D space like this one, but can be automated through GMacAPI to produce (an insane amount of) efficient GA-based code, especially for more useful spaces of higher dimensions like the 5D Conformal GA space.

3.2. Preparing the Assembly Line Classes

The first step after settling on our desired target code design is always to inherit a class from the GMacCodeLibraryComposer base class, implement its abstract methods, and add any additional required members and assembly line components. We will call our code generator class MvLibrary and its initial implementation would look like this:

As explained in the second part of this guide, the following methods are overridden properly keeping in mind their sequence of calling:

  • CreateEmptyGenerator() : Returns an empty copy of this code library generator.
  • GetBaseSymbolsList() : Simply returns all frames of the root AST object.
  • GetSymbolTargetName() : For now it returns the AST symbol name (typically a frame) given in the GMacDSL code. This may be modified if several frames have the same name under different GMacDSL namespaces.
  • InitializeTemplates() : It just returns true for now.
  • InitializeOtherComponents() : Here we select to use double precision target variables and set the default properties for macro code generators to be used later. the two methods GeneratePreComputationsCode() and GeneratePostComputationsCode() will be described shortly.
  • VerifyReadyToGenerate() : We make sure the symbols selected by the MvLibrary  user are all valid GMacAST frames.
  • ComposeTextFiles() : This is the start point where all code is generated. We call the method GenerateFrameCode(), described shortly, per frame in the selected symbols list of the code assembly line.

Now we can add the following members and components to be used in assembling our code:

  • CurrentFrame : An object of type AstFrame holding information of the frame information used in code generation.
  • CurrentFrameName : The name of the current frame.
  • MultivectorClassesData : A list of objects of type MvClassData, to be explained shortly, made to hold information about sparse multivector types used for generating the multivector classes in the target code.

So we add this code to the MvLibrary class:

And modify its constructor to be:

We need to create a component class holding information for each of the 16 desired sparse multivector types. The class MvClassData  is very simple and is used, during this particular code generation task, to act as a concentrated source of information with the following implementation:

The main members include:

  • ClassId : Holds an integer whose binary bit pattern stores which of the 4 grades are present in the class to be generated. For example for the e3dFull class this member is 15, for the e3dScalar class this is 1, while for the e3dVector class this is 2 and so on.
  • ClassName : The name to be used in the generated code for this class.
  • Frame : The GMacAST frame object for this multivector type.
  • ClassBinding : A multivector binding object that tells GMac which basis blade coefficients are bound to variables for this particular sparse multivector type.
  • ClassBasisBlades : Returns a list of basis blades used in this sparse multivector type.

Next we typically inherit from the GMacCodeFileComposer and GMacMacroCodeFileComposer GMacAPI code generation sub-process classes to serve our particular assembly line design. The default implementations for these two classes are nice but we often need to add more specific members to them in order to better organize our assembly line code. The modified versions are:

3.3. Using Parametric Templates

Now we turn to generating a code file for the base e3dMultivector class. Most of this class’s code is fixed in our design. The computational code will go into the 16 derived sparse multivector classes. So, we can use a single parametric text template to generate the e3dMultivector class code. Since we need a single file for this class we inherit a class called BaseMvClassFileGenerator from the MvLibraryCodeFileGenerator class we created earlier. The code for this sub-process code generator class is as follows:

The ClassCodeTemplateText constant holds the text for the template used for generating the main body of the e3dMultivector class. The template has the following parameters as we can see in its Generate() method implementation and in its text:

  • frame : We substitute the name of the used frame here: “e3d”.
  • double : The target scalar type used in the library. This could have been fixed but it was parameterized to offer more flexibility to select from “float” or “double” for example.
  • zero_class_name : The name of the special multivector class containing no coefficients, the zero multivector class.
  • declare_coefs : In here we can substitute several lines of code for declaring read-only properties of the base multivector class to return all the values for any multivector’s coefficients.

This code generation sub-process class would generate a single file with the following text in our library:

This is a simple example of using the ParametricComposer class of the TextComposerLib library. We could have created this template in the Templates member of the MvLibrary but we chose to use it locally inside the BaseMvClassFileGenerator class because the template will only be needed here not anywhere else in the code assembly process.

Similarly we can abstract the main bodies for all the remaining 16 derived sparse multivector classes, using parametric templates, into a single code generator class called DerivedMvClassFileGenerator. The full code of this code generation sub-process class can be found in the GMacSamples.CodeGen.Multivectors namespace. The main difference between the abstract e3dMultivector base class and the 16 derived sparse multivector classes is the need for implementing custom, but highly regular, computations in the 16 classes. We will utilize C#’s partial classes feature for the 16 implementations. For each class we will create a main file and 16 other files; a total of 16 x 17 + 1 = 273 files for the whole multivector library. For example the class named e3dMultivector5 would have its main code in a file named e3dMultivector5.cs in addition to 16 files named e3dMultivector5CalcX.cs where X is an integer between 0 and 15. The generated code for the e3dMultivector5.cs file would be something like this:

The actual computations for, say, the geometric product of two sparse multivectors of type e3dMultivector5 and e3dMultivector11 can be found in the file named e3dMultivector5Calc11.cs and so for all implemented bilinear products for these two classes. Here are the full contents of the generated e3dMultivector5Calc11.cs file:

The main files for the 16 sparse multivector classes are generated using the DerivedMvClassFileGenerator sub-process class, mentioned earlier, derived from the MvLibraryCodeFileGenerator class. The remaining 256 computations files are generated using the DerivedMvClassCalcFileGenerator sub-process classes derived from the MvLibraryMacroCodeFileGenerator class. The full code for the DerivedMvClassFileGenerator sub-process class is as follows:

Most of this code is straight forward with one exception: the code of the GenerateNorm2Code() private method. In this method we create optimized computational code for calculating the squared norm of the sparse multivector. We will explain this code in the following sub-section.

3.4. Generating Computational Code From a Single Macro

By creating code generating classes for the main class bodies of our library the easy part is almost done. Now we turn to the interesting part by generating computational code from a GMac macro. One macro that we need to generate in our MvLibrary assembly line is this one:

This macro is automatically created, among many others, by GMac during the compilation of any frame defined in the GMacDSL source code. Basically it’s a wrapper around the built-in unary GMac operator norm2() that computes the (metric) squared norm of a multivector (equal to the scalar part of the geometric product of a multivector by its reverse). All such wrapper macros names can be found in the DefaultMacro static class under the GMac.GMacCompiler.Semantic.ASTConstants namespace. Let’s take a closer look at the code for the GenerateNorm2Code() private method in the DerivedMvClassFileGenerator sub-process class:

To explain this code note:

  • The first two lines in this method test for the special case of computing the norm for the zero multivector class in which case we just generate the string "return 0.0D;"; no macro code generation is needed in this case.
  • The next line of code creates a new instance of the GMacMacroCodeComposer class based on the main MvLibrary assembly line class and the GMac Norm2() macro. Here some of the initial properties of the new macro code generator are assigned automatically from the MacroGenDefaults member of the main MvLibrary assembly line class. These properties were set in the InitializeOtherComponents()  overridden method of the MvLibrary class. The two default properties are the ActionBeforeGenerateComputations and ActionAfterGenerateComputations member delegates. See the MvLibrary code again for this default assignment part. These two actions are responsible for generation some comments before and after the computational code in addition to declaring temporary variables if needed in the generated code. The actual code can be found in the GeneratePreComputationsCode() and GeneratePostComputationsCode() private methods of the MvLibrary class listed earlier.
  • Next we set the ActionSetMacroParametersBindings member of the macro code generator. This member takes a delegate with the internal GMacMacroBinding member passed as a parameter to set which macro parameters are treated as variables, which are set to constants, and which are left as zero.
  • Then we set the ActionSetTargetVariablesNames member of the macro code generator. This member takes a delegate with the internal GMacTargetVariableNaming member passed as a parameter to set the target variables names of macro parameters previously bound as variables in the internal macro binding object.
  • Finally we set the ActionBeforeGenerateSingleComputation member delegate. The code of this delegate replaces the code generated for the output macro parameter by simpler code, explained shortly. Now we are ready to call the Generate() method of the macro code generator as our last step.

Note that the code in line 22 of the GenerateNorm2Code() method is actually not needed. The generated code for the e3dVector class, for example, would look like this:

the last line in the code was originally result = (tempVar0000 + tempVar0001); but we replaced it by assigning the ActionBeforeGenerateSingleComputation member delegate so it becomes return (tempVar0000 + tempVar0001); instead and so no naming of the scalar macro output parameter is needed.

3.5. Generating Computational Code From Multiple Related Macros

In our library we need to create 16 files per derived multivector class where each file contains the computations related to that particular class. For example all bilinear products (geometric, outer, left contraction, etc.) between multivectors of type e3dMultivector5 and e3dMultivector11 can be found in the file e3dMultivector5Calc11.cs under the folder e3d.e3dMultivector5 in the final generated code, so we have 256 such files in total under 16 different folders. Each file contains the computational code from the same GMac macros but with different parameter bindings, and each file contains computational code for a set of related macros as we saw earlier. We can use the GMacMacroCodeComposer class for this code generation task but a better solution is to use the GMacMacroCodeFileComposer instead since we will be generating several related macros, the standard GA bilinear products, into the same file for each of the 256 combinations of multivector classes.

The code of the DerivedMvClassCalcFileGenerator code generator sub-process class is as follows:

Here are the main members used in this class:

  • The class is inherited from the GMacMacroCodeFileComposer class used to generate a single code file with, possibly, multiple instances of computational code generated from a related set of GMac macros.
  • The class contains two members of type MvClassData called ClassData and CalcClassData. These two members specify the multivector classes involved in all computations generated in the file.
  • The GenerateClassCode() method is where the main code of the generated file is created. This method calls the GenerateProductMethod() several times on each of the selected macros whose names are stored in the macroNames local array. The GenerateProductMethod() calls the GenerateProductMethodCode() method with a parameter holding the name of the macro to be generated and an output parameter holding the specifications of the tightest sparse class that can hold the result without any loss of information.

So the most important method in this sub-process class is the GenerateProductMethodCode() method. We will focus our discussion on this method and 3 others shown here:

We can understand the function of this class through the following points:

  • When the DerivedMvClassCalcFileGenerator class is created, an internal GMacMacroCodeComposer object is created to be used for computational code generation of any selected macro.
  • The default behavior of the internal macro code generator is taken from the parent GMacCodeLibraryComposer object’s MacroGenDefaults member.
  • Any remaining initialization of the internal macro code generator is implemented in the overridden InitializeGenerator() method of the DerivedMvClassCalcFileGenerator class as seen in the code above where the ActionBeforeGenerateSingleComputation member is set.
  • The other two overridden methods SetMacroParametersBindings() and SetTargetVariablesNames() are called by the internal macro code generator object when generating the computational code in their proper place as discussed in the previous sub section.

Now to explain the details of the GenerateProductMethodCode() method:

  • The first thing to do is to set the macro to be generated as seen in line 38 of the above code.
  • Next we call the GenerateComputationsCode() method, already defined in the GMacMacroCodeFileComposer() base class, to create the optimized intermediate code block based on the parameters bindings defined in the SetMacroParametersBindings() overridden method as seen in line 40.
  • We handle the special case when the result of the macro is a zero multivector or the zero scalar, depending on the returned type of the selected GMac macro, and return a simple zero if so as seen in lines 42 to 55.
  •  If the macro returns a multivector we select the tightest sparse multivector type we can use to hold the results without any loss of information as seen in lines 62 to 73. If the macro returns a scalar we simply create text suitable for a floating point result as seen in lines 77 and 79.
  • Finally we compose and return the full computational code in lines 82 to 90.

3.6. Wrapping Up

Going back to the main assembly line in the MvLibrary class we call our sub-process classes in the appropriate methods as shown here in the final code for the MvLibrary assembly line:

The actual steps for creating this assembly line are not as illustrated in this section. The assembly line typically undergoes several iterations of prototyping, testing, refactoring, adding more features, and repeating. For example, some methods and parametric templates local to some code generation sub-process classes are refactored into the main assembly line because they are used in other sub-processes. This is where the creativity of the library designer shines and the symmetries and regularities of GA-based models enlighten the way to more free and efficient geometric computing software design without the restrictions imposed by manual coding and verification of computational code.

 

WordPress Appliance - Powered by TurnKey Linux