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
7 changes: 6 additions & 1 deletion Sources/AngouriMath/Convenience/SettingClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,12 @@ internal static bool StillHolds(object?[] snapshot)
/// epoch its <see cref="Entity.Evaled"/> was computed at and recomputes when it has moved,
/// since a hundred digits of pi cached at one precision are the wrong answer at another.
/// One counter for every thread: a change on another thread costs a recomputation and
/// never a stale value. https://github.com/asc-community/AngouriMath/issues/1367
/// never a stale value. The downcasting setting does not advance it: the integrator's
/// sampled checks turn the downcasting off and on around every candidate, and an epoch
/// moved by those made every cached evaluation in the process stale each time -- the
/// unit suite took four times as long. What the downcasting changed in a cached value
/// that mattered, a constant's type, no longer depends on it.
/// https://github.com/asc-community/AngouriMath/issues/1367
/// </summary>
internal static class EvaluationEpoch
{
Expand Down
14 changes: 9 additions & 5 deletions Sources/AngouriMath/Core/Entity/Omni/Entity.Variable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,19 @@ public void Deconstruct(out string name)
/// Which names the language reads as mathematical constants, and what each is worth
/// -- asked at the moment of evaluation, so that the digits are the current
/// precision's and not those of whichever context was set when this class was
/// initialised (https://github.com/asc-community/AngouriMath/issues/1367). This
/// is the whole registry: a name is a constant exactly when it is a key here, and
/// nothing below asks about <c>pi</c> or <c>e</c> by name.
/// initialised (https://github.com/asc-community/AngouriMath/issues/1367), and a
/// <see cref="Real"/> whatever the downcasting says: through the conversion from a
/// decimal, with the downcasting off, <c>e</c> was a <see cref="Complex"/> with a
/// zero imaginary part, and cached on the constant that made <c>sgn(e^x - 1)</c>
/// not real-valued and <c>e^x</c> not the exponential of <c>x</c> for the rest of
/// the process. This is the whole registry: a name is a constant exactly when it
/// is a key here, and nothing below asks about <c>pi</c> or <c>e</c> by name.
/// </summary>
[ConstantField] internal static readonly IReadOnlyDictionary<string, Func<Complex>> ConstantList =
new Dictionary<string, Func<Complex>>
{
{ nameof(pi), static () => MathS.DecimalConst.pi },
{ nameof(e), static () => MathS.DecimalConst.e }
{ nameof(pi), static () => Real.Create(MathS.DecimalConst.pi) },
{ nameof(e), static () => Real.Create(MathS.DecimalConst.e) }
};

/// <summary>Each constant as the name a writer types, which is the form a binder can take.</summary>
Expand Down
32 changes: 22 additions & 10 deletions Sources/AngouriMath/Functions/Evaluation/Evaluation.Definition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,20 +184,32 @@ public Entity Evaled
{
get
{
// Cached for the precision it was computed under: a hundred digits of pi are
// the wrong answer at five hundred, and an expression held across a change of
// MathS.Settings.DecimalPrecisionContext used to answer from the first one.
// Cached for the epoch it was computed under -- the precision context and
// the downcasting: a hundred digits of pi are the wrong answer at five
// hundred, and an expression held across a change of the settings used to
// answer from the first ones. The epoch is read before the computation and
// recorded with its result, so that a scope opened and closed inside the
// computation -- the integrator's sampled checks turn the downcasting off --
// leaves a value stamped as stale, to be recomputed on the next read, and
// never a slot cleared under a computation still running; the lazy slot it
// was, reset on a stale epoch, was cleared that way and read back as null.
// https://github.com/asc-community/AngouriMath/issues/1367
// And stamped with its owner: a record's `with` -- WithCodomain, and the
// derivative's rewrites -- copies every field, this one included, and a
// value computed for the original is not the copy's; the lazy slot carried
// the same stamp.
var epoch = Convenience.EvaluationEpoch.Current;
if (evaledEpoch != epoch)
{
evaled = default;
evaledEpoch = epoch;
}
return evaled.GetValue(static @this => @this.InnerSimplifyWithCheck(false), this);
if (evaled is { } cached && evaledEpoch == epoch && ReferenceEquals(evaledOwner, this))
return cached;
var computed = InnerSimplifyWithCheck(false);
evaledEpoch = epoch;
evaledOwner = this;
evaled = computed;
return computed;
}
}
private LazyPropertyA<Entity> evaled;
private Entity? evaled;
private Entity? evaledOwner;
private int evaledEpoch;

/// <summary>
Expand Down
9 changes: 7 additions & 2 deletions Sources/AngouriMath/Functions/TreeAnalyzer/RealValued.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal static partial class TreeAnalyzer
internal static bool IsRealValued(Entity expr, Variable x)
{
if (!expr.ContainsNode(x))
return expr.Evaled is Real { IsNaN: false };
return IsARealNumber(expr.Evaled);
switch (expr)
{
case Variable variable:
Expand All @@ -51,7 +51,7 @@ internal static bool IsRealValued(Entity expr, Variable x)
// `e^x - 1/e^x - 2`, whose sign the antiderivative of a hyperbolic integrand
// carries under `u = e^x`.
case Powf(var constantBase, var exponent) when !constantBase.ContainsNode(x)
&& constantBase.Evaled is Real { IsNaN: false, IsPositive: true }:
&& IsARealNumber(constantBase.Evaled) && ((Complex)constantBase.Evaled).RealPart.IsPositive:
return IsRealValued(exponent, x);
case Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf
or Arctanf or Arccotanf or Signumf:
Expand All @@ -64,6 +64,11 @@ internal static bool IsRealValued(Entity expr, Variable x)
}
}

// A real number, or a complex one whose imaginary part is zero: with the downcasting
// off, `e` evaluates to the latter, and it is no less real for it.
private static bool IsARealNumber(Entity evaled)
=> evaled is Real { IsNaN: false } || evaled is Complex { IsNaN: false } complex && complex.ImaginaryPart.IsZero;

/// <summary>
/// Rewrites <c>abs(c * g)</c>, <c>abs(c / g)</c> and <c>abs(g / c)</c>, for a numeric
/// <c>c</c> off the real line whose modulus is exact, as <c>|c| * abs(g)</c>,
Expand Down
23 changes: 23 additions & 0 deletions Sources/Tests/UnitTests/Core/HighPrecisionFunctionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,29 @@ public void ConstantsAndCachedEvaluationsFollowThePrecision()
Assert.Equal(100, DigitsOf(held.EvalNumerical()));
}

