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
1 change: 1 addition & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ read first.
| **silent** | `k/(a x^2 + c)` and `k/sqrt(a x^2 + c)` for symbolic `a` | `NaN` | a piecewise on the sign of the discriminant |
| loud | `Compile` over a missing variable | `KeyNotFoundException` | `UncompilableNodeException` |
| loud | the target frameworks | `net7.0;netstandard2.0` | `netstandard2.0;net8.0;net10.0` |
| **silent** | `abs(x) = c` for a negative `c` | a set of non-solutions | the empty set |

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,11 +259,17 @@ partial record Absf
// abs(f(x)) = value
// f(x) = value * e ^ (i * n)
// x = f(x).InvertNode(value * e ^ (i * n), x)
//
// value * e ^ (i * r) has modulus |value|, not value, so it solves the equation
// only where the two agree -- that is, where value is real and not negative.
// Without that condition abs(x) = -1 came back as { -e ^ (i * r) provided r in RR },
// whose members have modulus 1 and solve nothing.
// https://github.com/asc-community/AngouriMath/issues/812
private protected override IEnumerable<Entity> InvertNode(Entity value, Entity x)
{
var @var = Variable.CreateUnique(value + Argument, "r");
return Argument.Invert(value * MathS.e.Pow(MathS.i * @var), x)
.Select(c => c.Provided(@var.In(MathS.Sets.R)));
.Select(c => c.Provided(@var.In(MathS.Sets.R) & new GreaterOrEqualf(value, 0)));
}
}

Expand Down
44 changes: 44 additions & 0 deletions Sources/Tests/UnitTests/Common/SolverRegressionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,5 +199,49 @@ public void NewtonSolverDoesNotLeakAnUncompilableNodeException()
// The omega constant: the root of x = -ln(x).
Assert.Equal(0.5671432904097838, root.EvalNumerical().RealPart.EDecimal.ToDouble(), 9);
}

// abs(x) = a was inverted to the circle a * e ^ (i * r), which has modulus |a| and
// so only solves the equation where a is real and not negative. The condition was
// missing, so a negative right-hand side came back with a set of non-solutions
// rather than with nothing. https://github.com/asc-community/AngouriMath/issues/812
[Theory]
[InlineData("abs(x) = -1")]
[InlineData("abs(x) = -3/2")]
[InlineData("abs(x) + 1 = 0")]
public void Issue812_AnAbsoluteValueCannotEqualANegative(string equation)
{
var solutions = (Entity.Set.FiniteSet)equation.ToEntity().Solve("x");
Assert.Empty(solutions);
}

// The guard must not cost the answers that were right. Each solution is checked by
// substituting it back, with the free parameter of the circle given a value, rather
// than by comparing the printed form.
[Theory]
[InlineData("3", 3.0)]
[InlineData("1/2", 0.5)]
[InlineData("0", 0.0)]
public void Issue812_ANonNegativeRightHandSideStillSolves(string rhs, double modulus)
{
var solutions = (Entity.Set.FiniteSet)$"abs(x) = {rhs}".ToEntity().Solve("x");
var solution = Assert.Single(solutions);
var parameter = solution.Vars.Single(v => v.Name != "x");
foreach (var point in new[] { 0.0, 1.0, -2.5 })
{
var candidate = solution.Substitute(parameter, point);
while (candidate is Entity.Providedf(var inner, _)) candidate = inner;
Assert.Equal(modulus,
MathS.Abs(candidate).EvalNumerical().RealPart.EDecimal.ToDouble(), 9);
}
}

// A symbolic right-hand side cannot be decided either way, so the condition is
// carried rather than resolved -- which is what #318 asked this inversion to do.
[Fact]
public void Issue812_ASymbolicRightHandSideCarriesTheCondition()
{
var printed = "abs(x) = a".ToEntity().Solve("x").Stringize();
Assert.Contains("a >= 0", printed);
}
}
}
Loading