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
13 changes: 13 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,19 @@ Both are real, `im(3 + 4i)` being `4`, real-linear, and turned by the conjugate
| `"im(3 + 4i)".ToEntity().Simplify()` | `(3 + 4i) * im` | `4` |
| `"re * x".ToEntity()` | `re * x` | `re * x` |

### The factorial and the gamma function are defined off the real line

**Silent: the condition a simplified factorial carries is `x in ZZ implies x >= 0`, where it was `x in RR and (x >= 0
or not x in ZZ)`.** A factorial, and the gamma function with it, has poles at the negative integers and nowhere else.
Its condition also asked for a real argument, though `i!` is `0.498 - 0.155i`. So `x!/x!` simplified to an answer with
no value at `i`, where it is 1 ([#2256](https://github.com/asc-community/AngouriMath/issues/2256)).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"x!/x!".ToEntity().Simplify()` | `1 provided x in RR and (x >= 0 or not x in ZZ)` | `1 provided x in ZZ implies x >= 0` |
| `"x! = 0".ToEntity().Simplify()` | `False provided x in RR and (x >= 0 or not x in ZZ)` | `False provided x in ZZ implies x >= 0` |
| `"x!".ToEntity().DomainCondition` | `x in RR and (x >= 0 or not x in ZZ)` | `x in ZZ implies x >= 0` |

### A number's conjugate is made under the setting in force

**Silent: `Complex.Conjugate` is made under the downcasting setting of the caller, where the first caller's setting was
Expand Down
3 changes: 3 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[Tests/UnitTests/Convenience/ConjugateAndArgumentTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[Tests/UnitTests/Convenience/FactorialOffTheRealLineTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[Tests/UnitTests/Algebra/SolveTest/FractionalPowerRangeTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ private static bool HoldsOverTheRange(Entity condition, Variable index, int star
=> HoldsOverTheRange(left, index, start) && HoldsOverTheRange(right, index, start),
Orf(var left, var right)
=> HoldsOverTheRange(left, index, start) || HoldsOverTheRange(right, index, start),
// A factorial's own condition, index + a in ZZ implies index + a >= 0: true wherever
// its conclusion is. https://github.com/asc-community/AngouriMath/issues/2256
Impliesf(_, var conclusion)
=> HoldsOverTheRange(conclusion, index, start),
_ => false,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,9 +524,15 @@ public partial record Factorialf
{
// Factorial is defined for non-negative integers in the traditional sense,
// but extends to complex numbers via the gamma function: n! = Γ(n+1)
// The gamma function has poles at negative integers, so factorial is undefined there
private protected override Entity IntrinsicCondition =>
Argument.In(MathS.Sets.R) & (Argument >= 0 | !Argument.In(MathS.Sets.Z));
// The gamma function has poles at negative integers, so factorial is undefined there,
// and only there. The condition asked for a real argument as well, though i! is
// 0.498 - 0.155i, so x!/x! simplified to 1 provided x in RR, which has no value at i.
// Off the integers x >= 0 may have no truth value and x in ZZ is false, so the implication
// is true. It is written as Simplify writes it, so that a quotient's x! = 0, which Simplify
// reads as False under this same condition, folds into it.
// https://github.com/asc-community/AngouriMath/issues/2256
private protected override Entity IntrinsicCondition =>
new Impliesf(Argument.In(MathS.Sets.Z), Argument >= 0);

/// <inheritdoc/>
protected override Entity InnerSimplify(bool isExact) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ internal static Entity PushNotInside(IEnumerable<Entity> operands, bool disjoin)
// a! = 0
// A factorial is never zero *where it is defined*, and at a negative integer it is
// not: `(-1)!` is a pole, so `(-1)! = 0` is NaN rather than False. The condition owed
// is therefore the factorial's own -- `x in RR and (x >= 0 or not x in ZZ)`.
// is therefore the factorial's own -- `x in ZZ implies x >= 0`.
//
// What was written was `Factorialf({ DomainCondition: var condition })`, a property
// pattern on the factorial's *argument*. For a bare variable that condition is True,
Expand Down
48 changes: 48 additions & 0 deletions Sources/Tests/UnitTests/Convenience/FactorialOffTheRealLineTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// 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;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Convenience
{
/// <summary>
/// The factorial, and the gamma function with it, is defined everywhere but at the negative integers, its poles, where
/// its condition asked for a real argument as well: <c>i!</c> is <c>0.498 - 0.155i</c>, and <c>x!/x!</c> simplified to
/// <c>1 provided x in RR</c>, which has no value at <c>i</c>.
/// <a href="https://github.com/asc-community/AngouriMath/issues/2256">#2256</a>
/// </summary>
[Trait("Area", "Convenience")]
public sealed class FactorialOffTheRealLineTest
{
[Theory]
[InlineData("x!", "i", true)]
[InlineData("x!", "1/2 + 6/5 * i", true)]
[InlineData("x!", "-2 * i", true)]
[InlineData("x!", "-1/2", true)]
[InlineData("x!", "0", true)]
[InlineData("x!", "3", true)]
[InlineData("x!", "-1", false)]
[InlineData("x!", "-2", false)]
[InlineData("gamma(x)", "i", true)]
[InlineData("gamma(x)", "0", false)]
[InlineData("gamma(x)", "-3", false)]
public void ItIsDefinedOffThePoles(string expression, string at, bool defined)
{
var condition = expression.ToEntity().DomainCondition.Substitute("x", at.ToEntity()).Evaled;
Assert.Equal(defined ? Entity.Boolean.True : Entity.Boolean.False, condition);
Assert.Equal(!defined, expression.ToEntity().Substitute("x", at.ToEntity()).Evaled.IsNaN);
}

[Theory]
[InlineData("x! / x!", "i")]
[InlineData("gamma(x) / gamma(x)", "1/2 + 6/5 * i")]
public void ASimplificationKeepsItsValueThere(string expression, string at)
=> Assert.Equal(Entity.Number.Integer.One, expression.ToEntity().Simplify().Substitute("x", at.ToEntity()).Evaled);
}
}
9 changes: 6 additions & 3 deletions Sources/Tests/UnitTests/PatternsTest/SimplifyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,16 @@ [Fact] public void Patt8Negative() =>
// with the unsimplified quotient at every one -- NaN at the three poles and 1 elsewhere.
// The old form was adequate here by accident, since `(1 + x)!` is NaN at a pole and a
// comparison against NaN is not True; the new one says so rather than relying on it.
[Fact] public void FactorialXP1OverFactorialXP1() => AssertSimplify(MathS.Factorial(x + 1) / MathS.Factorial(x + 1), "1 provided 1 + x in RR and (1 + x >= 0 or not 1 + x in ZZ)");
// A factorial is defined off its poles, the negative integers, and off the real line too: the
// condition asked for 1 + x in RR, so this had no value at x = i, where it is 1.
// https://github.com/asc-community/AngouriMath/issues/2256
[Fact] public void FactorialXP1OverFactorialXP1() => AssertSimplify(MathS.Factorial(x + 1) / MathS.Factorial(x + 1), "1 provided 1 + x in ZZ implies 1 + x >= 0");
[Fact] public void FactorialXP1OverFactorialX() => AssertSimplify(MathS.Factorial(1 + x) / MathS.Factorial(x), 1 + x);
[Fact] public void FactorialXP1OverFactorialXM2() => AssertSimplify(MathS.Factorial(1 + x) / MathS.Factorial(-2 + x), "x3 - x");
[Fact] public void FactorialXP1OverFactorialXM1() => AssertSimplify(MathS.Factorial(1 + x) / MathS.Factorial(-1 + x), x * (1 + x));
[Fact] public void FactorialXP1OverFactorialXM3() => AssertSimplifyIdentical(MathS.Factorial(x + 1) / MathS.Factorial(x - 3));
[Fact] public void FactorialXOverFactorialXP1() => AssertSimplify(MathS.Factorial(x) / MathS.Factorial(x + 1), 1 / (1 + x));
[Fact] public void FactorialXOverFactorialX() => AssertSimplify(MathS.Factorial(x) / MathS.Factorial(x), "1 provided x in RR and (x >= 0 or not x in ZZ)");
[Fact] public void FactorialXOverFactorialX() => AssertSimplify(MathS.Factorial(x) / MathS.Factorial(x), "1 provided x in ZZ implies x >= 0");
[Fact] public void FactorialXOverFactorialXM1() => AssertSimplify(MathS.Factorial(0 + x) / MathS.Factorial(x - 1), x);
[Fact] public void FactorialXOverFactorialXM2() => AssertSimplify(MathS.Factorial(x + 0) / MathS.Factorial(x - 2), x.Pow(2) - x);
[Fact] public void FactorialXOverFactorialXM3() => AssertSimplifyIdentical(MathS.Factorial(x) / MathS.Factorial(x - 3));
Expand All @@ -105,7 +108,7 @@ [Fact] public void Patt8Negative() =>
// commit, because a bare `x!` reached this through the candidate polynomial long division
// produces rather than through a cancellation rule. That division carries its divisor's
// domain now as well, so all three of these agree again.
[Fact] public void FactorialXM1OverFactorialXM1() => AssertSimplify(MathS.Factorial(x - 1) / MathS.Factorial(x - 1), "1 provided x - 1 in RR and (x - 1 >= 0 or not x - 1 in ZZ)");
[Fact] public void FactorialXM1OverFactorialXM1() => AssertSimplify(MathS.Factorial(x - 1) / MathS.Factorial(x - 1), "1 provided x - 1 in ZZ implies x - 1 >= 0");
[Fact] public void FactorialXM1OverFactorialXM2() => AssertSimplify(MathS.Factorial(-1 + x) / MathS.Factorial(-2 + x), x - 1);
// Same two factors, now in ascending order: the polynomial factorer offers
// (x - 1) * (x - 2) and the complexity metric cannot tell the two orders apart.
Expand Down
Loading