nerdamer 1.1.13 → 2.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +184 -0
- package/README.md +293 -312
- package/dist/bundle.js +2 -0
- package/dist/bundle.js.LICENSE.txt +7 -0
- package/dist/parser.js +2 -0
- package/dist/parser.js.LICENSE.txt +7 -0
- package/docs-data/parser-functions.json +1960 -0
- package/index.d.ts +10 -426
- package/output/algebra/adapters.d.ts +33 -0
- package/output/algebra/adapters.js +109 -0
- package/output/algebra/algorithms/arith.d.ts +84 -0
- package/output/algebra/algorithms/arith.js +323 -0
- package/output/algebra/algorithms/factor.d.ts +3 -0
- package/output/algebra/algorithms/factor.js +5 -0
- package/output/algebra/algorithms/factorMultivariate.d.ts +23 -0
- package/output/algebra/algorithms/factorMultivariate.js +2393 -0
- package/output/algebra/algorithms/factorUnivariate.d.ts +72 -0
- package/output/algebra/algorithms/factorUnivariate.js +1072 -0
- package/output/algebra/algorithms/gcd.d.ts +22 -0
- package/output/algebra/algorithms/gcd.js +690 -0
- package/output/algebra/algorithms/groebnerBase.d.ts +157 -0
- package/output/algebra/algorithms/groebnerBase.js +1166 -0
- package/output/algebra/algorithms/multiPoly/MultiPoly.d.ts +137 -0
- package/output/algebra/algorithms/multiPoly/MultiPoly.js +346 -0
- package/output/algebra/algorithms/poly.d.ts +228 -0
- package/output/algebra/algorithms/poly.js +1299 -0
- package/output/algebra/algorithms/rational.d.ts +17 -0
- package/output/algebra/algorithms/rational.js +115 -0
- package/output/algebra/factor/factor.d.ts +62 -0
- package/output/algebra/factor/factor.js +158 -0
- package/output/algebra/gcd/gcd.d.ts +29 -0
- package/output/algebra/gcd/gcd.js +141 -0
- package/output/algebra/groebner.d.ts +21 -0
- package/output/algebra/groebner.js +42 -0
- package/output/algebra/partfrac.d.ts +19 -0
- package/output/algebra/partfrac.js +398 -0
- package/output/algebra/polynomialize.d.ts +20 -0
- package/output/algebra/polynomialize.js +24 -0
- package/output/algebra/simplify/complexsimp.d.ts +7 -0
- package/output/algebra/simplify/complexsimp.js +24 -0
- package/output/algebra/simplify/factorCommon.d.ts +13 -0
- package/output/algebra/simplify/factorCommon.js +136 -0
- package/output/algebra/simplify/funcsimp.d.ts +26 -0
- package/output/algebra/simplify/funcsimp.js +633 -0
- package/output/algebra/simplify/invtrigrewrite.d.ts +10 -0
- package/output/algebra/simplify/invtrigrewrite.js +96 -0
- package/output/algebra/simplify/ratsimp.d.ts +21 -0
- package/output/algebra/simplify/ratsimp.js +140 -0
- package/output/algebra/simplify/simplify.d.ts +25 -0
- package/output/algebra/simplify/simplify.js +228 -0
- package/output/algebra/simplify/trigreduce.d.ts +38 -0
- package/output/algebra/simplify/trigreduce.js +424 -0
- package/output/algebra/simplify/trigrewrite.d.ts +17 -0
- package/output/algebra/simplify/trigrewrite.js +156 -0
- package/output/algebra/simplify/trigsimp.d.ts +11 -0
- package/output/algebra/simplify/trigsimp.js +369 -0
- package/output/algebra/simplify/utils.d.ts +32 -0
- package/output/algebra/simplify/utils.js +56 -0
- package/output/algebra/utils.d.ts +32 -0
- package/output/algebra/utils.js +48 -0
- package/output/api/advanced.d.ts +7 -0
- package/output/api/advanced.js +18 -0
- package/output/api/algebra.d.ts +17 -0
- package/output/api/algebra.js +32 -0
- package/output/api/assumptions.d.ts +4 -0
- package/output/api/assumptions.js +11 -0
- package/output/api/calculus.d.ts +9 -0
- package/output/api/calculus.js +21 -0
- package/output/api/core.d.ts +17 -0
- package/output/api/core.js +41 -0
- package/output/api/debug.d.ts +16 -0
- package/output/api/debug.js +23 -0
- package/output/api/parser.d.ts +85 -0
- package/output/api/parser.js +16 -0
- package/output/api/solve.d.ts +10 -0
- package/output/api/solve.js +16 -0
- package/output/api/structures.d.ts +10 -0
- package/output/api/structures.js +22 -0
- package/output/calculus/derivative/diff.d.ts +30 -0
- package/output/calculus/derivative/diff.js +238 -0
- package/output/calculus/fresnel.d.ts +16 -0
- package/output/calculus/fresnel.js +39 -0
- package/output/calculus/integrate/byParts.d.ts +18 -0
- package/output/calculus/integrate/byParts.js +339 -0
- package/output/calculus/integrate/bySubstitution.d.ts +135 -0
- package/output/calculus/integrate/bySubstitution.js +411 -0
- package/output/calculus/integrate/integrate.d.ts +38 -0
- package/output/calculus/integrate/integrate.js +652 -0
- package/output/calculus/integrate/integrationTable.d.ts +2 -0
- package/output/calculus/integrate/integrationTable.js +914 -0
- package/output/calculus/integrate/utils.d.ts +28 -0
- package/output/calculus/integrate/utils.js +96 -0
- package/output/calculus/laplace/ilaplace.d.ts +31 -0
- package/output/calculus/laplace/ilaplace.js +192 -0
- package/output/calculus/laplace/ilaplaceTable.d.ts +2 -0
- package/output/calculus/laplace/ilaplaceTable.js +200 -0
- package/output/calculus/laplace/laplace.d.ts +22 -0
- package/output/calculus/laplace/laplace.js +109 -0
- package/output/calculus/laplace/laplaceTable.d.ts +2 -0
- package/output/calculus/laplace/laplaceTable.js +187 -0
- package/output/calculus/limit/limit.d.ts +35 -0
- package/output/calculus/limit/limit.js +1183 -0
- package/output/calculus/limit/limitsTable.d.ts +2 -0
- package/output/calculus/limit/limitsTable.js +7 -0
- package/output/core/Settings.d.ts +32 -0
- package/output/core/Settings.js +68 -0
- package/output/core/adapters.d.ts +18 -0
- package/output/core/adapters.js +40 -0
- package/output/core/classes/assumption/Assumption.d.ts +218 -0
- package/output/core/classes/assumption/Assumption.js +605 -0
- package/output/core/classes/assumption/assertiveFunctions.d.ts +6 -0
- package/output/core/classes/assumption/assertiveFunctions.js +64 -0
- package/output/core/classes/assumption/assume.d.ts +90 -0
- package/output/core/classes/assumption/assume.js +145 -0
- package/output/core/classes/collection/Collection.d.ts +107 -0
- package/output/core/classes/collection/Collection.js +231 -0
- package/output/core/classes/complex/Complex.d.ts +99 -0
- package/output/core/classes/complex/Complex.js +135 -0
- package/output/core/classes/decimalSet/DecimalSet.d.ts +226 -0
- package/output/core/classes/decimalSet/DecimalSet.js +453 -0
- package/output/core/classes/dictionary/Dictionary.d.ts +102 -0
- package/output/core/classes/dictionary/Dictionary.js +213 -0
- package/output/core/classes/equation/Equation.d.ts +256 -0
- package/output/core/classes/equation/Equation.js +350 -0
- package/output/core/classes/expression/CoeffObject.d.ts +100 -0
- package/output/core/classes/expression/CoeffObject.js +198 -0
- package/output/core/classes/expression/Expression.d.ts +1674 -0
- package/output/core/classes/expression/Expression.js +2332 -0
- package/output/core/classes/expression/analysis.d.ts +25 -0
- package/output/core/classes/expression/analysis.js +218 -0
- package/output/core/classes/expression/collect.d.ts +9 -0
- package/output/core/classes/expression/collect.js +49 -0
- package/output/core/classes/expression/format.d.ts +45 -0
- package/output/core/classes/expression/format.js +217 -0
- package/output/core/classes/expression/products.d.ts +23 -0
- package/output/core/classes/expression/products.js +115 -0
- package/output/core/classes/expression/shortcuts.d.ts +35 -0
- package/output/core/classes/expression/shortcuts.js +129 -0
- package/output/core/classes/expression/traversal.d.ts +31 -0
- package/output/core/classes/expression/traversal.js +216 -0
- package/output/core/classes/expression/trig.d.ts +25 -0
- package/output/core/classes/expression/trig.js +40 -0
- package/output/core/classes/expression/utils.d.ts +52 -0
- package/output/core/classes/expression/utils.js +189 -0
- package/output/core/classes/lookupTable/LookupTable.d.ts +12 -0
- package/output/core/classes/lookupTable/LookupTable.js +55 -0
- package/output/core/classes/matrix/Matrix.d.ts +227 -0
- package/output/core/classes/matrix/Matrix.js +889 -0
- package/output/core/classes/matrix/Sylvester.d.ts +11 -0
- package/output/core/classes/matrix/Sylvester.js +54 -0
- package/output/core/classes/matrix/functions.d.ts +26 -0
- package/output/core/classes/matrix/functions.js +52 -0
- package/output/core/classes/matrix/utils.d.ts +9 -0
- package/output/core/classes/matrix/utils.js +22 -0
- package/output/core/classes/parser/Parser.d.ts +480 -0
- package/output/core/classes/parser/Parser.js +1812 -0
- package/output/core/classes/parser/Token.d.ts +58 -0
- package/output/core/classes/parser/Token.js +137 -0
- package/output/core/classes/parser/constants.d.ts +160 -0
- package/output/core/classes/parser/constants.js +227 -0
- package/output/core/classes/parser/helpers.d.ts +21 -0
- package/output/core/classes/parser/helpers.js +42 -0
- package/output/core/classes/parser/operations/add.d.ts +15 -0
- package/output/core/classes/parser/operations/add.js +238 -0
- package/output/core/classes/parser/operations/comma.d.ts +7 -0
- package/output/core/classes/parser/operations/comma.js +11 -0
- package/output/core/classes/parser/operations/compare.d.ts +21 -0
- package/output/core/classes/parser/operations/compare.js +461 -0
- package/output/core/classes/parser/operations/divide.d.ts +2 -0
- package/output/core/classes/parser/operations/divide.js +72 -0
- package/output/core/classes/parser/operations/functions.d.ts +46 -0
- package/output/core/classes/parser/operations/functions.js +430 -0
- package/output/core/classes/parser/operations/multiply.d.ts +11 -0
- package/output/core/classes/parser/operations/multiply.js +321 -0
- package/output/core/classes/parser/operations/power.d.ts +48 -0
- package/output/core/classes/parser/operations/power.js +673 -0
- package/output/core/classes/parser/operations/subtract.d.ts +3 -0
- package/output/core/classes/parser/operations/subtract.js +35 -0
- package/output/core/classes/parser/preprocess.d.ts +14 -0
- package/output/core/classes/parser/preprocess.js +259 -0
- package/output/core/classes/parser/scripting/controlFlow.d.ts +121 -0
- package/output/core/classes/parser/scripting/controlFlow.js +355 -0
- package/output/core/classes/parser/scripting/evaluate.d.ts +10 -0
- package/output/core/classes/parser/scripting/evaluate.js +112 -0
- package/output/core/classes/parser/scripting/functions.d.ts +14 -0
- package/output/core/classes/parser/scripting/functions.js +59 -0
- package/output/core/classes/parser/scripting/scope.d.ts +35 -0
- package/output/core/classes/parser/scripting/scope.js +155 -0
- package/output/core/classes/parser/types.d.ts +125 -0
- package/output/core/classes/parser/types.js +2 -0
- package/output/core/classes/parser/wrappers/IndexedReference.d.ts +31 -0
- package/output/core/classes/parser/wrappers/IndexedReference.js +68 -0
- package/output/core/classes/parser/wrappers/KeyValuePair.d.ts +24 -0
- package/output/core/classes/parser/wrappers/KeyValuePair.js +51 -0
- package/output/core/classes/polynomial/Polynomial.d.ts +500 -0
- package/output/core/classes/polynomial/Polynomial.js +1149 -0
- package/output/core/classes/polynomial/Term.d.ts +222 -0
- package/output/core/classes/polynomial/Term.js +430 -0
- package/output/core/classes/polynomial/functions.d.ts +69 -0
- package/output/core/classes/polynomial/functions.js +126 -0
- package/output/core/classes/polynomial/utils.d.ts +62 -0
- package/output/core/classes/polynomial/utils.js +206 -0
- package/output/core/classes/rational/Rational.d.ts +504 -0
- package/output/core/classes/rational/Rational.js +779 -0
- package/output/core/classes/seq/SEQ.d.ts +13 -0
- package/output/core/classes/seq/SEQ.js +137 -0
- package/output/core/classes/valuesSet/ValuesSet.d.ts +179 -0
- package/output/core/classes/valuesSet/ValuesSet.js +381 -0
- package/output/core/classes/vector/Vector.d.ts +222 -0
- package/output/core/classes/vector/Vector.js +497 -0
- package/output/core/classes/vector/functions.d.ts +28 -0
- package/output/core/classes/vector/functions.js +47 -0
- package/output/core/common/classes/MathematicalAggregate.d.ts +53 -0
- package/output/core/common/classes/MathematicalAggregate.js +140 -0
- package/output/core/common/classes/Scope.d.ts +62 -0
- package/output/core/common/classes/Scope.js +122 -0
- package/output/core/common/classes/StructuredEntity.d.ts +55 -0
- package/output/core/common/classes/StructuredEntity.js +151 -0
- package/output/core/common/common.d.ts +63 -0
- package/output/core/common/common.js +279 -0
- package/output/core/common/functions/functions.d.ts +14 -0
- package/output/core/common/functions/functions.js +33 -0
- package/output/core/common/functions/structuredEntityUtils.d.ts +27 -0
- package/output/core/common/functions/structuredEntityUtils.js +40 -0
- package/output/core/converters/BaseConverter.d.ts +97 -0
- package/output/core/converters/BaseConverter.js +405 -0
- package/output/core/converters/Converter.d.ts +111 -0
- package/output/core/converters/Converter.js +802 -0
- package/output/core/converters/Pattern.d.ts +71 -0
- package/output/core/converters/Pattern.js +302 -0
- package/output/core/dispatch.d.ts +34 -0
- package/output/core/dispatch.js +304 -0
- package/output/core/errors.d.ts +531 -0
- package/output/core/errors.js +621 -0
- package/output/core/functions/bigint/bigint.d.ts +121 -0
- package/output/core/functions/bigint/bigint.js +392 -0
- package/output/core/functions/bigint/primeFactor.d.ts +49 -0
- package/output/core/functions/bigint/primeFactor.js +267 -0
- package/output/core/functions/bigint/primes.d.ts +1 -0
- package/output/core/functions/bigint/primes.js +11 -0
- package/output/core/functions/build/definitions.d.ts +12 -0
- package/output/core/functions/build/definitions.js +133 -0
- package/output/core/functions/build/index.d.ts +32 -0
- package/output/core/functions/build/index.js +136 -0
- package/output/core/functions/complex.d.ts +90 -0
- package/output/core/functions/complex.js +482 -0
- package/output/core/functions/decimal.d.ts +24 -0
- package/output/core/functions/decimal.js +664 -0
- package/output/core/functions/expand/expand.d.ts +42 -0
- package/output/core/functions/expand/expand.js +376 -0
- package/output/core/functions/fresnelNumeric.d.ts +12 -0
- package/output/core/functions/fresnelNumeric.js +123 -0
- package/output/core/functions/numeric.d.ts +140 -0
- package/output/core/functions/numeric.js +790 -0
- package/output/core/functions/rationalNormalization.d.ts +11 -0
- package/output/core/functions/rationalNormalization.js +117 -0
- package/output/core/functions/setFunction.d.ts +19 -0
- package/output/core/functions/setFunction.js +75 -0
- package/output/core/functions/string.d.ts +26 -0
- package/output/core/functions/string.js +101 -0
- package/output/core/functions/subst.d.ts +90 -0
- package/output/core/functions/subst.js +498 -0
- package/output/core/functions/utils.d.ts +24 -0
- package/output/core/functions/utils.js +51 -0
- package/output/core/types.d.ts +48 -0
- package/output/core/types.js +2 -0
- package/output/index.d.ts +339 -0
- package/output/index.js +715 -0
- package/output/math/defint/defintDecimal.d.ts +37 -0
- package/output/math/defint/defintDecimal.js +320 -0
- package/output/math/defint/defintNative.d.ts +57 -0
- package/output/math/defint/defintNative.js +292 -0
- package/output/math/geometry.d.ts +8 -0
- package/output/math/geometry.js +32 -0
- package/output/math/math.d.ts +655 -0
- package/output/math/math.js +1811 -0
- package/output/math/trig.d.ts +518 -0
- package/output/math/trig.js +1444 -0
- package/output/math/trunc.d.ts +19 -0
- package/output/math/trunc.js +36 -0
- package/output/math/utils.d.ts +60 -0
- package/output/math/utils.js +180 -0
- package/output/solve/classes/DecimalMatrix.d.ts +17 -0
- package/output/solve/classes/DecimalMatrix.js +86 -0
- package/output/solve/classes/FunctionSolver.d.ts +135 -0
- package/output/solve/classes/FunctionSolver.js +433 -0
- package/output/solve/classes/MultivariateSolver.d.ts +84 -0
- package/output/solve/classes/MultivariateSolver.js +240 -0
- package/output/solve/classes/PolynomialSolver.d.ts +85 -0
- package/output/solve/classes/PolynomialSolver.js +292 -0
- package/output/solve/classes/SolutionSet.d.ts +153 -0
- package/output/solve/classes/SolutionSet.js +357 -0
- package/output/solve/classes/Solver.d.ts +17 -0
- package/output/solve/classes/Solver.js +132 -0
- package/output/solve/classes/SymbolicSolver.d.ts +74 -0
- package/output/solve/classes/SymbolicSolver.js +601 -0
- package/output/solve/linsolve.d.ts +51 -0
- package/output/solve/linsolve.js +225 -0
- package/output/solve/solve.d.ts +34 -0
- package/output/solve/solve.js +260 -0
- package/output/solve/solveSystem.d.ts +31 -0
- package/output/solve/solveSystem.js +290 -0
- package/output/solve/utils.d.ts +9 -0
- package/output/solve/utils.js +62 -0
- package/output/utils/array.d.ts +42 -0
- package/output/utils/array.js +98 -0
- package/output/utils/debug.d.ts +131 -0
- package/output/utils/debug.js +295 -0
- package/output/utils/decimal.d.ts +3 -0
- package/output/utils/decimal.js +16 -0
- package/output/utils/numeric.d.ts +14 -0
- package/output/utils/numeric.js +51 -0
- package/output/utils/object.d.ts +26 -0
- package/output/utils/object.js +56 -0
- package/package.json +213 -57
- package/.travis.yml +0 -3
- package/Algebra.js +0 -4568
- package/BREAKING_CHANGES.md +0 -5
- package/CODE_OF_CONDUCT.md +0 -46
- package/CONTRIBUTING.md +0 -14
- package/Calculus.js +0 -2675
- package/Extra.js +0 -622
- package/Solve.js +0 -1783
- package/all.js +0 -16
- package/all.min.js +0 -1
- package/gulpfile.js +0 -16
- package/index.html +0 -158
- package/license.txt +0 -19
- package/nerdamer.core.js +0 -12510
- package/spec/LaTeX.spec.js +0 -302
- package/spec/TeXConvert.spec.js +0 -8
- package/spec/algebra.spec.js +0 -287
- package/spec/basic_parser.spec.js +0 -134
- package/spec/build.spec.js +0 -131
- package/spec/calculus.spec.js +0 -183
- package/spec/core.spec.js +0 -2970
- package/spec/extra.spec.js +0 -54
- package/spec/solve.spec.js +0 -126
- package/spec/support/jasmine.json +0 -11
- package/spec/support/utils.js +0 -42
- package/spec/text.spec.js +0 -132
|
@@ -0,0 +1,1183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.limit = limit;
|
|
4
|
+
const simplify_1 = require("../../algebra/simplify/simplify");
|
|
5
|
+
const Expression_1 = require("../../core/classes/expression/Expression");
|
|
6
|
+
const shortcuts_1 = require("../../core/classes/expression/shortcuts");
|
|
7
|
+
const constants_1 = require("../../core/classes/parser/constants");
|
|
8
|
+
const Polynomial_1 = require("../../core/classes/polynomial/Polynomial");
|
|
9
|
+
const Pattern_1 = require("../../core/converters/Pattern");
|
|
10
|
+
const math_1 = require("../../math/math");
|
|
11
|
+
const diff_1 = require("../derivative/diff");
|
|
12
|
+
const limitsTable_1 = require("./limitsTable");
|
|
13
|
+
const L_HOPITAL_MAX_DEPTH = 4;
|
|
14
|
+
const LIMIT_MAX_DEPTH = 12;
|
|
15
|
+
function classifyAtPoint(expr, x, val) {
|
|
16
|
+
const vars = { [x.text()]: val };
|
|
17
|
+
try {
|
|
18
|
+
const num = Expression_1.Expression.create(expr.getNumerator().evaluate(vars));
|
|
19
|
+
const den = Expression_1.Expression.create(expr.getDenominator().evaluate(vars));
|
|
20
|
+
if (!den.isZero()) {
|
|
21
|
+
return 'regular';
|
|
22
|
+
}
|
|
23
|
+
if (num.isZero()) {
|
|
24
|
+
return 'zero_over_zero';
|
|
25
|
+
}
|
|
26
|
+
return 'nonzero_over_zero';
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return 'unknown';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Maximum number of derivatives to check when determining the sign
|
|
34
|
+
* the denominator approaches from (handles higher-order zeros).
|
|
35
|
+
*/
|
|
36
|
+
const SIGN_DERIVATIVE_MAX_DEPTH = 4;
|
|
37
|
+
/**
|
|
38
|
+
* Handle the nonzero/zero case: numerator is finite and nonzero at the point,
|
|
39
|
+
* denominator is zero. The limit is ±∞ depending on the sign of the numerator
|
|
40
|
+
* and the side from which the denominator approaches zero.
|
|
41
|
+
*
|
|
42
|
+
* Uses successive derivatives of the denominator to determine the sign it
|
|
43
|
+
* approaches from. If den'(val) ≠ 0, the sign of den'(val) tells us:
|
|
44
|
+
* - den is increasing through zero → negative from left, positive from right
|
|
45
|
+
* - den is decreasing through zero → positive from left, negative from right
|
|
46
|
+
* If den'(val) = 0, we check den''(val), den'''(val), etc. For the k-th nonzero
|
|
47
|
+
* derivative: if k is odd, the function changes sign (like above); if k is even,
|
|
48
|
+
* the function touches zero without crossing (same sign both sides).
|
|
49
|
+
*/
|
|
50
|
+
function tryNonzeroOverZero(expr, x, val, dir) {
|
|
51
|
+
const num = expr.getNumerator();
|
|
52
|
+
const den = expr.getDenominator();
|
|
53
|
+
const vars = { [x.text()]: val };
|
|
54
|
+
// Get numerator sign at the point
|
|
55
|
+
let numSign;
|
|
56
|
+
try {
|
|
57
|
+
numSign = Expression_1.Expression.create(num.evaluate(vars)).sign();
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
if (numSign === 0) {
|
|
63
|
+
return undefined; // Shouldn't happen given classification, but be safe
|
|
64
|
+
}
|
|
65
|
+
// Walk up the derivatives of the denominator to find the first nonzero one at val
|
|
66
|
+
let d = den;
|
|
67
|
+
let order = 0;
|
|
68
|
+
for (let k = 0; k < SIGN_DERIVATIVE_MAX_DEPTH; k++) {
|
|
69
|
+
d = (0, diff_1.diff)(d, x);
|
|
70
|
+
order = k + 1;
|
|
71
|
+
let dAtVal;
|
|
72
|
+
try {
|
|
73
|
+
dAtVal = Expression_1.Expression.create(d.evaluate(vars));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
if (dAtVal.isZero()) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const dSign = dAtVal.sign();
|
|
82
|
+
if (dSign === 0) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
// Odd-order derivative: denominator changes sign through zero
|
|
86
|
+
// Even-order derivative: denominator has same sign on both sides
|
|
87
|
+
const isOddOrder = order % 2 !== 0;
|
|
88
|
+
if (!isOddOrder) {
|
|
89
|
+
// Even order: den has same sign on both sides (like x² at 0)
|
|
90
|
+
// den approaches from positive if dSign > 0, negative if dSign < 0
|
|
91
|
+
const resultSign = numSign * dSign;
|
|
92
|
+
if (dir === 'both' || dir === 'left' || dir === 'right') {
|
|
93
|
+
return resultSign > 0 ? Expression_1.Expression.Inf() : Expression_1.Expression.NegInf();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Odd order: den changes sign (like x at 0)
|
|
97
|
+
// From right: den sign matches dSign
|
|
98
|
+
// From left: den sign is -dSign
|
|
99
|
+
const rightDenSign = dSign;
|
|
100
|
+
const leftDenSign = -dSign;
|
|
101
|
+
if (dir === 'right') {
|
|
102
|
+
const resultSign = numSign * rightDenSign;
|
|
103
|
+
return resultSign > 0 ? Expression_1.Expression.Inf() : Expression_1.Expression.NegInf();
|
|
104
|
+
}
|
|
105
|
+
if (dir === 'left') {
|
|
106
|
+
const resultSign = numSign * leftDenSign;
|
|
107
|
+
return resultSign > 0 ? Expression_1.Expression.Inf() : Expression_1.Expression.NegInf();
|
|
108
|
+
}
|
|
109
|
+
// dir === 'both': check if both sides agree
|
|
110
|
+
const rightResult = numSign * rightDenSign;
|
|
111
|
+
const leftResult = numSign * leftDenSign;
|
|
112
|
+
if (rightResult === leftResult) {
|
|
113
|
+
// Shouldn't happen for odd order (signs are opposite), but handle it
|
|
114
|
+
return rightResult > 0 ? Expression_1.Expression.Inf() : Expression_1.Expression.NegInf();
|
|
115
|
+
}
|
|
116
|
+
// Two-sided limit does not exist — left and right disagree.
|
|
117
|
+
// Return undefined so it falls through to the unevaluated limit form.
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
// All derivatives up to max depth were zero — give up
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
function tryLHopital(expr, x, val, dir, depth) {
|
|
124
|
+
if (depth >= L_HOPITAL_MAX_DEPTH) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
const den = expr.getDenominator();
|
|
128
|
+
if (den.isOne()) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
const num = expr.getNumerator();
|
|
132
|
+
const vars = { [x.text()]: val };
|
|
133
|
+
let numAt;
|
|
134
|
+
let denAt;
|
|
135
|
+
try {
|
|
136
|
+
numAt = Expression_1.Expression.create(num.evaluate(vars));
|
|
137
|
+
denAt = Expression_1.Expression.create(den.evaluate(vars));
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
const isZeroOverZero = numAt.isZero() && denAt.isZero();
|
|
143
|
+
const isInfinityOverInfinity = (numAt.isPosInf() || numAt.isNegInf()) && (denAt.isPosInf() || denAt.isNegInf());
|
|
144
|
+
if (!isZeroOverZero && !isInfinityOverInfinity) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const dNum = (0, diff_1.diff)(num, x);
|
|
148
|
+
const dDen = (0, diff_1.diff)(den, x);
|
|
149
|
+
if (dDen.isZero()) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const next = (0, simplify_1.simplify)(dNum.div(dDen));
|
|
153
|
+
if (next.eq(expr)) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
return limit(next, x, val, dir, depth + 1);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Evaluate expr at x=val and classify the result.
|
|
160
|
+
* Returns 'zero', 'inf', 'neginf', 'one', 'finite', or undefined if evaluation fails.
|
|
161
|
+
*/
|
|
162
|
+
function evalCategory(expr, x, val) {
|
|
163
|
+
try {
|
|
164
|
+
const result = (0, simplify_1.simplify)(expr.subst(x, val));
|
|
165
|
+
if (result.isZero()) {
|
|
166
|
+
return 'zero';
|
|
167
|
+
}
|
|
168
|
+
if (result.isPosInf()) {
|
|
169
|
+
return 'inf';
|
|
170
|
+
}
|
|
171
|
+
if (result.isNegInf()) {
|
|
172
|
+
return 'neginf';
|
|
173
|
+
}
|
|
174
|
+
if (result.eq((0, shortcuts_1.one)())) {
|
|
175
|
+
return 'one';
|
|
176
|
+
}
|
|
177
|
+
return 'finite';
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Classify the indeterminate form of a power f^g at x→val.
|
|
185
|
+
*/
|
|
186
|
+
function classifyPowerForm(base, exponent, x, val) {
|
|
187
|
+
const categoryBase = evalCategory(base, x, val);
|
|
188
|
+
const categoryExp = evalCategory(exponent, x, val);
|
|
189
|
+
if (!categoryBase || !categoryExp) {
|
|
190
|
+
return 'none';
|
|
191
|
+
}
|
|
192
|
+
// 1^∞
|
|
193
|
+
if (categoryBase === 'one' && (categoryExp === 'inf' || categoryExp === 'neginf')) {
|
|
194
|
+
return 'one_to_inf';
|
|
195
|
+
}
|
|
196
|
+
// 0^0
|
|
197
|
+
if (categoryBase === 'zero' && categoryExp === 'zero') {
|
|
198
|
+
return 'zero_to_zero';
|
|
199
|
+
}
|
|
200
|
+
// ∞^0
|
|
201
|
+
if ((categoryBase === 'inf' || categoryBase === 'neginf') && categoryExp === 'zero') {
|
|
202
|
+
return 'inf_to_zero';
|
|
203
|
+
}
|
|
204
|
+
return 'none';
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Detect ∞ - ∞: a sum where two additive terms individually diverge but with opposite signs.
|
|
208
|
+
*/
|
|
209
|
+
function isInfMinusInf(expr, x, val) {
|
|
210
|
+
if (!expr.isSum()) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
const terms = expr.elementsArray().map(e => Expression_1.Expression.create(e));
|
|
214
|
+
let hasPos = false;
|
|
215
|
+
let hasNeg = false;
|
|
216
|
+
for (const term of terms) {
|
|
217
|
+
const cat = evalCategory(term, x, val);
|
|
218
|
+
if (cat === 'inf') {
|
|
219
|
+
hasPos = true;
|
|
220
|
+
}
|
|
221
|
+
else if (cat === 'neginf') {
|
|
222
|
+
hasNeg = true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return hasPos && hasNeg;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Try to rewrite an indeterminate form into 0/0 or ∞/∞ so L'Hôpital can handle it.
|
|
229
|
+
*
|
|
230
|
+
* Rewrites:
|
|
231
|
+
* 0·∞: f·g → f / (1/g) (puts the zero factor in the numerator)
|
|
232
|
+
* ∞-∞: f - g → (1/g - 1/f) / (1/(f·g)) i.e. combine over common denominator
|
|
233
|
+
* 1^∞, 0^0, ∞^0: f^g → e^(g·ln(f)) then limit the exponent g·ln(f)
|
|
234
|
+
*/
|
|
235
|
+
function tryRewriteIndeterminate(expr, x, val, dir, depth) {
|
|
236
|
+
// --- 0 · ∞ ---
|
|
237
|
+
// Check if the whole expression (with denominator 1) is a product containing
|
|
238
|
+
// a zero-approaching and an infinity-approaching factor.
|
|
239
|
+
if (expr.getDenominator().isOne() && expr.isProduct()) {
|
|
240
|
+
const factors = expr.elementsArray().map(e => Expression_1.Expression.create(e));
|
|
241
|
+
// Find the first zero factor and first inf factor
|
|
242
|
+
let zeroIdx = -1;
|
|
243
|
+
let infIdx = -1;
|
|
244
|
+
for (let i = 0; i < factors.length; i++) {
|
|
245
|
+
const cat = evalCategory(factors[i], x, val);
|
|
246
|
+
if (cat === 'zero' && zeroIdx === -1) {
|
|
247
|
+
zeroIdx = i;
|
|
248
|
+
}
|
|
249
|
+
else if ((cat === 'inf' || cat === 'neginf') && infIdx === -1) {
|
|
250
|
+
infIdx = i;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (zeroIdx !== -1 && infIdx !== -1) {
|
|
254
|
+
// Rewrite as: zeroFactor / (1/infFactor) · remaining
|
|
255
|
+
// This gives 0/0 form
|
|
256
|
+
const zeroFactor = factors[zeroIdx];
|
|
257
|
+
const infFactor = factors[infIdx];
|
|
258
|
+
// Collect remaining factors
|
|
259
|
+
const remaining = [];
|
|
260
|
+
for (let i = 0; i < factors.length; i++) {
|
|
261
|
+
if (i !== zeroIdx && i !== infIdx) {
|
|
262
|
+
remaining.push(factors[i]);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
let rewritten = zeroFactor.div((0, shortcuts_1.one)().div(infFactor));
|
|
266
|
+
for (const r of remaining) {
|
|
267
|
+
rewritten = rewritten.times(r);
|
|
268
|
+
}
|
|
269
|
+
return limit((0, simplify_1.simplify)(rewritten), x, val, dir, depth + 1);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// --- ∞ - ∞ ---
|
|
273
|
+
if (isInfMinusInf(expr, x, val)) {
|
|
274
|
+
// Combine into a single fraction by finding a common denominator.
|
|
275
|
+
// Use simplify which should call toCommonDenominator internally.
|
|
276
|
+
const combined = (0, simplify_1.simplify)(expr);
|
|
277
|
+
// If simplify produced something different, recurse on it
|
|
278
|
+
if (!combined.eq(expr)) {
|
|
279
|
+
return limit(combined, x, val, dir, depth + 1);
|
|
280
|
+
}
|
|
281
|
+
// Manual fallback: combine to common denominator
|
|
282
|
+
const terms = expr.elementsArray().map(e => Expression_1.Expression.create(e));
|
|
283
|
+
if (terms.length === 2) {
|
|
284
|
+
const [a, b] = terms;
|
|
285
|
+
const aNum = a.getNumerator();
|
|
286
|
+
const aDen = a.getDenominator();
|
|
287
|
+
const bNum = b.getNumerator();
|
|
288
|
+
const bDen = b.getDenominator();
|
|
289
|
+
const combinedNum = (0, simplify_1.simplify)(aNum.times(bDen).plus(bNum.times(aDen)));
|
|
290
|
+
const combinedDen = (0, simplify_1.simplify)(aDen.times(bDen));
|
|
291
|
+
const combined2 = combinedNum.div(combinedDen);
|
|
292
|
+
if (!combined2.eq(expr)) {
|
|
293
|
+
return limit(combined2, x, val, dir, depth + 1);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// --- 1^∞, 0^0, ∞^0 (exponential indeterminate forms) ---
|
|
298
|
+
if (expr.isEXP()) {
|
|
299
|
+
const base = expr.getBase();
|
|
300
|
+
const exponent = expr.getPower();
|
|
301
|
+
const form = classifyPowerForm(base, exponent, x, val);
|
|
302
|
+
if (form === 'one_to_inf' || form === 'zero_to_zero' || form === 'inf_to_zero') {
|
|
303
|
+
// f^g = e^(g · ln(f))
|
|
304
|
+
// limit(f^g) = e^(limit(g · ln(f)))
|
|
305
|
+
// The inner product g·ln(f) will typically be a 0·∞ form,
|
|
306
|
+
// which the 0·∞ handler above will rewrite into 0/0 for L'Hôpital.
|
|
307
|
+
const innerExpr = exponent.times(Expression_1.Expression.toFunction(constants_1.LOG, [base]));
|
|
308
|
+
const innerLimit = limit(innerExpr, x, val, dir, depth + 1);
|
|
309
|
+
// If the inner limit resolved to a concrete value, exponentiate it
|
|
310
|
+
if (innerLimit.isPosInf()) {
|
|
311
|
+
return Expression_1.Expression.Inf();
|
|
312
|
+
}
|
|
313
|
+
if (innerLimit.isNegInf()) {
|
|
314
|
+
return (0, shortcuts_1.zero)();
|
|
315
|
+
}
|
|
316
|
+
// If it resolved to a finite value, return e^value
|
|
317
|
+
if (!innerLimit.isFunction(constants_1.LIMIT)) {
|
|
318
|
+
return (0, math_1.exp)(innerLimit);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Numeric tier for comparing growth classifications.
|
|
326
|
+
* Higher tier dominates lower tier as x→±∞.
|
|
327
|
+
*
|
|
328
|
+
* exponential > polynomial > logarithmic > bounded
|
|
329
|
+
*/
|
|
330
|
+
function growthTier(c) {
|
|
331
|
+
switch (c.type) {
|
|
332
|
+
case 'bounded':
|
|
333
|
+
return 0;
|
|
334
|
+
case 'logarithmic':
|
|
335
|
+
return 1;
|
|
336
|
+
case 'polynomial':
|
|
337
|
+
return 2;
|
|
338
|
+
case 'exponential':
|
|
339
|
+
return 3;
|
|
340
|
+
case 'unknown':
|
|
341
|
+
return -1;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function getInfinityGrowthClassification(expr, x) {
|
|
345
|
+
if (!expr.hasVariable(x.text())) {
|
|
346
|
+
return { type: 'bounded' };
|
|
347
|
+
}
|
|
348
|
+
// Exponential: e^f(x) where f(x) contains x
|
|
349
|
+
// An EXP node with base e and a power that depends on x
|
|
350
|
+
if (expr.isEXP() && expr.getBase().isE() && expr.getPower().hasVariable(x.text())) {
|
|
351
|
+
return { type: 'exponential', exponent: expr.getPower() };
|
|
352
|
+
}
|
|
353
|
+
// Products: classify by the dominant factor.
|
|
354
|
+
// e.g. x*e^x → exponential (exponential dominates polynomial co-factors)
|
|
355
|
+
// e.g. x^2*log(x) → polynomial (polynomial dominates logarithmic co-factors)
|
|
356
|
+
//
|
|
357
|
+
// However, if bounded factors are variable-dependent functions (like sin(1/x),
|
|
358
|
+
// cos(x)), we can't determine at classification time whether they approach 0 or
|
|
359
|
+
// a nonzero constant. A product like x*sin(1/x) looks polynomial-degree-1 but
|
|
360
|
+
// actually approaches 1 (not ∞). We return unknown in these cases to let the limit
|
|
361
|
+
// function handle it.
|
|
362
|
+
if (expr.isProduct()) {
|
|
363
|
+
const factors = expr.elementsArray().map(e => Expression_1.Expression.create(e));
|
|
364
|
+
let best = { type: 'bounded' };
|
|
365
|
+
let bestTier = 0;
|
|
366
|
+
let hasBoundedVariableFunction = false;
|
|
367
|
+
for (const factor of factors) {
|
|
368
|
+
const c = getInfinityGrowthClassification(factor, x);
|
|
369
|
+
if (c.type === 'unknown') {
|
|
370
|
+
return { type: 'unknown' };
|
|
371
|
+
}
|
|
372
|
+
if (c.type === 'bounded' && factor.hasVariable(x.text())) {
|
|
373
|
+
hasBoundedVariableFunction = true;
|
|
374
|
+
}
|
|
375
|
+
const t = growthTier(c);
|
|
376
|
+
if (t > bestTier) {
|
|
377
|
+
best = c;
|
|
378
|
+
bestTier = t;
|
|
379
|
+
}
|
|
380
|
+
else if (t === bestTier && c.type === 'polynomial' && best.type === 'polynomial') {
|
|
381
|
+
best = { type: 'polynomial', degree: best.degree + c.degree };
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (hasBoundedVariableFunction && bestTier > 0) {
|
|
385
|
+
return { type: 'unknown' };
|
|
386
|
+
}
|
|
387
|
+
return best;
|
|
388
|
+
}
|
|
389
|
+
// Logarithmic: log(f(x)) where f(x) grows unboundedly.
|
|
390
|
+
// If the argument approaches a finite value (e.g. log(1+1/x) where 1+1/x→1),
|
|
391
|
+
// then the whole expression is bounded, not logarithmic.
|
|
392
|
+
if (expr.isFunction(constants_1.LOG) && expr.getArguments()[0]?.hasVariable(x.text())) {
|
|
393
|
+
const argClass = getInfinityGrowthClassification(expr.getArguments()[0], x);
|
|
394
|
+
if (argClass.type === 'polynomial' ||
|
|
395
|
+
argClass.type === 'exponential' ||
|
|
396
|
+
argClass.type === 'logarithmic') {
|
|
397
|
+
return { type: 'logarithmic' };
|
|
398
|
+
}
|
|
399
|
+
// Argument is bounded (e.g. 1+1/x → 1) → log approaches a constant
|
|
400
|
+
return { type: 'bounded' };
|
|
401
|
+
}
|
|
402
|
+
if (expr.isPolynomialLike()) {
|
|
403
|
+
const poly = new Polynomial_1.Polynomial(expr);
|
|
404
|
+
return { type: 'polynomial', degree: poly.deg() };
|
|
405
|
+
}
|
|
406
|
+
// Variable or EXP with a constant power: classify by the sign of the power.
|
|
407
|
+
// Positive powers (x^(1/2), x^2, x^(3/2)) grow at infinity → polynomial tier.
|
|
408
|
+
// Negative powers (x^(-1), x^(-1/2)) vanish at infinity but are classified as
|
|
409
|
+
// polynomial with negative degree so that the product handler can correctly sum
|
|
410
|
+
// degrees (e.g. x^(-1) * (1+x) has effective degree 0, not degree 1).
|
|
411
|
+
// getDominantAtInfinity handles standalone negative-degree terms as dominated
|
|
412
|
+
// by constants.
|
|
413
|
+
if ((expr.isVAR() || expr.isEXP()) && !expr.getPower().hasVariable(x.text())) {
|
|
414
|
+
try {
|
|
415
|
+
const pow = expr.getPower();
|
|
416
|
+
// Use the Rational multiplier to get a proper numeric value.
|
|
417
|
+
// Number(text()) fails for rational strings like "1/2" since
|
|
418
|
+
// JS Number('1/2') returns NaN.
|
|
419
|
+
const m = pow.getMultiplier();
|
|
420
|
+
const powVal = Number(m.numerator) / Number(m.denominator);
|
|
421
|
+
if (isFinite(powVal) && powVal !== 0 && pow.isNUM()) {
|
|
422
|
+
return { type: 'polynomial', degree: powVal };
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
// Do nothing
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (expr.isFunction(constants_1.SIN) || expr.isFunction(constants_1.COS) || expr.isFunction(constants_1.ATAN)) {
|
|
430
|
+
return { type: 'bounded' };
|
|
431
|
+
}
|
|
432
|
+
return { type: 'unknown' };
|
|
433
|
+
}
|
|
434
|
+
function getAdditiveTerms(expr) {
|
|
435
|
+
const distributed = expr.distributeMultiplier();
|
|
436
|
+
if (!distributed.isSum()) {
|
|
437
|
+
return [distributed];
|
|
438
|
+
}
|
|
439
|
+
return distributed.elementsArray().map(e => Expression_1.Expression.create(e));
|
|
440
|
+
}
|
|
441
|
+
function getDominantAtInfinity(expr, x) {
|
|
442
|
+
const terms = getAdditiveTerms(expr);
|
|
443
|
+
let maxTier = -1;
|
|
444
|
+
let maxDegree = -Infinity;
|
|
445
|
+
const kept = [];
|
|
446
|
+
for (const term of terms) {
|
|
447
|
+
const classification = getInfinityGrowthClassification(term, x);
|
|
448
|
+
if (classification.type === 'unknown') {
|
|
449
|
+
return undefined;
|
|
450
|
+
}
|
|
451
|
+
// For additive dominance, polynomial terms with negative degree vanish
|
|
452
|
+
// at infinity (x^(-1) → 0), so they're dominated by bounded constants.
|
|
453
|
+
// Use an effective tier of -1 (below bounded) for these.
|
|
454
|
+
let tier = growthTier(classification);
|
|
455
|
+
if (classification.type === 'polynomial' && classification.degree < 0) {
|
|
456
|
+
tier = -1;
|
|
457
|
+
}
|
|
458
|
+
if (tier > maxTier) {
|
|
459
|
+
// This term's growth class strictly dominates everything up to this point
|
|
460
|
+
maxTier = tier;
|
|
461
|
+
maxDegree = classification.type === 'polynomial' ? classification.degree : -Infinity;
|
|
462
|
+
kept.length = 0;
|
|
463
|
+
kept.push(term);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
if (tier < maxTier) {
|
|
467
|
+
// This term is dominated — skip it
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
// Same tier — compare within tier
|
|
471
|
+
if (classification.type === 'polynomial') {
|
|
472
|
+
if (classification.degree > maxDegree) {
|
|
473
|
+
maxDegree = classification.degree;
|
|
474
|
+
kept.length = 0;
|
|
475
|
+
kept.push(term);
|
|
476
|
+
}
|
|
477
|
+
else if (classification.degree === maxDegree) {
|
|
478
|
+
kept.push(term);
|
|
479
|
+
}
|
|
480
|
+
// If degree < maxDegree, skip (dominated within polynomial tier)
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
// For exponential, logarithmic, or bounded tiers with same tier:
|
|
484
|
+
// keep all terms (let simplification handle the comparison downstream)
|
|
485
|
+
kept.push(term);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (kept.length === 0) {
|
|
489
|
+
return undefined;
|
|
490
|
+
}
|
|
491
|
+
let out = kept[0];
|
|
492
|
+
for (let i = 1; i < kept.length; i++) {
|
|
493
|
+
out = out.plus(kept[i]);
|
|
494
|
+
}
|
|
495
|
+
return (0, simplify_1.simplify)(out);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Try to resolve a limit at infinity when dominant terms are non-polynomial
|
|
499
|
+
* (exponential or logarithmic). Simplifies the ratio and recurses.
|
|
500
|
+
*
|
|
501
|
+
* Key reductions:
|
|
502
|
+
* e^f(x) / e^g(x) = e^(f(x)-g(x)) → recurse on the simplified form
|
|
503
|
+
* log(f(x)) / log(g(x)) → recurse on simplified form
|
|
504
|
+
* e^f(x) / polynomial → ±∞ (exponential dominates)
|
|
505
|
+
* polynomial / e^f(x) → 0 (exponential dominates)
|
|
506
|
+
* log(f(x)) / polynomial → 0 (polynomial dominates)
|
|
507
|
+
* polynomial / log(f(x)) → ±∞ (polynomial dominates)
|
|
508
|
+
*/
|
|
509
|
+
function tryNonPolynomialInfinityLimit(dominantNum, dominantDen, x, val, dir, depth) {
|
|
510
|
+
const numClass = getInfinityGrowthClassification(dominantNum, x);
|
|
511
|
+
const denClass = getInfinityGrowthClassification(dominantDen, x);
|
|
512
|
+
const numTier = growthTier(numClass);
|
|
513
|
+
const denTier = growthTier(denClass);
|
|
514
|
+
if (numTier < 0 || denTier < 0) {
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
// Different tiers: higher tier dominates
|
|
518
|
+
if (numTier > denTier) {
|
|
519
|
+
// Numerator grows faster → ±∞
|
|
520
|
+
if (numClass.type === 'exponential') {
|
|
521
|
+
const expLimit = limit(numClass.exponent, x, val, dir, depth + 1);
|
|
522
|
+
if (expLimit.isPosInf()) {
|
|
523
|
+
// e^(→+∞) / anything_slower → ±∞ based on denominator sign
|
|
524
|
+
const denSign = dominantDen.sign();
|
|
525
|
+
if (denSign > 0) {
|
|
526
|
+
return Expression_1.Expression.Inf();
|
|
527
|
+
}
|
|
528
|
+
if (denSign < 0) {
|
|
529
|
+
return Expression_1.Expression.NegInf();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (expLimit.isNegInf()) {
|
|
533
|
+
// e^(→-∞) → 0 regardless of denominator
|
|
534
|
+
return (0, shortcuts_1.zero)();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
// Polynomial over logarithmic or bounded → ±∞
|
|
538
|
+
// Sign determined by the numerator's leading coefficient behavior
|
|
539
|
+
if (numClass.type === 'polynomial' &&
|
|
540
|
+
(denClass.type === 'logarithmic' || denClass.type === 'bounded')) {
|
|
541
|
+
// For x→+∞, sign of x^n / log(x) is determined by sign of coefficient
|
|
542
|
+
// For x→-∞, also account for odd/even degree
|
|
543
|
+
const numSign = dominantNum.sign();
|
|
544
|
+
const denSign = dominantDen.sign();
|
|
545
|
+
if (numSign !== 0 && denSign !== 0) {
|
|
546
|
+
const resultSign = numSign * denSign;
|
|
547
|
+
return resultSign > 0 ? Expression_1.Expression.Inf() : Expression_1.Expression.NegInf();
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
if (numTier < denTier) {
|
|
553
|
+
// Denominator grows faster → 0
|
|
554
|
+
if (denClass.type === 'exponential') {
|
|
555
|
+
const expLimit = limit(denClass.exponent, x, val, dir, depth + 1);
|
|
556
|
+
if (expLimit.isPosInf()) {
|
|
557
|
+
return (0, shortcuts_1.zero)();
|
|
558
|
+
}
|
|
559
|
+
if (expLimit.isNegInf()) {
|
|
560
|
+
// e^(→-∞) in denominator → denominator→0, this is nonzero/zero
|
|
561
|
+
// Fall through to let other handlers deal with it
|
|
562
|
+
return undefined;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
// Logarithmic or bounded over polynomial or higher → 0
|
|
566
|
+
return (0, shortcuts_1.zero)();
|
|
567
|
+
}
|
|
568
|
+
// Same tier, both exponential: e^f / e^g = e^(f-g), recurse on that
|
|
569
|
+
if (numClass.type === 'exponential' && denClass.type === 'exponential') {
|
|
570
|
+
const exponentDiff = (0, simplify_1.simplify)(numClass.exponent.minus(denClass.exponent));
|
|
571
|
+
const expDiff = (0, math_1.exp)(exponentDiff);
|
|
572
|
+
// Carry through any multipliers from the original expressions
|
|
573
|
+
const numMultiplier = dominantNum.getMultiplier(true);
|
|
574
|
+
const denMultiplier = dominantDen.getMultiplier(true);
|
|
575
|
+
const coeff = (0, simplify_1.simplify)(numMultiplier.div(denMultiplier));
|
|
576
|
+
const reduced = (0, simplify_1.simplify)(coeff.times(expDiff));
|
|
577
|
+
return limit(reduced, x, val, dir, depth + 1);
|
|
578
|
+
}
|
|
579
|
+
// Same tier, both logarithmic: simplify the ratio and recurse
|
|
580
|
+
if (numClass.type === 'logarithmic' && denClass.type === 'logarithmic') {
|
|
581
|
+
const reduced = (0, simplify_1.simplify)(dominantNum.div(dominantDen));
|
|
582
|
+
if (!reduced.eq(dominantNum.div(dominantDen))) {
|
|
583
|
+
return limit(reduced, x, val, dir, depth + 1);
|
|
584
|
+
}
|
|
585
|
+
// L'Hôpital will handle log/log as ∞/∞
|
|
586
|
+
return undefined;
|
|
587
|
+
}
|
|
588
|
+
return undefined;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Try to resolve limit(f(g(x))) by computing the inner limit first.
|
|
592
|
+
*
|
|
593
|
+
* For function calls f(g(x)):
|
|
594
|
+
* 1. Compute L = limit(g(x))
|
|
595
|
+
* 2. If L is finite and not an unevaluated limit, return f(L)
|
|
596
|
+
* 3. If L is ±∞, handle known function behaviors at infinity
|
|
597
|
+
*
|
|
598
|
+
* For EXP nodes base^power where both may depend on x:
|
|
599
|
+
* 1. Compute limits of base and power separately
|
|
600
|
+
* 2. If both resolve, combine (but skip indeterminate forms — those
|
|
601
|
+
* are handled earlier by tryRewriteIndeterminate)
|
|
602
|
+
*
|
|
603
|
+
* This runs late in the pipeline to avoid interfering with indeterminate
|
|
604
|
+
* form rewriting (e.g. (1+1/x)^x must be caught by the 1^∞ handler first).
|
|
605
|
+
*/
|
|
606
|
+
function tryComposition(expr, x, val, dir, depth) {
|
|
607
|
+
// --- Named functions: sin, cos, exp, log, tan, atan, etc. ---
|
|
608
|
+
if (expr.isFunction() && !expr.isFunction(constants_1.LIMIT)) {
|
|
609
|
+
const args = expr.getArguments();
|
|
610
|
+
// Only handle functions whose arguments actually depend on x
|
|
611
|
+
if (args.length === 0 || !args.some(a => a.hasVariable(x.text()))) {
|
|
612
|
+
return undefined;
|
|
613
|
+
}
|
|
614
|
+
const name = expr.name;
|
|
615
|
+
// --- Piecewise functions: abs, sign ---
|
|
616
|
+
// These need special handling because they have different behaviors
|
|
617
|
+
// depending on the direction of approach, and evaluate(0) gives 0
|
|
618
|
+
// which loses the one-sided information.
|
|
619
|
+
if (name === constants_1.ABS || name === constants_1.SGN) {
|
|
620
|
+
const innerArg = args[0];
|
|
621
|
+
const innerLimit = limit(innerArg, x, val, dir, depth + 1);
|
|
622
|
+
if (innerLimit.isFunction(constants_1.LIMIT)) {
|
|
623
|
+
return undefined;
|
|
624
|
+
}
|
|
625
|
+
// If inner limit is nonzero, abs and sign are continuous — evaluate directly
|
|
626
|
+
if (!innerLimit.isZero()) {
|
|
627
|
+
try {
|
|
628
|
+
return (0, simplify_1.simplify)(Expression_1.Expression.toFunction(name, [innerLimit]));
|
|
629
|
+
}
|
|
630
|
+
catch {
|
|
631
|
+
return undefined;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// Inner limit is zero — behavior depends on direction
|
|
635
|
+
if (innerLimit.isZero()) {
|
|
636
|
+
if (name === constants_1.ABS) {
|
|
637
|
+
// abs(f(x)) → 0 regardless of direction when f(x) → 0
|
|
638
|
+
return (0, shortcuts_1.zero)();
|
|
639
|
+
}
|
|
640
|
+
if (name === constants_1.SGN) {
|
|
641
|
+
// sign(f(x)) as f(x) → 0: depends on which side f approaches from
|
|
642
|
+
if (dir === 'both') {
|
|
643
|
+
// Two-sided: sign changes, limit doesn't exist
|
|
644
|
+
return undefined;
|
|
645
|
+
}
|
|
646
|
+
// Determine the sign of the argument near the limit point
|
|
647
|
+
// by checking the derivative's sign at the point
|
|
648
|
+
try {
|
|
649
|
+
const innerDeriv = (0, diff_1.diff)(innerArg, x);
|
|
650
|
+
const derivAtVal = (0, simplify_1.simplify)(innerDeriv.subst(x, val));
|
|
651
|
+
const derivSign = derivAtVal.sign();
|
|
652
|
+
if (derivSign !== 0) {
|
|
653
|
+
// Argument is increasing (derivSign > 0) or decreasing (derivSign < 0) through zero
|
|
654
|
+
if (dir === 'right') {
|
|
655
|
+
return Expression_1.Expression.create(derivSign > 0 ? 1 : -1);
|
|
656
|
+
}
|
|
657
|
+
if (dir === 'left') {
|
|
658
|
+
return Expression_1.Expression.create(derivSign > 0 ? -1 : 1);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
return undefined;
|
|
664
|
+
}
|
|
665
|
+
return undefined;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return undefined;
|
|
669
|
+
}
|
|
670
|
+
// Compute the limit of each argument
|
|
671
|
+
const argLimits = [];
|
|
672
|
+
for (const arg of args) {
|
|
673
|
+
let argLimit;
|
|
674
|
+
try {
|
|
675
|
+
argLimit = limit(arg, x, val, dir, depth + 1);
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
return undefined;
|
|
679
|
+
}
|
|
680
|
+
// If any argument limit didn't resolve, bail
|
|
681
|
+
if (argLimit.isFunction(constants_1.LIMIT)) {
|
|
682
|
+
return undefined;
|
|
683
|
+
}
|
|
684
|
+
argLimits.push(argLimit);
|
|
685
|
+
}
|
|
686
|
+
const innerLimit = argLimits[0];
|
|
687
|
+
// Handle functions at ±∞ arguments
|
|
688
|
+
if (innerLimit.isPosInf()) {
|
|
689
|
+
switch (name) {
|
|
690
|
+
case constants_1.LOG:
|
|
691
|
+
return Expression_1.Expression.Inf();
|
|
692
|
+
case constants_1.ATAN:
|
|
693
|
+
return Expression_1.Expression.Pi().div((0, shortcuts_1.two)());
|
|
694
|
+
case constants_1.SQRT:
|
|
695
|
+
return Expression_1.Expression.Inf();
|
|
696
|
+
// sin, cos at ∞ → oscillates, no limit
|
|
697
|
+
case constants_1.SIN:
|
|
698
|
+
case constants_1.COS:
|
|
699
|
+
return undefined;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (innerLimit.isNegInf()) {
|
|
703
|
+
switch (name) {
|
|
704
|
+
case constants_1.ATAN:
|
|
705
|
+
return Expression_1.Expression.Pi().div((0, shortcuts_1.minusTwo)());
|
|
706
|
+
// log(-∞) is undefined in reals
|
|
707
|
+
case constants_1.LOG:
|
|
708
|
+
return undefined;
|
|
709
|
+
// sin, cos at -∞ → oscillates, no limit
|
|
710
|
+
case constants_1.SIN:
|
|
711
|
+
case constants_1.COS:
|
|
712
|
+
return undefined;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
// Finite inner limit: evaluate the outer function at the resolved value
|
|
716
|
+
try {
|
|
717
|
+
const rebuilt = Expression_1.Expression.toFunction(name, argLimits);
|
|
718
|
+
const result = (0, simplify_1.simplify)(rebuilt);
|
|
719
|
+
// Make sure we actually resolved it (didn't just get the same function back)
|
|
720
|
+
if (!result.isFunction(name)) {
|
|
721
|
+
return result;
|
|
722
|
+
}
|
|
723
|
+
// Try evaluate as a fallback
|
|
724
|
+
const evaluated = Expression_1.Expression.create(rebuilt.evaluate());
|
|
725
|
+
if (!evaluated.isFunction(name)) {
|
|
726
|
+
return evaluated;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
catch {
|
|
730
|
+
// evaluation failed — fall through
|
|
731
|
+
}
|
|
732
|
+
return undefined;
|
|
733
|
+
}
|
|
734
|
+
// --- EXP nodes: base^power where base or power depends on x ---
|
|
735
|
+
// Only handle non-indeterminate cases here. Indeterminate forms
|
|
736
|
+
// (1^∞, 0^0, ∞^0) are caught earlier by tryRewriteIndeterminate.
|
|
737
|
+
if (expr.isEXP()) {
|
|
738
|
+
const base = expr.getBase();
|
|
739
|
+
const pow = expr.getPower();
|
|
740
|
+
// Only proceed if at least one part depends on x
|
|
741
|
+
if (!base.hasVariable(x.text()) && !pow.hasVariable(x.text())) {
|
|
742
|
+
return undefined;
|
|
743
|
+
}
|
|
744
|
+
let baseLimit;
|
|
745
|
+
let powLimit;
|
|
746
|
+
try {
|
|
747
|
+
baseLimit = limit(base, x, val, dir, depth + 1);
|
|
748
|
+
powLimit = limit(pow, x, val, dir, depth + 1);
|
|
749
|
+
}
|
|
750
|
+
catch {
|
|
751
|
+
return undefined;
|
|
752
|
+
}
|
|
753
|
+
// If either didn't resolve, bail
|
|
754
|
+
if (baseLimit.isFunction(constants_1.LIMIT) || powLimit.isFunction(constants_1.LIMIT)) {
|
|
755
|
+
return undefined;
|
|
756
|
+
}
|
|
757
|
+
// Check for indeterminate forms — these should have been caught already,
|
|
758
|
+
// but guard against re-entry
|
|
759
|
+
const catBase = baseLimit.isPosInf() || baseLimit.isNegInf()
|
|
760
|
+
? 'inf'
|
|
761
|
+
: baseLimit.isZero()
|
|
762
|
+
? 'zero'
|
|
763
|
+
: baseLimit.eq((0, shortcuts_1.one)())
|
|
764
|
+
? 'one'
|
|
765
|
+
: 'finite';
|
|
766
|
+
const catPow = powLimit.isPosInf() || powLimit.isNegInf()
|
|
767
|
+
? 'inf'
|
|
768
|
+
: powLimit.isZero()
|
|
769
|
+
? 'zero'
|
|
770
|
+
: 'finite';
|
|
771
|
+
// Skip indeterminate forms (1^∞, 0^0, ∞^0)
|
|
772
|
+
if (catBase === 'one' && catPow === 'inf') {
|
|
773
|
+
return undefined;
|
|
774
|
+
}
|
|
775
|
+
if (catBase === 'zero' && catPow === 'zero') {
|
|
776
|
+
return undefined;
|
|
777
|
+
}
|
|
778
|
+
if (catBase === 'inf' && catPow === 'zero') {
|
|
779
|
+
return undefined;
|
|
780
|
+
}
|
|
781
|
+
// Determinate cases with infinities
|
|
782
|
+
if (baseLimit.isPosInf() && powLimit.isPosInf()) {
|
|
783
|
+
return Expression_1.Expression.Inf();
|
|
784
|
+
}
|
|
785
|
+
if (baseLimit.isPosInf() && powLimit.isNegInf()) {
|
|
786
|
+
return (0, shortcuts_1.zero)();
|
|
787
|
+
}
|
|
788
|
+
if (baseLimit.isZero() && powLimit.isPosInf()) {
|
|
789
|
+
return (0, shortcuts_1.zero)();
|
|
790
|
+
}
|
|
791
|
+
// finite^0 = 1 (nonzero finite base, zero power — not indeterminate)
|
|
792
|
+
if (catBase === 'finite' && catPow === 'zero') {
|
|
793
|
+
return (0, shortcuts_1.one)();
|
|
794
|
+
}
|
|
795
|
+
// Finite base and finite/zero power: compute directly
|
|
796
|
+
if ((catBase === 'finite' || catBase === 'one') &&
|
|
797
|
+
(catPow === 'finite' || catPow === 'zero')) {
|
|
798
|
+
try {
|
|
799
|
+
return (0, simplify_1.simplify)(baseLimit.pow(powLimit));
|
|
800
|
+
}
|
|
801
|
+
catch {
|
|
802
|
+
return undefined;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
// e^(+∞) = +∞, e^(-∞) = 0
|
|
806
|
+
if (baseLimit.isE()) {
|
|
807
|
+
if (powLimit.isPosInf()) {
|
|
808
|
+
return Expression_1.Expression.Inf();
|
|
809
|
+
}
|
|
810
|
+
if (powLimit.isNegInf()) {
|
|
811
|
+
return (0, shortcuts_1.zero)();
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return undefined;
|
|
815
|
+
}
|
|
816
|
+
// --- Sums: limit(a + b + ...) = limit(a) + limit(b) + ... ---
|
|
817
|
+
// when no indeterminate ∞ - ∞ forms arise.
|
|
818
|
+
if (expr.isSum()) {
|
|
819
|
+
const terms = expr.elementsArray().map(e => Expression_1.Expression.create(e));
|
|
820
|
+
// Only decompose if there are multiple terms that depend on x
|
|
821
|
+
if (terms.length < 2) {
|
|
822
|
+
return undefined;
|
|
823
|
+
}
|
|
824
|
+
const termLimits = [];
|
|
825
|
+
let hasPosInf = false;
|
|
826
|
+
let hasNegInf = false;
|
|
827
|
+
for (const term of terms) {
|
|
828
|
+
let termLimit;
|
|
829
|
+
try {
|
|
830
|
+
termLimit = limit(term, x, val, dir, depth + 1);
|
|
831
|
+
}
|
|
832
|
+
catch {
|
|
833
|
+
return undefined;
|
|
834
|
+
}
|
|
835
|
+
if (termLimit.isFunction(constants_1.LIMIT)) {
|
|
836
|
+
return undefined;
|
|
837
|
+
}
|
|
838
|
+
if (termLimit.isPosInf()) {
|
|
839
|
+
hasPosInf = true;
|
|
840
|
+
}
|
|
841
|
+
if (termLimit.isNegInf()) {
|
|
842
|
+
hasNegInf = true;
|
|
843
|
+
}
|
|
844
|
+
termLimits.push(termLimit);
|
|
845
|
+
}
|
|
846
|
+
// ∞ - ∞ is indeterminate — bail and let tryRewriteIndeterminate handle it
|
|
847
|
+
if (hasPosInf && hasNegInf) {
|
|
848
|
+
return undefined;
|
|
849
|
+
}
|
|
850
|
+
// All finite: sum them up
|
|
851
|
+
if (!hasPosInf && !hasNegInf) {
|
|
852
|
+
try {
|
|
853
|
+
let result = termLimits[0];
|
|
854
|
+
for (let i = 1; i < termLimits.length; i++) {
|
|
855
|
+
result = result.plus(termLimits[i]);
|
|
856
|
+
}
|
|
857
|
+
return (0, simplify_1.simplify)(result);
|
|
858
|
+
}
|
|
859
|
+
catch {
|
|
860
|
+
return undefined;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
// Has infinity but no cancellation: return the dominant infinity
|
|
864
|
+
if (hasPosInf && !hasNegInf) {
|
|
865
|
+
return Expression_1.Expression.Inf();
|
|
866
|
+
}
|
|
867
|
+
if (hasNegInf && !hasPosInf) {
|
|
868
|
+
return Expression_1.Expression.NegInf();
|
|
869
|
+
}
|
|
870
|
+
return undefined;
|
|
871
|
+
}
|
|
872
|
+
// --- Products: limit(a · b · ...) = limit(a) · limit(b) · ... ---
|
|
873
|
+
// when no indeterminate 0 · ∞ forms arise.
|
|
874
|
+
if (expr.isProduct()) {
|
|
875
|
+
const factors = expr.elementsArray(true).map(e => Expression_1.Expression.create(e));
|
|
876
|
+
if (factors.length < 2) {
|
|
877
|
+
return undefined;
|
|
878
|
+
}
|
|
879
|
+
const factorLimits = [];
|
|
880
|
+
let hasZero = false;
|
|
881
|
+
let hasInf = false;
|
|
882
|
+
for (const factor of factors) {
|
|
883
|
+
let factorLimit;
|
|
884
|
+
try {
|
|
885
|
+
factorLimit = limit(factor, x, val, dir, depth + 1);
|
|
886
|
+
}
|
|
887
|
+
catch {
|
|
888
|
+
return undefined;
|
|
889
|
+
}
|
|
890
|
+
if (factorLimit.isFunction(constants_1.LIMIT)) {
|
|
891
|
+
return undefined;
|
|
892
|
+
}
|
|
893
|
+
if (factorLimit.isZero()) {
|
|
894
|
+
hasZero = true;
|
|
895
|
+
}
|
|
896
|
+
if (factorLimit.isPosInf() || factorLimit.isNegInf()) {
|
|
897
|
+
hasInf = true;
|
|
898
|
+
}
|
|
899
|
+
factorLimits.push(factorLimit);
|
|
900
|
+
}
|
|
901
|
+
// 0 · ∞ is indeterminate — apply L'Hôpital directly.
|
|
902
|
+
// For f·g where f→0 and g→∞, rewrite as f/(1/g), both→0.
|
|
903
|
+
// L'Hôpital gives f'/(1/g)' = f'/(-g'/g²) = -f'·g²/g'.
|
|
904
|
+
// Compute this directly since Expression arithmetic recombines
|
|
905
|
+
// f/(1/g) back into f·g, defeating the rewrite.
|
|
906
|
+
if (hasZero && hasInf) {
|
|
907
|
+
let zeroIdx = -1;
|
|
908
|
+
let infIdx = -1;
|
|
909
|
+
for (let i = 0; i < factorLimits.length; i++) {
|
|
910
|
+
if (factorLimits[i].isZero() && zeroIdx === -1) {
|
|
911
|
+
zeroIdx = i;
|
|
912
|
+
}
|
|
913
|
+
else if ((factorLimits[i].isPosInf() || factorLimits[i].isNegInf()) &&
|
|
914
|
+
infIdx === -1) {
|
|
915
|
+
infIdx = i;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (zeroIdx !== -1 && infIdx !== -1) {
|
|
919
|
+
const f = factors[zeroIdx]; // → 0
|
|
920
|
+
const g = factors[infIdx]; // → ∞
|
|
921
|
+
try {
|
|
922
|
+
const fPrime = (0, diff_1.diff)(f, x);
|
|
923
|
+
const gPrime = (0, diff_1.diff)(g, x);
|
|
924
|
+
if (!gPrime.isZero()) {
|
|
925
|
+
// L'Hôpital on f/(1/g): result = -f'·g²/g'
|
|
926
|
+
let lhopitalExpr = (0, simplify_1.simplify)(fPrime.times(g.pow(2)).div(gPrime).neg());
|
|
927
|
+
// Multiply back any remaining factors
|
|
928
|
+
for (let i = 0; i < factors.length; i++) {
|
|
929
|
+
if (i !== zeroIdx && i !== infIdx) {
|
|
930
|
+
lhopitalExpr = lhopitalExpr.times(factors[i]);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return limit(lhopitalExpr, x, val, dir, depth + 1);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
catch {
|
|
937
|
+
return undefined;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return undefined;
|
|
941
|
+
}
|
|
942
|
+
// All finite (including zeros): multiply them
|
|
943
|
+
if (!hasInf) {
|
|
944
|
+
try {
|
|
945
|
+
let result = factorLimits[0];
|
|
946
|
+
for (let i = 1; i < factorLimits.length; i++) {
|
|
947
|
+
result = result.times(factorLimits[i]);
|
|
948
|
+
}
|
|
949
|
+
return (0, simplify_1.simplify)(result);
|
|
950
|
+
}
|
|
951
|
+
catch {
|
|
952
|
+
return undefined;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
// Has zero with no infinity: result is zero
|
|
956
|
+
if (hasZero && !hasInf) {
|
|
957
|
+
return (0, shortcuts_1.zero)();
|
|
958
|
+
}
|
|
959
|
+
// Has infinity with no zero: determine sign
|
|
960
|
+
if (hasInf && !hasZero) {
|
|
961
|
+
let sign = 1;
|
|
962
|
+
for (const fl of factorLimits) {
|
|
963
|
+
if (fl.isNegInf()) {
|
|
964
|
+
sign *= -1;
|
|
965
|
+
}
|
|
966
|
+
else if (fl.isPosInf()) {
|
|
967
|
+
// sign unchanged
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
// Finite nonzero factor: multiply sign
|
|
971
|
+
const s = fl.sign();
|
|
972
|
+
if (s === 0) {
|
|
973
|
+
return (0, shortcuts_1.zero)();
|
|
974
|
+
}
|
|
975
|
+
sign *= s;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return sign > 0 ? Expression_1.Expression.Inf() : Expression_1.Expression.NegInf();
|
|
979
|
+
}
|
|
980
|
+
return undefined;
|
|
981
|
+
}
|
|
982
|
+
return undefined;
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Computes a symbolic finite or infinite limit.
|
|
986
|
+
*
|
|
987
|
+
* The implementation combines direct substitution, side-aware pole analysis,
|
|
988
|
+
* simplification, bounded L'Hopital recursion, indeterminate-form rewrites,
|
|
989
|
+
* composition rules, dominant-growth analysis at infinity, and a table of
|
|
990
|
+
* known limits.
|
|
991
|
+
*
|
|
992
|
+
* @remarks
|
|
993
|
+
* `'left'` and `'right'` request one-sided limits; `'both'` is the default
|
|
994
|
+
* two-sided direction. Direction is significant for poles and piecewise
|
|
995
|
+
* functions such as `sign`. When the available strategies cannot establish a
|
|
996
|
+
* result, the function returns an unevaluated `limit(expr, x, val, dir)`
|
|
997
|
+
* Expression. That form can therefore mean unsupported, undefined, or
|
|
998
|
+
* non-existent; it is not a proof that a limit exists.
|
|
999
|
+
*
|
|
1000
|
+
* The implementation primarily reasons about real signs and real growth.
|
|
1001
|
+
* Oscillatory and branch-sensitive complex cases are intentionally left
|
|
1002
|
+
* symbolic unless a specific rule resolves them. Decimal input is preserved in
|
|
1003
|
+
* the formatting of a resolved result.
|
|
1004
|
+
*
|
|
1005
|
+
* `depth` is an internal recursion counter and should normally be omitted.
|
|
1006
|
+
*
|
|
1007
|
+
* @param expr - Expression whose limit is sought.
|
|
1008
|
+
* @param x - Independent variable.
|
|
1009
|
+
* @param val - Finite value or positive/negative infinity to approach.
|
|
1010
|
+
* @param dir - Two-sided, left-sided, or right-sided approach.
|
|
1011
|
+
* @param depth - Internal recursion depth. Defaults to zero.
|
|
1012
|
+
* @returns The resolved limit or an unevaluated `limit(...)` Expression.
|
|
1013
|
+
*/
|
|
1014
|
+
function limit(expr, x, val, dir = 'both', depth = 0) {
|
|
1015
|
+
let retval = undefined;
|
|
1016
|
+
expr = Expression_1.Expression.create(expr);
|
|
1017
|
+
x = Expression_1.Expression.create(x);
|
|
1018
|
+
val = Expression_1.Expression.create(val);
|
|
1019
|
+
const asDecimal = expr.hasDecimal();
|
|
1020
|
+
// Global recursion depth guard — last line of defense against infinite recursion.
|
|
1021
|
+
// Hidden from the user (depth defaults to 0 in the public signature).
|
|
1022
|
+
if (depth >= LIMIT_MAX_DEPTH) {
|
|
1023
|
+
return Expression_1.Expression.toFunction(constants_1.LIMIT, [expr, x, val, dir]);
|
|
1024
|
+
}
|
|
1025
|
+
if (val.isPosInf() || val.isNegInf()) {
|
|
1026
|
+
// Resolve additive limits term-by-term before dropping asymptotically smaller terms.
|
|
1027
|
+
// This preserves finite constants when another term decays, e.g. e^(-x)+2 -> 2.
|
|
1028
|
+
if (expr.isSum()) {
|
|
1029
|
+
retval = tryComposition(expr, x, val, dir, depth);
|
|
1030
|
+
}
|
|
1031
|
+
const dominantNumerator = !retval
|
|
1032
|
+
? getDominantAtInfinity(expr.getNumerator(), x)
|
|
1033
|
+
: undefined;
|
|
1034
|
+
const dominantDenominator = !retval
|
|
1035
|
+
? getDominantAtInfinity(expr.getDenominator(), x)
|
|
1036
|
+
: undefined;
|
|
1037
|
+
if (dominantNumerator &&
|
|
1038
|
+
dominantDenominator &&
|
|
1039
|
+
dominantNumerator.isPolynomialLike() &&
|
|
1040
|
+
dominantDenominator.isPolynomialLike()) {
|
|
1041
|
+
const numPoly = new Polynomial_1.Polynomial(dominantNumerator);
|
|
1042
|
+
const denPoly = new Polynomial_1.Polynomial(dominantDenominator);
|
|
1043
|
+
const degNum = numPoly.deg();
|
|
1044
|
+
const degDen = denPoly.deg();
|
|
1045
|
+
const degDiff = degNum - degDen;
|
|
1046
|
+
if (degDiff < 0) {
|
|
1047
|
+
retval = (0, shortcuts_1.zero)();
|
|
1048
|
+
}
|
|
1049
|
+
else if (degDiff === 0) {
|
|
1050
|
+
retval = (0, simplify_1.simplify)(Expression_1.Expression.create(numPoly.LC()).div(Expression_1.Expression.create(denPoly.LC())));
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
let sign = Expression_1.Expression.create(numPoly.LC())
|
|
1054
|
+
.div(Expression_1.Expression.create(denPoly.LC()))
|
|
1055
|
+
.sign();
|
|
1056
|
+
if (val.isNegInf() && degDiff % 2 !== 0) {
|
|
1057
|
+
sign *= -1;
|
|
1058
|
+
}
|
|
1059
|
+
if (sign > 0) {
|
|
1060
|
+
retval = Expression_1.Expression.Inf();
|
|
1061
|
+
}
|
|
1062
|
+
else if (sign < 0) {
|
|
1063
|
+
retval = Expression_1.Expression.NegInf();
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
// Fallback polynomial-tier comparison using our growth classifier.
|
|
1068
|
+
// Handles cases like x^(-1/2) which are polynomial-tier but fail isPolynomialLike().
|
|
1069
|
+
// Bounded constants are treated as polynomial degree 0 for this comparison.
|
|
1070
|
+
if (!retval && dominantNumerator && dominantDenominator) {
|
|
1071
|
+
const numClass = getInfinityGrowthClassification(dominantNumerator, x);
|
|
1072
|
+
const denClass = getInfinityGrowthClassification(dominantDenominator, x);
|
|
1073
|
+
const numDeg = numClass.type === 'polynomial'
|
|
1074
|
+
? numClass.degree
|
|
1075
|
+
: numClass.type === 'bounded'
|
|
1076
|
+
? 0
|
|
1077
|
+
: undefined;
|
|
1078
|
+
const denDeg = denClass.type === 'polynomial'
|
|
1079
|
+
? denClass.degree
|
|
1080
|
+
: denClass.type === 'bounded'
|
|
1081
|
+
? 0
|
|
1082
|
+
: undefined;
|
|
1083
|
+
if (numDeg !== undefined && denDeg !== undefined) {
|
|
1084
|
+
const degDiff = numDeg - denDeg;
|
|
1085
|
+
if (degDiff < 0) {
|
|
1086
|
+
retval = (0, shortcuts_1.zero)();
|
|
1087
|
+
}
|
|
1088
|
+
else if (degDiff > 0) {
|
|
1089
|
+
const numSign = dominantNumerator.sign();
|
|
1090
|
+
const denSign = dominantDenominator.sign();
|
|
1091
|
+
let sign = numSign * denSign;
|
|
1092
|
+
if (val.isNegInf() && degDiff % 2 !== 0) {
|
|
1093
|
+
sign *= -1;
|
|
1094
|
+
}
|
|
1095
|
+
if (sign > 0) {
|
|
1096
|
+
retval = Expression_1.Expression.Inf();
|
|
1097
|
+
}
|
|
1098
|
+
else if (sign < 0) {
|
|
1099
|
+
retval = Expression_1.Expression.NegInf();
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
// degDiff === 0 is already handled by the isPolynomialLike() branch above
|
|
1103
|
+
// via leading coefficient comparison, which needs Polynomial construction.
|
|
1104
|
+
// If we reach here with degDiff === 0, fall through to other handlers.
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
// Try non-polynomial dominant term comparison (exponential, logarithmic)
|
|
1108
|
+
if (!retval && dominantNumerator && dominantDenominator) {
|
|
1109
|
+
retval = tryNonPolynomialInfinityLimit(dominantNumerator, dominantDenominator, x, val, dir, depth);
|
|
1110
|
+
}
|
|
1111
|
+
if (!retval && dominantNumerator && dominantDenominator) {
|
|
1112
|
+
const reduced = dominantNumerator.div(dominantDenominator);
|
|
1113
|
+
if (!reduced.eq(expr)) {
|
|
1114
|
+
retval = limit(reduced, x, val, dir, depth);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
// Try rewriting indeterminate forms at infinity too
|
|
1118
|
+
// (e.g. (1+1/x)^x → 1^∞ form as x→∞)
|
|
1119
|
+
if (!retval) {
|
|
1120
|
+
const rewritten = tryRewriteIndeterminate(expr, x, val, dir, depth);
|
|
1121
|
+
if (rewritten) {
|
|
1122
|
+
retval = rewritten;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
// Try compositional limit: f(g(x)) → f(limit(g(x)))
|
|
1126
|
+
if (!retval) {
|
|
1127
|
+
retval = tryComposition(expr, x, val, dir, depth);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
// Intercept piecewise functions (abs, sign) before classifyAtPoint,
|
|
1132
|
+
// since regular evaluation at the limit point loses direction information.
|
|
1133
|
+
// e.g. sign(0) = 0 but lim sign(x) as x→0+ = 1.
|
|
1134
|
+
if (expr.isFunction(constants_1.SGN) || expr.isFunction(constants_1.ABS)) {
|
|
1135
|
+
const piecewiseResult = tryComposition(expr, x, val, dir, depth);
|
|
1136
|
+
if (piecewiseResult !== undefined) {
|
|
1137
|
+
retval = piecewiseResult;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
if (!retval) {
|
|
1141
|
+
const classification = classifyAtPoint(expr, x, val);
|
|
1142
|
+
if (classification === 'regular') {
|
|
1143
|
+
retval = Expression_1.Expression.create(expr.evaluate({ [x.text()]: val }));
|
|
1144
|
+
}
|
|
1145
|
+
// Nonzero numerator, zero denominator → ±∞ based on direction
|
|
1146
|
+
if (!retval && classification === 'nonzero_over_zero') {
|
|
1147
|
+
retval = tryNonzeroOverZero(expr, x, val, dir);
|
|
1148
|
+
}
|
|
1149
|
+
if (!retval && classification === 'zero_over_zero') {
|
|
1150
|
+
const simplified = (0, simplify_1.simplify)(expr);
|
|
1151
|
+
if (!simplified.eq(expr)) {
|
|
1152
|
+
retval = limit(simplified, x, val, dir, depth);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
// Try rewriting indeterminate forms (0·∞, ∞-∞, 1^∞, 0^0, ∞^0)
|
|
1156
|
+
// into 0/0 or ∞/∞ so L'Hôpital or simplification can resolve them.
|
|
1157
|
+
if (!retval) {
|
|
1158
|
+
const rewritten = tryRewriteIndeterminate(expr, x, val, dir, depth);
|
|
1159
|
+
if (rewritten) {
|
|
1160
|
+
retval = rewritten;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
if (!retval) {
|
|
1164
|
+
const lh = tryLHopital(expr, x, val, dir, depth);
|
|
1165
|
+
if (lh) {
|
|
1166
|
+
retval = lh;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
// Try compositional limit: f(g(x)) → f(limit(g(x)))
|
|
1170
|
+
if (!retval) {
|
|
1171
|
+
retval = tryComposition(expr, x, val, dir, depth);
|
|
1172
|
+
}
|
|
1173
|
+
} // end if (!retval) after piecewise check
|
|
1174
|
+
}
|
|
1175
|
+
if (!retval) {
|
|
1176
|
+
retval = limitsTable_1.tableOfLimits.lookup(new Pattern_1.Pattern(expr, [x.text()]), 0);
|
|
1177
|
+
}
|
|
1178
|
+
retval ??= Expression_1.Expression.toFunction(constants_1.LIMIT, [expr, x, val, dir]);
|
|
1179
|
+
if (asDecimal) {
|
|
1180
|
+
retval.getMultiplier().asDecimal = true;
|
|
1181
|
+
}
|
|
1182
|
+
return retval;
|
|
1183
|
+
}
|