diff --git a/Symbolism/Symbolism.cs b/Symbolism/Symbolism.cs index e83d385..b038f36 100644 --- a/Symbolism/Symbolism.cs +++ b/Symbolism/Symbolism.cs @@ -23,6 +23,8 @@ namespace Symbolism { public abstract class MathObject { + public const string MathmlDeclaration = "{0}"; + public const string EmptyMathmlDeclaration = "{}"; ////////////////////////////////////////////////////////////////////// public static implicit operator MathObject(int n) => new Integer(n); @@ -137,6 +139,13 @@ public override string ToString() throw new Exception(); } + public virtual string ToMathml(bool useMathmlDeclaration = false) => string.Empty; + + protected static string ToMathml(string result, bool useMathmlDeclaration = false) + { + return useMathmlDeclaration ? string.Format(MathmlDeclaration, result) : result; + } + public virtual MathObject Numerator() => this; public virtual MathObject Denominator() => 1; @@ -145,6 +154,7 @@ public override bool Equals(object obj) { throw new Exception("MathObject.Equals called - abstract class"); } public override int GetHashCode() => base.GetHashCode(); + } public class Equation : MathObject @@ -171,6 +181,15 @@ public override string FullForm() throw new Exception(); } + public override string ToMathml(bool useMathmlDeclaration = false) + { + if (Operator == Operators.Equal) return ToMathml(a.ToMathml() + "=" + b.ToMathml(), useMathmlDeclaration); + if (Operator == Operators.NotEqual) return ToMathml(a.ToMathml() + "" + b.ToMathml(), useMathmlDeclaration); + if (Operator == Operators.LessThan) return ToMathml(a.ToMathml() + "<" + b.ToMathml(), useMathmlDeclaration); + if (Operator == Operators.GreaterThan) return ToMathml(a.ToMathml() + ">" + b.ToMathml(), useMathmlDeclaration); + throw new Exception(); + } + public override bool Equals(object obj) => obj is Equation && a.Equals((obj as Equation).a) && @@ -272,7 +291,7 @@ public class Integer : Number public Integer(int n) { val = n; } public Integer(BigInteger n) { val = n; } - + public static implicit operator Integer(BigInteger n) => new Integer(n); // public static MathObject operator *(MathObject a, MathObject b) => new Product(a, b).Simplify(); @@ -288,6 +307,11 @@ public class Integer : Number public override int GetHashCode() => val.GetHashCode(); public override DoubleFloat ToDouble() => new DoubleFloat((double)val); + + public override string ToMathml(bool useMathmlDeclaration = false) + { + return ToMathml($"{val}", useMathmlDeclaration); + } } public class DoubleFloat : Number @@ -321,6 +345,13 @@ public override bool Equals(object obj) public override int GetHashCode() => val.GetHashCode(); public override DoubleFloat ToDouble() => this; + + public override string ToMathml(bool useMathmlDeclaration = false) + { + var arr = val.ToString().Split('.'); + var rest = arr.Length > 1 ? $".{arr[1]}" : string.Empty; + return ToMathml($"{arr[0]}{rest}", useMathmlDeclaration); + } } public class Fraction : Number @@ -346,16 +377,116 @@ public override bool Equals(object obj) => public override MathObject Numerator() => numerator; public override MathObject Denominator() => denominator; + + public override string ToMathml(bool useMathmlDeclaration = false) + { + if (new Norm(numerator).Simplify() < denominator || numerator == denominator) + { + return ToMathml($"{numerator.ToMathml()}{denominator.ToMathml()}", useMathmlDeclaration); + } + + BigInteger rem; + + var quot = BigInteger.DivRem(numerator.val, denominator.val, out rem); + return new MixedNumber(new Integer(quot), new Norm(rem).Simplify() as Integer, denominator).ToMathml(useMathmlDeclaration); + } + } + + public class MixedNumber : Number + { + public Integer quotient; + public Integer numerator; + public Integer denominator; + + public MixedNumber(Integer q, Integer a, Integer b) + { + quotient = q; + numerator = a; + denominator = b; + } + + public override string FullForm() => $"({quotient} + {numerator} / {denominator})"; + + public override string ToMathml(bool useMathmlDeclaration = false) + { + return ToMathml($"{quotient.ToMathml()}{numerator.ToMathml()}{denominator.ToMathml()}", useMathmlDeclaration); + } + + public Fraction ToFraction() => new Fraction((quotient * denominator + numerator) as Integer, denominator); + public override DoubleFloat ToDouble() => this.ToFraction().ToDouble(); + ////////////////////////////////////////////////////////////////////// + + public override bool Equals(object obj) => + quotient == (obj as MixedNumber)?.quotient + && + numerator == (obj as Fraction)?.numerator + && + denominator == (obj as Fraction)?.denominator; + + public override int GetHashCode() => new { quotient, numerator, denominator }.GetHashCode(); + + public override MathObject Numerator() => numerator; + + public MathObject Quotient() => quotient; + + public override MathObject Denominator() => denominator; + } + + + public class Norm : Function + { + public Norm(MathObject x) : base("norm", null, new List { x}) + { + this.proc = proc??NormProc; + } + + MathObject NormProc(MathObject[] ls) + { + if (ls[0] == null) + { + return 0; + } + + if (ls[0] is Number) + { + if ((Number)ls[0] > new DoubleFloat(0) || (Number)ls[0] == new DoubleFloat(0)) + { + return ls[0]; + } + + return -1 * ls[0]; + } + + if (ls[0] is Product && ((Product)ls[0]).elts[0] == -1) + { + return new Norm(-1 * ls[0]); + } + + return new Norm(ls[0]); + } + + //public override bool Equals(object obj) => + // GetType() == obj.GetType() && + // name == (obj as Function).name && + // this.Simplify() == ((Norm)obj).Simplify(); + + public override int GetHashCode() => new { name, args }.GetHashCode(); + + public override string ToMathml(bool useMathmlDeclaration = false) + { + return ToMathml($"{args[0].ToMathml()}", useMathmlDeclaration); + } } + public static class Rational { static BigInteger Div(BigInteger a, BigInteger b) { BigInteger rem; return BigInteger.DivRem(a, b, out rem); } - + static BigInteger Rem(BigInteger a, BigInteger b) { BigInteger rem; BigInteger.DivRem(a, b, out rem); return rem; } - + static BigInteger Gcd(BigInteger a, BigInteger b) { BigInteger r; @@ -377,13 +508,13 @@ public static MathObject SimplifyRationalNumber(MathObject u) var u_ = (Fraction)u; var n = u_.numerator.val; var d = u_.denominator.val; - + if (Rem(n, d) == 0) return Div(n, d); var g = Gcd(n, d); - + if (d > 0) return new Fraction(Div(n, g), Div(d, g)); - + if (d < 0) return new Fraction(Div(-n, g), Div(-d, g)); } @@ -437,7 +568,7 @@ public static Integer Denominator(MathObject u) throw new Exception(); } - public static Fraction EvaluateSum(MathObject v, MathObject w) => + public static Fraction EvaluateSum(MathObject v, MathObject w) => // a / b + c / d // a d / b d + c b / b d @@ -446,13 +577,13 @@ public static Fraction EvaluateSum(MathObject v, MathObject w) => new Fraction( Numerator(v) * Denominator(w) + Numerator(w) * Denominator(v), Denominator(v) * Denominator(w)); - + public static Fraction EvaluateDifference(MathObject v, MathObject w) => new Fraction( Numerator(v) * Denominator(w) - Numerator(w) * Denominator(v), Denominator(v) * Denominator(w)); - public static Fraction EvaluateProduct(MathObject v, MathObject w) => + public static Fraction EvaluateProduct(MathObject v, MathObject w) => new Fraction( Numerator(v) * Numerator(w), Denominator(v) * Denominator(w)); @@ -474,7 +605,7 @@ public static MathObject EvaluatePower(MathObject v, BigInteger n) if (n > 0) return EvaluateProduct(EvaluatePower(v, n - 1), v); if (n == 0) return 1; - + if (n == -1) return new Fraction(Denominator(v), Numerator(v)); if (n < -1) @@ -484,7 +615,7 @@ public static MathObject EvaluatePower(MathObject v, BigInteger n) return EvaluatePower(s, -n); } } - + if (n >= 1) return 0; if (n <= 0) return new Undefined(); @@ -507,7 +638,7 @@ public static MathObject SimplifyRNERec(MathObject u) var v = SimplifyRNERec(((Difference)u).elts[0]); if (v == new Undefined()) return v; - + return EvaluateProduct(-1, v); } @@ -591,6 +722,11 @@ public class Symbol : MathObject public override bool Equals(Object obj) => obj is Symbol ? name == (obj as Symbol).name : false; + + public override string ToMathml(bool useMathmlDeclaration = false) + { + return ToMathml(string.Format("{0}", name), useMathmlDeclaration); + } } public static class ListConstructor @@ -602,9 +738,9 @@ public static class ListConstructor public static class ListUtils { - public static ImmutableList Cons(this ImmutableList obj, MathObject elt) => + public static ImmutableList Cons(this ImmutableList obj, MathObject elt) => obj.Insert(0, elt); - + public static ImmutableList Cdr(this ImmutableList obj) => obj.RemoveAt(0); public static bool equal(ImmutableList a, ImmutableList b) @@ -625,11 +761,11 @@ public class Function : MathObject { public delegate MathObject Proc(params MathObject[] ls); - public readonly String name; + public String name { get; protected set; } - public readonly Proc proc; - - public readonly ImmutableList args; + public Proc proc { get; protected set; } + + public ImmutableList args { get; protected set; } public Function(string name, Proc proc, IEnumerable args) { @@ -637,7 +773,7 @@ public Function(string name, Proc proc, IEnumerable args) this.proc = proc; this.args = ImmutableList.CreateRange(args); } - + public override bool Equals(object obj) => GetType() == obj.GetType() && name == (obj as Function).name && @@ -659,7 +795,7 @@ public static class FunctionExtensions // // return new T() { args = obj.args.Select(proc).ToList() }.Simplify(); // // return - + //} } @@ -675,7 +811,7 @@ static MathObject AndProc(MathObject[] ls) if (ls.Any(elt => elt == true)) return new And(ls.Where(elt => elt != true).ToArray()).Simplify(); - + if (ls.Any(elt => elt is And)) { var items = new List(); @@ -692,7 +828,7 @@ static MathObject AndProc(MathObject[] ls) return new And(ls); } - + public And(params MathObject[] ls) : base("and", AndProc, ls) { } public And() : base("and", AndProc, new List()) { } @@ -704,9 +840,14 @@ public MathObject Add(MathObject obj) => public MathObject AddRange(IEnumerable ls) => And.FromRange(args.AddRange(ls)).Simplify(); - - public MathObject Map(Func proc) => + + public MathObject Map(Func proc) => And.FromRange(args.Select(proc)).Simplify(); + + public override string ToMathml(bool useMathmlDeclaration = false) + { + return ToMathml($"{string.Join(",", args.ConvertAll(arg => arg.ToMathml()))}", useMathmlDeclaration); + } } public class Or : Function @@ -723,7 +864,7 @@ static MathObject OrProc(params MathObject[] ls) if (ls.Any(elt => (elt is Bool) && (elt as Bool).val)) return new Bool(true); if (ls.All(elt => (elt is Bool) && (elt as Bool).val == false)) return new Bool(false); - + if (ls.Any(elt => elt is Or)) { var items = new List(); @@ -733,13 +874,13 @@ static MathObject OrProc(params MathObject[] ls) if (elt is Or) items.AddRange((elt as Or).args); else items.Add(elt); } - + return Or.FromRange(items).Simplify(); } return new Or(ls); } - + public Or(params MathObject[] ls) : base("or", OrProc, ls) { } public Or() : base("or", OrProc, new List()) { } @@ -747,6 +888,11 @@ public Or(params MathObject[] ls) : base("or", OrProc, ls) { } public static Or FromRange(IEnumerable ls) => new Or(ls.ToArray()); public MathObject Map(Func proc) => Or.FromRange(args.Select(proc)).Simplify(); + + public override string ToMathml(bool useMathmlDeclaration = false) + { + return ToMathml($"{string.Join(",", args.ConvertAll(arg => arg.ToMathml()))}", useMathmlDeclaration); + } } public static class OrderRelation @@ -759,7 +905,7 @@ public static MathObject Term(this MathObject u) { if (u is Product && ((Product)u).elts[0] is Number) return Product.FromRange((u as Product).elts.Cdr()); - // return (u as Product).Cdr() + // return (u as Product).Cdr() if (u is Product) return u; @@ -829,7 +975,7 @@ public static bool Compare(MathObject u, MathObject v) return O3( (u as Product).elts.Reverse(), (v as Product).elts.Reverse()); - + if (u is Sum && v is Sum) return O3( (u as Sum).elts.Reverse(), @@ -938,7 +1084,7 @@ public MathObject Simplify() return Rational.SimplifyRNE(new Power(v, n)); if (v is DoubleFloat && w is Integer) - return new DoubleFloat(Math.Pow(((DoubleFloat)v).val, (double) ((Integer)w).val)); + return new DoubleFloat(Math.Pow(((DoubleFloat)v).val, (double)((Integer)w).val)); if (v is DoubleFloat && w is Fraction) return new DoubleFloat(Math.Pow(((DoubleFloat)v).val, ((Fraction)w).ToDouble().val)); @@ -954,7 +1100,7 @@ public MathObject Simplify() if (v is Product && w is Integer) return (v as Product).Map(elt => elt ^ w); - + return new Power(v, w); } @@ -977,12 +1123,30 @@ public override MathObject Denominator() } public override int GetHashCode() => new { bas, exp }.GetHashCode(); + + public override string ToMathml(bool useMathmlDeclaration = false) + { + if (exp == new Integer(1) / new Integer(2)) + { + return ToMathml($"{bas.ToMathml()}", useMathmlDeclaration); + } + + if (exp is Fraction && (exp as Fraction).numerator == 1) + { + return ToMathml($"{bas.ToMathml()}{(exp as Fraction).denominator.ToMathml()}", useMathmlDeclaration); + } + + return ToMathml(string.Format("{0}{1}", + bas.Precedence() < Precedence() ? $"{bas.ToMathml()}" : $"{bas.ToMathml()}", + exp.Precedence() < Precedence() ? $"{exp.ToMathml()}" : $"{exp.ToMathml()}"), useMathmlDeclaration); + + } } public class Product : MathObject { public readonly ImmutableList elts; - + public Product(params MathObject[] ls) => elts = ImmutableList.Create(ls); public static Product FromRange(IEnumerable ls) => new Product(ls.ToArray()); @@ -1050,7 +1214,7 @@ static ImmutableList SimplifyDoubleNumberProduct(DoubleFloat a, Numb if (b is Integer) val = a.val * (double)((Integer)b).val; if (b is Fraction) val = a.val * ((Fraction)b).ToDouble().val; - + if (val == 1.0) return ImmutableList.Create(); return ImList(new DoubleFloat(val)); @@ -1084,7 +1248,7 @@ public static ImmutableList RecursiveSimplify(ImmutableList(); return ImList(P); @@ -1099,7 +1263,7 @@ public static ImmutableList RecursiveSimplify(ImmutableList(); return ImList(res); @@ -1137,9 +1301,9 @@ public MathObject Simplify() // Without the below, the following throws an exception: // sqrt(a * b) * (sqrt(a * b) / a) / c - + if (res.Any(elt => elt is Product)) return Product.FromRange(res).Simplify(); - + return Product.FromRange(res); } @@ -1151,13 +1315,21 @@ public override MathObject Denominator() => public MathObject Map(Func proc) => Product.FromRange(elts.Select(proc)).Simplify(); + + public override string ToMathml(bool useMathmlDeclaration = false) + { + return ToMathml(string.Join("·", elts.ConvertAll(elt => elt.Precedence() < Precedence() ? $"{elt.ToMathml()}" : $"{elt.ToMathml()}")), useMathmlDeclaration); + } } public class Sum : MathObject { public readonly ImmutableList elts; - public Sum(params MathObject[] ls) { elts = ImmutableList.Create(ls); } + public Sum(params MathObject[] ls) + { + elts = ImmutableList.Create(ls); + } public static Sum FromRange(IEnumerable ls) => new Sum(ls.ToArray()); @@ -1201,7 +1373,7 @@ static ImmutableList SimplifyDoubleNumberSum(DoubleFloat a, Number b if (b is Fraction) val = a.val + ((Fraction)b).ToDouble().val; if (val == 0.0) return ImmutableList.Create(); - + return ImmutableList.Create(new DoubleFloat(val)); } @@ -1239,7 +1411,7 @@ static ImmutableList RecursiveSimplify(ImmutableList elt (elts[1] is Integer || elts[1] is Fraction)) { var P = Rational.SimplifyRNE(new Sum(elts[0], elts[1])); - + if (P == 0) return ImmutableList.Create(); return ImList(P); @@ -1312,6 +1484,37 @@ public override string StandardForm() public MathObject Map(Func proc) => Sum.FromRange(elts.Select(proc)).Simplify(); + + public override string ToMathml(bool useMathmlDeclaration = false) + { + string mathmlResult = string.Empty; + int count = 0; + foreach (var item in elts) + { + if (item is Product && (item as Product).elts[0] is Number) + { + if (((item as Product).elts[0] as Number).ToDouble().val < 0) + { + mathmlResult = string.Concat(mathmlResult, "-", (-1 * item).ToMathml()); + } + else + { + mathmlResult = string.Concat(mathmlResult, count == 0 ? string.Empty : "+", item.ToMathml()); + } + } + else + { + mathmlResult = string.Concat(mathmlResult, count == 0 ? string.Empty : "+", item.ToMathml()); + } + + count++; + } + + var result = mathmlResult; + return ToMathml(result, useMathmlDeclaration); + + //return String.Join("+", elts.ConvertAll(elt => elt.Precedence() < Precedence() ? $"{elt.ToMathml()})" : $"{elt.ToMathml()}")); + } } class Difference : MathObject @@ -1333,7 +1536,7 @@ public MathObject Simplify() class Quotient : MathObject { public readonly ImmutableList elts; - + public Quotient(params MathObject[] ls) => elts = ImmutableList.Create(ls); public MathObject Simplify() => elts[0] * (elts[1] ^ -1); diff --git a/Symbolism/Utils.cs b/Symbolism/Utils.cs index 73d8d6a..ecaee28 100644 --- a/Symbolism/Utils.cs +++ b/Symbolism/Utils.cs @@ -6,42 +6,18 @@ namespace Symbolism.Utils { public static class Extensions { - public static T Disp(this T obj) - { - Console.WriteLine(obj); - return obj; - } - - public static T Disp(this T obj, string format) - { - Console.WriteLine(string.Format(format, obj)); - return obj; - } - - public static MathObject DispLong(this MathObject obj, int indent = 0, bool comma = false) - { - if (obj is Or || obj is And) - { - Console.WriteLine(new String(' ', indent) + (obj as Function).name + "("); - - var i = 0; - - foreach (var elt in (obj as Function).args) - { - if (i < (obj as Function).args.Count - 1) - elt.DispLong(indent + 2, comma: true); - else - elt.DispLong(indent + 2); - - i++; - } - - Console.WriteLine(new String(' ', indent) + ")" + (comma ? "," : "")); - } - - else Console.WriteLine(new String(' ', indent) + obj + (comma ? "," : "")); - - return obj; - } + //public static T Disp(this T obj) + //{ + // Console.WriteLine(obj); + // return obj; + //} + + //public static T Disp(this T obj, string format) + //{ + // Console.WriteLine(string.Format(format, obj)); + // return obj; + //} + + } } diff --git a/Tests/Tests.cs b/Tests/Tests.cs index 842bea9..1e77204 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -6776,5 +6776,35 @@ public void PSE_5E_Example_8_5() .AssertEqTo(ΔE == -302.0); } } + + [Fact] + public void SummeryTest() + { + var expected = "5+6"; + var mo = new Sum(new Integer(5), 6); + var mathml = mo.ToMathml(true); + Assert.Equal(expected, mathml); + + expected = "x+y"; + mo = new Sum(new Symbol("x"), new Symbol("y")); + mathml = mo.ToMathml(true); + Assert.Equal(expected, mathml); + + } + + [Fact] + public void DotTest() + { + var expected = "5·6"; + var mo = new Product(new Integer(5), 6); + var mathml = mo.ToMathml(true); + Assert.Equal(expected, mathml); + + expected = "x·y·z"; + mo = new Product(new Symbol("x"), new Symbol("y"), new Symbol("z")); + mathml = mo.ToMathml(true); + Assert.Equal(expected, mathml); + + } } } diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index afddea6..fb9dfa2 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.2 @@ -7,9 +7,12 @@ - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive +