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)
{