7
$\begingroup$

I would like to implement algebraic structures in Mathematica such as groups, algebras, Lie-algebras, Hopf-algebras, ... In particular, after initialising such a structure, I would like to have

  1. functions that calculate properties and store these in the structure,
  2. that the elements of such structures can be used in calculations to gain insight in the structure. It is important that the notation for doing computations is not tedious such that I don't lose overview of what is going on.
  3. that multiple defined structures can be used in the same notebook without naming collisions.

At this moment my plan to realise the above would be to use the Association structure as a starting point. Initialising, e.g. an algebra, could then be done like

initialiseAlgebra[ multTable_ ] := Module[ {...}, 
  ...
  (* Possibly some basic calculations *)
  ...
  <|
     generators -> {...},
     dimension  -> ...,
     sum -> Function[...],
     product -> Function[...],
     ...,
     center     -> {}
  |>
]

But to be honest

  1. I would prefer to have the structure have a Head like algebra so that I could use the structure for pattern matching
  2. I am not sure what 'names' or 'structure' the generators or elements should have/be. I could add global symbols a1,...,an but that doesn't seem wise since I want to combine multiple structures.
  3. The sum, product, coproduct, ... are defined per structure so even if I could call the elements of the structure by
A = initialiseAlgebra[...];
(* First element *)
A[a1]
(* Tenth element *)
A[a10]

multiplying these elements would still look like

A[product][ A[a1], A[a10] ]

and thus simple calculations become tedious to read.

So the question is: how would one implement abstract algebraic structures in Mathematica such that one can both: calculate general properties AND compute with the elements in a non-tedious way? Is an association the right way to go or would other means be better? I know that Mathematica has built in support for finite groups. Is it known how this structure is implemented?

$\endgroup$
1
  • 1
    $\begingroup$ It looks like the issue you're running into is really one of object-oriented programming in Mathematica, not the specifics of implementing an algebra. Might be helpful to look at stuff on the site detailing how to do OOP. $\endgroup$
    – b3m2a1
    Jan 29, 2020 at 22:12

1 Answer 1

10
+100
$\begingroup$

I decided it was worth giving another example of modern OOP in Mathematica. There will be a small amount of code, but almost all of it is boiler-plate. I use a package to handle most of the boiler plater, myself, but I find that links to packages make the code less-likely to be used.

So here we go.

First off, we'll define a constructor for your Algebra object. We'll store all the core data for it in an Association for convenience of access. You'll take the passed in arguments and turn them into an Association in canonicalizeAlgebraData

ClearAll[Algebra];
Options[Algebra] = {
   "MultTable" -> None,
   "Generators" -> None,
   "Dimension" -> None
   };
Algebra[ops : OptionsPattern[]] :=
  Algebra[canonicalizeAlgebraData[ops]];
(* do the minimum work necessary to make sure all the data for the Algebra is there *)
canonicalizeAlgebraData[ops : OptionsPattern[]] :=
  Association[ops];

Next we'll add a validator to the object so that we can check if something with Algebra as its Head is truly a proper algebra object. I use System`Private`SetValid to set a flag so that Mathematica doesn't check this again and again. I also provide a AlgebraQ and a NotAlgebraQ just for convenience.

(* make some validators so you can always be sure you have a valid algebra without constantly having to check it *)

validateAlgebraData[a_Association] :=
  Length[a]>0; (* reimplement this *)
Algebra[a_Association]?NotAlgebraQ :=
  System`Private`HoldSetValid[Algebra[a]] /; validateAlgebraData[a];
AlgebraQ[a_Algebra] := System`Private`HoldValidQ[a];
AlgebraQ[_] := False;
AlgebraQ[s_Symbol] := (Head[s] === Algebra && AlgebraQ[Evaluate[s]]);
AlgebraQ~SetAttributes~HoldFirst;
NotAlgebraQ[a_] := Not[AlgebraQ[a]];
NotAlgebraQ~SetAttributes~HoldFirst;

Next we can supply a formatting rule so that it looks nice when displayed. I use ArrangeSummaryBox for that.

(* define formatting if you want to *)

