mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-11 03:47: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,208 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
using AngouriMath;
|
||||
|
||||
using PeterO.Numbers;
|
||||
|
||||
namespace AIStudio.Tools.Mathematics;
|
||||
|
||||
/// <summary>
|
||||
/// Writes the results of the Mathematics tools for the model: as text, as LaTeX, and as decimals.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Every number is written the same way whatever the language of the user, because it goes to a
|
||||
/// model and comes back in the plain syntax of the tools.
|
||||
/// </remarks>
|
||||
public static class MathFormatting
|
||||
{
|
||||
/// <summary>
|
||||
/// The smallest decimal exponent written without scientific notation.
|
||||
/// </summary>
|
||||
private const int MIN_PLAIN_EXPONENT = -6;
|
||||
|
||||
/// <summary>
|
||||
/// The largest decimal exponent written without scientific notation.
|
||||
/// </summary>
|
||||
private const int MAX_PLAIN_EXPONENT = 20;
|
||||
|
||||
/// <summary>
|
||||
/// An expression in the plain syntax which the tools also read.
|
||||
/// </summary>
|
||||
/// <param name="expression">The expression.</param>
|
||||
/// <param name="what">What the expression is, named when it is too long.</param>
|
||||
/// <returns>The expression as text.</returns>
|
||||
/// <exception cref="MathComputationException">The text is longer than MathLimits.MAX_RESULT_LENGTH.</exception>
|
||||
public static string ToText(Entity expression, string what) => EnsureLength(expression.Stringize(), what);
|
||||
|
||||
/// <summary>
|
||||
/// An expression as LaTeX, which the chat renders.
|
||||
/// </summary>
|
||||
/// <param name="expression">The expression.</param>
|
||||
/// <param name="what">What the expression is, named when it is too long.</param>
|
||||
/// <returns>The expression as LaTeX.</returns>
|
||||
/// <exception cref="MathComputationException">The LaTeX is longer than MathLimits.MAX_RESULT_LENGTH.</exception>
|
||||
public static string ToLatex(Entity expression, string what) => EnsureLength(expression.Latexize(), what);
|
||||
|
||||
/// <summary>
|
||||
/// Refuses a text longer than MathLimits.MAX_RESULT_LENGTH.
|
||||
/// </summary>
|
||||
/// <param name="text">The text of a result.</param>
|
||||
/// <param name="what">What the text is, named when it is too long.</param>
|
||||
/// <returns>The text.</returns>
|
||||
/// <exception cref="MathComputationException">The text is too long.</exception>
|
||||
public static string EnsureLength(string text, string what)
|
||||
{
|
||||
if (text.Length > MathLimits.MAX_RESULT_LENGTH)
|
||||
throw new MathComputationException(string.Create(CultureInfo.InvariantCulture, $"{what} has {text.Length:N0} characters, but at most {MathLimits.MAX_RESULT_LENGTH:N0} are returned."));
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A decimal, rounded to the given number of significant digits.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Trailing zeros are left out, so 0.5 stays 0.5 at twenty digits; how many digits the value
|
||||
/// carries is reported next to it. Between 1e-6 and 1e21 the decimal is written out, beyond that
|
||||
/// in scientific notation such as 6.02214076e+23.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="digits">How many significant digits to keep.</param>
|
||||
/// <returns>The decimal.</returns>
|
||||
public static string FormatDecimal(EDecimal value, int digits)
|
||||
{
|
||||
if (value.IsNaN())
|
||||
return "NaN";
|
||||
|
||||
if (value.IsInfinity())
|
||||
return value.IsNegative ? "-oo" : "+oo";
|
||||
|
||||
var rounded = value.RoundToPrecision(EContext.ForPrecisionAndRounding(digits, ERounding.HalfEven)).Reduce(null);
|
||||
if (rounded.IsZero)
|
||||
return "0";
|
||||
|
||||
var mantissa = rounded.Mantissa.Abs().ToString();
|
||||
var exponent = rounded.Exponent.ToInt64Checked() + mantissa.Length - 1;
|
||||
if (exponent is >= MIN_PLAIN_EXPONENT and <= MAX_PLAIN_EXPONENT)
|
||||
return rounded.ToPlainString();
|
||||
|
||||
var scientific = new StringBuilder();
|
||||
if (rounded.IsNegative)
|
||||
scientific.Append('-');
|
||||
|
||||
scientific.Append(mantissa[0]);
|
||||
if (mantissa.Length > 1)
|
||||
scientific.Append('.').Append(mantissa, 1, mantissa.Length - 1);
|
||||
|
||||
scientific.Append('e').Append(exponent < 0 ? '-' : '+').Append(Math.Abs(exponent).ToString(CultureInfo.InvariantCulture));
|
||||
return scientific.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A double, rounded to the given number of significant digits.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="digits">How many significant digits to keep, 17 at most.</param>
|
||||
/// <returns>The decimal.</returns>
|
||||
public static string FormatDecimal(double value, int digits)
|
||||
{
|
||||
if (double.IsNaN(value))
|
||||
return "NaN";
|
||||
|
||||
if (double.IsInfinity(value))
|
||||
return value < 0 ? "-oo" : "+oo";
|
||||
|
||||
return FormatDecimal(EDecimal.FromDouble(value), Math.Min(digits, 17));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A real or complex number, rounded to the given number of significant digits, as in 1.5 - 2i.
|
||||
/// </summary>
|
||||
/// <param name="value">The number.</param>
|
||||
/// <param name="digits">How many significant digits to keep in each part.</param>
|
||||
/// <returns>The decimal.</returns>
|
||||
public static string FormatNumber(Entity.Number.Complex value, int digits)
|
||||
{
|
||||
var real = value.RealPart.EDecimal;
|
||||
var imaginary = value.ImaginaryPart.EDecimal;
|
||||
if (imaginary.IsZero)
|
||||
return FormatDecimal(real, digits);
|
||||
|
||||
var imaginaryText = FormatDecimal(imaginary.Abs(), digits) + "i";
|
||||
if (real.IsZero)
|
||||
return imaginary.IsNegative ? "-" + imaginaryText : imaginaryText;
|
||||
|
||||
return $"{FormatDecimal(real, digits)} {(imaginary.IsNegative ? '-' : '+')} {imaginaryText}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How far apart two numbers lie, as the sum of the distances of their parts.
|
||||
/// </summary>
|
||||
/// <param name="first">One number.</param>
|
||||
/// <param name="second">The other number.</param>
|
||||
/// <returns>The distance, as a double.</returns>
|
||||
public static double Distance(Entity.Number.Complex first, Entity.Number.Complex second)
|
||||
{
|
||||
var context = EContext.ForPrecision(20);
|
||||
var real = first.RealPart.EDecimal.Subtract(second.RealPart.EDecimal, context).Abs();
|
||||
var imaginary = first.ImaginaryPart.EDecimal.Subtract(second.ImaginaryPart.EDecimal, context).Abs();
|
||||
return real.Add(imaginary, context).ToDouble();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How many significant digits two real or complex values of the same quantity have in common,
|
||||
/// measured against the larger of them, so a tiny imaginary part does not count for more than it is.
|
||||
/// </summary>
|
||||
/// <param name="first">One value.</param>
|
||||
/// <param name="second">The other value.</param>
|
||||
/// <param name="maximum">The most digits to report, for values which are equal.</param>
|
||||
/// <returns>The number of digits, between 0 and maximum.</returns>
|
||||
public static int CountMatchingDigits(Entity.Number.Complex first, Entity.Number.Complex second, int maximum)
|
||||
{
|
||||
if (first.ImaginaryPart.EDecimal.IsZero && second.ImaginaryPart.EDecimal.IsZero)
|
||||
return CountMatchingDigits(first.RealPart.EDecimal, second.RealPart.EDecimal, maximum);
|
||||
|
||||
var context = EContext.ForPrecision(20);
|
||||
var firstSize = first.RealPart.EDecimal.Abs().Add(first.ImaginaryPart.EDecimal.Abs(), context);
|
||||
var secondSize = second.RealPart.EDecimal.Abs().Add(second.ImaginaryPart.EDecimal.Abs(), context);
|
||||
var scale = EDecimal.Max(firstSize, secondSize).ToDouble();
|
||||
var distance = Distance(first, second);
|
||||
if (!double.IsFinite(distance) || !double.IsFinite(scale))
|
||||
return 0;
|
||||
|
||||
if (distance <= 0)
|
||||
return maximum;
|
||||
|
||||
return (int)Math.Clamp(Math.Floor(-Math.Log10(distance / scale)), 0, maximum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How many significant digits two values of the same quantity have in common.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Counted from their relative difference, as is usual for correct digits: 0.99999 and 1.00001
|
||||
/// agree to four digits although no digit is the same. Evaluated at two working precisions, the
|
||||
/// digits they share are the ones the computation carries.
|
||||
/// </remarks>
|
||||
/// <param name="first">One value.</param>
|
||||
/// <param name="second">The other value.</param>
|
||||
/// <param name="maximum">The most digits to report, for values which are equal.</param>
|
||||
/// <returns>The number of digits, between 0 and maximum.</returns>
|
||||
public static int CountMatchingDigits(EDecimal first, EDecimal second, int maximum)
|
||||
{
|
||||
if (first.IsNaN() || second.IsNaN() || first.IsInfinity() || second.IsInfinity())
|
||||
return 0;
|
||||
|
||||
if (first.CompareTo(second) == 0)
|
||||
return maximum;
|
||||
|
||||
var context = EContext.ForPrecision(20);
|
||||
var scale = first.Abs().CompareTo(second.Abs()) >= 0 ? first.Abs() : second.Abs();
|
||||
var relative = first.Subtract(second, context).Abs().Divide(scale, context).ToDouble();
|
||||
if (relative <= 0)
|
||||
return maximum;
|
||||
|
||||
return (int)Math.Clamp(Math.Floor(-Math.Log10(relative)), 0, maximum);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user