diff --git a/CSharpMath.Core.Tests/Atom/LaTeXParserTest.cs b/CSharpMath.Core.Tests/Atom/LaTeXParserTest.cs
index dfacb74a..1f197bae 100644
--- a/CSharpMath.Core.Tests/Atom/LaTeXParserTest.cs
+++ b/CSharpMath.Core.Tests/Atom/LaTeXParserTest.cs
@@ -1469,6 +1469,30 @@ public void TestModulo() {
Assert.Equal(@"x\bmod y", LaTeXParser.MathListToLaTeX(list).ToString());
}
+ ///
+ /// \pmod{m} is a quad, then "(mod m)": the operator name and the modulus inside parentheses.
+ ///
+ [Fact]
+ public void TestParenthesisedModulo() {
+ var list = ParseLaTeX(@"x\equiv y\pmod{z}");
+ Assert.Collection(list,
+ CheckAtom("x"),
+ CheckAtom("≡"),
+ CheckAtom("y"),
+ CheckAtom("", space => {
+ Assert.Equal(Length.EmWidth.Amount, space.Length);
+ Assert.Equal(Length.EmWidth.IsMu, space.IsMu);
+ }),
+ CheckAtom("", inner => {
+ Assert.Equal(new Boundary("("), inner.LeftBoundary);
+ Assert.Equal(new Boundary(")"), inner.RightBoundary);
+ Assert.Collection(inner.InnerList,
+ CheckAtom("mod"),
+ CheckAtom("z"));
+ }));
+ Assert.Equal(@"x\equiv y\quad \left( \operatorname{mod} z\right) ", LaTeXParser.MathListToLaTeX(list).ToString());
+ }
+
[Theory]
[InlineData(@"\TeX")]
[InlineData(@"\left.\mathrm{T\! \raisebox{-4.5mu}{E}\mkern-2.25muX}\right.")]
diff --git a/CSharpMath.Evaluation.Tests/EvaluationTests.cs b/CSharpMath.Evaluation.Tests/EvaluationTests.cs
index 687e0d77..77d0cb1f 100644
--- a/CSharpMath.Evaluation.Tests/EvaluationTests.cs
+++ b/CSharpMath.Evaluation.Tests/EvaluationTests.cs
@@ -137,8 +137,8 @@ public void Numbers(string input, string converted, string output) =>
[InlineData(@"a \cdot b / c", @"\frac{ab}{c}", @"\frac{ab}{c}")]
[InlineData(@"a / b + c", @"\frac{a}{b}+c", @"\frac{a}{b}+c")]
[InlineData(@"a / b - c", @"\frac{a}{b}-c", @"\frac{a}{b}-c")]
- [InlineData(@"a / b \times c", @"\frac{a}{b}\cdot c", @"\frac{a}{b}\cdot c")]
- [InlineData(@"a / b \cdot c", @"\frac{a}{b}\cdot c", @"\frac{a}{b}\cdot c")]
+ [InlineData(@"a / b \times c", @"\frac{a}{b}\cdot c", @"c\frac{a}{b}")]
+ [InlineData(@"a / b \cdot c", @"\frac{a}{b}\cdot c", @"c\frac{a}{b}")]
[InlineData(@"a / b / c", @"\frac{\frac{a}{b}}{c}", @"\frac{a}{bc}")]
[InlineData(@"2+\frac ab", @"2+\frac{a}{b}", @"2+\frac{a}{b}")]
[InlineData(@"\frac ab+2", @"\frac{a}{b}+2", @"\frac{a}{b}+2")]
@@ -146,8 +146,8 @@ public void Numbers(string input, string converted, string output) =>
[InlineData(@"\frac ab-2", @"\frac{a}{b}-2", @"\frac{a}{b}-2")]
[InlineData(@"2\times\frac ab", @"2\cdot \frac{a}{b}", @"2\cdot \frac{a}{b}")]
[InlineData(@"2\cdot\frac ab", @"2\cdot \frac{a}{b}", @"2\cdot \frac{a}{b}")]
- [InlineData(@"\frac ab\times 2", @"\frac{a}{b}\cdot 2", @"\frac{a}{b}\cdot 2")]
- [InlineData(@"\frac ab\cdot 2", @"\frac{a}{b}\cdot 2", @"\frac{a}{b}\cdot 2")]
+ [InlineData(@"\frac ab\times 2", @"\frac{a}{b}\cdot 2", @"2\cdot \frac{a}{b}")]
+ [InlineData(@"\frac ab\cdot 2", @"\frac{a}{b}\cdot 2", @"2\cdot \frac{a}{b}")]
[InlineData(@"2/\frac ab", @"\frac{2}{\frac{a}{b}}", @"\frac{2b}{a}")]
[InlineData(@"\frac ab/2", @"\frac{\frac{a}{b}}{2}", @"\frac{a}{2b}")]
[InlineData(@"1+\mathrm{i}", @"1+\mathrm{i}", @"1+\mathrm{i}")]
@@ -177,7 +177,7 @@ public void Numbers(string input, string converted, string output) =>
[InlineData(@"\frac{-\infty}{\infty}", @"\frac{-\infty }{\infty }", @"\mathrm{undefined}")]
[InlineData(@"a / bc", @"\frac{a}{bc}", @"\frac{a}{bc}")]
[InlineData(@"a / bc / d", @"\frac{\frac{a}{bc}}{d}", @"\frac{a}{bcd}")]
- [InlineData(@"-2/\sin x/y", @"\frac{\frac{-2}{\sin \left( x\right) }}{y}", @"\frac{-2}{\sin \left( x\right) \cdot y}")]
+ [InlineData(@"-2/\sin x/y", @"\frac{\frac{-2}{\sin \left( x\right) }}{y}", @"\frac{-2}{y\sin \left( x\right) }")]
public void BinaryOperators(string latex, string converted, string result) => Test(latex, converted, result);
///
/// The two forms AngouriMath emits that had no reading here: \bmod for its modulo node
@@ -193,6 +193,15 @@ public void Numbers(string input, string converted, string output) =>
[InlineData(@"\operatorname{\varphi}(10)", @"\operatorname{φ} \left( 10\right) ", @"4")]
[InlineData(@"\operatorname{\varphi}(x)", @"\operatorname{φ} \left( x\right) ", @"\operatorname{φ} \left( x\right) ")]
public void AngouriMathOnlyForms(string latex, string converted, string result) => Test(latex, converted, result);
+ ///
+ /// AngouriMath's congruence, which it writes a \equiv b \pmod{m}. \pmod reads as
+ /// "(mod m)" after a quad, and is taken as the modulus of the \equiv before it.
+ ///
+ [Theory]
+ [InlineData(@"x\equiv y\pmod{z}", @"x\equiv y\quad \left( \operatorname{mod} z\right) ", @"x\equiv y\quad \left( \operatorname{mod} z\right) ")]
+ [InlineData(@"15625\equiv 1\pmod{7}", @"15625\equiv 1\quad \left( \operatorname{mod} 7\right) ", @"\top ")]
+ [InlineData(@"x+1\equiv y\pmod{z+1}", @"x+1\equiv y\quad \left( \operatorname{mod} z+1\right) ", @"1+x\equiv y\quad \left( \operatorname{mod} 1+z\right) ")]
+ public void Congruence(string latex, string converted, string result) => Test(latex, converted, result);
[Theory]
[InlineData(@"+i", @"\mathrm{i}", @"\mathrm{i}")]
[InlineData(@"-i", @"-\mathrm{i}", @"-\mathrm{i}")]
@@ -207,9 +216,9 @@ public void Numbers(string input, string converted, string output) =>
[InlineData("a++a", "a+a", @"2a")]
[InlineData("a+-a", "a-a", "0")]
[InlineData("a-+a", "a-a", "0")]
- [InlineData("a--a", @"a--a", @"2a")]
+ [InlineData("a--a", @"a+a", @"2a")]
[InlineData("a+++a", "a+a", @"2a")]
- [InlineData("a---a", @"a--\left( -1\right) \cdot a", "0")]
+ [InlineData("a---a", @"a+\left( -1\right) \cdot a", @"0")]
[InlineData(@"a\times+a", @"aa", "a^2")]
[InlineData(@"a\times-a", @"a\left( -1\right) \cdot a", "-a^2")]
[InlineData(@"+a\times+a", @"aa", "a^2")]
@@ -219,7 +228,7 @@ public void Numbers(string input, string converted, string output) =>
[InlineData("+a/+a", @"\frac{a}{a}", @"1\quad \mathrm{for}\quad a\neq 0")]
[InlineData("-a/-a", @"\frac{-a}{-a}", @"1\quad \mathrm{for}\quad a\neq 0")]
[InlineData(@"-2+-2+-2", @"-2-2-2", "-6")]
- [InlineData(@"-2--2--2", @"-2--2--2", "2")]
+ [InlineData(@"-2--2--2", @"-2+2+2", @"2")]
[InlineData(@"-2\times -2\times -2", @"-2\left( -1\right) \cdot 2\left( -1\right) \cdot 2", "-8")]
[InlineData(@"-2/-2/-2", @"\frac{\frac{-2}{-2}}{-2}", @"\frac{-1}{2}")]
public void UnaryOperators(string latex, string converted, string result) => Test(latex, converted, result);
@@ -277,7 +286,7 @@ public void Numbers(string input, string converted, string output) =>
[InlineData(@"2\times3^4", @"2\cdot 3^4", "162")]
[InlineData(@"1/x", @"\frac{1}{x}", @"\frac{1}{x}")]
[InlineData(@"2/x", @"\frac{2}{x}", @"\frac{2}{x}")]
- [InlineData(@"0^x", @"0^x", @"0\quad \mathrm{for}\quad \left( 1+\frac{1}{\operatorname{sgn} \left( x\right) ^2}\right) \cdot x>0")]
+ [InlineData(@"0^x", @"0^x", @"0\quad \mathrm{for}\quad x\left( 1+\frac{1}{\operatorname{sgn} \left( x\right) ^2}\right) >0")]
[InlineData(@"1^x", @"1^x", @"1")]
[InlineData(@"x^0", @"x^0", @"1\quad \mathrm{for}\quad x\neq 0")]
[InlineData(@"x^{-1}", @"x^{-1}", @"\frac{1}{x}")]
@@ -358,7 +367,7 @@ public void Numbers(string input, string converted, string output) =>
[InlineData(@"\cos -x", @"\cos \left( -x\right) ", @"\cos \left( x\right) ")] // 2.2.0 uses evenness
[InlineData(@"\tan x\%", @"\tan \left( \frac{x}{100}\right) ", @"\tan \left( \frac{x}{100}\right) ")]
[InlineData(@"\tan x\%^2", @"\tan \left( \left( \frac{x}{100}\right) ^2\right) ", @"\tan \left( \left( \frac{x}{100}\right) ^2\right) ")]
- [InlineData(@"\cot x\times y", @"\cot \left( x\right) \cdot y", @"\cot \left( x\right) \cdot y")]
+ [InlineData(@"\cot x\times y", @"\cot \left( x\right) \cdot y", @"y\cot \left( x\right) ")]
[InlineData(@"\cot x/y", @"\frac{\cot \left( x\right) }{y}", @"\frac{\cot \left( x\right) }{y}")]
[InlineData(@"\cos \arccos x", @"\cos \left( \arccos \left( x\right) \right) ", @"x")]
[InlineData(@"\sin^2 x", @"\sin \left( x\right) ^2", @"\sin \left( x\right) ^2")]
@@ -403,7 +412,7 @@ public void Numbers(string input, string converted, string output) =>
[InlineData(@"\cos^{-1} -x", @"\arccos \left( -x\right) ", @"\arccos \left( -x\right) ")]
[InlineData(@"\tan^{-1} x\%", @"\arctan \left( \frac{x}{100}\right) ", @"\arctan \left( \frac{x}{100}\right) ")]
[InlineData(@"\tan^{-1} x\%^2", @"\arctan \left( \left( \frac{x}{100}\right) ^2\right) ", @"\arctan \left( \left( \frac{x}{100}\right) ^2\right) ")]
- [InlineData(@"\cot^{-1} x\times y", @"\arccot \left( x\right) \cdot y", @"\arccot \left( x\right) \cdot y")]
+ [InlineData(@"\cot^{-1} x\times y", @"\arccot \left( x\right) \cdot y", @"y\arccot \left( x\right) ")]
[InlineData(@"\cot^{-1} x/y", @"\frac{\arccot \left( x\right) }{y}", @"\frac{\arccot \left( x\right) }{y}")]
// arccos(cos x) is x only on [0, pi]; AngouriMath 2.2.0 no longer claims it in general.
[InlineData(@"\cos^{-1} \arccos^{-1} x", @"\arccos \left( \cos \left( x\right) \right) ", @"\arccos \left( \cos \left( x\right) \right) ")]
@@ -458,7 +467,7 @@ public void Numbers(string input, string converted, string output) =>
[InlineData(@"\sin((x+1)+2)", @"\sin \left( x+1+2\right) ", @"\sin \left( 3+x\right) ")]
[InlineData(@"\sin(x)2", @"\sin \left( x\right) \cdot 2", @"2\sin \left( x\right) ")]
[InlineData(@"\sin(x)(x+1)", @"\sin \left( x\right) \left( x+1\right) ", @"\sin \left( x\right) \left( 1+x\right) ")]
- [InlineData(@"\sin(x)(x+1)(x)", @"\sin \left( x\right) \left( x+1\right) \cdot x", @"\sin \left( x\right) \left( 1+x\right) \cdot x")]
+ [InlineData(@"\sin(x)(x+1)(x)", @"\sin \left( x\right) \left( x+1\right) \cdot x", @"x\sin \left( x\right) \left( 1+x\right) ")]
[InlineData(@"\sin(x^2)", @"\sin \left( x^2\right) ", @"\sin \left( x^2\right) ")]
[InlineData(@"\sin\ (x^2)", @"\sin \left( x^2\right) ", @"\sin \left( x^2\right) ")]
[InlineData(@"\sin\; (x^2)", @"\sin \left( x^2\right) ", @"\sin \left( x^2\right) ")]
@@ -492,14 +501,14 @@ public void Numbers(string input, string converted, string output) =>
[InlineData(@"\sin^a\ (x)^2", @"\sin \left( x\right) ^{a\cdot 2}", @"\sin \left( x\right) ^{2a}")]
[InlineData(@"\sin^a\; (x)^2", @"\sin \left( x\right) ^{a\cdot 2}", @"\sin \left( x\right) ^{2a}")]
[InlineData(@"\sin^a\ \; (x)^2", @"\sin \left( x\right) ^{a\cdot 2}", @"\sin \left( x\right) ^{2a}")]
- [InlineData(@"\sin(x)^2(x)", @"\sin \left( x\right) ^2\cdot x", @"\sin \left( x\right) ^2\cdot x")]
- [InlineData(@"\sin\ (x)^2(x)", @"\sin \left( x\right) ^2\cdot x", @"\sin \left( x\right) ^2\cdot x")]
- [InlineData(@"\sin\; (x)^2(x)", @"\sin \left( x\right) ^2\cdot x", @"\sin \left( x\right) ^2\cdot x")]
- [InlineData(@"\sin\ \; (x)^2(x)", @"\sin \left( x\right) ^2\cdot x", @"\sin \left( x\right) ^2\cdot x")]
- [InlineData(@"\sin^a(x)^2(x)", @"\sin \left( x\right) ^{a\cdot 2}\cdot x", @"\sin \left( x\right) ^{2a}\cdot x")]
- [InlineData(@"\sin^a\ (x)^2(x)", @"\sin \left( x\right) ^{a\cdot 2}\cdot x", @"\sin \left( x\right) ^{2a}\cdot x")]
- [InlineData(@"\sin^a\; (x)^2(x)", @"\sin \left( x\right) ^{a\cdot 2}\cdot x", @"\sin \left( x\right) ^{2a}\cdot x")]
- [InlineData(@"\sin^a\ \; (x)^2(x)", @"\sin \left( x\right) ^{a\cdot 2}\cdot x", @"\sin \left( x\right) ^{2a}\cdot x")]
+ [InlineData(@"\sin(x)^2(x)", @"\sin \left( x\right) ^2\cdot x", @"x\sin \left( x\right) ^2")]
+ [InlineData(@"\sin\ (x)^2(x)", @"\sin \left( x\right) ^2\cdot x", @"x\sin \left( x\right) ^2")]
+ [InlineData(@"\sin\; (x)^2(x)", @"\sin \left( x\right) ^2\cdot x", @"x\sin \left( x\right) ^2")]
+ [InlineData(@"\sin\ \; (x)^2(x)", @"\sin \left( x\right) ^2\cdot x", @"x\sin \left( x\right) ^2")]
+ [InlineData(@"\sin^a(x)^2(x)", @"\sin \left( x\right) ^{a\cdot 2}\cdot x", @"x\sin \left( x\right) ^{2a}")]
+ [InlineData(@"\sin^a\ (x)^2(x)", @"\sin \left( x\right) ^{a\cdot 2}\cdot x", @"x\sin \left( x\right) ^{2a}")]
+ [InlineData(@"\sin^a\; (x)^2(x)", @"\sin \left( x\right) ^{a\cdot 2}\cdot x", @"x\sin \left( x\right) ^{2a}")]
+ [InlineData(@"\sin^a\ \; (x)^2(x)", @"\sin \left( x\right) ^{a\cdot 2}\cdot x", @"x\sin \left( x\right) ^{2a}")]
[InlineData(@"\sin (\frac\pi2)", @"\sin \left( \frac{\mathrm{\pi }}{2}\right) ", @"1")]
[InlineData(@"\sin (\frac\pi2)+1", @"\sin \left( \frac{\mathrm{\pi }}{2}\right) +1", @"2")]
public void Parentheses(string latex, string converted, string result) {
@@ -576,7 +585,7 @@ public void Matrices(string latex, string converted, string result) {
[InlineData(@"(xy,xy]", @"\emptyset ", @"\emptyset ")]
[InlineData(@"[xy,xy)", @"\emptyset ", @"\emptyset ")]
[InlineData(@"(xy,xy)", @"\emptyset ", @"\emptyset ")]
- [InlineData(@"\frac{[7, 8]}{9}", @"\frac{\left[ 7,8\right] }{9}", @"\frac{\left[ 7,8\right] }{9}")]
+ [InlineData(@"\frac{[7, 8]}{9}", @"\frac{\left[ 7,8\right] }{9}", @"\left[ \frac{7}{9},\frac{8}{9}\right] ")]
[InlineData(@"+(3,4]", @"\left( 3,4\right] ", @"\left( 3,4\right] ")]
public void Intervals(string latex, string converted, string result) {
Test(latex, converted, result);
@@ -818,6 +827,9 @@ public void Intervals(string latex, string converted, string result) {
[InlineData(@"\begin{cases}1&\end{cases}", "Missing case predicate")]
[InlineData(@"\begin{cases}1&\mathrm{for}^23\end{cases}", "for operator cannot have a superscript")]
[InlineData(@"\begin{cases}1&\mathrm{for}_23\end{cases}", "for operator cannot have a subscript")]
+ [InlineData(@"x\equiv y", @"≡ is read as a congruence, so its right side must be followed by \pmod")]
+ [InlineData(@"\equiv y\pmod{z}", "Missing left side for ≡")]
+ [InlineData(@"x\pmod{y}", @"\pmod is read only after the right side of ≡")]
[InlineData(@"\left\{ \, \begin{array}{ll}\textstyle x+1&\textstyle \mathrm{otherwise}&ERROR\\ \textstyle x-1&\textstyle \mathrm{otherwise}\end{array}\right. ", "The cases environment must have 1 to 2 columns per row")]
public void Error(string badLaTeX, string error) =>
Evaluation.Evaluate(ParseLaTeX(badLaTeX))
@@ -1007,30 +1019,30 @@ public void ChainedComparisons(string latex, string converted, string result, st
[InlineData(@"\frac{\mathrm{d}}{\mathrm{d}(x)}(x)y+1", @"\frac{\mathrm{d}}{\mathrm{d}x}xy+1", @"1+y")]
[InlineData(@"\frac{\mathrm{d}}{\mathrm{d}x^2}x^2y+1", @"\frac{\mathrm{d}}{\mathrm{d}\left( x^2\right) }x^2\cdot y+1", @"1+y")]
[InlineData(@"\frac{\mathrm{d}x^2}{\mathrm{d}x^2}y+1", @"\left( \frac{\mathrm{d}}{\mathrm{d}\left( x^2\right) }x^2\right) \cdot y+1", @"1+y")]
- [InlineData(@"\frac{\mathrm{d}x}{\mathrm{d}x^2}xy+1", @"\left( \frac{\mathrm{d}}{\mathrm{d}\left( x^2\right) }x\right) \cdot xy+1", @"1")]
- [InlineData(@"\frac{\mathrm{d}x}{\mathrm{d}(x^2)}(x)y+1", @"\left( \frac{\mathrm{d}}{\mathrm{d}\left( x^2\right) }x\right) \cdot xy+1", @"1")]
+ [InlineData(@"\frac{\mathrm{d}x}{\mathrm{d}x^2}xy+1", @"\left( \frac{\mathrm{d}}{\mathrm{d}\left( x^2\right) }x\right) \cdot xy+1", @"1+\frac{y}{2}\quad \mathrm{for}\quad x\neq 0")]
+ [InlineData(@"\frac{\mathrm{d}x}{\mathrm{d}(x^2)}(x)y+1", @"\left( \frac{\mathrm{d}}{\mathrm{d}\left( x^2\right) }x\right) \cdot xy+1", @"1+\frac{y}{2}\quad \mathrm{for}\quad x\neq 0")]
[InlineData(@"\frac{\mathrm{d}^2}{\mathrm{d}x^2}x^2y+1", @"\frac{\mathrm{d^{\mathnormal{2}}}}{\mathrm{d}x^2}x^2\cdot y+1", @"1+2y")]
[InlineData(@"\frac{\mathrm{d}^2}{\mathrm{d}(x^2)^2}x^2y+1", @"\frac{\mathrm{d^{\mathnormal{2}}}}{\mathrm{d}\left( x^2\right) ^2}x^2\cdot y+1", @"1")]
[InlineData(@"\frac{\mathrm{d}^2x}{\mathrm{d}x^2}x^2y+1", @"\left( \frac{\mathrm{d^{\mathnormal{2}}}}{\mathrm{d}x^2}x\right) \cdot x^2\cdot y+1", @"1")]
- [InlineData(@"\frac{\mathrm{d}^2x}{\mathrm{d}(x^2)^2}x^2y+1", @"\left( \frac{\mathrm{d^{\mathnormal{2}}}}{\mathrm{d}\left( x^2\right) ^2}x\right) \cdot x^2\cdot y+1", @"1")]
+ [InlineData(@"\frac{\mathrm{d}^2x}{\mathrm{d}(x^2)^2}x^2y+1", @"\left( \frac{\mathrm{d^{\mathnormal{2}}}}{\mathrm{d}\left( x^2\right) ^2}x\right) \cdot x^2\cdot y+1", @"1+\frac{\frac{-1}{4}\cdot y}{x}")]
[InlineData(@"\frac{\mathrm{d}}{\mathrm{d}x}\lim_{y\to z}xy+x", @"\frac{\mathrm{d}}{\mathrm{d}x}\lim _{y\rightarrow z}xy+x", @"x+z")]
[InlineData(@"\frac{\mathrm{d}^0x}{\mathrm{d}x^0}y+1", @"\left( \frac{\mathrm{d^{\mathnormal{0}}}}{\mathrm{d}x^0}x\right) \cdot y+1", @"1+xy")]
- [InlineData(@"\frac{\mathrm{d}^{-1}x}{\mathrm{d}x^{-1}}y+1", @"\left( \int x\, \mathrm{d}x\right) \cdot y+1", @"\left( \frac{x^2}{2}+C\right) \cdot y+1")]
+ [InlineData(@"\frac{\mathrm{d}^{-1}x}{\mathrm{d}x^{-1}}y+1", @"\left( \int x\, \mathrm{d}x\right) \cdot y+1", @"y\left( \frac{x^2}{2}+C\right) +1")]
[InlineData(@"\frac\mathrm{dy}\mathrm{dx}\mathrm{dx}", @"\frac{\mathrm{dy}}{\mathrm{dx}}\cdot \mathrm{dx}", @"\mathrm{dy}\quad \mathrm{for}\quad \mathrm{dx}\neq 0")] // upright letters are interpreted together.
[InlineData(@"\frac\mathrm{d\ y}\mathrm{d\ x}", @"\frac{\mathrm{d}}{\mathrm{d}x}y", @"0")] // spaces are required to separate upright letters.
[InlineData(@"\frac\mathrm{dx}\mathrm{dx}", @"\frac{\mathrm{dx}}{\mathrm{dx}}", @"1\quad \mathrm{for}\quad \mathrm{dx}\neq 0")]
public void Derivative(string latex, string converted, string result) => Test(latex, converted, result);
[Theory]
[InlineData(@"\int\mathrm{d}x", @"\int 1\, \mathrm{d}x", @"C+x")]
- [InlineData(@"\int\int\mathrm{d}x\mathrm{d}y", @"\int \int 1\, \mathrm{d}x\, \mathrm{d}y", @"C_1+\left( C+x\right) \cdot y")]
- [InlineData(@"(\int\int\mathrm{d}x\mathrm{d}y)", @"\int \int 1\, \mathrm{d}x\, \mathrm{d}y", @"C_1+\left( C+x\right) \cdot y")]
- [InlineData(@"\int\int C\mathrm{d}x\mathrm{d}y", @"\int \int C\, \mathrm{d}x\, \mathrm{d}y", @"\left( C_1+Cx\right) \cdot y+C_2")]
+ [InlineData(@"\int\int\mathrm{d}x\mathrm{d}y", @"\int \int 1\, \mathrm{d}x\, \mathrm{d}y", @"C_1+y\left( C+x\right) ")]
+ [InlineData(@"(\int\int\mathrm{d}x\mathrm{d}y)", @"\int \int 1\, \mathrm{d}x\, \mathrm{d}y", @"C_1+y\left( C+x\right) ")]
+ [InlineData(@"\int\int C\mathrm{d}x\mathrm{d}y", @"\int \int C\, \mathrm{d}x\, \mathrm{d}y", @"y\left( C_1+Cx\right) +C_2")]
[InlineData(@"\int\frac\mathrm{d}{\mathrm{d}x}\int C\mathrm{d}x\mathrm{d}y", @"\int \frac{\mathrm{d}}{\mathrm{d}x}\int C\, \mathrm{d}x\, \mathrm{d}y", @"C_1+Cy")]
[InlineData(@"\int x\,\mathrm{d}x", @"\int x\, \mathrm{d}x", @"\frac{x^2}{2}+C")]
- [InlineData(@"\int x+y\,\mathrm{d}x", @"\int \left( x+y\right) \, \mathrm{d}x", @"\frac{\left( x+y\right) ^2}{2}+C")]
- [InlineData(@"2\int x+y\,\mathrm{d}x\cdot 3", @"2\left( \int \left( x+y\right) \, \mathrm{d}x\right) \cdot 3", @"6\left( C+\frac{\left( x+y\right) ^2}{2}\right) ")]
- [InlineData(@"2+\int\sin x+y\,\mathrm{d}\sin x+3", @"2+\int \left( \sin \left( x\right) +y\right) \, \mathrm{d}\left( \sin \left( x\right) \right) +3", @"5+\frac{\left( \sin \left( x\right) +y\right) ^2}{2}+C")]
- [InlineData(@"2+\int xy+z\,\mathrm{d}xy\cdot 3", @"2+\left( \int \left( xy+z\right) \, \mathrm{d}\left( xy\right) \right) \cdot 3", @"2+\left( \frac{\left( xy+z\right) ^2}{2}+C\right) \cdot 3")]
+ [InlineData(@"\int x+y\,\mathrm{d}x", @"\int \left( x+y\right) \, \mathrm{d}x", @"\frac{x^2}{2}+yx+C")]
+ [InlineData(@"2\int x+y\,\mathrm{d}x\cdot 3", @"2\left( \int \left( x+y\right) \, \mathrm{d}x\right) \cdot 3", @"6\left( C+\frac{x^2}{2}+xy\right) ")]
+ [InlineData(@"2+\int\sin x+y\,\mathrm{d}\sin x+3", @"2+\int \left( \sin \left( x\right) +y\right) \, \mathrm{d}\left( \sin \left( x\right) \right) +3", @"5+\frac{\sin \left( x\right) ^2}{2}+y\sin \left( x\right) +C")]
+ [InlineData(@"2+\int xy+z\,\mathrm{d}xy\cdot 3", @"2+\left( \int \left( xy+z\right) \, \mathrm{d}\left( xy\right) \right) \cdot 3", @"2+3\left( \frac{\left( xy\right) ^2}{2}+zxy+C\right) ")]
[InlineData(@"\int_0^1\mathrm{d}x", @"\int _0^11\, \mathrm{d}x", @"1")]
[InlineData(@"\int_{10}^{11}\mathrm{d}x", @"\int _{10}^{11}1\, \mathrm{d}x", @"1")]
[InlineData(@"\int_{10}^{11}x\mathrm{d}x", @"\int _{10}^{11}x\, \mathrm{d}x", @"\frac{21}{2}")]
@@ -1039,7 +1051,7 @@ public void ChainedComparisons(string latex, string converted, string result, st
[Theory]
[InlineData(@"x\mathrm{for}y", @"x\quad \mathrm{for}\quad y", @"x\quad \mathrm{for}\quad y")]
[InlineData(@"x\quad \mathrm{for}\quad y", @"x\quad \mathrm{for}\quad y", @"x\quad \mathrm{for}\quad y")]
- [InlineData(@"(x\mathrm{for}y)\mathrm{for}z", @"x\quad \mathrm{for}\quad y\quad \mathrm{for}\quad z", @"x\quad \mathrm{for}\quad y\wedge z")]
+ [InlineData(@"(x\mathrm{for}y)\mathrm{for}z", @"\left( x\quad \mathrm{for}\quad y\right) \quad \mathrm{for}\quad z", @"x\quad \mathrm{for}\quad y\wedge z")]
[InlineData(@"x\mathrm{for}(y\mathrm{for}z)", @"x\quad \mathrm{for}\quad y\quad \mathrm{for}\quad z", @"x\quad \mathrm{for}\quad y\wedge z")]
[InlineData(@"x\mathrm{for}y\mathrm{for}z", @"x\quad \mathrm{for}\quad y\quad \mathrm{for}\quad z", @"x\quad \mathrm{for}\quad y\wedge z")]
[InlineData(@"x\mathrm{for}y,y\mathrm{for}z,z\mathrm{for}a\mathrm{for}b", @"x\quad \mathrm{for}\quad y,y\quad \mathrm{for}\quad z,z\quad \mathrm{for}\quad a\quad \mathrm{for}\quad b", null)]
diff --git a/CSharpMath.Evaluation/CSharpMath.Evaluation.csproj b/CSharpMath.Evaluation/CSharpMath.Evaluation.csproj
index 9b5aa3db..e2b58836 100644
--- a/CSharpMath.Evaluation/CSharpMath.Evaluation.csproj
+++ b/CSharpMath.Evaluation/CSharpMath.Evaluation.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/CSharpMath.Evaluation/Evaluation.cs b/CSharpMath.Evaluation/Evaluation.cs
index 7fe4bcdd..67cd33e6 100644
--- a/CSharpMath.Evaluation/Evaluation.cs
+++ b/CSharpMath.Evaluation/Evaluation.cs
@@ -82,6 +82,17 @@ public static MathList Visualize(MathItem entity) =>
.Match(list => list, e => throw new InvalidCodePathException(e));
public static Result Evaluate(MathList mathList) =>
Transform(mathList.Clone(true)).Bind(result => result is { } r ? Result.Ok(r) : Result.Err("There is nothing to evaluate"));
+ /// Whether is the "(mod m)" that \pmod{m} produces, and m if so.
+ static bool IsModulus(MathAtom atom, out MathList modulus) {
+ if (atom is Atoms.Inner {
+ LeftBoundary.Nucleus: "(", RightBoundary.Nucleus: ")",
+ InnerList: [Atoms.LargeOperator { Nucleus: "mod" }, ..] inner }) {
+ modulus = new MathList(inner.Skip(1));
+ return true;
+ }
+ modulus = new MathList();
+ return false;
+ }
static Result Transform(MathList mathList) {
int i = 0;
return Transform(mathList, ref i, Precedence.DefaultContext);
@@ -699,6 +710,30 @@ string GreekToLaTeXCommandName(string n) =>
handlePrecedence = Precedence.Comparison;
handleBinary = (x, y) => MathS.Negation(new Entity.Equalsf(x, y));
goto handleBinary;
+ // AngouriMath's congruence, a ≡ b (mod m), written a \equiv b \pmod{m}.
+ case Atoms.Relation { Nucleus: "≡" }:
+ if (atom.Superscript is not [] || atom.Subscript is not [])
+ return "Scripts are unsupported for Relation ≡";
+ if (prec >= Precedence.Comparison) {
+ i--;
+ return prev;
+ }
+ if (prev is null) return "Missing left side for ≡";
+ i++;
+ (next, error) = Transform(mathList, ref i, Precedence.Comparison);
+ if (error != null) return error;
+ (var congruent, error) = prev.AsEntity("left side of ≡");
+ if (error != null) return error;
+ (var to, error) = next.AsEntity("right side of ≡");
+ if (error != null) return error;
+ if (i + 1 >= mathList.Count || !IsModulus(mathList[i + 1], out var modulusList))
+ return @"≡ is read as a congruence, so its right side must be followed by \pmod";
+ i++;
+ (var modulus, error) = Transform(modulusList).Bind(m => m.AsEntity("modulus of ≡"));
+ if (error != null) return error;
+ @this = MathS.NumberTheory.Congruent(congruent, to, modulus);
+ prev = null; // We used up prev, don't keep it
+ goto handleThis;
case Atoms.Relation { Nucleus: "<" }:
handlePrecedence = Precedence.Comparison;
handleBinary = (x, y) => new Entity.Lessf(x, y);
@@ -803,6 +838,12 @@ string GreekToLaTeXCommandName(string n) =>
if (InnerHandlers.TryGetValue((contextInfo.KnownOpening, rightBracket), out var handler))
return handler(prev).Bind(x => (MathItem?)x);
else return $"Unrecognized bracket pair {contextInfo.KnownOpening} {rightBracket}";
+ // "(mod m)", which \pmod produces: it ends the right side of a congruence the way a comparison would,
+ // and the ≡ case below reads it as the modulus rather than letting it multiply onto that side.
+ case Atoms.Inner pmod when IsModulus(pmod, out _):
+ if (prec < Precedence.Comma) return @"\pmod is read only after the right side of ≡";
+ i--;
+ return prev;
case Atoms.Inner { LeftBoundary.Nucleus: var left, InnerList: var inner, RightBoundary.Nucleus: var right }:
(@this, error) = Transform(inner);
if (error != null) return error;
diff --git a/CSharpMath.Rendering.Tests/TestAngouriMathForms.cs b/CSharpMath.Rendering.Tests/TestAngouriMathForms.cs
index 23452f75..6f3cf731 100644
--- a/CSharpMath.Rendering.Tests/TestAngouriMathForms.cs
+++ b/CSharpMath.Rendering.Tests/TestAngouriMathForms.cs
@@ -19,6 +19,7 @@ static System.Drawing.RectangleF Measure(string latex) {
[InlineData(@"x\bmod y")]
[InlineData(@"\operatorname{φ}\left( x\right) ")]
[InlineData(@"\operatorname{φ}\left( x\bmod y\right) ")]
+ [InlineData(@"x\equiv y\pmod{z}")]
public void TheyLayOut(string latex) {
var measured = Measure(latex);
Assert.True(measured.Width > 0, $"zero width for {latex}");
@@ -33,5 +34,12 @@ public void TheyLayOut(string latex) {
[Fact]
public void ModuloTakesUpRoom() =>
Assert.True(Measure(@"x\bmod y").Width > Measure(@"xy").Width * 2);
+
+ ///
+ /// \pmod{z} is a quad, the parentheses, "mod" and z, so it is wider than the quad and z alone.
+ ///
+ [Fact]
+ public void ParenthesisedModuloTakesUpRoom() =>
+ Assert.True(Measure(@"y\pmod{z}").Width > Measure(@"y\quad z").Width + Measure(@"xy").Width);
}
}
diff --git a/CSharpMath/Atom/LaTeXSettings.cs b/CSharpMath/Atom/LaTeXSettings.cs
index 18ccdddb..c178698b 100644
--- a/CSharpMath/Atom/LaTeXSettings.cs
+++ b/CSharpMath/Atom/LaTeXSettings.cs
@@ -264,6 +264,16 @@ public static class LaTeXSettings {
if (!parser.ReadCharIfAvailable('}')) return "Expected }";
return Ok(new LargeOperator(operatorname.ToString(), null));
} },
+ // \pmod{m} is LaTeX's "(mod m)", set off by a quad from what precedes it. AngouriMath emits it
+ // for its congruence node, `a \equiv b \pmod{m}`. It is built from atoms that already lay out,
+ // as \Bra and \Ket are: the space, then a parenthesised list opening with the operator name.
+ { @"\pmod", (parser, accumulate, stopChar) =>
+ parser.ReadArgument().Bind(modulus => {
+ accumulate.Add(new Space(Length.EmWidth));
+ var inner = new MathList(new LargeOperator("mod", null));
+ inner.Append(modulus);
+ return Ok(new Inner(new Boundary("("), inner, new Boundary(")")));
+ }) },
// Bra and Ket implementations are derived from Donald Arseneau's braket LaTeX package.
// See: https://www.ctan.org/pkg/braket
{ @"\Bra", (parser, accumulate, stopChar) =>