Format[Algebra[a_]?AlgebraQ, StandardForm] :=
 RawBoxes@
  BoxForm`ArrangeSummaryBox[
   Algebra,
   Algebra[a],
   None,
   {"Put summary info here"},
   {},
   StandardForm
   ]

Finally we get to the fun stuff. First we'll make it so that you can treat the Algebra like an Association by defining a standard accessor. Then I have two examples of how you can delegate accessors for things that should be computed (like the "Generators") to specialized functions.

(* define some accessors / methods on your alebgra *)

Algebra[a_]?AlgebraQ[k_] := 
  Lookup[a, k];(* general lookup *)
(g : Algebra[a_]?AlgebraQ)["Generators"] :=
   getAlgebraicGenerators[g];
(g : Algebra[a_]?AlgebraQ)["Dimensions"] := getAlgebraDimension[g];

Now we get the part where we hook this into the functions Mathematica already has in it. All of this is done with TagSet. First is the Dimensions, because it's a pretty obvious analog to alg["Generators"].

The next one is more subtle. I first overload Part so that it wraps the requested part in the head AlgebraicElement. I do this so that you can then define an overload for NonCommutativeMultiply with this head. That means that now you can do, e.g. alg[[1]] ** alg[[10]] and it'll turn into getAlgebraProduct[alg, {1, 10}]. I'm not sure if it makes sense to multiply elements from different algebras, so I didn't support that.

You can play with this however you like.

(* define some overloads for your algebra *)

Algebra /: Dimensions[a_Algebra?AlgebraQ] := a["Dimensions"];
Algebra /: a_Algebra?AlgebraQ[[el_]] :=
 AlgebraicElement[a, el];(* getting elements *)
AlgebraicElement /: 
 NonCommutativeMultiply[
  AlgebraicElement[a_Algebra?AlgebraQ, el1_],
  AlgebraicElement[a_Algebra?AlgebraQ, el2_]
  ] := getAlgebraProduct[a, {el1, el2}];

You can also add support for "mutation" of your structure, that is allowing the object to be affected by stuff like AssociateTo[alg, someRule]. I do this via a simple function that merges the changes into the structure (mutateAlgebra) and then I create a second function that'll hook this into Mathematica's assignment system for Symbol using Language`SetMutationHandler on a handler function (algebraMutationHandler). This part is a bit subtle, but really powerful and can be very useful.

(* allow for natural modifications of the algebraic structure *)

mutateAlgebra[Algebra[a_]?AlgebraQ,  changes_Association] :=
  Algebra[Join[changes, a]];
mutateAlgebra[a_Algebra,  changes_] :=
 mutateAlgebra[a,  Association@changes]
algebraMutationHandler~SetAttributes~HoldAllComplete;
algebraMutationHandler[
   AssociateTo[s_Symbol?AlgebraQ, stuff_]
   ] :=
  (s = mutateAlgebra[s, stuff]);
algebraMutationHandler[
   Set[s_Symbol?AlgebraQ[key_], val_]
   ] :=
  (s = mutateAlgebra[s, key -> val]);
Language`SetMutationHandler[Algebra, algebraMutationHandler];

Finally this is the part that you have to do: actually implementing the functions that do algebraic calculations on one of these objects.

(* implement the core algebra calculations some other way *)

getAlgebraGenerators[a_Algebra?AlgebraQ] :=
  ...;
getAlgebraDimension[a_Algebra?AlgebraQ] :=
  ...;
getAlgebraProduct[a_Algebra?AlgebraQ, {el1_, el2_}] :=
  ...;

Here's an example of how this all works:

a = Algebra["MultTable" -> {}]
(* Out: Algebra[<|"MultTable" -> {}|>] *)

Since I added those formatting rules, this looks like:

enter image description here

Then we can mutate this thing, say to add a rule:

a["Gens"] = 1;
a["Gens"]
(* Out: 1 *)

a
(* Out: Algebra[<|"Gens" -> 1, "MultTable" -> {}|>] *)

And if we ask for those overloads we get the desired results:

Dimensions[a]
(* Out: getAlgebraDimension[Algebra[<|"Gens" -> 1, "MultTable" -> {}]|>] *)

a[[1]] ** a[[10]]
(* Out: getAlgebraProduct[Algebra[<|"Gens" -> 1, "MultTable" -> {}|>], {1, 10}] *)

Hopefully this has been a nice little example of how a specific case of OOP can work. Also you can probably see how most of that boiler plate (defining a validator, constructor, mutation handlers, etc.) can be abstracted away into a package. I create a lot of objects like this, using a package I called InterfaceObjects since they define interfaces to Association data.

$\endgroup$
1
  • $\begingroup$ This answers all the questions I had and even more! I've learned (and am still learning) a lot by experimenting with the code you provided. Thank you so much :) $\endgroup$
    – Gert
    Jan 31, 2020 at 17:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.