mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-11 05:07:21 +00:00
Added the Mathematics tools as a prototype (#1162)
This commit is contained in:
1 parent
ce846e540e
commit
9c98bdae86
49 files changed
+4373
-11
No files matched your search
@@ -0,0 +1,160 @@
|
||||
using System.Globalization;
|
||||
|
||||
using AIStudio.Chat;
|
||||
|
||||
using AngouriMath;
|
||||
|
||||
using PeterO.Numbers;
|
||||
|
||||
namespace AIStudio.Tools.Mathematics;
|
||||
|
||||
/// <summary>
|
||||
/// Refuses the computations which AngouriMath cannot stop once they started.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// AngouriMath computes an exact power or factorial in full, and an expansion term by term, without
|
||||
/// checking its cancellation token meanwhile: 9^9^9, (10^9)!, and the expansion of (x + y)^500 run
|
||||
/// on long past any budget, see AngouriMathBehaviorTests. The guards estimate the size of such a
|
||||
/// result from the expression alone, before anything is computed, and refuse it when it would be
|
||||
/// larger than any result the tools return anyway.<br/><br/>
|
||||
/// The estimate works from the inside out, so the inner power of 9^9^9^9 is known before the outer
|
||||
/// one is judged. Only numbers of known value count: a power of a variable is never computed as a
|
||||
/// number. Call the guards inside a computation of MathComputationService, since they evaluate the
|
||||
/// exponents and the arguments of factorials, which have passed the guards before.
|
||||
/// </remarks>
|
||||
public static class MathGuards
|
||||
{
|
||||
private const int MAX_SHOWN_LENGTH = 120;
|
||||
|
||||
/// <summary>
|
||||
/// Refuses an expression whose exact evaluation would produce a number of more than
|
||||
/// MathLimits.MAX_EXACT_DIGITS digits.
|
||||
/// </summary>
|
||||
/// <param name="expression">The expression to be evaluated or simplified.</param>
|
||||
/// <exception cref="MathComputationException">A part of the expression would grow too large.</exception>
|
||||
public static void CheckExactSize(Entity expression) => EstimateDigits(expression);
|
||||
|
||||
/// <summary>
|
||||
/// Refuses an expression whose expansion would take a power of a sum beyond
|
||||
/// MathLimits.MAX_EXPANSION_EXPONENT.
|
||||
/// </summary>
|
||||
/// <param name="expression">The expression to be expanded.</param>
|
||||
/// <exception cref="MathComputationException">A power of a sum has too large an exponent.</exception>
|
||||
public static void CheckExpansion(Entity expression)
|
||||
{
|
||||
foreach (var node in expression.Nodes)
|
||||
{
|
||||
if (node is not Entity.Powf { Base: Entity.Sumf or Entity.Minusf } power)
|
||||
continue;
|
||||
|
||||
var exponent = ValueOf(power.Exponent);
|
||||
if (exponent is null || Math.Abs(exponent.Value) <= MathLimits.MAX_EXPANSION_EXPONENT)
|
||||
continue;
|
||||
|
||||
throw new MathComputationException(string.Create(CultureInfo.InvariantCulture, $"'{Show(power)}' raises a sum to the power of {exponent.Value:0.###}. Where the tool may expand, a sum is raised to exponents up to {MathLimits.MAX_EXPANSION_EXPONENT} only, since it cannot stop a larger expansion midway."));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Estimates how many digits the exact value of a node needs, and refuses it beyond the limit.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An upper bound for sums, products, and quotients: the digits of numerator and denominator
|
||||
/// add up. A number which is not exact, such as a decimal of the current precision, needs none.
|
||||
/// </remarks>
|
||||
private static double EstimateDigits(Entity node)
|
||||
{
|
||||
var digits = node switch
|
||||
{
|
||||
Entity.Number.Rational rational => DigitsOf(rational.ERational.Numerator) + DigitsOf(rational.ERational.Denominator),
|
||||
Entity.Powf power => EstimatePower(power),
|
||||
Entity.Factorialf factorial => EstimateFactorial(factorial),
|
||||
_ => node.DirectChildren.Sum(EstimateDigits),
|
||||
};
|
||||
|
||||
if (digits > MathLimits.MAX_EXACT_DIGITS)
|
||||
throw new MathComputationException(string.Create(CultureInfo.InvariantCulture, $"'{Show(node)}' would have about {digits:N0} digits as an exact number. The tool computes such a number in full and cannot stop midway, so exact numbers are limited to {MathLimits.MAX_EXACT_DIGITS:N0} digits."));
|
||||
|
||||
return digits;
|
||||
}
|
||||
|
||||
private static double EstimatePower(Entity.Powf power)
|
||||
{
|
||||
var baseDigits = EstimateDigits(power.Base);
|
||||
var exponentDigits = EstimateDigits(power.Exponent);
|
||||
if (MathVariables.HasAny(power.Base))
|
||||
return baseDigits + exponentDigits;
|
||||
|
||||
var exponent = ValueOf(power.Exponent);
|
||||
if (exponent is null)
|
||||
return baseDigits + exponentDigits;
|
||||
|
||||
var magnitude = Math.Abs(exponent.Value);
|
||||
if (power.Base.Evaled is Entity.Number.Rational rational)
|
||||
{
|
||||
var size = Log10(rational.ERational.Numerator) + Log10(rational.ERational.Denominator);
|
||||
var digits = magnitude * size;
|
||||
if (digits > MathLimits.MAX_EXACT_DIGITS && rational.ERational.Sign > 0)
|
||||
throw new MathComputationException(string.Create(CultureInfo.InvariantCulture, $"'{Show(power)}' would have about {digits:N0} digits as an exact number; its decimal logarithm is about {exponent.Value * (Log10(rational.ERational.Numerator) - Log10(rational.ERational.Denominator)):0.###}. The tool computes such a number in full and cannot stop midway, so exact numbers are limited to {MathLimits.MAX_EXACT_DIGITS:N0} digits."));
|
||||
|
||||
return digits;
|
||||
}
|
||||
|
||||
// A base which is exact without being rational, such as sqrt(2), can still become one, as 2^500000:
|
||||
return magnitude * Math.Max(baseDigits, 0);
|
||||
}
|
||||
|
||||
private static double EstimateFactorial(Entity.Factorialf factorial)
|
||||
{
|
||||
var argumentDigits = EstimateDigits(factorial.Argument);
|
||||
var argument = ValueOf(factorial.Argument);
|
||||
if (argument is null)
|
||||
return argumentDigits;
|
||||
|
||||
// Stirling: log10(n!) is about n * log10(n / e) + log10(2 * pi * n) / 2.
|
||||
var n = Math.Abs(argument.Value);
|
||||
if (n < 2)
|
||||
return argumentDigits + 1;
|
||||
|
||||
return n * Math.Log10(n / Math.E) + Math.Log10(2 * Math.PI * n) / 2 + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value of a part without variables, as a double, or null when it has none.
|
||||
/// </summary>
|
||||
private static double? ValueOf(Entity part)
|
||||
{
|
||||
if (MathVariables.HasAny(part))
|
||||
return null;
|
||||
|
||||
var value = part.Evaled switch
|
||||
{
|
||||
Entity.Number.Real real => real.EDecimal.ToDouble(),
|
||||
Entity.Number.Complex complex => Math.Abs(complex.RealPart.EDecimal.ToDouble()) + Math.Abs(complex.ImaginaryPart.EDecimal.ToDouble()),
|
||||
_ => double.NaN,
|
||||
};
|
||||
|
||||
return double.IsNaN(value) ? null : value;
|
||||
}
|
||||
|
||||
private static double DigitsOf(EInteger value) => value.IsZero ? 1 : value.Abs().GetDigitCountAsInt64();
|
||||
|
||||
/// <summary>
|
||||
/// The decimal logarithm of an integer's magnitude, also beyond the range of a double.
|
||||
/// </summary>
|
||||
private static double Log10(EInteger value)
|
||||
{
|
||||
var magnitude = value.Abs();
|
||||
if (magnitude.IsZero)
|
||||
return 0;
|
||||
|
||||
var digits = magnitude.GetDigitCountAsInt64();
|
||||
if (digits <= 15)
|
||||
return Math.Log10(magnitude.ToInt64Checked());
|
||||
|
||||
var leading = double.Parse(magnitude.ToString()[..15], CultureInfo.InvariantCulture);
|
||||
return Math.Log10(leading) + (digits - 15);
|
||||
}
|
||||
|
||||
private static string Show(Entity node) => node.ToString().Shorten(MAX_SHOWN_LENGTH);
|
||||
}
|
||||
Reference in new issue
Block a user