UNPKG

@stemcmicro/core

Version:

Computer Algebra System in TypeScript

1,633 lines (1,601 loc) 965 kB
/** * @stemcmicro/core 0.9.79 * (c) David Geo Holmes david.geo.holmes@gmail.com * Released under the MIT License. */ import { native_sym, Native, is_native, log as log$1, multiply as multiply$1, real as real$1, exp as exp$1 } from '@stemcmicro/native'; export { NATIVE_MAX, NATIVE_MIN, Native, code_from_native_sym, is_native_sym, native_sym } from '@stemcmicro/native'; import { create_sym, is_boo as is_boo$1, Flt, negOne, is_num as is_num$1, is_rat, imu, is_flt as is_flt$1, Rat, bigInt, is_tensor, zero, one, Tensor, create_int, BigInteger, is_sym as is_sym$1, is_str as is_str$2, is_err, is_uom, is_blade, is_hyp as is_hyp$1, is_keyword, is_imu, Err, Cell, Map as Map$1, create_flt as create_flt$1, create_str as create_str$1, create_tensor, et, booT, epsilon, Str, piAsFlt as piAsFlt$1, eAsFlt, two, four, half, negFour, three, nine, third, eight, Sym, Boo, assert_tensor, Hyp, create_tensor_elements_diagonal, is_map, is_tag, is_jsobject, create_boo, JsObject, assert_jsobject, assert_sym as assert_sym$1, booU, Uom, QQ, create_rat, zeroAsFlt as zeroAsFlt$1, is_cell, assert_str, assert_map, assert_cell, assert_flt, create_hyp, booF, assert_rat, Keyword, create_tensor_elements, oneAsFlt as oneAsFlt$1 } from '@stemcmicro/atoms'; import { diagnostic, Diagnostics, is_localizable, Localizable } from '@stemcmicro/diagnostics'; import { str_to_string, isone as isone$1, is_add as is_add$1, is_multiply as is_multiply$1, is_power as is_power$1, guess, is_num_and_negative, is_rat_and_integer, is_num_and_eq_rational, is_cons_opr_eq_power, is_num_and_eq_number, is_num_and_eq_one_half, multiply_items, negate, subst, add as add$1, divide as divide$1, is_mul_2_any_any, num_to_number, multiply, power, inverse, subtract as subtract$1, iszero, is_negative, is_cons_opr_eq_multiply, compare_num_num, is_cons_opr_eq_add, is_cons_opr_eq_sym, is_outer, is_inner_or_dot, lt_num_num, is_cons_opr_eq_inv, is_base_of_natural_logarithm, is_pi, is_num_and_eq_two, count_factors, contains_single_blade, abs as abs$1, is_factorial as is_factorial$1, is_rat_and_fraction as is_rat_and_fraction$1, convertMetricToNative, convert_tensor_to_strings, create_algebra_as_tensor, predicate_return_value, prolog_eval_varargs, float, is_safe_integer_range, is_opr_2_any_any } from '@stemcmicro/helpers'; export { create_algebra_as_blades } from '@stemcmicro/helpers'; import { is_atom, nil, is_nil, is_cons, cadr as cadr$1, caddr as caddr$1, cadnr, items_to_cons, cdr, car, cons, assert_cons as assert_cons$1, assert_U as assert_U$1, assert_cons_or_nil as assert_cons_or_nil$1, cddr as cddr$1, cdddr, caadr as caadr$1, cadadr, is_cons2, cadddr as cadddr$1, is_singleton } from '@stemcmicro/tree'; import { Lambda, SIGN_GT as SIGN_GT$1, SIGN_LT as SIGN_LT$1, SIGN_EQ as SIGN_EQ$1, is_lambda } from '@stemcmicro/context'; import { Directive } from '@stemcmicro/directive'; import { StackU, Stack } from '@stemcmicro/stack'; import { scan_meta } from '@stemcmicro/em-parse'; import { complexity, conjfunc, inner as inner$1, push_rational, power as power$1, make_stack_draw, value_of, stopf, pop_integer, multiply as multiply$2, expfunc, add as add$2, sqrtfunc, subtract as subtract$2, negate as negate$1, push_integer, multiply_factors as multiply_factors$1, power_e_expo, stack_infix, stack_kronecker, stack_log, stack_mag, stack_mod, stack_noexpand, stack_outer, stack_rank, stack_arccosh, stack_arcsin, stack_arcsinh, stack_arctan, stack_arctanh, stack_arg, stack_binding, stack_circexp, stack_clock, stack_cos, stack_cosh, stack_exp, stack_expcos, stack_expcosh, stack_expsin, stack_expsinh, stack_exptan, stack_exptanh, stack_floor, stack_hadamard, stack_imag, stack_index, stack_inv, stack_minor, stack_minormatrix, stack_nroots, stack_polar, stack_rationalize, stack_real, stack_rect, stack_sin, stack_sinh, stack_sqrt, stack_testlt, stack_testge, stack_testgt, stack_transpose, stack_unit } from '@stemcmicro/eigenmath'; const SIGN_LT = -1; const SIGN_EQ = 0; const SIGN_GT = 1; /** * The expression was ignored by the transformer, usually because it did not match the transformer. */ const TFLAG_NONE = 0; /** * The expression changed as a result of the transformation. */ const TFLAG_DIFF = 1 << 0; /** * The expression did not change as a result of the transformation because it is stable. */ const TFLAG_HALT = 1 << 1; /** * Returns true if flags has the "diff" bit set. */ function diffFlag(flags) { return (flags & TFLAG_DIFF) === TFLAG_DIFF; } const ALL_FEATURES = ["Blade", "Boo", "Cell", "Flt", "Imu", "Map", "Rat", "Sym", "Tensor", "Uom"]; function directive_from_flag(value) { if (typeof value === "boolean") { return value ? 1 : 0; } else { return 0; } } const MODE_EXPANDING = 1; const MODE_FACTORING = 2; const MODE_SEQUENCE = [MODE_EXPANDING, MODE_FACTORING]; const MODE_FLAGS_ALL = MODE_EXPANDING | MODE_FACTORING; const PHASE_FLAGS_EXPANDING_UNION_FACTORING = MODE_EXPANDING | MODE_FACTORING; let Builder$k = class Builder { extension; constructor(extension) { this.extension = extension; } create(config) { return new this.extension(config); } }; function mkbuilder(extension) { return new Builder$k(extension); } /** * A convenience function for constructing transform results and peforming correct reference counting. * If the expressions are the same then we return the oldExpr (it may have better positional metadata), and TFLAG_NONE. * If the expressions differ, we return newExpr and TFLAG_DIFF. * */ function wrap_as_transform(newExpr, oldExpr) { if (newExpr.equals(oldExpr)) { // If the expressions have the same value, we return the oldExpr because it may have better meta information // such as pos and end properties. oldExpr.addRef(); return [TFLAG_NONE, oldExpr]; } else { newExpr.addRef(); return [TFLAG_DIFF, newExpr]; } } class ProgrammingError extends Error { constructor(message) { super(); this.name = "ProgrammingError"; if (typeof message === "string") { this.message = message; } } } /* eslint-disable @typescript-eslint/no-unused-vars */ class AtomExtensionFromExprHandler { handler; #type; #guard; constructor(handler, type, guard) { this.handler = handler; this.#type = type; this.#guard = guard; } get hash() { return this.#type; } get name() { return this.#type; } iscons() { return false; } operator() { throw new ProgrammingError(); } isKind(expr) { if (is_atom(expr)) { return this.#guard(expr); } else { return false; } } toHumanString(expr, $) { return str_to_string(this.handler.dispatch(expr, native_sym(Native.human), nil, $)); } toInfixString(expr, $) { return str_to_string(this.handler.dispatch(expr, native_sym(Native.infix), nil, $)); } toLatexString(expr, $) { return str_to_string(this.handler.dispatch(expr, native_sym(Native.latex), nil, $)); } toListString(expr, $) { return str_to_string(this.handler.dispatch(expr, native_sym(Native.sexpr), nil, $)); } evaluate(opr, argList, $) { throw new Error("evaluate method not implemented."); } transform(expr, env) { const newExpr = this.valueOf(expr, env); try { return wrap_as_transform(newExpr, expr); } finally { newExpr.release(); } } valueOf(expr, env) { return this.dispatch(expr, create_sym("valueof"), nil, env); } binL(lhs, opr, rhs, env) { return this.handler.binL(lhs, opr, rhs, env); } binR(rhs, opr, lhs, env) { return this.handler.binR(rhs, opr, lhs, env); } dispatch(target, opr, argList, env) { const response = this.handler.dispatch(target, opr, argList, env); if (is_nil(response)) { return diagnostic(Diagnostics.Property_0_does_not_exist_on_type_1, opr, create_sym(target.type)); } else { return response; } } subst(expr, oldExpr, newExpr, env) { throw new Error("subst method not implemented."); } test(expr, opr, env) { const response = this.handler.dispatch(expr, opr, nil, env); if (is_boo$1(response)) { return response.isTrue(); } else { throw diagnostic(Diagnostics.Property_0_does_not_exist_on_type_1, opr, create_sym(expr.type)); } } } class AtomExtensionBuilderFromExprHandlerBuilder { builder; type; guard; constructor(builder, type, guard) { this.builder = builder; this.type = type; this.guard = guard; } create(config) { return new AtomExtensionFromExprHandler(this.builder.create(), this.type, this.guard); } } /** * */ class ExtensionFromExprHandler { handler; constructor(handler) { this.handler = handler; } get hash() { throw new Error("Method not implemented."); } get name() { throw new Error("Method not implemented."); } phases; dependencies; iscons() { throw new Error("Method not implemented."); } operator() { throw new Error("Method not implemented."); } isKind(expr, $) { throw new Error("Method not implemented."); } subst(expr, oldExpr, newExpr, $) { throw new Error("Method not implemented."); } toHumanString(expr, $) { throw new Error("Method not implemented."); } toInfixString(expr, $) { throw new Error("Method not implemented."); } toLatexString(expr, $) { throw new Error("Method not implemented."); } toListString(expr, $) { throw new Error("Method not implemented."); } evaluate(opr, argList, $) { throw new Error("Method not implemented."); } transform(expr, $) { throw new Error("Method not implemented."); } valueOf(expr, $) { throw new Error("Method not implemented."); } test(atom, opr, env) { return this.handler.test(atom, opr, env); } binL(lhs, opr, rhs, env) { return this.handler.binL(lhs, opr, rhs, env); } binR(rhs, opr, lhs, env) { return this.handler.binR(rhs, opr, lhs, env); } // eslint-disable-next-line @typescript-eslint/no-unused-vars dispatch(target, opr, argList, env) { return this.handler.dispatch(target, opr, argList, env); } } /** * Constructs a floating point number object from a number primitive. * @param value The floating point number value. * @param pos The start position of the number in the source text. * @param end The end position of the number in the source text. */ function create_flt(value, pos, end) { if (value === zeroAsFlt.d) { return zeroAsFlt; } if (value === oneAsFlt.d) { return oneAsFlt; } if (value === negOneAsFlt.d) { return negOneAsFlt; } if (value === twoAsFlt.d) { return twoAsFlt; } if (value === negTwoAsFlt.d) { return negTwoAsFlt; } // console.lg("wrap_as_flt", value); return new Flt(value, pos, end); } const zeroAsFlt = new Flt(0.0); const oneAsFlt = new Flt(1.0); const twoAsFlt = new Flt(2.0); const piAsFlt = new Flt(Math.PI); new Flt(1e-6); new Flt(Math.E); const negOneAsFlt = new Flt(-1.0); const negTwoAsFlt = new Flt(-2.0); // The canonical keys for various mathematical symbols. // These MUST be unique. // These MUST be stable over time. // Think of these as a universal standard identifying mathematical standards. // Implementations do not need to couple to this file, but they should use it as a standard. // TODO: Don't need to define functions; these are pluggable and should not be centrally defined. const MATH_ADD$1 = native_sym(Native.add); const MATH_COS = native_sym(Native.cos); const MATH_TAN = native_sym(Native.tan); native_sym(Native.subtract); const MATH_MUL$2 = native_sym(Native.multiply); native_sym(Native.divide); const MATH_POW$1 = native_sym(Native.pow); const MATH_OUTER = native_sym(Native.outer); const MATH_INNER = native_sym(Native.inner); const MATH_INV = native_sym(Native.inv); const MATH_LCO = native_sym(Native.lco); const MATH_RCO = native_sym(Native.rco); const MATH_SIN$1 = native_sym(Native.sin); native_sym(Native.succ); native_sym(Native.pred); native_sym(Native.E); const MATH_PI$1 = native_sym(Native.PI); const MATH_FACTORIAL = native_sym(Native.factorial); native_sym(Native.iszero); native_sym(Native.testlt); native_sym(Native.testgt); native_sym(Native.testle); /** * ':' */ create_sym(":"); /** * tau(x) = 2 * pi * x */ native_sym(Native.tau); const MATH_IMU$1 = native_sym(Native.IMU); // // WARNING This module should not depend on anything. // The imports below are for types only and will not create a dependency. // var PrintMode; (function (PrintMode) { /** * Two-dimensional rendering. */ PrintMode[PrintMode["Ascii"] = 0] = "Ascii"; /** * Like infix but with extra whitespace and may have multiplication operators removed. */ PrintMode[PrintMode["Human"] = 1] = "Human"; /** * Infix is how we normally write math but whitespace is removed and may be parsed by a computer. */ PrintMode[PrintMode["Infix"] = 2] = "Infix"; /** * MathJax compatible. */ PrintMode[PrintMode["LaTeX"] = 3] = "LaTeX"; /** * Symbolic Expression is LISP-like. */ PrintMode[PrintMode["SExpr"] = 4] = "SExpr"; PrintMode[PrintMode["EcmaScript"] = 5] = "EcmaScript"; })(PrintMode || (PrintMode = {})); class Defs { constructor() { // Nothing to see here yet. } /** * top of stack */ tos = 0; /** * The program execution stack. * TODO: This should be moved to the $ to achieve isolation of executions. * It should also not allow undefined and null values as this requires casting elsewhere. * Encapsulation with assertion may help. */ stack = []; } /** * Global (singleton) instance of Defs. */ const defs = new Defs(); /** * This should only be used for scripting when the stack is being used. * Otherwise, there should be a convenient way to throw structured Error(s). */ function halt(s) { move_top_of_stack(0); throw new Error(`Stop: ${s}`); } function move_top_of_stack(stackPos) { if (defs.tos <= stackPos) { // we are moving the stack pointer // "up" the stack (as if we were doing a push) defs.tos = stackPos; return; } // we are moving the stack pointer // "down" the stack i.e. as if we were // doing a pop, we can zero- // out all the elements that we pass // so we can reclaim the memory while (defs.tos > stackPos) { defs.stack[defs.tos] = null; defs.tos--; } } function noexpand_unary(func, arg, $) { $.pushDirective(Directive.expanding, 0); try { return func(arg, $); } finally { $.popDirective(); } } function noexpand_binary(func, lhs, rhs, $) { $.pushDirective(Directive.expanding, 0); try { return func(lhs, rhs, $); } finally { $.popDirective(); } } function doexpand_unary(func, arg, $) { $.pushDirective(Directive.expanding, 1); try { return func(arg, $); } finally { $.popDirective(); } } function doexpand_binary(func, lhs, rhs, $) { $.pushDirective(Directive.expanding, 1); try { return func(lhs, rhs, $); } finally { $.popDirective(); } } /** * */ class DynamicConstants { static NegOne($) { return $.getDirective(Directive.evaluatingAsFloat) ? negOneAsFlt : negOne; } static PI($) { return $.getDirective(Directive.evaluatingAsFloat) ? piAsFlt : MATH_PI$1; } } function length_of_cons_otherwise_zero(expr) { return is_cons(expr) ? [...expr].length : 0; } const do_simplify_nested_radicals = true; // TODO: Migrate to a situation of only creating these on demand by extensions. const ADD$9 = native_sym(Native.add); create_sym("adj"); create_sym("algebra"); const AND = create_sym("and"); const APPROXRATIO = create_sym("approxratio"); const ARCCOS$1 = create_sym("arccos"); const ARCCOSH = create_sym("arccosh"); const ARCSIN = create_sym("arcsin"); const ARCSINH = create_sym("arcsinh"); const ARCTAN = create_sym("arctan"); const ARCTANH = create_sym("arctanh"); create_sym("atomize"); const BESSELJ = create_sym("besselj"); const BESSELY = create_sym("bessely"); create_sym("binding"); const BINOMIAL = create_sym("binomial"); const CEILING = create_sym("ceiling"); const CHECK = create_sym("check"); const CHOOSE = create_sym("choose"); native_sym(Native.circexp); const CLEAR = create_sym("clear"); create_sym("clearall"); create_sym("clearpatterns"); const COEFF = create_sym("coeff"); create_sym("cofactor"); create_sym("compare"); create_sym("compare-factors"); create_sym("compare-terms"); const COMPONENT$2 = native_sym(Native.component); const CONDENSE = native_sym(Native.condense); const CONTRACT = create_sym("contract"); const COS$2 = MATH_COS; const COSH = create_sym("cosh"); create_sym("decomp"); const DEFINT = create_sym("defint"); create_sym("denominator"); const DET$1 = create_sym("det"); const DIM = create_sym("dim"); const DIRAC = create_sym("dirac"); create_sym("divide"); create_sym("divisors"); const DO = create_sym("do"); const DOT = create_sym("dot"); create_sym("draw"); create_sym("dsolve"); const EIGEN = create_sym("eigen"); const EIGENVAL = create_sym("eigenval"); const EIGENVEC = create_sym("eigenvec"); create_sym("equal"); const ERF = create_sym("erf"); const ERFC = create_sym("erfc"); /** * 'eval' */ const EVAL = create_sym("eval"); const EXP$3 = native_sym(Native.exp); const EXPAND$1 = native_sym(Native.expand); create_sym("expcos"); native_sym(Native.factor); const FACTORIAL = MATH_FACTORIAL; create_sym("factorpoly"); const FLOAT = native_sym(Native.float); const FLOOR = create_sym("floor"); /** * (fn [params*] expr*) */ const FN = native_sym(Native.fn); const FOR = create_sym("for"); /** * (Sym("function") body paramList) * * Notice that the syntax is different from ClojureScript, which is (fn params body), and params is a Tensor of symbols. */ const FUNCTION = native_sym(Native.function); const GAMMA = create_sym("gamma"); const GCD = native_sym(Native.gcd); const HERMITE = native_sym(Native.hermite); native_sym(Native.hilbert); create_sym("if"); const IMAG$1 = native_sym(Native.imag); native_sym(Native.inner); const INTEGRAL = native_sym(Native.integral); const INV$1 = MATH_INV; create_sym("invg"); create_sym("isinteger"); const ISPRIME = native_sym(Native.isprime); const LAGUERRE = create_sym("laguerre"); create_sym("laplace"); const LCM = create_sym("lcm"); native_sym(Native.lco); const LEGENDRE = create_sym("legendre"); const LET$1 = create_sym("let"); const LOG$1 = native_sym(Native.log); const MULTIPLY$2 = MATH_MUL$2; native_sym(Native.not); create_sym("nroots"); create_sym("number"); create_sym("numerator"); const OPERATOR = create_sym("operator"); const OR = create_sym("or"); native_sym(Native.outer); const PATTERN = create_sym("pattern"); native_sym(Native.polar); const POWER$2 = MATH_POW$1; const PRINT_LEAVE_E_ALONE = create_sym("printLeaveEAlone"); const PRINT_LEAVE_X_ALONE = create_sym("printLeaveXAlone"); create_sym("print"); const PRODUCT = create_sym("product"); const QUOTE = create_sym("quote"); create_sym("quotient"); const RANK = create_sym("rank"); native_sym(Native.rco); const REAL$1 = native_sym(Native.real); const ISREAL$3 = native_sym(Native.isreal); const ROUND$1 = create_sym("round"); native_sym(Native.rect); create_sym("roots"); const ASSIGN = native_sym(Native.assign); const SGN = create_sym("sgn"); const SIN = MATH_SIN$1; const SINH = create_sym("sinh"); create_sym("shape"); const SQRT$1 = create_sym("sqrt"); create_sym("stop"); native_sym(Native.subst); create_sym("-"); const SUM = create_sym("sum"); const TAN = MATH_TAN; const TANH = create_sym("tanh"); const TAYLOR = native_sym(Native.taylor); const TEST = native_sym(Native.test); const TESTGE = native_sym(Native.testge); const TESTGT = native_sym(Native.testgt); const TESTLE = native_sym(Native.testle); const TESTLT = native_sym(Native.testlt); const TRANSPOSE = create_sym("transpose"); const UNIT = create_sym("unit"); const UOM = create_sym("uom"); native_sym(Native.zero); create_sym("lastPrint"); create_sym("lastAsciiPrint"); create_sym("lastInfixPrint"); create_sym("lastLatexPrint"); create_sym("lastSexprPrint"); create_sym("lastHumanPrint"); create_sym("autoexpand"); create_sym("autofactor"); const BAKE = create_sym("bake"); create_sym("trace"); const METAA = create_sym("$METAA"); const METAB = create_sym("$METAB"); const METAX = create_sym("$METAX"); const SECRETX = create_sym("$SECRETX"); create_sym("version"); /** * 'a' */ create_sym("a"); create_sym("b"); create_sym("c"); /** * 'd' is commonly used for the derivative. */ const SYMBOL_D = create_sym("d"); const SYMBOL_I = create_sym("i"); const SYMBOL_J = create_sym("j"); create_sym("n"); create_sym("r"); const SYMBOL_S = create_sym("s"); const SYMBOL_T = create_sym("t"); /** * x */ const SYMBOL_X = create_sym("x"); const SYMBOL_Y = create_sym("y"); const SYMBOL_Z = create_sym("z"); const SYMBOL_IDENTITY_MATRIX = create_sym("I"); const SYMBOL_A_UNDERSCORE = create_sym("a_"); const SYMBOL_B_UNDERSCORE = create_sym("b_"); const SYMBOL_X_UNDERSCORE = create_sym("x_"); create_sym("$C1"); create_sym("$C2"); create_sym("$C3"); create_sym("$C4"); create_sym("$C5"); create_sym("$C6"); const MAXPRIMETAB = 10000; //define _USE_MATH_DEFINES // for MS C++ const MAXDIM = 24; const primetab = (function () { const primes = [2]; let i = 3; while (primes.length < MAXPRIMETAB) { let j = 0; const ceil = Math.sqrt(i); while (j < primes.length && primes[j] <= ceil) { if (i % primes[j] === 0) { j = -1; break; } j++; } if (j !== -1) { primes.push(i); } i += 2; } primes[MAXPRIMETAB] = 0; return primes; })(); function MSIGN(p) { if (p.isZero()) { return 0; } if (p.isPositive()) { return 1; } return -1; } /** * p.equals(n) */ function MEQUAL(p, n) { return p.equals(n); } // // TODO: In order not to torture our future selves, these should be documented and have coverage unit testing. // Maybe document the matching patterns? // Using block comments makes it possible to compose these with logical operators. // /** * The expression must be a Rat or Flt, otherwise the return value is false. * TODO: Interesting that this required expr > 0, when positive usually includes zero. * @param expr The expression being tested. */ function is_num_and_gt_zero(expr) { if (is_rat(expr)) { return MSIGN(expr.a) === 1; } else if (is_flt$1(expr)) { return expr.d > 0.0; } else { return false; } } /** * */ function is_num_and_eq_minus_one(p) { if (is_num$1(p)) { return p.isMinusOne(); } else { return false; } } /** * */ function is_plus_or_minus_one(x, $) { return isone$1(x, $) || is_num_and_eq_minus_one(x); } // -------------------------------------- function isunivarpolyfactoredorexpandedform(p, x) { // console.lg("isunivarpolyfactoredorexpandedform", `${p}`, `${x}`); if (x == null) { x = guess(p); } if (is_poly_factored_or_expanded_form(p, x) && countTrue(p.contains(SYMBOL_X), p.contains(SYMBOL_Y), p.contains(SYMBOL_Z)) === 1) { return x; } else { return false; } } function countTrue(...a) { // Number(true) = 1 return a.reduce((count, x) => count + Number(x), 0); } // -------------------------------------- // sometimes we want to check if we have a poly in our // hands, however it's in factored form and we don't // want to expand it. function is_poly_factored_or_expanded_form(p, x) { return is_poly_factored_or_expanded_form_factor(p, x); } function is_poly_factored_or_expanded_form_factor(p, x) { if (is_multiply$1(p)) { return p.tail().every((el) => { const bool = is_poly_factored_or_expanded_form_power(el, x); return bool; }); } else { return is_poly_factored_or_expanded_form_power(p, x); } } function is_poly_factored_or_expanded_form_power(p, x) { if (is_power$1(p)) { const base = p.base; const expo = p.expo; return is_rat(expo) && expo.isPositiveInteger() && is_poly_expanded_form_expr(base, x); } else { return is_poly_expanded_form_expr(p, x); } } // -------------------------------------- /** * Determines whether the expression, p, is a polynomial in the variable, x. */ function is_poly_expanded_form(p, x) { // console.lg(`is_poly_expanded_form ${print_expr(p, $)} ${x}`); if (p.contains(x)) { return is_poly_expanded_form_expr(p, x); } else { // If the expression does not contain the variable then it's a non-starter. return false; } } function is_poly_expanded_form_expr(p, x) { // console.lg(`is_poly_expanded_form_expr ${print_expr(p, $)} ${x}`); if (is_add$1(p)) { return p.tail().every((term) => is_poly_expanded_form_term(term, x)); } else { return is_poly_expanded_form_term(p, x); } } function is_poly_expanded_form_term(p, x) { // console.lg(`is_poly_expanded_form_term ${print_expr(p, $)} ${x}`); if (is_multiply$1(p)) { return p.tail().every((factor) => is_poly_expanded_form_factor(factor, x)); } else { return is_poly_expanded_form_factor(p, x); } } // eslint-disable-next-line @typescript-eslint/no-unused-vars function is_poly_expanded_form_factor(p, x) { // console.lg(`is_poly_expanded_form_factor ${print_expr(p, $)} ${x}`); if (p.equals(x)) { return true; } if (is_power$1(p) && p.base.equals(x)) { const expo = p.expo; return is_rat(expo) && expo.isPositiveInteger(); } if (p.contains(x)) { return false; } else { return true; } } function is_power_and_has_rational_exponent_and_negative_base(expr) { if (is_power$1(expr)) { const expo = expr.expo; if (is_rat(expo)) { if (is_num_and_negative(expr.base)) { return true; } } } return false; } function isimaginarynumberdouble(p, $) { return (is_multiply$1(p) && length_of_cons_otherwise_zero(p) === 3 && is_flt$1(cadnr(p, 1)) && is_power_and_has_rational_exponent_and_negative_base(cadnr(p, 2))) || $.equals(p, imu); } /** * (multiply Num i) */ function is_imaginary_number(expr) { if (is_multiply$1(expr)) { // (multiply a1 a2 a3 ...) if (length_of_cons_otherwise_zero(expr) === 3) { // (multiply x y) if (is_num$1(cadr$1(expr)) && caddr$1(expr).equals(imu)) { // (multiply Num i) return true; } if (expr.equals(imu)) { // Probbaly dead code because i => (pow -1 1/2), which isn't a (multiply ) return true; } if (is_power_and_has_rational_exponent_and_negative_base(caddr$1(expr))) { return true; } } } return false; } function iscomplexnumberdouble(p, $) { return (is_add$1(p) && length_of_cons_otherwise_zero(p) === 3 && is_flt$1(cadr$1(p)) && isimaginarynumberdouble(caddr$1(p), $)) || isimaginarynumberdouble(p, $); } /** * Determines whether expr is of the form (+ Num (something times i)) * For this to work it is crucial that the complex number terms be arranged with * the real part on the left hand side. e.g. 1.0 + 2.0*i. */ function is_complex_number(expr) { // console.lg(`is_complex_number ${render_as_sexpr(expr, $)}`); if (is_add$1(expr)) { // console.lg(`${$.toInfixString(expr)} is an add expression`); const n = length_of_cons_otherwise_zero(expr); // console.lg(`${$.toInfixString(expr)} n = ${n}`); if (n === 3) { const x = cadr$1(expr); // console.lg(`${$.toInfixString(expr)} X = ${$.toInfixString(X)}`); if (is_num$1(x)) { if (is_imaginary_number(caddr$1(expr)) || is_imaginary_number(expr)) { return true; } } } } return false; } function is_rat_and_even_integer(expr) { return is_rat_and_integer(expr) && expr.a.isEven(); } function isNumberOneOverSomething(p) { return is_rat(p) && p.isFraction() && MEQUAL(p.a.abs(), 1); } /** * */ function is_rat_and_fraction(p) { return is_rat(p) && p.isFraction(); } // p == -1/2 ? function is_num_and_equal_minus_half(p) { return is_num_and_eq_rational(p, -1, 2); } /** * * @param x * @returns */ function is_one_over_sqrt_two(x) { // 1/sqrt(2) = (power sqrt(2) -1) = (power (power 2 -1/2) -1) if (is_cons(x) && is_cons_opr_eq_power(x)) { return is_power$1(x) && is_num_and_eq_number(cadr$1(x), 2) && is_num_and_eq_rational(caddr$1(x), -1, 2); } else { return false; } } function is_minus_one_over_sqrt_two(p) { return is_multiply$1(p) && is_num_and_eq_number(cadr$1(p), -1) && is_one_over_sqrt_two(caddr$1(p)) && length_of_cons_otherwise_zero(p) === 3; } // Check if the value is sqrt(3)/2 function isSqrtThreeOverTwo(p) { return is_multiply$1(p) && is_num_and_eq_one_half(cadr$1(p)) && isSqrtThree(caddr$1(p)) && length_of_cons_otherwise_zero(p) === 3; } // Check if the value is -sqrt(3)/2 function isMinusSqrtThreeOverTwo(p) { return is_multiply$1(p) && is_num_and_equal_minus_half(cadr$1(p)) && isSqrtThree(caddr$1(p)) && length_of_cons_otherwise_zero(p) === 3; } // Check if value is sqrt(3) function isSqrtThree(p) { return is_power$1(p) && is_num_and_eq_number(cadr$1(p), 3) && is_num_and_eq_one_half(caddr$1(p)); } function contains_floating_values_or_floatf(expr) { // TODO: This probably needs to go into extension types as well. if (is_flt$1(expr) || FLOAT.equals(expr)) { return true; } if (is_cons(expr)) { return [...expr].some(contains_floating_values_or_floatf); } return false; } /** * Constructs a binary expression and evaluates it. * The arguments are not evaluated. * @param opr The symbol in the position of the zeroth element of the list. * @param lhs The expression in the first element of the list. * @param rhs The expression in the second element of the list. */ function binop(opr, lhs, rhs, $) { const expr = items_to_cons(opr, lhs, rhs); try { return $.valueOf(expr); } finally { expr.release(); } } function multiply_binary(lhs, rhs, _) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const hook = function (retval, description) { return retval; }; return hook(binop(MATH_MUL$2, lhs, rhs, _)); } // this is useful for example when you are just adding/removing // factors from an already factored quantity. // e.g. if you factored x^2 + 3x + 2 into (x+1)(x+2) // and you want to divide by (x+1) , i.e. you multiply by (x-1)^-1, // then there is no need to expand. function multiply_noexpand(arg1, arg2, _) { return noexpand_binary(multiply_binary, arg1, arg2, _); } // n an integer function multiply_items_factoring(items, _) { _.pushDirective(Directive.factoring, 1); try { return multiply_items(items, _); } finally { _.popDirective(); } } function negate_noexpand(p1, _) { function neg(expr, $) { return negate($, expr); } return noexpand_unary(neg, p1, _); } function mmul(a, b) { return a.multiply(b); } function mdiv(a, b) { return a.divide(b); } function mmod(a, b) { return a.mod(b); } // return both quotient and remainder of a/b // we'd have this method as divmod(number) // but obviously doesn't change the passed parameters function mdivrem(a, b) { const toReturn = a.divmod(b); return [toReturn.quotient, toReturn.remainder]; } function mint(a) { return bigInt(a); } function makePositive(a) { if (a.isNegative()) { return a.multiply(bigInt(-1)); } return a; } function makeSignSameAs(a, b) { if (a.isPositive()) { if (b.isNegative()) { return a.multiply(bigInt(-1)); } } else { // a is negative if (b.isPositive()) { return a.multiply(bigInt(-1)); } } return a; } function setSignTo(a, b) { if (a.isPositive()) ; else { // a is negative { return a.multiply(bigInt(-1)); } } return a; } function bignum_truncate(p1) { const a = mdiv(p1.a, p1.b); return new Rat(a, bigInt.one); } // expo is an integer function bignum_power_number(base, expo) { const abs_expo = Math.abs(expo); let a = base.a.pow(abs_expo); let b = base.b.pow(abs_expo); if (expo < 0) { // swap a and b const t = a; a = b; b = t; a = makeSignSameAs(a, b); b = setSignTo(b); } return new Rat(a, b); } function rational(a, b) { // `as any as number` cast added because bigInt(number) and bigInt(BigInteger) // are both accepted signatures, but bigInt(number|BigInteger) is not return new Rat(bigInt(a), bigInt(b)); } function nativeDouble(p1) { if (is_rat(p1)) { return p1.toNumber(); } else if (is_flt$1(p1)) { return p1.toNumber(); } else { return 0; } } // n is an int function bignum_factorial(n) { return new Rat(__factorial(n), bigInt.one); } // n is an int function __factorial(n) { let a; if (n === 0 || n === 1) { a = bigInt(1); return a; } a = bigInt(2); let b = bigInt(0); if (3 <= n) { for (let i = 3; i <= n; i++) { b = bigInt(i); a = mmul(a, b); } } return a; } function complex_conjugate(expr, _) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const hook = function (retval, where) { // console.lg(`conj of ${$.toInfixString(z)} => ${$.toInfixString(retval)} (${where})`); return retval; }; const minus_i = negate(_, imu); const z_star = subst(expr, imu, minus_i, _); return hook(_.valueOf(z_star)); } const EQUALS = native_sym(Native.testeq); /** * WARNING: Be careful using this function. It will construct (testeq lhs rhs), which can lead to strange treks to simplify. * In general it shuld be used sparingly. */ function equals(lhs, rhs, env) { const raw = items_to_cons(EQUALS, lhs, rhs); try { return env.valueOf(raw); } finally { raw.release(); } } const RECT$1 = native_sym(Native.rect); function rect(arg, env) { const raw = items_to_cons(RECT$1, arg); try { return env.valueOf(raw); } finally { raw.release(); } } function subtract(lhs, rhs, _) { const hook = function (retval) { return retval; }; const A = negate(_, rhs); const B = add$1(_, lhs, A); return hook(B); } /* Remove terms that involve a given symbol or expression. For example... filter(x^2 + x + 1, x) => 1 filter(x^2 + x + 1, x^2) => x + 1 */ function eval_filter(p1, $) { p1 = cdr(p1); let result = $.valueOf(car(p1)); if (is_cons(p1)) { result = p1.tail().reduce((acc, p) => filter(acc, $.valueOf(p), $), result); } return result; } /** * Filter out terms in the polynomial f that contain x. * In other words, return only the constant part of f when x is the variable. * @param F The polynomial. * @param X The variable. */ function filter(F, X, $) { return filter_main(F, X, $); } function filter_main(F, X, $) { if (is_add$1(F)) { return filter_sum(F, X, $); } if (is_tensor(F)) { return filter_tensor(F, X, $); } if (F.contains(X)) { return zero; } return F; } function filter_sum(F, X, $) { return F.tail().reduce((a, b) => $.add(a, filter(b, X, $)), zero); } function filter_tensor(F, X, $) { return F.map((f) => filter(f, X, $)); } function divide_expand(lhs, rhs, $) { $.pushDirective(Directive.expanding, 1); try { return divide$1(lhs, rhs, $); } finally { $.popDirective(); } } /** * (coeff p x n) * * Returns the coefficient of x^n in polynomial p. The x argument can be omitted for polynomials in x. */ function eval_coeff(expr, $) { const p = $.valueOf(expr.item1); let x = $.valueOf(expr.item2); let n = $.valueOf(expr.item3); if (nil.equals(n)) { // only 2 args? n = x; x = SYMBOL_X; } // divide p by x^n, keep the constant term (the term not containing x) const x_pow_n = $.power(x, n); const p_div_x_pow_n = divide$1(p, x_pow_n, $); const k = filter(p_div_x_pow_n, x, $); return k; } //----------------------------------------------------------------------------- // // Get polynomial coefficients // // Input: p(x) (the polynomial) // // x (the variable) // // Output: Returns the array of coefficients: // // [Coefficient of x^0, ..., Coefficient of x^(n-1)] // //----------------------------------------------------------------------------- /** * The coefficients of the polynomial expression are returned in an array in the order [a0, a1, a2, ..., an]. * * There are no gaps. */ function coefficients(expr, x, $) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const hook = function (retval, description) { return retval; }; const coefficients = new StackU(); let p = expr; // eslint-disable-next-line no-constant-condition while (true) { const c = $.valueOf(subst(p, x, zero, $)); coefficients.push(c); if (p.equals(c)) { // Optimization is if p == c then result is zero. p = zero; } else { p = $.valueOf(subtract(p, c, $)); } if (p.equals(zero)) { // This appears to be the only way we get out of the loop. try { return hook(coefficients.elements, "A"); } finally { coefficients.release(); } } // We should get the same result through division, although I would be concerned that // x/x depends upon whether x is non-zero. In future, our analysis may reflect that by // returning a conditioned expression. We may even try to do better than this by seeing if // p has the factor x on the right (in some form or other). // Certainly, this shortcut avoids a lot of computation of coefficients etc. if (p.equals(x)) { p = one; } else if (is_cons(p) && is_mul_2_any_any_and_rhs_equals(p, x)) { p = p.lhs; } else { p = divide_expand(p, x, $); p = $.valueOf(p); } } } function is_mul_2_any_any_and_rhs_equals(expr, x) { if (is_mul_2_any_any(expr)) { return expr.rhs.equals(x); } else { return false; } } /** * Sorts an array of factors while respecting whether the factors are scalars (can they commute?). * @param factors The unsorted array of factors. WARNING: This array may be reordered in future implementations. * @param $ * @returns A new array containing the sorted factors. */ function sort_factors(factors, $) { const sortable = factors.map(function (value, index) { return { value, index }; }); sortable.sort(function (x, y) { const x_comp_y = $.compareFn(native_sym(Native.multiply))(x.value, y.value); // If either side is a scalar then we are allowed to take the canonical reordering as is. if ($.isscalar(x.value) || $.isscalar(y.value)) { return x_comp_y; } else { // If neither are scalars then keep the order stable by sorting based on original index. return x.index - y.index; } }); // TODO: It's tempting to copy const sorted = sortable.map(function (elem) { return elem.value; }); return sorted; } function signum(n) { if (n < 0) { return -1; } else if (n > 0) { return 1; } else { return 0; } } //----------------------------------------------------------------------------- // // Generate all divisors of a term // // Input: Term (factor * factor * ...) // // Output: Divisors // //----------------------------------------------------------------------------- function divisors(term, $) { const factors = ydivisors(term, $); const n = factors.length; return new Tensor([n], sort_factors(factors, $)); } function ydivisors(term, $) { const stack = []; // push all of the term's factors if (is_num$1(term)) { stack.push(...factor_small_number(num_to_number(term))); } else if (is_cons(term) && is_add$1(term)) { stack.push(...__factor_add(term, $)); } else if (is_multiply$1(term)) { let p1 = cdr(term); if (is_num$1(car(p1))) { stack.push(...factor_small_number(num_to_number(car(p1)))); p1 = cdr(p1); } if (is_cons(p1)) { const mapped = [...p1].map((p2) => { if (is_power$1(p2)) { return [cadr$1(p2), caddr$1(p2)]; } return [p2, one]; }); stack.push(...mapped.flat()); } } else if (is_power$1(term)) { stack.push(cadr$1(term), caddr$1(term)); } else { stack.push(term, one); } const k = stack.length; // contruct divisors by recursive descent stack.push(one); gen(stack, 0, k, $); return stack.slice(k); } //----------------------------------------------------------------------------- // // Generate divisors // // Input: Base-exponent pairs on stack // // h first pair // // k just past last pair // // Output: Divisors on stack // // For example, factor list 2 2 3 1 results in 6 divisors, // // 1 // 3 // 2 // 6 // 4 // 12 // //----------------------------------------------------------------------------- function gen(stack, h, k, _) { const ACCUM = stack.pop(); if (h === k) { stack.push(ACCUM); return; } const BASE = stack[h + 0]; const EXPO = stack[h + 1]; const expo = num_to_number(EXPO); if (!isNaN(expo)) { for (let i = 0; i <= Math.abs(expo); i++) { stack.push(multiply(_, ACCUM, power(_, BASE, create_int(signum(expo) * i)))); gen(stack, h + 2, k, _); } } } //----------------------------------------------------------------------------- // // Factor ADD expression // // Input: Expression // // Output: Factors // // Each factor consists of two expressions, the factor itself followed // by the exponent. // //----------------------------------------------------------------------------- function __factor_add(p1, $) { // get gcd of all terms const temp1 = is_cons(p1) ? p1.tail().reduce(function (x, y) { return gcd(x, y, $); }) : car(p1); const stack = []; // check gcd let p2 = temp1; if (isone$1(p2, $)) { stack.push(p1, one); return stack; } // push factored gcd if (is_num$1(p2)) { stack.push(...factor_small_number(num_to_number(p2))); } else if (is_multiply$1(p2)) { const p3 = cdr(p2); if (is_num$1(car(p3))) { stack.push(...factor_small_number(num_to_number(car(p3)))); } else { stack.push(car(p3), one); } if (is_cons(p3)) { p3.tail().forEach((p) => stack.push(p, one)); } } else { stack.push(p2, one); } // divide each term by gcd p2 = inverse(p2, $); const temp2 = is_cons(p1) ? p1.tail().reduce((a, b) => add$1($, a, multiply($, p2, b)), zero) : cdr(p1); stack.push(temp2, one); return stack; } // Find the least common multiple of two expressions. function eval_lcm(p1, $) { p1 = cdr(p1); let result = $.valueOf(car(p1)); if (is_cons(p1)) { result = p1.tail().reduce((a, b) => lcm(a, $.valueOf(b), $), result); } return result; } function lcm(p1, p2, $) { return doexpand_binary(yylcm, p1, p2, $); } function yylcm(p1, p2, $) { const A = gcd(p1, p2, $); const B = divide$1(A, p1, $); const C = divide$1(B, p2, $); return inverse(C, $); } /** * (quotient p q x) * * @returns the quotient of the polynomial p(x) over q(x). * * The remainder can be calculated by p - q * quotient(p,q) */ function eval_quotient(expr, $) { const p = $.valueOf(expr.item1); const q = $.valueOf(expr.item2); const X = $.valueOf(expr.item3); if (!X.isnil) { return quotient(p, q, X, $); } else { return quotient(p, q, SYMBOL_X, $); } } function quotient(p, q, X, _) { const dividendCs = coefficients(p, X, _); let m = dividendCs.length - 1; // m is dividend's highest power const divisorCs = coefficients(q, X, _); const n = divisorCs.length - 1; // n is divisor's highest power let x = m - n; let retval = zero; while (x >= 0) { const Q = divide$1(dividendCs[m], divisorCs[n], _); for (let i = 0; i <= n; i++) { dividendCs[x + i] = subtract$1(_, dividendCs[x + i], multiply(_, divisorCs[i], Q)); } retval = add$1(_, retval, multiply(_, Q, power(_, X, create_int(x)))); m--; x--; } return retval; } /** * Use this exclusively for failures of the system due programming errors. */ class SystemError extends Error { name = "SystemError"; /** * Constructs a SystemError using the message. The stack will be logged to the console. */ constructor(message) { super(message); // eslint-disable-next-line no-console console.warn(message, new Error().stack); } } /** * @deprecated */ function stack_push(expr) { // console.lg(`push(${expr})`); if (typeof expr === "undefined") { throw new Error("expr must be defined."); } defs.stack[defs.tos++] = expr; } /** * @deprecated */ function stack_pop() { if (defs.tos === 0) { throw new SystemError("stack underflow"); } const popped = defs.stack[--defs.tos]; defs.stack[defs.tos] = null; // No need to do any reference counting stuff if it is a symbol because the receiver // of the return value now owns the thing. // console.lg(`pop ${popped}`); return popped; } /** * @deprecated */ function stack_push_items(items) { while (items.length > 0) { stack_push(items.shift()); } } /** * * @param P polynomial expression * @param X polynomial variable * @param $ * @returns factored polynomial */ function factor_polynomial(P, X, $) { // console.lg("yyfactorpoly", `${($ as ExtensionEnv).toInfixString(P)}`); // eslint-disable-next-line @typescript-eslint/no-unused-vars const hook = function (retval, description) { // console.lg("retval", ($ as ExtensionEnv).toInfixString(retval), description); return retval; }; if (contains_floating_values_or_floatf(P)) { throw new Error("floating point numbers in polynomial"); } const cs = coefficients(P, X, $); for (let i = 0; i < cs.length; i++) { // console.lg(`cs[${i}] => `, `${cs[i]}`); } // WARNING: This mutates the coefficients array argument with the scaling as the return value. const scaling = rationalize_coefficients(cs, $); // console.lg("scaling => ", ($ as ExtensionEnv).toInfixString(scaling)); for (let i = 0; i < cs.length; i++) { // console.lg(`cs[${i}] => `, `${cs[i]}`); } // console.lg("k", ($ as ExtensionEnv).toInfixString(k)); // console.lg(`rationalized coes ${coes}, with k = ${p7}`); // We start out by looking for real roots. let kind = "ℝ"; let remainingPoly = null; // TODO: What are these values... let a; let b; let p8; // We start from the largest coefficient. let coeffIdx = cs.length - 1; let k = scaling; while (coeffIdx > 0) { // console.lg("coeffIdx", coeffIdx); let foundCRoot = false; let foundRRoot = false; if (iszero(cs[0], $)) { a = one; b = zero; } else { if (kind === "ℝ") { [foundRRoot, a, b] = get_factor_from_real_root(cs, coeffIdx, X, a, b, $); // console.lg("foundRRoot", foundRRoot, ($ as ExtensionEnv).toInfixString(a), ($ as ExtensionEnv).toInfixString(b)); } else if (kind === "ℂ") { [foundCRoot, a] = get_factor_from_complex_root(remainingPoly, cs, coeffIdx, $); // console.lg("foundComplexRoot", foundRealRoot); // console.lg("p4", p4 ? render_as_infix(p4, $) : "undefined"); } } // console.lg(`whichRootsAreWeFinding ${whichRootsAreWeFinding}`); // console.lg(`foundRealRoot ${foundRealRoot}`); if (kind === "ℝ") { if (foundRRoot === false) { kind = "ℂ"; continue; } else { p8 = add$1($, multiply($, a, X), b); // A, x, B // factor out negative sign (not req'd because p4 > 1)