From 66a0f8ebe81a3057de6d6bef104ac6240befcb4c Mon Sep 17 00:00:00 2001 From: Hadrian Tang Date: Sat, 17 Jan 2026 01:14:05 +0800 Subject: [PATCH 1/2] Place calculus operator priority between addition/subtraction and multiplication/division in Latexise --- .../Entity.Continuous.Calculus.Classes.cs | 6 +-- .../Entity.Continuous.Definition.cs | 24 ++++++---- .../Core/Entity/Entity.Definition.cs | 3 +- .../Functions/Output/Latex.Definition.cs | 5 ++ .../Output/Latex/Latex.Arithmetics.Classes.cs | 44 ++++++++--------- .../Output/Latex/Latex.Calculus.Classes.cs | 34 +++++-------- .../Output/Latex/Latex.Discrete.Classes.cs | 30 ++++++------ .../Output/Latex/Latex.Omni.Classes.cs | 18 +++---- .../Tests/UnitTests/Convenience/LatexTest.cs | 48 +++++++++++-------- 9 files changed, 111 insertions(+), 101 deletions(-) diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs index 826c98cd5..ffb5e2740 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs @@ -16,7 +16,7 @@ partial record Entity /// /// A node of derivative /// - public sealed partial record Derivativef(Entity Expression, Entity Var, int Iterations) : Function + public sealed partial record Derivativef(Entity Expression, Entity Var, int Iterations) : CalculusOperator(Expression, Var) { /// Reuse the cache by returning the same object if possible private Derivativef New(Entity expression, Entity var) => @@ -32,7 +32,7 @@ public override Entity Replace(Func func) => /// /// A node of integral /// - public sealed partial record Integralf(Entity Expression, Entity Var, int Iterations) : Function + public sealed partial record Integralf(Entity Expression, Entity Var, int Iterations) : CalculusOperator(Expression, Var) { /// Reuse the cache by returning the same object if possible private Integralf New(Entity expression, Entity var) => @@ -48,7 +48,7 @@ public override Entity Replace(Func func) => /// /// A node of limit /// - public sealed partial record Limitf(Entity Expression, Entity Var, Entity Destination, ApproachFrom ApproachFrom) : Function + public sealed partial record Limitf(Entity Expression, Entity Var, Entity Destination, ApproachFrom ApproachFrom) : CalculusOperator(Expression, Var) { /// Reuse the cache by returning the same object if possible private Limitf New(Entity expression, Entity var, Entity destination, ApproachFrom approachFrom) => diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Definition.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Definition.cs index de10e4871..76b8e37da 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Definition.cs @@ -17,12 +17,27 @@ public abstract partial record ContinuousNode : Entity } + /// + /// Describes any node that is a function (e. g. sin, cos, etc.) or calculus operator (e. g. derivative, integral, limit) + /// but not an arithmetic operator or leaf + /// + public abstract record Function : ContinuousNode + { + internal override Priority Priority => Priority.Func; + } + /// /// Describes any function that is related to trigonometry /// public abstract record TrigonometricFunction : Function { + } + /// + /// Describes any calculus operator + /// + public abstract partial record CalculusOperator(Entity Expression, Entity Var) : Function + { } /// @@ -100,14 +115,5 @@ public abstract record TrigonometricFunction : Function public Entity Signum() => new Signumf(this); /// public Entity Abs() => new Absf(this); - - /// - /// Describes any node that is a function (e. g. sin, cos, etc.) - /// but not an operator or leaf - /// - public abstract record Function : ContinuousNode - { - internal override Priority Priority => Priority.Func; - } } } diff --git a/Sources/AngouriMath/Core/Entity/Entity.Definition.cs b/Sources/AngouriMath/Core/Entity/Entity.Definition.cs index 5400575a2..a2fafd958 100644 --- a/Sources/AngouriMath/Core/Entity/Entity.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Entity.Definition.cs @@ -47,13 +47,14 @@ internal enum Priority Sum = 20 | NumericalOperation, Minus = 20 | NumericalOperation, + /// For text formats, is used instead. + LatexCalculusOperation = 30 | NumericalOperation, Mul = 40 | NumericalOperation, Div = 40 | NumericalOperation, Pow = 60 | NumericalOperation, Factorial = 70 | NumericalOperation, Func = 80 | NumericalOperation, - Leaf = 100 | NumericalOperation, } #pragma warning restore CA1069 // Enums values should not be duplicated diff --git a/Sources/AngouriMath/Functions/Output/Latex.Definition.cs b/Sources/AngouriMath/Functions/Output/Latex.Definition.cs index af3c86388..5eaaa03c8 100644 --- a/Sources/AngouriMath/Functions/Output/Latex.Definition.cs +++ b/Sources/AngouriMath/Functions/Output/Latex.Definition.cs @@ -45,6 +45,11 @@ partial record Entity : ILatexiseable /// /// public abstract string Latexise(); + /// + /// Calculus operators, unlike other functions, have a between addition/subtraction + /// and multiplication/division which is different from . + /// + internal virtual Priority LatexPriority => Priority; /// Returns the expression in LaTeX (for example, a / b -> \frac{a}{b}) /// Whether to wrap it with parentheses diff --git a/Sources/AngouriMath/Functions/Output/Latex/Latex.Arithmetics.Classes.cs b/Sources/AngouriMath/Functions/Output/Latex/Latex.Arithmetics.Classes.cs index 82396cc91..1a57a32a9 100644 --- a/Sources/AngouriMath/Functions/Output/Latex/Latex.Arithmetics.Classes.cs +++ b/Sources/AngouriMath/Functions/Output/Latex/Latex.Arithmetics.Classes.cs @@ -15,8 +15,8 @@ public partial record Sumf { /// public override string Latexise() => - Augend.Latexise(Augend.Priority < Priority) - + (Addend.Latexise(Addend.Priority < Priority) is var addend && addend.StartsWith("-") + Augend.Latexise(Augend.LatexPriority < LatexPriority) + + (Addend.Latexise(Addend.LatexPriority < LatexPriority) is var addend && addend.StartsWith("-") ? addend : "+" + addend); } @@ -24,8 +24,8 @@ public partial record Minusf { /// public override string Latexise() => - Subtrahend.Latexise(Subtrahend.Priority < Priority) - + "-" + Minuend.Latexise(Minuend.Priority <= Priority); + Subtrahend.Latexise(Subtrahend.LatexPriority < LatexPriority) + + "-" + Minuend.Latexise(Minuend.LatexPriority <= LatexPriority); } public partial record Mulf @@ -43,16 +43,16 @@ public override string Latexise() return currIn switch { // -1, -2, 2i, i, -i, -2i etc. in the front and not (1+i) etc. - Number { Priority: Priority.Sum } and not Complex { RealPart.IsZero: false, ImaginaryPart.IsZero: false } => + Number { LatexPriority: Priority.Sum } and not Complex { RealPart.IsZero: false, ImaginaryPart.IsZero: false } => currIn.Latexise(false), - _ => currIn.Latexise(currIn.Priority < Priority) + _ => currIn.Latexise(currIn.LatexPriority < LatexPriority) }; case 1: if (longArray[index - 1] is Integer(-1)) - return $"-{currIn.Latexise(currIn.Priority < Priority)}"; // display "-1 * x * y" as "-x \cdot y", only for the first -1 + return $"-{currIn.Latexise(currIn.LatexPriority < LatexPriority)}"; // display "-1 * x * y" as "-x \cdot y", only for the first -1 break; } - var currOut = currIn.Latexise(currIn.Priority < Priority); + var currOut = currIn.Latexise(currIn.LatexPriority < LatexPriority); return (longArray[index - 1], currIn) switch // whether we use juxtaposition and omit \cdot { @@ -61,20 +61,20 @@ public override string Latexise() // Don't juxtapose upright variables with numbers like displaying "var2" for "var*2" since "var2" may be interpreted as one variable. // Also, don't produce upright "ei" (one variable with two chars) for e*i, or "ei^2" for e*i^2. - // but "e (2+i)" and "e (2+i)^2" are fine with the parentheses - so we have the priority check. + // but "e (2+i)" and "e (2+i)^2" are fine with the parentheses - so we have the LatexPriority check. (Variable { IsLatexUprightFormatted: true } - or Complex { ImaginaryPart.IsZero: false, Priority: >= Priority.Mul } /* don't combine upright "i" with an upright variable*/, - Variable { IsLatexUprightFormatted: true } or Number { Priority: >= Priority.Mul } - or Factorialf(Number { Priority: Priority.Leaf } or Variable { IsLatexUprightFormatted: true }) - or Powf(Number { Priority: Priority.Leaf } or Variable { IsLatexUprightFormatted: true } - or Factorialf(Number { Priority: Priority.Leaf } or Variable { IsLatexUprightFormatted: true }), _)) => false, + or Complex { ImaginaryPart.IsZero: false, LatexPriority: >= Priority.Mul } /* don't combine upright "i" with an upright variable*/, + Variable { IsLatexUprightFormatted: true } or Number { LatexPriority: >= Priority.Mul } + or Factorialf(Number { LatexPriority: Priority.Leaf } or Variable { IsLatexUprightFormatted: true }) + or Powf(Number { LatexPriority: Priority.Leaf } or Variable { IsLatexUprightFormatted: true } + or Factorialf(Number { LatexPriority: Priority.Leaf } or Variable { IsLatexUprightFormatted: true }), _)) => false, // 2 * (3/4) instead of 2 (3/4) which is a mixed number (= 2 + 3/4) - (Number { Priority: Priority.Leaf }, { Priority: Priority.Div }) => false, - // 2 * 3 instead of 2 3 (= 23), 2 * 3^4 instead of 2 3^4 (= 23^4), but "(2+i) 2", "2 (2+i)" and "2 (2+i)^2" are fine with the parentheses - so we have the priority check. - (_, Number { Priority: >= Priority.Mul } or Factorialf(Number { Priority: Priority.Leaf }) - or Powf(Number { Priority: Priority.Leaf } or Factorialf(Number { Priority: Priority.Leaf }), _)) => false, // Keep the \cdot in "f(x) \cdot -2" "f(x) \cdot 2i" "f(x) \cdot -2i" - (var left, var right) => left.Priority >= right.Priority && - !(left.Priority == Priority.Div && right.Priority == Priority.Div) // Without \cdot, the fraction lines may appear too closely together. + (Number { LatexPriority: Priority.Leaf }, { LatexPriority: Priority.Div }) => false, + // 2 * 3 instead of 2 3 (= 23), 2 * 3^4 instead of 2 3^4 (= 23^4), but "(2+i) 2", "2 (2+i)" and "2 (2+i)^2" are fine with the parentheses - so we have the LatexPriority check. + (_, Number { LatexPriority: >= Priority.Mul } or Factorialf(Number { LatexPriority: Priority.Leaf }) + or Powf(Number { LatexPriority: Priority.Leaf } or Factorialf(Number { LatexPriority: Priority.Leaf }), _)) => false, // Keep the \cdot in "f(x) \cdot -2" "f(x) \cdot 2i" "f(x) \cdot -2i" + (var left, var right) => left.LatexPriority >= right.LatexPriority && + !(left.Priority == Priority.Div && right.LatexPriority == Priority.Div) // Without \cdot, the fraction lines may appear too closely together. } ? $@"{prevOut} {currOut}" : $@"{prevOut} \cdot {currOut}"; }); @@ -125,7 +125,7 @@ public override string Latexise() } else { - return "{" + Base.Latexise(Base.Priority <= Priority) + "}^{" + Exponent.Latexise() + "}"; + return "{" + Base.Latexise(Base.LatexPriority <= LatexPriority) + "}^{" + Exponent.Latexise() + "}"; } } } @@ -134,7 +134,7 @@ public partial record Factorialf { /// public override string Latexise() => - Argument.Latexise(Argument.Priority <= Priority) + "!"; + Argument.Latexise(Argument.LatexPriority <= LatexPriority) + "!"; } partial record Signumf diff --git a/Sources/AngouriMath/Functions/Output/Latex/Latex.Calculus.Classes.cs b/Sources/AngouriMath/Functions/Output/Latex/Latex.Calculus.Classes.cs index 0c860fb36..788bd18fa 100644 --- a/Sources/AngouriMath/Functions/Output/Latex/Latex.Calculus.Classes.cs +++ b/Sources/AngouriMath/Functions/Output/Latex/Latex.Calculus.Classes.cs @@ -11,16 +11,20 @@ namespace AngouriMath { partial record Entity { + partial record CalculusOperator + { + /// + internal override Priority LatexPriority => Priority.LatexCalculusOperation; + } public partial record Derivativef { internal static string LatexiseDerivative(Entity expression, Entity var, int iterations) { var powerIfNeeded = iterations == 1 ? "" : "^{" + iterations + "}"; - var varOverDeriv = var is Variable { IsLatexUprightFormatted: false } ? var.Latexise() : @"\left(" + var.Latexise() + @"\right)"; - string ParenIfNeeded(string paren) => expression.Priority < Priority.Pow ? paren : ""; // NOTE: \mathrm{d} is used for upright 'd' following ISO 80000-2 standard. // The differential operator should be upright (roman) to distinguish it from variables, similar to sin, cos, log, etc. - return $$"""\frac{\mathrm{d}{{powerIfNeeded}}}{\mathrm{d}{{varOverDeriv}}{{powerIfNeeded}}}{{ParenIfNeeded(@"\left[")}}{{expression.Latexise()}}{{ParenIfNeeded(@"\right]")}}"""; + return $$"""\frac{\mathrm{d}{{powerIfNeeded}}}{\mathrm{d}{{var.Latexise(var is not Variable { IsLatexUprightFormatted: false }) + }}{{powerIfNeeded}}}{{expression.Latexise(expression.LatexPriority < Priority.LatexCalculusOperation)}}"""; } /// public override string Latexise() => LatexiseDerivative(Expression, Var, Iterations); @@ -40,9 +44,7 @@ public override string Latexise() var sb = new StringBuilder(); for (int i = 0; i < Iterations; i++) sb.Append(@"\int"); - sb.Append(@" \left["); - sb.Append(Expression.Latexise(false)); - sb.Append(@"\right]"); + sb.Append(' ').Append(Expression.Latexise(Expression.LatexPriority < Priority.LatexCalculusOperation)); // NOTE: \mathrm{d} is used for upright 'd' following ISO 80000-2 standard. // The differential operator should be upright (roman) to distinguish it from variables. @@ -54,14 +56,7 @@ public override string Latexise() { sb.Append(@"\,");// Leading space before first differential and between differentials sb.Append(@"\mathrm{d}"); - if (Var is Variable { IsLatexUprightFormatted: false }) - sb.Append(Var.Latexise()); - else - { - sb.Append(@"\left("); - sb.Append(Var.Latexise()); - sb.Append(@"\right)"); - } + sb.Append(Var.Latexise(Var is not Variable { IsLatexUprightFormatted: false })); } return sb.ToString(); } @@ -79,10 +74,10 @@ public override string Latexise() switch (ApproachFrom) { case ApproachFrom.Left: - sb.Append(Destination.Latexise(Destination.Priority <= Priority.Pow)).Append("^-"); + sb.Append(Destination.Latexise(Destination.LatexPriority <= Priority.Pow)).Append("^-"); break; case ApproachFrom.Right: - sb.Append(Destination.Latexise(Destination.Priority <= Priority.Pow)).Append("^+"); + sb.Append(Destination.Latexise(Destination.LatexPriority <= Priority.Pow)).Append("^+"); break; case ApproachFrom.BothSides: sb.Append(Destination.Latexise()); @@ -90,12 +85,7 @@ public override string Latexise() } sb.Append("} "); - if (Expression.Priority < Priority.Pow) - sb.Append(@"\left["); - sb.Append(Expression.Latexise()); - if (Expression.Priority < Priority.Pow) - sb.Append(@"\right]"); - + sb.Append(Expression.Latexise(Expression.LatexPriority < Priority.LatexCalculusOperation)); return sb.ToString(); } } diff --git a/Sources/AngouriMath/Functions/Output/Latex/Latex.Discrete.Classes.cs b/Sources/AngouriMath/Functions/Output/Latex/Latex.Discrete.Classes.cs index 131794d1b..0ee10e0ff 100644 --- a/Sources/AngouriMath/Functions/Output/Latex/Latex.Discrete.Classes.cs +++ b/Sources/AngouriMath/Functions/Output/Latex/Latex.Discrete.Classes.cs @@ -21,8 +21,8 @@ partial record Notf /// public override string Latexise() => Argument is Equalsf(var left, var right) - ? $@"{left.Latexise(left.Priority <= Priority.Equal)} \neq {right.Latexise(right.Priority <= Priority.Equal)}" - : $@"\neg{{{Argument.Latexise(Argument.Priority < Priority)}}}"; + ? $@"{left.Latexise(left.LatexPriority <= Priority.Equal)} \neq {right.Latexise(right.LatexPriority <= Priority.Equal)}" + : $@"\neg{{{Argument.Latexise(Argument.LatexPriority < LatexPriority)}}}"; } partial record Andf @@ -42,7 +42,7 @@ public override string Latexise() var renew = left != child.DirectChildren[0]; if (!first && renew) result.Append(@" \land "); first = false; - if (first || renew) result.Append(child.DirectChildren[0].Latexise(child.DirectChildren[0].Priority <= child.Priority)); + if (first || renew) result.Append(child.DirectChildren[0].Latexise(child.DirectChildren[0].LatexPriority <= child.LatexPriority)); renew = false; result.Append(child switch { Equalsf => " = ", @@ -51,23 +51,23 @@ public override string Latexise() Lessf => " < ", LessOrEqualf => @" \leq ", _ => throw new Core.Exceptions.AngouriBugException("Unexpected comparison sign in Andf.Latexise") - }).Append(child.DirectChildren[1].Latexise(child.DirectChildren[1].Priority <= child.Priority)); + }).Append(child.DirectChildren[1].Latexise(child.DirectChildren[1].LatexPriority <= child.LatexPriority)); left = child.DirectChildren[1]; break; case Notf (Equalsf eq): renew = left != eq.Left; if (!first && renew) result.Append(@" \land "); first = false; - if (first || renew) result.Append(eq.Left.Latexise(eq.Left.Priority <= eq.Priority)); + if (first || renew) result.Append(eq.Left.Latexise(eq.Left.LatexPriority <= eq.LatexPriority)); renew = false; - result.Append(@" \neq ").Append(eq.Right.Latexise(eq.Right.Priority <= eq.Priority)); + result.Append(@" \neq ").Append(eq.Right.Latexise(eq.Right.LatexPriority <= eq.LatexPriority)); left = eq.Right; break; default: if (!first) result.Append(" \\land "); left = null; first = false; - result.Append(child.Latexise(child.Priority < Priority)); + result.Append(child.Latexise(child.LatexPriority < LatexPriority)); break; } } @@ -79,7 +79,7 @@ partial record Orf { /// public override string Latexise() - => $@"{Left.Latexise(Left.Priority < Priority)} \lor {Right.Latexise(Right.Priority < Priority)}"; + => $@"{Left.Latexise(Left.LatexPriority < LatexPriority)} \lor {Right.Latexise(Right.LatexPriority < LatexPriority)}"; } partial record Xorf @@ -87,7 +87,7 @@ partial record Xorf /// // NOTE: \veebar (⊻) can disambiguate better than \oplus (⊕) which can mean the direct sum in algebra. public override string Latexise() - => $@"{Left.Latexise(Left.Priority < Priority)} \veebar {Right.Latexise(Right.Priority < Priority)}"; + => $@"{Left.Latexise(Left.LatexPriority < LatexPriority)} \veebar {Right.Latexise(Right.LatexPriority < LatexPriority)}"; } partial record Impliesf @@ -99,21 +99,21 @@ partial record Impliesf // ISO 80000-2 puts ⇒ first for implies, but → is also accepted and is more common in logic contexts. // ⇒ can be used in steps for proofs if we were to add it later. public override string Latexise() - => $@"{Assumption.Latexise(Assumption.Priority <= Priority)} \to {Conclusion.Latexise(Conclusion.Priority < Priority)}"; + => $@"{Assumption.Latexise(Assumption.LatexPriority <= LatexPriority)} \to {Conclusion.Latexise(Conclusion.LatexPriority < LatexPriority)}"; } partial record Equalsf { /// public override string Latexise() - => $@"{Left.Latexise(Left.Priority <= Priority)} = {Right.Latexise(Right.Priority <= Priority)}"; + => $@"{Left.Latexise(Left.LatexPriority <= LatexPriority)} = {Right.Latexise(Right.LatexPriority <= LatexPriority)}"; } partial record Greaterf { /// public override string Latexise() - => $@"{Left.Latexise(Left.Priority <= Priority)} > {Right.Latexise(Right.Priority <= Priority)}"; + => $@"{Left.Latexise(Left.LatexPriority <= LatexPriority)} > {Right.Latexise(Right.LatexPriority <= LatexPriority)}"; } partial record GreaterOrEqualf @@ -122,14 +122,14 @@ partial record GreaterOrEqualf // NOTE: While \geqslant (⩾) is more used in e.g. Russian texts, \geq (≥) is more universally used in English texts. // Since the output language of LaTeX is English (referencing Piecewise and Providedf), \geq is more appropriate. public override string Latexise() - => $@"{Left.Latexise(Left.Priority <= Priority)} \geq {Right.Latexise(Right.Priority <= Priority)}"; + => $@"{Left.Latexise(Left.LatexPriority <= LatexPriority)} \geq {Right.Latexise(Right.LatexPriority <= LatexPriority)}"; } partial record Lessf { /// public override string Latexise() - => $@"{Left.Latexise(Left.Priority <= Priority)} < {Right.Latexise(Right.Priority <= Priority)}"; + => $@"{Left.Latexise(Left.LatexPriority <= LatexPriority)} < {Right.Latexise(Right.LatexPriority <= LatexPriority)}"; } partial record LessOrEqualf @@ -138,7 +138,7 @@ partial record LessOrEqualf // NOTE: While \leqslant (⩽) is more used in e.g. Russian texts, \leq (≤) is more universally used in English texts. // Since the output language of LaTeX is English (referencing Piecewise and Providedf), \leq is more appropriate. public override string Latexise() - => $@"{Left.Latexise(Left.Priority <= Priority)} \leq {Right.Latexise(Right.Priority <= Priority)}"; + => $@"{Left.Latexise(Left.LatexPriority <= LatexPriority)} \leq {Right.Latexise(Right.LatexPriority <= LatexPriority)}"; } } } diff --git a/Sources/AngouriMath/Functions/Output/Latex/Latex.Omni.Classes.cs b/Sources/AngouriMath/Functions/Output/Latex/Latex.Omni.Classes.cs index 051b86f57..455ac77b4 100644 --- a/Sources/AngouriMath/Functions/Output/Latex/Latex.Omni.Classes.cs +++ b/Sources/AngouriMath/Functions/Output/Latex/Latex.Omni.Classes.cs @@ -51,35 +51,35 @@ partial record Unionf { /// public override string Latexise() - => $@"{Left.Latexise(Left.Priority < Priority)} \cup {Right.Latexise(Right.Priority < Priority)}"; + => $@"{Left.Latexise(Left.LatexPriority < LatexPriority)} \cup {Right.Latexise(Right.LatexPriority < LatexPriority)}"; } partial record Intersectionf { /// public override string Latexise() - => $@"{Left.Latexise(Left.Priority < Priority)} \cap {Right.Latexise(Right.Priority < Priority)}"; + => $@"{Left.Latexise(Left.LatexPriority < LatexPriority)} \cap {Right.Latexise(Right.LatexPriority < LatexPriority)}"; } partial record SetMinusf { /// public override string Latexise() - => $@"{Left.Latexise(Left.Priority < Priority)} \setminus {Right.Latexise(Right.Priority < Priority)}"; + => $@"{Left.Latexise(Left.LatexPriority < LatexPriority)} \setminus {Right.Latexise(Right.LatexPriority < LatexPriority)}"; } partial record Inf { /// public override string Latexise() - => $@"{Element.Latexise(Element.Priority < Priority)} \in {SupSet.Latexise(SupSet.Priority < Priority)}"; + => $@"{Element.Latexise(Element.LatexPriority < LatexPriority)} \in {SupSet.Latexise(SupSet.LatexPriority < LatexPriority)}"; } } partial record Providedf { /// - public override string Latexise() => $@"{Expression.Latexise(Expression.Priority < Priority)} \quad \text{{for}} \quad {Predicate.Latexise(Predicate.Priority <= Priority)}"; + public override string Latexise() => $@"{Expression.Latexise(Expression.LatexPriority < LatexPriority)} \quad \text{{for}} \quad {Predicate.Latexise(Predicate.LatexPriority <= LatexPriority)}"; } partial record Piecewise @@ -90,8 +90,8 @@ public override string Latexise() => $@"\begin{{cases}}" + Cases.Select(c => { if (c.Predicate == Boolean.True) - return $@"{c.Expression.Latexise(c.Expression.Priority < Priority.Provided)} & \text{{otherwise}}"; - return $@"{c.Expression.Latexise(c.Expression.Priority < Priority.Provided)} & \text{{for }} {c.Predicate.Latexise(c.Predicate.Priority <= Priority.Provided)}"; // "for" used in https://mathworld.wolfram.com/Derivative.html + return $@"{c.Expression.Latexise(c.Expression.LatexPriority < Priority.Provided)} & \text{{otherwise}}"; + return $@"{c.Expression.Latexise(c.Expression.LatexPriority < Priority.Provided)} & \text{{for }} {c.Predicate.Latexise(c.Predicate.LatexPriority <= Priority.Provided)}"; // "for" used in https://mathworld.wolfram.com/Derivative.html } )) + @@ -139,7 +139,7 @@ partial record Application // are rendered as separate applications: f(x)(y) rather than f(x, y) public override string Latexise() { - var result = new StringBuilder(Expression.Latexise(Expression.Priority < Priority)); + var result = new StringBuilder(Expression.Latexise(Expression.LatexPriority < LatexPriority)); foreach (var arg in Arguments) { result.Append(@"\left(").Append(arg.Latexise()).Append(@"\right)"); @@ -154,7 +154,7 @@ partial record Lambda // NOTE: \mapsto (↦) links the function variable with its output while // \rightarrow (→) links the function domain with its codomain. See https://math.stackexchange.com/a/651240, https://math.stackexchange.com/a/936591 public override string Latexise() - => Parameter.Latexise() + @" \mapsto " + Body.Latexise(Body.Priority < Priority); + => Parameter.Latexise() + @" \mapsto " + Body.Latexise(Body.LatexPriority < LatexPriority); } } } diff --git a/Sources/Tests/UnitTests/Convenience/LatexTest.cs b/Sources/Tests/UnitTests/Convenience/LatexTest.cs index f1d796ec7..f4463c8e4 100644 --- a/Sources/Tests/UnitTests/Convenience/LatexTest.cs +++ b/Sources/Tests/UnitTests/Convenience/LatexTest.cs @@ -225,45 +225,53 @@ [Fact] public void VectorSingle() => [Fact] public void Derivative7() => Test(@"\frac{\mathrm{d}^{2}}{\mathrm{d}\left({x}^{2}\right)^{2}}{x}^{2}", MathS.Derivative(x.Pow(2), x.Pow(2), 2)); [Fact] public void Derivative8() => Test(@"\frac{\mathrm{d}}{\mathrm{d}\left(\mathrm{xy}\right)}\mathrm{xy}", MathS.Derivative("xy", "xy")); [Fact] public void Derivative9() => Test(@"\frac{\mathrm{d}^{2}}{\mathrm{d}\left(\mathrm{xy}\right)^{2}}\mathrm{xy}", MathS.Derivative("xy", "xy", 2)); - [Fact] public void Derivative10() => Test(@"\frac{\mathrm{d}}{\mathrm{d}x}\left[\frac{x}{2}\right]", MathS.Derivative("x / 2", x)); - [Fact] public void Derivative11() => Test(@"\frac{\mathrm{d}}{\mathrm{d}\left(\mathrm{quack}\right)}\left[x+y\right]", MathS.Derivative("x + y", "quack")); - [Fact] public void Derivative12() => Test(@"\frac{\mathrm{d}^{3}}{\mathrm{d}x_{f}^{3}}\left[x+1\right]", MathS.Derivative("x + 1", "x_f", 3)); - [Fact] public void Derivative13() => Test(@"\frac{\mathrm{d}}{\mathrm{d}x_{f}}\left[\frac{1}{x}\right]", MathS.Derivative("1/x", "x_f")); - [Fact] public void Derivative14() => Test(@"\frac{\mathrm{d}}{\mathrm{d}\alpha}\left[{x}^{23}-x_{\mathrm{16}}\right]", MathS.Derivative("x^23-x_16", "alpha")); + [Fact] public void Derivative10() => Test(@"\frac{\mathrm{d}}{\mathrm{d}x}\frac{x}{2}", MathS.Derivative("x / 2", x)); + [Fact] public void Derivative11() => Test(@"\frac{\mathrm{d}}{\mathrm{d}\left(\mathrm{quack}\right)}\left(x+y\right)", MathS.Derivative("x + y", "quack")); + [Fact] public void Derivative12() => Test(@"\frac{\mathrm{d}^{3}}{\mathrm{d}x_{f}^{3}}\left(x+1\right)", MathS.Derivative("x + 1", "x_f", 3)); + [Fact] public void Derivative13() => Test(@"\frac{\mathrm{d}}{\mathrm{d}x_{f}}\frac{1}{x}", MathS.Derivative("1/x", "x_f")); + [Fact] public void Derivative14() => Test(@"\frac{\mathrm{d}}{\mathrm{d}\alpha}\left({x}^{23}-x_{\mathrm{16}}\right)", MathS.Derivative("x^23-x_16", "alpha")); [Fact] public void Derivative15() => Test(@"\frac{\mathrm{d}}{\mathrm{d}\alpha_{\beta}}\alpha", MathS.Derivative("alpha", "alpha_beta")); [Fact] public void Derivative16() => Test(@"\frac{\mathrm{d}}{\mathrm{d}\left(\mathrm{alphaa}_{\beta}\right)}\alpha", MathS.Derivative("alpha", "alphaa_beta")); - [Fact] public void Integral1() => Test(@"\int \left[x\right]\,\mathrm{d}x", MathS.Integral(x, x)); + [Fact] public void Integral1() => Test(@"\int x\,\mathrm{d}x", MathS.Integral(x, x)); [Fact] public void Integral2() => - Test(@"\int \left[x+1\right]\,\mathrm{d}\left(x+1\right)", MathS.Integral("x + 1", "x+1")); + Test(@"\int \left(x+1\right)\,\mathrm{d}\left(x+1\right)", MathS.Integral("x + 1", "x+1")); [Fact] public void Integral3() => - Test(@"\int\int \left[x+1\right]\,\mathrm{d}x\,\mathrm{d}x", MathS.Integral("x + 1", x, 2)); + Test(@"\int\int \left(x+1\right)\,\mathrm{d}x\,\mathrm{d}x", MathS.Integral("x + 1", x, 2)); [Fact] public void Integral4() => - Test(@"\int\int \left[x+1\right]\,\mathrm{d}\left(\mathrm{xf}\right)\,\mathrm{d}\left(\mathrm{xf}\right)", MathS.Integral("x + 1", "xf", 2)); + Test(@"\int\int \left(x+1\right)\,\mathrm{d}\left(\mathrm{xf}\right)\,\mathrm{d}\left(\mathrm{xf}\right)", MathS.Integral("x + 1", "xf", 2)); [Fact] public void Integral5() => - Test(@"\int \left[\frac{1}{x}\right]\,\mathrm{d}x_{f}", MathS.Integral("1/x", "x_f")); + Test(@"\int \frac{1}{x}\,\mathrm{d}x_{f}", MathS.Integral("1/x", "x_f")); [Fact] public void Integral6() => - Test(@"\int\int\int \left[{x}^{23}-x_{\mathrm{16}}\right]\,\mathrm{d}\left({x_{f}}^{2}\right)\,\mathrm{d}\left({x_{f}}^{2}\right)\,\mathrm{d}\left({x_{f}}^{2}\right)", MathS.Integral("x^23-x_16", "x_f^2", 3)); - [Fact] public void IntegralSpecialCase1() => Test(@"\frac{\mathrm{d}^{0}}{\mathrm{d}x^{0}}\left[x+1\right]+1", MathS.Integral("x+1", "x", 0) + 1); - [Fact] public void IntegralSpecialCase2() => Test(@"\frac{\mathrm{d}}{\mathrm{d}x}\left[x+1\right] \cdot 2", MathS.Integral("x+1", "x", -1) * 2); + Test(@"\int\int\int \left({x}^{23}-x_{\mathrm{16}}\right)\,\mathrm{d}\left({x_{f}}^{2}\right)\,\mathrm{d}\left({x_{f}}^{2}\right)\,\mathrm{d}\left({x_{f}}^{2}\right)", MathS.Integral("x^23-x_16", "x_f^2", 3)); + [Fact] public void IntegralSpecialCase1() => Test(@"\frac{\mathrm{d}^{0}}{\mathrm{d}x^{0}}\left(x+1\right)+1", MathS.Integral("x+1", "x", 0) + 1); + [Fact] public void IntegralSpecialCase2() => Test(@"\left(\frac{\mathrm{d}}{\mathrm{d}x}\left(x+1\right)\right) \cdot 2", MathS.Integral("x+1", "x", -1) * 2); [Theory, CombinatorialData] public void IntegralSpecialCase3([CombinatorialRange(0, 3)] int power, [CombinatorialValues("x", "x_f", "ab", "ab_cd", "e", "alpha", "alpha_beta", "alpha_e")] string v) => Test(MathS.Derivative(Integer.One + v, v, power).Latexise(), MathS.Integral(Integer.One + v, v, -power)); [Fact] public void Limit1() => - Test(@"\lim_{x\to 0^-} \left[x+y\right]", (Entity)"limitleft(x + y, x, 0)"); + Test(@"\lim_{x\to 0^-} \left(x+y\right)", (Entity)"limitleft(x + y, x, 0)"); [Fact] public void Limit2() => Test(@"\lim_{x\to 0^+} {a}^{5}", (Entity)"limitright(a^5, x, 0)"); [Fact] public void Limit3() => - Test(@"\lim_{x\to \infty } \left[x+y\right]", MathS.Limit("x + y", x, Real.PositiveInfinity)); + Test(@"\lim_{x\to \infty } \left(x+y\right)", MathS.Limit("x + y", x, Real.PositiveInfinity)); [Fact] public void Limit4() => - Test(@"\lim_{\mathrm{xf}\to 1+x} \left[\frac{1}{x}\right]", MathS.Limit("1/x", "xf", "1+x")); + Test(@"\lim_{\mathrm{xf}\to 1+x} \frac{1}{x}", MathS.Limit("1/x", "xf", "1+x")); [Fact] public void Limit5() => - Test(@"\lim_{\mathrm{xf}\to {x_{2}}^{3}} \left[{x}^{23}-x_{\mathrm{16}}\right]", MathS.Limit("x^23-x_16", "xf", "x_2^3")); + Test(@"\lim_{\mathrm{xf}\to {x_{2}}^{3}} \left({x}^{23}-x_{\mathrm{16}}\right)", MathS.Limit("x^23-x_16", "xf", "x_2^3")); [Theory, InlineData("+"), InlineData("-")] public void LimitOneSided1(string sign) => - Test($$"""\lim_{x\to \left(a+2\right)^{{sign}}} \left[x+y\right]""", (Entity)$"limit{(sign == "-" ? "left" : "right")}(x + y, x, a+2)"); + Test($$"""\lim_{x\to \left(a+2\right)^{{sign}}} \left(x+y\right)""", (Entity)$"limit{(sign == "-" ? "left" : "right")}(x + y, x, a+2)"); [Theory, InlineData("+"), InlineData("-")] public void LimitOneSided2(string sign) => - Test($$"""\lim_{x\to \left({a}^{2}\right)^{{sign}}} \left[x+y\right]""", (Entity)$"limit{(sign == "-" ? "left" : "right")}(x + y, x, a^2)"); + Test($$"""\lim_{x\to \left({a}^{2}\right)^{{sign}}} \left(x+y\right)""", (Entity)$"limit{(sign == "-" ? "left" : "right")}(x + y, x, a^2)"); [Theory, InlineData("+"), InlineData("-")] public void LimitOneSided3(string sign) => - Test($$"""\lim_{x\to \left({a}^{2}\right)!^{{sign}}} \left[x+y\right]""", (Entity)$"limit{(sign == "-" ? "left" : "right")}(x + y, x, (a^2)!)"); + Test($$"""\lim_{x\to \left({a}^{2}\right)!^{{sign}}} \left(x+y\right)""", (Entity)$"limit{(sign == "-" ? "left" : "right")}(x + y, x, (a^2)!)"); + [Fact] public void LimitOfExponential() => + Test(@"\lim_{x\to \infty } {\sin\left(x\right)}^{x} = \infty ", MathS.Limit(MathS.Pow(MathS.Sin(x), x), x, Real.PositiveInfinity).Equalizes(Real.PositiveInfinity)); + [Fact] public void NestedLimitDerivative() => + Test(@"\left(\lim_{x\to 0} \frac{\mathrm{d}^{2}}{\mathrm{d}x^{2}}\lim_{y\to \infty } \frac{x}{y}\right) \cdot x", MathS.Limit(MathS.Derivative(MathS.Limit("x / y", "y", Real.PositiveInfinity), x, 2), x, 0) * x); + [Fact] public void NestedDerivativeIntegral1() => + Test(@"\frac{\mathrm{d}}{\mathrm{d}x}\int \frac{\mathrm{d}}{\mathrm{d}y}\int {x}^{2} \cdot y\,\mathrm{d}y\,\mathrm{d}\left(x+1\right)+1", MathS.Derivative(MathS.Integral(MathS.Derivative(MathS.Integral(MathS.Pow(x, 2) * "y", "y"), "y"), x + 1), x) + 1); + [Fact] public void NestedDerivativeIntegral2() => + Test(@"\int \frac{\mathrm{d}}{\mathrm{d}x}\int \frac{\mathrm{d}}{\mathrm{d}y}\left(1-{x}^{2} \cdot y\right)\,\mathrm{d}y\,\mathrm{d}x+1", MathS.Integral(MathS.Derivative(MathS.Integral(MathS.Derivative(1 - MathS.Pow(x, 2) * "y", "y"), "y"), x), x) + 1); [Fact] public void Interval1() => Test(@"\left[3, 4\right]", MathS.Sets.Interval(3, true, 4, true)); [Fact] public void Interval2() => From 3b219c478b9d3dadfb5f048760522ddd925ba77c Mon Sep 17 00:00:00 2001 From: Hadrian Tang Date: Sat, 17 Jan 2026 01:20:57 +0800 Subject: [PATCH 2/2] Fix missing replacement --- .../Functions/Output/Latex/Latex.Arithmetics.Classes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AngouriMath/Functions/Output/Latex/Latex.Arithmetics.Classes.cs b/Sources/AngouriMath/Functions/Output/Latex/Latex.Arithmetics.Classes.cs index 1a57a32a9..94bc3f361 100644 --- a/Sources/AngouriMath/Functions/Output/Latex/Latex.Arithmetics.Classes.cs +++ b/Sources/AngouriMath/Functions/Output/Latex/Latex.Arithmetics.Classes.cs @@ -74,7 +74,7 @@ or Factorialf(Number { LatexPriority: Priority.Leaf } or Variable { IsLatexUprig (_, Number { LatexPriority: >= Priority.Mul } or Factorialf(Number { LatexPriority: Priority.Leaf }) or Powf(Number { LatexPriority: Priority.Leaf } or Factorialf(Number { LatexPriority: Priority.Leaf }), _)) => false, // Keep the \cdot in "f(x) \cdot -2" "f(x) \cdot 2i" "f(x) \cdot -2i" (var left, var right) => left.LatexPriority >= right.LatexPriority && - !(left.Priority == Priority.Div && right.LatexPriority == Priority.Div) // Without \cdot, the fraction lines may appear too closely together. + !(left.LatexPriority == Priority.Div && right.LatexPriority == Priority.Div) // Without \cdot, the fraction lines may appear too closely together. } ? $@"{prevOut} {currOut}" : $@"{prevOut} \cdot {currOut}"; });