Skip to content

Commit 0d1b1e1

Browse files
committed
abs, sgn, lim
1 parent 3b3510b commit 0d1b1e1

File tree

2 files changed

+149
-26
lines changed

2 files changed

+149
-26
lines changed

CSharpMath.Evaluation.Tests/EvaluationTests.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,26 @@ public void Intervals(string latex, string converted, string result) {
714714
[InlineData(@"1;\cap", "Unsupported Unary Operator ∩")]
715715
[InlineData(@"\cap,", "Unsupported Unary Operator ∩")]
716716
[InlineData(@"\cap;", "Unsupported Unary Operator ∩")]
717+
[InlineData(@"1^+", "+ alone in superscript but not in limit subscript context")]
718+
[InlineData(@"1^-", "\u2212 alone in superscript but not in limit subscript context")]
719+
[InlineData(@"\lim", "Missing limit variable in subscript")]
720+
[InlineData(@"\lim_x", "Missing → in limit subscript")]
721+
[InlineData(@"\lim_{xy}", "Missing → in limit subscript")]
722+
[InlineData(@"\lim_{xy^+}", "Missing limit target in subscript")]
723+
[InlineData(@"\lim_{xy^-}", "Missing limit target in subscript")]
724+
[InlineData(@"\lim_{\to}", "Missing limit variable in subscript")]
725+
[InlineData(@"\lim_{\to2}", "Missing limit variable in subscript")]
726+
[InlineData(@"\lim_{x\to}", "Missing limit target in subscript")]
727+
[InlineData(@"\lim_{xy\to}", "Missing limit target in subscript")]
728+
[InlineData(@"\lim_{xy\to^+}", "Missing limit target in subscript")]
729+
[InlineData(@"\lim_{xy\to^-}", "Missing limit target in subscript")]
730+
[InlineData(@"\lim_{x\to2^+2}", "Limit direction indicator + not placed at the end")]
731+
[InlineData(@"\lim_{xy\to2^+2}", "Limit direction indicator + not placed at the end")]
732+
[InlineData(@"\lim_{xy\to2^-2}", "Limit direction indicator \u2212 not placed at the end")]
733+
[InlineData(@"\lim_{xy\to(2^+)}", "+ alone in superscript but not in limit subscript context")]
734+
[InlineData(@"\lim_{xy\to(2^-)}", "\u2212 alone in superscript but not in limit subscript context")]
735+
[InlineData(@"\lim_{xy\to\left(2^+\right)}", "+ alone in superscript but not in limit subscript context")]
736+
[InlineData(@"\lim_{xy\to\left(2^-\right)}", "\u2212 alone in superscript but not in limit subscript context")]
717737
public void Error(string badLaTeX, string error) =>
718738
Evaluation.Evaluate(ParseLaTeX(badLaTeX))
719739
.Match(entity => throw new Xunit.Sdk.XunitException(entity.Latexise()), e => Assert.Equal(error, e));
@@ -792,7 +812,7 @@ public void SimpleArithmeticSyntax(string simpleSyntax, string latex) =>
792812
[InlineData(@"1\geq1", @"1\geq 1", @"\top ")]
793813
[InlineData(@"1\geqslant1", @"1\geq 1", @"\top ")]
794814
[InlineData(@"x\in\{x,y\}", @"x\in \left\{ x,y\right\} ", @"\top ")]
795-
[InlineData(@"z\notin\{x,y\}", @"\neg z\in \left\{ x,y\right\} ", @"\top ")] // BUG: This should simplify to \top since z is not in {x,y}, but AngouriMath cannot determine this without \mathrm{e}xplicit variable declarations
815+
[InlineData(@"z\notin\{x,y\}", @"\neg z\in \left\{ x,y\right\} ", @"\top ")] // BUG: This simplified to \top since z is not in {x,y}, but AngouriMath cannot determine this without explicit variable declarations
796816
[InlineData(@"\{x,y\}\ni x", @"x\in \left\{ x,y\right\} ", @"\top ")]
797817
public void LogicalAndRelationalOperators(string latex, string converted, string result) => Test(latex, converted, result);
798818

@@ -842,5 +862,27 @@ public void ChainedComparisons(string latex, string converted, string result, st
842862
converted.Replace("<", ">").Replace(@"\leq ", @"\geq "),
843863
geqResult ?? result);
844864
}
865+
[Theory]
866+
[InlineData(@"\mathrm{undefined}", @"\mathrm{undefined}", @"\mathrm{undefined}")]
867+
[InlineData(@"0\times\infty", @"0\cdot \infty ", @"\mathrm{undefined}")]
868+
[InlineData(@"\mathrm{undefined}+1", @"\mathrm{undefined}+1", @"\mathrm{undefined}")]
869+
[InlineData(@"\sin\mathrm{undefined}", @"\sin \left( \mathrm{undefined}\right) ", @"\mathrm{undefined}")]
870+
public void Undefined(string latex, string converted, string result) => Test(latex, converted, result);
871+
[Theory]
872+
[InlineData(@"\left|1\right|", @"\left| 1\right| ", @"1")]
873+
[InlineData(@"-\left|-1\right|", @"-\left| -1\right| ", @"-1")]
874+
[InlineData(@"-\operatorname{abs}(-1)", @"-\left| -1\right| ", @"-1")]
875+
[InlineData(@"-\operatorname{abs}\left|-1\right|", @"-\left| \left| -1\right| \right| ", @"-1")]
876+
[InlineData(@"-\left|1\right|^2", @"-\left| 1\right| ^2", @"-1")]
877+
[InlineData(@"\operatorname{sgn}\operatorname{abs} x", @"\operatorname{sgn} \left( \left| x\right| \right) ", @"1")]
878+
public void Abs(string latex, string converted, string result) => Test(latex, converted, result);
879+
[Theory]
880+
[InlineData(@"\lim_{x\to2}x", @"\lim _{x\rightarrow 2}x", @"2")]
881+
[InlineData(@"\lim_{x\to2^-}x", @"\lim _{x\rightarrow 2^-}x", @"2")]
882+
[InlineData(@"\lim_{x\to2^+}x", @"\lim _{x\rightarrow 2^+}x", @"2")]
883+
[InlineData(@"\lim_{x\to\infty}x", @"\lim _{x\rightarrow \infty }x", @"\infty ")]
884+
//[InlineData(@"\lim_{x\to\left(1+x\right)^+}x", @"\lim _{x\rightarrow \left( 1+x\right) ^+}\left( x\right) ", @"1+x")]
885+
//[InlineData(@"\lim_{x\to(1+x)^+}x", @"\lim _{x\rightarrow \left( 1+x\right) ^+}\left( x\right) ", @"1+x")] // WIP
886+
public void Limit(string latex, string converted, string result) => Test(latex, converted, result);
845887
}
846888
}

