public static OuterProduct Factor(Operand operand, Context context) { // TODO: Support symbolic factorization? For example, it would be quite useful // to be able to evaluate factor(n*(a^b)*n). I wouldn't expect any kind // of miracle, though, like finding (n*a*n)^(n*b*n). Sum multivector = CanonicalizeMultivector(operand); OuterProduct factorization = FactorMultivectorAsBlade(multivector, context); operand = Operand.ExhaustEvaluation(factorization.Copy(), context); Sum expansion = CanonicalizeMultivector(operand); if (expansion.operandList.Count != multivector.operandList.Count) { throw new MathException("The multivector is not a blade."); } // Note that this should work by the sorting performed by the sum operation. double commonRatio = 0.0; for (int i = 0; i < expansion.operandList.Count; i++) { Blade bladeA = multivector.operandList[i] as Blade; Blade bladeB = expansion.operandList[i] as Blade; if (!bladeA.IsLike(bladeB)) { throw new MathException("The multivector is not a blade."); } double ratio = 0.0; try { ratio = (bladeA.scalar as NumericScalar).value / (bladeB.scalar as NumericScalar).value; } catch (DivideByZeroException) { ratio = 1.0; } if (Double.IsNaN(ratio)) { ratio = 1.0; } if (commonRatio == 0.0) { commonRatio = ratio; } else if (Math.Abs(ratio - commonRatio) >= context.epsilon) { throw new MathException("The multivector is not a blade."); } } factorization.operandList.Insert(0, new NumericScalar(commonRatio)); return(factorization); }
public override Operand Explode(ITranslator translator, Context context) { OuterProduct outerProduct = new OuterProduct(); outerProduct.operandList.Add(translator.Translate(scalar.Copy(), context)); foreach (string vectorName in vectorList) { outerProduct.operandList.Add(translator.Translate(new Blade(vectorName), context)); } return(outerProduct); }
public static Operand CalculateJoin(List <Operand> operandList, Context context) { OuterProduct[] bladeArray = new OuterProduct[operandList.Count]; int j = -1; for (int i = 0; i < bladeArray.Length; i++) { try { bladeArray[i] = FactorBlade.Factor(operandList[i], context); if (j < 0 || bladeArray[i].Grade > bladeArray[j].Grade) { j = i; } } catch (MathException exc) { throw new MathException($"Failed to factor argument {i} as blade.", exc); } } OuterProduct join = bladeArray[j]; for (int i = 0; i < bladeArray.Length; i++) { if (i != j) { foreach (Operand vector in bladeArray[i].operandList) { Operand operand = Operand.ExhaustEvaluation(new Trim(new List <Operand>() { new OuterProduct(new List <Operand>() { vector.Copy(), join.Copy() }) }), context); if (!operand.IsAdditiveIdentity) { join.operandList.Add(vector); } } } } return(join); }
public override Operand EvaluationStep(Context context) { if (operandList.Count != 1) { throw new MathException(string.Format("Factor operation expects exactly one operand, got {0}.", operandList.Count)); } Operand operand = base.EvaluationStep(context); if (operand != null) { return(operand); } operand = operandList[0]; if (operand.Grade < 0) { throw new MathException("Unable to determine grade of argument."); } else if (operand.Grade == 0 || operand.Grade == 1) { return(operand); } OuterProduct factorization = Factor(operand, context); factorization.freezeFlags |= FreezeFlag.DISTRIBUTION; int count = context.ReturnBasisVectors().Count; Evaluate("del(" + string.Join(", ", Enumerable.Range(0, count).Select(i => $"@factor{i}")) + ")", context); // Provide a way to get at the individual factors. int j = 0; for (int i = 0; i < factorization.operandList.Count; i++) { if (factorization.operandList[i].Grade == 1) { context.operandStorage.SetStorage($"factor{j++}", factorization.operandList[i].Copy()); } } return(factorization); }
public override Operand EvaluationStep(Context context) { Operand operand = base.EvaluationStep(context); if (operand != null) { return(operand); } // To avoid infinite evaluation looping, we must apply... // 1) vB = v.B + v^B, and // 2) v^B = vB - v.B, // ...according to rules that dictate when and where they're appropriate. // Also to avoid infinite looping, the distributive property must take // precedence over anything we do here. // All reduction cases must be eliminated before it is safe to handle the expansion cases. for (int i = 0; i < operandList.Count - 1; i++) { Blade bladeA = operandList[i] as Blade; Blade bladeB = operandList[i + 1] as Blade; if (bladeA != null && bladeB != null && bladeA.Grade > 1 && bladeB.Grade > 1) { GeometricProduct geometricProduct; if (context.useOperandCache) { geometricProduct = new GeometricProduct(new List <Operand>() { new Blade(new NumericScalar(1.0), bladeA.vectorList.ToList()), new Blade(new NumericScalar(1.0), bladeB.vectorList.ToList()) }); string key = geometricProduct.Print(Format.PARSEABLE, context); Operand cachedResult = null; if (!context.operandCache.GetStorage(key, ref cachedResult)) { context.useOperandCache = false; cachedResult = Operand.ExhaustEvaluation(geometricProduct, context); context.useOperandCache = true; context.operandCache.SetStorage(key, cachedResult); } return(new GeometricProduct(new List <Operand>() { bladeA.scalar, bladeB.scalar, cachedResult })); } // Here our choice of which blade to reduce is arbitrary from a stand-point of correctness. // However, we might converge faster by choosing the blade with smaller grade. // Note there is also something arbitrary about how we're reducing the blades. int j = bladeA.Grade <= bladeB.Grade ? i : i + 1; Blade blade = operandList[j] as Blade; Blade subBlade = blade.MakeSubBlade(0); Blade vector = new Blade(blade.vectorList[0]); geometricProduct = new GeometricProduct(new List <Operand>() { vector, subBlade }); InnerProduct innerProduct = new InnerProduct(new List <Operand>() { vector.Copy(), subBlade.Copy() }); operandList[j] = new Sum(new List <Operand>() { geometricProduct, new GeometricProduct(new List <Operand>() { new NumericScalar(-1.0), innerProduct }) }); return(this); } } // All reduction cases eliminated, it is now safe to handle some expansion cases. for (int i = 0; i < operandList.Count - 1; i++) { Blade bladeA = operandList[i] as Blade; Blade bladeB = operandList[i + 1] as Blade; if (bladeA == null || bladeB == null) { continue; } if ((bladeA.Grade == 1 && bladeB.Grade > 1) || (bladeA.Grade > 1 && bladeB.Grade == 1)) { InnerProduct innerProduct = new InnerProduct(new List <Operand>() { bladeA, bladeB }); OuterProduct outerProduct = new OuterProduct(new List <Operand>() { bladeA.Copy(), bladeB.Copy() }); operandList[i] = new Sum(new List <Operand>() { innerProduct, outerProduct }); operandList.RemoveAt(i + 1); return(this); } } // It is now safe to handle the remaining expansion cases. for (int i = 0; i < operandList.Count - 1; i++) { Blade bladeA = operandList[i] as Blade; Blade bladeB = operandList[i + 1] as Blade; if (bladeA == null || bladeB == null) { continue; } if (bladeA.Grade == 1 && bladeB.Grade == 1) { operandList.RemoveAt(i + 1); GeometricProduct innerProduct = new GeometricProduct(new List <Operand>() { bladeA.scalar, bladeB.scalar, context.BilinearForm(bladeA.vectorList[0], bladeB.vectorList[0]) }); Blade outerProduct = new Blade(new GeometricProduct(new List <Operand>() { bladeA.scalar.Copy(), bladeB.scalar.Copy() })); outerProduct.vectorList.Add(bladeA.vectorList[0]); outerProduct.vectorList.Add(bladeB.vectorList[0]); operandList[i] = new Sum(new List <Operand>() { innerProduct, outerProduct }); return(this); } } return(null); }
// Note that factorizations of blades are not generally unique. // Here I'm just going to see if I can find any factorization. // My method here is the obvious one, and probably quite naive. // Lastly, the returned factorization, if any, will be correct up to scale. // It is up to the caller to determine the correct scale. public static OuterProduct FactorMultivectorAsBlade(Sum multivector, Context context) { if (!multivector.operandList.All(operand => operand is Blade)) { throw new MathException("Can only factor elements in multivector form."); } if (!multivector.operandList.All(blade => (blade as Blade).scalar is NumericScalar)) { throw new MathException("Cannot yet perform symbolic factorization of blades."); } OuterProduct factorization = new OuterProduct(); int grade = multivector.Grade; if (grade == -1) { throw new MathException("Could not determine grade of given element. It might not be homogeneous of a single grade."); } else if (grade == 0 || grade == 1) { factorization.operandList.Add(multivector.Copy()); } else { // Given a blade A of grade n>1 and any vector v such that v.A != 0, // our method here is based on the identity L*A = (v.A) ^ ((v.A).A), // where L is a non-zero scalar. Here, v.A is of grade n-1, and // (v.A).A is of grade 1. This suggests a recursive algorithm. // This all, however, assumes a purely euclidean geometric algebra. // For those involving null-vectors, the search for a useful probing // vector requires that we take the algorithm to its conclusion before // we know if a given probing vector worked. bool foundFactorization = false; List <string> basisVectorList = context.ReturnBasisVectors(); foreach (Sum probingVector in GenerateProbingVectors(basisVectorList)) { Operand reduction = Operand.ExhaustEvaluation(new InnerProduct(new List <Operand>() { probingVector, multivector.Copy() }), context); if (!reduction.IsAdditiveIdentity) { Sum reducedMultivector = CanonicalizeMultivector(reduction); Operand vectorFactor = Operand.ExhaustEvaluation(new InnerProduct(new List <Operand>() { reducedMultivector, multivector }), context); if (vectorFactor.Grade == 1) // I'm pretty sure that this check is not necessary in a purely euclidean GA. { OuterProduct subFactorization = FactorMultivectorAsBlade(reducedMultivector, context); if (subFactorization.Grade != grade - 1) { throw new MathException($"Expected sub-factorization to be of grade {grade - 1}."); } factorization.operandList = subFactorization.operandList; factorization.operandList.Add(vectorFactor); // In a purely euclidean geometric algebra, this check is also not necessary. Operand expansion = Operand.ExhaustEvaluation(factorization.Copy(), context); if (!expansion.IsAdditiveIdentity) { foundFactorization = true; break; } } } } if (!foundFactorization) { throw new MathException("Failed to find a vector factor of the given multivector. This does not necessarily mean that the multivector doesn't factor as a blade."); } } return(factorization); }
// The algorithm implemented here comes straight out of Christian Perwass' book. // TODO: Also, as noted by Perwass, this algorithm doesn't work for null versors. public static GeometricProduct Factor(Operand operand, Context context) { Operand currentVersor = operand.Copy(); GeometricProduct versorFactorization = new GeometricProduct(); while (true) { HashSet <int> gradeSet = DiscoverGrades(currentVersor, context); int maxGrade = gradeSet.ToList().Aggregate(0, (currentMaxGrade, grade) => grade > currentMaxGrade ? grade : currentMaxGrade); if (maxGrade <= 0) { break; } Operand blade = Operand.ExhaustEvaluation(new GradePart(new List <Operand>() { currentVersor.Copy(), new NumericScalar(maxGrade) }), context); OuterProduct bladeFactorization = null; try { bladeFactorization = FactorBlade.Factor(blade, context); } catch (MathException exc) { throw new MathException("Highest-grade-part of multivector does not factor as a blade.", exc); } Operand nonNullVector = null, magnitude = null; foreach (Operand vector in bladeFactorization.operandList) { if (vector.Grade == 1) { magnitude = Operand.ExhaustEvaluation(new Trim(new List <Operand>() { new Magnitude(new List <Operand>() { vector.Copy() }) }), context); if (!magnitude.IsAdditiveIdentity) { nonNullVector = vector; break; } } } if (nonNullVector == null) { throw new MathException("Failed to find non-null vector in blade factorization."); } Operand unitVector = Operand.ExhaustEvaluation(new Normalize(new List <Operand>() { nonNullVector }), context); versorFactorization.operandList.Insert(0, unitVector); currentVersor = Operand.ExhaustEvaluation(new Trim(new List <Operand>() { new GeometricProduct(new List <Operand>() { currentVersor, unitVector.Copy() }) }), context); } versorFactorization.operandList.Add(currentVersor); return(versorFactorization); }
// Note that here that we do not consider unary operator precedence. // So for example, if we have -1~, we don't try to choose between (-1)~ and -(1~), // though both are the same in this particular case. Also, we don't recognize unary // operator stacking. E.g., -~1 will not parse as -(~1) would. In short, working with // unary operators will sometimes requires parenthesis. public Operand BuildOperandTree(List <Token> tokenList) { while (tokenList.Count > 0) { int count = tokenList.Count; if (tokenList[0].kind == Token.Kind.LEFT_PARAN && tokenList[0].paranType == Token.ParanType.ROUND) { int i = FindMatchingParan(tokenList, 0); if (i == tokenList.Count - 1) { tokenList.RemoveAt(0); tokenList.RemoveAt(tokenList.Count - 1); } } if (tokenList.Count == count) { break; } } if (tokenList.Count == 0) { throw new ParseException("Encountered empty token list."); } if (tokenList.Count == 1) { Token token = tokenList[0]; switch (token.kind) { case Token.Kind.SYMBOL: { if (token.data[0] == '@') { return(new Variable(token.data.Substring(1))); } if (token.data[0] == '$') { return(new SymbolicScalarTerm(token.data.Substring(1))); } string vectorName = token.data; bool isBasisVector = false; List <string> basisVectorList = context.ReturnBasisVectors(); if (basisVectorList != null) { isBasisVector = basisVectorList.Contains(vectorName); if (basisVectorsOnly && !isBasisVector) { Sum sum = new Sum(); foreach (string basisVectorName in basisVectorList) { InnerProduct dot = new InnerProduct(new List <Operand>() { new Blade(vectorName), new Blade(basisVectorName) }); sum.operandList.Add(new GeometricProduct(new List <Operand>() { dot, new Blade(basisVectorName) })); } return(sum); } } generatedSymbolicVector = !isBasisVector; return(new Blade(vectorName)); } case Token.Kind.NUMBER: { double value; if (!double.TryParse(token.data, out value)) { throw new ParseException(string.Format("Encountered non-parsable number ({0}).", token.data)); } return(new NumericScalar(value)); } default: { throw new ParseException(string.Format("Encountered lone token ({0}) that isn't handled.", token.data)); } } } else if (tokenList[0].kind == Token.Kind.OPERATOR && (ParansMatch(tokenList, 1, tokenList.Count - 1) || tokenList.Count == 2 || IsFunctionPattern(tokenList.Skip(1).ToList()))) { Token token = tokenList[0]; if (token.data == "-") { return(new GeometricProduct(new List <Operand>() { new Blade(-1.0), BuildOperandTree(tokenList.Skip(1).ToList()) })); } throw new ParseException(string.Format("Encounterd unary operator ({0}) that isn't recognized on the left.", token.data)); } else if (tokenList[tokenList.Count - 1].kind == Token.Kind.OPERATOR && (ParansMatch(tokenList, 0, tokenList.Count - 2) || tokenList.Count == 2 || IsFunctionPattern(tokenList.Take(tokenList.Count - 1).ToList()))) { Token token = tokenList[tokenList.Count - 1]; if (token.data == "~") { return(new Reverse(new List <Operand>() { BuildOperandTree(tokenList.Take(tokenList.Count - 1).ToList()) })); } throw new ParseException(string.Format("Encountered unary operator ({0}) that isn't recognized on the right.", token.data)); } else if (IsFunctionPattern(tokenList)) { Token token = tokenList[0]; Operation operation = context.CreateFunction(token.data); if (operation == null) { throw new ParseException(string.Format("Encountered unknown function \"{0}\".", token.data)); } List <List <Token> > argumentList = ParseListOfTokenLists(tokenList.Skip(2).Take(tokenList.Count - 3).ToList()); foreach (List <Token> subTokenList in argumentList) { operation.operandList.Add(BuildOperandTree(subTokenList)); } return(operation); } else if (tokenList[0].paranType == Token.ParanType.SQUARE && ParansMatch(tokenList, 0, tokenList.Count - 1)) { List <List <Operand> > listOfOperandLists = new List <List <Operand> >(); List <List <Token> > rowList = ParseListOfTokenLists(tokenList.Skip(1).Take(tokenList.Count - 2).ToList()); foreach (List <Token> rowTokenList in rowList) { listOfOperandLists.Add(new List <Operand>()); List <List <Token> > colList; if (rowTokenList[0].paranType == Token.ParanType.SQUARE && ParansMatch(rowTokenList, 0, rowTokenList.Count - 1)) { colList = ParseListOfTokenLists(rowTokenList.Skip(1).Take(rowTokenList.Count - 2).ToList()); } else { colList = new List <List <Token> >() { rowTokenList } }; foreach (List <Token> subTokenList in colList) { listOfOperandLists[listOfOperandLists.Count - 1].Add(BuildOperandTree(subTokenList)); } } return(new Matrix(listOfOperandLists)); } else { // Our goal here is to find an operator of lowest precedence. It will never be // at the very beginning or end of the entire token sequence. List <Token> opTokenList = null; foreach (Token token in WalkTokensSkipSubexpressions(tokenList)) { if (token.kind != Token.Kind.OPERATOR) { continue; } // Only unary operators can be at the start or end of the token list. if (tokenList.IndexOf(token) == 0 || tokenList.IndexOf(token) == tokenList.Count - 1) { continue; } // Ignore unary operators on left. if (token.data == "-" && tokenList[tokenList.IndexOf(token) - 1].kind == Token.Kind.OPERATOR) { continue; } // Ignore unary operators on right. if (token.data == "~" && tokenList[tokenList.IndexOf(token) + 1].kind == Token.Kind.OPERATOR) { continue; } // At this point we should be reasonably sure it's a binary operator we're looking at. if (opTokenList == null || PrecedenceLevel(opTokenList[0].data) > PrecedenceLevel(token.data)) { opTokenList = new List <Token>() { token } } ; else if (opTokenList != null && PrecedenceLevel(opTokenList[0].data) == PrecedenceLevel(token.data)) { opTokenList.Add(token); } } if (opTokenList == null) { throw new ParseException("Did not encounter binary operator token."); } Token operatorToken = null; switch (OperatorAssociativity(opTokenList[0].data)) { case Associativity.LEFT_TO_RIGHT: operatorToken = opTokenList[opTokenList.Count - 1]; break; case Associativity.RIGHT_TO_LEFT: operatorToken = opTokenList[0]; break; } Operation operation = null; if (operatorToken.data == ";") { operation = new Sequence(); } else if (operatorToken.data == "+" || operatorToken.data == "-") { operation = new Sum(); } else if (operatorToken.data == "*" || operatorToken.data == "/") { operation = new GeometricProduct(); } else if (operatorToken.data == ".") { operation = new InnerProduct(); } else if (operatorToken.data == "^") { operation = new OuterProduct(); } else if (operatorToken.data == "=") { operation = new Assignment(); } else if (operatorToken.data == ":=") { operation = new Assignment(false); } if (operation == null) { throw new ParseException(string.Format("Did not recognized operator token ({0}).", operatorToken.data)); } int i = tokenList.IndexOf(operatorToken); Operand leftOperand = BuildOperandTree(tokenList.Take(i).ToList()); Operand rightOperand = BuildOperandTree(tokenList.Skip(i + 1).Take(tokenList.Count - 1 - i).ToList()); operation.operandList.Add(leftOperand); if (operatorToken.data == "-") { operation.operandList.Add(new GeometricProduct(new List <Operand>() { new NumericScalar(-1.0), rightOperand })); } else if (operatorToken.data == "/") { operation.operandList.Add(new Inverse(new List <Operand>() { rightOperand })); } else { operation.operandList.Add(rightOperand); } return(operation); } }