diff --git a/Sources/AngouriMath/Convenience/AngouriMathExtensions.cs b/Sources/AngouriMath/Convenience/AngouriMathExtensions.cs index a7a826767..38287da83 100644 --- a/Sources/AngouriMath/Convenience/AngouriMathExtensions.cs +++ b/Sources/AngouriMath/Convenience/AngouriMathExtensions.cs @@ -298,7 +298,7 @@ public static FastExpression Compile(this string str, params Variable[] variable /// Finds the symbolical derivative of the given expression /// /// - /// The expresion to be parsed and differentiated + /// The expression to be parsed and differentiated /// /// /// Over which variable to find the derivative @@ -315,7 +315,7 @@ public static Entity Derive(this string str, Variable x) /// Finds the symbolical derivative of the given expression /// /// - /// The expresion to be parsed and differentiated + /// The expression to be parsed and differentiated /// /// /// Over which variable to find the derivative @@ -328,21 +328,35 @@ public static Entity Differentiate(this string str, Variable x) => str.ToEntity().Differentiate(x); /// - /// Integrates the given expression over the `x` variable, if can. + /// Integrates indefinitely the given expression over the `x` variable, if can. /// May return an unresolved node. /// /// - /// The expression to be parsed and integrated over + /// The expression to be parsed and integrated /// - /// Over which to integrate + /// Over which variable to integrate /// - /// An integrated expression. It might remain the same, - /// it might have no integrals, and it might be transformed so that - /// only a few nodes have unresolved integrals. + /// An integrated expression. It might remain the same or be transformed into nodes with no integrals. /// public static Entity Integrate(this string str, Variable x) => str.ToEntity().Integrate(x); + /// + /// Integrates definitely the given expression over the `x` variable, if can. + /// May return an unresolved node. + /// + /// + /// The expression to be parsed and integrated + /// + /// Over which variable to integrate + /// The lower bound for integrating + /// The upper bound for integrating + /// + /// An integrated expression. It might remain the same or be transformed into nodes with no integrals. + /// + public static Entity Integrate(this string str, Variable x, Entity from, Entity to) + => str.ToEntity().Integrate(x, from, to); + /// /// Finds the limit of the given expression over the given variable /// diff --git a/Sources/AngouriMath/Convenience/MathS.cs b/Sources/AngouriMath/Convenience/MathS.cs index 858d06ba5..ea08c5d11 100644 --- a/Sources/AngouriMath/Convenience/MathS.cs +++ b/Sources/AngouriMath/Convenience/MathS.cs @@ -6338,7 +6338,7 @@ public static Entity Integral(Entity expr, Variable x) /// The complex upper bound for integrating [Obsolete("Now these functions are available as non-static methods at Entity")] public static Complex DefiniteIntegral(Entity expr, Variable x, (EDecimal Re, EDecimal Im) from, (EDecimal Re, EDecimal Im) to) => - Integration.Integrate(expr, x, from, to, 100); + DefiniteIntegral(expr, x, from, to, 100); /// /// Returns a value of a definite integral of a function. Only works for one-variable functions @@ -6349,7 +6349,7 @@ public static Complex DefiniteIntegral(Entity expr, Variable x, (EDecimal Re, ED /// The real upper bound for integrating [Obsolete("Now these functions are available as non-static methods at Entity")] public static Complex DefiniteIntegral(Entity expr, Variable x, EDecimal from, EDecimal to) => - Integration.Integrate(expr, x, (from, 0), (to, 0), 100); + DefiniteIntegral(expr, x, (from, 0), (to, 0), 100); /// /// Returns a value of a definite integral of a function. Only works for one-variable functions @@ -6361,7 +6361,7 @@ public static Complex DefiniteIntegral(Entity expr, Variable x, EDecimal from, E /// Accuracy (initially, amount of iterations) [Obsolete("Now these functions are available as non-static methods at Entity")] public static Complex DefiniteIntegral(Entity expr, Variable x, (EDecimal Re, EDecimal Im) from, (EDecimal Re, EDecimal Im) to, int stepCount) => - Integration.Integrate(expr, x, from, to, stepCount); + Integration.IntegrateNumerically(expr, x, Complex.Create(from.Re, from.Im), Complex.Create(to.Re, to.Im), stepCount); /// @@ -6626,7 +6626,7 @@ public static Complex DefiniteIntegral(Entity expr, Variable x, (EDecimal Re, ED /// a /// /// - public static Entity Integral(Entity expr, Entity var) => new Integralf(expr, var, 1); + public static Entity Integral(Entity expr, Entity var) => new Integralf(expr, var, null); /// /// Hangs your entity to an integral node @@ -6634,7 +6634,8 @@ public static Complex DefiniteIntegral(Entity expr, Variable x, (EDecimal Re, ED /// /// Expression to be hung /// Variable over which integral is taken - /// Number of times integral is taken. Only integers will be simplified or evaluated. + /// The lower bound for integrating + /// The upper bound for integrating /// /// /// using System; @@ -6680,7 +6681,7 @@ public static Complex DefiniteIntegral(Entity expr, Variable x, (EDecimal Re, ED /// a /// /// - public static Entity Integral(Entity expr, Entity var, int power) => new Integralf(expr, var, power); + public static Entity Integral(Entity expr, Entity var, Entity from, Entity to) => new Integralf(expr, var, (from, to)); /// /// Hangs your entity to a limit node @@ -6820,6 +6821,18 @@ public static class Boolean /// public static Entity.Boolean Create(bool b) => Entity.Boolean.Create(b); + + /// + /// One of the Boolean's states, which also behaves as Entity + /// That is, hangable + /// + [ConstantField] public static readonly Entity.Boolean True = Entity.Boolean.True; + + /// + /// One of the Boolean's states, which also behaves as Entity + /// That is, hangable + /// + [ConstantField] public static readonly Entity.Boolean False = Entity.Boolean.False; } /// diff --git a/Sources/AngouriMath/Core/Antlr/AngouriMath.g b/Sources/AngouriMath/Core/Antlr/AngouriMath.g index 875ba6c81..36929bb97 100644 --- a/Sources/AngouriMath/Core/Antlr/AngouriMath.g +++ b/Sources/AngouriMath/Core/Antlr/AngouriMath.g @@ -1,9 +1,9 @@ /* -Remember to run the "antlr_rerun.bat" file at the project root every time you modify this file so that other -source files under the Antlr folder can update and be reflected in other parts of AngouriMath! -It only consists of commands that are consistent across CMD and Bash so you should be able to run that file -regardless of whether you are on Windows, Linux or Mac. You need to have an installed Java Runtime, however. +Remember to run the "antlr_rerun.bat" file located at "Sources/Utils/antlr_rerun.bat" (relative to the repository root) +every time you modify this file so that the generated source files under the Antlr folder are updated and changes are +reflected in other parts of AngouriMath. The script only consists of commands that are consistent across CMD and Bash, +so you should be able to run it on Windows, Linux, or Mac. You need to have an installed Java Runtime, however. */ @@ -362,12 +362,12 @@ atom returns[Entity value] } | 'integral(' args = function_arguments ')' { - if (Assert("integral", (3, 2), $args.list.Count)) + if (Assert("integral", (4, 2), $args.list.Count)) { - if ($args.list[2] is Integer { EInteger: var asEInt }) - $value = MathS.Integral($args.list[0], $args.list[1], asEInt.ToInt32Checked()); + if ($args.list.Count == 4) + $value = MathS.Integral($args.list[0], $args.list[1], $args.list[2], $args.list[3]); else - throw new InvalidArgumentParseException("Expected number for the third argument of integral"); + $value = MathS.Integral($args.list[0], $args.list[1]); } else $value = MathS.Integral($args.list[0], $args.list[1]); diff --git a/Sources/AngouriMath/Core/Antlr/AngouriMathParser.cs b/Sources/AngouriMath/Core/Antlr/AngouriMathParser.cs index 867bac735..62854badb 100644 --- a/Sources/AngouriMath/Core/Antlr/AngouriMathParser.cs +++ b/Sources/AngouriMath/Core/Antlr/AngouriMathParser.cs @@ -2968,12 +2968,12 @@ public AtomContext atom() { State = 734; Match(T__39); - if (Assert("integral", (3, 2), _localctx.args.list.Count)) + if (Assert("integral", (4, 2), _localctx.args.list.Count)) { - if (_localctx.args.list[2] is Integer { EInteger: var asEInt }) - _localctx.value = MathS.Integral(_localctx.args.list[0], _localctx.args.list[1], asEInt.ToInt32Checked()); + if (_localctx.args.list.Count == 4) + _localctx.value = MathS.Integral(_localctx.args.list[0], _localctx.args.list[1], _localctx.args.list[2], _localctx.args.list[3]); else - throw new InvalidArgumentParseException("Expected number for the third argument of integral"); + _localctx.value = MathS.Integral(_localctx.args.list[0], _localctx.args.list[1]); } else _localctx.value = MathS.Integral(_localctx.args.list[0], _localctx.args.list[1]); diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs index ffb5e2740..ff1dffcf7 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs @@ -12,10 +12,12 @@ namespace AngouriMath partial record Entity { #pragma warning disable CS1591 // only while records' parameters cannot be documented - // Iterations should be refactored? to be int instead of Entity /// /// A node of derivative /// + /// + /// Negative iterations convert to integrals. + /// public sealed partial record Derivativef(Entity Expression, Entity Var, int Iterations) : CalculusOperator(Expression, Var) { /// Reuse the cache by returning the same object if possible @@ -32,17 +34,19 @@ public override Entity Replace(Func func) => /// /// A node of integral /// - public sealed partial record Integralf(Entity Expression, Entity Var, int Iterations) : CalculusOperator(Expression, Var) + public sealed partial record Integralf(Entity Expression, Entity Var, (Entity from, Entity to)? Range) : CalculusOperator(Expression, Var) { /// Reuse the cache by returning the same object if possible - private Integralf New(Entity expression, Entity var) => + private Integralf New(Entity expression, Entity var, (Entity from, Entity to)? range) => ReferenceEquals(Expression, expression) && ReferenceEquals(Var, var) - ? this : new(expression, var, Iterations); + && (range is null && Range is null || range is var (newFrom, newTo) && Range is var (oldFrom, oldTo) + && ReferenceEquals(newFrom, oldFrom) && ReferenceEquals(newTo, oldTo)) + ? this : new(expression, var, range); /// public override Entity Replace(Func func) => - func(New(Expression.Replace(func), Var.Replace(func))); + func(New(Expression.Replace(func), Var, Range is var (from, to) ? (from.Replace(func), to.Replace(func)) : null)); /// - protected override Entity[] InitDirectChildren() => new[] { Expression, Var, Iterations }; + protected override Entity[] InitDirectChildren() => Range is var (from, to) ? [Expression, Var, from, to] : [Expression, Var]; } /// diff --git a/Sources/AngouriMath/Core/Entity/Discrete/Entity.Discrete.Classes.cs b/Sources/AngouriMath/Core/Entity/Discrete/Entity.Discrete.Classes.cs index ec164ac1e..077e3f042 100644 --- a/Sources/AngouriMath/Core/Entity/Discrete/Entity.Discrete.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Discrete/Entity.Discrete.Classes.cs @@ -20,13 +20,13 @@ partial record Entity public sealed partial record Boolean(bool Value) : Statement { /// - /// One of the Boolean's state, which also behaves as Entity + /// One of the Boolean's states, which also behaves as Entity /// That is, hangable /// [ConstantField] public static readonly Boolean True = new Boolean(true); /// - /// One of the Boolean's state, which also behaves as Entity + /// One of the Boolean's states, which also behaves as Entity /// That is, hangable /// [ConstantField] public static readonly Boolean False = new Boolean(false); diff --git a/Sources/AngouriMath/Functions/Continuous/Differentiation.cs b/Sources/AngouriMath/Functions/Continuous/Differentiation.cs index 299f4c25f..55ceac71d 100644 --- a/Sources/AngouriMath/Functions/Continuous/Differentiation.cs +++ b/Sources/AngouriMath/Functions/Continuous/Differentiation.cs @@ -81,11 +81,15 @@ partial record Matrix } /// Derives over times - public Entity Differentiate(Variable x, EInteger power) + public Entity Differentiate(Variable x, int power) { var ent = this; - for (var _ = 0; _ < power; _++) - ent = ent.InnerDifferentiate(x); + if (power < 0) + for (var _ = 0; _ < -power; _++) + ent = ent.Integrate(x); + else + for (var _ = 0; _ < power; _++) + ent = ent.InnerDifferentiate(x); return ent; } @@ -276,7 +280,11 @@ partial record Integralf /// protected override Entity InnerDifferentiate(Variable variable) => Var == variable - ? this with { Iterations = Iterations - 1 } + ? Range is var (from, to) + // https://math.stackexchange.com/a/139191/627798 + ? to.InnerDifferentiate(variable) * Expression.Substitute(variable, to) - + from.InnerDifferentiate(variable) * Expression.Substitute(variable, from) + : Expression : MathS.Derivative(this, variable, 1); } #pragma warning restore IDE0054 // Use compound assignment @@ -303,10 +311,9 @@ protected override Entity InnerDifferentiate(Variable variable) partial record Absf { - // TODO: derivative of the absolute function? /// protected override Entity InnerDifferentiate(Variable variable) - => MathS.Signum(Argument) * Argument.InnerDifferentiate(variable); + => MathS.Signum(Argument).Provided(!Argument.Equalizes(Integer.Zero)) * Argument.InnerDifferentiate(variable); } partial record Providedf diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 05f599ee7..dc3ca89ec 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -4,46 +4,51 @@ // Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. // Website: https://am.angouri.org. // +using HonkSharp.Fluency; +using static AngouriMath.Entity; +using System.Linq; +using System.Collections.Generic; namespace AngouriMath.Functions.Algebra { internal static class IndefiniteIntegralSolver { - internal static Entity? SolveBySplittingSum(Entity expr, Entity.Variable x) + internal static Entity? SolveBySplittingSum(Entity expr, Entity.Variable x, bool integrateByParts) { var splitted = TreeAnalyzer.GatherLinearChildrenOverSumAndExpand(expr, e => e.ContainsNode(x)); - if (splitted is null || splitted.Count < 2) return null; // nothing to do, let other solvers do the work - splitted[0] = Integration.ComputeIndefiniteIntegral(splitted[0], x); // base case for aggregate - var result = splitted.Aggregate((e1, e2) => e1 + Integration.ComputeIndefiniteIntegral(e2, x)); - return result; + if (splitted is null || splitted.Count < 2) return null; // nothing to do, let other solvers do the work + return splitted.Select(e => Integration.ComputeIndefiniteIntegral(e, x, integrateByParts)).Aggregate((e1, e2) => (e1, e2) switch { + (null, _) or (_, null) => null, + (var int1, var int2) => int1 + int2 + }); } internal static Entity? SolveAsPolynomialTerm(Entity expr, Entity.Variable x) => expr switch { - Entity.Mulf(var m1, var m2) => - !m1.ContainsNode(x) ? - m1 * Integration.ComputeIndefiniteIntegral(m2, x) : + Entity.Mulf(var m1, var m2) => + !m1.ContainsNode(x) ? + Integration.ComputeIndefiniteIntegral(m2, x)?.Pipe(i => m1 * i) : !m2.ContainsNode(x) ? - m2 * Integration.ComputeIndefiniteIntegral(m1, x) : + Integration.ComputeIndefiniteIntegral(m1, x)?.Pipe(i => m2 * i) : null, Entity.Divf(var div, var over) => !div.ContainsNode(x) ? over is Entity.Powf(var @base, var power) ? - div * Integration.ComputeIndefiniteIntegral(MathS.Pow(@base, -power), x) : - div * Integration.ComputeIndefiniteIntegral(MathS.Pow(over, -1), x) : + Integration.ComputeIndefiniteIntegral(MathS.Pow(@base, -power), x)?.Pipe(i => div * i) : + Integration.ComputeIndefiniteIntegral(MathS.Pow(over, -1), x)?.Pipe(i => div * i) : !over.ContainsNode(x) ? - Integration.ComputeIndefiniteIntegral(div, x) / over : + Integration.ComputeIndefiniteIntegral(div, x)?.Pipe(i => i / over) : null, Entity.Powf(var @base, var power) => !power.ContainsNode(x) && @base == x ? power == -1 ? - MathS.Ln(@base) : // TODO: here should be ln(abs(x)) but for now I left it as is - MathS.Pow(x, power + 1) / (power + 1) : + MathS.Ln(MathS.Abs(@base)) : + MathS.Pow(x, power + 1) / (power + 1) : null, - Entity.Variable(var v) => + Entity.Variable v => v == x ? MathS.Pow(x, 2) / 2 : v * x, _ => null @@ -51,30 +56,61 @@ over is Entity.Powf(var @base, var power) ? internal static Entity? SolveIntegratingByParts(Entity expr, Entity.Variable x) { - static Entity? IntegrateByParts(Entity v, Entity u, Entity.Variable x, int currentRecursion = 0) + // Standard integration by parts for polynomial × function + static Entity? IntegrateByPartsPolynomial(Entity polynomialToDifferentiate, Entity toIntegrate, Variable x, int currentRecursion = 0) { - if (v == 0) return 0; + if (polynomialToDifferentiate == 0) return 0; if (currentRecursion == MathS.Settings.MaxExpansionTermCount) return null; - var integral = Integration.ComputeIndefiniteIntegral(u, x); - var differential = v.Differentiate(x); - var result = IntegrateByParts(differential, integral, x, currentRecursion + 1); - return (result is null) ? null : v * integral - result; + var integral = Integration.ComputeIndefiniteIntegral(toIntegrate, x, false); + if (integral is null) return null; + var differential = polynomialToDifferentiate.Differentiate(x); + var result = IntegrateByPartsPolynomial(differential, integral, x, currentRecursion + 1); + return (result is null) ? null : polynomialToDifferentiate * integral - result; + } + + // Generalized integration by parts: tries once with v and u both being integrable + // ∫ v·u dx = v·∫u dx - ∫(v'·∫u dx) dx + // Only attempts if both v and u can be integrated + static Entity? TryIntegrateByPartsOnce(Entity v, Entity u, Variable x) + { + // Try to integrate u + var integralOfU = Integration.ComputeIndefiniteIntegral(u, x, false); + if (integralOfU is null) return null; + + // Differentiate v + var derivativeOfV = v.Differentiate(x).InnerSimplified; + if (derivativeOfV is Providedf(var inner, _)) derivativeOfV = inner; // TODO: signularities ignored but not handled properly + if (derivativeOfV == Integer.Zero) + return v * integralOfU; // If v is constant, we're done + + // Try to integrate the remaining term: v' · ∫u dx + var remaining = (derivativeOfV * integralOfU).Simplify(1); + if (remaining is Providedf(var inner_, _)) remaining = inner_; // TODO: signularities ignored but not handled properly + var remainingIntegral = Integration.ComputeIndefiniteIntegral(remaining, x, false); + if (remainingIntegral is null) return null; + + return v * integralOfU - remainingIntegral; } if (expr is Entity.Mulf(var f, var g)) { - if (MathS.TryPolynomial(f, x, out var fPoly)) - { - return IntegrateByParts(fPoly, g, x); - } - if (MathS.TryPolynomial(g, x, out var gPoly)) - { - return IntegrateByParts(gPoly, f, x); - } - else return null; + // Case 1: One term is polynomial - use recursive polynomial integration by parts + if (MathS.TryPolynomial(f, x, out var fPoly)) return IntegrateByPartsPolynomial(fPoly, g, x); + if (MathS.TryPolynomial(g, x, out var gPoly)) return IntegrateByPartsPolynomial(gPoly, f, x); + + // Case 2: Neither is polynomial - try single-step integration by parts + // This handles cases like ln(abs(x)) × ln(abs(x)) + // Try both orderings: f as v, g as u OR g as v, f as u + if (TryIntegrateByPartsOnce(f, g, x) is { } result1) return result1; + if (TryIntegrateByPartsOnce(g, f, x) is { } result2) return result2; } - else return null; + + // Special case for powers of integrable functions, try integration by parts on base × base + // e.g., ln(abs(x))^2 = ln(abs(x)) × ln(abs(x)) + if (expr is Powf(var @base, Integer(2)) && TryIntegrateByPartsOnce(@base, @base, x) is { } result) return result; + + return null; } internal static Entity? SolveLogarithmic(Entity expr, Entity.Variable x) => expr switch @@ -98,5 +134,73 @@ arg is Entity.Powf(var y, var pow) ? // log(b, y^p) = ln(y^p) / ln(b) = ln(p) / _ => null }; + + /// + /// Attempts to solve an integral using u-substitution. + /// Looks for patterns where f(g(x)) * g'(x) can be integrated as F(g(x)). + /// + internal static Entity? SolveBySubstitution(Entity expr, Entity.Variable x) + { + // Try to find a suitable substitution u = g(x) + // We need to identify a composite function and check if du/dx appears in the integrand + var candidates = FindSubstitutionCandidates(expr, x); + foreach (var u in candidates) + { + var duDx = u.Differentiate(x).InnerSimplified; + + // Try to express expr as h(u) * du/dx + // If successful, integral becomes ∫h(u)du + var uSub = Variable.CreateUnique(expr, "u_sub"); + + // Try to divide expr by duDx and check if result is independent of x + // Replace all occurrences of u's expression with a temporary variable + var integrandInU = (expr / duDx).Substitute(u, uSub).Simplify(1); + if (integrandInU is Providedf(var innerExpr, _)) integrandInU = innerExpr; // TODO: singularities ignored but not handled properly + + // If the result doesn't contain x anymore, we found a valid substitution + // and we can integrate with respect to u (treating u as a variable) + if (!integrandInU.ContainsNode(x) && Integration.ComputeIndefiniteIntegral(integrandInU, uSub) is { } resultInU) + // Substitute back: replace u with g(x) + return resultInU.Substitute(uSub, u); + } + + return null; + } + + /// + /// Finds potential substitution candidates u = g(x) from the expression. + /// For example, common patterns to try: + /// 1. f(ax + b) * a -> u = ax + b + /// 2. f(x^n) * x^(n-1) -> u = x^n + /// 3. f(g(x)) * g'(x) -> u = g(x) + /// + private static IEnumerable FindSubstitutionCandidates(Entity expr, Entity.Variable x) + { + var candidates = new List(); + foreach (var node in expr.Nodes) // Look for composite functions (functions of functions) + switch (node) + { + case TrigonometricFunction: + candidates.Add(node); // Trigonometric function itself (for cases like sin(x)*cos(x)) + if (node.DirectChildren[0] != x && node.DirectChildren[0].ContainsNode(x)) + candidates.Add(node.DirectChildren[0]); // Trigonometric functions with non-trivial arguments + break; + case Powf(var @base, var exp): + if (@base == x) candidates.Add(node); // Power expressions x^n + // Exponential with non-trivial argument + if (@base != x && @base.ContainsNode(x)) candidates.Add(@base); + if (exp != x && exp.ContainsNode(x)) candidates.Add(exp); + break; + case Logf(_, var antilog): + candidates.Add(node); // Logarithm itself (for cases like 1/(x*ln(x))) + if (antilog != x && antilog.ContainsNode(x)) candidates.Add(antilog); // Also add the argument if it's not just x + break; + case Sumf(var aug, var add): + if (aug.ContainsNode(x) || add.ContainsNode(x)) candidates.Add(node); // Linear expressions ax + b + break; + } + // Sort by complexity - try simpler substitutions first + return candidates.OrderBy(c => c.Complexity).Distinct(); + } } -} +} \ No newline at end of file diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs index 28163f99a..dbc38bf13 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs @@ -25,15 +25,15 @@ internal static class IntegralPatterns Entity.Cosecantf(var arg) when TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) => - MathS.Ln(MathS.Tan(0.5 * arg)) / a, + MathS.Ln(MathS.Abs(MathS.Tan(0.5 * arg))) / a, Entity.Tanf(var arg) when TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) => - -MathS.Ln(MathS.Cos(arg)) / a, + -MathS.Ln(MathS.Abs(MathS.Cos(arg))) / a, Entity.Cotanf(var arg) when TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) => - MathS.Ln(MathS.Sin(arg)) / a, + MathS.Ln(MathS.Abs(MathS.Sin(arg))) / a, Entity.Logf(var @base, var arg) when !@base.ContainsNode(x) && TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out var b) => @@ -43,7 +43,60 @@ internal static class IntegralPatterns !@base.ContainsNode(x) && TreeAnalyzer.TryGetPolyLinear(power, x, out var a, out _) => MathS.Pow(@base, power) / (a * MathS.Ln(@base)), + Entity.Absf(var arg) when + TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) => // ∫ |ax + b| dx = sgn(ax + b) * (ax + b)^2 / (2a) + MathS.Signum(arg) * MathS.Pow(arg, 2) / (2 * a), + + Entity.Signumf(var arg) when + TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) => // ∫ sgn(ax + b) dx = |ax + b| / a + MathS.Abs(arg) / a, + + // ∫ ln|ax + b| dx = ((ax + b)/a) * (ln|ax + b| - 1) + Entity.Logf(var @base, Entity.Absf(var arg)) when + @base == MathS.e && TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) => + (arg / a) * (MathS.Ln(MathS.Abs(arg)) - 1), + + Entity.Divf(var numerator, var denominator) when + !numerator.ContainsNode(x) + && TreeAnalyzer.TryGetPolyQuadratic(denominator, x, out var a, out var b, out var c) // ∫ k/(ax^2 + bx + c) dx + => IntegrateRationalQuadratic(numerator, a, b, c, x), + _ => null }; + + private static Entity IntegrateRationalQuadratic(Entity numerator, Entity a, Entity b, Entity c, Entity.Variable x) + { + // The formula depends on whether it's linear (a = 0) or quadratic (a ≠ 0) + // Case 0: a = 0 (linear, not quadratic) + // ∫ k/(bx + c) dx = (k/b) * ln|bx + c| + var linearCase = numerator * MathS.Ln(MathS.Abs(b * x + c)) / b; + + // For true quadratics (a ≠ 0), discriminant Δ = 4ac - b^2 determines the form + var discriminant = 4 * a * c - b * b; + + // Case 1: Δ > 0 (no real roots, use arctan) + // Result: (2k/√Δ) * arctan((2ax + b)/√Δ) + var sqrtDiscriminant = MathS.Sqrt(discriminant); + var twoAxPlusB = 2 * a * x + b; + var arctanCase = 2 * numerator * MathS.Arctan(twoAxPlusB / sqrtDiscriminant) / sqrtDiscriminant; + + // Case 2: Δ = 0 (perfect square, one repeated root) + // ax^2 + bx + c = a(x + b/(2a))^2 + // Result: -2k/(2ax + b) + var perfectSquareCase = -2 * numerator / twoAxPlusB; + + // Case 3: Δ < 0 (two distinct real roots, use logarithm) + // Result: (k/√(-Δ)) * ln|(2ax + b - √(-Δ))/(2ax + b + √(-Δ))| + var sqrtNegDiscriminant = MathS.Sqrt(-discriminant); + var lnCase = numerator * MathS.Ln(MathS.Abs((twoAxPlusB - sqrtNegDiscriminant) / (twoAxPlusB + sqrtNegDiscriminant))) / sqrtNegDiscriminant; + + // Return as piecewise based on a and discriminant + return MathS.Piecewise([ + new Entity.Providedf(linearCase, a.Equalizes(0)), + new Entity.Providedf(arctanCase, discriminant > 0), + new Entity.Providedf(perfectSquareCase, discriminant.Equalizes(0)), + new Entity.Providedf(lnCase, discriminant < 0) + ]).InnerSimplified; + } } } diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index e2b03dae6..f36ecd8e8 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -14,17 +14,31 @@ namespace AngouriMath partial record Entity { /// - /// Integrates the given expression over the `x` variable, if can. + /// Integrates indefinitely the given expression over the `x` variable, if can. /// May return an unresolved node. /// /// Over which variable to integrate /// - /// An integrated expression. It might remain the same, - /// it might have no integrals, and it might be transformed so that - /// only a few nodes have unresolved integrals. + /// An integrated expression. It might remain the same or be transformed into nodes with no integrals. /// - public Entity Integrate(Variable x) - => Integration.ComputeIndefiniteIntegral(this, x).InnerSimplified; + public Entity Integrate(Variable x) => + Integration.ComputeIndefiniteIntegral(InnerSimplified, x) is { } antiderivative + ? antiderivative + (antiderivative.VarsAndConsts.Contains("C") ? Variable.CreateUnique(antiderivative, "C") : "C") + : new Integralf(this, x, null); + /// + /// Integrates definitely the given expression over the `x` variable, if can. + /// May return an unresolved node. + /// + /// Over which variable to integrate + /// The lower bound for integrating + /// The upper bound for integrating + /// + /// An integrated expression. It might remain the same or be transformed into nodes with no integrals. + /// + public Entity Integrate(Variable x, Entity from, Entity to) => + Integration.ComputeIndefiniteIntegral(InnerSimplified, x)?.InnerSimplified is { } antiderivative + ? antiderivative.Substitute(x, to) - antiderivative.Substitute(x, from) + : new Integralf(this, x, (from, to)); } } @@ -32,47 +46,40 @@ namespace AngouriMath.Functions.Algebra { internal static partial class Integration { - internal static Entity ComputeIndefiniteIntegral(Entity expr, Entity.Variable x) + /// Does not add the constant of integration because this is called recursively. + internal static Entity? ComputeIndefiniteIntegral(Entity expr, Entity.Variable x, bool integrateByParts = true) { if (!expr.ContainsNode(x)) return expr * x; // base case, handle here - - Entity? answer; - - answer = IntegralPatterns.TryStandardIntegrals(expr, x); - if (answer is { }) return answer; - - answer = IndefiniteIntegralSolver.SolveBySplittingSum(expr, x); - if (answer is { }) return answer; - - answer = IndefiniteIntegralSolver.SolveAsPolynomialTerm(expr, x); - if (answer is { }) return answer; - - answer = IndefiniteIntegralSolver.SolveIntegratingByParts(expr, x); - if (answer is { }) return answer; - - answer = IndefiniteIntegralSolver.SolveLogarithmic(expr, x); - if (answer is { }) return answer; - - return new Entity.Integralf(expr, x, 1); // return as integral if nothing can be done with expression + if ((IntegralPatterns.TryStandardIntegrals(expr, x)) is { } answer) return answer; + if ((answer = IndefiniteIntegralSolver.SolveAsPolynomialTerm(expr, x)) is { }) return answer; + if ((answer = IndefiniteIntegralSolver.SolveLogarithmic(expr, x)) is { }) return answer; + if ((answer = IndefiniteIntegralSolver.SolveBySubstitution(expr, x)) is { }) return answer; + if (integrateByParts && (answer = IndefiniteIntegralSolver.SolveIntegratingByParts(expr, x)) is { }) return answer; + // this may expand to too many terms + if ((answer = IndefiniteIntegralSolver.SolveBySplittingSum(expr, x, integrateByParts)) is { }) return answer; + return null; } - - - /// - /// Numerical definite integration + /// Returns the approximate numeric value of a definite integral of a function. Only works for one-variable functions. + /// Accuracy is limited to the number specified (default is 100). /// See more at /// - internal static Complex Integrate(Entity func, Entity.Variable x, (EDecimal Re, EDecimal Im) from, (EDecimal Re, EDecimal Im) to, int stepCount) + /// Expression to integrate + /// Variable to integrate over + /// The complex lower bound for integrating + /// The complex upper bound for integrating + /// Accuracy (for now, number of iterations) + internal static Complex IntegrateNumerically(Entity expr, Entity.Variable x, Complex from, Complex to, int accuracy = 100) { System.Numerics.Complex res = 0; - var cfunc = func.Compile(x); - for (int i = 0; i <= stepCount; i++) + var cfunc = expr.Compile(x); + for (int i = 0; i <= accuracy; i++) { - var share = ((EDecimal)i) / stepCount; - var tmp = Complex.Create(from.Re * share + to.Re * (1 - share), from.Im * share + to.Im * (1 - share)); + var share = ((EDecimal)i) / accuracy; + var tmp = Complex.Create(from.RealPart.EDecimal * share + to.RealPart.EDecimal * (1 - share), from.ImaginaryPart.EDecimal * share + to.ImaginaryPart.EDecimal * (1 - share)); res += cfunc.Substitute(tmp.ToNumerics()); } - return res.ToNumber() / (stepCount + 1) * (Complex.Create(to.Re, to.Im) - Complex.Create(from.Re, from.Im)); + return res.ToNumber() / (accuracy + 1) * (to - from); } } } diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs index 67cfa1444..cd892b480 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs @@ -265,7 +265,11 @@ partial record Integralf ComputeLimitImpl(this, x, dist, side) is { } lim ? lim : ComputeLimitImpl(New( Expression.ComputeLimitDivideEtImpera(x, dist, side) is { IsFinite: true } lim1 ? lim1 : Expression, - Var.ComputeLimitDivideEtImpera(x, dist, side) is { IsFinite: true } lim2 ? lim2 : Var), + Var.ComputeLimitDivideEtImpera(x, dist, side) is { IsFinite: true } lim2 ? lim2 : Var, + Range is var (from, to) + ? (from.ComputeLimitDivideEtImpera(x, dist, side) is { IsFinite: true } lim3 ? lim3 : from, + to.ComputeLimitDivideEtImpera(x, dist, side) is { IsFinite: true } lim4 ? lim4 : to) + : null), x, dist, side); } diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs index 6b8cc52b8..e6c41c98d 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs @@ -6,6 +6,7 @@ // using AngouriMath.Core.Exceptions; +using AngouriMath.Functions.Algebra; namespace AngouriMath { @@ -209,7 +210,7 @@ partial record Derivativef { private protected override IEnumerable InvertNode(Entity value, Entity x) => Expression.ContainsNode(x) - ? Expression.Invert(MathS.Integral(value, Var, Iterations), x) + ? Expression.Invert(MathS.Derivative(value, Var, -Iterations), x) : Enumerable.Empty(); } @@ -217,7 +218,11 @@ partial record Integralf { private protected override IEnumerable InvertNode(Entity value, Entity x) => Expression.ContainsNode(x) - ? Expression.Invert(MathS.Derivative(value, Var, Iterations), x) + ? Range is { } + ? InnerSimplified is var integrated && integrated != this + ? integrated.Invert(value, x) + : Enumerable.Empty() + : Expression.Invert(MathS.Derivative(value, Var), x) : Enumerable.Empty(); } diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs index 10071ad4f..2c79d42a1 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs @@ -214,6 +214,8 @@ protected override Entity InnerSimplify(bool isExact) { Real n => n.EDecimal.Sign, Complex n when !isExact => Number.Signum(n), + Absf({ DomainCondition: var condition }) => Integer.One.Provided(condition), + Signumf signum => signum, _ => null }, (@this, a) => ((Signumf)@this).New(a), isExact); @@ -232,6 +234,8 @@ protected override Entity InnerSimplify(bool isExact) { Matrix m when m.IsVector => Sumf.Sum(m.Select(c => c.Pow(2))).Pow(0.5).InnerSimplified, Complex n when !isExact => Number.Abs(n), + Absf abs => abs, + Signumf({ DomainCondition: var condition }) => Integer.One.Provided(condition), _ => null }, (@this, a) => ((Absf)@this).New(a), isExact); diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Calculus.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Calculus.Classes.cs index 8cbf8adbd..10eaea842 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Calculus.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Calculus.Classes.cs @@ -16,15 +16,11 @@ public partial record Derivativef // The derivative operator is always defined symbolically, even though // the resulting expression may be undefined at certain points. private protected override Entity IntrinsicCondition => Boolean.True; - /// protected override Entity InnerSimplify(bool isExact) => ExpandOnTwoAndTArguments(Expression, Var, Iterations, (a, b, c) => (a, b, c) switch { - (var expr, _, 0) => expr, - (_, _, int.MinValue) => null, - (_, _, < 0) => new Integralf(a, b, -c).InnerSimplified(isExact), // TODO: should we call InnerSimplified here? (var expr, Variable var, int asInt) when expr.Differentiate(var, asInt) is var res and not Derivativef @@ -34,7 +30,7 @@ when expr.Differentiate(var, asInt) is var res and not Derivativef (var expr, Entity otherExpr, int asInt) when Variable.CreateTemp(otherExpr.Vars) is var tempVar && expr.Substitute(otherExpr, tempVar) is var tempSubstituted - && tempSubstituted.Differentiate(tempVar) is var res and not Derivativef + && tempSubstituted.Differentiate(tempVar, asInt) is var res and not Derivativef => res.Substitute(tempVar, otherExpr).InnerSimplified(isExact), _ => null }, @@ -46,33 +42,26 @@ public partial record Integralf // The integral operator is always defined symbolically, even though // the antiderivative may not exist in closed form or may be undefined at certain points. private protected override Entity IntrinsicCondition => Boolean.True; - - private Entity SequentialIntegrating(Entity expr, Variable var, int iterations) - { - if (iterations < 0) - return this; - var changed = expr; - for (int i = 0; i < iterations; i++) - changed = Integration.ComputeIndefiniteIntegral(changed, var); - return changed; - } + private static Entity? ConditionallySimplified(Entity e, bool isExact) => e is Integralf ? null : e.InnerSimplified(isExact); /// protected override Entity InnerSimplify(bool isExact) => - ExpandOnTwoAndTArguments(Expression, Var, Iterations, + ExpandOnTwoAndTArguments(Expression, Var, Range, (a, b, c) => (a, b, c) switch { - (var expr, _, 0) => expr, - (_, _, int.MinValue) => null, - (_, _, < 0 and not int.MinValue) => new Derivativef(a, b, -c).InnerSimplified(isExact), - // TODO: should we apply InnerSimplified? - (var expr, Variable var, int asInt) - when SequentialIntegrating(expr, var, asInt) is var res and not Integralf - && !res.Nodes.Any(n => n is Integralf) - => res.InnerSimplified(isExact), + (var expr, Variable var, var (from, to)) => ConditionallySimplified(expr.Integrate(var, from, to), isExact), + (var expr, var otherExpr, var (from, to)) + when Variable.CreateTemp(otherExpr.Vars) is var tempVar + && expr.Substitute(otherExpr, tempVar) is var tempSubstituted + && tempSubstituted.Integrate(tempVar, from, to) is var res => ConditionallySimplified(res.Substitute(tempVar, otherExpr), isExact), + (var expr, Variable var, null) => ConditionallySimplified(expr.Integrate(var), isExact), + (var expr, var otherExpr, null) + when Variable.CreateTemp(otherExpr.Vars) is var tempVar + && expr.Substitute(otherExpr, tempVar) is var tempSubstituted + && tempSubstituted.Integrate(tempVar) is var res => ConditionallySimplified(res.Substitute(tempVar, otherExpr), isExact), _ => null }, - (@this, a, b, _) => ((Integralf)@this).New(a, b), isExact); + (@this, a, b, c) => ((Integralf)@this).New(a, b, c), isExact); } diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Trigonometry.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Trigonometry.Classes.cs index fd293d59d..4c150c0f0 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Trigonometry.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Trigonometry.Classes.cs @@ -224,6 +224,8 @@ protected override Entity InnerSimplify(bool isExact) => a => a switch { Complex n when !isExact => Number.Arctan(n), + Real r when r.EDecimal.IsPositiveInfinity() => MathS.pi / 2, + Real r when r.EDecimal.IsNegativeInfinity() => -MathS.pi / 2, _ => null }, (@this, a) => ((Arctanf)@this).New(a), isExact); diff --git a/Sources/AngouriMath/Functions/Output/Latex/Latex.Calculus.Classes.cs b/Sources/AngouriMath/Functions/Output/Latex/Latex.Calculus.Classes.cs index 788bd18fa..3091653ef 100644 --- a/Sources/AngouriMath/Functions/Output/Latex/Latex.Calculus.Classes.cs +++ b/Sources/AngouriMath/Functions/Output/Latex/Latex.Calculus.Classes.cs @@ -18,16 +18,30 @@ partial record CalculusOperator } public partial record Derivativef { - internal static string LatexiseDerivative(Entity expression, Entity var, int iterations) + /// + public override string Latexise() { - var powerIfNeeded = iterations == 1 ? "" : "^{" + iterations + "}"; + if (Iterations < 0) + { + var sb = new StringBuilder(); + for (int i = 0; i < -Iterations; i++) + sb.Append(@"\int"); + sb.Append(' ').Append(Expression.Latexise(Expression.LatexPriority < Priority.LatexCalculusOperation)); + + for (int i = 0; i < -Iterations; i++) + { + sb.Append(@"\,"); + sb.Append(@"\mathrm{d}"); + sb.Append(Var.Latexise(Var is not Variable { IsLatexUprightFormatted: false })); + } + return sb.ToString(); + } + var powerIfNeeded = Iterations == 1 ? "" : "^{" + Iterations + "}"; // NOTE: \mathrm{d} is used for upright 'd' following ISO 80000-2 standard. // The differential operator should be upright (roman) to distinguish it from variables, similar to sin, cos, log, etc. - return $$"""\frac{\mathrm{d}{{powerIfNeeded}}}{\mathrm{d}{{var.Latexise(var is not Variable { IsLatexUprightFormatted: false }) - }}{{powerIfNeeded}}}{{expression.Latexise(expression.LatexPriority < Priority.LatexCalculusOperation)}}"""; + return $$"""\frac{\mathrm{d}{{powerIfNeeded}}}{\mathrm{d}{{Var.Latexise(Var is not Variable { IsLatexUprightFormatted: false }) + }}{{powerIfNeeded}}}{{Expression.Latexise(Expression.LatexPriority < Priority.LatexCalculusOperation)}}"""; } - /// - public override string Latexise() => LatexiseDerivative(Expression, Var, Iterations); } public partial record Integralf @@ -35,15 +49,8 @@ public partial record Integralf /// public override string Latexise() { - // Unlike derivatives, integrals do not have "power" that would be equal to sequentially applying integration to a function. - // So for non-positive iterations, we just latexise the derivative. Since we treat integrals as functions for parenthesization, - // we still need to latexize the 0th derivative explicitly and not just return the inner expression's latex form. - if (Iterations <= 0) - return Derivativef.LatexiseDerivative(Expression, Var, -Iterations); - - var sb = new StringBuilder(); - for (int i = 0; i < Iterations; i++) - sb.Append(@"\int"); + var sb = new StringBuilder(@"\int"); + if (Range is var (from, to)) sb.Append('_').Append('{').Append(from.Latexise()).Append('}').Append('^').Append('{').Append(to.Latexise()).Append('}'); sb.Append(' ').Append(Expression.Latexise(Expression.LatexPriority < Priority.LatexCalculusOperation)); // NOTE: \mathrm{d} is used for upright 'd' following ISO 80000-2 standard. @@ -52,12 +59,9 @@ public override string Latexise() // While derivatives use \mathrm{d}^n / \mathrm{d}x^n, power notation for integrals (\mathrm{d}^2 x) would be confusing // as the number of \mathrm{d} is usually expected to match the number of \int. // Thin spaces (\,) are added between differentials following standard practice. - for (int i = 0; i < Iterations; i++) - { - sb.Append(@"\,");// Leading space before first differential and between differentials - sb.Append(@"\mathrm{d}"); - sb.Append(Var.Latexise(Var is not Variable { IsLatexUprightFormatted: false })); - } + sb.Append(@"\,"); // Leading space before first differential and between differentials + sb.Append(@"\mathrm{d}"); + sb.Append(Var.Latexise(Var is not Variable { IsLatexUprightFormatted: false })); return sb.ToString(); } } diff --git a/Sources/AngouriMath/Functions/Output/ToString/ToString.Calculus.Classes.cs b/Sources/AngouriMath/Functions/Output/ToString/ToString.Calculus.Classes.cs index 9e9f2879e..3cf055fce 100644 --- a/Sources/AngouriMath/Functions/Output/ToString/ToString.Calculus.Classes.cs +++ b/Sources/AngouriMath/Functions/Output/ToString/ToString.Calculus.Classes.cs @@ -28,13 +28,10 @@ public override string Stringize() public partial record Integralf { /// - public override string Stringize() - { - if (Iterations == 1) - return $"integral({Expression.Stringize()}, {Var.Stringize()})"; - else - return $"integral({Expression.Stringize()}, {Var.Stringize()}, {Iterations})"; - } + public override string Stringize() => + Range is var (from, to) + ? $"integral({Expression.Stringize()}, {Var.Stringize()}, {from.Stringize()}, {to.Stringize()})" + : $"integral({Expression.Stringize()}, {Var.Stringize()})"; /// public override string ToString() => Stringize(); } diff --git a/Sources/AngouriMath/Functions/Output/ToSympy/ToSympy.Calculus.Classes.cs b/Sources/AngouriMath/Functions/Output/ToSympy/ToSympy.Calculus.Classes.cs index d54ea5b31..ecee8e933 100644 --- a/Sources/AngouriMath/Functions/Output/ToSympy/ToSympy.Calculus.Classes.cs +++ b/Sources/AngouriMath/Functions/Output/ToSympy/ToSympy.Calculus.Classes.cs @@ -18,9 +18,7 @@ public partial record Derivativef public partial record Integralf { - // TODO: The 3rd parameter of sympy.integrate is not interpreted as iterations, unlike sympy.diff - // which allows both sympy.diff(expr, var, iterations) and sympy.diff(expr, var1, var2, var3...) - internal override string ToSymPy() => $"sympy.integrate({Expression.ToSymPy()}, {Var.ToSymPy()}, {Iterations})"; + internal override string ToSymPy() => $"sympy.integrate({Expression.ToSymPy()}, {(Range is var (from, to) ? $"({Var.ToSymPy()}, {from.ToSymPy()}, {to.ToSymPy()})" : Var.ToSymPy())})"; } public partial record Limitf diff --git a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Common.cs b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Common.cs index c012bb868..554f71f43 100644 --- a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Common.cs +++ b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Common.cs @@ -166,15 +166,15 @@ internal static partial class Patterns when (var1, any1) == (var1a, any1a) => new Powf(var1, 2) - new Powf(any1, 2), // a / a - Divf(var any1, var any1a) when any1 == any1a => new Providedf(1, !any1.Equalizes(0)), + Divf(var any1, var any1a) when any1 == any1a => Integer.One.Provided(!any1.Equalizes(Integer.Zero)), // (a * c) / c - Divf(Mulf(var any1, var any2), var any2a) when any2 == any2a => any1, - Divf(Mulf(var any2, var any1), var any2a) when any2 == any2a => any1, - Divf(Mulf(var any1, var any2), Mulf(var any2a, var any3)) when any2 == any2a => any1 / any3, - Divf(Mulf(var any1, var any2), Mulf(var any3, var any2a)) when any2 == any2a => any1 / any3, - Divf(Mulf(var any2, var any1), Mulf(var any2a, var any3)) when any2 == any2a => any1 / any3, - Divf(Mulf(var any2, var any1), Mulf(var any3, var any2a)) when any2 == any2a => any1 / any3, + Divf(Mulf(var any1, var any2), var any2a) when any2 == any2a => any1.Provided(!any2.Equalizes(Integer.Zero)), + Divf(Mulf(var any2, var any1), var any2a) when any2 == any2a => any1.Provided(!any2.Equalizes(Integer.Zero)), + Divf(Mulf(var any1, var any2), Mulf(var any2a, var any3)) when any2 == any2a => (any1 / any3).Provided(!any2.Equalizes(Integer.Zero)), + Divf(Mulf(var any1, var any2), Mulf(var any3, var any2a)) when any2 == any2a => (any1 / any3).Provided(!any2.Equalizes(Integer.Zero)), + Divf(Mulf(var any2, var any1), Mulf(var any2a, var any3)) when any2 == any2a => (any1 / any3).Provided(!any2.Equalizes(Integer.Zero)), + Divf(Mulf(var any2, var any1), Mulf(var any3, var any2a)) when any2 == any2a => (any1 / any3).Provided(!any2.Equalizes(Integer.Zero)), // ({1} - {2}) / ({2} - {1}) Divf(Minusf(var any1, var any2), Minusf(var any2a, var any1a) denom) when (any1, any2) == (any1a, any2a) => new Providedf(-1, !denom.Equalizes(0)), @@ -213,13 +213,9 @@ internal static partial class Patterns Minusf(Minusf(var any2, var any1), var any1a) when any1 == any1a => any2 - 2 * any1, Minusf(Minusf(var any1, var any2), var any1a) when any1 == any1a => -any2, - Signumf(Signumf(var any1)) => new Signumf(any1), - Absf(Absf(var any1)) => new Absf(any1), - Absf(Signumf(_)) => 1, - Signumf(Absf(_)) => 1, - Mulf(Absf(var any1), Absf(var any2)) => new Absf(any1 * any2), Divf(Absf(var any1), Absf(var any2)) => new Absf(any1 / any2), + Divf(Mulf(Signumf(var any1), Mulf(var any2, var any1a)), Absf(var any1b)) when any1 == any1a && any1a == any1b => any2.Provided(!any1.Equalizes(Integer.Zero)), Mulf(Signumf(var any1), Absf(var any1a)) when any1 == any1a => any1, Mulf(Absf(var any1a), Signumf(var any1)) when any1 == any1a => any1, diff --git a/Sources/AngouriMath/Functions/Substitute.cs b/Sources/AngouriMath/Functions/Substitute.cs index 0a694894c..5386a6207 100644 --- a/Sources/AngouriMath/Functions/Substitute.cs +++ b/Sources/AngouriMath/Functions/Substitute.cs @@ -372,7 +372,7 @@ public override Entity Substitute(Entity x, Entity value) // integrate(temp_1 ^ 2 + a, temp_1) -> integrate(x ^ 2 + a, temp_1) var postSubs = subs.Substitute(replacement, Var); - return New(postSubs, Var); + return New(postSubs, Var, Range is var (from, to) ? (from.Substitute(x, value), to.Substitute(x, value)) : null); } } diff --git a/Sources/Tests/FSharpWrapperUnitTests/Functions.Order.fs b/Sources/Tests/FSharpWrapperUnitTests/Functions.Order.fs index 169c9ffb8..37bbfea0e 100644 --- a/Sources/Tests/FSharpWrapperUnitTests/Functions.Order.fs +++ b/Sources/Tests/FSharpWrapperUnitTests/Functions.Order.fs @@ -17,6 +17,6 @@ let ``Limit test`` () = Assert.Equal(parsed "limit(x, y, z)", limitNode y z x) [] let ``Differentiate test`` () = Assert.Equal(parsed "2x + 2", derivative x "x2 + 2x") [] -let ``Integrate test`` () = Assert.Equal(parsed "sin(x) + x", integral x "1 + cos(x)") +let ``Integrate test`` () = Assert.Equal(parsed "sin(x) + x + C", integral x "1 + cos(x)") [] let ``Limited test`` () = Assert.Equal(parsed "a", limit x 0 "a x / sin(x)") diff --git a/Sources/Tests/FSharpWrapperUnitTests/ShortcutsTest.fs b/Sources/Tests/FSharpWrapperUnitTests/ShortcutsTest.fs index 060515cf4..88f1ebdf7 100644 --- a/Sources/Tests/FSharpWrapperUnitTests/ShortcutsTest.fs +++ b/Sources/Tests/FSharpWrapperUnitTests/ShortcutsTest.fs @@ -9,7 +9,7 @@ let ``Test d/dx`` () = Assert.Equal(parsed "a + cos(x)", ``d/dx`` "a x + sin(x)") [] let ``Test int [dx]`` () = - Assert.Equal(parsed "sin(x) + a x", ``int [dx]`` "a + cos(x)") + Assert.Equal(parsed "sin(x) + a x + C", ``int [dx]`` "a + cos(x)") [] let ``Test lim x->+oo`` () = Assert.Equal(parsed "6", ``lim x->+oo`` "(6x6 + 3x3 + a x) / (x6 - 4x)") diff --git a/Sources/Tests/UnitTests/Calculus/DerivativeTest.cs b/Sources/Tests/UnitTests/Calculus/DerivativeTest.cs index a22b10f3e..03b8fbf02 100644 --- a/Sources/Tests/UnitTests/Calculus/DerivativeTest.cs +++ b/Sources/Tests/UnitTests/Calculus/DerivativeTest.cs @@ -134,7 +134,7 @@ public void TestAbsDer() { Entity func = "abs(x + 2)"; var derived = func.Differentiate("x"); - Assert.Equal(MathS.Signum("x + 2").Simplify(), derived.Simplify()); + Assert.Equal(MathS.Signum("2 + x").Provided("not 2 + x = 0"), derived.Simplify()); } [Fact] diff --git a/Sources/Tests/UnitTests/Calculus/IntegrationTest.cs b/Sources/Tests/UnitTests/Calculus/IntegrationTest.cs index a78e6cfc4..7a7efb29f 100644 --- a/Sources/Tests/UnitTests/Calculus/IntegrationTest.cs +++ b/Sources/Tests/UnitTests/Calculus/IntegrationTest.cs @@ -15,22 +15,275 @@ public sealed class IntegrationTest { // TODO: add more tests [Theory] - [InlineData("2x", "x2")] - [InlineData("x2", "(1/3) * x3")] - [InlineData("x2 + x", "(1/3) * x3 + (1/2) * x2")] - [InlineData("x2 - x", "1/3 * x ^ 3 - 1/2 * x ^ 2")] - [InlineData("a / x", "ln(x) * a")] - [InlineData("x cos(x)", "cos(x) + sin(x) * x")] - [InlineData("sin(x)cos(x)", "-1/4 cos(2x)")] - [InlineData("ln(x)", "x * (ln(x) - 1)")] - [InlineData("log(a, x)", "x * (ln(x) - 1) / ln(a)")] - [InlineData("e ^ x", "e ^ x")] - [InlineData("a ^ x", "a ^ x / ln(a)")] - [InlineData("sec(a x + b)", "1/2 * ln((1 + sin(a x + b)) / (1 - sin(a x + b))) / a")] - [InlineData("csc(a x + b)", "ln(tan(1/2(a x + b))) / a")] + [InlineData("2x", "x2 + C")] + [InlineData("x2", "(1/3) * x3 + C")] + [InlineData("x2 + x", "(1/3) * x3 + (1/2) * x2 + C")] + [InlineData("x2 - x", "1/3 * x ^ 3 - 1/2 * x ^ 2 + C")] + [InlineData("a / x", "ln(abs(x)) * a + C")] + [InlineData("x cos(x)", "cos(x) + sin(x) * x + C")] + [InlineData("sin(x)cos(x)", "sin(x) ^ 2 / 2 + C")] + [InlineData("ln(x)", "x * (ln(x) - 1) + C")] + [InlineData("log(a, x)", "x * (ln(x) - 1) / ln(a) + C")] + [InlineData("e ^ x", "e ^ x + C")] + [InlineData("a ^ x", "a ^ x / ln(a) + C")] + [InlineData("sec(a x + b)", "1/2 * ln((1 + sin(a x + b)) / (1 - sin(a x + b))) / a + C")] + [InlineData("csc(a x + b)", "ln(abs(tan(1/2(a x + b)))) / a + C")] + [InlineData("C", "C x + C_1")] + [InlineData("C C_1", "C C_1 x + C_2")] + [InlineData("integral(x, x)", "C_1 + C * x + x ^ 3 / 6")] + [InlineData("e^e^x", "integral(e ^ e ^ x, x)")] // don't recurse infinitely public void TestIndefinite(string initial, string expected) { - Assert.Equal(expected.ToEntity().Simplify(), initial.Integrate("x").Simplify()); + Assert.Equal(MathS.Boolean.True, initial.Integrate("x").Equalizes(expected).Simplify()); + } + [Theory] + [InlineData("2x * e ^ (x2)", "e ^ (x2) + C")] + [InlineData("x * e ^ (x2)", "1/2 * e ^ (x2) + C")] + [InlineData("3 * x2 * e ^ (x3)", "e ^ (x3) + C")] + [InlineData("2 * x * e ^ (x ^ 2) + x ^ 2", "e^x^2 + x^3/3 + C")] + public void TestExponentialSubstitution(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory] + [InlineData("sin(x) * cos(x)", "1/2 * sin(x)2 + C")] + [InlineData("2x * sin(x2)", "-cos(x2) + C")] + [InlineData("cos(x) / sin(x)", "ln(abs(sin(x))) + C")] + public void TestTrigonometricSubstitution(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory] + [InlineData("x / (x2 + 1)", "1/2 * ln(abs(x2 + 1)) + C")] + [InlineData("2x / (x2 + 5)", "ln(abs(x2 + 5)) + C")] + [InlineData("x / (1 + x2)", "1/2 * ln(abs(1 + x2)) + C")] + public void TestRationalFunctionSubstitution(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory] + [InlineData("x * (x2 + 1) ^ 3", "C + (x ^ 2 + x ^ 6 + 3/2 * x ^ 4 + x ^ 8 / 4) / 2")] + [InlineData("3 * x2 * (x3 + 2) ^ 2", "C + -5/4 * x ^ 6 + (x ^ 3 + 2) * (x ^ 6 / 2 + 2 * x ^ 3)")] + public void TestPowerSubstitution(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory] + [InlineData("sin(2x + 3)", "-1/2 * cos(2x + 3) + C")] + [InlineData("cos(3x - 1)", "1/3 * sin(3x - 1) + C")] + [InlineData("e ^ (2x)", "1/2 * e ^ (2x) + C")] + public void TestLinearSubstitution(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory] + [InlineData("x / sqrt(x2 + 1)", "sqrt(x2 + 1) + C")] + [InlineData("x * sqrt(x2 + 1)", "1/3 * (x2 + 1) ^ (3/2) + C")] + public void TestSquareRootSubstitution(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory] + [InlineData("x2 * e ^ (x3)", "1/3 * e ^ (x3) + C")] + [InlineData("sin(x) ^ 2 * cos(x)", "1/3 * sin(x) ^ 3 + C")] + public void TestCompositeSubstitution(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory] + [InlineData("1 / (x * ln(x))", "ln(abs(ln(x))) + C")] + public void TestLogarithmicSubstitution(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory] + [InlineData("tan(x)", "-ln(abs(cos(x))) + C")] + [InlineData("sec(x) * tan(x)", "sec(x) + C")] + public void TestAdvancedTrigSubstitution(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory] + [InlineData("abs(x)", "signum(x) * x^2 / 2 + C")] + [InlineData("abs(x + 0)", "signum(x) * x^2 / 2 + C")] + [InlineData("abs(2x)", "signum(2x) * (2x)^2 / 4 + C")] + [InlineData("abs(x + 3)", "signum(x + 3) * (x + 3)^2 / 2 + C")] + [InlineData("abs(3x - 1)", "signum(3x - 1) * (3x - 1)^2 / 6 + C")] + [InlineData("signum(x)", "abs(x) + C")] + [InlineData("signum(2x)", "abs(2x) / 2 + C")] + [InlineData("signum(x + 5)", "abs(x + 5) + C")] + [InlineData("signum(4x - 2)", "abs(4x - 2) / 4 + C")] + public void TestAbsAndSignumIntegration(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory] + [InlineData("ln(abs(x))", "x * (ln(abs(x)) - 1) + C")] // Direct pattern: x * (ln|x| - 1) + [InlineData("ln(abs(2x))", "2 * x / 2 * (ln(abs(2 * x)) - 1) + C")] // Direct pattern with linear arg + [InlineData("ln(abs(x + 3))", "(x + 3) * (ln(abs(x + 3)) - 1) + C")] // Direct pattern with linear arg + [InlineData("ln(abs(3x - 1))", "(3 * x - 1) / 3 * (ln(abs(3 * x - 1)) - 1) + C")] // Direct pattern with linear arg + [InlineData("ln(abs(x + 5))", "(x + 5) * (ln(abs(x + 5)) - 1) + C")] // Direct pattern with linear arg + [InlineData("ln(abs(2x + 4))", "(2 * x + 4) / 2 * (ln(abs(2 * x + 4)) - 1) + C")] // Direct pattern with linear arg + [InlineData("ln(abs(x)) / x", "integral(ln(abs(x)) / x, x)")] // Unsolvable - logarithmic integral Li(x), not expressible in elementary functions + [InlineData("ln(abs(x)) * ln(abs(x))", "ln(abs(x)) * x * (ln(abs(x)) - 1) - (x * (ln(abs(x)) - 1) + -x) + C")] + [InlineData("x * ln(abs(x^2))", "x ^ 2 * (ln(abs(x ^ 2)) - 1) / 2 + C")] // Solvable via u-substitution + [InlineData("ln(abs(sin(x))) * cos(x)", "sin(x) * (ln(abs(sin(x))) - 1) + C")] // Solvable via u-substitution + [InlineData("abs(x) * ln(abs(x))", "integral(abs(x) * ln(abs(x)), x)")] // Unsolvable - requires piecewise handling and integration by parts + [InlineData("signum(x) * ln(abs(x))", "abs(x) * ln(abs(x)) - abs(x) + C")] // Solvable! sgn(x) = x/|x|, reduces to clever substitution + [InlineData("ln(abs(abs(x)))", "x * (ln(abs(x)) - 1) + C")] // Simplifies abs(abs(x)) → abs(x), then integrates + [InlineData("abs(ln(abs(x)))", "integral(abs(ln(abs(x))), x)")] // Unsolvable - nested absolute value with logarithm, no elementary form + public void TestLnAbsIntegration(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + Assert.Equal(expected, result.Stringize()); + } + + [Theory] + [InlineData("x * abs(x^2)", "signum(x^2) * (x^2)^2 / 4 + C")] + [InlineData("2x * abs(x^2 + 1)", "signum(x^2 + 1) * (x^2 + 1)^2 / 2 + C")] + [InlineData("x * signum(x^2)", "abs(x^2) / 2 + C")] + [InlineData("2x * signum(x^2 + 3)", "abs(x^2 + 3) + C")] + [InlineData("cos(x) * abs(sin(x))", "signum(sin(x)) * sin(x)^2 / 2 + C")] + [InlineData("cos(x) * signum(sin(x))", "abs(sin(x)) + C")] + [InlineData("sin(x) * abs(cos(x))", "signum(cos(x)) * cos(x)^2 / (-2) + C")] + [InlineData("sin(x) * signum(cos(x))", "abs(cos(x)) / (-1) + C")] + public void TestAbsAndSignumWithSubstitution(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory] + [InlineData("x * (x ^ 2 + 1) ^ 10", "C + (5 * (x^18 + x^4) + x^22 / 11 + x^2 + x^20 + 42 * (x^10 + x^12) + 15 * (x^16 + x^6) + 30 * (x^14 + x^8)) / 2")] // differs from C + (x^2+1)^11 / 22 by -1/22 + [InlineData("x * (x ^ 2 + 1) ^ n", "1/(2(n+1)) * (x^2 + 1) ^ (n+1) + C")] + public void TestHighPowerSubstitution(string initial, string expected) + { + var result = initial.Integrate("x").Simplify(); + var expectedResult = expected.ToEntity().Simplify(); + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory] + [InlineData("1", "0", "1", "1")] + [InlineData("x", "0", "1", "1/2")] + [InlineData("sin(x)", "-1", "1", "0")] + [InlineData("cos(x)", "0", "pi", "0")] + [InlineData("1/(x^2+1)", "-oo", "+oo", "pi")] + public void TestDefinite(string initial, string from, string to, string expected) + { + Assert.Equal(expected, initial.Integrate("x", from, to).Simplify().Stringize()); + } + + [Theory] // TODO: Some of these results can be further simplified, e.g. (4 + 2 * x) / 2 + [InlineData("1 / (x^2 + 1)", "arctan(x) + C")] // no linear term + [InlineData("1 / (x^2 + 4)", "1/2 * arctan(x/2) + C")] // no linear term + [InlineData("1 / (4*x^2 + 1)", "1/2 * arctan(2*x) + C")] // no linear term + [InlineData("2 / (x^2 + 1)", "2 * arctan(x) + C")] // no linear term + [InlineData("1 / (x^2 - 1)", "1/2 * ln(abs((x - 1) / (x + 1))) + C")] // no linear term + [InlineData("1 / (x^2 - 4)", "ln(abs(1 + (-8) / (4 + 2 * x))) / 4 + C")] // no linear term + [InlineData("1 / (9 - x^2)", "ln(abs(1 + (-12) / (6 + (-2) * x))) / 6 + C")] // no linear term + [InlineData("1 / (x^2 + 2*x + 1)", "-1 / (x + 1) + C")] // perfect square + [InlineData("1 / (x^2 + 4*x + 4)", "(-2) / (2 * x + 4) + C")] // perfect square + [InlineData("2 / (x^2 - 6*x + 9)", "(-4) / (2 * x - 6) + C")] // perfect square + [InlineData("1 / (x^2 + 2*x + 2)", "arctan(x + 1) + C")] // complete the square via arctan + [InlineData("1 / (x^2 + 4*x + 5)", "arctan((4 + 2 * x) / 2) + C")] // complete the square via arctan + [InlineData("1 / (x^2 - 2*x + 2)", "arctan(x - 1) + C")] // complete the square via arctan + [InlineData("3 / (x^2 + 6*x + 10)", "3 * arctan((6 + 2 * x) / 2) + C")] // complete the square via arctan + [InlineData("1 / (x^2 + 2*x - 3)", "ln(abs(1 + (-8) / (6 + 2 * x))) / 4 + C")] // complete the square via log + [InlineData("1 / (x^2 - 2*x - 3)", "1/4 * ln(abs((x - 3) / (x + 1))) + C")] // complete the square via log + [InlineData("2 / (x^2 + 4*x - 5)", "1/3 * ln(abs(1 + (-12) / (10 + 2 * x))) + C")] // complete the square via log + [InlineData("1 / (2*x^2 + 3*x + 1)", "ln(abs(1 + -1/2 / (x + 1))) + C")] // factorable + [InlineData("1 / (3*x^2 + 5*x + 2)", "ln(abs(1 + -1/3 / (x + 1))) + C")] // factorable + public void TestQuadraticDenominator(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Fact] + public void TestLnAbsSquared() + { + // Test that ln(abs(x))^2 can be integrated without stack overflow + // ∫ln²(abs(x)) dx = x·ln²(abs(x)) - 2x·ln(abs(x)) + 2x + C + // = x(ln²(abs(x)) - 2ln(abs(x)) + 2) + C + + var expr = "ln(abs(x)) ^ 2".ToEntity(); + var result = expr.Integrate("x").Simplify(1); + Assert.Equal("C + x * (ln(abs(x)) ^ 2 - ln(abs(x)) - ln(abs(x))) + 2 * x", result.Stringize()); + + // Verify the result by differentiation + var derivative = result.Differentiate("x"); // TODO: Make this simplify to expr with Simplify() + foreach (var point in new[] { -3, 7 }) // TODO: Implement and test for "provided x in R" in result + Assert.Equal(expr.Substitute(x, point).Evaled, derivative.Substitute(x, point).Evaled); + } + + [Theory(Skip = "TODO: integration by parts multiple times")] + [InlineData("ln(abs(x)) ^ 3", "C + x * (ln(abs(x)) ^ 3 - ln(abs(x)) ^ 2 - ln(abs(x)) ^ 2 - ln(abs(x)) ^ 2) + 6 * (x * (ln(abs(x)) - 1) + -x)")] // Triple integration by parts + [InlineData("e^x * sin(x)", "-1/2 * cos(x) * e ^ x + 1/2 * sin(x) * e ^ x + C")] // Classic integration by parts + [InlineData("e^x * cos(x)", "1/2 * cos(x) * e ^ x + 1/2 * sin(x) * e ^ x + C")] // Classic integration by parts + [InlineData("arctan(x)", "x * arctan(x) - 1/2 * ln(abs(x ^ 2 + 1)) + C")] // Integration by parts with 1 * arctan(x) + [InlineData("arcsin(x)", "x * arcsin(x) + sqrt(1 - x ^ 2) + C")] // Integration by parts with 1 * arcsin(x) + [InlineData("arccos(x)", "x * arccos(x) - sqrt(1 - x ^ 2) + C")] // Integration by parts with 1 * arccos(x) + public void TestIntegrationByPartsNonPolynomial(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory(Skip = "TODO: integration by parts multiple times")] + [InlineData("sin(ln(abs(x)))", "x / 2 * (sin(ln(abs(x))) - cos(ln(abs(x)))) + C")] // Integration by parts twice + [InlineData("cos(ln(abs(x)))", "x / 2 * (sin(ln(abs(x))) + cos(ln(abs(x)))) + C")] // Integration by parts twice + [InlineData("ln(abs(x)) * sin(x)", "-ln(abs(x)) * cos(x) + sin(x) + C")] // Integration by parts + [InlineData("ln(abs(x)) * cos(x)", "ln(abs(x)) * sin(x) + cos(x) + C")] // Integration by parts + public void TestIntegrationByPartsMixed(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); + } + + [Theory] + [InlineData("e^x * x^2", "e ^ x * (x ^ 2 - 2 * (x - 1)) + C")] // Polynomial times exponential (should use recursive polynomial IBP) + [InlineData("x^3 * sin(x)", "-cos(x) * x ^ 3 + 6 * cos(x) * x + (-6) * sin(x) + 3 * sin(x) * x ^ 2 + C")] // Polynomial times trig (should use recursive polynomial IBP) + // [InlineData("x * ln(abs(x)) ^ 2", "x ^ 2 / 2 * (ln(abs(x)) ^ 2 - ln(abs(x)) - ln(abs(x))) + x ^ 2 + C")] // TODO: ln(abs(x)) ^ 2 needs integration by parts + public void TestPolynomialIntegrationByParts(string initial, string expected) + { + var result = initial.Integrate("x").InnerSimplified; + var expectedResult = expected.ToEntity().InnerSimplified; + Assert.Equal(MathS.Boolean.True, result.Equalizes(expectedResult).Simplify()); } static readonly Entity.Variable x = nameof(x); @@ -40,7 +293,7 @@ public void Test1() { var expr = x; - Assert.True((MathS.Compute.DefiniteIntegral(expr, x, 0, 1).RealPart - 1.0/2).Abs() < 0.1); + Assert.True((MathS.Compute.DefiniteIntegral(expr, x, 0, 1).RealPart - 1.0 / 2).Abs() < 0.1); } [Fact] public void Test2() diff --git a/Sources/Tests/UnitTests/Common/BuiltinFunctionsAppliedTest.cs b/Sources/Tests/UnitTests/Common/BuiltinFunctionsAppliedTest.cs index d2509d7fd..b0fb7bb18 100644 --- a/Sources/Tests/UnitTests/Common/BuiltinFunctionsAppliedTest.cs +++ b/Sources/Tests/UnitTests/Common/BuiltinFunctionsAppliedTest.cs @@ -130,7 +130,7 @@ public void TestMathSOneArgIntoExpression(string name, string entityMethodName) [Fact] public void TestDerivativeFull() => "derivative".ToEntity().Apply("x ^ 2").Apply("x").InnerSimplified.ShouldBe("2 * x"); [Fact] public void TestDerivativeCurried() => "derivative".ToEntity().Apply("x ^ 2").InnerSimplified.ShouldBe("derivative".ToEntity().Apply("x ^ 2")); - [Fact] public void TestIntegralFull() => "integral".ToEntity().Apply("1").Apply("x").InnerSimplified.ShouldBe("x"); + [Fact] public void TestIntegralFull() => "integral".ToEntity().Apply("1").Apply("x").InnerSimplified.ShouldBe("x + C"); [Fact] public void TestIntegralCurried() => "integral".ToEntity().Apply("x ^ 2").InnerSimplified.ShouldBe("integral".ToEntity().Apply("x ^ 2")); [Fact] public void TestLimitFull() => "limit".ToEntity().Apply("sin(x)").Apply("x").Apply("0").InnerSimplified.ShouldBe(0); [Fact] public void TestLimitCurried1() => "limit".ToEntity().Apply("x").Apply("y").InnerSimplified.ShouldBe("limit".ToEntity().Apply("x", "y")); diff --git a/Sources/Tests/UnitTests/Common/CircleTest.cs b/Sources/Tests/UnitTests/Common/CircleTest.cs index a36a40344..3a93f1d94 100644 --- a/Sources/Tests/UnitTests/Common/CircleTest.cs +++ b/Sources/Tests/UnitTests/Common/CircleTest.cs @@ -104,7 +104,7 @@ public void TestDiscrete(string inputIsOutput) => [InlineData("derivative(y, x)")] [InlineData("integral(y, x)")] [InlineData("derivative(y, x, 2)")] - [InlineData("integral(y, x, 2)")] + [InlineData("integral(y, x, a, b)")] public void TestCalculus(string inputIsOutput) => Assert.Equal(inputIsOutput, inputIsOutput.ToEntity().Stringize()); diff --git a/Sources/Tests/UnitTests/Common/InnerSimplifyTest.cs b/Sources/Tests/UnitTests/Common/InnerSimplifyTest.cs index 7289410f9..2408a93b9 100644 --- a/Sources/Tests/UnitTests/Common/InnerSimplifyTest.cs +++ b/Sources/Tests/UnitTests/Common/InnerSimplifyTest.cs @@ -15,16 +15,28 @@ namespace AngouriMath.Tests.Common { public class InnerSimplifyTest { + [Theory] + [InlineData("ln(abs(x))")] + [InlineData("ln(abs(x)) + 1")] + [InlineData("ln(abs(x)) - 1")] + [InlineData("2 * ln(abs(x))")] + [InlineData("ln(abs(x))^2")] + public void ShouldNotChangeTest(string expr) + { + var expected = expr.ToEntity(); + var actual = expected.InnerSimplified; + Assert.Same(expected, actual); + } [Theory(Skip = "Moved to the 1.2.2 milestone, see issue here https://github.com/asc-community/AngouriMath/issues/263")] [InlineData("3 ^ 100")] [InlineData("(-3) ^ 100")] [InlineData("0.01 ^ 100")] [InlineData("integral((4x^2+5x-4)/((5x-2)(4x^2+2)), x)")] - public void ShouldNotChangeTest(string expr) + public void ShouldNotChangeTestTodo(string expr) { var expected = expr.ToEntity(); var actual = expr.ToEntity().InnerSimplified; - Assert.Equal(expected, actual); + Assert.Same(expected, actual); } [Theory] @@ -67,6 +79,15 @@ public void ShouldNotChangeTest(string expr) [InlineData("a implies false", "not a")] [InlineData("true implies a", "a")] [InlineData("false implies a", "true")] + + [InlineData("abs(abs(x))", "abs(x)")] + [InlineData("signum(signum(x))", "signum(x)")] + [InlineData("signum(abs(x))", "1")] + [InlineData("abs(signum(x))", "1")] + [InlineData("signum(abs(x/x))", "1 provided not x = 0")] + [InlineData("abs(signum(x/x))", "1 provided not x = 0")] + + [InlineData("log(10, x) * log(10, x)", "log(10, x)^2")] public void ShouldChangeTo(string from, string to) { var expected = to.ToEntity().Replace(c => c == "NaN" ? MathS.NaN : c); @@ -141,42 +162,31 @@ public void PiecewiseAndPiecewise(string before, string inBetween) [InlineData("(|[2, 3, 6]|)", "7")] public void TestMatrices(string before, string after) => ShouldChangeTo(before, after); - [Fact] public void IntegralToDerivativeSimplify() => - "integral(apply(f, x), x, -1)".ToEntity().InnerSimplified.ShouldBe("derivative(apply(f, x), x)"); - [Fact] public void IntegralToDerivativeSimplify2() => - "integral(apply(f, x), x, -2)".ToEntity().InnerSimplified.ShouldBe("derivative(apply(f, x), x, 2)"); - [Fact] public void IntegralToDerivativeSimplifyMinValue() => - $"integral(apply(f, x), x, {int.MinValue})".ToEntity().InnerSimplified.ShouldBe($"integral(apply(f, x), x, {int.MinValue})"); - [Fact] public void IntegralToDerivativeEval() => - "integral(apply(f, x) + (sin(3)^2 + cos(3)^2), x, -1)".ToEntity().Evaled.ShouldBe("derivative(apply(f, x), x)"); - [Fact] public void IntegralToDerivativeEval2() => - "integral(apply(f, x), x, -2)".ToEntity().Evaled.ShouldBe("derivative(apply(f, x), x, 2)"); - [Fact] public void IntegralToDerivativeEvalMinValue() => - $"integral(apply(f, x) + (sin(3)^2 + cos(3)^2), x, {int.MinValue})".ToEntity().Evaled.ShouldBe($"integral(apply(f, x) + 1, x, {int.MinValue})"); + [Fact] public void DefiniteIntegralSimplify1() => + "integral(apply(f, x), x, a, b)".ToEntity().InnerSimplified.ShouldBe("integral(apply(f, x), x, a, b)"); + [Fact] public void DefiniteIntegralSimplify2() => + "integral(sin(x), x, a, b)".ToEntity().InnerSimplified.ShouldBe("-cos(b)--cos(a)"); + [Fact] public void DefiniteIntegralEval1() => + "integral(apply(f, x), x, a, b)".ToEntity().Evaled.ShouldBe("integral(apply(f, x), x, a, b)"); + [Fact] public void DefiniteIntegralEval2() => + "integral(sin(x), x, a, b)".ToEntity().Evaled.ShouldBe("-cos(b)--cos(a)"); [Fact] public void DerivativeToIntegralSimplify() => "derivative(apply(f, x), x, -1)".ToEntity().InnerSimplified.ShouldBe("integral(apply(f, x), x)"); [Fact] public void DerivativeToIntegralSimplify2() => - "derivative(apply(f, x), x, -2)".ToEntity().InnerSimplified.ShouldBe("integral(apply(f, x), x, 2)"); - [Fact] public void DerivativeToIntegralSimplifyMinValue() => - $"derivative(apply(f, x), x, {int.MinValue})".ToEntity().InnerSimplified.ShouldBe($"derivative(apply(f, x), x, {int.MinValue})"); + "derivative(apply(f, x), x, -2)".ToEntity().InnerSimplified.ShouldBe("integral(integral(apply(f, x), x), x)"); [Fact] public void DerivativeToIntegralEval() => "derivative(apply(f, x) + (sin(3)^2 + cos(3)^2), x, -1)".ToEntity().Evaled.ShouldBe("integral(apply(f, x) + 1, x)"); - [Fact] public void DerivativeToIntegralEval2() => - "derivative(apply(f, x), x, -2)".ToEntity().Evaled.ShouldBe("integral(apply(f, x), x, 2)"); - [Fact] public void DerivativeToIntegralEvalMinValue() => - $"derivative(apply(f, x) + (sin(3)^2 + cos(3)^2), x, {int.MinValue})".ToEntity().Evaled.ShouldBe($"derivative(apply(f, x) + 1, x, {int.MinValue})"); - [Fact] public void PiecewiseIntegrate1() => "piecewise(2 provided a, 3)".Integrate("x") - .ShouldBe("piecewise(2x provided a, 3x)"); + .ShouldBe("piecewise(2 provided a, 3) * x + C"); [Fact] public void PiecewiseIntegrate2() => "piecewise(2 provided a, 3)".Integrate("d") - .ShouldBe("piecewise(2d provided a, 3d)"); + .ShouldBe("piecewise(2 provided a, 3) * d + C"); [Fact] public void PiecewiseIntegrate3() => "piecewise(x provided a, 1/x)".Integrate("x") - .ShouldBe("piecewise(x ^ 2 / 2 provided a, ln(x))"); + .ShouldBe("integral(piecewise(x provided a, 1/x), x)"); // evaluation requires inner simplification! [Fact] public void PiecewiseDerivative1() => "piecewise(x provided a, 1/x)".Differentiate("x") @@ -198,15 +208,15 @@ [Fact] public void PiecewiseLimit1() => [Fact] public void PiecewiseIntegrate1NodeEvaled() => "integral(piecewise(2 provided a, 3), x)".ToEntity().Evaled - .ShouldBe("piecewise(2x provided a, 3x)"); + .ShouldBe("piecewise(2x + C provided a, 3x + C)"); [Fact] public void PiecewiseIntegrate2NodeEvaled() => "integral(piecewise(2 provided a, 3), d)".ToEntity().Evaled - .ShouldBe("piecewise(2d provided a, 3d)"); + .ShouldBe("piecewise(2d + C provided a, 3d + C)"); [Fact] public void PiecewiseIntegrate3NodeEvaled() => "integral(piecewise(x provided a, 1/x), x)".ToEntity().Evaled - .ShouldBe("piecewise(x ^ 2 / 2 provided a, ln(x))".ToEntity().Evaled); + .ShouldBe("piecewise(x ^ 2 / 2 + C provided a, ln(abs(x)) + C)".ToEntity().Evaled); [Fact] public void PiecewiseDerivative1NodeEvaled() => "derivative(piecewise(x provided a, 1/x), x)".ToEntity().Evaled @@ -229,15 +239,15 @@ [Fact] public void PiecewiseLimit1NodeEvaled() => [Fact] public void PiecewiseIntegrate1NodeInnerSimplified() => "integral(piecewise(2 provided a, 3), x)".ToEntity().InnerSimplified - .ShouldBe("piecewise(2x provided a, 3x)"); + .ShouldBe("piecewise(2 * x + C provided a, 3 * x + C)"); [Fact] public void PiecewiseIntegrate2NodeInnerSimplified() => "integral(piecewise(2 provided a, 3), d)".ToEntity().InnerSimplified - .ShouldBe("piecewise(2d provided a, 3d)"); + .ShouldBe("piecewise(2 * d + C provided a, 3 * d + C)"); [Fact] public void PiecewiseIntegrate3NodeInnerSimplified() => "integral(piecewise(x provided a, 1/x), x)".ToEntity().InnerSimplified - .ShouldBe("piecewise(x ^ 2 / 2 provided a, ln(x))"); + .ShouldBe("piecewise(x ^ 2 / 2 + C provided a, ln(abs(x)) + C)"); [Fact] public void PiecewiseDerivative1NodeInnerSimplified() => "derivative(piecewise(x provided a, 1/x), x)".ToEntity().InnerSimplified diff --git a/Sources/Tests/UnitTests/Common/SubstituteTest.cs b/Sources/Tests/UnitTests/Common/SubstituteTest.cs index c82712f10..c5aee2eae 100644 --- a/Sources/Tests/UnitTests/Common/SubstituteTest.cs +++ b/Sources/Tests/UnitTests/Common/SubstituteTest.cs @@ -21,7 +21,8 @@ public sealed class SubstituteTest [InlineData("{ x : x > 3 }", "{ x : x > 3 }")] [InlineData("x + { x : x > 3 }", "1 + { x : x > 3 }")] [InlineData("x * derivative(x + 2, x, 1)", "1 * derivative(x + 2, x, 1)")] - [InlineData("x * integral(x + 2, x, 1)", "1 * integral(x + 2, x, 1)")] + [InlineData("x * integral(x + 2, x)", "1 * integral(x + 2, x)")] + [InlineData("x * integral(x + 2, x, x, x)", "1 * integral(x + 2, x, 1, 1)")] [InlineData("x * limit(x + 2, x, 1)", "1 * limit(x + 2, x, 1)")] [InlineData("sin(cos(sec(csc(x))))", "sin(cos(sec(csc(1))))")] [InlineData("arcsin(arccos(arcsec(arccsc(x))))", "arcsin(arccos(arcsec(arccsc(1))))")] diff --git a/Sources/Tests/UnitTests/Convenience/FromStringTest.cs b/Sources/Tests/UnitTests/Convenience/FromStringTest.cs index 57a8178a6..257715d89 100644 --- a/Sources/Tests/UnitTests/Convenience/FromStringTest.cs +++ b/Sources/Tests/UnitTests/Convenience/FromStringTest.cs @@ -46,7 +46,7 @@ public void Error(string input, string errorPrefix) => [Theory] [InlineData("limitleft()", "limitleft should have exactly 3 arguments but 0 arguments are provided")] [InlineData("derivative(3)", "derivative should have exactly 3 arguments or 2 arguments but 1 argument is provided")] - [InlineData("integral(3)", "integral should have exactly 3 arguments or 2 arguments but 1 argument is provided")] + [InlineData("integral(3)", "integral should have exactly 4 arguments or 2 arguments but 1 argument is provided")] [InlineData("ln(3, 5)", "ln should have exactly 1 argument but 2 arguments are provided")] [InlineData("sin(3, 5, 8)", "sin should have exactly 1 argument but 3 arguments are provided")] [InlineData("log()", "log should have exactly 1 argument or 2 arguments but 0 arguments are provided")] @@ -96,8 +96,8 @@ public void TestFormula8() [Fact] public void TestFormulaSys() => Assert.Equal(Sqr(x), FromString("x2")); [Fact] public void TestNode28() => Assert.Equal(Derivative("x + 1", x), FromString("derivative(x + 1, x, 1)")); [Fact] public void TestNode29() => Assert.Equal(Derivative("x + 1", x, 5), FromString("derivative(x + 1, x, 5)")); - [Fact] public void TestNode30() => Assert.Equal(Integral("x + 1", x), FromString("integral(x + 1, x, 1)")); - [Fact] public void TestNode31() => Assert.Equal(Integral("x + y", x, 3), FromString("integral(x + y, x, 3)")); + [Fact] public void TestNode30() => Assert.Equal(Integral("x + 1", x), FromString("integral(x + 1, x)")); + [Fact] public void TestNode31() => Assert.Equal(Integral("x + y", x, 3, 4), FromString("integral(x + y, x, 3, 4)")); [Fact] public void TestNode32() => Assert.Equal(Limit("x + y", x, 3), FromString("limit(x + y, x, 3)")); [Fact] public void TestNode33() => Assert.Equal(Limit("x + y", x, 3, ApproachFrom.Left), FromString("limitleft(x + y, x, 3)")); [Fact] public void TestNode34() => Assert.Equal(Signum("x"), FromString("signum(x)")); @@ -211,9 +211,8 @@ public void TestHyperbolic(string parsedName, string methodName) [Fact] public void TestInvalidArg1() => Assert.Throws(() => FromString("integral(x)")); [Fact] public void TestInvalidArg2() => Assert.Throws(() => FromString("integral(24)")); - [Fact] public void TestInvalidArg3() => Assert.Throws(() => FromString("integral(x, x, 4, x)")); - [Fact] public void TestInvalidArg4() => Assert.Throws(() => FromString("integral(x, x, x, x)")); - [Fact] public void TestInvalidArg5() => Assert.Throws(() => FromString("integral(x, x, a)")); + [Fact] public void TestInvalidArg3() => Assert.Throws(() => FromString("integral(x, x, 4)")); + [Fact] public void TestInvalidArg4() => Assert.Throws(() => FromString("integral(x, x, x, x, x)")); [Fact] public void TestInvalidArg6() => Assert.Throws(() => FromString("derivative(x)")); [Fact] public void TestInvalidArg7() => Assert.Throws(() => FromString("derivative(24)")); [Fact] public void TestInvalidArg8() => Assert.Throws(() => FromString("derivative(x, x, 4, x)")); diff --git a/Sources/Tests/UnitTests/Convenience/LatexTest.cs b/Sources/Tests/UnitTests/Convenience/LatexTest.cs index f4463c8e4..3e2a6b93e 100644 --- a/Sources/Tests/UnitTests/Convenience/LatexTest.cs +++ b/Sources/Tests/UnitTests/Convenience/LatexTest.cs @@ -236,17 +236,16 @@ [Fact] public void VectorSingle() => [Fact] public void Integral2() => Test(@"\int \left(x+1\right)\,\mathrm{d}\left(x+1\right)", MathS.Integral("x + 1", "x+1")); [Fact] public void Integral3() => - Test(@"\int\int \left(x+1\right)\,\mathrm{d}x\,\mathrm{d}x", MathS.Integral("x + 1", x, 2)); + Test(@"\int\int \left(x+1\right)\,\mathrm{d}x\,\mathrm{d}x", MathS.Derivative("x + 1", x, -2)); [Fact] public void Integral4() => - Test(@"\int\int \left(x+1\right)\,\mathrm{d}\left(\mathrm{xf}\right)\,\mathrm{d}\left(\mathrm{xf}\right)", MathS.Integral("x + 1", "xf", 2)); + Test(@"\int\int \left(x+1\right)\,\mathrm{d}\left(\mathrm{xf}\right)\,\mathrm{d}\left(\mathrm{xf}\right)", MathS.Derivative("x + 1", "xf", -2)); [Fact] public void Integral5() => Test(@"\int \frac{1}{x}\,\mathrm{d}x_{f}", MathS.Integral("1/x", "x_f")); [Fact] public void Integral6() => - Test(@"\int\int\int \left({x}^{23}-x_{\mathrm{16}}\right)\,\mathrm{d}\left({x_{f}}^{2}\right)\,\mathrm{d}\left({x_{f}}^{2}\right)\,\mathrm{d}\left({x_{f}}^{2}\right)", MathS.Integral("x^23-x_16", "x_f^2", 3)); - [Fact] public void IntegralSpecialCase1() => Test(@"\frac{\mathrm{d}^{0}}{\mathrm{d}x^{0}}\left(x+1\right)+1", MathS.Integral("x+1", "x", 0) + 1); - [Fact] public void IntegralSpecialCase2() => Test(@"\left(\frac{\mathrm{d}}{\mathrm{d}x}\left(x+1\right)\right) \cdot 2", MathS.Integral("x+1", "x", -1) * 2); - [Theory, CombinatorialData] public void IntegralSpecialCase3([CombinatorialRange(0, 3)] int power, [CombinatorialValues("x", "x_f", "ab", "ab_cd", "e", "alpha", "alpha_beta", "alpha_e")] string v) => - Test(MathS.Derivative(Integer.One + v, v, power).Latexise(), MathS.Integral(Integer.One + v, v, -power)); + Test(@"\int\int\int \left({x}^{23}-x_{\mathrm{16}}\right)\,\mathrm{d}\left({x_{f}}^{2}\right)\,\mathrm{d}\left({x_{f}}^{2}\right)\,\mathrm{d}\left({x_{f}}^{2}\right)", MathS.Derivative("x^23-x_16", "x_f^2", -3)); + [Fact] public void DerivativeAt0() => Test(@"\frac{\mathrm{d}^{0}}{\mathrm{d}x^{0}}\left(x+1\right)+1", MathS.Derivative("x+1", "x", 0) + 1); + [Theory, CombinatorialData] public void DerivativeAsIntegral([CombinatorialValues("x", "x_f", "ab", "ab_cd", "e", "alpha", "alpha_beta", "alpha_e")] string v) => + Test(MathS.Integral(Integer.One + v, v).Latexise(), MathS.Derivative(Integer.One + v, v, -1)); [Fact] public void Limit1() => Test(@"\lim_{x\to 0^-} \left(x+y\right)", (Entity)"limitleft(x + y, x, 0)"); [Fact] public void Limit2() => diff --git a/Sources/Tests/UnitTests/Convenience/ToSymPyTEst.cs b/Sources/Tests/UnitTests/Convenience/ToSymPyTEst.cs index f4c5c017c..16150eb6a 100644 --- a/Sources/Tests/UnitTests/Convenience/ToSymPyTEst.cs +++ b/Sources/Tests/UnitTests/Convenience/ToSymPyTEst.cs @@ -35,7 +35,8 @@ public class ToSymPyTest [InlineData("arctan(x)", "sympy.atan(x)")] [InlineData("arccotan(x)", "sympy.acot(x)")] [InlineData("derivative(y, x, 2)", "sympy.diff(y, x, 2)")] - [InlineData("integral(y, x, 2)", "sympy.integrate(y, x, 2)")] + [InlineData("integral(y, x)", "sympy.integrate(y, x)")] + [InlineData("integral(y, x, a, b)", "sympy.integrate(y, (x, a, b))")] [InlineData("limit(y, x, 2)", "sympy.limit(y, x, 2)")] [InlineData("limitleft(y, x, 2)", "sympy.limit(y, x, 2, '-')")] [InlineData("limitright(y, x, 2)", "sympy.limit(y, x, 2, '+')")] diff --git a/Sources/Tests/UnitTests/PatternsTest/SimplifyTest.cs b/Sources/Tests/UnitTests/PatternsTest/SimplifyTest.cs index 2c79c0c19..84376dee4 100644 --- a/Sources/Tests/UnitTests/PatternsTest/SimplifyTest.cs +++ b/Sources/Tests/UnitTests/PatternsTest/SimplifyTest.cs @@ -97,7 +97,7 @@ [Fact] public void Patt2() => AssertSimplify( [Fact] public void NaNPow0() => AssertSimplify(MathS.Pow(nan, 0), nan); [Fact] public void Derive1() => AssertSimplify(MathS.Derivative("x + 2", x), 1); [Fact] public void Derive2() => AssertSimplify(MathS.Derivative("7x2 - x + 2", x, 2), 14); - [Fact] public void Integral1() => AssertSimplify(MathS.Integral("x + y", x, 0), "x + y"); + [Fact] public void Derive3() => AssertSimplify(MathS.Derivative("x + y", x, 0), "x + y"); [Fact] public void Divide1() => AssertSimplifyToString("(x2 + 2 x y + y2) / (x + y)", "x + y provided not x + y = 0"); // TODO: Smart factorizer [Fact] public void Divide2() => AssertSimplifyToString("(x3 + 3 x 2 y + 3 x y 2 + y3) / (x + y)", "x ^ 2 + 2 * x * y + y ^ 2 provided not x + y = 0"); diff --git a/Sources/Utils/Utils/AntlrPostProcessor.cs b/Sources/Utils/Utils/AntlrPostProcessor.cs index 22aa3efec..e7c8aafb8 100644 --- a/Sources/Utils/Utils/AntlrPostProcessor.cs +++ b/Sources/Utils/Utils/AntlrPostProcessor.cs @@ -12,7 +12,7 @@ namespace Utils { public static class AntlrPostProcessorReplacePublicWithInternal { - public const string ANTLR_PATH = "../AngouriMath/AngouriMath/Core/Antlr/"; + public const string ANTLR_PATH = "../AngouriMath/Core/Antlr/"; private static void ProcessFile(string path) {