Skip to content

Instantly share code, notes, and snippets.

@Biotronic
Last active February 9, 2018 17:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Biotronic/833680b37d4afe774c8562fd21554c6b to your computer and use it in GitHub Desktop.
Save Biotronic/833680b37d4afe774c8562fd21554c6b to your computer and use it in GitHub Desktop.
A template for defining algebras
import std.algorithm : among;
import std.array : appender;
import std.string : split, isNumeric;
import std.meta : allSatisfy, aliasSeqOf, Filter, templateNot, Repeat;
import std.conv : to, text;
import std.format : format;
alias complex = Algebra!(
float,
"1,i",
"i" * "i".op = -1);
alias dual = Algebra!(
float,
"1,e",
"e" * "e".op = 0);
alias splitComplex = Algebra!(
float,
"1,j",
"j" * "j".op = 1
);
alias quaternion = Algebra!(
float,
"1,i,j,k",
"i,j,k" * "i,j,k".op = -1,
"i,j,k" * "j,k,i".op = "k,i,j".antiCommutative
);
unittest
{
auto a = complex(1,1);
assert(a*a == complex(0,2));
}
unittest
{
auto a = dual(1,1);
assert(a*a == dual(1,2));
}
unittest
{
auto a = splitComplex(1,2);
assert(a*a == splitComplex(5, 4));
}
unittest
{
auto a = quaternion(1,0,0,0);
auto b = quaternion(1,0,0,0);
assert(a*b == quaternion(1,0,0,0));
a = quaternion(0,1,0,0);
assert(a*a == quaternion(-1,0,0,0));
}
unittest
{
auto a = quaternion(1,0,1,0);
auto b = quaternion(1,0.5, 0.5, 0.75);
assert(a * b == quaternion(0.5, 1.25,1.5,0.25));
}
struct Algebra(T, string units, rules...)
{
enum Units = aliasSeqOf!(units.split(','));
alias Rules = Canonicalize!(units, rules);
static assert(allSatisfy!(isValidRule!Units, Rules), "Invalid rule set for units "~units~": "~[Filter!(templateNot!(isValidRule!Units), Rules)].to!string);
T[Units.length] components;
alias components this;
enum zero = Algebra(Repeat!(Units.length, create!T(0)));
this(Repeat!(Units.length, T) args)
{
components = [args];
}
Algebra opBinary(string op)(Algebra rhs)
if (op.among("+", "-"))
{
Algebra result;
foreach (i; 0..Units.length)
result[i] = binOp!op(this[i], rhs[i]);
return result;
}
Algebra opBinary(string op : "*")(Algebra rhs)
{
Algebra result = zero;
static foreach (rule; Rules)
{{
enum lhsIdx = rule.lhs.among(Units) - 1;
enum rhsIdx = rule.rhs.among(Units) - 1;
enum resultIdx = rule.result.among(Units) - 1;
result[resultIdx] += this[lhsIdx] * rhs[rhsIdx] * rule.factor;
}}
return result;
}
bool opEquals(Algebra other)
{
foreach (i; 0..Units.length)
if (this[i] != other[i]) return false;
return true;
}
string toString()
{
auto result = appender!string;
result.put("[");
static foreach (i; 0..Units.length)
{
if (i > 0) result.put(", ");
result.put(text(this[i]));
if (!Units[i].isNumeric)
result.put(Units[i]);
}
result.put("]");
return result.data;
}
}
auto op(string s) { return Op(s); }
auto antiCommutative(string rule) { return AntiCommutative(rule); }
struct AntiCommutative
{
string rules;
alias rules this;
}
struct Op
{
string rhs;
auto opBinaryRight(string op : "*")(string lhs)
{
return OpProxy(lhs, rhs);
}
}
struct OpProxy
{
string lhs, rhs;
auto opAssign(T)(T value)
if (is(T == string) || is(T == AntiCommutative) || is(T == int))
{
return Rule(lhs, rhs, value.to!string, !is(T == AntiCommutative));
}
}
struct Rule
{
string lhs, rhs;
string result;
bool isCommutative;
int factor;
string toString()
{
return format("%s * %s => %s * %s", lhs, rhs, result, factor);
}
}
enum isRule(alias T) = is(typeof(T) == Rule);
template isValidRule(Units...)
{
bool isUnit(string s, bool acceptNegative = false)
{
if (s.length == 0 || s == "-") return false;
if (acceptNegative && s[0] == '-') s = s[1..$];
if (s.isNumeric) return "1".among(Units) > 0;
return s.among(Units) > 0;
}
enum isValidRule(Rule op) =
isUnit(op.lhs) &&
isUnit(op.rhs) &&
isUnit(op.result, true);
}
// Create a canonical list of rules from compound rules.
template Canonicalize(string units, Rules...)
if (allSatisfy!(isRule, Rules))
{
Rule[] canonicalSet(Rule base)
{
Rule[] list;
auto lhsList = base.lhs.split(',');
auto rhsList = base.rhs.split(',');
auto resultList = base.result.split(',');
assert(lhsList.length == rhsList.length || rhsList.length == 1, "Incompatible LHS and RHS lists: "~base.lhs~" vs "~base.rhs~".\n"~
"Lists should be of equal length, or there should be only one RHS element.");
assert(rhsList.length == resultList.length || resultList.length == 1, "Incompatible result list: "~base.result~" vs "~base.rhs~".\n"~
"Lists should be of equal length, or there should be only one result element.");
foreach (i; 0..lhsList.length)
{
auto resultType = resultList[i%resultList.length];
int fact = 1;
if (resultType[0] == '-')
{
resultType = resultType[1..$];
fact = -1;
}
if (resultType.isNumeric)
{
fact *= resultType.to!int;
resultType = "1";
}
auto leftUnit = lhsList[i];
auto rightUnit = rhsList[i%rhsList.length];
list ~= Rule(leftUnit, rightUnit, resultType, true, fact);
if (leftUnit == rightUnit) continue;
if (!base.isCommutative) fact = -fact;
list ~= Rule(rightUnit, leftUnit, resultType, true, fact);
}
return list;
}
Rule[] CanonicalizeImpl(Rule[] rules)
{
Rule[] result;
foreach (rule; rules)
result ~= rule.canonicalSet();
auto unitList = units.split(',');
foreach (leftUnit; unitList)
foreach (rightUnit; unitList)
{
// Find existing rules for this lhs/rhs combination.
int found = 0;
foreach (rule; result)
if (rule.lhs == leftUnit && rule.rhs == rightUnit)
found++;
if (found == 1) break;
assert(found == 0, "Duplicate rules detected for "~leftUnit~" * "~rightUnit);
// Create missing rules with sensible default behavior.
auto resultUnit = leftUnit;
if (leftUnit == "1") resultUnit = rightUnit;
result ~= Rule(leftUnit, rightUnit, resultUnit, true, 1);
}
return result;
}
alias Canonicalize = aliasSeqOf!(CanonicalizeImpl([Rules]));
}
// Creates an instance of T from a V, either using T's constrcutor, or forcefully settings its fields using .tupleof.
template create(T)
{
T create(V)(V value)
{
static if (is(typeof({T t = value;})))
{
T t = value;
return t;
}
else static if (is(typeof({T t = void; t.tupleof[0] = value;})) && T.init.tupleof.length == 1)
{
T t = void;
t.tupleof[0] = value;
return t;
}
else
{
static assert(false, "Cannot initialize "~T.stringof~" from "~V.stringof);
}
}
}
template binOp(string op)
{
auto binOp(Lhs, Rhs)(Lhs lhs, Rhs rhs)
{
mixin("return lhs "~op~" rhs;");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment