This guide explains GMacAPI by covering the following:
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:
- First, we will explain the concepts behind GMacAPI and its base classes in general terms.
- Next, we will dive into the details of GMacAPI classes and the processes required to produce the desired result.
- 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.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.
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:
- 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.
- 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:
- The Abstract Side: Where a GMac macro represent some clear abstract GA-based computational procedure.
- 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.
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:
1 2 3 4 5 6 7 8 9 10 11 |
//The frame of the selected macro frame e3d (e1, e2, e3) euclidean //A macro to compute the inverse of a versor in the e3d frame macro e3d.versorInverse( mv : Multivector) : Multivector begin let b = reverse( mv ) let a = mv gp b return b / a.#E0# end |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Macro Binding Data: result.#E0# <=> <Variable> result.#e1# <=> <Variable> result.#e2# <=> <Variable> result.#e1^e2# <=> <Variable> result.#e3# <=> <Variable> result.#e1^e3# <=> <Variable> result.#e2^e3# <=> <Variable> result.#e1^e2^e3# <=> <Variable> mv.#E0# <=> <Constant> 1 mv.#e1^e2# <=> <Variable> mv.Coef[3] mv.#e1^e3# <=> <Variable> mv.Coef[5] mv.#e2^e3# <=> <Variable> mv.Coef[6] |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
BEGIN Input: LLDI0009 Input: LLDI000A Input: LLDI000B Input: LLDI000C Temp: LLDI0011 = Plus[1,Power[LLDI000A,2],Power[LLDI000B,2],Power[LLDI000C,2]] Output: LLDI0001 = Power[LLDI0011,-1] Output: LLDI0002 = 0 Output: LLDI0003 = 0 Output: LLDI0004 = Times[-1,LLDI000A,Power[LLDI0011,-1]] Output: LLDI0005 = 0 Output: LLDI0006 = Times[-1,LLDI000B,Power[LLDI0011,-1]] Output: LLDI0007 = Times[-1,LLDI000C,Power[LLDI0011,-1]] Output: LLDI0008 = 0 END |
After GMac optimizes the computations we get this equivalent sequence of computations in the code block:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
BEGIN Input: LLDI0009 Input: LLDI000A Input: LLDI000B Input: LLDI000C Output: LLDI0002 = 0 Output: LLDI0003 = 0 Output: LLDI0005 = 0 Output: LLDI0008 = 0 Sub-expression: LLDI0013 = Power[LLDI000A,2] Sub-expression: LLDI0014 = Plus[1,LLDI0013] Sub-expression: LLDI0015 = Power[LLDI000B,2] Sub-expression: LLDI0016 = Plus[LLDI0014,LLDI0015] Sub-expression: LLDI0017 = Power[LLDI000C,2] Sub-expression: LLDI0018 = Plus[LLDI0016,LLDI0017] Output: LLDI0001 = Power[LLDI0018,-1] Sub-expression: LLDI0019 = Power[LLDI0018,-1] Output: LLDI0004 = Times[-1,LLDI000A,LLDI0019] Output: LLDI0006 = Times[-1,LLDI000B,LLDI0019] Output: LLDI0007 = Times[-1,LLDI000C,LLDI0019] END |
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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
public static geometry3d.e3d.Multivector versorInverse(geometry3d.e3d.Multivector mv) { var result = new geometry3d.e3d.Multivector(); //Bagin GMac Macro Code Generation, 2015-12-22T14:46:18.3015065+02:00 //Macro: geometry3d.e3d.versorInverse //Input Variables: 3 used, 1 not used, 4 total. //Temp Variables: 7 sub-expressions, 0 generated temps, 7 total. //Target Temp Variables: 2 total. //Output Variables: 8 total. //Computations: 0.933333333333333 average, 14 total. //Memory Reads: 1.06666666666667 average, 16 total. //Memory Writes: 15 total. // //Macro Binding Data: // result.#E0# <=> <Variable> result.Coef[0] // result.#e1# <=> <Variable> result.Coef[1] // result.#e2# <=> <Variable> result.Coef[2] // result.#e1^e2# <=> <Variable> result.Coef[3] // result.#e3# <=> <Variable> result.Coef[4] // result.#e1^e3# <=> <Variable> result.Coef[5] // result.#e2^e3# <=> <Variable> result.Coef[6] // result.#e1^e2^e3# <=> <Variable> result.Coef[7] // mv.#E0# <=> <Constant> 1 // mv.#e1^e2# <=> <Variable> mv.Coef[3] // mv.#e1^e3# <=> <Variable> mv.Coef[5] // mv.#e2^e3# <=> <Variable> mv.Coef[6] double LLDI0013; double LLDI0014; double LLDI0015; double LLDI0016; double LLDI0017; double LLDI0018; double LLDI0019; result.Coef[1] = 0; result.Coef[2] = 0; result.Coef[4] = 0; result.Coef[7] = 0; LLDI0013 = Math.Pow(mv.Coef[3], 2); LLDI0014 = 1 + LLDI0013; LLDI0015 = Math.Pow(mv.Coef[5], 2); LLDI0016 = LLDI0014 + LLDI0015; LLDI0017 = Math.Pow(mv.Coef[6], 2); LLDI0018 = LLDI0016 + LLDI0017; result.Coef[0] = Math.Pow(LLDI0018, -1); LLDI0019 = Math.Pow(LLDI0018, -1); result.Coef[3] = -1 * mv.Coef[3] * LLDI0019; result.Coef[5] = -1 * mv.Coef[5] * LLDI0019; result.Coef[6] = -1 * mv.Coef[6] * LLDI0019; //Finish GMac Macro Code Generation, 2015-12-22T14:46:18.3055067+02:00 return result; } |
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.
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 GMacTargetVariablesNaming, GMacMathematicaExpressionConverter 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:
- 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.
- 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.
- 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.
- 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.
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public sealed partial class BladesLibrary : GMacCodeLibraryComposer { //Some members used in this particular code library generator internal int MaxTargetLocalVars { get { return 16; } } internal UniqueNameFactory UniqueNameGenerator { get; private set; } internal AstFrame CurrentFrame { get; private set; } internal string CurrentFrameName { get; private set; } internal GMacTempSymbolCompiler TempSymbolsCompiler { get; private set; } //Other code goes here } |
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:
1 2 3 4 5 6 7 8 9 |
public BladesLibrary(AstRoot astInfo) : base(astInfo, GMacTargetLanguage.CSharp4()) { MacroGenDefaults = new GMacMacroCodeComposerDefaults(this); UniqueNameGenerator = new UniqueNameFactory() { IndexFormatString = "X4" }; TempSymbolsCompiler = new GMacTempSymbolCompiler(); } |
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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/// <summary> /// Performs text files generation based on the given AST information. /// This method calls InitializeGenerator(), ComposeTextFiles(), then FinalizeGenerator(). /// </summary> public void Generate() { Generate(ComposeTextFiles); } /// <summary> /// Performs text files generation based on the given AST information. /// This method calls InitializeGenerator(), composeTextFilesAction(), then FinalizeGenerator(). /// </summary> /// <param name="composeTextFilesAction"></param> public void Generate(Action composeTextFilesAction) { if (this.SetProgressRunning() == false) return; if (VerifyReadyToGenerate() == false) { this.SetProgressNotRunning(); return; } InitializeGenerator(); try { composeTextFilesAction(); } catch (OperationCanceledException e) { this.ReportError(e); } finally { CodeFilesComposer.FinalizeAllFiles(); FinalizeGenerator(); this.SetProgressNotRunning(); } } |
The InitializeGenerator() and FinalizeGenerator() protected methods perform initialization and finalization of the code generator components as shown in their code:
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 28 29 30 |
/// <summary> /// Initializes the text file generation process. This method must be called before /// any generation process /// </summary> protected void InitializeGenerator() { //Call initialize templates if needed if (TemplatesReady == false) TemplatesReady = InitializeTemplates(); //For each template, clear all parameters bindings foreach (var template in Templates.Values) template.ClearBindings(); //Clear the contents of the files composer CodeFilesComposer.Clear(); //Initialize any other components of a generator sub-class inherited from this one InitializeOtherComponents(); } /// <summary> /// Finalizes the text file generation process. This method must be called after /// any generation process /// </summary> protected void FinalizeGenerator() { //Finalize any other components of a generator sub-class inherited from this one FinalizeOtherComponents(); } |
As we can see from the code above the actual sequence of called member methods in the Generate() method is as follows:
- VerifyReadyToGenerate()
- InitializeTemplates()
- InitializeOtherComponents()
- ComposeTextFiles()
- 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/// <summary> /// Generate for a single frame /// </summary> /// <param name="frame"></param> /// <param name="generateMacroCode"></param> /// <returns></returns> public static BladesLibrary Generate(AstFrame frame, bool generateMacroCode = true) { var libGen = new BladesLibrary(frame.Root) { MacroGenDefaults = {AllowGenerateMacroCode = generateMacroCode} }; libGen.SelectedSymbols.Add(frame); libGen.Generate(); return libGen; } |
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.
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.
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:
- Clear the SyntaxList, MacroBinding, CodeBlock, and TargetVariablesNaming members.
- If the AllowGenerateMacroCode is true, it goes ahead with code generation else it returns an empty string.
- 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.
- If the IsMacroBindingReady member returns true it goes ahead with code generation else it returns an empty string.
- 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.
- The TargetVariablesNaming member is created based on the TargetLanguage and CodeBlock members.
- 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.
- 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.
- The
GenerateProcessingCode() method is called to performs the following tasks per computation of the
CodeBlock member:
- Use the ExpressionConverter member’s Convert() method to convert the RHS symbolic expression into target language code.
- Create a new instance of the GMacComputationCodeInfo class and initialize its members.
- 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.
- 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.
- Finally, a call to the ActionAfterGenerateSingleComputation delegate is made, if it’s not set to null.
- 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.
- The SyntaxList member is unparsed into the final target code.
- 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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
/// <summary> /// Generate the code for a single computation /// </summary> /// <param name="codeInfo"></param> protected virtual void GenerateSingleComputationCode(GMacComputationCodeInfo codeInfo) { //Generate assignment statement for this computation SyntaxList.Add( GMacLanguage .SyntaxFactory .AssignToLocalVariable(codeInfo.TargetVariableName, codeInfo.RhsExpressionCode) ); } /// <summary> /// Generate the low-level assignments code from the low-level optimized macro code /// </summary> private void GenerateProcessingCode() { ExpressionConverter.ActiveCodeBlock = CodeBlock; //Iterate over optimized low-level computations foreach (var computedVar in CodeBlock.ComputedVariables) { //Convert the rhs text expression tree into target language code var rhsExprCode = ExpressionConverter.Convert(computedVar.RhsExpr); //Create the codeInfo object var codeInfo = new GMacComputationCodeInfo() { ComputedVariable = computedVar, RhsExpressionCode = rhsExprCode, GMacLanguage = GMacLanguage, EnableCodeGeneration = true }; //Generate the assignment target code based on the codeInfo object //Execute this action before generating computation code if (ReferenceEquals(ActionBeforeGenerateSingleComputation, null) == false) ActionBeforeGenerateSingleComputation(SyntaxList, codeInfo); //If the action prevented generation of code don't generating computation code if (codeInfo.EnableCodeGeneration) GenerateSingleComputationCode(codeInfo); //Execute this action after generating computation code if (ReferenceEquals(ActionAfterGenerateSingleComputation, null) == false) ActionAfterGenerateSingleComputation(SyntaxList, codeInfo); } SyntaxList.AddEmptyLine(); } /// <summary> /// Generate optimized macro code in the target language given a list of macro parameters bindings /// </summary> /// <returns></returns> public override string Generate() { //Initialize components of macro code generator SyntaxList.Clear(); MacroBinding.Clear(); CodeBlock = null; TargetVariablesNaming = null; if (AllowGenerateMacroCode == false) return string.Empty; LibraryComposer.CheckProgressRequestStop(); var progressId = this.ReportStart( "Generating Macro Code For: " + BaseMacro.AccessName ); try { //Bind macro parameters to variables and constants as needed if (ActionSetMacroParametersBindings == null) DefaultActionSetMacroParametersBindings(MacroBinding); else ActionSetMacroParametersBindings(MacroBinding); if (IsMacroBindingReady == false) { this.ReportWarning("Macro Binding Not Ready", MacroBinding.ToString()); this.ReportFinish(progressId); return string.Empty; } this.ReportNormal("Macro Binding Ready", MacroBinding.ToString()); //Create the optimizd code block holding abstract computations based on the macro parameters //bindings. This step is typically the most time consuming because of many symbolic computations CodeBlock = MacroBinding.CreateOptimizedCodeBlock(); //Assign target variable names to low-level code block variables. The code block is generated //automatically in the call to the OptimizedCodeBlock member TargetVariablesNaming = new GMacTargetVariablesNaming(GMacLanguage, CodeBlock); if (ActionSetTargetVariablesNames == null) DefaultActionSetTargetVariablesNames(TargetVariablesNaming); else ActionSetTargetVariablesNames(TargetVariablesNaming); //Generate code before computations for comments, temp declarations, and the like var result = ActionBeforeGenerateComputations == null ? DefaultActionBeforeGenerateComputations(this) : ActionBeforeGenerateComputations(this); //Generate computations code if allowed by last action result if (result) GenerateProcessingCode(); //Generate code after computations for comments, temp destruction, and the like if (ActionAfterGenerateComputations == null) DefaultActionAfterGenerateComputations(this); else ActionAfterGenerateComputations(this); } catch (Exception e) { this.ReportError(e); } //Clean everything up and return final generated code ExpressionConverter.ActiveCodeBlock = null; //Un-parse the SyntaxList into the final code var codeText = CodeGenerator.GenerateCode(SyntaxList); this.ReportFinish(progressId, codeText); return codeText; } |
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.
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.
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
namespace geometry3d frame e3d (e1, e2, e3) euclidean structure e3d.ray ( origin : Multivector, direction : Multivector ) structure e3d.HitInfo ( Ray : ray, tParameter : scalar, HitPoint : Multivector, NormalVector : Multivector ) |
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:
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 28 29 30 31 32 33 34 35 36 37 |
hitInfo.Ray.origin.#E0# hitInfo.Ray.origin.#e1# hitInfo.Ray.origin.#e2# hitInfo.Ray.origin.#e1^e2# hitInfo.Ray.origin.#e3# hitInfo.Ray.origin.#e1^e3# hitInfo.Ray.origin.#e2^e3# hitInfo.Ray.origin.#e1^e2^e3# hitInfo.Ray.direction.#E0# hitInfo.Ray.direction.#e1# hitInfo.Ray.direction.#e2# hitInfo.Ray.direction.#e1^e2# hitInfo.Ray.direction.#e3# hitInfo.Ray.direction.#e1^e3# hitInfo.Ray.direction.#e2^e3# hitInfo.Ray.direction.#e1^e2^e3# hitInfo.tParameter hitInfo.HitPoint.#E0# hitInfo.HitPoint.#e1# hitInfo.HitPoint.#e2# hitInfo.HitPoint.#e1^e2# hitInfo.HitPoint.#e3# hitInfo.HitPoint.#e1^e3# hitInfo.HitPoint.#e2^e3# hitInfo.HitPoint.#e1^e2^e3# hitInfo.NormalVector.#E0# hitInfo.NormalVector.#e1# hitInfo.NormalVector.#e2# hitInfo.NormalVector.#e1^e2# hitInfo.NormalVector.#e3# hitInfo.NormalVector.#e1^e3# hitInfo.NormalVector.#e2^e3# hitInfo.NormalVector.#e1^e2^e3# |
In addition, we can access or use a partial components tree of the hitInfo parameter using any of the following expressions:
1 2 3 4 5 6 7 8 9 |
hitInfo.Ray hitInfo.Ray.origin hitInfo.Ray.direction hitInfo.HitPoint hitInfo.NormalVector |
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
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, VariableInputBindings, VariableInputBindings, 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.
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.
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.
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.
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.
2.3. Code Block Classes
Code Block Variables Interfaces and Classes
There are three types of code block variables:
- Input variables represented by the GMacCbInputVariable class.
- Temporary variables represented by the GMacCbTempVariable class.
- 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.
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.
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.
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.
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.
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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
using System.Collections.Generic; using System.Linq; using GMac.GMacAPI.CodeGen; using GMac.GMacAPI.Target; using GMac.GMacAST; using GMac.GMacAST.Symbols; using TextComposerLib.Logs.Progress; namespace GMacSamples.CodeGen.Multivectors { public sealed class MvLibrary : GMacCodeLibraryComposer { public override string Name { get { return "Multivectors Library"; } } public override string Description { get { return "Generates a library for performing general GA operations on multivectors using a different class per multivector type"; } } public MvLibrary(AstRoot ast) : base(ast, GMacLanguageServer.CSharp4()) { MacroGenDefaults = new GMacMacroCodeComposerDefaults(this); } private bool GeneratePreComputationsCode(GMacMacroCodeComposer macroCodeGen) { throw new NotImplementedException(); } private void GeneratePostComputationsCode(GMacMacroCodeComposer macroCodeGen) { throw new NotImplementedException(); } private void GenerateFrameCode(AstFrame frameInfo) { throw new NotImplementedException(); } public override GMacCodeLibraryComposer CreateEmptyGenerator() { return new MvLibrary(Root); } public override IEnumerable<AstSymbol> GetBaseSymbolsList() { return Root.Frames; } protected override string GetSymbolTargetName(AstSymbol symbol) { return symbol.Name; } protected override bool InitializeTemplates() { return true; } protected override void InitializeOtherComponents() { MacroGenDefaults.ActionBeforeGenerateComputations = GeneratePreComputationsCode; MacroGenDefaults.ActionAfterGenerateComputations = GeneratePostComputationsCode; } protected override bool VerifyReadyToGenerate() { return SelectedSymbols.All(s => s.IsValidFrame); } protected override void ComposeTextFiles() { var framesList = SelectedSymbols.Cast<AstFrame>(); foreach (var frame in framesList) GenerateFrameCode(frame); } protected override void FinalizeOtherComponents() { } } } |
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:
1 2 3 4 5 |
internal AstFrame CurrentFrame { get; private set; } internal string CurrentFrameName { get; private set; } internal Dictionary<int, MvClassData> MultivectorClassesData { get; private set; } |
And modify its constructor to be:
1 2 3 4 5 6 7 |
public MvLibrary(AstRoot ast) : base(ast, GMacTargetLanguage.CSharp4()) { MacroGenDefaults = new GMacMacroCodeGenDefaults(this); MultivectorClassesData = new Dictionary<int, MvClassData>(); } |
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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
using System.Collections.Generic; using GMac.GMacAPI.Binding; using GMac.GMacAST.Symbols; using GMac.GMacUtils; namespace GMacSamples.CodeGen.Multivectors { internal sealed class MvClassData { public int ClassId { get; private set; } public string ClassName { get; private set; } public AstFrame Frame { get; private set; } public GMacMultivectorBinding ClassBinding { get; private set; } public IEnumerable<int> ClassGrades { get { var grade = 0; var id = ClassId; while (id > 0) { if ((id & 1) == 1) yield return grade; grade++; id = id >> 1; } } } public IEnumerable<int> ClassBasisBladeIds { get { return Frame.BasisBladeIDsOfGrades(ClassGrades); } } public IEnumerable<AstFrameBasisBlade> ClassBasisBlades { get { return Frame.BasisBladesOfGrades(ClassGrades); } } public MvClassData(AstFrame frame, int id, string name) { ClassId = id; ClassName = name; Frame = frame; ClassBinding = GMacMultivectorBinding.Create(frame.FrameMultivector); if (id < 1) return; foreach (var grade in ClassGrades) ClassBinding.BindKVectorToVariables(grade); } } } |
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:
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 28 29 30 31 |
using GMac.GMacAPI.CodeGen; using GMac.GMacAST.Symbols; namespace GMacSamples.CodeGen.Multivectors { internal abstract class MvLibraryCodeFileGenerator : GMacCodeFileComposer { internal AstFrame CurrentFrame { get; private set; } internal string CurrentFrameName { get; private set; } internal MvLibrary MvLibraryGenerator { get { return (MvLibrary) LibraryComposer; } } internal MvLibraryCodeFileGenerator(MvLibrary libGen) : base(libGen) { CurrentFrame = libGen.CurrentFrame; CurrentFrameName = libGen.CurrentFrameName; } internal GMacMacroCodeComposer CreateMacroCodeGenerator(string macroName) { return new GMacMacroCodeComposer(LibraryComposer, CurrentFrame.Macro(macroName)); } } } |
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 28 29 30 31 32 33 34 35 36 37 38 39 |
using GMac.GMacAPI.CodeGen; using GMac.GMacAST.Symbols; namespace GMacSamples.CodeGen.Multivectors { internal abstract class MvLibraryMacroCodeFileGenerator : GMacMacroCodeFileComposer { internal AstFrame CurrentFrame { get; private set; } internal string CurrentFrameName { get; private set; } internal MvLibrary MvLibraryGenerator { get { return (MvLibrary) LibraryComposer; } } internal MvLibraryMacroCodeFileGenerator(MvLibrary libGen) : base(libGen) { CurrentFrame = libGen.CurrentFrame; CurrentFrameName = libGen.CurrentFrameName; } internal MvLibraryMacroCodeFileGenerator(MvLibrary libGen, string baseMacroName) : base(libGen, libGen.CurrentFrame.Macro(baseMacroName)) { CurrentFrame = libGen.CurrentFrame; CurrentFrameName = libGen.CurrentFrameName; } internal MvLibraryMacroCodeFileGenerator(MvLibrary libGen, AstMacro baseMacro) : base(libGen, baseMacro) { CurrentFrame = libGen.CurrentFrame; CurrentFrameName = libGen.CurrentFrameName; } } } |
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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
using System.Text; using TextComposerLib.Text.Linear; using TextComposerLib.Text.Parametric; namespace GMacSamples.CodeGen.Multivectors { internal sealed class BaseMvClassFileGenerator : MvLibraryCodeFileGenerator { #region Templates private const string ClassCodeTemplateText = @" namespace GMacModel.#frame# { public abstract class #frame#Multivector { #region Static Members public static #zero_class_name# Zero { get; private set; } public static #double# Epsilon { get; set; } static #frame#Multivector() { Epsilon = 1e-12; Zero = new #zero_class_name#(); } #endregion #declare_coefs# public int ClassId { get; protected set; } public abstract int ActiveClassId { get; } public abstract bool IsZero { get; } public abstract bool IsKVector { get; } public abstract bool IsBlade { get; } public abstract bool IsScalar { get; } public abstract bool IsVector { get; } public abstract bool IsPseudoVector { get; } public abstract bool IsPseudoScalar { get; } public abstract bool IsTerm { get; } public abstract bool IsEven { get; } public abstract bool IsOdd { get; } public abstract #double# Norm2 { get; } public abstract bool IsEqual(#frame#Multivector mv); public abstract #frame#Multivector Simplify(); public abstract #frame#Multivector OP(#frame#Multivector mv); public abstract #frame#Multivector GP(#frame#Multivector mv); public abstract #frame#Multivector LCP(#frame#Multivector mv); public abstract #frame#Multivector RCP(#frame#Multivector mv); public abstract #double# SP(#frame#Multivector mv); public abstract #frame#Multivector Add(#frame#Multivector mv); public abstract #frame#Multivector Subtract(#frame#Multivector mv); } } "; #endregion internal static void Generate(MvLibrary libGen) { var generator = new BaseMvClassFileGenerator(libGen); generator.Generate(); } private BaseMvClassFileGenerator(MvLibrary libGen) : base(libGen) { } private string GenerateDeclareCoefsText() { var s = new StringBuilder(); var template = new ParametricComposer("#", "#", "public abstract #double# #coef_name# { get; }"); template["double"] = GMacLanguage.ScalarTypeName; for (var id = 0; id < CurrentFrame.GaSpaceDimension; id++) { //var coefName = "Coef" + GaUtils.ID_To_BinaryString(CurrentFrame.VSpaceDimension, id); var coefName = "Coef" + id; s.AppendLine(template.GenerateText("coef_name", coefName)); } return s.ToString(); } public override void Generate() { var declareCoefsText = GenerateDeclareCoefsText(); var template = new ParametricComposer("#", "#", ClassCodeTemplateText); TextComposer.Append( template, "frame", CurrentFrameName, "double", GMacLanguage.ScalarTypeName, "zero_class_name", MvLibraryGenerator.MultivectorClassesData[0].ClassName, "declare_coefs", declareCoefsText ); FileComposer.FinalizeText(); } } } |
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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
namespace GMacModel.e3d { public abstract class e3dMultivector { #region Static Members public static e3dZero Zero { get; private set; } public static double Epsilon { get; set; } static e3dMultivector() { Epsilon = 1e-12; Zero = new e3dZero(); } #endregion public abstract double Coef0 { get; } public abstract double Coef1 { get; } public abstract double Coef2 { get; } public abstract double Coef3 { get; } public abstract double Coef4 { get; } public abstract double Coef5 { get; } public abstract double Coef6 { get; } public abstract double Coef7 { get; } public int ClassId { get; protected set; } public abstract double Norm2 { get; } public abstract bool IsZero { get; } public abstract bool IsEqual(e3dMultivector mv); public abstract e3dMultivector Simplify(); public abstract e3dMultivector OP(e3dMultivector mv); public abstract e3dMultivector GP(e3dMultivector mv); public abstract e3dMultivector LCP(e3dMultivector mv); public abstract e3dMultivector RCP(e3dMultivector mv); public abstract double SP(e3dMultivector mv); public abstract e3dMultivector Add(e3dMultivector mv); public abstract e3dMultivector Subtract(e3dMultivector mv); } } |
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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
using System; namespace GMacModel.e3d { public sealed partial class e3dMultivector5 : e3dMultivector { public double G0I0 { get; set; } public double G2I0 { get; set; } public double G2I1 { get; set; } public double G2I2 { get; set; } public override double Coef0 { get { return G0I0; } } public override double Coef1 { get { return 0.0D; } } public override double Coef2 { get { return 0.0D; } } public override double Coef3 { get { return G2I0; } } public override double Coef4 { get { return 0.0D; } } public override double Coef5 { get { return G2I1; } } public override double Coef6 { get { return G2I2; } } public override double Coef7 { get { return 0.0D; } } public e3dMultivector5() { ClassId = 5; } public override double Norm2 { get { //Bagin GMac Macro Code Generation, 2016-01-11T22:35:59.4064112+02:00 //Macro: geometry3d.e3d.Norm2 //Input Variables: 4 used, 0 not used, 4 total. //Temp Variables: 10 sub-expressions, 0 generated temps, 10 total. //Target Temp Variables: 2 total. //Output Variables: 1 total. //Computations: 1 average, 11 total. //Memory Reads: 1.27272727272727 average, 14 total. //Memory Writes: 11 total. // //Macro Binding Data: // result <=> <Variable> result // mv.#E0# <=> <Variable> G0I0 // mv.#e1^e2# <=> <Variable> G2I0 // mv.#e1^e3# <=> <Variable> G2I1 // mv.#e2^e3# <=> <Variable> G2I2 double tempVar0000; double tempVar0001; tempVar0000 = Math.Pow(G0I0, 2); tempVar0000 = (-1 * tempVar0000); tempVar0001 = Math.Pow(G2I0, 2); tempVar0001 = (-1 * tempVar0001); tempVar0000 = (tempVar0000 + tempVar0001); tempVar0001 = Math.Pow(G2I1, 2); tempVar0001 = (-1 * tempVar0001); tempVar0000 = (tempVar0000 + tempVar0001); tempVar0001 = Math.Pow(G2I2, 2); tempVar0001 = (-1 * tempVar0001); return (tempVar0000 + tempVar0001); //Finish GMac Macro Code Generation, 2016-01-11T22:35:59.4074113+02:00 } } public override bool IsZero { get { return !( G0I0 <= -Epsilon || G0I0 >= Epsilon || G2I0 <= -Epsilon || G2I0 >= Epsilon || G2I1 <= -Epsilon || G2I1 >= Epsilon || G2I2 <= -Epsilon || G2I2 >= Epsilon ); } } public override bool IsEqual(e3dMultivector mv) { switch (mv.ClassId) { case 0: return IsEqual((e3dZero)mv); case 1: return IsEqual((e3dScalar)mv); case 2: return IsEqual((e3dVector)mv); case 3: return IsEqual((e3dMultivector3)mv); case 4: return IsEqual((e3dPseudoVector)mv); case 5: return IsEqual((e3dMultivector5)mv); case 6: return IsEqual((e3dMultivector6)mv); case 7: return IsEqual((e3dMultivector7)mv); case 8: return IsEqual((e3dPseudoScalar)mv); case 9: return IsEqual((e3dMultivector9)mv); case 10: return IsEqual((e3dMultivector10)mv); case 11: return IsEqual((e3dMultivector11)mv); case 12: return IsEqual((e3dMultivector12)mv); case 13: return IsEqual((e3dMultivector13)mv); case 14: return IsEqual((e3dMultivector14)mv); case 15: return IsEqual((e3dFull)mv); default: throw new InvalidOperationException(); } } public override e3dMultivector Simplify() { throw new NotImplementedException(); } public override e3dMultivector OP(e3dMultivector mv) { switch (mv.ClassId) { case 0: return OP((e3dZero)mv); case 1: return OP((e3dScalar)mv); case 2: return OP((e3dVector)mv); case 3: return OP((e3dMultivector3)mv); case 4: return OP((e3dPseudoVector)mv); case 5: return OP((e3dMultivector5)mv); case 6: return OP((e3dMultivector6)mv); case 7: return OP((e3dMultivector7)mv); case 8: return OP((e3dPseudoScalar)mv); case 9: return OP((e3dMultivector9)mv); case 10: return OP((e3dMultivector10)mv); case 11: return OP((e3dMultivector11)mv); case 12: return OP((e3dMultivector12)mv); case 13: return OP((e3dMultivector13)mv); case 14: return OP((e3dMultivector14)mv); case 15: return OP((e3dFull)mv); default: throw new InvalidOperationException(); } } public override e3dMultivector GP(e3dMultivector mv) { switch (mv.ClassId) { case 0: return GP((e3dZero)mv); case 1: return GP((e3dScalar)mv); case 2: return GP((e3dVector)mv); case 3: return GP((e3dMultivector3)mv); case 4: return GP((e3dPseudoVector)mv); case 5: return GP((e3dMultivector5)mv); case 6: return GP((e3dMultivector6)mv); case 7: return GP((e3dMultivector7)mv); case 8: return GP((e3dPseudoScalar)mv); case 9: return GP((e3dMultivector9)mv); case 10: return GP((e3dMultivector10)mv); case 11: return GP((e3dMultivector11)mv); case 12: return GP((e3dMultivector12)mv); case 13: return GP((e3dMultivector13)mv); case 14: return GP((e3dMultivector14)mv); case 15: return GP((e3dFull)mv); default: throw new InvalidOperationException(); } } public override e3dMultivector LCP(e3dMultivector mv) { switch (mv.ClassId) { case 0: return LCP((e3dZero)mv); case 1: return LCP((e3dScalar)mv); case 2: return LCP((e3dVector)mv); case 3: return LCP((e3dMultivector3)mv); case 4: return LCP((e3dPseudoVector)mv); case 5: return LCP((e3dMultivector5)mv); case 6: return LCP((e3dMultivector6)mv); case 7: return LCP((e3dMultivector7)mv); case 8: return LCP((e3dPseudoScalar)mv); case 9: return LCP((e3dMultivector9)mv); case 10: return LCP((e3dMultivector10)mv); case 11: return LCP((e3dMultivector11)mv); case 12: return LCP((e3dMultivector12)mv); case 13: return LCP((e3dMultivector13)mv); case 14: return LCP((e3dMultivector14)mv); case 15: return LCP((e3dFull)mv); default: throw new InvalidOperationException(); } } public override e3dMultivector RCP(e3dMultivector mv) { switch (mv.ClassId) { case 0: return RCP((e3dZero)mv); case 1: return RCP((e3dScalar)mv); case 2: return RCP((e3dVector)mv); case 3: return RCP((e3dMultivector3)mv); case 4: return RCP((e3dPseudoVector)mv); case 5: return RCP((e3dMultivector5)mv); case 6: return RCP((e3dMultivector6)mv); case 7: return RCP((e3dMultivector7)mv); case 8: return RCP((e3dPseudoScalar)mv); case 9: return RCP((e3dMultivector9)mv); case 10: return RCP((e3dMultivector10)mv); case 11: return RCP((e3dMultivector11)mv); case 12: return RCP((e3dMultivector12)mv); case 13: return RCP((e3dMultivector13)mv); case 14: return RCP((e3dMultivector14)mv); case 15: return RCP((e3dFull)mv); default: throw new InvalidOperationException(); } } public override double SP(e3dMultivector mv) { switch (mv.ClassId) { case 0: return SP((e3dZero)mv); case 1: return SP((e3dScalar)mv); case 2: return SP((e3dVector)mv); case 3: return SP((e3dMultivector3)mv); case 4: return SP((e3dPseudoVector)mv); case 5: return SP((e3dMultivector5)mv); case 6: return SP((e3dMultivector6)mv); case 7: return SP((e3dMultivector7)mv); case 8: return SP((e3dPseudoScalar)mv); case 9: return SP((e3dMultivector9)mv); case 10: return SP((e3dMultivector10)mv); case 11: return SP((e3dMultivector11)mv); case 12: return SP((e3dMultivector12)mv); case 13: return SP((e3dMultivector13)mv); case 14: return SP((e3dMultivector14)mv); case 15: return SP((e3dFull)mv); default: throw new InvalidOperationException(); } } public override e3dMultivector Add(e3dMultivector mv) { switch (mv.ClassId) { case 0: return Add((e3dZero)mv); case 1: return Add((e3dScalar)mv); case 2: return Add((e3dVector)mv); case 3: return Add((e3dMultivector3)mv); case 4: return Add((e3dPseudoVector)mv); case 5: return Add((e3dMultivector5)mv); case 6: return Add((e3dMultivector6)mv); case 7: return Add((e3dMultivector7)mv); case 8: return Add((e3dPseudoScalar)mv); case 9: return Add((e3dMultivector9)mv); case 10: return Add((e3dMultivector10)mv); case 11: return Add((e3dMultivector11)mv); case 12: return Add((e3dMultivector12)mv); case 13: return Add((e3dMultivector13)mv); case 14: return Add((e3dMultivector14)mv); case 15: return Add((e3dFull)mv); default: throw new InvalidOperationException(); } } public override e3dMultivector Subtract(e3dMultivector mv) { switch (mv.ClassId) { case 0: return Subtract((e3dZero)mv); case 1: return Subtract((e3dScalar)mv); case 2: return Subtract((e3dVector)mv); case 3: return Subtract((e3dMultivector3)mv); case 4: return Subtract((e3dPseudoVector)mv); case 5: return Subtract((e3dMultivector5)mv); case 6: return Subtract((e3dMultivector6)mv); case 7: return Subtract((e3dMultivector7)mv); case 8: return Subtract((e3dPseudoScalar)mv); case 9: return Subtract((e3dMultivector9)mv); case 10: return Subtract((e3dMultivector10)mv); case 11: return Subtract((e3dMultivector11)mv); case 12: return Subtract((e3dMultivector12)mv); case 13: return Subtract((e3dMultivector13)mv); case 14: return Subtract((e3dMultivector14)mv); case 15: return Subtract((e3dFull)mv); default: throw new InvalidOperationException(); } } } } |
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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
using System; namespace GMacModel.e3d { public sealed partial class e3dMultivector5 : e3dMultivector { public e3dFull OP(e3dMultivector11 mv) { var result = new e3dFull(); //Bagin GMac Macro Code Generation, 2016-01-11T22:35:59.6944277+02:00 //Macro: geometry3d.e3d.OP //Input Variables: 9 used, 0 not used, 9 total. //Temp Variables: 6 sub-expressions, 0 generated temps, 6 total. //Target Temp Variables: 2 total. //Output Variables: 8 total. //Computations: 1.71428571428571 average, 24 total. //Memory Reads: 2 average, 28 total. //Memory Writes: 14 total. // //Macro Binding Data: // result.#E0# <=> <Variable> result.G0I0 // result.#e1# <=> <Variable> result.G1I0 // result.#e2# <=> <Variable> result.G1I1 // result.#e1^e2# <=> <Variable> result.G2I0 // result.#e3# <=> <Variable> result.G1I2 // result.#e1^e3# <=> <Variable> result.G2I1 // result.#e2^e3# <=> <Variable> result.G2I2 // result.#e1^e2^e3# <=> <Variable> result.G3I0 // mv1.#E0# <=> <Variable> G0I0 // mv1.#e1^e2# <=> <Variable> G2I0 // mv1.#e1^e3# <=> <Variable> G2I1 // mv1.#e2^e3# <=> <Variable> G2I2 // mv2.#E0# <=> <Variable> mv.G0I0 // mv2.#e1# <=> <Variable> mv.G1I0 // mv2.#e2# <=> <Variable> mv.G1I1 // mv2.#e3# <=> <Variable> mv.G1I2 // mv2.#e1^e2^e3# <=> <Variable> mv.G3I0 double tempVar0000; double tempVar0001; result.G0I0 = (-1 * G0I0 * mv.G0I0); result.G1I0 = (-1 * G0I0 * mv.G1I0); result.G1I1 = (-1 * G0I0 * mv.G1I1); result.G2I0 = (-1 * G2I0 * mv.G0I0); result.G1I2 = (-1 * G0I0 * mv.G1I2); result.G2I1 = (-1 * G2I1 * mv.G0I0); result.G2I2 = (-1 * G2I2 * mv.G0I0); tempVar0000 = (-1 * G2I2 * mv.G1I0); tempVar0001 = (G2I1 * mv.G1I1); tempVar0000 = (tempVar0000 + tempVar0001); tempVar0001 = (-1 * G2I0 * mv.G1I2); tempVar0000 = (tempVar0000 + tempVar0001); tempVar0001 = (-1 * G0I0 * mv.G3I0); result.G3I0 = (tempVar0000 + tempVar0001); //Finish GMac Macro Code Generation, 2016-01-11T22:35:59.6964278+02:00 return result; } public e3dFull GP(e3dMultivector11 mv) { var result = new e3dFull(); //Bagin GMac Macro Code Generation, 2016-01-11T22:35:59.7014281+02:00 //Macro: geometry3d.e3d.GP //Input Variables: 9 used, 0 not used, 9 total. //Temp Variables: 24 sub-expressions, 0 generated temps, 24 total. //Target Temp Variables: 2 total. //Output Variables: 8 total. //Computations: 1.4375 average, 46 total. //Memory Reads: 2 average, 64 total. //Memory Writes: 32 total. // //Macro Binding Data: // result.#E0# <=> <Variable> result.G0I0 // result.#e1# <=> <Variable> result.G1I0 // result.#e2# <=> <Variable> result.G1I1 // result.#e1^e2# <=> <Variable> result.G2I0 // result.#e3# <=> <Variable> result.G1I2 // result.#e1^e3# <=> <Variable> result.G2I1 // result.#e2^e3# <=> <Variable> result.G2I2 // result.#e1^e2^e3# <=> <Variable> result.G3I0 // mv1.#E0# <=> <Variable> G0I0 // mv1.#e1^e2# <=> <Variable> G2I0 // mv1.#e1^e3# <=> <Variable> G2I1 // mv1.#e2^e3# <=> <Variable> G2I2 // mv2.#E0# <=> <Variable> mv.G0I0 // mv2.#e1# <=> <Variable> mv.G1I0 // mv2.#e2# <=> <Variable> mv.G1I1 // mv2.#e3# <=> <Variable> mv.G1I2 // mv2.#e1^e2^e3# <=> <Variable> mv.G3I0 double tempVar0000; double tempVar0001; result.G0I0 = (-1 * G0I0 * mv.G0I0); result.G2I0 = (-1 * G2I0 * mv.G0I0); result.G2I1 = (-1 * G2I1 * mv.G0I0); result.G2I2 = (-1 * G2I2 * mv.G0I0); tempVar0000 = (-1 * G0I0 * mv.G1I0); tempVar0001 = (-1 * G2I0 * mv.G1I1); tempVar0000 = (tempVar0000 + tempVar0001); tempVar0001 = (-1 * G2I1 * mv.G1I2); tempVar0000 = (tempVar0000 + tempVar0001); tempVar0001 = (G2I2 * mv.G3I0); result.G1I0 = (tempVar0000 + tempVar0001); tempVar0000 = (G2I0 * mv.G1I0); tempVar0001 = (-1 * G0I0 * mv.G1I1); tempVar0000 = (tempVar0000 + tempVar0001); tempVar0001 = (-1 * G2I2 * mv.G1I2); tempVar0000 = (tempVar0000 + tempVar0001); tempVar0001 = (-1 * G2I1 * mv.G3I0); result.G1I1 = (tempVar0000 + tempVar0001); tempVar0000 = (G2I1 * mv.G1I0); tempVar0001 = (G2I2 * mv.G1I1); tempVar0000 = (tempVar0000 + tempVar0001); tempVar0001 = (-1 * G0I0 * mv.G1I2); tempVar0000 = (tempVar0000 + tempVar0001); tempVar0001 = (G2I0 * mv.G3I0); result.G1I2 = (tempVar0000 + tempVar0001); tempVar0000 = (-1 * G2I2 * mv.G1I0); tempVar0001 = (G2I1 * mv.G1I1); tempVar0000 = (tempVar0000 + tempVar0001); tempVar0001 = (-1 * G2I0 * mv.G1I2); tempVar0000 = (tempVar0000 + tempVar0001); tempVar0001 = (-1 * G0I0 * mv.G3I0); result.G3I0 = (tempVar0000 + tempVar0001); //Finish GMac Macro Code Generation, 2016-01-11T22:35:59.7044283+02:00 return result; } public e3dMultivector11 LCP(e3dMultivector11 mv) { var result = new e3dMultivector11(); //Bagin GMac Macro Code Generation, 2016-01-11T22:35:59.7084285+02:00 //Macro: geometry3d.e3d.LCP //Input Variables: 9 used, 0 not used, 9 total. //Temp Variables: 6 sub-expressions, 0 generated temps, 6 total. //Target Temp Variables: 2 total. //Output Variables: 8 total. //Computations: 1.21428571428571 average, 17 total. //Memory Reads: 1.57142857142857 average, 22 total. //Memory Writes: 14 total. // //Macro Binding Data: // result.#E0# <=> <Variable> result.G0I0 // result.#e1# <=> <Variable> result.G1I0 // result.#e2# <=> <Variable> result.G1I1 // result.#e1^e2# <=> <Variable> result.G2I0 // result.#e3# <=> <Variable> result.G1I2 // result.#e1^e3# <=> <Variable> result.G2I1 // result.#e2^e3# <=> <Variable> result.G2I2 // result.#e1^e2^e3# <=> <Variable> result.G3I0 // mv1.#E0# <=> <Variable> G0I0 // mv1.#e1^e2# <=> <Variable> G2I0 // mv1.#e1^e3# <=> <Variable> G2I1 // mv1.#e2^e3# <=> <Variable> G2I2 // mv2.#E0# <=> <Variable> mv.G0I0 // mv2.#e1# <=> <Variable> mv.G1I0 // mv2.#e2# <=> <Variable> mv.G1I1 // mv2.#e3# <=> <Variable> mv.G1I2 // mv2.#e1^e2^e3# <=> <Variable> mv.G3I0 double tempVar0000; double tempVar0001; result.G0I0 = (-1 * G0I0 * mv.G0I0); result.G3I0 = (-1 * G0I0 * mv.G3I0); tempVar0000 = (-1 * G0I0 * mv.G1I0); tempVar0001 = (G2I2 * mv.G3I0); result.G1I0 = (tempVar0000 + tempVar0001); tempVar0000 = (-1 * G0I0 * mv.G1I1); tempVar0001 = (-1 * G2I1 * mv.G3I0); result.G1I1 = (tempVar0000 + tempVar0001); tempVar0000 = (-1 * G0I0 * mv.G1I2); tempVar0001 = (G2I0 * mv.G3I0); result.G1I2 = (tempVar0000 + tempVar0001); //Finish GMac Macro Code Generation, 2016-01-11T22:35:59.7104286+02:00 return result; } public e3dMultivector7 RCP(e3dMultivector11 mv) { var result = new e3dMultivector7(); //Bagin GMac Macro Code Generation, 2016-01-11T22:35:59.7144288+02:00 //Macro: geometry3d.e3d.RCP //Input Variables: 8 used, 1 not used, 9 total. //Temp Variables: 6 sub-expressions, 0 generated temps, 6 total. //Target Temp Variables: 2 total. //Output Variables: 8 total. //Computations: 1.42857142857143 average, 20 total. //Memory Reads: 1.85714285714286 average, 26 total. //Memory Writes: 14 total. // //Macro Binding Data: // result.#E0# <=> <Variable> result.G0I0 // result.#e1# <=> <Variable> result.G1I0 // result.#e2# <=> <Variable> result.G1I1 // result.#e1^e2# <=> <Variable> result.G2I0 // result.#e3# <=> <Variable> result.G1I2 // result.#e1^e3# <=> <Variable> result.G2I1 // result.#e2^e3# <=> <Variable> result.G2I2 // result.#e1^e2^e3# <=> <Variable> result.G3I0 // mv1.#E0# <=> <Variable> G0I0 // mv1.#e1^e2# <=> <Variable> G2I0 // mv1.#e1^e3# <=> <Variable> G2I1 // mv1.#e2^e3# <=> <Variable> G2I2 // mv2.#E0# <=> <Variable> mv.G0I0 // mv2.#e1# <=> <Variable> mv.G1I0 // mv2.#e2# <=> <Variable> mv.G1I1 // mv2.#e3# <=> <Variable> mv.G1I2 // mv2.#e1^e2^e3# <=> <Variable> mv.G3I0 double tempVar0000; double tempVar0001; result.G0I0 = (-1 * G0I0 * mv.G0I0); result.G2I0 = (-1 * G2I0 * mv.G0I0); result.G2I1 = (-1 * G2I1 * mv.G0I0); result.G2I2 = (-1 * G2I2 * mv.G0I0); tempVar0000 = (-1 * G2I0 * mv.G1I1); tempVar0001 = (-1 * G2I1 * mv.G1I2); result.G1I0 = (tempVar0000 + tempVar0001); tempVar0000 = (G2I0 * mv.G1I0); tempVar0001 = (-1 * G2I2 * mv.G1I2); result.G1I1 = (tempVar0000 + tempVar0001); tempVar0000 = (G2I1 * mv.G1I0); tempVar0001 = (G2I2 * mv.G1I1); result.G1I2 = (tempVar0000 + tempVar0001); //Finish GMac Macro Code Generation, 2016-01-11T22:35:59.7164290+02:00 return result; } public double SP(e3dMultivector11 mv) { var result = 0.0D; //Bagin GMac Macro Code Generation, 2016-01-11T22:35:59.7174290+02:00 //Macro: geometry3d.e3d.SP //Input Variables: 2 used, 7 not used, 9 total. //Temp Variables: 0 sub-expressions, 0 generated temps, 0 total. //Output Variables: 1 total. //Computations: 2 average, 2 total. //Memory Reads: 2 average, 2 total. //Memory Writes: 1 total. // //Macro Binding Data: // result <=> <Variable> result // mv1.#E0# <=> <Variable> G0I0 // mv1.#e1^e2# <=> <Variable> G2I0 // mv1.#e1^e3# <=> <Variable> G2I1 // mv1.#e2^e3# <=> <Variable> G2I2 // mv2.#E0# <=> <Variable> mv.G0I0 // mv2.#e1# <=> <Variable> mv.G1I0 // mv2.#e2# <=> <Variable> mv.G1I1 // mv2.#e3# <=> <Variable> mv.G1I2 // mv2.#e1^e2^e3# <=> <Variable> mv.G3I0 result = (-1 * G0I0 * mv.G0I0); //Finish GMac Macro Code Generation, 2016-01-11T22:35:59.7184291+02:00 return result; } public e3dFull Add(e3dMultivector11 mv) { return new e3dFull() { G2I0 = G2I0, G2I1 = G2I1, G2I2 = G2I2, G1I0 = mv.G1I0, G1I1 = mv.G1I1, G1I2 = mv.G1I2, G3I0 = mv.G3I0, G0I0 = G0I0 + mv.G0I0 }; } public e3dFull Subtract(e3dMultivector11 mv) { return new e3dFull() { G2I0 = G2I0, G2I1 = G2I1, G2I2 = G2I2, G1I0 = -mv.G1I0, G1I1 = -mv.G1I1, G1I2 = -mv.G1I2, G3I0 = -mv.G3I0, G0I0 = G0I0 - mv.G0I0 }; } public bool IsEqual(e3dMultivector11 mv) { return !( G2I0 <= -Epsilon || G2I0 >= Epsilon || G2I1 <= -Epsilon || G2I1 >= Epsilon || G2I2 <= -Epsilon || G2I2 >= Epsilon || mv.G1I0 <= -Epsilon || mv.G1I0 >= Epsilon || mv.G1I1 <= -Epsilon || mv.G1I1 >= Epsilon || mv.G1I2 <= -Epsilon || mv.G1I2 >= Epsilon || mv.G3I0 <= -Epsilon || mv.G3I0 >= Epsilon || (G0I0 - mv.G0I0) <= -Epsilon || (G0I0 - mv.G0I0) >= Epsilon ); } } } |
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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
using System; using GMac.GMacCompiler.Semantic.ASTConstants; using TextComposerLib.Text.Linear; using TextComposerLib.Text.Parametric; using TextComposerLib.Text.Structured; namespace GMacSamples.CodeGen.Multivectors { internal sealed class DerivedMvClassFileGenerator : MvLibraryCodeFileGenerator { #region Templates private static readonly ParametricComposer DeclareCoefsTemplate = new ParametricComposer("#", "#", @"public #double# #coef# { get; set; }"); private static readonly ParametricComposer AbstractCoefsTemplate = new ParametricComposer("#", "#", @"public override #double# #coef# { get { return #value#; } }"); private static readonly ParametricComposer AbstractMethodCaseTemplate = new ParametricComposer("#", "#", @"case #id#: return #op_name#((#class_name#)mv);"); private static readonly ParametricComposer MainCodeTemplate = new ParametricComposer("#", "#", @" public #mv_class_name#() { ClassId = #mv_class_id#; } public override int ActiveClassId { get { #activeclassid_code# } } public override #double# Norm2 { get { #norm2_code# } } public override bool IsZero { get { #iszero_code# } } public override bool IsKVector { get { #iskvector_code# } } public override bool IsBlade { get { #isblade_code# } } public override bool IsScalar { get { #isscalar_code# } } public override bool IsVector { get { #isvector_code# } } public override bool IsPseudoVector { get { #ispseudovector_code# } } public override bool IsPseudoScalar { get { #ispseudoscalar_code# } } public override bool IsTerm { get { #isterm_code# } } public override bool IsEven { get { #iseven_code# } } public override bool IsOdd { get { #isodd_code# } } public override bool IsEqual(#frame#Multivector mv) { switch (mv.ClassId) { #isequal_cases# default: throw new InvalidOperationException(); } } public override #frame#Multivector Simplify() { #simplify_code# } public override #frame#Multivector OP(#frame#Multivector mv) { switch (mv.ClassId) { #op_cases# default: throw new InvalidOperationException(); } } public override #frame#Multivector GP(#frame#Multivector mv) { switch (mv.ClassId) { #gp_cases# default: throw new InvalidOperationException(); } } public override #frame#Multivector LCP(#frame#Multivector mv) { switch (mv.ClassId) { #lcp_cases# default: throw new InvalidOperationException(); } } public override #frame#Multivector RCP(#frame#Multivector mv) { switch (mv.ClassId) { #rcp_cases# default: throw new InvalidOperationException(); } } public override #double# SP(#frame#Multivector mv) { switch (mv.ClassId) { #sp_cases# default: throw new InvalidOperationException(); } } public override #frame#Multivector Add(#frame#Multivector mv) { switch (mv.ClassId) { #add_cases# default: throw new InvalidOperationException(); } } public override #frame#Multivector Subtract(#frame#Multivector mv) { switch (mv.ClassId) { #subtract_cases# default: throw new InvalidOperationException(); } } "); #endregion internal static void Generate(MvLibrary libGen, MvClassData classData) { var generator = new DerivedMvClassFileGenerator(libGen, classData); generator.Generate(); } internal MvClassData ClassData { get; private set; } private DerivedMvClassFileGenerator(MvLibrary libGen, MvClassData classData) : base(libGen) { ClassData = classData; } private string GenerateDeclareCoefsText() { var s = new ListComposer(Environment.NewLine); DeclareCoefsTemplate["double"] = GMacLanguage.ScalarTypeName; foreach (var basisBlade in ClassData.ClassBasisBlades) s.Add(DeclareCoefsTemplate, "coef", basisBlade.GradeIndexName); return s.Add().ToString(); } private string GenerateAbstractCoefsText() { var s = new ListComposer(Environment.NewLine); AbstractCoefsTemplate["double"] = GMacLanguage.ScalarTypeName; foreach (var basisBlade in ClassData.Frame.BasisBlades()) { var id = basisBlade.BasisBladeId; var value = ClassData.ClassBinding.ContainsVariable(id) ? basisBlade.GradeIndexName : "0.0D"; s.Add(AbstractCoefsTemplate, "coef", ("Coef" + id), "value", value); } return s.Add().ToString(); } private string GenerateAbstractMethodCases(string opName) { var composer = new ListComposer(Environment.NewLine); AbstractMethodCaseTemplate["op_name"] = opName; foreach (var classData in MvLibraryGenerator.MultivectorClassesData.Values) composer.Add( AbstractMethodCaseTemplate, "id", classData.ClassId, "class_name", classData.ClassName ); return composer.ToString(); } private string GenerateIsZeroCode() { if (ClassData.ClassId == 0) return "return true;"; var composer = new ListComposer(" || " + Environment.NewLine) { FinalPrefix = "return !(" + Environment.NewLine, FinalSuffix = Environment.NewLine + ");", ActiveItemPrefix = " " }; foreach (var basisBlade in ClassData.ClassBasisBlades) { composer.Add( String.Format("{0} <= -Epsilon || {0} >= Epsilon", basisBlade.GradeIndexName) ); } return composer.ToString(); } private string GenerateNorm2Code() { if (ClassData.ClassId == 0) return "return " + GMacLanguage.ScalarZero + ";"; //var macroGen = new GMacMacroCodeGenerator(LibraryComposer, CurrentFrame.Macro(DefaultMacro.MetricUnary.NormSquared)); var macroGen = CreateMacroCodeGenerator(DefaultMacro.MetricUnary.NormSquared); macroGen.ActionSetMacroParametersBindings = macroBinding => { macroBinding.BindToVariables(macroBinding.BaseMacro.OutputParameterValueAccess); macroBinding.BindMultivectorPartToVariables("mv", ClassData.ClassBasisBladeIds); }; macroGen.ActionSetTargetVariablesNames = targetNaming => { //This line is actually not needed because we will override the macro output assignment code //using the ActionBeforeGenerateSingleComputation member delegate targetNaming.SetScalarParameter(targetNaming.BaseMacro.OutputParameterValueAccess, "result"); targetNaming.SetMultivectorParameters("mv", b => b.GradeIndexName); //targetNaming.SetTempVariables(index => "tempVar" + index.ToString("X4") + ""); MvLibraryGenerator.SetTargetTempVariablesNames(targetNaming); }; macroGen.ActionBeforeGenerateSingleComputation = (composer, codeInfo) => { if (!codeInfo.ComputedVariable.IsOutput) return; composer .AddEmptyLine() .Add(SyntaxFactory.ReturnValue(codeInfo.RhsExpressionCode)) .AddEmptyLine(); codeInfo.EnableCodeGeneration = false; }; return macroGen.Generate(); } private string GenerateClassCode() { var composer = new LinearComposer(); composer.AppendLineAtNewLine(GenerateDeclareCoefsText()); composer.AppendLineAtNewLine(GenerateAbstractCoefsText()); composer.AppendAtNewLine( MainCodeTemplate, "mv_class_name", ClassData.ClassName, "mv_class_id", ClassData.ClassId, "frame", CurrentFrameName, "double", GMacLanguage.ScalarTypeName, "iszero_code", GenerateIsZeroCode(), "norm2_code", GenerateNorm2Code(), "isequal_cases", GenerateAbstractMethodCases("IsEqual"), "op_cases", GenerateAbstractMethodCases("OP"), "gp_cases", GenerateAbstractMethodCases("GP"), "lcp_cases", GenerateAbstractMethodCases("LCP"), "rcp_cases", GenerateAbstractMethodCases("RCP"), "sp_cases", GenerateAbstractMethodCases("SP"), "add_cases", GenerateAbstractMethodCases("Add"), "subtract_cases", GenerateAbstractMethodCases("Subtract") ); return composer.ToString(); } public override void Generate() { var classCodeText = GenerateClassCode(); TextComposer.Append( Templates["mv_class_file"], "frame", CurrentFrameName, "mv_class_name", ClassData.ClassName, "mv_class_code", classCodeText ); FileComposer.FinalizeText(); } } } |
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:
1 2 3 4 |
macro e3d.Norm2(mv : Multivector) : scalar begin return norm2(mv) end |
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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
private string GenerateNorm2Code() { if (ClassData.ClassId == 0) return "return " + GMacLanguage.ScalarZero + ";"; //var macroGen = new GMacMacroCodeGenerator(LibraryComposer, CurrentFrame.Macro(DefaultMacro.MetricUnary.NormSquared)); var macroGen = CreateMacroCodeGenerator(DefaultMacro.MetricUnary.NormSquared); macroGen.ActionSetMacroParametersBindings = macroBinding => { macroBinding.BindToVariables(macroBinding.BaseMacro.OutputParameterValueAccess); macroBinding.BindMultivectorPartToVariables("mv", ClassData.ClassBasisBladeIds); }; macroGen.ActionSetTargetVariablesNames = targetNaming => { //This line is actually not needed because we will override the macro output assignment code //using the ActionBeforeGenerateSingleComputation member delegate targetNaming.SetScalarParameter(targetNaming.BaseMacro.OutputParameterValueAccess, "result"); targetNaming.SetMultivectorParameters("mv", b => b.GradeIndexName); //targetNaming.SetTempVariables(index => "tempVar" + index.ToString("X4") + ""); MvLibraryGenerator.SetTargetTempVariablesNames(targetNaming); }; macroGen.ActionBeforeGenerateSingleComputation = (composer, codeInfo) => { if (!codeInfo.ComputedVariable.IsOutput) return; composer .AddEmptyLine() .Add(SyntaxFactory.ReturnValue(codeInfo.RhsExpressionCode)) .AddEmptyLine(); codeInfo.EnableCodeGeneration = false; }; return macroGen.Generate(); } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 |
double tempVar0000; double tempVar0001; tempVar0000 = Math.Pow(G1I0, 2); tempVar0000 = (-1 * tempVar0000); tempVar0001 = Math.Pow(G1I1, 2); tempVar0001 = (-1 * tempVar0001); tempVar0000 = (tempVar0000 + tempVar0001); tempVar0001 = Math.Pow(G1I2, 2); tempVar0001 = (-1 * tempVar0001); return (tempVar0000 + tempVar0001); |
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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 |
using System; using System.Linq; using GMac.GMacAPI.Binding; using GMac.GMacAPI.CodeGen; using GMac.GMacAPI.Target; using GMac.GMacCompiler.Semantic.ASTConstants; using GMac.GMacUtils; using TextComposerLib.Text.Linear; using TextComposerLib.Text.Parametric; using TextComposerLib.Text.Structured; namespace GMacSamples.CodeGen.Multivectors { internal sealed class DerivedMvClassCalcFileGenerator : MvLibraryMacroCodeFileGenerator { #region Templates private const string BilinearTemplateText = @" public #result_type# #op_name#(#mv_class_name# mv) { #code# } "; #endregion internal static void Generate(MvLibrary libGen, MvClassData derivedClassData, MvClassData calcClassData) { var generator = new DerivedMvClassCalcFileGenerator(libGen, derivedClassData, calcClassData); generator.Generate(); } internal MvClassData ClassData { get; private set; } internal MvClassData CalcClassData { get; private set; } private DerivedMvClassCalcFileGenerator(MvLibrary libGen, MvClassData classData, MvClassData calcClassData) : base(libGen) { ClassData = classData; CalcClassData = calcClassData; } protected override void InitializeGenerator(GMacMacroCodeComposer macroCodeGen) { macroCodeGen.ActionBeforeGenerateSingleComputation = (composer, codeInfo) => codeInfo.EnableCodeGeneration = codeInfo.ComputedVariable.IsTemp || codeInfo.ComputedVariable.IsNonZero; } protected override void SetMacroParametersBindings(GMacMacroBinding macroBinding) { macroBinding.BindToVariables("result"); macroBinding.BindToTreePattern("mv1", ClassData.ClassBinding); macroBinding.BindToTreePattern("mv2", CalcClassData.ClassBinding); } protected override void SetTargetVariablesNames(GMacTargetVariablesNaming targetNaming) { //Set names of output parameter components var outputParam = targetNaming.BaseMacro.OutputParameterValueAccess; if (outputParam.GMacType.IsValidMultivectorType) targetNaming.SetMultivectorParameters(outputParam, b => "result." + b.GradeIndexName); else targetNaming.SetScalarParameter(outputParam, "result"); //Set names for input parameters components targetNaming.SetMultivectorParameters("mv1", b => b.GradeIndexName); targetNaming.SetMultivectorParameters("mv2", b => "mv." + b.GradeIndexName); //Set names for temp variables MvLibraryGenerator.SetTargetTempVariablesNames(targetNaming); } private string GenerateProductMethodCode(string macroName, out string resultClassName) { SetBaseMacro(CurrentFrame.Macro(macroName)); var computationsText = GenerateComputationsCode(); //The result is zero if (MacroCodeGenerator.CodeBlock.NonZeroOutputVariables.Any() == false) { //The result is the zero multivector if (BaseMacro.OutputType.IsValidMultivectorType) { resultClassName = MvLibraryGenerator.MultivectorClassesData[0].ClassName; return "return Zero;"; } //The result is the scalar zero resultClassName = GMacLanguage.ScalarTypeName; return "return " + GMacLanguage.ScalarZero + ";"; } //The result is not zero string resultDeclarationText; if (BaseMacro.OutputType.IsValidMultivectorType) { var grades = MacroCodeGenerator .CodeBlock .NonZeroOutputVariables .Select(v => v.ValueAccess.GetBasisBlade().Grade) .Distinct(); var id = grades.Sum(grade => 1 << grade); resultClassName = MvLibraryGenerator.MultivectorClassesData[id].ClassName; resultDeclarationText = "var result = new " + resultClassName + "();"; } else { resultClassName = GMacLanguage.ScalarTypeName; resultDeclarationText = "var result = " + GMacLanguage.ScalarZero + ";"; } var composer = new LinearComposer(); composer .AppendLineAtNewLine(resultDeclarationText) .AppendLine() .AppendLineAtNewLine(computationsText) .AppendAtNewLine("return result;"); return composer.ToString(); } private string GenerateProductMethod(string macroName) { string resultClassName; var codeText = GenerateProductMethodCode(macroName, out resultClassName); var template = new ParametricComposer("#", "#", BilinearTemplateText); var composer = new LinearComposer(); composer.Append( template, "result_type", resultClassName, "op_name", BaseMacro.Name, "mv_class_name", CalcClassData.ClassName, "code", codeText ); return composer.ToString(); } private string GenerateIsEqualMethodCode() { //The two classes are zero multivector classes if (ClassData.ClassId == 0 && CalcClassData.ClassId == 0) return "return true;"; var idCommon = ClassData.ClassId & CalcClassData.ClassId; var idDiff1 = ClassData.ClassId & ~CalcClassData.ClassId; var idDiff2 = ~ClassData.ClassId & CalcClassData.ClassId; var composer = new ListComposer(" || " + Environment.NewLine) { FinalPrefix = "return !(" + Environment.NewLine, FinalSuffix = Environment.NewLine + ");", ActiveItemPrefix = " " }; var basisBlades = ClassData.Frame.BasisBladesOfGrades(idDiff1.PatternToPositions()); foreach (var basisBlade in basisBlades) composer.Add( String.Format("{0} <= -Epsilon || {0} >= Epsilon", basisBlade.GradeIndexName) ); basisBlades = ClassData.Frame.BasisBladesOfGrades(idDiff2.PatternToPositions()); foreach (var basisBlade in basisBlades) composer.Add( String.Format("{0} <= -Epsilon || {0} >= Epsilon", "mv." + basisBlade.GradeIndexName) ); basisBlades = ClassData.Frame.BasisBladesOfGrades(idCommon.PatternToPositions()); foreach (var basisBlade in basisBlades) { var term = "(" + basisBlade.GradeIndexName + " - mv." + basisBlade.GradeIndexName + ")"; composer.Add( String.Format("{0} <= -Epsilon || {0} >= Epsilon", term) ); } return composer.ToString(); } private string GenerateIsEqualMethod() { var codeText = GenerateIsEqualMethodCode(); var template = new ParametricComposer("#", "#", BilinearTemplateText); var composer = new LinearComposer(); composer.Append( template, "result_type", "bool", "op_name", "IsEqual", "mv_class_name", CalcClassData.ClassName, "code", codeText ); return composer.ToString(); } private string GenerateAddMethodCode(MvClassData resultClassData) { //The two classes are zero multivector classes if (resultClassData.ClassId == 0) return "return Zero;"; var idCommon = ClassData.ClassId & CalcClassData.ClassId; var idDiff1 = ClassData.ClassId & ~CalcClassData.ClassId; var idDiff2 = ~ClassData.ClassId & CalcClassData.ClassId; var composer = new ListComposer("," + Environment.NewLine) { FinalPrefix = "return new " + resultClassData.ClassName + "()" + Environment.NewLine + "{" + Environment.NewLine, FinalSuffix = Environment.NewLine + "};", ActiveItemPrefix = " " }; var basisBlades = ClassData.Frame.BasisBladesOfGrades(idDiff1.PatternToPositions()); foreach (var basisBlade in basisBlades) composer.Add( String.Format("{0} = {1}", basisBlade.GradeIndexName, basisBlade.GradeIndexName) ); basisBlades = ClassData.Frame.BasisBladesOfGrades(idDiff2.PatternToPositions()); foreach (var basisBlade in basisBlades) composer.Add( String.Format("{0} = {1}", basisBlade.GradeIndexName, "mv." + basisBlade.GradeIndexName) ); basisBlades = ClassData.Frame.BasisBladesOfGrades(idCommon.PatternToPositions()); foreach (var basisBlade in basisBlades) { var term = basisBlade.GradeIndexName + " + mv." + basisBlade.GradeIndexName; composer.Add( String.Format("{0} = {1}", basisBlade.GradeIndexName, term) ); } return composer.ToString(); } private string GenerateAddMethod() { var resultId = ClassData.ClassId | CalcClassData.ClassId; var resultClassData = MvLibraryGenerator.MultivectorClassesData[resultId]; var codeText = GenerateAddMethodCode(resultClassData); var template = new ParametricComposer("#", "#", BilinearTemplateText); var composer = new LinearComposer(); composer.Append( template, "result_type", resultClassData.ClassName, "op_name", "Add", "mv_class_name", CalcClassData.ClassName, "code", codeText ); return composer.ToString(); } private string GenerateSubtractMethodCode(MvClassData resultClassData) { //The two classes are zero multivector classes if (resultClassData.ClassId == 0) return "return Zero;"; var idCommon = ClassData.ClassId & CalcClassData.ClassId; var idDiff1 = ClassData.ClassId & ~CalcClassData.ClassId; var idDiff2 = ~ClassData.ClassId & CalcClassData.ClassId; var composer = new ListComposer("," + Environment.NewLine) { FinalPrefix = "return new " + resultClassData.ClassName + "()" + Environment.NewLine + "{" + Environment.NewLine, FinalSuffix = Environment.NewLine + "};", ActiveItemPrefix = " " }; var basisBlades = ClassData.Frame.BasisBladesOfGrades(idDiff1.PatternToPositions()); foreach (var basisBlade in basisBlades) composer.Add( String.Format("{0} = {1}", basisBlade.GradeIndexName, basisBlade.GradeIndexName) ); basisBlades = ClassData.Frame.BasisBladesOfGrades(idDiff2.PatternToPositions()); foreach (var basisBlade in basisBlades) composer.Add( String.Format("{0} = {1}", basisBlade.GradeIndexName, "-mv." + basisBlade.GradeIndexName) ); basisBlades = ClassData.Frame.BasisBladesOfGrades(idCommon.PatternToPositions()); foreach (var basisBlade in basisBlades) { var term = basisBlade.GradeIndexName + " - mv." + basisBlade.GradeIndexName; composer.Add( String.Format("{0} = {1}", basisBlade.GradeIndexName, term) ); } return composer.ToString(); } private string GenerateSubtractMethod() { var resultId = ClassData.ClassId | CalcClassData.ClassId; var resultClassData = MvLibraryGenerator.MultivectorClassesData[resultId]; var codeText = GenerateSubtractMethodCode(resultClassData); var template = new ParametricComposer("#", "#", BilinearTemplateText); var composer = new LinearComposer(); composer.Append( template, "result_type", resultClassData.ClassName, "op_name", "Subtract", "mv_class_name", CalcClassData.ClassName, "code", codeText ); return composer.ToString(); } private string GenerateClassCode() { var macroNames = new[] { DefaultMacro.EuclideanBinary.OuterProduct, DefaultMacro.MetricBinary.GeometricProduct, DefaultMacro.MetricBinary.LeftContractionProduct, DefaultMacro.MetricBinary.RightContractionProduct, DefaultMacro.MetricBinary.ScalarProduct }; var composer = new LinearComposer(); if (MvLibraryGenerator.MacroGenDefaults.AllowGenerateMacroCode) { foreach (var macroName in macroNames) composer.AppendAtNewLine( GenerateProductMethod(macroName) ); } composer.AppendAtNewLine( GenerateAddMethod() ); composer.AppendAtNewLine( GenerateSubtractMethod() ); composer.AppendAtNewLine( GenerateIsEqualMethod() ); return composer.ToString(); } public override void Generate() { var classCodeText = MvLibraryGenerator.MacroGenDefaults.AllowGenerateMacroCode ? GenerateClassCode() : ""; TextComposer.Append( Templates["mv_class_file"], "frame", CurrentFrameName, "mv_class_name", ClassData.ClassName, "mv_class_code", classCodeText ); FileComposer.FinalizeText(); } } } |
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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
protected override void InitializeGenerator(GMacMacroCodeComposer macroCodeGen) { macroCodeGen.ActionBeforeGenerateSingleComputation = (composer, codeInfo) => codeInfo.EnableCodeGeneration = codeInfo.ComputedVariable.IsTemp || codeInfo.ComputedVariable.IsNonZero; } protected override void SetMacroParametersBindings(GMacMacroBinding macroBinding) { macroBinding.BindToVariables("result"); macroBinding.BindToTreePattern("mv1", ClassData.ClassBinding); macroBinding.BindToTreePattern("mv2", CalcClassData.ClassBinding); } protected override void SetTargetVariablesNames(GMacTargetVariablesNaming targetNaming) { //Set names of output parameter components var outputParam = targetNaming.BaseMacro.OutputParameterValueAccess; if (outputParam.GMacType.IsValidMultivectorType) targetNaming.SetMultivectorParameters(outputParam, b => "result." + b.GradeIndexName); else targetNaming.SetScalarParameter(outputParam, "result"); //Set names for input parameters components targetNaming.SetMultivectorParameters("mv1", b => b.GradeIndexName); targetNaming.SetMultivectorParameters("mv2", b => "mv." + b.GradeIndexName); //Set names for temp variables MvLibraryGenerator.SetTargetTempVariablesNames(targetNaming); } private string GenerateProductMethodCode(string macroName, out string resultClassName) { SetBaseMacro(CurrentFrame.Macro(macroName)); var computationsText = GenerateComputationsCode(); //The result is zero if (MacroCodeGenerator.CodeBlock.NonZeroOutputVariables.Any() == false) { //The result is the zero multivector if (BaseMacro.OutputType.IsValidMultivectorType) { resultClassName = MvLibraryGenerator.MultivectorClassesData[0].ClassName; return "return Zero;"; } //The result is the scalar zero resultClassName = GMacLanguage.ScalarTypeName; return "return " + GMacLanguage.ScalarZero + ";"; } //The result is not zero string resultDeclarationText; if (BaseMacro.OutputType.IsValidMultivectorType) { var grades = MacroCodeGenerator .CodeBlock .NonZeroOutputVariables .Select(v => v.ValueAccess.GetBasisBlade().Grade) .Distinct(); var id = grades.Sum(grade => 1 << grade); resultClassName = MvLibraryGenerator.MultivectorClassesData[id].ClassName; resultDeclarationText = "var result = new " + resultClassName + "();"; } else { resultClassName = GMacLanguage.ScalarTypeName; resultDeclarationText = "var result = " + GMacLanguage.ScalarZero + ";"; } var composer = new LinearComposer(); composer .AppendLineAtNewLine(resultDeclarationText) .AppendLine() .AppendLineAtNewLine(computationsText) .AppendAtNewLine("return result;"); return composer.ToString(); } |
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:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
using System.Collections.Generic; using System.Linq; using GMac.GMacAPI.CodeGen; using GMac.GMacAPI.Target; using GMac.GMacAST; using GMac.GMacAST.Symbols; using TextComposerLib.Logs.Progress; namespace GMacSamples.CodeGen.Multivectors { public sealed class MvLibrary : GMacCodeLibraryComposer { #region Templates internal const string MultivectorTemplates = @" delimiters # # begin mv_class_file using System; namespace GMacModel.#frame# { public sealed partial class #mv_class_name# : #frame#Multivector { #mv_class_code# } } end mv_class_file "; #endregion internal int MaxTargetLocalVars { get { return 64; } } internal AstFrame CurrentFrame { get; private set; } internal string CurrentFrameName { get; private set; } internal Dictionary<int, MvClassData> MultivectorClassesData { get; private set; } public override string Name { get { return "Multivectors Library"; } } public override string Description { get { return "Generates a library for performing general GA operations on multivectors using a different class per multivector type"; } } public MvLibrary(AstRoot ast) : base(ast, GMacLanguageServer.CSharp4()) { MacroGenDefaults = new GMacMacroCodeComposerDefaults(this); MultivectorClassesData = new Dictionary<int, MvClassData>(); } internal void SetTargetTempVariablesNames(GMacTargetVariablesNaming targetNaming) { //Temp variables target naming if (targetNaming.CodeBlock.TargetTempVarsCount > MaxTargetLocalVars) { //Name as array items targetNaming.SetTempVariables((int index) => "tempArray[" + index + "]"); } else { //Name as set of local variables targetNaming.SetTempVariables(index => "tempVar" + index.ToString("X4") + ""); } } private bool GeneratePreComputationsCode(GMacMacroCodeComposer macroCodeGen) { //Generate comments GMacMacroCodeComposer.DefaultGenerateCommentsBeforeComputations(macroCodeGen); //Temp variables declaration if (macroCodeGen.CodeBlock.TargetTempVarsCount > MaxTargetLocalVars) { //Add array declaration code macroCodeGen.SyntaxList.Add( macroCodeGen.SyntaxFactory.DeclareLocalArray( GMacLanguage.ScalarTypeName, "tempArray", macroCodeGen.CodeBlock.TargetTempVarsCount.ToString() ) ); macroCodeGen.SyntaxList.AddEmptyLine(); } else { var tempVarNames = macroCodeGen.CodeBlock .TempVariables .Select(item => item.TargetVariableName) .Distinct(); //Add temp variables declaration code foreach (var tempVarName in tempVarNames) macroCodeGen.SyntaxList.Add( macroCodeGen.SyntaxFactory.DeclareLocalVariable(GMacLanguage.ScalarTypeName, tempVarName) ); macroCodeGen.SyntaxList.AddEmptyLine(); } return true; } private void GeneratePostComputationsCode(GMacMacroCodeComposer macroCodeGen) { //Generate comments GMacMacroCodeComposer.DefaultGenerateCommentsAfterComputations(macroCodeGen); } private void InitializeMultivectorClassesData() { MultivectorClassesData.Clear(); var maxId = (1 << (CurrentFrame.VSpaceDimension + 1)) - 1; //Create the default names for multivector classes var classNames = Enumerable.Range(0, maxId + 1).Select(id => "Multivector" + id).ToArray(); //Override some of the default names with special names for multivector classes classNames[0] = "Zero"; classNames[1] = "Scalar"; classNames[2] = "Vector"; if (CurrentFrame.VSpaceDimension >= 4) { for (var grade = 2; grade <= CurrentFrame.VSpaceDimension - 2; grade++) classNames[1 << grade] = "KVector" + grade; } if (CurrentFrame.VSpaceDimension >= 3) classNames[1 << (CurrentFrame.VSpaceDimension - 1)] = "PseudoVector"; classNames[1 << CurrentFrame.VSpaceDimension] = "PseudoScalar"; classNames[maxId] = "Full"; //Create classes data for (var id = 0; id <= maxId; id++) MultivectorClassesData.Add( id, new MvClassData( CurrentFrame, id, CurrentFrameName + classNames[id] ) ); } private void GenerateBaseMvClassFile() { CodeFilesComposer.InitalizeFile(CurrentFrameName + "Multivector.cs"); BaseMvClassFileGenerator.Generate(this); CodeFilesComposer.UnselectActiveFile(); } private void GenerateDerivedMvClassFiles(MvClassData classData) { CodeFilesComposer.DownFolder(classData.ClassName); CodeFilesComposer.InitalizeFile(classData.ClassName + ".cs"); DerivedMvClassFileGenerator.Generate(this, classData); CodeFilesComposer.UnselectActiveFile(); foreach (var classData2 in MultivectorClassesData.Values) { CodeFilesComposer.InitalizeFile(classData.ClassName + "Calc" + classData2.ClassId + ".cs"); DerivedMvClassCalcFileGenerator.Generate(this, classData, classData2); CodeFilesComposer.UnselectActiveFile(); } CodeFilesComposer.UpFolder(); } private void GenerateFrameCode(AstFrame frameInfo) { var progressId = this.ReportStart( "Generating code files for frame " + frameInfo.AccessName ); CurrentFrame = frameInfo; CurrentFrameName = GetSymbolTargetName(CurrentFrame); CodeFilesComposer.DownFolder(CurrentFrameName); InitializeMultivectorClassesData(); GenerateBaseMvClassFile(); foreach (var classData in MultivectorClassesData.Values) GenerateDerivedMvClassFiles(classData); //GenerateFctoredBladeFiles(); //GenerateOutermorphismFiles(); CodeFilesComposer.UpFolder(); this.ReportFinish(progressId); } public override GMacCodeLibraryComposer CreateEmptyGenerator() { return new MvLibrary(Root); } public override IEnumerable<AstSymbol> GetBaseSymbolsList() { return Root.Frames; } protected override string GetSymbolTargetName(AstSymbol symbol) { return symbol.Name; } protected override bool InitializeTemplates() { Templates.Parse(MultivectorTemplates); //Template for encoding grade1 multivectors as variables by basis blade index //Templates.Add("vmv", new ParametricComposer("#", "#", "#Var##index#")); return true; } protected override void InitializeOtherComponents() { MacroGenDefaults.ActionBeforeGenerateComputations = GeneratePreComputationsCode; MacroGenDefaults.ActionAfterGenerateComputations = GeneratePostComputationsCode; } protected override bool VerifyReadyToGenerate() { return SelectedSymbols.All(s => s.IsValidFrame); } protected override void ComposeTextFiles() { var framesList = SelectedSymbols.Cast<AstFrame>(); foreach (var frame in framesList) GenerateFrameCode(frame); } protected override void FinalizeOtherComponents() { } } } |
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.