In this post, I will explain the details of the Blades Library Composer GMac project. The purpose of this project is to generate a C# library to compute with GA blades of any frame. Here we use GMacAPI and TextComposerLib to compose the blades library code according to a selected design. In order to thoroughly follow this post, it’s better to have a good understanding of C#. Nevertheless, all the library composition code can be re-written using any .NET language and the generated code design can also be adapted to other target languages.
The objectives of composing this library are:
- To represent arbitrary blades in any desired GA frame using simple arrays in C#.
- To perform GA operations and products on blades as efficiently as we can.
- To illustrate how to use Geometric Algebra, GMac, and TextComposerLib for simplifying this complex task with minimal errors and maintenance effort.
First Steps
To follow the illustrations in this post you first need to download and install the GMacSamples .NET solution as explained in the GMac Samples page. You will find the source code for this post under the BladesLibraryComposer project. Make sure all references to GMac and Mathematica are set correctly and set the BladesLibraryComposer as the startup project. Now if you run the solution you see a screen like the following:
As we can see, the interface is really simple. It contains the following elements:
- Using the Output Folder text box and the Browse button you can select an output folder where the generated code will be put.
- Using the GMacDSL Code list box you can select the GMacDSL code containing definitions for GA frames used for composing the library.
- Using the Generate Macro Code checkbox you can select whether or not to generate full computational code in the library. It’s better to leave this unchecked if you just want to inspect the general structure of the generated library. If you choose to generate the full code this would take longer because many symbolic computations are involved.
- Using the Target Language list box you select the target language of the generated library. Currently, only C# can be used.
- Finally, you press the Compose Code button to begin the library generation process.
By inspecting the code of this screen in the project you can easily understand its function. You can find the code in the FormMain.cs file under the BladesLibraryComposer project.
Library Design
If you use GMacAPI to compose your own GA-based code library you first need to decide on the high-level code design of your generated library. Since our first objective is to represent blades, I will start by explaining this point.
In Geometric Algebra, a k-blade is a special type of multivectors having the same grade k and resulting from the outer product of k linearly independent vectors. Using the additive representation of multivectors we can store the real coefficients of multivectors as an array of double-precision floating point numbers. For our design, each blade
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 |
using System; using System.Globalization; using System.IO; using System.Text; namespace GMacBlade.e3d { /// <summary> /// This class represents an immutable blade in the e3d frame with arbitrary grade /// (i.e. grade is determined at runtime) based on additive representation of the blade as a /// linear combination of basis blades of the same grade (i.e. it's actually a k-vector representation). /// </summary> public sealed partial class e3dBlade { /// <summary> /// Grade of blade. /// </summary> public int Grade { get; private set; } /// <summary> /// The k-vector space dimension of this blade (equals the length of the Coef array) /// </summary> public int KvSpaceDim { get { return GradeToKvSpaceDim[Grade]; } } /// <summary> /// Ordered coefficients of blade in the additive representation. /// </summary> internal double[] Coefs { get; set; } /// <summary> /// This blade is a zero blade: it has no internal coefficients and its grade is any legal grade /// This kind of blade should be treated separately in operations on blades /// </summary> public bool IsZeroBlade { get { return Coefs.Length == 0; } } /// <summary> /// True if this blade is a null blade /// </summary> public bool IsNull { get { if (IsZeroBlade) return true; var c = Norm2; return !(c <= -Epsilon || c >= Epsilon); } } public bool IsScalar { get { return IsZeroBlade || Grade == 0; } } public bool IsVector { get { return IsZeroBlade || Grade == 1; } } public bool IsPseudoVector { get { return IsZeroBlade || Grade == MaxGrade - 1; } } public bool IsPseudoScalar { get { return IsZeroBlade || Grade == MaxGrade; } } public double this[int index] { get { return Coefs[index]; } } public string[] BasisBladesNames { get { return BasisBladesNamesArray[Grade]; } } /// <summary> /// True if the coefficients represent a blade; not a general non-simple k-vector. /// </summary> public bool IsBlade { get { return SelfDPGrade() == 0; } } /// <summary> /// True if the coefficients represent a general non-simple k-vector; not a blade. /// </summary> public bool IsNonBlade { get { return SelfDPGrade() != 0; } } /// <summary> /// Create a blade and initialize its coefficients by the given array. Use this vary /// carefully as the blade type is supposed to be immutable /// </summary> internal e3dBlade(int grade, double[] coefs) { if (coefs.Length != GradeToKvSpaceDim[grade]) throw new ArgumentException("The given array has the wrong number of items for this grade", "coefs"); Grade = grade; Coefs = coefs; } /// <summary> /// Create a scalar blade (a grade-0 blade) /// </summary> public e3dBlade(double scalar) { Grade = 0; Coefs = new [] { scalar }; } /// <summary> /// Create the zero blade /// </summary> private e3dBlade() { Grade = 0; Coefs = new double[0]; } /// <summary> /// Test if this blade is of a given grade. A zero blade is assumed to have any grade /// </summary> public bool IsOfGrade(int grade) { return Grade == grade || (grade >= 0 && grade <= MaxGrade && IsZero); } /// <summary> /// If this blade is of grade 1 convert it to a vector /// </summary> /// <returns></returns> public e3dVector ToVector() { if (Grade == 1) return new e3dVector(Coefs); if (IsZero) return new e3dVector(); throw new InvalidDataException("Internal error. Blade grade not acceptable!"); } public static e3dBlade Meet(e3dBlade bladeA, e3dBlade bladeB) { //blade A1 is the part of A not in B var bladeA1 = bladeA.DPDual(bladeB).DP(bladeA); return bladeA1.ELCP(bladeB); } public static e3dBlade Join(e3dBlade bladeA, e3dBlade bladeB) { //blade A1 is the part of A not in B var bladeA1 = bladeA.DPDual(bladeB).DP(bladeA); return bladeA1.OP(bladeB); } public static void MeetJoin(e3dBlade bladeA, e3dBlade bladeB, out e3dBlade bladeMeet, out e3dBlade bladeJoin) { //blade A1 is the part of A not in B var bladeA1 = bladeA.DPDual(bladeB).DP(bladeA); bladeMeet = bladeA1.ELCP(bladeB); bladeJoin = bladeA1.OP(bladeB); } public static void MeetJoin(e3dBlade bladeA, e3dBlade bladeB, out e3dBlade bladeA1, out e3dBlade bladeB1, out e3dBlade bladeMeet) { //blade A1 is the part of A not in B bladeA1 = bladeA.DPDual(bladeB).DP(bladeA); bladeMeet = bladeA1.ELCP(bladeB); bladeB1 = bladeMeet.ELCP(bladeB); } public static void MeetJoin(e3dBlade bladeA, e3dBlade bladeB, out e3dBlade bladeA1, out e3dBlade bladeB1, out e3dBlade bladeMeet, out e3dBlade bladeJoin) { //blade A1 is the part of A not in B bladeA1 = bladeA.DPDual(bladeB).DP(bladeA); bladeMeet = bladeA1.ELCP(bladeB); bladeJoin = bladeA1.OP(bladeB); bladeB1 = bladeMeet.ELCP(bladeB); } public override bool Equals(object obj) { return !ReferenceEquals(obj, null) && Equals(obj as e3dBlade); } public override int GetHashCode() { return Grade.GetHashCode() ^ Coefs.GetHashCode(); } public override string ToString() { if (IsZeroBlade) return default(double).ToString(CultureInfo.InvariantCulture); if (IsScalar) return Coefs[0].ToString(CultureInfo.InvariantCulture); var s = new StringBuilder(); for (var i = 0; i < KvSpaceDim; i++) { s.Append("(") .Append(Coefs[i].ToString(CultureInfo.InvariantCulture)) .Append(" ") .Append(BasisBladesNames[i]) .Append(") + "); } s.Length -= 3; return s.ToString(); } } } |
Then we can