RAQ/lib/structure.gi
Glen Whitney be82518697 Organize raq into standard GAP package file tree
In preparation for a beta release of the raq package, it's important for it to
be conformant to current GAP package conventions. Therefore, I have rearranged
the file tree to match the Example package quite thoroughly. In so doing, this
laid the foundation for the following aspects of the package:

  - Package documentation. Added makedoc.g at the top level to generate
  AutoDoc/GAPDoc documentation for raq. Currently the documentation is empty;
  future development will bring the documentation to cover all existing code.

  - Package tests. The basic structure to add .tst files in the tst
  subdirectory of the package is in place. The coverage of tests provided in
  this commit is minimal; again, further development will extend the test
  coverage.

In addition, PackageInfo.g has been elaborated to include much more complete
information about raq and where it will (initially) be posted on line.

Resolves: #1
2018-08-16 21:12:38 -07:00

716 lines
25 KiB
Plaintext

## structure.gi RAQ Implementation of definitiions, reps, and elt operations
## Testing properties of collections the hard way if we have to
InstallMethod(IsElementwiseIdempotent, "for finite collections",
[IsMultiplicativeElementCollection and IsFinite],
M -> ForAll(Elements(M), m->IsIdempotent(m))
);
InstallMethod(IsLSelfDistributive,
"for arbitrary multiplicative collections, the hard way",
[IsMultiplicativeElementCollection],
function (C)
local a,b,d;
for a in C do for b in C do for d in C do
if d*(a*b) <> (d*a)*(d*b) then return false; fi;
od; od; od;
return true;
end);
InstallMethod(IsRSelfDistributive,
"for arbitrary multiplicative collections, the hard way",
[IsMultiplicativeElementCollection],
function (C)
local a,b,d;
for a in C do for b in C do for d in C do
if (a*b)*d <> (a*d)*(b*d) then return false; fi;
od; od; od;
return true;
end);
## Create structures with generators
InstallMethod(GeneratorsOfMagma,
"finite left quasigroups",
[IsLeftQuasigroup and IsFinite],
q -> GeneratorsOfLeftQuasigroup(q)
);
InstallMethod(GeneratorsOfMagma,
"finite right quasigroups",
[IsRightQuasigroup and IsFinite],
q -> GeneratorsOfRightQuasigroup(q)
);
InstallGlobalFunction(CloneOfTypeByGenerators,
function(cat, fam, gens, genAttrib, tableCstr)
local M, elf;
if not(IsEmpty(gens) or IsIdenticalObj(FamilyObj(gens), fam)) then
Error("<fam> and family of <gens> do not match");
fi;
M := Objectify(NewType( fam, cat and IsAttributeStoringRep), rec());
Setter(genAttrib)(M, AsList(gens));
SetConstructorFromTable(M, tableCstr);
elf := ElementsFamily(fam);
# Since there doesn't seem to be a way to make the IsFinite method based
# on the family being finite into an immediate method:
if HasIsFinite(elf) and IsFinite(elf) then SetIsFinite(M, true); fi;
return M;
end);
## Helpers for the constructors below:
ArgHelper@ := function(parmlist)
local fam;
# returns a list of the family and the list of elements of parmlist
if Length(parmlist) = 0 then
Error("usage: RAQ constructors take an optional family, followed by gens");
fi;
fam := fail;
if IsFamily(parmlist[1]) then fam := Remove(parmlist,1); fi;
if Length(parmlist) = 1 and IsList(parmlist[1])
and not IsDirectProductElement(parmlist[1]) then
parmlist := parmlist[1];
fi;
if fam = fail then fam := FamilyObj(parmlist); fi;
return [fam, parmlist];
end;
CheckLQGprop@ := function(gens)
local g, h;
# Make sure all elements in gens have left quotient property pairwise
for g in gens do for h in gens do
if g*LeftQuotient(g,h) <> h or LeftQuotient(g,g*h) <> h then
Error("left quasigroup property of left quotients violated");
fi;
od; od;
return;
end;
CheckRQGprop@ := function(gens)
local g, h;
# Make sure all elements in gens have right quotient property pairwise
for g in gens do for h in gens do
if (h*g)/g <> h or (h/g)*g <> h then
Error("right quasigroup property of / violated");
fi;
od; od;
return;
end;
## Functions for each of the magma categories here
InstallGlobalFunction(LeftQuasigroup, function(arg)
arg := ArgHelper@(arg);
CheckLQGprop@(arg[2]);
return LeftQuasigroupNC(arg[1], arg[2]);
end);
InstallGlobalFunction(LeftQuasigroupNC, function(fam, gens)
return CloneOfTypeByGenerators(IsLeftQuasigroup, fam, gens,
GeneratorsOfLeftQuasigroup,
LeftQuasigroupByMultiplicationTableNC);
end);
InstallGlobalFunction(LeftRack, function(arg)
arg := ArgHelper@(arg);
CheckLQGprop@(arg[2]);
if not IsLSelfDistributive(arg[2]) then
Error("Left rack must have left distributive generators");
fi;
return LeftRackNC(arg[1], arg[2]);
end);
InstallGlobalFunction(LeftRackNC, function(fam, gens)
return CloneOfTypeByGenerators(IsLeftRack, fam, gens,
GeneratorsOfLeftQuasigroup,
LeftRackByMultiplicationTableNC);
end);
InstallGlobalFunction(LeftQuandle, function(arg)
arg := ArgHelper@(arg);
CheckLQGprop@(arg[2]);
if not IsLSelfDistributive(arg[2]) then
Error("Left quandle must have left distributive generators");
fi;
if not IsElementwiseIdempotent(arg[2]) then
Error("Quandles must contain only idempotent elements");
fi;
return LeftQuandleNC(arg[1], arg[2]);
end);
InstallGlobalFunction(LeftQuandleNC, function(fam, gens)
return CloneOfTypeByGenerators(IsLeftQuandle, fam, gens,
GeneratorsOfLeftQuasigroup,
LeftQuandleByMultiplicationTableNC);
end);
InstallGlobalFunction(RightQuasigroup, function(arg)
arg := ArgHelper@(arg);
CheckRQGprop@(arg[2]);
return RightQuasigroupNC(arg[1], arg[2]);
end);
InstallGlobalFunction(RightQuasigroupNC, function(fam, gens)
return CloneOfTypeByGenerators(IsRightQuasigroup, fam, gens,
GeneratorsOfRightQuasigroup,
RightQuasigroupByMultiplicationTableNC);
end);
InstallGlobalFunction(RightRack, function(arg)
arg := ArgHelper@(arg);
CheckRQGprop@(arg[2]);
if not IsRSelfDistributive(arg[2]) then
Error("Right rack must have right distributive generators");
fi;
return RightRackNC(arg[1], arg[2]);
end);
InstallGlobalFunction(RightRackNC, function(fam, gens)
return CloneOfTypeByGenerators(IsRightRack, fam, gens,
GeneratorsOfRightQuasigroup,
RightRackByMultiplicationTableNC);
end);
InstallGlobalFunction(RightQuandle, function(arg)
arg := ArgHelper@(arg);
CheckRQGprop@(arg[2]);
if not IsRSelfDistributive(arg[2]) then
Error("Right quandle must have right distributive generators");
fi;
if not IsElementwiseIdempotent(arg[2]) then
Error("Quandles must contain only idempotent elements");
fi;
return RightQuandleNC(arg[1], arg[2]);
end);
InstallGlobalFunction(RightQuandleNC, function(fam, gens)
return CloneOfTypeByGenerators(IsRightQuandle, fam, gens,
GeneratorsOfRightQuasigroup,
RightQuandleByMultiplicationTableNC);
end);
## NOTE: If it is the case that the magma generated by the
## GeneratorsOf[Left|Right]Quasigroup is finite, then since the [left|right]
## multiplication is injective, it is also surjective on that set and in fact
## it is the whole quasigroup. However, it is not yet clear to me how best to
## capture this fact in GAP in a computationally useful way.
## View and print and such
LeftObjString@ := function(Q)
# Don't test distributivity if we haven't already
if HasIsLSelfDistributive(Q) and IsLeftRack(Q) then
if HasIsElementwiseIdempotent(Q) and IsElementwiseIdempotent(Q) then
return "LeftQuandle";
fi;
return "LeftRack";
fi;
return "LeftQuasigroup";
end;
RightObjString@ := function(Q)
# Don't test distributivity if we haven't already
if HasIsRSelfDistributive(Q) and IsRightRack(Q) then
if HasIsElementwiseIdempotent(Q) and IsElementwiseIdempotent(Q) then
return "RightQuandle";
fi;
return "RightRack";
fi;
return "RightQuasigroup";
end;
InstallMethod(String, "for a left quasigroup",
[IsLeftQuasigroup],
Q -> Concatenation(LeftObjString@(Q), "(...)"));
InstallMethod(String, "for a left quasigroup with generators",
[IsLeftQuasigroup and HasGeneratorsOfLeftQuasigroup],
Q -> Concatenation(LeftObjString@(Q), "( ",
String(GeneratorsOfLeftQuasigroup(Q)), " )"));
InstallMethod(String, "for a left quasigroup with multiplication table",
[IsLeftQuasigroup and HasMultiplicationTable],
Q -> Concatenation(LeftObjString@(Q),
"ByMultiplicationTableNC( ",
String(MultiplicationTable(Q)), " )"));
InstallMethod(String, "for a right quasigroup",
[IsRightQuasigroup],
Q -> Concatenation(RightObjString@(Q), "(...)"));
InstallMethod(String, "for a right quasigroup with generators",
[IsRightQuasigroup and HasGeneratorsOfRightQuasigroup],
Q -> Concatenation(RightObjString@(Q), "( ",
String(GeneratorsOfRightQuasigroup(Q)), " )"));
InstallMethod(String, "for a right quasigroup with multiplication table",
[IsRightQuasigroup and HasMultiplicationTable],
Q -> Concatenation(RightObjString@(Q),
"ByMultiplicationTableNC( ",
String(MultiplicationTable(Q)), " )"));
InstallMethod(PrintString, "for a left quasigroup",
[IsLeftQuasigroup], Q -> String(Q));
InstallMethod(PrintString, "for a right quasigroup",
[IsRightQuasigroup], Q -> String(Q));
InstallMethod(DisplayString, "for a left quasigroup",
[IsLeftQuasigroup], Q -> String(Q));
InstallMethod(DisplayString, "for a right quasigroup",
[IsRightQuasigroup], Q -> String(Q));
InstallMethod(Display, "for a left quasigroup with multiplication table",
[IsLeftQuasigroup and HasMultiplicationTable],
function(Q)
Print(LeftObjString@(Q), " with ", Size(Q),
" elements, generated by ",
GeneratorsOfLeftQuasigroup(Q), ", with table\n");
Display(MultiplicationTable(Q));
end);
InstallMethod(Display, "for a right quasigroup with multiplication table",
[IsRightQuasigroup and HasMultiplicationTable],
function(Q)
Print(RightObjString@(Q), " with ", Size(Q),
" elements, generated by ",
GeneratorsOfRightQuasigroup(Q), ", with table\n");
Display(MultiplicationTable(Q));
end);
LeftObjView@ := function(Q)
# Don't test distributivity if we haven't already
if HasIsLSelfDistributive(Q) and IsLeftRack(Q) then
if HasIsElementwiseIdempotent(Q) and IsElementwiseIdempotent(Q) then
return "<left quandle";
fi;
return "<left rack";
fi;
return "<left quasigroup";
end;
RightObjView@ := function(Q)
# Don't test distributivity if we haven't already
if HasIsRSelfDistributive(Q) and IsRightRack(Q) then
if HasIsElementwiseIdempotent(Q) and IsElementwiseIdempotent(Q) then
return "<right quandle";
fi;
return "<right rack";
fi;
return "<right quasigroup";
end;
InstallMethod(ViewString, "for a left quasigroup",
[IsLeftQuasigroup],
Q -> Concatenation(LeftObjView@(Q), ">"));
InstallMethod(ViewString, "for a left quasigroup with generators",
[IsLeftQuasigroup and HasGeneratorsOfLeftQuasigroup],
Q -> Concatenation(LeftObjView@(Q), " with ",
String(Size(GeneratorsOfLeftQuasigroup(Q))),
" generators>"));
InstallMethod(ViewString, "for a right quasigroup",
[IsRightQuasigroup],
Q -> Concatenation(RightObjView@(Q), ">"));
InstallMethod(ViewString, "for a right quasigroup with generators",
[IsRightQuasigroup and HasGeneratorsOfRightQuasigroup],
Q -> Concatenation(RightObjView@(Q), " with ",
String(Size(GeneratorsOfRightQuasigroup(Q))),
" generators>"));
## Opposite structures
OFDir@ := NewDictionary("strings", true);
# What property does the Opposite family have for each property of the
# underlying elements? Hopefully we have gotten all of the relevant
# properties. What we really want is the implied (and maybe the required)
# properties of the family of the elements that we are taking the Opposite of,
# but there is not any documented way of obtaining those.
AddDictionary(OFDir@, "CanEasilyCompareElements", CanEasilyCompareElements);
AddDictionary(OFDir@, "CanEasilySortElements", CanEasilySortElements);
AddDictionary(OFDir@, "IsAssociativeElement", IsAssociativeElement);
AddDictionary(OFDir@, "IsCommutativeElement", IsCommutativeElement);
AddDictionary(OFDir@, "IsExtLElement", IsExtRElement);
AddDictionary(OFDir@, "IsExtRElement", IsExtLElement);
AddDictionary(OFDir@, "IsFiniteOrderElement", IsFiniteOrderElement);
AddDictionary(OFDir@, "IsLeftQuotientElement", IsRightQuotientElement);
AddDictionary(OFDir@, "IsLSelfDistElement", IsRSelfDistElement);
AddDictionary(OFDir@, "IsMultiplicativeElementWithInverse",
IsMultiplicativeElementWithInverse);
AddDictionary(OFDir@, "IsMultiplicativeElementWithOne",
IsMultiplicativeElementWithOne);
AddDictionary(OFDir@, "IsRightQuotientElement", IsLeftQuotientElement);
AddDictionary(OFDir@, "IsRSelfDistElement", IsLSelfDistElement);
InstallMethod(OppositeFamily, "for a family",
[IsFamily],
function(fam)
local F, elt, elt_props, opp_props, prop, opp_filt, filt;
elt := Representative(fam);
elt_props := CategoriesOfObject(elt);
Append(elt_props, KnownTruePropertiesOfObject(elt));
opp_props := [];
for prop in elt_props do
if KnowsDictionary(OFDir@, prop) then
Add(opp_props, LookupDictionary(OFDir@, prop));
fi;
od;
opp_filt := IsMultiplicativeElement;
for filt in opp_props do opp_filt := opp_filt and filt; od;
return NewFamily(Concatenation("OppositeFamily(", fam!.NAME, ")"),
IsOppositeObject, opp_filt);
end);
InstallMethod(OppositeType, "for a family",
[IsFamily],
fam -> NewType(OppositeFamily(fam), IsDefaultOppositeObject)
);
InstallMethod(OppositeObj, "for a multiplicative element",
[IsMultiplicativeElement],
function (obj)
local fam;
fam := FamilyObj(obj);
SetRepresentative(fam, obj);
return Objectify(OppositeType(fam), [Immutable(obj)]);
end);
InstallMethod(OppositeObj, "for a commutative element",
[IsMultiplicativeElement and IsCommutativeElement],
IdFunc
);
InstallMethod(UnderlyingMultiplicativeElement, "for a default opposite obj",
[IsDefaultOppositeObject],
obj -> obj![1]
);
## Printing and viewing opposite objects
InstallMethod(String, "for opposite objects",
[IsDefaultOppositeObject],
obj -> Concatenation("OppositeObj( ", String(obj![1]), " )")
);
InstallMethod(ViewString, "for opposite objects",
[IsDefaultOppositeObject],
obj -> Concatenation("o", ViewString(obj![1]))
);
InstallMethod(\=, "for two opposite objects",
IsIdenticalObj,
[IsDefaultOppositeObject, IsDefaultOppositeObject],
function(l,r) return l![1] = r![1]; end
);
InstallMethod(\<, "for two opposite objects",
IsIdenticalObj,
[IsDefaultOppositeObject, IsDefaultOppositeObject],
function(l,r) return l![1] < r![1]; end
);
InstallMethod(\*, "for two opposite objects",
IsIdenticalObj,
[IsDefaultOppositeObject, IsDefaultOppositeObject],
function(l,r) return OppositeObj(r![1]*l![1]); end
);
InstallOtherMethod(LeftQuotient, "for two opposite objects",
IsIdenticalObj,
[IsDefaultOppositeObject and IsLeftQuotientElement, IsDefaultOppositeObject],
function(l,r) return OppositeObj(r![1]/l![1]); end
);
InstallOtherMethod(\/, "for two opposite objects",
IsIdenticalObj,
[IsDefaultOppositeObject, IsDefaultOppositeObject and IsRightQuotientElement],
function(l,r) return OppositeObj(LeftQuotient(r![1], l![1])); end
);
InstallMethod(OneOp, "for an opposite object",
[IsDefaultOppositeObject and IsMultiplicativeElementWithOne],
ob -> OppositeObj(One(ob![1]))
);
InstallMethod(InverseOp, "for an opposite object",
[IsDefaultOppositeObject and IsMultiplicativeElementWithInverse],
ob -> OppositeObj(Inverse(ob![1]))
);
# Now the many Opposite implementations. How can we cut this down?
InstallMethod(Opposite, "for a commutative magma",
[IsMagma and IsCommutative],
IdFunc
);
InstallMethod(Opposite, "for a finitely generated magma",
[IsMagma and HasGeneratorsOfMagma],
function(M)
local fam, elts;
# Get elements first to prime the representative of the family
elts := List(GeneratorsOfMagma(M), m -> OppositeObj(m));
fam := CollectionsFamily(OppositeFamily(ElementsFamily(FamilyObj(M))));
return Magma(fam, elts);
end);
InstallMethod(Opposite, "for a finitely generated magma with one",
[IsMagmaWithOne and HasGeneratorsOfMagmaWithOne],
function(M)
local fam, elts;
# Get elements first to prime the representative of the family
elts := List(GeneratorsOfMagmaWithOne(M), m -> OppositeObj(m));
fam := CollectionsFamily(OppositeFamily(ElementsFamily(FamilyObj(M))));
return MagmaWithOne(fam, elts);
end);
InstallMethod(Opposite, "for a finitely generated magma with inverse",
[IsMagmaWithInverses and HasGeneratorsOfMagmaWithInverses],
function(M)
local fam, elts;
# Get elements first to prime the representative of the family
elts := List(GeneratorsOfMagmaWithInverses(M), m -> OppositeObj(m));
fam := CollectionsFamily(OppositeFamily(ElementsFamily(FamilyObj(M))));
return MagmaWithInverses(fam, elts);
end);
# Do we need Opposite() methods for Semigroups, Monoids, and Groups? Or have
# we copied enough properties that those filters will simply carry over? The
# couple of small tests I did the Opposite of a Group was again reported as a
# Group, so let's leave those methods out for now, and move on to quasigroups,
# racks, and quandles.
OppHelper@ := function(Q, whichgens, cnstr)
local fam, elts, opp;
elts := List(whichgens(Q), q -> OppositeObj(q));
fam := CollectionsFamily(OppositeFamily(ElementsFamily(FamilyObj(Q))));
opp := cnstr(fam, elts);
if HasIsFinite(Q) then SetIsFinite(opp, IsFinite(Q)); fi;
return opp;
end;
InstallMethod(Opposite, "for a left quasigroup",
[IsLeftQuasigroup and HasGeneratorsOfLeftQuasigroup],
Q -> OppHelper@(Q, GeneratorsOfLeftQuasigroup, RightQuasigroupNC)
);
InstallMethod(Opposite, "for a left rack",
[IsLeftRack and HasGeneratorsOfLeftQuasigroup],
Q -> OppHelper@(Q, GeneratorsOfLeftQuasigroup, RightRackNC)
);
InstallMethod(Opposite, "for a left quandle",
[IsLeftQuandle and HasGeneratorsOfLeftQuasigroup],
Q -> OppHelper@(Q, GeneratorsOfLeftQuasigroup, RightQuandleNC)
);
InstallMethod(Opposite, "for a right quasigroup",
[IsRightQuasigroup and HasGeneratorsOfRightQuasigroup],
Q -> OppHelper@(Q, GeneratorsOfRightQuasigroup, LeftQuasigroupNC)
);
InstallMethod(Opposite, "for a right rack",
[IsRightRack and HasGeneratorsOfRightQuasigroup],
Q -> OppHelper@(Q, GeneratorsOfRightQuasigroup, LeftRackNC)
);
InstallMethod(Opposite, "for a right quandle",
[IsRightQuandle and HasGeneratorsOfRightQuasigroup],
Q -> OppHelper@(Q, GeneratorsOfRightQuasigroup, LeftQuandleNC)
);
# Direct Products
# We actually try to handle here the general cases that are not otherwise
# handled by the groups functions in GAP as a whole, or the LOOPS package
# Helper for roughly creating the join of the filters of a bunch of objects.
FiltersOfObj@ := function(obj)
local f;
f := CategoriesOfObject(obj);
Append(f,KnownTruePropertiesOfObject(obj));
Append(f,List(KnownAttributesOfObject(obj), x -> Concatenation("Has", x)));
return f;
end;
# From the implementation of DirectProductOp for groups and quasigroups, the
# below will only be called with a second-argument collection which is not a
# group or quasigroup.
# This is a helper that creates a rough join of the filters of a list of
# objects, using the same arguments as directproduct op (the redundant "first"
# argument, which is handy for seeding the filter list)
RoughJoinOfFilters@ := function(list, first)
local item, jof;
jof := Set(FiltersOfObj@(first));
for item in list{[2..Length(list)]} do
IntersectSet(jof, FiltersOfObj@(item));
od;
return jof;
end;
InstallOtherMethod(DirectProductOp, "for a list and non-quasigroup magma",
[IsList, IsMagma],
function (list, first)
local item, i, jof, ids, gens, g, current, genfunc, genlists;
# Simple checks
if IsEmpty(list) then
Error("Usage: Cannot take DirectProduct of zero items.");
elif Length(list) = 1 then
return list[1];
fi;
jof := RoughJoinOfFilters@(list, first);
# The dispatch below is somewhat ad-hoc, but covers the major existing cases
# First, if we don't have generators for all of the items, we're kinda out
# of luck here:
if not ForAny(jof, nm -> StartsWith(nm,"HasGenerators")) then
TryNextMethod();
fi;
# Next, if anything is not even a magma, recommend cartesian product
if not "IsMagma" in jof then
Info(InfoRAQ, 1, "Try Cartesian() for products of collections.");
TryNextMethod();
fi;
# The primary division now is between entities with identities, for which
# the product will have a smaller generating set, and those which do
# not, for which the generators is the full cartesian product of generators.
if "IsMagmaWithOne" in jof then
# Code basically copied from DirectProductOp() for general groups;
# would be better if this could be moved into the GAP core
ids := List(list, One);
gens := [];
for i in [1..Length(list)] do
for g in GeneratorsOfMagmaWithOne(list[i]) do
current := ShallowCopy(ids);
current[i] := g;
Add(gens, DirectProductElement(current));
od;
od;
# Now, we want the most specific structure we know about here.
# Only aware of MagmaWithOne and Monoid, as direct products of
# loops are already covered by the LOOPS package:
if "IsMonoid" in jof then
return MonoidByGenerators(gens);
fi;
return MagmaWithOneByGenerators(gens);
fi;
# OK, here we know not all structures have inverses. Therefore, we must
# resort to the full cartesian product of generators.
# But we need to figure out what function to use to obtain the generators.
if "HasGeneratorsOfLeftQuasigroup" in jof then
genfunc := GeneratorsOfLeftQuasigroup;
elif "HasGeneratorsOfRightQuasigroup" in jof then
genfunc := GeneratorsOfRightQuasigroup;
elif "HasGeneratorsOfMagma" in jof then
genfunc := GeneratorsOfMagma;
elif "HasGeneratorsOfDomain" in jof then
genfunc := GeneratorsOfDomain;
else
Info(InfoRAQ,1, "RAQ: Unusual product, each of ", list,
" has generators, but not sure what kind; trying next method.");
TryNextMethod();
fi;
genlists := List(list, genfunc);
gens := Cartesian(genlists);
Apply(gens, DirectProductElement);
# Again, we need to figure out what sort of structure we might have
if "IsAssociative" in jof then
return SemigroupByGenerators(gens);
elif "IsLeftQuasigroup" in jof then
if "IsLSelfDistributive" in jof then
if "IsElementwiseIdempotent" in jof then
return LeftQuandleNC(FamilyObj(gens), gens);
fi;
return LeftRackNC(FamilyObj(gens), gens);
fi;
return LeftQuasigroupNC(FamilyObj(gens), gens);
elif "IsRightQuasigroup" in jof then
if "IsRSelfDistributive" in jof then
if "IsElementwiseIdempotent" in jof then
return RightQuandleNC(FamilyObj(gens), gens);
fi;
return RightRackNC(FamilyObj(gens), gens);
fi;
return RightQuasigroupNC(FamilyObj(gens), gens);
fi;
# Not seeing any additional structure; did I miss anything?
return MagmaByGenerators(gens);
end);
# Make sure that we can take quotients properly in the direct product
InstallOtherMethod(LeftQuotient,
IsIdenticalObj,
[IsDirectProductElement, IsDirectProductElement],
function (l,r)
local i, comps;
comps := [];
for i in [1..Length(l)] do
Add(comps, LeftQuotient(l![i],r![i]));
od;
return DirectProductElement(comps);
end);
InstallOtherMethod(\/,
IsIdenticalObj,
[IsDirectProductElement, IsDirectProductElement],
function (l,r)
local i, comps;
comps := [];
for i in [1..Length(l)] do
Add(comps, l![i] / r![i]);
od;
return DirectProductElement(comps);
end);
## Coversions among the structure types
InstallMethod(AsLeftQuasigroup, "for a left quasigroup",
[IsLeftQuasigroup], IdFunc);
InstallMethod(AsLeftRack, "for a left rack",
[IsLeftRack], IdFunc);
InstallMethod(AsLeftQuandle, "for a left quandle",
[IsLeftQuandle], IdFunc);
InstallMethod(AsRightQuasigroup, "for a right quasigroup",
[IsRightQuasigroup], IdFunc);
InstallMethod(AsRightRack, "for a right rack",
[IsRightRack], IdFunc);
InstallMethod(AsRightQuandle, "for a left quandle",
[IsLeftQuandle], IdFunc);
AsAStructure@ := function(struc, D)
local T,S;
D := AsSSortedList(D);
# Check if D is closed under *
T := MultiplicationTable(D);
if ForAny(T, l -> fail in l) then
return fail;
fi;
# it is, so check if it satisfies the properties of struc
S := struc(D);
# OK, good, so we are there, just record what we know
SetAsSSortedList(S, D);
SetMultiplicationTable(S, T);
SetIsFinite(S, true);
SetSize(S, Length(D));
return S;
end;
InstallMethod(AsLeftQuasigroup, "for an arbitrary collection",
[IsCollection], D -> AsAStructure@(LeftQuasigroup, D));
InstallMethod(AsLeftRack, "for an arbitrary collection",
[IsCollection], D -> AsAStructure@(LeftRack, D));
InstallMethod(AsLeftQuandle, "for an arbitrary collection",
[IsCollection], D -> AsAStructure@(LeftQuandle, D));
InstallMethod(AsRightQuasigroup, "for an arbitrary collection",
[IsCollection], D -> AsAStructure@(RightQuasigroup, D));
InstallMethod(AsRightRack, "for an arbitrary collection",
[IsCollection], D -> AsAStructure@(RightRack, D));
InstallMethod(AsRightQuandle, "for an arbitrary collection",
[IsCollection], D -> AsAStructure@(RightQuandle, D));