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
30 changes: 30 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ read first.
| **silent** | `log(1, 1)` | `0` | `NaN`, since it is `0/0` |
| **silent** | `log(b, 1)` | `0` for any base | `0 provided not b = 1` |
| **silent** | `log(1/2, 0)` and any base below 1 | `-oo` | `+oo` |
| **silent** | `abs(-sqrt(6))`, `abs(-pi)`, `abs(1 - sqrt(2))` | left as written | `sqrt(6)`, `pi`, `sqrt(2) - 1` |

---

Expand Down Expand Up @@ -383,6 +384,35 @@ passing at 23 points chosen for branch cuts and principal intervals, none of whi
where a rule's own arithmetic degenerates. Issue
[#892](https://github.com/asc-community/AngouriMath/issues/892).

### `abs` folds where the sign of its argument is known

`|x|` is `x` for a non-negative real `x` and `-x` for a negative one. That is the definition of the
function rather than an identity with a side condition, and it was applied only when the argument
was a *number*. An argument whose value is a known real without its node being a number was left
alone, so a radical or a constant kept its `abs`:

| | was | is |
|---|---|---|
| `abs(-sqrt(6))` | left as written | `sqrt(6)` |
| `abs(-pi)`, `abs(-e)` | left as written | `pi`, `e` |
| `abs(1 - sqrt(2))` | left as written | `sqrt(2) - 1` |
| `abs(-2)` | `2` | `2`, unchanged |
| `abs(sqrt(-4))` | `2` | `2`, unchanged — the magnitude of `2i` |
| `abs(-a)` for symbolic `a` | left as written | left as written |

Where this shows up is in an answer built out of radicals. `(2x^2 - 3 > 0) and (x > 0)` solved to
`(abs(-sqrt(6)) / 2; +oo)` and now solves to `(sqrt(6) / 2; +oo)`; the endpoint was always the same
number, printed in a form that looked like unfinished work. The
[Solvers wiki page](https://github.com/asc-community/AngouriMath/wiki/Solvers) shows the old output
and wants updating with the release.

**Nothing is assumed about a symbol**, and an argument off the real line is declined rather than
guessed at: `sqrt(-4)` evaluates to `2i`, whose absolute value is `2` — neither the argument nor its
negation, so a rule that read "negative, therefore negate" would be wrong there. The sign is read
off the value, and a value that is not a finite real does not answer the question.

Issue [#881](https://github.com/asc-community/AngouriMath/issues/881).

### A known gap no longer presents as a bug

`FutureReleaseException` is removed, and the twelve places that threw through it now throw
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,24 @@ protected override Entity InnerSimplify(bool isExact)
Signumf(var signOf) when ValueWithCondition(signOf) is { } known
=> (known.Value.IsZero ? Integer.Zero : Integer.One)
.Provided(known.Condition),

// |x| is x where the argument is a non-negative real and -x where it is
// negative, which is the definition rather than an identity needing an
// assumption. A Number folded already; what this reaches is an argument
// whose *value* is a known real without its node being a number --
// abs(-sqrt(6)) and abs(-pi) stayed as written, so a concrete quadratic
// inequality answered with abs(-sqrt(6)) / 2 in it.
//
// An argument off the real line has to decline: sqrt(-4) evaluates to 2i,
// and |2i| is 2, which is neither the argument nor its negation. Nothing
// is assumed about the sign of a symbol either -- there the value cannot
// be read at all and the node is left alone.
// https://github.com/asc-community/AngouriMath/issues/881
var argument when argument.Evaled is Real { EDecimal.IsFinite: true } value
=> value.EDecimal.IsNegative
? (-argument).InnerSimplified(isExact)
: argument,

_ => null
},
(@this, a) => ((Absf)@this).New(a), isExact);
Expand Down
41 changes: 41 additions & 0 deletions Sources/Tests/UnitTests/Common/SimplificationRegressionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,47 @@ public void ArctanPlusArccotanOfASymbolIsLeftAlone()
Assert.NotEqual(-MathS.pi / 2, simplified);
}

// https://github.com/asc-community/AngouriMath/issues/881
// |x| is x where the argument is a non-negative real and -x where it is negative, which
// is the definition of abs rather than an identity needing an assumption. Only a Number
// folded, so abs(-sqrt(6)) and abs(-pi) came back exactly as written, and a concrete
// quadratic inequality was answered with abs(-sqrt(6)) / 2 as an endpoint.
[Theory]
[InlineData("abs(-sqrt(6))", "sqrt(6)")]
[InlineData("abs(sqrt(6))", "sqrt(6)")]
[InlineData("abs(-pi)", "pi")]
[InlineData("abs(-e)", "e")]
[InlineData("abs(1 - sqrt(2))", "sqrt(2) - 1")]
[InlineData("abs(-sqrt(6)) / 2", "sqrt(6) / 2")]
public void AbsoluteValueFoldsWhereTheSignOfTheArgumentIsDecidable(string expression, string expected)
{
var simplified = expression.ToEntity().Simplify();
Assert.DoesNotContain(simplified.Nodes, node => node is Entity.Absf);
Assert.True(Magnitude(simplified - expected.ToEntity()) < 1e-20,
$"{expression} simplified to {simplified.Stringize()}, not {expected}");
}

// An argument off the real line is neither itself nor its negation under abs: sqrt(-4)
// is 2i, whose magnitude is 2. So the sign is read off the value, and a value that is
// not real does not answer the question.
[Theory]
[InlineData("abs(sqrt(-4))", "2")]
[InlineData("abs(-sqrt(-4))", "2")]
public void AbsoluteValueOffTheRealLineIsTheMagnitude(string expression, string expected)
{
var simplified = expression.ToEntity().Simplify();
Assert.True(Magnitude(simplified - expected.ToEntity()) < 1e-20,
$"{expression} simplified to {simplified.Stringize()}, not {expected}");
}

// A symbol has no decidable sign, so nothing is assumed about it: abs(-a) is not a, and
// it is not -a either. It stays as written.
[Fact]
public void AbsoluteValueOfASymbolIsLeftAlone()
{
var simplified = "abs(-a)".ToEntity().Simplify();
Assert.Contains(simplified.Nodes, node => node is Entity.Absf);
}
// https://github.com/asc-community/AngouriMath/issues/892
// |sgn(z)| and sgn(|z|) are 1 for every z except 0, where both are 0 -- sgn(0) is 0,
// which the comment above Signumf.InnerSimplify already said. Both rewrites answered 1
Expand Down
Loading