Last active
February 9, 2018 17:04
-
-
Save Biotronic/833680b37d4afe774c8562fd21554c6b to your computer and use it in GitHub Desktop.
A template for defining algebras
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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