Skip to content

Commit e412602

Browse files
Read an equality as a difference equal to zero in an equation and in a system (#2061)
MathS.SolveEquation, Entity.SolveEquation and MathS.Equations read each equation as an expression equal to zero, and passed an equality a = b on as it stood. One equation came back as the set builder { x : a = b and b = 0 }, which is empty unless b is zero, so "a * x = b".SolveEquation("x") held only where b = 0; a system came back null, no solution. An equality is now read as a - b = 0, as a statement's own solve reads it: "x + 1 = 3".Solve("x") was already { 2 }. The one- variable entry points meet in EquationSolver.Solve, and the system reads its equations once, in its constructor, so its printed form agrees with what it solves. The MCP server rewrote a = b to a - b before asking for a system, and its self-test documented the defect, which now no longer reproduces: both go, and its README lists three workarounds where it listed four. Fixes #1673. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent ec0d963 commit e412602

8 files changed

Lines changed: 96 additions & 28 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,26 @@ A not-equal written `<>` is read as `not (a = b)`, and printed that way.
4040

4141
---
4242

43+
## Unreleased — since 2.6.0
44+
45+
### An equality in an equation or a system is read as a difference equal to zero
46+
47+
**Silent: wrong answers.** `MathS.SolveEquation`, `Entity.SolveEquation` and `MathS.Equations` read each equation as an
48+
expression equal to zero, and passed an equality `a = b` on as it stood. One equation came back as the set of x where
49+
`a = b` and `b = 0`, which is empty unless `b` is zero, and a system came back with no solution. An equality is now read
50+
as `a - b = 0`, as a statement's own solve reads it: `"x + 1 = 3".Solve("x")` was already `{ 2 }`
51+
([#1673](https://github.com/asc-community/AngouriMath/issues/1673)).
52+
53+
| Input | Was (2.6.0) | Now |
54+
|---|---|---|
55+
| `MathS.SolveEquation("x + 1 = 3", "x")` | `{ x : x + 1 = 3 and 3 = 0 }` | `{ 2 }` |
56+
| `"x^2 = 4".SolveEquation("x")` | `{ x : x ^ 2 = 4 and 4 = 0 }` | `{ 2, -2 }` |
57+
| `"a * x = b".ToEntity().SolveEquation("x")` | `{ x : a * x = b and b = 0 }`, which holds only where `b = 0` | `{ b / a }` |
58+
| `MathS.Equations("x + y = 3", "x - y = 1").Solve("x", "y")` | `null` | `[[2, 1]]` |
59+
| `MathS.Equations("x + y = 3", "x - y = 1").ToString()` | `x + y = 3 = 0` and `x - y = 1 = 0` | `x + y - 3 = 0` and `x - y - 1 = 0` |
60+
61+
---
62+
4363
## 2.6.0 — since 2.5.0
4464

4565
### A high half-odd power of a quadratic beside a power of another is reduced in exact rationals

‎Sources/AngouriMath/Convenience/MathS.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ public static Integer GreatestCommonDivisor(Integer a, Integer b)
267267
public static EquationSystem Equations(IEnumerable<Entity> equations) => new EquationSystem(equations);
268268

269269
/// <summary>Solves one equation over one variable</summary>
270-
/// <param name="equation">An equation that is assumed to equal 0</param>
270+
/// <param name="equation">An expression that is assumed to equal 0, or an equality <c>a = b</c>, read as <c>a - b = 0</c></param>
271271
/// <param name="var">Variable whose values we are looking for</param>
272272
/// <returns>A <see cref="Set"/> of possible values or intervals of values</returns>
273273
/// <example>

‎Sources/AngouriMath/Core/EquationSystem.cs‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ public sealed class EquationSystem : ILatexizeable
2222
private readonly IEnumerable<Entity> equations;
2323

2424
/// <summary>
25-
/// The equations you pass should not have an <see cref="MathS.Equality"/> node.
25+
/// Each equation is an expression equal to zero, or an equality <c>a = b</c>, which is read as
26+
/// <c>a - b = 0</c>, as <see cref="MathS.SolveEquation"/> reads one in a single variable.
2627
/// After having created a system of equations, you may solve or latexize it.
2728
/// </summary>
2829
/// <param name="equations">
@@ -36,10 +37,11 @@ public sealed class EquationSystem : ILatexizeable
3637
/// });
3738
/// </code>
3839
/// </example>
39-
public EquationSystem(IEnumerable<Entity> equations) => this.equations = equations;
40+
public EquationSystem(IEnumerable<Entity> equations) => this.equations = equations.Select(AsAnExpressionEqualToZero).ToList();
4041

4142
/// <summary>
42-
/// The equations you pass should not have an <see cref="MathS.Equality"/> node.
43+
/// Each equation is an expression equal to zero, or an equality <c>a = b</c>, which is read as
44+
/// <c>a - b = 0</c>, as <see cref="MathS.SolveEquation"/> reads one in a single variable.
4345
/// After having created a system of equations, you may solve or latexize it.
4446
/// </summary>
4547
/// <param name="equations">
@@ -53,7 +55,13 @@ public sealed class EquationSystem : ILatexizeable
5355
/// );
5456
/// </code>
5557
/// </example>
56-
public EquationSystem(params Entity[] equations) => this.equations = equations;
58+
public EquationSystem(params Entity[] equations) => this.equations = equations.Select(AsAnExpressionEqualToZero).ToList();
59+
60+
// An equality was passed on as it stood and the system answered that it had no solution:
61+
// `x + y = 3` and `x - y = 1` gave null, where `x + y - 3` and `x - y - 1` give `[[2, 1]]`.
62+
// https://github.com/asc-community/AngouriMath/issues/1673
63+
private static Entity AsAnExpressionEqualToZero(Entity equation)
64+
=> equation is Equalsf(var left, var right) ? left - right : equation;
5765

5866
/// <summary>
5967
/// Solves a system of equations, if possible.

‎Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver.cs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ internal static class EquationSolver
2020
/// <summary>Solves one equation</summary>
2121
internal static Set Solve(Entity equation, Variable x)
2222
{
23+
// An equality `a = b` is `a - b = 0`, as a statement's own solve reads it: passed on as it
24+
// stood, `MathS.SolveEquation("x + 1 = 3", x)` was `{ x : x + 1 = 3 and 3 = 0 }`, which is
25+
// empty, where "x + 1 = 3".Solve("x") is `{ 2 }`.
26+
// https://github.com/asc-community/AngouriMath/issues/1673
27+
if (equation is Equalsf(var left, var right))
28+
equation = left - right;
2329
using var _ = MathS.Settings.PrecisionErrorZeroRange.Set(1e-12m);
2430
using var __ = MathS.Settings.FloatToRationalIterCount.Set(0);
2531
using var ___ = MathS.Settings.MaxExpansionTermCount.Set(50);

‎Sources/MCP/AngouriMath.Mcp/SelfTest.cs‎

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -75,22 +75,6 @@ private static readonly (string Name, Func<bool> StillBroken, string Documented)
7575
[
7676
("Simplify(sqrt(x^2))", () => Value("sqrt(x^2)") != "abs(x)",
7777
"left as written rather than reduced to abs(x)"),
78-
("MathS.Equations does not solve a system written as equalities", () =>
79-
{
80-
// x + y = 3 and x - y = 1 meet at (2, 1), which the same system written in '= 0'
81-
// form is solved to. A check for an exception alone would read a system that comes
82-
// back with no solution as fixed. https://github.com/asc-community/AngouriMath/issues/1673
83-
try
84-
{
85-
var solutions = MathS.Equations("x + y = 3".ToEntity(), "x - y = 1".ToEntity()).Solve("x", "y");
86-
return solutions is null || solutions.RowCount != 1
87-
|| solutions[0, 0].Evaled != 2 || solutions[0, 1].Evaled != 1;
88-
}
89-
catch (Exception)
90-
{
91-
return true;
92-
}
93-
}, "x + y = 3, x - y = 1 has no solution when written as equalities; each equation must be given in '= 0' form"),
9478
("DefiniteIntegral is a first-order rule", () =>
9579
{
9680
// Halving the step count roughly doubles the error iff the rule is first order.

‎Sources/MCP/AngouriMath.Mcp/Tools.cs‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,11 +1166,8 @@ private static JsonObject SolveSystem(JsonObject args)
11661166
if (!TryParse(eq, "equations", false, out var e, out var w, out var error))
11671167
return error!;
11681168

1169-
// EquationSystem wants each equation in "= 0" form, and handed an equality it
1170-
// finds no solution, so normalise here: writing 'x + y = 3' is the natural thing
1171-
// for a caller to do, and it should just work.
1172-
// https://github.com/asc-community/AngouriMath/issues/1673
1173-
parsed.Add(e is Equalsf equality ? equality.Left - equality.Right : e);
1169+
// An equality, 'x + y = 3', is read by the library as 'x + y - 3 = 0'.
1170+
parsed.Add(e);
11741171
warnings.AddRange(w);
11751172
}
11761173

‎Sources/MCP/README.md‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,8 @@ existing tool, then a prompt, and add a tool last. A tool's description is a rou
151151
documentation: say when to call it, and where a model would wrongly trust itself, say so.
152152

153153
This is an adapter, not a second computer algebra system. Mathematics belongs in the library, where
154-
the terminal and the notebooks get it too, and four workarounds here wait for it:
154+
the terminal and the notebooks get it too, and three workarounds here wait for it:
155155
eigenvalues by the characteristic polynomial ([#1676](https://github.com/asc-community/AngouriMath/issues/1676)),
156156
a numeric comparison with a tolerance ([#1674](https://github.com/asc-community/AngouriMath/issues/1674)),
157-
`a = b` rewritten to `a - b` for a system ([#1673](https://github.com/asc-community/AngouriMath/issues/1673)),
158157
and the numeric definite integral run twice to find its good digits
159158
([#1675](https://github.com/asc-community/AngouriMath/issues/1675)).
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using AngouriMath.Extensions;
9+
using Xunit;
10+
using static AngouriMath.Entity.Set;
11+
12+
namespace AngouriMath.Tests.Algebra
13+
{
14+
/// <summary>
15+
/// An equation written as an equality <c>a = b</c> is solved as <c>a - b = 0</c> where an expression
16+
/// equal to zero is expected, as a statement's own solve reads it. Passed on as it stood, a system
17+
/// answered that it had no solution, and <see cref="MathS.SolveEquation"/> answered a set builder
18+
/// with a false condition in it, which is empty.
19+
/// <a href="https://github.com/asc-community/AngouriMath/issues/1673">#1673</a>
20+
/// </summary>
21+
[Trait("Area", "Algebra")]
22+
public sealed class AnEqualityReadAsADifferenceTest
23+
{
24+
[Theory]
25+
[InlineData("x + y = 3", "x - y = 1")]
26+
[InlineData("x + y = 3", "x - y - 1")]
27+
[InlineData("x + y - 3", "x - y = 1")]
28+
[InlineData("x = 3 - y", "y = x - 1")]
29+
public void ASystemOfEqualitiesIsSolved(string first, string second)
30+
{
31+
var solution = MathS.Equations(first.ToEntity(), second.ToEntity()).Solve("x", "y");
32+
Assert.NotNull(solution);
33+
Assert.Equal(1, solution!.RowCount);
34+
Assert.Equal(2, (int)solution[0, 0].EvalNumerical().RealPart);
35+
Assert.Equal(1, (int)solution[0, 1].EvalNumerical().RealPart);
36+
}
37+
38+
[Fact]
39+
public void ASystemPrintsEachEquationAsADifferenceEqualToZero()
40+
=> Assert.Equal("x + y - 3 = 0\nx - y - 1 = 0", MathS.Equations("x + y = 3".ToEntity(), "x - y = 1".ToEntity()).ToString());
41+
42+
[Fact]
43+
public void OneEqualityIsSolvedInItsVariable()
44+
=> Assert.Equal(new FiniteSet(2), MathS.SolveEquation("x + 1 = 3", "x"));
45+
46+
[Fact]
47+
public void TheExtensionReadsAnEqualityTheSameWay()
48+
=> Assert.Equal(new FiniteSet(-2, 2), "x^2 = 4".SolveEquation("x"));
49+
50+
[Fact]
51+
public void AnEqualityWithAParameterIsSolvedForTheVariable()
52+
=> Assert.Equal(new FiniteSet("b / a".ToEntity().InnerSimplified), "a * x = b".ToEntity().SolveEquation("x").InnerSimplified);
53+
}
54+
}

0 commit comments

Comments
 (0)