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
17 changes: 17 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,23 @@ with the multiplier gathered from the whole product in the exponent
| `"e^(2*acoth(2*x))*(3 - 12*x^2)^2".Integrate("x")` | `integral(…)` — left unevaluated | `(144 * x ^ 5 / 5 + 144 * x ^ 4 / 4 + (-36) * x ^ 2 / 2 + (-9) * x provided not 2 * x + -1 = 0) + C` |
| `"e^(1/3*acoth(x))*x^2".Integrate("x")` | `integral(…)` — left unevaluated | an antiderivative in `((x + 1)/(x - 1))^(1/6)` |

### `Solve` no longer answers the empty set for a statement it cannot read

**Wrong answer fixed.** `"x^2 in (0; 1)".Solve("x")` was `{}` — and so was every `f(x) in S` with
anything but a bare `x` on the left, and every statement the solver had no arm for: the arm at the
end of the dispatch answered the empty set, which claims there is no such `x`. A membership is
solved now — the members of a listed set each as an equation, an interval as its bounds, each
strict or not as the end is — and a statement that is not read is the set of `x` with the
property, left as written. A quantifier that put the negated body to the solver read that
emptiness as a proof: `forall x in RR : x^2 in ZZ` was `True`
([#1409](https://github.com/asc-community/AngouriMath/issues/1409), the reference's pre-images).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"x^2 in (0; 1)".Solve("x")` | `{}` — wrong | `((-oo; 0) \/ (0; +oo)) /\ (-1; 1)` |
| `"x^2 in {1, 4}".Solve("x")` | `{}` — wrong | `{ 1, -1, 2, -2 }` |
| `"x^2 in ZZ".Solve("x")` | `{}` — wrong | `{ x : x ^ 2 in ZZ }`, left as written |
| `forall x in RR : x^2 in ZZ` | `UnhandledParseException` (quantifiers are new since) | `False` |
### `...` is the pattern operator

`{1, 2, ..., n}`, `{2, 4, ..., 2 n}`, `{5, 10, 15, ...}`, `{..., -1, 0}`, `1 + 2 + ... + n` and
Expand Down
3 changes: 3 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[AngouriMath/Functions/NumberTheory/ResidueClasses.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/MembershipSolvingTest.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

[AngouriMath/Core/PatternOperator.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 @@ -223,6 +223,13 @@ internal static partial class SetOperators
var over = sub is ConditionalSet { DeclaredMembership: (var declared, _) } ? declared as Set : sub;
switch (super)
{
// The operators are the connectives: x in A \/ B is x in A or x in B.
case Unionf(Set a, Set b):
return MembershipAsComparison(x, a, sub) is { } inA && MembershipAsComparison(x, b, sub) is { } inB ? inA | inB : null;
case Intersectionf(Set a, Set b):
return MembershipAsComparison(x, a, sub) is { } inBoth && MembershipAsComparison(x, b, sub) is { } andIn ? inBoth & andIn : null;
case SetMinusf(Set a, Set b):
return MembershipAsComparison(x, a, sub) is { } inLeft && MembershipAsComparison(x, b, sub) is { } notIn ? inLeft & !notIn : null;
case ConditionalSet { DeclaredMembership: (var declaredOfSuper, var rest), Var: Variable y } builder
when declaredOfSuper is Set declaredSet:
// x in { y in S : p } is x in S and p(x); the first conjunct is read the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using PeterO.Numbers;
using AngouriMath.Functions.Continuous.Solvers.SetSolver;
using static AngouriMath.Entity;
using static AngouriMath.Entity.Number;
using static AngouriMath.Entity.Set;

namespace AngouriMath.Functions.Algebra.AnalyticalSolving
Expand Down Expand Up @@ -319,6 +320,11 @@ internal static Set Solve(Entity expr, Variable x)
Variable when expr == x => new FiniteSet(true),

Inf(var var, Set set) when var == x => set,
// f(x) in S: the members of a listed S each as an equation, an interval as its
// two bounds, and anything else left as the set of x with the property, since
// a solver that answered "no x" here was answering wrongly -- x^2 in (0; 1) was
// the empty set. https://github.com/asc-community/AngouriMath/issues/1409
Inf(var member, Set set) when member.ContainsNode(x) => Membership(member, set, x) ?? new ConditionalSet(x, expr),

// a x + b = c (mod n): one residue class, or none, by the gcd; a congruence of
// higher degree by the residues that satisfy it, where the modulus is small
Expand All @@ -328,8 +334,43 @@ internal static Set Solve(Entity expr, Variable x)
Providedf(var e, var predicate) => Solve(e, x).Filter(predicate, x),
Piecewise p => EquationSolver.SolvePiecewise(p, x, Solve),

// TODO: Although piecewise needed?
_ => Set.Empty
// A statement the solver has no arm for is the set of x with the property, left
// as written -- not the empty set, which claims there is no such x. A statement
// that does not mention x is one of everything or of nothing, and the set
// builder over it evaluates to that.
_ => new ConditionalSet(x, expr)
};

/// <summary>
/// The <c>x</c> with <c>f(x)</c> in a set: for a listed set, the union of the equations
/// <c>f(x) = s</c>; for an interval, the conjunction of its bounds, each strict or not as
/// the end is; <see langword="null"/> for a set that is neither.
/// </summary>
private static Set? Membership(Entity member, Set set, Variable x)
{
switch (set)
{
case FiniteSet listed:
Set? union = null;
foreach (var element in listed)
{
var solutions = Solve(member.Equalizes(element), x);
union = union is null ? solutions : MathS.Union(union, solutions);
}
return union ?? Set.Empty;
case Interval interval:
Entity? condition = null;
if (interval.Left.Evaled != Real.NegativeInfinity)
condition = interval.LeftClosed ? member >= interval.Left : member > interval.Left;
if (interval.Right.Evaled != Real.PositiveInfinity)
{
var upper = interval.RightClosed ? member <= interval.Right : member < interval.Right;
condition = condition is null ? upper : condition & upper;
}
return condition is null ? MathS.Sets.R : Solve(condition, x);
default:
return null;
}
}
}
}
69 changes: 69 additions & 0 deletions Sources/Tests/UnitTests/Algebra/MembershipSolvingTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// 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;
using static AngouriMath.Entity;

namespace AngouriMath.Tests.Algebra
{
/// <summary>
/// <c>Solve</c> of <c>f(x) in S</c>: the members of a listed <c>S</c> each as an equation,
/// an interval as its bounds. It answered the <b>empty set</b> for every one of these, and
/// for every statement it had no arm for -- a claim that no <c>x</c> exists, made about
/// <c>x^2 in (0; 1)</c>. A statement the solver cannot read is the set of <c>x</c> with the
/// property now, left as written. The reference's pre-images (Sullivan and Mackey,
/// Ex 7.3.10, §7.3.5 Try 1) are this.
/// <see href="https://github.com/asc-community/AngouriMath/issues/1409"/>
/// </summary>
[Trait("Area", "Algebra")]
public sealed class MembershipSolvingTest
{
private static Set Solved(string statement) => statement.ToEntity().Solve("x");

/// <summary>Ex 7.3.10: the pre-images of <c>x^2</c>; the book prints <c>(-1, 1)</c> for the last, which is wrong at <c>0</c>.</summary>
[Theory]
[InlineData("x^2 in {1}", "{-1, 1}")]
[InlineData("x^2 in {1, 4}", "{-2, -1, 1, 2}")]
[InlineData("x^2 in (-oo; 0)", "{}")]
[InlineData("x^2 in [0; +oo)", "RR")]
[InlineData("x^2 in (0; 1)", "(-1; 0) \\/ (0; 1)")]
[InlineData("x + 1 in [2; 3]", "[1; 2]")]
[InlineData("2 x in (0; 4]", "(0; 2]")]
public void APreImageIsSolvedThroughTheMembersOrTheBounds(string statement, string expected)
{
// Compared at points rather than as printed forms: the solver writes a union of
// intervals in its own order, and the same set two ways is the same set.
var solved = Solved(statement);
var wanted = (Set)expected.ToEntity().Evaled;
foreach (var at in new[] { -3.0, -2.0, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 3.0 })
{
var point = (Entity)at;
Assert.True(solved.TryContains(point, out var got), $"{solved} does not decide {at}");
Assert.True(wanted.TryContains(point, out var want), $"{wanted} does not decide {at}");
Assert.True(got == want, $"{statement} solved as {solved}: {at} is {(got ? "in" : "out")}, and should be {(want ? "in" : "out")}");
}
}

/// <summary>A statement with no arm is the set of <c>x</c> with the property, not the empty set.</summary>
[Fact]
public void WhatIsNotReadIsLeftAsWritten()
{
Assert.Equal("{ x : x^2 in ZZ }".ToEntity(), Solved("x^2 in ZZ"));
Assert.Equal("{ x : x^2 in QQ }".ToEntity(), Solved("x^2 in QQ"));
// And a quantifier that asked the solver no longer reads that emptiness as a proof:
// forall x in RR : x^2 in ZZ was True.
Assert.Equal(Boolean.False, "forall x in RR : x^2 in ZZ".ToEntity().Evaled);
}

/// <summary>The trigonometric equation's family is the pre-image of <c>{0}</c> under the sine.</summary>
[Fact]
public void AMemberEquationKeepsItsFamily()
=> Assert.Contains("n_1", Solved("sin(x) in {0}").Stringize());
}
}
Loading