using System; using System.Collections; using System.Collections.Generic; using System.Numerics; using MathNet.Numerics.Random; using Scriban.Runtime; using Scriban.Syntax; namespace Kalk.Core { /// /// Mathematical module (builtin). /// public partial class MathModule : KalkModuleWithFunctions { public const string CategoryMathConstants = "Math Constants"; public const string CategoryMathFunctions = "Math Functions"; private readonly Func FibFunc; private readonly Func AbsFunc; private readonly Func RndFunc; private static readonly Func CosFunc = Math.Cos; private static readonly Func AcosFunc = Math.Acos; private static readonly Func CoshFunc = Math.Cosh; private static readonly Func AcoshFunc = Math.Acosh; private static readonly Func SinFunc = Math.Sin; private static readonly Func AsinFunc = Math.Asin; private static readonly Func SinhFunc = Math.Sinh; private static readonly Func AsinhFunc = Math.Asinh; private static readonly Func TanFunc = Math.Tan; private static readonly Func TanhFunc = Math.Tanh; private static readonly Func AtanFunc = Math.Atan; private static readonly Func AtanhFunc = Math.Atanh; private static readonly Func RsqrtFunc = x => 1.0 / Math.Sqrt(x); private static readonly Func SqrtFunc = Math.Sqrt; private static readonly Func LogFunc = Math.Log; private static readonly Func Log10Func = Math.Log10; private static readonly Func Log2Func = Math.Log2; private static readonly Func ExpFunc = Math.Exp; private static readonly Func Exp2Func = x => Math.Pow(2, x); private static readonly Func FracFunc = FracImpl; private static readonly Func FracSignedFunc = FracSignedImpl; private static readonly Func RoundFunc = Math.Round; private static readonly Func FloorFunc= Math.Floor; private static readonly Func CeilFunc = Math.Ceiling; private static readonly Func TruncFunc= Math.Truncate; private readonly Func SignFunc; private static readonly Func RadiansFunc = x => x * Math.PI / 180.0; private static readonly Func DegreesFunc = x => x * 180.0 / Math.PI; private static readonly Func IsFiniteFunc = IsFiniteFuncImpl; private static readonly Func IsInfFunc = IsInfFuncImpl; private static readonly Func IsNanFunc = IsNanFuncImpl; private static readonly Func SaturateFunc = SaturateImpl; private Random _random; public MathModule() { IsBuiltin = true; FibFunc = Fibonacci; AbsFunc = AbsImpl; SignFunc = SignFuncImpl; _random = new Random(); RndFunc = RndImpl; RegisterFunctionsAuto(); } /// /// Defines the "Not a Number" constant for a double. /// /// /// ```kalk /// >>> nan /// # nan /// out = nan /// ``` /// [KalkExport("nan", CategoryMathFunctions)] public const double Nan = double.NaN; /// /// Defines the infinity constant for a double. /// /// /// ```kalk /// >>> inf /// # inf /// out = inf /// ``` /// [KalkExport("inf", CategoryMathFunctions)] public const double Inf = double.PositiveInfinity; /// /// Defines the PI constant. pi = 3.14159265358979 /// /// /// ```kalk /// >>> pi /// # pi /// out = 3.141592653589793 /// ``` /// [KalkExport("pi", CategoryMathFunctions)] public const double Pi = Math.PI; /// /// Defines the natural logarithmic base. e = 2.71828182845905 /// /// /// ```kalk /// >>> e /// # e /// out = 2.718281828459045 /// ``` /// [KalkExport("e", CategoryMathFunctions)] public const double E = Math.E; /// /// Calculates the fibonacci number for the specified input. /// /// The input number. /// The fibonacci number. /// /// ```kalk /// >>> fib 50 /// # fib(50) /// out = 12_586_269_025 /// ``` /// [KalkExport("fib", CategoryMathFunctions, Functor = true)] public object Fib(KalkIntValue x) => x.TransformArg(Engine, FibFunc); /// /// Defines the imaginary part of a complex number. /// /// A complex number. /// /// ```kalk /// >>> 1 + 2i /// # 1 + 2 * i /// out = 1 + 2i /// ``` /// [KalkExport("i", CategoryMathFunctions)] public static object ComplexNumber() { return new KalkComplex(0, 1); } /// /// Determines if all components of the specified value are non-zero. /// /// The specified value. /// true if all components of the x parameter are non-zero; otherwise, false. /// This function is similar to the `any` function. /// The `any` function determines if any components of the specified value are non-zero, while the `all` function determines if all components of the specified value are non-zero. /// /// /// ```kalk /// >>> all(bool4(true, false, true, false)) /// # all(bool4(true, false, true, false)) /// out = false /// >>> all(bool4(true, true, true, true)) /// # all(bool4(true, true, true, true)) /// out = true /// >>> all([0,1,0,2]) /// # all([0,1,0,2]) /// out = false /// >>> all([1,1,1,1]) /// # all([1,1,1,1]) /// out = true /// ``` /// [KalkExport("all", CategoryMathFunctions)] public KalkBool All(object x) { if (x == null) throw new ArgumentNullException(nameof(x)); if (x is IEnumerable it) { foreach(var item in it) { if (!Engine.ToBool(Engine.CurrentSpan, item)) { return false; } } return true; } return Engine.ToBool(Engine.CurrentSpan, x); } /// /// Determines if any components of the specified value are non-zero. /// /// The specified value. /// true if any components of the x parameter are non-zero; otherwise, false. /// This function is similar to the `all` intrinsic function. /// The `any` function determines if any components of the specified value are non-zero, /// while the `all` function determines if all components of the specified value are non-zero. /// /// /// ```kalk /// >>> any(bool4(true, false, true, false)) /// # any(bool4(true, false, true, false)) /// out = true /// >>> any(bool4(false, false, false, false)) /// # any(bool4(false, false, false, false)) /// out = false /// >>> any([0,1,0,2]) /// # any([0,1,0,2]) /// out = true /// >>> any([0,0,0,0]) /// # any([0,0,0,0]) /// out = false /// ``` /// [KalkExport("any", CategoryMathFunctions)] public KalkBool Any(object x) { if (x == null) throw new ArgumentNullException(nameof(x)); if (x is IEnumerable it) { foreach (var item in it) { if (Engine.ToBool(Engine.CurrentSpan, item)) { return true; } } return false; } return Engine.ToBool(Engine.CurrentSpan, x); } /// /// Returns the absolute value of the specified value. /// /// The specified value. /// The absolute value of the parameter. /// /// ```kalk /// >>> abs(-1) /// # abs(-1) /// out = 1 /// >>> abs(float4(-1, 1, -2, -3)) /// # abs(float4(-1, 1, -2, -3)) /// out = float4(1, 1, 2, 3) /// ``` /// [KalkExport("abs", CategoryMathFunctions, Functor = true)] public object Abs(KalkCompositeValue x) => x.TransformArg(Engine, AbsFunc); /// /// Returns a random value. /// /// A value to create random values for. /// A random value or a random value of the parameter. /// /// ```kalk /// >>> seed(0); rnd /// # seed(0); rnd /// out = 0.7262432699679598 /// >>> rnd /// # rnd /// out = 0.8173253595909687 /// >>> rnd(float4) /// # rnd(float4) /// out = float4(0.7680227, 0.5581612, 0.20603316, 0.5588848) /// ``` /// [KalkExport("rnd", CategoryMathFunctions, Functor = true)] public object Rnd(KalkCompositeValue x = null) { if (x != null) { return x.TransformArg(Engine, RndFunc); } else { return RndFunc(null); } } /// /// Setup the seed function for rnd. The default seed is random. /// /// An original seed value for the `rnd` function. /// The x is not specified, it will generate a random seed automatically. /// /// ```kalk /// >>> seed(0); rnd /// # seed(0); rnd /// out = 0.7262432699679598 /// >>> seed(1); rnd /// # seed(1); rnd /// out = 0.24866858415709278 /// ``` /// [KalkExport("seed", CategoryMathFunctions)] public void Seed(int? x = null) { _random = x.HasValue ? new Random(x.Value) : new Random(); } /// /// Splits the value x into fractional and integer parts, each of which has the same sign as x. /// /// The input value. /// The signed-fractional portion of x. /// /// ```kalk /// >>> modf(1.5) /// # modf(1.5) /// out = [1, 0.5] /// >>> modf(float2(-1.2, 3.4)) /// # modf(float2(-1.2, 3.4)) /// out = [float2(-1, 3), float2(-0.20000005, 0.4000001)] /// ``` /// [KalkExport("modf", CategoryMathFunctions, Functor = true)] public ScriptArray Modf(KalkCompositeValue x) { return new ScriptArray(2) { x.TransformArg(Engine, TruncFunc), x.TransformArg(Engine, FracSignedFunc) }; } /// /// Converts the specified value from degrees to radians. /// /// The specified value in degrees. /// The x parameter converted from degrees to radians. /// /// ```kalk /// >>> radians(90) /// # radians(90) /// out = 1.5707963267948966 /// >>> radians(180) /// # radians(180) /// out = 3.141592653589793 /// ``` /// [KalkExport("radians", CategoryMathFunctions, Functor = true)] public object Radians(KalkCompositeValue x) => x.TransformArg(Engine, RadiansFunc); /// /// Converts the specified value from radians to degrees. /// /// The x parameter converted from radians to degrees. /// /// ```kalk /// >>> degrees(pi/2) /// # degrees(pi / 2) /// out = 90 /// >>> degrees(pi) /// # degrees(pi) /// out = 180 /// ``` /// [KalkExport("degrees", CategoryMathFunctions, Functor = true)] public object Degrees(KalkCompositeValue x) => x.TransformArg(Engine, DegreesFunc); /// /// Returns an integer that indicates the sign of a number. /// /// A signed number. /// /// A number that indicates the sign of x: /// - -1 if x is less than zero /// - 0 if x is equal to zero /// - 1 if x is greater than zero. /// /// /// ```kalk /// >>> sign(-5); sign(0); sign(2.3) /// # sign(-5); sign(0); sign(2.3) /// out = -1 /// out = 0 /// out = 1 /// >>> sign float4(-1, 2, 0, 1.5) /// # sign(float4(-1, 2, 0, 1.5)) /// out = float4(-1, 1, 0, 1) /// ``` /// [KalkExport("sign", CategoryMathFunctions, Functor = true)] public object Sign(KalkCompositeValue x) => x.TransformArg(Engine, SignFunc); /// /// Returns the cosine of the specified value. /// /// The specified value, in radians. /// The cosine of the x parameter. /// /// ```kalk /// >>> cos 0.5 /// # cos(0.5) /// out = 0.8775825618903728 /// >>> cos float4(pi, pi/2, 0, 0.5) /// # cos(float4(pi, pi / 2, 0, 0.5)) /// out = float4(-1, -4.371139E-08, 1, 0.87758255) /// ``` /// [KalkExport("cos", CategoryMathFunctions, Functor = true)] public object Cos(KalkDoubleValue x) => x.TransformArg(Engine, CosFunc); /// /// Returns the arccosine of the specified value. /// /// The specified value. Each component should be a floating-point value within the range of -1 to 1. /// The arccosine of the x parameter. /// /// ```kalk /// >>> acos(-1) /// # acos(-1) /// out = 3.141592653589793 /// >>> acos(0) /// # acos(0) /// out = 1.5707963267948966 /// >>> acos(1) /// # acos(1) /// out = 0 /// >>> acos(float4(-1,0,1,0.5)) /// # acos(float4(-1, 0, 1, 0.5)) /// out = float4(3.1415927, 1.5707964, 0, 1.0471976) /// ``` /// [KalkExport("acos", CategoryMathFunctions, Functor = true)] public object Acos(KalkDoubleValue x) => x.TransformArg(Engine, AcosFunc); /// /// Returns the hyperbolic cosine of the specified value. /// /// The specified value, in radians. /// The hyperbolic cosine of the x parameter. /// /// ```kalk /// >>> cosh(-1) /// # cosh(-1) /// out = 1.5430806348152437 /// >>> cosh(1) /// # cosh(1) /// out = 1.5430806348152437 /// >>> cosh(0) /// # cosh(0) /// out = 1 /// >>> cosh(float4(-1, 1, 0, 2)) /// # cosh(float4(-1, 1, 0, 2)) /// out = float4(1.5430807, 1.5430807, 1, 3.7621956) /// ``` /// [KalkExport("cosh", CategoryMathFunctions, Functor = true)] public object Cosh(KalkDoubleValue x) => x.TransformArg(Engine, CoshFunc); /// /// Returns the inverse hyperbolic cosine of a number. The number must be greater than or equal to 1. /// /// Any real number equal to or greater than 1. /// The inverse hyperbolic cosine of the x parameter /// /// ```kalk /// >>> acosh(1) /// # acosh(1) /// out = 0 /// >>> acosh(10) /// # acosh(10) /// out = 2.993222846126381 /// >>> acosh(float4(1,2,4,10)) /// # acosh(float4(1, 2, 4, 10)) /// out = float4(0, 1.316958, 2.063437, 2.993223) /// ``` /// [KalkExport("acosh", CategoryMathFunctions, Functor = true)] public object Acosh(KalkDoubleValue x) => x.TransformArg(Engine, AcoshFunc); /// /// Returns the sine of the specified value. /// /// The specified value, in radians. /// The sine of the x parameter. /// /// ```kalk /// >>> sin 0.5 /// # sin(0.5) /// out = 0.479425538604203 /// >>> sin float4(pi, pi/2, 0, 0.5) /// # sin(float4(pi, pi / 2, 0, 0.5)) /// out = float4(-8.742278E-08, 1, 0, 0.47942555) /// ``` /// [KalkExport("sin", CategoryMathFunctions, Functor = true)] public object Sin(KalkDoubleValue x) => x.TransformArg(Engine, SinFunc); /// /// Returns the arcsine of the specified value. /// /// The specified value. Each component of the x parameter should be within the range of -π/2 to π/2. /// The arcsine of the x parameter. /// /// ```kalk /// >>> asin 0.5 /// # asin(0.5) /// out = 0.5235987755982989 /// >>> asin float4(-1, 0, 1, 0.5) /// # asin(float4(-1, 0, 1, 0.5)) /// out = float4(-1.5707964, 0, 1.5707964, 0.5235988) /// ``` /// [KalkExport("asin", CategoryMathFunctions, Functor = true)] public object Asin(KalkDoubleValue x) => x.TransformArg(Engine, AsinFunc); /// /// Returns the hyperbolic sine of the specified value. /// /// The specified value, in radians. /// The hyperbolic sine of the x parameter. /// /// ```kalk /// >>> sinh(-1) /// # sinh(-1) /// out = -1.1752011936438014 /// >>> sinh(0) /// # sinh(0) /// out = 0 /// >>> sinh(1) /// # sinh(1) /// out = 1.1752011936438014 /// >>> sinh(float4(-1, 1, 0, 2)) /// # sinh(float4(-1, 1, 0, 2)) /// out = float4(-1.1752012, 1.1752012, 0, 3.6268604) /// ``` /// [KalkExport("sinh", CategoryMathFunctions, Functor = true)] public object Sinh(KalkDoubleValue x) => x.TransformArg(Engine, SinhFunc); /// /// Returns the inverse hyperbolic sine of a number. /// /// The specified value. /// The inverse hyperbolic sine of the x parameter. /// /// ```kalk /// >>> asinh(-1.1752011936438014) /// # asinh(-1.1752011936438014) /// out = -1 /// >>> asinh(0) /// # asinh(0) /// out = 0 /// >>> asinh(1.1752011936438014) /// # asinh(1.1752011936438014) /// out = 1 /// >>> asinh(float4(-1.1752011936438014, 0, 1.1752011936438014, 2)) /// # asinh(float4(-1.1752011936438014, 0, 1.1752011936438014, 2)) /// out = float4(-1, 0, 1, 1.4436355) /// ``` /// [KalkExport("asinh", CategoryMathFunctions, Functor = true)] public object Asinh(KalkDoubleValue x) => x.TransformArg(Engine, AsinhFunc); /// /// Returns the tangent of the specified value. /// /// The specified value, in radians. /// The tangent of the x parameter. /// /// ```kalk /// >>> tan(0.5) /// # tan(0.5) /// out = 0.5463024898437905 /// >>> tan(1) /// # tan(1) /// out = 1.5574077246549023 /// >>> tan float4(1, 2, 3, 4) /// # tan(float4(1, 2, 3, 4)) /// out = float4(1.5574077, -2.1850398, -0.14254655, 1.1578213) /// ``` /// [KalkExport("tan", CategoryMathFunctions, Functor = true)] public object Tan(KalkDoubleValue x) => x.TransformArg(Engine, TanFunc); /// /// Returns the arctangent of the specified value. /// /// The specified value. /// The arctangent of the x parameter. This value is within the range of -π/2 to π/2. /// /// ```kalk /// >>> atan(0.5) /// # atan(0.5) /// out = 0.4636476090008061 /// >>> atan(1) /// # atan(1) /// out = 0.7853981633974483 /// >>> atan(0) /// # atan(0) /// out = 0 /// >>> atan(float4(0,1,2,3)) /// # atan(float4(0, 1, 2, 3)) /// out = float4(0, 0.7853982, 1.1071488, 1.2490457) /// ``` /// [KalkExport("atan", CategoryMathFunctions, Functor = true)] public object Atan(KalkDoubleValue x) => x.TransformArg(Engine, AtanFunc); /// /// Returns the hyperbolic tangent of the specified value. /// /// The specified value, in radians. /// The hyperbolic tangent of the x parameter. /// /// ```kalk /// >>> tanh(0) /// # tanh(0) /// out = 0 /// >>> tanh(1) /// # tanh(1) /// out = 0.7615941559557649 /// >>> tanh(2) /// # tanh(2) /// out = 0.9640275800758169 /// >>> tanh(float4(0, 1, 2, 3)) /// # tanh(float4(0, 1, 2, 3)) /// out = float4(0, 0.7615942, 0.9640276, 0.9950548) /// ``` /// [KalkExport("tanh", CategoryMathFunctions, Functor = true)] public object Tanh(KalkDoubleValue x) => x.TransformArg(Engine, TanhFunc); /// /// Returns the inverse hyperbolic tangent of a number. /// /// The specified value. Number must be between -1 and 1 (excluding -1 and 1). /// The inverse hyperbolic tangent of the x parameter /// /// ```kalk /// >>> atanh(0) /// # atanh(0) /// out = 0 /// >>> atanh(0.5) /// # atanh(0.5) /// out = 0.5493061443340549 /// >>> atanh(float4(-0.5, 0, 0.5, 0.8)) /// # atanh(float4(-0.5, 0, 0.5, 0.8)) /// out = float4(-0.54930615, 0, 0.54930615, 1.0986123) /// ``` /// [KalkExport("atanh", CategoryMathFunctions, Functor = true)] public object Atanh(KalkDoubleValue x) => x.TransformArg(Engine, AtanhFunc); /// /// Returns the arctangent of two values (x,y). /// /// The y value. /// The x value. /// The arctangent of (y,x). /// The signs of the x and y parameters are used to determine the quadrant of the return values within the range of -π to π. The `atan2` function is well-defined for every point other than the origin, even if y equals 0 and x does not equal 0. /// /// ```kalk /// >>> atan2(1,1) /// # atan2(1, 1) /// out = 0.7853981633974483 /// >>> atan2(1,0) /// # atan2(1, 0) /// out = 1.5707963267948966 /// >>> atan2(0,1) /// # atan2(0, 1) /// out = 0 /// >>> atan2(float4(1), float4(0,1,-1,2)) /// # atan2(float4(1), float4(0, 1, -1, 2)) /// out = float4(1.5707964, 0.7853982, 2.3561945, 0.4636476) /// ``` /// [KalkExport("atan2", CategoryMathFunctions, Functor = true)] public object Atan2(KalkDoubleValue y, KalkDoubleValue x) { var (xValues, yValues) = GetPairValues(x, y); int index = 0; return x.TransformArg(Engine, (double v) => { var result = Math.Atan2(yValues[index], xValues[index]); index++; return result; }); } /// /// Returns the floating-point remainder of x/y. /// /// The floating-point dividend. /// The floating-point divisor. /// The floating-point remainder of the x parameter divided by the y parameter. /// The floating-point remainder is calculated such that x = i * y + f, where i is an integer, f has the same sign as x, and the absolute value of f is less than the absolute value of y. /// /// ```kalk /// >>> fmod(2.5, 2) /// # fmod(2.5, 2) /// out = 0.5 /// >>> fmod(2.5, 3) /// # fmod(2.5, 3) /// out = 2.5 /// >>> fmod(-1.5, 1) /// # fmod(-1.5, 1) /// out = -0.5 /// >>> fmod(float4(1.5, 1.2, -2.3, -4.6), 0.2) /// # fmod(float4(1.5, 1.2, -2.3, -4.6), 0.2) /// out = float4(0.09999998, 2.9802322E-08, -0.09999992, -0.19999984) /// ``` /// [KalkExport("fmod", CategoryMathFunctions, Functor = true)] public object Fmod(KalkDoubleValue x, KalkDoubleValue y) { var (xValues, yValues) = GetPairValues(x, y); int index = 0; return x.TransformArg(Engine, (double v) => { var result = xValues[index] % yValues[index]; index++; return result; }); } /// /// Returns the fractional (or decimal) part of x; which is greater than or equal to 0 and less than 1. /// /// The specified value. /// The fractional part of the x parameter. /// /// ```kalk /// >>> frac(1.25) /// # frac(1.25) /// out = 0.25 /// >>> frac(10.5) /// # frac(10.5) /// out = 0.5 /// >>> frac(-1.75) /// # frac(-1.75) /// out = 0.25 /// >>> frac(-10.25) /// # frac(-10.25) /// out = 0.75 /// >>> frac(float4(1.25, 10.5, -1.75, -10.25)) /// # frac(float4(1.25, 10.5, -1.75, -10.25)) /// out = float4(0.25, 0.5, 0.25, 0.75) /// ``` /// [KalkExport("frac", CategoryMathFunctions, Functor = true)] public object Frac(KalkDoubleValue x) => x.TransformArg(Engine, FracFunc); /// /// Returns the reciprocal of the square root of the specified value. /// /// The specified value. /// The reciprocal of the square root of the x parameter. /// This function uses the following formula: 1 / sqrt(x). /// /// ```kalk /// >>> rsqrt(1) /// # rsqrt(1) /// out = 1 /// >>> rsqrt(2) /// # rsqrt(2) /// out = 0.7071067811865475 /// >>> rsqrt(float4(1,2,3,4)) /// # rsqrt(float4(1, 2, 3, 4)) /// out = float4(1, 0.70710677, 0.57735026, 0.5) /// ``` /// [KalkExport("rsqrt", CategoryMathFunctions, Functor = true)] public object Rsqrt(KalkDoubleValue x) => x.TransformArg(Engine, RsqrtFunc); /// /// Returns the square root of the specified floating-point value, per component. /// /// The specified floating-point value. /// The square root of the x parameter, per component. /// /// ```kalk /// >>> sqrt(1) /// # sqrt(1) /// out = 1 /// >>> sqrt(2) /// # sqrt(2) /// out = 1.4142135623730951 /// >>> sqrt(float4(1,2,3,4)) /// # sqrt(float4(1, 2, 3, 4)) /// out = float4(1, 1.4142135, 1.7320508, 2) /// ``` /// [KalkExport("sqrt", CategoryMathFunctions, Functor = true)] public object Sqrt(KalkDoubleValue x) => x.TransformArg(Engine, SqrtFunc); /// /// Returns the base-e logarithm of the specified value. /// /// The specified value. /// The base-e logarithm of the x parameter. If the x parameter is negative, this function returns indefinite. If the x parameter is 0, this function returns `-inf`. /// /// ```kalk /// >>> log 1 /// # log(1) /// out = 0 /// >>> log 2 /// # log(2) /// out = 0.6931471805599453 /// >>> log 0 /// # log(0) /// out = -inf /// >>> log float4(0,1,2,3) /// # log(float4(0, 1, 2, 3)) /// out = float4(-inf, 0, 0.6931472, 1.0986123) /// ``` /// [KalkExport("log", CategoryMathFunctions, Functor = true)] public object Log(KalkDoubleValue x) => x.TransformArg(Engine, LogFunc); /// /// Returns the base-2 logarithm of the specified value. /// /// The specified value. /// The base-2 logarithm of the x parameter. If the x parameter is negative, this function returns indefinite. If the x parameter is 0, this function returns -inf. /// /// ```kalk /// >>> log2 0 /// # log2(0) /// out = -inf /// >>> log2 8 /// # log2(8) /// out = 3 /// >>> log2 129 /// # log2(129) /// out = 7.011227255423254 /// >>> log2 float4(0, 2, 16, 257) /// # log2(float4(0, 2, 16, 257)) /// out = float4(-inf, 1, 4, 8.005625) /// ``` /// [KalkExport("log2", CategoryMathFunctions, Functor = true)] public object Log2(KalkDoubleValue x) => x.TransformArg(Engine, Log2Func); /// /// Returns the base-10 logarithm of the specified value. /// /// The specified value. /// The base-10 logarithm of the x parameter. If the x parameter is negative, this function returns indefinite. If the x is 0, this function returns -inf. /// /// ```kalk /// >>> log10 0 /// # log10(0) /// out = -inf /// >>> log10 10 /// # log10(10) /// out = 1 /// >>> log10 100 /// # log10(100) /// out = 2 /// >>> log10 1001 /// # log10(1001) /// out = 3.000434077479319 /// >>> log10(float4(0,10,100,1001)) /// # log10(float4(0, 10, 100, 1001)) /// out = float4(-inf, 1, 2, 3.0004342) /// ``` /// [KalkExport("log10", CategoryMathFunctions, Functor = true)] public object Log10(KalkDoubleValue x) => x.TransformArg(Engine, Log10Func); /// /// Returns the base-e exponential, or e^x, of the specified value. /// /// The specified value. /// The base-e exponential of the x parameter. /// /// ```kalk /// >>> exp(0) /// # exp(0) /// out = 1 /// >>> exp(1) /// # exp(1) /// out = 2.718281828459045 /// >>> exp(float4(0,1,2,3)) /// # exp(float4(0, 1, 2, 3)) /// out = float4(1, 2.7182817, 7.389056, 20.085537) /// ``` /// [KalkExport("exp", CategoryMathFunctions, Functor = true)] public object Exp(KalkDoubleValue x) => x.TransformArg(Engine, ExpFunc); /// /// Returns the base 2 exponential, or 2^x, of the specified value. /// /// The specified value. /// The base-2 exponential of the x parameter. /// /// ```kalk /// >>> exp2(0) /// # exp2(0) /// out = 1 /// >>> exp2(1) /// # exp2(1) /// out = 2 /// >>> exp2(4) /// # exp2(4) /// out = 16 /// >>> exp2(float4(0,1,2,3)) /// # exp2(float4(0, 1, 2, 3)) /// out = float4(1, 2, 4, 8) /// ``` /// [KalkExport("exp2", CategoryMathFunctions, Functor = true)] public object Exp2(KalkDoubleValue x) => x.TransformArg(Engine, Exp2Func); /// /// Returns the specified value raised to the specified power. /// /// The specified value. /// The specified power. /// The x parameter raised to the power of the y parameter. /// /// ```kalk /// >>> pow(1.5, 3.5) /// # pow(1.5, 3.5) /// out = 4.133513940946613 /// >>> pow(2, 4) /// # pow(2, 4) /// out = 16 /// >>> pow(float4(1,2,3,4), 4) /// # pow(float4(1, 2, 3, 4), 4) /// out = float4(1, 16, 81, 256) /// >>> pow(float4(1..4), float4(5..8)) /// # pow(float4(1..4), float4(5..8)) /// out = float4(1, 64, 2187, 65536) /// ``` /// [KalkExport("pow", CategoryMathFunctions, Functor = true)] public object Pow(KalkDoubleValue x, KalkDoubleValue y) { var (xValues, yValues) = GetPairValues(x, y); int index = 0; return x.TransformArg(Engine, (double v) => { var result = Math.Pow(xValues[index], yValues[index]); index++; return result; }); } /// /// Rounds the specified value to the nearest integer. /// /// The specified value. /// The x parameter, rounded to the nearest integer within a floating-point type. /// /// ```kalk /// >>> round(0.2); round(1.5); round(10.7) /// # round(0.2); round(1.5); round(10.7) /// out = 0 /// out = 2 /// out = 11 /// >>> round(-0.2); round(-1.5); round(-10.7) /// # round(-0.2); round(-1.5); round(-10.7) /// out = -0 /// out = -2 /// out = -11 /// ``` /// [KalkExport("round", CategoryMathFunctions, Functor = true)] public object Round(KalkDoubleValue x) => x.TransformArg(Engine, RoundFunc); /// /// Returns the largest integer that is less than or equal to the specified value. /// /// The specified value. /// The largest integer value (returned as a floating-point type) that is less than or equal to the x parameter. /// /// ```kalk /// >>> floor(0.2); floor(1.5); floor(10.7) /// # floor(0.2); floor(1.5); floor(10.7) /// out = 0 /// out = 1 /// out = 10 /// >>> floor(-0.2); floor(-1.5); floor(-10.7) /// # floor(-0.2); floor(-1.5); floor(-10.7) /// out = -1 /// out = -2 /// out = -11 /// ``` /// [KalkExport("floor", CategoryMathFunctions, Functor = true)] public object Floor(KalkDoubleValue x) => x.TransformArg(Engine, FloorFunc); /// /// Returns the smallest integer value that is greater than or equal to the specified value. /// /// The specified input. /// The smallest integer value (returned as a floating-point type) that is greater than or equal to the x parameter. /// /// ```kalk /// >>> ceil(0.2); ceil(1.5); ceil(10.7) /// # ceil(0.2); ceil(1.5); ceil(10.7) /// out = 1 /// out = 2 /// out = 11 /// >>> ceil(-0.2); ceil(-1.5); ceil(-10.7) /// # ceil(-0.2); ceil(-1.5); ceil(-10.7) /// out = -0 /// out = -1 /// out = -10 /// ``` /// [KalkExport("ceil", CategoryMathFunctions, Functor = true)] public object Ceiling(KalkDoubleValue x) => x.TransformArg(Engine, CeilFunc); /// /// Truncates a floating-point value to the integer component. /// /// The specified input. /// The input value truncated to an integer component. /// This function truncates a floating-point value to the integer component. Given a floating-point value of 1.6, the trunc function would return 1.0, where as the round function would return 2.0. /// /// ```kalk /// >>> trunc(0.2); trunc(1.5); trunc(10.7) /// # trunc(0.2); trunc(1.5); trunc(10.7) /// out = 0 /// out = 1 /// out = 10 /// >>> trunc(-0.2); trunc(-1.5); trunc(-10.7) /// # trunc(-0.2); trunc(-1.5); trunc(-10.7) /// out = -0 /// out = -1 /// out = -10 /// ``` /// [KalkExport("trunc", CategoryMathFunctions, Functor = true)] public object Trunc(KalkDoubleValue x) => x.TransformArg(Engine, TruncFunc); /// /// Clamps the specified value within the range of 0 to 1. /// /// The specified value. /// The x parameter, clamped within the range of 0 to 1. /// /// ```kalk /// >>> saturate(10) /// # saturate(10) /// out = 1 /// >>> saturate(-10) /// # saturate(-10) /// out = 0 /// >>> saturate(float4(-1, 0.5, 1, 2)) /// # saturate(float4(-1, 0.5, 1, 2)) /// out = float4(0, 0.5, 1, 1) /// ``` /// [KalkExport("saturate", CategoryMathFunctions, Functor = true)] public object Saturate(KalkDoubleValue x) => x.TransformArg(Engine, SaturateFunc); /// /// Selects the lesser of x and y. /// /// The x input value. /// The y input value. /// The x or y parameter, whichever is the smallest value. /// /// ```kalk /// >>> min(-5, 6) /// # min(-5, 6) /// out = -5 /// >>> min(1, 0) /// # min(1, 0) /// out = 0 /// >>> min(float4(0, 1, 2, 3), float4(1, 0, 3, 2)) /// # min(float4(0, 1, 2, 3), float4(1, 0, 3, 2)) /// out = float4(0, 0, 2, 2) /// ``` /// [KalkExport("min", CategoryMathFunctions, Functor = true)] public object Min(KalkDoubleValue x, KalkDoubleValue y) { var (xValues,yValues) = GetPairValues(x,y); int index = 0; return x.TransformArg(Engine, (double v) => { var result = Math.Min(xValues[index], yValues[index]); index++; return result; }); } /// /// Selects the greater of x and y. /// /// The x input value. /// The y input value. /// The x or y parameter, whichever is the largest value. /// /// ```kalk /// >>> max(-5, 6) /// # max(-5, 6) /// out = 6 /// >>> max(1, 0) /// # max(1, 0) /// out = 1 /// >>> max(float4(0, 1, 2, 3), float4(1, 0, 3, 2)) /// # max(float4(0, 1, 2, 3), float4(1, 0, 3, 2)) /// out = float4(1, 1, 3, 3) /// ``` /// [KalkExport("max", CategoryMathFunctions, Functor = true)] public object Max(KalkDoubleValue x, KalkDoubleValue y) { var (xValues, yValues) = GetPairValues(x, y); int index = 0; return x.TransformArg(Engine, (double v) => { var result = Math.Max(xValues[index], yValues[index]); index++; return result; }); } /// /// Compares two values, returning 0 or 1 based on which value is greater. /// /// The first floating-point value to compare. /// The second floating-point value to compare. /// 1 if the x parameter is greater than or equal to the y parameter; otherwise, 0. /// This function uses the following formula: (x >= y) ? 1 : 0. The function returns either 0 or 1 depending on whether the x parameter is greater than the y parameter. To compute a smooth interpolation between 0 and 1, use the `smoothstep` function. /// /// ```kalk /// >>> step(1, 5) /// # step(1, 5) /// out = 1 /// >>> step(5, 1) /// # step(5, 1) /// out = 0 /// >>> step(float4(0, 1, 2, 3), float4(1, 0, 3, 2)) /// # step(float4(0, 1, 2, 3), float4(1, 0, 3, 2)) /// out = float4(1, 0, 1, 0) /// >>> step(-10, 5) /// # step(-10, 5) /// out = 1 /// >>> step(5.5, -10.5) /// # step(5.5, -10.5) /// out = 0 /// ``` /// [KalkExport("step", CategoryMathFunctions, Functor = true)] public object Step(KalkDoubleValue y, KalkDoubleValue x) { var (xValues, yValues) = GetPairValues(x, y, nameof(x), nameof(y)); int index = 0; return x.TransformArg(Engine, (double v) => { var yv = yValues[index]; var xv = xValues[index]; index++; return xv >= yv ? 1.0 : 0.0; }); } /// /// Returns a smooth Hermite interpolation between 0 and 1, if x is in the range [min, max]. /// /// The minimum range of the x parameter. /// The maximum range of the x parameter. /// The specified value to be interpolated. /// Returns 0 if x is less than min; 1 if x is greater than max; otherwise, a value between 0 and 1 if x is in the range [min, max]. /// Use the smoothstep function to create a smooth transition between two values. For example, you can use this function to blend two colors smoothly. /// /// ```kalk /// >>> smoothstep(float4(0), float4(1), float4(-0.5)) /// # smoothstep(float4(0), float4(1), float4(-0.5)) /// out = float4(0, 0, 0, 0) /// >>> smoothstep(float4(0), float4(1), float4(1.5)) /// # smoothstep(float4(0), float4(1), float4(1.5)) /// out = float4(1, 1, 1, 1) /// >>> smoothstep(float4(0), float4(1), float4(0.5)) /// # smoothstep(float4(0), float4(1), float4(0.5)) /// out = float4(0.5, 0.5, 0.5, 0.5) /// >>> smoothstep(float4(0), float4(1), float4(0.9)) /// # smoothstep(float4(0), float4(1), float4(0.9)) /// out = float4(0.972, 0.972, 0.972, 0.972) /// ``` /// [KalkExport("smoothstep", CategoryMathFunctions, Functor = true)] public object Smoothstep(KalkDoubleValue min, KalkDoubleValue max, KalkDoubleValue x) { var (xValues, minValues, maxValues) = GetTripleValues(x, min, max, nameof(x), nameof(min), nameof(max)); int index = 0; return x.TransformArg(Engine, (double v) => { var minValue = minValues[index]; var maxValue = maxValues[index]; index++; if (v < minValue) v = 0.0; if (v > maxValue) v = 1.0; return v * v * (3.0f - (2.0f * v)); }); } /// /// Performs a linear interpolation. /// /// The first-floating point value. /// The second-floating point value. /// A value that linearly interpolates between the x parameter and the y parameter. /// The result of the linear interpolation. /// /// ```kalk /// >>> lerp(0, 10, 0.5) /// # lerp(0, 10, 0.5) /// out = 5 /// >>> lerp(rgb("AliceBlue").xyz, rgb("Green").xyz, 0.5) /// # lerp(rgb("AliceBlue").xyz, rgb("Green").xyz, 0.5) /// out = float3(0.47058824, 0.7372549, 0.5) /// ``` /// [KalkExport("lerp", CategoryMathFunctions, Functor = true)] public object Lerp(KalkDoubleValue x, KalkDoubleValue y, KalkDoubleValue s) { var (xValues, yValues, sValues) = GetTripleValues(x, y, s, nameof(x), nameof(y), nameof(s)); int index = 0; return x.TransformArg(Engine, (double xv) => { var yv = yValues[index]; var sv = sValues[index]; index++; return xv * (1 - sv) + yv * sv; }); } /// /// Clamps the specified value to the specified minimum and maximum range. /// /// A value to clamp. /// The specified minimum range. /// The specified maximum range. /// The clamped value for the x parameter. /// For values of -inf or inf, clamp will behave as expected. However for values of `nan`, the results are undefined. /// /// ```kalk /// >>> clamp(-1, 0, 1) /// # clamp(-1, 0, 1) /// out = 0 /// >>> clamp(2, 0, 1) /// # clamp(2, 0, 1) /// out = 1 /// >>> clamp(0.5, 0, 1) /// # clamp(0.5, 0, 1) /// out = 0.5 /// >>> clamp(float4(0, 1, -2, 3), float4(0, -1, 3, 4), float4(1, 2, 5, 6)) /// # clamp(float4(0, 1, -2, 3), float4(0, -1, 3, 4), float4(1, 2, 5, 6)) /// out = float4(0, 1, 3, 4) /// ``` /// [KalkExport("clamp", CategoryMathFunctions, Functor = true)] public object Clamp(KalkDoubleValue x, KalkDoubleValue min, KalkDoubleValue max) { var (xValues, minValues, maxValues) = GetTripleValues(x, min, max, nameof(x), nameof(min), nameof(max)); int index = 0; return x.TransformArg(Engine, (double xv) => { var minv = minValues[index]; var maxv = maxValues[index]; index++; return ClampImpl(xv, minv, maxv); }); } /// /// Returns the real part of the complex number. /// /// A complex number. /// The real part of the parameter x complex number. /// /// ```kalk /// >>> real(1.5 + 2.5i) /// # real(1.5 + 2.5 * i) /// out = 1.5 /// ``` /// [KalkExport("real", CategoryMathFunctions)] public double Real(KalkComplex x) => x.Re; /// /// Returns the imaginary part of the complex number. /// /// A complex number. /// The imaginary part of the parameter x complex number. /// /// ```kalk /// >>> imag(1.5 + 2.5i) /// # imag(1.5 + 2.5 * i) /// out = 2.5 /// ``` /// [KalkExport("imag", CategoryMathFunctions)] public double Imag(KalkComplex x) => x.Im; /// /// Returns the phase of the complex number. /// /// A complex number. /// The phase of the parameter x complex number. /// /// ```kalk /// >>> phase(1.5 + 2.5i) /// # phase(1.5 + 2.5 * i) /// out = 1.0303768265243125 /// ``` /// [KalkExport("phase", CategoryMathFunctions)] public double Phase(KalkComplex x) => x.Phase; /// /// Determines if the specified floating-point value is finite. /// /// The specified value. /// Returns a value of the same size as the input, with a value set to `true` if the x parameter is finite; otherwise `false`. /// /// ```kalk /// >>> isfinite(1) /// # isfinite(1) /// out = true /// >>> isfinite(nan) /// # isfinite(nan) /// out = false /// >>> isfinite(inf) /// # isfinite(inf) /// out = false /// >>> isfinite(float4(1, -10.5, inf, nan)) /// # isfinite(float4(1, -10.5, inf, nan)) /// out = bool4(true, true, false, false) /// ``` /// [KalkExport("isfinite", CategoryMathFunctions, Functor = true)] public object IsFinite(KalkCompositeValue x) => x.TransformArg(Engine, IsFiniteFunc); /// /// Determines if the specified value is infinite. /// /// The specified value. /// Returns a value of the same size as the input, with a value set to `true` if the x parameter is +inf or -inf. Otherwise, `false`. /// /// ```kalk /// >>> isinf(1) /// # isinf(1) /// out = false /// >>> isinf(inf) /// # isinf(inf) /// out = true /// >>> isinf(float4(1, -10.5, inf, nan)) /// # isinf(float4(1, -10.5, inf, nan)) /// out = bool4(false, false, true, false) /// ``` /// [KalkExport("isinf", CategoryMathFunctions, Functor = true)] public object IsInf(KalkCompositeValue x) => x.TransformArg(Engine, IsInfFunc); /// /// Determines if the specified value is `nan`. /// /// The specified value. /// Returns a value of the same size as the input, with a value set to `true` if the x parameter is `nan`. Otherwise, `false`. /// /// ```kalk /// >>> isnan(1) /// # isnan(1) /// out = false /// >>> isnan(inf) /// # isnan(inf) /// out = false /// >>> isnan(nan) /// # isnan(nan) /// out = true /// >>> isnan(float4(1, -10.5, inf, nan)) /// # isnan(float4(1, -10.5, inf, nan)) /// out = bool4(false, false, false, true) /// ``` /// [KalkExport("isnan", CategoryMathFunctions, Functor = true)] public object IsNan(KalkCompositeValue x) => x.TransformArg(Engine, IsNanFunc); /// /// Performs the summation of the specified value. /// /// The specified value. /// Additional values. /// The summation of the values. /// /// ```kalk /// >>> sum(1,2,3,4) /// # sum(1, 2, 3, 4) /// out = 10 /// >>> sum(float4(1..4)) /// # sum(float4(1..4)) /// out = 10 /// >>> sum(float4(1..4), float4(5..8)) /// # sum(float4(1..4), float4(5..8)) /// out = float4(15, 16, 17, 18) /// >>> sum("a", "b", "c") /// # sum("a", "b", "c") /// out = "abc" /// >>> sum(["a", "b", "c"]) /// # sum(["a", "b", "c"]) /// out = "abc" /// ``` /// [KalkExport("sum", CategoryMathFunctions)] public object Sum(object value, params object[] values) { if (value == null) throw new ArgumentNullException(nameof(value)); object result = value; if (value is IEnumerable it) { bool resetFirst = true; foreach (var nextValue in it) { if (resetFirst) { result = nextValue; resetFirst = false; } else { result = ScriptBinaryExpression.Evaluate(Engine, Engine.CurrentSpan, ScriptBinaryOperator.Add, result, nextValue); } } } foreach (var nextValue in values) { result = ScriptBinaryExpression.Evaluate(Engine, Engine.CurrentSpan, ScriptBinaryOperator.Add, result, nextValue); } return result; } private (List, List) GetPairValues(KalkDoubleValue x, KalkDoubleValue y, string nameofx = "x", string nameofy = "y") { var xValues = new List(); var yValues = new List(); x.TransformArg(Engine, (double v) => { xValues.Add(v); return v; }); y = Cast(x, y, nameofy); y.TransformArg(Engine, (double v) => { yValues.Add(v); return v; }); if (xValues.Count != yValues.Count) { throw new ArgumentException($"Invalid length between {nameofx} with {xValues.Count} elements and {nameofy} with {yValues.Count} elements.", nameofx); } return (xValues, yValues); } /// /// Cast y to x type. /// private KalkDoubleValue Cast(KalkDoubleValue x, KalkDoubleValue y, string nameofy = "y") { if (x.Transformable != null && y.Transformable == null) { try { var ty = Engine.ToObject(1, y.Value); return new KalkDoubleValue((IScriptTransformable) x.TransformArg(Engine, (double v) => ty)); } catch { throw new ArgumentException($"Error converting {nameofy} to {Engine.GetTypeName(x)}.", nameofy); } } return y; } private (List, List, List) GetTripleValues(KalkDoubleValue x, KalkDoubleValue y, KalkDoubleValue z, string nameofx = "x", string nameofy = "y", string nameofz = "z") { var xValues = new List(); var yValues = new List(); var zValues = new List(); x.TransformArg(Engine, (double v) => { xValues.Add(v); return v; }); y = Cast(x, y, nameofy); y.TransformArg(Engine, (double v) => { yValues.Add(v); return v; }); z = Cast(x, z, nameofz); z.TransformArg(Engine, (double v) => { zValues.Add(v); return v; }); if (xValues.Count != yValues.Count || xValues.Count != zValues.Count) { throw new ArgumentException($"Invalid length between {nameofx} with {xValues.Count} elements, {nameofy} with {yValues.Count} elements and {nameofz} with {zValues.Count} elements.", nameofx); } return (xValues, yValues, zValues); } private static double FracImpl(double x) { if (x < 0) { return 1.0 + x + Math.Truncate(-x); } return x - Math.Truncate(x); } private static double FracSignedImpl(double x) { return x - Math.Truncate(x); } private static KalkBool IsFiniteFuncImpl(object arg) { if (arg is float f32) return float.IsFinite(f32); if (arg is double f64) return double.IsFinite(f64); return true; } private static KalkBool IsInfFuncImpl(object arg) { if (arg is float f32) return float.IsInfinity(f32); if (arg is double f64) return double.IsInfinity(f64); return false; } private static KalkBool IsNanFuncImpl(object arg) { if (arg is float f32) return float.IsNaN(f32); if (arg is double f64) return double.IsNaN(f64); return false; } private object SignFuncImpl(object value) { if (value == null) return null; var type = value.GetType(); if (type == typeof(int)) { return Math.Sign((int)value); } if (type == typeof(float)) { return Math.Sign((float)value); } if (type == typeof(double)) { return Math.Sign((double)value); } if (type == typeof(long)) { return Math.Sign((long)value); } if (type == typeof(decimal)) { return Math.Sign((decimal)value); } if (type == typeof(BigInteger)) { return ((BigInteger)value).Sign; } return Math.Sign(Engine.ToObject(0, value)); } private BigInteger Fibonacci(int value) { if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), "The value must be > 0"); if (value == 0) return BigInteger.Zero; if (value == 1) return BigInteger.One; var fn = BigInteger.Zero; var fn1 = BigInteger.One; var n = (uint) value; for (uint bit = (0x80000000 >> BitOperations.LeadingZeroCount(n)); bit != 0; bit >>= 1) { // F(2n) = F(n) * (2*F(n+1) - F(n)) // F(2n+1) = F(n+1)^2 + F(n)^2 var f2n = fn * ((fn1 << 1) - fn); var f2n1 = fn1 * fn1 + fn * fn; fn = f2n; fn1 = f2n1; if ((n & bit) != 0) { var nfn1 = fn + fn1; fn = fn1; fn1 = nfn1; } } return fn; } private object RndImpl(object value = null) { if (value == null) return _random.NextDouble(); var type = value.GetType(); if (type == typeof(int)) { return _random.NextFullRangeInt32(); } if (type == typeof(float)) { return (float)_random.NextDouble(); } if (type == typeof(double)) { return _random.NextDouble(); } if (type == typeof(long)) { return _random.NextFullRangeInt64(); } if (type == typeof(decimal)) { return _random.NextDecimal(); } if (type == typeof(BigInteger)) { return (BigInteger)_random.NextFullRangeInt64(); } return _random.NextDouble(); } private object AbsImpl(object value) { if (value == null) return null; var type = value.GetType(); if (type == typeof(int)) { return Math.Abs((int) value); } if (type == typeof(float)) { return Math.Abs((float)value); } if (type == typeof(double)) { return Math.Abs((double)value); } if (type == typeof(long)) { return Math.Abs((long)value); } if (type == typeof(decimal)) { return Math.Abs((decimal)value); } if (type == typeof(BigInteger)) { return BigInteger.Abs((BigInteger)value); } return Math.Abs(Engine.ToObject(0, value)); } public static double SaturateImpl(double x) => x < 0.0 ? 0.0 : x > 1.0 ? 1.0 : x; public static double ClampImpl(double x, double min, double max) => Math.Min(Math.Max(x, min), max); } }