Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ A not-equal written `<>` is read as `not (a = b)`, and printed that way.

---

## Unreleased — since 2.6.0

### An equality in an equation or a system is read as a difference equal to zero

**Silent: wrong answers.** `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 of x where
`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
as `a - b = 0`, as a statement's own solve reads it: `"x + 1 = 3".Solve("x")` was already `{ 2 }`
([#1673](https://github.com/asc-community/AngouriMath/issues/1673)).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `MathS.SolveEquation("x + 1 = 3", "x")` | `{ x : x + 1 = 3 and 3 = 0 }` | `{ 2 }` |
| `"x^2 = 4".SolveEquation("x")` | `{ x : x ^ 2 = 4 and 4 = 0 }` | `{ 2, -2 }` |
| `"a * x = b".ToEntity().SolveEquation("x")` | `{ x : a * x = b and b = 0 }`, which holds only where `b = 0` | `{ b / a }` |
| `MathS.Equations("x + y = 3", "x - y = 1").Solve("x", "y")` | `null` | `[[2, 1]]` |
| `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` |

---

## 2.6.0 — since 2.5.0

### A high half-odd power of a quadratic beside a power of another is reduced in exact rationals
Expand Down
2 changes: 1 addition & 1 deletion Sources/AngouriMath/Convenience/MathS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ public static Integer GreatestCommonDivisor(Integer a, Integer b)
public static EquationSystem Equations(IEnumerable<Entity> equations) => new EquationSystem(equations);

/// <summary>Solves one equation over one variable</summary>
/// <param name="equation">An equation that is assumed to equal 0</param>
/// <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>
/// <param name="var">Variable whose values we are looking for</param>
/// <returns>A <see cref="Set"/> of possible values or intervals of values</returns>
/// <example>
Expand Down
16 changes: 12 additions & 4 deletions Sources/AngouriMath/Core/EquationSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public sealed class EquationSystem : ILatexizeable
private readonly IEnumerable<Entity> equations;

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

/// <summary>
/// The equations you pass should not have an <see cref="MathS.Equality"/> node.
/// Each equation is an expression equal to zero, or an equality <c>a = b</c>, which is read as
/// <c>a - b = 0</c>, as <see cref="MathS.SolveEquation"/> reads one in a single variable.
/// After having created a system of equations, you may solve or latexize it.
/// </summary>
/// <param name="equations">
Expand All @@ -53,7 +55,13 @@ public sealed class EquationSystem : ILatexizeable
/// );
/// </code>
/// </example>
public EquationSystem(params Entity[] equations) => this.equations = equations;
public EquationSystem(params Entity[] equations) => this.equations = equations.Select(AsAnExpressionEqualToZero).ToList();

// An equality was passed on as it stood and the system answered that it had no solution:
// `x + y = 3` and `x - y = 1` gave null, where `x + y - 3` and `x - y - 1` give `[[2, 1]]`.
// https://github.com/asc-community/AngouriMath/issues/1673
private static Entity AsAnExpressionEqualToZero(Entity equation)
=> equation is Equalsf(var left, var right) ? left - right : equation;

/// <summary>
/// Solves a system of equations, if possible.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ internal static class EquationSolver
/// <summary>Solves one equation</summary>
internal static Set Solve(Entity equation, Variable x)
{
// An equality `a = b` is `a - b = 0`, as a statement's own solve reads it: passed on as it
// stood, `MathS.SolveEquation("x + 1 = 3", x)` was `{ x : x + 1 = 3 and 3 = 0 }`, which is
// empty, where "x + 1 = 3".Solve("x") is `{ 2 }`.
// https://github.com/asc-community/AngouriMath/issues/1673
if (equation is Equalsf(var left, var right))
equation = left - right;
using var _ = MathS.Settings.PrecisionErrorZeroRange.Set(1e-12m);
using var __ = MathS.Settings.FloatToRationalIterCount.Set(0);
using var ___ = MathS.Settings.MaxExpansionTermCount.Set(50);
Expand Down
16 changes: 0 additions & 16 deletions Sources/MCP/AngouriMath.Mcp/SelfTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,22 +75,6 @@ private static readonly (string Name, Func<bool> StillBroken, string Documented)
[
("Simplify(sqrt(x^2))", () => Value("sqrt(x^2)") != "abs(x)",
"left as written rather than reduced to abs(x)"),
("MathS.Equations does not solve a system written as equalities", () =>
{
// x + y = 3 and x - y = 1 meet at (2, 1), which the same system written in '= 0'
// form is solved to. A check for an exception alone would read a system that comes
// back with no solution as fixed. https://github.com/asc-community/AngouriMath/issues/1673
try
{
var solutions = MathS.Equations("x + y = 3".ToEntity(), "x - y = 1".ToEntity()).Solve("x", "y");
return solutions is null || solutions.RowCount != 1
|| solutions[0, 0].Evaled != 2 || solutions[0, 1].Evaled != 1;
}
catch (Exception)
{
return true;
}
}, "x + y = 3, x - y = 1 has no solution when written as equalities; each equation must be given in '= 0' form"),
("DefiniteIntegral is a first-order rule", () =>
{
// Halving the step count roughly doubles the error iff the rule is first order.
Expand Down
7 changes: 2 additions & 5 deletions Sources/MCP/AngouriMath.Mcp/Tools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1166,11 +1166,8 @@ private static JsonObject SolveSystem(JsonObject args)
if (!TryParse(eq, "equations", false, out var e, out var w, out var error))
return error!;

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

Expand Down
3 changes: 1 addition & 2 deletions Sources/MCP/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,8 @@ existing tool, then a prompt, and add a tool last. A tool's description is a rou
documentation: say when to call it, and where a model would wrongly trust itself, say so.

This is an adapter, not a second computer algebra system. Mathematics belongs in the library, where
the terminal and the notebooks get it too, and four workarounds here wait for it:
the terminal and the notebooks get it too, and three workarounds here wait for it:
eigenvalues by the characteristic polynomial ([#1676](https://github.com/asc-community/AngouriMath/issues/1676)),
a numeric comparison with a tolerance ([#1674](https://github.com/asc-community/AngouriMath/issues/1674)),
`a = b` rewritten to `a - b` for a system ([#1673](https://github.com/asc-community/AngouriMath/issues/1673)),
and the numeric definite integral run twice to find its good digits
([#1675](https://github.com/asc-community/AngouriMath/issues/1675)).
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// Copyright (c) 2019-2026 Angouri.
// AngouriMath is licensed under MIT.
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
// Website: https://am.angouri.org.
//

using AngouriMath.Extensions;
using Xunit;
using static AngouriMath.Entity.Set;

namespace AngouriMath.Tests.Algebra
{
/// <summary>
/// An equation written as an equality <c>a = b</c> is solved as <c>a - b = 0</c> where an expression
/// equal to zero is expected, as a statement's own solve reads it. Passed on as it stood, a system
/// answered that it had no solution, and <see cref="MathS.SolveEquation"/> answered a set builder
/// with a false condition in it, which is empty.
/// <a href="https://github.com/asc-community/AngouriMath/issues/1673">#1673</a>
/// </summary>
[Trait("Area", "Algebra")]
public sealed class AnEqualityReadAsADifferenceTest
{
[Theory]
[InlineData("x + y = 3", "x - y = 1")]
[InlineData("x + y = 3", "x - y - 1")]
[InlineData("x + y - 3", "x - y = 1")]
[InlineData("x = 3 - y", "y = x - 1")]
public void ASystemOfEqualitiesIsSolved(string first, string second)
{
var solution = MathS.Equations(first.ToEntity(), second.ToEntity()).Solve("x", "y");
Assert.NotNull(solution);
Assert.Equal(1, solution!.RowCount);
Assert.Equal(2, (int)solution[0, 0].EvalNumerical().RealPart);
Assert.Equal(1, (int)solution[0, 1].EvalNumerical().RealPart);
}

[Fact]
public void ASystemPrintsEachEquationAsADifferenceEqualToZero()
=> Assert.Equal("x + y - 3 = 0\nx - y - 1 = 0", MathS.Equations("x + y = 3".ToEntity(), "x - y = 1".ToEntity()).ToString());

[Fact]
public void OneEqualityIsSolvedInItsVariable()
=> Assert.Equal(new FiniteSet(2), MathS.SolveEquation("x + 1 = 3", "x"));

[Fact]
public void TheExtensionReadsAnEqualityTheSameWay()
=> Assert.Equal(new FiniteSet(-2, 2), "x^2 = 4".SolveEquation("x"));

[Fact]
public void AnEqualityWithAParameterIsSolvedForTheVariable()
=> Assert.Equal(new FiniteSet("b / a".ToEntity().InnerSimplified), "a * x = b".ToEntity().SolveEquation("x").InnerSimplified);
}
}
Loading