/// <summary>
/// <c>e</c> evaluated with the downcasting off used to be a complex number with a zero
/// imaginary part, and the cache on the constant kept it that way for the rest of the
/// process -- after which <c>sgn(e^x - 1)</c> was not real-valued and its derivative,
/// which is zero, stayed unevaluated. A constant is a real number whatever the
/// downcasting says, and a complex number with a zero imaginary part is real-valued
/// in any case. https://github.com/asc-community/AngouriMath/issues/1367
/// </summary>
[Fact]
public void AConstantIsRealWhateverTheDowncasting()
{
var sign = MathS.FromString("sgn((e^(2*x) - 1)/(e^(2*x) + 1))");
using (MathS.Settings.DowncastingEnabled.Set(false))
{
Assert.IsType<Real>(MathS.e.Evaled);
Assert.IsType<Real>(MathS.pi.Evaled);
Assert.DoesNotContain("derivative(", MathS.FromString("sgn(e^x - 1)").Differentiate("x").ToString());
Assert.DoesNotContain("derivative(", MathS.Signum(Complex.Create(EDecimal.FromString("1.5"), EDecimal.Zero) * MathS.Var("x")).Differentiate("x").ToString());
}
Assert.IsType<Real>(MathS.e.Evaled);
Assert.DoesNotContain("derivative(", sign.Differentiate("x").ToString());
}

/// <summary>
/// e to a power at three hundred digits, after the same expression's shape was
/// evaluated at a hundred: it used to come back correct to a hundred, since the base
Expand Down
Loading