Skip to content

Commit 2d2060a

Browse files
authored
Keep domains on simplify ("x/x" becomes "1 provided not x = 0"), fix 0/0 = 0 (now becomes undefined), fixed arcotanh and gamma definitions (#650)
1 parent bed23b3 commit 2d2060a

37 files changed

+629
-155
lines changed

Sources/AngouriMath/Convenience/MathS.cs

Lines changed: 42 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -868,28 +868,28 @@ public static Integer GreatestCommonDivisor(Integer a, Integer b)
868868

869869
/// <summary><a href="https://en.wikipedia.org/wiki/Gamma_function"/></summary>
870870
/// <param name="a">Argument node of which gamma function will be taken</param>
871-
/// <returns>Factorial node with one added to the argument</returns>
871+
/// <returns>Factorial node with one subtracted from the argument</returns>
872872
/// <example>
873873
/// <code>
874874
/// using System;
875875
/// using static AngouriMath.MathS;
876876
/// var expr = Factorial(5);
877877
/// Console.WriteLine(expr);
878878
/// Console.WriteLine(expr.Evaled);
879-
/// var expr2 = Gamma(4);
879+
/// var expr2 = Gamma(6);
880880
/// Console.WriteLine(expr2);
881881
/// Console.WriteLine(expr2.Evaled);
882882
/// </code>
883883
/// Prints
884884
/// <code>
885885
/// 5!
886886
/// 120
887-
/// (4 + 1)!
887+
/// (6 - 1)!
888888
/// 120
889889
/// </code>
890890
/// </example>
891891
[MethodImpl(MethodImplOptions.AggressiveInlining), NativeExport]
892-
public static Entity Gamma(Entity a) => new Factorialf(a + 1);
892+
public static Entity Gamma(Entity a) => new Factorialf(a - 1);
893893

894894
/// <summary>https://en.wikipedia.org/wiki/Sign_function</summary>
895895
/// <param name="a">Argument node of which Signum function will be taken</param>
@@ -1533,8 +1533,8 @@ public static class Hyperbolic
15331533
/// 1/2 * ln((1 + x) / (1 - x))
15341534
/// 10
15351535
/// ----------------------
1536-
/// 1/2 * ln((x - 1) / (x + 1))
1537-
/// -10
1536+
/// 1/2 * ln((x + 1) / (x - 1))
1537+
/// 10
15381538
/// ----------------------
15391539
/// ln(1 / x + sqrt(1 / x ^ 2 - 1))
15401540
/// 10
@@ -1585,8 +1585,8 @@ public static class Hyperbolic
15851585
/// 1/2 * ln((1 + x) / (1 - x))
15861586
/// 10
15871587
/// ----------------------
1588-
/// 1/2 * ln((x - 1) / (x + 1))
1589-
/// -10
1588+
/// 1/2 * ln((x + 1) / (x - 1))
1589+
/// 10
15901590
/// ----------------------
15911591
/// ln(1 / x + sqrt(1 / x ^ 2 - 1))
15921592
/// 10
@@ -1637,8 +1637,8 @@ public static class Hyperbolic
16371637
/// 1/2 * ln((1 + x) / (1 - x))
16381638
/// 10
16391639
/// ----------------------
1640-
/// 1/2 * ln((x - 1) / (x + 1))
1641-
/// -10
1640+
/// 1/2 * ln((x + 1) / (x - 1))
1641+
/// 10
16421642
/// ----------------------
16431643
/// ln(1 / x + sqrt(1 / x ^ 2 - 1))
16441644
/// 10
@@ -1689,8 +1689,8 @@ public static class Hyperbolic
16891689
/// 1/2 * ln((1 + x) / (1 - x))
16901690
/// 10
16911691
/// ----------------------
1692-
/// 1/2 * ln((x - 1) / (x + 1))
1693-
/// -10
1692+
/// 1/2 * ln((x + 1) / (x - 1))
1693+
/// 10
16941694
/// ----------------------
16951695
/// ln(1 / x + sqrt(1 / x ^ 2 - 1))
16961696
/// 10
@@ -1700,7 +1700,7 @@ public static class Hyperbolic
17001700
/// </code>
17011701
/// </example>
17021702
[MethodImpl(MethodImplOptions.AggressiveInlining), NativeExport]
1703-
public static Entity Arcotanh(Entity x) => 0.5 * Ln((x - 1) / (x + 1));
1703+
public static Entity Arcotanh(Entity x) => 0.5 * Ln((x + 1) / (x - 1));
17041704

17051705
/// <summary>
17061706
/// Inverse hyperbolic secant:
@@ -1741,8 +1741,8 @@ public static class Hyperbolic
17411741
/// 1/2 * ln((1 + x) / (1 - x))
17421742
/// 10
17431743
/// ----------------------
1744-
/// 1/2 * ln((x - 1) / (x + 1))
1745-
/// -10
1744+
/// 1/2 * ln((x + 1) / (x - 1))
1745+
/// 10
17461746
/// ----------------------
17471747
/// ln(1 / x + sqrt(1 / x ^ 2 - 1))
17481748
/// 10
@@ -1793,8 +1793,8 @@ public static class Hyperbolic
17931793
/// 1/2 * ln((1 + x) / (1 - x))
17941794
/// 10
17951795
/// ----------------------
1796-
/// 1/2 * ln((x - 1) / (x + 1))
1797-
/// -10
1796+
/// 1/2 * ln((x + 1) / (x - 1))
1797+
/// 10
17981798
/// ----------------------
17991799
/// ln(1 / x + sqrt(1 / x ^ 2 - 1))
18001800
/// 10
@@ -5564,42 +5564,31 @@ public sealed record NewtonSetting
55645564
complexityCriteria ??= new Func<Entity, double>(expr =>
55655565
{
55665566
// Those are of the 2nd power to avoid problems with floating numbers
5567-
static double TinyWeight(double w) => w * 0.5;
5568-
static double MinorWeight(double w) => w * 1.0;
5569-
static double Weight(double w) => w * 2.0;
5570-
static double MajorWeight(double w) => w * 4.0;
5571-
static double HeavyWeight(double w) => w * 8.0;
5572-
static double ExtraHeavyWeight(double w) => w * 12.0;
5573-
5574-
// Number of nodes
5575-
var res = Weight(expr.Complexity);
5576-
5577-
// Number of variables
5578-
res += Weight(expr.Nodes.Count(entity => entity is Variable));
5579-
5580-
// Number of divides
5581-
res += MinorWeight(expr.Nodes.Count(entity => entity is Divf));
5582-
5583-
// Number of rationals with unit numerator
5584-
res += Weight(expr.Nodes.Count(entity => entity is Rational rat and not Integer
5585-
&& (rat.Numerator == 1 || rat.Numerator == -1)));
5586-
5587-
// Number of negative powers
5588-
res += HeavyWeight(expr.Nodes.Count(entity => entity is Powf(_, Real { IsNegative: true })));
5589-
5590-
// Number of logarithms
5591-
res += TinyWeight(expr.Nodes.Count(entity => entity is Logf));
5592-
5593-
// Number of phi functions
5594-
res += ExtraHeavyWeight(expr.Nodes.Count(entity => entity is Phif));
5595-
5596-
// Number of negative reals
5597-
res += MajorWeight(expr.Nodes.Count(entity => entity is Real { IsNegative: true }));
5598-
5599-
// 0 < x is bad. x > 0 is good.
5600-
res += Weight(expr.Nodes.Count(entity => entity is ComparisonSign && entity.DirectChildren[0] == 0));
5601-
5602-
return res;
5567+
const double TinyWeight = 0.5;
5568+
const double MinorWeight = 1.0;
5569+
const double Weight = 2.0;
5570+
const double MajorWeight = 4.0;
5571+
const double HeavyWeight = 8.0;
5572+
const double ExtraHeavyWeight = 12.0;
5573+
5574+
static double DefaultCriteria(Entity expr) => expr switch {
5575+
// Weigh provided predicates much less but nested provideds heavy
5576+
Providedf(var inner, var predicate) =>
5577+
DefaultCriteria(inner) + 0.1 * DefaultCriteria(predicate) + ExtraHeavyWeight * (inner.Nodes.Count(n => n is Providedf) + predicate.Nodes.Count(n => n is Providedf)),
5578+
Entity.Piecewise { Cases: var cases } =>
5579+
cases.Sum(@case =>
5580+
DefaultCriteria(@case.Expression) + 0.1 * DefaultCriteria(@case.Predicate) + ExtraHeavyWeight * (@case.Expression.Nodes.Count(n => n is Providedf) + @case.Predicate.Nodes.Count(n => n is Providedf))),
5581+
Variable => Weight, // Number of variables
5582+
Divf => MinorWeight + expr.DirectChildren.Sum(DefaultCriteria), // Number of divides
5583+
Rational(Integer(1 or -1), _) and not Integer => Weight + expr.DirectChildren.Sum(DefaultCriteria), // Number of rationals with unit numerator
5584+
Powf(_, Real { IsNegative: true }) => HeavyWeight + expr.DirectChildren.Sum(DefaultCriteria), // Number of negative powers
5585+
Logf => TinyWeight + expr.DirectChildren.Sum(DefaultCriteria), // Number of logarithms
5586+
Phif => ExtraHeavyWeight + expr.DirectChildren.Sum(DefaultCriteria), // Number of phi functions
5587+
Real { IsNegative: true } => MajorWeight + expr.DirectChildren.Sum(DefaultCriteria), // Number of negative reals
5588+
ComparisonSign when expr.DirectChildren[0] == 0 => Weight + expr.DirectChildren.Sum(DefaultCriteria), // 0 < x is bad. x > 0 is good.
5589+
_ => expr.DirectChildren.Sum(DefaultCriteria)
5590+
} + Weight; // Number of nodes
5591+
return DefaultCriteria(expr);
56035592
});
56045593
[ThreadStatic] private static Setting<Func<Entity, double>>? complexityCriteria;
56055594

Sources/AngouriMath/Core/Entity/Discrete/Entity.Discrete.Classes.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,8 @@ public sealed partial record Impliesf(Entity Assumption, Entity Conclusion) : St
177177

178178
public Entity NodeSecondChild => Conclusion;
179179

180-
private Impliesf New(Entity left, Entity right) =>
181-
ReferenceEquals(Assumption, left) && ReferenceEquals(Conclusion, right) ? this : new(left, right);
180+
private Impliesf New(Entity assumption, Entity conclusion) =>
181+
ReferenceEquals(Assumption, assumption) && ReferenceEquals(Conclusion, conclusion) ? this : new(assumption, conclusion);
182182
/// <inheritdoc/>
183183
public override Entity Replace(Func<Entity, Entity> func)
184184
=> func(New(Assumption.Replace(func), Conclusion.Replace(func)));

Sources/AngouriMath/Core/Entity/Omni/Entity.Piecewise.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
// Website: https://am.angouri.org.
66
//
77

8-
using System;
98
using AngouriMath.Core.HashCode;
109
using AngouriMath.Extensions;
10+
using HonkSharp.Laziness;
11+
using System;
1112

1213
namespace AngouriMath
1314
{
@@ -28,7 +29,7 @@ internal Providedf New(Entity expression, Entity predicate)
2829

2930
/// <inheritdoc/>
3031
public override Entity Replace(Func<Entity, Entity> func)
31-
=> func(New(func(Expression), func(Predicate)));
32+
=> func(New(Expression.Replace(func), Predicate.Replace(func)));
3233

3334
internal override Priority Priority => Priority.Provided;
3435

@@ -82,7 +83,7 @@ public Piecewise Apply(Func<Providedf, Providedf> func)
8283

8384
/// <inheritdoc/>
8485
public override Entity Replace(Func<Entity, Entity> func)
85-
=> func(New(Cases.Select(c => c.New(func(c.Expression), func(c.Predicate)))));
86+
=> func(New(Cases.Select(c => c.New(c.Expression.Replace(func), c.Predicate.Replace(func)))));
8687

8788
/// <summary>
8889
/// Checks that two Piecewise are equal

Sources/AngouriMath/Functions/Continuous/Differentiation.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@ partial record Powf
135135
// (const ^ b)' = e^b * b'
136136
/// <inheritdoc/>
137137
protected override Entity InnerDifferentiate(Variable variable) =>
138-
Exponent is Number exp
138+
Exponent == Integer.One
139+
? Base.InnerDifferentiate(variable).WithCondition(Base.DomainCondition) // don't create x^0 which is undefined for x=0
140+
: Exponent is Number exp
139141
? exp * Base.Pow(exp - 1) * Base.InnerDifferentiate(variable)
140142
: Base is Number
141143
? Base.Pow(Exponent) * MathS.Ln(Base) * Exponent.InnerDifferentiate(variable)

Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Solvers.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,7 @@ internal static class LimitSolvers
4141
[ConstantField] private static readonly Real Infinity = Real.PositiveInfinity;
4242
internal static Entity? SolveBySubstitution(Entity expr, Variable x)
4343
{
44-
// For now we remove all provideds from an expression
45-
// Is it a correct thing to do?
46-
var res = expr.Replace(e => e is Providedf(var expr, _) ? expr : e).Substitute(x, Infinity);
44+
var res = expr.Substitute(x, Infinity);
4745
if (res.Evaled is Complex limit)
4846
{
4947
MultithreadingFunctional.ExitIfCancelled();

Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,8 @@ internal static partial class LimitFunctional
1717
private static Entity? SimplifyAndComputeLimitToInfinity(Entity expr, Variable x)
1818
{
1919
expr = expr.Simplify();
20-
return ComputeLimitToInfinity(expr, x);
21-
}
20+
if (expr is Providedf(var expression, _)) expr = expression; // limits operate assuming a continuous expression even though some points may be undefined.
2221

23-
private static Entity? ComputeLimitToInfinity(Entity expr, Variable x)
24-
{
2522
var substitutionResult = LimitSolvers.SolveBySubstitution(expr, x);
2623
if (substitutionResult is { }) return substitutionResult;
2724

Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,17 @@ private static Entity EquivalenceRules(Entity expr, Variable x, Entity dest)
1919
Sinf or Tanf or Arcsinf or Arctanf => expr.DirectChildren[0],
2020
_ => expr
2121
};
22-
22+
private static Entity EvalAssumingContinuous(Entity expr) =>
23+
expr.Evaled switch
24+
{
25+
Providedf(var inner, _) => inner,
26+
var x => x
27+
};
2328
private static Entity ApplyFirstRemarkable(Entity expr, Variable x, Entity dest)
2429
=> expr switch
2530
{
2631
Divf(var a, var b) div
27-
when a.Limit(x, dest).Evaled == 0 && b.Limit(x, dest).Evaled == 0
32+
when EvalAssumingContinuous(a.Limit(x, dest)) == 0 && EvalAssumingContinuous(b.Limit(x, dest)) == 0
2833
=> div.New(EquivalenceRules(a, x, dest), EquivalenceRules(b, x, dest)),
2934

3035
_ => expr
@@ -38,7 +43,7 @@ private static Entity ApplySecondRemarkable(Entity expr, Variable x, Entity dest
3843
// e ^ (g(x) * (f(x) - 1))
3944
Powf(var xPlusOne, var xPower) when
4045
xPlusOne.ContainsNode(x) && xPower.ContainsNode(x) &&
41-
(xPlusOne - 1).Limit(x, dest).Evaled == 0 && IsInfiniteNode(xPower.Limit(x, dest)) =>
46+
EvalAssumingContinuous((xPlusOne - 1).Limit(x, dest)) == 0 && IsInfiniteNode(xPower.Limit(x, dest)) =>
4247
MathS.e.Pow(xPower * (xPlusOne - 1)),
4348

4449
_ => expr
@@ -53,7 +58,7 @@ private static bool IsFiniteNode(Entity expr)
5358
private static Entity ApplylHopitalRule(Entity expr, Variable x, Entity dest)
5459
{
5560
if (expr is Divf(var num, var den))
56-
if (num.Limit(x, dest).Evaled is var numLimit && den.Limit(x, dest).Evaled is var denLimit)
61+
if (EvalAssumingContinuous(num.Limit(x, dest)) is var numLimit && EvalAssumingContinuous(den.Limit(x, dest)) is var denLimit)
5762
if (numLimit == 0 && denLimit == 0 ||
5863
IsInfiniteNode(numLimit) && IsInfiniteNode(denLimit))
5964
if (num is not Number && den is not Number)

Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ internal static Set Solve(Entity expr, Variable x, bool compensateSolving = fals
101101
if (expr == x)
102102
return new Entity[] { 0 }.ToSet();
103103

104+
if (ProvidedLifter.ExtractProvidedPredicates(ref expr, out var predicate))
105+
return ProvidedLifter.MergePredicateIntoSolveResult(Solve(expr, x, compensateSolving), x, predicate);
106+
104107
// Applies an attempt to downcast roots
105108
static Entity TryDowncast(Entity equation, Variable x, Entity root)
106109
{

Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/ExponentialSolver.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,26 @@ internal static class ExponentialSolver
1818
internal static Set? SolveLinear(Entity expr, Entity.Variable x)
1919
{
2020
var replacement = Variable.CreateTemp(expr.Vars);
21-
22-
Func<Entity, Entity> preparator = e => e switch
21+
static Entity NonZeroPow(Entity @base, Entity exponent) => exponent == Integer.Zero ? Integer.One : MathS.Pow(@base, exponent);
22+
Entity preparator(Entity e) => e switch
2323
{
2424
Powf(var @base, var arg) when
2525
TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out var b) =>
26-
MathS.Pow(@base, b) * MathS.Pow(MathS.Pow(MathS.e, x), MathS.Ln(@base) * a),
27-
26+
// Transformation base^(a*x + b) = base^b * e^(ln(base)*a*x) is safe when:
27+
// - base ≠ 0 (ensures ln(base) is defined and no 0^b issues)
28+
// For complex bases, ln uses the principal branch
29+
@base.Evaled switch
30+
{
31+
Complex { IsZero: false } => NonZeroPow(@base.InnerSimplified, b) * NonZeroPow(NonZeroPow(MathS.e, x), MathS.Ln(@base.InnerSimplified) * a),
32+
// If base is definitely 0, keep original form (will likely fail to solve anyway)
33+
Complex { IsZero: true } => e,
34+
// For symbolic bases that might be zero, add condition
35+
_ => (NonZeroPow(@base, b) * NonZeroPow(NonZeroPow(MathS.e, x), MathS.Ln(@base) * a)).Provided(!@base.Equalizes(0))
36+
},
2837
_ => e,
2938
};
3039

31-
Func<Entity, Entity> replacer = e => e switch
40+
Entity replacer(Entity e) => e switch
3241
{
3342
Powf(var @base, var arg)
3443
when @base == MathS.e && arg == x

Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/SolveStatement.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ internal static Set Solve(Entity expr, Variable x)
5555

5656
Inf(var var, Set set) when var == x => set,
5757

58+
Providedf(var inner, var predicate) => ProvidedLifter.MergePredicateIntoSolveResult(Solve(inner, x), x, predicate),
59+
5860
// TODO: Although piecewise needed?
5961
_ => Set.Empty
6062
};

0 commit comments

Comments
 (0)