CSharpMath.Evaluation/Evaluation.cs

Lines changed: 106 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66

77
namespace CSharpMath {
88
using System.Collections;
9+
using System.Data.SqlTypes;
10+
using System.Numerics;
911
using Atom;
1012
using Atoms = Atom.Atoms;
11-
using System.Numerics;
1213

1314
public static partial class Evaluation {
1415
enum Precedence {
1516
DefaultContext,
17+
LimitSubscriptContext,
1618
BraceContext,
1719
BracketContext,
1820
ParenthesisContext,
@@ -135,7 +137,7 @@ static Result<MathItem> TryMakeSet(MathItem.Comma c, bool leftClosed, bool right
135137
{ "[", ("]", Precedence.BracketContext) },
136138
{ "{", ("}", Precedence.BraceContext) },
137139
};
138-
static readonly Dictionary<(string? left, string? right), Func<MathItem?, Result<MathItem>>> BracketHandlers =
140+
static readonly Dictionary<(string? left, string? right), Func<MathItem?, Result<MathItem>>> InnerHandlers =
139141
new Dictionary<(string? left, string? right), Func<MathItem?, Result<MathItem>>> {
140142
{ ("(", ")"), item => item switch {
141143
null => "Missing math inside ( )",
@@ -155,7 +157,8 @@ static Result<MathItem> TryMakeSet(MathItem.Comma c, bool leftClosed, bool right
155157
MathItem.Comma c => TryMakeSet(c, true, true),
156158
_ => item
157159
} },
158-
{ ("{", "}"), item => item.AsEntities("set element").Bind(entities => (MathItem)MathS.Sets.Finite(entities)) }
160+
{ ("{", "}"), item => item.AsEntities("set element").Bind(entities => (MathItem)MathS.Sets.Finite(entities)) },
161+
{ ("|", "|"), item => item.AsEntity("abs argument").Bind(x => (MathItem)MathS.Abs(x)) }
159162
};
160163
static Result<MathItem?> Transform(MathList mathList, ref int i, Precedence prec) {
161164
MathItem? prev = null;
@@ -169,13 +172,19 @@ static Result<MathItem> TryMakeSet(MathItem.Comma c, bool leftClosed, bool right
169172
var atom = mathList[i];
170173
MathItem? @this;
171174
bool subscriptAllowed = false, binaryIsComparison = false, binaryIsRightAssociative = false;
172-
Result HandleSuperscript(ref MathItem? @this, MathList superscript) {
175+
Result HandleSuperscript(ref MathItem? @this, ref int i, MathList superscript) {
173176
switch (superscript) {
174-
case { Count: 1 } when superscript[0] is Atoms.Ordinary { Nucleus: "∁" }:
177+
case [Atoms.Ordinary { Nucleus: "∁" }]:
175178
(@this, error) =
176179
@this.AsEntity("target of set inversion").Bind(target => (MathItem?)MathS.SetSubtraction(MathS.Sets.C, target)); // we don't support domains yet
177180
if (error != null) return error;
178181
break;
182+
case [Atoms.UnaryOperator { Nucleus: ("+" or "\u2212") and var direction }]:
183+
if (prec != Precedence.LimitSubscriptContext) return $"{direction} alone in superscript but not in limit subscript context";
184+
if (i != mathList.Count - 1) return $"Limit direction indicator {direction} not placed at the end";
185+
if (direction == "+") i = mathList.Count + 1; // signal approach from right
186+
else i = mathList.Count; // signal approach from left
187+
break;
179188
default:
180189
Entity? exponent;
181190
(exponent, error) = Transform(superscript).ExpectEntityOrNull(nameof(exponent));
@@ -241,6 +250,7 @@ Result HandleSuperscript(ref MathItem? @this, MathList superscript) {
241250
("e", 0, FontStyle.Roman or FontStyle.Default or FontStyle.Italic) => MathS.e,
242251
("π", 0, FontStyle.Roman or FontStyle.Default or FontStyle.Italic) => MathS.pi,
243252
("i", 0, FontStyle.Roman or FontStyle.Default or FontStyle.Italic) => MathS.i,
253+
("undefined", 0, FontStyle.Roman) => MathS.NaN,
244254
// Convert θ to theta
245255
(_, _, FontStyle.Default or FontStyle.Italic) when LaTeXSettings.CommandForAtom(atom) is string s => MathS.Var(string.Concat(s.TrimStart('\\'), underscore, subscript)),
246256
_ => MathS.Var(name + underscore + subscript.ToString())
@@ -326,6 +336,54 @@ Result HandleSuperscript(ref MathItem? @this, MathList superscript) {
326336
handleFunction = MathS.Arccosec;
327337
handleFunctionInverse = MathS.Cosec;
328338
goto handleFunction;
339+
case Atoms.LargeOperator { Nucleus: "sinh" }:
340+
handleFunction = MathS.Hyperbolic.Sinh;
341+
handleFunctionInverse = MathS.Hyperbolic.Arsinh;
342+
goto handleFunction;
343+
case Atoms.LargeOperator { Nucleus: "cosh" }:
344+
handleFunction = MathS.Hyperbolic.Cosh;
345+
handleFunctionInverse = MathS.Hyperbolic.Arcosh;
346+
goto handleFunction;
347+
case Atoms.LargeOperator { Nucleus: "tanh" }:
348+
handleFunction = MathS.Hyperbolic.Tanh;
349+
handleFunctionInverse = MathS.Hyperbolic.Artanh;
350+
goto handleFunction;
351+
case Atoms.LargeOperator { Nucleus: "coth" }:
352+
handleFunction = MathS.Hyperbolic.Cotanh;
353+
handleFunctionInverse = MathS.Hyperbolic.Arcotanh;
354+
goto handleFunction;
355+
case Atoms.LargeOperator { Nucleus: "sech" }:
356+
handleFunction = MathS.Hyperbolic.Sech;
357+
handleFunctionInverse = MathS.Hyperbolic.Arsech;
358+
goto handleFunction;
359+
case Atoms.LargeOperator { Nucleus: "csch" }:
360+
handleFunction = MathS.Hyperbolic.Cosech;
361+
handleFunctionInverse = MathS.Hyperbolic.Arcosech;
362+
goto handleFunction;
363+
case Atoms.LargeOperator { Nucleus: "arsinh" }:
364+
handleFunction = MathS.Hyperbolic.Arsinh;
365+
handleFunctionInverse = MathS.Hyperbolic.Sinh;
366+
goto handleFunction;
367+
case Atoms.LargeOperator { Nucleus: "arcosh" }:
368+
handleFunction = MathS.Hyperbolic.Arcosh;
369+
handleFunctionInverse = MathS.Hyperbolic.Cosh;
370+
goto handleFunction;
371+
case Atoms.LargeOperator { Nucleus: "artanh" }:
372+
handleFunction = MathS.Hyperbolic.Artanh;
373+
handleFunctionInverse = MathS.Hyperbolic.Tanh;
374+
goto handleFunction;
375+
case Atoms.LargeOperator { Nucleus: "arcoth" }:
376+
handleFunction = MathS.Hyperbolic.Arcotanh;
377+
handleFunctionInverse = MathS.Hyperbolic.Cotanh;
378+
goto handleFunction;
379+
case Atoms.LargeOperator { Nucleus: "arsech" }:
380+
handleFunction = MathS.Hyperbolic.Arsech;
381+
handleFunctionInverse = MathS.Hyperbolic.Sech;
382+
goto handleFunction;
383+
case Atoms.LargeOperator { Nucleus: "arcsch" }:
384+
handleFunction = MathS.Hyperbolic.Arcosech;
385+
handleFunctionInverse = MathS.Hyperbolic.Cosech;
386+
goto handleFunction;
329387
case Atoms.LargeOperator { Nucleus: "log", Subscript: var logBaseList }:
330388
Entity? logBase;
331389
(logBase, error) = Transform(logBaseList).ExpectEntityOrNull(nameof(logBase));
@@ -339,10 +397,33 @@ Result HandleSuperscript(ref MathItem? @this, MathList superscript) {
339397
handleFunction = MathS.Ln;
340398
handleFunctionInverse = arg => MathS.Pow(MathS.e, arg);
341399
goto handleFunction;
400+
case Atoms.LargeOperator { Nucleus: "abs" }:
401+
handleFunction = MathS.Abs;
402+
handleFunctionInverse = arg => MathS.NaN;
403+
goto handleFunction;
342404
case Atoms.LargeOperator { Nucleus: "sgn" }:
343405
handleFunction = MathS.Signum;
344406
handleFunctionInverse = arg => MathS.NaN;
345407
goto handleFunction;
408+
case Atoms.LargeOperator { Nucleus: "lim", Subscript: var limitSubscript }:
409+
Entity limitVariable, limitTarget;
410+
int limitSubscriptIndex = 0;
411+
(limitVariable, error) = Transform(limitSubscript, ref limitSubscriptIndex, Precedence.LimitSubscriptContext).ExpectEntity("limit variable in subscript");
412+
if (error != null) return error;
413+
if (limitSubscriptIndex == limitSubscript.Count) return "Missing → in limit subscript";
414+
limitSubscriptIndex++;
415+
(limitTarget, error) = Transform(limitSubscript, ref limitSubscriptIndex, Precedence.LimitSubscriptContext).ExpectEntity("limit target in subscript");
416+
if (error != null) return error;
417+
var limitDirection =
418+
limitSubscriptIndex == limitSubscript.Count + 1
419+
? ApproachFrom.Left
420+
: limitSubscriptIndex == limitSubscript.Count + 2
421+
? ApproachFrom.Right
422+
: ApproachFrom.BothSides;
423+
handleFunction = limitBody => MathS.Limit(limitBody, limitVariable, limitTarget, limitDirection);
424+
handleFunctionInverse = arg => MathS.NaN;
425+
subscriptAllowed = true;
426+
goto handleFunction;
346427
case Atoms.BinaryOperator { Nucleus: "+" }:
347428
handlePrecedence = Precedence.AddSubtract;
348429
handleBinary = (a, b) => a + b;
@@ -436,10 +517,12 @@ Result HandleSuperscript(ref MathItem? @this, MathList superscript) {
436517
handleBinary = (x, y) => MathS.Negation(MathS.ExclusiveDisjunction(x, y)); // XNOR = equivalence
437518
goto handleBinary;
438519
case Atoms.Relation { Nucleus: "→" }:
439-
handlePrecedence = Precedence.Implication;
440-
handleBinary = MathS.Implication;
441-
binaryIsRightAssociative = true;
442-
goto handleBinary;
520+
if (prec != Precedence.LimitSubscriptContext) {
521+
handlePrecedence = Precedence.Implication;
522+
handleBinary = MathS.Implication;
523+
binaryIsRightAssociative = true;
524+
goto handleBinary;
525+
} else return prev;
443526
case Atoms.Relation { Nucleus: "↛" }:
444527
handlePrecedence = Precedence.Implication;
445528
handleBinary = (x, y) => MathS.Negation(MathS.Implication(x, y));
@@ -542,20 +625,18 @@ Result HandleSuperscript(ref MathItem? @this, MathList superscript) {
542625
i--;
543626
return prev;
544627
}
545-
return
546-
BracketHandlers.TryGetValue((contextInfo.KnownOpening, rightBracket), out var handler)
547-
? handler(prev).Bind(handled => {
548-
MathItem? nullable = handled;
549-
if (HandleSuperscript(ref nullable, super).Error is { } error)
550-
return Result.Err(error);
551-
return Result.Ok(nullable);
552-
})
553-
: $"Unrecognized bracket pair {contextInfo.KnownOpening} {rightBracket}";
554-
case Atoms.Inner { LeftBoundary: { Nucleus: var left }, InnerList: var inner, RightBoundary: { Nucleus: var right } }:
628+
if (InnerHandlers.TryGetValue((contextInfo.KnownOpening, rightBracket), out var handler)) {
629+
(MathItem? handled, error) = handler(prev);
630+
if (error != null) return error;
631+
else if (HandleSuperscript(ref handled, ref i, super).Error is { } superscriptError)
632+
return Result.Err(superscriptError);
633+
return Result.Ok(handled);
634+
} else return $"Unrecognized bracket pair {contextInfo.KnownOpening} {rightBracket}";
635+
case Atoms.Inner { LeftBoundary.Nucleus: var left, InnerList: var inner, RightBoundary.Nucleus: var right }:
555636
(@this, error) = Transform(inner);
556637
if (error != null) return error;
557638
(@this, error) =
558-
BracketHandlers.TryGetValue((left, right), out handler)
639+
InnerHandlers.TryGetValue((left, right), out handler)
559640
? handler(@this)
560641
: $"Unrecognized bracket pair {left ?? "(empty)"} {right ?? "(empty)"}";
561642
if (error != null) return error;
@@ -589,7 +670,7 @@ Result HandleSuperscript(ref MathItem? @this, MathList superscript) {
589670
case Atoms.Space _:
590671
case Atoms.Ordinary { Nucleus: var nucleus } when string.IsNullOrWhiteSpace(nucleus):
591672
break;
592-
case Atoms.Inner inner:
673+
case Atoms.Inner { LeftBoundary.Nucleus: "(" or "[", RightBoundary.Nucleus: ")" or "]" } inner:
593674
var superscript = inner.Superscript;
594675
bracketArgument = inner.InnerList;
595676
goto stealExponent;
@@ -685,17 +766,17 @@ chainedComparisonTarget is { } target
685766
return prev;
686767
}
687768

688-
handlePostfix:
769+
handlePostfix:
689770
(@this, error) =
690771
prev.AsEntity("left operand for " + atom.Nucleus).Bind(e => (MathItem)handlePostfix(e));
691772
if (error != null) return error;
692773
prev = null; // We used up prev, don't keep it
693774
goto handleThis;
694775

695-
handleThis:
776+
handleThis:
696777
if (!subscriptAllowed && atom.Subscript.Count > 0)
697778
return $"Subscripts are unsupported for {atom.TypeName} {atom.Nucleus}";
698-
error = HandleSuperscript(ref @this, atom.Superscript).Error;
779+
error = HandleSuperscript(ref @this, ref i, atom.Superscript).Error;
699780
if (error != null) return error;
700781
Entity? prevEntity, thisEntity;
701782
(prevEntity, error) =
@@ -707,7 +788,7 @@ chainedComparisonTarget is { } target
707788
if (error != null) return error;
708789
prev = prevEntity * thisEntity;
709790
break;
710-
}
791+
}
711792
}
712793
if (ContextInfo.TryGetValue(prec, out var info))
713794
return "Missing " + info.InferredClosing;

0 commit comments

Comments
 (0)