@stemcmicro/core
Version:
Computer Algebra System in TypeScript
1,633 lines (1,601 loc) • 965 kB
JavaScript
/**
* @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)