*storybook.log
storybook-static
*.code-workspace
+
+# Vitest browser mode failure artifacts
+.vitest-attachments/
+tests/**/__screenshots__/
/build/
/.svelte-kit/
test-results
+
+# Vendored third party sources, kept byte identical to upstream
+src/lib/vendors/
'.svelte-kit/**',
'test-results/**',
'.storybook/**/*',
- 'src/lib/services/sandbox-worker.js'
+ 'src/lib/services/sandbox-worker.js',
+ 'src/lib/vendors/**'
]
},
storybook.configs['flat/recommended']
--- /dev/null
+import { build } from 'esbuild';
+import { dirname, resolve } from 'path';
+import { fileURLToPath } from 'url';
+import type { Plugin } from 'vite';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+const VENDORS_DIR = resolve(__dirname, '../src/lib/vendors');
+const VIRTUAL_ID = 'virtual:nerdamer';
+const RESOLVED_ID = '\0' + VIRTUAL_ID;
+
+/**
+ * Bundle the vendored nerdamer-prime source into a minified IIFE string,
+ * exposed as the `virtual:nerdamer` module. Flags mirror the upstream
+ * build (esbuild --bundle --minify --format=iife --global-name=nerdamer),
+ * so only human readable source lives in the repo and minification is a
+ * build artifact. Vendored under src/lib/vendors/, upstream snapshot:
+ * https://github.com/together-science/nerdamer-prime/commit/1936145f8af306ec0d883b9bfd7730aedd175c24
+ */
+export function nerdamerPlugin(): Plugin {
+ let bundled: string | null = null;
+
+ return {
+ name: 'llamacpp:nerdamer',
+ resolveId(id) {
+ return id === VIRTUAL_ID ? RESOLVED_ID : undefined;
+ },
+ async load(id) {
+ if (id !== RESOLVED_ID) return undefined;
+ if (bundled === null) {
+ const result = await build({
+ entryPoints: [resolve(VENDORS_DIR, 'nerdamer-prime/all.js')],
+ bundle: true,
+ minify: true,
+ format: 'iife',
+ globalName: 'nerdamer',
+ alias: {
+ 'big-integer': resolve(VENDORS_DIR, 'big-integer/BigInteger.js'),
+ 'decimal.js': resolve(VENDORS_DIR, 'decimal.js/decimal.js')
+ },
+ write: false,
+ logLevel: 'silent'
+ });
+ bundled = result.outputFiles[0].text;
+ }
+ return `export default ${JSON.stringify(bundled)};`;
+ }
+ };
+}
export const SANDBOX_TRUNCATION_NOTICE = '[output truncated]';
-export const SANDBOX_TOOL_DEFINITION: OpenAIToolDefinition = {
- type: ToolCallType.FUNCTION,
- function: {
- name: SANDBOX_TOOL_NAME,
- description:
- 'Execute JavaScript in a sandboxed browser worker (no DOM, no page access). ' +
- 'Top level await is supported. Use console.log to print intermediate values; ' +
- 'a top level return statement is captured as the result.',
- parameters: {
- type: JsonSchemaType.OBJECT,
- properties: {
- code: {
- type: JsonSchemaType.STRING,
- description: 'JavaScript source to execute'
+const NERDAMER_DESCRIPTION = `
+Symbolic/numeric math via \`nerdamer\` (pre-loaded, do not require, use it directly).
+nerdamer('diff(sin(x)/x,x)') or nerdamer.diff('sin(x)/x','x') → Expression; convert with .toString()/.text()/.toTeX(), or .evaluate() (→ still Expression, then .toString()).
+nerdamer(expr,{x:2}) substitutes only; chain .evaluate() or pass 'numer' for numeric result.
+solve(expr,var)→Symbol[]; solveEquations([eq1,..])→[[var,val],..] pairs.
+Functions: simplify/expand/factor(expr), diff(expr,var[,n]), integrate(expr,var), defint(expr,from,to,var), limit(expr,var,to), laplace(expr,t,s), ilt(expr,s,t), gcd/lcm(a,b), roots/coeffs/partfrac(expr,var), pfactor(n), numer/decimals/erf(expr), product/sum(expr,var,from,to), mean/median/stdev/variance(...vals).
+Object.keys(nerdamer).filter(k=>typeof nerdamer[k]==='function') lists all available functions. If you need a function not documented above, list them first — do not guess function names.`;
+
+/**
+ * Build the sandbox tool definition. When `includeSymbolicMath` is true,
+ * the description includes nerdamer API documentation; otherwise it
+ * describes a plain JavaScript sandbox.
+ */
+export function buildSandboxToolDefinition(includeSymbolicMath: boolean): OpenAIToolDefinition {
+ return {
+ type: ToolCallType.FUNCTION,
+ function: {
+ name: SANDBOX_TOOL_NAME,
+ description: includeSymbolicMath
+ ? `Execute JS in a sandboxed browser worker (no DOM/page access). Top-level await ok; console.log for intermediates; top-level return is captured as result.${NERDAMER_DESCRIPTION}`
+ : 'Execute JS in a sandboxed browser worker (no DOM/page access). Top-level await ok; console.log for intermediates; top-level return is captured as result.',
+ parameters: {
+ type: JsonSchemaType.OBJECT,
+ properties: {
+ code: {
+ type: JsonSchemaType.STRING,
+ description: 'JavaScript source to execute'
+ },
+ timeout_ms: {
+ type: JsonSchemaType.NUMBER,
+ description: `Execution timeout in milliseconds, default ${SANDBOX_TIMEOUT_MS_DEFAULT}, max ${SANDBOX_TIMEOUT_MS_MAX}`
+ }
},
- timeout_ms: {
- type: JsonSchemaType.NUMBER,
- description: `Execution timeout in milliseconds, default ${SANDBOX_TIMEOUT_MS_DEFAULT}, max ${SANDBOX_TIMEOUT_MS_MAX}`
- }
- },
- required: ['code']
+ required: ['code']
+ }
}
- }
-};
+ };
+}
+
+/** @deprecated Use {@link buildSandboxToolDefinition} instead. Kept for backward compatibility. */
+export const SANDBOX_TOOL_DEFINITION = buildSandboxToolDefinition(true);
EXCLUDE_REASONING_FROM_CONTEXT: 'excludeReasoningFromContext',
SHOW_RAW_OUTPUT_SWITCH: 'showRawOutputSwitch',
JS_SANDBOX_ENABLED: 'jsSandboxEnabled',
+ SYMBOLIC_MATH_ENABLED: 'symbolicMathEnabled',
// PY_INTERPRETER_ENABLED: 'pyInterpreterEnabled',
CUSTOM_JSON: 'customJson',
CUSTOM_CSS: 'customCss'
paramType: SyncableParameterType.BOOLEAN
}
},
+ {
+ key: SETTINGS_KEYS.SYMBOLIC_MATH_ENABLED,
+ label: 'Symbolic math (nerdamer)',
+ help: 'Pre-load nerdamer in the sandbox for symbolic computation: simplify, diff, integrate, solve, and more. Requires "JavaScript sandbox tool" to be enabled.',
+ defaultValue: false,
+ type: SettingsFieldType.CHECKBOX,
+ section: SETTINGS_SECTION_SLUGS.DEVELOPER,
+ dependsOn: SETTINGS_KEYS.JS_SANDBOX_ENABLED
+ },
{
key: SETTINGS_KEYS.CUSTOM_JSON,
label: 'Custom JSON',
* - **toolsStore**: Exposes the tool definition when the sandbox is enabled
* - **agenticStore**: Dispatches ToolSource.FRONTEND calls here
*
- * @see SANDBOX_TOOL_DEFINITION in constants/sandbox.ts - tool schema sent to the LLM
+ * @see buildSandboxToolDefinition in constants/sandbox.ts - tool schema sent to the LLM
* @see agenticStore in stores/agentic.svelte.ts - tool dispatch
*/
export { SandboxService } from './sandbox.service';
+import { NEWLINE } from '$lib/constants';
import WORKER_SHIM from './sandbox-worker.js?raw';
+/**
+ * CSP for the harness document, inherited by the blob worker. connect-src
+ * falls back to default-src, removing network egress for model and vendored
+ * code. 'unsafe-eval' is required by the worker's AsyncFunction constructor,
+ * 'unsafe-inline' by the inline script below, worker-src by the blob worker.
+ */
+const HARNESS_CSP = `default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval'; worker-src blob:`;
+
/**
* Harness loaded as srcdoc into a sandboxed iframe (allow-scripts only).
* The opaque origin is the security boundary: no access to the app origin,
* its storage or its API. The harness spawns a worker so model code never
* runs on a main thread, which makes the parent timeout enforceable by
- * removing the iframe.
+ * removing the iframe. The prelude runs in the worker before the shim,
+ * exposing globals such as `nerdamer` to model code.
*/
-export const SANDBOX_HARNESS_HTML = `<!doctype html><script>
-const SHIM = ${JSON.stringify(WORKER_SHIM)};
+export function buildSandboxHarness(preludeJs: string): string {
+ return `<!doctype html><meta http-equiv="Content-Security-Policy" content="${HARNESS_CSP}"><script>
+const SHIM = ${JSON.stringify(preludeJs + NEWLINE + WORKER_SHIM)};
addEventListener('message', (event) => {
const respond = (payload) => parent.postMessage(payload, '*');
let worker;
worker.postMessage({ code: event.data.code });
});
</script>`;
+}
const reply = { logs, result: null, error: null };
try {
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
- const value = await new AsyncFunction(event.data.code)();
+ // The prelude bundled ahead of this shim defines self.nerdamer,
+ // passed into the execution scope as the `nerdamer` parameter.
+ const value = await new AsyncFunction('nerdamer', event.data.code)(self.nerdamer);
if (value !== undefined) reply.result = fmt(value);
} catch (err) {
reply.error = err instanceof Error ? err.stack || err.message : String(err);
SANDBOX_TOOL_NAME,
SANDBOX_TRUNCATION_NOTICE
} from '$lib/constants';
-import { SANDBOX_HARNESS_HTML } from './sandbox-harness';
+import { buildSandboxHarness } from './sandbox-harness';
+import { config } from '$lib/stores/settings.svelte';
import type { ToolExecutionResult } from '$lib/types';
+/** Cached harnesses keyed by whether nerdamer is included. */
+const harnessCache: Record<string, string> = {};
+
+/**
+ * Build the sandbox harness. When symbolic math is enabled, loads the
+ * nerdamer prelude lazily; otherwise builds a plain harness with an empty
+ * prelude. Cached per variant so toggling the setting is instant.
+ */
+async function getHarness(): Promise<string> {
+ const enabled = !!config().symbolicMathEnabled;
+ const key = enabled ? 'nerdamer' : 'plain';
+ if (!harnessCache[key]) {
+ if (enabled) {
+ const { default: nerdamerJs } = await import('virtual:nerdamer');
+ harnessCache[key] = buildSandboxHarness(nerdamerJs);
+ } else {
+ harnessCache[key] = buildSandboxHarness('');
+ }
+ }
+ return harnessCache[key];
+}
+
interface SandboxReply {
logs?: unknown;
result?: unknown;
* timeout or abort. Removing the iframe terminates the worker
* at the browser level, so runaway code cannot outlive it.
*/
- static executeTool(
+ static async executeTool(
toolName: string,
params: Record<string, unknown>,
signal?: AbortSignal
): Promise<ToolExecutionResult> {
if (toolName !== SANDBOX_TOOL_NAME) {
- return Promise.resolve({ content: `Unknown frontend tool: ${toolName}`, isError: true });
+ return { content: `Unknown frontend tool: ${toolName}`, isError: true };
}
const code = typeof params.code === 'string' ? params.code : '';
if (!code) {
- return Promise.resolve({ content: 'Missing required parameter: code', isError: true });
+ return { content: 'Missing required parameter: code', isError: true };
}
+ const harness = await getHarness();
+
const requested = Number(params.timeout_ms);
const timeoutMs =
Number.isFinite(requested) && requested > 0
const iframe = document.createElement('iframe');
iframe.setAttribute('sandbox', 'allow-scripts');
iframe.style.display = 'none';
- iframe.srcdoc = SANDBOX_HARNESS_HTML;
+ iframe.srcdoc = harness;
let settled = false;
import { config } from '$lib/stores/settings.svelte';
import {
DISABLED_TOOL_KEYS_LOCALSTORAGE_KEY,
- SANDBOX_TOOL_DEFINITION,
+ buildSandboxToolDefinition,
TOOL_GROUP_LABELS,
TOOL_SERVER_LABELS
} from '$lib/constants';
}
get frontendTools(): OpenAIToolDefinition[] {
- return config().jsSandboxEnabled ? [SANDBOX_TOOL_DEFINITION] : [];
+ return config().jsSandboxEnabled
+ ? [buildSandboxToolDefinition(!!config().symbolicMathEnabled)]
+ : [];
}
get customTools(): OpenAIToolDefinition[] {
--- /dev/null
+var bigInt = (function (undefined) {\r
+ "use strict";\r
+\r
+ var BASE = 1e7,\r
+ LOG_BASE = 7,\r
+ MAX_INT = 9007199254740992,\r
+ MAX_INT_ARR = smallToArray(MAX_INT),\r
+ DEFAULT_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz";\r
+\r
+ var supportsNativeBigInt = typeof BigInt === "function";\r
+\r
+ function Integer(v, radix, alphabet, caseSensitive) {\r
+ if (typeof v === "undefined") return Integer[0];\r
+ if (typeof radix !== "undefined") return +radix === 10 && !alphabet ? parseValue(v) : parseBase(v, radix, alphabet, caseSensitive);\r
+ return parseValue(v);\r
+ }\r
+\r
+ function BigInteger(value, sign) {\r
+ this.value = value;\r
+ this.sign = sign;\r
+ this.isSmall = false;\r
+ }\r
+ BigInteger.prototype = Object.create(Integer.prototype);\r
+\r
+ function SmallInteger(value) {\r
+ this.value = value;\r
+ this.sign = value < 0;\r
+ this.isSmall = true;\r
+ }\r
+ SmallInteger.prototype = Object.create(Integer.prototype);\r
+\r
+ function NativeBigInt(value) {\r
+ this.value = value;\r
+ }\r
+ NativeBigInt.prototype = Object.create(Integer.prototype);\r
+\r
+ function isPrecise(n) {\r
+ return -MAX_INT < n && n < MAX_INT;\r
+ }\r
+\r
+ function smallToArray(n) { // For performance reasons doesn't reference BASE, need to change this function if BASE changes\r
+ if (n < 1e7)\r
+ return [n];\r
+ if (n < 1e14)\r
+ return [n % 1e7, Math.floor(n / 1e7)];\r
+ return [n % 1e7, Math.floor(n / 1e7) % 1e7, Math.floor(n / 1e14)];\r
+ }\r
+\r
+ function arrayToSmall(arr) { // If BASE changes this function may need to change\r
+ trim(arr);\r
+ var length = arr.length;\r
+ if (length < 4 && compareAbs(arr, MAX_INT_ARR) < 0) {\r
+ switch (length) {\r
+ case 0: return 0;\r
+ case 1: return arr[0];\r
+ case 2: return arr[0] + arr[1] * BASE;\r
+ default: return arr[0] + (arr[1] + arr[2] * BASE) * BASE;\r
+ }\r
+ }\r
+ return arr;\r
+ }\r
+\r
+ function trim(v) {\r
+ var i = v.length;\r
+ while (v[--i] === 0);\r
+ v.length = i + 1;\r
+ }\r
+\r
+ function createArray(length) { // function shamelessly stolen from Yaffle's library https://github.com/Yaffle/BigInteger\r
+ var x = new Array(length);\r
+ var i = -1;\r
+ while (++i < length) {\r
+ x[i] = 0;\r
+ }\r
+ return x;\r
+ }\r
+\r
+ function truncate(n) {\r
+ if (n > 0) return Math.floor(n);\r
+ return Math.ceil(n);\r
+ }\r
+\r
+ function add(a, b) { // assumes a and b are arrays with a.length >= b.length\r
+ var l_a = a.length,\r
+ l_b = b.length,\r
+ r = new Array(l_a),\r
+ carry = 0,\r
+ base = BASE,\r
+ sum, i;\r
+ for (i = 0; i < l_b; i++) {\r
+ sum = a[i] + b[i] + carry;\r
+ carry = sum >= base ? 1 : 0;\r
+ r[i] = sum - carry * base;\r
+ }\r
+ while (i < l_a) {\r
+ sum = a[i] + carry;\r
+ carry = sum === base ? 1 : 0;\r
+ r[i++] = sum - carry * base;\r
+ }\r
+ if (carry > 0) r.push(carry);\r
+ return r;\r
+ }\r
+\r
+ function addAny(a, b) {\r
+ if (a.length >= b.length) return add(a, b);\r
+ return add(b, a);\r
+ }\r
+\r
+ function addSmall(a, carry) { // assumes a is array, carry is number with 0 <= carry < MAX_INT\r
+ var l = a.length,\r
+ r = new Array(l),\r
+ base = BASE,\r
+ sum, i;\r
+ for (i = 0; i < l; i++) {\r
+ sum = a[i] - base + carry;\r
+ carry = Math.floor(sum / base);\r
+ r[i] = sum - carry * base;\r
+ carry += 1;\r
+ }\r
+ while (carry > 0) {\r
+ r[i++] = carry % base;\r
+ carry = Math.floor(carry / base);\r
+ }\r
+ return r;\r
+ }\r
+\r
+ BigInteger.prototype.add = function (v) {\r
+ var n = parseValue(v);\r
+ if (this.sign !== n.sign) {\r
+ return this.subtract(n.negate());\r
+ }\r
+ var a = this.value, b = n.value;\r
+ if (n.isSmall) {\r
+ return new BigInteger(addSmall(a, Math.abs(b)), this.sign);\r
+ }\r
+ return new BigInteger(addAny(a, b), this.sign);\r
+ };\r
+ BigInteger.prototype.plus = BigInteger.prototype.add;\r
+\r
+ SmallInteger.prototype.add = function (v) {\r
+ var n = parseValue(v);\r
+ var a = this.value;\r
+ if (a < 0 !== n.sign) {\r
+ return this.subtract(n.negate());\r
+ }\r
+ var b = n.value;\r
+ if (n.isSmall) {\r
+ if (isPrecise(a + b)) return new SmallInteger(a + b);\r
+ b = smallToArray(Math.abs(b));\r
+ }\r
+ return new BigInteger(addSmall(b, Math.abs(a)), a < 0);\r
+ };\r
+ SmallInteger.prototype.plus = SmallInteger.prototype.add;\r
+\r
+ NativeBigInt.prototype.add = function (v) {\r
+ return new NativeBigInt(this.value + parseValue(v).value);\r
+ }\r
+ NativeBigInt.prototype.plus = NativeBigInt.prototype.add;\r
+\r
+ function subtract(a, b) { // assumes a and b are arrays with a >= b\r
+ var a_l = a.length,\r
+ b_l = b.length,\r
+ r = new Array(a_l),\r
+ borrow = 0,\r
+ base = BASE,\r
+ i, difference;\r
+ for (i = 0; i < b_l; i++) {\r
+ difference = a[i] - borrow - b[i];\r
+ if (difference < 0) {\r
+ difference += base;\r
+ borrow = 1;\r
+ } else borrow = 0;\r
+ r[i] = difference;\r
+ }\r
+ for (i = b_l; i < a_l; i++) {\r
+ difference = a[i] - borrow;\r
+ if (difference < 0) difference += base;\r
+ else {\r
+ r[i++] = difference;\r
+ break;\r
+ }\r
+ r[i] = difference;\r
+ }\r
+ for (; i < a_l; i++) {\r
+ r[i] = a[i];\r
+ }\r
+ trim(r);\r
+ return r;\r
+ }\r
+\r
+ function subtractAny(a, b, sign) {\r
+ var value;\r
+ if (compareAbs(a, b) >= 0) {\r
+ value = subtract(a, b);\r
+ } else {\r
+ value = subtract(b, a);\r
+ sign = !sign;\r
+ }\r
+ value = arrayToSmall(value);\r
+ if (typeof value === "number") {\r
+ if (sign) value = -value;\r
+ return new SmallInteger(value);\r
+ }\r
+ return new BigInteger(value, sign);\r
+ }\r
+\r
+ function subtractSmall(a, b, sign) { // assumes a is array, b is number with 0 <= b < MAX_INT\r
+ var l = a.length,\r
+ r = new Array(l),\r
+ carry = -b,\r
+ base = BASE,\r
+ i, difference;\r
+ for (i = 0; i < l; i++) {\r
+ difference = a[i] + carry;\r
+ carry = Math.floor(difference / base);\r
+ difference %= base;\r
+ r[i] = difference < 0 ? difference + base : difference;\r
+ }\r
+ r = arrayToSmall(r);\r
+ if (typeof r === "number") {\r
+ if (sign) r = -r;\r
+ return new SmallInteger(r);\r
+ } return new BigInteger(r, sign);\r
+ }\r
+\r
+ BigInteger.prototype.subtract = function (v) {\r
+ var n = parseValue(v);\r
+ if (this.sign !== n.sign) {\r
+ return this.add(n.negate());\r
+ }\r
+ var a = this.value, b = n.value;\r
+ if (n.isSmall)\r
+ return subtractSmall(a, Math.abs(b), this.sign);\r
+ return subtractAny(a, b, this.sign);\r
+ };\r
+ BigInteger.prototype.minus = BigInteger.prototype.subtract;\r
+\r
+ SmallInteger.prototype.subtract = function (v) {\r
+ var n = parseValue(v);\r
+ var a = this.value;\r
+ if (a < 0 !== n.sign) {\r
+ return this.add(n.negate());\r
+ }\r
+ var b = n.value;\r
+ if (n.isSmall) {\r
+ return new SmallInteger(a - b);\r
+ }\r
+ return subtractSmall(b, Math.abs(a), a >= 0);\r
+ };\r
+ SmallInteger.prototype.minus = SmallInteger.prototype.subtract;\r
+\r
+ NativeBigInt.prototype.subtract = function (v) {\r
+ return new NativeBigInt(this.value - parseValue(v).value);\r
+ }\r
+ NativeBigInt.prototype.minus = NativeBigInt.prototype.subtract;\r
+\r
+ BigInteger.prototype.negate = function () {\r
+ return new BigInteger(this.value, !this.sign);\r
+ };\r
+ SmallInteger.prototype.negate = function () {\r
+ var sign = this.sign;\r
+ var small = new SmallInteger(-this.value);\r
+ small.sign = !sign;\r
+ return small;\r
+ };\r
+ NativeBigInt.prototype.negate = function () {\r
+ return new NativeBigInt(-this.value);\r
+ }\r
+\r
+ BigInteger.prototype.abs = function () {\r
+ return new BigInteger(this.value, false);\r
+ };\r
+ SmallInteger.prototype.abs = function () {\r
+ return new SmallInteger(Math.abs(this.value));\r
+ };\r
+ NativeBigInt.prototype.abs = function () {\r
+ return new NativeBigInt(this.value >= 0 ? this.value : -this.value);\r
+ }\r
+\r
+\r
+ function multiplyLong(a, b) {\r
+ var a_l = a.length,\r
+ b_l = b.length,\r
+ l = a_l + b_l,\r
+ r = createArray(l),\r
+ base = BASE,\r
+ product, carry, i, a_i, b_j;\r
+ for (i = 0; i < a_l; ++i) {\r
+ a_i = a[i];\r
+ for (var j = 0; j < b_l; ++j) {\r
+ b_j = b[j];\r
+ product = a_i * b_j + r[i + j];\r
+ carry = Math.floor(product / base);\r
+ r[i + j] = product - carry * base;\r
+ r[i + j + 1] += carry;\r
+ }\r
+ }\r
+ trim(r);\r
+ return r;\r
+ }\r
+\r
+ function multiplySmall(a, b) { // assumes a is array, b is number with |b| < BASE\r
+ var l = a.length,\r
+ r = new Array(l),\r
+ base = BASE,\r
+ carry = 0,\r
+ product, i;\r
+ for (i = 0; i < l; i++) {\r
+ product = a[i] * b + carry;\r
+ carry = Math.floor(product / base);\r
+ r[i] = product - carry * base;\r
+ }\r
+ while (carry > 0) {\r
+ r[i++] = carry % base;\r
+ carry = Math.floor(carry / base);\r
+ }\r
+ return r;\r
+ }\r
+\r
+ function shiftLeft(x, n) {\r
+ var r = [];\r
+ while (n-- > 0) r.push(0);\r
+ return r.concat(x);\r
+ }\r
+\r
+ function multiplyKaratsuba(x, y) {\r
+ var n = Math.max(x.length, y.length);\r
+\r
+ if (n <= 30) return multiplyLong(x, y);\r
+ n = Math.ceil(n / 2);\r
+\r
+ var b = x.slice(n),\r
+ a = x.slice(0, n),\r
+ d = y.slice(n),\r
+ c = y.slice(0, n);\r
+\r
+ var ac = multiplyKaratsuba(a, c),\r
+ bd = multiplyKaratsuba(b, d),\r
+ abcd = multiplyKaratsuba(addAny(a, b), addAny(c, d));\r
+\r
+ var product = addAny(addAny(ac, shiftLeft(subtract(subtract(abcd, ac), bd), n)), shiftLeft(bd, 2 * n));\r
+ trim(product);\r
+ return product;\r
+ }\r
+\r
+ // The following function is derived from a surface fit of a graph plotting the performance difference\r
+ // between long multiplication and karatsuba multiplication versus the lengths of the two arrays.\r
+ function useKaratsuba(l1, l2) {\r
+ return -0.012 * l1 - 0.012 * l2 + 0.000015 * l1 * l2 > 0;\r
+ }\r
+\r
+ BigInteger.prototype.multiply = function (v) {\r
+ var n = parseValue(v),\r
+ a = this.value, b = n.value,\r
+ sign = this.sign !== n.sign,\r
+ abs;\r
+ if (n.isSmall) {\r
+ if (b === 0) return Integer[0];\r
+ if (b === 1) return this;\r
+ if (b === -1) return this.negate();\r
+ abs = Math.abs(b);\r
+ if (abs < BASE) {\r
+ return new BigInteger(multiplySmall(a, abs), sign);\r
+ }\r
+ b = smallToArray(abs);\r
+ }\r
+ if (useKaratsuba(a.length, b.length)) // Karatsuba is only faster for certain array sizes\r
+ return new BigInteger(multiplyKaratsuba(a, b), sign);\r
+ return new BigInteger(multiplyLong(a, b), sign);\r
+ };\r
+\r
+ BigInteger.prototype.times = BigInteger.prototype.multiply;\r
+\r
+ function multiplySmallAndArray(a, b, sign) { // a >= 0\r
+ if (a < BASE) {\r
+ return new BigInteger(multiplySmall(b, a), sign);\r
+ }\r
+ return new BigInteger(multiplyLong(b, smallToArray(a)), sign);\r
+ }\r
+ SmallInteger.prototype._multiplyBySmall = function (a) {\r
+ if (isPrecise(a.value * this.value)) {\r
+ return new SmallInteger(a.value * this.value);\r
+ }\r
+ return multiplySmallAndArray(Math.abs(a.value), smallToArray(Math.abs(this.value)), this.sign !== a.sign);\r
+ };\r
+ BigInteger.prototype._multiplyBySmall = function (a) {\r
+ if (a.value === 0) return Integer[0];\r
+ if (a.value === 1) return this;\r
+ if (a.value === -1) return this.negate();\r
+ return multiplySmallAndArray(Math.abs(a.value), this.value, this.sign !== a.sign);\r
+ };\r
+ SmallInteger.prototype.multiply = function (v) {\r
+ return parseValue(v)._multiplyBySmall(this);\r
+ };\r
+ SmallInteger.prototype.times = SmallInteger.prototype.multiply;\r
+\r
+ NativeBigInt.prototype.multiply = function (v) {\r
+ return new NativeBigInt(this.value * parseValue(v).value);\r
+ }\r
+ NativeBigInt.prototype.times = NativeBigInt.prototype.multiply;\r
+\r
+ function square(a) {\r
+ //console.assert(2 * BASE * BASE < MAX_INT);\r
+ var l = a.length,\r
+ r = createArray(l + l),\r
+ base = BASE,\r
+ product, carry, i, a_i, a_j;\r
+ for (i = 0; i < l; i++) {\r
+ a_i = a[i];\r
+ carry = 0 - a_i * a_i;\r
+ for (var j = i; j < l; j++) {\r
+ a_j = a[j];\r
+ product = 2 * (a_i * a_j) + r[i + j] + carry;\r
+ carry = Math.floor(product / base);\r
+ r[i + j] = product - carry * base;\r
+ }\r
+ r[i + l] = carry;\r
+ }\r
+ trim(r);\r
+ return r;\r
+ }\r
+\r
+ BigInteger.prototype.square = function () {\r
+ return new BigInteger(square(this.value), false);\r
+ };\r
+\r
+ SmallInteger.prototype.square = function () {\r
+ var value = this.value * this.value;\r
+ if (isPrecise(value)) return new SmallInteger(value);\r
+ return new BigInteger(square(smallToArray(Math.abs(this.value))), false);\r
+ };\r
+\r
+ NativeBigInt.prototype.square = function (v) {\r
+ return new NativeBigInt(this.value * this.value);\r
+ }\r
+\r
+ function divMod1(a, b) { // Left over from previous version. Performs faster than divMod2 on smaller input sizes.\r
+ var a_l = a.length,\r
+ b_l = b.length,\r
+ base = BASE,\r
+ result = createArray(b.length),\r
+ divisorMostSignificantDigit = b[b_l - 1],\r
+ // normalization\r
+ lambda = Math.ceil(base / (2 * divisorMostSignificantDigit)),\r
+ remainder = multiplySmall(a, lambda),\r
+ divisor = multiplySmall(b, lambda),\r
+ quotientDigit, shift, carry, borrow, i, l, q;\r
+ if (remainder.length <= a_l) remainder.push(0);\r
+ divisor.push(0);\r
+ divisorMostSignificantDigit = divisor[b_l - 1];\r
+ for (shift = a_l - b_l; shift >= 0; shift--) {\r
+ quotientDigit = base - 1;\r
+ if (remainder[shift + b_l] !== divisorMostSignificantDigit) {\r
+ quotientDigit = Math.floor((remainder[shift + b_l] * base + remainder[shift + b_l - 1]) / divisorMostSignificantDigit);\r
+ }\r
+ // quotientDigit <= base - 1\r
+ carry = 0;\r
+ borrow = 0;\r
+ l = divisor.length;\r
+ for (i = 0; i < l; i++) {\r
+ carry += quotientDigit * divisor[i];\r
+ q = Math.floor(carry / base);\r
+ borrow += remainder[shift + i] - (carry - q * base);\r
+ carry = q;\r
+ if (borrow < 0) {\r
+ remainder[shift + i] = borrow + base;\r
+ borrow = -1;\r
+ } else {\r
+ remainder[shift + i] = borrow;\r
+ borrow = 0;\r
+ }\r
+ }\r
+ while (borrow !== 0) {\r
+ quotientDigit -= 1;\r
+ carry = 0;\r
+ for (i = 0; i < l; i++) {\r
+ carry += remainder[shift + i] - base + divisor[i];\r
+ if (carry < 0) {\r
+ remainder[shift + i] = carry + base;\r
+ carry = 0;\r
+ } else {\r
+ remainder[shift + i] = carry;\r
+ carry = 1;\r
+ }\r
+ }\r
+ borrow += carry;\r
+ }\r
+ result[shift] = quotientDigit;\r
+ }\r
+ // denormalization\r
+ remainder = divModSmall(remainder, lambda)[0];\r
+ return [arrayToSmall(result), arrayToSmall(remainder)];\r
+ }\r
+\r
+ function divMod2(a, b) { // Implementation idea shamelessly stolen from Silent Matt's library http://silentmatt.com/biginteger/\r
+ // Performs faster than divMod1 on larger input sizes.\r
+ var a_l = a.length,\r
+ b_l = b.length,\r
+ result = [],\r
+ part = [],\r
+ base = BASE,\r
+ guess, xlen, highx, highy, check;\r
+ while (a_l) {\r
+ part.unshift(a[--a_l]);\r
+ trim(part);\r
+ if (compareAbs(part, b) < 0) {\r
+ result.push(0);\r
+ continue;\r
+ }\r
+ xlen = part.length;\r
+ highx = part[xlen - 1] * base + part[xlen - 2];\r
+ highy = b[b_l - 1] * base + b[b_l - 2];\r
+ if (xlen > b_l) {\r
+ highx = (highx + 1) * base;\r
+ }\r
+ guess = Math.ceil(highx / highy);\r
+ do {\r
+ check = multiplySmall(b, guess);\r
+ if (compareAbs(check, part) <= 0) break;\r
+ guess--;\r
+ } while (guess);\r
+ result.push(guess);\r
+ part = subtract(part, check);\r
+ }\r
+ result.reverse();\r
+ return [arrayToSmall(result), arrayToSmall(part)];\r
+ }\r
+\r
+ function divModSmall(value, lambda) {\r
+ var length = value.length,\r
+ quotient = createArray(length),\r
+ base = BASE,\r
+ i, q, remainder, divisor;\r
+ remainder = 0;\r
+ for (i = length - 1; i >= 0; --i) {\r
+ divisor = remainder * base + value[i];\r
+ q = truncate(divisor / lambda);\r
+ remainder = divisor - q * lambda;\r
+ quotient[i] = q | 0;\r
+ }\r
+ return [quotient, remainder | 0];\r
+ }\r
+\r
+ function divModAny(self, v) {\r
+ var value, n = parseValue(v);\r
+ if (supportsNativeBigInt) {\r
+ return [new NativeBigInt(self.value / n.value), new NativeBigInt(self.value % n.value)];\r
+ }\r
+ var a = self.value, b = n.value;\r
+ var quotient;\r
+ if (b === 0) throw new Error("Cannot divide by zero");\r
+ if (self.isSmall) {\r
+ if (n.isSmall) {\r
+ return [new SmallInteger(truncate(a / b)), new SmallInteger(a % b)];\r
+ }\r
+ return [Integer[0], self];\r
+ }\r
+ if (n.isSmall) {\r
+ if (b === 1) return [self, Integer[0]];\r
+ if (b == -1) return [self.negate(), Integer[0]];\r
+ var abs = Math.abs(b);\r
+ if (abs < BASE) {\r
+ value = divModSmall(a, abs);\r
+ quotient = arrayToSmall(value[0]);\r
+ var remainder = value[1];\r
+ if (self.sign) remainder = -remainder;\r
+ if (typeof quotient === "number") {\r
+ if (self.sign !== n.sign) quotient = -quotient;\r
+ return [new SmallInteger(quotient), new SmallInteger(remainder)];\r
+ }\r
+ return [new BigInteger(quotient, self.sign !== n.sign), new SmallInteger(remainder)];\r
+ }\r
+ b = smallToArray(abs);\r
+ }\r
+ var comparison = compareAbs(a, b);\r
+ if (comparison === -1) return [Integer[0], self];\r
+ if (comparison === 0) return [Integer[self.sign === n.sign ? 1 : -1], Integer[0]];\r
+\r
+ // divMod1 is faster on smaller input sizes\r
+ if (a.length + b.length <= 200)\r
+ value = divMod1(a, b);\r
+ else value = divMod2(a, b);\r
+\r
+ quotient = value[0];\r
+ var qSign = self.sign !== n.sign,\r
+ mod = value[1],\r
+ mSign = self.sign;\r
+ if (typeof quotient === "number") {\r
+ if (qSign) quotient = -quotient;\r
+ quotient = new SmallInteger(quotient);\r
+ } else quotient = new BigInteger(quotient, qSign);\r
+ if (typeof mod === "number") {\r
+ if (mSign) mod = -mod;\r
+ mod = new SmallInteger(mod);\r
+ } else mod = new BigInteger(mod, mSign);\r
+ return [quotient, mod];\r
+ }\r
+\r
+ BigInteger.prototype.divmod = function (v) {\r
+ var result = divModAny(this, v);\r
+ return {\r
+ quotient: result[0],\r
+ remainder: result[1]\r
+ };\r
+ };\r
+ NativeBigInt.prototype.divmod = SmallInteger.prototype.divmod = BigInteger.prototype.divmod;\r
+\r
+\r
+ BigInteger.prototype.divide = function (v) {\r
+ return divModAny(this, v)[0];\r
+ };\r
+ NativeBigInt.prototype.over = NativeBigInt.prototype.divide = function (v) {\r
+ return new NativeBigInt(this.value / parseValue(v).value);\r
+ };\r
+ SmallInteger.prototype.over = SmallInteger.prototype.divide = BigInteger.prototype.over = BigInteger.prototype.divide;\r
+\r
+ BigInteger.prototype.mod = function (v) {\r
+ return divModAny(this, v)[1];\r
+ };\r
+ NativeBigInt.prototype.mod = NativeBigInt.prototype.remainder = function (v) {\r
+ return new NativeBigInt(this.value % parseValue(v).value);\r
+ };\r
+ SmallInteger.prototype.remainder = SmallInteger.prototype.mod = BigInteger.prototype.remainder = BigInteger.prototype.mod;\r
+\r
+ BigInteger.prototype.pow = function (v) {\r
+ var n = parseValue(v),\r
+ a = this.value,\r
+ b = n.value,\r
+ value, x, y;\r
+ if (b === 0) return Integer[1];\r
+ if (a === 0) return Integer[0];\r
+ if (a === 1) return Integer[1];\r
+ if (a === -1) return n.isEven() ? Integer[1] : Integer[-1];\r
+ if (n.sign) {\r
+ return Integer[0];\r
+ }\r
+ if (!n.isSmall) throw new Error("The exponent " + n.toString() + " is too large.");\r
+ if (this.isSmall) {\r
+ if (isPrecise(value = Math.pow(a, b)))\r
+ return new SmallInteger(truncate(value));\r
+ }\r
+ x = this;\r
+ y = Integer[1];\r
+ while (true) {\r
+ if (b & 1 === 1) {\r
+ y = y.times(x);\r
+ --b;\r
+ }\r
+ if (b === 0) break;\r
+ b /= 2;\r
+ x = x.square();\r
+ }\r
+ return y;\r
+ };\r
+ SmallInteger.prototype.pow = BigInteger.prototype.pow;\r
+\r
+ NativeBigInt.prototype.pow = function (v) {\r
+ var n = parseValue(v);\r
+ var a = this.value, b = n.value;\r
+ var _0 = BigInt(0), _1 = BigInt(1), _2 = BigInt(2);\r
+ if (b === _0) return Integer[1];\r
+ if (a === _0) return Integer[0];\r
+ if (a === _1) return Integer[1];\r
+ if (a === BigInt(-1)) return n.isEven() ? Integer[1] : Integer[-1];\r
+ if (n.isNegative()) return new NativeBigInt(_0);\r
+ var x = this;\r
+ var y = Integer[1];\r
+ while (true) {\r
+ if ((b & _1) === _1) {\r
+ y = y.times(x);\r
+ --b;\r
+ }\r
+ if (b === _0) break;\r
+ b /= _2;\r
+ x = x.square();\r
+ }\r
+ return y;\r
+ }\r
+\r
+ BigInteger.prototype.modPow = function (exp, mod) {\r
+ exp = parseValue(exp);\r
+ mod = parseValue(mod);\r
+ if (mod.isZero()) throw new Error("Cannot take modPow with modulus 0");\r
+ var r = Integer[1],\r
+ base = this.mod(mod);\r
+ if (exp.isNegative()) {\r
+ exp = exp.multiply(Integer[-1]);\r
+ base = base.modInv(mod);\r
+ }\r
+ while (exp.isPositive()) {\r
+ if (base.isZero()) return Integer[0];\r
+ if (exp.isOdd()) r = r.multiply(base).mod(mod);\r
+ exp = exp.divide(2);\r
+ base = base.square().mod(mod);\r
+ }\r
+ return r;\r
+ };\r
+ NativeBigInt.prototype.modPow = SmallInteger.prototype.modPow = BigInteger.prototype.modPow;\r
+\r
+ function compareAbs(a, b) {\r
+ if (a.length !== b.length) {\r
+ return a.length > b.length ? 1 : -1;\r
+ }\r
+ for (var i = a.length - 1; i >= 0; i--) {\r
+ if (a[i] !== b[i]) return a[i] > b[i] ? 1 : -1;\r
+ }\r
+ return 0;\r
+ }\r
+\r
+ BigInteger.prototype.compareAbs = function (v) {\r
+ var n = parseValue(v),\r
+ a = this.value,\r
+ b = n.value;\r
+ if (n.isSmall) return 1;\r
+ return compareAbs(a, b);\r
+ };\r
+ SmallInteger.prototype.compareAbs = function (v) {\r
+ var n = parseValue(v),\r
+ a = Math.abs(this.value),\r
+ b = n.value;\r
+ if (n.isSmall) {\r
+ b = Math.abs(b);\r
+ return a === b ? 0 : a > b ? 1 : -1;\r
+ }\r
+ return -1;\r
+ };\r
+ NativeBigInt.prototype.compareAbs = function (v) {\r
+ var a = this.value;\r
+ var b = parseValue(v).value;\r
+ a = a >= 0 ? a : -a;\r
+ b = b >= 0 ? b : -b;\r
+ return a === b ? 0 : a > b ? 1 : -1;\r
+ }\r
+\r
+ BigInteger.prototype.compare = function (v) {\r
+ // See discussion about comparison with Infinity:\r
+ // https://github.com/peterolson/BigInteger.js/issues/61\r
+ if (v === Infinity) {\r
+ return -1;\r
+ }\r
+ if (v === -Infinity) {\r
+ return 1;\r
+ }\r
+\r
+ var n = parseValue(v),\r
+ a = this.value,\r
+ b = n.value;\r
+ if (this.sign !== n.sign) {\r
+ return n.sign ? 1 : -1;\r
+ }\r
+ if (n.isSmall) {\r
+ return this.sign ? -1 : 1;\r
+ }\r
+ return compareAbs(a, b) * (this.sign ? -1 : 1);\r
+ };\r
+ BigInteger.prototype.compareTo = BigInteger.prototype.compare;\r
+\r
+ SmallInteger.prototype.compare = function (v) {\r
+ if (v === Infinity) {\r
+ return -1;\r
+ }\r
+ if (v === -Infinity) {\r
+ return 1;\r
+ }\r
+\r
+ var n = parseValue(v),\r
+ a = this.value,\r
+ b = n.value;\r
+ if (n.isSmall) {\r
+ return a == b ? 0 : a > b ? 1 : -1;\r
+ }\r
+ if (a < 0 !== n.sign) {\r
+ return a < 0 ? -1 : 1;\r
+ }\r
+ return a < 0 ? 1 : -1;\r
+ };\r
+ SmallInteger.prototype.compareTo = SmallInteger.prototype.compare;\r
+\r
+ NativeBigInt.prototype.compare = function (v) {\r
+ if (v === Infinity) {\r
+ return -1;\r
+ }\r
+ if (v === -Infinity) {\r
+ return 1;\r
+ }\r
+ var a = this.value;\r
+ var b = parseValue(v).value;\r
+ return a === b ? 0 : a > b ? 1 : -1;\r
+ }\r
+ NativeBigInt.prototype.compareTo = NativeBigInt.prototype.compare;\r
+\r
+ BigInteger.prototype.equals = function (v) {\r
+ return this.compare(v) === 0;\r
+ };\r
+ NativeBigInt.prototype.eq = NativeBigInt.prototype.equals = SmallInteger.prototype.eq = SmallInteger.prototype.equals = BigInteger.prototype.eq = BigInteger.prototype.equals;\r
+\r
+ BigInteger.prototype.notEquals = function (v) {\r
+ return this.compare(v) !== 0;\r
+ };\r
+ NativeBigInt.prototype.neq = NativeBigInt.prototype.notEquals = SmallInteger.prototype.neq = SmallInteger.prototype.notEquals = BigInteger.prototype.neq = BigInteger.prototype.notEquals;\r
+\r
+ BigInteger.prototype.greater = function (v) {\r
+ return this.compare(v) > 0;\r
+ };\r
+ NativeBigInt.prototype.gt = NativeBigInt.prototype.greater = SmallInteger.prototype.gt = SmallInteger.prototype.greater = BigInteger.prototype.gt = BigInteger.prototype.greater;\r
+\r
+ BigInteger.prototype.lesser = function (v) {\r
+ return this.compare(v) < 0;\r
+ };\r
+ NativeBigInt.prototype.lt = NativeBigInt.prototype.lesser = SmallInteger.prototype.lt = SmallInteger.prototype.lesser = BigInteger.prototype.lt = BigInteger.prototype.lesser;\r
+\r
+ BigInteger.prototype.greaterOrEquals = function (v) {\r
+ return this.compare(v) >= 0;\r
+ };\r
+ NativeBigInt.prototype.geq = NativeBigInt.prototype.greaterOrEquals = SmallInteger.prototype.geq = SmallInteger.prototype.greaterOrEquals = BigInteger.prototype.geq = BigInteger.prototype.greaterOrEquals;\r
+\r
+ BigInteger.prototype.lesserOrEquals = function (v) {\r
+ return this.compare(v) <= 0;\r
+ };\r
+ NativeBigInt.prototype.leq = NativeBigInt.prototype.lesserOrEquals = SmallInteger.prototype.leq = SmallInteger.prototype.lesserOrEquals = BigInteger.prototype.leq = BigInteger.prototype.lesserOrEquals;\r
+\r
+ BigInteger.prototype.isEven = function () {\r
+ return (this.value[0] & 1) === 0;\r
+ };\r
+ SmallInteger.prototype.isEven = function () {\r
+ return (this.value & 1) === 0;\r
+ };\r
+ NativeBigInt.prototype.isEven = function () {\r
+ return (this.value & BigInt(1)) === BigInt(0);\r
+ }\r
+\r
+ BigInteger.prototype.isOdd = function () {\r
+ return (this.value[0] & 1) === 1;\r
+ };\r
+ SmallInteger.prototype.isOdd = function () {\r
+ return (this.value & 1) === 1;\r
+ };\r
+ NativeBigInt.prototype.isOdd = function () {\r
+ return (this.value & BigInt(1)) === BigInt(1);\r
+ }\r
+\r
+ BigInteger.prototype.isPositive = function () {\r
+ return !this.sign;\r
+ };\r
+ SmallInteger.prototype.isPositive = function () {\r
+ return this.value > 0;\r
+ };\r
+ NativeBigInt.prototype.isPositive = SmallInteger.prototype.isPositive;\r
+\r
+ BigInteger.prototype.isNegative = function () {\r
+ return this.sign;\r
+ };\r
+ SmallInteger.prototype.isNegative = function () {\r
+ return this.value < 0;\r
+ };\r
+ NativeBigInt.prototype.isNegative = SmallInteger.prototype.isNegative;\r
+\r
+ BigInteger.prototype.isUnit = function () {\r
+ return false;\r
+ };\r
+ SmallInteger.prototype.isUnit = function () {\r
+ return Math.abs(this.value) === 1;\r
+ };\r
+ NativeBigInt.prototype.isUnit = function () {\r
+ return this.abs().value === BigInt(1);\r
+ }\r
+\r
+ BigInteger.prototype.isZero = function () {\r
+ return false;\r
+ };\r
+ SmallInteger.prototype.isZero = function () {\r
+ return this.value === 0;\r
+ };\r
+ NativeBigInt.prototype.isZero = function () {\r
+ return this.value === BigInt(0);\r
+ }\r
+\r
+ BigInteger.prototype.isDivisibleBy = function (v) {\r
+ var n = parseValue(v);\r
+ if (n.isZero()) return false;\r
+ if (n.isUnit()) return true;\r
+ if (n.compareAbs(2) === 0) return this.isEven();\r
+ return this.mod(n).isZero();\r
+ };\r
+ NativeBigInt.prototype.isDivisibleBy = SmallInteger.prototype.isDivisibleBy = BigInteger.prototype.isDivisibleBy;\r
+\r
+ function isBasicPrime(v) {\r
+ var n = v.abs();\r
+ if (n.isUnit()) return false;\r
+ if (n.equals(2) || n.equals(3) || n.equals(5)) return true;\r
+ if (n.isEven() || n.isDivisibleBy(3) || n.isDivisibleBy(5)) return false;\r
+ if (n.lesser(49)) return true;\r
+ // we don't know if it's prime: let the other functions figure it out\r
+ }\r
+\r
+ function millerRabinTest(n, a) {\r
+ var nPrev = n.prev(),\r
+ b = nPrev,\r
+ r = 0,\r
+ d, t, i, x;\r
+ while (b.isEven()) b = b.divide(2), r++;\r
+ next: for (i = 0; i < a.length; i++) {\r
+ if (n.lesser(a[i])) continue;\r
+ x = bigInt(a[i]).modPow(b, n);\r
+ if (x.isUnit() || x.equals(nPrev)) continue;\r
+ for (d = r - 1; d != 0; d--) {\r
+ x = x.square().mod(n);\r
+ if (x.isUnit()) return false;\r
+ if (x.equals(nPrev)) continue next;\r
+ }\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+\r
+ // Set "strict" to true to force GRH-supported lower bound of 2*log(N)^2\r
+ BigInteger.prototype.isPrime = function (strict) {\r
+ var isPrime = isBasicPrime(this);\r
+ if (isPrime !== undefined) return isPrime;\r
+ var n = this.abs();\r
+ var bits = n.bitLength();\r
+ if (bits <= 64)\r
+ return millerRabinTest(n, [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]);\r
+ var logN = Math.log(2) * bits.toJSNumber();\r
+ var t = Math.ceil((strict === true) ? (2 * Math.pow(logN, 2)) : logN);\r
+ for (var a = [], i = 0; i < t; i++) {\r
+ a.push(bigInt(i + 2));\r
+ }\r
+ return millerRabinTest(n, a);\r
+ };\r
+ NativeBigInt.prototype.isPrime = SmallInteger.prototype.isPrime = BigInteger.prototype.isPrime;\r
+\r
+ BigInteger.prototype.isProbablePrime = function (iterations, rng) {\r
+ var isPrime = isBasicPrime(this);\r
+ if (isPrime !== undefined) return isPrime;\r
+ var n = this.abs();\r
+ var t = iterations === undefined ? 5 : iterations;\r
+ for (var a = [], i = 0; i < t; i++) {\r
+ a.push(bigInt.randBetween(2, n.minus(2), rng));\r
+ }\r
+ return millerRabinTest(n, a);\r
+ };\r
+ NativeBigInt.prototype.isProbablePrime = SmallInteger.prototype.isProbablePrime = BigInteger.prototype.isProbablePrime;\r
+\r
+ BigInteger.prototype.modInv = function (n) {\r
+ var t = bigInt.zero, newT = bigInt.one, r = parseValue(n), newR = this.abs(), q, lastT, lastR;\r
+ while (!newR.isZero()) {\r
+ q = r.divide(newR);\r
+ lastT = t;\r
+ lastR = r;\r
+ t = newT;\r
+ r = newR;\r
+ newT = lastT.subtract(q.multiply(newT));\r
+ newR = lastR.subtract(q.multiply(newR));\r
+ }\r
+ if (!r.isUnit()) throw new Error(this.toString() + " and " + n.toString() + " are not co-prime");\r
+ if (t.compare(0) === -1) {\r
+ t = t.add(n);\r
+ }\r
+ if (this.isNegative()) {\r
+ return t.negate();\r
+ }\r
+ return t;\r
+ };\r
+\r
+ NativeBigInt.prototype.modInv = SmallInteger.prototype.modInv = BigInteger.prototype.modInv;\r
+\r
+ BigInteger.prototype.next = function () {\r
+ var value = this.value;\r
+ if (this.sign) {\r
+ return subtractSmall(value, 1, this.sign);\r
+ }\r
+ return new BigInteger(addSmall(value, 1), this.sign);\r
+ };\r
+ SmallInteger.prototype.next = function () {\r
+ var value = this.value;\r
+ if (value + 1 < MAX_INT) return new SmallInteger(value + 1);\r
+ return new BigInteger(MAX_INT_ARR, false);\r
+ };\r
+ NativeBigInt.prototype.next = function () {\r
+ return new NativeBigInt(this.value + BigInt(1));\r
+ }\r
+\r
+ BigInteger.prototype.prev = function () {\r
+ var value = this.value;\r
+ if (this.sign) {\r
+ return new BigInteger(addSmall(value, 1), true);\r
+ }\r
+ return subtractSmall(value, 1, this.sign);\r
+ };\r
+ SmallInteger.prototype.prev = function () {\r
+ var value = this.value;\r
+ if (value - 1 > -MAX_INT) return new SmallInteger(value - 1);\r
+ return new BigInteger(MAX_INT_ARR, true);\r
+ };\r
+ NativeBigInt.prototype.prev = function () {\r
+ return new NativeBigInt(this.value - BigInt(1));\r
+ }\r
+\r
+ var powersOfTwo = [1];\r
+ while (2 * powersOfTwo[powersOfTwo.length - 1] <= BASE) powersOfTwo.push(2 * powersOfTwo[powersOfTwo.length - 1]);\r
+ var powers2Length = powersOfTwo.length, highestPower2 = powersOfTwo[powers2Length - 1];\r
+\r
+ function shift_isSmall(n) {\r
+ return Math.abs(n) <= BASE;\r
+ }\r
+\r
+ BigInteger.prototype.shiftLeft = function (v) {\r
+ var n = parseValue(v).toJSNumber();\r
+ if (!shift_isSmall(n)) {\r
+ throw new Error(String(n) + " is too large for shifting.");\r
+ }\r
+ if (n < 0) return this.shiftRight(-n);\r
+ var result = this;\r
+ if (result.isZero()) return result;\r
+ while (n >= powers2Length) {\r
+ result = result.multiply(highestPower2);\r
+ n -= powers2Length - 1;\r
+ }\r
+ return result.multiply(powersOfTwo[n]);\r
+ };\r
+ NativeBigInt.prototype.shiftLeft = SmallInteger.prototype.shiftLeft = BigInteger.prototype.shiftLeft;\r
+\r
+ BigInteger.prototype.shiftRight = function (v) {\r
+ var remQuo;\r
+ var n = parseValue(v).toJSNumber();\r
+ if (!shift_isSmall(n)) {\r
+ throw new Error(String(n) + " is too large for shifting.");\r
+ }\r
+ if (n < 0) return this.shiftLeft(-n);\r
+ var result = this;\r
+ while (n >= powers2Length) {\r
+ if (result.isZero() || (result.isNegative() && result.isUnit())) return result;\r
+ remQuo = divModAny(result, highestPower2);\r
+ result = remQuo[1].isNegative() ? remQuo[0].prev() : remQuo[0];\r
+ n -= powers2Length - 1;\r
+ }\r
+ remQuo = divModAny(result, powersOfTwo[n]);\r
+ return remQuo[1].isNegative() ? remQuo[0].prev() : remQuo[0];\r
+ };\r
+ NativeBigInt.prototype.shiftRight = SmallInteger.prototype.shiftRight = BigInteger.prototype.shiftRight;\r
+\r
+ function bitwise(x, y, fn) {\r
+ y = parseValue(y);\r
+ var xSign = x.isNegative(), ySign = y.isNegative();\r
+ var xRem = xSign ? x.not() : x,\r
+ yRem = ySign ? y.not() : y;\r
+ var xDigit = 0, yDigit = 0;\r
+ var xDivMod = null, yDivMod = null;\r
+ var result = [];\r
+ while (!xRem.isZero() || !yRem.isZero()) {\r
+ xDivMod = divModAny(xRem, highestPower2);\r
+ xDigit = xDivMod[1].toJSNumber();\r
+ if (xSign) {\r
+ xDigit = highestPower2 - 1 - xDigit; // two's complement for negative numbers\r
+ }\r
+\r
+ yDivMod = divModAny(yRem, highestPower2);\r
+ yDigit = yDivMod[1].toJSNumber();\r
+ if (ySign) {\r
+ yDigit = highestPower2 - 1 - yDigit; // two's complement for negative numbers\r
+ }\r
+\r
+ xRem = xDivMod[0];\r
+ yRem = yDivMod[0];\r
+ result.push(fn(xDigit, yDigit));\r
+ }\r
+ var sum = fn(xSign ? 1 : 0, ySign ? 1 : 0) !== 0 ? bigInt(-1) : bigInt(0);\r
+ for (var i = result.length - 1; i >= 0; i -= 1) {\r
+ sum = sum.multiply(highestPower2).add(bigInt(result[i]));\r
+ }\r
+ return sum;\r
+ }\r
+\r
+ BigInteger.prototype.not = function () {\r
+ return this.negate().prev();\r
+ };\r
+ NativeBigInt.prototype.not = SmallInteger.prototype.not = BigInteger.prototype.not;\r
+\r
+ BigInteger.prototype.and = function (n) {\r
+ return bitwise(this, n, function (a, b) { return a & b; });\r
+ };\r
+ NativeBigInt.prototype.and = SmallInteger.prototype.and = BigInteger.prototype.and;\r
+\r
+ BigInteger.prototype.or = function (n) {\r
+ return bitwise(this, n, function (a, b) { return a | b; });\r
+ };\r
+ NativeBigInt.prototype.or = SmallInteger.prototype.or = BigInteger.prototype.or;\r
+\r
+ BigInteger.prototype.xor = function (n) {\r
+ return bitwise(this, n, function (a, b) { return a ^ b; });\r
+ };\r
+ NativeBigInt.prototype.xor = SmallInteger.prototype.xor = BigInteger.prototype.xor;\r
+\r
+ var LOBMASK_I = 1 << 30, LOBMASK_BI = (BASE & -BASE) * (BASE & -BASE) | LOBMASK_I;\r
+ function roughLOB(n) { // get lowestOneBit (rough)\r
+ // SmallInteger: return Min(lowestOneBit(n), 1 << 30)\r
+ // BigInteger: return Min(lowestOneBit(n), 1 << 14) [BASE=1e7]\r
+ var v = n.value,\r
+ x = typeof v === "number" ? v | LOBMASK_I :\r
+ typeof v === "bigint" ? v | BigInt(LOBMASK_I) :\r
+ v[0] + v[1] * BASE | LOBMASK_BI;\r
+ return x & -x;\r
+ }\r
+\r
+ function integerLogarithm(value, base) {\r
+ if (base.compareTo(value) <= 0) {\r
+ var tmp = integerLogarithm(value, base.square(base));\r
+ var p = tmp.p;\r
+ var e = tmp.e;\r
+ var t = p.multiply(base);\r
+ return t.compareTo(value) <= 0 ? { p: t, e: e * 2 + 1 } : { p: p, e: e * 2 };\r
+ }\r
+ return { p: bigInt(1), e: 0 };\r
+ }\r
+\r
+ BigInteger.prototype.bitLength = function () {\r
+ var n = this;\r
+ if (n.compareTo(bigInt(0)) < 0) {\r
+ n = n.negate().subtract(bigInt(1));\r
+ }\r
+ if (n.compareTo(bigInt(0)) === 0) {\r
+ return bigInt(0);\r
+ }\r
+ return bigInt(integerLogarithm(n, bigInt(2)).e).add(bigInt(1));\r
+ }\r
+ NativeBigInt.prototype.bitLength = SmallInteger.prototype.bitLength = BigInteger.prototype.bitLength;\r
+\r
+ function max(a, b) {\r
+ a = parseValue(a);\r
+ b = parseValue(b);\r
+ return a.greater(b) ? a : b;\r
+ }\r
+ function min(a, b) {\r
+ a = parseValue(a);\r
+ b = parseValue(b);\r
+ return a.lesser(b) ? a : b;\r
+ }\r
+ function gcd(a, b) {\r
+ a = parseValue(a).abs();\r
+ b = parseValue(b).abs();\r
+ if (a.equals(b)) return a;\r
+ if (a.isZero()) return b;\r
+ if (b.isZero()) return a;\r
+ var c = Integer[1], d, t;\r
+ while (a.isEven() && b.isEven()) {\r
+ d = min(roughLOB(a), roughLOB(b));\r
+ a = a.divide(d);\r
+ b = b.divide(d);\r
+ c = c.multiply(d);\r
+ }\r
+ while (a.isEven()) {\r
+ a = a.divide(roughLOB(a));\r
+ }\r
+ do {\r
+ while (b.isEven()) {\r
+ b = b.divide(roughLOB(b));\r
+ }\r
+ if (a.greater(b)) {\r
+ t = b; b = a; a = t;\r
+ }\r
+ b = b.subtract(a);\r
+ } while (!b.isZero());\r
+ return c.isUnit() ? a : a.multiply(c);\r
+ }\r
+ function lcm(a, b) {\r
+ a = parseValue(a).abs();\r
+ b = parseValue(b).abs();\r
+ return a.divide(gcd(a, b)).multiply(b);\r
+ }\r
+ function randBetween(a, b, rng) {\r
+ a = parseValue(a);\r
+ b = parseValue(b);\r
+ var usedRNG = rng || Math.random;\r
+ var low = min(a, b), high = max(a, b);\r
+ var range = high.subtract(low).add(1);\r
+ if (range.isSmall) return low.add(Math.floor(usedRNG() * range));\r
+ var digits = toBase(range, BASE).value;\r
+ var result = [], restricted = true;\r
+ for (var i = 0; i < digits.length; i++) {\r
+ var top = restricted ? digits[i] + (i + 1 < digits.length ? digits[i + 1] / BASE : 0) : BASE;\r
+ var digit = truncate(usedRNG() * top);\r
+ result.push(digit);\r
+ if (digit < digits[i]) restricted = false;\r
+ }\r
+ return low.add(Integer.fromArray(result, BASE, false));\r
+ }\r
+\r
+ var parseBase = function (text, base, alphabet, caseSensitive) {\r
+ alphabet = alphabet || DEFAULT_ALPHABET;\r
+ text = String(text);\r
+ if (!caseSensitive) {\r
+ text = text.toLowerCase();\r
+ alphabet = alphabet.toLowerCase();\r
+ }\r
+ var length = text.length;\r
+ var i;\r
+ var absBase = Math.abs(base);\r
+ var alphabetValues = {};\r
+ for (i = 0; i < alphabet.length; i++) {\r
+ alphabetValues[alphabet[i]] = i;\r
+ }\r
+ for (i = 0; i < length; i++) {\r
+ var c = text[i];\r
+ if (c === "-") continue;\r
+ if (c in alphabetValues) {\r
+ if (alphabetValues[c] >= absBase) {\r
+ if (c === "1" && absBase === 1) continue;\r
+ throw new Error(c + " is not a valid digit in base " + base + ".");\r
+ }\r
+ }\r
+ }\r
+ base = parseValue(base);\r
+ var digits = [];\r
+ var isNegative = text[0] === "-";\r
+ for (i = isNegative ? 1 : 0; i < text.length; i++) {\r
+ var c = text[i];\r
+ if (c in alphabetValues) digits.push(parseValue(alphabetValues[c]));\r
+ else if (c === "<") {\r
+ var start = i;\r
+ do { i++; } while (text[i] !== ">" && i < text.length);\r
+ digits.push(parseValue(text.slice(start + 1, i)));\r
+ }\r
+ else throw new Error(c + " is not a valid character");\r
+ }\r
+ return parseBaseFromArray(digits, base, isNegative);\r
+ };\r
+\r
+ function parseBaseFromArray(digits, base, isNegative) {\r
+ var val = Integer[0], pow = Integer[1], i;\r
+ for (i = digits.length - 1; i >= 0; i--) {\r
+ val = val.add(digits[i].times(pow));\r
+ pow = pow.times(base);\r
+ }\r
+ return isNegative ? val.negate() : val;\r
+ }\r
+\r
+ function stringify(digit, alphabet) {\r
+ alphabet = alphabet || DEFAULT_ALPHABET;\r
+ if (digit < alphabet.length) {\r
+ return alphabet[digit];\r
+ }\r
+ return "<" + digit + ">";\r
+ }\r
+\r
+ function toBase(n, base) {\r
+ base = bigInt(base);\r
+ if (base.isZero()) {\r
+ if (n.isZero()) return { value: [0], isNegative: false };\r
+ throw new Error("Cannot convert nonzero numbers to base 0.");\r
+ }\r
+ if (base.equals(-1)) {\r
+ if (n.isZero()) return { value: [0], isNegative: false };\r
+ if (n.isNegative())\r
+ return {\r
+ value: [].concat.apply([], Array.apply(null, Array(-n.toJSNumber()))\r
+ .map(Array.prototype.valueOf, [1, 0])\r
+ ),\r
+ isNegative: false\r
+ };\r
+\r
+ var arr = Array.apply(null, Array(n.toJSNumber() - 1))\r
+ .map(Array.prototype.valueOf, [0, 1]);\r
+ arr.unshift([1]);\r
+ return {\r
+ value: [].concat.apply([], arr),\r
+ isNegative: false\r
+ };\r
+ }\r
+\r
+ var neg = false;\r
+ if (n.isNegative() && base.isPositive()) {\r
+ neg = true;\r
+ n = n.abs();\r
+ }\r
+ if (base.isUnit()) {\r
+ if (n.isZero()) return { value: [0], isNegative: false };\r
+\r
+ return {\r
+ value: Array.apply(null, Array(n.toJSNumber()))\r
+ .map(Number.prototype.valueOf, 1),\r
+ isNegative: neg\r
+ };\r
+ }\r
+ var out = [];\r
+ var left = n, divmod;\r
+ while (left.isNegative() || left.compareAbs(base) >= 0) {\r
+ divmod = left.divmod(base);\r
+ left = divmod.quotient;\r
+ var digit = divmod.remainder;\r
+ if (digit.isNegative()) {\r
+ digit = base.minus(digit).abs();\r
+ left = left.next();\r
+ }\r
+ out.push(digit.toJSNumber());\r
+ }\r
+ out.push(left.toJSNumber());\r
+ return { value: out.reverse(), isNegative: neg };\r
+ }\r
+\r
+ function toBaseString(n, base, alphabet) {\r
+ var arr = toBase(n, base);\r
+ return (arr.isNegative ? "-" : "") + arr.value.map(function (x) {\r
+ return stringify(x, alphabet);\r
+ }).join('');\r
+ }\r
+\r
+ BigInteger.prototype.toArray = function (radix) {\r
+ return toBase(this, radix);\r
+ };\r
+\r
+ SmallInteger.prototype.toArray = function (radix) {\r
+ return toBase(this, radix);\r
+ };\r
+\r
+ NativeBigInt.prototype.toArray = function (radix) {\r
+ return toBase(this, radix);\r
+ };\r
+\r
+ BigInteger.prototype.toString = function (radix, alphabet) {\r
+ if (radix === undefined) radix = 10;\r
+ if (radix !== 10 || alphabet) return toBaseString(this, radix, alphabet);\r
+ var v = this.value, l = v.length, str = String(v[--l]), zeros = "0000000", digit;\r
+ while (--l >= 0) {\r
+ digit = String(v[l]);\r
+ str += zeros.slice(digit.length) + digit;\r
+ }\r
+ var sign = this.sign ? "-" : "";\r
+ return sign + str;\r
+ };\r
+\r
+ SmallInteger.prototype.toString = function (radix, alphabet) {\r
+ if (radix === undefined) radix = 10;\r
+ if (radix != 10 || alphabet) return toBaseString(this, radix, alphabet);\r
+ return String(this.value);\r
+ };\r
+\r
+ NativeBigInt.prototype.toString = SmallInteger.prototype.toString;\r
+\r
+ NativeBigInt.prototype.toJSON = BigInteger.prototype.toJSON = SmallInteger.prototype.toJSON = function () { return this.toString(); }\r
+\r
+ BigInteger.prototype.valueOf = function () {\r
+ return parseInt(this.toString(), 10);\r
+ };\r
+ BigInteger.prototype.toJSNumber = BigInteger.prototype.valueOf;\r
+\r
+ SmallInteger.prototype.valueOf = function () {\r
+ return this.value;\r
+ };\r
+ SmallInteger.prototype.toJSNumber = SmallInteger.prototype.valueOf;\r
+ NativeBigInt.prototype.valueOf = NativeBigInt.prototype.toJSNumber = function () {\r
+ return parseInt(this.toString(), 10);\r
+ }\r
+\r
+ function parseStringValue(v) {\r
+ if (isPrecise(+v)) {\r
+ var x = +v;\r
+ if (x === truncate(x))\r
+ return supportsNativeBigInt ? new NativeBigInt(BigInt(x)) : new SmallInteger(x);\r
+ throw new Error("Invalid integer: " + v);\r
+ }\r
+ var sign = v[0] === "-";\r
+ if (sign) v = v.slice(1);\r
+ var split = v.split(/e/i);\r
+ if (split.length > 2) throw new Error("Invalid integer: " + split.join("e"));\r
+ if (split.length === 2) {\r
+ var exp = split[1];\r
+ if (exp[0] === "+") exp = exp.slice(1);\r
+ exp = +exp;\r
+ if (exp !== truncate(exp) || !isPrecise(exp)) throw new Error("Invalid integer: " + exp + " is not a valid exponent.");\r
+ var text = split[0];\r
+ var decimalPlace = text.indexOf(".");\r
+ if (decimalPlace >= 0) {\r
+ exp -= text.length - decimalPlace - 1;\r
+ text = text.slice(0, decimalPlace) + text.slice(decimalPlace + 1);\r
+ }\r
+ if (exp < 0) throw new Error("Cannot include negative exponent part for integers");\r
+ text += (new Array(exp + 1)).join("0");\r
+ v = text;\r
+ }\r
+ var isValid = /^([0-9][0-9]*)$/.test(v);\r
+ if (!isValid) throw new Error("Invalid integer: " + v);\r
+ if (supportsNativeBigInt) {\r
+ return new NativeBigInt(BigInt(sign ? "-" + v : v));\r
+ }\r
+ var r = [], max = v.length, l = LOG_BASE, min = max - l;\r
+ while (max > 0) {\r
+ r.push(+v.slice(min, max));\r
+ min -= l;\r
+ if (min < 0) min = 0;\r
+ max -= l;\r
+ }\r
+ trim(r);\r
+ return new BigInteger(r, sign);\r
+ }\r
+\r
+ function parseNumberValue(v) {\r
+ if (supportsNativeBigInt) {\r
+ return new NativeBigInt(BigInt(v));\r
+ }\r
+ if (isPrecise(v)) {\r
+ if (v !== truncate(v)) throw new Error(v + " is not an integer.");\r
+ return new SmallInteger(v);\r
+ }\r
+ return parseStringValue(v.toString());\r
+ }\r
+\r
+ function parseValue(v) {\r
+ if (typeof v === "number") {\r
+ return parseNumberValue(v);\r
+ }\r
+ if (typeof v === "string") {\r
+ return parseStringValue(v);\r
+ }\r
+ if (typeof v === "bigint") {\r
+ return new NativeBigInt(v);\r
+ }\r
+ return v;\r
+ }\r
+ // Pre-define numbers in range [-999,999]\r
+ for (var i = 0; i < 1000; i++) {\r
+ Integer[i] = parseValue(i);\r
+ if (i > 0) Integer[-i] = parseValue(-i);\r
+ }\r
+ // Backwards compatibility\r
+ Integer.one = Integer[1];\r
+ Integer.zero = Integer[0];\r
+ Integer.minusOne = Integer[-1];\r
+ Integer.max = max;\r
+ Integer.min = min;\r
+ Integer.gcd = gcd;\r
+ Integer.lcm = lcm;\r
+ Integer.isInstance = function (x) { return x instanceof BigInteger || x instanceof SmallInteger || x instanceof NativeBigInt; };\r
+ Integer.randBetween = randBetween;\r
+\r
+ Integer.fromArray = function (digits, base, isNegative) {\r
+ return parseBaseFromArray(digits.map(parseValue), parseValue(base || 10), isNegative);\r
+ };\r
+\r
+ return Integer;\r
+})();\r
+\r
+// Node.js check\r
+if (typeof module !== "undefined" && module.hasOwnProperty("exports")) {\r
+ module.exports = bigInt;\r
+}\r
+\r
+//amd check\r
+if (typeof define === "function" && define.amd) {\r
+ define( function () {\r
+ return bigInt;\r
+ });\r
+}\r
--- /dev/null
+This is free and unencumbered software released into the public domain.\r
+\r
+Anyone is free to copy, modify, publish, use, compile, sell, or\r
+distribute this software, either in source code form or as a compiled\r
+binary, for any purpose, commercial or non-commercial, and by any\r
+means.\r
+\r
+In jurisdictions that recognize copyright laws, the author or authors\r
+of this software dedicate any and all copyright interest in the\r
+software to the public domain. We make this dedication for the benefit\r
+of the public at large and to the detriment of our heirs and\r
+successors. We intend this dedication to be an overt act of\r
+relinquishment in perpetuity of all present and future rights to this\r
+software under copyright law.\r
+\r
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\r
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\r
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\r
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r
+OTHER DEALINGS IN THE SOFTWARE.\r
+\r
+For more information, please refer to <http://unlicense.org>\r
--- /dev/null
+The MIT Licence.\r
+\r
+Copyright (c) 2025 Michael Mclaughlin\r
+\r
+Permission is hereby granted, free of charge, to any person obtaining\r
+a copy of this software and associated documentation files (the\r
+'Software'), to deal in the Software without restriction, including\r
+without limitation the rights to use, copy, modify, merge, publish,\r
+distribute, sublicense, and/or sell copies of the Software, and to\r
+permit persons to whom the Software is furnished to do so, subject to\r
+the following conditions:\r
+\r
+The above copyright notice and this permission notice shall be\r
+included in all copies or substantial portions of the Software.\r
+\r
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\r
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\r
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\r
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\r
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\r
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
+\r
--- /dev/null
+;(function (globalScope) {\r
+ 'use strict';\r
+\r
+\r
+ /*!\r
+ * decimal.js v10.6.0\r
+ * An arbitrary-precision Decimal type for JavaScript.\r
+ * https://github.com/MikeMcl/decimal.js\r
+ * Copyright (c) 2025 Michael Mclaughlin <M8ch88l@gmail.com>\r
+ * MIT Licence\r
+ */\r
+\r
+\r
+ // ----------------------------------- EDITABLE DEFAULTS ------------------------------------ //\r
+\r
+\r
+ // The maximum exponent magnitude.\r
+ // The limit on the value of `toExpNeg`, `toExpPos`, `minE` and `maxE`.\r
+ var EXP_LIMIT = 9e15, // 0 to 9e15\r
+\r
+ // The limit on the value of `precision`, and on the value of the first argument to\r
+ // `toDecimalPlaces`, `toExponential`, `toFixed`, `toPrecision` and `toSignificantDigits`.\r
+ MAX_DIGITS = 1e9, // 0 to 1e9\r
+\r
+ // Base conversion alphabet.\r
+ NUMERALS = '0123456789abcdef',\r
+\r
+ // The natural logarithm of 10 (1025 digits).\r
+ LN10 = '2.3025850929940456840179914546843642076011014886287729760333279009675726096773524802359972050895982983419677840422862486334095254650828067566662873690987816894829072083255546808437998948262331985283935053089653777326288461633662222876982198867465436674744042432743651550489343149393914796194044002221051017141748003688084012647080685567743216228355220114804663715659121373450747856947683463616792101806445070648000277502684916746550586856935673420670581136429224554405758925724208241314695689016758940256776311356919292033376587141660230105703089634572075440370847469940168269282808481184289314848524948644871927809676271275775397027668605952496716674183485704422507197965004714951050492214776567636938662976979522110718264549734772662425709429322582798502585509785265383207606726317164309505995087807523710333101197857547331541421808427543863591778117054309827482385045648019095610299291824318237525357709750539565187697510374970888692180205189339507238539205144634197265287286965110862571492198849978748873771345686209167058',\r
+\r
+ // Pi (1025 digits).\r
+ PI = '3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632789',\r
+\r
+\r
+ // The initial configuration properties of the Decimal constructor.\r
+ DEFAULTS = {\r
+\r
+ // These values must be integers within the stated ranges (inclusive).\r
+ // Most of these values can be changed at run-time using the `Decimal.config` method.\r
+\r
+ // The maximum number of significant digits of the result of a calculation or base conversion.\r
+ // E.g. `Decimal.config({ precision: 20 });`\r
+ precision: 20, // 1 to MAX_DIGITS\r
+\r
+ // The rounding mode used when rounding to `precision`.\r
+ //\r
+ // ROUND_UP 0 Away from zero.\r
+ // ROUND_DOWN 1 Towards zero.\r
+ // ROUND_CEIL 2 Towards +Infinity.\r
+ // ROUND_FLOOR 3 Towards -Infinity.\r
+ // ROUND_HALF_UP 4 Towards nearest neighbour. If equidistant, up.\r
+ // ROUND_HALF_DOWN 5 Towards nearest neighbour. If equidistant, down.\r
+ // ROUND_HALF_EVEN 6 Towards nearest neighbour. If equidistant, towards even neighbour.\r
+ // ROUND_HALF_CEIL 7 Towards nearest neighbour. If equidistant, towards +Infinity.\r
+ // ROUND_HALF_FLOOR 8 Towards nearest neighbour. If equidistant, towards -Infinity.\r
+ //\r
+ // E.g.\r
+ // `Decimal.rounding = 4;`\r
+ // `Decimal.rounding = Decimal.ROUND_HALF_UP;`\r
+ rounding: 4, // 0 to 8\r
+\r
+ // The modulo mode used when calculating the modulus: a mod n.\r
+ // The quotient (q = a / n) is calculated according to the corresponding rounding mode.\r
+ // The remainder (r) is calculated as: r = a - n * q.\r
+ //\r
+ // UP 0 The remainder is positive if the dividend is negative, else is negative.\r
+ // DOWN 1 The remainder has the same sign as the dividend (JavaScript %).\r
+ // FLOOR 3 The remainder has the same sign as the divisor (Python %).\r
+ // HALF_EVEN 6 The IEEE 754 remainder function.\r
+ // EUCLID 9 Euclidian division. q = sign(n) * floor(a / abs(n)). Always positive.\r
+ //\r
+ // Truncated division (1), floored division (3), the IEEE 754 remainder (6), and Euclidian\r
+ // division (9) are commonly used for the modulus operation. The other rounding modes can also\r
+ // be used, but they may not give useful results.\r
+ modulo: 1, // 0 to 9\r
+\r
+ // The exponent value at and beneath which `toString` returns exponential notation.\r
+ // JavaScript numbers: -7\r
+ toExpNeg: -7, // 0 to -EXP_LIMIT\r
+\r
+ // The exponent value at and above which `toString` returns exponential notation.\r
+ // JavaScript numbers: 21\r
+ toExpPos: 21, // 0 to EXP_LIMIT\r
+\r
+ // The minimum exponent value, beneath which underflow to zero occurs.\r
+ // JavaScript numbers: -324 (5e-324)\r
+ minE: -EXP_LIMIT, // -1 to -EXP_LIMIT\r
+\r
+ // The maximum exponent value, above which overflow to Infinity occurs.\r
+ // JavaScript numbers: 308 (1.7976931348623157e+308)\r
+ maxE: EXP_LIMIT, // 1 to EXP_LIMIT\r
+\r
+ // Whether to use cryptographically-secure random number generation, if available.\r
+ crypto: false // true/false\r
+ },\r
+\r
+\r
+ // ----------------------------------- END OF EDITABLE DEFAULTS ------------------------------- //\r
+\r
+\r
+ Decimal, inexact, noConflict, quadrant,\r
+ external = true,\r
+\r
+ decimalError = '[DecimalError] ',\r
+ invalidArgument = decimalError + 'Invalid argument: ',\r
+ precisionLimitExceeded = decimalError + 'Precision limit exceeded',\r
+ cryptoUnavailable = decimalError + 'crypto unavailable',\r
+ tag = '[object Decimal]',\r
+\r
+ mathfloor = Math.floor,\r
+ mathpow = Math.pow,\r
+\r
+ isBinary = /^0b([01]+(\.[01]*)?|\.[01]+)(p[+-]?\d+)?$/i,\r
+ isHex = /^0x([0-9a-f]+(\.[0-9a-f]*)?|\.[0-9a-f]+)(p[+-]?\d+)?$/i,\r
+ isOctal = /^0o([0-7]+(\.[0-7]*)?|\.[0-7]+)(p[+-]?\d+)?$/i,\r
+ isDecimal = /^(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,\r
+\r
+ BASE = 1e7,\r
+ LOG_BASE = 7,\r
+ MAX_SAFE_INTEGER = 9007199254740991,\r
+\r
+ LN10_PRECISION = LN10.length - 1,\r
+ PI_PRECISION = PI.length - 1,\r
+\r
+ // Decimal.prototype object\r
+ P = { toStringTag: tag };\r
+\r
+\r
+ // Decimal prototype methods\r
+\r
+\r
+ /*\r
+ * absoluteValue abs\r
+ * ceil\r
+ * clampedTo clamp\r
+ * comparedTo cmp\r
+ * cosine cos\r
+ * cubeRoot cbrt\r
+ * decimalPlaces dp\r
+ * dividedBy div\r
+ * dividedToIntegerBy divToInt\r
+ * equals eq\r
+ * floor\r
+ * greaterThan gt\r
+ * greaterThanOrEqualTo gte\r
+ * hyperbolicCosine cosh\r
+ * hyperbolicSine sinh\r
+ * hyperbolicTangent tanh\r
+ * inverseCosine acos\r
+ * inverseHyperbolicCosine acosh\r
+ * inverseHyperbolicSine asinh\r
+ * inverseHyperbolicTangent atanh\r
+ * inverseSine asin\r
+ * inverseTangent atan\r
+ * isFinite\r
+ * isInteger isInt\r
+ * isNaN\r
+ * isNegative isNeg\r
+ * isPositive isPos\r
+ * isZero\r
+ * lessThan lt\r
+ * lessThanOrEqualTo lte\r
+ * logarithm log\r
+ * [maximum] [max]\r
+ * [minimum] [min]\r
+ * minus sub\r
+ * modulo mod\r
+ * naturalExponential exp\r
+ * naturalLogarithm ln\r
+ * negated neg\r
+ * plus add\r
+ * precision sd\r
+ * round\r
+ * sine sin\r
+ * squareRoot sqrt\r
+ * tangent tan\r
+ * times mul\r
+ * toBinary\r
+ * toDecimalPlaces toDP\r
+ * toExponential\r
+ * toFixed\r
+ * toFraction\r
+ * toHexadecimal toHex\r
+ * toNearest\r
+ * toNumber\r
+ * toOctal\r
+ * toPower pow\r
+ * toPrecision\r
+ * toSignificantDigits toSD\r
+ * toString\r
+ * truncated trunc\r
+ * valueOf toJSON\r
+ */\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the absolute value of this Decimal.\r
+ *\r
+ */\r
+ P.absoluteValue = P.abs = function () {\r
+ var x = new this.constructor(this);\r
+ if (x.s < 0) x.s = 1;\r
+ return finalise(x);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the value of this Decimal rounded to a whole number in the\r
+ * direction of positive Infinity.\r
+ *\r
+ */\r
+ P.ceil = function () {\r
+ return finalise(new this.constructor(this), this.e + 1, 2);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the value of this Decimal clamped to the range\r
+ * delineated by `min` and `max`.\r
+ *\r
+ * min {number|string|bigint|Decimal}\r
+ * max {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ P.clampedTo = P.clamp = function (min, max) {\r
+ var k,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+ min = new Ctor(min);\r
+ max = new Ctor(max);\r
+ if (!min.s || !max.s) return new Ctor(NaN);\r
+ if (min.gt(max)) throw Error(invalidArgument + max);\r
+ k = x.cmp(min);\r
+ return k < 0 ? min : x.cmp(max) > 0 ? max : new Ctor(x);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return\r
+ * 1 if the value of this Decimal is greater than the value of `y`,\r
+ * -1 if the value of this Decimal is less than the value of `y`,\r
+ * 0 if they have the same value,\r
+ * NaN if the value of either Decimal is NaN.\r
+ *\r
+ */\r
+ P.comparedTo = P.cmp = function (y) {\r
+ var i, j, xdL, ydL,\r
+ x = this,\r
+ xd = x.d,\r
+ yd = (y = new x.constructor(y)).d,\r
+ xs = x.s,\r
+ ys = y.s;\r
+\r
+ // Either NaN or ±Infinity?\r
+ if (!xd || !yd) {\r
+ return !xs || !ys ? NaN : xs !== ys ? xs : xd === yd ? 0 : !xd ^ xs < 0 ? 1 : -1;\r
+ }\r
+\r
+ // Either zero?\r
+ if (!xd[0] || !yd[0]) return xd[0] ? xs : yd[0] ? -ys : 0;\r
+\r
+ // Signs differ?\r
+ if (xs !== ys) return xs;\r
+\r
+ // Compare exponents.\r
+ if (x.e !== y.e) return x.e > y.e ^ xs < 0 ? 1 : -1;\r
+\r
+ xdL = xd.length;\r
+ ydL = yd.length;\r
+\r
+ // Compare digit by digit.\r
+ for (i = 0, j = xdL < ydL ? xdL : ydL; i < j; ++i) {\r
+ if (xd[i] !== yd[i]) return xd[i] > yd[i] ^ xs < 0 ? 1 : -1;\r
+ }\r
+\r
+ // Compare lengths.\r
+ return xdL === ydL ? 0 : xdL > ydL ^ xs < 0 ? 1 : -1;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the cosine of the value in radians of this Decimal.\r
+ *\r
+ * Domain: [-Infinity, Infinity]\r
+ * Range: [-1, 1]\r
+ *\r
+ * cos(0) = 1\r
+ * cos(-0) = 1\r
+ * cos(Infinity) = NaN\r
+ * cos(-Infinity) = NaN\r
+ * cos(NaN) = NaN\r
+ *\r
+ */\r
+ P.cosine = P.cos = function () {\r
+ var pr, rm,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (!x.d) return new Ctor(NaN);\r
+\r
+ // cos(0) = cos(-0) = 1\r
+ if (!x.d[0]) return new Ctor(1);\r
+\r
+ pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+ Ctor.precision = pr + Math.max(x.e, x.sd()) + LOG_BASE;\r
+ Ctor.rounding = 1;\r
+\r
+ x = cosine(Ctor, toLessThanHalfPi(Ctor, x));\r
+\r
+ Ctor.precision = pr;\r
+ Ctor.rounding = rm;\r
+\r
+ return finalise(quadrant == 2 || quadrant == 3 ? x.neg() : x, pr, rm, true);\r
+ };\r
+\r
+\r
+ /*\r
+ *\r
+ * Return a new Decimal whose value is the cube root of the value of this Decimal, rounded to\r
+ * `precision` significant digits using rounding mode `rounding`.\r
+ *\r
+ * cbrt(0) = 0\r
+ * cbrt(-0) = -0\r
+ * cbrt(1) = 1\r
+ * cbrt(-1) = -1\r
+ * cbrt(N) = N\r
+ * cbrt(-I) = -I\r
+ * cbrt(I) = I\r
+ *\r
+ * Math.cbrt(x) = (x < 0 ? -Math.pow(-x, 1/3) : Math.pow(x, 1/3))\r
+ *\r
+ */\r
+ P.cubeRoot = P.cbrt = function () {\r
+ var e, m, n, r, rep, s, sd, t, t3, t3plusx,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (!x.isFinite() || x.isZero()) return new Ctor(x);\r
+ external = false;\r
+\r
+ // Initial estimate.\r
+ s = x.s * mathpow(x.s * x, 1 / 3);\r
+\r
+ // Math.cbrt underflow/overflow?\r
+ // Pass x to Math.pow as integer, then adjust the exponent of the result.\r
+ if (!s || Math.abs(s) == 1 / 0) {\r
+ n = digitsToString(x.d);\r
+ e = x.e;\r
+\r
+ // Adjust n exponent so it is a multiple of 3 away from x exponent.\r
+ if (s = (e - n.length + 1) % 3) n += (s == 1 || s == -2 ? '0' : '00');\r
+ s = mathpow(n, 1 / 3);\r
+\r
+ // Rarely, e may be one less than the result exponent value.\r
+ e = mathfloor((e + 1) / 3) - (e % 3 == (e < 0 ? -1 : 2));\r
+\r
+ if (s == 1 / 0) {\r
+ n = '5e' + e;\r
+ } else {\r
+ n = s.toExponential();\r
+ n = n.slice(0, n.indexOf('e') + 1) + e;\r
+ }\r
+\r
+ r = new Ctor(n);\r
+ r.s = x.s;\r
+ } else {\r
+ r = new Ctor(s.toString());\r
+ }\r
+\r
+ sd = (e = Ctor.precision) + 3;\r
+\r
+ // Halley's method.\r
+ // TODO? Compare Newton's method.\r
+ for (;;) {\r
+ t = r;\r
+ t3 = t.times(t).times(t);\r
+ t3plusx = t3.plus(x);\r
+ r = divide(t3plusx.plus(x).times(t), t3plusx.plus(t3), sd + 2, 1);\r
+\r
+ // TODO? Replace with for-loop and checkRoundingDigits.\r
+ if (digitsToString(t.d).slice(0, sd) === (n = digitsToString(r.d)).slice(0, sd)) {\r
+ n = n.slice(sd - 3, sd + 1);\r
+\r
+ // The 4th rounding digit may be in error by -1 so if the 4 rounding digits are 9999 or 4999\r
+ // , i.e. approaching a rounding boundary, continue the iteration.\r
+ if (n == '9999' || !rep && n == '4999') {\r
+\r
+ // On the first iteration only, check to see if rounding up gives the exact result as the\r
+ // nines may infinitely repeat.\r
+ if (!rep) {\r
+ finalise(t, e + 1, 0);\r
+\r
+ if (t.times(t).times(t).eq(x)) {\r
+ r = t;\r
+ break;\r
+ }\r
+ }\r
+\r
+ sd += 4;\r
+ rep = 1;\r
+ } else {\r
+\r
+ // If the rounding digits are null, 0{0,4} or 50{0,3}, check for an exact result.\r
+ // If not, then there are further digits and m will be truthy.\r
+ if (!+n || !+n.slice(1) && n.charAt(0) == '5') {\r
+\r
+ // Truncate to the first rounding digit.\r
+ finalise(r, e + 1, 1);\r
+ m = !r.times(r).times(r).eq(x);\r
+ }\r
+\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ external = true;\r
+\r
+ return finalise(r, e, Ctor.rounding, m);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return the number of decimal places of the value of this Decimal.\r
+ *\r
+ */\r
+ P.decimalPlaces = P.dp = function () {\r
+ var w,\r
+ d = this.d,\r
+ n = NaN;\r
+\r
+ if (d) {\r
+ w = d.length - 1;\r
+ n = (w - mathfloor(this.e / LOG_BASE)) * LOG_BASE;\r
+\r
+ // Subtract the number of trailing zeros of the last word.\r
+ w = d[w];\r
+ if (w) for (; w % 10 == 0; w /= 10) n--;\r
+ if (n < 0) n = 0;\r
+ }\r
+\r
+ return n;\r
+ };\r
+\r
+\r
+ /*\r
+ * n / 0 = I\r
+ * n / N = N\r
+ * n / I = 0\r
+ * 0 / n = 0\r
+ * 0 / 0 = N\r
+ * 0 / N = N\r
+ * 0 / I = 0\r
+ * N / n = N\r
+ * N / 0 = N\r
+ * N / N = N\r
+ * N / I = N\r
+ * I / n = I\r
+ * I / 0 = I\r
+ * I / N = N\r
+ * I / I = N\r
+ *\r
+ * Return a new Decimal whose value is the value of this Decimal divided by `y`, rounded to\r
+ * `precision` significant digits using rounding mode `rounding`.\r
+ *\r
+ */\r
+ P.dividedBy = P.div = function (y) {\r
+ return divide(this, new this.constructor(y));\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the integer part of dividing the value of this Decimal\r
+ * by the value of `y`, rounded to `precision` significant digits using rounding mode `rounding`.\r
+ *\r
+ */\r
+ P.dividedToIntegerBy = P.divToInt = function (y) {\r
+ var x = this,\r
+ Ctor = x.constructor;\r
+ return finalise(divide(x, new Ctor(y), 0, 1, 1), Ctor.precision, Ctor.rounding);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return true if the value of this Decimal is equal to the value of `y`, otherwise return false.\r
+ *\r
+ */\r
+ P.equals = P.eq = function (y) {\r
+ return this.cmp(y) === 0;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the value of this Decimal rounded to a whole number in the\r
+ * direction of negative Infinity.\r
+ *\r
+ */\r
+ P.floor = function () {\r
+ return finalise(new this.constructor(this), this.e + 1, 3);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return true if the value of this Decimal is greater than the value of `y`, otherwise return\r
+ * false.\r
+ *\r
+ */\r
+ P.greaterThan = P.gt = function (y) {\r
+ return this.cmp(y) > 0;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return true if the value of this Decimal is greater than or equal to the value of `y`,\r
+ * otherwise return false.\r
+ *\r
+ */\r
+ P.greaterThanOrEqualTo = P.gte = function (y) {\r
+ var k = this.cmp(y);\r
+ return k == 1 || k === 0;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the hyperbolic cosine of the value in radians of this\r
+ * Decimal.\r
+ *\r
+ * Domain: [-Infinity, Infinity]\r
+ * Range: [1, Infinity]\r
+ *\r
+ * cosh(x) = 1 + x^2/2! + x^4/4! + x^6/6! + ...\r
+ *\r
+ * cosh(0) = 1\r
+ * cosh(-0) = 1\r
+ * cosh(Infinity) = Infinity\r
+ * cosh(-Infinity) = Infinity\r
+ * cosh(NaN) = NaN\r
+ *\r
+ * x time taken (ms) result\r
+ * 1000 9 9.8503555700852349694e+433\r
+ * 10000 25 4.4034091128314607936e+4342\r
+ * 100000 171 1.4033316802130615897e+43429\r
+ * 1000000 3817 1.5166076984010437725e+434294\r
+ * 10000000 abandoned after 2 minute wait\r
+ *\r
+ * TODO? Compare performance of cosh(x) = 0.5 * (exp(x) + exp(-x))\r
+ *\r
+ */\r
+ P.hyperbolicCosine = P.cosh = function () {\r
+ var k, n, pr, rm, len,\r
+ x = this,\r
+ Ctor = x.constructor,\r
+ one = new Ctor(1);\r
+\r
+ if (!x.isFinite()) return new Ctor(x.s ? 1 / 0 : NaN);\r
+ if (x.isZero()) return one;\r
+\r
+ pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+ Ctor.precision = pr + Math.max(x.e, x.sd()) + 4;\r
+ Ctor.rounding = 1;\r
+ len = x.d.length;\r
+\r
+ // Argument reduction: cos(4x) = 1 - 8cos^2(x) + 8cos^4(x) + 1\r
+ // i.e. cos(x) = 1 - cos^2(x/4)(8 - 8cos^2(x/4))\r
+\r
+ // Estimate the optimum number of times to use the argument reduction.\r
+ // TODO? Estimation reused from cosine() and may not be optimal here.\r
+ if (len < 32) {\r
+ k = Math.ceil(len / 3);\r
+ n = (1 / tinyPow(4, k)).toString();\r
+ } else {\r
+ k = 16;\r
+ n = '2.3283064365386962890625e-10';\r
+ }\r
+\r
+ x = taylorSeries(Ctor, 1, x.times(n), new Ctor(1), true);\r
+\r
+ // Reverse argument reduction\r
+ var cosh2_x,\r
+ i = k,\r
+ d8 = new Ctor(8);\r
+ for (; i--;) {\r
+ cosh2_x = x.times(x);\r
+ x = one.minus(cosh2_x.times(d8.minus(cosh2_x.times(d8))));\r
+ }\r
+\r
+ return finalise(x, Ctor.precision = pr, Ctor.rounding = rm, true);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the hyperbolic sine of the value in radians of this\r
+ * Decimal.\r
+ *\r
+ * Domain: [-Infinity, Infinity]\r
+ * Range: [-Infinity, Infinity]\r
+ *\r
+ * sinh(x) = x + x^3/3! + x^5/5! + x^7/7! + ...\r
+ *\r
+ * sinh(0) = 0\r
+ * sinh(-0) = -0\r
+ * sinh(Infinity) = Infinity\r
+ * sinh(-Infinity) = -Infinity\r
+ * sinh(NaN) = NaN\r
+ *\r
+ * x time taken (ms)\r
+ * 10 2 ms\r
+ * 100 5 ms\r
+ * 1000 14 ms\r
+ * 10000 82 ms\r
+ * 100000 886 ms 1.4033316802130615897e+43429\r
+ * 200000 2613 ms\r
+ * 300000 5407 ms\r
+ * 400000 8824 ms\r
+ * 500000 13026 ms 8.7080643612718084129e+217146\r
+ * 1000000 48543 ms\r
+ *\r
+ * TODO? Compare performance of sinh(x) = 0.5 * (exp(x) - exp(-x))\r
+ *\r
+ */\r
+ P.hyperbolicSine = P.sinh = function () {\r
+ var k, pr, rm, len,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (!x.isFinite() || x.isZero()) return new Ctor(x);\r
+\r
+ pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+ Ctor.precision = pr + Math.max(x.e, x.sd()) + 4;\r
+ Ctor.rounding = 1;\r
+ len = x.d.length;\r
+\r
+ if (len < 3) {\r
+ x = taylorSeries(Ctor, 2, x, x, true);\r
+ } else {\r
+\r
+ // Alternative argument reduction: sinh(3x) = sinh(x)(3 + 4sinh^2(x))\r
+ // i.e. sinh(x) = sinh(x/3)(3 + 4sinh^2(x/3))\r
+ // 3 multiplications and 1 addition\r
+\r
+ // Argument reduction: sinh(5x) = sinh(x)(5 + sinh^2(x)(20 + 16sinh^2(x)))\r
+ // i.e. sinh(x) = sinh(x/5)(5 + sinh^2(x/5)(20 + 16sinh^2(x/5)))\r
+ // 4 multiplications and 2 additions\r
+\r
+ // Estimate the optimum number of times to use the argument reduction.\r
+ k = 1.4 * Math.sqrt(len);\r
+ k = k > 16 ? 16 : k | 0;\r
+\r
+ x = x.times(1 / tinyPow(5, k));\r
+ x = taylorSeries(Ctor, 2, x, x, true);\r
+\r
+ // Reverse argument reduction\r
+ var sinh2_x,\r
+ d5 = new Ctor(5),\r
+ d16 = new Ctor(16),\r
+ d20 = new Ctor(20);\r
+ for (; k--;) {\r
+ sinh2_x = x.times(x);\r
+ x = x.times(d5.plus(sinh2_x.times(d16.times(sinh2_x).plus(d20))));\r
+ }\r
+ }\r
+\r
+ Ctor.precision = pr;\r
+ Ctor.rounding = rm;\r
+\r
+ return finalise(x, pr, rm, true);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the hyperbolic tangent of the value in radians of this\r
+ * Decimal.\r
+ *\r
+ * Domain: [-Infinity, Infinity]\r
+ * Range: [-1, 1]\r
+ *\r
+ * tanh(x) = sinh(x) / cosh(x)\r
+ *\r
+ * tanh(0) = 0\r
+ * tanh(-0) = -0\r
+ * tanh(Infinity) = 1\r
+ * tanh(-Infinity) = -1\r
+ * tanh(NaN) = NaN\r
+ *\r
+ */\r
+ P.hyperbolicTangent = P.tanh = function () {\r
+ var pr, rm,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (!x.isFinite()) return new Ctor(x.s);\r
+ if (x.isZero()) return new Ctor(x);\r
+\r
+ pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+ Ctor.precision = pr + 7;\r
+ Ctor.rounding = 1;\r
+\r
+ return divide(x.sinh(), x.cosh(), Ctor.precision = pr, Ctor.rounding = rm);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the arccosine (inverse cosine) in radians of the value of\r
+ * this Decimal.\r
+ *\r
+ * Domain: [-1, 1]\r
+ * Range: [0, pi]\r
+ *\r
+ * acos(x) = pi/2 - asin(x)\r
+ *\r
+ * acos(0) = pi/2\r
+ * acos(-0) = pi/2\r
+ * acos(1) = 0\r
+ * acos(-1) = pi\r
+ * acos(1/2) = pi/3\r
+ * acos(-1/2) = 2*pi/3\r
+ * acos(|x| > 1) = NaN\r
+ * acos(NaN) = NaN\r
+ *\r
+ */\r
+ P.inverseCosine = P.acos = function () {\r
+ var x = this,\r
+ Ctor = x.constructor,\r
+ k = x.abs().cmp(1),\r
+ pr = Ctor.precision,\r
+ rm = Ctor.rounding;\r
+\r
+ if (k !== -1) {\r
+ return k === 0\r
+ // |x| is 1\r
+ ? x.isNeg() ? getPi(Ctor, pr, rm) : new Ctor(0)\r
+ // |x| > 1 or x is NaN\r
+ : new Ctor(NaN);\r
+ }\r
+\r
+ if (x.isZero()) return getPi(Ctor, pr + 4, rm).times(0.5);\r
+\r
+ // TODO? Special case acos(0.5) = pi/3 and acos(-0.5) = 2*pi/3\r
+\r
+ Ctor.precision = pr + 6;\r
+ Ctor.rounding = 1;\r
+\r
+ // See https://github.com/MikeMcl/decimal.js/pull/217\r
+ x = new Ctor(1).minus(x).div(x.plus(1)).sqrt().atan();\r
+\r
+ Ctor.precision = pr;\r
+ Ctor.rounding = rm;\r
+\r
+ return x.times(2);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the inverse of the hyperbolic cosine in radians of the\r
+ * value of this Decimal.\r
+ *\r
+ * Domain: [1, Infinity]\r
+ * Range: [0, Infinity]\r
+ *\r
+ * acosh(x) = ln(x + sqrt(x^2 - 1))\r
+ *\r
+ * acosh(x < 1) = NaN\r
+ * acosh(NaN) = NaN\r
+ * acosh(Infinity) = Infinity\r
+ * acosh(-Infinity) = NaN\r
+ * acosh(0) = NaN\r
+ * acosh(-0) = NaN\r
+ * acosh(1) = 0\r
+ * acosh(-1) = NaN\r
+ *\r
+ */\r
+ P.inverseHyperbolicCosine = P.acosh = function () {\r
+ var pr, rm,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (x.lte(1)) return new Ctor(x.eq(1) ? 0 : NaN);\r
+ if (!x.isFinite()) return new Ctor(x);\r
+\r
+ pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+ Ctor.precision = pr + Math.max(Math.abs(x.e), x.sd()) + 4;\r
+ Ctor.rounding = 1;\r
+ external = false;\r
+\r
+ x = x.times(x).minus(1).sqrt().plus(x);\r
+\r
+ external = true;\r
+ Ctor.precision = pr;\r
+ Ctor.rounding = rm;\r
+\r
+ return x.ln();\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the inverse of the hyperbolic sine in radians of the value\r
+ * of this Decimal.\r
+ *\r
+ * Domain: [-Infinity, Infinity]\r
+ * Range: [-Infinity, Infinity]\r
+ *\r
+ * asinh(x) = ln(x + sqrt(x^2 + 1))\r
+ *\r
+ * asinh(NaN) = NaN\r
+ * asinh(Infinity) = Infinity\r
+ * asinh(-Infinity) = -Infinity\r
+ * asinh(0) = 0\r
+ * asinh(-0) = -0\r
+ *\r
+ */\r
+ P.inverseHyperbolicSine = P.asinh = function () {\r
+ var pr, rm,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (!x.isFinite() || x.isZero()) return new Ctor(x);\r
+\r
+ pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+ Ctor.precision = pr + 2 * Math.max(Math.abs(x.e), x.sd()) + 6;\r
+ Ctor.rounding = 1;\r
+ external = false;\r
+\r
+ x = x.times(x).plus(1).sqrt().plus(x);\r
+\r
+ external = true;\r
+ Ctor.precision = pr;\r
+ Ctor.rounding = rm;\r
+\r
+ return x.ln();\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the inverse of the hyperbolic tangent in radians of the\r
+ * value of this Decimal.\r
+ *\r
+ * Domain: [-1, 1]\r
+ * Range: [-Infinity, Infinity]\r
+ *\r
+ * atanh(x) = 0.5 * ln((1 + x) / (1 - x))\r
+ *\r
+ * atanh(|x| > 1) = NaN\r
+ * atanh(NaN) = NaN\r
+ * atanh(Infinity) = NaN\r
+ * atanh(-Infinity) = NaN\r
+ * atanh(0) = 0\r
+ * atanh(-0) = -0\r
+ * atanh(1) = Infinity\r
+ * atanh(-1) = -Infinity\r
+ *\r
+ */\r
+ P.inverseHyperbolicTangent = P.atanh = function () {\r
+ var pr, rm, wpr, xsd,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (!x.isFinite()) return new Ctor(NaN);\r
+ if (x.e >= 0) return new Ctor(x.abs().eq(1) ? x.s / 0 : x.isZero() ? x : NaN);\r
+\r
+ pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+ xsd = x.sd();\r
+\r
+ if (Math.max(xsd, pr) < 2 * -x.e - 1) return finalise(new Ctor(x), pr, rm, true);\r
+\r
+ Ctor.precision = wpr = xsd - x.e;\r
+\r
+ x = divide(x.plus(1), new Ctor(1).minus(x), wpr + pr, 1);\r
+\r
+ Ctor.precision = pr + 4;\r
+ Ctor.rounding = 1;\r
+\r
+ x = x.ln();\r
+\r
+ Ctor.precision = pr;\r
+ Ctor.rounding = rm;\r
+\r
+ return x.times(0.5);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the arcsine (inverse sine) in radians of the value of this\r
+ * Decimal.\r
+ *\r
+ * Domain: [-Infinity, Infinity]\r
+ * Range: [-pi/2, pi/2]\r
+ *\r
+ * asin(x) = 2*atan(x/(1 + sqrt(1 - x^2)))\r
+ *\r
+ * asin(0) = 0\r
+ * asin(-0) = -0\r
+ * asin(1/2) = pi/6\r
+ * asin(-1/2) = -pi/6\r
+ * asin(1) = pi/2\r
+ * asin(-1) = -pi/2\r
+ * asin(|x| > 1) = NaN\r
+ * asin(NaN) = NaN\r
+ *\r
+ * TODO? Compare performance of Taylor series.\r
+ *\r
+ */\r
+ P.inverseSine = P.asin = function () {\r
+ var halfPi, k,\r
+ pr, rm,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (x.isZero()) return new Ctor(x);\r
+\r
+ k = x.abs().cmp(1);\r
+ pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+\r
+ if (k !== -1) {\r
+\r
+ // |x| is 1\r
+ if (k === 0) {\r
+ halfPi = getPi(Ctor, pr + 4, rm).times(0.5);\r
+ halfPi.s = x.s;\r
+ return halfPi;\r
+ }\r
+\r
+ // |x| > 1 or x is NaN\r
+ return new Ctor(NaN);\r
+ }\r
+\r
+ // TODO? Special case asin(1/2) = pi/6 and asin(-1/2) = -pi/6\r
+\r
+ Ctor.precision = pr + 6;\r
+ Ctor.rounding = 1;\r
+\r
+ x = x.div(new Ctor(1).minus(x.times(x)).sqrt().plus(1)).atan();\r
+\r
+ Ctor.precision = pr;\r
+ Ctor.rounding = rm;\r
+\r
+ return x.times(2);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the arctangent (inverse tangent) in radians of the value\r
+ * of this Decimal.\r
+ *\r
+ * Domain: [-Infinity, Infinity]\r
+ * Range: [-pi/2, pi/2]\r
+ *\r
+ * atan(x) = x - x^3/3 + x^5/5 - x^7/7 + ...\r
+ *\r
+ * atan(0) = 0\r
+ * atan(-0) = -0\r
+ * atan(1) = pi/4\r
+ * atan(-1) = -pi/4\r
+ * atan(Infinity) = pi/2\r
+ * atan(-Infinity) = -pi/2\r
+ * atan(NaN) = NaN\r
+ *\r
+ */\r
+ P.inverseTangent = P.atan = function () {\r
+ var i, j, k, n, px, t, r, wpr, x2,\r
+ x = this,\r
+ Ctor = x.constructor,\r
+ pr = Ctor.precision,\r
+ rm = Ctor.rounding;\r
+\r
+ if (!x.isFinite()) {\r
+ if (!x.s) return new Ctor(NaN);\r
+ if (pr + 4 <= PI_PRECISION) {\r
+ r = getPi(Ctor, pr + 4, rm).times(0.5);\r
+ r.s = x.s;\r
+ return r;\r
+ }\r
+ } else if (x.isZero()) {\r
+ return new Ctor(x);\r
+ } else if (x.abs().eq(1) && pr + 4 <= PI_PRECISION) {\r
+ r = getPi(Ctor, pr + 4, rm).times(0.25);\r
+ r.s = x.s;\r
+ return r;\r
+ }\r
+\r
+ Ctor.precision = wpr = pr + 10;\r
+ Ctor.rounding = 1;\r
+\r
+ // TODO? if (x >= 1 && pr <= PI_PRECISION) atan(x) = halfPi * x.s - atan(1 / x);\r
+\r
+ // Argument reduction\r
+ // Ensure |x| < 0.42\r
+ // atan(x) = 2 * atan(x / (1 + sqrt(1 + x^2)))\r
+\r
+ k = Math.min(28, wpr / LOG_BASE + 2 | 0);\r
+\r
+ for (i = k; i; --i) x = x.div(x.times(x).plus(1).sqrt().plus(1));\r
+\r
+ external = false;\r
+\r
+ j = Math.ceil(wpr / LOG_BASE);\r
+ n = 1;\r
+ x2 = x.times(x);\r
+ r = new Ctor(x);\r
+ px = x;\r
+\r
+ // atan(x) = x - x^3/3 + x^5/5 - x^7/7 + ...\r
+ for (; i !== -1;) {\r
+ px = px.times(x2);\r
+ t = r.minus(px.div(n += 2));\r
+\r
+ px = px.times(x2);\r
+ r = t.plus(px.div(n += 2));\r
+\r
+ if (r.d[j] !== void 0) for (i = j; r.d[i] === t.d[i] && i--;);\r
+ }\r
+\r
+ if (k) r = r.times(2 << (k - 1));\r
+\r
+ external = true;\r
+\r
+ return finalise(r, Ctor.precision = pr, Ctor.rounding = rm, true);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return true if the value of this Decimal is a finite number, otherwise return false.\r
+ *\r
+ */\r
+ P.isFinite = function () {\r
+ return !!this.d;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return true if the value of this Decimal is an integer, otherwise return false.\r
+ *\r
+ */\r
+ P.isInteger = P.isInt = function () {\r
+ return !!this.d && mathfloor(this.e / LOG_BASE) > this.d.length - 2;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return true if the value of this Decimal is NaN, otherwise return false.\r
+ *\r
+ */\r
+ P.isNaN = function () {\r
+ return !this.s;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return true if the value of this Decimal is negative, otherwise return false.\r
+ *\r
+ */\r
+ P.isNegative = P.isNeg = function () {\r
+ return this.s < 0;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return true if the value of this Decimal is positive, otherwise return false.\r
+ *\r
+ */\r
+ P.isPositive = P.isPos = function () {\r
+ return this.s > 0;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return true if the value of this Decimal is 0 or -0, otherwise return false.\r
+ *\r
+ */\r
+ P.isZero = function () {\r
+ return !!this.d && this.d[0] === 0;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return true if the value of this Decimal is less than `y`, otherwise return false.\r
+ *\r
+ */\r
+ P.lessThan = P.lt = function (y) {\r
+ return this.cmp(y) < 0;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return true if the value of this Decimal is less than or equal to `y`, otherwise return false.\r
+ *\r
+ */\r
+ P.lessThanOrEqualTo = P.lte = function (y) {\r
+ return this.cmp(y) < 1;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return the logarithm of the value of this Decimal to the specified base, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ * If no base is specified, return log[10](arg).\r
+ *\r
+ * log[base](arg) = ln(arg) / ln(base)\r
+ *\r
+ * The result will always be correctly rounded if the base of the log is 10, and 'almost always'\r
+ * otherwise:\r
+ *\r
+ * Depending on the rounding mode, the result may be incorrectly rounded if the first fifteen\r
+ * rounding digits are [49]99999999999999 or [50]00000000000000. In that case, the maximum error\r
+ * between the result and the correctly rounded result will be one ulp (unit in the last place).\r
+ *\r
+ * log[-b](a) = NaN\r
+ * log[0](a) = NaN\r
+ * log[1](a) = NaN\r
+ * log[NaN](a) = NaN\r
+ * log[Infinity](a) = NaN\r
+ * log[b](0) = -Infinity\r
+ * log[b](-0) = -Infinity\r
+ * log[b](-a) = NaN\r
+ * log[b](1) = 0\r
+ * log[b](Infinity) = Infinity\r
+ * log[b](NaN) = NaN\r
+ *\r
+ * [base] {number|string|bigint|Decimal} The base of the logarithm.\r
+ *\r
+ */\r
+ P.logarithm = P.log = function (base) {\r
+ var isBase10, d, denominator, k, inf, num, sd, r,\r
+ arg = this,\r
+ Ctor = arg.constructor,\r
+ pr = Ctor.precision,\r
+ rm = Ctor.rounding,\r
+ guard = 5;\r
+\r
+ // Default base is 10.\r
+ if (base == null) {\r
+ base = new Ctor(10);\r
+ isBase10 = true;\r
+ } else {\r
+ base = new Ctor(base);\r
+ d = base.d;\r
+\r
+ // Return NaN if base is negative, or non-finite, or is 0 or 1.\r
+ if (base.s < 0 || !d || !d[0] || base.eq(1)) return new Ctor(NaN);\r
+\r
+ isBase10 = base.eq(10);\r
+ }\r
+\r
+ d = arg.d;\r
+\r
+ // Is arg negative, non-finite, 0 or 1?\r
+ if (arg.s < 0 || !d || !d[0] || arg.eq(1)) {\r
+ return new Ctor(d && !d[0] ? -1 / 0 : arg.s != 1 ? NaN : d ? 0 : 1 / 0);\r
+ }\r
+\r
+ // The result will have a non-terminating decimal expansion if base is 10 and arg is not an\r
+ // integer power of 10.\r
+ if (isBase10) {\r
+ if (d.length > 1) {\r
+ inf = true;\r
+ } else {\r
+ for (k = d[0]; k % 10 === 0;) k /= 10;\r
+ inf = k !== 1;\r
+ }\r
+ }\r
+\r
+ external = false;\r
+ sd = pr + guard;\r
+ num = naturalLogarithm(arg, sd);\r
+ denominator = isBase10 ? getLn10(Ctor, sd + 10) : naturalLogarithm(base, sd);\r
+\r
+ // The result will have 5 rounding digits.\r
+ r = divide(num, denominator, sd, 1);\r
+\r
+ // If at a rounding boundary, i.e. the result's rounding digits are [49]9999 or [50]0000,\r
+ // calculate 10 further digits.\r
+ //\r
+ // If the result is known to have an infinite decimal expansion, repeat this until it is clear\r
+ // that the result is above or below the boundary. Otherwise, if after calculating the 10\r
+ // further digits, the last 14 are nines, round up and assume the result is exact.\r
+ // Also assume the result is exact if the last 14 are zero.\r
+ //\r
+ // Example of a result that will be incorrectly rounded:\r
+ // log[1048576](4503599627370502) = 2.60000000000000009610279511444746...\r
+ // The above result correctly rounded using ROUND_CEIL to 1 decimal place should be 2.7, but it\r
+ // will be given as 2.6 as there are 15 zeros immediately after the requested decimal place, so\r
+ // the exact result would be assumed to be 2.6, which rounded using ROUND_CEIL to 1 decimal\r
+ // place is still 2.6.\r
+ if (checkRoundingDigits(r.d, k = pr, rm)) {\r
+\r
+ do {\r
+ sd += 10;\r
+ num = naturalLogarithm(arg, sd);\r
+ denominator = isBase10 ? getLn10(Ctor, sd + 10) : naturalLogarithm(base, sd);\r
+ r = divide(num, denominator, sd, 1);\r
+\r
+ if (!inf) {\r
+\r
+ // Check for 14 nines from the 2nd rounding digit, as the first may be 4.\r
+ if (+digitsToString(r.d).slice(k + 1, k + 15) + 1 == 1e14) {\r
+ r = finalise(r, pr + 1, 0);\r
+ }\r
+\r
+ break;\r
+ }\r
+ } while (checkRoundingDigits(r.d, k += 10, rm));\r
+ }\r
+\r
+ external = true;\r
+\r
+ return finalise(r, pr, rm);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the maximum of the arguments and the value of this Decimal.\r
+ *\r
+ * arguments {number|string|bigint|Decimal}\r
+ *\r
+ P.max = function () {\r
+ Array.prototype.push.call(arguments, this);\r
+ return maxOrMin(this.constructor, arguments, -1);\r
+ };\r
+ */\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the minimum of the arguments and the value of this Decimal.\r
+ *\r
+ * arguments {number|string|bigint|Decimal}\r
+ *\r
+ P.min = function () {\r
+ Array.prototype.push.call(arguments, this);\r
+ return maxOrMin(this.constructor, arguments, 1);\r
+ };\r
+ */\r
+\r
+\r
+ /*\r
+ * n - 0 = n\r
+ * n - N = N\r
+ * n - I = -I\r
+ * 0 - n = -n\r
+ * 0 - 0 = 0\r
+ * 0 - N = N\r
+ * 0 - I = -I\r
+ * N - n = N\r
+ * N - 0 = N\r
+ * N - N = N\r
+ * N - I = N\r
+ * I - n = I\r
+ * I - 0 = I\r
+ * I - N = N\r
+ * I - I = N\r
+ *\r
+ * Return a new Decimal whose value is the value of this Decimal minus `y`, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ */\r
+ P.minus = P.sub = function (y) {\r
+ var d, e, i, j, k, len, pr, rm, xd, xe, xLTy, yd,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ y = new Ctor(y);\r
+\r
+ // If either is not finite...\r
+ if (!x.d || !y.d) {\r
+\r
+ // Return NaN if either is NaN.\r
+ if (!x.s || !y.s) y = new Ctor(NaN);\r
+\r
+ // Return y negated if x is finite and y is ±Infinity.\r
+ else if (x.d) y.s = -y.s;\r
+\r
+ // Return x if y is finite and x is ±Infinity.\r
+ // Return x if both are ±Infinity with different signs.\r
+ // Return NaN if both are ±Infinity with the same sign.\r
+ else y = new Ctor(y.d || x.s !== y.s ? x : NaN);\r
+\r
+ return y;\r
+ }\r
+\r
+ // If signs differ...\r
+ if (x.s != y.s) {\r
+ y.s = -y.s;\r
+ return x.plus(y);\r
+ }\r
+\r
+ xd = x.d;\r
+ yd = y.d;\r
+ pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+\r
+ // If either is zero...\r
+ if (!xd[0] || !yd[0]) {\r
+\r
+ // Return y negated if x is zero and y is non-zero.\r
+ if (yd[0]) y.s = -y.s;\r
+\r
+ // Return x if y is zero and x is non-zero.\r
+ else if (xd[0]) y = new Ctor(x);\r
+\r
+ // Return zero if both are zero.\r
+ // From IEEE 754 (2008) 6.3: 0 - 0 = -0 - -0 = -0 when rounding to -Infinity.\r
+ else return new Ctor(rm === 3 ? -0 : 0);\r
+\r
+ return external ? finalise(y, pr, rm) : y;\r
+ }\r
+\r
+ // x and y are finite, non-zero numbers with the same sign.\r
+\r
+ // Calculate base 1e7 exponents.\r
+ e = mathfloor(y.e / LOG_BASE);\r
+ xe = mathfloor(x.e / LOG_BASE);\r
+\r
+ xd = xd.slice();\r
+ k = xe - e;\r
+\r
+ // If base 1e7 exponents differ...\r
+ if (k) {\r
+ xLTy = k < 0;\r
+\r
+ if (xLTy) {\r
+ d = xd;\r
+ k = -k;\r
+ len = yd.length;\r
+ } else {\r
+ d = yd;\r
+ e = xe;\r
+ len = xd.length;\r
+ }\r
+\r
+ // Numbers with massively different exponents would result in a very high number of\r
+ // zeros needing to be prepended, but this can be avoided while still ensuring correct\r
+ // rounding by limiting the number of zeros to `Math.ceil(pr / LOG_BASE) + 2`.\r
+ i = Math.max(Math.ceil(pr / LOG_BASE), len) + 2;\r
+\r
+ if (k > i) {\r
+ k = i;\r
+ d.length = 1;\r
+ }\r
+\r
+ // Prepend zeros to equalise exponents.\r
+ d.reverse();\r
+ for (i = k; i--;) d.push(0);\r
+ d.reverse();\r
+\r
+ // Base 1e7 exponents equal.\r
+ } else {\r
+\r
+ // Check digits to determine which is the bigger number.\r
+\r
+ i = xd.length;\r
+ len = yd.length;\r
+ xLTy = i < len;\r
+ if (xLTy) len = i;\r
+\r
+ for (i = 0; i < len; i++) {\r
+ if (xd[i] != yd[i]) {\r
+ xLTy = xd[i] < yd[i];\r
+ break;\r
+ }\r
+ }\r
+\r
+ k = 0;\r
+ }\r
+\r
+ if (xLTy) {\r
+ d = xd;\r
+ xd = yd;\r
+ yd = d;\r
+ y.s = -y.s;\r
+ }\r
+\r
+ len = xd.length;\r
+\r
+ // Append zeros to `xd` if shorter.\r
+ // Don't add zeros to `yd` if shorter as subtraction only needs to start at `yd` length.\r
+ for (i = yd.length - len; i > 0; --i) xd[len++] = 0;\r
+\r
+ // Subtract yd from xd.\r
+ for (i = yd.length; i > k;) {\r
+\r
+ if (xd[--i] < yd[i]) {\r
+ for (j = i; j && xd[--j] === 0;) xd[j] = BASE - 1;\r
+ --xd[j];\r
+ xd[i] += BASE;\r
+ }\r
+\r
+ xd[i] -= yd[i];\r
+ }\r
+\r
+ // Remove trailing zeros.\r
+ for (; xd[--len] === 0;) xd.pop();\r
+\r
+ // Remove leading zeros and adjust exponent accordingly.\r
+ for (; xd[0] === 0; xd.shift()) --e;\r
+\r
+ // Zero?\r
+ if (!xd[0]) return new Ctor(rm === 3 ? -0 : 0);\r
+\r
+ y.d = xd;\r
+ y.e = getBase10Exponent(xd, e);\r
+\r
+ return external ? finalise(y, pr, rm) : y;\r
+ };\r
+\r
+\r
+ /*\r
+ * n % 0 = N\r
+ * n % N = N\r
+ * n % I = n\r
+ * 0 % n = 0\r
+ * -0 % n = -0\r
+ * 0 % 0 = N\r
+ * 0 % N = N\r
+ * 0 % I = 0\r
+ * N % n = N\r
+ * N % 0 = N\r
+ * N % N = N\r
+ * N % I = N\r
+ * I % n = N\r
+ * I % 0 = N\r
+ * I % N = N\r
+ * I % I = N\r
+ *\r
+ * Return a new Decimal whose value is the value of this Decimal modulo `y`, rounded to\r
+ * `precision` significant digits using rounding mode `rounding`.\r
+ *\r
+ * The result depends on the modulo mode.\r
+ *\r
+ */\r
+ P.modulo = P.mod = function (y) {\r
+ var q,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ y = new Ctor(y);\r
+\r
+ // Return NaN if x is ±Infinity or NaN, or y is NaN or ±0.\r
+ if (!x.d || !y.s || y.d && !y.d[0]) return new Ctor(NaN);\r
+\r
+ // Return x if y is ±Infinity or x is ±0.\r
+ if (!y.d || x.d && !x.d[0]) {\r
+ return finalise(new Ctor(x), Ctor.precision, Ctor.rounding);\r
+ }\r
+\r
+ // Prevent rounding of intermediate calculations.\r
+ external = false;\r
+\r
+ if (Ctor.modulo == 9) {\r
+\r
+ // Euclidian division: q = sign(y) * floor(x / abs(y))\r
+ // result = x - q * y where 0 <= result < abs(y)\r
+ q = divide(x, y.abs(), 0, 3, 1);\r
+ q.s *= y.s;\r
+ } else {\r
+ q = divide(x, y, 0, Ctor.modulo, 1);\r
+ }\r
+\r
+ q = q.times(y);\r
+\r
+ external = true;\r
+\r
+ return x.minus(q);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the natural exponential of the value of this Decimal,\r
+ * i.e. the base e raised to the power the value of this Decimal, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ */\r
+ P.naturalExponential = P.exp = function () {\r
+ return naturalExponential(this);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the natural logarithm of the value of this Decimal,\r
+ * rounded to `precision` significant digits using rounding mode `rounding`.\r
+ *\r
+ */\r
+ P.naturalLogarithm = P.ln = function () {\r
+ return naturalLogarithm(this);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the value of this Decimal negated, i.e. as if multiplied by\r
+ * -1.\r
+ *\r
+ */\r
+ P.negated = P.neg = function () {\r
+ var x = new this.constructor(this);\r
+ x.s = -x.s;\r
+ return finalise(x);\r
+ };\r
+\r
+\r
+ /*\r
+ * n + 0 = n\r
+ * n + N = N\r
+ * n + I = I\r
+ * 0 + n = n\r
+ * 0 + 0 = 0\r
+ * 0 + N = N\r
+ * 0 + I = I\r
+ * N + n = N\r
+ * N + 0 = N\r
+ * N + N = N\r
+ * N + I = N\r
+ * I + n = I\r
+ * I + 0 = I\r
+ * I + N = N\r
+ * I + I = I\r
+ *\r
+ * Return a new Decimal whose value is the value of this Decimal plus `y`, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ */\r
+ P.plus = P.add = function (y) {\r
+ var carry, d, e, i, k, len, pr, rm, xd, yd,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ y = new Ctor(y);\r
+\r
+ // If either is not finite...\r
+ if (!x.d || !y.d) {\r
+\r
+ // Return NaN if either is NaN.\r
+ if (!x.s || !y.s) y = new Ctor(NaN);\r
+\r
+ // Return x if y is finite and x is ±Infinity.\r
+ // Return x if both are ±Infinity with the same sign.\r
+ // Return NaN if both are ±Infinity with different signs.\r
+ // Return y if x is finite and y is ±Infinity.\r
+ else if (!x.d) y = new Ctor(y.d || x.s === y.s ? x : NaN);\r
+\r
+ return y;\r
+ }\r
+\r
+ // If signs differ...\r
+ if (x.s != y.s) {\r
+ y.s = -y.s;\r
+ return x.minus(y);\r
+ }\r
+\r
+ xd = x.d;\r
+ yd = y.d;\r
+ pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+\r
+ // If either is zero...\r
+ if (!xd[0] || !yd[0]) {\r
+\r
+ // Return x if y is zero.\r
+ // Return y if y is non-zero.\r
+ if (!yd[0]) y = new Ctor(x);\r
+\r
+ return external ? finalise(y, pr, rm) : y;\r
+ }\r
+\r
+ // x and y are finite, non-zero numbers with the same sign.\r
+\r
+ // Calculate base 1e7 exponents.\r
+ k = mathfloor(x.e / LOG_BASE);\r
+ e = mathfloor(y.e / LOG_BASE);\r
+\r
+ xd = xd.slice();\r
+ i = k - e;\r
+\r
+ // If base 1e7 exponents differ...\r
+ if (i) {\r
+\r
+ if (i < 0) {\r
+ d = xd;\r
+ i = -i;\r
+ len = yd.length;\r
+ } else {\r
+ d = yd;\r
+ e = k;\r
+ len = xd.length;\r
+ }\r
+\r
+ // Limit number of zeros prepended to max(ceil(pr / LOG_BASE), len) + 1.\r
+ k = Math.ceil(pr / LOG_BASE);\r
+ len = k > len ? k + 1 : len + 1;\r
+\r
+ if (i > len) {\r
+ i = len;\r
+ d.length = 1;\r
+ }\r
+\r
+ // Prepend zeros to equalise exponents. Note: Faster to use reverse then do unshifts.\r
+ d.reverse();\r
+ for (; i--;) d.push(0);\r
+ d.reverse();\r
+ }\r
+\r
+ len = xd.length;\r
+ i = yd.length;\r
+\r
+ // If yd is longer than xd, swap xd and yd so xd points to the longer array.\r
+ if (len - i < 0) {\r
+ i = len;\r
+ d = yd;\r
+ yd = xd;\r
+ xd = d;\r
+ }\r
+\r
+ // Only start adding at yd.length - 1 as the further digits of xd can be left as they are.\r
+ for (carry = 0; i;) {\r
+ carry = (xd[--i] = xd[i] + yd[i] + carry) / BASE | 0;\r
+ xd[i] %= BASE;\r
+ }\r
+\r
+ if (carry) {\r
+ xd.unshift(carry);\r
+ ++e;\r
+ }\r
+\r
+ // Remove trailing zeros.\r
+ // No need to check for zero, as +x + +y != 0 && -x + -y != 0\r
+ for (len = xd.length; xd[--len] == 0;) xd.pop();\r
+\r
+ y.d = xd;\r
+ y.e = getBase10Exponent(xd, e);\r
+\r
+ return external ? finalise(y, pr, rm) : y;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return the number of significant digits of the value of this Decimal.\r
+ *\r
+ * [z] {boolean|number} Whether to count integer-part trailing zeros: true, false, 1 or 0.\r
+ *\r
+ */\r
+ P.precision = P.sd = function (z) {\r
+ var k,\r
+ x = this;\r
+\r
+ if (z !== void 0 && z !== !!z && z !== 1 && z !== 0) throw Error(invalidArgument + z);\r
+\r
+ if (x.d) {\r
+ k = getPrecision(x.d);\r
+ if (z && x.e + 1 > k) k = x.e + 1;\r
+ } else {\r
+ k = NaN;\r
+ }\r
+\r
+ return k;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the value of this Decimal rounded to a whole number using\r
+ * rounding mode `rounding`.\r
+ *\r
+ */\r
+ P.round = function () {\r
+ var x = this,\r
+ Ctor = x.constructor;\r
+\r
+ return finalise(new Ctor(x), x.e + 1, Ctor.rounding);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the sine of the value in radians of this Decimal.\r
+ *\r
+ * Domain: [-Infinity, Infinity]\r
+ * Range: [-1, 1]\r
+ *\r
+ * sin(x) = x - x^3/3! + x^5/5! - ...\r
+ *\r
+ * sin(0) = 0\r
+ * sin(-0) = -0\r
+ * sin(Infinity) = NaN\r
+ * sin(-Infinity) = NaN\r
+ * sin(NaN) = NaN\r
+ *\r
+ */\r
+ P.sine = P.sin = function () {\r
+ var pr, rm,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (!x.isFinite()) return new Ctor(NaN);\r
+ if (x.isZero()) return new Ctor(x);\r
+\r
+ pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+ Ctor.precision = pr + Math.max(x.e, x.sd()) + LOG_BASE;\r
+ Ctor.rounding = 1;\r
+\r
+ x = sine(Ctor, toLessThanHalfPi(Ctor, x));\r
+\r
+ Ctor.precision = pr;\r
+ Ctor.rounding = rm;\r
+\r
+ return finalise(quadrant > 2 ? x.neg() : x, pr, rm, true);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the square root of this Decimal, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ * sqrt(-n) = N\r
+ * sqrt(N) = N\r
+ * sqrt(-I) = N\r
+ * sqrt(I) = I\r
+ * sqrt(0) = 0\r
+ * sqrt(-0) = -0\r
+ *\r
+ */\r
+ P.squareRoot = P.sqrt = function () {\r
+ var m, n, sd, r, rep, t,\r
+ x = this,\r
+ d = x.d,\r
+ e = x.e,\r
+ s = x.s,\r
+ Ctor = x.constructor;\r
+\r
+ // Negative/NaN/Infinity/zero?\r
+ if (s !== 1 || !d || !d[0]) {\r
+ return new Ctor(!s || s < 0 && (!d || d[0]) ? NaN : d ? x : 1 / 0);\r
+ }\r
+\r
+ external = false;\r
+\r
+ // Initial estimate.\r
+ s = Math.sqrt(+x);\r
+\r
+ // Math.sqrt underflow/overflow?\r
+ // Pass x to Math.sqrt as integer, then adjust the exponent of the result.\r
+ if (s == 0 || s == 1 / 0) {\r
+ n = digitsToString(d);\r
+\r
+ if ((n.length + e) % 2 == 0) n += '0';\r
+ s = Math.sqrt(n);\r
+ e = mathfloor((e + 1) / 2) - (e < 0 || e % 2);\r
+\r
+ if (s == 1 / 0) {\r
+ n = '5e' + e;\r
+ } else {\r
+ n = s.toExponential();\r
+ n = n.slice(0, n.indexOf('e') + 1) + e;\r
+ }\r
+\r
+ r = new Ctor(n);\r
+ } else {\r
+ r = new Ctor(s.toString());\r
+ }\r
+\r
+ sd = (e = Ctor.precision) + 3;\r
+\r
+ // Newton-Raphson iteration.\r
+ for (;;) {\r
+ t = r;\r
+ r = t.plus(divide(x, t, sd + 2, 1)).times(0.5);\r
+\r
+ // TODO? Replace with for-loop and checkRoundingDigits.\r
+ if (digitsToString(t.d).slice(0, sd) === (n = digitsToString(r.d)).slice(0, sd)) {\r
+ n = n.slice(sd - 3, sd + 1);\r
+\r
+ // The 4th rounding digit may be in error by -1 so if the 4 rounding digits are 9999 or\r
+ // 4999, i.e. approaching a rounding boundary, continue the iteration.\r
+ if (n == '9999' || !rep && n == '4999') {\r
+\r
+ // On the first iteration only, check to see if rounding up gives the exact result as the\r
+ // nines may infinitely repeat.\r
+ if (!rep) {\r
+ finalise(t, e + 1, 0);\r
+\r
+ if (t.times(t).eq(x)) {\r
+ r = t;\r
+ break;\r
+ }\r
+ }\r
+\r
+ sd += 4;\r
+ rep = 1;\r
+ } else {\r
+\r
+ // If the rounding digits are null, 0{0,4} or 50{0,3}, check for an exact result.\r
+ // If not, then there are further digits and m will be truthy.\r
+ if (!+n || !+n.slice(1) && n.charAt(0) == '5') {\r
+\r
+ // Truncate to the first rounding digit.\r
+ finalise(r, e + 1, 1);\r
+ m = !r.times(r).eq(x);\r
+ }\r
+\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ external = true;\r
+\r
+ return finalise(r, e, Ctor.rounding, m);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the tangent of the value in radians of this Decimal.\r
+ *\r
+ * Domain: [-Infinity, Infinity]\r
+ * Range: [-Infinity, Infinity]\r
+ *\r
+ * tan(0) = 0\r
+ * tan(-0) = -0\r
+ * tan(Infinity) = NaN\r
+ * tan(-Infinity) = NaN\r
+ * tan(NaN) = NaN\r
+ *\r
+ */\r
+ P.tangent = P.tan = function () {\r
+ var pr, rm,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (!x.isFinite()) return new Ctor(NaN);\r
+ if (x.isZero()) return new Ctor(x);\r
+\r
+ pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+ Ctor.precision = pr + 10;\r
+ Ctor.rounding = 1;\r
+\r
+ x = x.sin();\r
+ x.s = 1;\r
+ x = divide(x, new Ctor(1).minus(x.times(x)).sqrt(), pr + 10, 0);\r
+\r
+ Ctor.precision = pr;\r
+ Ctor.rounding = rm;\r
+\r
+ return finalise(quadrant == 2 || quadrant == 4 ? x.neg() : x, pr, rm, true);\r
+ };\r
+\r
+\r
+ /*\r
+ * n * 0 = 0\r
+ * n * N = N\r
+ * n * I = I\r
+ * 0 * n = 0\r
+ * 0 * 0 = 0\r
+ * 0 * N = N\r
+ * 0 * I = N\r
+ * N * n = N\r
+ * N * 0 = N\r
+ * N * N = N\r
+ * N * I = N\r
+ * I * n = I\r
+ * I * 0 = N\r
+ * I * N = N\r
+ * I * I = I\r
+ *\r
+ * Return a new Decimal whose value is this Decimal times `y`, rounded to `precision` significant\r
+ * digits using rounding mode `rounding`.\r
+ *\r
+ */\r
+ P.times = P.mul = function (y) {\r
+ var carry, e, i, k, r, rL, t, xdL, ydL,\r
+ x = this,\r
+ Ctor = x.constructor,\r
+ xd = x.d,\r
+ yd = (y = new Ctor(y)).d;\r
+\r
+ y.s *= x.s;\r
+\r
+ // If either is NaN, ±Infinity or ±0...\r
+ if (!xd || !xd[0] || !yd || !yd[0]) {\r
+\r
+ return new Ctor(!y.s || xd && !xd[0] && !yd || yd && !yd[0] && !xd\r
+\r
+ // Return NaN if either is NaN.\r
+ // Return NaN if x is ±0 and y is ±Infinity, or y is ±0 and x is ±Infinity.\r
+ ? NaN\r
+\r
+ // Return ±Infinity if either is ±Infinity.\r
+ // Return ±0 if either is ±0.\r
+ : !xd || !yd ? y.s / 0 : y.s * 0);\r
+ }\r
+\r
+ e = mathfloor(x.e / LOG_BASE) + mathfloor(y.e / LOG_BASE);\r
+ xdL = xd.length;\r
+ ydL = yd.length;\r
+\r
+ // Ensure xd points to the longer array.\r
+ if (xdL < ydL) {\r
+ r = xd;\r
+ xd = yd;\r
+ yd = r;\r
+ rL = xdL;\r
+ xdL = ydL;\r
+ ydL = rL;\r
+ }\r
+\r
+ // Initialise the result array with zeros.\r
+ r = [];\r
+ rL = xdL + ydL;\r
+ for (i = rL; i--;) r.push(0);\r
+\r
+ // Multiply!\r
+ for (i = ydL; --i >= 0;) {\r
+ carry = 0;\r
+ for (k = xdL + i; k > i;) {\r
+ t = r[k] + yd[i] * xd[k - i - 1] + carry;\r
+ r[k--] = t % BASE | 0;\r
+ carry = t / BASE | 0;\r
+ }\r
+\r
+ r[k] = (r[k] + carry) % BASE | 0;\r
+ }\r
+\r
+ // Remove trailing zeros.\r
+ for (; !r[--rL];) r.pop();\r
+\r
+ if (carry) ++e;\r
+ else r.shift();\r
+\r
+ y.d = r;\r
+ y.e = getBase10Exponent(r, e);\r
+\r
+ return external ? finalise(y, Ctor.precision, Ctor.rounding) : y;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a string representing the value of this Decimal in base 2, round to `sd` significant\r
+ * digits using rounding mode `rm`.\r
+ *\r
+ * If the optional `sd` argument is present then return binary exponential notation.\r
+ *\r
+ * [sd] {number} Significant digits. Integer, 1 to MAX_DIGITS inclusive.\r
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.\r
+ *\r
+ */\r
+ P.toBinary = function (sd, rm) {\r
+ return toStringBinary(this, 2, sd, rm);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the value of this Decimal rounded to a maximum of `dp`\r
+ * decimal places using rounding mode `rm` or `rounding` if `rm` is omitted.\r
+ *\r
+ * If `dp` is omitted, return a new Decimal whose value is the value of this Decimal.\r
+ *\r
+ * [dp] {number} Decimal places. Integer, 0 to MAX_DIGITS inclusive.\r
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.\r
+ *\r
+ */\r
+ P.toDecimalPlaces = P.toDP = function (dp, rm) {\r
+ var x = this,\r
+ Ctor = x.constructor;\r
+\r
+ x = new Ctor(x);\r
+ if (dp === void 0) return x;\r
+\r
+ checkInt32(dp, 0, MAX_DIGITS);\r
+\r
+ if (rm === void 0) rm = Ctor.rounding;\r
+ else checkInt32(rm, 0, 8);\r
+\r
+ return finalise(x, dp + x.e + 1, rm);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a string representing the value of this Decimal in exponential notation rounded to\r
+ * `dp` fixed decimal places using rounding mode `rounding`.\r
+ *\r
+ * [dp] {number} Decimal places. Integer, 0 to MAX_DIGITS inclusive.\r
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.\r
+ *\r
+ */\r
+ P.toExponential = function (dp, rm) {\r
+ var str,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (dp === void 0) {\r
+ str = finiteToString(x, true);\r
+ } else {\r
+ checkInt32(dp, 0, MAX_DIGITS);\r
+\r
+ if (rm === void 0) rm = Ctor.rounding;\r
+ else checkInt32(rm, 0, 8);\r
+\r
+ x = finalise(new Ctor(x), dp + 1, rm);\r
+ str = finiteToString(x, true, dp + 1);\r
+ }\r
+\r
+ return x.isNeg() && !x.isZero() ? '-' + str : str;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a string representing the value of this Decimal in normal (fixed-point) notation to\r
+ * `dp` fixed decimal places and rounded using rounding mode `rm` or `rounding` if `rm` is\r
+ * omitted.\r
+ *\r
+ * As with JavaScript numbers, (-0).toFixed(0) is '0', but e.g. (-0.00001).toFixed(0) is '-0'.\r
+ *\r
+ * [dp] {number} Decimal places. Integer, 0 to MAX_DIGITS inclusive.\r
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.\r
+ *\r
+ * (-0).toFixed(0) is '0', but (-0.1).toFixed(0) is '-0'.\r
+ * (-0).toFixed(1) is '0.0', but (-0.01).toFixed(1) is '-0.0'.\r
+ * (-0).toFixed(3) is '0.000'.\r
+ * (-0.5).toFixed(0) is '-0'.\r
+ *\r
+ */\r
+ P.toFixed = function (dp, rm) {\r
+ var str, y,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (dp === void 0) {\r
+ str = finiteToString(x);\r
+ } else {\r
+ checkInt32(dp, 0, MAX_DIGITS);\r
+\r
+ if (rm === void 0) rm = Ctor.rounding;\r
+ else checkInt32(rm, 0, 8);\r
+\r
+ y = finalise(new Ctor(x), dp + x.e + 1, rm);\r
+ str = finiteToString(y, false, dp + y.e + 1);\r
+ }\r
+\r
+ // To determine whether to add the minus sign look at the value before it was rounded,\r
+ // i.e. look at `x` rather than `y`.\r
+ return x.isNeg() && !x.isZero() ? '-' + str : str;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return an array representing the value of this Decimal as a simple fraction with an integer\r
+ * numerator and an integer denominator.\r
+ *\r
+ * The denominator will be a positive non-zero value less than or equal to the specified maximum\r
+ * denominator. If a maximum denominator is not specified, the denominator will be the lowest\r
+ * value necessary to represent the number exactly.\r
+ *\r
+ * [maxD] {number|string|bigint|Decimal} Maximum denominator. Integer >= 1 and < Infinity.\r
+ *\r
+ */\r
+ P.toFraction = function (maxD) {\r
+ var d, d0, d1, d2, e, k, n, n0, n1, pr, q, r,\r
+ x = this,\r
+ xd = x.d,\r
+ Ctor = x.constructor;\r
+\r
+ if (!xd) return new Ctor(x);\r
+\r
+ n1 = d0 = new Ctor(1);\r
+ d1 = n0 = new Ctor(0);\r
+\r
+ d = new Ctor(d1);\r
+ e = d.e = getPrecision(xd) - x.e - 1;\r
+ k = e % LOG_BASE;\r
+ d.d[0] = mathpow(10, k < 0 ? LOG_BASE + k : k);\r
+\r
+ if (maxD == null) {\r
+\r
+ // d is 10**e, the minimum max-denominator needed.\r
+ maxD = e > 0 ? d : n1;\r
+ } else {\r
+ n = new Ctor(maxD);\r
+ if (!n.isInt() || n.lt(n1)) throw Error(invalidArgument + n);\r
+ maxD = n.gt(d) ? (e > 0 ? d : n1) : n;\r
+ }\r
+\r
+ external = false;\r
+ n = new Ctor(digitsToString(xd));\r
+ pr = Ctor.precision;\r
+ Ctor.precision = e = xd.length * LOG_BASE * 2;\r
+\r
+ for (;;) {\r
+ q = divide(n, d, 0, 1, 1);\r
+ d2 = d0.plus(q.times(d1));\r
+ if (d2.cmp(maxD) == 1) break;\r
+ d0 = d1;\r
+ d1 = d2;\r
+ d2 = n1;\r
+ n1 = n0.plus(q.times(d2));\r
+ n0 = d2;\r
+ d2 = d;\r
+ d = n.minus(q.times(d2));\r
+ n = d2;\r
+ }\r
+\r
+ d2 = divide(maxD.minus(d0), d1, 0, 1, 1);\r
+ n0 = n0.plus(d2.times(n1));\r
+ d0 = d0.plus(d2.times(d1));\r
+ n0.s = n1.s = x.s;\r
+\r
+ // Determine which fraction is closer to x, n0/d0 or n1/d1?\r
+ r = divide(n1, d1, e, 1).minus(x).abs().cmp(divide(n0, d0, e, 1).minus(x).abs()) < 1\r
+ ? [n1, d1] : [n0, d0];\r
+\r
+ Ctor.precision = pr;\r
+ external = true;\r
+\r
+ return r;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a string representing the value of this Decimal in base 16, round to `sd` significant\r
+ * digits using rounding mode `rm`.\r
+ *\r
+ * If the optional `sd` argument is present then return binary exponential notation.\r
+ *\r
+ * [sd] {number} Significant digits. Integer, 1 to MAX_DIGITS inclusive.\r
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.\r
+ *\r
+ */\r
+ P.toHexadecimal = P.toHex = function (sd, rm) {\r
+ return toStringBinary(this, 16, sd, rm);\r
+ };\r
+\r
+\r
+ /*\r
+ * Returns a new Decimal whose value is the nearest multiple of `y` in the direction of rounding\r
+ * mode `rm`, or `Decimal.rounding` if `rm` is omitted, to the value of this Decimal.\r
+ *\r
+ * The return value will always have the same sign as this Decimal, unless either this Decimal\r
+ * or `y` is NaN, in which case the return value will be also be NaN.\r
+ *\r
+ * The return value is not affected by the value of `precision`.\r
+ *\r
+ * y {number|string|bigint|Decimal} The magnitude to round to a multiple of.\r
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.\r
+ *\r
+ * 'toNearest() rounding mode not an integer: {rm}'\r
+ * 'toNearest() rounding mode out of range: {rm}'\r
+ *\r
+ */\r
+ P.toNearest = function (y, rm) {\r
+ var x = this,\r
+ Ctor = x.constructor;\r
+\r
+ x = new Ctor(x);\r
+\r
+ if (y == null) {\r
+\r
+ // If x is not finite, return x.\r
+ if (!x.d) return x;\r
+\r
+ y = new Ctor(1);\r
+ rm = Ctor.rounding;\r
+ } else {\r
+ y = new Ctor(y);\r
+ if (rm === void 0) {\r
+ rm = Ctor.rounding;\r
+ } else {\r
+ checkInt32(rm, 0, 8);\r
+ }\r
+\r
+ // If x is not finite, return x if y is not NaN, else NaN.\r
+ if (!x.d) return y.s ? x : y;\r
+\r
+ // If y is not finite, return Infinity with the sign of x if y is Infinity, else NaN.\r
+ if (!y.d) {\r
+ if (y.s) y.s = x.s;\r
+ return y;\r
+ }\r
+ }\r
+\r
+ // If y is not zero, calculate the nearest multiple of y to x.\r
+ if (y.d[0]) {\r
+ external = false;\r
+ x = divide(x, y, 0, rm, 1).times(y);\r
+ external = true;\r
+ finalise(x);\r
+\r
+ // If y is zero, return zero with the sign of x.\r
+ } else {\r
+ y.s = x.s;\r
+ x = y;\r
+ }\r
+\r
+ return x;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return the value of this Decimal converted to a number primitive.\r
+ * Zero keeps its sign.\r
+ *\r
+ */\r
+ P.toNumber = function () {\r
+ return +this;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a string representing the value of this Decimal in base 8, round to `sd` significant\r
+ * digits using rounding mode `rm`.\r
+ *\r
+ * If the optional `sd` argument is present then return binary exponential notation.\r
+ *\r
+ * [sd] {number} Significant digits. Integer, 1 to MAX_DIGITS inclusive.\r
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.\r
+ *\r
+ */\r
+ P.toOctal = function (sd, rm) {\r
+ return toStringBinary(this, 8, sd, rm);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the value of this Decimal raised to the power `y`, rounded\r
+ * to `precision` significant digits using rounding mode `rounding`.\r
+ *\r
+ * ECMAScript compliant.\r
+ *\r
+ * pow(x, NaN) = NaN\r
+ * pow(x, ±0) = 1\r
+\r
+ * pow(NaN, non-zero) = NaN\r
+ * pow(abs(x) > 1, +Infinity) = +Infinity\r
+ * pow(abs(x) > 1, -Infinity) = +0\r
+ * pow(abs(x) == 1, ±Infinity) = NaN\r
+ * pow(abs(x) < 1, +Infinity) = +0\r
+ * pow(abs(x) < 1, -Infinity) = +Infinity\r
+ * pow(+Infinity, y > 0) = +Infinity\r
+ * pow(+Infinity, y < 0) = +0\r
+ * pow(-Infinity, odd integer > 0) = -Infinity\r
+ * pow(-Infinity, even integer > 0) = +Infinity\r
+ * pow(-Infinity, odd integer < 0) = -0\r
+ * pow(-Infinity, even integer < 0) = +0\r
+ * pow(+0, y > 0) = +0\r
+ * pow(+0, y < 0) = +Infinity\r
+ * pow(-0, odd integer > 0) = -0\r
+ * pow(-0, even integer > 0) = +0\r
+ * pow(-0, odd integer < 0) = -Infinity\r
+ * pow(-0, even integer < 0) = +Infinity\r
+ * pow(finite x < 0, finite non-integer) = NaN\r
+ *\r
+ * For non-integer or very large exponents pow(x, y) is calculated using\r
+ *\r
+ * x^y = exp(y*ln(x))\r
+ *\r
+ * Assuming the first 15 rounding digits are each equally likely to be any digit 0-9, the\r
+ * probability of an incorrectly rounded result\r
+ * P([49]9{14} | [50]0{14}) = 2 * 0.2 * 10^-14 = 4e-15 = 1/2.5e+14\r
+ * i.e. 1 in 250,000,000,000,000\r
+ *\r
+ * If a result is incorrectly rounded the maximum error will be 1 ulp (unit in last place).\r
+ *\r
+ * y {number|string|bigint|Decimal} The power to which to raise this Decimal.\r
+ *\r
+ */\r
+ P.toPower = P.pow = function (y) {\r
+ var e, k, pr, r, rm, s,\r
+ x = this,\r
+ Ctor = x.constructor,\r
+ yn = +(y = new Ctor(y));\r
+\r
+ // Either ±Infinity, NaN or ±0?\r
+ if (!x.d || !y.d || !x.d[0] || !y.d[0]) return new Ctor(mathpow(+x, yn));\r
+\r
+ x = new Ctor(x);\r
+\r
+ if (x.eq(1)) return x;\r
+\r
+ pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+\r
+ if (y.eq(1)) return finalise(x, pr, rm);\r
+\r
+ // y exponent\r
+ e = mathfloor(y.e / LOG_BASE);\r
+\r
+ // If y is a small integer use the 'exponentiation by squaring' algorithm.\r
+ if (e >= y.d.length - 1 && (k = yn < 0 ? -yn : yn) <= MAX_SAFE_INTEGER) {\r
+ r = intPow(Ctor, x, k, pr);\r
+ return y.s < 0 ? new Ctor(1).div(r) : finalise(r, pr, rm);\r
+ }\r
+\r
+ s = x.s;\r
+\r
+ // if x is negative\r
+ if (s < 0) {\r
+\r
+ // if y is not an integer\r
+ if (e < y.d.length - 1) return new Ctor(NaN);\r
+\r
+ // Result is positive if x is negative and the last digit of integer y is even.\r
+ if ((y.d[e] & 1) == 0) s = 1;\r
+\r
+ // if x.eq(-1)\r
+ if (x.e == 0 && x.d[0] == 1 && x.d.length == 1) {\r
+ x.s = s;\r
+ return x;\r
+ }\r
+ }\r
+\r
+ // Estimate result exponent.\r
+ // x^y = 10^e, where e = y * log10(x)\r
+ // log10(x) = log10(x_significand) + x_exponent\r
+ // log10(x_significand) = ln(x_significand) / ln(10)\r
+ k = mathpow(+x, yn);\r
+ e = k == 0 || !isFinite(k)\r
+ ? mathfloor(yn * (Math.log('0.' + digitsToString(x.d)) / Math.LN10 + x.e + 1))\r
+ : new Ctor(k + '').e;\r
+\r
+ // Exponent estimate may be incorrect e.g. x: 0.999999999999999999, y: 2.29, e: 0, r.e: -1.\r
+\r
+ // Overflow/underflow?\r
+ if (e > Ctor.maxE + 1 || e < Ctor.minE - 1) return new Ctor(e > 0 ? s / 0 : 0);\r
+\r
+ external = false;\r
+ Ctor.rounding = x.s = 1;\r
+\r
+ // Estimate the extra guard digits needed to ensure five correct rounding digits from\r
+ // naturalLogarithm(x). Example of failure without these extra digits (precision: 10):\r
+ // new Decimal(2.32456).pow('2087987436534566.46411')\r
+ // should be 1.162377823e+764914905173815, but is 1.162355823e+764914905173815\r
+ k = Math.min(12, (e + '').length);\r
+\r
+ // r = x^y = exp(y*ln(x))\r
+ r = naturalExponential(y.times(naturalLogarithm(x, pr + k)), pr);\r
+\r
+ // r may be Infinity, e.g. (0.9999999999999999).pow(-1e+40)\r
+ if (r.d) {\r
+\r
+ // Truncate to the required precision plus five rounding digits.\r
+ r = finalise(r, pr + 5, 1);\r
+\r
+ // If the rounding digits are [49]9999 or [50]0000 increase the precision by 10 and recalculate\r
+ // the result.\r
+ if (checkRoundingDigits(r.d, pr, rm)) {\r
+ e = pr + 10;\r
+\r
+ // Truncate to the increased precision plus five rounding digits.\r
+ r = finalise(naturalExponential(y.times(naturalLogarithm(x, e + k)), e), e + 5, 1);\r
+\r
+ // Check for 14 nines from the 2nd rounding digit (the first rounding digit may be 4 or 9).\r
+ if (+digitsToString(r.d).slice(pr + 1, pr + 15) + 1 == 1e14) {\r
+ r = finalise(r, pr + 1, 0);\r
+ }\r
+ }\r
+ }\r
+\r
+ r.s = s;\r
+ external = true;\r
+ Ctor.rounding = rm;\r
+\r
+ return finalise(r, pr, rm);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a string representing the value of this Decimal rounded to `sd` significant digits\r
+ * using rounding mode `rounding`.\r
+ *\r
+ * Return exponential notation if `sd` is less than the number of digits necessary to represent\r
+ * the integer part of the value in normal notation.\r
+ *\r
+ * [sd] {number} Significant digits. Integer, 1 to MAX_DIGITS inclusive.\r
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.\r
+ *\r
+ */\r
+ P.toPrecision = function (sd, rm) {\r
+ var str,\r
+ x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (sd === void 0) {\r
+ str = finiteToString(x, x.e <= Ctor.toExpNeg || x.e >= Ctor.toExpPos);\r
+ } else {\r
+ checkInt32(sd, 1, MAX_DIGITS);\r
+\r
+ if (rm === void 0) rm = Ctor.rounding;\r
+ else checkInt32(rm, 0, 8);\r
+\r
+ x = finalise(new Ctor(x), sd, rm);\r
+ str = finiteToString(x, sd <= x.e || x.e <= Ctor.toExpNeg, sd);\r
+ }\r
+\r
+ return x.isNeg() && !x.isZero() ? '-' + str : str;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the value of this Decimal rounded to a maximum of `sd`\r
+ * significant digits using rounding mode `rm`, or to `precision` and `rounding` respectively if\r
+ * omitted.\r
+ *\r
+ * [sd] {number} Significant digits. Integer, 1 to MAX_DIGITS inclusive.\r
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.\r
+ *\r
+ * 'toSD() digits out of range: {sd}'\r
+ * 'toSD() digits not an integer: {sd}'\r
+ * 'toSD() rounding mode not an integer: {rm}'\r
+ * 'toSD() rounding mode out of range: {rm}'\r
+ *\r
+ */\r
+ P.toSignificantDigits = P.toSD = function (sd, rm) {\r
+ var x = this,\r
+ Ctor = x.constructor;\r
+\r
+ if (sd === void 0) {\r
+ sd = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+ } else {\r
+ checkInt32(sd, 1, MAX_DIGITS);\r
+\r
+ if (rm === void 0) rm = Ctor.rounding;\r
+ else checkInt32(rm, 0, 8);\r
+ }\r
+\r
+ return finalise(new Ctor(x), sd, rm);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a string representing the value of this Decimal.\r
+ *\r
+ * Return exponential notation if this Decimal has a positive exponent equal to or greater than\r
+ * `toExpPos`, or a negative exponent equal to or less than `toExpNeg`.\r
+ *\r
+ */\r
+ P.toString = function () {\r
+ var x = this,\r
+ Ctor = x.constructor,\r
+ str = finiteToString(x, x.e <= Ctor.toExpNeg || x.e >= Ctor.toExpPos);\r
+\r
+ return x.isNeg() && !x.isZero() ? '-' + str : str;\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the value of this Decimal truncated to a whole number.\r
+ *\r
+ */\r
+ P.truncated = P.trunc = function () {\r
+ return finalise(new this.constructor(this), this.e + 1, 1);\r
+ };\r
+\r
+\r
+ /*\r
+ * Return a string representing the value of this Decimal.\r
+ * Unlike `toString`, negative zero will include the minus sign.\r
+ *\r
+ */\r
+ P.valueOf = P.toJSON = function () {\r
+ var x = this,\r
+ Ctor = x.constructor,\r
+ str = finiteToString(x, x.e <= Ctor.toExpNeg || x.e >= Ctor.toExpPos);\r
+\r
+ return x.isNeg() ? '-' + str : str;\r
+ };\r
+\r
+\r
+ // Helper functions for Decimal.prototype (P) and/or Decimal methods, and their callers.\r
+\r
+\r
+ /*\r
+ * digitsToString P.cubeRoot, P.logarithm, P.squareRoot, P.toFraction, P.toPower,\r
+ * finiteToString, naturalExponential, naturalLogarithm\r
+ * checkInt32 P.toDecimalPlaces, P.toExponential, P.toFixed, P.toNearest,\r
+ * P.toPrecision, P.toSignificantDigits, toStringBinary, random\r
+ * checkRoundingDigits P.logarithm, P.toPower, naturalExponential, naturalLogarithm\r
+ * convertBase toStringBinary, parseOther\r
+ * cos P.cos\r
+ * divide P.atanh, P.cubeRoot, P.dividedBy, P.dividedToIntegerBy,\r
+ * P.logarithm, P.modulo, P.squareRoot, P.tan, P.tanh, P.toFraction,\r
+ * P.toNearest, toStringBinary, naturalExponential, naturalLogarithm,\r
+ * taylorSeries, atan2, parseOther\r
+ * finalise P.absoluteValue, P.atan, P.atanh, P.ceil, P.cos, P.cosh,\r
+ * P.cubeRoot, P.dividedToIntegerBy, P.floor, P.logarithm, P.minus,\r
+ * P.modulo, P.negated, P.plus, P.round, P.sin, P.sinh, P.squareRoot,\r
+ * P.tan, P.times, P.toDecimalPlaces, P.toExponential, P.toFixed,\r
+ * P.toNearest, P.toPower, P.toPrecision, P.toSignificantDigits,\r
+ * P.truncated, divide, getLn10, getPi, naturalExponential,\r
+ * naturalLogarithm, ceil, floor, round, trunc\r
+ * finiteToString P.toExponential, P.toFixed, P.toPrecision, P.toString, P.valueOf,\r
+ * toStringBinary\r
+ * getBase10Exponent P.minus, P.plus, P.times, parseOther\r
+ * getLn10 P.logarithm, naturalLogarithm\r
+ * getPi P.acos, P.asin, P.atan, toLessThanHalfPi, atan2\r
+ * getPrecision P.precision, P.toFraction\r
+ * getZeroString digitsToString, finiteToString\r
+ * intPow P.toPower, parseOther\r
+ * isOdd toLessThanHalfPi\r
+ * maxOrMin max, min\r
+ * naturalExponential P.naturalExponential, P.toPower\r
+ * naturalLogarithm P.acosh, P.asinh, P.atanh, P.logarithm, P.naturalLogarithm,\r
+ * P.toPower, naturalExponential\r
+ * nonFiniteToString finiteToString, toStringBinary\r
+ * parseDecimal Decimal\r
+ * parseOther Decimal\r
+ * sin P.sin\r
+ * taylorSeries P.cosh, P.sinh, cos, sin\r
+ * toLessThanHalfPi P.cos, P.sin\r
+ * toStringBinary P.toBinary, P.toHexadecimal, P.toOctal\r
+ * truncate intPow\r
+ *\r
+ * Throws: P.logarithm, P.precision, P.toFraction, checkInt32, getLn10, getPi,\r
+ * naturalLogarithm, config, parseOther, random, Decimal\r
+ */\r
+\r
+\r
+ function digitsToString(d) {\r
+ var i, k, ws,\r
+ indexOfLastWord = d.length - 1,\r
+ str = '',\r
+ w = d[0];\r
+\r
+ if (indexOfLastWord > 0) {\r
+ str += w;\r
+ for (i = 1; i < indexOfLastWord; i++) {\r
+ ws = d[i] + '';\r
+ k = LOG_BASE - ws.length;\r
+ if (k) str += getZeroString(k);\r
+ str += ws;\r
+ }\r
+\r
+ w = d[i];\r
+ ws = w + '';\r
+ k = LOG_BASE - ws.length;\r
+ if (k) str += getZeroString(k);\r
+ } else if (w === 0) {\r
+ return '0';\r
+ }\r
+\r
+ // Remove trailing zeros of last w.\r
+ for (; w % 10 === 0;) w /= 10;\r
+\r
+ return str + w;\r
+ }\r
+\r
+\r
+ function checkInt32(i, min, max) {\r
+ if (i !== ~~i || i < min || i > max) {\r
+ throw Error(invalidArgument + i);\r
+ }\r
+ }\r
+\r
+\r
+ /*\r
+ * Check 5 rounding digits if `repeating` is null, 4 otherwise.\r
+ * `repeating == null` if caller is `log` or `pow`,\r
+ * `repeating != null` if caller is `naturalLogarithm` or `naturalExponential`.\r
+ */\r
+ function checkRoundingDigits(d, i, rm, repeating) {\r
+ var di, k, r, rd;\r
+\r
+ // Get the length of the first word of the array d.\r
+ for (k = d[0]; k >= 10; k /= 10) --i;\r
+\r
+ // Is the rounding digit in the first word of d?\r
+ if (--i < 0) {\r
+ i += LOG_BASE;\r
+ di = 0;\r
+ } else {\r
+ di = Math.ceil((i + 1) / LOG_BASE);\r
+ i %= LOG_BASE;\r
+ }\r
+\r
+ // i is the index (0 - 6) of the rounding digit.\r
+ // E.g. if within the word 3487563 the first rounding digit is 5,\r
+ // then i = 4, k = 1000, rd = 3487563 % 1000 = 563\r
+ k = mathpow(10, LOG_BASE - i);\r
+ rd = d[di] % k | 0;\r
+\r
+ if (repeating == null) {\r
+ if (i < 3) {\r
+ if (i == 0) rd = rd / 100 | 0;\r
+ else if (i == 1) rd = rd / 10 | 0;\r
+ r = rm < 4 && rd == 99999 || rm > 3 && rd == 49999 || rd == 50000 || rd == 0;\r
+ } else {\r
+ r = (rm < 4 && rd + 1 == k || rm > 3 && rd + 1 == k / 2) &&\r
+ (d[di + 1] / k / 100 | 0) == mathpow(10, i - 2) - 1 ||\r
+ (rd == k / 2 || rd == 0) && (d[di + 1] / k / 100 | 0) == 0;\r
+ }\r
+ } else {\r
+ if (i < 4) {\r
+ if (i == 0) rd = rd / 1000 | 0;\r
+ else if (i == 1) rd = rd / 100 | 0;\r
+ else if (i == 2) rd = rd / 10 | 0;\r
+ r = (repeating || rm < 4) && rd == 9999 || !repeating && rm > 3 && rd == 4999;\r
+ } else {\r
+ r = ((repeating || rm < 4) && rd + 1 == k ||\r
+ (!repeating && rm > 3) && rd + 1 == k / 2) &&\r
+ (d[di + 1] / k / 1000 | 0) == mathpow(10, i - 3) - 1;\r
+ }\r
+ }\r
+\r
+ return r;\r
+ }\r
+\r
+\r
+ // Convert string of `baseIn` to an array of numbers of `baseOut`.\r
+ // Eg. convertBase('255', 10, 16) returns [15, 15].\r
+ // Eg. convertBase('ff', 16, 10) returns [2, 5, 5].\r
+ function convertBase(str, baseIn, baseOut) {\r
+ var j,\r
+ arr = [0],\r
+ arrL,\r
+ i = 0,\r
+ strL = str.length;\r
+\r
+ for (; i < strL;) {\r
+ for (arrL = arr.length; arrL--;) arr[arrL] *= baseIn;\r
+ arr[0] += NUMERALS.indexOf(str.charAt(i++));\r
+ for (j = 0; j < arr.length; j++) {\r
+ if (arr[j] > baseOut - 1) {\r
+ if (arr[j + 1] === void 0) arr[j + 1] = 0;\r
+ arr[j + 1] += arr[j] / baseOut | 0;\r
+ arr[j] %= baseOut;\r
+ }\r
+ }\r
+ }\r
+\r
+ return arr.reverse();\r
+ }\r
+\r
+\r
+ /*\r
+ * cos(x) = 1 - x^2/2! + x^4/4! - ...\r
+ * |x| < pi/2\r
+ *\r
+ */\r
+ function cosine(Ctor, x) {\r
+ var k, len, y;\r
+\r
+ if (x.isZero()) return x;\r
+\r
+ // Argument reduction: cos(4x) = 8*(cos^4(x) - cos^2(x)) + 1\r
+ // i.e. cos(x) = 8*(cos^4(x/4) - cos^2(x/4)) + 1\r
+\r
+ // Estimate the optimum number of times to use the argument reduction.\r
+ len = x.d.length;\r
+ if (len < 32) {\r
+ k = Math.ceil(len / 3);\r
+ y = (1 / tinyPow(4, k)).toString();\r
+ } else {\r
+ k = 16;\r
+ y = '2.3283064365386962890625e-10';\r
+ }\r
+\r
+ Ctor.precision += k;\r
+\r
+ x = taylorSeries(Ctor, 1, x.times(y), new Ctor(1));\r
+\r
+ // Reverse argument reduction\r
+ for (var i = k; i--;) {\r
+ var cos2x = x.times(x);\r
+ x = cos2x.times(cos2x).minus(cos2x).times(8).plus(1);\r
+ }\r
+\r
+ Ctor.precision -= k;\r
+\r
+ return x;\r
+ }\r
+\r
+\r
+ /*\r
+ * Perform division in the specified base.\r
+ */\r
+ var divide = (function () {\r
+\r
+ // Assumes non-zero x and k, and hence non-zero result.\r
+ function multiplyInteger(x, k, base) {\r
+ var temp,\r
+ carry = 0,\r
+ i = x.length;\r
+\r
+ for (x = x.slice(); i--;) {\r
+ temp = x[i] * k + carry;\r
+ x[i] = temp % base | 0;\r
+ carry = temp / base | 0;\r
+ }\r
+\r
+ if (carry) x.unshift(carry);\r
+\r
+ return x;\r
+ }\r
+\r
+ function compare(a, b, aL, bL) {\r
+ var i, r;\r
+\r
+ if (aL != bL) {\r
+ r = aL > bL ? 1 : -1;\r
+ } else {\r
+ for (i = r = 0; i < aL; i++) {\r
+ if (a[i] != b[i]) {\r
+ r = a[i] > b[i] ? 1 : -1;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ return r;\r
+ }\r
+\r
+ function subtract(a, b, aL, base) {\r
+ var i = 0;\r
+\r
+ // Subtract b from a.\r
+ for (; aL--;) {\r
+ a[aL] -= i;\r
+ i = a[aL] < b[aL] ? 1 : 0;\r
+ a[aL] = i * base + a[aL] - b[aL];\r
+ }\r
+\r
+ // Remove leading zeros.\r
+ for (; !a[0] && a.length > 1;) a.shift();\r
+ }\r
+\r
+ return function (x, y, pr, rm, dp, base) {\r
+ var cmp, e, i, k, logBase, more, prod, prodL, q, qd, rem, remL, rem0, sd, t, xi, xL, yd0,\r
+ yL, yz,\r
+ Ctor = x.constructor,\r
+ sign = x.s == y.s ? 1 : -1,\r
+ xd = x.d,\r
+ yd = y.d;\r
+\r
+ // Either NaN, Infinity or 0?\r
+ if (!xd || !xd[0] || !yd || !yd[0]) {\r
+\r
+ return new Ctor(// Return NaN if either NaN, or both Infinity or 0.\r
+ !x.s || !y.s || (xd ? yd && xd[0] == yd[0] : !yd) ? NaN :\r
+\r
+ // Return ±0 if x is 0 or y is ±Infinity, or return ±Infinity as y is 0.\r
+ xd && xd[0] == 0 || !yd ? sign * 0 : sign / 0);\r
+ }\r
+\r
+ if (base) {\r
+ logBase = 1;\r
+ e = x.e - y.e;\r
+ } else {\r
+ base = BASE;\r
+ logBase = LOG_BASE;\r
+ e = mathfloor(x.e / logBase) - mathfloor(y.e / logBase);\r
+ }\r
+\r
+ yL = yd.length;\r
+ xL = xd.length;\r
+ q = new Ctor(sign);\r
+ qd = q.d = [];\r
+\r
+ // Result exponent may be one less than e.\r
+ // The digit array of a Decimal from toStringBinary may have trailing zeros.\r
+ for (i = 0; yd[i] == (xd[i] || 0); i++);\r
+\r
+ if (yd[i] > (xd[i] || 0)) e--;\r
+\r
+ if (pr == null) {\r
+ sd = pr = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+ } else if (dp) {\r
+ sd = pr + (x.e - y.e) + 1;\r
+ } else {\r
+ sd = pr;\r
+ }\r
+\r
+ if (sd < 0) {\r
+ qd.push(1);\r
+ more = true;\r
+ } else {\r
+\r
+ // Convert precision in number of base 10 digits to base 1e7 digits.\r
+ sd = sd / logBase + 2 | 0;\r
+ i = 0;\r
+\r
+ // divisor < 1e7\r
+ if (yL == 1) {\r
+ k = 0;\r
+ yd = yd[0];\r
+ sd++;\r
+\r
+ // k is the carry.\r
+ for (; (i < xL || k) && sd--; i++) {\r
+ t = k * base + (xd[i] || 0);\r
+ qd[i] = t / yd | 0;\r
+ k = t % yd | 0;\r
+ }\r
+\r
+ more = k || i < xL;\r
+\r
+ // divisor >= 1e7\r
+ } else {\r
+\r
+ // Normalise xd and yd so highest order digit of yd is >= base/2\r
+ k = base / (yd[0] + 1) | 0;\r
+\r
+ if (k > 1) {\r
+ yd = multiplyInteger(yd, k, base);\r
+ xd = multiplyInteger(xd, k, base);\r
+ yL = yd.length;\r
+ xL = xd.length;\r
+ }\r
+\r
+ xi = yL;\r
+ rem = xd.slice(0, yL);\r
+ remL = rem.length;\r
+\r
+ // Add zeros to make remainder as long as divisor.\r
+ for (; remL < yL;) rem[remL++] = 0;\r
+\r
+ yz = yd.slice();\r
+ yz.unshift(0);\r
+ yd0 = yd[0];\r
+\r
+ if (yd[1] >= base / 2) ++yd0;\r
+\r
+ do {\r
+ k = 0;\r
+\r
+ // Compare divisor and remainder.\r
+ cmp = compare(yd, rem, yL, remL);\r
+\r
+ // If divisor < remainder.\r
+ if (cmp < 0) {\r
+\r
+ // Calculate trial digit, k.\r
+ rem0 = rem[0];\r
+ if (yL != remL) rem0 = rem0 * base + (rem[1] || 0);\r
+\r
+ // k will be how many times the divisor goes into the current remainder.\r
+ k = rem0 / yd0 | 0;\r
+\r
+ // Algorithm:\r
+ // 1. product = divisor * trial digit (k)\r
+ // 2. if product > remainder: product -= divisor, k--\r
+ // 3. remainder -= product\r
+ // 4. if product was < remainder at 2:\r
+ // 5. compare new remainder and divisor\r
+ // 6. If remainder > divisor: remainder -= divisor, k++\r
+\r
+ if (k > 1) {\r
+ if (k >= base) k = base - 1;\r
+\r
+ // product = divisor * trial digit.\r
+ prod = multiplyInteger(yd, k, base);\r
+ prodL = prod.length;\r
+ remL = rem.length;\r
+\r
+ // Compare product and remainder.\r
+ cmp = compare(prod, rem, prodL, remL);\r
+\r
+ // product > remainder.\r
+ if (cmp == 1) {\r
+ k--;\r
+\r
+ // Subtract divisor from product.\r
+ subtract(prod, yL < prodL ? yz : yd, prodL, base);\r
+ }\r
+ } else {\r
+\r
+ // cmp is -1.\r
+ // If k is 0, there is no need to compare yd and rem again below, so change cmp to 1\r
+ // to avoid it. If k is 1 there is a need to compare yd and rem again below.\r
+ if (k == 0) cmp = k = 1;\r
+ prod = yd.slice();\r
+ }\r
+\r
+ prodL = prod.length;\r
+ if (prodL < remL) prod.unshift(0);\r
+\r
+ // Subtract product from remainder.\r
+ subtract(rem, prod, remL, base);\r
+\r
+ // If product was < previous remainder.\r
+ if (cmp == -1) {\r
+ remL = rem.length;\r
+\r
+ // Compare divisor and new remainder.\r
+ cmp = compare(yd, rem, yL, remL);\r
+\r
+ // If divisor < new remainder, subtract divisor from remainder.\r
+ if (cmp < 1) {\r
+ k++;\r
+\r
+ // Subtract divisor from remainder.\r
+ subtract(rem, yL < remL ? yz : yd, remL, base);\r
+ }\r
+ }\r
+\r
+ remL = rem.length;\r
+ } else if (cmp === 0) {\r
+ k++;\r
+ rem = [0];\r
+ } // if cmp === 1, k will be 0\r
+\r
+ // Add the next digit, k, to the result array.\r
+ qd[i++] = k;\r
+\r
+ // Update the remainder.\r
+ if (cmp && rem[0]) {\r
+ rem[remL++] = xd[xi] || 0;\r
+ } else {\r
+ rem = [xd[xi]];\r
+ remL = 1;\r
+ }\r
+\r
+ } while ((xi++ < xL || rem[0] !== void 0) && sd--);\r
+\r
+ more = rem[0] !== void 0;\r
+ }\r
+\r
+ // Leading zero?\r
+ if (!qd[0]) qd.shift();\r
+ }\r
+\r
+ // logBase is 1 when divide is being used for base conversion.\r
+ if (logBase == 1) {\r
+ q.e = e;\r
+ inexact = more;\r
+ } else {\r
+\r
+ // To calculate q.e, first get the number of digits of qd[0].\r
+ for (i = 1, k = qd[0]; k >= 10; k /= 10) i++;\r
+ q.e = i + e * logBase - 1;\r
+\r
+ finalise(q, dp ? pr + q.e + 1 : pr, rm, more);\r
+ }\r
+\r
+ return q;\r
+ };\r
+ })();\r
+\r
+\r
+ /*\r
+ * Round `x` to `sd` significant digits using rounding mode `rm`.\r
+ * Check for over/under-flow.\r
+ */\r
+ function finalise(x, sd, rm, isTruncated) {\r
+ var digits, i, j, k, rd, roundUp, w, xd, xdi,\r
+ Ctor = x.constructor;\r
+\r
+ // Don't round if sd is null or undefined.\r
+ out: if (sd != null) {\r
+ xd = x.d;\r
+\r
+ // Infinity/NaN.\r
+ if (!xd) return x;\r
+\r
+ // rd: the rounding digit, i.e. the digit after the digit that may be rounded up.\r
+ // w: the word of xd containing rd, a base 1e7 number.\r
+ // xdi: the index of w within xd.\r
+ // digits: the number of digits of w.\r
+ // i: what would be the index of rd within w if all the numbers were 7 digits long (i.e. if\r
+ // they had leading zeros)\r
+ // j: if > 0, the actual index of rd within w (if < 0, rd is a leading zero).\r
+\r
+ // Get the length of the first word of the digits array xd.\r
+ for (digits = 1, k = xd[0]; k >= 10; k /= 10) digits++;\r
+ i = sd - digits;\r
+\r
+ // Is the rounding digit in the first word of xd?\r
+ if (i < 0) {\r
+ i += LOG_BASE;\r
+ j = sd;\r
+ w = xd[xdi = 0];\r
+\r
+ // Get the rounding digit at index j of w.\r
+ rd = w / mathpow(10, digits - j - 1) % 10 | 0;\r
+ } else {\r
+ xdi = Math.ceil((i + 1) / LOG_BASE);\r
+ k = xd.length;\r
+ if (xdi >= k) {\r
+ if (isTruncated) {\r
+\r
+ // Needed by `naturalExponential`, `naturalLogarithm` and `squareRoot`.\r
+ for (; k++ <= xdi;) xd.push(0);\r
+ w = rd = 0;\r
+ digits = 1;\r
+ i %= LOG_BASE;\r
+ j = i - LOG_BASE + 1;\r
+ } else {\r
+ break out;\r
+ }\r
+ } else {\r
+ w = k = xd[xdi];\r
+\r
+ // Get the number of digits of w.\r
+ for (digits = 1; k >= 10; k /= 10) digits++;\r
+\r
+ // Get the index of rd within w.\r
+ i %= LOG_BASE;\r
+\r
+ // Get the index of rd within w, adjusted for leading zeros.\r
+ // The number of leading zeros of w is given by LOG_BASE - digits.\r
+ j = i - LOG_BASE + digits;\r
+\r
+ // Get the rounding digit at index j of w.\r
+ rd = j < 0 ? 0 : w / mathpow(10, digits - j - 1) % 10 | 0;\r
+ }\r
+ }\r
+\r
+ // Are there any non-zero digits after the rounding digit?\r
+ isTruncated = isTruncated || sd < 0 ||\r
+ xd[xdi + 1] !== void 0 || (j < 0 ? w : w % mathpow(10, digits - j - 1));\r
+\r
+ // The expression `w % mathpow(10, digits - j - 1)` returns all the digits of w to the right\r
+ // of the digit at (left-to-right) index j, e.g. if w is 908714 and j is 2, the expression\r
+ // will give 714.\r
+\r
+ roundUp = rm < 4\r
+ ? (rd || isTruncated) && (rm == 0 || rm == (x.s < 0 ? 3 : 2))\r
+ : rd > 5 || rd == 5 && (rm == 4 || isTruncated || rm == 6 &&\r
+\r
+ // Check whether the digit to the left of the rounding digit is odd.\r
+ ((i > 0 ? j > 0 ? w / mathpow(10, digits - j) : 0 : xd[xdi - 1]) % 10) & 1 ||\r
+ rm == (x.s < 0 ? 8 : 7));\r
+\r
+ if (sd < 1 || !xd[0]) {\r
+ xd.length = 0;\r
+ if (roundUp) {\r
+\r
+ // Convert sd to decimal places.\r
+ sd -= x.e + 1;\r
+\r
+ // 1, 0.1, 0.01, 0.001, 0.0001 etc.\r
+ xd[0] = mathpow(10, (LOG_BASE - sd % LOG_BASE) % LOG_BASE);\r
+ x.e = -sd || 0;\r
+ } else {\r
+\r
+ // Zero.\r
+ xd[0] = x.e = 0;\r
+ }\r
+\r
+ return x;\r
+ }\r
+\r
+ // Remove excess digits.\r
+ if (i == 0) {\r
+ xd.length = xdi;\r
+ k = 1;\r
+ xdi--;\r
+ } else {\r
+ xd.length = xdi + 1;\r
+ k = mathpow(10, LOG_BASE - i);\r
+\r
+ // E.g. 56700 becomes 56000 if 7 is the rounding digit.\r
+ // j > 0 means i > number of leading zeros of w.\r
+ xd[xdi] = j > 0 ? (w / mathpow(10, digits - j) % mathpow(10, j) | 0) * k : 0;\r
+ }\r
+\r
+ if (roundUp) {\r
+ for (;;) {\r
+\r
+ // Is the digit to be rounded up in the first word of xd?\r
+ if (xdi == 0) {\r
+\r
+ // i will be the length of xd[0] before k is added.\r
+ for (i = 1, j = xd[0]; j >= 10; j /= 10) i++;\r
+ j = xd[0] += k;\r
+ for (k = 1; j >= 10; j /= 10) k++;\r
+\r
+ // if i != k the length has increased.\r
+ if (i != k) {\r
+ x.e++;\r
+ if (xd[0] == BASE) xd[0] = 1;\r
+ }\r
+\r
+ break;\r
+ } else {\r
+ xd[xdi] += k;\r
+ if (xd[xdi] != BASE) break;\r
+ xd[xdi--] = 0;\r
+ k = 1;\r
+ }\r
+ }\r
+ }\r
+\r
+ // Remove trailing zeros.\r
+ for (i = xd.length; xd[--i] === 0;) xd.pop();\r
+ }\r
+\r
+ if (external) {\r
+\r
+ // Overflow?\r
+ if (x.e > Ctor.maxE) {\r
+\r
+ // Infinity.\r
+ x.d = null;\r
+ x.e = NaN;\r
+\r
+ // Underflow?\r
+ } else if (x.e < Ctor.minE) {\r
+\r
+ // Zero.\r
+ x.e = 0;\r
+ x.d = [0];\r
+ // Ctor.underflow = true;\r
+ } // else Ctor.underflow = false;\r
+ }\r
+\r
+ return x;\r
+ }\r
+\r
+\r
+ function finiteToString(x, isExp, sd) {\r
+ if (!x.isFinite()) return nonFiniteToString(x);\r
+ var k,\r
+ e = x.e,\r
+ str = digitsToString(x.d),\r
+ len = str.length;\r
+\r
+ if (isExp) {\r
+ if (sd && (k = sd - len) > 0) {\r
+ str = str.charAt(0) + '.' + str.slice(1) + getZeroString(k);\r
+ } else if (len > 1) {\r
+ str = str.charAt(0) + '.' + str.slice(1);\r
+ }\r
+\r
+ str = str + (x.e < 0 ? 'e' : 'e+') + x.e;\r
+ } else if (e < 0) {\r
+ str = '0.' + getZeroString(-e - 1) + str;\r
+ if (sd && (k = sd - len) > 0) str += getZeroString(k);\r
+ } else if (e >= len) {\r
+ str += getZeroString(e + 1 - len);\r
+ if (sd && (k = sd - e - 1) > 0) str = str + '.' + getZeroString(k);\r
+ } else {\r
+ if ((k = e + 1) < len) str = str.slice(0, k) + '.' + str.slice(k);\r
+ if (sd && (k = sd - len) > 0) {\r
+ if (e + 1 === len) str += '.';\r
+ str += getZeroString(k);\r
+ }\r
+ }\r
+\r
+ return str;\r
+ }\r
+\r
+\r
+ // Calculate the base 10 exponent from the base 1e7 exponent.\r
+ function getBase10Exponent(digits, e) {\r
+ var w = digits[0];\r
+\r
+ // Add the number of digits of the first word of the digits array.\r
+ for ( e *= LOG_BASE; w >= 10; w /= 10) e++;\r
+ return e;\r
+ }\r
+\r
+\r
+ function getLn10(Ctor, sd, pr) {\r
+ if (sd > LN10_PRECISION) {\r
+\r
+ // Reset global state in case the exception is caught.\r
+ external = true;\r
+ if (pr) Ctor.precision = pr;\r
+ throw Error(precisionLimitExceeded);\r
+ }\r
+ return finalise(new Ctor(LN10), sd, 1, true);\r
+ }\r
+\r
+\r
+ function getPi(Ctor, sd, rm) {\r
+ if (sd > PI_PRECISION) throw Error(precisionLimitExceeded);\r
+ return finalise(new Ctor(PI), sd, rm, true);\r
+ }\r
+\r
+\r
+ function getPrecision(digits) {\r
+ var w = digits.length - 1,\r
+ len = w * LOG_BASE + 1;\r
+\r
+ w = digits[w];\r
+\r
+ // If non-zero...\r
+ if (w) {\r
+\r
+ // Subtract the number of trailing zeros of the last word.\r
+ for (; w % 10 == 0; w /= 10) len--;\r
+\r
+ // Add the number of digits of the first word.\r
+ for (w = digits[0]; w >= 10; w /= 10) len++;\r
+ }\r
+\r
+ return len;\r
+ }\r
+\r
+\r
+ function getZeroString(k) {\r
+ var zs = '';\r
+ for (; k--;) zs += '0';\r
+ return zs;\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the value of Decimal `x` to the power `n`, where `n` is an\r
+ * integer of type number.\r
+ *\r
+ * Implements 'exponentiation by squaring'. Called by `pow` and `parseOther`.\r
+ *\r
+ */\r
+ function intPow(Ctor, x, n, pr) {\r
+ var isTruncated,\r
+ r = new Ctor(1),\r
+\r
+ // Max n of 9007199254740991 takes 53 loop iterations.\r
+ // Maximum digits array length; leaves [28, 34] guard digits.\r
+ k = Math.ceil(pr / LOG_BASE + 4);\r
+\r
+ external = false;\r
+\r
+ for (;;) {\r
+ if (n % 2) {\r
+ r = r.times(x);\r
+ if (truncate(r.d, k)) isTruncated = true;\r
+ }\r
+\r
+ n = mathfloor(n / 2);\r
+ if (n === 0) {\r
+\r
+ // To ensure correct rounding when r.d is truncated, increment the last word if it is zero.\r
+ n = r.d.length - 1;\r
+ if (isTruncated && r.d[n] === 0) ++r.d[n];\r
+ break;\r
+ }\r
+\r
+ x = x.times(x);\r
+ truncate(x.d, k);\r
+ }\r
+\r
+ external = true;\r
+\r
+ return r;\r
+ }\r
+\r
+\r
+ function isOdd(n) {\r
+ return n.d[n.d.length - 1] & 1;\r
+ }\r
+\r
+\r
+ /*\r
+ * Handle `max` (`n` is -1) and `min` (`n` is 1).\r
+ */\r
+ function maxOrMin(Ctor, args, n) {\r
+ var k, y,\r
+ x = new Ctor(args[0]),\r
+ i = 0;\r
+\r
+ for (; ++i < args.length;) {\r
+ y = new Ctor(args[i]);\r
+\r
+ // NaN?\r
+ if (!y.s) {\r
+ x = y;\r
+ break;\r
+ }\r
+\r
+ k = x.cmp(y);\r
+\r
+ if (k === n || k === 0 && x.s === n) {\r
+ x = y;\r
+ }\r
+ }\r
+\r
+ return x;\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the natural exponential of `x` rounded to `sd` significant\r
+ * digits.\r
+ *\r
+ * Taylor/Maclaurin series.\r
+ *\r
+ * exp(x) = x^0/0! + x^1/1! + x^2/2! + x^3/3! + ...\r
+ *\r
+ * Argument reduction:\r
+ * Repeat x = x / 32, k += 5, until |x| < 0.1\r
+ * exp(x) = exp(x / 2^k)^(2^k)\r
+ *\r
+ * Previously, the argument was initially reduced by\r
+ * exp(x) = exp(r) * 10^k where r = x - k * ln10, k = floor(x / ln10)\r
+ * to first put r in the range [0, ln10], before dividing by 32 until |x| < 0.1, but this was\r
+ * found to be slower than just dividing repeatedly by 32 as above.\r
+ *\r
+ * Max integer argument: exp('20723265836946413') = 6.3e+9000000000000000\r
+ * Min integer argument: exp('-20723265836946411') = 1.2e-9000000000000000\r
+ * (Math object integer min/max: Math.exp(709) = 8.2e+307, Math.exp(-745) = 5e-324)\r
+ *\r
+ * exp(Infinity) = Infinity\r
+ * exp(-Infinity) = 0\r
+ * exp(NaN) = NaN\r
+ * exp(±0) = 1\r
+ *\r
+ * exp(x) is non-terminating for any finite, non-zero x.\r
+ *\r
+ * The result will always be correctly rounded.\r
+ *\r
+ */\r
+ function naturalExponential(x, sd) {\r
+ var denominator, guard, j, pow, sum, t, wpr,\r
+ rep = 0,\r
+ i = 0,\r
+ k = 0,\r
+ Ctor = x.constructor,\r
+ rm = Ctor.rounding,\r
+ pr = Ctor.precision;\r
+\r
+ // 0/NaN/Infinity?\r
+ if (!x.d || !x.d[0] || x.e > 17) {\r
+\r
+ return new Ctor(x.d\r
+ ? !x.d[0] ? 1 : x.s < 0 ? 0 : 1 / 0\r
+ : x.s ? x.s < 0 ? 0 : x : 0 / 0);\r
+ }\r
+\r
+ if (sd == null) {\r
+ external = false;\r
+ wpr = pr;\r
+ } else {\r
+ wpr = sd;\r
+ }\r
+\r
+ t = new Ctor(0.03125);\r
+\r
+ // while abs(x) >= 0.1\r
+ while (x.e > -2) {\r
+\r
+ // x = x / 2^5\r
+ x = x.times(t);\r
+ k += 5;\r
+ }\r
+\r
+ // Use 2 * log10(2^k) + 5 (empirically derived) to estimate the increase in precision\r
+ // necessary to ensure the first 4 rounding digits are correct.\r
+ guard = Math.log(mathpow(2, k)) / Math.LN10 * 2 + 5 | 0;\r
+ wpr += guard;\r
+ denominator = pow = sum = new Ctor(1);\r
+ Ctor.precision = wpr;\r
+\r
+ for (;;) {\r
+ pow = finalise(pow.times(x), wpr, 1);\r
+ denominator = denominator.times(++i);\r
+ t = sum.plus(divide(pow, denominator, wpr, 1));\r
+\r
+ if (digitsToString(t.d).slice(0, wpr) === digitsToString(sum.d).slice(0, wpr)) {\r
+ j = k;\r
+ while (j--) sum = finalise(sum.times(sum), wpr, 1);\r
+\r
+ // Check to see if the first 4 rounding digits are [49]999.\r
+ // If so, repeat the summation with a higher precision, otherwise\r
+ // e.g. with precision: 18, rounding: 1\r
+ // exp(18.404272462595034083567793919843761) = 98372560.1229999999 (should be 98372560.123)\r
+ // `wpr - guard` is the index of first rounding digit.\r
+ if (sd == null) {\r
+\r
+ if (rep < 3 && checkRoundingDigits(sum.d, wpr - guard, rm, rep)) {\r
+ Ctor.precision = wpr += 10;\r
+ denominator = pow = t = new Ctor(1);\r
+ i = 0;\r
+ rep++;\r
+ } else {\r
+ return finalise(sum, Ctor.precision = pr, rm, external = true);\r
+ }\r
+ } else {\r
+ Ctor.precision = pr;\r
+ return sum;\r
+ }\r
+ }\r
+\r
+ sum = t;\r
+ }\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the natural logarithm of `x` rounded to `sd` significant\r
+ * digits.\r
+ *\r
+ * ln(-n) = NaN\r
+ * ln(0) = -Infinity\r
+ * ln(-0) = -Infinity\r
+ * ln(1) = 0\r
+ * ln(Infinity) = Infinity\r
+ * ln(-Infinity) = NaN\r
+ * ln(NaN) = NaN\r
+ *\r
+ * ln(n) (n != 1) is non-terminating.\r
+ *\r
+ */\r
+ function naturalLogarithm(y, sd) {\r
+ var c, c0, denominator, e, numerator, rep, sum, t, wpr, x1, x2,\r
+ n = 1,\r
+ guard = 10,\r
+ x = y,\r
+ xd = x.d,\r
+ Ctor = x.constructor,\r
+ rm = Ctor.rounding,\r
+ pr = Ctor.precision;\r
+\r
+ // Is x negative or Infinity, NaN, 0 or 1?\r
+ if (x.s < 0 || !xd || !xd[0] || !x.e && xd[0] == 1 && xd.length == 1) {\r
+ return new Ctor(xd && !xd[0] ? -1 / 0 : x.s != 1 ? NaN : xd ? 0 : x);\r
+ }\r
+\r
+ if (sd == null) {\r
+ external = false;\r
+ wpr = pr;\r
+ } else {\r
+ wpr = sd;\r
+ }\r
+\r
+ Ctor.precision = wpr += guard;\r
+ c = digitsToString(xd);\r
+ c0 = c.charAt(0);\r
+\r
+ if (Math.abs(e = x.e) < 1.5e15) {\r
+\r
+ // Argument reduction.\r
+ // The series converges faster the closer the argument is to 1, so using\r
+ // ln(a^b) = b * ln(a), ln(a) = ln(a^b) / b\r
+ // multiply the argument by itself until the leading digits of the significand are 7, 8, 9,\r
+ // 10, 11, 12 or 13, recording the number of multiplications so the sum of the series can\r
+ // later be divided by this number, then separate out the power of 10 using\r
+ // ln(a*10^b) = ln(a) + b*ln(10).\r
+\r
+ // max n is 21 (gives 0.9, 1.0 or 1.1) (9e15 / 21 = 4.2e14).\r
+ //while (c0 < 9 && c0 != 1 || c0 == 1 && c.charAt(1) > 1) {\r
+ // max n is 6 (gives 0.7 - 1.3)\r
+ while (c0 < 7 && c0 != 1 || c0 == 1 && c.charAt(1) > 3) {\r
+ x = x.times(y);\r
+ c = digitsToString(x.d);\r
+ c0 = c.charAt(0);\r
+ n++;\r
+ }\r
+\r
+ e = x.e;\r
+\r
+ if (c0 > 1) {\r
+ x = new Ctor('0.' + c);\r
+ e++;\r
+ } else {\r
+ x = new Ctor(c0 + '.' + c.slice(1));\r
+ }\r
+ } else {\r
+\r
+ // The argument reduction method above may result in overflow if the argument y is a massive\r
+ // number with exponent >= 1500000000000000 (9e15 / 6 = 1.5e15), so instead recall this\r
+ // function using ln(x*10^e) = ln(x) + e*ln(10).\r
+ t = getLn10(Ctor, wpr + 2, pr).times(e + '');\r
+ x = naturalLogarithm(new Ctor(c0 + '.' + c.slice(1)), wpr - guard).plus(t);\r
+ Ctor.precision = pr;\r
+\r
+ return sd == null ? finalise(x, pr, rm, external = true) : x;\r
+ }\r
+\r
+ // x1 is x reduced to a value near 1.\r
+ x1 = x;\r
+\r
+ // Taylor series.\r
+ // ln(y) = ln((1 + x)/(1 - x)) = 2(x + x^3/3 + x^5/5 + x^7/7 + ...)\r
+ // where x = (y - 1)/(y + 1) (|x| < 1)\r
+ sum = numerator = x = divide(x.minus(1), x.plus(1), wpr, 1);\r
+ x2 = finalise(x.times(x), wpr, 1);\r
+ denominator = 3;\r
+\r
+ for (;;) {\r
+ numerator = finalise(numerator.times(x2), wpr, 1);\r
+ t = sum.plus(divide(numerator, new Ctor(denominator), wpr, 1));\r
+\r
+ if (digitsToString(t.d).slice(0, wpr) === digitsToString(sum.d).slice(0, wpr)) {\r
+ sum = sum.times(2);\r
+\r
+ // Reverse the argument reduction. Check that e is not 0 because, besides preventing an\r
+ // unnecessary calculation, -0 + 0 = +0 and to ensure correct rounding -0 needs to stay -0.\r
+ if (e !== 0) sum = sum.plus(getLn10(Ctor, wpr + 2, pr).times(e + ''));\r
+ sum = divide(sum, new Ctor(n), wpr, 1);\r
+\r
+ // Is rm > 3 and the first 4 rounding digits 4999, or rm < 4 (or the summation has\r
+ // been repeated previously) and the first 4 rounding digits 9999?\r
+ // If so, restart the summation with a higher precision, otherwise\r
+ // e.g. with precision: 12, rounding: 1\r
+ // ln(135520028.6126091714265381533) = 18.7246299999 when it should be 18.72463.\r
+ // `wpr - guard` is the index of first rounding digit.\r
+ if (sd == null) {\r
+ if (checkRoundingDigits(sum.d, wpr - guard, rm, rep)) {\r
+ Ctor.precision = wpr += guard;\r
+ t = numerator = x = divide(x1.minus(1), x1.plus(1), wpr, 1);\r
+ x2 = finalise(x.times(x), wpr, 1);\r
+ denominator = rep = 1;\r
+ } else {\r
+ return finalise(sum, Ctor.precision = pr, rm, external = true);\r
+ }\r
+ } else {\r
+ Ctor.precision = pr;\r
+ return sum;\r
+ }\r
+ }\r
+\r
+ sum = t;\r
+ denominator += 2;\r
+ }\r
+ }\r
+\r
+\r
+ // ±Infinity, NaN.\r
+ function nonFiniteToString(x) {\r
+ // Unsigned.\r
+ return String(x.s * x.s / 0);\r
+ }\r
+\r
+\r
+ /*\r
+ * Parse the value of a new Decimal `x` from string `str`.\r
+ */\r
+ function parseDecimal(x, str) {\r
+ var e, i, len;\r
+\r
+ // TODO BigInt str: no need to check for decimal point, exponential form or leading zeros.\r
+\r
+ // Decimal point?\r
+ if ((e = str.indexOf('.')) > -1) str = str.replace('.', '');\r
+\r
+ // Exponential form?\r
+ if ((i = str.search(/e/i)) > 0) {\r
+\r
+ // Determine exponent.\r
+ if (e < 0) e = i;\r
+ e += +str.slice(i + 1);\r
+ str = str.substring(0, i);\r
+ } else if (e < 0) {\r
+\r
+ // Integer.\r
+ e = str.length;\r
+ }\r
+\r
+ // Determine leading zeros.\r
+ for (i = 0; str.charCodeAt(i) === 48; i++);\r
+\r
+ // Determine trailing zeros.\r
+ for (len = str.length; str.charCodeAt(len - 1) === 48; --len);\r
+ str = str.slice(i, len);\r
+\r
+ if (str) {\r
+ len -= i;\r
+ x.e = e = e - i - 1;\r
+ x.d = [];\r
+\r
+ // Transform base\r
+\r
+ // e is the base 10 exponent.\r
+ // i is where to slice str to get the first word of the digits array.\r
+ i = (e + 1) % LOG_BASE;\r
+ if (e < 0) i += LOG_BASE;\r
+\r
+ if (i < len) {\r
+ if (i) x.d.push(+str.slice(0, i));\r
+ for (len -= LOG_BASE; i < len;) x.d.push(+str.slice(i, i += LOG_BASE));\r
+ str = str.slice(i);\r
+ i = LOG_BASE - str.length;\r
+ } else {\r
+ i -= len;\r
+ }\r
+\r
+ for (; i--;) str += '0';\r
+ x.d.push(+str);\r
+\r
+ if (external) {\r
+\r
+ // Overflow?\r
+ if (x.e > x.constructor.maxE) {\r
+\r
+ // Infinity.\r
+ x.d = null;\r
+ x.e = NaN;\r
+\r
+ // Underflow?\r
+ } else if (x.e < x.constructor.minE) {\r
+\r
+ // Zero.\r
+ x.e = 0;\r
+ x.d = [0];\r
+ // x.constructor.underflow = true;\r
+ } // else x.constructor.underflow = false;\r
+ }\r
+ } else {\r
+\r
+ // Zero.\r
+ x.e = 0;\r
+ x.d = [0];\r
+ }\r
+\r
+ return x;\r
+ }\r
+\r
+\r
+ /*\r
+ * Parse the value of a new Decimal `x` from a string `str`, which is not a decimal value.\r
+ */\r
+ function parseOther(x, str) {\r
+ var base, Ctor, divisor, i, isFloat, len, p, xd, xe;\r
+\r
+ if (str.indexOf('_') > -1) {\r
+ str = str.replace(/(\d)_(?=\d)/g, '$1');\r
+ if (isDecimal.test(str)) return parseDecimal(x, str);\r
+ } else if (str === 'Infinity' || str === 'NaN') {\r
+ if (!+str) x.s = NaN;\r
+ x.e = NaN;\r
+ x.d = null;\r
+ return x;\r
+ }\r
+\r
+ if (isHex.test(str)) {\r
+ base = 16;\r
+ str = str.toLowerCase();\r
+ } else if (isBinary.test(str)) {\r
+ base = 2;\r
+ } else if (isOctal.test(str)) {\r
+ base = 8;\r
+ } else {\r
+ throw Error(invalidArgument + str);\r
+ }\r
+\r
+ // Is there a binary exponent part?\r
+ i = str.search(/p/i);\r
+\r
+ if (i > 0) {\r
+ p = +str.slice(i + 1);\r
+ str = str.substring(2, i);\r
+ } else {\r
+ str = str.slice(2);\r
+ }\r
+\r
+ // Convert `str` as an integer then divide the result by `base` raised to a power such that the\r
+ // fraction part will be restored.\r
+ i = str.indexOf('.');\r
+ isFloat = i >= 0;\r
+ Ctor = x.constructor;\r
+\r
+ if (isFloat) {\r
+ str = str.replace('.', '');\r
+ len = str.length;\r
+ i = len - i;\r
+\r
+ // log[10](16) = 1.2041... , log[10](88) = 1.9444....\r
+ divisor = intPow(Ctor, new Ctor(base), i, i * 2);\r
+ }\r
+\r
+ xd = convertBase(str, base, BASE);\r
+ xe = xd.length - 1;\r
+\r
+ // Remove trailing zeros.\r
+ for (i = xe; xd[i] === 0; --i) xd.pop();\r
+ if (i < 0) return new Ctor(x.s * 0);\r
+ x.e = getBase10Exponent(xd, xe);\r
+ x.d = xd;\r
+ external = false;\r
+\r
+ // At what precision to perform the division to ensure exact conversion?\r
+ // maxDecimalIntegerPartDigitCount = ceil(log[10](b) * otherBaseIntegerPartDigitCount)\r
+ // log[10](2) = 0.30103, log[10](8) = 0.90309, log[10](16) = 1.20412\r
+ // E.g. ceil(1.2 * 3) = 4, so up to 4 decimal digits are needed to represent 3 hex int digits.\r
+ // maxDecimalFractionPartDigitCount = {Hex:4|Oct:3|Bin:1} * otherBaseFractionPartDigitCount\r
+ // Therefore using 4 * the number of digits of str will always be enough.\r
+ if (isFloat) x = divide(x, divisor, len * 4);\r
+\r
+ // Multiply by the binary exponent part if present.\r
+ if (p) x = x.times(Math.abs(p) < 54 ? mathpow(2, p) : Decimal.pow(2, p));\r
+ external = true;\r
+\r
+ return x;\r
+ }\r
+\r
+\r
+ /*\r
+ * sin(x) = x - x^3/3! + x^5/5! - ...\r
+ * |x| < pi/2\r
+ *\r
+ */\r
+ function sine(Ctor, x) {\r
+ var k,\r
+ len = x.d.length;\r
+\r
+ if (len < 3) {\r
+ return x.isZero() ? x : taylorSeries(Ctor, 2, x, x);\r
+ }\r
+\r
+ // Argument reduction: sin(5x) = 16*sin^5(x) - 20*sin^3(x) + 5*sin(x)\r
+ // i.e. sin(x) = 16*sin^5(x/5) - 20*sin^3(x/5) + 5*sin(x/5)\r
+ // and sin(x) = sin(x/5)(5 + sin^2(x/5)(16sin^2(x/5) - 20))\r
+\r
+ // Estimate the optimum number of times to use the argument reduction.\r
+ k = 1.4 * Math.sqrt(len);\r
+ k = k > 16 ? 16 : k | 0;\r
+\r
+ x = x.times(1 / tinyPow(5, k));\r
+ x = taylorSeries(Ctor, 2, x, x);\r
+\r
+ // Reverse argument reduction\r
+ var sin2_x,\r
+ d5 = new Ctor(5),\r
+ d16 = new Ctor(16),\r
+ d20 = new Ctor(20);\r
+ for (; k--;) {\r
+ sin2_x = x.times(x);\r
+ x = x.times(d5.plus(sin2_x.times(d16.times(sin2_x).minus(d20))));\r
+ }\r
+\r
+ return x;\r
+ }\r
+\r
+\r
+ // Calculate Taylor series for `cos`, `cosh`, `sin` and `sinh`.\r
+ function taylorSeries(Ctor, n, x, y, isHyperbolic) {\r
+ var j, t, u, x2,\r
+ i = 1,\r
+ pr = Ctor.precision,\r
+ k = Math.ceil(pr / LOG_BASE);\r
+\r
+ external = false;\r
+ x2 = x.times(x);\r
+ u = new Ctor(y);\r
+\r
+ for (;;) {\r
+ t = divide(u.times(x2), new Ctor(n++ * n++), pr, 1);\r
+ u = isHyperbolic ? y.plus(t) : y.minus(t);\r
+ y = divide(t.times(x2), new Ctor(n++ * n++), pr, 1);\r
+ t = u.plus(y);\r
+\r
+ if (t.d[k] !== void 0) {\r
+ for (j = k; t.d[j] === u.d[j] && j--;);\r
+ if (j == -1) break;\r
+ }\r
+\r
+ j = u;\r
+ u = y;\r
+ y = t;\r
+ t = j;\r
+ i++;\r
+ }\r
+\r
+ external = true;\r
+ t.d.length = k + 1;\r
+\r
+ return t;\r
+ }\r
+\r
+\r
+ // Exponent e must be positive and non-zero.\r
+ function tinyPow(b, e) {\r
+ var n = b;\r
+ while (--e) n *= b;\r
+ return n;\r
+ }\r
+\r
+\r
+ // Return the absolute value of `x` reduced to less than or equal to half pi.\r
+ function toLessThanHalfPi(Ctor, x) {\r
+ var t,\r
+ isNeg = x.s < 0,\r
+ pi = getPi(Ctor, Ctor.precision, 1),\r
+ halfPi = pi.times(0.5);\r
+\r
+ x = x.abs();\r
+\r
+ if (x.lte(halfPi)) {\r
+ quadrant = isNeg ? 4 : 1;\r
+ return x;\r
+ }\r
+\r
+ t = x.divToInt(pi);\r
+\r
+ if (t.isZero()) {\r
+ quadrant = isNeg ? 3 : 2;\r
+ } else {\r
+ x = x.minus(t.times(pi));\r
+\r
+ // 0 <= x < pi\r
+ if (x.lte(halfPi)) {\r
+ quadrant = isOdd(t) ? (isNeg ? 2 : 3) : (isNeg ? 4 : 1);\r
+ return x;\r
+ }\r
+\r
+ quadrant = isOdd(t) ? (isNeg ? 1 : 4) : (isNeg ? 3 : 2);\r
+ }\r
+\r
+ return x.minus(pi).abs();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return the value of Decimal `x` as a string in base `baseOut`.\r
+ *\r
+ * If the optional `sd` argument is present include a binary exponent suffix.\r
+ */\r
+ function toStringBinary(x, baseOut, sd, rm) {\r
+ var base, e, i, k, len, roundUp, str, xd, y,\r
+ Ctor = x.constructor,\r
+ isExp = sd !== void 0;\r
+\r
+ if (isExp) {\r
+ checkInt32(sd, 1, MAX_DIGITS);\r
+ if (rm === void 0) rm = Ctor.rounding;\r
+ else checkInt32(rm, 0, 8);\r
+ } else {\r
+ sd = Ctor.precision;\r
+ rm = Ctor.rounding;\r
+ }\r
+\r
+ if (!x.isFinite()) {\r
+ str = nonFiniteToString(x);\r
+ } else {\r
+ str = finiteToString(x);\r
+ i = str.indexOf('.');\r
+\r
+ // Use exponential notation according to `toExpPos` and `toExpNeg`? No, but if required:\r
+ // maxBinaryExponent = floor((decimalExponent + 1) * log[2](10))\r
+ // minBinaryExponent = floor(decimalExponent * log[2](10))\r
+ // log[2](10) = 3.321928094887362347870319429489390175864\r
+\r
+ if (isExp) {\r
+ base = 2;\r
+ if (baseOut == 16) {\r
+ sd = sd * 4 - 3;\r
+ } else if (baseOut == 8) {\r
+ sd = sd * 3 - 2;\r
+ }\r
+ } else {\r
+ base = baseOut;\r
+ }\r
+\r
+ // Convert the number as an integer then divide the result by its base raised to a power such\r
+ // that the fraction part will be restored.\r
+\r
+ // Non-integer.\r
+ if (i >= 0) {\r
+ str = str.replace('.', '');\r
+ y = new Ctor(1);\r
+ y.e = str.length - i;\r
+ y.d = convertBase(finiteToString(y), 10, base);\r
+ y.e = y.d.length;\r
+ }\r
+\r
+ xd = convertBase(str, 10, base);\r
+ e = len = xd.length;\r
+\r
+ // Remove trailing zeros.\r
+ for (; xd[--len] == 0;) xd.pop();\r
+\r
+ if (!xd[0]) {\r
+ str = isExp ? '0p+0' : '0';\r
+ } else {\r
+ if (i < 0) {\r
+ e--;\r
+ } else {\r
+ x = new Ctor(x);\r
+ x.d = xd;\r
+ x.e = e;\r
+ x = divide(x, y, sd, rm, 0, base);\r
+ xd = x.d;\r
+ e = x.e;\r
+ roundUp = inexact;\r
+ }\r
+\r
+ // The rounding digit, i.e. the digit after the digit that may be rounded up.\r
+ i = xd[sd];\r
+ k = base / 2;\r
+ roundUp = roundUp || xd[sd + 1] !== void 0;\r
+\r
+ roundUp = rm < 4\r
+ ? (i !== void 0 || roundUp) && (rm === 0 || rm === (x.s < 0 ? 3 : 2))\r
+ : i > k || i === k && (rm === 4 || roundUp || rm === 6 && xd[sd - 1] & 1 ||\r
+ rm === (x.s < 0 ? 8 : 7));\r
+\r
+ xd.length = sd;\r
+\r
+ if (roundUp) {\r
+\r
+ // Rounding up may mean the previous digit has to be rounded up and so on.\r
+ for (; ++xd[--sd] > base - 1;) {\r
+ xd[sd] = 0;\r
+ if (!sd) {\r
+ ++e;\r
+ xd.unshift(1);\r
+ }\r
+ }\r
+ }\r
+\r
+ // Determine trailing zeros.\r
+ for (len = xd.length; !xd[len - 1]; --len);\r
+\r
+ // E.g. [4, 11, 15] becomes 4bf.\r
+ for (i = 0, str = ''; i < len; i++) str += NUMERALS.charAt(xd[i]);\r
+\r
+ // Add binary exponent suffix?\r
+ if (isExp) {\r
+ if (len > 1) {\r
+ if (baseOut == 16 || baseOut == 8) {\r
+ i = baseOut == 16 ? 4 : 3;\r
+ for (--len; len % i; len++) str += '0';\r
+ xd = convertBase(str, base, baseOut);\r
+ for (len = xd.length; !xd[len - 1]; --len);\r
+\r
+ // xd[0] will always be be 1\r
+ for (i = 1, str = '1.'; i < len; i++) str += NUMERALS.charAt(xd[i]);\r
+ } else {\r
+ str = str.charAt(0) + '.' + str.slice(1);\r
+ }\r
+ }\r
+\r
+ str = str + (e < 0 ? 'p' : 'p+') + e;\r
+ } else if (e < 0) {\r
+ for (; ++e;) str = '0' + str;\r
+ str = '0.' + str;\r
+ } else {\r
+ if (++e > len) for (e -= len; e-- ;) str += '0';\r
+ else if (e < len) str = str.slice(0, e) + '.' + str.slice(e);\r
+ }\r
+ }\r
+\r
+ str = (baseOut == 16 ? '0x' : baseOut == 2 ? '0b' : baseOut == 8 ? '0o' : '') + str;\r
+ }\r
+\r
+ return x.s < 0 ? '-' + str : str;\r
+ }\r
+\r
+\r
+ // Does not strip trailing zeros.\r
+ function truncate(arr, len) {\r
+ if (arr.length > len) {\r
+ arr.length = len;\r
+ return true;\r
+ }\r
+ }\r
+\r
+\r
+ // Decimal methods\r
+\r
+\r
+ /*\r
+ * abs\r
+ * acos\r
+ * acosh\r
+ * add\r
+ * asin\r
+ * asinh\r
+ * atan\r
+ * atanh\r
+ * atan2\r
+ * cbrt\r
+ * ceil\r
+ * clamp\r
+ * clone\r
+ * config\r
+ * cos\r
+ * cosh\r
+ * div\r
+ * exp\r
+ * floor\r
+ * hypot\r
+ * ln\r
+ * log\r
+ * log2\r
+ * log10\r
+ * max\r
+ * min\r
+ * mod\r
+ * mul\r
+ * pow\r
+ * random\r
+ * round\r
+ * set\r
+ * sign\r
+ * sin\r
+ * sinh\r
+ * sqrt\r
+ * sub\r
+ * sum\r
+ * tan\r
+ * tanh\r
+ * trunc\r
+ */\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the absolute value of `x`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function abs(x) {\r
+ return new this(x).abs();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the arccosine in radians of `x`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function acos(x) {\r
+ return new this(x).acos();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the inverse of the hyperbolic cosine of `x`, rounded to\r
+ * `precision` significant digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal} A value in radians.\r
+ *\r
+ */\r
+ function acosh(x) {\r
+ return new this(x).acosh();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the sum of `x` and `y`, rounded to `precision` significant\r
+ * digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ * y {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function add(x, y) {\r
+ return new this(x).plus(y);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the arcsine in radians of `x`, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function asin(x) {\r
+ return new this(x).asin();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the inverse of the hyperbolic sine of `x`, rounded to\r
+ * `precision` significant digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal} A value in radians.\r
+ *\r
+ */\r
+ function asinh(x) {\r
+ return new this(x).asinh();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the arctangent in radians of `x`, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function atan(x) {\r
+ return new this(x).atan();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the inverse of the hyperbolic tangent of `x`, rounded to\r
+ * `precision` significant digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal} A value in radians.\r
+ *\r
+ */\r
+ function atanh(x) {\r
+ return new this(x).atanh();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the arctangent in radians of `y/x` in the range -pi to pi\r
+ * (inclusive), rounded to `precision` significant digits using rounding mode `rounding`.\r
+ *\r
+ * Domain: [-Infinity, Infinity]\r
+ * Range: [-pi, pi]\r
+ *\r
+ * y {number|string|bigint|Decimal} The y-coordinate.\r
+ * x {number|string|bigint|Decimal} The x-coordinate.\r
+ *\r
+ * atan2(±0, -0) = ±pi\r
+ * atan2(±0, +0) = ±0\r
+ * atan2(±0, -x) = ±pi for x > 0\r
+ * atan2(±0, x) = ±0 for x > 0\r
+ * atan2(-y, ±0) = -pi/2 for y > 0\r
+ * atan2(y, ±0) = pi/2 for y > 0\r
+ * atan2(±y, -Infinity) = ±pi for finite y > 0\r
+ * atan2(±y, +Infinity) = ±0 for finite y > 0\r
+ * atan2(±Infinity, x) = ±pi/2 for finite x\r
+ * atan2(±Infinity, -Infinity) = ±3*pi/4\r
+ * atan2(±Infinity, +Infinity) = ±pi/4\r
+ * atan2(NaN, x) = NaN\r
+ * atan2(y, NaN) = NaN\r
+ *\r
+ */\r
+ function atan2(y, x) {\r
+ y = new this(y);\r
+ x = new this(x);\r
+ var r,\r
+ pr = this.precision,\r
+ rm = this.rounding,\r
+ wpr = pr + 4;\r
+\r
+ // Either NaN\r
+ if (!y.s || !x.s) {\r
+ r = new this(NaN);\r
+\r
+ // Both ±Infinity\r
+ } else if (!y.d && !x.d) {\r
+ r = getPi(this, wpr, 1).times(x.s > 0 ? 0.25 : 0.75);\r
+ r.s = y.s;\r
+\r
+ // x is ±Infinity or y is ±0\r
+ } else if (!x.d || y.isZero()) {\r
+ r = x.s < 0 ? getPi(this, pr, rm) : new this(0);\r
+ r.s = y.s;\r
+\r
+ // y is ±Infinity or x is ±0\r
+ } else if (!y.d || x.isZero()) {\r
+ r = getPi(this, wpr, 1).times(0.5);\r
+ r.s = y.s;\r
+\r
+ // Both non-zero and finite\r
+ } else if (x.s < 0) {\r
+ this.precision = wpr;\r
+ this.rounding = 1;\r
+ r = this.atan(divide(y, x, wpr, 1));\r
+ x = getPi(this, wpr, 1);\r
+ this.precision = pr;\r
+ this.rounding = rm;\r
+ r = y.s < 0 ? r.minus(x) : r.plus(x);\r
+ } else {\r
+ r = this.atan(divide(y, x, wpr, 1));\r
+ }\r
+\r
+ return r;\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the cube root of `x`, rounded to `precision` significant\r
+ * digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function cbrt(x) {\r
+ return new this(x).cbrt();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is `x` rounded to an integer using `ROUND_CEIL`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function ceil(x) {\r
+ return finalise(x = new this(x), x.e + 1, 2);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is `x` clamped to the range delineated by `min` and `max`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ * min {number|string|bigint|Decimal}\r
+ * max {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function clamp(x, min, max) {\r
+ return new this(x).clamp(min, max);\r
+ }\r
+\r
+\r
+ /*\r
+ * Configure global settings for a Decimal constructor.\r
+ *\r
+ * `obj` is an object with one or more of the following properties,\r
+ *\r
+ * precision {number}\r
+ * rounding {number}\r
+ * toExpNeg {number}\r
+ * toExpPos {number}\r
+ * maxE {number}\r
+ * minE {number}\r
+ * modulo {number}\r
+ * crypto {boolean|number}\r
+ * defaults {true}\r
+ *\r
+ * E.g. Decimal.config({ precision: 20, rounding: 4 })\r
+ *\r
+ */\r
+ function config(obj) {\r
+ if (!obj || typeof obj !== 'object') throw Error(decimalError + 'Object expected');\r
+ var i, p, v,\r
+ useDefaults = obj.defaults === true,\r
+ ps = [\r
+ 'precision', 1, MAX_DIGITS,\r
+ 'rounding', 0, 8,\r
+ 'toExpNeg', -EXP_LIMIT, 0,\r
+ 'toExpPos', 0, EXP_LIMIT,\r
+ 'maxE', 0, EXP_LIMIT,\r
+ 'minE', -EXP_LIMIT, 0,\r
+ 'modulo', 0, 9\r
+ ];\r
+\r
+ for (i = 0; i < ps.length; i += 3) {\r
+ if (p = ps[i], useDefaults) this[p] = DEFAULTS[p];\r
+ if ((v = obj[p]) !== void 0) {\r
+ if (mathfloor(v) === v && v >= ps[i + 1] && v <= ps[i + 2]) this[p] = v;\r
+ else throw Error(invalidArgument + p + ': ' + v);\r
+ }\r
+ }\r
+\r
+ if (p = 'crypto', useDefaults) this[p] = DEFAULTS[p];\r
+ if ((v = obj[p]) !== void 0) {\r
+ if (v === true || v === false || v === 0 || v === 1) {\r
+ if (v) {\r
+ if (typeof crypto != 'undefined' && crypto &&\r
+ (crypto.getRandomValues || crypto.randomBytes)) {\r
+ this[p] = true;\r
+ } else {\r
+ throw Error(cryptoUnavailable);\r
+ }\r
+ } else {\r
+ this[p] = false;\r
+ }\r
+ } else {\r
+ throw Error(invalidArgument + p + ': ' + v);\r
+ }\r
+ }\r
+\r
+ return this;\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the cosine of `x`, rounded to `precision` significant\r
+ * digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal} A value in radians.\r
+ *\r
+ */\r
+ function cos(x) {\r
+ return new this(x).cos();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the hyperbolic cosine of `x`, rounded to precision\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal} A value in radians.\r
+ *\r
+ */\r
+ function cosh(x) {\r
+ return new this(x).cosh();\r
+ }\r
+\r
+\r
+ /*\r
+ * Create and return a Decimal constructor with the same configuration properties as this Decimal\r
+ * constructor.\r
+ *\r
+ */\r
+ function clone(obj) {\r
+ var i, p, ps;\r
+\r
+ /*\r
+ * The Decimal constructor and exported function.\r
+ * Return a new Decimal instance.\r
+ *\r
+ * v {number|string|bigint|Decimal} A numeric value.\r
+ *\r
+ */\r
+ function Decimal(v) {\r
+ var e, i, t,\r
+ x = this;\r
+\r
+ // Decimal called without new.\r
+ if (!(x instanceof Decimal)) return new Decimal(v);\r
+\r
+ // Retain a reference to this Decimal constructor, and shadow Decimal.prototype.constructor\r
+ // which points to Object.\r
+ x.constructor = Decimal;\r
+\r
+ if (isDecimalInstance(v)) {\r
+ x.s = v.s;\r
+\r
+ if (external) {\r
+ if (!v.d || v.e > Decimal.maxE) {\r
+\r
+ // Infinity.\r
+ x.e = NaN;\r
+ x.d = null;\r
+ } else if (v.e < Decimal.minE) {\r
+\r
+ // Zero.\r
+ x.e = 0;\r
+ x.d = [0];\r
+ } else {\r
+ x.e = v.e;\r
+ x.d = v.d.slice();\r
+ }\r
+ } else {\r
+ x.e = v.e;\r
+ x.d = v.d ? v.d.slice() : v.d;\r
+ }\r
+\r
+ return;\r
+ }\r
+\r
+ t = typeof v;\r
+\r
+ if (t === 'number') {\r
+ if (v === 0) {\r
+ x.s = 1 / v < 0 ? -1 : 1;\r
+ x.e = 0;\r
+ x.d = [0];\r
+ return;\r
+ }\r
+\r
+ if (v < 0) {\r
+ v = -v;\r
+ x.s = -1;\r
+ } else {\r
+ x.s = 1;\r
+ }\r
+\r
+ // Fast path for small integers.\r
+ if (v === ~~v && v < 1e7) {\r
+ for (e = 0, i = v; i >= 10; i /= 10) e++;\r
+\r
+ if (external) {\r
+ if (e > Decimal.maxE) {\r
+ x.e = NaN;\r
+ x.d = null;\r
+ } else if (e < Decimal.minE) {\r
+ x.e = 0;\r
+ x.d = [0];\r
+ } else {\r
+ x.e = e;\r
+ x.d = [v];\r
+ }\r
+ } else {\r
+ x.e = e;\r
+ x.d = [v];\r
+ }\r
+\r
+ return;\r
+ }\r
+\r
+ // Infinity or NaN?\r
+ if (v * 0 !== 0) {\r
+ if (!v) x.s = NaN;\r
+ x.e = NaN;\r
+ x.d = null;\r
+ return;\r
+ }\r
+\r
+ return parseDecimal(x, v.toString());\r
+ }\r
+\r
+ if (t === 'string') {\r
+ if ((i = v.charCodeAt(0)) === 45) { // minus sign\r
+ v = v.slice(1);\r
+ x.s = -1;\r
+ } else {\r
+ if (i === 43) v = v.slice(1); // plus sign\r
+ x.s = 1;\r
+ }\r
+\r
+ return isDecimal.test(v) ? parseDecimal(x, v) : parseOther(x, v);\r
+ }\r
+\r
+ if (t === 'bigint') {\r
+ if (v < 0) {\r
+ v = -v;\r
+ x.s = -1;\r
+ } else {\r
+ x.s = 1;\r
+ }\r
+\r
+ return parseDecimal(x, v.toString());\r
+ }\r
+\r
+ throw Error(invalidArgument + v);\r
+ }\r
+\r
+ Decimal.prototype = P;\r
+\r
+ Decimal.ROUND_UP = 0;\r
+ Decimal.ROUND_DOWN = 1;\r
+ Decimal.ROUND_CEIL = 2;\r
+ Decimal.ROUND_FLOOR = 3;\r
+ Decimal.ROUND_HALF_UP = 4;\r
+ Decimal.ROUND_HALF_DOWN = 5;\r
+ Decimal.ROUND_HALF_EVEN = 6;\r
+ Decimal.ROUND_HALF_CEIL = 7;\r
+ Decimal.ROUND_HALF_FLOOR = 8;\r
+ Decimal.EUCLID = 9;\r
+\r
+ Decimal.config = Decimal.set = config;\r
+ Decimal.clone = clone;\r
+ Decimal.isDecimal = isDecimalInstance;\r
+\r
+ Decimal.abs = abs;\r
+ Decimal.acos = acos;\r
+ Decimal.acosh = acosh; // ES6\r
+ Decimal.add = add;\r
+ Decimal.asin = asin;\r
+ Decimal.asinh = asinh; // ES6\r
+ Decimal.atan = atan;\r
+ Decimal.atanh = atanh; // ES6\r
+ Decimal.atan2 = atan2;\r
+ Decimal.cbrt = cbrt; // ES6\r
+ Decimal.ceil = ceil;\r
+ Decimal.clamp = clamp;\r
+ Decimal.cos = cos;\r
+ Decimal.cosh = cosh; // ES6\r
+ Decimal.div = div;\r
+ Decimal.exp = exp;\r
+ Decimal.floor = floor;\r
+ Decimal.hypot = hypot; // ES6\r
+ Decimal.ln = ln;\r
+ Decimal.log = log;\r
+ Decimal.log10 = log10; // ES6\r
+ Decimal.log2 = log2; // ES6\r
+ Decimal.max = max;\r
+ Decimal.min = min;\r
+ Decimal.mod = mod;\r
+ Decimal.mul = mul;\r
+ Decimal.pow = pow;\r
+ Decimal.random = random;\r
+ Decimal.round = round;\r
+ Decimal.sign = sign; // ES6\r
+ Decimal.sin = sin;\r
+ Decimal.sinh = sinh; // ES6\r
+ Decimal.sqrt = sqrt;\r
+ Decimal.sub = sub;\r
+ Decimal.sum = sum;\r
+ Decimal.tan = tan;\r
+ Decimal.tanh = tanh; // ES6\r
+ Decimal.trunc = trunc; // ES6\r
+\r
+ if (obj === void 0) obj = {};\r
+ if (obj) {\r
+ if (obj.defaults !== true) {\r
+ ps = ['precision', 'rounding', 'toExpNeg', 'toExpPos', 'maxE', 'minE', 'modulo', 'crypto'];\r
+ for (i = 0; i < ps.length;) if (!obj.hasOwnProperty(p = ps[i++])) obj[p] = this[p];\r
+ }\r
+ }\r
+\r
+ Decimal.config(obj);\r
+\r
+ return Decimal;\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is `x` divided by `y`, rounded to `precision` significant\r
+ * digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ * y {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function div(x, y) {\r
+ return new this(x).div(y);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the natural exponential of `x`, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal} The power to which to raise the base of the natural log.\r
+ *\r
+ */\r
+ function exp(x) {\r
+ return new this(x).exp();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is `x` round to an integer using `ROUND_FLOOR`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function floor(x) {\r
+ return finalise(x = new this(x), x.e + 1, 3);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the square root of the sum of the squares of the arguments,\r
+ * rounded to `precision` significant digits using rounding mode `rounding`.\r
+ *\r
+ * hypot(a, b, ...) = sqrt(a^2 + b^2 + ...)\r
+ *\r
+ * arguments {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function hypot() {\r
+ var i, n,\r
+ t = new this(0);\r
+\r
+ external = false;\r
+\r
+ for (i = 0; i < arguments.length;) {\r
+ n = new this(arguments[i++]);\r
+ if (!n.d) {\r
+ if (n.s) {\r
+ external = true;\r
+ return new this(1 / 0);\r
+ }\r
+ t = n;\r
+ } else if (t.d) {\r
+ t = t.plus(n.times(n));\r
+ }\r
+ }\r
+\r
+ external = true;\r
+\r
+ return t.sqrt();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return true if object is a Decimal instance (where Decimal is any Decimal constructor),\r
+ * otherwise return false.\r
+ *\r
+ */\r
+ function isDecimalInstance(obj) {\r
+ return obj instanceof Decimal || obj && obj.toStringTag === tag || false;\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the natural logarithm of `x`, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function ln(x) {\r
+ return new this(x).ln();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the log of `x` to the base `y`, or to base 10 if no base\r
+ * is specified, rounded to `precision` significant digits using rounding mode `rounding`.\r
+ *\r
+ * log[y](x)\r
+ *\r
+ * x {number|string|bigint|Decimal} The argument of the logarithm.\r
+ * y {number|string|bigint|Decimal} The base of the logarithm.\r
+ *\r
+ */\r
+ function log(x, y) {\r
+ return new this(x).log(y);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the base 2 logarithm of `x`, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function log2(x) {\r
+ return new this(x).log(2);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the base 10 logarithm of `x`, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function log10(x) {\r
+ return new this(x).log(10);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the maximum of the arguments.\r
+ *\r
+ * arguments {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function max() {\r
+ return maxOrMin(this, arguments, -1);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the minimum of the arguments.\r
+ *\r
+ * arguments {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function min() {\r
+ return maxOrMin(this, arguments, 1);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is `x` modulo `y`, rounded to `precision` significant digits\r
+ * using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ * y {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function mod(x, y) {\r
+ return new this(x).mod(y);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is `x` multiplied by `y`, rounded to `precision` significant\r
+ * digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ * y {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function mul(x, y) {\r
+ return new this(x).mul(y);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is `x` raised to the power `y`, rounded to precision\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal} The base.\r
+ * y {number|string|bigint|Decimal} The exponent.\r
+ *\r
+ */\r
+ function pow(x, y) {\r
+ return new this(x).pow(y);\r
+ }\r
+\r
+\r
+ /*\r
+ * Returns a new Decimal with a random value equal to or greater than 0 and less than 1, and with\r
+ * `sd`, or `Decimal.precision` if `sd` is omitted, significant digits (or less if trailing zeros\r
+ * are produced).\r
+ *\r
+ * [sd] {number} Significant digits. Integer, 0 to MAX_DIGITS inclusive.\r
+ *\r
+ */\r
+ function random(sd) {\r
+ var d, e, k, n,\r
+ i = 0,\r
+ r = new this(1),\r
+ rd = [];\r
+\r
+ if (sd === void 0) sd = this.precision;\r
+ else checkInt32(sd, 1, MAX_DIGITS);\r
+\r
+ k = Math.ceil(sd / LOG_BASE);\r
+\r
+ if (!this.crypto) {\r
+ for (; i < k;) rd[i++] = Math.random() * 1e7 | 0;\r
+\r
+ // Browsers supporting crypto.getRandomValues.\r
+ } else if (crypto.getRandomValues) {\r
+ d = crypto.getRandomValues(new Uint32Array(k));\r
+\r
+ for (; i < k;) {\r
+ n = d[i];\r
+\r
+ // 0 <= n < 4294967296\r
+ // Probability n >= 4.29e9, is 4967296 / 4294967296 = 0.00116 (1 in 865).\r
+ if (n >= 4.29e9) {\r
+ d[i] = crypto.getRandomValues(new Uint32Array(1))[0];\r
+ } else {\r
+\r
+ // 0 <= n <= 4289999999\r
+ // 0 <= (n % 1e7) <= 9999999\r
+ rd[i++] = n % 1e7;\r
+ }\r
+ }\r
+\r
+ // Node.js supporting crypto.randomBytes.\r
+ } else if (crypto.randomBytes) {\r
+\r
+ // buffer\r
+ d = crypto.randomBytes(k *= 4);\r
+\r
+ for (; i < k;) {\r
+\r
+ // 0 <= n < 2147483648\r
+ n = d[i] + (d[i + 1] << 8) + (d[i + 2] << 16) + ((d[i + 3] & 0x7f) << 24);\r
+\r
+ // Probability n >= 2.14e9, is 7483648 / 2147483648 = 0.0035 (1 in 286).\r
+ if (n >= 2.14e9) {\r
+ crypto.randomBytes(4).copy(d, i);\r
+ } else {\r
+\r
+ // 0 <= n <= 2139999999\r
+ // 0 <= (n % 1e7) <= 9999999\r
+ rd.push(n % 1e7);\r
+ i += 4;\r
+ }\r
+ }\r
+\r
+ i = k / 4;\r
+ } else {\r
+ throw Error(cryptoUnavailable);\r
+ }\r
+\r
+ k = rd[--i];\r
+ sd %= LOG_BASE;\r
+\r
+ // Convert trailing digits to zeros according to sd.\r
+ if (k && sd) {\r
+ n = mathpow(10, LOG_BASE - sd);\r
+ rd[i] = (k / n | 0) * n;\r
+ }\r
+\r
+ // Remove trailing words which are zero.\r
+ for (; rd[i] === 0; i--) rd.pop();\r
+\r
+ // Zero?\r
+ if (i < 0) {\r
+ e = 0;\r
+ rd = [0];\r
+ } else {\r
+ e = -1;\r
+\r
+ // Remove leading words which are zero and adjust exponent accordingly.\r
+ for (; rd[0] === 0; e -= LOG_BASE) rd.shift();\r
+\r
+ // Count the digits of the first word of rd to determine leading zeros.\r
+ for (k = 1, n = rd[0]; n >= 10; n /= 10) k++;\r
+\r
+ // Adjust the exponent for leading zeros of the first word of rd.\r
+ if (k < LOG_BASE) e -= LOG_BASE - k;\r
+ }\r
+\r
+ r.e = e;\r
+ r.d = rd;\r
+\r
+ return r;\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is `x` rounded to an integer using rounding mode `rounding`.\r
+ *\r
+ * To emulate `Math.round`, set rounding to 7 (ROUND_HALF_CEIL).\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function round(x) {\r
+ return finalise(x = new this(x), x.e + 1, this.rounding);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return\r
+ * 1 if x > 0,\r
+ * -1 if x < 0,\r
+ * 0 if x is 0,\r
+ * -0 if x is -0,\r
+ * NaN otherwise\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function sign(x) {\r
+ x = new this(x);\r
+ return x.d ? (x.d[0] ? x.s : 0 * x.s) : x.s || NaN;\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the sine of `x`, rounded to `precision` significant digits\r
+ * using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal} A value in radians.\r
+ *\r
+ */\r
+ function sin(x) {\r
+ return new this(x).sin();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the hyperbolic sine of `x`, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal} A value in radians.\r
+ *\r
+ */\r
+ function sinh(x) {\r
+ return new this(x).sinh();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the square root of `x`, rounded to `precision` significant\r
+ * digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function sqrt(x) {\r
+ return new this(x).sqrt();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is `x` minus `y`, rounded to `precision` significant digits\r
+ * using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ * y {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function sub(x, y) {\r
+ return new this(x).sub(y);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the sum of the arguments, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ * Only the result is rounded, not the intermediate calculations.\r
+ *\r
+ * arguments {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function sum() {\r
+ var i = 0,\r
+ args = arguments,\r
+ x = new this(args[i]);\r
+\r
+ external = false;\r
+ for (; x.s && ++i < args.length;) x = x.plus(args[i]);\r
+ external = true;\r
+\r
+ return finalise(x, this.precision, this.rounding);\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the tangent of `x`, rounded to `precision` significant\r
+ * digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal} A value in radians.\r
+ *\r
+ */\r
+ function tan(x) {\r
+ return new this(x).tan();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is the hyperbolic tangent of `x`, rounded to `precision`\r
+ * significant digits using rounding mode `rounding`.\r
+ *\r
+ * x {number|string|bigint|Decimal} A value in radians.\r
+ *\r
+ */\r
+ function tanh(x) {\r
+ return new this(x).tanh();\r
+ }\r
+\r
+\r
+ /*\r
+ * Return a new Decimal whose value is `x` truncated to an integer.\r
+ *\r
+ * x {number|string|bigint|Decimal}\r
+ *\r
+ */\r
+ function trunc(x) {\r
+ return finalise(x = new this(x), x.e + 1, 1);\r
+ }\r
+\r
+\r
+ // Create and configure initial Decimal constructor.\r
+ Decimal = clone(DEFAULTS);\r
+ Decimal.prototype.constructor = Decimal;\r
+ Decimal['default'] = Decimal.Decimal = Decimal;\r
+\r
+ // Create the internal constants from their string values.\r
+ LN10 = new Decimal(LN10);\r
+ PI = new Decimal(PI);\r
+\r
+\r
+ // Export.\r
+\r
+\r
+ // AMD.\r
+ if (typeof define == 'function' && define.amd) {\r
+ define(function () {\r
+ return Decimal;\r
+ });\r
+\r
+ // Node and other environments that support module.exports.\r
+ } else if (typeof module != 'undefined' && module.exports) {\r
+ if (typeof Symbol == 'function' && typeof Symbol.iterator == 'symbol') {\r
+ P[Symbol['for']('nodejs.util.inspect.custom')] = P.toString;\r
+ P[Symbol.toStringTag] = 'Decimal';\r
+ }\r
+\r
+ module.exports = Decimal;\r
+\r
+ // Browser.\r
+ } else {\r
+ if (!globalScope) {\r
+ globalScope = typeof self != 'undefined' && self && self.self == self ? self : window;\r
+ }\r
+\r
+ noConflict = globalScope.Decimal;\r
+ Decimal.noConflict = function () {\r
+ globalScope.Decimal = noConflict;\r
+ return Decimal;\r
+ };\r
+\r
+ globalScope.Decimal = Decimal;\r
+ }\r
+})(this);\r
--- /dev/null
+/*
+ * Author : Martin Donk
+ * Website : http://www.nerdamer.com
+ * Email : martin.r.donk@gmail.com
+ * License : MIT
+ * Source : https://github.com/jiggzson/nerdamer
+ */
+
+// Type imports for JSDoc ======================================================
+// These typedefs provide type aliases for the interfaces defined in index.d.ts.
+// They enable proper type checking when working with the classes defined in this file.
+//
+// Usage patterns:
+// - For return types: @returns {NerdamerSymbolType}
+// - For parameters: @param {NerdamerSymbolType} symbol
+// - For variable declarations: /** @type {NerdamerSymbolType} */
+//
+// Note: When casting local class instances to interface types, use the pattern:
+// /** @type {InterfaceType} */ (/** @type {unknown} */ (localInstance))
+// This is needed because TypeScript sees local classes and interfaces as separate types.
+
+/**
+ * Core type aliases from index.d.ts
+ *
+ * @typedef {import('./index').NerdamerCore.NerdamerSymbol} NerdamerSymbolType
+ *
+ * @typedef {import('./index').NerdamerCore.Frac} FracType
+ *
+ * @typedef {import('./index').NerdamerCore.Vector} VectorType
+ *
+ * @typedef {import('./index').NerdamerCore.Matrix} MatrixType
+ *
+ * @typedef {NerdamerSymbolType | VectorType | MatrixType} ParseResultType Union type for parse results
+ *
+ * @typedef {import('./index').NerdamerCore.Parser} ParserType
+ *
+ * @typedef {import('./index').NerdamerCore.Collection} CollectionType
+ *
+ * @typedef {import('./index').NerdamerCore.Settings} SettingsType
+ *
+ * @typedef {import('./index').NerdamerExpression} ExpressionType
+ *
+ * @typedef {typeof import('./index')} NerdamerType
+ *
+ * @typedef {import('./index').NerdamerCore.Utils} UtilsInterface
+ *
+ * @typedef {import('./index').NerdamerCore.Math2} Math2Interface
+ *
+ * @typedef {import('./index').NerdamerCore.Core} CoreType
+ *
+ * @typedef {import('./index').ExpressionParam} ExpressionParam
+ *
+ * @typedef {import('./index').ArithmeticOperand} ArithmeticOperand
+ *
+ * @typedef {import('./index').ExpandOptions} ExpandOptions
+ *
+ * @typedef {import('./index').NerdamerCore.DecomposeResultObject} DecomposeResultType Constructor types
+ *
+ * @typedef {import('./index').NerdamerCore.FracConstructor} FracConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.SymbolConstructor} SymbolConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.VectorConstructor} VectorConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.AlgebraModule} AlgebraModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.Polynomial} Polynomial
+ *
+ * @typedef {import('./index').NerdamerCore.Factors} Factors
+ *
+ * @typedef {import('./index').NerdamerCore.FactorsLike} FactorsLike
+ *
+ * @typedef {import('./index').NerdamerCore.MVTerm} MVTerm
+ *
+ * @typedef {import('./index').NerdamerCore.FactorSubModule} FactorInterface
+ *
+ * @typedef {import('./index').NerdamerCore.SimplifySubModule} SimplifyInterface
+ *
+ * @typedef {import('./index').NerdamerCore.PartFracSubModule} PartFracInterface
+ *
+ * @typedef {new () => Factors} FactorsConstructor
+ */
+
+// Check if nerdamer exists globally (browser) or needs to be required (Node.js)
+let nerdamer = typeof globalThis !== 'undefined' && globalThis.nerdamer ? globalThis.nerdamer : undefined;
+if (typeof module !== 'undefined' && nerdamer === undefined) {
+ nerdamer = require('./nerdamer.core.js');
+ require('./Calculus.js');
+}
+
+(function initAlgebraModule() {
+ /* Shortcuts*/
+ /** @type {CoreType} */
+ const core = nerdamer.getCore();
+ /** @type {ParserType} */
+ const _ = core.PARSER;
+ const { N, P, S, EX, FN, PL, CP, CB } = core.groups;
+ const { keys, even, variables, format, round, isInt } = core.Utils;
+ const { Frac, NerdamerSymbol, Vector: _Vector, Expression: _Expression } = core;
+ const { CONST_HASH } = core.Settings;
+ /** @type {Record<string, Function>} */
+ const math = core.Utils.importFunctions();
+ const _evaluate = core.Utils.evaluate;
+ //* ************** CLASSES ***************//
+ /**
+ * Converts a symbol into an equivalent polynomial arrays of the form [[coefficient_1, power_1],[coefficient_2,
+ * power_2], ... ] Univariate polymials only.
+ *
+ * @class
+ * @this {Polynomial}
+ * @param {NerdamerSymbolType | number | string} [symbol]
+ * @param {string} [variable] The variable name of the polynomial
+ * @param {number} [order]
+ */
+ function Polynomial(symbol, variable, order) {
+ /** @type {FracType[]} */
+ this.coeffs = [];
+ /** @type {string} */
+ this.variable = '';
+
+ if (core.Utils.isSymbol(symbol)) {
+ this.parse(/** @type {NerdamerSymbolType} */ (symbol));
+ this.variable ||= variable || '';
+ } else if (typeof symbol === 'number' && !isNaN(symbol)) {
+ order ||= 0;
+ if (variable === undefined) {
+ throw new core.exceptions.InvalidVariableNameError(
+ 'Polynomial expects a variable name when creating using order'
+ );
+ }
+ this.coeffs = [];
+ this.coeffs[order] = new Frac(symbol);
+ this.fill(symbol);
+ } else if (typeof symbol === 'string') {
+ this.parse(_.parse(symbol));
+ }
+ }
+ /**
+ * Creates a Polynomial given an array of coefficients
+ *
+ * @param {FracType[]} arr
+ * @param {string} variable
+ * @returns {Polynomial}
+ */
+ Polynomial.fromArray = function fromArray(arr, variable) {
+ if (typeof variable === 'undefined') {
+ throw new core.exceptions.InvalidVariableNameError(
+ 'A variable name must be specified when creating polynomial from array'
+ );
+ }
+ /** @type {Polynomial} */
+ const p = new Polynomial();
+ p.coeffs = arr;
+ p.variable = variable;
+ return p;
+ };
+
+ /**
+ * @param {number} c1
+ * @param {number} c2
+ * @param {number} n
+ * @param {number} base
+ * @param {number} p
+ * @param {string} variable
+ * @returns {Polynomial | null}
+ */
+ Polynomial.fit = function fit(c1, c2, n, base, p, variable) {
+ // After having looped through and mod 10 the number to get the matching factor
+ const terms = new Array(p + 1);
+ let t = n - c2;
+ terms[0] = c2; // The constants is assumed to be correct
+ // constant for x^p is also assumed know so add
+ terms[p] = c1;
+ t -= c1 * base ** p;
+ // Start fitting
+ for (let i = p - 1; i > 0; i--) {
+ const b = base ** i; // We want as many wholes as possible
+ const q = t / b;
+ const sign = Math.sign(q);
+ const c = sign * Math.floor(Math.abs(q));
+ t -= c * b;
+ terms[i] = c;
+ }
+ if (t !== 0) {
+ return null;
+ }
+ for (let i = 0; i < terms.length; i++) {
+ terms[i] = new Frac(terms[i]);
+ }
+
+ return Polynomial.fromArray(terms, variable);
+ };
+
+ Polynomial.prototype = {
+ /**
+ * Converts NerdamerSymbol to Polynomial
+ *
+ * @this {Polynomial}
+ * @param {NerdamerSymbolType} symbol
+ * @param {FracType[]} [c] - A collector array
+ * @returns {void}
+ */
+ parse(symbol, c) {
+ this.variable = variables(symbol)[0];
+ if (!symbol.isPoly()) {
+ throw new core.exceptions.NerdamerTypeError(`Polynomial Expected! Received ${core.Utils.text(symbol)}`);
+ }
+ c ||= [];
+ if (!(/** @type {FracType} */ (symbol.power).absEquals(1))) {
+ symbol = /** @type {NerdamerSymbolType} */ (_.expand(symbol));
+ }
+
+ if (symbol.group === core.groups.N) {
+ c[0] = symbol.multiplier;
+ } else if (symbol.group === core.groups.S) {
+ c[Number(/** @type {FracType} */ (symbol.power).toDecimal())] = symbol.multiplier;
+ } else {
+ for (const x in symbol.symbols) {
+ if (!Object.hasOwn(symbol.symbols, x)) {
+ continue;
+ }
+ const sub = symbol.symbols[x];
+ const p = sub.power;
+ if (core.Utils.isSymbol(p)) {
+ throw new core.exceptions.NerdamerTypeError('power cannot be a NerdamerSymbol');
+ }
+
+ const pNum = sub.group === N ? 0 : Number(/** @type {FracType} */ (p).toDecimal());
+ if (sub.symbols) {
+ this.parse(sub, c);
+ } else {
+ c[pNum] = sub.multiplier;
+ }
+ }
+ }
+
+ this.coeffs = c;
+
+ this.fill();
+ },
+ /**
+ * Fills in the holes in a polynomial with zeroes
+ *
+ * @this {Polynomial}
+ * @param {number} [x] - The number to fill the holes with
+ * @returns {Polynomial}
+ */
+ fill(x) {
+ x = Number(x) || 0;
+ const l = this.coeffs.length;
+ for (let i = 0; i < l; i++) {
+ if (this.coeffs[i] === undefined) {
+ this.coeffs[i] = new Frac(x);
+ }
+ }
+ return this;
+ },
+ /**
+ * Removes higher order zeros or a specific coefficient
+ *
+ * @this {Polynomial}
+ * @returns {Polynomial}
+ */
+ trim() {
+ let l = this.coeffs.length;
+ while (l--) {
+ const c = this.coeffs[l];
+ const equalsZero = c.equals(0);
+ if (c && equalsZero) {
+ if (l === 0) {
+ break;
+ }
+ this.coeffs.pop();
+ } else {
+ break;
+ }
+ }
+
+ return this;
+ },
+ /**
+ * Returns polynomial mod p **currently fails**
+ *
+ * @this {Polynomial}
+ * @param {number} p
+ * @returns {Polynomial}
+ */
+ modP(p) {
+ const l = this.coeffs.length;
+ for (let i = 0; i < l; i++) {
+ let c = this.coeffs[i];
+ let j;
+ if (c.lessThan(0)) {
+ // Go borrow
+ /** @type {FracType | undefined} */
+ let b; // A coefficient > 0
+ for (j = i; j < l; j++) {
+ // Starting from where we left off
+ if (this.coeffs[j].greaterThan(0)) {
+ b = this.coeffs[j];
+ break;
+ }
+ }
+
+ if (b) {
+ // If such a coefficient exists
+ for (; j > i; j--) {
+ // Go down the line and adjust using p
+ this.coeffs[j] = this.coeffs[j].subtract(new Frac(1));
+ this.coeffs[j - 1] = this.coeffs[j - 1].add(new Frac(p));
+ }
+ c = this.coeffs[i]; // Reset c
+ }
+ }
+
+ const d = c.mod(new Frac(p));
+ const w = c.subtract(d).divide(new Frac(p));
+ if (!w.equals(0)) {
+ const upOne = i + 1;
+ let next = this.coeffs[upOne] || new Frac(0);
+ next = next.add(w);
+ this.coeffs[upOne] = next;
+ this.coeffs[i] = d;
+ }
+ }
+
+ return this;
+ },
+ /**
+ * Adds together 2 polynomials
+ *
+ * @this {Polynomial}
+ * @param {Polynomial} poly
+ * @returns {Polynomial}
+ */
+ add(poly) {
+ const l = Math.max(this.coeffs.length, poly.coeffs.length);
+ for (let i = 0; i < l; i++) {
+ const a = this.coeffs[i] || new Frac(0);
+ const b = poly.coeffs[i] || new Frac(0);
+ this.coeffs[i] = a.add(b);
+ }
+ return this;
+ },
+ /**
+ * Subtracts 2 polynomials
+ *
+ * @this {Polynomial}
+ * @param {Polynomial} poly
+ * @returns {Polynomial}
+ */
+ subtract(poly) {
+ const l = Math.max(this.coeffs.length, poly.coeffs.length);
+ for (let i = 0; i < l; i++) {
+ const a = this.coeffs[i] || new Frac(0);
+ const b = poly.coeffs[i] || new Frac(0);
+ this.coeffs[i] = a.subtract(b);
+ }
+ return this;
+ },
+ /**
+ * Divides two polynomials
+ *
+ * @this {Polynomial}
+ * @param {Polynomial} poly
+ * @returns {[Polynomial, Polynomial]}
+ */
+ divide(poly) {
+ const { variable } = this;
+ /** @type {FracType[]} */
+ const dividend = /** @type {FracType[]} */ (core.Utils.arrayClone(this.coeffs));
+ /** @type {FracType[]} */
+ const divisor = /** @type {FracType[]} */ (core.Utils.arrayClone(poly.coeffs));
+ const n = dividend.length;
+ const mp = divisor.length - 1;
+ /** @type {FracType[]} */
+ const quotient = [];
+
+ // Loop through the dividend
+ for (let i = 0; i < n; i++) {
+ const p = n - (i + 1);
+ // Get the difference of the powers
+ const d = p - mp;
+ // Get the quotient of the coefficients
+ const q = dividend[p].divide(divisor[mp]);
+
+ if (d < 0) {
+ break;
+ } // The divisor is not greater than the dividend
+ // place it in the quotient
+ quotient[d] = q;
+
+ for (let j = 0; j <= mp; j++) {
+ // Reduce the dividend
+ dividend[j + d] = dividend[j + d].subtract(divisor[j].multiply(q));
+ }
+ }
+
+ // Clean up
+ const p1 = Polynomial.fromArray(dividend, variable || 'x').trim(); // Pass in x for safety
+ const p2 = Polynomial.fromArray(quotient, variable || 'x');
+ return [p2, p1];
+ },
+ /**
+ * Multiplies two polynomials
+ *
+ * @this {Polynomial}
+ * @param {Polynomial} poly
+ * @returns {Polynomial}
+ */
+ multiply(poly) {
+ const l1 = this.coeffs.length;
+ const l2 = poly.coeffs.length;
+ /** @type {FracType[]} */
+ const c = []; // Array to be returned
+ for (let i = 0; i < l1; i++) {
+ const x1 = this.coeffs[i];
+ for (let j = 0; j < l2; j++) {
+ const k = i + j; // Add the powers together
+ const x2 = poly.coeffs[j];
+ const e = c[k] || new Frac(0); // Get the existing term from the new array
+ c[k] = e.add(x1.multiply(x2)); // Multiply the coefficients and add to new polynomial array
+ }
+ }
+ this.coeffs = c;
+ return this;
+ },
+ /**
+ * Checks if a polynomial is zero
+ *
+ * @this {Polynomial}
+ * @returns {boolean}
+ */
+ isZero() {
+ const l = this.coeffs.length;
+ for (let i = 0; i < l; i++) {
+ const e = this.coeffs[i];
+ if (!e.equals(0)) {
+ return false;
+ }
+ }
+ return true;
+ },
+ /**
+ * Substitutes in a number n into the polynomial p(n)
+ *
+ * @this {Polynomial}
+ * @param {number} n
+ * @returns {FracType}
+ */
+ sub(n) {
+ let sum = new Frac(0);
+ const l = this.coeffs.length;
+ for (let i = 0; i < l; i++) {
+ const t = this.coeffs[i];
+ if (!t.equals(0)) {
+ sum = sum.add(t.multiply(new Frac(n ** i)));
+ }
+ }
+ return sum;
+ },
+ /**
+ * Returns a clone of the polynomial
+ *
+ * @this {Polynomial}
+ * @returns {Polynomial}
+ */
+ clone() {
+ /** @type {Polynomial} */
+ const p = new Polynomial();
+ p.coeffs = this.coeffs.slice();
+ p.variable = this.variable;
+ return p;
+ },
+ /**
+ * Gets the degree of the polynomial
+ *
+ * @this {Polynomial}
+ * @returns {number}
+ */
+ deg() {
+ this.trim();
+ return this.coeffs.length - 1;
+ },
+ /**
+ * Returns a lead coefficient
+ *
+ * @this {Polynomial}
+ * @returns {FracType}
+ */
+ lc() {
+ return this.coeffs[this.deg()].clone();
+ },
+ /**
+ * Converts polynomial into a monic polynomial
+ *
+ * @this {Polynomial}
+ * @returns {Polynomial}
+ */
+ monic() {
+ const lc = this.lc();
+ const l = this.coeffs.length;
+ for (let i = 0; i < l; i++) {
+ this.coeffs[i] = this.coeffs[i].divide(lc);
+ }
+ return this;
+ },
+ /**
+ * Returns the GCD of two polynomials
+ *
+ * @this {Polynomial}
+ * @param {Polynomial} poly
+ * @returns {Polynomial}
+ */
+ gcd(poly) {
+ // Get the maximum power of each
+ const mp1 = this.coeffs.length - 1;
+ const mp2 = poly.coeffs.length - 1;
+ /** @type {[Polynomial, Polynomial]} */
+ let T;
+ // Swap so we always have the greater power first
+ if (mp1 < mp2) {
+ return poly.gcd(this);
+ }
+ /** @type {Polynomial} */
+ let a = this;
+
+ while (!poly.isZero()) {
+ const t = poly.clone();
+ a = a.clone();
+ T = a.divide(t);
+ poly = T[1];
+ a = t;
+ }
+
+ const gcd = core.Math2.QGCD.apply(null, a.coeffs);
+ if (!gcd.equals(1)) {
+ const l = a.coeffs.length;
+ for (let i = 0; i < l; i++) {
+ a.coeffs[i] = a.coeffs[i].divide(gcd);
+ }
+ }
+ return a;
+ },
+ /**
+ * Differentiates the polynomial
+ *
+ * @this {Polynomial}
+ * @returns {Polynomial}
+ */
+ diff() {
+ /** @type {FracType[]} */
+ const newArray = [];
+ const l = this.coeffs.length;
+ for (let i = 1; i < l; i++) {
+ newArray.push(this.coeffs[i].multiply(new Frac(i)));
+ }
+ this.coeffs = newArray;
+ return this;
+ },
+ /**
+ * Integrates the polynomial
+ *
+ * @this {Polynomial}
+ * @returns {Polynomial}
+ */
+ integrate() {
+ /** @type {FracType[]} */
+ const newArray = [new Frac(0)];
+ const l = this.coeffs.length;
+ for (let i = 0; i < l; i++) {
+ const c = new Frac(i + 1);
+ newArray[i + 1] = this.coeffs[i].divide(c);
+ }
+ this.coeffs = newArray;
+ return this;
+ },
+ /**
+ * Returns the Greatest common factor of the polynomial
+ *
+ * @this {Polynomial}
+ * @param {boolean} [toPolynomial] - True if a polynomial is wanted
+ * @returns {[FracType, number] | Polynomial}
+ */
+ gcf(toPolynomial) {
+ // Get the first nozero coefficient and returns its power
+ /**
+ * @param {FracType[]} a
+ * @returns {number | undefined}
+ */
+ const fnz = function (a) {
+ for (let i = 0; i < a.length; i++) {
+ if (!a[i].equals(0)) {
+ return i;
+ }
+ }
+ return undefined;
+ };
+ /** @type {FracType[]} */
+ const ca = [];
+ for (let i = 0; i < this.coeffs.length; i++) {
+ const c = this.coeffs[i];
+ if (!c.equals(0) && ca.indexOf(c) === -1) {
+ ca.push(c);
+ }
+ }
+ /** @type {[FracType, number] | Polynomial} */
+ let p = [core.Math2.QGCD.apply(undefined, ca), fnz(this.coeffs) || 0];
+
+ if (toPolynomial) {
+ const parr = [];
+ parr[p[1] - 1] = p[0];
+ p = Polynomial.fromArray(parr, this.variable).fill();
+ }
+
+ return p;
+ },
+ /**
+ * Raises a polynomial P to a power p -> P^p. e.g. (x+1)^2
+ *
+ * @this {Polynomial}
+ * @param {boolean} [inclImg] - Include imaginary numbers
+ * @returns {number[]}
+ */
+ quad(inclImg) {
+ /** @type {number[]} */
+ const roots = [];
+ if (this.coeffs.length > 3) {
+ throw new Error(`Cannot calculate quadratic order of ${this.coeffs.length - 1}`);
+ }
+ if (this.coeffs.length === 0) {
+ throw new Error('Polynomial array has no terms');
+ }
+ const a = this.coeffs[2] ? Number(this.coeffs[2].toDecimal()) : 0;
+ const b = this.coeffs[1] ? Number(this.coeffs[1].toDecimal()) : 0;
+ const c = Number(this.coeffs[0].toDecimal());
+ const dsc = b * b - 4 * a * c;
+ if (dsc < 0 && !inclImg) {
+ return roots;
+ }
+ roots[0] = (-b + Math.sqrt(dsc)) / (2 * a);
+ roots[1] = (-b - Math.sqrt(dsc)) / (2 * a);
+
+ return roots;
+ },
+ /**
+ * Makes polynomial square free
+ *
+ * @this {Polynomial}
+ * @returns {[Polynomial, Polynomial, number]}
+ */
+ squareFree() {
+ const a = this.clone();
+ let i = 1;
+ const b = a.clone().diff();
+ let c = a.clone().gcd(b);
+ let w = a.divide(c)[0];
+ let output = Polynomial.fromArray([new Frac(1)], a.variable);
+ while (!c.equalsNumber(1)) {
+ const y = w.gcd(c);
+ let z = w.divide(y)[0];
+ // One of the factors may have shown up since it's square but smaller than the
+ // one where finding
+ if (!z.equalsNumber(1) && i > 1) {
+ const t = z.clone();
+ for (let j = 1; j < i; j++) {
+ t.multiply(z.clone());
+ }
+ z = t;
+ }
+ output = output.multiply(z);
+ i++;
+ w = y;
+ c = c.divide(y)[0];
+ }
+
+ return [output, w, i];
+ },
+ /**
+ * Converts polynomial to NerdamerSymbol
+ *
+ * @this {Polynomial}
+ * @returns {NerdamerSymbolType}
+ */
+ toSymbol() {
+ const l = this.coeffs.length;
+ const { variable } = this;
+ if (l === 0) {
+ return new NerdamerSymbol(0);
+ }
+
+ // Polynomials must have a variable
+ if (!variable) {
+ throw new core.exceptions.NerdamerTypeError(
+ 'Polynomial.toSymbol requires a variable. Constants should not be converted to Polynomial.'
+ );
+ }
+
+ const terms = [];
+
+ for (let i = 0; i < l; i++) {
+ const e = this.coeffs[i];
+ if (!e.equals(0)) {
+ terms.push(`${e}*${variable}^${i}`);
+ }
+ }
+ if (terms.length === 0) {
+ return new NerdamerSymbol(0);
+ }
+ return _.parse(terms.join('+'));
+ },
+ /**
+ * Checks if polynomial is equal to a number
+ *
+ * @this {Polynomial}
+ * @param {number} x
+ * @returns {boolean}
+ */
+ equalsNumber(x) {
+ this.trim();
+ return this.coeffs.length === 1 && this.coeffs[0].toDecimal() === String(x);
+ },
+ /**
+ * @this {Polynomial}
+ * @returns {string}
+ */
+ toString() {
+ return this.toSymbol().toString();
+ },
+ };
+
+ /**
+ * # TODO
+ *
+ * # THIS METHOD HAS A NASTY HIDDEN BUG. IT HAS INCONSISTENT RETURN TYPES PRIMARILY DUE TO
+ *
+ * WRONG ASSUMPTIONS AT THE BEGINNING. THE ASSUMPTION WAS THAT COEFFS WERE ALWAYS GOING BE NUMBERS NOT TAKING INTO
+ * ACCOUNT THAT IMAGINARY NUMBERS. FIXING THIS BREAKS WAY TOO MANY TESTS AT THEM MOMENT WHICH I DON'T HAVE TO FIX
+ *
+ * If the symbols is of group PL or CP it will return the multipliers of each symbol as these are polynomial
+ * coefficients. CB symbols are glued together by multiplication so the symbol multiplier carries the coefficients
+ * for all contained symbols. For S it just returns it's own multiplier. This function doesn't care if it's a
+ * polynomial or not
+ *
+ * @this {NerdamerSymbolType}
+ * @param {Array} [c] The coefficient array
+ * @param {boolean} [withOrder]
+ * @returns {Array}
+ */
+ NerdamerSymbol.prototype.coeffs = function coeffs(c, withOrder) {
+ if (withOrder && !this.isPoly(true)) {
+ _.error('Polynomial expected when requesting coefficients with order');
+ }
+ c ||= [];
+ const s = this.clone().distributeMultiplier();
+ if (s.isComposite()) {
+ for (const x in s.symbols) {
+ if (!Object.hasOwn(s.symbols, x)) {
+ continue;
+ }
+ const sub = s.symbols[x];
+ if (sub.isComposite()) {
+ sub.clone().distributeMultiplier().coeffs(c, withOrder);
+ } else if (withOrder) {
+ c[sub.isConstant() ? 0 : Number(/** @type {FracType} */ (sub.power).toDecimal())] = sub.multiplier;
+ } else {
+ c.push(sub.multiplier);
+ }
+ }
+ } else if (withOrder) {
+ c[s.isConstant(true) ? 0 : Number(/** @type {FracType} */ (s.power).toDecimal())] = s.multiplier;
+ } else if (s.group === CB && s.isImaginary()) {
+ let m = new NerdamerSymbol(s.multiplier);
+ s.each(x => {
+ // Add the imaginary part
+ if (x.isConstant(true) || x.imaginary) {
+ m = /** @type {NerdamerSymbolType} */ (_.multiply(m, x));
+ }
+ });
+ c.push(m);
+ } else {
+ c.push(s.multiplier);
+ }
+ // Fill the holes
+ if (withOrder) {
+ for (let i = 0; i < c.length; i++) {
+ if (c[i] === undefined) {
+ c[i] = new NerdamerSymbol(0);
+ }
+ }
+ }
+ return c;
+ };
+ /**
+ * @this {NerdamerSymbolType}
+ * @param {Record<string, number> & { length: number }} map
+ * @returns {MVTerm[]}
+ */
+ NerdamerSymbol.prototype.tBase = function tBase(map) {
+ if (typeof map === 'undefined') {
+ throw new Error('NerdamerSymbol.tBase requires a map object!');
+ }
+ /** @type {MVTerm[]} */
+ const terms = [];
+ const symbols = /** @type {NerdamerSymbolType[]} */ (this.collectSymbols(null, null, null, true));
+ const l = symbols.length;
+ for (let i = 0; i < l; i++) {
+ const symbol = symbols[i];
+ const g = symbol.group;
+ /** @type {MVTerm} */
+ const nterm = new MVTerm(symbol.multiplier, [], map);
+ if (g === CB) {
+ for (const x in symbol.symbols) {
+ if (!Object.hasOwn(symbol.symbols, x)) {
+ continue;
+ }
+ const sym = symbol.symbols[x];
+ nterm.terms[map[x]] = /** @type {FracType} */ (sym.power);
+ }
+ } else {
+ nterm.terms[map[symbol.value]] = /** @type {FracType} */ (symbol.power);
+ }
+
+ terms.push(nterm.fill());
+ nterm.updateCount();
+ }
+ return terms;
+ };
+ /**
+ * @this {NerdamerSymbolType}
+ * @param {string} x
+ * @returns {string}
+ */
+ NerdamerSymbol.prototype.altVar = function altVar(x) {
+ const m = this.multiplier.toString();
+ const p = this.power.toString();
+ return (m === '1' ? '' : `${m}*`) + x + (p === '1' ? '' : `^${p}`);
+ };
+ /**
+ * Checks to see if the symbols contain the same variables
+ *
+ * @this {NerdamerSymbolType}
+ * @param {NerdamerSymbolType} symbol
+ * @returns {boolean}
+ */
+ NerdamerSymbol.prototype.sameVars = function sameVars(symbol) {
+ if (!(this.symbols || this.group === symbol.group)) {
+ return false;
+ }
+ for (const x in this.symbols) {
+ if (!Object.hasOwn(this.symbols, x)) {
+ continue;
+ }
+ const a = this.symbols[x];
+ const b = symbol.symbols[x];
+ if (!b) {
+ return false;
+ }
+ if (a.value !== b.value) {
+ return false;
+ }
+ }
+ return true;
+ };
+ /**
+ * Groups the terms in a symbol with respect to a variable For instance the symbol {a_b^2_x^2+a_b_x^2+x+6} returns
+ * [6,1,a_b+a_b^2]
+ *
+ * @this {NerdamerSymbolType}
+ * @param {string} x
+ * @returns {NerdamerSymbolType[]}
+ */
+ NerdamerSymbol.prototype.groupTerms = function groupTerms(x) {
+ x = String(x);
+ /** @type {DecomposeResultType | undefined} */
+ let f;
+ /** @type {number} */
+ let p;
+ /** @type {NerdamerSymbolType[] | undefined} */
+ let egrouped;
+ /** @type {NerdamerSymbolType[]} */
+ const grouped = [];
+ this.each(e => {
+ if (e.group === PL) {
+ egrouped = e.groupTerms(x);
+ for (let i = 0; i < egrouped.length; i++) {
+ const el = egrouped[i];
+ if (el) {
+ grouped[i] = el;
+ }
+ }
+ } else {
+ f = /** @type {DecomposeResultType} */ (core.Utils.decompose_fn(e, x, true));
+ p =
+ /** @type {NerdamerSymbolType} */ (f.x).value === x
+ ? Number(/** @type {NerdamerSymbolType} */ (f.x).power)
+ : 0;
+ // Check if there's an existing value
+ grouped[p] = /** @type {NerdamerSymbolType} */ (_.add(grouped[p] || new NerdamerSymbol(0), f.a));
+ }
+ });
+ return grouped;
+ };
+ /**
+ * Use this to collect Factors
+ *
+ * @this {NerdamerSymbolType}
+ * @returns {NerdamerSymbolType[]}
+ */
+ NerdamerSymbol.prototype.collectFactors = function collectFactors() {
+ /** @type {NerdamerSymbolType[]} */
+ const factors = [];
+ if (this.group === CB) {
+ this.each(x => {
+ factors.push(x.clone());
+ });
+ } else {
+ factors.push(this.clone());
+ }
+ return factors;
+ };
+ /**
+ * A container class for factors
+ *
+ * @class
+ * @this {Factors}
+ */
+ function Factors() {
+ /** @type {Record<string, NerdamerSymbolType>} */
+ this.factors = {};
+ /** @type {number} */
+ this.length = 0;
+ /** @type {((s: NerdamerSymbolType) => NerdamerSymbolType) | undefined} */
+ this.preAdd = undefined;
+ /** @type {number | string | undefined} */
+ this.pFactor = undefined;
+ }
+ /**
+ * @this {Factors}
+ * @returns {number}
+ */
+ Factors.prototype.getNumberSymbolics = function getNumberSymbolics() {
+ let n = 0;
+ this.each(x => {
+ if (!x.isConstant(true)) {
+ n++;
+ }
+ });
+ return n;
+ };
+ /**
+ * Adds the factors to the factor object
+ *
+ * @this {Factors}
+ * @param {NerdamerSymbolType} s
+ * @returns {Factors}
+ */
+ Factors.prototype.add = function add(s) {
+ if (s.equals(0)) {
+ return this;
+ } // Nothing to add
+
+ // we don't want to carry -1 as a factor. If a factor already exists,
+ // then add the minus one to that factor and return.
+ if (s.equals(-1) && this.length > 0) {
+ const fo = core.Utils.firstObject(this.factors, null, true);
+ const newObj = /** @type {NerdamerSymbolType} */ (
+ _.symfunction(core.Settings.PARENTHESIS, [fo.obj]).negate()
+ );
+ delete this.factors[fo.key];
+ this.add(newObj);
+ this.length--;
+ return this;
+ }
+
+ if (s.group === CB) {
+ const factors = this;
+ if (!s.multiplier.equals(1)) {
+ factors.add(new NerdamerSymbol(s.multiplier));
+ }
+ s.each(x => {
+ factors.add(x);
+ });
+ } else {
+ if (this.preAdd) // If a preAdd function was defined call it to do prep
+ {
+ s = this.preAdd(s);
+ }
+ if (this.pFactor) // If the symbol isn't linear add back the power
+ {
+ s = /** @type {NerdamerSymbolType} */ (_.pow(s, new NerdamerSymbol(this.pFactor)));
+ }
+
+ const isConstant = s.isConstant();
+ if (isConstant && s.equals(1)) {
+ return this;
+ } // Don't add 1
+ const v = isConstant ? s.value : s.text();
+ if (v in this.factors) {
+ this.factors[v] = /** @type {NerdamerSymbolType} */ (_.multiply(this.factors[v], s));
+ // Did the addition cancel out the existing factor? If so remove it and decrement the length
+ if (this.factors[v].equals(1)) {
+ delete this.factors[v];
+ this.length--;
+ }
+ } else {
+ this.factors[v] = s;
+ this.length++;
+ }
+ }
+ return this;
+ };
+ /**
+ * Converts the factor object to a NerdamerSymbol
+ *
+ * @this {Factors}
+ * @returns {NerdamerSymbolType}
+ */
+ Factors.prototype.toSymbol = function toSymbol() {
+ /** @type {NerdamerSymbolType} */
+ let factored = new NerdamerSymbol(1);
+ const factors = Object.values(this.factors).sort((a, b) => (a.group > b.group ? 1 : -1));
+
+ for (let i = 0, l = factors.length; i < l; i++) {
+ const f = factors[i];
+
+ // Don't wrap group S or FN
+ const factor =
+ f.power.equals(1) && f.fname !== '' /* Don't wrap it twice */
+ ? _.symfunction(core.Settings.PARENTHESIS, [f])
+ : f;
+
+ factored = /** @type {NerdamerSymbolType} */ (_.multiply(factored, factor));
+ }
+ if (factored.fname === '') {
+ factored = NerdamerSymbol.unwrapPARENS(factored);
+ }
+ return factored;
+ };
+ /**
+ * Merges 2 factor objects into one
+ *
+ * @this {Factors}
+ * @param {Record<string, NerdamerSymbolType>} o
+ * @returns {Factors}
+ */
+ Factors.prototype.merge = function merge(o) {
+ for (const x in o) {
+ if (x in this.factors) {
+ this.factors[x] = /** @type {NerdamerSymbolType} */ (_.multiply(this.factors[x], o[x]));
+ } else {
+ this.factors[x] = o[x];
+ }
+ }
+ return this;
+ };
+ /**
+ * The iterator for the factor object
+ *
+ * @this {Factors}
+ * @param {(factor: NerdamerSymbolType, key: string) => void} f - Callback
+ * @returns {Factors}
+ */
+ Factors.prototype.each = function each(f) {
+ for (const x in this.factors) {
+ if (!Object.hasOwn(this.factors, x)) {
+ continue;
+ }
+ let factor = this.factors[x];
+ if (factor.fname === core.Settings.PARENTHESIS && factor.isLinear()) {
+ factor = factor.args[0];
+ }
+ f.call(this, factor, x);
+ }
+ return this;
+ };
+ /**
+ * Return the number of factors contained in the factor object
+ *
+ * @this {Factors}
+ * @returns {number}
+ */
+ Factors.prototype.count = function count() {
+ return keys(this.factors).length;
+ };
+ /**
+ * Cleans up factors from -1
+ *
+ * @this {Factors}
+ * @returns {void}
+ */
+ Factors.prototype.clean = function clean() {
+ try {
+ const h = core.Settings.CONST_HASH;
+ if (this.factors[h].lessThan(0)) {
+ if (this.factors[h].equals(-1)) {
+ delete this.factors[h];
+ } else {
+ this.factors[h].negate();
+ }
+ this.each(x => {
+ x.negate();
+ });
+ }
+ } catch (e) {
+ if (/** @type {Error} */ (e).message === 'timeout') {
+ throw e;
+ }
+ }
+ };
+ /**
+ * @this {Factors}
+ * @returns {string}
+ */
+ Factors.prototype.toString = function toString() {
+ return this.toSymbol().toString();
+ };
+
+ /**
+ * A wrapper for performing multivariate division
+ *
+ * @class
+ * @this {MVTerm}
+ * @param {FracType} coeff
+ * @param {FracType[]} [terms]
+ * @param {Record<string, number> & { length: number }} [map]
+ */
+ function MVTerm(coeff, terms, map) {
+ /** @type {FracType[]} */
+ this.terms = terms || [];
+ /** @type {FracType} */
+ this.coeff = coeff;
+ /** @type {(Record<string, number> & { length: number }) | undefined} */
+ this.map = map; // Careful! all maps are the same object
+ /** @type {FracType} */
+ this.sum = new Frac(0);
+ /** @type {string | undefined} */
+ this.image = undefined;
+ /** @type {Record<number, string> | undefined} */
+ this.revMap = undefined;
+ /** @type {number | undefined} */
+ this.count = undefined;
+ }
+ /**
+ * @this {MVTerm}
+ * @returns {MVTerm}
+ */
+ MVTerm.prototype.updateCount = function updateCount() {
+ this.count ||= 0;
+ for (let i = 0; i < this.terms.length; i++) {
+ if (!this.terms[i].equals(0)) {
+ this.count++;
+ }
+ }
+ return this;
+ };
+ /**
+ * @this {MVTerm}
+ * @returns {string}
+ */
+ MVTerm.prototype.getVars = function getVars() {
+ /** @type {string[]} */
+ const vars = [];
+ for (let i = 0; i < this.terms.length; i++) {
+ const term = this.terms[i];
+ this.getRevMap();
+ if (!term.equals(0) && this.revMap) {
+ vars.push(this.revMap[i]);
+ }
+ }
+ return vars.join(' ');
+ };
+ /**
+ * @this {MVTerm}
+ * @returns {number}
+ */
+ MVTerm.prototype.len = function len() {
+ if (typeof this.count === 'undefined') {
+ this.updateCount();
+ }
+ return this.count || 0;
+ };
+ /**
+ * @this {MVTerm}
+ * @param {Record<number, string>} [revMap]
+ * @returns {NerdamerSymbolType}
+ */
+ MVTerm.prototype.toSymbol = function toSymbol(revMap) {
+ revMap ||= this.getRevMap();
+ /** @type {NerdamerSymbolType} */
+ let symbol = new NerdamerSymbol(this.coeff);
+ for (let i = 0; i < this.terms.length; i++) {
+ const v = revMap[i];
+ const t = this.terms[i];
+ if (t.equals(0) || v === CONST_HASH) {
+ continue;
+ }
+ const mapped = new NerdamerSymbol(v);
+ mapped.power = t;
+ symbol = /** @type {NerdamerSymbolType} */ (_.multiply(symbol, mapped));
+ }
+ return symbol;
+ };
+ /**
+ * @this {MVTerm}
+ * @returns {Record<number, string>}
+ */
+ MVTerm.prototype.getRevMap = function getRevMap() {
+ if (this.revMap) {
+ return this.revMap;
+ }
+ /** @type {Record<number, string>} */
+ const o = {};
+ if (this.map) {
+ for (const x in this.map) {
+ if (!Object.hasOwn(this.map, x)) {
+ continue;
+ }
+ o[this.map[x]] = x;
+ }
+ }
+ this.revMap = o;
+ return o;
+ };
+ /**
+ * @this {MVTerm}
+ * @returns {MVTerm}
+ */
+ MVTerm.prototype.generateImage = function generateImage() {
+ this.image = this.terms.join(' ');
+ return this;
+ };
+ /**
+ * @this {MVTerm}
+ * @returns {string}
+ */
+ MVTerm.prototype.getImg = function getImg() {
+ if (!this.image) {
+ this.generateImage();
+ }
+ return this.image || '';
+ };
+ /**
+ * @this {MVTerm}
+ * @returns {MVTerm}
+ */
+ MVTerm.prototype.fill = function fill() {
+ const l = this.map ? this.map.length : 0;
+ for (let i = 0; i < l; i++) {
+ if (typeof this.terms[i] === 'undefined') {
+ this.terms[i] = new Frac(0);
+ } else {
+ this.sum = this.sum.add(this.terms[i]);
+ }
+ }
+ return this;
+ };
+ /**
+ * @this {MVTerm}
+ * @param {MVTerm} mvterm
+ * @returns {MVTerm}
+ */
+ MVTerm.prototype.divide = function divide(mvterm) {
+ const c = this.coeff.divide(mvterm.coeff);
+ const l = this.terms.length;
+ /** @type {MVTerm} */
+ const newMvterm = new MVTerm(c, [], this.map);
+ for (let i = 0; i < l; i++) {
+ newMvterm.terms[i] = this.terms[i].subtract(mvterm.terms[i]);
+ newMvterm.sum = newMvterm.sum.add(newMvterm.terms[i]);
+ }
+ return newMvterm;
+ };
+ /**
+ * @this {MVTerm}
+ * @param {MVTerm} mvterm
+ * @returns {MVTerm}
+ */
+ MVTerm.prototype.multiply = function multiply(mvterm) {
+ const c = this.coeff.multiply(mvterm.coeff);
+ const l = this.terms.length;
+ /** @type {MVTerm} */
+ const newMvterm = new MVTerm(c, [], this.map);
+ for (let i = 0; i < l; i++) {
+ newMvterm.terms[i] = this.terms[i].add(mvterm.terms[i]);
+ newMvterm.sum = newMvterm.sum.add(newMvterm.terms[i]);
+ }
+ return newMvterm;
+ };
+ /**
+ * @this {MVTerm}
+ * @returns {boolean}
+ */
+ MVTerm.prototype.isZero = function isZero() {
+ return this.coeff.equals(0);
+ };
+ /**
+ * @this {MVTerm}
+ * @returns {string}
+ */
+ MVTerm.prototype.toString = function toString() {
+ return `{ coeff: ${this.coeff.toString()}, terms: [${this.terms.join(
+ ','
+ )}]: sum: ${this.sum.toString()}, count: ${this.count}}`;
+ };
+
+ /**
+ * @param {string[]} arr
+ * @returns {Record<string, number> & { length: number }}
+ */
+ core.Utils.toMapObj = function toMapObj(arr) {
+ let c = 0;
+ /** @type {Record<string, number> & { length: number }} */
+ const o = /** @type {Record<string, number> & { length: number }} */ ({ length: 0 });
+ for (let i = 0; i < arr.length; i++) {
+ const v = arr[i];
+ if (typeof o[v] === 'undefined') {
+ o[v] = c;
+ c++;
+ }
+ }
+ o.length = c;
+ return o;
+ };
+ /**
+ * @template T
+ * @param {T} v
+ * @param {number} n
+ * @param {new (v: T) => T} [Clss]
+ * @returns {T[]}
+ */
+ core.Utils.filledArray = function filledArray(v, n, Clss) {
+ const a = [];
+ while (n--) {
+ a[n] = Clss ? new Clss(v) : v;
+ }
+ return a;
+ };
+ /**
+ * @param {number[]} arr
+ * @returns {number}
+ */
+ core.Utils.arrSum = function arrSum(arr) {
+ let sum = 0;
+ const l = arr.length;
+ for (let i = 0; i < l; i++) {
+ sum += arr[i];
+ }
+ return sum;
+ };
+ /**
+ * Determines if 2 arrays have intersecting elements.
+ *
+ * @template T
+ * @param {T[]} a
+ * @param {T[]} b
+ * @returns {boolean} True if a and b have intersecting elements.
+ */
+ core.Utils.haveIntersection = function haveIntersection(a, b) {
+ if (b.length > a.length) {
+ [a, b] = [b, a]; // IndexOf to loop over shorter
+ }
+ return a.some(e => b.indexOf(e) > -1);
+ };
+ /**
+ * Substitutes out functions as variables so they can be used in regular algorithms
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {Record<string, string>} [map]
+ * @returns {string} The expression string
+ */
+ core.Utils.subFunctions = function subFunctions(symbol, map) {
+ map ||= {};
+ /** @type {string[]} */
+ const subbed = [];
+ const vars = new Set(variables(symbol));
+ symbol.each(x => {
+ if (x.group === FN || x.previousGroup === FN) {
+ // We need a new variable name so why not use one of the existing
+ const val = core.Utils.text(x, 'hash');
+ const tvar = map[val];
+ if (tvar) {
+ subbed.push(x.altVar(tvar));
+ } else {
+ // Generate a unique enough name
+ // GM make sure it's not the name of an existing variable
+ let i = 0;
+ let t;
+ do {
+ t = x.fname + keys(map).length + (i > 0 ? String(i) : '');
+ i++;
+ } while (vars.has(t));
+ map[val] = t;
+ subbed.push(x.altVar(t));
+ }
+ } else if (x.group === CB || x.group === PL || x.group === CP) {
+ subbed.push(core.Utils.subFunctions(x, map));
+ } else {
+ subbed.push(x.text());
+ }
+ });
+ if (symbol.group === CP || symbol.group === PL) {
+ return symbol.altVar(core.Utils.inBrackets(subbed.join('+')));
+ }
+ if (symbol.group === CB) {
+ return symbol.altVar(core.Utils.inBrackets(subbed.join('*')));
+ }
+ return symbol.text();
+ };
+ /**
+ * @param {Record<string, string>} map
+ * @returns {Record<string, NerdamerSymbolType>}
+ */
+ core.Utils.getFunctionsSubs = function getFunctionsSubs(map) {
+ /** @type {Record<string, NerdamerSymbolType>} */
+ const subs = {};
+ // Prepare substitutions
+ for (const x in map) {
+ if (!Object.hasOwn(map, x)) {
+ continue;
+ }
+ subs[map[x]] = _.parse(x);
+ }
+ return subs;
+ };
+
+ /** @type {AlgebraModuleType} */
+ const __ = (core.Algebra = {
+ version: '1.4.6',
+ /**
+ * @param {NerdamerSymbolType | Array} symbol
+ * @param {number} [decp]
+ * @returns {(string | number)[]}
+ */
+ proots(symbol, decp) {
+ // The roots will be rounded up to 7 decimal places.
+ // if this causes trouble you can explicitly pass in a different number of places
+ // rarr for polynomial of power n is of format [n, coeff x^n, coeff x^(n-1), ..., coeff x^0]
+ decp ||= 7;
+ const zeros = 0;
+ /** @type {(string | number)[]} */
+ const knownRoots = [];
+ /**
+ * @param {FracType[]} rarr
+ * @param {(string | number)[]} powers
+ * @param {number} max
+ * @returns {(string | number)[]}
+ */
+ const getRoots = function (rarr, powers, max) {
+ const roots = calcroots(rarr, powers, max).concat(knownRoots);
+ for (let i = 0; i < zeros; i++) {
+ roots.unshift(0);
+ }
+ return /** @type {string[]} */ (roots);
+ };
+
+ if (core.Utils.isSymbol(symbol) && /** @type {NerdamerSymbolType} */ (symbol).isPoly()) {
+ let sym = /** @type {NerdamerSymbolType} */ (symbol);
+ sym.distributeMultiplier();
+ // Make it so the symbol has a constants as the lowest term
+ if (sym.group === PL) {
+ const lowestPow = core.Utils.arrayMin(
+ /** @type {number[]} */ (/** @type {unknown} */ (keys(sym.symbols)))
+ );
+ const lowestSymbol = sym.symbols[lowestPow].clone().toUnitMultiplier();
+ sym = /** @type {NerdamerSymbolType} */ (_.expand(_.divide(sym, lowestSymbol)));
+ knownRoots.push(0); // Add zero since this is a known root
+ }
+ if (sym.group === core.groups.S) {
+ return [/** @type {string} */ ('0')];
+ }
+ if (sym.group === core.groups.PL) {
+ const powers = keys(sym.symbols);
+ const minpower = core.Utils.arrayMin(/** @type {number[]} */ (/** @type {unknown} */ (powers)));
+ sym = /** @type {NerdamerSymbolType} */ (
+ core.PARSER.divide(sym, core.PARSER.parse(`${sym.value}^${minpower}`))
+ );
+ }
+
+ const variable = keys(sym.symbols).sort().pop();
+ const subSym = sym.group === core.groups.PL ? sym.symbols : sym.symbols[variable || ''];
+ const g = subSym.group;
+ const powers = g === S ? [/** @type {FracType} */ (subSym.power).toDecimal()] : keys(subSym.symbols);
+ /** @type {(FracType | number)[]} */
+ const rarr = [];
+ const max = core.Utils.arrayMax(/** @type {number[]} */ (/** @type {unknown} */ (powers))); // Maximum power and degree of polynomial to be solved
+
+ // Prepare the data
+ for (let i = 1; i <= max; i++) {
+ /** @type {FracType | number} */
+ let c = 0; // If there is no power then the hole must be filled with a zero
+ if (powers.indexOf(`${i}`) !== -1) {
+ if (g === S) {
+ c = /** @type {FracType} */ (subSym.multiplier);
+ } else {
+ c = /** @type {FracType} */ (subSym.symbols[i].multiplier);
+ }
+ }
+ // Insert the coeffient but from the front
+ rarr.unshift(c);
+ }
+
+ rarr.push(/** @type {NerdamerSymbolType} */ (symbol).symbols[CONST_HASH].multiplier);
+
+ if (sym.group === S) {
+ rarr[0] = sym.multiplier;
+ } // The symbol maybe of group CP with one variable
+
+ return /** @type {(string | number)[]} */ (getRoots(/** @type {FracType[]} */ (rarr), powers, max));
+ }
+ if (core.Utils.isArray(symbol)) {
+ const parr = symbol;
+ const rarr = [];
+ const powers = [];
+ let lastPower = 0;
+ for (let i = 0; i < parr.length; i++) {
+ const coeff = parr[i][0];
+ const pow = parr[i][1];
+ const d = pow - lastPower - 1;
+ // Insert the zeros
+ for (let j = 0; j < d; j++) {
+ rarr.unshift(0);
+ }
+
+ rarr.unshift(coeff);
+ if (pow !== 0) {
+ powers.push(pow);
+ }
+ lastPower = pow;
+ }
+ const max = Math.max.apply(undefined, powers);
+
+ return getRoots(rarr, powers, max);
+ }
+ throw new core.exceptions.NerdamerTypeError('Cannot calculate roots. NerdamerSymbol must be a polynomial!');
+
+ function calcroots(coeffArr, powArr, maxPow) {
+ const MAXDEGREE = 100; // Degree of largest polynomial accepted by this script.
+ let i;
+
+ // Make a clone of the coefficients before appending the max power
+ const p = coeffArr.slice(0);
+
+ // Divide the string up into its individual entries, which--presumably--are separated by whitespace
+ coeffArr.unshift(maxPow);
+
+ if (maxPow > MAXDEGREE) {
+ throw new core.exceptions.ValueLimitExceededError(
+ `This utility accepts polynomials of degree up to ${MAXDEGREE}. `
+ );
+ }
+
+ const zeroi = []; // Vector of imaginary components of roots
+ const degreePar = {}; // DegreePar is a dummy variable for passing the parameter POLYDEGREE by reference
+ degreePar.Degree = maxPow;
+
+ for (i = 0; i < maxPow; i++) {
+ zeroi.push(0);
+ }
+ const zeror = zeroi.slice(0); // Vector of real components of roots
+
+ // Find the roots
+ // --> Begin Jenkins-Traub
+
+ /*
+ * A verbatim copy of Mr. David Binner's Jenkins-Traub port
+ */
+ function quadSdAk1(NN, u, v, poly, q, iPar) {
+ // Divides poly by the quadratic 1, u, v placing the quotient in q and the remainder in a, b
+ // iPar is a dummy variable for passing in the two parameters--a and b--by reference
+ q[0] = iPar.b = poly[0];
+ q[1] = iPar.a = -(u * iPar.b) + poly[1];
+
+ for (let idx = 2; idx < NN; idx++) {
+ q[idx] = -(u * iPar.a + v * iPar.b) + poly[idx];
+ iPar.b = iPar.a;
+ iPar.a = q[idx];
+ }
+ }
+
+ function calcScAk1(DBL_EPSILON, degree, a, b, iPar, K, u, v, qk) {
+ // This routine calculates scalar quantities used to compute the next K polynomial and
+ // new estimates of the quadratic coefficients.
+ // calcSC - integer variable set here indicating how the calculations are normalized
+ // to avoid overflow.
+ // iPar is a dummy variable for passing in the nine parameters--a1, a3, a7, c, d, e, f, g, and h --by reference
+
+ // sdPar is a dummy variable for passing the two parameters--c and d--into quadSdAk1 by reference
+ const sdPar = {};
+ // TYPE = 3 indicates the quadratic is almost a factor of K
+ let dumFlag = 3;
+
+ // Synthetic division of K by the quadratic 1, u, v
+ sdPar.b = sdPar.a = 0.0;
+ quadSdAk1(degree, u, v, K, qk, sdPar);
+ iPar.c = sdPar.a;
+ iPar.d = sdPar.b;
+
+ if (Math.abs(iPar.c) <= 100.0 * DBL_EPSILON * Math.abs(K[degree - 1])) {
+ if (Math.abs(iPar.d) <= 100.0 * DBL_EPSILON * Math.abs(K[degree - 2])) {
+ return dumFlag;
+ }
+ }
+
+ iPar.h = v * b;
+ if (Math.abs(iPar.d) >= Math.abs(iPar.c)) {
+ // TYPE = 2 indicates that all formulas are divided by d
+ dumFlag = 2;
+ iPar.e = a / iPar.d;
+ iPar.f = iPar.c / iPar.d;
+ iPar.g = u * b;
+ iPar.a3 = iPar.e * (iPar.g + a) + iPar.h * (b / iPar.d);
+ iPar.a1 = -a + iPar.f * b;
+ iPar.a7 = iPar.h + (iPar.f + u) * a;
+ } else {
+ // TYPE = 1 indicates that all formulas are divided by c;
+ dumFlag = 1;
+ iPar.e = a / iPar.c;
+ iPar.f = iPar.d / iPar.c;
+ iPar.g = iPar.e * u;
+ iPar.a3 = iPar.e * a + (iPar.g + iPar.h / iPar.c) * b;
+ iPar.a1 = -(a * (iPar.d / iPar.c)) + b;
+ iPar.a7 = iPar.g * iPar.d + iPar.h * iPar.f + a;
+ }
+ return dumFlag;
+ }
+
+ function nextKAk1(DBL_EPSILON, degree, tFlag, a, b, iPar, K, qk, qp) {
+ // Computes the next K polynomials using the scalars computed in calcScAk1
+ // iPar is a dummy variable for passing in three parameters--a1, a3, and a7
+ if (tFlag === 3) {
+ // Use unscaled form of the recurrence
+ K[1] = K[0] = 0.0;
+ for (let idx = 2; idx < degree; idx++) {
+ K[idx] = qk[idx - 2];
+ }
+ return;
+ }
+
+ const temp = tFlag === 1 ? b : a;
+ if (Math.abs(iPar.a1) > 10.0 * DBL_EPSILON * Math.abs(temp)) {
+ // Use scaled form of the recurrence
+ iPar.a7 /= iPar.a1;
+ iPar.a3 /= iPar.a1;
+ K[0] = qp[0];
+ K[1] = -(qp[0] * iPar.a7) + qp[1];
+ for (let idx = 2; idx < degree; idx++) {
+ K[idx] = -(qp[idx - 1] * iPar.a7) + qk[idx - 2] * iPar.a3 + qp[idx];
+ }
+ } else {
+ // If a1 is nearly zero, then use a special form of the recurrence
+ K[0] = 0.0;
+ K[1] = -(qp[0] * iPar.a7);
+ for (let idx = 2; idx < degree; idx++) {
+ K[idx] = -(qp[idx - 1] * iPar.a7) + qk[idx - 2] * iPar.a3;
+ }
+ }
+ }
+
+ function newestAk1(tFlag, iPar, a, a1, a3, a7, b, c, d, f, g, h, u, v, K, degree, poly) {
+ // Compute new estimates of the quadratic coefficients using the scalars computed in calcScAk1
+ // iPar is a dummy variable for passing in the two parameters--uu and vv--by reference
+ // iPar.a = uu, iPar.b = vv
+
+ let a4;
+ let a5;
+ let b1;
+ let b2;
+ let c1;
+ let c2;
+ let c3;
+ let c4;
+ let temp;
+ iPar.b = iPar.a = 0.0; // The quadratic is zeroed
+
+ if (tFlag === 3) {
+ // No action needed when tFlag is 3
+ } else {
+ if (tFlag === 2) {
+ a4 = (a + g) * f + h;
+ a5 = (f + u) * c + v * d;
+ } else {
+ a4 = a + u * b + h * f;
+ a5 = c + (u + v * f) * d;
+ }
+
+ // Evaluate new quadratic coefficients
+ b1 = -(K[degree - 1] / poly[degree]);
+ b2 = -(K[degree - 2] + b1 * poly[degree - 1]) / poly[degree];
+ c1 = v * b2 * a1;
+ c2 = b1 * a7;
+ c3 = b1 * b1 * a3;
+ c4 = -(c2 + c3) + c1;
+ temp = -c4 + a5 + b1 * a4;
+ if (temp !== 0.0) {
+ iPar.a = -((u * (c3 + c2) + v * (b1 * a1 + b2 * a7)) / temp) + u;
+ iPar.b = v * (1.0 + c4 / temp);
+ }
+ }
+ }
+
+ function quadAk1(a, b1, c, iPar) {
+ // Calculates the zeros of the quadratic a*Z^2 + b1*Z + c
+ // The quadratic formula, modified to avoid overflow, is used to find the larger zero if the
+ // zeros are real and both zeros are complex. The smaller real zero is found directly from
+ // the product of the zeros c/a.
+
+ // iPar is a dummy variable for passing in the four parameters--sr, si, lr, and li--by reference
+
+ let d;
+ let e;
+ iPar.sr = iPar.si = iPar.lr = iPar.li = 0.0;
+
+ if (a === 0) {
+ iPar.sr = b1 === 0 ? iPar.sr : -(c / b1);
+ return;
+ }
+ if (c === 0) {
+ iPar.lr = -(b1 / a);
+ return;
+ }
+
+ // Compute discriminant avoiding overflow
+ const b = b1 / 2.0;
+ if (Math.abs(b) < Math.abs(c)) {
+ e = c >= 0 ? a : -a;
+ e = -e + b * (b / Math.abs(c));
+ d = Math.sqrt(Math.abs(e)) * Math.sqrt(Math.abs(c));
+ } else {
+ e = -((a / b) * (c / b)) + 1.0;
+ d = Math.sqrt(Math.abs(e)) * Math.abs(b);
+ }
+
+ if (e >= 0) {
+ // Real zeros
+ d = b >= 0 ? -d : d;
+ iPar.lr = (-b + d) / a;
+ iPar.sr = iPar.lr === 0 ? iPar.sr : c / iPar.lr / a;
+ } else {
+ // Complex conjugate zeros
+ iPar.lr = iPar.sr = -(b / a);
+ iPar.si = Math.abs(d / a);
+ iPar.li = -iPar.si;
+ }
+ }
+
+ function quadItAk1(DBL_EPSILON, degree, iPar, uu, vv, qp, NN, sdPar, poly, qk, calcPar, K) {
+ // Variable-shift K-polynomial iteration for a quadratic factor converges only if the
+ // zeros are equimodular or nearly so.
+ // iPar is a dummy variable for passing in the five parameters--NZ, lzi, lzr, szi, and szr--by reference
+ // sdPar is a dummy variable for passing the two parameters--a and b--in by reference
+ // calcPar is a dummy variable for passing the nine parameters--a1, a3, a7, c, d, e, f, g, and h --in by reference
+
+ // qPar is a dummy variable for passing the four parameters--szr, szi, lzr, and lzi--into quadAk1 by reference
+ const qPar = {};
+ let ee;
+ let mp;
+ let omp;
+ /** @type {number} */
+ let relstp = 0;
+ let t;
+ let u;
+ let ui;
+ let v;
+ let vi;
+ let zm;
+ let idx;
+ let j = 0;
+ let tFlag;
+ let triedFlag = 0; // Integer variables
+
+ iPar.NZ = 0; // Number of zeros found
+ u = uu; // Uu and vv are coefficients of the starting quadratic
+ v = vv;
+
+ do {
+ qPar.li = qPar.lr = qPar.si = qPar.sr = 0.0;
+ quadAk1(1.0, u, v, qPar);
+ iPar.szr = qPar.sr;
+ iPar.szi = qPar.si;
+ iPar.lzr = qPar.lr;
+ iPar.lzi = qPar.li;
+
+ // Return if roots of the quadratic are real and not close to multiple or nearly
+ // equal and of opposite sign.
+ if (Math.abs(Math.abs(iPar.szr) - Math.abs(iPar.lzr)) > 0.01 * Math.abs(iPar.lzr)) {
+ break;
+ }
+
+ // Evaluate polynomial by quadratic synthetic division
+
+ quadSdAk1(NN, u, v, poly, qp, sdPar);
+
+ mp = Math.abs(-(iPar.szr * sdPar.b) + sdPar.a) + Math.abs(iPar.szi * sdPar.b);
+
+ // Compute a rigorous bound on the rounding error in evaluating p
+
+ zm = Math.sqrt(Math.abs(v));
+ ee = 2.0 * Math.abs(qp[0]);
+ t = -(iPar.szr * sdPar.b);
+
+ for (idx = 1; idx < degree; idx++) {
+ ee = ee * zm + Math.abs(qp[idx]);
+ }
+
+ ee = ee * zm + Math.abs(t + sdPar.a);
+ ee =
+ (9.0 * ee + 2.0 * Math.abs(t) - 7.0 * (Math.abs(sdPar.a + t) + zm * Math.abs(sdPar.b))) *
+ DBL_EPSILON;
+
+ // Iteration has converged sufficiently if the polynomial value is less than 20 times this bound
+ if (mp <= 20.0 * ee) {
+ iPar.NZ = 2;
+ break;
+ }
+
+ j++;
+ // Stop iteration after 20 steps
+ if (j > 20) {
+ break;
+ }
+ if (j >= 2) {
+ if (relstp <= 0.01 && mp >= omp && !triedFlag) {
+ // A cluster appears to be stalling the convergence. Five fixed shift
+ // steps are taken with a u, v close to the cluster.
+ relstp = relstp < DBL_EPSILON ? Math.sqrt(DBL_EPSILON) : Math.sqrt(relstp);
+ u -= u * relstp;
+ v += v * relstp;
+
+ quadSdAk1(NN, u, v, poly, qp, sdPar);
+ for (idx = 0; idx < 5; idx++) {
+ tFlag = calcScAk1(DBL_EPSILON, degree, sdPar.a, sdPar.b, calcPar, K, u, v, qk);
+ nextKAk1(DBL_EPSILON, degree, tFlag, sdPar.a, sdPar.b, calcPar, K, qk, qp);
+ }
+
+ triedFlag = 1;
+ j = 0;
+ }
+ }
+ omp = mp;
+
+ // Calculate next K polynomial and new u and v
+ tFlag = calcScAk1(DBL_EPSILON, degree, sdPar.a, sdPar.b, calcPar, K, u, v, qk);
+ nextKAk1(DBL_EPSILON, degree, tFlag, sdPar.a, sdPar.b, calcPar, K, qk, qp);
+ tFlag = calcScAk1(DBL_EPSILON, degree, sdPar.a, sdPar.b, calcPar, K, u, v, qk);
+ newestAk1(
+ tFlag,
+ sdPar,
+ sdPar.a,
+ calcPar.a1,
+ calcPar.a3,
+ calcPar.a7,
+ sdPar.b,
+ calcPar.c,
+ calcPar.d,
+ calcPar.f,
+ calcPar.g,
+ calcPar.h,
+ u,
+ v,
+ K,
+ degree,
+ poly
+ );
+ ui = sdPar.a;
+ vi = sdPar.b;
+
+ // If vi is zero, the iteration is not converging
+ if (vi !== 0) {
+ relstp = Math.abs((-v + vi) / vi);
+ u = ui;
+ v = vi;
+ }
+ } while (vi !== 0);
+ }
+
+ function realItAk1(DBL_EPSILON, iPar, sdPar, degree, poly, NN, qp, K, qk) {
+ // Variable-shift H-polynomial iteration for a real zero
+ // sss - starting iterate = sdPar.a
+ // NZ - number of zeros found = iPar.NZ
+ // dumFlag - flag to indicate a pair of zeros near real axis, returned to iFlag
+
+ let ee;
+ let kv;
+ let mp;
+ let ms;
+ let omp;
+ let pv;
+ let s;
+ let t;
+ let dumFlag;
+ let idx;
+ let j;
+ const nm1 = degree - 1; // Integer variables
+
+ iPar.NZ = j = dumFlag = 0;
+ s = sdPar.a;
+
+ for (;;) {
+ pv = poly[0];
+
+ // Evaluate p at s
+ qp[0] = pv;
+ for (idx = 1; idx < NN; idx++) {
+ qp[idx] = pv = pv * s + poly[idx];
+ }
+ mp = Math.abs(pv);
+
+ // Compute a rigorous bound on the error in evaluating p
+ ms = Math.abs(s);
+ ee = 0.5 * Math.abs(qp[0]);
+ for (idx = 1; idx < NN; idx++) {
+ ee = ee * ms + Math.abs(qp[idx]);
+ }
+
+ // Iteration has converged sufficiently if the polynomial value is less than
+ // 20 times this bound
+ if (mp <= 20.0 * DBL_EPSILON * (2.0 * ee - mp)) {
+ iPar.NZ = 1;
+ iPar.szr = s;
+ iPar.szi = 0.0;
+ break;
+ }
+ j++;
+ // Stop iteration after 10 steps
+ if (j > 10) {
+ break;
+ }
+
+ if (j >= 2) {
+ if (Math.abs(t) <= 0.001 * Math.abs(-t + s) && mp > omp) {
+ // A cluster of zeros near the real axis has been encountered.
+ // Return with iFlag set to initiate a quadratic iteration.
+ dumFlag = 1;
+ iPar.a = s;
+ break;
+ } // End if ((fabs(t) <= 0.001*fabs(s - t)) && (mp > omp))
+ } // End if (j >= 2)
+
+ // Return if the polynomial value has increased significantly
+ omp = mp;
+
+ // Compute t, the next polynomial and the new iterate
+ qk[0] = kv = K[0];
+ for (idx = 1; idx < degree; idx++) {
+ qk[idx] = kv = kv * s + K[idx];
+ }
+
+ if (Math.abs(kv) > Math.abs(K[nm1]) * 10.0 * DBL_EPSILON) {
+ // Use the scaled form of the recurrence if the value of K at s is non-zero
+ t = -(pv / kv);
+ K[0] = qp[0];
+ for (idx = 1; idx < degree; idx++) {
+ K[idx] = t * qk[idx - 1] + qp[idx];
+ }
+ } else {
+ // Use unscaled form
+ K[0] = 0.0;
+ for (idx = 1; idx < degree; idx++) {
+ K[idx] = qk[idx - 1];
+ }
+ }
+
+ kv = K[0];
+ for (idx = 1; idx < degree; idx++) {
+ kv = kv * s + K[idx];
+ }
+ t = Math.abs(kv) > Math.abs(K[nm1]) * 10.0 * DBL_EPSILON ? -(pv / kv) : 0.0;
+ s += t;
+ }
+ return dumFlag;
+ }
+
+ function fxshfrAk1(DBL_EPSILON, MDP1, L2, sr, v, K, degree, poly, NN, qp, u, iPar) {
+ // Computes up to L2 fixed shift K-polynomials, testing for convergence in the linear or
+ // quadratic case. Initiates one of the variable shift iterations and returns with the
+ // number of zeros found.
+ // L2 limit of fixed shift steps
+ // iPar is a dummy variable for passing in the five parameters--NZ, lzi, lzr, szi, and szr--by reference
+ // NZ number of zeros found
+ const sdPar = {}; // SdPar is a dummy variable for passing the two parameters--a and b--into quadSdAk1 by reference
+ const calcPar = {};
+ // CalcPar is a dummy variable for passing the nine parameters--a1, a3, a7, c, d, e, f, g, and h --into calcScAk1 by reference
+
+ const qk = new Array(MDP1);
+ const svk = new Array(MDP1);
+ let a;
+ let b;
+ let betas;
+ let betav;
+ let oss;
+ let ots;
+ let otv;
+ let ovv;
+ let s;
+ let ss;
+ let ts;
+ let tss;
+ let tv;
+ let tvv;
+ let ui;
+ let vi;
+ let vv;
+ let fflag;
+ let idx;
+ let iFlag = 1;
+ let j;
+ let spass;
+ let stry;
+ let tFlag;
+ let vpass;
+ let vtry; // Integer variables
+
+ iPar.NZ = 0;
+ betav = betas = 0.25;
+ oss = sr;
+ ovv = v;
+
+ // Evaluate polynomial by synthetic division
+ sdPar.b = sdPar.a = 0.0;
+ quadSdAk1(NN, u, v, poly, qp, sdPar);
+ a = sdPar.a;
+ b = sdPar.b;
+ calcPar.h =
+ calcPar.g =
+ calcPar.f =
+ calcPar.e =
+ calcPar.d =
+ calcPar.c =
+ calcPar.a7 =
+ calcPar.a3 =
+ calcPar.a1 =
+ 0.0;
+ tFlag = calcScAk1(DBL_EPSILON, degree, a, b, calcPar, K, u, v, qk);
+
+ for (j = 0; j < L2; j++) {
+ fflag = 1;
+
+ // Calculate next K polynomial and estimate v
+ nextKAk1(DBL_EPSILON, degree, tFlag, a, b, calcPar, K, qk, qp);
+ tFlag = calcScAk1(DBL_EPSILON, degree, a, b, calcPar, K, u, v, qk);
+
+ // Use sdPar for passing in uu and vv instead of defining a brand-new variable.
+ // sdPar.a = ui, sdPar.b = vi
+ newestAk1(
+ tFlag,
+ sdPar,
+ a,
+ calcPar.a1,
+ calcPar.a3,
+ calcPar.a7,
+ b,
+ calcPar.c,
+ calcPar.d,
+ calcPar.f,
+ calcPar.g,
+ calcPar.h,
+ u,
+ v,
+ K,
+ degree,
+ poly
+ );
+ ui = sdPar.a;
+ vv = vi = sdPar.b;
+
+ // Estimate s
+ ss = K[degree - 1] === 0.0 ? 0.0 : -(poly[degree] / K[degree - 1]);
+ ts = tv = 1.0;
+
+ if (j !== 0 && tFlag !== 3) {
+ // Compute relative measures of convergence of s and v sequences
+ tv = vv === 0.0 ? tv : Math.abs((vv - ovv) / vv);
+ ts = ss === 0.0 ? ts : Math.abs((ss - oss) / ss);
+
+ // If decreasing, multiply the two most recent convergence measures
+ tvv = tv < otv ? tv * otv : 1.0;
+ tss = ts < ots ? ts * ots : 1.0;
+
+ // Compare with convergence criteria
+ vpass = tvv < betav ? 1 : 0;
+ spass = tss < betas ? 1 : 0;
+
+ if (spass || vpass) {
+ // At least one sequence has passed the convergence test.
+ // Store variables before iterating
+
+ for (idx = 0; idx < degree; idx++) {
+ svk[idx] = K[idx];
+ }
+ s = ss;
+
+ // Choose iteration according to the fastest converging sequence
+
+ stry = vtry = 0;
+
+ for (;;) {
+ if (fflag && (fflag = 0) === 0 && spass && (!vpass || tss < tvv)) {
+ // Do nothing. Provides a quick "short circuit".
+ } else {
+ quadItAk1(
+ DBL_EPSILON,
+ degree,
+ iPar,
+ ui,
+ vi,
+ qp,
+ NN,
+ sdPar,
+ poly,
+ qk,
+ calcPar,
+ K
+ );
+ a = sdPar.a;
+ b = sdPar.b;
+
+ if (iPar.NZ > 0) {
+ return;
+ }
+
+ // Quadratic iteration has failed. Flag that it has been tried and decrease the
+ // convergence criterion
+ iFlag = vtry = 1;
+ betav *= 0.25;
+
+ // Try linear iteration if it has not been tried and the s sequence is converging
+ if (stry || !spass) {
+ iFlag = 0;
+ } else {
+ for (idx = 0; idx < degree; idx++) {
+ K[idx] = svk[idx];
+ }
+ }
+ }
+ // Fflag = 0;
+ if (iFlag !== 0) {
+ // Use sdPar for passing in s instead of defining a brand-new variable.
+ // sdPar.a = s
+ sdPar.a = s;
+ iFlag = realItAk1(DBL_EPSILON, iPar, sdPar, degree, poly, NN, qp, K, qk);
+ s = sdPar.a;
+
+ if (iPar.NZ > 0) {
+ return;
+ }
+
+ // Linear iteration has failed. Flag that it has been tried and decrease the
+ // convergence criterion
+ stry = 1;
+ betas *= 0.25;
+
+ if (iFlag !== 0) {
+ // If linear iteration signals an almost double real zero, attempt quadratic iteration
+ ui = -(s + s);
+ vi = s * s;
+ continue;
+ }
+ }
+
+ // Restore variables
+ for (idx = 0; idx < degree; idx++) {
+ K[idx] = svk[idx];
+ }
+
+ // Try quadratic iteration if it has not been tried and the v sequence is converging
+ if (!vpass || vtry) {
+ break;
+ } // Break out of infinite for loop
+ }
+
+ // Re-compute qp and scalar values to continue the second stage
+
+ quadSdAk1(NN, u, v, poly, qp, sdPar);
+ a = sdPar.a;
+ b = sdPar.b;
+
+ tFlag = calcScAk1(DBL_EPSILON, degree, a, b, calcPar, K, u, v, qk);
+ }
+ }
+ ovv = vv;
+ oss = ss;
+ otv = tv;
+ ots = ts;
+ }
+ }
+
+ function rpSolve(degPar, poly, zeroReal, zeroImag) {
+ let degree = degPar.Degree;
+ const RADFAC = Math.PI / 180; // Degrees-to-radians conversion factor = PI/180
+ const LB2 = Math.LN2; // Dummy variable to avoid re-calculating this value in loop below
+ const MDP1 = degPar.Degree + 1;
+ const K = new Array(MDP1);
+ const pt = new Array(MDP1);
+ const qp = new Array(MDP1);
+ const temp = new Array(MDP1);
+ // QPar is a dummy variable for passing the four parameters--sr, si, lr, and li--by reference
+ const qPar = {};
+ // FxshfrPar is a dummy variable for passing parameters by reference : NZ, lzi, lzr, szi, szr);
+ const fxshfrPar = {};
+ let bnd;
+ let DBL_EPSILON;
+ let df;
+ let dx;
+ let factor;
+ let ff;
+ let moduliMax;
+ let moduliMin;
+ let sc;
+ let x;
+ let xm;
+ let aa;
+ let bb;
+ let cc;
+ let sr;
+ let t;
+ let u;
+ let xxx;
+ let j;
+ let jj;
+ let l;
+ let NM1;
+ let NN;
+ let zerok; // Integer variables
+
+ // Calculate the machine epsilon and store in the variable DBL_EPSILON.
+ // To calculate this value, just use existing variables rather than create new ones that will be used only for this code block
+ aa = 1.0;
+ do {
+ DBL_EPSILON = aa;
+ aa /= 2;
+ bb = 1.0 + aa;
+ } while (bb > 1.0);
+
+ const LO = Number.MIN_VALUE / DBL_EPSILON;
+ const cosr = Math.cos(94.0 * RADFAC); // = -0.069756474
+ const sinr = Math.sin(94.0 * RADFAC); // = 0.99756405
+ let xx = Math.sqrt(0.5); // = 0.70710678
+ let yy = -xx;
+
+ fxshfrPar.NZ = j = 0;
+ fxshfrPar.szr = fxshfrPar.szi = fxshfrPar.lzr = fxshfrPar.lzi = 0.0;
+
+ // Remove zeros at the origin, if any
+ while (poly[degree] === 0) {
+ zeroReal[j] = zeroImag[j] = 0;
+ degree--;
+ j++;
+ }
+ NN = degree + 1;
+
+ // >>>>> Begin Main Loop <<<<<
+ while (degree >= 1) {
+ // Main loop
+ // Start the algorithm for one zero
+ if (degree <= 2) {
+ // Calculate the final zero or pair of zeros
+ if (degree < 2) {
+ zeroReal[degPar.Degree - 1] = -(poly[1] / poly[0]);
+ zeroImag[degPar.Degree - 1] = 0;
+ } else {
+ qPar.li = qPar.lr = qPar.si = qPar.sr = 0.0;
+ quadAk1(poly[0], poly[1], poly[2], qPar);
+ zeroReal[degPar.Degree - 2] = qPar.sr;
+ zeroImag[degPar.Degree - 2] = qPar.si;
+ zeroReal[degPar.Degree - 1] = qPar.lr;
+ zeroImag[degPar.Degree - 1] = qPar.li;
+ }
+ break;
+ }
+
+ // Find the largest and smallest moduli of the coefficients
+ moduliMax = 0.0;
+ moduliMin = Number.MAX_VALUE;
+
+ for (i = 0; i < NN; i++) {
+ x = Math.abs(poly[i]);
+ if (x > moduliMax) {
+ moduliMax = x;
+ }
+ if (x !== 0 && x < moduliMin) {
+ moduliMin = x;
+ }
+ }
+
+ // Scale if there are large or very small coefficients
+ // Computes a scale factor to multiply the coefficients of the polynomial. The scaling
+ // is done to avoid overflow and to avoid undetected underflow interfering with the
+ // convergence criterion.
+ // The factor is a power of the base.
+ sc = LO / moduliMin;
+
+ if ((sc <= 1.0 && moduliMax >= 10) || (sc > 1.0 && Number.MAX_VALUE / sc >= moduliMax)) {
+ sc = sc === 0 ? Number.MIN_VALUE : sc;
+ l = Math.floor(Math.log(sc) / LB2 + 0.5);
+ factor = 2.0 ** l;
+ if (factor !== 1.0) {
+ for (i = 0; i < NN; i++) {
+ poly[i] *= factor;
+ }
+ }
+ }
+
+ // Compute lower bound on moduli of zeros
+ for (let idx = 0; idx < NN; idx++) {
+ pt[idx] = Math.abs(poly[idx]);
+ }
+ pt[degree] = -pt[degree];
+ NM1 = degree - 1;
+
+ // Compute upper estimate of bound
+ x = Math.exp((Math.log(-pt[degree]) - Math.log(pt[0])) / degree);
+
+ if (pt[NM1] !== 0) {
+ // If Newton step at the origin is better, use it
+ xm = -pt[degree] / pt[NM1];
+ x = xm < x ? xm : x;
+ }
+
+ // Chop the interval (0, x) until ff <= 0
+ xm = x;
+ do {
+ x = xm;
+ xm = 0.1 * x;
+ ff = pt[0];
+ for (let idx = 1; idx < NN; idx++) {
+ ff = ff * xm + pt[idx];
+ }
+ } while (ff > 0); // End do-while loop
+
+ dx = x;
+ // Do Newton iteration until x converges to two decimal places
+
+ do {
+ df = ff = pt[0];
+ for (let idx = 1; idx < degree; idx++) {
+ ff = x * ff + pt[idx];
+ df = x * df + ff;
+ } // End for i
+ ff = x * ff + pt[degree];
+ dx = ff / df;
+ x -= dx;
+ } while (Math.abs(dx / x) > 0.005); // End do-while loop
+
+ bnd = x;
+
+ // Compute the derivative as the initial K polynomial and do 5 steps with no shift
+ for (let idx = 1; idx < degree; idx++) {
+ K[idx] = ((degree - idx) * poly[idx]) / degree;
+ }
+ K[0] = poly[0];
+ aa = poly[degree];
+ bb = poly[NM1];
+ zerok = K[NM1] === 0 ? 1 : 0;
+
+ for (jj = 0; jj < 5; jj++) {
+ cc = K[NM1];
+ if (zerok) {
+ // Use unscaled form of recurrence
+ for (let idx = 0; idx < NM1; idx++) {
+ j = NM1 - idx;
+ K[j] = K[j - 1];
+ } // End for i
+ K[0] = 0;
+ zerok = K[NM1] === 0 ? 1 : 0;
+ } else {
+ // Used scaled form of recurrence if value of K at 0 is nonzero
+ t = -aa / cc;
+ for (let idx = 0; idx < NM1; idx++) {
+ j = NM1 - idx;
+ K[j] = t * K[j - 1] + poly[j];
+ } // End for i
+ K[0] = poly[0];
+ zerok = Math.abs(K[NM1]) <= Math.abs(bb) * DBL_EPSILON * 10.0 ? 1 : 0;
+ }
+ }
+
+ // Save K for restarts with new shifts
+ for (let idx = 0; idx < degree; idx++) {
+ temp[idx] = K[idx];
+ }
+
+ // Loop to select the quadratic corresponding to each new shift
+ for (jj = 1; jj <= 20; jj++) {
+ // Quadratic corresponds to a double shift to a non-real point and its
+ // complex conjugate. The point has modulus BND and amplitude rotated
+ // by 94 degrees from the previous shift.
+
+ xxx = -(sinr * yy) + cosr * xx;
+ yy = sinr * xx + cosr * yy;
+ xx = xxx;
+ sr = bnd * xx;
+ u = -(2.0 * sr);
+
+ // Second stage calculation, fixed quadratic
+ fxshfrAk1(DBL_EPSILON, MDP1, 20 * jj, sr, bnd, K, degree, poly, NN, qp, u, fxshfrPar);
+
+ if (fxshfrPar.NZ === 0) {
+ // If the iteration is unsuccessful, another quadratic is chosen after restoring K
+ for (let idx = 0; idx < degree; idx++) {
+ K[idx] = temp[idx];
+ }
+ } else {
+ // The second stage jumps directly to one of the third stage iterations and
+ // returns here if successful. Deflate the polynomial, store the zero or
+ // zeros, and return to the main algorithm.
+ j = degPar.Degree - degree;
+ zeroReal[j] = fxshfrPar.szr;
+ zeroImag[j] = fxshfrPar.szi;
+ NN -= fxshfrPar.NZ;
+ degree = NN - 1;
+ for (let idx = 0; idx < NN; idx++) {
+ poly[idx] = qp[idx];
+ }
+ if (fxshfrPar.NZ === 1) {
+ // Single zero found, no additional zeros to store
+ } else {
+ zeroReal[j + 1] = fxshfrPar.lzr;
+ zeroImag[j + 1] = fxshfrPar.lzi;
+ }
+ break;
+ }
+ }
+ // Return with failure if no convergence with 20 shifts
+ if (jj > 20) {
+ degPar.Degree -= degree;
+ break;
+ }
+ }
+ // >>>>> End Main Loop <<<<<
+ }
+ // --> End Jenkins-Traub
+ rpSolve(degreePar, p, zeror, zeroi);
+
+ const l = zeroi.length;
+ /** @type {(string | number)[]} */
+ const results = [];
+ // Format the output
+ for (i = 0; i < l; i++) {
+ // We round the imaginary part to avoid having something crazy like 5.67e-16.
+ const img = round(zeroi[i], decp + 8);
+ let real = round(Number(zeror[i]), decp + 8);
+ // Did the rounding pay off? If the rounding did nothing more than chop off a few digits then no.
+ // If the rounding results in a a number at least 3 digits shorter we'll keep it else we'll keep
+ // the original otherwise the rounding was worth it.
+ real = decp - String(real).length > 2 ? real : Number(zeror[i]);
+ const sign = Number(img) < 0 ? '-' : '';
+
+ // Remove the zeroes
+ /** @type {string | number} */
+ let realStr = real;
+ /** @type {string | number} */
+ let imgStr = img;
+ if (real === 0) {
+ realStr = '';
+ }
+ if (img === 0) {
+ imgStr = '';
+ }
+
+ // Remove 1 as the multiplier and discard imaginary part if there isn't one.
+ if (Math.abs(Number(img)) === 1) {
+ imgStr = `${sign}i`;
+ } else if (img) {
+ imgStr = `${img}*i`;
+ } else {
+ imgStr = '';
+ }
+
+ const num = realStr && imgStr ? `${realStr}+${imgStr}` : String(realStr) + String(imgStr);
+ results[i] = num.replace(/\+-/gu, '-');
+ }
+ return results;
+ }
+ },
+ roots(symbol) {
+ if (symbol.isConstant(true, true)) {
+ return core.Utils.nroots(symbol);
+ }
+ const roots = __.proots(symbol).map(x => _.parse(x));
+ return core.Vector.fromArray(roots);
+ },
+ /**
+ * Find root using Newton-Raphson method.
+ *
+ * @param {NerdamerSymbolType | ((x: number) => number)} f - Function or symbol
+ * @param {number} guess - Initial guess
+ * @param {((x: number) => number) | undefined} [dx] - Optional derivative
+ * @returns {number | null}
+ */
+ froot(f, guess, dx) {
+ /**
+ * @param {number | null} xn
+ * @returns {number | null}
+ */
+ const newtonraph = function (xn) {
+ const mesh = 1e-12;
+ // If the derivative was already provided then don't recalculate.
+ const df = dx
+ ? dx
+ : core.Build.build(core.Calculus.diff(/** @type {NerdamerSymbolType} */ (f).clone()));
+ // If the function was passed in as a function then don't recalculate.
+ const fn = f instanceof Function ? f : core.Build.build(f);
+ const max = 10000;
+ let done = false;
+ let safety = 0;
+ while (!done) {
+ const x =
+ /** @type {number} */ (xn) - fn(/** @type {number} */ (xn)) / df(/** @type {number} */ (xn));
+ // Absolute values for both x & xn ensures that we indeed have the radius
+ const r = Math.abs(x) - Math.abs(/** @type {number} */ (xn));
+ const delta = Math.abs(r);
+ xn = x;
+
+ if (delta < mesh) {
+ done = true;
+ } else if (safety > max) {
+ xn = null;
+ done = true;
+ }
+
+ safety++;
+ }
+ return xn;
+ };
+ return newtonraph(Number(guess));
+ },
+ /**
+ * Solve quadratic equation.
+ *
+ * @param {NerdamerSymbolType | string} a
+ * @param {NerdamerSymbolType | string} b
+ * @param {NerdamerSymbolType | string} c
+ * @returns {NerdamerSymbolType[]}
+ */
+ quad(a, b, c) {
+ /**
+ * @param {NerdamerSymbolType | string} qa
+ * @param {NerdamerSymbolType | string} qb
+ * @param {NerdamerSymbolType | string} qc
+ * @param {number} sign
+ * @returns {NerdamerSymbolType}
+ */
+ const q = function (qa, qb, qc, sign) {
+ return /** @type {NerdamerSymbolType} */ (
+ _.parse(`-(${qb}+${sign}*sqrt((${qb})^2-4*(${qa})*(${qc})))/(2*${qa})`)
+ );
+ };
+ return [q(a, b, c, 1), q(a, b, c, -1)];
+ },
+ /**
+ * Returns sum and product given roots.
+ *
+ * @param {NerdamerSymbolType | string} a
+ * @param {NerdamerSymbolType | string} b
+ * @returns {NerdamerSymbolType[]}
+ */
+ sumProd(a, b) {
+ return __.quad(String(-b), String(a), '-1').map(x => x.invert());
+ },
+ coeffs(symbol, wrt, coeffs) {
+ symbol = /** @type {NerdamerSymbolType} */ (_.expand(symbol));
+ coeffs ||= [new NerdamerSymbol(0)];
+ // We cannot get coeffs for group EX
+ let vars = variables(symbol);
+
+ // If wrt is not provided and there's only one variable, use it
+ if (wrt === undefined && vars.length === 1) {
+ wrt = vars[0];
+ }
+ wrt = String(wrt);
+
+ if (symbol.group === EX && symbol.contains(wrt, true)) {
+ _.error(`Unable to get coefficients using expression ${symbol.toString()}`);
+ }
+ vars = variables(symbol);
+
+ // Check if symbol contains irrational constants that would be lost by Polynomial
+ // These include pi, e, and sqrt (which are treated as constants but aren't simple numbers)
+ const hasIrrationalConstants =
+ symbol.contains('pi') || symbol.contains('e') || symbol.containsFunction('sqrt');
+
+ if (vars.length === 1 && vars[0] === wrt && !symbol.isImaginary() && !hasIrrationalConstants) {
+ const a = new Polynomial(symbol).coeffs.map(x => new NerdamerSymbol(x));
+
+ for (let i = 0, l = a.length; i < l; i++) {
+ let coeff = a[i];
+ const e = coeffs[i];
+ if (e) {
+ coeff = /** @type {NerdamerSymbolType} */ (_.add(e, coeff));
+ }
+ coeffs[i] = coeff; // Transfer it all over
+ }
+ } else if (
+ vars.length === 1 &&
+ vars[0] === wrt &&
+ !symbol.isImaginary() &&
+ hasIrrationalConstants &&
+ symbol.group === CP
+ ) {
+ // Use getCoeffs which properly preserves symbolic constants
+ // Only for CP (sum) groups - CB (product) groups are handled in the else branch
+ const a = core.Utils.getCoeffs(symbol, wrt);
+
+ for (let i = 0, l = a.length; i < l; i++) {
+ let coeff = /** @type {NerdamerSymbolType} */ (a[i]);
+ const e = coeffs[i];
+ if (e) {
+ coeff = /** @type {NerdamerSymbolType} */ (_.add(e, coeff));
+ }
+ coeffs[i] = coeff;
+ }
+ } else {
+ if (!wrt) {
+ _.error('Polynomial contains more than one variable. Please specify which variable is to be used!');
+ }
+ // If the variable isn't part of this polynomial then we're looking at x^0
+
+ if (vars.indexOf(wrt) === -1) {
+ coeffs[0] = /** @type {NerdamerSymbolType} */ (_.add(symbol, coeffs[0]));
+ } else {
+ coeffs ||= [new NerdamerSymbol(0)];
+ let coeff;
+ if (symbol.group === CB) {
+ const s = symbol.symbols[wrt];
+ if (!s) {
+ _.error('Expression is not a polynomial!');
+ }
+ const p = Number(s.power);
+ coeff = /** @type {NerdamerSymbolType} */ (_.divide(symbol.clone(), s.clone()));
+ if (/** @type {NerdamerSymbolType} */ (coeff).contains(wrt, true) || p < 0 || !isInt(p)) {
+ _.error('Expression is not a polynomial!');
+ }
+ const e = coeffs[p];
+ if (e) {
+ coeff = /** @type {NerdamerSymbolType} */ (_.add(e, coeff));
+ }
+ coeffs[p] = coeff;
+ } else if (symbol.group === CP) {
+ symbol.each(x => {
+ __.coeffs(x.clone(), wrt, coeffs);
+ }, true);
+ }
+ }
+ }
+ // Fill holes
+ for (let i = 0, l = coeffs.length; i < l; i++) {
+ if (typeof coeffs[i] === 'undefined') {
+ coeffs[i] = new NerdamerSymbol(0);
+ }
+ }
+
+ return coeffs;
+ },
+ /**
+ * Get's all the powers of a particular polynomial including the denominators. The denominators powers are
+ * returned as negative. All remaining polynomials are returned as zero order polynomials. for example
+ * polyPowers(x^2+1/x+y+t) will return [ '-1', 0, '2' ]
+ *
+ * @param {NerdamerSymbolType} e
+ * @param {string} forVariable
+ * @param {Array} powers
+ * @returns {Array} An array of the powers
+ */
+ // assumes you've already verified that it's a polynomial
+ polyPowers(e, forVariable, powers) {
+ powers ||= [];
+ const g = e.group;
+ if (g === PL && forVariable === e.value) {
+ powers = powers.concat(keys(e.symbols));
+ } else if (g === CP) {
+ for (const s in e.symbols) {
+ if (!Object.hasOwn(e.symbols, s)) {
+ continue;
+ }
+ const symbol = e.symbols[s];
+ const symGroup = symbol.group;
+ const v = symbol.value;
+ if (symGroup === S && forVariable === v) {
+ powers.push(symbol.power);
+ } else if (symGroup === PL || symGroup === CP) {
+ powers = __.polyPowers(symbol, forVariable, powers);
+ } else if (symGroup === CB && symbol.contains(forVariable)) {
+ const t = symbol.symbols[forVariable];
+ if (t) {
+ powers.push(t.power);
+ }
+ } else if (symGroup === N || forVariable !== v) {
+ powers.push(0);
+ }
+ }
+ } else if (g === CB && e.contains(forVariable)) {
+ const decomp = /** @type {DecomposeResultType} */ (core.Utils.decompose_fn(e, forVariable, true));
+ powers.push(decomp.x.power);
+ }
+ return core.Utils.arrayUnique(powers).sort();
+ },
+ // The factor object
+ Factor: {
+ // Splits the symbol in symbol and constant
+ split(symbol) {
+ let c = new NerdamerSymbol(1); // The constants part
+ let s = new NerdamerSymbol(1); // The symbolic part
+ __.Factor.factorInner(symbol, new Factors()).each(x => {
+ const t = /** @type {NerdamerSymbolType} */ (_.parse(x));
+ if (x.isConstant(true)) {
+ c = /** @type {NerdamerSymbolType} */ (_.multiply(c, t));
+ } else {
+ s = /** @type {NerdamerSymbolType} */ (_.multiply(s, t));
+ }
+ });
+ return [c, s];
+ },
+ mix(o, includeNegatives) {
+ const factors = keys(o);
+ const l = factors.length;
+ const m = []; // Create a row which we'r going to be mixing
+ for (let i = 0; i < l; i++) {
+ const factor = Number(factors[i]);
+ const p = o[factors[i]];
+ const ll = m.length;
+ for (let j = 0; j < ll; j++) {
+ const t = m[j] * factor;
+ m.push(t);
+ if (includeNegatives) {
+ m.push(-t);
+ }
+ }
+
+ for (let j = 1; j <= p; j++) {
+ m.push(factor ** j);
+ }
+ }
+ return m;
+ },
+ // TODO: this method is to replace common factoring
+ common(symbol, factors) {
+ try {
+ if (symbol.group === CP) {
+ // This may have the unfortunate side effect of expanding and factoring again
+ // to only end up with the same result.
+ // TODO: try to avoid this
+ // collect the symbols and sort to have the longest first. Thinking is that the longest terms
+ // has to contain the variable in order for it to be factorable
+ const expanded = /** @type {NerdamerSymbolType} */ (
+ _.expand(symbol.clone(), { expand_denominator: true })
+ );
+ /** @type {(sym: unknown) => number} */
+ const getLength = sym => /** @type {{ length?: number }} */ (sym).length || 1;
+ const symbols = /** @type {NerdamerSymbolType[]} */ (
+ expanded.collectSymbols(null, null, (a, b) => getLength(b) - getLength(a))
+ );
+
+ /** @type {Record<string, [number, NerdamerSymbolType[]]>} */
+ const map = {}; // Create a map of common factors
+ /** @type {FracType[]} */
+ const coeffs = [];
+ for (let i = 0; i < symbols.length; i++) {
+ const sym = symbols[i];
+ coeffs.push(sym.multiplier.clone());
+ sym.each(x => {
+ const p = Number(x.power);
+ // This check exits since we have a symbolic power.
+ // For the future... think about removing this check and modify for symbolic powers
+ if (isNaN(p)) {
+ throw new Error('exiting');
+ }
+ // Loop through the symbols and lump together common terms
+ if (x.value in map) {
+ if (p < map[x.value][0]) {
+ map[x.value][0] = p;
+ }
+ map[x.value][1].push(x);
+ } else {
+ map[x.value] = [p, [x]];
+ }
+ });
+ }
+ // The factor
+ let factor = new NerdamerSymbol(1);
+ for (const x in map) {
+ // If this factor is found in all terms since the length of
+ // matching variable terms matches the number of original terms
+ if (map[x][1].length === symbols.length) {
+ // Generate a symbol and multiply into the factor
+ factor = /** @type {NerdamerSymbolType} */ (
+ _.multiply(
+ factor,
+ /** @type {NerdamerSymbolType} */ (
+ _.pow(new NerdamerSymbol(x), new NerdamerSymbol(map[x][0]))
+ )
+ )
+ );
+ }
+ }
+ // Get coefficient factor
+ const c = core.Math2.QGCD.apply(null, coeffs);
+
+ if (!c.equals(1)) {
+ factors.add(new NerdamerSymbol(c));
+ for (let i = 0; i < symbols.length; i++) {
+ symbols[i].multiplier = symbols[i].multiplier.divide(c);
+ }
+ }
+
+ // If we actuall found any factors
+ if (!factor.equals(1)) {
+ factors.add(factor);
+ symbol = new NerdamerSymbol(0);
+ for (let i = 0; i < symbols.length; i++) {
+ symbol = /** @type {NerdamerSymbolType} */ (
+ _.add(symbol, _.divide(symbols[i], factor.clone()))
+ );
+ }
+ }
+ }
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ }
+
+ return symbol;
+ },
+ zeroes(symbol, factors) {
+ const exit = function () {
+ throw new core.exceptions.ValueLimitExceededError('Exiting');
+ };
+ try {
+ let term;
+ let sum;
+ let p;
+ symbol = /** @type {NerdamerSymbolType} */ (_.expand(symbol.clone()));
+ const e = symbol.toString();
+ const vars = variables(symbol);
+
+ sum = new NerdamerSymbol(0);
+
+ const terms = [];
+ /** @type {FracType[]} */
+ const powers = [];
+
+ // Start setting each variable to zero
+ for (let i = 0, l = vars.length; i < vars.length; i++) {
+ /** @type {Record<string, ExpressionParam>} */
+ const subs = {};
+ // We want to create a subs object with all but the current variable set to zero
+ for (let j = 0; j < l; j++) {
+ if (i !== j) // Make sure we're not looking at the same variable
+ {
+ subs[vars[j]] = 0;
+ }
+ }
+ term = /** @type {NerdamerSymbolType} */ (_.parse(e, subs));
+ const tp = term.power;
+ // The temporary power has to be an integer as well
+ if (!isInt(tp)) {
+ exit();
+ }
+ terms.push(term);
+ powers.push(/** @type {FracType} */ (term.power));
+ }
+
+ // Get the gcd. This will be the p in (a^n+b^m)^p
+ // if the gcd equals 1 meaning n = m then we need a tie breakder
+ if (core.Utils.allSame(powers)) {
+ // Get p given x number of terms
+ const nTerms = symbol.length;
+ // The number of zeroes determines
+ const nZeroes = terms.length;
+ const den = Math.round((Math.sqrt(8 * nTerms - 1) - 3) / 2);
+ if (nZeroes === 2) {
+ p = new Frac(Number(powers[0]) / (nTerms - 1));
+ } else if (nZeroes === 3 && den !== 0) {
+ p = new Frac(Number(powers[0]) / den);
+ } else {
+ // P is just the gcd of the powers
+ p = core.Math2.QGCD.apply(null, /** @type {FracType[]} */ (powers));
+ }
+ /*
+ //get the lowest possible power
+ //e.g. given b^4+2*a^2*b^2+a^4, the power we're looking for would be 2
+ symbol.each(function(x) {
+ if(x.group === CB)
+ x.each(function(y) {
+ if(!p || y.power.lessThan(p))
+ //p = Number(y.power);
+ p = y.power;
+ });
+ else if(!p || x.power.lessThan(p))
+ //p = Number(x.power);
+ p = x.power;
+ });
+ */
+ } else {
+ // P is just the gcd of the powers
+ p = core.Math2.QGCD.apply(null, powers);
+ }
+
+ // If we don't have an integer then exit
+ if (!isInt(p)) {
+ return symbol; // Nothing to do
+ // exit();
+ }
+
+ // Build the factor
+ for (let i = 0; i < terms.length; i++) {
+ const t = terms[i];
+ const nFrac = /** @type {FracType} */ (t.power).clone().divide(/** @type {FracType} */ (p));
+ const n = Number(nFrac);
+ // Don't take squareroots of negatives
+ if ((Number(t.multiplier.num) < 0 || Number(t.multiplier.den) < 0) && n % 2 === 0) {
+ return symbol;
+ }
+ t.multiplier = new Frac(Number(t.multiplier) ** (1 / n));
+ t.power = /** @type {FracType} */ (p).clone();
+ sum = /** @type {NerdamerSymbolType} */ (_.add(sum, t));
+ }
+
+ // By now we have the factor of zeroes. We'll know if we got it right because
+ // we'll get a remainder of zero each time we divide by it
+ if (/** @type {NerdamerSymbolType} */ (sum).group !== CP) {
+ return symbol;
+ } // Nothing to do
+
+ while (true) {
+ const d = __.div(symbol.clone(), sum.clone());
+ if (/** @type {NerdamerSymbolType} */ (d[1]).equals(0)) {
+ symbol = /** @type {NerdamerSymbolType} */ (d[0]);
+ factors.add(sum.clone());
+ if (symbol.equals(1)) // We've reached 1 so done.
+ {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ }
+ return symbol;
+ },
+ factor(symbol, factors) {
+ core.Utils.checkTimeout();
+ const originalFactors = factors ? { ...factors.factors } : null;
+ const originalLength = factors ? factors.length : 0;
+ try {
+ let retval = __.Factor.factorInner(symbol, factors);
+ retval = retval.pushMinus();
+ return retval;
+ } catch (error) {
+ if (error.message === 'timeout') {
+ throw error;
+ }
+
+ if (factors && originalFactors) {
+ factors.factors = originalFactors;
+ factors.length = originalLength;
+ }
+ return symbol;
+ }
+ },
+ factorInner(symbol, factors) {
+ core.Utils.checkTimeout();
+ // Don't try to factor constants,
+ // do it with Math2.factor
+ if (symbol.isConstant()) {
+ if (symbol.isInteger()) {
+ return core.Math2.factor(Number(symbol.multiplier));
+ }
+ // Return symbol;
+ }
+
+ const _symbol = /** @type {NerdamerSymbolType} */ (_.parse(symbol));
+
+ // Functions may have been evaluated in parse()
+ // STILL don't try to factor constants
+ // do it with Math2.factor
+ if (_symbol.isConstant()) {
+ if (_symbol.isInteger()) {
+ return core.Math2.factor(Number(_symbol.multiplier));
+ }
+ return symbol;
+ }
+
+ // Shortcut 0 and 1
+ if (_symbol.equals(0) || _symbol.equals(1)) {
+ return _symbol;
+ }
+
+ let retval = __.Factor._factor(_symbol, factors);
+ if (retval.equals(symbol)) {
+ return retval;
+ }
+
+ // Shortcut 0 and 1 AGAIN after factor (which does eval)
+ if (retval.equals(0) || retval.equals(1)) {
+ return retval;
+ }
+
+ if (retval.group === CB) {
+ let t = new NerdamerSymbol(1);
+ const p = _.parse(retval.power);
+ // Store the multiplier and strip it
+ let m = _.parse(retval.multiplier);
+
+ retval.toUnitMultiplier();
+
+ /*
+ * NOTE: for sign issues with factor START DEBUGGING HERE
+ */
+ // move the sign to t
+ if (retval.multiplier.lessThan(0)) {
+ t.negate();
+ retval.negate();
+ }
+
+ retval.each(x => {
+ // Related to #566. Since the symbol's group may not have been properly
+ // updated, it's easier to just parse the symbol and have the parser
+ // do the update for us.
+
+ const factored = /** @type {NerdamerSymbolType} */ (_.parse(__.Factor._factor(x)));
+ m = /** @type {NerdamerSymbolType} */ (
+ _.multiply(m, NerdamerSymbol.create(factored.multiplier.toString()))
+ );
+ factored.toUnitMultiplier();
+
+ if (factored.group === CB) {
+ let _t = new NerdamerSymbol(1);
+ factored.each(y => {
+ const _factored = /** @type {NerdamerSymbolType} */ (_.parse(__.Factor._factor(y)));
+ if (_factored.group === CB) {
+ m = /** @type {NerdamerSymbolType} */ (
+ _.multiply(m, NerdamerSymbol.create(_factored.multiplier.toString()))
+ );
+ _factored.toUnitMultiplier();
+ }
+ _t = /** @type {NerdamerSymbolType} */ (_.multiply(_t, _factored));
+ });
+ _t = /** @type {NerdamerSymbolType} */ (
+ _.pow(_t, new NerdamerSymbol(factored.power.toString()))
+ );
+ t = /** @type {NerdamerSymbolType} */ (_.multiply(t, _t));
+ } else {
+ t = /** @type {NerdamerSymbolType} */ (_.multiply(t, factored));
+ }
+ });
+
+ // Put back the multiplier and power
+ const pow = /** @type {NerdamerSymbolType} */ (_.pow(t, p));
+ retval = /** @type {NerdamerSymbolType} */ (_.multiply(m, pow));
+ }
+ return retval;
+ },
+ quadFactor(symbol, factors) {
+ if (symbol.isPoly() && __.degree(symbol).equals(2)) {
+ // We've already checked that we're dealing with a polynomial
+ const v = core.Utils.variables(symbol)[0]; // Get the variable
+ const coeffs = __.coeffs(symbol, v);
+ // Factor the lead coefficient
+ if (coeffs.length < 3) {
+ return symbol;
+ }
+ const cf = __.Factor._factor(coeffs[2].clone());
+ // Check if we have factors
+ if (cf.group === CB) {
+ const symbols = /** @type {NerdamerSymbolType[]} */ (cf.collectSymbols());
+ // If the factors are greater than 2 we're done so exit
+ if (symbols.length > 2) {
+ return symbol;
+ }
+ // If we have two factors then attempt to factor the polynomial
+ // let the factors be f1 and f1
+ // let the factors be (ax+b)(cx+d)
+ // let the coefficients be c1x^2+c2x+c3
+ // then a(x1)+c(x2)=c2 and x1*x2=c3
+ // we can solve for x1 and x2
+ const c = /** @type {NerdamerSymbolType} */ (
+ _.multiply(_.parse(coeffs[0]), _.parse(symbols[0]))
+ );
+ const b = /** @type {NerdamerSymbolType} */ (_.parse(coeffs[1])).negate();
+ const a = /** @type {NerdamerSymbolType} */ (_.parse(symbols[1]));
+ // Solve the system
+ const root = __.quad(a, b, c).filter(x => core.Utils.isInt(x));
+ // If we have one root then find the other one by dividing the constant
+ if (root.length === 1) {
+ const root1 = root[0];
+ const root2 = _.divide(coeffs[0], /** @type {NerdamerSymbolType} */ (_.parse(root1)));
+ if (core.Utils.isInt(root2)) {
+ // We found them both
+ factors.add(
+ /** @type {NerdamerSymbolType} */ (
+ _.parse(format('({0})*({1})+({2})', String(symbols[1]), v, String(root2)))
+ )
+ );
+ factors.add(
+ /** @type {NerdamerSymbolType} */ (
+ _.parse(format('({0})*({1})+({2})', String(symbols[0]), v, root1))
+ )
+ );
+ symbol = new NerdamerSymbol(1);
+ }
+ }
+ }
+ // // sanitization: eliminate "-(-x)"
+ // for (let xk in symbol.symbols) {
+ // let x = symbol.symbols[xk];
+ // if ((x.group === CB || x.group === CP || x.group === PL) &&
+ // x.multiplier.equals(-1)) {
+ // console.log("replacing "+x)
+ // symbol[xk] = _.parse(x);
+ // console.log("with "+symbol[xk])
+ // }
+ // }
+ }
+ return symbol;
+ },
+ cubeFactor(symbol, factors) {
+ if (symbol.isComposite()) {
+ const symbols = /** @type {NerdamerSymbolType[]} */ (symbol.collectSymbols());
+ // The symbol should be in the form of a^3+-b^3. The length
+ // should therefore only be two. If it's any different from this
+ // then we're done
+ if (symbols.length === 2) {
+ // Store the signs and then strip them from the symbols
+ let signA = symbols[0].sign();
+ let a = symbols[0].clone().abs();
+ let signB = symbols[1].sign();
+ let b = symbols[1].clone().abs();
+ // Check if they're cube
+ if (a.isCube() && b.isCube()) {
+ // Keep the negative sign on the right, meaning b is always negative.
+ if (signA < signB) {
+ // Swap the signs and then the values
+ [signA, signB] = [signB, signA];
+ [a, b] = [b, a];
+ }
+
+ // Get teh roots
+ const mRootA = _.parse(a.getNth(3));
+ const mRootB = _.parse(b.getNth(3));
+
+ // Remove the cube for both
+ const x = _.multiply(_.expand(_.pow(a.clone().toUnitMultiplier(), _.parse('1/3'))), mRootA);
+ const y = _.multiply(_.expand(_.pow(b.clone().toUnitMultiplier(), _.parse('1/3'))), mRootB);
+
+ if (signA === 1 && signB === -1) {
+ // Apply difference of cubes rule
+ factors.add(_.parse(format('(({0})-({1}))', String(x), String(y))));
+ factors.add(_.parse(format('(({0})^2+({0})*({1})+({1})^2)', String(x), String(y))));
+ symbol = new NerdamerSymbol(1);
+ } else if (signA === 1 && signB === 1) {
+ // Apply sum of cubes rule
+ factors.add(_.parse(format('(({0})+({1}))', String(x), String(y))));
+ factors.add(_.parse(format('(({0})^2-({0})*({1})+({1})^2)', String(x), String(y))));
+ symbol = new NerdamerSymbol(1);
+ }
+ }
+ }
+ }
+
+ return symbol;
+ },
+ /**
+ * Internal factorization implementation
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {FactorsLike} [factors]
+ * @returns {NerdamerSymbolType}
+ */
+ _factor(symbol, factors) {
+ core.Utils.checkTimeout();
+ const _g = symbol.group;
+ // Some items cannot be factored any further so return those right away
+ if (symbol.group === FN) {
+ const arg = symbol.args[0];
+ if (arg.group === S && arg.isSimple()) {
+ return symbol;
+ }
+ } else if (symbol.group === S && symbol.isSimple()) {
+ return symbol;
+ }
+
+ // Expand the symbol to get it in a predictable form. If this step
+ // is skipped some factors are missed.
+ // if(symbol.group === CP && !(even(symbol.power) && symbol.multiplier.lessThan(0))) {
+ if (symbol.group === CP) {
+ symbol.distributeMultiplier(true);
+ let t = new NerdamerSymbol(0);
+ symbol.each(x => {
+ if ((x.group === CP && x.power.greaterThan(1)) || x.group === CB) {
+ x = /** @type {NerdamerSymbolType} */ (_.expand(x));
+ }
+ t = /** @type {NerdamerSymbolType} */ (_.add(t, x));
+ });
+ t.power = symbol.power;
+
+ symbol = t;
+ }
+
+ if (symbol.group === FN && symbol.fname !== 'sqrt') {
+ symbol = core.Utils.evaluate(symbol);
+ }
+
+ // Make a copy of the symbol to return if something goes wrong
+ const untouched = symbol.clone();
+ try {
+ if (symbol.group === CB) {
+ const _p = _.parse(symbol.power);
+
+ // Grab the denominator and strip the multiplier and power. Store them in an array
+ const denArray = __.Simplify.strip(symbol.getDenom());
+ const numArray = __.Simplify.strip(symbol.getNum());
+
+ const den = denArray.pop();
+ const num = numArray.pop();
+
+ // If the numerator equals the symbol then we've hit the simplest form and then we're done
+ if (num.equals(symbol)) {
+ return symbol;
+ }
+ const nfact = __.Factor.factorInner(num);
+ const dfact = __.Factor.factorInner(den);
+
+ const n = __.Simplify.unstrip(
+ /** @type {[NerdamerSymbolType, NerdamerSymbolType]} */ (/** @type {unknown} */ (numArray)),
+ nfact
+ );
+ const d = __.Simplify.unstrip(
+ /** @type {[NerdamerSymbolType, NerdamerSymbolType]} */ (/** @type {unknown} */ (denArray)),
+ dfact
+ );
+
+ const retval = /** @type {NerdamerSymbolType} */ (_.divide(n, d));
+
+ return retval;
+ }
+ if (symbol.group === S) {
+ return symbol; // Absolutely nothing to do
+ }
+
+ if (symbol.isConstant()) {
+ if (symbol.equals(1) || symbol.equals(0) || !symbol.isInteger()) {
+ return symbol.clone();
+ }
+ const ret = core.Math2.factor(Number(symbol.multiplier));
+ return ret;
+ }
+
+ const p = symbol.power.clone();
+
+ if (isInt(p) && !(p.lessThan(0) && symbol.group === FN)) {
+ const sign = p.sign();
+ symbol.toLinear();
+ factors ||= new Factors();
+ /** @type {Record<string, string>} */
+ const map = {};
+ symbol = /** @type {NerdamerSymbolType} */ (_.parse(core.Utils.subFunctions(symbol, map)));
+ if (keys(map).length > 0) {
+ // It might have functions
+ factors.preAdd = function preAdd(factor) {
+ const ret = _.parse(factor, core.Utils.getFunctionsSubs(map));
+ return /** @type {NerdamerSymbolType} */ (ret);
+ };
+ }
+
+ // Strip the power
+ if (!symbol.isLinear()) {
+ factors.pFactor = symbol.power.toString();
+ symbol.toLinear();
+ }
+
+ const vars = variables(symbol);
+ // Bypass for imaginary. TODO: find a better solution
+ if (symbol.isImaginary()) {
+ vars.push(core.Settings.IMAGINARY);
+ }
+ const multiVar = vars.length > 1;
+
+ // Minor optimization. Seems to cut factor time by half in some cases.
+ if (multiVar) {
+ let allS = true;
+ let allUnit = true;
+ symbol.each(x => {
+ if (x.group !== S) {
+ allS = false;
+ }
+ if (!x.multiplier.equals(1)) {
+ allUnit = false;
+ }
+ });
+
+ if (allS && allUnit) {
+ return /** @type {NerdamerSymbolType} */ (
+ _.pow(_.parse(symbol, core.Utils.getFunctionsSubs(map)), _.parse(p))
+ );
+ }
+ }
+
+ // Factor the coefficients
+ const coeffFactors = new Factors();
+
+ symbol = __.Factor.coeffFactor(symbol, coeffFactors);
+
+ coeffFactors.each(x => {
+ // If the factor was negative but was within a square then it becomes positive
+ if (even(Number(p)) && x.lessThan(0)) {
+ x.negate();
+ }
+
+ if (sign < 0) {
+ x.invert();
+ }
+ factors.add(x);
+ });
+
+ // Factor the power
+ const powerFactors = new Factors();
+ symbol = __.Factor.powerFactor(symbol, powerFactors);
+ powerFactors.each(x => {
+ if (sign < 0) {
+ x.invert();
+ }
+ factors.add(x);
+ });
+
+ if (multiVar) {
+ // Try sum and difference of cubes
+ symbol = __.Factor.cubeFactor(symbol, factors);
+
+ symbol = __.Factor.mfactor(symbol, factors);
+
+ // Put back the sign of power
+ factors.each(x => {
+ if (sign < 0) {
+ x.power.negate();
+ }
+ });
+ } else {
+ // Pass in vars[0] for safety
+ const v = vars[0];
+
+ symbol = __.Factor.squareFree(symbol, factors, v);
+
+ const tFactors = new Factors();
+
+ symbol = __.Factor.trialAndError(symbol, tFactors, v);
+
+ // Generate a symbol based off the last factors
+ const tfSymbol = tFactors.toSymbol();
+ // If nothing was factored then return the factors
+ if (tfSymbol.equals(untouched)) {
+ return tfSymbol;
+ }
+
+ for (const x in tFactors.factors) {
+ if (!Object.hasOwn(tFactors.factors, x)) {
+ continue;
+ }
+ // Store the current factor in tFactor
+ const tFactor = tFactors.factors[x];
+ factors.add(/** @type {NerdamerSymbolType} */ (_.pow(tFactor, _.parse(p))));
+ }
+ // If we still don't have a factor and it's quadratic then let's just do a quad factor
+ if (symbol.equals(untouched)) {
+ symbol = __.Factor.quadFactor(symbol, factors);
+ }
+ }
+
+ // Last minute clean up
+ symbol = /** @type {NerdamerSymbolType} */ (_.parse(symbol, core.Utils.getFunctionsSubs(map)));
+
+ const addPower = factors.length === 1;
+
+ factors.add(/** @type {NerdamerSymbolType} */ (_.pow(symbol, _.parse(p))));
+
+ let retval = factors.toSymbol();
+
+ // We may have only factored out the symbol itself so we end up with a factor of one
+ // where the power needs to be placed back
+ // e.g. factor((2*y+p)^2). Here we end up having a factor of 1 remaining and a p of 2.
+ if (addPower && symbol.equals(1) && retval.isLinear()) {
+ retval = /** @type {NerdamerSymbolType} */ (_.pow(retval, _.parse(p)));
+ }
+
+ return retval;
+ }
+
+ return symbol;
+ } catch (e) {
+ if (e?.message === 'timeout') {
+ throw e;
+ }
+ // No need to stop the show because something went wrong :). Just return the unfactored.
+ return untouched;
+ }
+ },
+ reduce(symbol, factors) {
+ if (symbol.group === CP && symbol.length === 2) {
+ const symbols = /** @type {NerdamerSymbolType[]} */ (symbol.collectSymbols()).sort(
+ (a, b) => Number(b.multiplier) - Number(a.multiplier)
+ );
+ if (/** @type {FracType} */ (symbols[0].power).equals(/** @type {FracType} */ (symbols[1].power))) {
+ // X^n-a^n
+ const n = /** @type {NerdamerSymbolType} */ (_.parse(symbols[0].power));
+ const a = symbols[0].clone().toLinear();
+ const b = symbols[1].clone().toLinear();
+
+ // Apply rule: (a-b)*sum(a^(n-i)*b^(i-1),1,n)
+ factors.add(/** @type {NerdamerSymbolType} */ (_.add(a.clone(), b.clone())));
+ // Flip the sign
+ b.negate();
+ // Turn n into a number
+ const nn = Number(n);
+ // The remainder
+ let result = new NerdamerSymbol(0);
+ for (let i = 1; i <= nn; i++) {
+ const aa = /** @type {NerdamerSymbolType} */ (
+ _.pow(a.clone(), _.subtract(n.clone(), new NerdamerSymbol(i)))
+ );
+ const bb = /** @type {NerdamerSymbolType} */ (
+ _.pow(b.clone(), _.subtract(new NerdamerSymbol(i), new NerdamerSymbol(1)))
+ );
+ result = /** @type {NerdamerSymbolType} */ (
+ _.add(result, /** @type {NerdamerSymbolType} */ (_.multiply(aa, bb)))
+ );
+ }
+ return result;
+ }
+ }
+ return symbol;
+ },
+ /**
+ * Makes NerdamerSymbol square free
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {Factors} factors
+ * @param {string} [variable] The variable which is being factored
+ * @returns {NerdamerSymbolType}
+ */
+ squareFree(symbol, factors, variable) {
+ if (symbol.isConstant() || symbol.group === S) {
+ return symbol;
+ }
+
+ if (!symbol.isPoly()) {
+ return symbol;
+ }
+
+ const poly = new Polynomial(symbol, variable);
+ const sqfr = poly.squareFree();
+ const p = sqfr[2];
+ // If we found a square then the p entry in the array will be non-unit
+ if (p !== 1) {
+ // Make sure the remainder doesn't have factors
+ const t = sqfr[1].toSymbol();
+ t.power = /** @type {FracType} */ (t.power).multiply(new Frac(p));
+ // Send the factor to be fatored to be sure it's completely factored
+ factors.add(__.Factor.factorInner(t));
+
+ const retval = __.Factor.squareFree(sqfr[0].toSymbol(), factors);
+
+ return retval;
+ }
+
+ return symbol;
+ },
+ /**
+ * Factors the powers such that the lowest power is a constant
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {Factors} factors
+ * @returns {NerdamerSymbolType}
+ */
+ powerFactor(symbol, factors) {
+ // Only PL need apply
+ if (symbol.group !== PL || symbol.previousGroup === EX) {
+ return symbol;
+ }
+ const k = keys(symbol.symbols);
+ // We expect only numeric powers so return all else
+ if (!core.Utils.allNumeric(k)) {
+ return symbol;
+ }
+
+ const d = core.Utils.arrayMin(/** @type {number[]} */ (/** @type {unknown} */ (k)));
+ let retval = new NerdamerSymbol(0);
+ const q = /** @type {NerdamerSymbolType} */ (_.parse(`${symbol.value}^${d}`));
+ symbol.each(x => {
+ x = /** @type {NerdamerSymbolType} */ (_.divide(x, q.clone()));
+ retval = /** @type {NerdamerSymbolType} */ (_.add(retval, x));
+ });
+
+ factors.add(q);
+ return retval;
+ },
+ /**
+ * Removes GCD from coefficients
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {Factors} factors
+ * @returns {NerdamerSymbolType}
+ */
+ coeffFactor(symbol, factors) {
+ if (symbol.isComposite()) {
+ const gcd = core.Math2.QGCD.apply(null, symbol.coeffs());
+
+ if (gcd.equals(1)) {
+ // TODO: This should probably go to the prototype
+ const power = function (sym) {
+ let p;
+ if (sym.group === CB) {
+ p = 0;
+ sym.each(x => {
+ p += x.power;
+ });
+ } else {
+ p = Number(sym.power);
+ }
+ return p;
+ };
+ // Factor out negatives from the lead term
+ const terms = /** @type {NerdamerSymbolType[]} */ (
+ symbol.collectSymbols(null, null, null, true)
+ ).sort((a, b) => {
+ // Push constants to the back
+ if (a.isConstant(true)) {
+ return 1;
+ }
+ return Number(b.power) - Number(a.power);
+ });
+
+ const LT = terms[0];
+
+ // Check if the LT is indeed the greatest
+ if (power(LT) > power(terms[1]) || terms[1].isConstant(true)) {
+ if (LT.multiplier.lessThan(0)) {
+ // Although the symbol should always be linear at this point, remove the negative for squares
+ // to be safe.
+ factors.add(new NerdamerSymbol(-1));
+
+ symbol.each(x => {
+ x.negate();
+ }, true);
+ }
+ }
+ } else {
+ symbol.each(x => {
+ if (x.isComposite()) {
+ x.each(y => {
+ y.multiplier = y.multiplier.divide(gcd);
+ });
+ } else {
+ x.multiplier = x.multiplier.divide(gcd);
+ }
+ });
+ symbol.updateHash();
+ }
+
+ if (factors) {
+ factors.add(new NerdamerSymbol(gcd));
+ }
+ }
+
+ return symbol;
+ },
+ /**
+ * The name says it all :)
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {Factors} factors
+ * @param {string} variable
+ * @returns {NerdamerSymbolType}
+ */
+ trialAndError(symbol, factors, variable) {
+ const untouched = symbol.clone();
+ try {
+ // At temp holder for the factors. If all goes well then
+ // they'll be moved to the actual factors.
+ const factorArray = [];
+
+ if (symbol.isConstant() || symbol.group === S || !symbol.isPoly()) {
+ return symbol;
+ }
+ let poly = new Polynomial(symbol, variable);
+ const cnst = poly.coeffs[0];
+ const cfactors = core.Math2.ifactor(Number(cnst));
+ const roots = __.proots(symbol);
+ for (let i = 0; i < roots.length; i++) {
+ let r = roots[i];
+ /** @type {number} */
+ let p = 1;
+ if (!isNaN(Number(r))) {
+ // If it's a number
+ for (const x in cfactors) {
+ if (!Object.hasOwn(cfactors, x)) {
+ continue;
+ }
+ // Check it's raised to a power
+ const n = core.Utils.round(Math.log(Number(x)) / Math.log(Math.abs(Number(r))), 8);
+ if (isInt(n)) {
+ r = x; // X must be the root since n gave us a whole
+ p = Number(n);
+ break;
+ }
+ }
+ const root = new Frac(Number(r));
+ const terms = [new Frac(Number(root.num)).negate()];
+ terms[p] = new Frac(Number(root.den));
+ // Convert to Frac. The den is coeff of LT and the num is coeff of constant
+ const div = Polynomial.fromArray(terms, poly.variable).fill();
+ const t = poly.divide(div);
+ if (t[1].equalsNumber(0)) {
+ // If it's zero we have a root and divide it out
+ poly = t[0];
+ // Factors.add(div.toSymbol());
+ factorArray.push(div.toSymbol());
+ }
+ }
+ }
+
+ if (!poly.equalsNumber(1)) {
+ poly = __.Factor.search(poly, factors);
+ }
+
+ // Move the factors over since all went well.
+ factorArray.forEach(x => {
+ factors.add(x);
+ });
+
+ return poly.toSymbol();
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ return untouched;
+ }
+ },
+ search(poly, factors, base) {
+ base ||= 10; // I like 10 because numbers exhibit similar behaviours at 10
+ const v = poly.variable; // The polynmial variable name
+ /**
+ * Attempt to remove a root by division given a number by first creating a polynomial fromt he given
+ * information
+ *
+ * @param {number} c1 - Coeffient for the constant
+ * @param {number} c2 - Coefficient for the LT
+ * @param {number} n - The number to be used to construct the polynomial
+ * @param {number} p - The power at which to create the polynomial
+ * @returns {null | [Polynomial, Polynomial]} - Returns polynomial array if successful otherwise null
+ */
+ const check = function (c1, c2, n, p) {
+ const candidate = Polynomial.fit(c1, c2, n, base, p, v);
+ if (candidate && candidate.coeffs.length > 1) {
+ const t = poly.divide(candidate);
+ if (t[1].equalsNumber(0)) {
+ factors.add(candidate.toSymbol());
+ return [t[0], candidate];
+ }
+ }
+ return null;
+ };
+ const cnst = poly.coeffs[0];
+ const cfactors = core.Math2.ifactor(Number(cnst));
+ const lc = poly.lc();
+ const ltfactors = core.Math2.ifactor(Number(lc));
+ const subbed = poly.sub(base);
+ const isubbed = core.Math2.ifactor(/** @type {number} */ (/** @type {unknown} */ (subbed)));
+ const nfactors = __.Factor.mix(isubbed, /** @type {number} */ (/** @type {unknown} */ (subbed)) < 0);
+ let cp = Math.ceil(poly.coeffs.length / 2);
+ const lcIsNeg = lc.lessThan(0);
+ const cnstIsNeg = cnst.lessThan(0);
+ ltfactors['1'] = 1;
+ cfactors['1'] = 1;
+ while (cp--) {
+ for (const x in ltfactors) {
+ if (!Object.hasOwn(ltfactors, x)) {
+ continue;
+ }
+ for (const y in cfactors) {
+ if (!Object.hasOwn(cfactors, y)) {
+ continue;
+ }
+ for (let i = 0; i < nfactors.length; i++) {
+ let factorFound = check(Number(x), Number(y), nfactors[i], cp);
+ if (factorFound) {
+ poly = factorFound[0];
+ if (
+ !core.Utils.isPrime(
+ /** @type {number} */ (/** @type {unknown} */ (poly.sub(base)))
+ )
+ ) {
+ poly = __.Factor.search(poly, factors);
+ }
+ return poly;
+ }
+ if (!factorFound) {
+ if (lcIsNeg && cnstIsNeg) {
+ factorFound = check(-Number(x), -Number(y), nfactors[i], cp);
+ } else if (lcIsNeg) {
+ factorFound = check(-Number(x), Number(y), nfactors[i], cp);
+ } // Check a negative lc
+ else if (cnstIsNeg) {
+ factorFound = check(Number(x), -Number(y), nfactors[i], cp);
+ } // Check a negative constant
+ }
+ }
+ }
+ }
+ }
+ return poly;
+ },
+ /**
+ * Equivalent of square free factor for multivariate polynomials
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {Factors} factors
+ * @returns {NerdamerSymbolType}
+ */
+ mSqfrFactor(symbol, factors) {
+ if (symbol.group !== FN) {
+ const vars = variables(symbol).reverse();
+
+ // Loop through all the variable and remove the partial derivatives
+ for (let i = 0; i < vars.length; i++) {
+ let isFactor = false;
+ do {
+ if (vars[i] === symbol.value) {
+ // The derivative tells us nothing since this symbol is already the factor
+ factors.add(symbol);
+ symbol = new NerdamerSymbol(1);
+ continue;
+ }
+
+ const diff = core.Calculus.diff(symbol, vars[i]);
+
+ const d = __.Factor.coeffFactor(diff);
+
+ if (d.equals(0)) {
+ break;
+ }
+
+ // Sometimes nerdamer get too happy about factoring out 1 and -1
+ if (d.equals(1) || d.equals(-1)) {
+ break;
+ }
+
+ // Trial division to see if factors have whole numbers.
+ // This can be optimized by stopping as soon as canDivide is false
+ // this will also need utilize big number at some point
+ let canDivide = true;
+ if (d.isConstant() && symbol.isComposite()) {
+ // Check the coefficients
+
+ symbol.each(x => {
+ if (Number(x.multiplier) % Number(d.multiplier) !== 0) {
+ canDivide = false;
+ }
+ }, true);
+ }
+
+ // If we can divide then do so
+ let div;
+ if (canDivide) {
+ const s = symbol.clone();
+ div = __.divWithCheck(symbol, d.clone());
+ isFactor = /** @type {NerdamerSymbolType} */ (div[1]).equals(0);
+
+ // Break infinite loop for factoring e^t*x-1
+ if (
+ symbol.equals(/** @type {NerdamerSymbolType} */ (div[0])) &&
+ /** @type {NerdamerSymbolType} */ (div[1]).equals(0)
+ ) {
+ // Restore symbol, was mangled in __.div
+ symbol = s;
+ break;
+ }
+
+ if (/** @type {NerdamerSymbolType} */ (div[0]).isConstant()) {
+ factors.add(/** @type {NerdamerSymbolType} */ (div[0]));
+ break;
+ }
+ } else {
+ isFactor = false;
+ }
+
+ if (isFactor) {
+ factors.add(/** @type {NerdamerSymbolType} */ (div[0]));
+ symbol = d;
+ }
+ } while (isFactor);
+ }
+ }
+
+ return symbol;
+ },
+ // Difference of squares factorization
+ sqdiff(symbol, factors) {
+ if (symbol.isConstant('all')) {
+ // Nothing to do
+ return symbol;
+ }
+
+ try {
+ const removeSquare = function (x) {
+ return core.Utils.block(
+ 'POSITIVE_MULTIPLIERS',
+ () => NerdamerSymbol.unwrapPARENS(math.sqrt(math.abs(x))),
+ true
+ );
+ };
+ const separated = core.Utils.separate(symbol.clone());
+ if (!separated) {
+ return symbol;
+ }
+
+ const objArray = [];
+
+ // Get the unique variables
+ for (const x in separated) {
+ if (x !== 'constants') {
+ objArray.push(separated[x]);
+ }
+ }
+ objArray.sort((a, b) => Number(b.power) - Number(a.power));
+
+ // If we have the same number of variables as unique variables then we can apply the difference of squares
+ if (objArray.length === 2) {
+ let a;
+ let b;
+ a = objArray.pop();
+ b = objArray.pop();
+
+ if (
+ even(Number(a.power)) &&
+ even(Number(b.power)) &&
+ a.sign() === b.sign() &&
+ a.group === S &&
+ b.group === S
+ ) {
+ throw new Error('Unable to factor');
+ }
+ if (a.isComposite() && /** @type {FracType} */ (b.power).equals(2) && a.sign() !== b.sign()) {
+ // Remove the square from b
+ b = removeSquare(b);
+ const f = __.Factor.factorInner(
+ /** @type {NerdamerSymbolType} */ (_.add(a, separated.constants))
+ );
+ if (/** @type {FracType} */ (f.power).equals(2)) {
+ f.toLinear();
+ factors.add(/** @type {NerdamerSymbolType} */ (_.subtract(f.clone(), b.clone())));
+ factors.add(/** @type {NerdamerSymbolType} */ (_.add(f, b)));
+ symbol = new NerdamerSymbol(1);
+ }
+ } else {
+ a = a.powSimp();
+ b = b.powSimp();
+
+ if (
+ (a.group === S || a.fname === '') &&
+ a.power.equals(2) &&
+ (b.group === S || b.fname === '') &&
+ b.power.equals(2) &&
+ !separated.constants
+ ) {
+ if (a.multiplier.lessThan(0)) {
+ const t = b;
+ b = a;
+ a = t;
+ }
+ if (a.multiplier.greaterThan(0)) {
+ a = removeSquare(a);
+ b = removeSquare(b);
+ }
+
+ factors.add(/** @type {NerdamerSymbolType} */ (_.subtract(a.clone(), b.clone())));
+ factors.add(/** @type {NerdamerSymbolType} */ (_.add(a, b)));
+ symbol = new NerdamerSymbol(1);
+ }
+ }
+ }
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ }
+
+ return symbol;
+ },
+ // Factoring for multivariate
+ /**
+ * Factoring for multivariate polynomials
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {FactorsLike} factors
+ * @returns {NerdamerSymbolType}
+ */
+ mfactor(symbol, factors) {
+ if (symbol.group === FN) {
+ if (symbol.fname === 'sqrt') {
+ const factors2 = new Factors();
+ let arg = __.Factor.common(symbol.args[0].clone(), factors2);
+ arg = __.Factor.coeffFactor(arg, null);
+ symbol = /** @type {NerdamerSymbolType} */ (
+ _.multiply(_.symfunction('sqrt', [arg]), _.parse(symbol.multiplier))
+ );
+ factors2.each(x => {
+ symbol = /** @type {NerdamerSymbolType} */ (
+ _.multiply(symbol, _.parse(core.Utils.format('sqrt({0})', String(x))))
+ );
+ });
+ } else {
+ factors.add(symbol);
+ symbol = new NerdamerSymbol(1);
+ }
+ } else {
+ // Square free factorization
+ symbol = __.Factor.mSqfrFactor(symbol, factors);
+
+ // Try factor out common factors
+ // symbol = __.Factor.common(symbol, factors);
+
+ const vars = variables(symbol);
+ const symbols = /** @type {NerdamerSymbolType[]} */ (
+ symbol
+ .collectSymbols()
+ .map(x => NerdamerSymbol.unwrapSQRT(/** @type {NerdamerSymbolType} */ (x)))
+ );
+ const sorted = {};
+ const maxes = {};
+ const l = vars.length;
+ const n = symbols.length;
+ // Take all the variables in the symbol and organize by variable name
+ // e.g. a^2+a^2+b*a -> {a: {a^3, a^2, b*a}, b: {b*a}}
+
+ for (let i = 0; i < l; i++) {
+ const v = vars[i];
+ sorted[v] = new NerdamerSymbol(0);
+ for (let j = 0; j < n; j++) {
+ const s = symbols[j];
+ if (s.contains(v)) {
+ const p =
+ s.value === v
+ ? /** @type {FracType} */ (s.power).toDecimal()
+ : /** @type {FracType} */ (s.symbols[v].power).toDecimal();
+ if (!maxes[v] || p < maxes[v]) {
+ maxes[v] = p;
+ }
+ sorted[v] = /** @type {NerdamerSymbolType} */ (_.add(sorted[v], s.clone()));
+ }
+ }
+ }
+
+ for (const x in sorted) {
+ if (!Object.hasOwn(sorted, x)) {
+ continue;
+ }
+ const r = /** @type {NerdamerSymbolType} */ (_.parse(`${x}^${maxes[x]}`));
+ const div = /** @type {NerdamerSymbolType} */ (_.divide(sorted[x], r));
+ const newFactor = /** @type {NerdamerSymbolType} */ (_.expand(div));
+
+ if (newFactor.equals(1) || newFactor.equals(-1)) {
+ break;
+ } // Why divide by one. Just move
+ const divided = __.div(symbol.clone(), newFactor);
+
+ if (/** @type {NerdamerSymbolType} */ (divided[0]).equals(0)) {
+ // Cant factor anymore
+ break;
+ }
+
+ // We potentially ended up with fractional coefficients when the
+ // trial division was performed. We need to remove
+ // This check will more then likely become superfluous with improvements
+ // to polynomial division
+ if (/** @type {NerdamerSymbolType} */ (divided[1]).equals(0)) {
+ let hasFractions = false;
+
+ /** @type {NerdamerSymbolType} */ (divided[0]).each(elem => {
+ if (!isInt(elem.multiplier)) {
+ hasFractions = true;
+ }
+ });
+
+ // The factor isn't really a factor and needs to be put back
+ if (hasFractions) {
+ divided[1] = /** @type {NerdamerSymbolType} */ (
+ _.expand(_.multiply(divided[1], newFactor))
+ );
+ // Since the new factor is not just one, we exit.
+ break;
+ }
+ }
+
+ const negNumericFactor =
+ isInt(newFactor) && /** @type {NerdamerSymbolType} */ (newFactor).lessThan(0);
+
+ if (/** @type {NerdamerSymbolType} */ (divided[1]).equals(0) && !negNumericFactor) {
+ // We found at least one factor
+
+ // factors.add(newFactor);
+ const d = __.divWithCheck(
+ symbol.clone(),
+ /** @type {NerdamerSymbolType} */ (divided[0]).clone()
+ );
+ const innerR = /** @type {NerdamerSymbolType} */ (d[0]);
+
+ // Nothing left to do since we didn't get a reduction
+ if (innerR.equals(0)) {
+ return symbol;
+ }
+
+ symbol = /** @type {NerdamerSymbolType} */ (d[1]);
+ // We don't want to just flip the sign. If the remainder is -1 then we accomplished nothing
+ // and we just return the symbol;
+ // If r equals zero then there's nothing left to do so we're done
+
+ if (innerR.equals(-1) && !symbol.equals(0)) {
+ return symbol;
+ }
+
+ const factor = /** @type {NerdamerSymbolType} */ (divided[0]);
+
+ if (symbol.equals(factor)) {
+ const rem = __.Factor.reduce(factor, factors);
+
+ if (!symbol.equals(rem)) {
+ return __.Factor.mfactor(rem, factors);
+ }
+
+ return rem;
+ }
+ factors.add(factor);
+ // If the remainder of the symbol is zero then we're done. TODO: Rethink this logic a bit.
+ if (symbol.equals(0)) {
+ return innerR;
+ }
+
+ if (innerR.isConstant('all')) {
+ factors.add(innerR);
+ return innerR;
+ }
+
+ symbol = __.Factor.mfactor(innerR, factors);
+ // // sanitization: eliminate "-(-x)"
+ // for (let xk in symbol.symbols) {
+ // let x = symbol.symbols[xk];
+ // if ((x.group === CB || x.group === CP || x.group === PL) &&
+ // x.multiplier.equals(-1)) {
+ // console.log("replacing "+x)
+ // symbol[xk] = _.parse(x);
+ // console.log("with "+symbol[xk])
+ // }
+ // }
+ return symbol;
+ }
+ }
+ }
+
+ // Difference of squares factorization
+ symbol = __.Factor.sqdiff(symbol, factors);
+
+ // Factors by fishing for zeroes
+ symbol = __.Factor.zeroes(symbol, factors);
+
+ // // sanitization: eliminate "-(-x)"
+ // for (let xk in symbol.symbols) {
+ // let x = symbol.symbols[xk];
+ // if ((x.group === CB || x.group === CP || x.group === PL) &&
+ // x.multiplier.equals(-1)) {
+ // console.log("replacing "+x)
+ // symbol[xk] = _.parse(x);
+ // console.log("with "+symbol[xk])
+ // }
+ // }
+ return symbol;
+ },
+ },
+ /**
+ * Checks to see if a set of "equations" is linear.
+ *
+ * @param {Array} s - The set of equations to check
+ * @returns {boolean}
+ */
+ allLinear(s) {
+ const l = s.length;
+ for (let i = 0; i < l; i++) {
+ if (!__.isLinear(s[i])) {
+ return false;
+ }
+ }
+ return true;
+ },
+ /*
+ * Checks to see if the "equation" is linear
+ * @param {NerdamerSymbolType} e
+ * @returns {boolean}
+ */
+ isLinear(e) {
+ let status = false;
+ const g = e.group;
+ if (g === PL || g === CP) {
+ status = true;
+ for (const s in e.symbols) {
+ if (!Object.hasOwn(e.symbols, s)) {
+ continue;
+ }
+ const symbol = e.symbols[s];
+ const sg = symbol.group;
+ if (sg === FN || sg === EX) {
+ status = false;
+ }
+ if (sg === CB) {
+ // Needs further checking since it might be imaginary
+ status = variables(symbol).length === 1;
+ } else if (sg === PL || sg === CP) {
+ status = __.isLinear(symbol);
+ } else if (symbol.group !== N && symbol.power.toString() !== '1') {
+ status = false;
+ break;
+ }
+ }
+ } else if (g === S && /** @type {number} */ (/** @type {unknown} */ (e.power)) === 1) {
+ status = true;
+ }
+ return status;
+ },
+ gcd(...rest) {
+ let args;
+ if (rest.length === 1 && rest[0] instanceof core.Vector) {
+ args = rest[0].elements;
+ } else {
+ args = rest;
+ }
+
+ // Short-circuit early
+ if (args.length === 0) {
+ return new NerdamerSymbol(1);
+ }
+ if (args.length === 1) {
+ return args[0];
+ }
+
+ let appeared = [];
+ let evaluate = false;
+ for (let i = 0; i < args.length; i++) {
+ const arg = /** @type {NerdamerSymbolType} */ (args[i]);
+ if (arg.group === FN && arg.fname === 'gcd') {
+ // Compress gcd(a,gcd(b,c)) into gcd(a,b,c)
+ args = args.concat(arg.args);
+ // Do not keep gcd in args
+ args.splice(i, 1);
+ } else {
+ // Look if there are any common variables such that
+ // gcd(a,b) => gcd(a,b); gcd(a,a) => a
+ const vars = variables(arg);
+ if (core.Utils.haveIntersection(vars, appeared)) {
+ // Ok, there are common variables
+ evaluate = true;
+ break;
+ } else {
+ appeared = appeared.concat(vars);
+ }
+ }
+ }
+
+ // Appeared.length is 0 when all arguments are group N
+ if (evaluate || appeared.length === 0) {
+ // TODO: distribute exponent so that (a^-1*b^-1)^-1 => a*b
+ if (
+ args.every(symbol =>
+ /** @type {NerdamerSymbolType} */ (
+ /** @type {NerdamerSymbolType} */ (symbol).getDenom()
+ ).equals(1)
+ )
+ ) {
+ let aggregate = /** @type {NerdamerSymbolType} */ (args[0]);
+
+ for (let i = 1; i < args.length; i++) {
+ aggregate = /** @type {NerdamerSymbolType} */ (
+ __.gcd_(/** @type {NerdamerSymbolType} */ (args[i]), aggregate)
+ );
+ }
+ return aggregate;
+ }
+ // Gcd_ cannot handle denominators correctly
+ return _.divide(
+ __.gcd.apply(
+ null,
+ /** @type {NerdamerSymbolType[]} */ (
+ args.map(
+ symbol =>
+ /** @type {NerdamerSymbolType} */ (
+ /** @type {NerdamerSymbolType} */ (symbol).getNum()
+ )
+ )
+ )
+ ),
+ __.lcm.apply(
+ null,
+ /** @type {NerdamerSymbolType[]} */ (
+ args.map(
+ symbol =>
+ /** @type {NerdamerSymbolType} */ (
+ /** @type {NerdamerSymbolType} */ (symbol).getDenom()
+ )
+ )
+ )
+ )
+ );
+ }
+ return _.symfunction('gcd', args);
+ },
+ gcd_(a, b) {
+ if (a.group === FN || a.group === P) {
+ a = /** @type {NerdamerSymbolType} */ (core.Utils.block('PARSE2NUMBER', () => _.parse(a)));
+ }
+ if (b.group === FN || b.group === P) {
+ b = /** @type {NerdamerSymbolType} */ (core.Utils.block('PARSE2NUMBER', () => _.parse(b)));
+ }
+
+ if (b.group === FN) {
+ b = /** @type {NerdamerSymbolType} */ (core.Utils.block('PARSE2NUMBER', () => _.parse(b)));
+ }
+
+ if (a.isConstant() && b.isConstant()) {
+ // Return core.Math2.QGCD(new Frac(Number(a)), new Frac(Number(b)));
+ return new NerdamerSymbol(core.Math2.QGCD(new Frac(Number(a)), new Frac(Number(b))));
+ }
+
+ const den = /** @type {NerdamerSymbolType} */ (
+ _.multiply(
+ /** @type {NerdamerSymbolType} */ (a.getDenom()) || new NerdamerSymbol(1),
+ /** @type {NerdamerSymbolType} */ (b.getDenom()) || new NerdamerSymbol(1)
+ )
+ ).invert();
+ a = /** @type {NerdamerSymbolType} */ (_.multiply(a.clone(), den.clone()));
+ b = /** @type {NerdamerSymbolType} */ (_.multiply(b.clone(), den.clone()));
+
+ // Feels counter intuitive but it works. Issue #123 (nerdamer("gcd(x+y,(x+y)^2)"))
+ a = /** @type {NerdamerSymbolType} */ (_.expand(a));
+ b = /** @type {NerdamerSymbolType} */ (_.expand(b));
+
+ if (a.group === CB || b.group === CB) {
+ const q = /** @type {NerdamerSymbolType} */ (_.divide(a.clone(), b.clone())); // Get the quotient
+ const t = /** @type {NerdamerSymbolType} */ (_.multiply(b.clone(), q.getDenom().invert())); // Multiply by the denominator
+ // if they have a common factor then the result will not equal one
+ if (!t.equals(1)) {
+ return t;
+ }
+ }
+
+ // Just take the gcd of each component when either of them is in group EX
+ if (a.group === EX || b.group === EX) {
+ const gcdM = new NerdamerSymbol(core.Math2.QGCD(a.multiplier, b.multiplier));
+ const gcdV = __.gcd_(
+ a.value === CONST_HASH
+ ? new NerdamerSymbol(1)
+ : /** @type {NerdamerSymbolType} */ (_.parse(a.value)),
+ b.value === CONST_HASH
+ ? new NerdamerSymbol(1)
+ : /** @type {NerdamerSymbolType} */ (_.parse(b.value))
+ );
+ const gcdP = __.gcd_(
+ /** @type {NerdamerSymbolType} */ (_.parse(a.power)),
+ /** @type {NerdamerSymbolType} */ (_.parse(b.power))
+ );
+ return _.multiply(gcdM, _.pow(gcdV, gcdP));
+ }
+
+ if (a.length < b.length) {
+ // Swap'm
+ const t = a;
+ a = b;
+ b = t;
+ }
+ const varsA = variables(a);
+ const varsB = variables(b);
+
+ // GCD of a polynomial and a constant: gcd(poly, const) = gcd of coefficients with const
+ // For symbolic variables, gcd(a, 1) = 1, gcd(a, 0) = a
+ if ((varsA.length === 1 && varsB.length === 0) || (varsA.length === 0 && varsB.length === 1)) {
+ // One is a variable/polynomial, one is a constant
+ const polySymbol = varsA.length === 1 ? a : b;
+ const constSymbol = varsA.length === 0 ? a : b;
+
+ if (constSymbol.equals(0)) {
+ return polySymbol;
+ }
+ // GCD of polynomial with non-zero constant
+ // For symbolic case, this is just the gcd of coefficients
+ return new NerdamerSymbol(core.Math2.QGCD(polySymbol.multiplier, constSymbol.multiplier));
+ }
+
+ if (varsA.length === varsB.length && varsA.length === 1 && varsA[0] === varsB[0]) {
+ const polyA = new Polynomial(a);
+ const polyB = new Polynomial(b);
+ return _.divide(polyA.gcd(polyB).toSymbol(), den);
+ }
+ // Get the gcd of the multipiers
+ // get rid of gcd in coeffs
+ const multipliers = [];
+ a.each(x => {
+ multipliers.push(x.multiplier);
+ });
+ b.each(x => {
+ multipliers.push(x.multiplier);
+ });
+
+ let T;
+ while (!b.equals(0)) {
+ const t = b.clone();
+ a = a.clone();
+ T = __.div(a, t);
+
+ b = /** @type {NerdamerSymbolType} */ (T[1]);
+ if (/** @type {NerdamerSymbolType} */ (T[0]).equals(0)) {
+ // Return _.multiply(new NerdamerSymbol(core.Math2.QGCD(a.multiplier, b.multiplier)), b);
+ return _.divide(new NerdamerSymbol(core.Math2.QGCD(a.multiplier, b.multiplier)), den);
+ }
+ a = t;
+ }
+
+ const gcd = core.Math2.QGCD.apply(undefined, multipliers);
+
+ if (!gcd.equals(1)) {
+ a.each(x => {
+ x.multiplier = x.multiplier.divide(gcd);
+ });
+ }
+
+ // Return symbolic function for gcd in indeterminate form
+ if (a.equals(1) && !a.isConstant() && !b.isConstant()) {
+ return _.divide(_.symfunction('gcd', [a, b]), den);
+ }
+
+ return _.divide(a, den);
+ },
+ lcm(...rest) {
+ // https://math.stackexchange.com/a/319310
+ // generalization of the 2-variable formula of lcm
+
+ let args;
+ if (rest.length === 1) {
+ if (rest[0] instanceof core.Vector) {
+ args = rest[0].elements;
+ } else {
+ _.error('lcm expects either 1 vector or 2 or more arguments');
+ }
+ } else {
+ args = rest;
+ }
+
+ // Product of all arguments
+ // start with new NerdamerSymbol(1) so that prev.clone() which makes unnessesary clones can be avoided
+ const numer = args.reduce((prev, curr) => _.multiply(prev, curr.clone()), new NerdamerSymbol(1));
+
+ // Gcd of complementary terms
+ const denomArgs =
+ // https://stackoverflow.com/a/18223072
+ // take all complementary terms, e.g.
+ // [a,b,c] => [a*b, b*c, a*c]
+ // [a,b,c,d] => [a*b*c, a*b*d, a*c*d, b*c*d]
+ /** @type {NerdamerSymbolType[]} */ (
+ (function generateComplementTerms(input, size) {
+ size = Number(size);
+ const results = [];
+ let result;
+ let mask;
+ let i;
+ const total = 2 ** input.length;
+ for (mask = size; mask < total; mask++) {
+ result = [];
+ i = input.length - 1;
+
+ do {
+ // eslint-disable-next-line no-bitwise -- Bit masking for combinatorial generation
+ if ((mask & (1 << i)) !== 0) {
+ result.push(input[i]);
+ }
+ } while (i--);
+
+ if (result.length === size) {
+ results.push(result);
+ }
+ }
+ return results;
+ // Start with new NerdamerSymbol(1) so that prev.clone() which makes unnessesary clones can be avoided
+ })(args, args.length - 1).map(x =>
+ x.reduce(
+ (prev, curr) => /** @type {NerdamerSymbolType} */ (_.multiply(prev, curr.clone())),
+ new NerdamerSymbol(1)
+ )
+ )
+ );
+
+ let denom;
+ // Don't eat the gcd term if all arguments are symbols
+ if (args.every(x => core.Utils.isVariableSymbol(x))) {
+ denom = _.symfunction('gcd', core.Utils.arrayUnique(denomArgs));
+ } else {
+ denom = __.gcd.apply(
+ null,
+ /** @type {[NerdamerSymbolType, NerdamerSymbolType, ...NerdamerSymbolType[]]} */ (denomArgs)
+ );
+ }
+ // Divide product of all arguments by gcd of complementary terms
+ const div = _.divide(numer, denom);
+ return div;
+ },
+ /**
+ * Divides one expression by another
+ *
+ * @param {NerdamerSymbolType} symbol1
+ * @param {NerdamerSymbolType} symbol2
+ * @returns {NerdamerSymbolType}
+ */
+ divide(symbol1, symbol2) {
+ let den;
+ const factored = /** @type {NerdamerSymbolType} */ (__.Factor.factorInner(symbol1.clone()));
+ den = factored.getDenom();
+ if (den.isConstant('all')) {
+ // Reset the denominator since we're not dividing by it anymore
+ den = new NerdamerSymbol(1);
+ } else {
+ symbol1 = /** @type {NerdamerSymbolType} */ (
+ _.expand(
+ NerdamerSymbol.unwrapPARENS(
+ /** @type {NerdamerSymbolType} */ (_.multiply(factored, den.clone()))
+ )
+ )
+ );
+ }
+ const result = __.div(symbol1, symbol2);
+ const remainder = /** @type {NerdamerSymbolType} */ (_.divide(result[1], symbol2));
+ return /** @type {NerdamerSymbolType} */ (
+ _.divide(/** @type {NerdamerSymbolType} */ (_.add(result[0], remainder)), den)
+ );
+ },
+ divWithCheck(symbol1, symbol2) {
+ const fail = [new NerdamerSymbol(0), symbol1.clone()];
+ const div = __.div(symbol1, symbol2);
+ // GM safety check because __.div() produces b.s. sometimes
+ // see whether multiplication comes out clean
+ const a = symbol1.clone();
+ let b = /** @type {NerdamerSymbolType} */ (_.multiply(div[0].clone(), symbol2.clone()));
+ b = /** @type {NerdamerSymbolType} */ (_.add(b, div[1].clone()));
+ let test = /** @type {NerdamerSymbolType} */ (_.subtract(a, b));
+ test = /** @type {NerdamerSymbolType} */ (_.expand(test));
+ // Test = __.Simplify._simplify(test);
+
+ if (test.equals(0)) {
+ // Ok, seems good
+ return div;
+ }
+ // False alarm, get the default back
+ // console.log("nerdamer-prime: div failed: " + test);
+ return fail;
+ },
+ div(symbol1, symbol2) {
+ // If all else fails then assume that division failed with
+ // a remainder of zero and the original quotient
+ const fail = [new NerdamerSymbol(0), symbol1.clone()];
+
+ try {
+ // Division by constants
+ if (symbol2.isConstant('all')) {
+ symbol1.each(x => {
+ x.multiplier = x.multiplier.divide(symbol2.multiplier);
+ });
+ return [symbol1, new NerdamerSymbol(0)];
+ }
+ // So that factorized symbols don't affect the result
+ symbol1 = /** @type {NerdamerSymbolType} */ (_.expand(symbol1));
+ symbol2 = /** @type {NerdamerSymbolType} */ (_.expand(symbol2));
+ // Special case. May need revisiting
+ if (symbol1.group === S && symbol2.group === CP) {
+ const x = symbol1.value;
+ const f = /** @type {DecomposeResultType} */ (core.Utils.decompose_fn(symbol2.clone(), x, true));
+ if (symbol1.isLinear() && f.x && f.x.isLinear() && symbol2.isLinear()) {
+ const k = NerdamerSymbol.create(symbol1.multiplier);
+ return [
+ /** @type {NerdamerSymbolType} */ (_.divide(k.clone(), f.a.clone())),
+ /** @type {NerdamerSymbolType} */ (_.divide(_.multiply(k, f.b), f.a)).negate(),
+ ];
+ }
+ }
+ if (symbol1.group === S && symbol2.group === S) {
+ const r = /** @type {NerdamerSymbolType} */ (_.divide(symbol1.clone(), symbol2.clone()));
+ if (r.isConstant()) // We have a whole
+ {
+ return [r, new NerdamerSymbol(0)];
+ }
+ return [new NerdamerSymbol(0), symbol1.clone()];
+ }
+ const symbol1HasFunc = symbol1.hasFunc();
+ const symbol2HasFunc = symbol2.hasFunc();
+ let parseFuncs = false;
+ let subs;
+
+ // Substitute out functions so we can treat them as regular variables
+ if (symbol1HasFunc || symbol2HasFunc) {
+ parseFuncs = true;
+ /** @type {Record<string, string>} */
+ const map = {};
+ symbol1 = /** @type {NerdamerSymbolType} */ (_.parse(core.Utils.subFunctions(symbol1, map)));
+ symbol2 = /** @type {NerdamerSymbolType} */ (_.parse(core.Utils.subFunctions(symbol2, map)));
+ subs = core.Utils.getFunctionsSubs(map);
+ }
+ // Get a list of the variables
+ const vars = core.Utils.arrayUnique(variables(symbol1).concat(variables(symbol2)));
+ let quot;
+ let rem;
+ let den;
+
+ // Treat imaginary numbers as variables
+ if (symbol1.isImaginary() || symbol2.isImaginary()) {
+ vars.push(core.Settings.IMAGINARY);
+ }
+
+ if (vars.length === 1) {
+ const q = new Polynomial(symbol1).divide(new Polynomial(symbol2));
+ quot = q[0].toSymbol();
+ rem = q[1].toSymbol();
+ } else {
+ vars.push(CONST_HASH); // This is for the numbers
+ const reconvert = function (arr) {
+ let symbol = new NerdamerSymbol(0);
+ for (let i = 0; i < arr.length; i++) {
+ const x = arr[i].toSymbol();
+ symbol = /** @type {NerdamerSymbolType} */ (_.add(symbol, x));
+ }
+ return symbol;
+ };
+
+ // Silly Martin. This is why you document. I don't remember now
+ const getUniqueMax = function (term, any) {
+ const max = Math.max.apply(null, term.terms);
+ let count = 0;
+ let idx;
+
+ if (!any) {
+ for (let i = 0; i < term.terms.length; i++) {
+ if (term.terms[i].equals(max)) {
+ idx = i;
+ count++;
+ }
+ if (count > 1) {
+ return undefined;
+ }
+ }
+ }
+ if (any) {
+ for (let i = 0; i < term.terms.length; i++) {
+ if (term.terms[i].equals(max)) {
+ idx = i;
+ break;
+ }
+ }
+ }
+ return [max, idx, term];
+ };
+
+ const tMap = core.Utils.toMapObj(vars);
+ const initSort = function (a, b) {
+ return b.sum.subtract(a.sum);
+ };
+
+ const s1 = symbol1.tBase(tMap).sort(initSort);
+ const s2 = symbol2.tBase(tMap).sort(initSort);
+
+ // Tries to find an LT in the dividend that will satisfy division
+ const getDet = function (s, lookat) {
+ lookat ||= 0;
+ const det = s[lookat];
+ const l = s.length;
+ if (!det) {
+ return undefined;
+ }
+ // Eliminate the first term if it doesn't apply
+ let umax = getUniqueMax(det);
+ for (let i = lookat + 1; i < l; i++) {
+ const term = s[i];
+ const isEqual = det.sum.equals(term.sum);
+ if (!isEqual && umax) {
+ break;
+ }
+ if (isEqual) {
+ // Check the differences of their maxes. The one with the biggest difference governs
+ // e.g. x^2*y^3 vs x^2*y^3 is unclear but this isn't the case in x*y and x^2
+ let max1;
+ let max2;
+ let idx1;
+ let idx2;
+ const l2 = det.terms.length;
+ for (let j = 0; j < l2; j++) {
+ const item1 = det.terms[j];
+ const item2 = term.terms[j];
+ if (typeof max1 === 'undefined' || item1.greaterThan(max1)) {
+ max1 = item1;
+ idx1 = j;
+ }
+ if (typeof max2 === 'undefined' || item2.greaterThan(max2)) {
+ max2 = item2;
+ idx2 = j;
+ }
+ }
+ // Check their differences
+ const d1 = max1.subtract(term.terms[idx1]);
+ const d2 = max2.subtract(det.terms[idx2]);
+ if (d2 > d1) {
+ umax = [max2, idx2, term];
+ break;
+ }
+ if (d1 > d2) {
+ umax = [max1, idx1, det];
+ break;
+ }
+ } else {
+ // Check if it's a suitable pick to determine the order
+ umax = getUniqueMax(term);
+ // If(umax) return umax;
+ if (umax) {
+ break;
+ }
+ }
+ umax = getUniqueMax(term); // Calculate a new unique max
+ }
+
+ // If still no umax then any will do since we have a tie
+ if (!umax) {
+ return getUniqueMax(s[0], true);
+ }
+ let e;
+ let idx;
+ for (let i = 0; i < s2.length; i++) {
+ const cterm = s2[i].terms;
+ // Confirm that this is a good match for the denominator
+ idx = umax[1];
+ if (idx === cterm.length - 1) {
+ return undefined;
+ }
+ e = cterm[idx];
+ if (!e.equals(0)) {
+ break;
+ }
+ }
+ if (e.equals(0)) {
+ return getDet(s, ++lookat);
+ } // Look at the next term
+
+ return umax;
+ };
+
+ const isLarger = function (a, b) {
+ if (!a || !b) {
+ return false;
+ } // It's empty so...
+ for (let i = 0; i < a.terms.length; i++) {
+ if (a.terms[i].lessThan(b.terms[i])) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ const target = isLarger(s1[0], s2[0]) && s1[0].count > s2[0].count ? s2 : s1; // Since the num is already larger than we can get the det from denom
+ const det = getDet(target); // We'll begin by assuming that this will let us know which term
+ const quotient = [];
+ if (det) {
+ let leadVar = det[1];
+ const canDivide = function (a, b) {
+ if (a[0].sum.equals(b[0].sum)) {
+ return a.length >= b.length;
+ }
+ return true;
+ };
+
+ const tryBetterLeadVar = function (sym1, sym2, leadVarParam) {
+ const checked = [];
+ for (let i = 0; i < sym1.length; i++) {
+ const t = sym1[i];
+ for (let j = 0; j < t.terms.length; j++) {
+ const cf = checked[j];
+ const tt = t.terms[j];
+ if (i === 0) {
+ checked[j] = tt;
+ } // Add the terms for the first one
+ else if (cf && !cf.equals(tt)) {
+ checked[j] = undefined;
+ }
+ }
+ }
+ for (let i = 0; i < checked.length; i++) {
+ const t = checked[i];
+ if (t && !t.equals(0)) {
+ return i;
+ }
+ }
+ return leadVarParam;
+ };
+ const sf = function (a, b) {
+ const l1 = a.len();
+ const l2 = b.len();
+ const blv = b.terms[leadVar];
+ const alv = a.terms[leadVar];
+ if (l2 > l1 && blv.greaterThan(alv)) {
+ return l2 - l1;
+ }
+ return blv.subtract(alv);
+ };
+
+ // Check to see if there's a better leadVar
+ leadVar = tryBetterLeadVar(s1, s2, leadVar);
+ // Reorder both according to the max power
+ s1.sort(sf); // Sort them both according to the leading variable power
+ s2.sort(sf);
+
+ // Try to adjust if den is larger
+ const fdt = s2[0];
+ const fnt = s1[0];
+
+ den = new MVTerm(new Frac(1), [], fnt.map);
+ if (fdt.sum.greaterThan(fnt.sum) && fnt.len() > 1) {
+ for (let i = 0; i < fnt.terms.length; i++) {
+ const d = fdt.terms[i].subtract(fnt.terms[i]);
+ if (d.equals(0)) {
+ den.terms[i] = new Frac(0);
+ } else {
+ const nd = d.add(new Frac(1));
+ den.terms[i] = d;
+ for (let j = 0; j < s1.length; j++) {
+ s1[j].terms[i] = s1[j].terms[i].add(nd);
+ }
+ }
+ }
+ }
+
+ let dividendLarger = isLarger(s1[0], s2[0]);
+
+ let safety = 0;
+ const max = 200;
+
+ while (dividendLarger && canDivide(s1, s2)) {
+ if (safety++ > max) {
+ throw new core.exceptions.InfiniteLoopError('Unable to compute!');
+ }
+
+ const q = s1[0].divide(s2[0]);
+
+ quotient.push(q); // Add what's divided to the quotient
+ s1.shift(); // The first one is guaranteed to be gone so remove from dividend
+ for (let i = 1; i < s2.length; i++) {
+ // Loop through the denominator
+ const t = s2[i].multiply(q).generateImage();
+ const l2 = s1.length;
+ // If we're subtracting from 0
+ if (l2 === 0) {
+ t.coeff = t.coeff.neg();
+ s1.push(t);
+ s1.sort(sf);
+ }
+
+ for (let j = 0; j < l2; j++) {
+ const cur = s1[j];
+ if (cur.getImg() === t.getImg()) {
+ cur.coeff = cur.coeff.subtract(t.coeff);
+ if (cur.coeff.equals(0)) {
+ core.Utils.remove(s1, j);
+ j--; // Adjust the iterator
+ }
+ break;
+ }
+ if (j === l2 - 1) {
+ t.coeff = t.coeff.neg();
+ s1.push(t);
+ s1.sort(sf);
+ }
+ }
+ }
+ dividendLarger = isLarger(s1[0], s2[0]);
+
+ if (!dividendLarger && s1.length >= s2.length) {
+ // One more try since there might be a terms that is larger than the LT of the divisor
+ for (let i = 1; i < s1.length; i++) {
+ dividendLarger = isLarger(s1[i], s2[0]);
+ if (dividendLarger) {
+ // Take it from its current position and move it to the front
+ s1.unshift(core.Utils.remove(s1, i));
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ quot = reconvert(quotient);
+ rem = reconvert(s1);
+
+ if (typeof den !== 'undefined') {
+ den = den.toSymbol();
+ quot = _.divide(quot, den.clone());
+ rem = _.divide(rem, den);
+ }
+ }
+
+ // Put back the functions
+ if (parseFuncs) {
+ quot = _.parse(quot.text(), subs);
+ rem = _.parse(rem.text(), subs);
+ }
+
+ return [quot, rem];
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ return fail;
+ }
+ },
+ line(v1, v2, x) {
+ if (core.Utils.isArray(v1)) {
+ v1 = core.Utils.convertToVector(/** @type {ExpressionParam[]} */ (v1));
+ }
+ if (core.Utils.isArray(v2)) {
+ v2 = core.Utils.convertToVector(/** @type {ExpressionParam[]} */ (v2));
+ }
+ const xVar = /** @type {NerdamerSymbolType} */ (_.parse(x || 'x'));
+ if (!core.Utils.isVector(v1) || !core.Utils.isVector(v2)) {
+ _.error(`Line expects a vector! Received "${v1}" & "${v2}"`);
+ }
+ const vec1 = /** @type {VectorType} */ (v1);
+ const vec2 = /** @type {VectorType} */ (v2);
+ const dx = _.subtract(
+ /** @type {NerdamerSymbolType} */ (vec2.e(1)).clone(),
+ /** @type {NerdamerSymbolType} */ (vec1.e(1)).clone()
+ );
+ const dy = _.subtract(
+ /** @type {NerdamerSymbolType} */ (vec2.e(2)).clone(),
+ /** @type {NerdamerSymbolType} */ (vec1.e(2)).clone()
+ );
+ const m = _.divide(dy, dx);
+ const a = _.multiply(xVar, /** @type {NerdamerSymbolType} */ (m).clone());
+ const b = _.multiply(/** @type {NerdamerSymbolType} */ (vec1.e(1)).clone(), m);
+ return _.add(_.subtract(a, b), /** @type {NerdamerSymbolType} */ (vec1.e(2)).clone());
+ },
+ PartFrac: {
+ /**
+ * Creates a template for partial fraction decomposition
+ *
+ * @param {NerdamerSymbolType} den - The denominator
+ * @param {NerdamerSymbolType} denomFactors - Factored form of denominator
+ * @param {NerdamerSymbolType[]} fArray - Array to collect factor components
+ * @param {NerdamerSymbolType} v - The variable
+ * @returns {[NerdamerSymbolType[], (NerdamerSymbolType | VectorType | MatrixType)[], number[]]}
+ */
+ createTemplate(den, denomFactors, fArray, v) {
+ // Clean up the denominator function by factors so it reduces nicely
+ den = __.Factor.factorInner(den);
+
+ // Clean up factors. This is so inefficient but factors are wrapped in parens for safety
+ den.each((x, key) => {
+ if (x.group === FN && x.fname === '' && x.args[0].group === S) {
+ const y = x.args[0];
+ if (den.symbols) {
+ delete den.symbols[key];
+ den.symbols[y.value] = y;
+ } else {
+ den = x.args[0];
+ }
+ }
+ });
+
+ let f;
+ let p;
+ let deg;
+ const factors = /** @type {NerdamerSymbolType[]} */ (denomFactors.collectFactors?.() || []);
+ const factorsVec = []; // A vector for the template
+ const degrees = [];
+ const m = new NerdamerSymbol(1);
+
+ for (let i = 0; i < factors.length; i++) {
+ // Loop through the factors
+ const factor = NerdamerSymbol.unwrapPARENS(factors[i]);
+ // If in he for P^n where P is polynomial and n = integer
+ if (factor.power.greaterThan(1)) {
+ p = Number(factor.power);
+ f = factor.clone().toLinear(); // Remove the power so we have only the function
+ deg = Number(__.degree(f, v)); // Get the degree of f
+ // expand the factor
+ for (let j = 0; j < p; j++) {
+ const efactor = /** @type {NerdamerSymbolType} */ (
+ _.pow(f.clone(), new NerdamerSymbol(j + 1))
+ );
+ fArray.push(efactor.clone());
+ const d = _.divide(den.clone(), efactor.clone());
+ degrees.push(deg);
+ factorsVec.push(d);
+ }
+ } else {
+ /*
+ Possible bug.
+ Removed: causes 1/(20+24*x+4*x^2) to result in (-1/64)*(5+x)^(-1)+(1/64)*(1+x)^(-1)
+ else if(factor.isConstant('all')) {
+ m = _.multiply(m, factor);
+ }
+ */
+ // get the degree of the factor so we tack it on tot he factor. This should probably be an array
+ // but for now we note it on the symbol
+ deg = Number(__.degree(factor, v));
+ fArray.push(factor);
+ let d = _.divide(den.clone(), factor.clone());
+ d = /** @type {NerdamerSymbolType} */ (
+ _.expand(NerdamerSymbol.unwrapPARENS(/** @type {NerdamerSymbolType} */ (d)))
+ );
+ degrees.push(deg);
+ factorsVec.push(d);
+ }
+ }
+ // Put back the constant
+ fArray = /** @type {NerdamerSymbolType[]} */ (fArray.map(x => _.multiply(x, m.clone())));
+ return [fArray, factorsVec, degrees];
+ },
+ /**
+ * Performs partial fraction decomposition
+ *
+ * @param {NerdamerSymbolType} symbol - The expression to decompose
+ * @param {NerdamerSymbolType} [v] - The variable
+ * @param {boolean} [asArray] - Whether to return as array
+ * @returns {NerdamerSymbolType | NerdamerSymbolType[] | VectorType | MatrixType}
+ */
+ partfrac(symbol, v, asArray) {
+ const vars = variables(symbol);
+
+ v ||= /** @type {NerdamerSymbolType} */ (_.parse(vars[0])); // Make wrt optional and assume first variable
+ try {
+ let nterms;
+ let div;
+ /** @type {NerdamerSymbolType | VectorType | MatrixType} */
+ let r;
+ let num = /** @type {NerdamerSymbolType} */ (_.expand(symbol.getNum()));
+ const den = /** @type {NerdamerSymbolType} */ (_.expand(symbol.getDenom().toUnitMultiplier()));
+ // Move the entire multipier to the numerator
+ num.multiplier = symbol.multiplier;
+ // We only have a meaningful change if n factors > 1. This means that
+ // the returned group will be a CB
+ // collect the terms wrt the x
+ const vValue = v.value;
+ nterms = num.groupTerms(vValue);
+ // Divide out wholes if top is larger
+ if (Number(__.degree(num, v)) >= Number(__.degree(den, v))) {
+ div = __.div(num.clone(), /** @type {NerdamerSymbolType} */ (_.expand(den.clone())));
+ r = /** @type {NerdamerSymbolType} */ (div[0]); // Remove the wholes
+ num = /** @type {NerdamerSymbolType} */ (div[1]); // Work with the remainder
+ nterms = num.groupTerms(vValue); // Recalculate the nterms
+ } else {
+ r = new NerdamerSymbol(0);
+ }
+
+ if (Number(__.degree(den, v)) === 1) {
+ const q = /** @type {NerdamerSymbolType} */ (_.divide(num, den));
+ if (asArray) {
+ return [r, q];
+ }
+ return _.add(r, q);
+ }
+ // First factor the denominator. This means that the strength of this
+ // algorithm depends on how well we can factor the denominator.
+ const ofactors = __.Factor.factorInner(den);
+ // Create the template. This method will create the template for solving
+ // the partial fractions. So given x/(x-1)^2 the template creates A/(x-1)+B/(x-1)^2
+ const template = __.PartFrac.createTemplate(den.clone(), ofactors, [], v);
+ const tfactors = template[0]; // Grab the factors
+ const factorsVec = template[1]; // Grab the factor vectors
+ const degrees = template[2]; // Grab the degrees
+ // make note of the powers of each term
+ /** @type {number[]} */
+ const powers = [nterms.length];
+ // Create the dterms vector
+ /** @type {NerdamerSymbolType[][]} */
+ const dterms = [];
+ /** @type {NerdamerSymbolType[]} */
+ const factors = [];
+ /** @type {NerdamerSymbolType[]} */
+ const ks = [];
+ /** @type {NerdamerSymbolType} */
+ let factor;
+ /** @type {number} */
+ let deg;
+ factorsVec.forEach((x, idx) => {
+ factor = tfactors[idx];
+ deg = degrees[idx];
+ for (let i = 0; i < deg; i++) {
+ factors.push(factor.clone());
+ const k = NerdamerSymbol.create(vValue, i);
+ const t = /** @type {NerdamerSymbolType} */ (
+ _.expand(/** @type {NerdamerSymbolType} */ (_.multiply(x, k.clone())))
+ ).groupTerms(vValue);
+ // Make a note of the power which corresponds to the length of the array
+ const p = t.length;
+ powers.push(p);
+ dterms.push(t);
+ ks.push(k.clone());
+ }
+ });
+ // Get the max power
+ const max = core.Utils.arrayMax(/** @type {number[]} */ (powers));
+
+ // Fill the holes and create a matrix
+ const c = new core.Matrix(core.Utils.fillHoles(nterms, max)).transpose();
+ // For each of the factors we do the same
+ const M = new core.Matrix();
+ for (let i = 0; i < dterms.length; i++) {
+ M.elements.push(core.Utils.fillHoles(dterms[i], max));
+ }
+
+ // Solve the system of equations
+ const partials = /** @type {MatrixType} */ (_.multiply(M.transpose().invert(), c));
+ // The results are backwards to reverse it
+ // partials.elements.reverse();
+ // convert it all back
+ if (asArray) {
+ /** @type {(NerdamerSymbolType | VectorType | MatrixType)[]} */
+ const retval = [r];
+ partials.each((e, i) => {
+ const term = _.multiply(ks[i], _.divide(e, factors[i]));
+ retval.push(term);
+ });
+ return /** @type {NerdamerSymbolType[]} */ (retval);
+ }
+ /** @type {NerdamerSymbolType | VectorType | MatrixType} */
+ let retval = r;
+ partials.each((e, i) => {
+ const term = _.multiply(ks[i], _.divide(e, factors[i]));
+ retval = _.add(retval, term);
+ });
+ return retval;
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ // Try to group symbols
+ try {
+ if (symbol.isComposite()) {
+ // Group denominators
+ const denominators = {};
+
+ symbol.each(x => {
+ const d = x.getDenom();
+ const n = x.getNum();
+ const existing = denominators[d];
+ denominators[d] = existing ? _.add(existing, n) : n;
+ });
+
+ let t = new NerdamerSymbol(0);
+
+ for (const x in denominators) {
+ if (!Object.hasOwn(denominators, x)) {
+ continue;
+ }
+ t = /** @type {NerdamerSymbolType} */ (_.add(t, _.divide(denominators[x], _.parse(x))));
+ }
+
+ symbol = t;
+ }
+ } catch (e2) {
+ if (e2.message === 'timeout') {
+ throw e2;
+ }
+ }
+ }
+ return symbol;
+ },
+ },
+ /**
+ * Computes the degree of a polynomial
+ *
+ * @param {NerdamerSymbolType} symbol - The polynomial
+ * @param {NerdamerSymbolType} [v] - The variable
+ * @param {{ nd: NerdamerSymbolType[]; sd: (NerdamerSymbolType | FracType)[]; depth: number }} [o] - Options for
+ * tracking
+ * @returns {NerdamerSymbolType}
+ */
+ degree(symbol, v, o) {
+ o ||= {
+ nd: [], // Numeric degrees (stored as NerdamerSymbol)
+ sd: [], // Symbolic degrees
+ depth: 0, // Call depth
+ };
+
+ if (!v) {
+ const vars = variables(symbol);
+ // The user must specify the variable for multivariate
+ if (vars.length > 1) {
+ throw new Error('You must specify the variable for multivariate polynomials!');
+ }
+ // If it's empty then we're dealing with a constant
+ if (vars.length === 0) {
+ return new NerdamerSymbol(0);
+ }
+ // Assume the variable for univariate
+ v = _.parse(vars[0]);
+ }
+
+ // Store the group
+ const g = symbol.group;
+ // We're going to trust the user and assume no EX. Calling isPoly
+ // would eliminate this but no sense in checking twice.
+ if (symbol.isComposite()) {
+ symbol = symbol.clone();
+ symbol.distributeExponent();
+ symbol.each(x => {
+ o.depth++; // Mark a depth increase
+ __.degree(x, v, o);
+ o.depth--; // We're back
+ });
+ } else if (symbol.group === CB) {
+ symbol.each(x => {
+ o.depth++;
+ __.degree(x, v, o);
+ o.depth++;
+ });
+ } else if (g === EX && symbol.value === v.value) {
+ o.sd.push(symbol.power.clone());
+ } else if (g === S && symbol.value === v.value) {
+ o.nd.push(/** @type {NerdamerSymbolType} */ (_.parse(symbol.power)));
+ } else {
+ o.nd.push(new NerdamerSymbol(0));
+ }
+
+ // Get the max out of the array - arrayMax uses valueOf() on each symbol to compare numerically
+ /** @type {number | undefined} */
+ const deg =
+ o.nd.length > 0
+ ? core.Utils.arrayMax(/** @type {number[]} */ (/** @type {unknown} */ (o.nd)))
+ : undefined;
+
+ if (o.depth === 0 && o.sd.length > 0) {
+ if (deg !== undefined) {
+ // Convert numeric deg back to symbol for the max function
+ o.sd.unshift(/** @type {NerdamerSymbolType} */ (_.parse(deg)));
+ }
+ return /** @type {NerdamerSymbolType} */ (
+ _.symfunction('max', /** @type {NerdamerSymbolType[]} */ (o.sd))
+ );
+ }
+ // Convert numeric degree to symbol
+ return /** @type {NerdamerSymbolType} */ (_.parse(deg ?? 0));
+ },
+ /**
+ * Attempts to complete the square of a polynomial
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {string | NerdamerSymbolType} v - The variable to complete the square with respect to
+ * @param {boolean} raw
+ * @returns {object | NerdamerSymbol[]}
+ * @throws {Error}
+ */
+ sqComplete(symbol, v, raw) {
+ if (!core.Utils.isSymbol(v)) {
+ v = _.parse(v);
+ }
+ const stop = function (msg) {
+ msg ||= 'Stopping';
+ throw new core.exceptions.ValueLimitExceededError(msg);
+ };
+ // If not CP then nothing to do
+ if (!symbol.isPoly(true)) {
+ stop('Must be a polynomial!');
+ }
+
+ // Declare vars
+ const br = core.Utils.inBrackets;
+ // Make a copy
+ symbol = symbol.clone();
+ const deg = core.Algebra.degree(symbol, v); // Get the degree of polynomial
+ // must be in form ax^2 +/- bx +/- c
+ if (!deg.equals(2)) {
+ stop(`Cannot complete square for degree ${deg.text()}`);
+ }
+ // Get the coeffs
+ const coeffs = core.Algebra.coeffs(symbol, v);
+ const a = coeffs[2];
+ // Store the sign
+ const sign = coeffs[1].sign();
+ // Divide the linear term by two and square it
+ const b = _.divide(coeffs[1], new NerdamerSymbol(2));
+ // Add the difference to the constant
+ const c = _.pow(b.clone(), new NerdamerSymbol(2));
+ const sqrtA = math.sqrt(a);
+ const e = _.divide(math.sqrt(c), sqrtA.clone());
+ // Calculate d which is the constant
+ const d = _.subtract(coeffs[0], _.pow(e.clone(), new NerdamerSymbol(2)));
+ if (raw) {
+ return [a, b, d];
+ }
+ // Compute the square part
+ const sym = _.parse(br(`${sqrtA.clone()}*${v}${sign < 0 ? '-' : '+'}${e}`));
+ return {
+ a: sym,
+ c: d,
+ f: _.add(_.pow(sym.clone(), new NerdamerSymbol(2)), d.clone()),
+ };
+ },
+ Simplify: {
+ /**
+ * @param {NerdamerSymbolType} symbol
+ * @returns {[NerdamerSymbolType, NerdamerSymbolType, NerdamerSymbolType]}
+ */
+ strip(symbol) {
+ const c = /** @type {NerdamerSymbolType} */ (_.parse(symbol.multiplier));
+ symbol.toUnitMultiplier();
+ const p = /** @type {NerdamerSymbolType} */ (_.parse(symbol.power));
+ symbol.toLinear();
+ return [c, p, symbol];
+ },
+ /**
+ * @param {[NerdamerSymbolType, NerdamerSymbolType] | NerdamerSymbolType[]} cp
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ unstrip(cp, symbol) {
+ const c = cp[0];
+ const p = cp[1];
+ const result = /** @type {NerdamerSymbolType} */ (_.multiply(c, _.pow(symbol, p)));
+ return result;
+ },
+ /**
+ * @param {NerdamerSymbolType} num
+ * @param {NerdamerSymbolType} den
+ * @returns {NerdamerSymbolType}
+ */
+ complexSimp(num, den) {
+ const r1 = num.realpart();
+ const i1 = num.imagpart();
+ const r2 = den.realpart();
+ const i2 = den.imagpart();
+ // Apply complex arithmatic rule
+ const ac = _.multiply(r1.clone(), r2.clone());
+ const bd = _.multiply(i1.clone(), i2.clone());
+ const bc = _.multiply(r2.clone(), i1);
+ const ad = _.multiply(r1, i2.clone());
+ const cd = _.add(_.pow(r2, new NerdamerSymbol(2)), _.pow(i2, new NerdamerSymbol(2)));
+
+ return /** @type {NerdamerSymbolType} */ (
+ _.divide(_.add(_.add(ac, bd), _.multiply(_.subtract(bc, ad), NerdamerSymbol.imaginary())), cd)
+ );
+ },
+ /**
+ * Simplify trigonometric expressions.
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ trigSimp(symbol) {
+ let workDone = true;
+ let iterations = 0;
+ while (workDone && symbol.containsFunction(['cos', 'sin', 'tan'])) {
+ iterations++;
+ workDone = false;
+ symbol = symbol.clone();
+ // Remove power and multiplier
+ const symArray = __.Simplify.strip(symbol);
+ symbol = symArray.pop();
+ // The default return value is the symbol
+ let retval = symbol.clone();
+
+ // Rewrite the symbol
+ if (symbol.group === CP) {
+ let sym = new NerdamerSymbol(0);
+ symbol.each(x => {
+ // Rewrite the function
+ const tr = __.Simplify.trigSimp(x.fnTransform());
+ sym = /** @type {NerdamerSymbolType} */ (_.add(sym, tr));
+ }, true);
+
+ // Put back the power and multiplier and return
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.pow(
+ _.multiply(new NerdamerSymbol(symbol.multiplier), sym),
+ new NerdamerSymbol(/** @type {FracType} */ (symbol.power))
+ )
+ );
+ workDone = retval.text() !== symbol.text();
+ } else if (symbol.group === CB) {
+ const n = symbol.getNum();
+ const d = symbol.getDenom();
+
+ // Try for tangent or fractions with tangent
+ if (
+ n.fname === 'sin' &&
+ d.fname === 'cos' &&
+ n.args[0].equals(d.args[0]) &&
+ /** @type {FracType} */ (n.power).equals(/** @type {FracType} */ (d.power))
+ ) {
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.parse(
+ core.Utils.format(
+ '(({1})/({0}))*tan({2})^({3})',
+ d.multiplier,
+ n.multiplier,
+ n.args[0],
+ n.power
+ )
+ )
+ );
+ workDone = true;
+ } else if (
+ n.fname === 'tan' &&
+ d.fname === 'sin' &&
+ n.args[0].equals(d.args[0]) &&
+ /** @type {FracType} */ (n.power).equals(/** @type {FracType} */ (d.power))
+ ) {
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.parse(
+ core.Utils.format(
+ '(({1})/({0}))*cos({2})^(-({3}))',
+ d.multiplier,
+ n.multiplier,
+ n.args[0],
+ n.power
+ )
+ )
+ );
+ workDone = true;
+ } else {
+ let t = new NerdamerSymbol(1);
+ const state = { workDone };
+ retval.each(x => {
+ if (x.fname === 'tan') {
+ x = _.parse(
+ core.Utils.format(
+ '({0})*sin({1})^({2})/cos({1})^({2})',
+ x.multiplier,
+ __.Simplify._simplify(x.args[0]),
+ x.power
+ )
+ );
+ state.workDone = true;
+ } else if (x.containsFunction(['cos', 'sin', 'tan'])) {
+ // Rewrite the function
+ const y = __.Simplify.trigSimp(x);
+ if (!x.equals(y)) {
+ x = y;
+ state.workDone = true;
+ }
+ }
+ t = /** @type {NerdamerSymbolType} */ (_.multiply(t, x));
+ });
+ workDone = state.workDone;
+ retval = /** @type {NerdamerSymbolType} */ (t);
+ }
+ } else if ((symbol.fname === 'cos' || symbol.fname === 'sin') && symbol.args[0].group === CP) {
+ // Capture cos(x-pi/2) => sin(x) and sin(x+pi/2) = cos(x)
+ // but generalized
+ // test the sum for presence of a "n*pi/2" summands
+ let count = 0;
+ let newArg = new NerdamerSymbol(0);
+ const piOverTwo = _.parse('pi/2');
+ symbol.args[0].each(x => {
+ let c = /** @type {NerdamerSymbolType} */ (_.divide(x.clone(), piOverTwo.clone()));
+ c = __.Simplify._simplify(c);
+ c = core.Utils.evaluate(c);
+ if (isInt(c)) {
+ count += c.multiplier.num.toJSNumber();
+ } else {
+ newArg = /** @type {NerdamerSymbolType} */ (_.add(newArg, x));
+ }
+ });
+ if (count) {
+ count += symbol.fname === 'cos' ? 1 : 0;
+ count %= 4;
+ count += count < 0 ? 4 : 0;
+ // Console.log(count);
+ // debugger;
+ const results = ['sin({0})', 'cos({0})', '-sin({0})', '-cos({0})'];
+ const s = core.Utils.format(results[count], String(newArg));
+ retval = _.parse(s);
+ workDone = true;
+ } else if (Object.keys(symbol.args[0].symbols).length > 1) {
+ // Apply sin(a+-b) => sin(a)cos(b)+-cos(a)sin(b)
+ // and cos(a+-b) => cos(a)cos(b)-+sin(a)sin(b)
+ const arg = symbol.args[0].clone();
+ const summands = Object.values(arg.symbols);
+ const a = summands[0];
+ const b = summands.slice(1);
+ const bStr = b.map(x => `(${x.text()})`).join('+');
+ let s;
+ if (symbol.fname === 'sin') {
+ s = core.Utils.format('sin({0})cos({1})+sin({1})cos({0})', a, bStr);
+ } else {
+ s = core.Utils.format('cos({0})cos({1})-sin({1})sin({0})', a, bStr);
+ }
+ retval = _.parse(s);
+ workDone = true;
+ }
+ } else if (
+ (symbol.fname === 'cos' || symbol.fname === 'sin') &&
+ symbol.args[0].multiplier.sign() === -1
+ ) {
+ // Sin(-x) => -sin(x), cos(-x) => cos(x)
+ // remove the minus from the argument
+ const newArg = symbol.args[0].clone().negate();
+ // Make the new trig call
+ let s = core.Utils.format(`${symbol.fname}({0})`, newArg);
+ if (symbol.fname === 'sin') {
+ s = `-${s}`;
+ }
+ retval = _.parse(s);
+ // Continue with the simpler form
+ workDone = true;
+ }
+ if (symbol.fname === 'sin' && symbol.args[0].multiplier.equals(2) && !symbol.args[0].equals(2)) {
+ // Sin(2x) => 2sin(x)cos(x)
+ // remove the minus from the argument
+ const newArg = symbol.args[0].clone().toUnitMultiplier();
+ // Make the new trig call
+ const s = core.Utils.format('2sin({0})cos({0})', newArg);
+ retval = _.parse(s);
+ // Continue with the simpler form
+ workDone = true;
+ }
+
+ retval = __.Simplify.unstrip(symArray, retval).distributeMultiplier();
+ symbol = retval;
+ // Safety check: prevent infinite loops
+ if (iterations > 10) {
+ break;
+ }
+ }
+
+ return symbol;
+ },
+ logArgSimp(fn, term) {
+ // Console.log("----- log term: "+ term.text());
+ // note: use symbol.equals
+ if (term.value === '1' || term.value === String(1)) {
+ return new NerdamerSymbol(0);
+ }
+ // Work on all factors of the arg term
+ // inintialize the sum
+ let r = new NerdamerSymbol(0);
+ // First up: the numerator's multiplier
+ const m = term.multiplier.clone();
+ // Console.log("---- multiplier: "+m);
+ term.toUnitMultiplier();
+ // Console.log("term with unit multiplier: "+term);
+
+ if (!m.equals(1)) {
+ const a = core.Utils.format('({0}({1}))', fn, m);
+ // Console.log("m transformed: "+a);
+ r = /** @type {NerdamerSymbolType} */ (_.add(r, _.parse(a)));
+ // Console.log("m r: "+r.text());
+ }
+ // Now each factor, with its power
+ // console.log("---- term factors");
+ if (term.group === CB) {
+ // Product
+ term.each(x => {
+ x = x.clone();
+ const p = x.power.clone();
+ // Note: there will be no multiplier
+ // strip modifies the original
+ __.Simplify.strip(x);
+ // Console.log("factor: "+m+" * "+x+"^"+p+" = "+original);
+ const a = core.Utils.format('(({1})*{0}({2}))', fn, p, x);
+ // Console.log("factor transformed: "+a);
+ r = /** @type {NerdamerSymbolType} */ (_.add(r, _.parse(a)));
+ // Console.log("running sum: "+r.text());
+ });
+ } else {
+ // Everything else
+ const x = term.clone();
+ const p = x.power.clone();
+ // Note: there will be no multiplier
+ // strip modifies the original
+ __.Simplify.strip(x);
+ // Console.log("factor: "+m+" * "+x+"^"+p+" = "+original);
+ const a = core.Utils.format('(({1})*{0}({2}))', fn, p, x);
+ // Console.log("factor transformed: "+r+"+"+a);
+ r = /** @type {NerdamerSymbolType} */ (_.add(r, _.parse(a)));
+ // Console.log("running sum: "+r.text());
+ }
+ // Console.log("result: "+r.text());
+ return r;
+ },
+ logSimp(symbol) {
+ if (symbol.group === FN && (symbol.fname === 'log' || symbol.fname === 'log10')) {
+ // Console.log();
+ // console.log("Initial: "+symbol.text());
+ // remove power and multiplier
+ const _original = symbol.clone();
+ const symArray = __.Simplify.strip(symbol);
+ symbol = symArray.pop();
+
+ // Work on the argument
+ const arg = symbol.args[0].clone();
+ const n = arg.getNum().clone();
+ // Console.log("n: "+n.text());
+ const d = arg.getDenom().clone();
+ // Console.log("d: "+d.text());
+ const fn = symbol.fname;
+
+ let retval = __.Simplify.logArgSimp(fn, n);
+ if (!d.equals(1)) {
+ const rd = __.Simplify.logArgSimp(fn, d);
+ retval = /** @type {NerdamerSymbolType} */ (_.subtract(retval, rd));
+ }
+
+ retval = __.Simplify.unstrip(symArray, retval).distributeMultiplier();
+ symbol = retval;
+ // Console.log("result: "+symbol.text());
+ } else if (symbol.containsFunction(['log', 'log10'])) {
+ for (const termkey in symbol.symbols) {
+ if (!Object.hasOwn(symbol.symbols, termkey)) {
+ continue;
+ }
+ const term = symbol.symbols[termkey];
+ symbol.symbols[termkey] = __.Simplify.logSimp(term);
+ }
+ }
+
+ return symbol;
+ },
+ /**
+ * Compresses sqrt expressions in fractions.
+ *
+ * @param {NerdamerSymbolType} symbol The symbol
+ * @param {NerdamerSymbolType} num Numerator
+ * @param {NerdamerSymbolType} den Denominator
+ * @returns {NerdamerSymbolType}
+ */
+ _sqrtCompression(symbol, num, den) {
+ // Return symbol;
+ // preserve power and multiplier
+ const symArray = __.Simplify.strip(symbol);
+
+ // Helper functions
+ const isABS = s => s.fname === 'abs';
+ const getArg = s => s.args[0];
+ const absArg = s => (isABS(s) ? getArg(s) : null);
+ const isUnit = s => s.type === S && s.value.startsWith('baseunit_');
+
+ // Main workhorse function
+ const cancel = (a, sqrt) => {
+ const sqrtArg = getArg(sqrt);
+ // Abs(x):sqrt(x) => sqrt(x)
+ if (sqrtArg.equals(absArg(a))) {
+ return [sqrt, null];
+ }
+ // Unit(x):sqrt(x) => sqrt(x)
+ if (sqrtArg.equals(a) && isUnit(a)) {
+ return [sqrt, null];
+ }
+
+ // N*sqrt(a):d*sqrt(x) => (n/d)*sqrt(a/x)
+ // if (a.isSQRT()) {
+ // let newArg = getArg(a);
+ // let m = new NerdamerSymbol(a.multiplier);
+ // m = _.divide(m, sqrt.multiplier);
+ // newArg = _.divide(newArg, sqrtArg);
+ // const combinedSqrt = core.Utils.format('sqrt({0})', newArg);
+ // const result = _.multiply(new NerdamerSymbol(m), _.parse(combinedSqrt));
+ // return [result, null];
+ // }
+
+ // nothing to be done
+ return [null, sqrt];
+ };
+
+ let workDone;
+ let totalWorkDone = false;
+
+ const cancelTerms = (top, bottom) => {
+ for (let i = 0; i < top.length; i++) {
+ // Examine the first top symbol
+ let sqrt = top[i];
+ if (!sqrt.isSQRT()) {
+ continue;
+ }
+ // It's a sqrt. try to cancel it against each
+ // bottom term
+ for (let j = 0; j < bottom.length; j++) {
+ let term = bottom[j];
+ [term, sqrt] = cancel(term, sqrt);
+ if (term !== null) {
+ // We found a match, substitute the remains and exit here
+ bottom[j] = term;
+ workDone = true;
+ totalWorkDone = true;
+ break;
+ }
+ }
+ // Whatever remains of sqrt gets put back
+ top[i] = sqrt;
+ top = top.filter(x => x);
+ bottom = bottom.filter(x => x);
+ }
+ return [top, bottom];
+ };
+
+ // Look for sqrt terms in products in num and den
+ // if we find any, combine them with other terms
+
+ // first, collect all factors in numerator and denominator
+ let numSymbols = num.collectFactors();
+ let denSymbols = den.collectFactors();
+
+ // Now cancel terms until nothing to cancel was found
+ do {
+ workDone = false;
+ [numSymbols, denSymbols] = cancelTerms(numSymbols, denSymbols);
+ [denSymbols, numSymbols] = cancelTerms(denSymbols, numSymbols);
+ } while (workDone);
+
+ if (totalWorkDone) {
+ // Reassemble the fraction symbol
+ symbol = /** @type {NerdamerSymbolType} */ (
+ numSymbols.reduce(
+ (acc, s) => (acc = /** @type {NerdamerSymbolType} */ (_.multiply(acc, s))),
+ new NerdamerSymbol(1)
+ )
+ );
+ symbol = /** @type {NerdamerSymbolType} */ (
+ denSymbols.reduce(
+ (acc, s) => (acc = /** @type {NerdamerSymbolType} */ (_.divide(acc, s))),
+ symbol
+ )
+ );
+ }
+
+ // Add power etc. back in
+ symbol = __.Simplify.unstrip(symArray, symbol);
+
+ return symbol;
+ },
+
+ fracSimp(symbol) {
+ // Try a quick simplify of imaginary numbers
+ let den = symbol.getDenom();
+ let num = symbol.getNum();
+
+ if (num.isImaginary() && den.isImaginary()) {
+ symbol = __.Simplify.complexSimp(num, den);
+ }
+
+ if (symbol.isComposite()) {
+ if (/** @type {FracType} */ (symbol.power).gt(1)) {
+ symbol = /** @type {NerdamerSymbolType} */ (_.expand(symbol));
+ }
+
+ const symbols = symbol.collectSymbols();
+ // Assumption 1.
+ // since it's a composite, it has a length of at least 1
+ /** @type {NerdamerSymbolType | VectorType | MatrixType} */
+ let retval;
+ /** @type {NerdamerSymbolType} */
+ let a;
+ /** @type {NerdamerSymbolType} */
+ let b;
+ /** @type {NerdamerSymbolType} */
+ let d1;
+ /** @type {NerdamerSymbolType} */
+ let d2;
+ /** @type {NerdamerSymbolType | VectorType | MatrixType} */
+ let n1;
+ /** @type {NerdamerSymbolType | VectorType | MatrixType} */
+ let n2;
+ /** @type {NerdamerSymbolType | VectorType | MatrixType} */
+ let s;
+ /** @type {NerdamerSymbolType | VectorType | MatrixType} */
+ let x;
+ /** @type {NerdamerSymbolType | VectorType | MatrixType} */
+ let y;
+ /** @type {NerdamerSymbolType | VectorType | MatrixType} */
+ let c;
+ a = /** @type {NerdamerSymbolType} */ (symbols.pop()); // Grab the first symbol
+ // loop through each term and make denominator common
+ while (symbols.length) {
+ b = /** @type {NerdamerSymbolType} */ (symbols.pop()); // Grab the second symbol
+ d1 = /** @type {NerdamerSymbolType} */ (_.parse(a.getDenom()));
+ d2 = /** @type {NerdamerSymbolType} */ (_.parse(b.getDenom()));
+ n1 = a.getNum();
+ n2 = b.getNum();
+ c = _.multiply(d1.clone(), d2.clone());
+ x = _.multiply(n1, d2);
+ y = _.multiply(n2, d1);
+ s = _.add(x, y);
+ a = /** @type {NerdamerSymbolType} */ (_.divide(s, c));
+ }
+ den = /** @type {NerdamerSymbolType} */ (_.expand(a.getDenom()));
+ num = /** @type {NerdamerSymbolType} */ (_.expand(a.getNum()));
+ // Simplify imaginary
+ if (num.isImaginary() && den.isImaginary()) {
+ retval = __.Simplify.complexSimp(num, den);
+ } else {
+ retval = _.divide(num, den);
+ }
+
+ // We've already hit the simplest form so return that
+ if (/** @type {NerdamerSymbolType} */ (retval).equals(symbol)) {
+ return symbol;
+ }
+
+ // Otherwise simplify it some more
+ return __.Simplify._simplify(retval);
+ }
+ symbol = __.Simplify._sqrtCompression(
+ symbol,
+ /** @type {NerdamerSymbolType} */ (num),
+ /** @type {NerdamerSymbolType} */ (den)
+ );
+ symbol = /** @type {NerdamerSymbolType} */ (__.Simplify.simpleFracSimp(symbol));
+ return symbol;
+ },
+ simpleFracSimp(symbol) {
+ let den = /** @type {NerdamerSymbolType} */ (symbol.getDenom());
+ let num = /** @type {NerdamerSymbolType} */ (symbol.getNum());
+ /** @type {NerdamerSymbolType | VectorType | MatrixType} */
+ let retval;
+ den = /** @type {NerdamerSymbolType} */ (_.expand(den));
+ num = /** @type {NerdamerSymbolType} */ (_.expand(num));
+ // Simplify imaginary
+ if (num.isImaginary() && den.isImaginary()) {
+ retval = __.Simplify.complexSimp(num, den);
+ } else {
+ retval = _.divide(num, den);
+ }
+ // We've already hit the simplest form so return that
+ if (/** @type {NerdamerSymbolType} */ (retval).equals(symbol)) {
+ return symbol;
+ }
+ // Otherwise simplify it some more
+ // retval = __.Simplify._simplify(retval);
+ return retval;
+ },
+ ratSimp(symbol) {
+ if (symbol.group === CB) {
+ const den = symbol.getDenom();
+ const num = symbol.getNum().distributeMultiplier();
+ const d = __.Simplify.fracSimp(den);
+ const n = __.Simplify.fracSimp(num);
+ symbol = /** @type {NerdamerSymbolType} */ (_.divide(n, d));
+ }
+ return symbol;
+ },
+ sqrtSimp(symbol, _sym_array) {
+ let retval;
+ let workDone = false;
+
+ const original = symbol.clone();
+ try {
+ // Debuglevel(1);
+ // debugout("input: "+symbol.toString());
+
+ if (symbol.isSQRT()) {
+ // Symbol is itself sqrt
+ // save outer multiplier
+ const mOuter = symbol.multiplier.clone();
+
+ // Now factor it
+ const sqrtArg = symbol.args[0].clone();
+ const factored = __.Factor.factorInner(sqrtArg);
+
+ // Get a sanitized version of the argument's multiplier
+ const m = _.parse(factored.multiplier);
+ // And its sign
+ const sign = m.sign();
+
+ // Make an initial return value
+ retval = new NerdamerSymbol(1);
+ let arg;
+
+ if (factored.group === CB) {
+ // Monomial arg
+ let rem = new NerdamerSymbol(1);
+
+ factored.each(x => {
+ x = _.parse(x);
+ if (x.group === N) {
+ const trial = _.sqrt(x.clone());
+
+ // Multiply back sqrt if it's an integer otherwise just put back the number
+ if (isInt(trial)) {
+ retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, trial));
+ } else {
+ rem = /** @type {NerdamerSymbolType} */ (_.multiply(rem, x));
+ }
+ } else {
+ rem = /** @type {NerdamerSymbolType} */ (_.multiply(rem, x));
+ }
+ });
+ const t = /** @type {NerdamerSymbolType} */ (_.multiply(rem, _.parse(sign)));
+ arg = /** @type {NerdamerSymbolType} */ (_.sqrt(t.clone()));
+
+ // Expand if it's imaginary
+ if (arg.isImaginary()) {
+ arg = /** @type {NerdamerSymbolType} */ (
+ _.sqrt(/** @type {NerdamerSymbolType} */ (_.expand(t.clone())))
+ );
+ }
+ } else {
+ // Put together the argument with the sign
+ // but without the multiplier
+ arg = factored.clone().toUnitMultiplier();
+ arg = /** @type {NerdamerSymbolType} */ (_.multiply(arg, new NerdamerSymbol(sign)));
+ arg = /** @type {NerdamerSymbolType} */ (_.sqrt(arg));
+ }
+
+ // Put the result back
+ retval = _.multiply(retval, arg);
+ // Put back the multiplier
+ retval = _.pow(retval, _.parse(symbol.power));
+ retval = _.multiply(retval, _.sqrt(m.abs()));
+ retval = _.multiply(retval, _.parse(mOuter));
+ workDone = true;
+ } else if (symbol.isComposite() && symbol.isLinear()) {
+ // Polynomial or CP => sum of things
+ retval = new NerdamerSymbol(0);
+ symbol.each(x => {
+ retval = _.add(retval, __.Simplify.sqrtSimp(x));
+ }, true);
+ // Put back the multiplier and power
+ retval = _.pow(retval, _.parse(symbol.power));
+ retval = _.multiply(retval, _.parse(symbol.multiplier));
+ workDone = true;
+ } else if (symbol.group === CB) {
+ // Monomial
+ retval = new NerdamerSymbol(1);
+ symbol.each(x => {
+ const simp = __.Simplify.sqrtSimp(x);
+ retval = _.multiply(retval, simp);
+ });
+ // Put back the power and multiplier
+ retval = _.pow(retval, _.parse(symbol.power));
+ retval = _.multiply(retval, _.parse(symbol.multiplier));
+ workDone = true;
+ }
+
+ if (!workDone) {
+ if (retval && !isInt(retval)) {
+ // If we can't even pull an integer out, revert
+ // to the cautious fallback
+ retval = null;
+ }
+ }
+
+ // Fallback: original symbol
+ retval ||= _.parse(symbol);
+ // Debugout("result: "+retval.toString());
+ // debugout("");
+ return retval;
+ } catch (error) {
+ if (error.message === 'timeout') {
+ throw error;
+ }
+ // Error in sqrtsimp - return original symbol
+ return original;
+ } finally {
+ // Debuglevel(-1);
+ }
+ },
+ /**
+ * Unused. The goal is to substitute out patterns but it currently doesn't work.
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {Array} The symbol and the matched patterns
+ */
+ patternSub(symbol) {
+ const patterns = {};
+
+ const hasCP = function (sym) {
+ let found = false;
+ sym.each(x => {
+ if (x.group === CP) {
+ found = true;
+ } else if (x.symbols) {
+ found = hasCP(x);
+ }
+ });
+
+ return found;
+ };
+
+ const collect = function (sym) {
+ // We loop through each symbol looking for anything in the simplest
+ // form of ax+byz+...
+ sym.each(x => {
+ // Items of group N,P,S, need to apply
+ if (!x.symbols && x.group !== FN) {
+ return;
+ }
+
+ // Check to see if it has any symbols of group CP
+ // Get the patterns in that symbol instead if it has anything of group CP
+ if (hasCP(x)) {
+ collect(x);
+ } else if (!patterns[x.value]) {
+ const u = core.Utils.getU(symbol);
+ // Get a u value and mark it for subsitution
+ patterns[x.value] = u;
+ symbol = symbol.sub(x.value, u);
+ }
+ }, true);
+ };
+
+ // Collect a list of patterns
+ collect(symbol);
+
+ return [symbol, patterns];
+ },
+ simplify(symbol) {
+ if (symbol.simplify) {
+ return symbol.simplify();
+ }
+ let retval = __.Simplify._simplify(symbol);
+ retval = retval.pushMinus();
+ retval = _.parse(retval);
+ return retval;
+ },
+ _simplify(symbol) {
+ // Debuglevel(1);
+ // debugout("input to _simplify: "+symbol.text());
+ // try {
+ // remove the multiplier to make calculation easier;
+ const symArray = __.Simplify.strip(/** @type {NerdamerSymbolType} */ (symbol).clone());
+ symbol = /** @type {NerdamerSymbolType | VectorType | MatrixType} */ (symArray.pop());
+ // Remove gcd from denominator
+ symbol = __.Simplify.fracSimp(/** @type {NerdamerSymbolType} */ (symbol));
+ // Nothing more to do
+ if (
+ /** @type {NerdamerSymbolType} */ (symbol).isConstant() ||
+ /** @type {NerdamerSymbolType} */ (symbol).group === core.groups.S
+ ) {
+ symArray.push(/** @type {NerdamerSymbolType} */ (symbol));
+ const ret = __.Simplify.unstrip(symArray, /** @type {NerdamerSymbolType} */ (symbol));
+ // Debugout("final result: "+ret.text());
+ return ret;
+ }
+ // Console.log("array: "+symArray);
+
+ // let patterns;
+
+ let simplified = /** @type {NerdamerSymbolType} */ (symbol).clone(); // Make a copy
+
+ // [simplified, patterns] = __.Simplify.patternSub(symbol);
+
+ // Simplify sqrt within the symbol
+ // todo: why does this break calculus tests?
+ simplified = /** @type {NerdamerSymbolType} */ (__.Simplify.sqrtSimp(simplified, symArray));
+
+ // Try trig simplificatons e.g. cos(x)^2+sin(x)^2
+ simplified = __.Simplify.trigSimp(simplified);
+
+ // Try log simplificatons e.g. log(a/b)=> log(a)-log(b)
+ simplified = __.Simplify.logSimp(simplified);
+
+ // Simplify common denominators
+ simplified = /** @type {NerdamerSymbolType} */ (__.Simplify.ratSimp(simplified));
+
+ // First go for the "cheapest" simplification which may eliminate
+ // your problems right away. factor -> evaluate. Remember
+ // that there's no need to expand since factor already does that
+
+ // console.log("before factor: "+simplified.text());
+ simplified = __.Factor.factorInner(simplified);
+ // Console.log("after factor: "+simplified.text());
+
+ // If the simplified is a sum then we can make a few more simplifications
+ // e.g. simplify(1/(x-1)+1/(1-x)) as per issue #431
+ // console.log("before sums: "+simplified.text());
+ if (simplified.group === core.groups.CP && simplified.isLinear()) {
+ const m = simplified.multiplier.clone();
+ simplified.toUnitMultiplier(); // Strip the multiplier
+ let r = new NerdamerSymbol(0);
+ // Return the sum of simplifications
+ simplified.each(x => {
+ const s = __.Simplify._simplify(x);
+ r = /** @type {NerdamerSymbolType} */ (_.add(r, s));
+ });
+ simplified = r;
+ // Mult on back the multiplier we saved here
+ simplified = /** @type {NerdamerSymbolType} */ (_.multiply(simplified, new NerdamerSymbol(m)));
+ if (simplified.multiplier.equals(-1)) {
+ simplified.distributeMultiplier();
+ }
+ // Place back original multiplier and return
+ simplified = __.Simplify.unstrip(symArray, simplified);
+ // Debugout("final result: "+simplified.text());
+ return simplified;
+ }
+
+ // Place back original multiplier and return
+ simplified = __.Simplify.unstrip(symArray, simplified);
+ // Debugout("final result: "+simplified.text());
+ return simplified;
+ // } finally {
+ // // debuglevel(-1);
+ // }
+ },
+ },
+
+ Classes: {
+ Polynomial,
+ Factors: /** @type {FactorsConstructor} */ (/** @type {unknown} */ (Factors)),
+ MVTerm,
+ },
+ });
+
+ // Add a link to simplify
+ core.Expression.prototype.simplify = function simplify() {
+ core.Utils.armTimeout();
+ try {
+ let retval;
+ // Equation?
+ if (typeof this.symbol.LHS === 'undefined') {
+ retval = new core.Expression(__.Simplify.simplify(this.symbol));
+ } else {
+ // Don't have access to equation here, so we clone instead
+ const eq = this.symbol.clone();
+ eq.LHS = __.Simplify.simplify(eq.LHS);
+ eq.RHS = __.Simplify.simplify(eq.RHS);
+ retval = eq;
+ }
+ return retval;
+ } catch (error) {
+ if (error.message === 'timeout') {
+ throw error;
+ }
+ return this;
+ } finally {
+ core.Utils.disarmTimeout();
+ }
+ };
+
+ core.Collection.prototype.simplify = function simplify() {
+ this.elements = this.elements.map(e => __.Simplify.simplify(e));
+ return this;
+ };
+
+ core.Matrix.prototype.simplify = function simplify() {
+ this.elements = this.elements.map(row => row.map(e => __.Simplify.simplify(e)));
+ return this;
+ };
+
+ nerdamer.useAlgebraDiv = function useAlgebraDiv() {
+ const _originalDivide = (__.divideFn = _.divide);
+ let calls = 0; // Keep track of how many calls were made
+ _.divide = function divide(a, b) {
+ calls++;
+ let ans;
+ if (calls === 1) // Check if this is the first call. If it is use algebra divide
+ {
+ ans = core.Algebra.divide(/** @type {NerdamerSymbolType} */ (a), /** @type {NerdamerSymbolType} */ (b));
+ } // Otherwise use parser divide
+ else {
+ ans = divide(a, b);
+ }
+ calls = 0; // Reset the number of calls back to none
+ return ans;
+ };
+ };
+
+ nerdamer.useParserDiv = function useParserDiv() {
+ if (__.divideFn) {
+ _.divide = __.divideFn;
+ }
+ delete __.divideFn;
+ };
+
+ nerdamer.register([
+ {
+ name: 'factor',
+ visible: true,
+ numargs: 1,
+ build() {
+ return __.Factor.factor;
+ },
+ },
+ {
+ name: 'simplify',
+ visible: true,
+ numargs: 1,
+ build() {
+ return __.Simplify.simplify;
+ },
+ },
+ {
+ name: 'gcd',
+ visible: true,
+ numargs: [1],
+ build() {
+ return __.gcd;
+ },
+ },
+ {
+ name: 'lcm',
+ visible: true,
+ numargs: [1],
+ build() {
+ return __.lcm;
+ },
+ },
+ {
+ name: 'roots',
+ visible: true,
+ numargs: -1,
+ build() {
+ return __.roots;
+ },
+ },
+ {
+ name: 'divide',
+ visible: true,
+ numargs: 2,
+ build() {
+ return __.divide;
+ },
+ },
+ {
+ name: 'div',
+ visible: true,
+ numargs: 2,
+ build() {
+ return __.div;
+ },
+ },
+ {
+ name: 'partfrac',
+ visible: true,
+ numargs: [1, 2],
+ build() {
+ return __.PartFrac.partfrac;
+ },
+ },
+ {
+ name: 'deg',
+ visible: true,
+ numargs: [1, 2],
+ build() {
+ return __.degree;
+ },
+ },
+ {
+ name: 'coeffs',
+ visible: true,
+ numargs: [1, 2],
+ build() {
+ const f = function (...args) {
+ const coeffs = __.coeffs(/** @type {NerdamerSymbolType} */ (args[0]), args[1]);
+ return new core.Vector(coeffs);
+ };
+ return f;
+ },
+ },
+ ]);
+
+ // Register coeffs with direct access to nerdamer that preserves symbolic constants
+ // The standard updateAPI wrapper uses PARSE2NUMBER which converts pi, e, sqrt(2) to rationals
+ // This version parses arguments without PARSE2NUMBER to preserve symbolic constants
+ /** @type {any} */ (nerdamer).coeffs = function coeffs(...args) {
+ const parser = core.PARSER;
+ // Parse arguments WITHOUT PARSE2NUMBER to preserve symbolic constants like pi, e, sqrt(2)
+ for (let i = 0; i < args.length; i++) {
+ if (typeof args[i] === 'string') {
+ args[i] = parser.parse(/** @type {string} */ (args[i]));
+ } else if (args[i] && /** @type {ExpressionType} */ (args[i]).symbol) {
+ // It's an Expression, get the symbol
+ args[i] = /** @type {ExpressionType} */ (args[i]).symbol.clone();
+ } else if (core.Utils.isSymbol(args[i])) {
+ args[i] = /** @type {NerdamerSymbolType} */ (args[i]).clone();
+ }
+ }
+ const resultCoeffs = __.coeffs(/** @type {NerdamerSymbolType} */ (args[0]), args[1]);
+ return new core.Expression(/** @type {VectorType} */ (new core.Vector(resultCoeffs)));
+ };
+
+ nerdamer.register([
+ {
+ name: 'line',
+ visible: true,
+ numargs: [2, 3],
+ build() {
+ return __.line;
+ },
+ },
+ {
+ name: 'sqcomp',
+ visible: true,
+ numargs: [1, 2],
+ build() {
+ const f = function (x, v) {
+ try {
+ v ||= variables(x)[0];
+ const sq = __.sqComplete(x.clone(), v);
+ return /** @type {{ f: NerdamerSymbol; a: NerdamerSymbol; c: NerdamerSymbol }} */ (sq).f;
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ return x;
+ }
+ };
+ return f;
+ },
+ },
+ ]);
+ nerdamer.updateAPI();
+})();
--- /dev/null
+/*
+ * Author : Martin Donk
+ * Website : http://www.nerdamer.com
+ * Email : martin.r.donk@gmail.com
+ * Source : https://github.com/jiggzson/nerdamer
+ */
+
+// Type imports for JSDoc ======================================================
+// These typedefs provide type aliases for the interfaces defined in index.d.ts.
+// They enable proper type checking when working with the classes defined in this file.
+//
+// Usage patterns:
+// - For return types: @returns {NerdamerSymbolType}
+// - For parameters: @param {NerdamerSymbolType} symbol
+// - For variable declarations: /** @type {NerdamerSymbolType} */
+//
+// Note: When casting local class instances to interface types, use the pattern:
+// /** @type {InterfaceType} */ (/** @type {unknown} */ (localInstance))
+// This is needed because TypeScript sees local classes and interfaces as separate types.
+
+/**
+ * Core type aliases from index.d.ts
+ *
+ * @typedef {import('./index').NerdamerCore.NerdamerSymbol} NerdamerSymbolType
+ *
+ * @typedef {import('./index').NerdamerCore.Frac} FracType
+ *
+ * @typedef {import('./index').NerdamerCore.Vector} VectorType
+ *
+ * @typedef {import('./index').NerdamerCore.Matrix} MatrixType
+ *
+ * @typedef {import('./index').NerdamerCore.Parser} ParserType
+ *
+ * @typedef {import('./index').NerdamerCore.Collection} CollectionType
+ *
+ * @typedef {import('./index').NerdamerCore.Settings} SettingsType
+ *
+ * @typedef {import('./index').NerdamerExpression} ExpressionType
+ *
+ * @typedef {typeof import('./index')} NerdamerType
+ *
+ * @typedef {import('./index').NerdamerCore.Utils} UtilsInterface
+ *
+ * @typedef {import('./index').NerdamerCore.Math2} Math2Interface
+ *
+ * @typedef {import('./index').NerdamerCore.Core} CoreType
+ *
+ * @typedef {import('./index').ExpressionParam} ExpressionParam
+ *
+ * @typedef {import('./index').ArithmeticOperand} ArithmeticOperand
+ *
+ * @typedef {import('./index').ExpandOptions} ExpandOptions
+ *
+ * @typedef {import('./index').NerdamerCore.FactorSubModule} FactorSubModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.PartFracSubModule} PartFracSubModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.AlgebraClassesSubModule} AlgebraClassesSubModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.Factors} FactorsType
+ *
+ * @typedef {import('./index').NerdamerCore.SimplifySubModule} SimplifySubModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.CalculusModule} CalculusModuleType
+ *
+ * Constructor types
+ *
+ * @typedef {import('./index').NerdamerCore.FracConstructor} FracConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.SymbolConstructor} SymbolConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.VectorConstructor} VectorConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.MatrixConstructor} MatrixConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.DecomposeResultObject} DecomposeResultType
+ *
+ * @typedef {import('./index').NerdamerCore.IntegrationOptions} IntegrationOptions
+ */
+
+// Check if nerdamer exists globally (browser) or needs to be required (Node.js)
+let nerdamer = typeof globalThis !== 'undefined' && globalThis.nerdamer ? globalThis.nerdamer : undefined;
+if (typeof module !== 'undefined' && nerdamer === undefined) {
+ nerdamer = require('./nerdamer.core.js');
+ require('./Algebra.js');
+}
+
+/** @returns {CalculusModuleType} */
+(function initCalculusModule() {
+ const core = nerdamer.getCore();
+ const _ = core.PARSER;
+ const { Frac } = core;
+ const { Settings } = core;
+ const { isSymbol } = core.Utils;
+ const { FN } = core.groups;
+ const { NerdamerSymbol } = core;
+ const { text } = core.Utils;
+ const { inBrackets } = core.Utils;
+ const { isInt } = core.Utils;
+ const { format } = core.Utils;
+ const { even } = core.Utils;
+ const { evaluate } = core.Utils;
+ const { N } = core.groups;
+ const { S } = core.groups;
+ const { PL } = core.groups;
+ const { CP } = core.groups;
+ const { CB } = core.groups;
+ const { EX } = core.groups;
+ const { P } = core.groups;
+ const { LOG } = Settings;
+ const EXP = 'exp';
+ const ABS = 'abs';
+ const SQRT = 'sqrt';
+ const SIN = 'sin';
+ const COS = 'cos';
+ const TAN = 'tan';
+ const SEC = 'sec';
+ const CSC = 'csc';
+ const COT = 'cot';
+ const ASIN = 'asin';
+ const ACOS = 'acos';
+ const ATAN = 'atan';
+ const ASEC = 'asec';
+ const ACSC = 'acsc';
+ const ACOT = 'acot';
+ const SINH = 'sinh';
+ const COSH = 'cosh';
+ const TANH = 'tanh';
+ const CSCH = 'csch';
+ const SECH = 'sech';
+ const COTH = 'coth';
+ const ASECH = 'asech';
+ const ACSCH = 'acsch';
+ const ACOTH = 'acoth';
+
+ /**
+ * Check if a symbol's power is itself a symbol with group S or CB
+ *
+ * @param {NerdamerSymbolType} sym
+ * @returns {boolean}
+ */
+ function hasPowerGroupSOrCB(sym) {
+ return isSymbol(sym.power) && (sym.power.group === S || sym.power.group === CB);
+ }
+
+ // Custom errors
+ function NoIntegralFound(msg) {
+ this.message = msg || '';
+ }
+ NoIntegralFound.prototype = new Error();
+
+ // Preparations
+ NerdamerSymbol.prototype.hasIntegral = function hasIntegral() {
+ return this.containsFunction('integrate');
+ };
+ // Transforms a function
+ NerdamerSymbol.prototype.fnTransform = function fnTransform() {
+ if (this.group !== FN) {
+ return this;
+ }
+ let retval;
+ const a = this.args[0];
+ const m = new NerdamerSymbol(this.multiplier);
+ const sym = this.clone().toUnitMultiplier();
+ if (this.isLinear()) {
+ switch (this.fname) {
+ case SINH:
+ retval = _.parse(format('(e^({0})-e^(-({0})))/2', a));
+ break;
+ case COSH:
+ retval = _.parse(format('(e^({0})+e^(-({0})))/2', a));
+ break;
+ case TANH:
+ retval = _.parse(format('(e^({0})-e^(-({0})))/(e^({0})+e^(-({0})))', a));
+ break;
+ case TAN:
+ retval = _.parse(format('sin({0})/cos({0})', a));
+ break;
+ case CSC:
+ retval = _.parse(format('1/sin({0})', a));
+ break;
+ case SEC:
+ retval = _.parse(format('1/cos({0})', a));
+ break;
+ default:
+ retval = sym;
+ }
+ } else if (this.power.equals(2)) {
+ switch (this.fname) {
+ case SIN:
+ retval = _.parse(format('1/2-cos(2*({0}))/2', a));
+ break;
+ case COS:
+ retval = _.parse(format('1/2+cos(2*({0}))/2', a));
+ break;
+ case TAN:
+ // Retval = _.parse(format('(1-cos(2*({0})))/(1+cos(2*({0})))', a));
+ retval = _.parse(format('sin({0})^2/cos({0})^2', a));
+ break;
+ case COSH:
+ retval = _.parse(format('1/2+cosh(2*({0}))/2', a));
+ break;
+ case SINH:
+ retval = _.parse(format('-1/2+cosh(2*({0}))/2', a));
+ break;
+ case TANH:
+ retval = _.parse(format('(1+cosh(2*({0})))/(-1+cosh(2*({0})))', a));
+ break;
+ case SEC:
+ retval = _.parse(format('(1-cos(2*({0})))/(1+cos(2*({0})))+1', a));
+ break;
+ default:
+ retval = sym;
+ }
+ } else if (this.fname === SEC) {
+ retval = _.parse(format('1/cos({0})^({1})', this.args[0], this.power));
+ } else if (this.fname === CSC) {
+ retval = _.parse(format('1/sin({0})^({1})', this.args[0], this.power));
+ } else if (this.fname === TAN) {
+ if (this.power.lessThan(0)) {
+ retval = _.parse(format('cos({0})^(-({1}))/sin({0})^({1})', this.args[0], this.power.negate()));
+ } else {
+ retval = _.parse(format('sin({0})^({1})/cos({0})^({1})', this.args[0], this.power));
+ }
+ } else if (this.fname === SIN && this.power.lessThan(0)) {
+ retval = _.parse(format('csc({0})^(-({1}))', this.args[0], this.power.negate()));
+ } else if (this.fname === COS && this.power.lessThan(0)) {
+ retval = _.parse(format('sec({0})^(-({1}))', this.args[0], this.power.negate()));
+ } else if (this.fname === SIN && this.power.equals(3)) {
+ retval = _.parse(format('(3*sin({0})-sin(3*({0})))/4', this.args[0]));
+ } else if (this.fname === COS && this.power.equals(3)) {
+ retval = _.parse(format('(cos(3*({0}))+3*cos({0}))/4', this.args[0]));
+ }
+ // Cos(a*x)^(2*n) or sin(a*x)^(2*n)
+ else if ((this.fname === COS || this.fname === SIN) && even(this.power)) {
+ const n = this.power / 2;
+ // Convert to a double angle
+ const cloned = /** @type {NerdamerSymbolType} */ (this.clone().toLinear());
+ const doubleAngle = /** @type {NerdamerSymbolType} */ (_.pow(cloned, _.parse(2)));
+ const transformed = /** @type {NerdamerSymbolType} */ (
+ _.expand(_.pow(doubleAngle.fnTransform(), _.parse(n)))
+ );
+
+ retval = new NerdamerSymbol(0);
+
+ transformed.each(s => {
+ const t = s.fnTransform();
+ retval = /** @type {NerdamerSymbolType} */ (_.add(retval, t));
+ }, true);
+ } else {
+ retval = sym;
+ }
+
+ return _.multiply(retval, m);
+ };
+
+ NerdamerSymbol.prototype.hasTrig = function hasTrig() {
+ if (this.isConstant(true) || this.group === S) {
+ return false;
+ }
+ if (this.fname && (core.Utils.inTrig(this.fname) || core.Utils.inInverseTrig(this.fname))) {
+ return true;
+ }
+ if (this.symbols) {
+ for (const x in this.symbols) {
+ if (this.symbols[x].hasTrig()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ core.Expression.prototype.hasIntegral = function hasIntegral() {
+ return this.symbol.hasIntegral();
+ };
+ /**
+ * Attempts to rewrite a symbol under one common denominator
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ core.Utils.toCommonDenominator = function toCommonDenominator(symbol) {
+ // Transform x/a+x -> (ax+x)/a
+ if (symbol.isComposite() && symbol.isLinear()) {
+ const m = new NerdamerSymbol(symbol.multiplier);
+ let denominator = new NerdamerSymbol(1);
+ let numerator = new NerdamerSymbol(0);
+ symbol.each(x => {
+ denominator = /** @type {NerdamerSymbolType} */ (_.multiply(denominator, x.getDenom()));
+ }, true);
+
+ // Remove the denomitor in each term
+ symbol.each(x => {
+ const num = x.getNum();
+ const den = x.getDenom();
+ const factor = /** @type {NerdamerSymbolType} */ (_.multiply(num, _.divide(denominator.clone(), den)));
+ numerator = /** @type {NerdamerSymbolType} */ (_.add(numerator, factor));
+ });
+ const retval = /** @type {NerdamerSymbolType} */ (
+ _.multiply(
+ m,
+ core.Algebra.divide(
+ /** @type {NerdamerSymbolType} */ (_.expand(numerator)),
+ /** @type {NerdamerSymbolType} */ (_.expand(denominator))
+ )
+ )
+ );
+ return retval;
+ }
+ return symbol;
+ };
+ // A function to check if a function name is an inverse trig function
+ core.Utils.inInverseTrig = function inInverseTrig(x) {
+ const invTrigFns = [ASIN, ACOS, ATAN, ACSC, ASEC, ACOT];
+ return invTrigFns.indexOf(x) !== -1;
+ };
+ // A function to check if a function name is a trig function
+ core.Utils.inTrig = function inTrig(x) {
+ const trigFns = [COS, SIN, TAN, SEC, CSC, COT];
+ return trigFns.indexOf(x) !== -1;
+ };
+
+ core.Utils.inHtrig = function inHtrig(x) {
+ const trigFns = [SINH, COSH, TANH, ACSCH, ASECH, ACOTH];
+ return trigFns.indexOf(x) !== -1;
+ };
+
+ // Matrix functions
+ core.Matrix.jacobian = function jacobian(eqns, vars) {
+ const result = new core.Matrix();
+ // Get the variables if not supplied
+ vars ||= core.Utils.arrayGetVariables(eqns);
+
+ vars.forEach((v, i) => {
+ eqns.forEach((eq, j) => {
+ const e = core.Calculus.diff(eq.clone(), v);
+ result.set(j, i, e);
+ });
+ });
+
+ return result;
+ };
+
+ core.Matrix.prototype.max = function max() {
+ let maxValue = new NerdamerSymbol(0);
+ this.each(x => {
+ const e = x.abs();
+ if (e.gt(maxValue)) {
+ maxValue = e;
+ }
+ });
+ return maxValue;
+ };
+
+ core.Matrix.cMatrix = function cMatrix(value, vars) {
+ const m = new core.Matrix();
+ // Make an initial guess
+ vars.forEach((v, i) => {
+ m.set(i, 0, _.parse(value));
+ });
+ return m;
+ };
+
+ /**
+ * Checks if all elements in an array are function symbols
+ *
+ * @param {NerdamerSymbolType[]} arr
+ * @returns {boolean}
+ */
+ const allFunctions = (core.Utils.allFunctions = function allFunctions(arr) {
+ for (let i = 0, l = arr.length; i < l; i++) {
+ if (arr[i].group !== FN) {
+ return false;
+ }
+ }
+ return true;
+ });
+ /**
+ * Transforms cos(a)*sin(b) into (sin(a+b)-sin(a-b))/2
+ *
+ * @param {NerdamerSymbolType} symbol1
+ * @param {NerdamerSymbolType} symbol2
+ * @returns {NerdamerSymbolType}
+ */
+ const cosAsinBtransform = (core.Utils.cosAsinBtranform = function cosAsinBtranform(symbol1, symbol2) {
+ const a = symbol1.args[0];
+ const b = symbol2.args[0];
+ return /** @type {NerdamerSymbolType} */ (_.parse(format('(sin(({0})+({1}))-sin(({0})-({1})))/2', a, b)));
+ });
+ /**
+ * Transforms cos(a)*sin(a) into sin(2a)/2
+ *
+ * @param {NerdamerSymbolType} symbol1
+ * @param {NerdamerSymbolType} symbol2
+ * @returns {NerdamerSymbolType}
+ */
+ const cosAsinAtransform = (core.Utils.cosAsinAtranform = function cosAsinAtranform(symbol1, symbol2) {
+ // TODO: temporary fix for integrate(e^x*sin(x)*cos(x)^2).
+ // we technically know how to do this transform but more is needed for correct output
+ if (Number(symbol2.power) !== 1) {
+ return /** @type {NerdamerSymbolType} */ (_.multiply(symbol1, symbol2));
+ }
+ const a = symbol1.args[0];
+ return /** @type {NerdamerSymbolType} */ (_.parse(format('(sin(2*({0})))/2', a)));
+ });
+ /**
+ * Transforms sin(a)*sin(b) into (cos(a+b)-cos(a-b))/2
+ *
+ * @param {NerdamerSymbolType} symbol1
+ * @param {NerdamerSymbolType} symbol2
+ * @returns {NerdamerSymbolType}
+ */
+ const sinAsinBtransform = (core.Utils.cosAsinBtranform = function cosAsinBtranform(symbol1, symbol2) {
+ const a = symbol1.args[0];
+ const b = symbol2.args[0];
+ return /** @type {NerdamerSymbolType} */ (_.parse(format('(cos(({0})+({1}))-cos(({0})-({1})))/2', a, b)));
+ });
+ /**
+ * Transforms an array of trig functions into simplified form
+ *
+ * @param {NerdamerSymbolType[]} arr
+ * @returns {NerdamerSymbolType}
+ */
+ const trigTransform = (core.Utils.trigTransform = function trigTransform(arr) {
+ /** @type {Record<string, NerdamerSymbolType>} */
+ const map = {};
+ let symbol;
+ let t;
+ let retval = new NerdamerSymbol(1);
+ for (let i = 0, l = arr.length; i < l; i++) {
+ symbol = arr[i];
+
+ if (symbol.group === FN) {
+ const { fname } = symbol;
+
+ if (fname === COS && map[SIN]) {
+ if (map[SIN].args[0].toString() === symbol.args[0].toString()) {
+ t = cosAsinAtransform(symbol, map[SIN]);
+ } else {
+ t = cosAsinBtransform(symbol, map[SIN]);
+ }
+ delete map[SIN];
+
+ retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, t));
+ } else if (fname === SIN && map[COS]) {
+ if (map[COS].args[0].toString() === symbol.args[0].toString()) {
+ t = cosAsinAtransform(symbol, map[COS]);
+ } else {
+ t = cosAsinBtransform(symbol, map[COS]);
+ }
+ delete map[COS];
+
+ retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, t));
+ } else if (fname === SIN && map[SIN]) {
+ if (map[SIN].args[0].toString() === symbol.args[0].toString()) {
+ // This should actually be redundant code but let's put just in case
+ t = /** @type {NerdamerSymbolType} */ (_.multiply(symbol, map[SIN]));
+ delete map[SIN];
+ } else {
+ t = sinAsinBtransform(symbol, map[SIN]);
+ delete map[SIN];
+ }
+
+ retval = t;
+ } else {
+ map[fname] = symbol;
+ }
+ } else {
+ retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, symbol));
+ }
+ }
+
+ // Put back the remaining functions
+ for (const x in map) {
+ if (!Object.hasOwn(map, x)) {
+ continue;
+ }
+ retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, map[x]));
+ }
+
+ return retval;
+ });
+
+ core.Settings.integration_depth = 10;
+
+ core.Settings.max_lim_depth = 10;
+
+ /** @type {CalculusModuleType} */
+ const __ = (core.Calculus = {
+ version: '1.4.6',
+
+ /**
+ * Computes the sum of a function over an index range
+ *
+ * @param {NerdamerSymbolType} fn
+ * @param {NerdamerSymbolType} index
+ * @param {NerdamerSymbolType} start
+ * @param {NerdamerSymbolType} end
+ * @returns {NerdamerSymbolType}
+ */
+ sum(fn, index, start, end) {
+ if (!(index.group === core.groups.S)) {
+ throw new core.exceptions.NerdamerTypeError(`Index must be symbol. ${text(index)} provided`);
+ }
+ const indexName = index.value;
+ let retval;
+ if (core.Utils.isNumericSymbol(start) && core.Utils.isNumericSymbol(end)) {
+ const modifier = /** @type {'' | 'PARSE2NUMBER'} */ (
+ Number(end) - Number(start) < 200 ? '' : 'PARSE2NUMBER'
+ );
+ const startNum = Number(start);
+ const endNum = Number(end);
+ retval = core.Utils.block(modifier, () => {
+ const f = fn.text();
+ /** @type {Record<string, NerdamerSymbolType | boolean>} */
+ const subs = { '~': true }; // Lock subs. Is this even being used?
+ let result = new core.NerdamerSymbol(0);
+
+ for (let i = startNum; i <= endNum; i++) {
+ subs[indexName] = new NerdamerSymbol(i);
+ const ans = _.parse(f, /** @type {Record<string, ExpressionParam>} */ (subs));
+ result = /** @type {NerdamerSymbolType} */ (_.add(result, ans));
+ }
+ return result;
+ });
+ } else {
+ retval = _.symfunction('sum', [fn, new NerdamerSymbol(indexName), start, end]);
+ }
+
+ return retval;
+ },
+ /**
+ * Computes the product of a function over an index range
+ *
+ * @param {NerdamerSymbolType} fn
+ * @param {NerdamerSymbolType} index
+ * @param {NerdamerSymbolType} start
+ * @param {NerdamerSymbolType} end
+ * @returns {NerdamerSymbolType}
+ */
+ product(fn, index, start, end) {
+ if (!(index.group === core.groups.S)) {
+ throw new core.exceptions.NerdamerTypeError(`Index must be symbol. ${text(index)} provided`);
+ }
+ const indexName = index.value;
+ let retval;
+ if (core.Utils.isNumericSymbol(start) && core.Utils.isNumericSymbol(end)) {
+ const modifier = /** @type {'' | 'PARSE2NUMBER'} */ (
+ Number(end) - Number(start) < 200 ? '' : 'PARSE2NUMBER'
+ );
+ retval = core.Utils.block(modifier, () => {
+ const startNum = Number(start);
+ const endNum = Number(end.multiplier);
+
+ const f = fn.text();
+ /** @type {Record<string, NerdamerSymbolType>} */
+ const subs = {};
+ let result = new core.NerdamerSymbol(1);
+
+ for (let i = startNum; i <= endNum; i++) {
+ subs[indexName] = new NerdamerSymbol(i);
+ result = /** @type {NerdamerSymbolType} */ (
+ _.multiply(result, _.parse(f, /** @type {Record<string, ExpressionParam>} */ (subs)))
+ );
+ }
+ return result;
+ });
+ } else {
+ retval = _.symfunction('product', [fn, new NerdamerSymbol(indexName), start, end]);
+ }
+
+ return retval;
+ },
+ /**
+ * Computes the derivative of a symbol
+ *
+ * @param {NerdamerSymbolType | VectorType | MatrixType} symbol
+ * @param {NerdamerSymbolType | string} [wrt]
+ * @param {NerdamerSymbolType | number} [nth]
+ * @returns {NerdamerSymbolType | VectorType | MatrixType}
+ */
+ diff(symbol, wrt, nth) {
+ if (core.Utils.isVector(symbol)) {
+ const vector = new core.Vector([]);
+ symbol.each(x => {
+ vector.elements.push(__.diff(/** @type {NerdamerSymbolType} */ (x), wrt, nth));
+ });
+ return vector;
+ }
+ if (core.Utils.isMatrix(symbol)) {
+ const matrix = new core.Matrix();
+ symbol.each((x, i, j) => {
+ matrix.set(i, j, __.diff(/** @type {NerdamerSymbolType} */ (x), wrt, nth));
+ });
+ return matrix;
+ }
+ const sym = /** @type {NerdamerSymbolType & { LHS?: NerdamerSymbolType; RHS?: NerdamerSymbolType }} */ (
+ symbol
+ );
+ if (sym.LHS && sym.RHS) {
+ // Equation, diff both sides
+ const result = new core.Equation(
+ /** @type {NerdamerSymbolType} */ (__.diff(sym.LHS.clone(), wrt, nth)),
+ /** @type {NerdamerSymbolType} */ (__.diff(sym.RHS.clone(), wrt, nth))
+ );
+ return /** @type {NerdamerSymbolType} */ (/** @type {unknown} */ (result));
+ }
+
+ let d = isSymbol(wrt) ? wrt.text() : wrt;
+ // The nth derivative
+ nth = /** @type {number} */ (isSymbol(nth) ? nth.multiplier.toDecimal() : nth || 1);
+
+ if (d === undefined) {
+ d = core.Utils.variables(/** @type {NerdamerSymbolType} */ (symbol))[0];
+ }
+
+ // Unwrap sqrt
+ if (sym.group === FN && sym.fname === SQRT) {
+ const s = sym.args[0];
+ const sp = /** @type {FracType} */ (sym.power).clone();
+ // These groups go to zero anyway so why waste time?
+ if (s.group !== N || s.group !== P) {
+ s.power = isSymbol(s.power)
+ ? /** @type {NerdamerSymbolType | FracType} */ (
+ _.multiply(_.multiply(s.power, new NerdamerSymbol(1 / 2)), new NerdamerSymbol(sp))
+ )
+ : s.power.multiply(new Frac(0.5)).multiply(sp);
+ s.multiplier = s.multiplier.multiply(sym.multiplier);
+ }
+
+ symbol = s;
+ }
+
+ if (symbol.group === FN && !isSymbol(symbol.power)) {
+ const a = derive(_.parse(symbol));
+ const b = __.diff(symbol.args[0].clone(), d);
+ symbol = _.multiply(a, b); // Chain rule
+ } else {
+ symbol = derive(symbol);
+ }
+
+ if (nth > 1) {
+ nth--;
+ symbol = __.diff(symbol, wrt, nth);
+ }
+
+ return symbol;
+
+ // Equivalent to "derivative of the outside".
+ function polydiff(s) {
+ if (s.value === d || s.contains(d, true)) {
+ s.multiplier = s.multiplier.multiply(s.power);
+ s.power = s.power.subtract(new Frac(1));
+ if (s.power.equals(0)) {
+ s = new NerdamerSymbol(s.multiplier);
+ }
+ }
+
+ return s;
+ }
+
+ function derive(s) {
+ const g = s.group;
+ let _a;
+ let b;
+ let cp;
+
+ if (g === N || (g === S && s.value !== d) || g === P) {
+ s = new NerdamerSymbol(0);
+ } else if (g === S) {
+ s = polydiff(s);
+ } else if (g === CB) {
+ const m = s.multiplier.clone();
+ s.toUnitMultiplier();
+ const retval = _.multiply(productRule(s), polydiff(s));
+ retval.multiplier = retval.multiplier.multiply(m);
+ return retval;
+ } else if (g === FN && s.power.equals(1)) {
+ // Table of known derivatives
+ const m = s.multiplier.clone();
+ s.toUnitMultiplier();
+
+ switch (s.fname) {
+ case LOG:
+ cp = s.clone();
+ s = s.args[0].clone(); // Get the arguments
+ s.power = s.power.negate();
+ s.multiplier = cp.multiplier.divide(s.multiplier);
+ break;
+ case COS:
+ // Cos -> -sin
+ s.fname = SIN;
+ s.multiplier.negate();
+ break;
+ case SIN:
+ // Sin -> cos
+ s.fname = COS;
+ break;
+ case TAN:
+ // Tan -> sec^2
+ s.fname = SEC;
+ s.power = new Frac(2);
+ break;
+ case SEC:
+ // Use a clone if this gives errors
+ s = qdiff(s, TAN);
+ break;
+ case CSC:
+ s = qdiff(s, '-cot');
+ break;
+ case COT:
+ s.fname = CSC;
+ s.multiplier.negate();
+ s.power = new Frac(2);
+ break;
+ case ASIN:
+ s = _.parse(`(sqrt(1-(${text(s.args[0])})^2))^(-1)`);
+ break;
+ case ACOS:
+ s = _.parse(`-(sqrt(1-(${text(s.args[0])})^2))^(-1)`);
+ break;
+ case ATAN:
+ s = _.parse(`(1+(${text(s.args[0])})^2)^(-1)`);
+ break;
+ case ABS:
+ // Depending on the complexity of the symbol it's easier to just parse it into a new symbol
+ // this should really be readdressed soon
+ b = s.args[0].clone();
+ b.toUnitMultiplier();
+ s = _.parse(`${inBrackets(text(s.args[0]))}/abs${inBrackets(text(b))}`);
+ break;
+ case 'parens':
+ // See product rule: f'.g goes to zero since f' will return zero. This way we only get back
+ // 1*g'
+ s = new NerdamerSymbol(1);
+ break;
+ case 'cosh':
+ // Cosh -> -sinh
+ s.fname = 'sinh';
+ break;
+ case 'sinh':
+ // Sinh -> cosh
+ s.fname = 'cosh';
+ break;
+ case TANH:
+ // Tanh -> sech^2
+ s.fname = SECH;
+ s.power = new Frac(2);
+ break;
+ case SECH:
+ // Use a clone if this gives errors
+ s = qdiff(s, '-tanh');
+ break;
+ case CSCH: {
+ const cschArg = String(s.args[0]);
+ s = _.parse(`-coth(${cschArg})*csch(${cschArg})`);
+ break;
+ }
+ case COTH: {
+ const cothArg = String(s.args[0]);
+ s = _.parse(`-csch(${cothArg})^2`);
+ break;
+ }
+ case 'asinh':
+ s = _.parse(`(sqrt(1+(${text(s.args[0])})^2))^(-1)`);
+ break;
+ case 'acosh':
+ s = _.parse(`(sqrt(-1+(${text(s.args[0])})^2))^(-1)`);
+ break;
+ case 'atanh':
+ s = _.parse(`(1-(${text(s.args[0])})^2)^(-1)`);
+ break;
+ case ASECH: {
+ const asechArg = String(s.args[0]);
+ s = _.parse(`-1/(sqrt(1/(${asechArg})^2-1)*(${asechArg})^2)`);
+ break;
+ }
+ case ACOTH:
+ s = _.parse(`-1/((${s.args[0]})^2-1)`);
+ break;
+ case ACSCH: {
+ const arg = String(s.args[0]);
+ s = _.parse(`-1/(sqrt(1/(${arg})^2+1)*(${arg})^2)`);
+ break;
+ }
+ case ASEC: {
+ const arg = String(s.args[0]);
+ s = _.parse(`1/(sqrt(1-1/(${arg})^2)*(${arg})^2)`);
+ break;
+ }
+ case ACSC: {
+ const arg = String(s.args[0]);
+ s = _.parse(`-1/(sqrt(1-1/(${arg})^2)*(${arg})^2)`);
+ break;
+ }
+ case ACOT:
+ s = _.parse(`-1/((${s.args[0]})^2+1)`);
+ break;
+ case 'S': {
+ const arg = String(s.args[0]);
+ s = _.parse(`sin((pi*(${arg})^2)/2)`);
+ break;
+ }
+ case 'C': {
+ const arg = String(s.args[0]);
+ s = _.parse(`cos((pi*(${arg})^2)/2)`);
+ break;
+ }
+ case 'Si': {
+ const arg = s.args[0];
+ s = _.parse(`sin(${arg})/(${arg})`);
+ break;
+ }
+ case 'Shi': {
+ const arg = s.args[0];
+ s = _.parse(`sinh(${arg})/(${arg})`);
+ break;
+ }
+ case 'Ci': {
+ const arg = s.args[0];
+ s = _.parse(`cos(${arg})/(${arg})`);
+ break;
+ }
+ case 'Chi': {
+ const arg = s.args[0];
+ s = _.parse(`cosh(${arg})/(${arg})`);
+ break;
+ }
+ case 'Ei': {
+ const arg = s.args[0];
+ s = _.parse(`e^(${arg})/(${arg})`);
+ break;
+ }
+ case 'Li': {
+ const arg = s.args[0];
+ s = _.parse(`1/${Settings.LOG}(${arg})`);
+ break;
+ }
+ case 'erf':
+ s = _.parse(`(2*e^(-(${s.args[0]})^2))/sqrt(pi)`);
+ break;
+ case 'atan2': {
+ const x_ = String(s.args[0]);
+ const y_ = String(s.args[1]);
+ s = _.parse(`(${y_})/((${y_})^2+(${x_})^2)`);
+ break;
+ }
+ case 'sign':
+ s = new NerdamerSymbol(0);
+ break;
+ case 'sinc':
+ s = _.parse(format('(({0})*cos({0})-sin({0}))*({0})^(-2)', s.args[0]));
+ break;
+ case Settings.LOG10:
+ s = _.parse(`1/((${s.args[0]})*${Settings.LOG}(10))`);
+ break;
+ default:
+ s = _.symfunction('diff', [s, wrt]);
+ }
+ s.multiplier = s.multiplier.multiply(m);
+ } else if (g === EX || (g === FN && isSymbol(s.power))) {
+ let value;
+ if (g === EX) {
+ value = s.value;
+ } else if (g === FN && s.contains(d)) {
+ value = s.fname + inBrackets(text(s.args[0]));
+ } else {
+ value = s.value + inBrackets(text(s.args[0]));
+ }
+ b = __.diff(_.multiply(_.parse(LOG + inBrackets(value)), s.power.clone()), d);
+ s = _.multiply(s, b);
+ } else if (g === FN && !s.power.equals(1)) {
+ b = s.clone();
+ b.toLinear();
+ b.toUnitMultiplier();
+ s = _.multiply(polydiff(s.clone()), derive(b));
+ } else if (g === CP || g === PL) {
+ // Note: Do not use `parse` since this puts back the sqrt and causes a bug as in #610. Use clone.
+ const c = s.clone();
+ let result = new NerdamerSymbol(0);
+ for (const x in s.symbols) {
+ if (!Object.hasOwn(s.symbols, x)) {
+ continue;
+ }
+ result = /** @type {NerdamerSymbolType} */ (_.add(result, __.diff(s.symbols[x].clone(), d)));
+ }
+ s = _.multiply(polydiff(c), result);
+ }
+
+ s.updateHash();
+
+ return s;
+ }
+ function qdiff(s, val, altVal) {
+ return _.multiply(s, _.parse(val + inBrackets(altVal || text(s.args[0]))));
+ }
+ function productRule(s) {
+ // Grab all the symbols within the CB symbol
+ const symbols = s.collectSymbols();
+ let result = new NerdamerSymbol(0);
+ const l = symbols.length;
+ // Loop over all the symbols
+ for (let i = 0; i < l; i++) {
+ let df = __.diff(symbols[i].clone(), d);
+ for (let j = 0; j < l; j++) {
+ // Skip the symbol of which we just pulled the derivative
+ if (i !== j) {
+ // Multiply out the remaining symbols
+ df = /** @type {NerdamerSymbolType} */ (_.multiply(df, symbols[j].clone()));
+ }
+ }
+ // Add the derivative to the result
+ result = /** @type {NerdamerSymbolType} */ (_.add(result, df));
+ }
+ return result; // Done
+ }
+ },
+ integration: {
+ /**
+ * Performs u-substitution for integration.
+ *
+ * @param {NerdamerSymbolType[]} symbols - Array of symbols to work with
+ * @param {string} dx - Variable of integration
+ * @returns {NerdamerSymbolType | VectorType | MatrixType | undefined}
+ */
+ u_substitution(symbols, dx) {
+ // May cause problems if person is using this already. Will need
+ // to find algorithm for detecting conflict
+ const u = '__u__';
+
+ function tryCombo(a, b, f) {
+ const d = __.diff(b, dx);
+ const q = f ? f(a, b) : _.divide(a.clone(), d);
+ if (!q.contains(dx, true)) {
+ return q;
+ }
+ return null;
+ }
+ function doFnSub(fname, arg) {
+ let subbed = /** @type {NerdamerSymbolType} */ (
+ __.integrate(_.symfunction(fname, [new NerdamerSymbol(u)]), u, 0)
+ );
+ subbed = subbed.sub(new NerdamerSymbol(u), arg);
+ subbed.updateHash();
+ return subbed;
+ }
+
+ const a = symbols[0].clone();
+ const b = symbols[1].clone();
+ const g1 = a.group;
+ const g2 = b.group;
+ let Q;
+ if (g1 === FN && g2 !== FN) {
+ // E.g. 2*x*cos(x^2)
+ const arg = a.args[0];
+ Q = tryCombo(b, arg.clone());
+ if (Q) {
+ return _.multiply(Q, doFnSub(a.fname, arg));
+ }
+ Q = tryCombo(b, a);
+ if (Q) {
+ return __.integration.poly_integrate(a);
+ }
+ } else if (g2 === FN && g1 !== FN) {
+ // E.g. 2*(x+1)*cos((x+1)^2
+ const arg = b.args[0];
+ Q = tryCombo(a, arg.clone());
+ if (Q) {
+ return _.multiply(Q, doFnSub(b.fname, arg));
+ }
+ } else if (g1 === FN && g2 === FN) {
+ Q = tryCombo(a.clone(), b.clone());
+ if (Q) {
+ return _.multiply(__.integration.poly_integrate(b), Q);
+ }
+ Q = tryCombo(b.clone(), a.clone());
+ if (Q) {
+ return _.multiply(__.integration.poly_integrate(b), Q);
+ }
+ } else if (g1 === EX && g2 !== EX) {
+ const p = a.power;
+ Q = tryCombo(b, isSymbol(p) ? p.clone() : new NerdamerSymbol(p));
+ if (!Q) {
+ // One more try
+ const dc = __.integration.decompose_arg(isSymbol(p) ? p.clone() : new NerdamerSymbol(p), dx);
+ // Consider the possibility of a^x^(n-1)*x^n dx
+ const xp = /** @type {NerdamerSymbolType} */ (__.diff(dc[2].clone(), dx));
+ const dc2 = __.integration.decompose_arg(xp.clone(), dx);
+ // If their powers equal, so if dx*p == b
+ if (
+ /** @type {NerdamerSymbolType} */ (_.multiply(dc[1], dc2[1])).power.equals(
+ /** @type {FracType} */ (b.power)
+ )
+ ) {
+ const m = _.divide(dc[0].clone(), dc2[0].clone());
+
+ let newVal = _.multiply(
+ m.clone(),
+ _.pow(new NerdamerSymbol(a.value), _.multiply(dc[0], new NerdamerSymbol(u)))
+ );
+ newVal = _.multiply(newVal, new NerdamerSymbol(u));
+ return /** @type {NerdamerSymbolType} */ (__.integration.by_parts(newVal, u, 0, {})).sub(
+ u,
+ dc[1].clone()
+ );
+ }
+ }
+ const integrated = /** @type {NerdamerSymbolType} */ (
+ __.integrate(a.sub(/** @type {NerdamerSymbolType} */ (p.clone()), new NerdamerSymbol(u)), u, 0)
+ );
+ const retval = _.multiply(
+ integrated.sub(new NerdamerSymbol(u), /** @type {NerdamerSymbolType} */ (p)),
+ Q
+ );
+
+ return retval;
+ } else if (g2 === EX && g1 !== EX) {
+ const p = b.power;
+ Q = tryCombo(a, /** @type {NerdamerSymbolType} */ (p.clone()));
+ const integrated = /** @type {NerdamerSymbolType} */ (
+ __.integrate(b.sub(/** @type {NerdamerSymbolType} */ (p), new NerdamerSymbol(u)), u, 0)
+ );
+ return _.multiply(integrated.sub(new NerdamerSymbol(u), /** @type {NerdamerSymbolType} */ (p)), Q);
+ } else if (a.isComposite() || b.isComposite()) {
+ const f = function (sym1, sym2) {
+ const d = __.diff(sym2, dx);
+ const A = /** @type {FactorSubModuleType} */ (core.Algebra.Factor).factorInner(sym1);
+ const B = /** @type {FactorSubModuleType} */ (core.Algebra.Factor).factorInner(
+ /** @type {NerdamerSymbolType} */ (d)
+ );
+ const q = _.divide(A, B);
+ return q;
+ };
+ const f1 = a.isComposite() ? a.clone().toLinear() : a.clone();
+ const f2 = b.isComposite() ? b.clone().toLinear() : b.clone();
+ Q = tryCombo(f1.clone(), f2.clone(), f);
+ if (Q) {
+ return _.multiply(__.integration.poly_integrate(b), Q);
+ }
+ Q = tryCombo(f2.clone(), f1.clone(), f);
+ if (Q) {
+ return _.multiply(__.integration.poly_integrate(a), Q);
+ }
+ }
+ return undefined;
+ },
+ // Simple integration of a single polynomial x^(n+1)/(n+1)
+ /**
+ * @param {NerdamerSymbolType} x
+ * @returns {NerdamerSymbolType}
+ */
+ poly_integrate(x) {
+ const p = x.power.toString();
+ const m = x.multiplier.toDecimal();
+ const s = x.toUnitMultiplier().toLinear();
+ if (Number(p) === -1) {
+ return /** @type {NerdamerSymbolType} */ (
+ _.multiply(new NerdamerSymbol(m), _.symfunction(LOG, [s]))
+ );
+ }
+ return /** @type {NerdamerSymbolType} */ (_.parse(format('({0})*({1})^(({2})+1)/(({2})+1)', m, s, p)));
+ },
+ // If we're just spinning wheels we want to stop. This is why we
+ // wrap integration in a try catch block and call this to stop.
+ /**
+ * @param {string} [msg]
+ * @returns {never}
+ */
+ stop(msg) {
+ msg ||= 'Unable to compute integral!';
+ core.Utils.warn(msg);
+ throw new NoIntegralFound(msg);
+ },
+ /**
+ * @param {NerdamerSymbolType} input
+ * @param {NerdamerSymbolType | string} dx
+ * @param {number} depth
+ * @param {IntegrationOptions} opt
+ * @returns {NerdamerSymbolType}
+ */
+ partial_fraction(input, dx, depth, opt) {
+ // TODO: This whole thing needs to be rolled into one but for now I'll leave it as two separate parts
+ if (!isSymbol(dx)) {
+ dx = /** @type {NerdamerSymbolType} */ (_.parse(dx));
+ }
+
+ let result;
+ result = new NerdamerSymbol(0);
+ const partialFractions = /** @type {NerdamerSymbolType} */ (
+ /** @type {PartFracSubModuleType} */ (core.Algebra.PartFrac).partfrac(
+ input,
+ /** @type {NerdamerSymbolType} */ (dx)
+ )
+ );
+
+ if (partialFractions.group === CB && partialFractions.isLinear()) {
+ // Perform a quick check to make sure that all partial fractions are linear
+ partialFractions.each(x => {
+ if (!x.isLinear()) {
+ __.integration.stop();
+ }
+ });
+ partialFractions.each(x => {
+ result = /** @type {NerdamerSymbolType} */ (_.add(result, __.integrate(x, dx, depth, opt)));
+ });
+ } else {
+ result = /** @type {NerdamerSymbolType} */ (
+ _.add(result, __.integrate(partialFractions, dx, depth, opt))
+ );
+ }
+ return result;
+ },
+ get_udv(symbol) {
+ const parts = [
+ [
+ /* L*/
+ ],
+ [
+ /* I*/
+ ],
+ [
+ /* A*/
+ ],
+ [
+ /* T*/
+ ],
+ [
+ /* E*/
+ ],
+ ];
+ // First we sort them
+ const setSymbol = function (x) {
+ const g = x.group;
+ if (g === FN) {
+ const { fname } = x;
+ if (core.Utils.inTrig(fname) || core.Utils.inHtrig(fname)) {
+ parts[3].push(x);
+ } else if (core.Utils.inInverseTrig(fname)) {
+ parts[1].push(x);
+ } else if (fname === LOG) {
+ parts[0].push(x);
+ } else {
+ __.integration.stop();
+ }
+ } else if (g === S || (x.isComposite() && x.isLinear()) || (g === CB && x.isLinear())) {
+ parts[2].push(x);
+ } else if (g === EX || (x.isComposite() && !x.isLinear())) {
+ parts[4].push(x);
+ } else {
+ __.integration.stop();
+ }
+ };
+
+ if (symbol.group === CB) {
+ symbol.each(x => {
+ setSymbol(NerdamerSymbol.unwrapSQRT(x, true));
+ });
+ } else {
+ setSymbol(symbol);
+ }
+ let u;
+ let dv = new NerdamerSymbol(1);
+ // Compile u and dv
+ for (let i = 0; i < 5; i++) {
+ const part = parts[i];
+ let t;
+ const l = part.length;
+ if (l > 0) {
+ if (l > 1) {
+ t = new NerdamerSymbol(1);
+ for (let j = 0; j < l; j++) {
+ t = /** @type {NerdamerSymbolType} */ (_.multiply(t, part[j].clone()));
+ }
+ } else {
+ t = part[0].clone();
+ }
+
+ if (u) {
+ dv = /** @type {NerdamerSymbolType} */ (_.multiply(dv, t)); // Everything else belongs to dv
+ } else {
+ u = t; // The first u encountered gets chosen
+ u.multiplier = u.multiplier.multiply(symbol.multiplier); // The first one gets the mutliplier
+ }
+ }
+ }
+
+ return [u, dv];
+ },
+
+ trig_sub(symbol, dx, depth, opt, parts, _symbols) {
+ parts ||= __.integration.decompose_arg(symbol.clone().toLinear(), dx);
+ const _b = parts[3];
+ const _ax = parts[2];
+ const a = parts[0];
+ const x = parts[1];
+ if (x.power.equals(2) && a.greaterThan(0)) {
+ // Use tan(x)
+ const t = core.Utils.getU(symbol); // Get an appropriate u
+ const u = _.parse(TAN + inBrackets(t)); // U
+ const du = _.parse(`${SEC + inBrackets(t)}^2`); // Du
+ const f = _.multiply(symbol.sub(x, u), du);
+ const integral = /** @type {NerdamerSymbolType} */ (__.integrate(f, t, depth, opt)).sub(u, x);
+ core.Utils.clearU(/** @type {string} */ (/** @type {unknown} */ (u)));
+ return integral;
+ }
+ return undefined;
+ },
+
+ /**
+ * Integration by parts
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {string} dx
+ * @param {number} depth
+ * @param {IntegrationOptions} o
+ * @returns {NerdamerSymbolType}
+ */
+ by_parts(symbol, dx, depth, o) {
+ o.previous ||= [];
+ let retval;
+ // First LIATE
+ const udv = __.integration.get_udv(symbol);
+ const u = udv[0];
+ const dv = udv[1];
+ let du = NerdamerSymbol.unwrapSQRT(
+ /** @type {NerdamerSymbolType} */ (_.expand(__.diff(u.clone(), dx))),
+ true
+ );
+ const c = du.clone().stripVar(/** @type {string} */ (dx));
+ // Strip any coefficients
+ du = /** @type {NerdamerSymbolType} */ (_.divide(du, c.clone()));
+ const v = __.integrate(dv.clone(), dx, depth || 0);
+ const vdu = /** @type {NerdamerSymbolType} */ (_.multiply(v.clone(), du));
+ const vduS = vdu.toString();
+ // Currently only supports e^x*(some trig)
+ if (o.previous.indexOf(vduS) !== -1 && core.Utils.inTrig(u.fname) && dv.isE()) {
+ // We're going to exploit the fact that vdu can never be constant
+ // to work out way out of this cycle. We'll return the length of
+ // the this.previous array until we're back at level one
+ o.is_cyclic = true;
+ // Return the integral.
+ return new NerdamerSymbol(1);
+ }
+ o.previous.push(vduS);
+
+ const uv = _.multiply(u, v);
+ // Clear the multiplier so we're dealing with a bare integral
+ const m = vdu.multiplier.clone();
+ vdu.toUnitMultiplier();
+ const integralVdu = _.multiply(__.integrate(vdu.clone(), dx, depth, o), c);
+ integralVdu.multiplier = integralVdu.multiplier.multiply(m);
+ retval = _.subtract(uv, integralVdu);
+ // We know that there cannot be constants so they're a holdover from a cyclic integral
+ if (o.is_cyclic) {
+ // Start popping the previous stack so we know how deep in we are
+ o.previous.pop();
+ if (o.previous.length === 0) {
+ retval = /** @type {NerdamerSymbolType} */ (_.expand(retval));
+ let rem = new NerdamerSymbol(0);
+ retval.each(x => {
+ if (!x.contains(dx)) {
+ rem = /** @type {NerdamerSymbolType} */ (_.add(rem, x.clone()));
+ }
+ });
+ // Get the actual uv
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.divide(_.subtract(retval, rem.clone()), _.subtract(new NerdamerSymbol(1), rem))
+ );
+ }
+ }
+
+ return /** @type {NerdamerSymbolType} */ (retval);
+ },
+ /*
+ * Dependents: [Solve, integrate]
+ */
+
+ decompose_arg: core.Utils.decompose_fn,
+ },
+ // TODO: nerdamer.integrate('-e^(-a*t)*sin(t)', 't') -> gives incorrect output
+ /**
+ * Integrates a symbol with respect to a variable.
+ *
+ * @param {NerdamerSymbolType | VectorType} originalSymbol - The symbol to integrate
+ * @param {string | NerdamerSymbolType} [dt] - The variable to integrate with respect to
+ * @param {number} [depth] - Recursion depth for integration
+ * @param {object} [opt] - Configuration options
+ * @returns {NerdamerSymbolType | VectorType | MatrixType}
+ */
+ integrate(originalSymbol, dt, depth, opt) {
+ // Add support for integrating vectors
+ if (core.Utils.isVector(originalSymbol)) {
+ const vector = new core.Vector([]);
+ originalSymbol.each(
+ /** @param {NerdamerSymbolType} x */
+ x => {
+ vector.elements.push(/** @type {NerdamerSymbolType} */ (__.integrate(x, dt)));
+ }
+ );
+ return vector;
+ }
+
+ // Assume integration wrt independent variable if expression only has one variable
+ if (!dt) {
+ const vars = core.Utils.variables(originalSymbol);
+ if (vars.length === 1) {
+ dt = vars[0];
+ }
+ // Defaults to x
+ dt ||= 'x';
+ }
+ if (!isNaN(parseFloat(/** @type {string} */ (dt)))) {
+ _.error(`variable expected but received ${dt}`);
+ }
+ // Get rid of constants right away
+ if (originalSymbol.isConstant(true)) {
+ return _.multiply(originalSymbol.clone(), _.parse(dt));
+ }
+
+ // Configurations options for integral. This is needed for tracking extra options
+ // e.g. cyclic integrals or additional settings
+ opt ||= {};
+ return core.Utils.block(
+ 'PARSE2NUMBER',
+ () => {
+ // Make a note of the original symbol. Set only if undefined
+ depth ||= 0;
+ const dx = isSymbol(dt) ? dt.toString() : dt;
+ // We don't want the symbol in sqrt form. x^(1/2) is prefererred
+ let symbol = NerdamerSymbol.unwrapSQRT(originalSymbol.clone(), true);
+ const g = symbol.group;
+ let retval;
+
+ try {
+ // We stop integration after x amount of recursive calls
+ if (++depth > core.Settings.integration_depth) {
+ __.integration.stop('Maximum depth reached. Exiting!');
+ }
+
+ // Constants. We first eliminate anything that doesn't have dx. Everything after this has
+ // to have dx or else it would have been taken care of below
+ if (!symbol.contains(dx, true)) {
+ retval = _.multiply(symbol.clone(), _.parse(dx));
+ }
+ // E.g. 2*x
+ else if (g === S) {
+ retval = __.integration.poly_integrate(symbol, dx, depth);
+ } else if (g === EX) {
+ if (
+ symbol.previousGroup === FN &&
+ !(symbol.fname === 'sqrt' || symbol.fname === Settings.PARENTHESIS)
+ ) {
+ __.integration.stop();
+ }
+ // Check the base
+ if (symbol.contains(dx) && symbol.previousGroup !== FN) {
+ // If the symbol also contains dx then we stop since we currently
+ // don't know what to do with it e.g. x^x
+ if (/** @type {NerdamerSymbolType} */ (symbol.power).contains(dx)) {
+ __.integration.stop();
+ } else {
+ const t = /** @type {NerdamerSymbolType} */ (
+ __.diff(symbol.clone().toLinear(), dx)
+ );
+ if (t.contains(dx)) {
+ __.integration.stop();
+ }
+ // Since at this point it's the base only then we do standard single poly integration
+ // e.g. x^y
+ retval = __.integration.poly_integrate(symbol);
+ }
+ }
+ // E.g. a^x or 9^x
+ else {
+ const a = /** @type {NerdamerSymbolType} */ (__.diff(symbol.power.clone(), dx));
+ if (a.contains(dx)) {
+ const aa = a.stripVar(dx);
+ const x = /** @type {NerdamerSymbolType} */ (_.divide(a.clone(), aa.clone()));
+ if (x.group === S && x.isLinear()) {
+ aa.multiplier = aa.multiplier.divide(new Frac(2));
+ return _.parse(
+ format(
+ '({2})*(sqrt(pi)*erf(sqrt(-{0})*{1}))/(2*sqrt(-{0}))',
+ aa,
+ dx,
+ symbol.multiplier
+ )
+ );
+ }
+ __.integration.stop();
+ }
+ if (symbol.isE()) {
+ if (a.isLinear()) {
+ retval = symbol;
+ } else if (
+ a.isE() &&
+ isSymbol(a.power) &&
+ a.power.group === S &&
+ a.power.power.equals(1)
+ ) {
+ const powerSym = isSymbol(symbol.power)
+ ? symbol.power
+ : new NerdamerSymbol(symbol.power);
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.multiply(_.symfunction('Ei', [powerSym.clone()]), powerSym)
+ );
+ } else {
+ __.integration.stop();
+ }
+ } else {
+ const d = _.symfunction(LOG, [_.parse(symbol.value)]);
+ retval = _.divide(symbol, d);
+ }
+ retval = _.divide(retval, a);
+ }
+ } else if (symbol.isComposite() && symbol.isLinear()) {
+ const m = _.parse(symbol.multiplier);
+ symbol.toUnitMultiplier();
+ retval = new NerdamerSymbol(0);
+ symbol.each(elem => {
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.add(retval, __.integrate(elem, dx, depth))
+ );
+ });
+ retval = /** @type {NerdamerSymbolType} */ (_.multiply(m, retval));
+ } else if (g === CP) {
+ if (symbol.power.greaterThan(1)) {
+ symbol = /** @type {NerdamerSymbolType} */ (_.expand(symbol));
+ }
+ if (symbol.power.equals(1)) {
+ retval = new NerdamerSymbol(0);
+ symbol.each(elem => {
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.add(retval, __.integrate(elem, dx, depth))
+ );
+ }, true);
+ } else {
+ const p = Number(symbol.power);
+ const m = symbol.multiplier.clone(); // Temporarily remove the multiplier
+ symbol.toUnitMultiplier();
+ const // Below we consider the form ax+b
+ fn = symbol.clone().toLinear(); // Get just the pure function without the power
+ const decomp = __.integration.decompose_arg(fn, dx);
+ // I have no idea why I used bx+a and not ax+b. TODO change this to something that makes sense
+ const b = decomp[3];
+ const ax = decomp[2];
+ const a = decomp[0];
+ const x = decomp[1];
+ if (p === -1 && x.group !== PL && x.power.equals(2)) {
+ const bIsPositive = isInt(b) ? Number(b) > 0 : true;
+ // We can now check for atan
+ if (x.group === S && x.power.equals(2) && bIsPositive) {
+ /// /then we have atan
+ // abs is redundants since the sign appears in both denom and num.
+ /**
+ * @param {NerdamerSymbolType} s
+ * @returns {NerdamerSymbolType}
+ */
+ const unwrapAbs = function (s) {
+ let result = new NerdamerSymbol(1);
+ s.each(elem => {
+ result = /** @type {NerdamerSymbolType} */ (
+ _.multiply(result, elem.fname === 'abs' ? elem.args[0] : elem)
+ );
+ });
+ return result;
+ };
+ let A = a.clone();
+ let B = b.clone();
+ A = /** @type {NerdamerSymbolType} */ (_.pow(A, new NerdamerSymbol(1 / 2)));
+ B = /** @type {NerdamerSymbolType} */ (_.pow(B, new NerdamerSymbol(1 / 2)));
+ // Unwrap abs
+
+ const d = _.multiply(unwrapAbs(B), unwrapAbs(A));
+ const f = _.symfunction(ATAN, [
+ _.divide(_.multiply(a, x.toLinear()), d.clone()),
+ ]);
+ retval = _.divide(f, d);
+ } else if (x.group === S && x.isLinear()) {
+ retval = _.divide(__.integration.poly_integrate(symbol), a);
+ // 1/(x^4+1)
+ } else if (x.power.equals(4)) {
+ // https://www.freemathhelp.com/forum/threads/55678-difficult-integration-int-1-(1-x-4)-dx
+ const br = inBrackets;
+ // Apply rule: ax^4+b = (√ax^2+√2∜a∜bx+√b)(√ax^2-√2∜a∜bx+√b)
+ // get quadratic factors
+ const A = _.parse(`${SQRT + br(String(a))}*${dx}^2`);
+ const B = _.parse(
+ `${SQRT + br(String(2))}*${br(String(a))}^${br('1/4')}*${br(String(b))}^${br('1/4')}*${dx}`
+ );
+ const C = _.parse(SQRT + br(String(b)));
+ const f1 = _.add(_.add(A.clone(), B.clone()), C.clone());
+ const f2 = _.add(_.subtract(A, B), C);
+ // Calculate numerators: [D+E, D-E] -> [√2*b^(3/4)+√b∜ax, √2*b^(3/4)-√b∜ax]
+ const D = _.parse(`${SQRT + br(String(2))}*${br(String(b))}^${br('3/4')}`);
+ const E = _.parse(
+ `${SQRT + br(String(b))}*${br(String(b))}^${br('1/4')}*${dx}`
+ );
+ // Let F = 2b√2∜b
+ const F = _.parse(
+ `${2}*${br(String(b))}*${SQRT}${br(String(2))}*${br(String(b))}^${br('1/4')}`
+ );
+ // Calculate the factors
+ const L1 = _.divide(
+ _.subtract(D.clone(), E.clone()),
+ _.multiply(F.clone(), f2)
+ );
+ const L2 = _.divide(_.add(D, E), _.multiply(F, f1.clone()));
+ retval = _.add(
+ __.integrate(L1, dx, depth, opt),
+ __.integrate(L2, dx, depth, opt)
+ );
+ // Let's try partial fractions
+ } else {
+ retval = __.integration.partial_fraction(symbol, dx, depth);
+ }
+ } else if (p === -1 / 2) {
+ // Detect asin and atan
+ if (x.group === S && x.power.equals(2)) {
+ if (ax.multiplier.lessThan(0) && !b.multiplier.lessThan(0)) {
+ a.negate();
+ // It's asin
+ if (b.isConstant() && a.isConstant()) {
+ const d = _.symfunction(SQRT, [a.clone()]);
+ const d2 = _.symfunction(SQRT, [_.multiply(a.clone(), b)]);
+ retval = _.divide(
+ _.symfunction(ASIN, [_.divide(ax.toLinear(), d2)]),
+ d
+ );
+ }
+ // I'm not sure about this one. I'm trusting Wolfram Alpha here
+ else {
+ const sqrtA = _.symfunction(SQRT, [a]);
+ const sqrtAx = _.multiply(sqrtA.clone(), x.clone().toLinear());
+ retval = _.divide(
+ _.symfunction(ATAN, [
+ _.divide(sqrtAx, _.symfunction(SQRT, [fn.clone()])),
+ ]),
+ sqrtA
+ );
+ }
+ } else {
+ /* WHAT HAPPENS HERE???? e.g. integrate(3/sqrt(-a+b*x^2),x) or integrate(3/sqrt(a+b*x^2),x)*/
+ __.integration.stop();
+ }
+ } else {
+ // This would be a case like 1/(sqrt(1-x^3) or 1/(1-(x+1)^2)
+ __.integration.stop();
+ }
+ } else if (p === 1 / 2 && x.power.equals(2) && a.greaterThan(0)) {
+ // TODO: Revisit
+ // should become (sinh(2*acosh(x))/4-acosh(x)/2))
+ __.integration.stop();
+ } else if (x.isLinear() && x.group !== PL) {
+ retval = _.divide(__.integration.poly_integrate(symbol), a);
+ } else if (x.power.equals(2) && a.greaterThan(0)) {
+ // 1/(a*x^2+b^2)^n
+ // strip the value of b so b = 1
+ const sqa = _.parse(SQRT + inBrackets(a)); // Strip a so b = 1
+ const sqb = _.parse(SQRT + inBrackets(b));
+ const aob = /** @type {NerdamerSymbolType} */ (
+ _.multiply(sqa.clone(), sqb.clone())
+ ).invert();
+ const bsqi = _.pow(
+ b,
+ new NerdamerSymbol(/** @type {FracType} */ (symbol.power).toDecimal())
+ );
+ const uv = core.Utils.getU(symbol);
+ const u = _.multiply(aob, x.clone().toLinear());
+ // Use symfunction instead of _.parse(ATAN + inBrackets(u)) to preserve
+ // exact fractions. String concatenation triggers valueOf() which converts
+ // fractions to decimals, and parsing them back loses precision.
+ // e.g., 1/3 → "0.333..." → 321685687669321/965057063007964
+ const v = _.symfunction(ATAN, [u]);
+ // The conversion will be 1+tan(x)^2 -> sec(x)^2
+ // since the denominator is now (sec(x)^2)^n and the numerator is sec(x)^2
+ // then the remaining sec will be (n-1)*2;
+ const n = (Math.abs(Number(/** @type {FracType} */ (symbol.power))) - 1) * 2;
+ // 1/sec(x)^n can now be converted to cos(x)^n and we can pull the integral of that
+ const integral = /** @type {NerdamerSymbolType} */ (
+ __.integrate(_.parse(`${COS + inBrackets(uv)}^${n}`))
+ );
+ core.Utils.clearU(uv);
+ return _.multiply(integral.sub(uv, v), bsqi);
+ } else if (
+ symbol.group !== CB &&
+ !(/** @type {FracType} */ (symbol.power).lessThan(0))
+ ) {
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ } else {
+ const f = symbol.clone().toLinear();
+ const factored = /** @type {FactorSubModuleType} */ (
+ core.Algebra.Factor
+ ).factorInner(f);
+ const wasFactored = factored.toString() !== f.toString();
+ if (core.Algebra.degree(f, _.parse(dx)).equals(2) && !wasFactored) {
+ try {
+ const sq = core.Algebra.sqComplete(f, dx);
+ const u = core.Utils.getU(f);
+ const f1 = sq.f.sub(sq.a, u);
+ const fx = _.pow(f1, _.parse(symbol.power));
+ retval = /** @type {NerdamerSymbolType} */ (__.integrate(fx, u)).sub(
+ u,
+ sq.a
+ );
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ __.integration.stop();
+ }
+ } else {
+ retval = __.integration.partial_fraction(symbol, dx, depth, opt);
+ }
+ }
+ retval.multiplier = retval.multiplier.multiply(m);
+ }
+ } else if (g === FN) {
+ const arg = symbol.args[0];
+ const m = symbol.multiplier.clone();
+ symbol.toUnitMultiplier();
+ const decomp = __.integration.decompose_arg(arg, dx);
+ // Easies way I can think of to get the coefficient and to make sure
+ // that the symbol is linear wrt dx. I'm not actually trying to get the
+ // derivative
+ const a = decomp[0];
+ const x = decomp[1];
+ const { fname } = symbol;
+ // Log is a special case that can be handled with integration by parts
+ if (fname === LOG || fname === ASIN || fname === ACOS || (fname === ATAN && x.isLinear())) {
+ /* Integration by parts */
+ const p = symbol.power.toString();
+ if (isInt(p)) {
+ depth -= Number(p);
+ } // It needs more room to find the integral
+
+ if (arg.isComposite()) {
+ // Integral u du
+ const u = core.Utils.getU(symbol);
+ const f = _.pow(_.parse(LOG + inBrackets(u)), new NerdamerSymbol(p));
+ const du = __.diff(arg, dx);
+ const uDu = _.multiply(f, du);
+ const integral = /** @type {NerdamerSymbolType} */ (
+ __.integrate(uDu, u, depth, opt)
+ );
+ retval = _.multiply(_.parse(m), integral.sub(u, arg));
+ } else {
+ retval = _.multiply(_.parse(m), __.integration.by_parts(symbol, dx, depth, opt));
+ }
+ } else if (fname === TAN && symbol.power.lessThan(0)) {
+ // Convert to cotangent
+ const sym = symbol.clone();
+ sym.power.negate();
+ sym.fname = COT;
+ return _.multiply(_.parse(m), __.integrate(sym, dx, depth));
+ } else {
+ if (!a.contains(dx, true) && symbol.isLinear()) {
+ // Perform a deep search for safety
+ // first handle the special cases
+ if (fname === ABS) {
+ // REVISIT **TODO**
+ const absX = /** @type {NerdamerSymbolType} */ (
+ _.divide(arg.clone(), a.clone())
+ );
+ if (absX.group === S && !absX.power.lessThan(0)) {
+ if (core.Utils.even(/** @type {FracType} */ (absX.power))) {
+ retval = __.integrate(arg, dx, depth);
+ } else {
+ const integrated = /** @type {NerdamerSymbolType} */ (
+ __.integrate(absX, dx, depth)
+ );
+ integrated.power = /** @type {FracType} */ (integrated.power).subtract(
+ new Frac(1)
+ );
+ retval = _.multiply(
+ _.multiply(_.symfunction(ABS, [absX.toLinear()]), integrated),
+ a
+ );
+ }
+ } else {
+ __.integration.stop();
+ }
+ } else {
+ const ag = symbol.args[0].group;
+ const decomposed = __.integration.decompose_arg(arg, dx);
+
+ if (
+ !(ag === CP || ag === S || ag === CB) ||
+ !(/** @type {FracType} */ (decomposed[1].power).equals(1)) ||
+ arg.hasFunc('')
+ ) {
+ __.integration.stop();
+ }
+ /** TODO */ // ASIN, ACOS, ATAN
+ switch (fname) {
+ case COS:
+ retval = _.symfunction(SIN, [arg]);
+ break;
+ case SIN:
+ retval = _.symfunction(COS, [arg]);
+ retval.negate();
+ break;
+ case TAN:
+ retval = _.parse(format(`${Settings.LOG}(sec({0}))`, arg));
+ break;
+ case SEC:
+ retval = _.parse(format(`${Settings.LOG}(tan({0})+sec({0}))`, arg));
+ break;
+ case CSC:
+ retval = _.parse(format(`-${Settings.LOG}(csc({0})+cot({0}))`, arg));
+ break;
+ case COT:
+ retval = _.parse(format(`${Settings.LOG}(sin({0}))`, arg));
+ break;
+ case SINH:
+ retval = _.symfunction(COSH, [arg]);
+ break;
+ case COSH:
+ retval = _.symfunction(SINH, [arg]);
+ break;
+ case TANH:
+ retval = _.parse(format(`${Settings.LOG}(cosh({0}))`, arg));
+ break;
+ case ASEC:
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ break;
+ case ACSC:
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ break;
+ case ACOT:
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ break;
+ // Inverse htrig
+ case ASECH:
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ break;
+ case ACSCH:
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ break;
+ case ACOTH:
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ break;
+ // End inverse htrig
+ // htrigh
+ case SECH:
+ retval = _.parse(format('atan(sinh({0}))', arg));
+ break;
+ case CSCH:
+ retval = _.parse(format(`${Settings.LOG}(tanh(({0})/2))`, arg));
+ break;
+ case COTH:
+ retval = _.parse(format(`${Settings.LOG}(sinh({0}))`, arg));
+ break;
+ // End htrig
+ case EXP:
+ retval = __.integrate(_.parse(format('e^({0})', arg)), dx, depth);
+ break;
+ case 'S': {
+ const sArg = symbol.args[0].clone();
+ const sDc = __.integration.decompose_arg(sArg, dx);
+ const _sX_ = sDc[1]; // Unused, x is used in format string
+ const sA_ = sDc[0];
+ const sB_ = sDc[3];
+ retval = _.parse(
+ format(
+ '(cos((1/2)*pi*(({1})+({0})*({2}))^2)+pi*(({1})+({0})*({2}))*S(({1})+({0})*({2})))/(({0})*pi)',
+ sA_,
+ sB_,
+ x
+ )
+ );
+ break;
+ }
+ case 'C': {
+ const cArg = symbol.args[0].clone();
+ const cDc = __.integration.decompose_arg(cArg, dx);
+ const cX_ = cDc[1];
+ const cA_ = cDc[0];
+ const cB_ = cDc[3];
+ retval = _.parse(
+ format(
+ '(pi*(({1})+({0})*({2}))*C(({1})+({0})*({2}))-sin((1/2)*pi*(({1})+({0})*({2}))^2))/(({0})*pi)',
+ cA_,
+ cB_,
+ cX_
+ )
+ );
+ break;
+ }
+ case 'erf': {
+ const erfArg = symbol.args[0].clone();
+ const erfDc = __.integration.decompose_arg(erfArg, dx);
+ const erfX_ = erfDc[1];
+ const erfA_ = erfDc[0];
+ retval = _.parse(
+ format(
+ 'e^(-(({2}))^2)/(({0})*sqrt(pi))+(1/({0})+({1}))*erf(({2}))',
+ erfA_,
+ erfX_,
+ erfArg
+ )
+ );
+ break;
+ }
+ case 'sign':
+ retval = _.multiply(symbol.clone(), arg.clone());
+ break;
+ default:
+ __.integration.stop();
+ }
+
+ retval = _.divide(retval, a);
+ }
+ } else if (x.isLinear()) {
+ if (fname === COS || fname === SIN) {
+ const p = Number(symbol.power);
+ // Check to see if it's negative and then just transform it to sec or csc
+ if (p < 0) {
+ symbol.fname = fname === SIN ? CSC : SEC;
+ symbol.invert().updateHash();
+ retval = __.integrate(symbol, dx, depth);
+ } else {
+ const _innerArg = symbol.args[0];
+ const rd = symbol.clone(); // Cos^(n-1)
+ const rd2 = symbol.clone(); // Cos^(n-2)
+ const q = new NerdamerSymbol((p - 1) / p); //
+ const na = /** @type {NerdamerSymbolType} */ (
+ _.multiply(a.clone(), new NerdamerSymbol(p))
+ ).invert(); // 1/(n*a)
+ rd.power = /** @type {FracType} */ (rd.power).subtract(new Frac(1));
+ rd2.power = /** @type {FracType} */ (rd2.power).subtract(new Frac(2));
+
+ const t = _.symfunction(fname === COS ? SIN : COS, [arg.clone()]);
+ if (fname === SIN) {
+ t.negate();
+ }
+ retval = _.add(
+ _.multiply(_.multiply(na, rd), t),
+ _.multiply(q, __.integrate(_.parse(rd2), dx, depth))
+ );
+ }
+ }
+ // Tan(x)^n or cot(x)^n
+ else if (fname === TAN || fname === COT) {
+ // http://www.sosmath.com/calculus/integration/moretrigpower/moretrigpower.html
+ if (symbol.args[0].isLinear(dx)) {
+ const n = /** @type {FracType} */ (symbol.power)
+ .subtract(new Frac(1))
+ .toString();
+ let r = symbol.clone().toUnitMultiplier();
+ const w = _.parse(
+ format(
+ `${fname === COT ? '-' : ''}1/({2}*{0})*{3}({1})^({0})`,
+ n,
+ arg,
+ a,
+ fname
+ )
+ );
+ r.power = /** @type {FracType} */ (r.power).subtract(new Frac(2));
+ if (r.power.equals(0)) {
+ r = /** @type {NerdamerSymbolType} */ (_.parse(r));
+ }
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.subtract(w, __.integrate(r, dx, depth))
+ );
+ }
+ }
+ // Sec(x)^n or csc(x)^n
+ else if (fname === SEC || fname === CSC) {
+ // http://www.sosmath.com/calculus/integration/moretrigpower/moretrigpower.html
+ const n1 = /** @type {FracType} */ (symbol.power)
+ .subtract(new Frac(1))
+ .toString();
+ const n2 = /** @type {FracType} */ (symbol.power)
+ .subtract(new Frac(2))
+ .toString();
+ const f2 = fname === SEC ? TAN : COT;
+ let r = symbol.clone().toUnitMultiplier();
+ const parseStr = format(
+ `${fname === CSC ? '-' : ''}1/({0}*{1})*{4}({3})^({2})*{5}({3})`,
+ a,
+ n1,
+ n2,
+ arg,
+ fname,
+ f2
+ );
+ const w = _.parse(parseStr);
+ r.power = /** @type {FracType} */ (r.power).subtract(new Frac(2));
+ if (r.power.equals(0)) {
+ r = /** @type {NerdamerSymbolType} */ (_.parse(r));
+ }
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.add(
+ w,
+ _.multiply(
+ new NerdamerSymbol(Number(n2) / Number(n1)),
+ __.integrate(r, dx, depth)
+ )
+ )
+ );
+ } else if ((fname === COSH || fname === SINH) && symbol.power.equals(2)) {
+ retval = __.integrate(symbol.fnTransform(), dx, depth);
+ } else {
+ __.integration.stop();
+ }
+ } else {
+ __.integration.stop();
+ }
+
+ retval.multiplier = retval.multiplier.multiply(m);
+ }
+ } else if (g === PL) {
+ retval = __.integration.partial_fraction(symbol, dx, depth);
+ } else if (g === CB) {
+ const den = symbol.getDenom();
+ if (den.group === S) {
+ symbol = /** @type {NerdamerSymbolType} */ (_.expand(symbol));
+ }
+
+ // Separate the coefficient since all we care about are symbols containing dx
+ let coeff = symbol.stripVar(dx);
+ // Now get only those that apply
+ let cfsymbol = /** @type {NerdamerSymbolType} */ (_.divide(symbol.clone(), coeff.clone())); // A coeff free symbol
+ // peform a correction for stripVar. This is a serious TODO!
+ if (coeff.contains(dx)) {
+ cfsymbol = /** @type {NerdamerSymbolType} */ (_.multiply(cfsymbol, coeff));
+ coeff = new NerdamerSymbol(1);
+ }
+
+ // If we only have one symbol left then let's not waste time. Just pull the integral
+ // and let the chips fall where they may
+ if (cfsymbol.group === CB) {
+ // We collect the symbols and sort them descending group, descending power, descending alpabethically
+ const symbols = cfsymbol
+ .collectSymbols()
+ .sort(
+ /**
+ * @param {NerdamerSymbolType} s1
+ * @param {NerdamerSymbolType} s2
+ */
+ (s1, s2) => {
+ if (s1.group === s2.group) {
+ if (Number(s1.power) === Number(s2.power)) {
+ if (s1 < s2) {
+ return 1;
+ } // I want sin first
+
+ return -1;
+ }
+ return Number(s2.power) - Number(s1.power); // Descending power
+ }
+ return s2.group - s1.group; // Descending groups
+ }
+ )
+ .map(
+ /**
+ * @param {NerdamerSymbolType} elem
+ * @returns {NerdamerSymbolType}
+ */
+ elem => {
+ const unwrapped = NerdamerSymbol.unwrapSQRT(elem, true);
+ if (unwrapped.fname === EXP) {
+ return /** @type {NerdamerSymbolType} */ (
+ _.parse(
+ format('({1})*e^({0})', unwrapped.args[0], unwrapped.multiplier)
+ )
+ );
+ }
+ return /** @type {NerdamerSymbolType} */ (unwrapped);
+ }
+ );
+ const l = symbols.length;
+ if (Number(symbol.power) < 0) {
+ if (l === 2) {
+ return __.integrate(
+ /** @type {NerdamerSymbolType} */ (_.expand(symbol)),
+ dx,
+ depth,
+ opt
+ );
+ }
+ }
+ // Otherwise the denominator is one lumped together symbol
+ // Generate an image for
+ else if (l === 2) {
+ // Try u substitution
+ try {
+ retval = __.integration.u_substitution(symbols, dx);
+ } catch (e) {
+ /* Failed :`(*/
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ }
+
+ if (!retval) {
+ // No success with u substitution so let's try known combinations
+ // are they two functions
+ const g1 = symbols[0].group;
+ const g2 = symbols[1].group;
+ let sym1 = symbols[0];
+ let sym2 = symbols[1];
+ const fn1 = sym1.fname;
+ const fn2 = sym2.fname;
+ // Reset the symbol minus the coeff
+ symbol = /** @type {NerdamerSymbolType} */ (
+ _.multiply(sym1.clone(), sym2.clone())
+ );
+ if (g1 === FN && g2 === FN) {
+ if (fn1 === LOG || fn2 === LOG) {
+ retval = __.integration.by_parts(symbol.clone(), dx, depth, opt);
+ } else {
+ symbols.sort((s1, s2) => (s2.fname > s1.fname ? 1 : -1));
+ const arg1 = sym1.args[0];
+ // Make sure the arguments are suitable. We don't know how to integrate non-linear arguments
+ if (
+ !arg1.isLinear() ||
+ !(arg1.group === CP || arg1.group === CB || arg1.group === S)
+ ) {
+ __.integration.stop();
+ }
+
+ const decomp = __.integration.decompose_arg(arg1, dx);
+ const x = decomp[1];
+ const a = decomp[0];
+ if (!x.isLinear()) // Again... linear arguments only wrt x
+ {
+ __.integration.stop();
+ }
+
+ // They have to have the same arguments and then we have cleared all the check to
+ // make sure we can integrate FN & FN
+ const arg2 = sym2.args[0];
+ // Make sure that their argument matches
+ if (arg1.equals(arg2)) {
+ if ((fn1 === SIN && fn2 === COS) || (fn1 === COS && fn2 === SIN)) {
+ if (/** @type {FracType} */ (sym1.power).lessThan(0)) {
+ __.integration.stop();
+ } // We don't know how to handle, sin(x)^n/cos(x)^m where m > n, yet
+ // if it's in the form sin(x)^n*cos(x)^n then we can just return tan(x)^n which we know how to integrate
+ if (
+ fn1 === SIN &&
+ /** @type {FracType} */ (sym1.power)
+ .add(/** @type {FracType} */ (sym2.power))
+ .equals(0)
+ ) {
+ sym1.fname = TAN;
+ sym1.updateHash();
+ retval = __.integrate(sym1, dx, depth);
+ } else if (
+ even(/** @type {FracType} */ (sym1.power)) &&
+ fn2 === COS &&
+ /** @type {FracType} */ (sym2.power).lessThan(0)
+ ) {
+ // Transform sin^(2*n) to (1-cos^2)^n
+ const n = Number(sym1.power) / 2;
+ const newSym = _.parse(
+ format('(1-cos({0})^2)^({1})', sym1.args[0], n)
+ );
+ retval = __.integrate(
+ _.expand(_.multiply(newSym, sym2.clone())),
+ dx,
+ depth,
+ opt
+ );
+ } else if (
+ even(/** @type {FracType} */ (sym1.power)) &&
+ fn2 === SIN &&
+ /** @type {FracType} */ (sym2.power).lessThan(0)
+ ) {
+ // Transform cos^(2*n) to (1-sin^2)^n
+ const n = Number(sym1.power) / 2;
+ const newSym = _.parse(
+ format('(1-sin({0})^2)^({1})', sym1.args[0], n)
+ );
+ retval = __.integrate(
+ _.expand(_.multiply(newSym, sym2.clone())),
+ dx,
+ depth,
+ opt
+ );
+ } else {
+ const p1Even = core.Utils.even(
+ /** @type {FracType} */ (sym1.power)
+ );
+ const p2Even = core.Utils.even(
+ /** @type {FracType} */ (sym2.power)
+ );
+ retval = new NerdamerSymbol(0);
+ if (!p1Even || !p2Even) {
+ let u;
+ let r;
+ // Since cos(x) is odd it carries du. If sin was odd then it would be the other way around
+ // know that p1 satifies the odd portion in this case. If p2 did than it would contain r
+ if (p1Even) {
+ u = sym1;
+ r = sym2;
+ } else {
+ // U = sin(x)
+ u = sym2;
+ r = sym1;
+ }
+ // Get the sign of du. In this case r carries du as stated before and D(cos(x),x) = -sin(x)
+ const sign = u.fname === COS ? -1 : 1;
+ const n = Number(r.power);
+ // Remove the du e.g. cos(x)^2*sin(x)^3 dx -> cos(x)^2*sin(x)^2*sin(x). We're left with two
+ // even powers afterwards which can be transformed
+ const k = (n - 1) / 2;
+ // Make the transformation cos(x)^2 = 1 - sin(x)^2
+ const trigTrans = _.parse(
+ `(1-${u.fname}${core.Utils.inBrackets(
+ arg1.toString()
+ )}^2)^${k}`
+ );
+ const sym = _.expand(
+ _.multiply(
+ new NerdamerSymbol(sign),
+ _.multiply(u.clone(), trigTrans)
+ )
+ );
+ // We can now just loop through and integrate each since it's now just a polynomial with functions
+ sym.each(elem => {
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.add(
+ retval,
+ __.integration.poly_integrate(elem.clone())
+ )
+ );
+ });
+ } else {
+ // Performs double angle transformation
+ const doubleAngle = function (s) {
+ const pow = s.power;
+ const k = pow / 2;
+ let e;
+ if (s.fname === COS) {
+ e = `((1/2)+(cos(2*(${s.args[0]}))/2))^${k}`;
+ } else {
+ e = `((1/2)-(cos(2*(${s.args[0]}))/2))^${k}`;
+ }
+
+ return _.parse(e);
+ };
+ // They're both even so transform both using double angle identities and we'll just
+ // be able to integrate by the sum of integrals
+ const daA = doubleAngle(sym1);
+ const daB = doubleAngle(sym2);
+ const t = _.multiply(daA, daB);
+ const sym = _.expand(t);
+ sym.each(elem => {
+ retval = _.add(
+ retval,
+ __.integrate(elem, dx, depth)
+ );
+ });
+ return _.multiply(retval, coeff);
+ }
+ }
+ }
+ // Tan(x)*sec(x)^n
+ else if (
+ fn1 === SEC &&
+ fn2 === TAN &&
+ x.isLinear() &&
+ sym2.isLinear()
+ ) {
+ retval = _.parse(
+ format('sec({0})^({1})/({1})', sym1.args[0], sym1.power)
+ );
+ } else if (fn1 === TAN && fn2 === SEC && x.isLinear()) {
+ // Remaining: tan(x)^3*sec(x)^6
+ if (sym1.isLinear() && sym2.isLinear()) {
+ retval = _.divide(_.symfunction(SEC, [arg1.clone()]), a);
+ } else if (even(/** @type {FracType} */ (sym1.power))) {
+ const p = Number(sym1.power) / 2;
+ // Transform tangent
+ const t = _.parse(
+ format('(sec({0})^2-1)^({1})', sym1.args[0], p)
+ );
+ retval = __.integrate(
+ _.expand(_.multiply(t, sym2)),
+ dx,
+ depth
+ );
+ } else {
+ __.integration.stop();
+ }
+ } else if (fn1 === SEC && fn2 === COS) {
+ sym1.fname = COS;
+ sym1.invert().updateHash();
+ retval = __.integrate(_.multiply(sym1, sym2), dx, depth);
+ } else if (fn1 === SIN && fn2 === CSC) {
+ sym2.fname = SIN;
+ sym2.invert().updateHash();
+ retval = __.integrate(_.multiply(sym1, sym2), dx, depth);
+ }
+ // Tan/cos
+ else if (
+ fn1 === TAN &&
+ (fn2 === COS || fn2 === SIN) &&
+ sym2.power.lessThan(0)
+ ) {
+ const t = _.multiply(sym1.fnTransform(), sym2);
+ retval = __.integrate(_.expand(t), dx, depth);
+ } else {
+ const t = _.multiply(sym1.fnTransform(), sym2.fnTransform());
+ retval = __.integrate(_.expand(t), dx, depth);
+ }
+ }
+ // TODO: In progress
+ else if ((fn1 === SIN || fn1 === COS) && (fn2 === SIN || fn2 === COS)) {
+ if (sym1.isLinear() && sym2.isLinear()) {
+ // If in the form cos(a*x)*sin(b*x)
+ if (sym1.args[0].isLinear() && sym2.args[0].isLinear()) {
+ // Use identity (sin(b*x+a*x)+sin(b*x-a*x))/2
+ let ax;
+ let bx;
+ if (fn2 === SIN) {
+ ax = sym1.args[0];
+ bx = sym2.args[0];
+ } else {
+ bx = sym1.args[0];
+ ax = sym2.args[0];
+ }
+
+ // Make the transformation
+ const f = _.parse(
+ format(
+ '(sin(({1})+({0}))+sin(({1})-({0})))/2',
+ ax.toString(),
+ bx.toString()
+ )
+ );
+
+ // Integrate it
+ retval = __.integrate(f, dx, depth);
+ } else {
+ const transformed = trigTransform(symbols);
+ retval = __.integrate(_.expand(transformed), dx, depth);
+ }
+ } else {
+ let transformed = new NerdamerSymbol(1);
+ symbols.forEach(s => {
+ const transformedS = s.fnTransform();
+ transformed = /** @type {NerdamerSymbolType} */ (
+ _.multiply(transformed, transformedS)
+ );
+ });
+ const t = /** @type {NerdamerSymbolType} */ (
+ _.expand(transformed)
+ );
+
+ retval = /** @type {NerdamerSymbolType} */ (
+ __.integrate(t, dx, depth)
+ );
+
+ if (retval.hasIntegral()) {
+ retval = __.integrate(
+ trigTransform(
+ /** @type {NerdamerSymbolType[]} */ (
+ transformed.collectSymbols()
+ )
+ ),
+ dx,
+ depth
+ );
+ }
+ }
+ } else {
+ __.integration.stop();
+ }
+ }
+ } else if (g1 === FN && g2 === S) {
+ const sym1IsLinear = sym1.isLinear();
+ if (sym1.fname === COS && sym1IsLinear && sym2.power.equals(-1)) {
+ retval = _.symfunction('Ci', [sym1.args[0]]);
+ } else if (sym1.fname === COS && sym2.power.equals(-1)) {
+ retval = __.integrate(
+ _.multiply(sym1.fnTransform(), sym2.clone()),
+ dx,
+ depth
+ );
+ } else if (sym1.fname === COSH && sym1IsLinear && sym2.power.equals(-1)) {
+ retval = _.symfunction('Chi', [sym1.args[0]]);
+ } else if (sym1.fname === COSH && sym2.power.equals(-1)) {
+ retval = __.integrate(
+ _.multiply(sym1.fnTransform(), sym2.clone()),
+ dx,
+ depth
+ );
+ } else if (sym1.fname === SIN && sym1IsLinear && sym2.power.equals(-1)) {
+ retval = _.symfunction('Si', [sym1.args[0]]);
+ } else if (sym1.fname === SIN && sym2.power.equals(-1)) {
+ retval = __.integrate(
+ _.multiply(sym1.fnTransform(), sym2.clone()),
+ dx,
+ depth
+ );
+ } else if (sym1.fname === SINH && sym1IsLinear && sym2.power.equals(-1)) {
+ retval = _.symfunction('Shi', [sym1.args[0]]);
+ } else if (sym1.fname === SINH && sym2.power.equals(-1)) {
+ retval = __.integrate(
+ _.multiply(sym1.fnTransform(), sym2.clone()),
+ dx,
+ depth
+ );
+ } else if (sym1.fname === LOG && sym2.power.equals(-1)) {
+ // Log(x)^n/x = log(x)^(n+1)/(n+1)
+ retval = __.integration.poly_integrate(sym1, dx, depth);
+ } else if (sym1.fname === 'erf') {
+ if (sym2.power.equals(1)) {
+ const dc = __.integration.decompose_arg(sym1.args[0], dx);
+ const a_ = dc[0];
+ const x_ = dc[1];
+ const arg = sym1.args[0].toString();
+ retval = _.parse(
+ format(
+ '(e^(-(({2}))^2)*(sqrt(pi)*e^((({2}))^2)*(2*({0})^2*({1})^2-3)*erf(({2}))+2*({0})*({1})-2))/(4*sqrt(pi)*({0})^2)',
+ a_,
+ x_,
+ arg
+ )
+ );
+ }
+ } else {
+ // Since group S is guaranteed convergence we need not worry about tracking depth of integration
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ }
+ } else if (g1 === EX && g2 === S) {
+ const x =
+ fn1 === LOG ? __.integration.decompose_arg(sym1.args[0], dx)[1] : null;
+ if (
+ sym1.isE() &&
+ hasPowerGroupSOrCB(sym1) &&
+ /** @type {FracType} */ (sym2.power).equals(-1)
+ ) {
+ retval = _.symfunction('Ei', [
+ /** @type {NerdamerSymbolType} */ (sym1.power.clone()),
+ ]);
+ } else if (fn1 === LOG && x.value === sym2.value) {
+ retval = __.integration.poly_integrate(sym1);
+ } else {
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ }
+ } else if (g1 === PL && g2 === S) {
+ // First try to reduce the top
+ if (
+ sym2.value === sym1.value &&
+ /** @type {FracType} */ (sym1.power).equals(-1)
+ ) {
+ // Find the lowest power in the denominator
+ const pd = Math.min.apply(null, core.Utils.keys(sym1.symbols));
+ // Get the lowest common value between denominator and numerator
+ const pc = Math.min(pd, Number(sym2.power));
+ // Reduce both denominator and numerator by that factor
+ const factor = sym2.clone();
+ factor.power = new Frac(pc);
+ sym2 = /** @type {NerdamerSymbolType} */ (
+ _.divide(sym2, factor.clone())
+ ); // Reduce the denominator
+ let t = new NerdamerSymbol(0);
+ sym1.each(elem => {
+ t = /** @type {NerdamerSymbolType} */ (
+ _.add(t, _.divide(elem.clone(), factor.clone()))
+ );
+ });
+ t.multiplier = sym1.multiplier;
+ symbol = /** @type {NerdamerSymbolType} */ (_.divide(sym2, t));
+ } else {
+ symbol = /** @type {NerdamerSymbolType} */ (_.expand(symbol));
+ }
+ retval = __.integration.partial_fraction(symbol, dx, depth);
+ } else if (g1 === CP && g2 === S) {
+ const f = sym1.clone().toLinear();
+ const fIsLinear = core.Algebra.degree(f, _.parse(dx)).equals(1);
+ // Handle cases x^(2*n)/sqrt(1-x^2)
+ if (sym1.power.equals(-1 / 2)) {
+ const decomp = __.integration.decompose_arg(
+ sym1.clone().toLinear(),
+ dx
+ );
+ const a = decomp[0].negate();
+ const x = decomp[1];
+ const b = decomp[3];
+ const p1 = Number(sym1.power);
+ const p2 = Number(sym2.power);
+ if (isInt(p2) && core.Utils.even(p2) && x.power.equals(2)) {
+ // If the substitution
+ let c = _.divide(
+ _.multiply(
+ _.pow(b.clone(), new NerdamerSymbol(2)),
+ _.symfunction(SQRT, [_.divide(b.clone(), a.clone())])
+ ),
+ _.pow(a.clone(), new NerdamerSymbol(2))
+ );
+ c = _.multiply(c, _.symfunction(SQRT, [b]).invert());
+ const dummy = _.parse('sin(u)');
+ dummy.power = /** @type {FracType} */ (dummy.power).multiply(
+ /** @type {FracType} */ (sym2.power)
+ );
+ const integral = /** @type {NerdamerSymbolType} */ (
+ __.integrate(dummy, 'u', depth)
+ );
+ const bksub = _.parse(`${ASIN}(${SQRT}(${a}/${b})*${dx})`);
+ retval = _.multiply(
+ c,
+ integral.sub(new NerdamerSymbol('u'), bksub)
+ );
+ } else if (p1 === -1 / 2) {
+ const uTransform = function (func, subst) {
+ const intg = _.parse(
+ /** @type {NerdamerSymbolType} */ (
+ __.integrate(func, dx, depth, opt)
+ ).sub(dx, format(subst, dx))
+ );
+ if (!intg.hasIntegral()) {
+ return intg;
+ }
+ return undefined;
+ };
+ if (p2 === -1) {
+ retval = uTransform(
+ /** @type {NerdamerSymbolType} */ (
+ _.expand(
+ _.expand(
+ _.pow(
+ _.multiply(sym1.invert(), sym2.invert()),
+ new NerdamerSymbol(2)
+ )
+ )
+ )
+ ).invert(),
+ 'sqrt(1-1/({0})^2)'
+ );
+ } else if (p2 === -2) {
+ // Apply transformation to see if it matches asin(x)
+ retval = uTransform(
+ /** @type {NerdamerSymbolType} */ (
+ _.sqrt(
+ /** @type {NerdamerSymbolType} */ (
+ _.expand(
+ /** @type {NerdamerSymbolType} */ (
+ _.divide(
+ /** @type {NerdamerSymbolType} */ (
+ _.pow(
+ symbol,
+ new NerdamerSymbol(2)
+ )
+ ).invert(),
+ _.pow(
+ new NerdamerSymbol(dx),
+ new NerdamerSymbol(2)
+ )
+ )
+ ).negate()
+ )
+ )
+ )
+ ).invert(),
+ 'sqrt(1-1/({0})^2)'
+ );
+ }
+ }
+ } else if (sym1.power.equals(-1) && sym2.isLinear() && fIsLinear) {
+ retval = __.integration.partial_fraction(symbol, dx, depth);
+ } else if (!sym1.power.lessThan(0) && isInt(sym1.power)) {
+ // Sum of integrals
+ const expanded = _.expand(sym1);
+ retval = new NerdamerSymbol(0);
+ expanded.each(elem => {
+ if (elem.group === PL) {
+ elem.each(inner => {
+ retval = _.add(
+ retval,
+ __.integrate(_.multiply(sym2.clone(), inner), dx, depth)
+ );
+ });
+ } else {
+ retval = _.add(
+ retval,
+ __.integrate(_.multiply(sym2.clone(), elem), dx, depth)
+ );
+ }
+ });
+ } else if (sym1.power.lessThan(-2)) {
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ } else if (sym1.power.lessThan(0) && sym2.power.greaterThan(1)) {
+ const decomp = __.integration.decompose_arg(
+ sym1.clone().toLinear(),
+ dx
+ );
+ const _a = decomp[0].negate();
+ const x = decomp[1];
+ const b = decomp[3];
+ const fn = sym1.clone().toLinear();
+
+ if (x.group !== PL && x.isLinear()) {
+ const p = Number(sym2.power);
+ const du = '_u_';
+ const u = new NerdamerSymbol(du);
+ // Pull the integral with the subsitution
+ const U = _.expand(
+ _.divide(
+ _.pow(
+ _.subtract(u.clone(), b.clone()),
+ new NerdamerSymbol(p)
+ ),
+ u.clone()
+ )
+ );
+ /** @type {Record<string, NerdamerSymbolType>} */
+ const scope = {};
+
+ // Generate a scope for resubbing the symbol
+ scope[du] = /** @type {NerdamerSymbolType} */ (fn);
+ const U2 = /** @type {NerdamerSymbolType} */ (
+ _.parse(/** @type {NerdamerSymbolType} */ (U), scope)
+ );
+ retval = __.integrate(U2, dx, 0);
+ } else if (
+ /** @type {FracType} */ (sym2.power).greaterThan(
+ /** @type {FracType} */ (x.power)
+ ) ||
+ /** @type {FracType} */ (sym2.power).equals(
+ /** @type {FracType} */ (x.power)
+ )
+ ) {
+ // Factor out coefficients
+ const factors = new /** @type {AlgebraClassesSubModuleType} */ (
+ core.Algebra.Classes
+ ).Factors();
+ sym1 = /** @type {FactorSubModuleType} */ (
+ core.Algebra.Factor
+ ).coeffFactor(sym1.invert(), factors);
+ const div = core.Algebra.divide(sym2, sym1);
+ // It assumed that the result will be of group CB
+ if (/** @type {NerdamerSymbolType} */ (div).group === CB) {
+ // Try something else
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ } else {
+ retval = new NerdamerSymbol(0);
+ /** @type {NerdamerSymbolType} */ (div).each(elem => {
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.add(retval, __.integrate(elem, dx, depth))
+ );
+ });
+ // Put back the factors
+ factors.each(factor => {
+ retval = _.divide(retval, factor);
+ });
+
+ retval = _.expand(retval);
+ }
+ } else {
+ retval = __.integration.partial_fraction(symbol, dx, depth);
+ }
+ // Handle cases such as (1-x^2)^(n/2)*x^(m) where n is odd ___ cracking knuckles... This can get a little hairy
+ } else if (/** @type {FracType} */ (sym1.power).den.equals(2)) {
+ // Assume the function is in the form (a^2-b*x^n)^(m/2)
+ const dc = /** @type {NerdamerSymbolType[]} */ (
+ __.integration.decompose_arg(sym1.clone().toLinear(), dx)
+ );
+ // Using the above definition
+ const a = dc[3];
+ const x = dc[1];
+ const b = dc[0];
+ const _bx = dc[2];
+ if (/** @type {FracType} */ (x.power).equals(2) && b.lessThan(0)) {
+ // If n is even && b is negative
+ // make a equal 1 so we can do a trig sub
+ if (!a.equals(1)) {
+ // Divide a out of everything
+ // move a to the coeff
+ coeff = /** @type {NerdamerSymbolType} */ (
+ _.multiply(coeff, _.pow(a, new NerdamerSymbol(2)))
+ );
+ }
+ const u = dx;
+ const c = /** @type {NerdamerSymbolType} */ (
+ _.divide(
+ _.pow(b.clone().negate(), new NerdamerSymbol(1 / 2)),
+ _.pow(a, new NerdamerSymbol(1 / 2))
+ )
+ );
+ const du = _.symfunction(COS, [new NerdamerSymbol(u)]);
+ const cosn = _.pow(
+ _.symfunction(COS, [new NerdamerSymbol(u)]),
+ new NerdamerSymbol(
+ Number(/** @type {FracType} */ (sym1.power).num)
+ )
+ );
+ const X = _.pow(
+ _.symfunction(SIN, [new NerdamerSymbol(u)]),
+ new NerdamerSymbol(Number(/** @type {FracType} */ (sym2.power)))
+ );
+ const val = /** @type {NerdamerSymbolType} */ (
+ _.multiply(_.multiply(cosn, du), X)
+ );
+ const integral = /** @type {NerdamerSymbolType} */ (
+ __.integrate(val, u, depth)
+ );
+ // But remember that u = asin(sqrt(b)*a*x)
+ retval = integral.sub(
+ u,
+ _.symfunction(ASIN, [_.multiply(new NerdamerSymbol(dx), c)])
+ );
+ } else {
+ retval = __.integration.partial_fraction(symbol, dx, depth, opt);
+ }
+ } else if (fIsLinear) {
+ retval = __.integration.partial_fraction(symbol, dx, depth);
+ }
+ } else if (sym1.isComposite() && sym2.isComposite()) {
+ // Sum of integrals
+ retval = new NerdamerSymbol(0);
+ if (sym1.power.greaterThan(0) && sym2.power.greaterThan(0)) {
+ // Combine and pull the integral of each
+ const sym = _.expand(symbol);
+ sym.each(elem => {
+ retval = _.add(retval, __.integrate(elem, dx, depth));
+ }, true);
+ } else {
+ const p1 = Number(sym1.power);
+ const p2 = Number(sym2.power);
+ if (p1 < 0 && p2 > 0) {
+ // Swap
+ const t = sym1;
+ sym1 = sym2;
+ sym2 = t;
+ }
+ if (p1 === -1 && p2 === -1) {
+ retval = __.integration.partial_fraction(symbol, dx, depth);
+ } else {
+ sym1.each(elem => {
+ const k = _.multiply(elem, sym2.clone());
+ const intg = __.integrate(k, dx, depth);
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.add(retval, intg)
+ );
+ });
+ }
+ }
+ } else if (
+ g1 === CP &&
+ /** @type {FracType} */ (symbols[0].power).greaterThan(0)
+ ) {
+ sym1 = /** @type {NerdamerSymbolType} */ (_.expand(sym1));
+ retval = new NerdamerSymbol(0);
+ sym1.each(elem => {
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.add(
+ retval,
+ __.integrate(
+ /** @type {NerdamerSymbolType} */ (
+ _.multiply(elem, sym2.clone())
+ ),
+ dx,
+ depth
+ )
+ )
+ );
+ }, true);
+ } else if (g1 === FN && g2 === EX && core.Utils.inHtrig(sym1.fname)) {
+ sym1 = sym1.fnTransform();
+ retval = __.integrate(_.expand(_.multiply(sym1, sym2)), dx, depth);
+ } else if ((g1 === FN && g2 === CP) || (g2 === FN && g1 === CP)) {
+ if (g2 === FN && g1 === CP) {
+ const t = sym1;
+ sym1 = sym2;
+ sym2 = t; // Swap
+ }
+ let p;
+ let q;
+ let sa;
+ let sb;
+ const du = NerdamerSymbol.unwrapSQRT(
+ /** @type {NerdamerSymbolType} */ (__.diff(sym1.clone(), dx)),
+ true
+ );
+ const sym2Clone = NerdamerSymbol.unwrapSQRT(sym2, true);
+ if (
+ /** @type {FracType} */ (du.power).equals(
+ /** @type {FracType} */ (sym2Clone.power)
+ )
+ ) {
+ p = new NerdamerSymbol(Number(sym2.power));
+ sa = du.clone().toLinear();
+ sb = sym2.clone().toLinear();
+ q = /** @type {NerdamerSymbolType} */ (
+ core.Algebra.divide(sa.toLinear(), sb)
+ );
+ if (q.isConstant()) {
+ const nq = _.pow(q, p.negate());
+ retval = _.multiply(
+ nq,
+ __.integration.poly_integrate(sym1.clone())
+ );
+ }
+ } else {
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ }
+ } else {
+ const syma = sym1.clone().toLinear();
+ const symb = sym2.clone().toLinear();
+ if (
+ g1 === EX &&
+ g2 === EX &&
+ /** @type {NerdamerSymbolType} */ (sym1.power).contains(dx) &&
+ /** @type {NerdamerSymbolType} */ (sym2.power).contains(dx) &&
+ !syma.contains(dx) &&
+ !symb.contains(dx)
+ ) {
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.parse(
+ format(
+ '(({0})^(({2})*({4}))*({1})^(({3})*({4})))/(log(({0})^({2}))+log(({1})^({3})))',
+ syma.toString(),
+ symb.toString(),
+ /** @type {NerdamerSymbolType} */ (
+ sym1.power
+ ).multiplier.toString(),
+ /** @type {NerdamerSymbolType} */ (
+ sym2.power
+ ).multiplier.toString(),
+ dx
+ )
+ )
+ );
+ } else {
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ }
+ }
+ }
+ } else if (
+ l === 3 &&
+ ((symbols[2].group === S &&
+ /** @type {FracType} */ (symbols[2].power).lessThan(2)) ||
+ symbols[0].group === CP)
+ ) {
+ let first = symbols[0];
+ if (first.group === CP) {
+ // TODO {support higher powers of x in the future}
+ if (/** @type {FracType} */ (first.power).greaterThan(1)) {
+ first = /** @type {NerdamerSymbolType} */ (_.expand(first));
+ }
+ const r = _.multiply(symbols[1], symbols[2]);
+ retval = new NerdamerSymbol(0);
+ first.each(elem => {
+ const prod = _.multiply(elem, r.clone());
+ const intg = __.integrate(prod, dx, depth);
+ retval = /** @type {NerdamerSymbolType} */ (_.add(retval, intg));
+ }, true);
+ } else {
+ // Try integration by parts although technically it will never work
+ retval = __.integration.by_parts(symbol, dx, depth, opt);
+ }
+ } else if (allFunctions(symbols)) {
+ let t = new NerdamerSymbol(1);
+ for (let i = 0, len = symbols.length; i < len; i++) {
+ t = /** @type {NerdamerSymbolType} */ (_.multiply(t, symbols[i].fnTransform()));
+ }
+ t = /** @type {NerdamerSymbolType} */ (_.expand(t));
+ retval = __.integrate(t, dx, depth);
+ } else {
+ // One more go
+ const transformed = trigTransform(symbols);
+ retval = __.integrate(
+ /** @type {NerdamerSymbolType} */ (_.expand(transformed)),
+ dx,
+ depth
+ );
+ }
+ } else {
+ if (cfsymbol.equals(1)) {
+ return __.integrate(
+ /** @type {NerdamerSymbolType} */ (_.expand(symbol)),
+ dx,
+ depth
+ );
+ }
+
+ // Only factor for multivariate which are polynomials
+ if (
+ cfsymbol.clone().toLinear().isPoly(true) &&
+ core.Utils.variables(cfsymbol).length > 1
+ ) {
+ cfsymbol = /** @type {FactorSubModuleType} */ (core.Algebra.Factor).factorInner(
+ cfsymbol
+ );
+ }
+
+ retval = __.integrate(cfsymbol, dx, depth);
+ }
+
+ retval = _.multiply(retval, coeff);
+ }
+ // If an integral was found then we return it
+ if (retval) {
+ return retval;
+ }
+ } catch (error) {
+ if (error.message === 'timeout') {
+ throw error;
+ }
+ // Do nothing if it's a NoIntegralFound error otherwise let it bubble
+ if (!(error instanceof NoIntegralFound || error instanceof core.exceptions.DivisionByZero)) {
+ throw error;
+ }
+ }
+
+ // No symbol found so we return the integral again
+ const dtStr = isSymbol(dt) ? dt.toString() : dt;
+ return /** @type {NerdamerSymbolType} */ (
+ _.symfunction('integrate', [originalSymbol, new NerdamerSymbol(dtStr)])
+ );
+ },
+ false
+ );
+ },
+ /**
+ * Definite integral from `from` to `to`
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {NerdamerSymbolType} from
+ * @param {NerdamerSymbolType} to
+ * @param {string} [dx]
+ * @returns {NerdamerSymbolType}
+ */
+ defint(symbol, from, to, dx) {
+ dx ||= 'x'; // Make x the default variable of integration
+ /**
+ * @param {NerdamerSymbolType} integral
+ * @param {Record<string, NerdamerSymbolType>} vars
+ * @param {NerdamerSymbolType} point
+ * @returns {NerdamerSymbolType}
+ */
+ const getValue = function (integral, vars, point) {
+ try {
+ return /** @type {NerdamerSymbolType} */ (_.parse(integral, vars));
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ // It failed for some reason so return the limit
+ const lim = /** @type {NerdamerSymbolType} */ (__.Limit.limit(integral, dx, point));
+ return lim;
+ }
+ };
+
+ const vars = core.Utils.variables(symbol);
+ const hasTrig = symbol.hasTrig();
+ let retval;
+ let integral;
+
+ // Fix #593 - Only assume the first variable if dx is not defined.
+ if (vars.length === 1 && !dx) {
+ dx = vars[0];
+ }
+
+ if (!hasTrig) {
+ integral = /** @type {NerdamerSymbolType} */ (__.integrate(symbol, dx));
+ }
+
+ if (!hasTrig && !integral.hasIntegral()) {
+ /** @type {Record<string, NerdamerSymbolType>} */
+ const upper = {};
+ /** @type {Record<string, NerdamerSymbolType>} */
+ const lower = {};
+ upper[dx] = to;
+ lower[dx] = from;
+
+ const a = getValue(integral, upper, to);
+ const b = getValue(integral, lower, from);
+ retval = /** @type {NerdamerSymbolType} */ (_.subtract(a, b));
+ } else if (vars.length === 1 && from.isConstant() && to.isConstant()) {
+ const f = core.Build.build(symbol);
+ retval = new NerdamerSymbol(
+ core.Math2.num_integrate(/** @type {(x: number) => number} */ (f), Number(from), Number(to))
+ );
+ } else {
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.symfunction('defint', [symbol, from, to, new NerdamerSymbol(dx)])
+ );
+ }
+ return retval;
+ },
+
+ Limit: {
+ /**
+ * @param {string} start
+ * @param {string} end
+ * @returns {VectorType}
+ */
+ interval(start, end) {
+ return /** @type {VectorType} */ (/** @type {unknown} */ (_.parse(format('[{0}, {1}]', start, end))));
+ },
+ diverges() {
+ return __.Limit.interval('-Infinity', 'Infinity');
+ },
+ /**
+ * Computes limit using L'Hopital's rule for 0/0 or inf/inf forms.
+ *
+ * @param {NerdamerSymbolType} f - Numerator
+ * @param {NerdamerSymbolType} g - Denominator
+ * @param {string} x - Variable
+ * @param {NerdamerSymbolType} lim - Limit value
+ * @param {number} depth - Recursion depth
+ * @returns {NerdamerSymbolType | VectorType | MatrixType | undefined}
+ */
+ divide(f, g, x, lim, depth) {
+ if (depth++ > Settings.max_lim_depth) {
+ return undefined;
+ }
+
+ const _fin = f.clone();
+ const gin = g.clone();
+
+ // But first a little "cheating". x/|x| ends up in an infinite loop since the d/dx |x| -> x/|x|
+ // To break this loop we simply provide the answer. Keep in mind that currently limit only provides
+ // the two-sided limit.
+ // Known limit
+ if (g.fname === ABS) {
+ const sign = f.sign();
+ const limSign = lim.sign();
+
+ if (/** @type {NerdamerSymbolType} */ (lim).isInfinity) {
+ return _.multiply(new NerdamerSymbol(sign), new NerdamerSymbol(limSign));
+ }
+ if (lim.equals(0)) {
+ const fm = _.parse(f.multiplier);
+ const gm = _.parse(g.multiplier);
+ return _.divide(_.multiply(fm, __.Limit.interval('-1', '1')), gm);
+ }
+ // TODO: Support more limits
+ return __.Limit.diverges();
+ }
+
+ /**
+ * @param {NerdamerSymbolType | VectorType} L
+ * @returns {boolean}
+ */
+ const isInfinity = function (L) {
+ if (core.Utils.isVector(L)) {
+ const vec = /** @type {VectorType} */ (L);
+ for (let i = 0; i < vec.elements.length; i++) {
+ if (!(/** @type {NerdamerSymbolType} */ (vec.elements[i]).isInfinity)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return /** @type {NerdamerSymbolType} */ (L).isInfinity;
+ };
+
+ const equals = function (L, v) {
+ if (core.Utils.isVector(L)) {
+ return false;
+ }
+ return L.equals(v);
+ };
+
+ let retval;
+ let count = 0;
+ let lim1;
+ let lim2;
+ let indeterminate;
+ // Let fOrig = f.clone();
+ // let gOrig = g.clone();
+ do {
+ lim1 = evaluate(/** @type {NerdamerSymbolType} */ (__.Limit.limit(f.clone(), x, lim, depth)));
+ lim2 = evaluate(/** @type {NerdamerSymbolType} */ (__.Limit.limit(g.clone(), x, lim, depth)));
+
+ // If it's in indeterminate form apply L'Hopital's rule
+ indeterminate = (isInfinity(lim1) && isInfinity(lim2)) || (equals(lim1, 0) && equals(lim2, 0));
+ // Pull the derivatives
+ if (indeterminate) {
+ const ft = __.diff(f.clone(), x);
+ const gt = __.diff(g.clone(), x);
+
+ // Expanding here causes issue #12.
+ // there is something fishy with expand that we will
+ // have to find some day.
+ // let tSymbol = _.expand(_.divide(ft, gt));
+ const tSymbol = /** @type {NerdamerSymbolType} */ (_.divide(ft, gt));
+ f = tSymbol.getNum();
+ g = tSymbol.getDenom();
+ }
+ } while (indeterminate && ++count < Settings.max_lim_depth);
+
+ if (count >= Settings.max_lim_depth) {
+ // Console.log("L'Hospital likely endless loop");
+ // console.log(" f:"+f);
+ // console.log(" g:"+g);
+ return undefined;
+ }
+
+ // REMEMBER:
+ // - 1/cos(x)
+ // n/0 is still possible since we only checked for 0/0
+ const denIsZero = lim2.equals(0);
+ const _p = Number(gin.power);
+
+ if (lim.isConstant(true) && denIsZero) {
+ // The sign of infinity depends on:
+ // - For even powers (x^2, x^4, etc.): denominator is always positive, so sign = sign(lim1)
+ // - For odd powers (x, x^3, etc.): two-sided limit doesn't exist, but we return
+ // the right-hand limit by convention, so sign = sign(lim1)
+ // In both cases, if lim1 < 0, the result is -Infinity
+ retval = NerdamerSymbol.infinity(lim1.lessThan(0) ? -1 : undefined);
+ } else if (denIsZero) {
+ retval = __.Limit.diverges();
+ } else {
+ retval = _.divide(lim1, lim2);
+ }
+
+ return retval;
+ },
+ /**
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ rewriteToLog(symbol) {
+ const p = /** @type {NerdamerSymbolType} */ (symbol.power.clone());
+ symbol.toLinear();
+ return /** @type {NerdamerSymbolType} */ (
+ _.pow(
+ new NerdamerSymbol('e'),
+ /** @type {NerdamerSymbolType} */ (_.multiply(p, _.symfunction(`${Settings.LOG}`, [symbol])))
+ )
+ );
+ },
+ /**
+ * @param {NerdamerSymbolType} f
+ * @param {string} x
+ * @param {NerdamerSymbolType} lim
+ * @returns {NerdamerSymbolType}
+ */
+ getSubbed(f, x, lim) {
+ let retval;
+ // 1. rewrite EX with base e
+ if (f.group === EX) {
+ f = /** @type {NerdamerSymbolType} */ (__.Limit.rewriteToLog(f));
+ }
+ // 2. try simple substitution
+ try {
+ retval = f.sub(x, lim);
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ // Nope. No go, so just return the unsubbed function so we can test the limit instead.
+ retval = f;
+ }
+
+ return retval;
+ },
+ isInterval(limit) {
+ return core.Utils.isVector(limit);
+ },
+ /**
+ * @param {NerdamerSymbolType | VectorType} limit
+ * @returns {boolean}
+ */
+ isConvergent(limit) {
+ // It's not convergent if it lies on the interval -Infinity to Infinity
+ if (
+ // It lies on the interval -Infinity to Infinity
+ (__.Limit.isInterval(limit) &&
+ /** @type {NerdamerSymbolType} */ (/** @type {VectorType} */ (limit).elements[0]).isInfinity &&
+ /** @type {NerdamerSymbolType} */ (/** @type {VectorType} */ (limit).elements[1]).isInfinity) ||
+ // We weren't able to calculate the limit
+ /** @type {NerdamerSymbolType} */ (limit).containsFunction('limit')
+ ) {
+ return false; // Then no
+ }
+ return true; // It is
+ },
+ /**
+ * @param {NerdamerSymbolType} symbol
+ * @param {string} x
+ * @param {NerdamerSymbolType} lim
+ * @param {number} [depth]
+ * @returns {NerdamerSymbolType | VectorType | undefined}
+ */
+ limit(symbol, x, lim, depth) {
+ // Simplify the symbol
+ if (symbol.isLinear() && symbol.isComposite()) {
+ // Apply sum of limits
+ let limit = new NerdamerSymbol(0);
+ symbol.each(s => {
+ limit = /** @type {NerdamerSymbolType} */ (_.add(limit, __.Limit.limit(s, x, lim, depth)));
+ }, true);
+
+ return limit;
+ }
+ symbol = /** @type {NerdamerSymbolType} */ (
+ /** @type {SimplifySubModuleType} */ (core.Algebra.Simplify).simplify(symbol)
+ );
+
+ depth ||= 1;
+
+ if (depth++ > Settings.max_lim_depth) {
+ return undefined;
+ }
+
+ // Store the multiplier
+ const m = _.parse(symbol.multiplier);
+ // Strip the multiplier
+ symbol.toUnitMultiplier();
+ // https://en.wikipedia.org/wiki/List_of_limits
+ let retval;
+ try {
+ // We try the simplest option first where c is some limit
+ // lim a as x->c = a where c
+ if (symbol.isConstant(true)) {
+ retval = symbol;
+ } else {
+ /** @type {Record<string, ExpressionParam>} */
+ const point = {};
+ point[x] = lim;
+ // Lim x as x->c = c where c
+
+ try {
+ // Evaluate the function at the given limit
+ const t = _.parse(symbol.sub(x, lim), point);
+
+ // A constant or infinity is known so we're done
+ if (t.isConstant(true) || t.isInfinity) {
+ retval = t;
+ }
+ } catch (e) {
+ /* Nothing. Maybe we tried to divide by zero.*/
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ }
+ if (!retval) {
+ // Split the symbol in the numerator and the denominator
+ const num = symbol.getNum();
+ const den = symbol.getDenom();
+
+ if (den.isConstant(true)) {
+ // We still don't have a limit so we generate tests.
+ if (symbol.group === EX) {
+ // https://en.wikipedia.org/wiki/List_of_limits
+ // Speed boost for exponentials by detecting patterns
+ const f = symbol.clone().toLinear();
+ const _p = symbol.power.clone();
+ const _num = f.getNum();
+ const _den = f.getDenom();
+ const fn = /** @type {DecomposeResultType} */ (
+ core.Utils.decompose_fn(_den, x, true)
+ );
+ // Start detection of pattern (x/(x+1))^x
+ if (
+ _num.group === S &&
+ _num.multiplier.isOne() &&
+ fn.ax.group === S &&
+ fn.b.isConstant(true) &&
+ fn.a.isOne() &&
+ fn.b.isConstant(true)
+ ) {
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.parse(format('(1/e^({0}))', fn.b))
+ );
+ } else {
+ const symbol_ = __.Limit.rewriteToLog(symbol.clone());
+ // Get the base
+ const pow = symbol_.power.clone();
+ const base = symbol_.clone().toLinear();
+ const limBase = __.Limit.limit(base, x, lim, depth);
+ // Convert Frac to NerdamerSymbol if needed
+ const powSymbol = isSymbol(pow) ? pow : new NerdamerSymbol(pow);
+ const limPow = __.Limit.limit(powSymbol, x, lim, depth);
+ retval = _.pow(limBase, limPow);
+ }
+ } else if (symbol.group === FN && symbol.args.length === 1) {
+ let evaluates;
+ // Squeeze theorem lim f(g(x)) = lim f(lim g))
+ const arg = __.Limit.limit(symbol.args[0], x, lim, depth);
+ if (core.Utils.isVector(arg)) {
+ // Get the limit over that interval
+ retval = arg.map(e => {
+ const clone = symbol.clone();
+ clone.args[0] = e;
+ return /** @type {NerdamerSymbolType} */ (
+ __.Limit.limit(
+ /** @type {NerdamerSymbolType} */ (
+ _.symfunction(symbol.fname, [e])
+ ),
+ x,
+ lim,
+ depth
+ )
+ );
+ });
+
+ return /** @type {NerdamerSymbolType} */ (_.multiply(m, retval));
+ }
+ // If the argument is constant then we're done
+ let trial;
+ if (arg.isConstant(true)) {
+ // Double check that it evaluates
+ trial = _.symfunction(symbol.fname, [arg]);
+ // Trial evaluation
+ try {
+ evaluate(trial);
+ evaluates = true;
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+
+ evaluates = false;
+ }
+ }
+ if (evaluates) {
+ retval = trial;
+ // If the limit converges. We'll deal with non-convergent ones later
+ } else if (__.Limit.isConvergent(arg)) {
+ if (symbol.fname === LOG) {
+ switch (arg.toString()) {
+ // Lim -> 0
+ case '0':
+ retval = NerdamerSymbol.infinity().negate();
+ break;
+ case 'Infinity':
+ retval = NerdamerSymbol.infinity();
+ break;
+ case '-Infinity':
+ retval = NerdamerSymbol.infinity();
+ break;
+ }
+ } else if ((symbol.fname === COS || symbol.fname === SIN) && lim.isInfinity) {
+ retval = __.Limit.interval(-1, 1);
+ } else if (symbol.fname === TAN) {
+ const sArg = symbol.args[0];
+ const n = sArg.getNum();
+ const d = sArg.getDenom();
+ const pi = n.toUnitMultiplier();
+ if (lim.isInfinity || (pi.equals('pi') && d.equals(2))) {
+ retval = __.Limit.diverges();
+ }
+ } else if (symbol.fname === Settings.FACTORIAL) {
+ if (arg.isInfinity) {
+ return NerdamerSymbol.infinity();
+ }
+ }
+ }
+ } else if (symbol.group === S) {
+ if (Number(symbol.power) > 0) // These functions always converge to the limit
+ {
+ return /** @type {NerdamerSymbolType} */ (_.parse(symbol, point));
+ }
+ // We're dealing with 1/x^n but remember that infinity has already been dealt
+ // with by substitution
+ if (core.Utils.even(/** @type {FracType} */ (symbol.power))) {
+ // Even powers converge to infinity
+ retval = NerdamerSymbol.infinity();
+ } else {
+ // Odd ones don't
+ retval = __.Limit.diverges();
+ }
+ } else if (symbol.group === CB) {
+ let lim1;
+ let lim2;
+ // Loop through all the symbols
+ // thus => lim f*g*h = lim (f*g)*h = (lim f*g)*(lim h)
+ // symbols of lower groups are generally easier to differentiatee so get them to the right by first sorting
+ const symbols = /** @type {NerdamerSymbolType[]} */ (symbol.collectSymbols()).sort(
+ (a, b) => a.group - b.group
+ );
+
+ let f = symbols.pop();
+ // Calculate the first limit so we can keep going down the list
+ lim1 = /** @type {NerdamerSymbolType} */ (
+ evaluate(/** @type {NerdamerSymbolType} */ (__.Limit.limit(f, x, lim, depth)))
+ );
+
+ // Reduces all the limits one at a time
+ while (symbols.length) {
+ // Get the second limit
+ let g = symbols.pop();
+ // Get the limit of g
+ lim2 = /** @type {NerdamerSymbolType} */ (
+ evaluate(
+ /** @type {NerdamerSymbolType} */ (__.Limit.limit(g, x, lim, depth))
+ )
+ );
+
+ // If the limit is in indeterminate form aplly L'Hospital by inverting g and then f/(1/g)
+ if (
+ lim1.isInfinity ||
+ (!__.Limit.isConvergent(lim1) && lim2.equals(0)) ||
+ (lim1.equals(0) && __.Limit.isConvergent(lim2))
+ ) {
+ if (g.containsFunction(LOG)) {
+ // Swap them
+ g = [f, (f = g)][0];
+ }
+ // Invert the symbol
+ g.invert();
+
+ // Product of infinities
+ if (lim1.isInfinity && lim2.isInfinity) {
+ lim1 = NerdamerSymbol.infinity();
+ } else {
+ lim1 = /** @type {NerdamerSymbolType | undefined} */ (
+ __.Limit.divide(f, g, x, lim, depth)
+ );
+ }
+ } else {
+ // Lim f*g = (lim f)*(lim g)
+ lim1 = /** @type {NerdamerSymbolType} */ (_.multiply(lim1, lim2));
+ // Let f*g equal f and h equal g
+ f = /** @type {NerdamerSymbolType} */ (_.multiply(f, g));
+ }
+ }
+
+ // Done, lim1 is the limit we're looking for
+ retval = lim1;
+ } else if (symbol.isComposite()) {
+ let _lim;
+ if (!symbol.isLinear()) {
+ symbol = /** @type {NerdamerSymbolType} */ (_.expand(symbol));
+ }
+ // Apply lim f+g = (lim f)+(lim g)
+ retval = new NerdamerSymbol(0);
+
+ let symbols = /** @type {NerdamerSymbolType[]} */ (symbol.collectSymbols()).sort(
+ (a, b) => b.group - a.group
+ );
+
+ const _symbols = [];
+ // Analyze the functions first
+ let fns = new NerdamerSymbol(0);
+ for (let i = 0, l = symbols.length; i < l; i++) {
+ const sym = symbols[i].clone();
+ if (sym.group === FN || (sym.group === CB && sym.hasFunc(''))) {
+ fns = /** @type {NerdamerSymbolType} */ (_.add(fns, sym));
+ } else {
+ _symbols.push(sym);
+ }
+ }
+ _symbols.unshift(/** @type {NerdamerSymbolType} */ (fns));
+
+ // Make sure that we didn't just repackage the exact same symbol
+ if (_symbols.length !== 1) {
+ symbols = _symbols;
+ }
+
+ for (let i = 0, l = symbols.length; i < l; i++) {
+ const sym = symbols[i];
+ // If the addition of the limits is undefined then the limit diverges so return -infinity to infinity
+ try {
+ _lim = __.Limit.limit(sym, x, lim, depth);
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ _lim = __.Limit.diverges();
+ }
+
+ try {
+ retval = /** @type {NerdamerSymbolType} */ (_.add(retval, _lim));
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ if (depth++ > Settings.max_lim_depth) {
+ return undefined;
+ }
+ retval = __.Limit.limit(__.diff(symbol, x), x, lim, depth);
+ }
+ }
+ }
+ } else {
+ retval = __.Limit.divide(num, den, x, lim, depth);
+ }
+ }
+ }
+
+ // If we still don't have a solution, return it symbolically
+ retval ||= /** @type {NerdamerSymbolType} */ (
+ _.symfunction('limit', [symbol, new NerdamerSymbol(x), lim])
+ );
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ // If all else fails return the symbolic function
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.symfunction('limit', [symbol, new NerdamerSymbol(x), lim])
+ );
+ }
+
+ return /** @type {NerdamerSymbolType | VectorType} */ (_.multiply(m, retval));
+ },
+ },
+ Fresnel: {
+ S(x) {
+ if (x.isConstant(true)) {
+ return __.defint(_.parse('sin(pi*x^2/2)'), new NerdamerSymbol(0), x, 'x');
+ }
+ return _.symfunction('S', [x]);
+ },
+ C(x) {
+ if (x.isConstant(true)) {
+ return __.defint(_.parse('cos(pi*x^2/2)'), new NerdamerSymbol(0), x, 'x');
+ }
+ return _.symfunction('C', [x]);
+ },
+ },
+ });
+
+ nerdamer.register([
+ {
+ name: 'diff',
+ visible: true,
+ numargs: [1, 3],
+ build() {
+ return __.diff;
+ },
+ },
+ {
+ name: 'sum',
+ visible: true,
+ numargs: 4,
+ build() {
+ return __.sum;
+ },
+ },
+ {
+ name: 'product',
+ visible: true,
+ numargs: 4,
+ build() {
+ return __.product;
+ },
+ },
+ {
+ name: 'integrate',
+ visible: true,
+ numargs: [1, 2],
+ build() {
+ return __.integrate;
+ },
+ },
+ {
+ name: 'defint',
+ visible: true,
+ numargs: [3, 4],
+ build() {
+ return __.defint;
+ },
+ },
+ {
+ name: 'S',
+ visible: true,
+ numargs: 1,
+ build() {
+ return __.Fresnel.S;
+ },
+ },
+ {
+ name: 'C',
+ visible: true,
+ numargs: 1,
+ build() {
+ return __.Fresnel.C;
+ },
+ },
+ {
+ name: 'limit',
+ visible: true,
+ numargs: [3, 4],
+ build() {
+ return __.Limit.limit;
+ },
+ },
+ ]);
+ // Link registered functions externally
+ nerdamer.updateAPI();
+})();
--- /dev/null
+/*
+ * Author : Martin Donk
+ * Website : http://www.nerdamer.com
+ * Email : martin.r.donk@gmail.com
+ * License : MIT
+ * Source : https://github.com/jiggzson/nerdamer
+ */
+
+// Type imports for JSDoc ======================================================
+// These typedefs provide type aliases for the interfaces defined in index.d.ts.
+// They enable proper type checking when working with the classes defined in this file.
+//
+// Usage patterns:
+// - For return types: @returns {NerdamerSymbolType}
+// - For parameters: @param {NerdamerSymbolType} symbol
+// - For variable declarations: /** @type {NerdamerSymbolType} */
+//
+// Note: When casting local class instances to interface types, use the pattern:
+// /** @type {InterfaceType} */ (/** @type {unknown} */ (localInstance))
+// This is needed because TypeScript sees local classes and interfaces as separate types.
+
+/**
+ * Core type aliases from index.d.ts
+ *
+ * @typedef {import('./index').NerdamerCore.NerdamerSymbol} NerdamerSymbolType
+ *
+ * @typedef {import('./index').NerdamerCore.Frac} FracType
+ *
+ * @typedef {import('./index').NerdamerCore.Vector} VectorType
+ *
+ * @typedef {import('./index').NerdamerCore.Matrix} MatrixType
+ *
+ * @typedef {import('./index').NerdamerCore.Parser} ParserType
+ *
+ * @typedef {import('./index').NerdamerCore.Settings} SettingsType
+ *
+ * @typedef {import('./index').NerdamerExpression} ExpressionType
+ *
+ * @typedef {typeof import('./index')} NerdamerType
+ *
+ * @typedef {import('./index').NerdamerCore.Utils} UtilsInterface
+ *
+ * @typedef {import('./index').NerdamerCore.Math2} Math2Interface
+ *
+ * @typedef {import('./index').NerdamerCore.Core} CoreType
+ *
+ * @typedef {import('./index').ExpressionParam} ExpressionParam
+ *
+ * @typedef {import('./index').ArithmeticOperand} ArithmeticOperand
+ *
+ * @typedef {import('./index').NerdamerCore.AlgebraModule} AlgebraModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.PartFracSubModule} PartFracSubModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.CalculusModule} CalculusModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.ExtraModule} ExtraModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.LaPlaceSubModule} LaPlaceSubModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.StatisticsSubModule} StatisticsSubModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.UnitsSubModule} UnitsSubModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.DecomposeResultObject} DecomposeResultType
+ */
+
+// Check if nerdamer exists globally (browser) or needs to be required (Node.js)
+let nerdamer = typeof globalThis !== 'undefined' && globalThis.nerdamer ? globalThis.nerdamer : undefined;
+if (typeof module !== 'undefined' && nerdamer === undefined) {
+ nerdamer = require('./nerdamer.core.js');
+ require('./Calculus');
+ require('./Algebra');
+}
+
+/** @returns {ExtraModuleType} */
+(function initExtraModule() {
+ /** @type {CoreType} */
+ const core = nerdamer.getCore();
+ /** @type {ParserType} */
+ const _ = core.PARSER;
+ const {
+ NerdamerSymbol,
+ Vector: _Vector,
+ /** @type {AlgebraModuleType} */
+ Algebra,
+ /** @type {CalculusModuleType} */
+ Calculus,
+ } = core;
+ const { format, isVector, isArray, isSymbol } = core.Utils;
+ const { S, EX: _EX, CP, PL, CB, FN } = core.groups;
+ core.Settings.Laplace_integration_depth = 40;
+
+ /**
+ * Check if a symbol's power is itself a symbol with group S or CB
+ *
+ * @param {NerdamerSymbolType} sym
+ * @returns {boolean}
+ */
+ function hasPowerGroupSOrCB(sym) {
+ return isSymbol(sym.power) && (sym.power.group === S || sym.power.group === CB);
+ }
+
+ /**
+ * Finds a function by name within this symbol's tree.
+ *
+ * @this {NerdamerSymbolType}
+ * @param {string} fname The function name to search for
+ * @returns {NerdamerSymbolType | undefined} The found function symbol clone, or undefined if not found
+ */
+ NerdamerSymbol.prototype.findFunction = function findFunction(fname) {
+ // This is what we're looking for
+ if (this.group === FN && this.fname === fname) {
+ return this.clone();
+ }
+ let found;
+ if (this.symbols) {
+ for (const x in this.symbols) {
+ if (!Object.hasOwn(this.symbols, x)) {
+ continue;
+ }
+ found = this.symbols[x].findFunction(fname);
+ if (found) {
+ break;
+ }
+ }
+ }
+
+ return found;
+ };
+
+ /** @type {ExtraModuleType} */
+ const __ = (core.Extra = {
+ version: '1.4.2',
+ // http://integral-table.com/downloads/LaplaceTable.pdf
+ // Laplace assumes all coefficients to be positive
+ LaPlace: {
+ // Using: integral_0^oo f(t)*e^(-s*t) dt
+ /**
+ * @param {NerdamerSymbolType} symbol
+ * @param {NerdamerSymbolType | string} t
+ * @param {NerdamerSymbolType | string} s
+ * @returns {NerdamerSymbolType}
+ */
+ transform(symbol, t, s) {
+ /** @type {NerdamerSymbolType} */
+ symbol = symbol.clone();
+
+ t = t.toString();
+ // First try a lookup for a speed boost
+ symbol = NerdamerSymbol.unwrapSQRT(symbol, true);
+ /** @type {NerdamerSymbolType} */
+ let retval;
+ const coeff = symbol.stripVar(t);
+ const g = symbol.group;
+
+ symbol = /** @type {NerdamerSymbolType} */ (_.divide(symbol, coeff.clone()));
+
+ if (symbol.isConstant() || !symbol.contains(t, true)) {
+ retval = _.parse(format('({0})/({1})', symbol, s));
+ } else if (g === S && core.Utils.isInt(symbol.power)) {
+ const n = String(symbol.power);
+ retval = _.parse(format('factorial({0})/({1})^({0}+1)', n, s));
+ } else if (symbol.group === S && symbol.power.equals(1 / 2)) {
+ retval = _.parse(format('sqrt(pi)/(2*({0})^(3/2))', s));
+ } else if (symbol.isComposite()) {
+ retval = new NerdamerSymbol(0);
+ symbol.each(x => {
+ retval = /** @type {NerdamerSymbolType} */ (_.add(retval, __.LaPlace.transform(x, t, s)));
+ }, true);
+ } else if (symbol.isE() && hasPowerGroupSOrCB(symbol)) {
+ const a = /** @type {NerdamerSymbolType} */ (symbol.power).stripVar(t);
+ retval = _.parse(format('1/(({1})-({0}))', a, s));
+ } else {
+ const fns = ['sin', 'cos', 'sinh', 'cosh'];
+ // Support for symbols in fns with arguments in the form a*t or n*t where a = symbolic and n = Number
+ if (
+ symbol.group === FN &&
+ fns.indexOf(symbol.fname) !== -1 &&
+ (symbol.args[0].group === S || symbol.args[0].group === CB)
+ ) {
+ const a = symbol.args[0].stripVar(t);
+
+ switch (symbol.fname) {
+ case 'sin':
+ retval = _.parse(format('({0})/(({1})^2+({0})^2)', a, s));
+ break;
+ case 'cos':
+ retval = _.parse(format('({1})/(({1})^2+({0})^2)', a, s));
+ break;
+ case 'sinh':
+ retval = _.parse(format('({0})/(({1})^2-({0})^2)', a, s));
+ break;
+ case 'cosh':
+ retval = _.parse(format('({1})/(({1})^2-({0})^2)', a, s));
+ break;
+ }
+ } else {
+ // Try to integrate for a solution
+ // we need at least the Laplace integration depth
+ const depthIsLower = core.Settings.integration_depth < core.Settings.Laplace_integration_depth;
+
+ let savedIntegrationDepth;
+ if (depthIsLower) {
+ savedIntegrationDepth = core.Settings.integration_depth; // Save the depth
+ core.Settings.integration_depth = core.Settings.Laplace_integration_depth; // Transforms need a little more room
+ }
+
+ core.Utils.block(
+ 'PARSE2NUMBER',
+ () => {
+ const u = t;
+ const sym = symbol.sub(t, u);
+ const integrationExpr = _.parse(`e^(-${s}*${u})*${sym}`);
+ retval = Calculus.integrate(integrationExpr, u);
+ if (retval.hasIntegral?.()) {
+ retval = _.symfunction('laplace', [symbol, _.parse(String(t)), _.parse(String(s))]);
+ return;
+ }
+ // _.error('Unable to compute transform');
+ retval = retval.sub(t, 0);
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.expand(_.multiply(retval, new NerdamerSymbol(-1)))
+ );
+ retval = retval.sub(u, t);
+ },
+ false
+ );
+
+ retval = /** @type {NerdamerSymbolType} */ (
+ core.Utils.block('PARSE2NUMBER', () => _.parse(retval), true)
+ );
+
+ if (depthIsLower) // Put the integration depth as it was
+ {
+ core.Settings.integration_depth = savedIntegrationDepth;
+ }
+ }
+ }
+
+ return /** @type {NerdamerSymbolType} */ (_.multiply(retval, coeff));
+ },
+ /**
+ * @param {NerdamerSymbolType} symbol
+ * @param {NerdamerSymbolType | string} s_
+ * @param {NerdamerSymbolType | string} t
+ * @returns {NerdamerSymbolType}
+ */
+ inverse(symbol, s_, t) {
+ const inputSymbol = symbol.clone();
+ return core.Utils.block(
+ 'POSITIVE_MULTIPLIERS',
+ () => {
+ /** @type {NerdamerSymbolType | undefined} */
+ let retval;
+ // Expand and get partial fractions
+ if (symbol.group === CB) {
+ symbol = /** @type {NerdamerSymbolType} */ (
+ /** @type {PartFracSubModuleType} */ (Algebra.PartFrac).partfrac(
+ /** @type {NerdamerSymbolType} */ (_.expand(symbol)),
+ s_
+ )
+ );
+ }
+
+ if (symbol.group === S || symbol.group === CB || symbol.isComposite()) {
+ /** @type {number | FracType} */
+ let p;
+ /** @type {FracType} */
+ let denP;
+ /** @type {NerdamerSymbolType} */
+ let a;
+ /** @type {NerdamerSymbolType | string} */
+ let b;
+ /** @type {NerdamerSymbolType} */
+ let d;
+ /** @type {string} */
+ let exp;
+ /** @type {DecomposeResultType} */
+ let f2;
+ /** @type {string | number} */
+ let fact;
+ // Remove the multiplier
+ const m = symbol.multiplier.clone();
+ symbol.toUnitMultiplier();
+ // Get the numerator and denominator
+ let num = symbol.getNum();
+ const den = symbol.getDenom().toUnitMultiplier();
+
+ // TODO: Make it so factor doesn't destroy pi
+ // num = core.Algebra.Factor.factor(symbol.getNum());
+ // den = core.Algebra.Factor.factor(symbol.getDenom().invert(null, true));
+
+ if (den.group === CP || den.group === PL) {
+ denP = /** @type {FracType} */ (den.power.clone());
+ den.toLinear();
+ } else {
+ denP = new core.Frac(1);
+ }
+
+ // Convert s to a string
+ const s = s_.toString();
+ // Split up the denominator if in the form ax+b
+ /** @type {DecomposeResultType} */
+ const f = core.Utils.decompose_fn(den, s, true);
+ // Move the multiplier to the numerator
+ /** @type {DecomposeResultType} */
+ const _fe = core.Utils.decompose_fn(
+ /** @type {NerdamerSymbolType} */ (_.expand(num.clone())),
+ s,
+ true
+ );
+ num.multiplier = num.multiplier.multiply(m);
+
+ const finalize = function () {
+ // Put back the numerator
+ retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, num));
+ retval.multiplier = retval.multiplier.multiply(symbol.multiplier);
+ // Put back a
+ retval = /** @type {NerdamerSymbolType} */ (_.divide(retval, f.a));
+ };
+
+ // Store the parts in variables for easy recognition
+ // check if in the form t^n where n = integer
+ if (
+ (den.group === S || den.group === CB) &&
+ f.x.value === s &&
+ f.b.equals(0) &&
+ core.Utils.isInt(f.x.power)
+ ) {
+ p = /** @type {number} */ (/** @type {unknown} */ (f.x.power)) - 1;
+ fact = core.Math2.factorial(p);
+ // N!/s^(n-1)
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.divide(_.pow(_.parse(String(t)), new NerdamerSymbol(p)), new NerdamerSymbol(fact))
+ );
+ // Wrap it up
+ finalize();
+ } else if (den.group === CP && denP.equals(1)) {
+ if (f.x.group === core.groups.PL && Algebra.degree(den).equals(2)) {
+ // Possibly in the form 1/(s^2+2*s+1)
+ // Try factoring to get it in a more familiar form{
+ // Apply inverse of F(s-a)
+ /**
+ * @type {{
+ * f: NerdamerSymbolType;
+ * a: NerdamerSymbolType;
+ * h: NerdamerSymbolType;
+ * c?: NerdamerSymbolType;
+ * }}
+ */
+ const completed = Algebra.sqComplete(den, s);
+ const u = core.Utils.getU(den);
+ // Get a for the function above
+ a = core.Utils.decompose_fn(completed.a, s, true).b;
+ const tf = __.LaPlace.inverse(
+ _.parse(`1/((${u})^2+(${completed.c}))`),
+ u,
+ String(t)
+ );
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.multiply(tf, _.parse(`(${m})*e^(-(${a})*(${t}))`))
+ );
+ // A/(b*s-c) -> ae^(-bt)
+ } else if (f.x.isLinear() && !num.contains(s)) {
+ t = /** @type {NerdamerSymbolType | string} */ (
+ _.divide(_.parse(String(t)), f.a.clone())
+ );
+
+ // Don't add factorial of one or zero
+ p = /** @type {number} */ (/** @type {unknown} */ (denP)) - 1;
+ fact = p === 0 || p === 1 ? '1' : `(${denP}-1)!`;
+ retval = _.parse(
+ format(
+ '(({0})^({3}-1)*e^(-(({2})*({0}))/({1})))/(({4})*({1})^({3}))',
+ t,
+ f.a,
+ f.b,
+ denP,
+ fact
+ )
+ );
+ // Wrap it up
+ finalize();
+ } else if (f.x.group === S && f.x.power.equals(2)) {
+ if (num.contains(s)) {
+ // A*s/(b*s^2+c^2)
+ a = new NerdamerSymbol(1);
+ if (num.group === CB) {
+ /** @type {NerdamerSymbolType} */
+ let newNum = new NerdamerSymbol(1);
+ num.each(x => {
+ if (x.contains(s)) {
+ newNum = /** @type {NerdamerSymbolType} */ (_.multiply(newNum, x));
+ } else {
+ a = /** @type {NerdamerSymbolType} */ (_.multiply(a, x));
+ }
+ });
+ num = newNum;
+ }
+
+ // We need more information about the denominator to decide
+ f2 = core.Utils.decompose_fn(num, s, true);
+ const fn1 = f2.a;
+ const fn2 = f2.b;
+ const aHasSin = fn1.containsFunction('sin');
+ const aHasCos = fn1.containsFunction('cos');
+ const bHasCos = fn2.containsFunction('cos');
+ const bHasSin = fn2.containsFunction('sin');
+ if (
+ f2.x.value === s &&
+ f2.x.isLinear() &&
+ !((aHasSin && bHasCos) || aHasCos || bHasSin)
+ ) {
+ retval = _.parse(
+ format(
+ '(({1})*cos((sqrt(({2})*({3}))*({0}))/({2})))/({2})',
+ t,
+ f2.a,
+ f.a,
+ f.b
+ )
+ );
+ } else if (aHasSin && bHasCos) {
+ const sin = /** @type {NerdamerSymbolType} */ (fn1.findFunction?.('sin'));
+ const cos = /** @type {NerdamerSymbolType} */ (fn2.findFunction?.('cos'));
+ // Who has the s?
+ if (sin?.args?.[0].equals(cos?.args?.[0]) && !sin?.args?.[0].contains(s)) {
+ b = /** @type {NerdamerSymbolType} */ (
+ _.divide(fn2, cos.toUnitMultiplier())
+ ).toString();
+ const c = sin.args[0].toString();
+ d = f.b;
+ const e = _.divide(fn1, sin.toUnitMultiplier());
+ exp =
+ '(({1})*({2})*cos({3})*sin(sqrt({4})*({0})))/sqrt({4})+({1})*sin({3})*({5})*cos(sqrt({4})*({0}))';
+ retval = _.parse(format(exp, t, a, b, c, d, e));
+ }
+ }
+ } else {
+ retval = _.parse(
+ format(
+ '(({1})*sin((sqrt(({2})*({3}))*({0}))/({2})))/sqrt(({2})*({3}))',
+ t,
+ num,
+ f.a,
+ f.b
+ )
+ );
+ }
+ }
+ } else if (
+ /** @type {FracType} */ (f.x.power).num &&
+ /** @type {FracType} */ (f.x.power).num.equals(3) &&
+ /** @type {FracType} */ (f.x.power).den.equals(2) &&
+ num.contains('sqrt(pi)') &&
+ !num.contains(s) &&
+ num.isLinear()
+ ) {
+ b = /** @type {NerdamerSymbolType} */ (_.divide(num.clone(), _.parse('sqrt(pi)')));
+ retval = _.parse(format('(2*({2})*sqrt({0}))/({1})', t, f.a, b, num));
+ } else if (denP.equals(2) && f.x.power.equals(2)) {
+ if (num.contains(s)) {
+ // Decompose the numerator to check value of s
+ f2 = core.Utils.decompose_fn(
+ /** @type {NerdamerSymbolType} */ (_.expand(num.clone())),
+ s,
+ true
+ );
+ if (f2.x.isComposite()) {
+ /** @type {DecomposeResultType[]} */
+ const sTerms = [];
+ // First collect the factors e.g. (a)(bx)(cx^2+d)
+ /** @type {DecomposeResultType[]} */
+ const symbols = /** @type {DecomposeResultType[]} */ (
+ num
+ .collectSymbols(x => {
+ x = NerdamerSymbol.unwrapPARENS(x);
+ /** @type {DecomposeResultType} */
+ const decomp = core.Utils.decompose_fn(x, s, true);
+ decomp.symbol = x;
+ return decomp;
+ })
+ // Then sort them by power hightest to lowest
+ .sort((x1, x2) => {
+ const p1 =
+ /** @type {DecomposeResultType} */ (x1).x.value === s
+ ? /** @type {number} */ (
+ /** @type {unknown} */ (
+ /** @type {DecomposeResultType} */ (x1).x.power
+ )
+ )
+ : 0;
+ const p2 =
+ /** @type {DecomposeResultType} */ (x2).x.value === s
+ ? /** @type {number} */ (
+ /** @type {unknown} */ (
+ /** @type {DecomposeResultType} */ (x2).x.power
+ )
+ )
+ : 0;
+ return p2 - p1;
+ })
+ );
+ a = new NerdamerSymbol(-1);
+ // Grab only the ones which have s
+ for (let i = 0; i < symbols.length; i++) {
+ const fc = symbols[i];
+ if (fc.x.value === s) {
+ sTerms.push(fc);
+ } else {
+ a = /** @type {NerdamerSymbolType} */ (_.multiply(a, fc.symbol));
+ }
+ }
+ // The following 2 assumptions are made
+ // 1. since the numerator was factored above then each s_term has a unique power
+ // 2. because the terms are sorted by descending powers then the first item
+ // has the highest power
+ // We can now check for the next type s(s^2-a^2)/(s^2+a^2)^2
+ if (
+ sTerms[0].x.power.equals(2) &&
+ sTerms[1].x.power.equals(1) &&
+ sTerms[1].b.equals(0) &&
+ !sTerms[0].b.equals(0)
+ ) {
+ b = sTerms[0].a.negate();
+ exp =
+ '-(({1})*({2})*({5})*({0})*sin((sqrt(({4})*({5}))*({0}))/({4})))/' +
+ '(2*({4})^2*sqrt(({4})*({5})))-(({1})*({3})*({0})*sin((sqrt(({4})*({5}))*({0}))/({4})))' +
+ '/(2*({4})*sqrt(({4})*({5})))+(({1})*({2})*cos((sqrt(({4})*({5}))*({0}))/({4})))/({4})^2';
+ retval = _.parse(format(exp, t, a, b, sTerms[0].b, f.a, f.b));
+ }
+ } else if (f2.x.isLinear()) {
+ a = /** @type {NerdamerSymbolType} */ (_.divide(f2.a, new NerdamerSymbol(2)));
+ exp =
+ '(({1})*({0})*sin((sqrt(({2})*({3}))*({0}))/({2})))/(({2})*sqrt(({2})*({3})))';
+ retval = _.parse(format(exp, t, a, f.a, f.b));
+ } else if (f2.x.power.equals(2)) {
+ if (f2.b.equals(0)) {
+ a = /** @type {NerdamerSymbolType} */ (
+ _.divide(f2.a, new NerdamerSymbol(2))
+ );
+ exp =
+ '(({1})*sin((sqrt(({2})*({3}))*({0}))/({2})))/(({2})*sqrt(({2})*({3})))+(({1})*({0})*cos((sqrt(({2})*({3}))*({0}))/({2})))/({2})^2';
+ retval = _.parse(format(exp, t, a, f.a, f.b));
+ } else {
+ a = /** @type {NerdamerSymbolType} */ (
+ _.divide(f2.a, new NerdamerSymbol(2))
+ );
+ d = f2.b.negate();
+ exp =
+ '-((({2})*({4})-2*({1})*({3}))*sin((sqrt(({2})*({3}))*({0}))/({2})))/(2*({2})*({3})*sqrt(({2})*({3})))+' +
+ '(({4})*({0})*cos((sqrt(({2})*({3}))*({0}))/({2})))/(2*({2})*({3}))+(({1})*({0})*cos((sqrt(({2})*({3}))*({0}))/({2})))/({2})^2';
+ retval = _.parse(format(exp, t, a, f.a, f.b, d));
+ }
+ }
+ } else {
+ a = /** @type {NerdamerSymbolType} */ (_.divide(num, new NerdamerSymbol(2)));
+ exp =
+ '(({1})*sin((sqrt(({2})*({3}))*({0}))/({2})))/(({3})*sqrt(({2})*({3})))-(({1})*({0})*cos((sqrt(({2})*({3}))*({0}))/({2})))/(({2})*({3}))';
+ retval = _.parse(format(exp, t, a, f.a, f.b));
+ }
+ } else if (symbol.isComposite()) {
+ // 1/(s+1)^2
+ if (denP.equals(2) && f.x.group === S) {
+ retval = _.parse(`(${m})*(${t})*e^(-(${f.b})*(${t}))`);
+ } else {
+ retval = new NerdamerSymbol(0);
+
+ symbol = /** @type {NerdamerSymbolType} */ (
+ /** @type {PartFracSubModuleType} */ (Algebra.PartFrac).partfrac(
+ /** @type {NerdamerSymbolType} */ (_.expand(symbol)),
+ s_
+ )
+ );
+
+ symbol.each(x => {
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.add(retval, __.LaPlace.inverse(x, s_, t))
+ );
+ }, true);
+ }
+ }
+ }
+
+ retval ||= _.symfunction('ilt', [inputSymbol, _.parse(String(s_)), _.parse(String(t))]);
+
+ return /** @type {NerdamerSymbolType} */ (retval);
+ },
+ true
+ );
+ },
+ },
+ Statistics: {
+ /**
+ * @param {NerdamerSymbolType[]} arr
+ * @returns {Record<string, number>}
+ */
+ frequencyMap(arr) {
+ /** @type {Record<string, number>} */
+ const map = {};
+ // Get the frequency map
+ for (let i = 0, l = arr.length; i < l; i++) {
+ const e = arr[i];
+ const key = e.toString();
+ map[key] ||= 0; // Default it to zero
+ map[key]++; // Increment
+ }
+ return map;
+ },
+ /**
+ * @param {NerdamerSymbolType[]} arr
+ * @returns {NerdamerSymbolType[]}
+ */
+ sort(arr) {
+ return arr.sort((a, b) => {
+ if (!a.isConstant() || !b.isConstant()) {
+ _.error('Unable to sort! All values must be numeric');
+ }
+ return /** @type {number} */ (/** @type {unknown} */ (a.multiplier.subtract(b.multiplier)));
+ });
+ },
+ /**
+ * @param {NerdamerSymbolType[]} arr
+ * @returns {NerdamerSymbolType}
+ */
+ count(arr) {
+ return new NerdamerSymbol(arr.length);
+ },
+ /**
+ * @param {NerdamerSymbolType[]} arr
+ * @param {NerdamerSymbolType} [x_]
+ * @returns {NerdamerSymbolType}
+ */
+ sum(arr, x_) {
+ /** @type {NerdamerSymbolType} */
+ let sum = new NerdamerSymbol(0);
+ for (let i = 0, l = arr.length; i < l; i++) {
+ const xi = arr[i].clone();
+ if (x_) {
+ sum = /** @type {NerdamerSymbolType} */ (
+ _.add(_.pow(_.subtract(xi, x_.clone()), new NerdamerSymbol(2)), sum)
+ );
+ } else {
+ sum = /** @type {NerdamerSymbolType} */ (_.add(xi, sum));
+ }
+ }
+
+ return sum;
+ },
+ /**
+ * @param {...NerdamerSymbolType} args
+ * @returns {NerdamerSymbolType}
+ */
+ mean(...args) {
+ // Handle arrays
+ if (isVector(args[0])) {
+ return __.Statistics.mean(.../** @type {NerdamerSymbolType[]} */ (args[0].elements));
+ }
+ return /** @type {NerdamerSymbolType} */ (_.divide(__.Statistics.sum(args), __.Statistics.count(args)));
+ },
+ /**
+ * @param {...NerdamerSymbolType} args
+ * @returns {NerdamerSymbolType}
+ */
+ median(...args) {
+ /** @type {NerdamerSymbolType} */
+ let retval;
+ // Handle arrays
+ if (isVector(args[0])) {
+ return __.Statistics.median(.../** @type {NerdamerSymbolType[]} */ (args[0].elements));
+ }
+ try {
+ const sorted = __.Statistics.sort(args);
+ const l = args.length;
+ if (core.Utils.even(l)) {
+ const mid = l / 2;
+ retval = __.Statistics.mean(sorted[mid - 1], sorted[mid]);
+ } else {
+ retval = sorted[Math.floor(l / 2)];
+ }
+ } catch (e) {
+ if (/** @type {Error} */ (e).message === 'timeout') {
+ throw e;
+ }
+ retval = _.symfunction('median', args);
+ }
+ return retval;
+ },
+ /**
+ * @param {...NerdamerSymbolType} args
+ * @returns {NerdamerSymbolType}
+ */
+ mode(...args) {
+ /** @type {NerdamerSymbolType} */
+ let retval;
+ // Handle arrays
+ if (isVector(args[0])) {
+ return __.Statistics.mode(.../** @type {NerdamerSymbolType[]} */ (args[0].elements));
+ }
+
+ const map = __.Statistics.frequencyMap(args);
+
+ // The mode of 1 item is that item as per issue #310 (verified by Happypig375).
+ if (core.Utils.keys(map).length === 1) {
+ retval = args[0];
+ } else {
+ // Invert by arraning them according to their frequency
+ /** @type {Record<number, string | string[]>} */
+ const inverse = {};
+ for (const x in map) {
+ if (!Object.hasOwn(map, x)) {
+ continue;
+ }
+ const freq = map[x];
+ // Check if it's in the inverse already
+ if (freq in inverse) {
+ const e = inverse[freq];
+ // If it's already an array then just add it
+ if (isArray(e)) {
+ e.push(x);
+ }
+ // Convert it to and array
+ else {
+ inverse[freq] = [x, /** @type {string} */ (inverse[freq])];
+ }
+ } else {
+ inverse[freq] = x;
+ }
+ }
+ // The keys now represent the maxes. We want the max of those keys
+ const keyNums = core.Utils.keys(inverse).map(k => Number(k));
+ const maxKey = Math.max.apply(null, keyNums);
+ const max = inverse[maxKey];
+ // Check it's an array. If it is then map over the results and convert
+ // them to NerdamerSymbol
+ if (isArray(max)) {
+ retval = _.symfunction(
+ 'mode',
+ max.sort().map(v => _.parse(v))
+ );
+ } else {
+ retval = _.parse(/** @type {string} */ (max));
+ }
+ }
+
+ return retval;
+ },
+ /**
+ * @param {NerdamerSymbolType} k
+ * @param {NerdamerSymbolType[]} args
+ * @returns {NerdamerSymbolType}
+ */
+ gVariance(k, args) {
+ const x_ = __.Statistics.mean(...args);
+ const sum = __.Statistics.sum(args, x_);
+ return /** @type {NerdamerSymbolType} */ (_.multiply(k, sum));
+ },
+ /**
+ * @param {...NerdamerSymbolType} args
+ * @returns {NerdamerSymbolType}
+ */
+ variance(...args) {
+ // Handle arrays
+ if (isVector(args[0])) {
+ return __.Statistics.variance(.../** @type {NerdamerSymbolType[]} */ (args[0].elements));
+ }
+ const k = /** @type {NerdamerSymbolType} */ (
+ _.divide(new NerdamerSymbol(1), __.Statistics.count(args))
+ );
+ return __.Statistics.gVariance(k, args);
+ },
+ /**
+ * @param {...NerdamerSymbolType} args
+ * @returns {NerdamerSymbolType}
+ */
+ sampleVariance(...args) {
+ // Handle arrays
+ if (isVector(args[0])) {
+ return __.Statistics.sampleVariance(.../** @type {NerdamerSymbolType[]} */ (args[0].elements));
+ }
+
+ const k = /** @type {NerdamerSymbolType} */ (
+ _.divide(new NerdamerSymbol(1), _.subtract(__.Statistics.count(args), new NerdamerSymbol(1)))
+ );
+ return __.Statistics.gVariance(k, args);
+ },
+ /**
+ * @param {...NerdamerSymbolType} args
+ * @returns {NerdamerSymbolType}
+ */
+ standardDeviation(...args) {
+ // Handle arrays
+ if (isVector(args[0])) {
+ return __.Statistics.standardDeviation(.../** @type {NerdamerSymbolType[]} */ (args[0].elements));
+ }
+ return /** @type {NerdamerSymbolType} */ (
+ _.pow(__.Statistics.variance(...args), new NerdamerSymbol(1 / 2))
+ );
+ },
+ /**
+ * @param {...NerdamerSymbolType} args
+ * @returns {NerdamerSymbolType}
+ */
+ sampleStandardDeviation(...args) {
+ // Handle arrays
+ if (isVector(args[0])) {
+ return __.Statistics.sampleStandardDeviation(
+ .../** @type {NerdamerSymbolType[]} */ (args[0].elements)
+ );
+ }
+ return /** @type {NerdamerSymbolType} */ (
+ _.pow(__.Statistics.sampleVariance(...args), new NerdamerSymbol(1 / 2))
+ );
+ },
+ /**
+ * @param {NerdamerSymbolType} x
+ * @param {NerdamerSymbolType} mean
+ * @param {NerdamerSymbolType} stdev
+ * @returns {NerdamerSymbolType}
+ */
+ zScore(x, mean, stdev) {
+ return /** @type {NerdamerSymbolType} */ (_.divide(_.subtract(x, mean), stdev));
+ },
+ },
+ Units: {
+ table: {
+ foot: '12 inch',
+ meter: '100 cm',
+ decimeter: '10 cm',
+ },
+ },
+ });
+
+ nerdamer.register([
+ {
+ name: 'laplace',
+ visible: true,
+ numargs: 3,
+ build() {
+ return __.LaPlace.transform;
+ },
+ },
+ {
+ name: 'ilt',
+ visible: true,
+ numargs: 3,
+ build() {
+ return __.LaPlace.inverse;
+ },
+ },
+ // Statistical
+ {
+ name: 'mean',
+ visible: true,
+ numargs: -1,
+ build() {
+ return __.Statistics.mean;
+ },
+ },
+ {
+ name: 'median',
+ visible: true,
+ numargs: -1,
+ build() {
+ return __.Statistics.median;
+ },
+ },
+ {
+ name: 'mode',
+ visible: true,
+ numargs: -1,
+ build() {
+ return __.Statistics.mode;
+ },
+ },
+ {
+ name: 'smpvar',
+ visible: true,
+ numargs: -1,
+ build() {
+ return __.Statistics.sampleVariance;
+ },
+ },
+ {
+ name: 'variance',
+ visible: true,
+ numargs: -1,
+ build() {
+ return __.Statistics.variance;
+ },
+ },
+ {
+ name: 'smpstdev',
+ visible: true,
+ numargs: -1,
+ build() {
+ return __.Statistics.sampleStandardDeviation;
+ },
+ },
+ {
+ name: 'stdev',
+ visible: true,
+ numargs: -1,
+ build() {
+ return __.Statistics.standardDeviation;
+ },
+ },
+ {
+ name: 'zscore',
+ visible: true,
+ numargs: 3,
+ build() {
+ return __.Statistics.zScore;
+ },
+ },
+ ]);
+
+ // Link registered functions externally
+ nerdamer.updateAPI();
+})();
+
+// Added for all.min.js
+if (typeof module !== 'undefined') {
+ module.exports = nerdamer;
+}
--- /dev/null
+MIT License
+
+Copyright (c) 2023 together-science
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+/*
+ * Author : Martin Donk
+ * Website : http://www.nerdamer.com
+ * Email : martin.r.donk@gmail.com
+ * Source : https://github.com/jiggzson/nerdamer
+ */
+
+// Type imports for JSDoc ======================================================
+// These typedefs provide type aliases for the interfaces defined in index.d.ts.
+// They enable proper type checking when working with the classes defined in this file.
+//
+// Usage patterns:
+// - For return types: @returns {NerdamerSymbolType}
+// - For parameters: @param {NerdamerSymbolType} symbol
+// - For variable declarations: /** @type {NerdamerSymbolType} */
+
+/**
+ * Core type aliases from index.d.ts
+ *
+ * @typedef {import('./index').NerdamerCore.NerdamerSymbol} NerdamerSymbolType
+ *
+ * @typedef {import('./index').NerdamerCore.Frac} FracType
+ *
+ * @typedef {import('./index').NerdamerCore.Vector} VectorType
+ *
+ * @typedef {import('./index').NerdamerCore.Matrix} MatrixType
+ *
+ * @typedef {import('./index').NerdamerCore.Parser} ParserType
+ *
+ * @typedef {import('./index').NerdamerCore.Settings} SettingsType
+ *
+ * @typedef {import('./index').NerdamerExpression} ExpressionType
+ *
+ * @typedef {typeof import('./index')} NerdamerType
+ *
+ * Constructor types (for factory functions)
+ *
+ * @typedef {import('./index').NerdamerCore.SymbolConstructor} SymbolConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.VectorConstructor} VectorConstructor
+ *
+ * Module types
+ *
+ * @typedef {import('./index').NerdamerCore.AlgebraModule} AlgebraModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.CalculusModule} CalculusModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.FactorSubModule} FactorSubModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.SimplifySubModule} SimplifySubModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.IntegrationSubModule} IntegrationSubModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.AlgebraClassesSubModule} AlgebraClassesSubModuleType
+ *
+ * @typedef {import('./index').NerdamerCore.DecomposeResultObject} DecomposeResultType
+ *
+ * @typedef {import('./index').NerdamerCore.SolveModule} SolveModuleType
+ *
+ * Utility types
+ *
+ * @typedef {import('./index').NerdamerCore.Utils} UtilsInterface
+ *
+ * @typedef {import('./index').NerdamerCore.Build} BuildInterface
+ *
+ * @typedef {import('big-integer').BigInteger} BigIntegerType
+ *
+ * Equation instance type
+ *
+ * @typedef {import('./index').NerdamerCore.EquationInstance} EquationInstanceType
+ *
+ * Solution result types
+ *
+ * @typedef {import('./index').NerdamerCore.SystemSolutionResult} SystemSolutionResultType
+ *
+ * @typedef {import('./index').NerdamerCore.SystemSolutionValue} SystemSolutionValueType
+ *
+ * @typedef {import('./index').NerdamerCore.CircleSolutionResult} CircleSolutionResultType
+ *
+ * @typedef {(NerdamerSymbolType | EquationInstanceType | string)[]} SolveEquationArray
+ */
+
+// Check if nerdamer exists globally (browser) or needs to be required (Node.js)
+let nerdamer = typeof globalThis !== 'undefined' && globalThis.nerdamer ? globalThis.nerdamer : undefined;
+if (typeof module !== 'undefined' && nerdamer === undefined) {
+ nerdamer = require('./nerdamer.core.js');
+ require('./Calculus.js');
+ require('./Algebra.js');
+}
+
+/** @returns {SolveModuleType} */
+(function initSolveModule() {
+ // Handle imports
+ const core = nerdamer.getCore();
+ const _ = core.PARSER;
+ /** @type {AlgebraModuleType} */
+ const _A = /** @type {AlgebraModuleType} */ (core.Algebra);
+ /** @type {CalculusModuleType} */
+ const _C = /** @type {CalculusModuleType} */ (core.Calculus);
+ const { integration } = /** @type {{ integration: IntegrationSubModuleType }} */ (_C);
+ const { decompose_arg: explode } = integration;
+ const {
+ Factor,
+ Simplify,
+ Classes: AlgebraClasses,
+ } = /** @type {{ Factor: FactorSubModuleType; Simplify: SimplifySubModuleType; Classes: AlgebraClassesSubModuleType }} */ (
+ _A
+ );
+ const { evaluate, remove, format, knownVariable, isSymbol, variables, range } = core.Utils;
+ const { build } = core.Build;
+ const { NerdamerSymbol } = core;
+ const { S, PL, CB, CP, FN } = core.groups;
+ const { Settings } = core;
+ const { isArray } = core.Utils;
+
+ // The search radius for the roots
+ core.Settings.SOLVE_RADIUS = 1000;
+ // The maximum number to fish for on each side of the zero
+ core.Settings.ROOTS_PER_SIDE = 10;
+ // Covert the number to multiples of pi if possible
+ core.Settings.make_pi_conversions = false;
+ // The step size
+ core.Settings.STEP_SIZE = 0.1;
+
+ // The epsilon size
+ core.Settings.EPSILON = 2e-13;
+ // The maximum iterations for Newton's method
+ core.Settings.MAX_NEWTON_ITERATIONS = 200;
+ // The epsilon used in Newton's iteration
+ // core.Settings.NEWTON_EPSILON = Number.EPSILON * 2;
+ core.Settings.NEWTON_EPSILON = 2e-15;
+
+ // The maximum number of time non-linear solve tries another jump point
+ core.Settings.MAX_NON_LINEAR_TRIES = 12;
+ // The amount of iterations the function will start to jump at
+ core.Settings.NON_LINEAR_JUMP_AT = 50;
+ // The size of the jump
+ core.Settings.NON_LINEAR_JUMP_SIZE = 100;
+ // The original starting point for nonlinear solving
+ core.Settings.NON_LINEAR_START = 0.01;
+ // When points are generated as starting points for Newton's method, they are sliced into small
+ // slices to make sure that we have convergence on the right point. This defines the
+ // size of the slice
+ core.Settings.NEWTON_SLICES = 200;
+ // The distance in which two solutions are deemed the same
+ core.Settings.SOLUTION_PROXIMITY = 1e-14;
+ // Indicate wheter to filter the solutions are not
+ core.Settings.FILTER_SOLUTIONS = true;
+ // The maximum number of recursive calls
+ core.Settings.MAX_SOLVE_DEPTH = 10;
+ // The tolerance that's considered close enough to zero
+ core.Settings.ZERO_EPSILON = 1e-9;
+ // The maximum iteration for the bisection method incase of some JS strangeness
+ core.Settings.MAX_BISECTION_ITER = 2000;
+ // The tolerance for the bisection method
+ core.Settings.BI_SECTION_EPSILON = 1e-12;
+
+ core.NerdamerSymbol.prototype.hasTrig = function hasTrig() {
+ return this.containsFunction(['cos', 'sin', 'tan', 'cot', 'csc', 'sec']);
+ };
+
+ core.NerdamerSymbol.prototype.hasNegativeTerms = function hasNegativeTerms() {
+ if (this.isComposite()) {
+ for (const x in this.symbols) {
+ if (!Object.hasOwn(this.symbols, x)) {
+ continue;
+ }
+ const sym = this.symbols[x];
+ if ((sym.group === PL && sym.hasNegativeTerms()) || this.symbols[x].power.lessThan(0)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ /* Nerdamer version 0.7.x and up allows us to make better use of operator overloading
+ * As such we can have this data type be supported completely outside of the core.
+ * This is an equation that has a left hand side and a right hand side
+ */
+ /**
+ * Equation class representing LHS = RHS.
+ *
+ * @class
+ * @param {NerdamerSymbolType} lhs - The left hand side symbol
+ * @param {NerdamerSymbolType} rhs - The right hand side symbol
+ */
+ function Equation(lhs, rhs) {
+ if (
+ (rhs.isConstant() && lhs.isConstant() && !lhs.equals(rhs)) ||
+ (lhs.equals(core.Settings.IMAGINARY) && rhs.isConstant(true)) ||
+ (rhs.equals(core.Settings.IMAGINARY) && lhs.isConstant(true))
+ ) {
+ throw new core.exceptions.NerdamerValueError(`${lhs.toString()} does not equal ${rhs.toString()}`);
+ }
+ /** @type {NerdamerSymbolType} */
+ this.LHS = lhs; // Left hand side
+ /** @type {NerdamerSymbolType} */
+ this.RHS = rhs; // Right and side
+ }
+ // UTILS ##!!
+
+ Equation.prototype = {
+ toString() {
+ return `${this.LHS.toString()}=${this.RHS.toString()}`;
+ },
+ text(option) {
+ return `${this.LHS.text(option)}=${this.RHS.text(option)}`;
+ },
+ /**
+ * Brings the equation to LHS (sets RHS to zero).
+ *
+ * @param {boolean} [expand] - Whether to expand the result
+ * @returns {NerdamerSymbolType} The LHS with RHS subtracted
+ */
+ toLHS(expand) {
+ expand = !!expand;
+ const eqn = this.removeDenom();
+ let a = eqn.LHS;
+ let b = eqn.RHS;
+
+ if (a.isConstant(true) && !b.isConstant(true)) {
+ // Swap them to avoid confusing parser and cause an infinite loop
+ [a, b] = [b, a];
+ }
+ const _t = /** @type {NerdamerSymbolType} */ (_.subtract(a, b));
+ /** @type {NerdamerSymbolType} */
+ let retval = expand ? /** @type {NerdamerSymbolType} */ (_.expand(_t)) : _t;
+
+ // Quick workaround for issue #636
+ // This basically borrows the removeDenom method from the Equation class.
+ // TODO: Make this function a stand-alone function
+ retval = new Equation(retval, new NerdamerSymbol(0)).removeDenom().LHS;
+
+ return retval;
+ },
+ /**
+ * Removes denominators from both sides.
+ *
+ * @returns {Equation} Equation with denominators removed
+ */
+ removeDenom() {
+ let a = this.LHS.clone();
+ let b = this.RHS.clone();
+ // Remove the denominator on both sides
+ const den = /** @type {NerdamerSymbolType} */ (_.multiply(a.getDenom(), b.getDenom()));
+ a = /** @type {NerdamerSymbolType} */ (_.expand(_.multiply(a, den.clone())));
+ b = /** @type {NerdamerSymbolType} */ (_.expand(_.multiply(b, den)));
+ // Swap the groups
+ if (b.group === CP && b.group !== CP) {
+ const t = a;
+ a = b;
+ b = t; // Swap
+ }
+
+ // Scan to eliminate denominators
+ if (a.group === CB) {
+ let t = new NerdamerSymbol(a.multiplier);
+ /** @type {NerdamerSymbolType} */
+ let newRHS = b.clone();
+ a.each(y => {
+ if (y.power.lessThan(0)) {
+ newRHS = /** @type {NerdamerSymbolType} */ (_.divide(newRHS, y));
+ } else {
+ t = /** @type {NerdamerSymbolType} */ (_.multiply(t, y));
+ }
+ });
+ a = t;
+ b = newRHS;
+ } else if (a.group === CP) {
+ // The logic: loop through each and if it has a denominator then multiply it out on both ends
+ // and then start over
+ for (const x in a.symbols) {
+ if (!Object.hasOwn(a.symbols, x)) {
+ continue;
+ }
+ const sym = a.symbols[x];
+ if (sym.group === CB) {
+ for (const y in sym.symbols) {
+ if (!Object.hasOwn(sym.symbols, y)) {
+ continue;
+ }
+ const sym2 = sym.symbols[y];
+ if (sym2.power.lessThan(0)) {
+ const result = new Equation(
+ /** @type {NerdamerSymbolType} */ (
+ _.expand(_.multiply(sym2.clone().toLinear(), a))
+ ),
+ /** @type {NerdamerSymbolType} */ (_.expand(_.multiply(sym2.clone().toLinear(), b)))
+ );
+ return result;
+ }
+ }
+ }
+ }
+ }
+
+ return new Equation(a, b);
+ },
+ /**
+ * Creates a copy of this equation.
+ *
+ * @returns {Equation}
+ */
+ clone() {
+ return new Equation(this.LHS.clone(), this.RHS.clone());
+ },
+ /**
+ * Substitutes a value for a variable on both sides.
+ *
+ * @param {NerdamerSymbolType} x - Variable to replace
+ * @param {NerdamerSymbolType} y - Value to substitute
+ * @returns {Equation}
+ */
+ sub(x, y) {
+ const clone = this.clone();
+ clone.LHS = clone.LHS.sub(x.clone(), y.clone());
+ clone.RHS = clone.RHS.sub(x.clone(), y.clone());
+ return clone;
+ },
+ /**
+ * Checks if the equation evaluates to zero.
+ *
+ * @returns {boolean}
+ */
+ isZero() {
+ return core.Utils.evaluate(this.toLHS()).equals(0);
+ },
+ /**
+ * Returns LaTeX representation.
+ *
+ * @param {string} [option]
+ * @returns {string}
+ */
+ latex(option) {
+ return [this.LHS.latex(option), this.RHS.latex(option)].join('=');
+ },
+ };
+ // Overwrite the equals function
+ /**
+ * Creates an Equation from two symbols. This extends the parser's equals function to return Equation objects.
+ *
+ * @param {NerdamerSymbolType} a
+ * @param {NerdamerSymbolType} b
+ * @returns {Equation}
+ */
+ // @ts-ignore - Overriding parser.equals to return Equation instead of Symbol
+ _.equals = function equals(a, b) {
+ return new Equation(a, b);
+ };
+
+ // Extend simplify
+ (function extendSimplifyForEquations() {
+ const simplify = _.functions.simplify[0];
+ _.functions.simplify[0] = function simplifyWithEquationSupport(symbol) {
+ if (symbol instanceof Equation) {
+ symbol.LHS = simplify(symbol.LHS);
+ symbol.RHS = simplify(symbol.RHS);
+ return symbol;
+ }
+ // Just call the original simplify
+ return simplify(symbol);
+ };
+ })();
+
+ /**
+ * Sets two expressions equal
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {Equation}
+ */
+ core.Expression.prototype.equals = function equals(symbol) {
+ if (symbol instanceof core.Expression) {
+ symbol = symbol.symbol;
+ } // Grab the symbol if it's an expression
+ const eq = new Equation(this.symbol, symbol);
+ return eq;
+ };
+
+ core.Expression.prototype.solveFor = function solveFor(x) {
+ core.Utils.armTimeout();
+ try {
+ const { symbol } = this;
+ if (this.symbol instanceof Equation) {
+ // Exit right away if we already have the answer
+ // check the LHS
+ if (this.symbol.LHS.isConstant() && this.symbol.RHS.equals(x)) {
+ return [new core.Expression(this.symbol.LHS)];
+ }
+
+ // Check the RHS
+ if (this.symbol.RHS.isConstant() && this.symbol.LHS.equals(x)) {
+ return [new core.Expression(this.symbol.RHS)];
+ }
+ }
+
+ const terms = solve(symbol, x);
+ const result = terms.map(term => {
+ term = /** @type {NerdamerSymbolType} */ (
+ Simplify.simplify(/** @type {NerdamerSymbolType} */ (_.parse(term)))
+ );
+ const expr = new core.Expression(term);
+ return expr;
+ });
+ return result;
+ } finally {
+ core.Utils.disarmTimeout();
+ }
+ };
+
+ core.Expression.prototype.expand = function expand() {
+ if (this.symbol instanceof Equation) {
+ const clone = this.symbol.clone();
+ clone.RHS = /** @type {NerdamerSymbolType} */ (_.expand(clone.RHS));
+ clone.LHS = /** @type {NerdamerSymbolType} */ (_.expand(clone.LHS));
+ return new core.Expression(clone);
+ }
+ return new core.Expression(_.expand(/** @type {NerdamerSymbolType} */ (this.symbol)));
+ };
+
+ // eslint-disable-next-line func-names -- naming this 'variables' would shadow the imported variables utility
+ core.Expression.prototype.variables = function () {
+ if (this.symbol instanceof Equation) {
+ return core.Utils.arrayUnique(
+ core.Utils.variables(this.symbol.LHS).concat(core.Utils.variables(this.symbol.RHS))
+ );
+ }
+ return core.Utils.variables(this.symbol);
+ };
+
+ const setEq = function setEq(a, b) {
+ return _.equals(a, b);
+ };
+
+ // Link the Equation class back to the core
+ core.Equation = Equation;
+
+ // Loops through an array and attempts to fails a test. Stops if manages to fail.
+ const checkAll = (core.Utils.checkAll = function checkAll(args, test) {
+ for (let i = 0; i < args.length; i++) {
+ if (test(args[i])) {
+ return false;
+ }
+ }
+ return true;
+ });
+
+ // Version solve
+ /** @type {SolveModuleType} */
+ const __ = (core.Solve = {
+ version: '2.0.3',
+ /** @type {NerdamerSymbolType[]} */
+ solutions: [],
+ solve(eq, variable) {
+ const save = Settings.PARSE2NUMBER;
+ Settings.PARSE2NUMBER = false;
+ const solution = solve(eq, String(variable));
+ Settings.PARSE2NUMBER = save;
+ return new core.Vector(solution);
+ // Return new core.Vector(solve(eq.toString(), variable ? variable.toString() : variable));
+ },
+ /**
+ * Brings the equation to LHS. A string can be supplied which will be converted to an Equation
+ *
+ * @param {Equation | string | NerdamerSymbolType} eqn
+ * @param {boolean} [expand]
+ * @returns {NerdamerSymbolType}
+ */
+ toLHS(eqn, expand) {
+ if (isSymbol(eqn)) {
+ return eqn;
+ }
+ // If it's an equation then call its toLHS function instead
+ if (!(eqn instanceof Equation)) {
+ const eqnStr = /** @type {string} */ (eqn);
+ const es = eqnStr.split('=');
+ // Convert falsey values to zero
+ es[1] ||= '0';
+ eqn = new Equation(
+ /** @type {NerdamerSymbolType} */ (_.parse(es[0])),
+ /** @type {NerdamerSymbolType} */ (_.parse(es[1]))
+ );
+ }
+ return eqn.toLHS(expand);
+ },
+ // GetSystemVariables: function(eqns) {
+ // vars = variables(eqns[0], null, null, true);
+ //
+ // //get all variables
+ // for (let i = 1, l=eqns.length; i < l; i++)
+ // vars = vars.concat(variables(eqns[i]));
+ // //remove duplicates
+ // vars = core.Utils.arrayUnique(vars).sort();
+ //
+ // //done
+ // return vars;
+ // },
+ /**
+ * Solve a set of circle equations.
+ *
+ * @param {NerdamerSymbolType[]} eqns
+ * @param {string[]} vars
+ * @returns {Array | object}
+ */
+ solveCircle(eqns, vars) {
+ // Convert the variables to symbols
+ const svars = vars.map(x => /** @type {NerdamerSymbolType} */ (_.parse(x)));
+
+ /** @type {number[][]} */
+ const deg = [];
+
+ /** @type {CircleSolutionResultType} */
+ let solutions = [];
+
+ // Get the degree for the equations
+ for (let i = 0; i < eqns.length; i++) {
+ /** @type {number[]} */
+ const d = [];
+ for (let j = 0; j < svars.length; j++) {
+ d.push(Number(_A.degree(eqns[i], svars[j])));
+ }
+ // Store the total degree
+ d.push(/** @type {number} */ (core.Utils.arraySum(d, true)));
+ deg.push(d);
+ }
+
+ let a = eqns[0];
+ let b = eqns[1];
+
+ if (deg[0][2] > deg[1][2]) {
+ [b, a] = [a, b];
+ [deg[1], deg[0]] = [deg[0], deg[1]];
+ }
+
+ // Only solve it's truly a circle
+ if (deg[0][0] === 1 && deg[0][2] === 2 && deg[1][0] === 2 && deg[1][2] === 4) {
+ // For clarity we'll refer to the variables as x and y
+ const x = vars[0];
+ const y = vars[1];
+
+ // We can now get the two points for y
+ const yPoints = solve(
+ /** @type {NerdamerSymbolType} */ (
+ _.parse(b, knownVariable(x, solve(/** @type {NerdamerSymbolType} */ (_.parse(a)), x)[0]))
+ ),
+ y
+ ).map(pt => pt.toString());
+
+ // Since we now know y we can get the two x points from the first equation
+ const xPoints = [
+ solve(/** @type {NerdamerSymbolType} */ (_.parse(a, knownVariable(y, yPoints[0]))))[0].toString(),
+ ];
+
+ if (yPoints[1]) {
+ xPoints.push(
+ solve(
+ /** @type {NerdamerSymbolType} */ (_.parse(a, knownVariable(y, yPoints[1])))
+ )[0].toString()
+ );
+ }
+
+ if (Settings.SOLUTIONS_AS_OBJECT) {
+ /** @type {Record<string, string[]>} */
+ const solObj = {};
+ solObj[x] = xPoints;
+ solObj[y] = yPoints;
+ solutions = solObj;
+ } else {
+ yPoints.unshift(y);
+ xPoints.unshift(x);
+ solutions = [xPoints, yPoints];
+ }
+ }
+
+ return solutions;
+ },
+ /**
+ * Solve a system of nonlinear equations
+ *
+ * @param {NerdamerSymbolType[]} eqns The array of equations
+ * @param {number} [tries] The maximum number of tries
+ * @param {number} [start] The starting point where to start looking for solutions
+ * @returns {SystemSolutionResultType | []}
+ */
+ solveNonLinearSystem(eqns, tries, start) {
+ if (tries < 0) {
+ return []; // Can't find a solution
+ }
+
+ start = typeof start === 'undefined' ? core.Settings.NON_LINEAR_START : start;
+
+ // The maximum number of times to jump
+ const maxTries = core.Settings.MAX_NON_LINEAR_TRIES;
+
+ // Halfway through the tries
+ const halfway = Math.floor(maxTries / 2);
+
+ // Initialize the number of tries to 10 if not specified
+ tries = typeof tries === 'undefined' ? maxTries : tries;
+
+ // A point at which we check to see if we're converging. By inspection it seems that we can
+ // use around 20 iterations to see if we're converging. If not then we retry a jump of x
+ const jumpAt = core.Settings.NON_LINEAR_JUMP_AT;
+
+ // We jump by this many points at each pivot point
+ const jump = core.Settings.NON_LINEAR_JUMP_SIZE;
+
+ // Used to check if we actually found a solution or if we gave up. Assume we will find a solution.
+ let found = true;
+
+ const createSubs = function (vars, matrix) {
+ return vars.map((x, i) => Number(matrix.get(i, 0)));
+ };
+
+ const vars = core.Utils.arrayGetVariables(eqns);
+ const jacobian = core.Matrix.jacobian(eqns, vars, x => build(x, vars), true);
+
+ const maxIter = core.Settings.MAX_NEWTON_ITERATIONS;
+ let o;
+ let y;
+ let iters;
+ let xn1;
+ let norm;
+ let lnorm;
+ let xn;
+ let d;
+
+ const fEqns = eqns.map(eq => build(eq, vars));
+
+ // Note: J stores compiled functions, not symbols. We use Matrix for its iteration
+ // capabilities, but elements are actually compiled functions `(...args: number[]) => number`
+ // The type system expects NerdamerSymbol but we're deliberately storing functions.
+ const J = jacobian.map(
+ (/** @type {NerdamerSymbolType} */ e) =>
+ /** @type {NerdamerSymbolType} */ (/** @type {unknown} */ (build(e, vars))),
+ true
+ );
+ // Initial values
+ xn1 = core.Matrix.cMatrix(0, vars);
+
+ // Initialize the c matrix with something close to 0.
+ let c = core.Matrix.cMatrix(start, vars);
+
+ iters = 0;
+
+ // Start of algorithm
+ do {
+ // If we've reached the max iterations then exit
+ if (iters > maxIter) {
+ found = false;
+ break;
+ }
+
+ // Set the substitution object
+ o = createSubs(vars, c);
+
+ // Set xn
+ xn = c.clone();
+
+ // Capture current values for use in callbacks
+ const currentO = o;
+ const currentC = c;
+
+ // Make all the substitutions for each of the equations
+ fEqns.forEach((f, i) => {
+ currentC.set(i, 0, f(...currentO));
+ });
+
+ let m = new core.Matrix();
+ // J actually contains compiled functions, cast to access them
+ /** @type {{ each: (fn: (element: unknown, row: number, col: number) => void) => void }} */ (
+ /** @type {unknown} */ (J)
+ ).each((fn, i, j) => {
+ const ans = /** @type {(...args: number[]) => number} */ (fn)(...currentO);
+ m.set(i, j, ans);
+ });
+
+ m = m.invert();
+
+ // Preform the elimination
+ y = /** @type {MatrixType} */ (_.multiply(m, c)).negate();
+
+ // The callback is to avoid overflow in the coeffient denonimator
+ // it converts it to a decimal and then back to a fraction. Some precision
+ // is lost be it's better than overflow.
+ d = y.subtract(xn1, x => _.parse(Number(x)));
+
+ xn1 = xn.add(y, x => _.parse(Number(x)));
+
+ // Move c is now xn1
+ c = xn1;
+
+ // Get the norm
+
+ // the expectation is that we're converging to some answer as this point regardless of where we start
+ // this may have to be adjusted at some point because of erroneous assumptions
+ if (iters >= jumpAt) {
+ // Check the norm. If the norm is greater than one then it's time to try another point
+ if (Number(norm) > 1) {
+ // Reset the start point at halway
+ if (tries === halfway) {
+ start = 0;
+ }
+ const sign = tries > halfway ? 1 : -1; // Which side are we incrementing
+ // we increment +n at one side and -n at the other.
+ const n = (tries % Math.floor(halfway)) + 1;
+ // Adjust the start point
+ start += sign * n * jump;
+ // Call restart
+ return __.solveNonLinearSystem(eqns, --tries, start);
+ }
+ }
+ lnorm = norm;
+ iters++;
+ norm = d.max();
+
+ // Exit early. Revisit if we get bugs
+ if (Number(norm) === Number(lnorm)) {
+ break;
+ }
+ } while (Number(norm) >= Number.EPSILON);
+
+ // Return a blank set if nothing was found;
+ if (!found) {
+ return [];
+ }
+
+ // Return c since that's the answer
+ return /** @type {SystemSolutionResultType | []} */ (
+ __.systemSolutions(c, vars, true, x => core.Utils.round(Number(x), 14))
+ );
+ },
+ /**
+ * Converts solution results to the appropriate format based on Settings.SOLUTIONS_AS_OBJECT.
+ *
+ * @param {MatrixType} result The result matrix
+ * @param {string[]} vars The variable names
+ * @param {boolean} [expandResult] Whether to expand the result
+ * @param {Function} [callback] Optional callback to transform each solution value
+ * @returns {SystemSolutionResultType}
+ */
+ systemSolutions(result, vars, expandResult, callback) {
+ if (core.Settings.SOLUTIONS_AS_OBJECT) {
+ /** @type {Record<string, SystemSolutionValueType>} */
+ const solutions = {};
+ result.each((e, idx) => {
+ /** @type {SystemSolutionValueType} */
+ let solution = /** @type {string | number} */ ((expandResult ? _.expand(e) : e).valueOf());
+ if (callback) {
+ solution = callback.call(e, solution);
+ }
+ solutions[vars[idx]] = solution;
+ });
+ return solutions;
+ }
+ /** @type {[string, SystemSolutionValueType][]} */
+ const solutions = [];
+ result.each((e, idx) => {
+ /** @type {SystemSolutionValueType} */
+ let solution = /** @type {string | number} */ ((expandResult ? _.expand(e) : e).valueOf());
+ if (callback) {
+ solution = callback.call(e, solution);
+ }
+ solutions.push([vars[idx], solution]);
+ });
+ return solutions;
+ },
+ /**
+ * Solves a system of equations by substitution. This is useful when no distinct solution exists. e.g. a line,
+ * plane, etc.
+ *
+ * @param {Array} eqns
+ * @returns {CircleSolutionResultType | []}
+ */
+ solveSystemBySubstitution(eqns) {
+ // Assume at least 2 equations. The function variables will just return an empty array if undefined is provided
+ const varsA = variables(eqns[0]);
+ const varsB = variables(eqns[1]);
+ // Check if it's a circle equation
+ if (eqns.length === 2 && varsA.length === 2 && core.Utils.arrayEqual(varsA, varsB)) {
+ return /** @type {CircleSolutionResultType | []} */ (__.solveCircle(eqns, varsA));
+ }
+
+ return []; // Return an empty set
+ },
+
+ // https://www.lakeheadu.ca/sites/default/files/uploads/77/docs/RemaniFinal.pdf
+ /**
+ * Solves a systems of equations
+ *
+ * @param {Array} eqns An array of equations
+ * @param {Array} varArray An array of variables
+ * @returns {Array | object}
+ */
+ solveSystem(eqns, varArray) {
+ // Check if a varArray was specified
+ // nerdamer.clearVars();// this deleted ALL variables: not what we want
+ // parse all the equations to LHS. Remember that they come in as strings
+ for (let i = 0; i < eqns.length; i++) {
+ eqns[i] = __.toLHS(eqns[i]);
+ }
+
+ const l = eqns.length;
+ let m = new core.Matrix();
+ const c = new core.Matrix();
+ let expandResult = false;
+ let vars;
+
+ if (typeof varArray === 'undefined') {
+ // Check to make sure that all the equations are linear
+ if (!_A.allLinear(eqns)) {
+ try {
+ return __.solveNonLinearSystem(eqns);
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ if (e instanceof core.exceptions.DivisionByZero) {
+ return __.solveSystemBySubstitution(eqns);
+ }
+ }
+ }
+
+ vars = core.Utils.arrayGetVariables(eqns);
+
+ // If the system only has one variable then we solve for the first one and
+ // then test the remaining equations with that solution. If any of the remaining
+ // equation fails then the system has no solution
+ if (vars.length === 1) {
+ let n = 0;
+ let sol;
+ let e;
+ do {
+ e = eqns[n].clone();
+
+ if (n > 0) {
+ e = e.sub(vars[0], sol[0]);
+ }
+
+ sol = solve(e, vars[0]);
+ // Skip the first one
+ if (n === 0) {
+ continue;
+ }
+ } while (++n < eqns.length);
+
+ // Format the output
+ let solutions;
+ if (Settings.SOLUTIONS_AS_OBJECT) {
+ solutions = {};
+ solutions[vars[0]] = sol;
+ } else if (sol.length === 0) {
+ solutions = sol; // No solutions
+ } else {
+ solutions = [vars[0], sol];
+ }
+
+ return solutions;
+ }
+
+ // Deal with redundant equations as expressed in #562
+ // The fix is to remove all but the number of equations equal to the number
+ // of variables. We then solve those and then evaluate the remaining equations
+ // with those solutions. If the all equal true then those are just redundant
+ // equations and we can return the solution set.
+ if (vars.length < eqns.length) {
+ const reduced = [];
+ const n = eqns.length;
+ for (let i = 0; i < n - 1; i++) {
+ reduced.push(_.parse(eqns[i]));
+ }
+
+ /** @type {Record<string, NerdamerSymbolType | string | number>} */
+ const knowns = {};
+ const solutions = __.solveSystem(reduced, vars);
+ // The solutions may have come back as an array
+ if (Array.isArray(solutions)) {
+ solutions.forEach(sol => {
+ // For substitution, we only use single-value solutions (not arrays)
+ if (!Array.isArray(sol[1])) {
+ knowns[sol[0]] = sol[1];
+ }
+ });
+ } else {
+ // Filter out array solutions for substitution
+ for (const key of Object.keys(solutions)) {
+ const val = solutions[key];
+ if (!Array.isArray(val)) {
+ knowns[key] = val;
+ }
+ }
+ }
+
+ // Start by assuming they will all evaluate to zero. If even one fails
+ // then all zero will be false
+ let allZero = true;
+ // Check if the last solution evalutes to zero given these solutions
+ for (let i = n - 1; i < n; i++) {
+ if (!(/** @type {NerdamerSymbolType} */ (_.parse(eqns[i], knowns)).equals(0))) {
+ allZero = false;
+ }
+ }
+
+ if (allZero) {
+ return solutions;
+ }
+ }
+
+ // Deletes only the variables of the linear equations in the nerdamer namespace
+ for (let i = 0; i < vars.length; i++) {
+ nerdamer.setVar(vars[i], 'delete');
+ }
+ // TODO: move this to cMatrix or something similar
+ // populate the matrix
+ for (let i = 0; i < l; i++) {
+ const e = eqns[i]; // Store the expression
+ // Iterate over the columns
+ for (let j = 0; j < vars.length; j++) {
+ const v = vars[j];
+ let coeffs = [];
+ e.each(x => {
+ if (x.contains(v)) {
+ coeffs = coeffs.concat(x.coeffs());
+ }
+ });
+
+ const cf = core.Utils.arraySum(coeffs);
+ m.set(i, j, cf);
+ }
+
+ // Strip the variables from the symbol so we're left with only the zeroth coefficient
+ // start with the symbol and remove each variable and its coefficient
+ let num = e.clone();
+ vars.forEach(varName => {
+ num = num.stripVar(varName, true);
+ });
+ c.set(i, 0, num.negate());
+ }
+ } else {
+ /**
+ * The idea is that we loop through each equation and then expand it. Afterwards we loop through each
+ * term and see if and check to see if it matches one of the variables. When a match is found we mark
+ * it. No other match should be found for that term. If it is we stop since it's not linear.
+ */
+ vars = varArray;
+ expandResult = true;
+ for (let i = 0; i < l; i++) {
+ // Prefill
+ c.set(i, 0, new NerdamerSymbol(0));
+ const e = /** @type {NerdamerSymbolType[]} */ (
+ /** @type {NerdamerSymbolType} */ (_.expand(eqns[i])).collectSummandSymbols()
+ ); // Expand and store
+ // go trough each of the variables
+ for (let j = 0; j < varArray.length; j++) {
+ m.set(i, j, new NerdamerSymbol(0));
+ const v = varArray[j];
+ // Go through the terms and sort the variables
+ for (let k = 0; k < e.length; k++) {
+ const term = e[k];
+ let check = false;
+ for (let z = 0; z < varArray.length; z++) {
+ // Check to see if terms contain multiple variables
+ if (term.contains(varArray[z])) {
+ if (check) {
+ core.Utils.err(`Multiple variables found for term ${term}`);
+ }
+ check = true;
+ }
+ }
+ // We made sure that every term contains one variable so it's safe to assume that if the
+ // variable is found then the remainder is the coefficient.
+ if (term.contains(v)) {
+ const tparts = /** @type {(NerdamerSymbolType | VectorType | MatrixType)[]} */ (
+ explode(remove(e, k), v)
+ );
+ k--; // Issue #52: decrement k to hit this spot in the array e again next loop
+ m.set(i, j, _.add(m.get(i, j), /** @type {NerdamerSymbolType} */ (tparts[0])));
+ }
+ }
+ }
+ // All the remaining terms go to the c matrix
+ for (let k = 0; k < e.length; k++) {
+ c.set(i, 0, _.add(c.get(i, 0), e[k]));
+ }
+ }
+ // Consider case (a+b)*I+u
+ }
+
+ // Check if the system has a distinct solution
+ if (vars.length !== eqns.length || m.determinant().equals(0)) {
+ // Solve the system by hand
+ // return __.solveSystemBySubstitution(eqns, vars, m, c);
+ throw new core.exceptions.SolveError('System does not have a distinct solution');
+ }
+
+ // Use M^-1*c to solve system
+ m = m.invert();
+ const result = m.multiply(c);
+ // Correct the sign as per issue #410
+ if (core.Utils.isArray(varArray)) {
+ result.each(x => x.negate());
+ }
+
+ return __.systemSolutions(result, vars, expandResult);
+ },
+ /**
+ * The quadratic function but only one side.
+ *
+ * @param {NerdamerSymbolType} c
+ * @param {NerdamerSymbolType} b
+ * @param {NerdamerSymbolType} a
+ * @returns {(NerdamerSymbolType | VectorType | MatrixType)[]}
+ */
+ quad(c, b, a) {
+ let discriminant = _.subtract(
+ _.pow(b.clone(), new NerdamerSymbol(2)),
+ _.multiply(_.multiply(a.clone(), c.clone()), new NerdamerSymbol(4))
+ ); /* B^2 - 4ac*/
+ // Fix for #608
+ discriminant = /** @type {NerdamerSymbolType} */ (_.expand(discriminant));
+ const det = /** @type {NerdamerSymbolType} */ (_.pow(discriminant, new NerdamerSymbol(0.5)));
+ const den = /** @type {NerdamerSymbolType} */ (
+ _.parse(/** @type {NerdamerSymbolType} */ (_.multiply(new NerdamerSymbol(2), a.clone())))
+ );
+ const retval = [
+ _.parse(format('(-({0})+({1}))/({2})', b, det, den)),
+ _.parse(format('(-({0})-({1}))/({2})', b, det, den)),
+ ];
+
+ return retval;
+ },
+ /**
+ * The cubic equation
+ * http://math.stackexchange.com/questions/61725/is-there-a-systematic-way-of-solving-cubic-equations
+ *
+ * @param {NerdamerSymbolType} dO
+ * @param {NerdamerSymbolType} cO
+ * @param {NerdamerSymbolType} bO
+ * @param {NerdamerSymbolType} aO
+ * @returns {Array}
+ */
+ cubic(dO, cO, bO, aO) {
+ // Convert everything to text
+ const a = aO.text();
+ const b = bO.text();
+ const c = cO.text();
+ const d = dO.text();
+
+ const t = `(-(${b})^3/(27*(${a})^3)+(${b})*(${c})/(6*(${a})^2)-(${d})/(2*(${a})))`;
+ const u = `((${c})/(3*(${a}))-(${b})^2/(9*(${a})^2))`;
+ const v = `(${b})/(3*(${a}))`;
+ const x = `((${t})+sqrt((${t})^2+(${u})^3))^(1/3)+((${t})-sqrt((${t})^2+(${u})^3))^(1/3)-(${v})`;
+
+ // Convert a to one
+ const w = '1/2+sqrt(3)/2*i'; // Cube root of unity
+
+ return [_.parse(x), _.parse(`(${x})(${w})`), _.parse(`(${x})(${w})^2`)];
+
+ // https://www.wikihow.com/Solve-a-Cubic-Equation method 3
+ // const delta0 = `(${b})^2-(3*(${a})(${c}))`;
+ // _.parse(delta0);
+ // const delta1 = `2(${b})^3-(9*(${a})(${b})(${c}))+27((${a})^2)(${d})`;
+ // _.parse(delta1);
+ // // const delta = `(${delta1})^2-(4*(${delta0})^3)/(-27(${a})^2)`;
+
+ // const C = `((sqrt((${delta1})^2-(4*(${delta0})^3))+(${delta1}))/2)^(1/3)`;
+ // _.parse(C);
+ // const u = `(-1+sqrt(-3))/2`
+ // _.parse(u);
+
+ // const result = []
+ // for (let n = 1; n <=3; n++) {
+ // let x = `-((${b})+ (${u})^${n}*(${C})+(${delta0})/((${u})^${n}*(${C})))/(3(${a}))`;
+ // console.log(x);
+ // console.log(x.substring(168));
+ // result.push(_.parse(x));
+ // }
+
+ // return result.map((x)=>_.parse(x))
+ },
+ /**
+ * The quartic equation
+ *
+ * @param {NerdamerSymbolType} e
+ * @param {NerdamerSymbolType} d
+ * @param {NerdamerSymbolType} c
+ * @param {NerdamerSymbolType} b
+ * @param {NerdamerSymbolType} a
+ * @returns {Array}
+ */
+ quartic(e, d, c, b, a) {
+ /** @type {Record<string, number>} */
+ const scope = {};
+ core.Utils.arrayUnique(
+ variables(a).concat(variables(b)).concat(variables(c)).concat(variables(d)).concat(variables(e))
+ ).forEach(x => {
+ scope[x] = 1;
+ });
+ const aStr = a.toString();
+ const bStr = b.toString();
+ const cStr = c.toString();
+ const dStr = d.toString();
+ const eStr = e.toString();
+ let _D;
+ /* Var D = core.Utils.block('PARSE2NUMBER', function() {
+ return _.parse(format("256*({0})^3*({4})^3-192*({0})^2*({1})*({3})*({4})^2-128*({0})^2*({2})^2*({4})^2+144*({0})^2*({2})*({3})^2*({4})"+
+ "-27*({0})^2*({3})^4+144*({0})*({1})^2*({2})*({4})^2-6*({0})*({1})^2*({3})^2*({4})-80*({0})*({1})*({2})^2*({3})*({4})+18*({0})*({1})*({2})*({3})^3"+
+ "+16*({0})*({2})^4*({4})-4*({0})*({2})^3*({3})^2-27*({1})^4*({4})^2+18*({1})^3*({2})*({3})*({4})-4*({1})^3*({3})^3-4*({1})^2*({2})^3*({4})+({1})^2*({2})^2*({3})^2",
+ aStr, bStr, cStr, dStr, eStr), scope);
+ });*/
+
+ const p = _.parse(format('(8*({0})*({2})-3*({1})^2)/(8*({0})^2)', aStr, bStr, cStr)).toString(); // A, b, c
+ const q = _.parse(
+ format('(({1})^3-4*({0})*({1})*({2})+8*({0})^2*({3}))/(8*({0})^3)', aStr, bStr, cStr, dStr)
+ ).toString(); // A, b, c, d, e
+ const D0 = _.parse(format('12*({0})*({4})-3*({1})*({3})+({2})^2', aStr, bStr, cStr, dStr, eStr)).toString(); // A, b, c, d, e
+ const D1 = _.parse(
+ format(
+ '2*({2})^3-9*({1})*({2})*({3})+27*({1})^2*({4})+27*({0})*({3})^2-72*({0})*({2})*({4})',
+ aStr,
+ bStr,
+ cStr,
+ dStr,
+ eStr
+ )
+ ).toString(); // A, b, c, d, e
+ const Q = _.parse(format('((({1})+(({1})^2-4*({0})^3)^(1/2))/2)^(1/3)', D0, D1)).toString(); // D0, D1
+ const quarticS = _.parse(
+ format('(1/2)*(-(2/3)*({1})+(1/(3*({0}))*(({2})+(({3})/({2})))))^(1/2)', aStr, p, Q, D0)
+ ).toString(); // A, p, Q, D0
+ const x1 = _.parse(
+ format(
+ '-(({1})/(4*({0})))-({4})+(1/2)*sqrt(-4*({4})^2-2*({2})+(({3})/({4})))',
+ aStr,
+ bStr,
+ p,
+ q,
+ quarticS
+ )
+ ); // A, b, p, q, S
+ const x2 = _.parse(
+ format(
+ '-(({1})/(4*({0})))-({4})-(1/2)*sqrt(-4*({4})^2-2*({2})+(({3})/({4})))',
+ aStr,
+ bStr,
+ p,
+ q,
+ quarticS
+ )
+ ); // A, b, p, q, S
+ const x3 = _.parse(
+ format(
+ '-(({1})/(4*({0})))+({4})+(1/2)*sqrt(-4*({4})^2-2*({2})-(({3})/({4})))',
+ aStr,
+ bStr,
+ p,
+ q,
+ quarticS
+ )
+ ); // A, b, p, q, S
+ const x4 = _.parse(
+ format(
+ '-(({1})/(4*({0})))+({4})-(1/2)*sqrt(-4*({4})^2-2*({2})-(({3})/({4})))',
+ aStr,
+ bStr,
+ p,
+ q,
+ quarticS
+ )
+ ); // A, b, p, q, S
+ return [x1, x2, x3, x4];
+ },
+ /**
+ * Breaks the equation up in its factors and tries to solve the smaller parts
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {string} solveFor
+ * @returns {Array}
+ */
+ divideAndConquer(symbol, solveFor) {
+ let sols = [];
+ // See if we can solve the factors
+ const factors = Factor.factorInner(symbol);
+ if (factors.group === CB) {
+ factors.each(x => {
+ x = NerdamerSymbol.unwrapPARENS(x);
+ sols = sols.concat(solve(x, solveFor));
+ });
+ }
+ return sols;
+ },
+ /**
+ * Attempts to solve the equation assuming it's a polynomial with numeric coefficients
+ *
+ * @param {NerdamerSymbolType} eq
+ * @param {string} solveFor
+ * @returns {Array}
+ */
+ csolve(eq, solveFor) {
+ return core.Utils.block(
+ 'IGNORE_E',
+ () => {
+ let p;
+ let pn;
+ let n;
+ let pf;
+ let r;
+ let _theta;
+ let sr;
+ let _sp;
+ const roots = [];
+ const f = /** @type {DecomposeResultType} */ (core.Utils.decompose_fn(eq, solveFor, true));
+ if (f.x.group === S) {
+ p = _.parse(f.x.power);
+ pn = Number(p);
+ n = _.pow(_.divide(f.b.negate(), f.a), /** @type {NerdamerSymbolType} */ (p).invert());
+ pf = NerdamerSymbol.toPolarFormArray(/** @type {NerdamerSymbolType} */ (n));
+ r = pf[0];
+ _theta = pf[1];
+ sr = r.toString();
+ _sp = p.toString();
+ let k;
+ let root;
+ let str;
+ for (let i = 0; i < pn; i++) {
+ k = i;
+ str = format('({0})*e^(2*{1}*pi*{2}*{3})', sr, k, p, core.Settings.IMAGINARY);
+ root = _.parse(str);
+ roots.push(root);
+ }
+ }
+ return roots;
+ },
+ true
+ );
+ },
+ /**
+ * Generates starting points for the Newton solver given an expression at zero. It begins by checking if zero is
+ * a good point and starts expanding by a provided step size. Builds on the fact that if the sign changes over
+ * an interval then a zero must exist on that interval
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {number} step
+ * @param {boolean} extended
+ * @returns {Array}
+ */
+ getPoints(symbol, step, extended) {
+ step ||= 0.01;
+ let points = [];
+ const f = build(symbol);
+ const x0 = 0;
+
+ const start = Math.round(x0);
+ const _last = f(start);
+ const rside = core.Settings.ROOTS_PER_SIDE; // The max number of roots on right side
+ const lside = rside; // The max number of roots on left side
+ // check around the starting point
+ points.push(Math.floor(start / 2)); // Half way from zero might be a good start
+ points.push(Math.abs(start)); // |f(0)| could be a good start
+ points.push(start); // |f(0)| could be a good start
+ // adjust for log. A good starting point to include for log is 0.1
+ symbol.each(x => {
+ if (x.containsFunction(core.Settings.LOG)) {
+ points.push(0.1);
+ }
+ });
+
+ const left = range(-core.Settings.SOLVE_RADIUS, start, step);
+ const right = range(start, core.Settings.SOLVE_RADIUS, step);
+
+ const testSide = function (side, numRoots) {
+ // Console.log("test side "+side[0]+":"+side.at(-1));
+ let xi;
+ let val;
+ let sign;
+ const hits = [];
+ const lastPoint = side[0];
+ let lastSign = Math.sign(f(lastPoint));
+ for (let i = 0, l = side.length; i < l && hits.length < numRoots; i++) {
+ xi = side[i]; // The point being evaluated
+ val = f(xi);
+ sign = Math.sign(val);
+ // Don't add non-numeric values
+ if (isNaN(sign)) {
+ continue;
+ }
+
+ // Compare the signs. The have to be different if they cross a zero
+ if (sign !== lastSign) {
+ hits.push(xi); // Take note of the possible zero location
+ hits.push(side[i - 1]); // Also the other side
+ // console.log(" hit at "+xi);
+ // if (hits.length >= numRoots){
+ // break;
+ // }
+ }
+ lastSign = sign;
+ }
+
+ points = points.concat(hits);
+ };
+
+ testSide(left, lside);
+ testSide(right, rside);
+
+ if (extended) {
+ // Check for sign changes way outside the range
+ // in a limited way
+ const max = core.Settings.SOLVE_RADIUS;
+ testSide([max, max * max], 1);
+ testSide([-max * max, -max], 1);
+ }
+
+ // Console.log("points: "+points);
+ return points;
+ },
+ /**
+ * Implements the bisection method. Returns undefined in no solution is found
+ *
+ * @param {number} point
+ * @param {Function} f
+ * @returns {undefined | number}
+ */
+ bisection(point, f) {
+ let left = point - 1;
+ let right = point + 1;
+ // First test if this point is even worth evaluating. It should
+ // be crossing the x axis so the signs should be different
+ if (Math.sign(f(left)) !== Math.sign(f(right))) {
+ let safety = 0;
+
+ let epsilon;
+ let middle;
+
+ do {
+ epsilon = Math.abs(right - left);
+ // Safety against an infinite loop
+ if (safety++ > core.Settings.MAX_BISECTION_ITER || isNaN(epsilon)) {
+ return undefined;
+ }
+ // Calculate the middle point
+ middle = (left + right) / 2;
+
+ if (f(left) * f(middle) > 0) {
+ left = middle;
+ } else {
+ right = middle;
+ }
+ } while (epsilon >= Settings.EPSILON);
+
+ const solution = (left + right) / 2;
+
+ // Test the solution to make sure that it's within tolerance
+ const xPoint = f(solution);
+
+ if (!isNaN(xPoint) && Math.abs(xPoint) <= core.Settings.BI_SECTION_EPSILON) {
+ // Returns too many junk solutions if not rounded at 13th place.
+ return /** @type {number} */ (core.Utils.round(solution, 13));
+ }
+ return undefined;
+ }
+ return undefined;
+ },
+ // Helper function for when Newton gets into the weeds
+ // look from left and right of a sign-change interval
+ // narrows it down
+ // result: A real point with tractable numbers to continue from
+ // or undefined
+ bSearch(left, right, f) {
+ let fLeft = f(left);
+ let fRight = f(right);
+ // Reject imposters
+ if (Math.sign(fLeft) === Math.sign(fRight) || isNaN(fLeft) || isNaN(fRight)) {
+ return undefined;
+ }
+
+ const maxIter = 80; // Guess the amount of iterations to outrun precision?
+ let iterations = 0;
+ do {
+ const x = (left + right) / 2;
+ const sLeft = Math.sign(fLeft);
+ const sX = Math.sign(f(x));
+ if (sLeft === sX) {
+ if (x === left) {
+ break; // Precision exceeded
+ }
+ left = x;
+ fLeft = f(left);
+ } else {
+ if (x === right) {
+ break; // Precision exceeded
+ }
+ right = x;
+ fRight = f(right);
+ }
+ iterations++;
+ } while (left !== right && iterations < maxIter);
+ // If one of them is infinite or NaN, there is probably a singularity here
+ if (!isFinite(f(left)) || !isFinite(f(right))) {
+ return undefined;
+ }
+ // Return the point where the absolute value is smaller
+ // return (Math.abs(f(left)) < Math.abs(f(right)))? left:right;
+ return left;
+ },
+ /**
+ * Implements Newton's iterations. Returns undefined if no solutions if found
+ *
+ * @param {number} point
+ * @param {Function} f
+ * @param {Function} fp
+ * @returns {undefined | number}
+ */
+ Newton(point, f, fp, point2) {
+ // Console.log("Newton point "+point);
+ const maxiter = core.Settings.MAX_NEWTON_ITERATIONS;
+ let iter = 0;
+ // First try the point itself. If it's zero voila. We're done
+ let x0 = point;
+ let x;
+ let e;
+ let delta;
+ do {
+ const fx0 = f(x0); // Store the result of the function
+ // if the value is zero then we're done because 0 - (0/d f(x0)) = 0
+ if (x0 === 0 && fx0 === 0) {
+ x = 0;
+ // Console.log(" exact zero");
+ break;
+ }
+
+ iter++;
+ if (iter > maxiter) {
+ // Console.log(" iter:"+iter+", last e:"+e);
+ return undefined;
+ }
+
+ const fpx0 = fp(x0);
+ // Infinite or NaN or 0 derivative at x0?
+ if (isNaN(fpx0) || isNaN(fx0)) {
+ // Nothing we can do
+ // console.log(" non-finite derivative");
+ return undefined;
+ }
+ if (fpx0 === 0) {
+ // Max/min or saddle point. what can we do? repeat last delta.
+ x += delta;
+ } else if (!isFinite(fx0) || !isFinite(fpx0) || Math.abs(fx0) > 1e25) {
+ // Hail Mary: binary search through the
+ // sign-switch interval
+ return __.bSearch(point2, x0, /** @type {(x: number) => number} */ (f));
+ // // numbers got too big
+ // // at least follow the slope down
+ // const direction = Math.sign(fpx0)/Math.sign(fx0);
+ // // direction is 1 or -1
+ // // big and growing: shrink x
+ // // -big and -growing: shrink x
+ // // big and -growing: grow x
+ // // -big and growing: grow x
+ // if (x0 === 0) {
+ // // just move it a bit, so in the next loop
+ // // we can make progress
+ // x = x0 + direction;
+ // } else {
+ // // can shrink/grow by dividing or multiplying
+ // x = x0 / (direction===1?2:0.5);
+ // }
+ } else {
+ // Regular case, follow tangent
+ x = x0 - fx0 / fpx0;
+ // Console.log("new x: "+x);
+ }
+ delta = x - x0;
+ if (delta === 0 && !isFinite(fpx0)) {
+ // No movement
+ return undefined;
+ }
+ e = Math.abs(delta);
+ x0 = x;
+ } while (e > Settings.NEWTON_EPSILON);
+
+ // Console.log(" found "+x);
+ return x;
+ },
+ rewrite(rhs, lhs, forVariable) {
+ lhs ||= new NerdamerSymbol(0);
+ if (rhs.isComposite() && rhs.isLinear()) {
+ // Try to isolate the square root
+ // container for the square roots
+ const sqrts = [];
+ // All else
+ const rem = [];
+ rhs.each(x => {
+ x = x.clone();
+ if (x.fname === 'sqrt' && x.contains(forVariable)) {
+ sqrts.push(x);
+ } else {
+ rem.push(x);
+ }
+ }, true);
+
+ if (sqrts.length === 1) {
+ // Move the remainder to the RHS
+ lhs = /** @type {NerdamerSymbolType} */ (
+ _.expand(
+ _.pow(
+ _.subtract(lhs, /** @type {NerdamerSymbolType} */ (core.Utils.arraySum(rem))),
+ new NerdamerSymbol(2)
+ )
+ )
+ );
+ // Square both sides
+ rhs = /** @type {NerdamerSymbolType} */ (
+ _.expand(_.pow(NerdamerSymbol.unwrapSQRT(sqrts[0]), new NerdamerSymbol(2)))
+ );
+ }
+ } else {
+ rhs = NerdamerSymbol.unwrapSQRT(/** @type {NerdamerSymbolType} */ (_.expand(rhs))); // Expand the term expression go get rid of quotients when possible
+ }
+
+ let c = 0; // A counter to see if we have all terms with the variable
+ const l = rhs.length;
+ // Try to rewrite the whole thing
+ if (rhs.group === CP && rhs.contains(forVariable) && rhs.isLinear()) {
+ rhs.distributeMultiplier();
+ let t = new NerdamerSymbol(0);
+ // First bring all the terms containing the variable to the lhs
+ rhs.each(x => {
+ if (x.contains(forVariable)) {
+ c++;
+ t = /** @type {NerdamerSymbolType} */ (_.add(t, x.clone()));
+ } else {
+ lhs = /** @type {NerdamerSymbolType} */ (_.subtract(lhs, x.clone()));
+ }
+ });
+ rhs = t;
+
+ // If not all the terms contain the variable so it's in the form
+ // a*x^2+x
+ if (c !== l) {
+ return __.rewrite(rhs, lhs, forVariable);
+ }
+ return [rhs, lhs];
+ }
+ if (rhs.group === CB && rhs.contains(forVariable) && rhs.isLinear()) {
+ if (rhs.multiplier.lessThan(0)) {
+ rhs.multiplier = rhs.multiplier.multiply(new core.Frac(-1));
+ lhs.multiplier = lhs.multiplier.multiply(new core.Frac(-1));
+ }
+ if (lhs.equals(0)) {
+ return new NerdamerSymbol(0);
+ }
+ let t = new NerdamerSymbol(1);
+ rhs.each(x => {
+ if (x.contains(forVariable)) {
+ t = /** @type {NerdamerSymbolType} */ (_.multiply(t, x.clone()));
+ } else {
+ lhs = /** @type {NerdamerSymbolType} */ (_.divide(lhs, x.clone()));
+ }
+ });
+ rhs = t;
+ return __.rewrite(rhs, lhs, forVariable);
+ }
+ if (!rhs.isLinear() && rhs.contains(forVariable)) {
+ const p = /** @type {NerdamerSymbolType} */ (_.parse(rhs.power.clone().invert()));
+ rhs = /** @type {NerdamerSymbolType} */ (_.pow(rhs, p.clone()));
+ lhs = /** @type {NerdamerSymbolType} */ (
+ _.pow(/** @type {NerdamerSymbolType} */ (_.expand(lhs)), p.clone())
+ );
+ return __.rewrite(rhs, lhs, forVariable);
+ }
+ if (rhs.group === FN || rhs.group === S || rhs.group === PL) {
+ return [rhs, lhs];
+ }
+ return [rhs, lhs];
+ },
+ sqrtSolve(symbol, v) {
+ let sqrts = new NerdamerSymbol(0);
+ let rem = new NerdamerSymbol(0);
+ if (symbol.isComposite()) {
+ symbol.each(x => {
+ if (x.fname === 'sqrt' && x.contains(v)) {
+ sqrts = /** @type {NerdamerSymbolType} */ (_.add(sqrts, x.clone()));
+ } else {
+ rem = /** @type {NerdamerSymbolType} */ (_.add(rem, x.clone()));
+ }
+ });
+ // Quick and dirty ATM
+ if (!sqrts.equals(0)) {
+ const t = _.expand(
+ _.multiply(
+ _.parse(symbol.multiplier),
+ _.subtract(_.pow(rem, new NerdamerSymbol(2)), _.pow(sqrts, new NerdamerSymbol(2)))
+ )
+ );
+ // Square both sides
+ let solutions = solve(t, v);
+ // Test the points. The dumb way of getting the answers
+ solutions = solutions.filter(e => {
+ if (e.isImaginary()) {
+ return true;
+ }
+ /** @type {Record<string, NerdamerSymbolType>} */
+ const subs = {};
+ subs[v] = e;
+ const point = evaluate(symbol, subs);
+ if (point.equals(0)) {
+ return true;
+ }
+ return false;
+ });
+ return solutions;
+ }
+ }
+ return undefined;
+ },
+ });
+
+ // Special case to handle solving equations with exactly one abs() correctly
+ const absSolve = function (eqns, solveFor, depth, fn) {
+ const eq = eqns.toString();
+ const match = eq.match(/(?<![a-z])abs/gu);
+ // Not found or more than 1 occurrence? get out!
+ if (!match || match.length > 2) {
+ return null;
+ }
+ // Can handle only abs at beginning
+ if ((eqns.LHS.group !== FN || false) && eqns.RHS.group !== FN) {
+ return null;
+ }
+ // We have exactly one abs. kill it and make two cases
+ const eqplus = eqns.constructor(eq.replace(/(?<![a-z])abs/u, ''));
+ const eqminus = eqns.constructor(eq.replace(/(?<![a-z])abs/u, '(-1)'));
+
+ const resultplus = solve(eqplus, solveFor, null, depth, fn);
+ const resultminus = solve(eqminus, solveFor, null, depth, fn);
+
+ return [resultminus, resultplus];
+ };
+ /*
+ *
+ * @param {string[]|string|Equation} eqns
+ * @param {string} solveFor
+ * @param {Array} solutions
+ * @param {number} depth
+ * @param {string|Equation} fn
+ * @returns {Array}
+ */
+ // let solve = function (eqns, solveFor, solutions, depth, fn) {
+ // let original = "<multiple>";
+ // original = eqns.toString();
+ // try {
+ // solutions = _solve(eqns, solveFor, solutions, depth, fn);
+ // } catch (error) {
+ // console.error(error);
+ // }
+ // console.log("solve: "+original+" for "+solveFor+" = "+solutions);
+ // return solutions;
+ // }
+ function solve(eqns, solveFor, solutions, depth, fn) {
+ depth ||= 0;
+
+ if (depth++ > Settings.MAX_SOLVE_DEPTH) {
+ return solutions;
+ }
+
+ // Parse out functions. Fix for issue #300
+ // eqns = core.Utils.evaluate(eqns);
+ solutions ||= [];
+ // Mark existing solutions as not to have duplicates
+ const existing = {};
+
+ // Easy fail. If it's a rational function and the denominator is zero
+ // then we're done. Issue #555
+ /** @type {Record<string, number>} */
+ const known = {};
+ known[solveFor] = 0;
+
+ // Is used to add solutions to set.
+ // TODO: Set is now implemented and should be utilized
+ const addToResult = function (r, hasTrig) {
+ const rIsSymbol = isSymbol(r);
+ if (r === undefined || (typeof r === 'number' && isNaN(r))) {
+ return;
+ }
+ if (isArray(r)) {
+ r.forEach(sol => {
+ addToResult(sol);
+ });
+ } else if (r.valueOf() !== 'null') {
+ // Call the pre-add function if defined. This could be useful for rounding
+ if (typeof core.Settings.PRE_ADD_SOLUTION === 'function') {
+ r = core.Settings.PRE_ADD_SOLUTION(r);
+ }
+
+ if (!rIsSymbol) {
+ r = _.parse(r);
+ }
+ // Try to convert the number to multiples of pi
+ if (core.Settings.make_pi_conversions && hasTrig) {
+ const temp = _.divide(r.clone(), new NerdamerSymbol(Math.PI));
+ const m = temp.multiplier;
+ const a = Math.abs(Number(m.num));
+ const b = Math.abs(Number(m.den));
+ if (a < 10 && b < 10) {
+ r = _.multiply(temp, new NerdamerSymbol('pi'));
+ }
+ }
+
+ // And check if we get a number otherwise we might be throwing out symbolic solutions.
+ const rStr = r.toString();
+
+ if (!existing[rStr]) {
+ solutions.push(r);
+ }
+ // Mark the answer as seen
+ existing[rStr] = true;
+ }
+ };
+
+ // Make preparations if it's an Equation
+ if (eqns instanceof Equation) {
+ // See absSolve above
+ // the rest of solve does a crappy job at solving abs,
+ // so we wrap it here if necessary
+ const absResult = absSolve(eqns, solveFor, depth, fn);
+ if (absResult) {
+ addToResult(absResult);
+ return solutions;
+ }
+
+ // If it's zero then we're done
+ if (eqns.isZero()) {
+ return [new NerdamerSymbol(0)];
+ }
+ // If the lhs = x then we're done
+ if (eqns.LHS.equals(solveFor) && !eqns.RHS.contains(solveFor, true)) {
+ return [eqns.RHS];
+ }
+ // If the rhs = x then we're done
+ if (eqns.RHS.equals(solveFor) && !eqns.LHS.contains(solveFor, true)) {
+ return [eqns.LHS];
+ }
+ }
+
+ // Unwrap the vector since what we want are the elements
+ if (eqns instanceof core.Vector) {
+ eqns = /** @type {SolveEquationArray} */ (eqns.elements);
+ }
+ // If it's an array then solve it as a system of equations
+ // Must check BEFORE the default assignment to preserve original solveFor value
+ if (isArray(eqns)) {
+ return __.solveSystem(/** @type {SolveEquationArray} */ (eqns), solveFor);
+ }
+ solveFor ||= 'x'; // Assumes x by default
+
+ if (isSymbol(eqns) && evaluate(eqns.getDenom(), known).equals(0) === true) {
+ return solutions;
+ }
+
+ // Maybe we get lucky. Try the point at the function. If it works we have a point
+ // If not it failed
+ if (eqns.group === S && eqns.contains(solveFor)) {
+ try {
+ /** @type {Record<string, number>} */
+ const o = {};
+ o[solveFor] = 0;
+ evaluate(fn, o);
+ addToResult(new NerdamerSymbol(0));
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ // Do nothing;
+ }
+
+ return solutions;
+ }
+ if (eqns.group === CB) {
+ // It suffices to solve for the numerator
+ const num = eqns.getNum();
+
+ if (num.group === CB) {
+ const sf = String(solveFor); // Everything else belongs to the coeff
+ // get the denominator and make sure it doesn't have x since we don't know how to solve for those
+ num.each(x => {
+ if (x.contains(sf)) {
+ solve(x, solveFor, solutions, depth, eqns);
+ }
+ });
+
+ return solutions;
+ }
+
+ return solve(num, solveFor, solutions, depth, fn);
+ }
+
+ if (eqns.group === FN && eqns.fname === 'sqrt') {
+ eqns = _.pow(NerdamerSymbol.unwrapSQRT(eqns), new NerdamerSymbol(2));
+ }
+ // Pass in false to not expand equations such as (x+y)^5.
+ // It suffices to solve for the numerator since there's no value in the denominator which yields a zero for the function
+ let eq = (core.Utils.isSymbol(eqns) ? eqns : __.toLHS(eqns, false)).getNum();
+ const vars = core.Utils.variables(eq); // Get a list of all the variables
+ const numvars = vars.length; // How many variables are we dealing with
+
+ // it sufficient to solve (x+y) if eq is (x+y)^n since 0^n
+ if (core.Utils.isInt(eq.power) && Number(eq.power) > 1) {
+ eq = _.parse(eq).toLinear();
+ }
+
+ // If we're dealing with a single variable then we first check if it's a
+ // polynomial (including rationals).If it is then we use the Jenkins-Traubb algorithm.
+ // Don't waste time
+ if ((eq.group === S || eq.group === CB) && eq.contains(solveFor)) {
+ return [new NerdamerSymbol(0)];
+ }
+ // Force to polynomial. We go through each and then we look at what it would
+ // take for its power to be an integer
+ // if the power is a fractional we divide by the fractional power
+ let fractionals = {};
+ let cfact;
+
+ const correctDenom = function (symbol) {
+ symbol = _.expand(symbol, {
+ expand_denominator: true,
+ expand_functions: true,
+ });
+ const original = symbol.clone(); // Preserve the original
+
+ if (symbol.symbols) {
+ for (const x in symbol.symbols) {
+ if (!Object.hasOwn(symbol.symbols, x)) {
+ continue;
+ }
+ const sym = symbol.symbols[x];
+
+ // Get the denominator of the sub-symbol
+ const den = sym.getDenom();
+
+ if (!den.isConstant(true) && symbol.isComposite()) {
+ /** @type {NerdamerSymbolType} */
+ let t = new NerdamerSymbol(0);
+ symbol.each(e => {
+ t = /** @type {NerdamerSymbolType} */ (_.add(t, _.multiply(e, den.clone())));
+ });
+
+ return correctDenom(_.multiply(_.parse(symbol.multiplier), t));
+ }
+
+ const parts = explode(sym, solveFor);
+ const isSqrt = /** @type {NerdamerSymbolType} */ (parts[1]).fname === core.Settings.SQRT;
+ const v = /** @type {NerdamerSymbolType} */ (
+ NerdamerSymbol.unwrapSQRT(/** @type {NerdamerSymbolType} */ (parts[1]))
+ );
+ /** @type {FracType} */
+ const p = /** @type {FracType} */ (v.power.clone());
+ // Circular logic with sqrt. Since sqrt(x) becomes x^(1/2) which then becomes sqrt(x), this continues forever
+ // this needs to be terminated if p = 1/2
+ if (!isSymbol(p) && !p.equals(1 / 2)) {
+ if (Number(p.den) > 1) {
+ if (isSqrt) {
+ symbol = _.subtract(symbol, sym.clone());
+ symbol = _.add(symbol, _.multiply(parts[0].clone(), v));
+ return correctDenom(symbol);
+ }
+ let c = fractionals[Number(p.den)];
+ fractionals[Number(p.den)] = c ? c++ : 1;
+ } else if (p.sign() === -1) {
+ const factor = _.parse(`${solveFor}^${Math.abs(Number(p))}`); // This
+ // unwrap the symbol's denoniator
+ const currentSymbol = symbol;
+ currentSymbol.each((y, index) => {
+ if (y.contains(solveFor)) {
+ currentSymbol.symbols[index] = _.multiply(y, factor.clone());
+ }
+ });
+ fractionals = {};
+ return correctDenom(_.parse(currentSymbol));
+ } else if (sym.group === PL) {
+ const minP = core.Utils.arrayMin(core.Utils.keys(sym.symbols).map(Number));
+ if (minP < 0) {
+ const factor = /** @type {NerdamerSymbolType} */ (
+ _.parse(`${solveFor}^${Math.abs(minP)}`)
+ );
+ /** @type {NerdamerSymbolType} */
+ let corrected = new NerdamerSymbol(0);
+ original.each(origSym => {
+ corrected = /** @type {NerdamerSymbolType} */ (
+ _.add(corrected, _.multiply(origSym.clone(), factor.clone()))
+ );
+ }, true);
+ return corrected;
+ }
+ }
+ }
+ }
+ }
+
+ return symbol;
+ };
+
+ // Separate the equation
+ const separate = function (equation) {
+ /** @type {NerdamerSymbolType} */
+ let lhs = new NerdamerSymbol(0);
+ /** @type {NerdamerSymbolType} */
+ let rhs = new NerdamerSymbol(0);
+ equation.each(x => {
+ if (x.contains(solveFor, true)) {
+ lhs = /** @type {NerdamerSymbolType} */ (_.add(lhs, x.clone()));
+ } else {
+ rhs = /** @type {NerdamerSymbolType} */ (_.subtract(rhs, x.clone()));
+ }
+ });
+ return [lhs, rhs];
+ };
+
+ /**
+ * @type {(
+ * name: string,
+ * lhs: NerdamerSymbolType,
+ * rhs: NerdamerSymbolType
+ * ) => NerdamerSymbolType | undefined}
+ */
+ __.inverseFunctionSolve = function inverseFunctionSolve(name, lhs, rhs) {
+ // Ax+b comes back as [a, x, ax, b];
+ const parts = explode(lhs.args[0], solveFor);
+ // Check if x is by itself
+ const x = /** @type {NerdamerSymbolType} */ (parts[1]);
+ if (x.group === S) {
+ return /** @type {NerdamerSymbolType} */ (
+ _.divide(_.symfunction(name, [_.divide(rhs, _.parse(lhs.multiplier))]), parts[0])
+ );
+ }
+ return undefined;
+ };
+
+ // First remove any denominators
+ eq = correctDenom(eq);
+
+ if (eq.equals(0)) {
+ return [eq];
+ }
+ // Correct fractionals. I can only handle one type right now
+ const fkeys = core.Utils.keys(fractionals);
+ if (fkeys.length === 1) {
+ // Make a note of the factor
+ cfact = fkeys[0];
+ eq.each((x, index) => {
+ if (x.contains(solveFor)) {
+ const parts = explode(x, solveFor);
+ const v = /** @type {NerdamerSymbolType} */ (parts[1]);
+ const p = /** @type {FracType} */ (v.power);
+ if (p.den.gt(1)) {
+ v.power = p.multiply(new core.Frac(cfact));
+ eq.symbols[index] = /** @type {NerdamerSymbolType} */ (_.multiply(v, parts[0]));
+ }
+ }
+ });
+ eq = _.parse(eq);
+ }
+
+ // Try for nested sqrts as per issue #486
+ addToResult(__.sqrtSolve(eq, solveFor));
+
+ // Polynomial single variable
+ if (numvars === 1) {
+ if (eq.isPoly(true)) {
+ // Try to factor and solve
+ const factors = new AlgebraClasses.Factors();
+
+ Factor.factorInner(eq, factors);
+ // If the equation has more than one symbolic factor then solve those individually
+ if (factors.getNumberSymbolics() > 1) {
+ for (const factorKey in factors.factors) {
+ if (!Object.hasOwn(factors.factors, factorKey)) {
+ continue;
+ }
+ addToResult(solve(factors.factors[factorKey], solveFor));
+ }
+ } else {
+ const coeffs = core.Utils.getCoeffs(eq, solveFor);
+ const deg = coeffs.length - 1;
+ let wasCalculated = false;
+ if (vars[0] === solveFor) {
+ // Check to see if all the coefficients are constant
+ if (
+ checkAll(coeffs, coeff => /** @type {NerdamerSymbolType} */ (coeff).group !== core.groups.N)
+ ) {
+ const roots = core.Algebra.proots(eq);
+ // If all the roots are integers then return those
+ if (checkAll(roots, root => !core.Utils.isInt(root))) {
+ // Roots have been calculates
+ wasCalculated = true;
+ roots.forEach(root => {
+ addToResult(new NerdamerSymbol(root));
+ });
+ }
+ }
+
+ if (!wasCalculated) {
+ eqns = _.parse(eqns);
+ if (eqns instanceof core.Equation) {
+ eqns = eqns.toLHS();
+ }
+
+ // We can solve algebraically for degrees 1, 2, 3. The remainder we switch to Jenkins-
+ if (deg === 1) {
+ addToResult(
+ _.divide(
+ /** @type {NerdamerSymbolType} */ (coeffs[0]),
+ /** @type {NerdamerSymbolType} */ (coeffs[1]).negate()
+ )
+ );
+ } else if (deg === 2) {
+ addToResult(_.expand(__.quad.apply(undefined, coeffs)));
+ } else if (deg === 3) {
+ let cubicSolutions = []; // Set to blank
+ // first try to factor and solve
+ const _factored = Factor.factorInner(/** @type {NerdamerSymbolType} */ (eqns));
+
+ // If it was successfully factored
+ cubicSolutions = [];
+ if (cubicSolutions.length > 0) {
+ addToResult(cubicSolutions);
+ } else {
+ addToResult(__.cubic.apply(undefined, coeffs));
+ }
+ } else {
+ /*
+ Var sym_roots = csolve(eq, solveFor);
+ if(sym_roots.length === 0)
+ sym_roots = divnconsolve(eq, solveFor);
+ if(sym_roots.length > 0)
+ addToResult(sym_roots);
+ else
+ */
+ _A.proots(eq).map(addToResult);
+ }
+ }
+ }
+ }
+ } else {
+ // Attempt Newton
+ // Since it's not a polynomial then we'll try to look for a solution using Newton's method
+ const hasTrig = eq.hasTrig();
+ // We get all the points where a possible zero might exist.
+ const points1 = __.getPoints(eq, 0.1);
+ const points2 = __.getPoints(eq, 0.05);
+ const points3 = __.getPoints(eq, 0.01, true);
+ let points = core.Utils.arrayUnique(points1.concat(points2).concat(points3)).sort((a, b) => a - b);
+ // Console.log("all points: "+points);
+ let i;
+ let point;
+ let solution;
+
+ // Compile the function
+ const f = build(eq.clone());
+
+ // First try to eliminate some points using bisection
+ const tPoints = [];
+ for (i = 0; i < points.length; i++) {
+ point = points[i];
+
+ // See if there's a solution at this point
+ solution = __.bisection(point, /** @type {(x: number) => number} */ (f));
+
+ // If there's no solution then add it to the array for further investigation
+ if (typeof solution === 'undefined') {
+ tPoints.push(point);
+ continue;
+ }
+
+ // Add the solution to the solution set
+ // console.log("added without Newton: "+solution);
+ // console.log("for: "+eq.text());
+ addToResult(solution, hasTrig);
+ }
+
+ // Reset the points to the remaining points
+ points = tPoints;
+ // Console.log("Newton points: "+points);
+
+ // Build the derivative and compile a function
+ const d = _C.diff(eq.clone());
+ const fp = build(/** @type {NerdamerSymbolType} */ (d));
+ let lastPoint = points[0];
+ for (i = 0; i < points.length; i++) {
+ point = points[i];
+
+ addToResult(
+ __.Newton(
+ point,
+ /** @type {(x: number) => number} */ (f),
+ /** @type {(x: number) => number} */ (fp),
+ lastPoint
+ ),
+ hasTrig
+ );
+ lastPoint = point;
+ }
+
+ // Sort by numerical value to be ready for uniquefy filter
+ solutions.sort((a, b) => {
+ const sa = a.text('decimals');
+ const sb = b.text('decimals');
+ const xa = Number(sa);
+ const xb = Number(sb);
+ if (isNaN(xa) && isNaN(xb)) {
+ return sa.localeCompare(sb);
+ }
+ if (isNaN(xa) && !isNaN(xb)) {
+ return -1;
+ }
+ if (!isNaN(xa) && isNaN(xb)) {
+ return 1;
+ }
+ return xa - xb;
+ });
+
+ // Round to 15 digits
+ solutions = solutions.map(a =>
+ a.isConstant() ? new NerdamerSymbol(Number(Number(a).toPrecision(15))) : a
+ );
+
+ // Uniquefy to epsilon
+ // console.log("solutions: "+solutions);
+ solutions = solutions.filter((sol, idx, arr) => {
+ const val = Number(Number(sol).toPrecision(15));
+ const prevVal = Number(arr[idx - 1]);
+ // Console.log(" x: "+val)
+ if (idx === 0 || isNaN(val) || isNaN(prevVal)) {
+ return true;
+ }
+ // If ((Math.abs(val-prevVal) < Settings.EPSILON)) {
+ // console.log("diff too small: "+val+", "+prevVal);
+ // }
+ return Math.abs(val - prevVal) >= Settings.EPSILON;
+ });
+ // Console.log("solutions after filter: "+solutions);
+ }
+ // The idea here is to go through the equation and collect the coefficients
+ // place them in an array and call the quad or cubic function to get the results
+ } else if (!eq.hasFunc(solveFor) && eq.isComposite()) {
+ try {
+ // This is where solving certain quads goes wrong
+
+ const factored = Factor.factorInner(eq.clone());
+ const test = _.expand(/** @type {NerdamerSymbolType} */ (_.parse(factored)));
+ const test2 = _.expand(eq.clone());
+ const diff = /** @type {NerdamerSymbolType} */ (_.subtract(test, test2));
+ let validFactorization = true;
+ if (!diff.equals(0)) {
+ // Console.log("factored: "+test);
+ // console.log("original: "+test2);
+ validFactorization = false;
+ }
+
+ if (validFactorization && factored.group === CB) {
+ factored.each(factor => {
+ addToResult(solve(factor, solveFor));
+ });
+ } else {
+ const coeffs = core.Utils.getCoeffs(eq, solveFor);
+
+ const l = coeffs.length;
+ const deg = l - 1; // The degree of the polynomial
+ // get the denominator and make sure it doesn't have x
+
+ // handle the problem based on the degree
+ switch (deg) {
+ case 0: {
+ const separated = separate(eq);
+ const lhs = separated[0];
+ const rhs = separated[1];
+
+ if (lhs.group === core.groups.EX) {
+ // We have a*b^(mx) = rhs
+ // => log(b^(mx)) = log(rhs/a)
+ // => mx*log(b) = log(rhs/a)
+ // => x = log(rhs/a)/(m*log(b))
+
+ const log = core.Settings.LOG;
+ const exprStr = `${log}((${rhs})/(${lhs.multiplier}))/(${log}(${lhs.value})*${/** @type {NerdamerSymbolType} */ (lhs.power).multiplier})`;
+ const parsed = _.parse(exprStr);
+ addToResult(parsed);
+ }
+ break;
+ }
+ case 1:
+ // Nothing to do but to return the quotient of the constant and the LT
+ // e.g. 2*x-1
+ addToResult(
+ _.divide(
+ /** @type {NerdamerSymbolType} */ (coeffs[0]),
+ /** @type {NerdamerSymbolType} */ (coeffs[1]).negate()
+ )
+ );
+ break;
+ case 2:
+ addToResult(__.quad.apply(undefined, coeffs));
+ break;
+ case 3:
+ addToResult(__.cubic.apply(undefined, coeffs));
+ break;
+ case 4:
+ addToResult(__.quartic.apply(undefined, coeffs));
+ break;
+ default:
+ addToResult(__.csolve(eq, solveFor));
+ if (solutions.length === 0) {
+ addToResult(__.divideAndConquer(eq, solveFor));
+ }
+ }
+
+ if (solutions.length === 0) {
+ // Try factoring
+ addToResult(solve(factored, solveFor, solutions, depth));
+ }
+ }
+ } catch (e) {
+ /* Something went wrong. EXITING*/
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ }
+ } else {
+ try {
+ const rw = __.rewrite(eq, null, solveFor);
+ const lhs = rw[0];
+ let rhs = rw[1];
+ if (lhs.group === FN) {
+ if (lhs.fname === 'abs') {
+ // Solve only if solveFor was the only arg
+ if (lhs.args[0].toString() === solveFor) {
+ addToResult([rhs.clone(), rhs.negate()]);
+ }
+ } else if (lhs.fname === 'sin') {
+ // Asin
+ addToResult(__.inverseFunctionSolve('asin', lhs, rhs));
+ } else if (lhs.fname === 'cos') {
+ // Asin
+ addToResult(__.inverseFunctionSolve('acos', lhs, rhs));
+ } else if (lhs.fname === 'tan') {
+ // Asin
+ addToResult(__.inverseFunctionSolve('atan', lhs, rhs));
+ } else if (lhs.fname === core.Settings.LOG) {
+ // Ax+b comes back as [a, x, ax, b];
+ const parts = explode(lhs.args[0], solveFor);
+ // Check if x is by itself
+ const x = /** @type {NerdamerSymbolType} */ (parts[1]);
+ if (x.group === S) {
+ rhs = _.divide(
+ _.subtract(
+ _.pow(
+ lhs.args.length > 1 ? lhs.args[1] : new NerdamerSymbol('e'),
+ _.divide(rhs, _.parse(lhs.multiplier))
+ ),
+ parts[3]
+ ),
+ parts[0]
+ );
+ const newEq = new Equation(x, rhs).toLHS();
+ addToResult(solve(newEq, solveFor));
+ }
+ } else {
+ addToResult(_.subtract(lhs, rhs));
+ }
+ } else {
+ const neq = new Equation(lhs, rhs).toLHS(); // Create a new equation
+
+ if (neq.equals(eq)) {
+ throw new Error('Stopping. No stop condition exists');
+ }
+ addToResult(solve(neq, solveFor));
+ }
+ } catch (error) {
+ if (error.message === 'timeout') {
+ throw error;
+ }
+ // Let's try this another way
+ // 1. if the symbol is in the form a*b*c*... then the solution is zero if
+ // either a or b or c is zero.
+ if (eq.group === CB) {
+ addToResult(0);
+ } else if (eq.group === CP) {
+ const separated = separate(eq);
+ const lhs = separated[0];
+ const rhs = separated[1];
+
+ // Reduce the equation
+ if (lhs.group === core.groups.EX && lhs.value === solveFor) {
+ // Change the base of both sides
+ const p = /** @type {NerdamerSymbolType} */ (lhs.power.clone().invert());
+ addToResult(_.pow(rhs, p));
+ }
+ }
+ }
+ }
+
+ if (cfact) {
+ solutions = solutions.map(sol => _.pow(sol, new NerdamerSymbol(cfact)));
+ }
+
+ // Perform some cleanup but don't do it agains arrays, etc
+ // Check it actually evaluates to zero
+ if (isSymbol(eqns)) {
+ /** @type {Record<string, NerdamerSymbolType>} */
+ const knowns = {};
+ solutions = solutions.filter(sol => {
+ try {
+ knowns[solveFor] = sol;
+ const zero = Number(evaluate(eqns, knowns));
+
+ // Allow symbolic answers
+ if (isNaN(zero)) {
+ return true;
+ }
+ return true;
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ return false;
+ }
+ });
+ }
+
+ return solutions;
+ }
+
+ // Register the functions for external use
+ nerdamer.register([
+ {
+ name: 'solveEquations',
+ parent: 'nerdamer',
+ numargs: -1,
+ visible: true,
+ build() {
+ return solve; // Comment out to return a vector
+ /*
+ return function() {
+ return core.Utils.convertToVector(solve.apply(null, arguments));
+ };
+ */
+ },
+ },
+ {
+ name: 'solve',
+ parent: 'Solve',
+ numargs: 2,
+ visible: true,
+ /** @returns {(...args: unknown[]) => unknown} */
+ build() {
+ return /** @type {(...args: unknown[]) => unknown} */ (core.Solve.solve);
+ },
+ },
+ {
+ name: 'setEquation',
+ parent: 'Solve',
+ numargs: 2,
+ visible: true,
+ build() {
+ return setEq;
+ },
+ },
+ ]);
+ nerdamer.updateAPI();
+})();
--- /dev/null
+/*
+ * Author : Martin Donk
+ * Website : http://www.nerdamer.com
+ * Email : martin.r.donk@gmail.com
+ * Source : https://github.com/jiggzson/nerdamer
+ * Can be used to load all add-ons with one require
+ */
+
+const nerdamer = require('./nerdamer.core.js');
+require('./Algebra.js');
+require('./Calculus.js');
+require('./Solve.js');
+require('./Extra.js');
+
+// Export nerdamer
+module.exports = nerdamer;
--- /dev/null
+/*
+ * Mathematical constants for nerdamer
+ * This file contains precomputed values and mathematical constants
+ * used throughout the library.
+ */
+
+/**
+ * Container of pregenerated prime numbers up to 2083 This array is used as a cache and can be extended at runtime by
+ * functions like generatePrimes()
+ */
+const PRIMES = [
+ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109,
+ 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239,
+ 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379,
+ 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521,
+ 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661,
+ 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827,
+ 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991,
+ 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109,
+ 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259,
+ 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423,
+ 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543,
+ 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667,
+ 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823,
+ 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987,
+ 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083,
+];
+
+/** Set representation of PRIMES for O(1) lookup This object is used as a cache and can be extended at runtime */
+/** @type {Record<number, boolean>} */
+const PRIMES_SET = {};
+for (const p of PRIMES) {
+ PRIMES_SET[p] = true;
+}
+
+/** High precision value of Pi (200 decimal places) Used for high-precision calculations */
+const LONG_PI =
+ '3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214' +
+ '808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196';
+
+/** High precision value of Euler's number e (200 decimal places) Used for high-precision calculations */
+const LONG_E =
+ '2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427427466' +
+ '39193200305992181741359662904357290033429526059563073813232862794349076323382988075319525101901';
+
+/**
+ * Precomputed high-precision fraction values for the bigLog function. These are used for arbitrary-precision logarithm
+ * calculations. Each entry is a string representation of a high-precision rational number.
+ */
+const BIG_LOG_CACHE = [
+ '-253631954333118718762629409109262279926288908775918712466601196032/39970093576053625963957478139049824030906352922262642968060706375',
+ '0',
+ '24553090145869607172412918483124184864289170814122579923404694986469653261608528681589949629750677407356463601998534945057511664951799678336/35422621391945757431676178435630229283255250779216421054188228659061954317501699707236864189383591478024245495110561124597124995986978302375',
+ '369017335340917140706044240090243368728616279239227943871048759140274862131699550043150713059889196223917527172547/335894053932612728969975338549993764554481173661218585876475837409922537622385232776657791604345125227005476864000',
+ '24606853025626737903121303930100462245506322607985779603220820323211395607931699126390918477501325805513849611930008427268176602460462988972957593458726734897129954728102144/17750092415977639787139561330326170936321452137635322313122938207611787444311735251389066106937796085669460151963285086542745859461943369606018450213014148175716400146484375',
+ '399073568781976806715759409052286641738926636328983929439450824555613704676637191564699164303012247386095942144825603522401740680808466858044/247958349743620302021733249049411604982786755454514947379317600613433680222511897950658049325685140346169718465773927872179874971908848116625',
+ '1468102989495846944084741146947295378041808701256909016224309866143294556551407470861354311593351276612463858816796714569499021375899793849136855085849133702029337910502448189055357182595424959360/819363879309286303497217527375463120404739098260200279520788950777458900438307356738082930586032462601215802636320993648007907724899611296693997216938989854861043298494990214825163523387600982777',
+ '5896704855274661767824574093605344871722790278354431422729640950821239030785642943033153793245906863203822369276271050164634206965056233097479117980782641839669/3030306850569309344013726745100070601277982132543905537366562638553198167007159067544789592089960911065181606283478843359856123992707598685058297067179343872000',
+ '76631772943534985713873427262830314617912556928476573358548256872141516989538374761909611879922349479420014771499018155447198112155515453671128814488139633810493264352294560043912066253026059140653027326566801398784/36852092933388988649396042883218509607503204211148493545892849595498822817623842579026942621098851631842754395231561679671400197056377380063233740202370686144673585955581403046886083948450136247134308381940165804875',
+ '3159076083816399509754948610929467278257473888282947311280653574634802580912280940686954763313882823327077171624015737719617373932318151594325834524000275847475866299387913048/1437757485694188822758304467756419845842037623148461107362957994816554782989250555362514354661961482939226272309026092009962414616417412938087494467254146002233028411865234375',
+ '22266067259907364984531611601870291368272674573653403965630628996687370994139884833897773468149149664829922302484782423514167405397665098388400450149078982462318781750661005833037235183394221496186539779712428265837926417581952/9670030144664428565128962309657100138096047028794689249320859276197340398920725569428532293373676415359965773460364494998334259893079003125373872108770534788283842907318071170285038777091588292539102269617376180390982915567375',
+ '14604654564989239958569331443385369522850975185358647132770022716433280072271007767111036877803328768910274400515590151934676819262085211828028638417329558229123989556376108454497813055/6090614019162516693013973409650613208227889078878781039105047015752493519149314227721984436973374032279421344818329285207124280297611253861173835238379831004010748379874393292231671808',
+ '1901241885407696031217292877862925220917660047127261026827869027159993239567933534052663335498281439239753018507182016153657409777749792228538380379703411298411623469292891476969894084838876001545818141543890273256985768690847587711270930688/765116019778838839812655402103512685695769161212360553099732689795578904762091216998790589926057819838537805856579109910198553330075924857419395160755642371550113347465300208422126945265887065434116781678702741657275181694851670325469434625',
+ '139459806786604751793737926146840623607010208216289543036026206208962059593900745886202214788747453279179283344350478734275973878932538430194363355795823581315329311220701640235653288975569812161436/54371368534412517053056101353618694718215711767266376573138772968257303578467926450212293233332401067673270853953399269852376592855992724934941173346260129257754416412476202526978443681584633116375',
+ '1045669091124493070709683241190022970908640501171378776604126771144008324358233819560649021940145166254659028524319517244711645162132513416238958170819347361185944945680269442845829390112062101255500836072082817820950448463314034677353723256969344/396228259004446234921310936915931611736815598535963504660076315228798989932959459406702091180060429080345146735173591749448509810270759531977278642135591672189002006272326131885315743181289970885337574780897529347356567086535505950450897216796875',
+ '9912919238915437302006264477931031611447467070103973106567538528951878797932559935860738745374437522819124347510590800370471910492338584284092534264608801221235029062881964101996762011296996851893455828946521/3660537472668264151218961634689665210933936249986285290553357254224360417386515311493310199319523687171757653216994741150377508234317025158302057758196429623723072084157928224798322861732880034847243894784000',
+ '9263710175433181746575186369318246002919895649622127410824041370079225200282403368319370743363303164313395723904510539050157032684710468364067204876434546848634842333436957245275217583248805993142227630297924119330553308466662488683624783307023014909360640/3341177182697517248552428837661919299725031035849865632511882688786226888137634168024976033652753689210700218163621739078534353578510364301481093730054725078138658805025014615651043313990684347632166030359086885561104034510990826655289288319840595753002771',
+ '5116082230713622171832327542439052727465114322479570603905499496221224653983960598946033081212909066917137546065542953865612718836914393275681318667667521726785633638189373998191090501201427906618075889744489190209584/1805752553736060443820406101277706970767657006346276183748749630179442318063568286372320188433843729960294965366346522303898609655762491623098453269916163621089005711823488749297418113474056676109581110715068124438875',
+ '246569125619713282434448566970352231845414317018379160824176638351574938993535464763890962336882760882398479702237564384291290459961036068916857265499633061660562532011248501476114401629839742058389195725393702000011860799793778295606988057303225493814005789533570432/85307063020836305797178273029353623060860009152114361453434032434699636078115114412588719432277441055049132559782203988387794711585368296817222565434951256788867244687081233632650953850383220864394261763844194948389861147622944651546912394593164406926489862036343375',
+ '133672026303452911046163998480860917119290576658330909785707604886881155606725822685088929236266583416708668502760907677019598002175122453170574729028452721476464728566191464897928696630979863154661704374206171469014225143/45398130975270785045482567762871405072140548998125471025451666500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+ '6041693953360002800224091673336562508913199995987479264605216252220579740134601435770085920869376641180763419907442721705887169884230643795126568815123647603047739799302562095542459344811429882053086550900803768964612193941424128649976704727183797495759082741166938351872/2016766992122395667828553277997478570503475626107286343497917705446132017125079612756035254750822860815515899557855166824523851779156336235294914777307802256439645525835223691751931866188957324792276149549076500784191791380803500156776088683900346065830066370370083309875',
+ '705868391597244582764749229356331441978820024796066870551110486625729826111158236686696326058778874201639006234449557592353247542995871491078308187261304930042019640830629526023972693107193897009168955674240659026247094657679060/231848642748474339277532000336338632910990823562381469441716922006107433404523316252618490265927265734670539384485699132080062215196462178933963957679882342083893417545858074378754089719547920901917516016346211301054206383643383',
+ '101832160604157943093944673541651013907278188571533075311673249923948856034633446617630054761681006062910980371900782781226979391765818325065031889334563981235894369036439929651260587335544056975715076598739977065390678221999918899003881778449092038750712969437519295878491018112/32944277910571666002449086492515464541550138004002141571670657643770713783329063548790202120805341989608877739811787937782240802963962520261844114327432160788193314874913687387269408387417806176202979244637915812905426565263196954203487934225589622864145960079736633434831996625',
+ '10655703119271468913597640479490594180964700448340778168715956712130636958373270202484276402718566314881119559090842449610957974112230306343486091910217340665146602598568991520563987490686996746558858366002301982443029430290679385551/3398412687366638541233365137084722368200311117891192348532156645374786104142009695796409107380345795998400850838706661851176885183144928701608654514812261697598380070746520197171576610572921007069104300695592751543563472456384512000',
+ '1903039332876763837419920240543738799531131775028971323439870868730321221615515008394327723508670975623498588291298064320786970626232668956372004004897872810230178526101184611242511193415796638694370503100219710864543168952682617801833318493436174387568067811938490953495819438108686336/598806534367503338307287246320963280558134937382149405305466709787179429317914803617527827862441615350396864359976273212272586892074799651088317544101755361439294687323233086696182687664637422796995789967075271448560870681210580691574924544896656175563265378514188341796398162841796875',
+ '525573915563826130963525826191411949262846916750432019596028344808298471293378917508549164993368392834023782480702893643486699787870059946429810070222126260200026332874480239090370088123833491499400991181659445914352500247596757005142623368/163278727324937389095822405034435687776345799835442022795533783889356344755225815267819508608559076191292900367982490827396869405536484846115955581043091229202052407483776587687967125885665493681707461345895999542381476164157058393971431375',
+ '9263815657177858787273494705338516861045771674838057329170239610953039987023429736752079544014780707408666628475997291124805562998227296677616204140605356257712022384368492575381355563976330347792504605666631512343447560301417325154003481040250148561839861837778597346623630046623751094400/2843321709948499955095590862256744532227698001408929142548057792217790532624003190447363578048562448168721539177458065482170148482375585867230123873178100117094533143052886527452665480614620123764036974180917207421482431983407742154634391264619615289225747664532332469783301704643254076601',
+ '407959339726114455622180187758753007349209016396248763075759257357925636039752474207685682218422721827857994768023399625060206708378433960993946156803948655098667156937949174400873748557248801874735834957795040139401560494087476967548060208243867/123780218751812156744401121690996305978134694678934447237402511116731459214498784497436358160964198336874043702652746834763131444030185151143987331404604087778514863973633941401826334750268416015224906056576641018962863645043976537664227639296000',
+ '2547676391598917379516698439971914695230548782904479778605691338364453606537643088857116141939170899135026552016969320061900926954008522781162186995856580955090548471448276736878300717869625651893741316530109438876067419826217901657017506157997588944233677467357220316084583383623602865379325184/764562034757392298786420374672266498815021229519853724850874576419885380830752931701831256959159800764672605004880389358601658343203513177084389490286723240185146570925957286083025676875197029662038213216541352875570101363668917766225709569356861275434470568767077844675593176178611021135573625',
+ '186545352286463730559933346565311535598243666022232037054735807289501173444103692309735768703898330430135399033529355360391658728987379385732098960609744313878477967971557204207043802935782878745271859468248704012618254203101767841517569443555143252/55399179641621656233589820996143825959365789093262978988289445625153099592463372579496245442338653053662134699646413817866770218574795378644415019944304868289119443774932782235638737888469746745621382139263856603239588594078668393194675445556640625',
+ '664884440164786473344854955309049113269357314957985265728106924238588705533437169796551912202931185746193155801905841712503407258166135075966280435780812714252670362202091663287095423712596462690753468682634261029392794173636943978404002804413009590005984736612421172979101972556772005594499779860608/195485517776407145286424460448995460754674039560651791192647586550615878988380153730602665795647187884543361218962125172808792176382956599256188706636727418572541254480798303566840010217729386905041217793614214518363859058348249961790104618910877813067510758225302884815410347238200133693756493703875',
+ '2614957283934314904315471338485451166053664494383241929385424599389309215073267052860464009981063483440201193771607520572077231889699858482582363845275452280606276949653970992719332472370351170732899676316967244504534154616036371979031399425846100527685/761493664432749089312665480773496290658029971027686543404885407644062485746072719559288231362060149626237939029641098328278650939665665969011529293869562636656650999759724704272743235210867676873525147820749560155294022488994426729939894753293900972032',
+ '124843380518493746761140367283007507854364503961156704095198010255465940085534099747297600085903814014415830785663764373057896014399822131175202342399536439284123918855893825207202244831315575594886675813256448846863723093240955901916229136393454605455444105444987028391748121054399538064686074523506176/36022228212051654395480210378626648518430280334458144892889271272122662467638331091863215146548048144675657239846337165813938424387499358852301016926312083940212100001220180762189978024821166744964908871443681332664798940660421469519997746775275873085770018269706847741064037876137315001228315806659875',
+ '827992369063043155578730871896750570951766628472810506926098505028264552046829097082095665194000002802661600196840639204300804225352337632259980703832713031790922485730615305441309917696044954289187837653933158950774246017223571461858939407386087081525130831392/236805932823686534991153393869288530368011574665859226704279685567723830696754821658770176385138917722808377962346690757191122309876922069867472518117628639913077442806147910884267694879089753138429767401700283014143248445966474839193628309668702223994071394625',
+ '17347276886878323736540051321582548724378497839789943634071026331001588645519865992773157565595886250230140452154269197770615097377486013097979087647774513500701793885978192218455687078883766086309728287172567466406449372659680040183273634701092561727514713494914793425407149186041796935055187281744386432/4919325621804683623339606849970832094714371903709195539440424738973575902329797546592497378000858196173718145883783709223158260700365224756081275272021856393735663399552166737690038832550853145831185979094979556715294990257315369124065787473707136464772247917156232366320267601622617803514003753662109375',
+ '137984231830526866236186357461458917020538108058615632801298091031540729111527734872044790487396302545910108285921421417358113055522725197998483383380192391312304647004240060970929072498293210057120617332323445379424867965764749534125081131327565507524502163460761/38810445792642817561168950890315210470940006613819790543653745327778579787694809782601777514116858514049585074667085399925278459138508514838268321349069481334967221455722811414399738756151414906092225265355449011152267068726417045644222323488445626292574879744000',
+ '746567120547823334914136339633766098626636643449144032626270358619125402826113269699709721071135471625588981126637674402048519990010499180844665151971356149292818375448504122545400227696621572263621729512461528550588108384619064912224884465737417596190735966915167530332762203074440688676123756162572829692160/208334337057923929636884170505570363171441147899816815785150954417598643614152856767186132467069365605496210036171429712485182162940460120834349006784956522600679357307849981862006710239311750261522832996877712350330290831638640913932265004107623954913155144975252743257846945609734368518424172846119306643431',
+ '64649371728330695076928013661001819989330953381731372450140483779536126948957993261299287753791770622512248630224724990234903928056275080682537641377393210728546364176267034339221558641084730052304770498929958838997239635790469536857863963589118888238069738647239076/17903951498200212327802847425913723358452100686246224008745414214690047078122925247086521362329833307849817944645647750649290248110509395628305970523384831671737569872597295947593410067364379687588919135621621162007748635920864926867870502568935739725312687094047375',
+ '2454918942158003099688922026016393688092399295166304634317616773083386087532869193458590448918958337530406410803840837646465522656670050113548208618655070231274778592766244282964463702354872753657766121825196898916725498553882689210280080206627916046484942827487726300822318764058084323314109595329304407466188383616/674880185931325925966586583820010578979699141814417326552629206140252348822939845006845669570885271576698771404162512001549922909048916000017837898649100825976232784446638776021483802989797501705685620612986771521390439936066527738682396560462899753657942715306792783283782238662155922082005591512296007820682995125',
+ '74018558041066162916454010680594042518462756234254788158141115244349044958441521749277686851928706433556285971088455226217644009628399441967508838553345152310730562224910795446341601049647392069373970101491741830623078126344928804029524181578945586663110848142571149861/20204153620006780689923328634586091101021423979622170579036140596085566172775051595588438592742563923428900864000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+ '2127032036758045513335690185608563023954009095206088224487365541995326714285119384743928987635752931664240752323937321097955456543854943206092931247498833001499955456190701695430459583885125382086777607021670447795321669948733328973350279846928613949120929250312666393359442423066212311060931469017737106028339882830848/576612418511902928757340062840968526862381326698309578771238715462180282212422302261044980131594522407066369222998903808960617461164985318633518680304995784614308979881735537678182134128319596636920719106506829571072447362052319438091347699720147003209417806230149598345068078717948025207635448205253184540936478445125',
+ '35289653975561083576641954928762116897061274899517309102784750384002335187117263273488751066569234386120759866204372398611196356888479036949053282301027789530999737306501029700128744408015642211359442183943916106790666114870974212159410284751571905275610921784716184508440/9503006066880728386808143045924119024212377150217533250562188228062174064693375135306438120385877320162710918716613546077156389583384656340709638430674364232343609717735574035535102953482366914421205216675248471695111720986346092738728929878538430662191272737183832556131',
+ '102633551023964794485575491065909467125458972250222581133681080524371507544152979467328048718122409841060527545925136196267751819689935599599321090571687632103850847605493223603751038996548520557330016046032671961857623066292962260173840972332108111505971231021442896036760967107060309991355545554631003681544611731245475968/27459658121882266328752886605529964804078316737648012166874496015808620265471203512606463219297059547428855195782384236337998738233668399173746663289852416697917397644234441300570212555870401420579737973722145663287124151049692290432756231390864184491891697469874600345958989433125942336757049639797225309327019275689074625',
+ '10034393558388390065766795008210457368713365491566387292163814915435906649268119060550511145023450790393353937124495488860451123302412204483570913557762460385297770427946219119911920640306914453207097103853766023934602534502476962159682750262143380527529536498215384467975023/2667919902603322771586358077760621955455470781865624844984169443739075976572061827709528710108877015489050369589117491611045518221354793418884447632063538994046714401229510497599783726376490260140723032102883617341970952663947646017489439179953454964374887388652792446976000',
+ '248528145263843375390386172800048509380966183384567983242213959113927668429802237067505890436957693495616107089384741585283620097982859345081736730899912519273262934785992235852866637878831878448348444611412764161078458068549719800733237024285525816723480868704742804077255242682077291713092790250511567621735004237450946304/65676865669148624809340872151906045781446981664561196686217551358486802274698228825404698950974939545099727242259547145392352658637333562345477931951890984276718673618736565926663528625796412420753961231404680876558659735251469326707567479071881966875336951133475135427640218972722939427821842173216282390058040618896484375',
+ '7805448718805635696495809414501206964843262114470109146341305656318015059743127114324245035489577134938579856003956861881125856595981500593426840968087618241785931128978516340812066502964561231235073012672356530509663384739132686548934288703179479011016719045530855033205271548/2050525178024039744126592505352202216905491833360272553169520915020715464206141942151086176509423406413311520838568324134077402841030113427309725873344806030836314500267104070131451720947531994814710189000076651895520222646974590481497382830325485174899169093049299764813276375',
+ '3355325071293197839434119105039673324264765809771192815982246040415580387729382404624613875653005261578877047405365032178619450963731719777167015959920645055600439987161800547901539269321100559393048973255388860193948274255340335876890491746900991668165565729269698196233805991206691196045182214641935483083662356666996922240/876402579119117579582569839757462461050855174353108858954282915644790659429341853404829661899850841645529640454766173209897510988090318303454542547519850473808789222552969933222203420847859171250332350076509996295844203965564448154484566493395403967626596213792922784509892086361572955175655987334882030766001799867659814117',
+ '218871061991045868372866381545267589365410350294028138778572466235486397478028823720846191998825628156716190463263492304639890659254282445466806224943413446008645087186307985343574807361972238230520975439736199291019544576443791916302825193643774360055545186783819367378492631806297/56849560726416896431557940314760680962653658127458002233782028041537121216487790008085876994020812492987733987414743604239935223783349870516284048368761617736127892160849065895223288023531930411718807065209903593668117085505482007061969339237404945180379460053180570404846043136000',
+ '5008685108365226931582937964451700746853986170633433728409171904803795018146152804690759530990140552460596075588463394200510044617816085275660078502126507209302951286606953039953843685800941558212440519542602092919776366067720586295390886070120828199562643208637974347390938772070049344991272621102622931576339988103674070876518912/1293888539680354282541277646947380627241979967611883341823378331667976045287311988103163380651334828012840330710760757271860219584371109472132211215957402251594055009937397184768184517621978947384029376766290498101728971145633139541827544539988344772578184316843734267915665730981857376872622787627370859411909330227080697966353375',
+ '15388340113525711660227566446101909585796746979396093776960989868457211684028149502578116456785221720682202816140911944661051001675127262774824593420825587319436537346311831003212424497488485098543512314062112948777572038731823948224734505930748371522309451168088057190162878224801232/3954220582960831691377435160890656173654063611768428458807273708040518769541211737927975894584024448193835165167801976423275767590502552964407494549049777006346189436817215329891530811451811864579644894987864267389290848598289794977382504890216219362031324635609053075313568115234375',
+ '5099039333987561374222193551155323470675617979816941646196895589439391685938046865391119484510329634015275893520725135141878751153360264368353595348921951280561029028912953500944814771064409611917475818956659775131751121312316084465321917769679881052144364834485866477379437705913911371481828140817759401117780199246301705600020671104/1303503600297679371136943454060319958680553228879031326679449263682048703103464872914972900105569835004878963701599765030590097739639045890060548760692125546754294514068052902543220382104483822438283040090444827980927544440984823535260277595466339403795403200720622852069244768910603820007632395190204569927612348189089161551951106625',
+ '4902837141334073026145827027361937996261324349722726869116185158777439337041263482852376194988371853413467559557923410949898048139830183335197992754748294810838187068126867611615800383834975563313220497573778480109264178673389149671194149749735833378557143135481387904961537942569904075/1247045310545991266291285730016853118981099516935251861146038369985109288084420528171217942065832292739130145780833406014673689119563698528225048800794718789218267628507713621235056538202070171596177775095071513194885568843375526804796016261173388452184505503341132236719484809714335744',
+ '38114743522716832107917466438257616720476488812538316101658139632867788464381862291240727309611460187159930652186486096300862388591521625093237019662273764387591494074792574929490381910446287947994150655077877204446864004067956087975012773988833339521775463977233068498404144221045837190392670308437391686081418318624745039402145439223552/9647001083383999453668111809775451078976046488746916070976218645431946648087171586252172936600115032316383427265217993193444199863138429602138841976586190525451324093772097241349417938578878934577091671046050326087898259692917931230974174799815198493279413438192301437068820185757869608523761456160341754512329264442115351926967120404125',
+ '573695055225225727008803730767518906490704995929177617646275646884555707960986625481944101622708415415988844740028718027554452662358957933526173824325955904005404113684003841990198157072540659184995738719040024647370869010473254071681533880576462368600901824622431045529064651675640055917092/144509482511118816399089096021290587489594541280398871255876563615464628718527634679330291741479135415168539765887291789615790513527330600394937614433502341116068305347468133950204152174094704092402978083370792135432486240914953928188835819767755172666693219213868545854371103120604946200875',
+ '23876960329653589647925126180903391687666378233201794403339630995420215267415575142266707357255726330536094448314199602616026935251126469221925945960901748679919435908556550271504767784553484434363646489174587463466333864577705745452492395785557425904735048180164697040313528831173448025400634629163795223739061661461986923675833880378496/5986312408594306954013526197465608559068621248896320652512228238115589875514604632230098997609482248000888567135685167138762172475788060284232459813998201719590208742091697294562538265829954186149162974972471533202880368317237508987477069872431064075005305838801862900501819963793062041081601844759452202282545840716920793056488037109375',
+ '58168289917567723171226992383559866214094157894992327555495441698028867727845766488121900626912848698952863438654895252811583144479300382761129433911280049009362667380001406579175563745824368613319103673817094498117944856004415812877213722455299491145649879676787079744410765053845551958756701/14517067289347903655500020160671113450349743650636953726251191692074385521975132268313263723831804150872238173602847065423463131917373356798750100313145228608894881457107689499956903046984443545789053438946050974567665049237414588435796381674590098629779384355275820782532479708807512981504000',
+ '728621890568281859295409481422447012528302594365693410763821707074444799793690738137592101239862736313347273167450056625929591960610208335290882047413011571781161008296084630072829079783328937418641417642857196346026366370059522990813537731394823630207433267854616768658990289454635793326766697884798538576055949457122067828153655416688640/181030730759516991863708593747964787874073354051675597050399087612142539517308720603687322924426591889179726492403913356461908748733972707460063017057809060190437917851790767968877215795679844983288935075688219234885360839984681619084834228226744165610073685719017596630302462070188937998558312507638434329299017584329479516410907786681093',
+ '86855946923438322218622470067224691860808273886184997065663554841573982963995340977083049132518812923329423480393306918856650577072525633920456721265953575424233701929892019410099166322511413146891121248381648145391642571638857576890568882512129960291171866772665863159474602604647289052079991768/21485753507365901947528588896402264670781310878547726104482740647554738151100954835784115119035980523529677083504495839730499664052882400915208251594384038810917282207449860876251558307288700200910747338758723324686939379138206117634546981163355060740270734146780942696291669461182599512320099625',
+ '2158989152301022938148680102142188531448821359505188055264665167313418619665693092337665573150374231484840948447637297247277576415460889296724813940128955070240137590073233263168835678714131062764247434144994737610229909964847568491446606012581370840699582055341626266533733744293929658949697805855362114229666626620766245630122333733703618176/531794915405164005613733454597931482878479882704956110685223892325074211694837836221759995948610212818642789132749082430059593652854659130217225506942675608692701447738732031302987802196501895840510235161825501235133794449421919927396142470196961877376701957829921152848178076410141813926924749057304222282687697297216661687583257901415465125',
+ '139432548574396829074586704387656697097760057897628994548358619815052936481650396157428747411173567801047221928593253479330480454469358220685854351236980383914223693722868233819483137401339800304943891968050399345430243790898955416907228948287367356990263740207046902209563417267686591994743547621/34201151688775214071963206765436083445901621442002061707492082843232231754829227303539041286301398668437202547003300396162741375435703188500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+ '11008517174872833286150985180322584448162884832099344969609291070844193524816852920942383850580217443209402836100467940651581092350600329145627967515818684442171571156446321228596914355704205623857871497315955269266498229823278800717909321269179839084452384509142712677235552103459737790674103994445173074670347080506698168482564009465276165824768/2689223396936080856855299215659204161946704205931885125148201643087176556822542895325191478283706585400237901215485150928036895428721912118467760766508162631903585126377676412573187912443878232521444786090510891599171741773242011017926658231638022943018461086517502584854390836347781674626615709751386455292026775663545470794167629144456268750125',
+ '16108638074211260588800537540680707641986073914251424878121255234668558067988171568946079848860335948991834525552515669040163026131919804987340113244760738846884911038097907756220945883750502673899084880578229601870882631165510396775126850307838505063922101682333806284668762825609556049426829531780/3919363961344261777100658318137884299575193089462944554282218278496298610828757650104922583359642384253066896538203596057302203635134833545580869871333892935330950583664400555463557735723364497947986885146043017010159347046389604172186788902608216894094289769850517098027486468084407618748895626853',
+ '34420755849180279597302103726180110022640946692592540634353734157479505420320000324260530767186132260970572450489530034440214259559325114511265075416512316229177952140217732655405289808326341696986755141965043719344169685611217958619102774617224847284122901023774956887687026904767714958090256282893003000752947427857703259704682455375442735857024/8342030311716679826889917494957593165464748884572298173556257652389845294530325764837124998293398445804458613956489096007564811101361266196542129764287084823604897187311540561857741285793447174119667215803837719660675298308873496219385226998078648428368061868944322478384684509466965129972030932418920415308276430355882329457342937549162000252625',
+ '345888075261020004071220843714060353763382280664960929903544964118831237876694384053904571498830068831026644303797377762345709976595360421502594656308937649239978525713471393570536680412814805076323426256584504251728507416368609420882442293831684681071553766603478479006495757222912500012444787804577811/83505703731469734628961395063481893801938371516752417759131774530720075262459158384433785006689548434701904106312038822969658455364219435022841597243178757423598248565463985786213156556523685666430799283870548238467817226915680747412191245046634279766450629886904716776719219698922088211154187845632000',
+ '1061717830619177527082296723099890392273896386613997004874669053445943252046748251883532634529759169500795452576392700472771365240996842610207274128102329096619028487369622001737128463631016494371635687841733644339636164570819431573829173533941056258744442930643735587780907310433371453992062647737259587563398111688659657406089003293576961475848704/255359631537215747979895955806995352799574790340218399351168178555478073997876110889483456972687438702262017800167048243754141722496276537685853311434069991222324039005160057724073156957530106623908696241268268096879569794431919729620178375212905203484165745866913773304319069321426245521467122472046370356725530914587807274074293673038482666015625',
+ '274122944106300296738399632684955400761495830361663966466225652918683099779465438024846903286816813856490888796372134557295699980528187779624865098445756013563535339056233912394908544185885547842235097677765325396255649207317018754967666450708249125316192200151505568416495274671679500594656671785202496/65687592621976546250581560102201535533608158256953087745856906437400149205693427285162333502528793675585022025602144243543064185647792948495372442630333800126269123531636800213405254045262127593759539706750242430153456891792533267948231185296091297979933562727112487057234422009426868531651634706262125',
+ '7842680480716516803148821198697967237136721860017131244266974996267074742248599085253569637183007740566941125452215834642683053334607896723447140851344501084122965014242091312411884985569341166545074688756440728922408743841592658677792796881188604773469108807869960161395759837407978596679911066586626885830991556090978327508459276025943279064965688960/1872528612245648675720382138045071131304652050696842872529163720558126655075937845539792108048310219395746259570506175902206215101518698490144716531697689534559827422735649881381597761684154409796315455445459537515308174919488497154409643876490472215352056502193150125644288086294418253309947229151074464928874881827227706992859640236086417889990541889',
+ '5300824422251242070074569186825929119848111723012841627275830216301188228660779008353049603527567784119877706984722171178137272986345560485784907345500893648715341273841147320288851034078863843374665850852481747000237834238703248634174397792745914847774297223176674917912406659831206869442510948965571661/1261140476013707338477604677428573831791396352814802149994640617701773078174882455512668089072441176857892331468691160991310474734143842336092636848492066592397892638052212250229129355009939118431643425836944282456647571558383755315238500832868535816144280088644939696339160092963629012001958205063168000',
+ '2220223718762215584659309059880106334425515875615107369399767892051551634000614327272260081056973863669004224981561870246078120862256383581012183852291444462730018546753183156982897386563561418424093883164027305254176874653780425452987066512563140531367766900610414277825262239199580925879453806414860409441845631158680721091621460775043562065815179617536/526383206607841251253861841374779803798480623722760367843070466043030228662340154304405180907941079883976168609082254331465595267209149963786388600028701073430773581228212441424400748220833542964971495005714483235359479470452593264280645360131482713147116366500300066771223383007216182988263355451923333319170174755334598973202740108032097242475554128875',
+ '521891797109626296684891455959263713257353500867652268541535940159815152120871142196535233326890353914761242025931373491906127275561002910157909306979093246574207104081108188995072105948138299097848175016082947174156278439986705241571619793059501724269644447572323501261424770743329858038040685313621446524/123310256826873923765604825413207481739886340225713108649758575106598510022338480189649787216845041382860899099250547657534972156328080736149239332330143771138115695598493059325064119176038137294863053148618656356436332991079150723235214278848602671333076219529535123842212129829931654967367649078369140625',
+ '66086044538329677372986118727999622900471937619891337714357792768200341519193500393739322894033303245376225584865369486696276607060432449792893028061817203932068085863800494054274423512956136695211796751845295921015953538329385253280866669403169919614982155350899648626481405781514434761541281229159396787287553493046927448595964103589100429722948913403008/15562137339474350565671240515273666798063901504051979980452491653975250630723677279081058884163396938548780856293034775459223871281049026140999055923743471466471830572672766633086347312178711643724485955576579988182546105048041649947277672869613992334541438784737993706482731696809943027528882927942967419447250586964258807454003775693567366165507144866375',
+ '335377615394100148751647837967017467711612297170079949298328061159559939969228226474615711044891085626519877634842694983669611974807129333052471799687426665556738316626171408219730853872410792831871526174987402129691897433888027072807302411474690613948951673562473758814664346259109886876538510453475290967835/78715592752271462306588358880337347638000605031000575876214116610339827495261512281635361568951675037834544811575026718101166562072917855004822606752296233435017284127594847656529606648345533195437635894948829857913798336356647286032372695130461573940500785137424365840081503133157308796505622439791698116608',
+ '20090879701618729602554170716780970848925039917987945471322994867171660307998603515745066411687983450400412739285577269751603921163835619296822801840348319742203974023505186187060251544248644338412667631232247108675504629538319425769464277309915502144443973397371136256151336255138506001292355330875114245901820438821732843540725116728866301271466614762497024/4700223519410528857298732096729483544820841497820611795617923063440946097326817340637303431283005509904481323205480729806879570430868897342398783028649633951362398196137429076844504529051072393709154483678349272930361110568616112723747726853614661953537957117231900032044221535502745676310313569997665352252492568100075191900969170979460298189170486601502625',
+ '2904778979985524171206573028445379872240558084236464200857594814631031581387804621371822074061289363372523364167184697785570324832815972970658633551879143187709707164796663015180877412717910872234647704536817108676736661804878068078543241390828229923424191204586313620612539678930999769543756218765870513049986792/677408099044823641581658869221044375312077929976719183424865834811543737800956896926637625166844372424044003929341361734886232742770909683021563822987505236295727478159938135467975522336774471915167606673489722102077041330652185811196423400701795791669780695158730756241178262962515917389382302757366325768069625',
+ '31270155809329751863885224732454397292230969002004953832354065319735530624996254695453061851449600345977646455072512400760539747054003851289540339425848681804190284451253462663731135337775088379954403740058084949675460445909826322297817535400604180338201322667139062500269285493417563095365899631360901732684124930296643108551710704785906431324876072470231424/7269578038000504017073007978844992319987411732848567116655821196644382777088703228960020894756722675887473977480537577509061256138261063926845643360849217556370868752909531088361229374467207196928745673402380473721018157327193509586295879051411183657185176812738231456253321187419224704301236205478184115996135940848503487199394612616742961108684539794921875',
+ '432538822079707760382094121020421735679118830363764570640789368235407853152380328891350816400541189148550353337874309885334920995713154225799660601389784410911658967499100610376065640785585342035058364676314084595283850213942576431310823836792440218271879354669291052589804956435743500204185107215929849054782893113/100246574739326291035824954677502591279343311051719151327066341370995390423713403739043396503785261917771859220535505691760472395306543276314938287868734009582906895763073519374099272340577921671298878837400921045252035507925021904954445172372479744465666760762909731237634082051855588025732494461939980856983552000',
+ '1914333673689206389116942789116917579088664511118582610293383428712902211612554212779880638065888518488492298586641997844141510832940409501694726851666478650414191249534733087933879981733561565249818572204932715347752949087800778646065986244657260832234479202201129845117955957242616947361016603702640821256099895469088229339240402478576285854783063612307200/442333801076281757298117784528962837115323835962460661601905131618341609371649347131724700192551574625400701487125984359494804828935577124602622582550626336986871791407398609915208114339012374456785705161994343348351473385887949809051796407340988735853810174589261300681030826392672282630613354461927208579993042968520650313621522933214063366558703422757071',
+ '5704691626402072213006354545292364761246893919997531024861408248746241619817955824682639582830486790618668221530365426203424888737658778881721063941495350237419723855000515747725926735319471480027293210991869255971365522170749568996651406002311020883635577590045650037569906001924971041810547543163363976464780729932/1314240362076792592671773873754757443276256223533339004339330559325754574023619698171225777585408160438834449576526997055649849875516310105297894855264038450585295422343454458568152668980131977005808840141079502436391909349182185596381509091427752151860204235071122788499996991078935216686010888734471173124487937875',
+ '451219364084386208718456142329444023337343409261545444643031014769484085278440612677813682892926852469540118625738238137458321112005189595703619178533263706811689687213128887082197330137502064260105387500552856851972124172206996205919556553246133218441769325133725631665593372188755090094541462474970525820334385058333756591222492801647110594428922046641945259392/103648250172203340865458115839764297558925693061798169434516001775068769911768281084188883278842546791612199025413573394167639925287068809631958006622842716869211374513136766899877504136929177267362862319748507372147243911522667591375015611312165464514308451222180272935398828092646393830572838772085366567154646425598388620105539619174837489536378605144891769625',
+ '1591076564577634575701791393842535460875733974464805197283632670013516183281542903377750304419996681222758401497321278555686661981435637461350320471258386388843198706277657208526372100698700615835733712519332548607115875288787602084336341594576426630670911478276101702119972195558314357975365863803265163991961173/364433108410193393847203348728981296285742202617988970384277162225847256295865554341611171460436362780497179090329831885853324392923449579538286804729856000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+ '5307507148709435807261229345132535134848030343901300324623409721698217134932866488937772986386501538026693956121121771089740095760486362654754229904633476234090792827930433598453200536551779789049352131005222659995845427680686677324963223653949277037520711609343166926627713758990384011274777087968799793410506391884872456420041494598273703914689586637995139580794368/1212244290381524115082005575105703496583315188540177702780216570265159923654197746388568151706509243638302707511238539845531608313334248788980188054651601536067740709977678222790481348003085034888244447626347312094586389347535167903408519024105748523384932942441843118813819781347089702286481924493361058339731949844620763272778960860509570622673048786968660129650125',
+ '3046577098843580578619955603029073328361298436129791931304665777036152915858575282362742008136721284817899542475666054101957899858138590963789072530710775790433466847100422875906866496318804986202089528198912098377828580031852152997907433335176267300286466072997014023120087988748396598176622765923059385876855303940720/693911859077752895978833241338902695755528613729508555938416419864772385336777924892434701804078893822446727762077537955240605927550548046309823051841326355655848406646248595628715185413852040295631448250459076043816328082561248420925930431777502622148019371383254316636979070731427737764160145097293260938978413488819',
+ '155310926743873343426312607182060072939030765297630534544899230213054714482456829198485999782086944271490229412707980997432528653509945301574794242118433868747172279224118534460563400440670015323324602117517342201640016853927923976796453577048995243600967202462466681112804476672348206491490513419336308575442086511015101999847896197546900512764233674747415844045184/35278162986589659300679088538176070649273991865663041616088851923111175518405117391134863644540911005782372831496121355954470744169969774540892096320978686548284501139861783292226140413769665461494668479430833892857992401391262903582109993838728281915806394747833588629467613308837196269424421131934859079260185052081536487462257809987002198437182539441349474426375',
+ '46729591025621874782758519074451728476386657576036360734358719976400940301493939192083339293779149127132651616972817165172116269307276487158069293114860391502484125554406945249728802484128756924044633825692779783425628292959170597009935305429239702926898931975023598456207165067568919757902764815108751735753431153581849/10585868084079030838651390738371141142245086465033459640458366146849314274285871375459898014414833295804139979016362796357043372316321872357817727821559232353993714062535883074661734509440994664726425399880995133711038483607773782532430879196405793694658185175583691180757783597895616920432527125993118171361116684288000',
+ '3736372348124144720852190769710129461145889011598636925228657393934132828633132357232883470466940330848177425542748100211498184494252714616379450272611850068867816250209867530921278645286769418080018709947826876461419654782341309127709703626401211996255743831998918894661053669189312375159058718767082163156988766821194002596331826150321864927832618126580509732359424/844176851007504003627016945212023239308348428094023437269532743221937069345682900884618378849283002998220513307273333096775669556093005637615012750733216460458689282791632437851364674879152695438589692227159038555722539345972833161146108367329370564838671791241431125003401861887477969954740544092178721327724620450947646209914621007186497081420384347438812255859375',
+ '1258774755828991281578968023382624723772927642002016270484090409043454336040857926581316994594109169123354553321469500848146015719851609220423736153365139804086413284787598253618361769125996755159571523632747129480387254164008968993734442164892486441152227433281625391753702577143985047832519062595123255569172968685060844/283659859661671181526547833415653453506477950678651675193210969173130116121017723360258249430884213011988678011357458727603413521688184521573094783291496368005697481333739504490647194454695504722542014845706216224432373442438242355188461951883454664693262684873988061018976711201351382163527093784753398257094429403691625',
+ '84932063355292829988908961192574710493098897148701473172754949846455626381329456661808566365329266898990829247446356970454502007127269708487563279536825277374133681167235811080298134899629580318813382668399644553111080625918213250223197440426147821225593304993621451053135332451997633132772608233430131400186571793929377129211228689703376067763625568623535588709576320/19089978133324852910950469658566458037096027722326716800113107848115231563787455584278193954518442601810776347091253561956877155673550458955562102935555510392954425196165785410319126098393353878286400877305164869548380670204577544630353859009177051698096136470072137579698473017257397949994722015089768745013713383769765609613514021200888647472804720456494757423299627',
+ '802638881530832431828249604040579750916118423833791608589560402449036920165704012070349537114920882938466635598602387718300074733476150548724726460209016834416094317724261857969955414000155807312852092720310159572547644569797512233899495300028159721348599816083166712365215075728968005941610056018023633235372936903015771583/179951197386119079732438617407921535065140503043429174394605652913879982486051627760652197484142547447000508189455126493868229565647284332735552462525598465192073558793335913005459266977086104359621022691931002488052727597513413492393525660272900161375677499228252863529934576881596384036401784035248649026076581302370304000',
+ '25208742399375362881099811032135575360109715964024747212026245529087599633280142314962581193303683759605084995818253124445773115574470717199218828756449187055537877478033129862600982068782249943150019637186466260707552416433010545437321814115233841687700051830170191107127799355485920046505591193770164750886037885397478191534797655616745528343172318318678405576430544896/5637713398995569614196397857525646325234056219513202928587580534159596897880731043336790273040813044077153051260989730372846713618900145444802234629922717464041261370803598799826604841654608724727320798324006129524610666235998113655193642594744544226880944882342204407750193512869672849910003246504052298022468012594459974209940607450480609190841893267203392880360823875',
+ '732332637178584560220688900268566130246820235956768724845747830959547501950765063982943061181526237061809052444110437930100210105274824607344902764184151030827266142225894655531497849161692760385938786736436977268616029862577293984376170905024712952813234949508186306774257035535805330366742322777611063402455261391021097128/163377988152179636922409938163005948596822656868040662831003991395905185823836089887990201522673196719628247106777881741843786365701135555917263285268753605514812568258179391272113405920369587922702002732667842511108732068683407168090725712401954314293193572654347237716691784386690948494003094992876867763698101043701171875',
+ '22391374854299462107923583267570593886002658786775211597896252879708753450794332301142909715845151746786016535157797023153041007263258732991465037648536702217273897876864351559736449481285518249846264600935543582562018874574655740660800634883403597181876364712521253650744197321080655028374602772696770227741610874399454362583980465225235674816785988164157351243193265401728/4983275997188967758382167867656806681565521964135131710678447245984239733544941881418957694612909599261843693554043504855207170782744518943778096768859337980470219783210191765678908537645270392500777411134164918803949155037581108678968701961544979451081836872425574123683574475790997414488641534074873248802450108535183413970057319247033379016606809226547805269752123746375',
+ '21252344995592269775107236774689012760750850598567799560343535731251766780016530978663079386453430272698006977668063208502408607227544322945446079708000304179073184745623740466334127867464883762843346619853325415963062181018736410570113171387891518398846617197097407612852037905687899800932343156979591740310928257766081697645/4718515378484509142377558412184183991357747235892194234377932213613746008373918923611236346330189287336493794499188640380975364991548794341177060325509698404571318037298112293926833877103554546466055612498927474225619680186119313129604319096374924037870803554153458104225741255753314955115645304948564151765516183663634546688',
+ '76703085666560609319365659209445044957052359500745196718269665234646121134787807938680787341023203786904308047981099228357850016602203539979246579454229078497537148799349353250167621935384193502383187510928609818011142642954550330069991570983902841318203794113898871515702445720207144863877026526025447819537396146822201819435536435835285098485990260677737192230985069824/16989892821104122916312992616665764943723222199277412857053896319814438961475117951904867066845412639236790762432996309717924037467863024360211163971190006272168845197000304328480920483651558672879967350982199768255256753110375467976246339260326422891913564151453729285191351273342206198366624882195135056176502724912849575385576554208844814085228696838834665339811027625',
+ '40153831166521391225489894857551838468150576827129133168972859441122728710165233865309084872159583787083130713473702296122465319276461991457173085312407612757280915853584698420083436946976844240063731333638150599017886937783470942007376523947840104246927580254612991191040951001539641947153847776050138532219595746056076776809812/8873576113581065493273519627544307418139908640325227196912114520903505426722086265723900326735989771660987609604507503750212298754414472822477243293638486047313990878348820573874809636084574108909615044524663712495422565151340037564156235745628465172219286664653343193162657374410926171153516871765680561505768223660055617934625',
+ '200736374277835272485186523480177159453030082779872429648449412313247640312479214261681899286513818912991945046297334418102711629933437654377760028476767482162031641781499114859553677502002901248124750539270235098492377826240319766677856465093871367817683734079621359186477306173620331748496369122143019303740252461824523590096923561928354020960176605008889579578495073283712/44259440065125442964014453739391594153290923937737136823355312892557975399035370631521014554980129291582486968083228888083079118116286312583989433037097397252506140224404130371160608365777625538148303917306340620019882928744151294738308047800711028158277500317134537540772759486365658024194428796846364486187105343266398338060080611643110071184992193593643605709075927734375',
+ '488459049325494693259159444507437983381645757291858092983371672334043029615965882574409808932509285079401239403272414271652617474184321852388397021836909585659327974611273820676128650810907598106588433939541654215766888212287311943387232664417347883717203611092450971786083806323404432266197250919880225892099146722474124874256523/107455855466267410923480140898552598306699341366032095904938430084768624148829473848763761936703072253841751966906142283640379336131083461646777390874405323298406517250231389493084918065243079819898691146841695350589992171102939046740169198394671965069747042621265775948217054519855346617967866614303497258042439052681849864192000',
+ '8430374068596413768975326329313648683222744787097373111477788794692418932056901235174019333392845529821722488982447683794746518712070635063397464904489452840169472596494433874412726723065560358405027764015273789053064830528919091414531400589850704395887250756457780868677553068164933299802944396576360356896758745030955907258232257358056931680617626606715393885378887657377280/1850480358582748412767893656294669486390769246349349706200869446679212812264400876685764762211659843658541567071977813110496329850010288179702589501255367345935389050373021591572539260951149696801665953123597521983257852207280970577287472932167362521740230973547070264273850381896786822127290397075758780174083415923482640313277341120938690594859118603017930500014640169211239',
+ '58273615882491925540881784328370957720539797010816130007322211512070634295154626813477498607761260613353633996451797686143131893884559909668805628269959901044961666535533876340116728982072626875010391195372759135469446048394835148654407846242721443845351855234347422256700864370273092971804207514736924787577569355949601385705517152/12763172138328432984573837529764902730448666594097466544652830703737195804747682450548488162227364538052585484418491914323658215406327363805353540266048943197822838925184942600367647278764952826664684839953902567694832690683467635727409976388280092262839126779730168889686628630302519063225007497707028600865624133968363921915016625',
+ '2685278694947152969468407055109959900160804835228466214479525891103128459065131221736713585038130737099067040196326815283171857977706979990467444622550497565521863441617001097354649972660183038850033950647594754644997436624623325003537308428828750008305464174443210079059253920383411910911594636045517662796811001285984447671371943267419151220518726940455756833040934506414133888/586864816044968996825907488721678304211296267371936463272955488077666796280028127333229453342355554450708284934221461946991556216095213627059047984199091625921432110913096253352065238765240063555347292393427726758010888546427415276046238297382474708612526770055488323003698641493939145624184943388159614111350839555141202024991876221874110644879676602924730506882384603409121625',
+ '27700345710264347957758638741952394530538598225904772664391173119594616680996031886665218221392453628824570256438960349220263575741495285780845399187013582169907518462437966962923592601721119258663490655013419675469864809004562272799012227293974254329929404036071055528766397079743200179924243479370594973626764330855493789112414191/6040997839051213541001279276287478343874107660287651711609067205111574718442229224212245187655300348484144001298428958025052162253854676210451657425437588244676589965820312500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+ '80160269787574270953020489212619791839643207793889009503234879683494928396231769167082355143564723274576166638869430572977442020236299319718643894871736539109822173538266278165276591962215927816541165548699380129014840956895931285215480350321259054359835472320394089646764361802445068798451796525897304221529850133401768027469126408090577121172238802281079916258951770135485696/17444957084936455555074876721808241311651668802091828589911310253709517226715880722948267977560381436307152342821304525073651379763144281678911047276389231713860392794975453518107055568765009486973017341479560154792340888780337029481646046233967923289468141293699236815196553819373828280398488966665994092877680860643266914861837639322607761804740256920429518434353839878280875',
+ '2352816897072623416220002134476921108405735955266703519252095150412419264317091785317803024028565851487861978477208755211589867803009700996828082850796103789009194767813611798769297617674631277847910238088040257037678126316866517601388289837575778165301828137259948173292658462765645645868094197736382683775415645307647043205988394700/510973411316690313485681833991645423802776865720580280058344111363363091616558230182669376841383375321912553771027107891991318313938505988903735114191313214327683449514676737796942389784957658395806951715454372568523597420866050825822028817422805046287798054682484158011143949412956697442991898914560191411624040490122951328364833797',
+ '6403295584873165688372907494046202150046769667837790834896334486679541887567517050446119511695248926941383207478170182650927368177009669717288184903306689332746127845953193587519575304974203099873732502605739219028995266139383163062837007982999189114810534856227848568800302527760100163350814120545587074865568436789021082619398126713943637898657861949091545516403987546145915409024/1387794272010111535893205703999712437783041553900341112488641528986385462810448493444968969845352401058333929711237978223214186693177251566069419805757440174840170213159651962686467523533938145629444468366235554597245713128812532716616087753947246800626006504878203666972651384731498770435755225220796872155249202960801768048854869001310722927230237083418017482134588865244642658875',
+ '82993247683514419570466529457059660634483860665557779709153549045427987672829778520201315148149878525274005978368939092115193636113741972236218502664881450367443614971109677363668874484696543982239492409231870942414193419634675024621942196087473557914167832058111113476295926250739099284241826553737074679953551191767148712684157318697/17950946423927357725787689855263532224005643859095168852729513034456208872420513601894508438640531171097082516559962755244698695622824386001219435651555513795509616906355363573638916895074349491513539093024980575852693293474288638209680085037286354050958859425647536735341886663074581909148323105020337857959651624760873736590065664000',
+ '584891611376763781852144397260140844977346305541197362434227194779766612939978629636198589818106137319267243431810481928639442343946346034433828599323416877248326356345631611148749005937144684862502198147087702668524450709118588741606955966569427636630159793409544653944608958808602551061186799401212712216156799273254257486955348236914237644151956226336750212957092204830385441792/126256710861549838395499078249922986417488985866522660521294610280820007499099176190820291214370699289977888813109514854178180265382471972583921477022411657285850911270389720508719377071949050253246021921916685716353393018411683757067093259101151481189635423399611625009617884983318141463140199839724797412514785751549277231259345923662499623640886881048572831787168979644775390625',
+ '4743054867460856425399742072925732465660626340183690464743217147109403130730445842673866624947360862438925194786600531200056131309608642363389833474026007798643235346104937733349791667694862514383520689594596660275306247615314272223660862212527346572811422915223417783887717426641317921972456913534338708745549252254865615725859881457906376/1021840037832289788284691535543138164288462770384961802287250236519983887262771944174557087207566030730743075334169628971336358708580124427857321953981475719503574867471090607795242192417162791762511940848493176847925838738242153177311649130718886794249673254183907621449520415060660496225939963471753122766421338241291756892571824984344625',
+ '3784664074155769467702999785016514468281913375341134899878893061325465790589101335015569840325786070795267055386681356241209412947116340524588831510768864231937929260236754881829005065056310226407358204278658699999612596866156294195316867934035877283950841910726224355461522065773816109849107487214275801829843762482082803559183694631856772777313673086715631547326400170962722842240/813783281473223559981291694175087508812520505931454895884442580280342455516154674683217348039336712901850738745276445107477919518905155156380620466135946654952837573797479076027688866326359448543065305071605591497778941561986579230698384305536224430794233462949056326864499827444363206162251104552740175503996670997705423888382328014210171324376593090187604268086334796245552762333',
+ '1946671258536842642381655747294621776070051525209940130115769153666368932042152311477746728678182920842238801763565726086459485050237860693394471644023999467670017470376746609892484192072297938254898880354014176373253875722410186683852253828299669530022166361304397631667133689128358365296701757782382475692465977794960109690362462330857/417774039698408581013003883929127512062321623871486379101498968145670269174833505080260389860863417408848209525427705249526516766731427603641806256289098209429110794311660844125377702016056984573671024035213045475132134896835814746425864304907974005950155657789157496389234919107772201305672421982198984613601511388413381215220924416000',
+ '328578487723377153600821813410631465225159589727320893988991729306688663770697528156625398176929288628930496338036815910669579019719633124832162035588583242320537435554101841406566009219059042888412893352417446437227440076869427577701706713564212185163005644118869496869980925214424591264870388237079776503547314587137721633918824664946974571838634860386893404364499977966899068777016064/70382695927096628347637455030970644630942872888311870124896575877585692281624151103204551947776906510366521972834357031777287506848887607457751667816514216907338309372900327919413372044625602555866294337672022362865161523684052940126509854895320132828009551012639156341878073682501185688461720664801829933689280752623773789373199210473995823367199793407048741191786377993390965297664875',
+ '2617201476921368517857942326432090876874414269689140439151907982631768946799200744678055980827789859579196832718849393046147656447672531861353456343821196812881882023188898815179947651274131166835133965629115749368441605680383605331300030886676081418867305170012032824011912531673468215561506423833911621270680104083533917562622600478548/559567121085534865189976875600841717161617153776904752351231210970240323460800785728739412474960421770806162360125774000625035522428160837855944215417821324758873127567654244465281354654299068546570789547691342609793050301258532586131013585959164719533123826201937801986942606844854123769532447570107525081084531848318874835968017578125',
+ '636225736038986537559880265988431731529837451289737542395494683393492040808565905376235074534207026537145408562785279823547657299565440309510931336394031904920056464959974743525662459433889398003683078967642651812081450227654478095420306880762753401111260630654049389197602389949892636943971690212484981672010398108426002137114819838399222096538921225458913266993881960732663394566024064/135776510176793971074115131648637508758953050390591773574951317807919051619690313331192027871176160424663811116849856489187562728496099757910540362703888937768555824513740118941387831822900198029266206334350448626733139136083404404120210893986654422850183837974770675600952078956326317698998103770833069712616832650406225828969036781514645731022616236082175582937900731419575337473384125',
+ '266695771933124633677367149389643417608461366874310588884377151539325854547826373711099517873721616543570605935954334944030816383858485296542260152894035979141266909050267414072982042090341712035518685997484257326212454742816979806460287972757626105526907510197321350895873473656215941034605746494172316089636216915825022339855304925515685/56811706665210352283362623728191218698295056176625217939528332247537278605113496147630185544004654583441448319260578659922931798845493756189402805173037491645434052737405379674607517658118427614090338938517963215812444779184193933749520313676564187507594274551791388039139409235056119788261689087831209441779870873305232021728002651979776',
+ '3511806683161697708497547617957719390189982761002154386881580160856792742952365159764830433511949678304281539875366378131195670004345568047690216126001067194904446295336734931691743477531830892015690816210752795806120303198745685394015161323982229908105397857791180342330098240702332072396030780386362735967021055250450666535422528637737695053315137238368787607412459874094164027214953984/746741569878639983491390741637813989978804202898438708743258000150996080386381281307609038830698579358879333079215327202911977568726258527646560497079622703052765164031089590965199628534477381843079892123440214378949632707668935001371616475282883095939750704292616758568964786737752325652839226013335092148777788733453702438432279149298482004122593243860829060557386699231448957232420125',
+ '499543951252504651717279461487337168721376180441322735807713499521878076780205427598086756686061009718016175215146305489885835839673419698751530207404115002383180054704927695269921072232395828258826213554806570423602966743337801741633869267620843626804016742412046844770601322513184123515405692795346790813502805238635003099976693786012816/106032986203682550514602969462803214831559442358811656484036951559991322084935692953211120289352600484987931812632505499612220494311324679562152078883141464406938524087376964935494245010636163705698220308243980789514815579319533341929989455211613890905485981353837321342730307819060825125980500975023479813757024395422041501208805339176375',
+ '8540849722242122835873311629952985285477986765819584558233324868363310302206006773828897816547299245727087876984857726652932481899766510637403577175623031467933486823994040576431755172220921921877192006685053572215922347418846423419683723609799729359551828522978186208983138345476801247634773975311405913274552616037005854836040162212761099347518242200807692224460514057530656658616850816/1809694575992816440924165741094996511361288430727981159314194000585536832192004274726651828760263522962868944612215633976203596150606503849595633388319338251977160257979846235995654357082321611719654136058257458753105122024859078420173515189641515736029725847315662215778251370398007468665986604529263071981040014321717598469319370651877296085250322599247141397427185438573360443115234375',
+ '51377057693118720457387330519321684810411289582445982078756989158447711080414032055843805733348558621949380554029375294451303430269197115810494758213980833379214402452992657502777067850631862453239349835215260705131864911194740832694498014048717871429797341104227759557199716944554347879767916801106279081864127192611546757627038037780244683/10867246748205139797826516105458406878398263495890048637741584969144406206902037615496936724683237700974333817527350121106320991054058406504571347680049945745432432384570515723033245430148177097144850158758462968940525568041926860856763884474605057056550146491001458649244094211354153171337463406192651150028767064600270112838159624568832000',
+ '2517199821548153657910904242290029026229621935918771922146425373057248090467388430999870036608278542449661971830076845113363443421757278612394167195431759807908413353743377586893872930828548256216510734912027450642648789925142482023713336937258932978503310551945630359646777080073329099268529064437120002567758664419443374110941824106727218341795100740316967386497091058915066147604481280/531524260324016969370728057738851340792702640911631807178654723224511108065633433027773388768889035083734041289308124943130099580765995149865288328550751289346866467755881013217287175392101334538392596956248952430438673292186779006015549928829953979591250274457658042926539541422697852390294886719770034058916886894408015624288115732154492554105512936468520265305162358665491880968652687',
+ '937159485027553069020805864547349554547661693803137696125140510201297417077233061462068930061915381468160677500445535971818609631674361074587732183297775728389124966713749450996643183965795829780345678634356741756033039293590264355732129789110416943789326482663579413229893878422643110852069225671783017610387570935863228248464396615988222968188/197552642195166614912991396771845374656891854357948006221100585931656016640982345159207601253599235265353060942313995345483862152489766993442419668492877390774016299878943454249209917002169480134131612530977452579520564400124908392339263545911443803619972765231094502745980083350567509014991735227390978850048088378721210219321877150756855372125',
+ '5119346675109082499980756672052066247676641510661024682574296075659671743397880591881419516307292610449161239233139088348510165442597278965718490070997173182184417648987611422446347235659085092530927312760229499083348217392125906851921500360553758600756698605047197529446594606336369425040236883171542367713951500007600355406492128741649090501989105696576697387613035366083536629358444229248/1077349190930018641197987339365056771667455373500846354778079878558146320193384166982231171317419356136237346389570422332278447217033773023556383420372685049620361481309067017697564760747047839930422553324681218253359586320952896352245366885171583221864112040038531116947187482413031483309534553461613717398523132176948321964066833901215878133594739833910960843185464241840039390204295097875',
+ '58469980853327028628854378052548435225264353747414624620847476036979290604604988637469977624105451959796142112200285366199247075503424204044884512269071061176877225315884188402954249683107639777479663275428465242905314418930053869461904934138752031124685541853599407727446896558064252696813370692668496452783003306386669962110734257851955001481/12284458784412533668960387046583440199646081362484321078872891438551639470826582933879934711861551014258493898870867541349962531447724393381011633785884743889445407749238183080941846528000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+ '632694338036291086868292541736272151517212971647680989553388199374739841494700315711180961760384484042159533979873604395621016272772109619401112452105475906812313548587880899058905419220497456502543971131226430775692744506047039958551641270070996110661036371362526401525207493270590999387476275629412687473362310414634469788979236895300129227082421021999210841384596617452097273320509348159488/132710874087075221626157136110978536488168313127228703025408943153782802265472978945703742152954881438058373686972477195846162708986982742647038014147903817539418442869011995988024638278746664230046452545499722614320284478925059527086663455478374200352375121910000967044366831275681254530914306064535110528723482282501358749001657608122307686482720926189537227492041135055090784059994118192625',
+ '2022422468242151190347511883185841333968390991430363660248687284021524206079162212007263606819387821055183192871951076644707111802972354128011904720586729919826758651167984507804867954970064938022106484656560866447290934136610214175516487579122328055482392137619829739560923807880779865442016530712986750711199998190148885726144596663143651240/423528009127070458603837580844559347493775236970253513203186912091140251858605354218715508091394564000304400858761758838182654201653901408046974668599216804223721114331085507752600832390227068985693006017591304916519540245902645712532993889000048992026931475754508248265356181099817467608863068068953756055334213584649136541490530849577363647',
+ '150542201776735231618709653388506394887777837646651406023174643241783006776019388336318216053045300417086929061565868663031237502137150880313922488106751241006715449458736723129545400133450376339156062387859536289694727635468338235084533552501618659272047966040838509286942825414463511175531001410923014751124732748099355476332649458471135027773371541572374016038525336728276024676601778299264/31475618522527975728853391146702172347290819252268551219771201846701658848754300838383637247014705706066050673096278897933831272607774110183488615338021656923433335661890414740664480671162459271850877855521869741833030384682549648626038314686632195158507693321505500888988183270276134667071014075017317692121986144742473139078857816578133693776368882596780120407949076129685719314703426278875',
+ '162664351499283182546788129866259011542529017155637405328005084357078048074065548349602626724265307733005745244338982661090506394708170105916177679714664432754153795772811389612700051002336719842784098507498286260450939289109123759859710248621544296008356071415713931721102824434964336667980062728653562369697289761913925215639430496443820411811651/33956433298509232632327667206936379248172049619640091186698677954874297837764235300806836350019565970550533206216590639008637925266405524715041372407701947953283803854153876844320283735281209725651036513491246238622967031979233265136280687995181375582844780830517825595245506321879177965868747546127685256867456319112247572348416655367667187712000',
+ '27228583713734183629451808889147559240254570554044667607519238865891084165382075658707675405673276615039702916443152954157535195508033284355580742761569101934821088036084480297442018005972653124248838420021440899368869744141698556359295489470499243402799483082406238128875386939389123336303631993557844565927960009744780823166462314116039469622216263221278402673028225214534859076857413557466368/5675134787340359276387085910732530678163604187009567576895331134521132071092293139167824957253050637085885426100206370974479279292804959049999647539684766632278449357893560561608633329561385765035738071726214510832066885156767060324427330401967034889966727638276371320401053302413136551142103678412692111973028587922399670456670380189570979581674699102934733563330382821732200682163238525390625',
+ '1340937726818688387636018677960518622106989311577445614347061615417831160296369500704524022869612973331444242000788110133157689786976834913297544087249593601420972111789552905846168933014570399562460789075871112224533723259660967481288525220689577290386781169316008987012566216235876842757183233207943071523447855623001920854571597886491163165150556/279054368814522483241446594911968519760869180429079780837994812436395792594449929617293503975169489945136244602350941695478861584261665582123038340824700224583170270427163469762789603657025958695551448477050958557681299495041994050951692916032501884523150972841782658389836059236040091605838583126400660344344601879969354053137568588338096589095875',
+ '8910951660339249479517731530773509037034977353457185453617569046885132434552771722727442649095007828468878374139001808825741829728375370140505088133244952288239619141544661695065318568074863317054989982620944602245967155400712702259180443238473885690297281525960618905625084639870407113029712510345262279507055197024677292523214467306898573956604928730377318210588837126450942086527893437954864000/1851573255256476362977604759655284263358595254392797078730401113550522214182030016660458780180636930591149034499142464357511158868625309442843792189394292787657774627425816694628839987456477799348874853555586931850788645677998004186437300173080088839930384424797706381282430208961565555953254145002318893188197561315855286173276564036402546798318351229978103276843822345004757661005876638005274037',
+ '84997663352987536417038496128111159210386455419431137931308301989414462311135708114322046234708053448098538629166672456703651524480738219822858469100454933583774404032968304328859365484249897913676382909050450855222567147661842184538302991526635974158862033287154321908483007506326807469239745851379559259262425675035790574846229470041596801343087041/17634687575122715507915388096857069366146981156897230620371714518707518105157483283253374797127075689236298114335734874553183291806268149747826050838089931351158483980735336772879045027213982661634865556129548356487722537193914986994283729746578044621115589195066212043187229606921062007047940293972381121200723292297716621231320254309060321476608000',
+ '1648849257486312935416274009474520589799750757877252390543329851029437452988499485179026845292068377957697884739052491455807688429630524210823560524122596610276957537756142278585091315141096146341773982302888007751632401090642472506538817998043326415693840862261701364114923327286462650797963969289743571514890723145251583881752876340628073395708716735192690808352188867334842743895738955762067968/341580411902962140529547622267381834425264893226447658723654120920549166416114849731588552578626041597847002946105459812230979900480135423787717825697955152193526724029890829615845314765605770060975538542976523177976182401820926854406945392167140347444433885786340415437006775075196031433365827874254477351374236932624514469241634422312990555534789607377023339236417434574755566856489013828206625',
+ '4899626659231633406569362199187192352933567344960498139854460545644210119722856870634652562532083546344965166340063297647520613455037240144734014575196764021102614748218834054157165781057405100578668000290493431869797883155764963238760612333048336953434663867580862692267574574465371615023999531030446250351305046572267686566999209229202587258144318592/1013522377571209303732207048597964555261512342860326827024786500686692475697570284120359840724596444209277948648863014479460188496587721578075151793081823795161404110811480269106846359964436993190293615381593756076515012514100289406862487971220020752628653845345780766244842386136740768474703009091552043945843632199999007070800871588289737701416015625',
+ '362903847358088423032098939589019598036593525722040887883164115981767777873860799955715587436566233010543937498922435963385091400670303956612881974450548904906949032005878241638546734597308786514086678561900713741746905742866635779432216761799371793260025549698865319760786921444505323758235164807966277024031605673481480590193724980659448692487108892154809490199414370023696404259691299614147712/74959603173756091922435708504751461448318819548943586676159545028063979695880930926805574951275602951047845904770828900645717477098988124553474616764040938121171776556999069351772286262529729056288451037717198273521810585868307402693261479298923209587143468206907278220909447483738730657438961435587752873879244136801168836767273934997417156543866722171766794171623224855358640156858606843576125',
+ '8521348154958613550574095745596657249924328336262127244827955886474742319277424381360677655239017583606851483318951440691757875270001283229585075137091617073509041644097526047127618954293881264113694362598157456878784814696577083997902588926421675843291157049153701100218784481406086810264243143337016042301806376864424632290226377881206765051896745/1757584673254145109614914862134217345456065873908262753169404848658913774363650863904627183104777337461445063704655164325930891269843669860409763569362863600932267319335889540453302785099532688264425988677082044905948179153286276609910158854349711054144968131934713419772852663860893241364433872853338630216232313607969790083448433666334742108176384',
+ '1802461420562646993856730082999823508145602238125054717836501201545920604020389361370931345491160549787411668288359013059160331370751496329806488246135100776263777863399096485894306306621852596694700845918608199329091852956315870664531614358379176680326508877329862713333336188556181470928613423972314983964578645688876556351433429494008513812136152576866650152623510296911708111085518974142728903424/371238439252064016214448115231139360835481920731625321084601566992497057371416121407293286027832357816257507287295719261141426130159269433199862002841433235955835172774430690352481806933860390015899973299268361513643524088242973968537595085600335021120523852420135081643310663869327371821064862449426404044951571585406092523478861879148545678639697679215289523234342337347529225543852787599016265125',
+ '88795280670112240977945082069219541902481768504536167808816453021962616596410396813316064685579412429940019071114917828928080181638058444302439626425155946562362550070187433083541414569447612195370911498321149367969974762244140788494955146280201020345849385865084095189982291190135293114489407801749533448443222584092575861096946605418438181411489276/18262596223069549313969288693970246663726147886364354584825624311486557881239003216971630599389642752648815377795018130449384513824927138566203714891090017787258920529990523519479383032564209629983810866231367438270859421572641494420831435563580050954867101292568399107965982704216434467243223964964535464624988344342084084237484262612100980519602625',
+ '752911409358158070688133336918078236438086521781731735123294741731669530734909563155732092406099003582749182967246236657021875488130304108082404134004381196925042617909108654109138702993903561036987511410396567636331465803949049946947888490287210013788802287247422993384874670368649616782837096233384279817294778916419067404863458318451722971117669642134894906235066332205740872938406767767019265664/154635332883086377656813458754577288834216641577439356891585125701020579768315520324293435008561626926046988056302745604248220408154454267643222328696606367409715369481567213640103076112146615938180089303294709009813772509602954514391675152597458428326644988225555987883250642389550531163153338541870029415077982539614784206119074702022117790679351778993541426110436276530890609137713909149169921875',
+ '15286089077439918584953144558775765002061832952090847117286473868694909713791678395221438112006475047633028543501632631679316850512797978594476229228325228403849089079675681042224057234415951253954044235099139983386056610384489202589484570852048157272551098909455253037561994944665563291526597323199530997923272894119350512727802414680772513760081360291/3135161418037836259442831302977219750614726139657718770206960776514822107155928800694375522572523712277389679035632531727155957120566617736817225541673148354829212969778664144907503411589126111888917929495106029890532891919001138770210977012708107496187227751496980000137223047079127096028939624830654227758737339725345681745847598157538483308068864000',
+ '17723482381737693269787076798246423310802126092500438681864673375704464394105734049099094818738115345706100191580982712146882034618103161268453808819471603805345990162762722549964406042295355916458624836894291267553138041035528315839108252422478610879301656207772491548742981990469454529537049123948604931661837539943536878260665617524824604291547373872716194154198824107266275716083848262625068958720/3630091565725887087605600771358216927099748013831912080380983545872077197366172619225751217619097657405660172801849926220948413919823038425519006413005998509677472226729295278652284861834178791332630676772284971762683579047508016687969040658592436230062049051143001484384767836353233324570686249824980508520186643477313941054900197268584495128610811273834192099099592687485176400539748616360799518357',
+ '39160514032490258389003214587901781721548011632821053230881239001436341012396166857515197251499460842954863342641015306304854226194551189224721257664420778416733889030680295206179109679627522113592373938602737416822981698362363553492610842201005420727750505681358595340127099855823746410663750133019743170700888375554918213182862789028531284364877435896/8009919337434786244380818390213546866794958389603666737562053423775386173149395943310276429895991903753441584557917565343673605019989336264748588735390182130334278887642569515202100870042209261561001627386525182096090194076839370589780431326097965428311786635366819094633651297179987340498215947144870164066409654464551017761658775207392803825996394875',
+ '18188388167811476762477659006849121912679763597132233588170406666718758511478154418948855160838212151370453213943784067796172102916618102024199200086478021533171984254799008541948973652219533819511807681148179806051835318645591102296256347927018942348574774821478499092054497390790734798143983945096982240517058861467440217558586822663985584775920517755909444876443211404222400335500608733284461685888/3715278241795087610941547133490827711133909145414878217965273061493740719983191775105550559905283733134189195190321643965858771461560130864714477448011225816210219450040558581571206711791272284197614810026220745160693898421068168630870850392739817340370146186703867097982858560347971281142766235850971837330222970900661653800041795020382847588362774677568059355238174402415384788280852059532958121625',
+ '46353430636874284402376008361176880938798775506236714098123916668545331718677407145199311191108199195405139575147933283523305343027297808443653460575141799729554209181454676876263582758919969948546918471128299522715691694855904535448703118221685887195383025133924852224568922999317583667985459047407473419196367812949579858081181660610155317911318937349/9455890179897829052705408931064120820559254220394152447502395298827357144081378513080398823188243663943177539817982481713456798757563952162598464929784370305696420245648035222083207596797327498500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+ '13607414211126412689435920170832779523235279308039712294681570127241403406455725064605210473923965685858012114332021572088551127762628892611459785625407107300355287623761046438724706918300976102581928643740596336865698485838431972050267114940079353996100620311097561405192365496385584164725035842307146351211183443267583872067352881387294766804298681841402861626441205037637282007810811602289501477350144/2772205947240095684743358245950494400934136973845543049938212403703005150954425087866758842625041409060217562878157900197951382737615822655863548356735553650755342108183641353142157728364469706919993519071329069805875834057743793434164720370026073143626055293058601124362130539040404491770346043977106988100969901297669523170508321703496903672333781306743488573086901583022550356345156457496396749797875',
+ '23264350093315690507131500726914401771633259602904894387312742275462601574788485574913195967854009886824436237740789910851843045306673524324611130536719802717912615609109285261163220007241492964833933975789257681755002054890453747803418465119567389315754801791950553304034666399677630030683759466627797123971742061866928213780452325847848901682792886840980/4733430508316851445525799359992627142007028326865116523033957869609125212251136452396410236930025876982265915963089597825393317909648054808274488356887197933393799241941120691826916741647173843634076299630884655997401370400734954098249519111198509015387869462198674657171808841005212058822254513429836457539629035675822719720295352508916898476137111981397',
+ '538960934807826161927564251064896730663918579817797131801326848839524373936888402762331254883505157846659830555799909822861853145306547699817986010353774500545305596630924815657016569444824389893996399550343022444937690798126189162954188680257730893424775303801562327757655350002001080811866451425665301674688928515041429654624970707803336800125648742168380676815769457467038849213225206824671025803392/109517448833211208909213522579666376007937648875042608355810687327424621469323788799169718267715808580775296460944943658428295481347915131091297305260674731651980547668762317138753250458639043477453950774629862540017034566407286963562574188172753764978963685535201028286359818524519980885827473904574840285411260037264057197155527809916623487144493019982419606308144576939532055057697567269576800542875',
+ '24451481554350251656598350124163983489544445991992661049829014268366250968046589127810466483349378560701491445658725080497078590986743512397927747885285095846398852574219367580068049729894667374214861939897557930527646930793431512683750733092448862551580494245486308114129295467976874843852138144539250235632985676953662288536669286915983261820949386845806781/4962240683851186225340479316959605207278948397833849899236383273145853045488131674290332609876751222112917095096150531825255611911377071887145019702433883343849119017643117746273523770961304609989779231048095665388209125939879595509516490230961558353074751312876252070889983946768175488341370453053495319823381943793679003096504200440997908071027962806272000',
+ '166591964685609920525429719961758335887941461052357297159523350448241484106515158310298216150030768812873781356326923214637847449893575074813991141092681107583183275003570804134461187213143832123628701876041315736048451374824101256980832991214945762266748755723551899001458245164317581617731332466039757786839492869211248064662857987111284518155462148044812626055842061283907063784492325693994911811072/33765943466859756347231737285768516240749714264564410814839966011490233598006227246396923594069488496368994857718734549787000213944990695920130591750377483911116845697231074543526982580529937081378303555262933800375576494067959129682770065443667091668104132884375840812635624862495548888467575848428463895756394050252457592540570490061561616914443009224917652440212112452400106121785938739776611328125',
+ '142464729519206642943985715465196065680101634264787375740459792619754842396453535646509170066091478069799080172299369994438836484938378991283020978342879057271045033903518255546705211647900945143546293360496661466657600494787228476009216508124736254967761901224547269496682078470378676865981855568197705507111970901389105723589666709580851611656838285777566736/28839643075889294346635430272525349651555039807615358795334663734940126053968249608601725991946342449660829847322517564370606746964714098508298705743689729306507554363954348780144675937510900983622836770223945538844314468550644543502501484521561567788992156632892028591678742014134130878101553633195551605796158103033794695984958987367799607407626319024034625',
+ '380457154857422076784416625436182734840012277442496600178664323821064977485271288502806124740204295811759917468598315932331219906040253463960961995849761235911145436552991677050565022181516596464968316292789048809559129516930574340710998611088996508613850407294743457197519091160170604426002924022923388338999324182261954685794522778833029950214443271526469607532589775916381786492089432407550224137344640/76922142291839859196033215395526448408989799722090330866256930898223012427332209054699494033511478050536463051459450844520020426942553107946282832072826069342120835351421637970462054707566481086202287686453371483126873188020538661871147778312986131920021929455276187425940666580158173676284545586944833678819622895550345083175685846944633982680908505900099388037050135210936852210263717185637166725605577',
+ '79999800708369666670693340813609265988600461251844239982904928629083975986385235816587497657678297663151298709076884516168798694292862559083730259839524968813932151813233366175091317046758260934357096757322369138747731224414176608247605314668045838769433811781490030635374856042786947109101585097035794969502111354126773165391577217071956928171336060247633607/16154850265413610119312596299635391433380319311682208755390341659152160578366244774029302281068833963848811596545791104919556346810193385001196395489740571188884873246131813864782581356887110499639544662322008824704830818136660544673293865921754569356583698951612797219776290835161780326945625621758204931298948625885081325268123344406261740843197232316416000',
+ '26517014004241498798848003208133111307300592620947908933072661812816683085261359696887064436611747292468717739609776825664344064024535028242737616786238200200498291226984541942836459188328346609549239724653950302145388877472274074665166631509490297630344351573245265440164082334092997307729007429218385933622623935704482371360985836659823269511115937443161532987660855490906546130131716829909388620539153152/5348257529530073524280989257577050878033239486885664777340947102417077875628238501791711336803418503616042922880218860266611595594400020206192599559925169589023339295256084579966533260861249529670141854632965231468533448709363477120437277477668173149958267956654411822360053335695956270719680086524322530514249891157439878407417971839744392044064720476417844056346174795601710447583861127956455406902139625',
+ '71197348290771978405602732839537012310626009318133570890482364371056186523989330157966045289127980687764218363064941391483781008265266963537623770685390148810455244349859354857421474277963009857011653865476081389968122418691934354122230780300833008753686234433642072919923583176333668289998661499146617277981691372057042930131717336250309680764289256080895092/14342680993892886303954853579883363225921109635079605392320944523109187641283117181461662846411912306480691901515655220490850586434947504504317080144378185989596578890875561336496479576651516902048606328604696610314441229321219627021610788398730173161473456954861916116391687748151443335513249810808309201830214153565966339609616397865465842187404632568359375',
+ '342401018754023891059352629509715357053454506037284788725127073168445189422181540408896452651232759821766616850264122591225331120588918865882061344322996688880669665450231982002597398732201789017495427566260651180470635467002453249479483900268320570004293679361696197905823690145326531180324429318916092471863347575533714871355651558610515931373080997536792242278529575700808878786732127630024064418468916608/68894647682461956089382461406499759613691148650738957016015844830333168307020284281532026442272618372290646283794122772122665531108484704223311663270836645399554940741928358317884620420886609705267248656765357348477857852684140561714900614303731786621656041881975328169543884661780531057279582784036689401206706069028229798913082362331334839997133771880514515972024899408556517159759907883793675672065360125',
+ '190286571838805495686895463752492174798162827967490343625540320679760700551286395956241059969689020885714190883323266375067607282890063444652926151862650426323339187596278760962390404656342309737428214493422028545912504503974763216230927196187743439226243880933947417760147785405444976177822143722569696182520826218120595298059020305548681823408268556482981325/38242608291912408815838081993045123864617058390583394828737640036167893859507494091864963383596867701436244007905770284939991912884465101575490548813894148831966132970730263562113961685364216989999844961688441914455435934703797359360780356482629495214859917249465567846934284121032247250332634283689409883499295469118485912489058982596725212975311140618764288',
+ '15345379701153714436938289567639102307887273898834051456693981514956329731572600026866162286097662958712922836383881840211072389867463987895894067288699955858337488812343674818565432780224654423872618380928462939482247900055572079914753652915184313685270722583926261499687728814560680092231526501970431280783177357735398580700300402036873780385926225005561766822479878280891130232036389707452005183370152749056/3080426623087819678602049171756961824196425548529619576972793653585089380165862889092832375934535464160425580449193072125955685099151500755278763306599307218302635233595329082702148677300945556130106197481236122437978375963261172524503625593066102601042060541617791008722569799735202779005407381267021641545644264334109052258081225159748043792288699674153722046659880085850980843460715080965607434114275256125',
+ '36443601662144279337033484452272105547257065463167427418525882851507989449375452076227652634440476502702584889057376829320548464677623038648843251719168810701744338735202853412651160681259533015293997501525430222870696235141468274274064390273214484922080219643095978640300874086585386991338440415203863901714217818222666715642467237183267303214471854472286478344/7307237990148312451155090506264642657872224684879860008436735430879314349554023096366846856470375025555139219961666754780864835035902856314981482268319886395881809659368275720375576152008289949085453326722156553603358879420820012964006978808113307340321824660484441618487292138631579616351777191144073820686595597986548374588839763719082323114663844043046087375',
+ '1575294930860663922843149113097927168024893739962464327016763741314068438447271326302129681931564068796631337571133444163024224071270063174829664848259482066723104251718808936762938249028582992557822551950212098797718627418782376027133576209718376784834456568694813577764300716139817031545408608110880973380162736536313173329973171844528083756524804574267826691156368696436273087171467254161930265910972517504/315498546654512047637461608750018349421492986465373840347519942494904200733840189483934245529561231604050629653992944140351106887747105945984325466452136415067807666316202346895657914393512001212656936450425268507235863153258769360296536050028695266363553141327896064944506216017933135251875087069031098099192325817395958019524956320840925176589616439477134480262100237057953933117460110224783420562744140625',
+ '7692057599553133417225997786980128299372439542315125030077404519679450372805989755158000864378924877036225189784045171045139667691524763609840822628706422918926410377185494048355128046783294954373197082851501927410350313585640237550107070936498164597231431242858771457898152684298047446530220873884022213114819556822050178579707210264243087257050468000092842761/1538814487241112760739561704846381585063002784795057066438330325566843108114787083741788078680861495340026117682606689156065008672344707615420501964123121620401073675430690541617930520947170306763541251152854304975131871537939618684186766168376514664494488691311397307418298556185724993104760692216619178358268934740746932420562664234738576394742559349407744000',
+ '15190836470550557926140012263310230378455340797095100474720776113765300081463960235506950799869073894127884189857505951314428810423734978025409205413789025720642609035117121385453856877706229778165533034683130064865256448444765902482146676668197546098809055341050873494446231793312407516463576380102073171090898973046611572020564659042146465345334273595086941562425006763960197801018796906114925005810069684480/3035556598829526968124942916297025416522606357010637652635799096498208643970273828496655227572100159414352020909846298817521082538898702393916708804775798552206401718323929157863173504795127180177622667376867235806208021329981136637175570409791442328468473111768140746895005561471574356889047981458002257872092408771959478275534098789982899056118498417649167444480913552548249005268380838457505944462494874581',
+ '2923754549090941424546974281011770689534961442264350984393235983189168024079617848190236639328676436485460311093391982825027512234467853098757486086492333410753919927528867817406389678461354257841007722472106464785557179844602277350411593959136520732862935740433163383885526249897183805981219416420390137516812715245569037561966929572448247077578419534651990827044/583599691615378545100950777601104007812792435102945248411439102112424251478512952087517463400054567832353967731887062349496616445419782499817548928078665212077449420216892583997505908858360011413290038645573504472422610780073975450391707084016931661068694024023083417126286468909117254531307085545768412513447197205482285022417611018126886491416133665625891075125',
+ '441922815213568908489589193556560586318864326425397702047965372289295962228254658331201274291697626694859414786292266008476137667843874780506886929181835469302142365772763129838217316953835831059616268288399119642314758261677639945004547833810080700127847214666763863338091523753898362699222880772298183760217731228628442782431573977490761665091005756534420789258276075174436091798475838370152192077494553263232/88113527373573049332749294663402406454524640221866407165839162576979477138694653404281889227416977618913249275712917582743426231744980461918772793673202077716234026090308791893409200158969108910565467273725064769890443408324386897017825489615600787634985397885230455168591240328585041836771921929567914891379235573982442311269392541694564675053636078990836491104116280492254795616055818375774141667319774630875',
+ '91197133767962483852278456285591810579974783077874307589555559715577599604626819245000672929518645004771085106770784762994476973842713035791487204122653245797126906189270204029735872508462106527240320932458036231707311992868347221176460407003487373754014365881209649632547685657666691589606571865740595829983849941049450471212387808617247938941417000965198123059467/18163679611214677813643455199979098261849405189805364986036911945012016248297645596270602508015864371788974684317239728239847888960155365342460843559658233069613218442910226128113196984801126248862273503232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+ '733396724434554481470656964167233690851279923085197144879644750437402627936723531635898542414136943003236478432848965516925891956615111678444586384493883113385335463639674699443808084827715994137410743992130712903997815471369201406683862985677774155704267039224546352414417632475628136460919900347977754644214725932476856018335195927604771408949980634142255497812566866795799230501026066145434755838336990365184/145912495644159810313026420025053989609942265686388630266226856650796088768164358961644113076809511566125145395699271530802891523741636707532676065833327082092521989958971154798465784847092870457944857590623097781956884297103961713362268264159015928554707158239398721416218953003215109202771651993622244788505649098445109438104466256408060944298437033058380804126574878377874459371251056800895394904459469832875',
+ '167606423853658713615749101123336326908381619586838606922565378505211386185130407423587264776519589295420758009764678759213587906216543160801059029133457062067529468464636376639307690657945597847080706144898920305379653754920571797771178341550808041652360635584401789901525709166172283390177673009483341826911179861584213314395893323035527116283758445038413646757280/33310364848744488727470761889861069993071965180756187311178124887286133554324042422503263994717993129539448310743598138364172889305532401505890384806078790034557049074169245078416704866867380089103064821732673967120808611636618526737138435792818828990403374766228037003927569643972203993263329403469747342204808360947840103843317533210523853631711601450617095736473',
+ '22466735478643771803624909352181817912387732117378538284107485621001246177152644141853604633676939694208131243081227668237716194316732125445041260568403752325416203858329469477774720998849334961751541191761521224644520742691981372782009394826739249604257098248751626957528543558542443122470657230835176462612530464044652140581367666271771882198747182114685116336512604038833327707163107207664705120728068987996560512/4460342260599110071819142535903990817893424192972341872394014748315090430999224107274777695306032783105944721337053156074851240201179902835353097683269298594190456286888326067932777720561181760369783486872666974623910515795208353605697065447942804414120030678603538761538385793829787734830129764152263691296719726909633580318897787729004972323599773548689272008712427926734258913271101899780538635213826239542884125',
+ '1972862002066252798837977155272535342727415329632252213508267781390870646909686724955861608046325765650407340102472833428905279473185818898693854344138403744757857928883601637909604082588338869612221777263582365745419719805587946558326048087138197694016224613118304623864709183681172362693106460535117459192992832969130895829916210933710820035479169562970982784580861/391262854245936198649509275719624152305206807261382621390946060636032376057385723427601113233050260359684153963520782285097664628279596618521963444574543409076428660874564026651742234728997339844161514273669735686516761239076529088145208958938384732800586298943281728435207452253170935715239392534815683367114290676180839217919539381651841151674859715992309727232000',
+ '25408691426286653276004556426513401767718328640048945917745783225459709487487356095164164652218100925888847689099153320777389381851598236046402124684511968385325107497047025485302333503075085562533472482645023963717347867696861885363417595298751490212495932766872748125111115039311507374363000062513977436921772096443948159289707996749061007112458126301618769295162649600673247799961118662602756889448152517364992/5033870770817104909025512331238419776407331281944565644457345507262881100618908405924951559853352841154730802006750688581603270849880756026262217651685813584900300357685874044482329601894134443542753690048962260914642678445058891387041919581376195543193199307524572671344323361282633828144708683536721118910549933336800461248338059209853766305058943888965279863852107507075916981165164543199352920055389404296875',
+ '616565940481577765011174617619124841631603944267256178093555235591208387588894068005162766513720546626399308168923904625210748051788652328072241494243040185611373035059619136712559505023703153194815611268503291295059648269772663939137446253437248541496631852986499688052678961495116210133458596866684413931188348798244969797973846568160999564540652111599082181871436/122025682149627484528651507368051367620578469392233875493556828623437869650284356703295836998178256875766319508797903923638967073687991229708051285605187358616531926112736402511567721889845693991963610192161413692928378708223712791930113386840109729368772998347036090902364801014045328348830748307179590755723160142468563977011700764517880467116766150322736003147125',
+ '979882056834583241477188686550561625289337025747787132773853690143359657519481397575561812727394470125172272246774578404543257210124412718725081536309818383310896102958167337333430555168671327992395916735022711297435263157670501117734412587691271084981140167017421900852550822579410636184333142944411060788723653596377015359036101170054828623669169047118551010102258802465554033173968468183492058271365801755986560/193732066620504775981759812703028757608600125742477178751189607602983198559778039141192492589734277739017909889924488699372940787977794555519474807920198944109549023547259912324279741006975129518191081373554817584214941629021088206977532599055014150433581544599721903881284581257812850697423350805715586436114842183295730680490110793225514602133304729484547181740290534056445482186430729398701311563285494964645419',
+ '1138059441890428903336718712159907364975959482163004276901676063186939296217569849990951930955270849692775077813359573394435924934540269310629731801376134549169322946310756738187834128956671195358413024062230774111935109203207768898525882417078119314003162592087920755572050993355843681931139257303484526223461840939880759908449181135029327442039666513325916729112853/224777393704985303260433439401503507705148576727364388720387174977154204629336985298082893443205223017966145098064861566754244624732671470935725326043491911142467178027013112527668183783450932992534517827863293825061296257420183669383719043873666284378647952385401303733274771042385666436214517205678674213118374889121542369996042313879258622144464565403138392064000',
+ '614262617634904509410555695398092190166323289787703954329113412647342230855826568873946233136303566178604275296873234926652080462259552775254755109931312516616838091852915397846984381183732773411263828600108443070778861986849272794442105107285234729077818614332155890946804257724112143631642663084660012362691100872465148864413343598067567142977134846266425075195546265101720326272502009234689388355365794889444257557504/121200854655310345878069155190734381476914416893031941761756379148622591310152793328910334357724762079139509813381997287954319983165971628429295803162563856219211627796200599196263868781937234015821367575512548522790158101892507996780675863234181700253545912643548466256938936835469646114659598727592390628209362096738343417775009361678599075671154405137663077798419897932404104579113056038271753988669813988076515476125',
+ '106893445531995430131837569895703697604978445859412739331042151546083234373443601923983714252801103551874986033841637079479794895452077803182698291822629713500021995241940133037200268381793282318691568234728329378829301282121562652452936297422136961147432559766896143081059518238234777055931999714973999553456547746825617168005650221542417216291412324174772893635832/21070287546583154895102927029203125577385528350171457634203261197137238923923245382493868134869009844094435068284335909266152042903157700510534904847257436666980671067329084409346149729385625108233234542133838924281113454526471855573271066865534480974484196644173270141056470857666632902827625475719738076524047736123182255306429677688129231682978570461273193359375',
+ '1112742798776748551928744983308584493817112400536084744850969025063360679785378789764875076529575583792571198510306186417773101346425704969671256564494135750783642910394213727189639661184379341961737135764570942961692159177189753139209638929302928699705042832922774012622017270172331722295881412111812933593370743210214549038212395941675257558442878012136484052827439007483777859391215222647499741572327725689840850048/219121905514652575155579185322367175400669386131891843382447432851122462422992147756333729893028362013318264155489537138133372703295804002840920402482110260590055749843170064921843460498824128274420925357547023876488939786863662801268798987685503391058250902634999352652063668663245829496836853509616873246554292796823779397317819134891873583857621623143253788854530146303641400986028503534408870194989478421253057625',
+ '983995170259637497940121707857673268620603982844439610076334749792119502557380361624096960070148627663305171311646851566811683351232268240169848810658089698606409195744471343416197434926513684882439596141368794175162315633192028977211909698595778839497844847281262559899914504080092844816319298286511436690429175757804604673752838786852277255064434365401871903307643705/193579499709739777577893639665643417521924510242119561765932078835121983190401762561436208758210519231103767232899529457440602166918701550386152070813526760823513884391055054076485295945972185402977095815609448651316824455250005505410192101148149284756928250258632031981439345164037318129987999546683113483700660879431801728391911161900686707231543216283304182063038464',
+ '30691013076836380319652884848585431917439358375118437035944950498156836885409110087616602043242747413728866747975631727566613593257623099549583001362246649928075859944695060947434885707102684568632559339383409378997568182445019183928058902445738702610436187907199482604867148445903921474846103586253320950728989228898324829144701782792767763794895137313050381112872972806712570559942583906619373159053654481250809168128/6031936190594376585238407905469035598668211679521553144958053099463931217856780171872253258738635531385711702176347575597387389685526098889278278029314982721284691198967204664771178761416891317672705719303088746953644978471289001577698554025410820041497108311270431130911021653860874238212040640737171103823631718483572464361276703378587106311153455557896527384426327053165214814954154328483257657424873929502112904625',
+ '33633002641218571027682690377239300567277297301791434890431645909447040830457253186262970747405514897340315490970846553666201190386457221846694126637998462873786334837901001833771510353365825087187582807843940479192544772559118293851131234910638705258664901225494129023244916125545283621607920983287799648992632814461828998517949449887522866259724675318233522060729654796/6603800575190864815125307817934327053530311915572323544225173935830559756077637251538096164499112411672913910767032482188570876526606118068929913515497400397562722669603861813453418621246538114872330536044519242663538662076583511078523573320035084744399535581216815034837553165853576616422592790103728141443697362408727056813977234078214386771115586436985105038873998875',
+ '9090550743395453237608373761422705922989356346696677314277463900103283296889488490217663400349361962695279888433677856471792856853109469433355422802663947305503479561493033851081993247556828544308258821557994512133708656174864544457143793106703578476914924328960109390350040954158797845288786321215010324565010420734044059968931558099077932776525974187717655105951506223649947806568984848883258144616897526257038550656/1783219994432430133950472014582130883873515063182143523712063859964133099770763874744121895885645346884371273930727450069430704145915261127061538288702855011220545530326588799412076879985026006593893648911637986337776174555198865379578424838210466703683642106934154603710587408512357351455891557594977191641529451749437270208258072920514602243985184744955476557012910446023911358037761232253615162335336208343505859375',
+ '396226087129511787616470716244996965529391571019620342657409150252952330537366537352182763477820160355931003174946109480036915624974500187179367618457008802667089004678925595775249725629495848146669773574395699032785024522882892752987825063933573399691764866816284097232869198405981830106568826923407996367321353003569052419999880089180418759265274331299787468134974811/77651132902382550540201641628906752945078964002822585000619340261128555048628863996784740469908853602590877726845475670110127527874088625214912598995012971630852593597027649154487264595100300180591016581254965295280089902497540043916128232871685159131211213497239568483141031137909208031392464815187505714066235868748950184065923236670418822911614780061752905170944000',
+ '1288523130900767412477856952035690867153496584747510253984686336382980221603917538996138294311274357713893956725672062832264871415025750136690143411884111580335458539824573239771267043414225271729141508235839982033182605445173050590151912297720800168687369852280323356691345317649445843078291369394324862039143247087442309135074453265658730028145500871559458519282433012494054724678302957276371418936282902434203225600/252284450515080370603949213084529993395050575951188044032908270470514999880554862449223347761507020354321422785760279154913310488296485989979706208254206974075438570521939826124860483842758137231373182496438568202110337751291106165654945352183167578675508602832478752400011567482434301614269579368882858973850914513870297071866544875650189106576672289734326492492059816669288907018095832842249738081022227281715919357',
+ '559506782175368748187145067236900803925823792088293851453005882621173124590368101213652371965740365119375069053747415415684193092607523211636912074219215053809881814859129515591479780669511533619258835199715146655774919418434080807837587396115955105126806762961292429904380132116986984435528307613054828929294602185927322238461491059740342387266084431988727321711184308176/109446188216864405154936682374609448082436347600301765944233278303643744736544109262794383069197271409398615846380701459882784547857821831816863216080069944818453908760476475874319791512782445408136169878207358663250499628002971644430164185578966399908622640732411885568638054229180673755690889471388585521113862492432946110902070139679164884208988489955874131206343597375',
+ '365842109776085256207829452385781129402656684569922320812625438296958174120084800064669048017227033185969596458778213306152397766619692644694641677223213185977626940193107608138827771259334379895020377700759789774994800008933137569543592829220611543060189849601828342989023692876182148818363689293009174358168826340010491728715549904988196347316703985894355217235339548078187164176255405596310602921647203428200269666176/71497266937783826306774367225149833789788300609530680399994804704196212252148324552193149332755057982662844988588678159610516634667597707204818296457616315484973288944637139939912166034691155272078609876888919170894915224022982785138928008133003473320018151370486895637936102520353951511525996197267572355782608352075229547764732602557133494648237258900273354364050662921889937355682486287799239363976556848320046933875',
+ '59378684894605773397070783936113244261297966863668546282887298999811950599772732377979382620766180662159460666220894896896564357533604962977968843474062092083279165837424210591042655271577974272394203253875965589611493384384257117088518521598379301169916060951350650621757237206805037261878212424267780312614942173870651684587166691539909068452932891649554575072205080763/11593916877920131260484148322810317629326850763057953025674154928920977965095141485536566217873796019006709247938019404840440511433467040918770165456925609124166970862430104329357419669175323763143377917913760028500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+ '440706629842402075137926797732405268015915199334341565218443748798479563505125605478061664327658137598204192554352688830679475840386882350034150077506804799859586772904929402683276898098601814091430598259347980573084686932949502809068565420937301632494387691284902026088950093888426872222813857928092871267130505744095697172494119689779474840037266295586359244899477781345249657455186847452249040895151288114085352974933708544/85971876318334010542313048056773419567285701852816309711738644074302467763075671531248793080425349570584102078764464196653843973936119037322000695949209283327606280423406302517302874806092835694196353778161663338667091195199032831587897410329509487165187753418615356836636352319310103639079123698118997500068644500893282438137614420288839230827096109835206605385673197123994357393257304049093683934224826006704198171592304125',
+ '13220371178312284248107863849287558696210741535280184003301555188086761767176560823174196915070378763091889333203635784037294769747641959860810039816533609140662043493962707445564057197150603226797446933532133956054671103942520142568674339411250001585991743608723150284270101326419464088290151483823061613403673956918458456165552482913079283537536214967294211244361353060/2576683013797055224377707713704899315377379548630511095136568220145808780162671473907260623503552385384482723173913562423316997528913651044277418227616447815609873991028073011738113927961824632868956802271260292075825561646156680740323686126811928372672670917332613490241615850953660246271541592056143611581223295045062157404996958100136358649109693659623847024011265147',
+ '1538372578072246705266078247950089219881197139399081643650271348770110178217959856819170325400476655944115553238940541695152110949081675985299189913515975242840473616169775880272489805123124548256666658584129873597192213080038937409046114908719298170078476325423466734429870048697383304265342125425087105698152643145531760853127801123272389134514333752173173161562273487752064850461823726948588221913540799816489566883798912/299566102446745552813544808169323270315327122249327394279467999308669285416179486075255023523726449428151807672528368606071047812907935426406886253284580296349349558797128593446316786243666290649442956134448480598303527795996222703837663605600624271807461605295956674925277658331760000117761013381199880905748188151556184620779667670278558839116983636972680749836497346863752662521197700717702790359162307617024566689271625',
+ '281696937222363685333688097915539146272117417024070728031063821781398985795813333650547982965341298894634762289669256129805545549280355643122501181852136332529515756074502902204877664395456278205770206019663082511098956468724829475220996427130295010430751942126263620206886404244432409164651524408198286831329682605959881993845189585640874220032515465014097345759490750307207/54806274927935495596891312029173862473687610099325998583092091915101265393157631244775387832621032230434007486602816972765210205596482942091518459849775458089575743699797096697542306049628939919466378189656065461708686421130956996965004776108499273871055615413566043083634090083096967567965373203683231171594922347812966905149873265654962896456451681332216735396249206784000',
+ '228506238533410805238253122961471637501498406104430046727793420497895852412513560203475440156216063574654372726194215013407325207763369075250819386897086702757125418327084312933061738392406321003231853349865003322697360659712771167066453430309997639548320673279964210730701373707765051246595806673865646607841876463608039111252607452843372970797479959213447237540830563469297144446069689149549515457318899981763489792/44418759813188470246596822974281932219088697005123285598909329328419879212164564561558656547678211265529803086702843380128457744445765859393426675396393053094588240457569380155526179040891009717323059641035189017918495485630657360525410506453554255734005459614858972904374739673834145083512160883402151636664208253116362089211658645312778404886460203600762546810897889629330022309705583438699250109493732452392578125',
+ '2396110297187570506257836426251930521245117960980667897194680897478996322590925380310917400763581787981992131769075961259393685331523600287377841990678117113566991100720996059187144934569703045966210306282348527729533806882408323961336166190162075154579344185244957673138372347997134388801085788353482276257401005734929059700346687783400772331280644647089520646562669857652648/465370154262863030800729180763091967573522779260407615567563274096037297593764955820626213827962697601334935773802201919483657310282236100250543992334865461800976838791035335861420892967699843608855185710753433852279737724446294443337363478679601049432404435672469098850676302728116369979460222148206944323730586138087862719386327396334468302608499235112520318322763857676375',
+ '1188272888075247520453568790651819187486389546755745961952033150576243575862120569399808467067800830890874391487822723879717525264875722720416173276963170672622214121308038530968291758445510982186014289745026439477197148445087614813262170975056866881909056450023324743409114115141145340480846088869807540798073417035168139476153897066325127540609756310068558610112731607522096096821938163134080426356894899245575410494080/230586762395914863625709048789913840511134004337945700368117795195769576856434588144361459740833007125516905017415184787078710001969815416281381427387949263792200577791644278234661952852149216540491852757174842146376788280677956350901082838474705227642420261641783722891948710833898248566893322144435457967452449965772543077421407816225704952270697016152452032506898311260979594550143344971416786595888053149375287120733',
+ '1072184299258468249004114536023934869518152618369895613271618698908405514630154819453979477077738526673716264830106745031385951736108376866253955319711026042389282009348594845497252059906901748860111986811345742998968284077119165082435598381810306691078037018432766116865792677955520465319184311529667642995217214507658693259694586107847099911341219913576038605872218680562047/207882129535607118520818565811162643811811563516006836924341123976848022970024793004700710992938987476728640943834012473687892481059618823520721737479364352887178017866425027953255856440099446112508663603319745066750522936408781591578321031173620445865113295407603090322283539974563690157418931060565698315406021569491751612761888641141446406531645379264720684248398299136000',
+ '158094023904374464057702010126993231151433474361820266584020400101533651587072432728050776506672527718975042616651177034635188178967462982926261516748842073587981953038445977514446838733557926696939591069712397947055261777416704028506441173540338327591514650398349069348381309688677335132100441905603664282735310299686268721634369502296867409514777359935022301425165750780988231639828675424239931533849896744198012747670272/30626384366513923671063955469537776291182594080083494737341604579575446319190916924682809546024369330706012653799783122328775875399363759308193432327358837772825609356553955350401725689682838701996446404559005896479618311357573203081153855961484398360670837599853294627153740224543175497655414585918231215639906924313779933211774291565425360415684556655021031131349317390382320286092667852173815259422737956767922154798375',
+ '12216278924286401897470540169822542417611356797676160128176706721343511598951866107858729405615631246003458062949723290192242594609994613705183460814797527214549142043573863947658832694800184691222426521703728394203722420336773310908687071670107794543022567943478843065246011544888390757810534211554666736239506503613750756246364621509359804902243487113673014588325806249988332/2364584127734680580481708368927921125934330000162935118371174269642831409084634375654239735054363928748162229294829526339927907778510530444646754603322373712788147193691037688552857183116332292048731726305451516954679277320777955342295564000586923227775208580772868590742136150431152495718132142742153643081187691849828217592954519485869135930755646768375299870967864990234375',
+ '578939195017447864385837341975558679292903716580114421030538728778491606282153588930983966100514147976321305616196463174070345884292672842497715966933722088536619333912687108737657068701352918582656105418491672377271304970407730786932913490869797471081081431072836814581099271832696990064828717287258860166842791984158634684078346453202928137987930328826212011774670396356151265781053547412633310327737900786639842674288716416/111966291649651600701875186827360807558960817589719096407887999108024557254221008849510152469119211567152616745769256123968832250104006560061907741567034087109512483357851919402017950812795426393415409198268246337542639470834168040295134130657073201020340432236723824670803498949071870597039309004562024033656488150406105175401372709011082725359382691270659245312133919518760328422827067578306005644825927050994860723405473375',
+ '8041514821545833865350873212853873042112343327573364523948780030849543753072688526717886590013028227714585965095216381829914996539132359247488639692828639170075726301411777307168737667077730792892326979446426349813642680537699905651125068091362135055902152994216233622733424128982011248923051410141033146604688083721413402395290842738021365861670415833356403364696759583009635/1553937833499559104910551811592138833189892464446761247731330672284191313590708439680460540329836344767557599746234213006493842842879398684745864919973051635127080738612248150108793397855113122368507483429209127035263504194847798809612278864438287815470711036548695032421148125849669175729331459149833945183228413961985381338162155596008680906676498435954178226973272371625984',
+ '12059659399309169167301353842123191409306995788633965680414620302285606896146667751472818541537183787935352700725982053536032930866708413010971856856768384974849695774437178790890015242323873769697407411558706626105140596160049735605389301414460825674631122462760014451298494875244754354054447697502521388381253293175367558014453865905104467382184997528879171136492277222127399012368677604910497789360212571238146627048080568400384/2328494054002703067219004484782931471919751585969487160602269605916676223315055450845942763690581877771159615428854802721943114463834992106300185328876910271612375509166975628540839858771078495267566008071532876816826751069408153323840515037497664871689085370699991860738368371510578076683395101062439493831773194498579620513073693022191702398601471237166387045790480861932005012807234071429628725981935147069738988682858417182625',
+ '683009426705008850682549700382901603742691705123356866338951397347368059105140826655486518416578697931244347554322654428162554903861783227680935341690740579867651545205562690172425016836559678937794312287639193745517811234257062356826622207475918293015140472942579713388671782202262396642735640315948612572908444501108579457520714632371056288874351015666995549139003946314085696/131769214246522869780429817236150406548549044658474416909806179734873399109656411633488099249198882165279745627038984487365589625562901914030850178330133080409420340227179447548044572594184601425691821617928130423410997853384425373999967759499571890326740433081063424903098252502791477854302204200941376983295622430177445507316706609802292121282957519571162158620971952084486375',
+ '1418116259749091420309271913909337143756589314597557128553117870573928055109322864794169592355058370891251425125187770724828108988587888087023982451205475980371440423992838481547236638456717520835668859093413956339261802305597694403381021863258753441889536361061407579332570493634254542013337999844407181346815549594836374497512447969751514466481466717590255369810506813543577735080228718660747940956248427356785784199073569408/273368882980516556922532339653303822104510203916024760371844491705599093435133738774975538364241630079468712981056336655766117378160822393876348755370711942273876284944422610974149712212944211513462921649872232656449940987552227525504529710373144228818606127541937831103642415534318303187393593236209298242574677293352798419428712873685798636474667204083938875672934235816396371477146089168375198141802684403955936431884765625',
+ '546411077581845999248238069684194831621691193366466972898628141511422317954477211865167402284211971296286346492698354214043840080181702652132835681239302798339667835439189275457116051279366194551632712819028177402221815140045210690908631767786711675029194359998826252332434573121740830827417293557234891406021102083555472155479148332697839972702631641910213821827820374586459319/105246945889799140338072754365901705544530661781295362563214285953881890233987516230568663143342952799036341701952267011236663118111226623815975847494372076420199418712941018288738590264475520229651370015377608638075866459684692488079223686296541020897904588347083045723167762682959422858996673894180805702299907478685374065593161226849447131100462022089783265346597553176576000',
+ '2398330640958841474772606439916070050977544535580605737383995160447105736276950196885906408317628083110923322157113892928963237845914017845444295040924101784423382681801754191301860383927129006953354739240926643562987838836997453985855576402628166875869041032631651591871962852884189548538272285387092843044669499688035134181859376665409767886188304314888753894905317929877238322615838524354191263502347881033855441181420399360/461588070868590122892265681879734295007029130965626060552783760068897000195207878227714842617470320231527222074701444349530952699708435668339712860464533455345665068841333232359698449088497137068713309811942968433868609329301082001752617420002377892756821532220676085014874112083615054550278903960627185675459015343606391094523511117705747842645927349130302549554534056269331809016770715819934970200483161548527932617036185253',
+ '6041015879424725383006424536130409209607854044642113747266098198777011981328765528361630516108680392500990580908509403483891763219659726090675140672989657743882183951954294745396417829943469201306594018454995862321821016087416840247422350906412007336103086620396467456181771583200365740253389107968122850063607085957109965406634738740996318415514360956028575560979203447735121436/1161752799109428422288020947061281540989708937450568100764830251908850596717606701047413407636907934320789870175907792017513896999208892282137299070761467096211814586909598705615312819596495636017728313513520193786266452836805291464826226833593878504804389728477191170027729963773716267868284479768397603444919008915279522376004326398403851684761808785381609370767169521034383625',
+ '13240077436443988749179508462267267187169441948722358165090554769250505713747934643200804819418670147225695324432684266924694524337920816452346599774452681831320005286326986675907899608537972384924882996757503264622991355949039882526389342174307168805166215838138277557052303430492669193939212362638263582899713198716541723383138016564027766560215944409353427176135895982596327685665844815618402881202645610620284792793420780517248/2544223084468158291883698813309541801455311468982232546872485444308211415529998472787377800559884210837213042932180479090277285630234238711851480232520137856848809986631784843528381778520727465146661792797924458540957133423665746229799675650290296217658444899605236550972043549278128087645211909479009099766619355677984218929672461506691980442071860591767266913041147587815452007726513853820116629482732060593116624596368806566625',
+ '1953999166296955830935495158735359200362904181792947794529339487489730042568305997099959302322956898299616194932283060554261566410988618045107398092345476532371402134206635235570281738377188438407703089325315446371127042537576093536896282955524842632708645655481028161471313608974238110718242273935956977555610147714316158486553633871312187084618154014921190595222799283957140353/375191165084882521037046014569185165885459082629136124177286500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+];
+
+if (typeof module !== 'undefined') {
+ module.exports = {
+ PRIMES,
+ PRIMES_SET,
+ LONG_PI,
+ LONG_E,
+ BIG_LOG_CACHE,
+ };
+}
--- /dev/null
+/*
+ * Author : Martin Donk
+ * Website : http://www.nerdamer.com
+ * Email : martin.r.donk@gmail.com
+ * Source : https://github.com/jiggzson/nerdamer
+ */
+
+// Type imports for JSDoc ======================================================
+// These typedefs provide type aliases for the interfaces defined in index.d.ts.
+// They enable proper type checking when working with the classes defined in this file.
+//
+// Usage patterns:
+// - For return types: @returns {NerdamerSymbolType}
+// - For parameters: @param {NerdamerSymbolType} symbol
+// - For variable declarations: /** @type {NerdamerSymbolType} */
+//
+// Note: When casting local class instances to interface types, use the pattern:
+// /** @type {InterfaceType} */ (/** @type {unknown} */ (localInstance))
+// This is needed because TypeScript sees local classes and interfaces as separate types.
+
+/**
+ * Core type aliases from index.d.ts
+ *
+ * @typedef {import('./index').NerdamerCore.NerdamerSymbol} NerdamerSymbolType
+ *
+ * @typedef {import('./index').NerdamerCore.Frac} FracType
+ *
+ * @typedef {import('./index').NerdamerCore.Vector} VectorType
+ *
+ * @typedef {import('./index').NerdamerCore.Matrix} MatrixType
+ *
+ * @typedef {import('./index').NerdamerCore.Parser} ParserType
+ *
+ * @typedef {import('./index').NerdamerCore.Collection} CollectionType
+ *
+ * @typedef {import('./index').NerdamerCore.NerdamerSet} SetType
+ *
+ * @typedef {import('./index').NerdamerCore.Settings} SettingsType
+ *
+ * @typedef {import('./index').NerdamerExpression} ExpressionType
+ *
+ * @typedef {typeof import('./index')} NerdamerType
+ *
+ * @typedef {import('./index').NerdamerCore.Token} TokenType
+ *
+ * @typedef {import('./index').NerdamerCore.ScopeArray} ScopeArrayType
+ *
+ * Arithmetic operand type (Symbol, Vector, or Matrix)
+ *
+ * @typedef {import('./index').ArithmeticOperand} ArithmeticOperand
+ *
+ * Expand options type
+ *
+ * @typedef {import('./index').ExpandOptions} ExpandOptions
+ *
+ * LaTeX token types
+ *
+ * @typedef {import('./index').LaTeXToken} LaTeXTokenType
+ *
+ * @typedef {import('./index').FilteredLaTeXToken} FilteredLaTeXTokenType
+ *
+ * Output and parameter types
+ *
+ * @typedef {import('./index').OutputType} OutputType
+ *
+ * @typedef {import('./index').ExpressionParam} ExpressionParam
+ *
+ * @typedef {import('./index').SortFn<unknown>} SortFn
+ *
+ * Constructor types (for factory functions)
+ *
+ * @typedef {import('./index').NerdamerCore.FracConstructor} FracConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.SymbolConstructor} SymbolConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.VectorConstructor} VectorConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.MatrixConstructor} MatrixConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.ExpressionConstructor} ExpressionConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.SetConstructor} SetConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.CollectionConstructor} CollectionConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.Fraction} FractionInterface
+ *
+ * @typedef {import('./index').NerdamerCore.ScientificConstructor} ScientificConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.ParserConstructor} ParserConstructor
+ *
+ * @typedef {import('./index').NerdamerCore.LaTeX} LaTeXInterface
+ *
+ * @typedef {import('./index').NerdamerCore.Math2} Math2Interface
+ *
+ * @typedef {import('./index').NerdamerCore.Build} BuildInterface
+ *
+ * @typedef {import('./index').NerdamerCore.CoreUtils} CoreUtilsInterface
+ *
+ * @typedef {import('./index').NerdamerCore.Utils} UtilsInterface
+ *
+ * @typedef {import('./index').NerdamerCore.InternalParseResult} InternalParseResult
+ *
+ * Exceptions object type (for CoreDeps.exceptions)
+ *
+ * @typedef {{
+ * DivisionByZero: CustomErrorConstructor;
+ * ParseError: CustomErrorConstructor;
+ * OutOfFunctionDomainError: CustomErrorConstructor;
+ * UndefinedError: CustomErrorConstructor;
+ * MaximumIterationsReached: CustomErrorConstructor;
+ * NerdamerTypeError: CustomErrorConstructor;
+ * ParityError: CustomErrorConstructor;
+ * OperatorError: CustomErrorConstructor;
+ * OutOfRangeError: CustomErrorConstructor;
+ * DimensionError: CustomErrorConstructor;
+ * InvalidVariableNameError: CustomErrorConstructor;
+ * ValueLimitExceededError: CustomErrorConstructor;
+ * NerdamerValueError: CustomErrorConstructor;
+ * SolveError: CustomErrorConstructor;
+ * InfiniteLoopError: CustomErrorConstructor;
+ * UnexpectedTokenError: CustomErrorConstructor;
+ * }} ExceptionsType
+ * Exception types
+ *
+ * @typedef {import('./index').NerdamerCore.DivisionByZero} DivisionByZeroType
+ *
+ * @typedef {import('./index').NerdamerCore.ParseError} ParseErrorType
+ *
+ * @typedef {import('./index').NerdamerCore.NerdamerTypeError} NerdamerTypeErrorType
+ *
+ * @typedef {import('./index').NerdamerCore.Core} CoreType
+ *
+ * @typedef {import('./index').NerdamerCore.PowerValue} PowerValueType
+ *
+ * @typedef {import('big-integer').BigInteger} BigIntegerType
+ *
+ * @typedef {import('big-integer').BigIntegerStatic} BigIntegerStaticType
+ *
+ * @typedef {import('decimal.js').Decimal} DecimalType
+ *
+ * @typedef {typeof import('decimal.js').default} DecimalStaticType
+ *
+ * Custom error constructor type - used for exception classes
+ *
+ * @typedef {new (message?: string) => Error} CustomErrorConstructor
+ *
+ * @typedef {Record<
+ * string,
+ * [Function, number] | [Function, number[]] | [Function, number, { name: string; params: string[]; body: string }]
+ * >} FunctionMapType
+ */
+
+// externals ====================================================================
+/* BigInteger.js v1.6.28 https://github.com/peterolson/BigInteger.js/blob/master/LICENSE */
+const nerdamerBigInt =
+ typeof globalThis.nerdamerBigInt === 'undefined' ? require('big-integer') : globalThis.nerdamerBigInt;
+/* Decimal.js v10.2.1 https://github.com/MikeMcl/decimal.js/LICENCE */
+const nerdamerBigDecimal =
+ typeof globalThis.nerdamerBigDecimal === 'undefined' ? require('decimal.js') : globalThis.nerdamerBigDecimal;
+
+// Set BigDecimal precision immediately after import
+nerdamerBigDecimal.set({ precision: 250 });
+
+/* Mathematical constants */
+const nerdamerConstants =
+ typeof globalThis.nerdamerConstants === 'undefined' ? require('./constants.js') : globalThis.nerdamerConstants;
+
+// ============================================================================
+// Runtime state variables - declared before CoreDeps to avoid forward references
+// ============================================================================
+// Custom operators registry - populated by IIFE
+/** @type {{ [key: string]: { precedence: number; operator: string; action: string; postfix?: boolean } }} */
+const CUSTOM_OPERATORS = {};
+
+// Runtime state arrays - used by CoreDeps.state getters
+/** @type {ExpressionType[]} */
+const EXPRESSIONS = [];
+/** @type {Record<string, NerdamerSymbolType>} */
+const VARS_STORE = {};
+/** @type {string[]} */
+const RESERVED = [];
+/** @type {string[]} */
+const WARNINGS = [];
+/** @type {string[]} */
+const USER_FUNCTIONS = [];
+
+// Late-binding references container - populated after classes are defined
+// Used by CoreDeps getters to avoid forward reference issues
+/**
+ * @type {{
+ * Settings: SettingsType | null;
+ * Math2: Math2Interface | null;
+ * }}
+ */
+const LateRefs = {
+ Settings: /** @type {SettingsType | null} */ (null),
+ Math2: /** @type {Math2Interface | null} */ (null),
+};
+
+// CoreDeps - Centralized Dependency Registry ==================================
+// This single registry replaces 45+ scattered *Deps objects with a unified,
+// hierarchical structure. Benefits:
+// - Single source of truth for all shared dependencies
+// - Clear initialization order (externals -> constants -> classes -> parser)
+// - Lazy getters for values defined later in initialization
+// - Type-safe access patterns
+//
+// Structure:
+// CoreDeps.ext - External imports (bigInt, bigDec, constants)
+// CoreDeps.groups - Symbol group constants (N, P, S, EX, FN, PL, CB, CP)
+// CoreDeps.fnNames - Function name constants (SQRT, ABS, FACTORIAL, etc.)
+// CoreDeps.settings - Settings reference
+// CoreDeps.state - Runtime state (EXPRESSIONS, VARS, RESERVED, etc.)
+// CoreDeps.classes - Class constructors (Frac, NerdamerSymbol, Vector, etc.)
+// CoreDeps.utils - Utility functions
+// CoreDeps.parser - Parser instance (set during IIFE init)
+// CoreDeps.core - Core object C (set during IIFE init)
+
+/**
+ * @type {{
+ * ext: {
+ * bigInt: BigIntegerStaticType;
+ * bigDec: DecimalStaticType;
+ * PRIMES: number[];
+ * PRIMES_SET: Record<number, boolean>;
+ * LONG_PI: string;
+ * LONG_E: string;
+ * BIG_LOG_CACHE: string[];
+ * };
+ * groups: {
+ * N: 1;
+ * P: 2;
+ * S: 3;
+ * EX: 4;
+ * FN: 5;
+ * PL: 6;
+ * CB: 7;
+ * CP: 8;
+ * };
+ * fnNames: {
+ * SQRT: 'sqrt';
+ * ABS: 'abs';
+ * FACTORIAL: 'factorial';
+ * DOUBLEFACTORIAL: 'dfactorial';
+ * PARENTHESIS: 'parens';
+ * LOG: 'log';
+ * CONST_HASH: '#';
+ * };
+ * settings: SettingsType;
+ * state: {
+ * EXPRESSIONS: ExpressionType[];
+ * VARS: Record<string, NerdamerSymbolType>;
+ * CONSTANTS: Record<string, NerdamerSymbolType | string | number>;
+ * RESERVED: string[];
+ * WARNINGS: string[];
+ * USER_FUNCTIONS: string[];
+ * CUSTOM_OPERATORS: {
+ * [key: string]: { precedence: number; operator: string; action: string; postfix?: boolean };
+ * };
+ * };
+ * classes: {
+ * Frac: FracConstructor;
+ * Fraction: FractionInterface;
+ * NerdamerSymbol: SymbolConstructor;
+ * Vector: VectorConstructor;
+ * Matrix: MatrixConstructor;
+ * Expression: ExpressionConstructor;
+ * Collection: CollectionConstructor;
+ * NerdamerSet: SetConstructor;
+ * Scientific: ScientificConstructor;
+ * Parser: ParserConstructor;
+ * LaTeX: LaTeXInterface;
+ * Math2: Math2Interface;
+ * Build: BuildInterface;
+ * };
+ * utils: {
+ * isSymbol: (x: unknown) => boolean;
+ * isVector: (x: unknown) => boolean;
+ * isMatrix: (x: unknown) => boolean;
+ * isExpression: (x: unknown) => boolean;
+ * isNumericSymbol: (symbol: NerdamerSymbolType) => boolean;
+ * isFraction: (x: unknown) => boolean;
+ * isArray: (arr: unknown) => boolean;
+ * isInt: (n: number | string | unknown) => boolean;
+ * text: (symbol: NerdamerSymbolType, opt?: OutputType, useGroup?: number, decp?: number) => string;
+ * variables: (obj: NerdamerSymbolType | FracType, poly?: boolean, vars?: unknown) => string[];
+ * scientificToDecimal: (num: number) => string;
+ * err: (msg: string, ErrorObj?: CustomErrorConstructor) => void;
+ * block: (setting: string, f: Function, opt?: boolean, obj?: unknown) => unknown;
+ * evaluate: (symbol: NerdamerSymbolType, o?: Record<string, ExpressionParam>) => NerdamerSymbolType;
+ * reserveNames: (obj: object) => void;
+ * nround: (x: string | number, s?: number) => string | number;
+ * remove: (arr: unknown[], index: number) => unknown;
+ * _setFunction: (fnName: string | Function, fnParams?: string[], fnBody?: string) => boolean;
+ * _clearFunctions: () => void;
+ * symfunction: (fname: string, args: NerdamerSymbolType[]) => NerdamerSymbolType;
+ * callfunction: (fname: string, args: NerdamerSymbolType[]) => NerdamerSymbolType;
+ * };
+ * exceptions: ExceptionsType;
+ * parser: ParserType;
+ * core: CoreType;
+ * libExports: typeof nerdamer;
+ * version: string;
+ * }}
+ */
+const CoreDeps = {
+ // External imports - available immediately
+ ext: {
+ bigInt: nerdamerBigInt,
+ bigDec: nerdamerBigDecimal,
+ PRIMES: nerdamerConstants.PRIMES,
+ PRIMES_SET: nerdamerConstants.PRIMES_SET,
+ LONG_PI: nerdamerConstants.LONG_PI,
+ LONG_E: nerdamerConstants.LONG_E,
+ BIG_LOG_CACHE: nerdamerConstants.BIG_LOG_CACHE,
+ },
+
+ // Symbol group constants - available immediately
+ groups: {
+ N: 1, // A number
+ P: 2, // A number with a rational power e.g. 2^(3/5)
+ S: 3, // A single variable e.g. x
+ EX: 4, // An exponential
+ FN: 5, // A function
+ PL: 6, // Same name, different powers e.g. 1/x + x^2
+ CB: 7, // Multiplication composite e.g. x*y
+ CP: 8, // Addition composite e.g. x+1 or x+y
+ },
+
+ // Function name constants - available immediately
+ fnNames: {
+ SQRT: 'sqrt',
+ ABS: 'abs',
+ FACTORIAL: 'factorial',
+ DOUBLEFACTORIAL: 'dfactorial',
+ PARENTHESIS: 'parens',
+ LOG: 'log',
+ CONST_HASH: '#',
+ },
+
+ // Settings reference - getter using LateRefs for forward reference safety
+ get settings() {
+ return LateRefs.Settings;
+ },
+
+ // Runtime state - arrays now defined before CoreDeps
+ state: {
+ get EXPRESSIONS() {
+ return EXPRESSIONS;
+ },
+ get VARS() {
+ return VARS_STORE;
+ },
+ CONSTANTS: /** @type {Record<string, NerdamerSymbolType | string>} */ ({}),
+ get RESERVED() {
+ return RESERVED;
+ },
+ get WARNINGS() {
+ return WARNINGS;
+ },
+ get USER_FUNCTIONS() {
+ return USER_FUNCTIONS;
+ },
+ get CUSTOM_OPERATORS() {
+ return CUSTOM_OPERATORS;
+ },
+ },
+
+ // Class constructors - set by IIFE after class definitions
+ classes: {
+ Frac: /** @type {FracConstructor} */ (null),
+ Fraction: /** @type {FractionInterface} */ (null),
+ NerdamerSymbol: /** @type {SymbolConstructor} */ (null),
+ Vector: /** @type {VectorConstructor} */ (null),
+ Matrix: /** @type {MatrixConstructor} */ (null),
+ Expression: /** @type {ExpressionConstructor} */ (null),
+ Collection: /** @type {CollectionConstructor} */ (null),
+ NerdamerSet: /** @type {SetConstructor} */ (null),
+ Scientific: /** @type {ScientificConstructor} */ (null),
+ Parser: /** @type {ParserConstructor} */ (null),
+ LaTeX: /** @type {LaTeXInterface} */ (null),
+ Math2: /** @type {Math2Interface} */ (null),
+ Build: /** @type {BuildInterface} */ (null),
+ },
+
+ // Utility functions - use getters for module-scope functions
+ // symfunction and callfunction are set by IIFE since they need parser binding
+ utils: {
+ get isSymbol() {
+ return isSymbol;
+ },
+ get isVector() {
+ return isVector;
+ },
+ get isMatrix() {
+ return isMatrix;
+ },
+ get isExpression() {
+ return isExpression;
+ },
+ get isNumericSymbol() {
+ return isNumericSymbol;
+ },
+ get isFraction() {
+ return isFraction;
+ },
+ get isArray() {
+ return isArray;
+ },
+ get isInt() {
+ return isInt;
+ },
+ get text() {
+ return text;
+ },
+ get variables() {
+ return variables;
+ },
+ get scientificToDecimal() {
+ return scientificToDecimal;
+ },
+ get err() {
+ return err;
+ },
+ get block() {
+ return block;
+ },
+ get evaluate() {
+ return evaluate;
+ },
+ get reserveNames() {
+ return reserveNames;
+ },
+ get nround() {
+ return nround;
+ },
+ get remove() {
+ return remove;
+ },
+ get _setFunction() {
+ return _setFunction;
+ },
+ get _clearFunctions() {
+ return _clearFunctions;
+ },
+ // Parser-bound methods - set by IIFE after parser instantiation
+ symfunction: /** @type {(fname: string, args: NerdamerSymbolType[]) => NerdamerSymbolType} */ (null),
+ callfunction: /** @type {(fname: string, args: NerdamerSymbolType[]) => NerdamerSymbolType} */ (null),
+ },
+
+ // Exception classes - assigned after exception definitions (see below Frac class)
+ exceptions: /** @type {ExceptionsType} */ (null),
+
+ // Parser instance - set by IIFE after Parser creation
+ parser: /** @type {ParserType} */ (null),
+
+ // Core object C - set by IIFE at end
+ core: /** @type {CoreType} */ (null),
+
+ // Library exports function - set by IIFE
+ libExports: /** @type {typeof nerdamer} */ (null),
+
+ // Version string
+ version: '1.1.16',
+};
+
+// Groups object - maps to CoreDeps.groups for external access
+const Groups = {
+ N: CoreDeps.groups.N,
+ P: CoreDeps.groups.P,
+ S: CoreDeps.groups.S,
+ EX: CoreDeps.groups.EX,
+ FN: CoreDeps.groups.FN,
+ PL: CoreDeps.groups.PL,
+ CB: CoreDeps.groups.CB,
+ CP: CoreDeps.groups.CP,
+};
+
+// ============================================================================
+// Math Polyfills
+// ============================================================================
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/
+Math.sign ||= function sign(x) {
+ x = Number(x); // Convert to a number
+ if (x === 0 || isNaN(x)) {
+ return x;
+ }
+ return x > 0 ? 1 : -1;
+};
+
+Math.cosh ||= function cosh(x) {
+ const y = Math.exp(x);
+ return (y + 1 / y) / 2;
+};
+
+Math.sech ||= function sech(x) {
+ return 1 / Math.cosh(x);
+};
+
+Math.csch ||= function csch(x) {
+ return 1 / Math.sinh(x);
+};
+
+Math.coth ||= function coth(x) {
+ return 1 / Math.tanh(x);
+};
+
+Math.sinh ||= function sinh(x) {
+ const y = Math.exp(x);
+ return (y - 1 / y) / 2;
+};
+
+Math.tanh ||= function tanh(x) {
+ if (x === Infinity) {
+ return 1;
+ }
+ if (x === -Infinity) {
+ return -1;
+ }
+ const y = Math.exp(2 * x);
+ return (y - 1) / (y + 1);
+};
+
+Math.asinh ||= function asinh(x) {
+ if (x === -Infinity) {
+ return x;
+ }
+ return Math.log(x + Math.sqrt(x * x + 1));
+};
+
+Math.acosh ||= function acosh(x) {
+ return Math.log(x + Math.sqrt(x * x - 1));
+};
+
+Math.atanh ||= function atanh(x) {
+ return Math.log((1 + x) / (1 - x)) / 2;
+};
+
+Math.trunc ||= function trunc(x) {
+ if (isNaN(x)) {
+ return NaN;
+ }
+ if (x > 0) {
+ return Math.floor(x);
+ }
+ return Math.ceil(x);
+};
+
+// ============================================================================
+// Scientific notation helper
+// ============================================================================
+// Extracted as standalone function to avoid forward-reference to Scientific class
+
+/**
+ * Checks if a string is in scientific notation (e.g., "1.5e10", "2E-5")
+ *
+ * @param {string} num
+ * @returns {boolean}
+ */
+function isScientificNotation(num) {
+ return /\d+\.?\d*e[+-]*\d+/iu.test(num);
+}
+
+// Fraction Object ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// This static utility object converts decimals to fractions.
+
+/** Static utility object for converting decimals to fractions. */
+const Fraction = {
+ /**
+ * Converts a decimal to a fraction
+ *
+ * @param {number | string} value
+ * @param {object} [_opts]
+ * @returns {Array} An array containing the numerator and the denominator
+ */
+ convert(value, _opts) {
+ const numValue = Number(value);
+ let frac;
+ if (numValue === 0) {
+ frac = [0, 1];
+ } else if (Math.abs(numValue) < 1e-6 || Math.abs(numValue) > 1e20) {
+ const qc = this.quickConversion(numValue);
+ if (qc[1] <= 1e16) {
+ const abs = Math.abs(numValue);
+ const sign = numValue / abs;
+ frac = this.fullConversion(abs.toFixed(`${qc[1]}`.length - 1));
+ frac[0] *= sign;
+ } else {
+ frac = qc;
+ }
+ } else {
+ frac = this.fullConversion(numValue);
+ }
+ return frac;
+ },
+ /**
+ * If the fraction is too small or too large this gets called instead of fullConversion method
+ *
+ * @param {number | string} value
+ * @returns {Array} An array containing the numerator and the denominator as strings
+ */
+ quickConversion(value) {
+ const stripSign = function (s) {
+ // Explicitely convert to a string
+ if (typeof s !== 'string') {
+ s = s.toString();
+ }
+
+ let sign = '';
+
+ // Remove and store the sign
+ const start = s.charAt(0);
+ if (start === '-') {
+ s = s.substr(1, s.length);
+ sign = '-';
+ } else if (start === '+') {
+ // Just remove the plus sign
+ s = s.substr(1, s.length);
+ }
+
+ return {
+ sign,
+ value: s,
+ };
+ };
+
+ function convert(val) {
+ // Explicitely convert to a decimal
+ if (isScientificNotation(val)) {
+ val = scientificToDecimal(val);
+ }
+
+ // Split the value into the sign and the value
+ const nparts = stripSign(val);
+
+ // Split it at the decimal. We'll refer to it as the coeffient parts
+ const cparts = nparts.value.split('.');
+
+ // Combine the entire number by removing leading zero and adding the decimal part
+ // This would be teh same as moving the decimal point to the end
+ let num;
+ // We're dealing with integers
+ if (cparts.length === 1) {
+ num = cparts[0];
+ } else {
+ num = cparts[0] + cparts[1];
+ }
+ const n = cparts[1] ? cparts[1].length : 0;
+ // Generate the padding for the zeros
+ const den = `1${'0'.repeat(n)}`;
+
+ if (num !== '0') {
+ num = num.replace(/^0+/u, '');
+ }
+ return [nparts.sign + num, den];
+ }
+
+ return convert(value);
+ },
+ /**
+ * Returns a good approximation of a fraction. This method gets called by convert
+ * http://mathforum.org/library/drmath/view/61772.html Decimal To Fraction Conversion - A Simpler Version Dr
+ * Peterson
+ *
+ * @param {number | string} dec
+ * @returns {Array} An array containing the numerator and the denominator
+ */
+ fullConversion(dec) {
+ const numDec = Number(dec);
+ // This doesn't work for values approaching as small as epsilon
+ const epsilon = Math.abs(numDec) > 1e10 ? 1e-16 : 1e-30;
+ let done = false;
+ // You can adjust the epsilon to a larger number if you don't need very high precision
+ let n1 = 0;
+ let d1 = 1;
+ let n2 = 1;
+ let d2 = 0;
+ let n = 0;
+ let q = numDec;
+ let num;
+ let den;
+ // Relative epsilon for rounding large q values to nearest integer.
+ // This fixes floating-point precision errors in reciprocals (e.g., 1/1e-15 = 999999999999999.9).
+ // We use ~45x Number.EPSILON to allow for accumulated rounding errors.
+ // This is independent of Settings.PRECISION since we're dealing with IEEE 754 double limits.
+ const roundingEpsilon = 1e-14; // ~45 * Number.EPSILON (2.2e-16)
+ while (!done) {
+ n++;
+ // For very large q values, round to nearest integer if within floating-point error
+ let a;
+ if (Math.abs(q) > 1e10) {
+ const rounded = Math.round(q);
+ const relDiff = Math.abs(q - rounded) / Math.abs(rounded);
+ a = relDiff < roundingEpsilon ? rounded : Math.floor(q);
+ } else {
+ a = Math.floor(q);
+ }
+ num = n1 + a * n2;
+ den = d1 + a * d2;
+ const e = q - a;
+ if (e < epsilon) {
+ done = true;
+ }
+ q = 1 / e;
+ n1 = n2;
+ d1 = d2;
+ n2 = num;
+ d2 = den;
+ if (Math.abs(num / den - numDec) < epsilon || n > 30) {
+ done = true;
+ }
+ }
+ return [num, den];
+ },
+};
+
+// Assign Fraction to CoreDeps immediately
+CoreDeps.classes.Fraction = /** @type {FractionInterface} */ (/** @type {unknown} */ (Fraction));
+
+// CustomError Function =============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// This function creates custom error classes.
+
+/**
+ * Creates a custom error class with the given name.
+ *
+ * @param {string} name - The name of the custom error class
+ * @returns {new (message?: string) => Error} A custom error constructor
+ */
+function customError(name) {
+ const E = function (message) {
+ this.name = name;
+ this.message = message === undefined ? '' : message;
+ const error = new Error(this.message);
+ error.name = this.name;
+ this.stack = error.stack;
+ }; // Create an empty error
+ E.prototype = Object.create(Error.prototype);
+ return E;
+}
+
+// DivisionByZero Error ================================================================
+/**
+ * Error thrown for division by zero.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const DivisionByZero = customError('DivisionByZero');
+
+// ParseError Error ====================================================================
+/**
+ * Error thrown if an error occurred during parsing.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const ParseError = customError('ParseError');
+
+// UndefinedError Error ================================================================
+/**
+ * Error thrown if the expression results in undefined.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const UndefinedError = customError('UndefinedError');
+
+// OutOfFunctionDomainError Error ======================================================
+/**
+ * Error thrown if input is out of the function domain.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const OutOfFunctionDomainError = customError('OutOfFunctionDomainError');
+
+// MaximumIterationsReached Error ======================================================
+/**
+ * Error thrown if a function exceeds maximum iterations.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const MaximumIterationsReached = customError('MaximumIterationsReached');
+
+// NerdamerTypeError Error =============================================================
+/**
+ * Error thrown if the parser receives an incorrect type.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const NerdamerTypeError = customError('NerdamerTypeError');
+
+// ParityError Error ===================================================================
+/**
+ * Error thrown if bracket parity is not correct.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const ParityError = customError('ParityError');
+
+// OperatorError Error =================================================================
+/**
+ * Error thrown if an unexpected or incorrect operator is encountered.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const OperatorError = customError('OperatorError');
+
+// OutOfRangeError Error ===============================================================
+/**
+ * Error thrown if an index is out of range.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const OutOfRangeError = customError('OutOfRangeError');
+
+// DimensionError Error ================================================================
+/**
+ * Error thrown if dimensions are incorrect (mostly for matrices).
+ *
+ * @type {new (message?: string) => Error}
+ */
+const DimensionError = customError('DimensionError');
+
+// InvalidVariableNameError Error ======================================================
+/**
+ * Error thrown if variable name violates naming rule.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const InvalidVariableNameError = customError('InvalidVariableNameError');
+
+// ValueLimitExceededError Error =======================================================
+/**
+ * Error thrown if the limits of the library are exceeded for a function.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const ValueLimitExceededError = customError('ValueLimitExceededError');
+
+// NerdamerValueError Error ============================================================
+/**
+ * Error thrown if the value is an incorrect LH or RH value.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const NerdamerValueError = customError('NerdamerValueError');
+
+// SolveError Error ====================================================================
+/**
+ * Error thrown for solve-related errors.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const SolveError = customError('SolveError');
+
+// InfiniteLoopError Error =============================================================
+/**
+ * Error thrown for an infinite loop.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const InfiniteLoopError = customError('InfiniteLoopError');
+
+// UnexpectedTokenError Error ==========================================================
+/**
+ * Error thrown if an operator is found when there shouldn't be one.
+ *
+ * @type {new (message?: string) => Error}
+ */
+const UnexpectedTokenError = customError('UnexpectedTokenError');
+
+// Assign CoreDeps.exceptions now that all exception classes are defined
+CoreDeps.exceptions = {
+ DivisionByZero,
+ ParseError,
+ OutOfFunctionDomainError,
+ UndefinedError,
+ MaximumIterationsReached,
+ NerdamerTypeError,
+ ParityError,
+ OperatorError,
+ OutOfRangeError,
+ DimensionError,
+ InvalidVariableNameError,
+ ValueLimitExceededError,
+ NerdamerValueError,
+ SolveError,
+ InfiniteLoopError,
+ UnexpectedTokenError,
+};
+
+// Frac Class ===================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Dependencies are accessed via CoreDeps for centralized management.
+
+/**
+ * Dependency accessor for Frac class. Uses CoreDeps as the single source of truth for all dependencies.
+ *
+ * Note: bigInt and bigDec are typed as BigIntegerStaticType and DecimalStaticType. While these types don't expose
+ * constructor signatures in TypeScript, the libraries support 'new' at runtime. Type assertions are used at call
+ * sites.
+ *
+ * @type {{
+ * bigInt: BigIntegerStaticType;
+ * bigDec: DecimalStaticType;
+ * isInt: (n: number | string | unknown) => boolean;
+ * scientificToDecimal: (num: number) => string;
+ * DivisionByZero: CustomErrorConstructor;
+ * Settings: SettingsType;
+ * Fraction: typeof Fraction;
+ * }}
+ */
+const FracDeps = {
+ get bigInt() {
+ return CoreDeps.ext.bigInt;
+ },
+ get bigDec() {
+ return CoreDeps.ext.bigDec;
+ },
+ get isInt() {
+ return CoreDeps.utils.isInt;
+ },
+ get scientificToDecimal() {
+ return CoreDeps.utils.scientificToDecimal;
+ },
+ get DivisionByZero() {
+ return DivisionByZero;
+ },
+ get Settings() {
+ return CoreDeps.settings;
+ },
+ get Fraction() {
+ return Fraction;
+ },
+};
+
+/**
+ * High-precision fraction class.
+ *
+ * @implements {FracType}
+ */
+class Frac {
+ /** @type {BigIntegerType} */
+ num;
+ /** @type {BigIntegerType} */
+ den;
+
+ /** @param {number | string | Frac} [n] */
+ constructor(n) {
+ if (n instanceof Frac) {
+ // eslint-disable-next-line no-constructor-return -- Frac is designed to return existing instances
+ return n;
+ }
+ if (n === undefined) {
+ // eslint-disable-next-line no-constructor-return -- Early return for undefined
+ return this;
+ }
+ try {
+ if (FracDeps.isInt(n)) {
+ try {
+ // @ts-expect-error - bigInt accepts string | number at runtime
+ this.num = FracDeps.bigInt(n);
+ this.den = FracDeps.bigInt(1);
+ } catch (e) {
+ if (/** @type {Error} */ (e).message === 'timeout') {
+ throw e;
+ }
+ // eslint-disable-next-line no-constructor-return -- Fallback to simple parsing
+ return Frac.simple(n);
+ }
+ } else {
+ const frac =
+ /** @type {unknown} */ (n) instanceof FracDeps.bigDec
+ ? FracDeps.Fraction.quickConversion(n)
+ : FracDeps.Fraction.convert(n);
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ this.num = new FracDeps.bigInt(frac[0]);
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ this.den = new FracDeps.bigInt(frac[1]);
+ }
+ } catch (e) {
+ if (/** @type {Error} */ (e).message === 'timeout') {
+ throw e;
+ }
+ // eslint-disable-next-line no-constructor-return -- Fallback to simple parsing
+ return Frac.simple(n);
+ }
+ }
+
+ /**
+ * Safe to use with negative numbers or other types
+ *
+ * @param {number | string | FracType} n
+ * @returns {FracType}
+ */
+ static create(n) {
+ if (n instanceof Frac) {
+ return n;
+ }
+ n = n.toString();
+ const isNeg = n.charAt(0) === '-';
+ if (isNeg) {
+ n = n.substr(1, n.length - 1);
+ }
+ const frac = new Frac(n);
+ if (isNeg) {
+ frac.negate();
+ }
+ return frac;
+ }
+
+ /**
+ * @param {unknown} o
+ * @returns {o is FracType}
+ */
+ static isFrac(o) {
+ return o instanceof Frac;
+ }
+
+ /**
+ * @param {string | number} n
+ * @param {string | number} d
+ * @returns {FracType}
+ */
+ static quick(n, d) {
+ const frac = new Frac();
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ frac.num = new FracDeps.bigInt(n);
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ frac.den = new FracDeps.bigInt(d);
+ return frac;
+ }
+
+ /**
+ * @param {number | string} n
+ * @returns {FracType}
+ */
+ static simple(n) {
+ const nstr = String(FracDeps.scientificToDecimal(/** @type {number} */ (n)));
+ const mDc = nstr.split('.');
+ const num = mDc.join('');
+ /** @type {string} */
+ let den = '1';
+ const l = (mDc[1] || '').length;
+ for (let i = 0; i < l; i++) {
+ den += '0';
+ }
+ const frac = Frac.quick(num, den);
+ return frac.simplify();
+ }
+
+ /**
+ * @param {FracType} m
+ * @returns {FracType}
+ */
+ multiply(m) {
+ if (this.isOne()) {
+ return m.clone();
+ }
+ if (m.isOne()) {
+ return this.clone();
+ }
+
+ const c = this.clone();
+ c.num = c.num.multiply(m.num);
+ c.den = c.den.multiply(m.den);
+
+ return c.simplify();
+ }
+
+ /**
+ * @param {FracType} m
+ * @returns {FracType}
+ */
+ divide(m) {
+ if (m.equals(0)) {
+ throw new FracDeps.DivisionByZero('Division by zero not allowed!');
+ }
+ return this.clone().multiply(m.clone().invert()).simplify();
+ }
+
+ /**
+ * @param {FracType} m
+ * @returns {FracType}
+ */
+ subtract(m) {
+ return this.clone().add(m.clone().neg());
+ }
+
+ /**
+ * Alias for subtract
+ *
+ * @param {FracType} m
+ * @returns {FracType}
+ */
+ sub(m) {
+ return this.subtract(m);
+ }
+
+ /** @returns {this} */
+ neg() {
+ this.num = this.num.multiply(-1);
+ return this;
+ }
+
+ /**
+ * @param {FracType} m
+ * @returns {FracType}
+ */
+ add(m) {
+ const n1 = this.den;
+ const n2 = m.den;
+ const c = this.clone();
+ const a = c.num;
+ const b = m.num;
+ if (n1.equals(n2)) {
+ c.num = a.add(b);
+ } else {
+ c.num = a.multiply(n2).add(b.multiply(n1));
+ c.den = n1.multiply(n2);
+ }
+
+ return c.simplify();
+ }
+
+ /**
+ * @param {FracType} m
+ * @returns {FracType}
+ */
+ mod(m) {
+ const a = this.clone();
+ const b = m.clone();
+ a.num = a.num.multiply(b.den);
+ a.den = a.den.multiply(b.den);
+ b.num = b.num.multiply(this.den);
+ b.den = b.den.multiply(this.den);
+ a.num = a.num.mod(b.num);
+ return a.simplify();
+ }
+
+ /** @returns {this} */
+ simplify() {
+ const gcd = FracDeps.bigInt.gcd(this.num, this.den);
+ this.num = this.num.divide(gcd);
+ this.den = this.den.divide(gcd);
+ return this;
+ }
+
+ /** @returns {FracType} */
+ clone() {
+ const m = new Frac();
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ m.num = new FracDeps.bigInt(this.num);
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ m.den = new FracDeps.bigInt(this.den);
+ return m;
+ }
+
+ /**
+ * @param {number} [prec]
+ * @returns {string}
+ */
+ decimal(prec) {
+ const sign = this.num.isNegative() ? '-' : '';
+ if (this.num.equals(this.den)) {
+ return '1';
+ }
+ prec ||= FracDeps.Settings.PRECISION;
+ prec += 2;
+ const narr = [];
+ let n = this.num.abs();
+ const d = this.den;
+ let i;
+ for (i = 0; i < prec; i++) {
+ const w = n.divide(d);
+ const r = n.subtract(w.multiply(d));
+ narr.push(w);
+ if (r.equals(0)) {
+ break;
+ }
+ n = r.times(10);
+ }
+ const whole = narr.shift();
+ if (narr.length === 0) {
+ return sign + whole.toString();
+ }
+
+ if (i === prec) {
+ const lt = [];
+ for (let j = 0; j < 2; j++) {
+ lt.unshift(narr.pop());
+ }
+ narr.push(Math.round(Number(lt.join('.'))));
+ }
+
+ const dec = `${whole.toString()}.${narr.join('')}`;
+ return sign + dec;
+ }
+
+ /**
+ * @param {number} [prec]
+ * @returns {string | number}
+ */
+ toDecimal(prec) {
+ prec ||= FracDeps.Settings.PRECISION;
+ if (prec) {
+ return this.decimal(prec);
+ }
+ return this.num.valueOf() / this.den.valueOf();
+ }
+
+ /**
+ * @param {FracType} n
+ * @returns {[BigIntegerType, BigIntegerType]}
+ */
+ qcompare(n) {
+ return [this.num.multiply(n.den), n.num.multiply(this.den)];
+ }
+
+ /**
+ * @param {number | FracType} n
+ * @returns {boolean}
+ */
+ equals(n) {
+ if (!isNaN(/** @type {number} */ (n))) {
+ n = new Frac(/** @type {number} */ (n));
+ }
+ const q = this.qcompare(/** @type {FracType} */ (n));
+ return q[0].equals(q[1]);
+ }
+
+ /**
+ * @param {number | FracType} n
+ * @returns {boolean}
+ */
+ absEquals(n) {
+ if (!isNaN(/** @type {number} */ (n))) {
+ n = new Frac(/** @type {number} */ (n));
+ }
+ const q = this.qcompare(/** @type {FracType} */ (n));
+ return q[0].abs().equals(q[1]);
+ }
+
+ /**
+ * @param {number | FracType} n
+ * @returns {boolean}
+ */
+ greaterThan(n) {
+ if (!isNaN(/** @type {number} */ (n))) {
+ n = new Frac(/** @type {number} */ (n));
+ }
+ const q = this.qcompare(/** @type {FracType} */ (n));
+ return q[0].gt(q[1]);
+ }
+
+ /**
+ * Alias for greaterThan
+ *
+ * @param {number | FracType} n
+ * @returns {boolean}
+ */
+ gt(n) {
+ return this.greaterThan(n);
+ }
+
+ /**
+ * @param {number | FracType} n
+ * @returns {boolean}
+ */
+ gte(n) {
+ return this.greaterThan(n) || this.equals(n);
+ }
+
+ /**
+ * @param {number | FracType} n
+ * @returns {boolean}
+ */
+ lte(n) {
+ return this.lessThan(n) || this.equals(n);
+ }
+
+ /**
+ * @param {number | FracType} n
+ * @returns {boolean}
+ */
+ lessThan(n) {
+ if (!isNaN(/** @type {number} */ (n))) {
+ n = new Frac(/** @type {number} */ (n));
+ }
+ const q = this.qcompare(/** @type {FracType} */ (n));
+ return q[0].lt(q[1]);
+ }
+
+ /**
+ * Alias for lessThan
+ *
+ * @param {number | FracType} n
+ * @returns {boolean}
+ */
+ lt(n) {
+ return this.lessThan(n);
+ }
+
+ /** @returns {boolean} */
+ isInteger() {
+ return this.den.equals(1);
+ }
+
+ /** @returns {this} */
+ negate() {
+ this.num = this.num.multiply(-1);
+ return this;
+ }
+
+ /** @returns {this} */
+ invert() {
+ const t = this.den;
+ if (!this.num.equals(0)) {
+ const isnegative = this.num.isNegative();
+ this.den = this.num.abs();
+ this.num = t;
+ if (isnegative) {
+ this.num = this.num.multiply(-1);
+ }
+ }
+ return this;
+ }
+
+ /** @returns {boolean} */
+ isOne() {
+ return this.num.equals(1) && this.den.equals(1);
+ }
+
+ /** @returns {-1 | 1} */
+ sign() {
+ return this.num.isNegative() ? -1 : 1;
+ }
+
+ /** @returns {this} */
+ abs() {
+ this.num = this.num.abs();
+ return this;
+ }
+
+ /**
+ * @param {FracType} f
+ * @returns {FracType}
+ */
+ gcd(f) {
+ // @ts-expect-error - bigInt.gcd accepts BigInteger at runtime
+ return Frac.quick(FracDeps.bigInt.gcd(f.num, this.num), FracDeps.bigInt.lcm(f.den, this.den));
+ }
+
+ /** @returns {string} */
+ toString() {
+ return this.den.equals(1) ? this.num.toString() : `${this.num.toString()}/${this.den.toString()}`;
+ }
+
+ /** @returns {number | DecimalType} */
+ valueOf() {
+ if (FracDeps.Settings.USE_BIG) {
+ return new FracDeps.bigDec(this.num.toString()).div(new FracDeps.bigDec(this.den.toString()));
+ }
+ const retval = this.num.valueOf() / this.den.valueOf();
+ return retval;
+ }
+
+ /** @returns {boolean} */
+ isNegative() {
+ return /** @type {number} */ (this.toDecimal()) < 0;
+ }
+
+ /**
+ * Checks if this fraction contains the given number (i.e., is divisible by it)
+ *
+ * @param {number | FracType} n
+ * @returns {boolean}
+ */
+ contains(n) {
+ const fracN = typeof n === 'number' ? new Frac(n) : n;
+ return this.mod(fracN).equals(0);
+ }
+}
+
+// Assign Frac to CoreDeps immediately for early access
+CoreDeps.classes.Frac = Frac;
+
+// NerdamerSet Class ====================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Dependencies are accessed via CoreDeps for centralized management.
+
+/**
+ * Dependency accessor for NerdamerSet class. Uses CoreDeps as the single source of truth.
+ *
+ * @type {{
+ * isVector: (x: unknown) => boolean;
+ * Vector: VectorConstructor;
+ * remove: (arr: unknown[], index: number) => unknown;
+ * }}
+ */
+const SetDeps = {
+ get isVector() {
+ return CoreDeps.utils.isVector;
+ },
+ get Vector() {
+ return CoreDeps.classes.Vector;
+ },
+ get remove() {
+ return remove;
+ }, // Remove is defined later in file
+};
+
+/**
+ * NerdamerSet class for mathematical set operations.
+ *
+ * @implements {SetType}
+ */
+class NerdamerSet {
+ /** @type {NerdamerSymbolType[]} */
+ elements = [];
+
+ /**
+ * @param {VectorType | NerdamerSymbolType | undefined} [setArg]
+ * @param {...NerdamerSymbolType} rest
+ */
+ constructor(setArg, ...rest) {
+ // If the first object isn't an array, convert it to one.
+ if (typeof setArg === 'undefined') {
+ // No arguments passed
+ return;
+ }
+ let setVal = /** @type {VectorType} */ (setArg);
+ if (!SetDeps.isVector(setArg)) {
+ setVal = SetDeps.Vector.fromArray([/** @type {NerdamerSymbolType} */ (setArg), ...rest]);
+ }
+
+ if (setVal) {
+ const { elements } = setVal;
+ for (let i = 0, l = elements.length; i < l; i++) {
+ this.add(/** @type {NerdamerSymbolType} */ (elements[i]));
+ }
+ }
+ }
+
+ /**
+ * @param {NerdamerSymbolType[]} arr
+ * @returns {SetType}
+ */
+ static fromArray(arr) {
+ const newSet = new NerdamerSet();
+ for (const item of arr) {
+ newSet.add(item);
+ }
+ return newSet;
+ }
+
+ /** @param {NerdamerSymbolType} x */
+ add(x) {
+ if (!this.contains(x)) {
+ this.elements.push(x.clone());
+ }
+ }
+
+ /**
+ * @param {NerdamerSymbolType} x
+ * @returns {boolean}
+ */
+ contains(x) {
+ for (let i = 0; i < this.elements.length; i++) {
+ const e = this.elements[i];
+ if (x.equals(e)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param {(e: NerdamerSymbolType, inputSet: SetType, i: number) => void} f
+ * @returns {SetType}
+ */
+ each(f) {
+ const { elements } = this;
+ const newSet = new NerdamerSet();
+ for (let i = 0, l = elements.length; i < l; i++) {
+ const e = elements[i];
+ f.call(this, e, newSet, i);
+ }
+ return newSet;
+ }
+
+ /** @returns {SetType} */
+ clone() {
+ const newSet = new NerdamerSet();
+ this.each(e => {
+ newSet.add(e.clone());
+ });
+ return newSet;
+ }
+
+ /**
+ * @param {SetType} inputSet
+ * @returns {SetType}
+ */
+ union(inputSet) {
+ const _union = this.clone();
+ inputSet.each(e => {
+ _union.add(e);
+ });
+
+ return _union;
+ }
+
+ /**
+ * @param {SetType} inputSet
+ * @returns {SetType}
+ */
+ difference(inputSet) {
+ const diff = this.clone();
+ inputSet.each(e => {
+ diff.remove(e);
+ });
+ return diff;
+ }
+
+ /**
+ * @param {NerdamerSymbolType} element
+ * @returns {boolean}
+ */
+ remove(element) {
+ for (let i = 0, l = this.elements.length; i < l; i++) {
+ const e = this.elements[i];
+ if (e.equals(element)) {
+ SetDeps.remove(this.elements, i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param {SetType} inputSet
+ * @returns {SetType}
+ */
+ intersection(inputSet) {
+ const _intersection = new NerdamerSet();
+ const A = this;
+ inputSet.each(e => {
+ if (A.contains(e)) {
+ _intersection.add(e);
+ }
+ });
+
+ return _intersection;
+ }
+
+ /**
+ * @param {SetType} inputSet
+ * @returns {boolean}
+ */
+ intersects(inputSet) {
+ return this.intersection(inputSet).elements.length > 0;
+ }
+
+ /**
+ * @param {SetType} inputSet
+ * @returns {boolean}
+ */
+ isSubset(inputSet) {
+ const { elements } = inputSet;
+ for (let i = 0, l = elements.length; i < l; i++) {
+ if (!this.contains(elements[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** @returns {string} */
+ toString() {
+ return `{${this.elements.join(',')}}`;
+ }
+}
+
+// Assign NerdamerSet to CoreDeps immediately
+CoreDeps.classes.NerdamerSet = NerdamerSet;
+
+// Collection Class =================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Dependencies are injected via CollectionDeps which is set by the IIFE after initialization.
+
+/**
+ * Dependency container for Collection class. Populated by the IIFE during initialization.
+ *
+ * @type {{
+ * _: ParserType;
+ * block: (setting: string, f: Function, opt?: boolean, obj?: unknown) => unknown;
+ * }}
+ */
+const CollectionDeps = {
+ get _() {
+ return CoreDeps.parser;
+ },
+ get block() {
+ return CoreDeps.utils.block;
+ },
+};
+
+/**
+ * Class used to collect arguments for functions
+ *
+ * @implements {CollectionType}
+ */
+class Collection {
+ /** @type {NerdamerSymbolType[]} */
+ elements = [];
+
+ /** @param {NerdamerSymbolType} [e] */
+ constructor(e) {
+ if (e) {
+ this.elements.push(e);
+ }
+ }
+
+ /**
+ * @param {NerdamerSymbolType} [e]
+ * @returns {CollectionType}
+ */
+ static create(e) {
+ return new Collection(e);
+ }
+
+ /** @param {NerdamerSymbolType} e */
+ append(e) {
+ this.elements.push(e);
+ }
+
+ /** @returns {NerdamerSymbolType[]} */
+ getItems() {
+ return this.elements;
+ }
+
+ /** @returns {string} */
+ toString() {
+ return CollectionDeps._.prettyPrint(this.elements);
+ }
+
+ /** @returns {number} */
+ dimensions() {
+ return this.elements.length;
+ }
+
+ /**
+ * @param {string} [options]
+ * @returns {string}
+ */
+ text(options) {
+ return `(${this.elements.map(e => e.text(options)).join(',')})`;
+ }
+
+ /** @returns {CollectionType} */
+ clone() {
+ const c = Collection.create();
+ c.elements = this.elements.map(e => e.clone());
+ return c;
+ }
+
+ /**
+ * @param {ExpandOptions} [options]
+ * @returns {this}
+ */
+ expand(options) {
+ this.elements = /** @type {NerdamerSymbolType[]} */ (
+ this.elements.map(e => CollectionDeps._.expand(e, options))
+ );
+ return this;
+ }
+
+ /**
+ * @param {Record<string, ExpressionParam>} [options]
+ * @returns {this}
+ */
+ evaluate(options) {
+ this.elements = /** @type {NerdamerSymbolType[]} */ (
+ this.elements.map(e => CollectionDeps._.evaluate(e, options))
+ );
+ return this;
+ }
+
+ /**
+ * @param {Function} lambda
+ * @returns {CollectionType}
+ */
+ map(lambda) {
+ const c2 = this.clone();
+ c2.elements = c2.elements.map((x, i) => lambda(x, i + 1));
+ return c2;
+ }
+
+ /**
+ * Returns the result of adding the argument to the vector
+ *
+ * @param {CollectionType} c2
+ * @returns {CollectionType | null}
+ */
+ add(c2) {
+ return /** @type {CollectionType | null} */ (
+ CollectionDeps.block(
+ 'SAFE',
+ () => {
+ const V = c2.elements;
+ if (this.elements.length !== V.length) {
+ return null;
+ }
+ return this.map((x, i) => CollectionDeps._.add(x, V[i - 1]));
+ },
+ undefined,
+ this
+ )
+ );
+ }
+
+ /**
+ * Returns the result of subtracting the argument from the vector
+ *
+ * @param {CollectionType} vector
+ * @returns {CollectionType | null}
+ */
+ subtract(vector) {
+ return /** @type {CollectionType | null} */ (
+ CollectionDeps.block(
+ 'SAFE',
+ () => {
+ const V = vector.elements;
+ if (this.elements.length !== V.length) {
+ return null;
+ }
+ return this.map((x, i) => CollectionDeps._.subtract(x, V[i - 1]));
+ },
+ undefined,
+ this
+ )
+ );
+ }
+}
+
+// Assign Collection to CoreDeps immediately
+CoreDeps.classes.Collection = Collection;
+
+// Scientific Class =================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Dependencies are injected via ScientificDeps which is set by the IIFE after initialization.
+
+/**
+ * Dependency container for Scientific class. Populated by the IIFE during initialization.
+ *
+ * @type {{
+ * Settings: SettingsType;
+ * nround: Function;
+ * }}
+ */
+const ScientificDeps = {
+ get Settings() {
+ return CoreDeps.settings;
+ },
+ get nround() {
+ return CoreDeps.utils.nround;
+ },
+};
+
+/*
+ * Javascript has the toExponential method but this allows you to work with string and therefore any number of digits of your choosing
+ * For example Scientific('464589498449496467924197545625247695464569568959124568489548454');
+ */
+class Scientific {
+ /** @type {number} */
+ sign;
+
+ /** @type {string} */
+ coeff;
+
+ /** @type {number} */
+ exponent;
+
+ /** @type {string} */
+ wholes;
+
+ /** @type {string} */
+ dec;
+
+ /** @type {number} */
+ decp;
+
+ /** @param {string | number} [num] */
+ constructor(num) {
+ num = String(typeof num === 'undefined' ? 0 : num); // Convert to a string
+
+ // remove the sign
+ if (num.startsWith('-')) {
+ this.sign = -1;
+ // Remove the sign
+ num = num.substr(1, num.length);
+ } else {
+ this.sign = 1;
+ }
+
+ if (Scientific.isScientific(num)) {
+ this.fromScientific(num);
+ } else {
+ this.convert(num);
+ }
+ }
+
+ /** @param {string} num */
+ static isScientific(num) {
+ return isScientificNotation(num);
+ }
+
+ /** @param {string} num */
+ static leadingZeroes(num) {
+ const match = num.match(/^(?<zeros>0*).*$/u);
+ return match ? match[1] : '';
+ }
+
+ /** @param {string} num */
+ static removeLeadingZeroes(num) {
+ const match = num.match(/^0*(?<rest>.*)$/u);
+ return match ? match[1] : '';
+ }
+
+ /** @param {string} num */
+ static removeTrailingZeroes(num) {
+ const match = num.match(/0*$/u);
+ return match ? num.substring(0, num.length - match[0].length) : '';
+ }
+
+ /**
+ * @param {string} c
+ * @param {number} n
+ */
+ static round(c, n) {
+ let coeff = String(ScientificDeps.nround(c, n));
+ const m = coeff.includes('.') ? coeff.split('.').pop() : '';
+ const d = n - m.length;
+ // If we're asking for more significant figures
+ if (d > 0) {
+ if (!coeff.includes('.')) {
+ coeff += '.';
+ }
+ coeff += new Array(d + 1).join('0');
+ }
+ return coeff;
+ }
+
+ /**
+ * @param {string} num
+ * @returns {this}
+ */
+ fromScientific(num) {
+ const parts = String(num).toLowerCase().split('e');
+ this.coeff = parts[0];
+ this.exponent = Number(parts[1]); // Convert to number for consistent === 0 checks in toString()
+
+ const coeffParts = this.coeff.split('.');
+ this.wholes = coeffParts[0] || '';
+ this.dec = coeffParts[1] || '';
+ const { dec } = this; // If it's undefined or zero it's going to blank
+ this.decp = dec === '0' ? 0 : dec.length;
+
+ return this;
+ }
+
+ /**
+ * @param {string} num
+ * @returns {this}
+ */
+ convert(num) {
+ // Get wholes and decimals
+ const parts = num.split('.');
+ // Make zero go away
+ let w = parts[0] || '';
+ let d = parts[1] || '';
+ // Convert zero to blank strings
+ w = Scientific.removeLeadingZeroes(w);
+ d = Scientific.removeTrailingZeroes(d);
+ // Find the location of the decimal place which is right after the wholes
+ const dotLocation = w.length;
+ // Add them together so we can move the dot
+ const n = w + d;
+ // Find the next number
+ const zeroes = Scientific.leadingZeroes(n).length;
+ // NerdamerSet the exponent
+ this.exponent = dotLocation - (zeroes + 1);
+ // NerdamerSet the coeff but first remove leading zeroes
+ const coeff = Scientific.removeLeadingZeroes(n);
+ this.coeff = `${coeff.charAt(0)}.${Scientific.removeTrailingZeroes(coeff.substr(1, coeff.length)) || '0'}`;
+
+ // The coeff decimal places
+ const dec = this.coeff.split('.')[1] || ''; // If it's undefined or zero it's going to blank
+
+ this.decp = dec === '0' ? 0 : dec.length;
+ // Decimals
+ this.dec = d;
+ // Wholes
+ this.wholes = w;
+
+ return this;
+ }
+
+ /**
+ * @param {number} num
+ * @returns {Scientific}
+ */
+ round(num) {
+ const n = this.copy();
+
+ num = Number(num); // Cast to number for safety
+ // since we know it guaranteed to be in the format {digit}{optional dot}{optional digits}
+ // we can round based on this
+ if (num === 0) {
+ n.coeff = n.coeff.charAt(0);
+ } else {
+ // Get up to n-1 digits
+ const rounded = this.coeff.substring(0, num + 1);
+ // Get the next two
+ const nextTwo = this.coeff.substring(num + 1, num + 3);
+ // The extra digit
+ let ed = Number(nextTwo.charAt(0));
+
+ if (Number(nextTwo.charAt(1)) > 4) {
+ ed++;
+ }
+
+ n.coeff = rounded + ed;
+ }
+
+ return n;
+ }
+
+ /** @returns {Scientific} */
+ copy() {
+ const n = new Scientific(0);
+ n.coeff = this.coeff;
+ n.exponent = this.exponent;
+ n.sign = this.sign;
+ return n;
+ }
+
+ /**
+ * @param {number} [n]
+ * @returns {string}
+ */
+ toString(n) {
+ let retval;
+
+ if (ScientificDeps.Settings.SCIENTIFIC_IGNORE_ZERO_EXPONENTS && this.exponent === 0 && this.decp < n) {
+ if (this.decp === 0 && this.wholes !== undefined) {
+ retval = this.wholes;
+ } else {
+ retval = this.coeff;
+ }
+ } else {
+ let coeff =
+ typeof n === 'undefined' ? this.coeff : Scientific.round(this.coeff, Math.min(n, this.decp || 1));
+ let exp = this.exponent;
+ if (coeff.startsWith('10.')) {
+ // Edge case when coefficient is 9.999999 rounds to 10
+ coeff =
+ typeof n === 'undefined'
+ ? coeff.replace(/^10\./u, '1.0')
+ : Scientific.round(coeff.replace(/^10\./u, '1.0'), Math.min(n, this.decp || 1));
+ exp = Number(exp) + 1;
+ }
+ retval = this.exponent === 0 ? coeff : `${coeff}e${exp}`;
+ }
+
+ return (this.sign === -1 ? '-' : '') + retval;
+ }
+}
+
+// Assign Scientific to CoreDeps immediately
+CoreDeps.classes.Scientific = Scientific;
+
+// IsArray Function =================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Checks to see if an object is an array
+ *
+ * @param {unknown} arr
+ * @returns {arr is unknown[]}
+ */
+function isArray(arr) {
+ return Array.isArray(arr);
+}
+
+// InBrackets Function ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * @param {unknown} str
+ * @returns {string} - Returns a formatted string surrounded by brackets
+ */
+function inBrackets(str) {
+ return `(${str})`;
+}
+
+// SameSign Function ================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Checks to see if numbers are both negative or are both positive
+ *
+ * @param {number} a
+ * @param {number} b
+ * @returns {boolean}
+ */
+function sameSign(a, b) {
+ return a < 0 === b < 0;
+}
+
+// Format Function ==================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * A helper function to replace multiple occurences in a string. Takes multiple arguments
+ *
+ * @example
+ * format('{0} nice, {0} sweet', 'something');
+ * //returns 'something nice, something sweet'
+ *
+ * @param {...unknown} args
+ * @returns {string}
+ */
+function format(...args) {
+ const str = /** @type {string} */ (args.shift());
+ const newStr = str.replace(/\{(?<idx>\d+)\}/gu, (match, index) => {
+ const arg = args[index];
+ return typeof arg === 'function' ? arg() : arg;
+ });
+
+ return newStr;
+}
+
+// Range Function ===================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Generates an array with values within a range. Multiplies by a step if provided
+ *
+ * @param {number} start
+ * @param {number} end
+ * @param {number} [step]
+ * @returns {number[]}
+ */
+function range(start, end, step) {
+ const arr = [];
+ step ||= 1;
+ for (let i = start; i <= end; i++) {
+ arr.push(i * step);
+ }
+ return arr;
+}
+
+// Stringify Function ===============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Safely stringify object
+ *
+ * @param {unknown} o
+ * @returns {string}
+ */
+function stringify(o) {
+ if (!o) {
+ return '';
+ }
+ return String(o);
+}
+
+// StringReplace Function ===========================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * A helper function to replace parts of string
+ *
+ * @param {string} str - The original string
+ * @param {number} from - The starting index
+ * @param {number} to - The ending index
+ * @param {string} withStr - The replacement string
+ * @returns {string} - A formatted string
+ */
+function stringReplace(str, from, to, withStr) {
+ return str.substr(0, from) + withStr + str.substr(to, str.length);
+}
+
+// CustomType Function ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * The Parser uses this to check if it's allowed to convert the obj to type NerdamerSymbol
+ *
+ * @param {object} obj
+ * @returns {boolean}
+ */
+function customType(obj) {
+ return obj !== undefined && obj.custom;
+}
+
+// ArrayMax Function ================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Returns the maximum number in an array
+ *
+ * @param {number[]} arr
+ * @returns {number}
+ */
+function arrayMax(arr) {
+ return Math.max.apply(undefined, arr);
+}
+
+// ArrayMin Function ================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Returns the minimum number in an array
+ *
+ * @param {number[]} arr
+ * @returns {number}
+ */
+function arrayMin(arr) {
+ return Math.min.apply(undefined, arr);
+}
+
+// Even Function ====================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Checks to see if a number is an even number
+ *
+ * @param {number | string | FracType | { valueOf(): number | string | DecimalType }} num
+ * @returns {boolean}
+ */
+function even(num) {
+ return Number(num) % 2 === 0;
+}
+
+// EvenFraction Function ============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Checks to see if a fraction is divisible by 2
+ *
+ * @param {number} num
+ * @returns {boolean}
+ */
+function evenFraction(num) {
+ return (1 / (num % 1)) % 2 === 0;
+}
+
+// ArrayUnique Function =============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Strips duplicates out of an array
+ *
+ * @template T
+ * @param {T[]} arr
+ * @returns {T[]}
+ */
+function arrayUnique(arr) {
+ const l = arr.length;
+ const a = [];
+ for (let i = 0; i < l; i++) {
+ const item = arr[i];
+ if (a.indexOf(item) === -1) {
+ a.push(item);
+ }
+ }
+ return a;
+}
+
+// Arguments2Array Function =========================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Converts function arguments to an array. Now used by gcd and lcm in Algebra.js :)
+ *
+ * @param {Parameters<typeof Array.prototype.slice.call>['0']} obj
+ * @returns {unknown[]}
+ */
+function arguments2Array(obj) {
+ return [].slice.call(obj);
+}
+
+// IsInt Function ===================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Checks to see if a number is an integer
+ *
+ * @param {number | string | { toString(): string }} num
+ * @returns {boolean}
+ */
+function isInt(num) {
+ if (typeof num === 'number') {
+ return Number.isInteger(num);
+ }
+ return typeof num !== 'undefined' && /^[-+]?\d+e?\+?\d*$/gimu.test(num.toString());
+}
+
+// ArrayEqual Function ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Checks to see if two arrays are equal
+ *
+ * @template T
+ * @param {T[]} arr1
+ * @param {T[]} arr2
+ * @returns {boolean}
+ */
+function arrayEqual(arr1, arr2) {
+ arr1.sort();
+ arr2.sort();
+
+ // The must be of the same length
+ if (arr1.length === arr2.length) {
+ for (let i = 0; i < arr1.length; i++) {
+ // If any two items don't match we're done
+ if (arr1[i] !== arr2[i]) {
+ return false;
+ }
+ }
+ // Otherwise they're equal
+ return true;
+ }
+
+ return false;
+}
+
+// ArrayClone Function ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Clones array with clonable items
+ *
+ * @template T
+ * @param {T[]} arr
+ * @returns {T[]}
+ */
+function arrayClone(arr) {
+ const newArray = [];
+ const l = arr.length;
+ for (let i = 0; i < l; i++) {
+ newArray[i] = /** @type {{ clone: () => unknown }} */ (arr[i]).clone();
+ }
+ return /** @type {T[]} */ (newArray);
+}
+
+// IsNumber Function ================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Checks if n is a number
+ *
+ * @param {string | number} n
+ * @returns {boolean}
+ */
+function isNumber(n) {
+ return /^\d+\.?\d*$/u.test(String(n));
+}
+
+// Nround Function ==================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Rounds a number up to x decimal places
+ *
+ * @param {number | string} x
+ * @param {number} [s]
+ * @returns {number | string}
+ */
+function nround(x, s = 14) {
+ if (isInt(x)) {
+ if (Number(x) >= Number.MAX_VALUE) {
+ return x.toString();
+ }
+ return Number(x);
+ }
+ return Math.round(/** @type {number} */ (x) * 10 ** s) / 10 ** s;
+}
+
+// ArrayAddSlices Function ==========================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Fills numbers between array values
+ *
+ * @param {number[]} arr
+ * @param {number} [slices]
+ * @returns {number[]}
+ */
+function arrayAddSlices(arr, slices) {
+ slices ||= 20;
+ const retval = [];
+ let c;
+ let delta;
+ let e;
+ retval.push(arr[0]); // Push the beginning
+ for (let i = 0; i < arr.length - 1; i++) {
+ c = arr[i];
+ delta = arr[i + 1] - c; // Get the difference
+ e = delta / slices; // Chop it up in the desired number of slices
+ for (let j = 0; j < slices; j++) {
+ c += e; // Add the mesh to the last slice
+ retval.push(c);
+ }
+ }
+
+ return retval;
+}
+
+// Each Function ====================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Loops through each item in object and calls function with item as param
+ *
+ * @param {object | unknown[]} obj
+ * @param {Function} fn
+ */
+function each(obj, fn) {
+ if (isArray(obj)) {
+ const l = obj.length;
+ for (let i = 0; i < l; i++) {
+ fn.call(obj, i);
+ }
+ } else {
+ for (const x in obj) {
+ if (Object.hasOwn(obj, x)) {
+ fn.call(obj, x);
+ }
+ }
+ }
+}
+
+// Remove Function ==================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Removes an item from either an array or an object. If the object is an array, the index must be specified after the
+ * array. If it's an object then the key must be specified
+ *
+ * @template T
+ * @param {Record<string, T> | T[]} obj
+ * @param {number | string} indexOrKey
+ * @returns {T | undefined}
+ */
+function remove(obj, indexOrKey) {
+ let result;
+ if (isArray(obj)) {
+ result = obj.splice(/** @type {number} */ (indexOrKey), 1)[0];
+ } else {
+ result = obj[indexOrKey];
+ delete obj[indexOrKey];
+ }
+ return result;
+}
+
+// KnownVariable Function ===========================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Generates an object with known variable value for evaluation
+ *
+ * @param {string} variable
+ * @param {string | number | NerdamerSymbolType} value Any stringifyable object
+ * @returns {Record<string, string | number | NerdamerSymbolType>}
+ */
+function knownVariable(variable, value) {
+ /** @type {Record<string, string | number | NerdamerSymbolType>} */
+ const o = {};
+ o[variable] = value;
+ return o;
+}
+
+// AllNumeric Function ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Checks to see if an array contains only numeric values
+ *
+ * @param {(string | number)[]} arr
+ * @returns {boolean}
+ */
+function allNumeric(arr) {
+ for (let i = 0; i < arr.length; i++) {
+ if (!isNumber(arr[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// ScientificToDecimal Function =====================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Convert number from scientific format to decimal format
+ *
+ * @param {number} num
+ * @returns {string}
+ */
+function scientificToDecimal(num) {
+ const nsign = Math.sign(num);
+ // Remove the sign
+ /** @type {number | string} */
+ let n = Math.abs(num);
+ // If the number is in scientific notation remove it
+ if (/\d+\.?\d*e[+-]*\d+/iu.test(String(n))) {
+ const zero = '0';
+ const parts = String(n).toLowerCase().split('e'); // Split into coeff and exponent
+ const e = parts.pop(); // Store the exponential part
+ let l = Math.abs(Number(e)); // Get the number of zeros
+ const sign = Math.sign(Number(e));
+ const coeffArray = parts[0].split('.');
+ if (sign === -1) {
+ // Return "("+parts[0]+"/1"+"0".repeat(l)+")";
+ l -= coeffArray[0].length;
+ if (l < 0) {
+ n = `${coeffArray[0].slice(0, l)}.${coeffArray[0].slice(
+ l
+ )}${coeffArray.length === 2 ? coeffArray[1] : ''}`;
+ } else {
+ n = `${zero}.${new Array(l + 1).join(zero)}${coeffArray.join('')}`;
+ }
+ } else {
+ const dec = coeffArray[1];
+ if (dec) {
+ l -= dec.length;
+ }
+ if (l < 0) {
+ n = `${coeffArray[0] + dec.slice(0, l)}.${dec.slice(l)}`;
+ } else {
+ n = coeffArray.join('') + new Array(l + 1).join(zero);
+ }
+ }
+ }
+
+ return nsign < 0 ? `-${n}` : String(n);
+}
+
+// AllSame Function =============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Checks to see that all items in array are equal using the equals method
+ *
+ * @param {{ equals(other: unknown): boolean }[]} arr
+ * @returns {boolean}
+ */
+function allSame(arr) {
+ const last = arr[0];
+ for (let i = 1, l = arr.length; i < l; i++) {
+ if (!arr[i].equals(last)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// RemoveDuplicates Function =======================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Removes duplicates from an array
+ *
+ * @param {Array} arr
+ * @param {Function} [condition]
+ * @returns {Array}
+ */
+function removeDuplicates(arr, condition) {
+ const conditionType = typeof condition;
+
+ if (conditionType !== 'function') {
+ condition = function (a, b) {
+ return a === b;
+ };
+ }
+
+ const seen = [];
+
+ while (arr.length) {
+ const a = arr[0];
+ // Only one element left so we're done
+ if (arr.length === 1) {
+ seen.push(a);
+ break;
+ }
+ const temp = [];
+ seen.push(a); // We already scanned these
+ for (let i = 1; i < arr.length; i++) {
+ const b = arr[i];
+ // If the number is outside the specified tolerance
+ if (!condition(a, b)) {
+ temp.push(b);
+ }
+ }
+ // Start over with the remainder
+ arr = temp;
+ }
+
+ return seen;
+}
+
+// ComboSort Function ===============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Sorts two arrays together, keeping elements at matching indices paired. Sorts by the first array's values
+ * numerically.
+ *
+ * @template T, U
+ * @param {T[]} a
+ * @param {U[]} b
+ * @returns {[T[], U[]]}
+ */
+function comboSort(a, b) {
+ const l = a.length;
+ /** @type {[T, U][]} */
+ const combined = []; // The linker
+ for (let i = 0; i < a.length; i++) {
+ combined.push([a[i], b[i]]); // Create the map
+ }
+
+ combined.sort((x, y) => Number(x[0]) - Number(y[0]));
+
+ /** @type {T[]} */
+ const na = [];
+ /** @type {U[]} */
+ const nb = [];
+
+ for (let i = 0; i < l; i++) {
+ na.push(combined[i][0]);
+ nb.push(combined[i][1]);
+ }
+
+ return [na, nb];
+}
+
+// IsCollection Function ===============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Checks to see if the object provided is a Collection
+ *
+ * @param {object} obj
+ * @returns {obj is CollectionType}
+ */
+function isCollection(obj) {
+ return obj instanceof Collection;
+}
+
+// IsSet Function ======================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Checks to see if the object provided is a NerdamerSet
+ *
+ * @param {object} obj
+ * @returns {obj is SetType}
+ */
+function isSet(obj) {
+ return obj instanceof NerdamerSet;
+}
+
+// FirstObject Function ================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Returns the first encountered item in an object. Items do not have a fixed order in objects so only use if you need
+ * any first random or if there's only one item in the object
+ *
+ * @template T
+ * @param {Record<string, T>} obj
+ * @param {string} [key] - Return this key as first object
+ * @param {boolean} [both] - Return both key and object
+ * @returns {string | T | { key: string; obj: T }}
+ */
+function firstObject(obj, key, both) {
+ const objKeys = Object.keys(obj);
+ const x = objKeys[0];
+ if (key) {
+ return x;
+ }
+ if (both) {
+ return {
+ key: x,
+ obj: obj[x],
+ };
+ }
+ return obj[x];
+}
+
+// Keys Alias ======================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/** Alias for Object.keys - returns an array of all the keys in an object */
+const { keys } = Object;
+
+// GroupConstantsDeps ==============================================================
+// Accessor for group constants. These constants define the type groups for nerdamer symbols.
+// Uses CoreDeps as the single source of truth.
+
+/**
+ * @type {{
+ * N: number;
+ * P: number;
+ * S: number;
+ * EX: number;
+ * FN: number;
+ * PL: number;
+ * CB: number;
+ * CP: number;
+ * }}
+ */
+const GroupConstantsDeps = {
+ get N() {
+ return CoreDeps.groups.N;
+ },
+ get P() {
+ return CoreDeps.groups.P;
+ },
+ get S() {
+ return CoreDeps.groups.S;
+ },
+ get EX() {
+ return CoreDeps.groups.EX;
+ },
+ get FN() {
+ return CoreDeps.groups.FN;
+ },
+ get PL() {
+ return CoreDeps.groups.PL;
+ },
+ get CB() {
+ return CoreDeps.groups.CB;
+ },
+ get CP() {
+ return CoreDeps.groups.CP;
+ },
+};
+
+// ParserDeps ======================================================================
+// Accessor for Parser dependencies. Uses CoreDeps as the single source of truth.
+
+/**
+ * @type {{
+ * _: ParserType;
+ * N: number;
+ * P: number;
+ * S: number;
+ * EX: number;
+ * FN: number;
+ * PL: number;
+ * CB: number;
+ * CP: number;
+ * SQRT: string;
+ * ABS: string;
+ * FACTORIAL: string;
+ * DOUBLEFACTORIAL: string;
+ * PARENTHESIS: string;
+ * bigDec: DecimalStaticType;
+ * PRIMES: number[];
+ * VARS: Record<string, NerdamerSymbolType>;
+ * }}
+ */
+const ParserDeps = {
+ get _() {
+ return CoreDeps.parser;
+ },
+ get N() {
+ return CoreDeps.groups.N;
+ },
+ get P() {
+ return CoreDeps.groups.P;
+ },
+ get S() {
+ return CoreDeps.groups.S;
+ },
+ get EX() {
+ return CoreDeps.groups.EX;
+ },
+ get FN() {
+ return CoreDeps.groups.FN;
+ },
+ get PL() {
+ return CoreDeps.groups.PL;
+ },
+ get CB() {
+ return CoreDeps.groups.CB;
+ },
+ get CP() {
+ return CoreDeps.groups.CP;
+ },
+ get SQRT() {
+ return CoreDeps.fnNames.SQRT;
+ },
+ get ABS() {
+ return CoreDeps.fnNames.ABS;
+ },
+ get FACTORIAL() {
+ return CoreDeps.fnNames.FACTORIAL;
+ },
+ get DOUBLEFACTORIAL() {
+ return CoreDeps.fnNames.DOUBLEFACTORIAL;
+ },
+ get PARENTHESIS() {
+ return CoreDeps.fnNames.PARENTHESIS;
+ },
+ get bigDec() {
+ return CoreDeps.ext.bigDec;
+ },
+ get PRIMES() {
+ return CoreDeps.ext.PRIMES;
+ },
+ get VARS() {
+ return CoreDeps.state.VARS;
+ },
+};
+
+/**
+ * Checks to see if a symbol is in group N (number) or P (power)
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {boolean}
+ */
+function isNumericSymbol(symbol) {
+ return symbol.group === GroupConstantsDeps.N || symbol.group === GroupConstantsDeps.P;
+}
+
+/**
+ * Checks to see if a symbol is a variable with no multiplier nor power
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {boolean}
+ */
+function isVariableSymbol(symbol) {
+ return symbol.group === GroupConstantsDeps.S && symbol.multiplier.equals(1) && symbol.power.equals(1);
+}
+
+/**
+ * Checks to see if all arguments are numbers
+ *
+ * @param {object} args
+ * @returns {boolean}
+ */
+function allNumbers(args) {
+ for (let i = 0; i < args.length; i++) {
+ if (args[i].group !== GroupConstantsDeps.N) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// ReservedDeps ====================================================================
+// Shared dependency container for RESERVED array access. Populated by the IIFE during initialization.
+
+/**
+ * @type {{
+ * RESERVED: (string | undefined)[];
+ * }}
+ */
+const ReservedDeps = {
+ get RESERVED() {
+ return CoreDeps.state.RESERVED;
+ },
+};
+
+/**
+ * Reserves the names in an object so they cannot be used as function names
+ *
+ * @param {object} obj
+ */
+function reserveNames(obj) {
+ const add = function (item) {
+ if (ReservedDeps.RESERVED.indexOf(item) === -1) {
+ ReservedDeps.RESERVED.push(item);
+ }
+ };
+
+ if (typeof obj === 'string') {
+ add(obj);
+ } else {
+ each(obj, x => {
+ add(x);
+ });
+ }
+}
+
+/**
+ * Clears the u variable so it's no longer reserved
+ *
+ * @param {string} u
+ */
+function clearU(u) {
+ const indx = ReservedDeps.RESERVED.indexOf(u);
+ if (indx !== -1) {
+ ReservedDeps.RESERVED[indx] = undefined;
+ }
+}
+
+// ValidateName Function ===========================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Dependencies are injected via ValidateNameDeps which is set by the IIFE after initialization.
+
+/**
+ * Dependency container for validateName function. Populated by the IIFE during initialization.
+ *
+ * @type {{
+ * ALLOW_CHARS: string[];
+ * VALIDATION_REGEX: RegExp;
+ * }}
+ */
+const ValidateNameDeps = {
+ get ALLOW_CHARS() {
+ return CoreDeps.settings?.ALLOW_CHARS ?? [];
+ },
+ VALIDATION_REGEX: /^[a-z_][a-z\d_]*$/iu,
+};
+
+/**
+ * Enforces rule: "must start with a letter or underscore and can have any number of underscores, letters, and numbers
+ * thereafter."
+ *
+ * @param {string} name The name of the symbol being checked
+ * @param {string} [typ] - The type of symbols that's being validated
+ * @throws {Error} - Throws an exception on fail
+ */
+function validateName(name, typ = 'variable') {
+ if (ValidateNameDeps.ALLOW_CHARS.indexOf(name) !== -1) {
+ return;
+ }
+ const regex = ValidateNameDeps.VALIDATION_REGEX;
+ if (!regex.test(name)) {
+ throw new InvalidVariableNameError(`${name} is not a valid ${typ} name`);
+ }
+}
+
+/**
+ * Checks to see if value is one of nerdamer's reserved names
+ *
+ * @param {string} value
+ * @returns {boolean}
+ */
+function isReserved(value) {
+ return ReservedDeps.RESERVED.indexOf(value) !== -1;
+}
+
+// Warn Function ===================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Dependencies are injected via WarnDeps which is set by the IIFE after initialization.
+
+/**
+ * Dependency container for warn function. Populated by the IIFE during initialization.
+ *
+ * @type {{
+ * WARNINGS: string[];
+ * SHOW_WARNINGS: boolean;
+ * }}
+ */
+const WarnDeps = {
+ get WARNINGS() {
+ return CoreDeps.state.WARNINGS;
+ },
+ get SHOW_WARNINGS() {
+ return !(CoreDeps.settings?.SILENCE_WARNINGS ?? true);
+ },
+};
+
+/**
+ * Used to pass warnings or low severity errors about the library
+ *
+ * @param {string} msg
+ */
+function warn(msg) {
+ WarnDeps.WARNINGS.push(msg);
+ if (WarnDeps.SHOW_WARNINGS && console && console.warn) {
+ console.warn(msg);
+ }
+}
+
+/**
+ * Get nerdamer generated warnings
+ *
+ * @returns {string[]}
+ */
+function getWarnings() {
+ return WarnDeps.WARNINGS;
+}
+
+// NumExpressions Function =======================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses NumExpressionsDeps for dependency injection of EXPRESSIONS array.
+
+/**
+ * Dependency container for numExpressions function. Populated by the IIFE during initialization.
+ *
+ * @type {{
+ * EXPRESSIONS: ExpressionType[];
+ * }}
+ */
+const NumExpressionsDeps = {
+ get EXPRESSIONS() {
+ return CoreDeps.state.EXPRESSIONS;
+ },
+};
+
+/**
+ * Returns the number of equations/expressions currently loaded
+ *
+ * @returns {number}
+ */
+function numExpressions() {
+ return NumExpressionsDeps.EXPRESSIONS.length;
+}
+
+// GetSetting Function ===========================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses GetSettingDeps for dependency injection of Settings object.
+
+/**
+ * Dependency container for getSetting function. Populated by the IIFE during initialization.
+ *
+ * @type {{
+ * Settings: SettingsType;
+ * }}
+ */
+const GetSettingDeps = {
+ get Settings() {
+ return CoreDeps.settings;
+ },
+};
+
+/**
+ * Get the value of a nerdamer setting
+ *
+ * @param {string} setting
+ * @returns {boolean | number | string | object | undefined}
+ */
+function getSetting(setting) {
+ return GetSettingDeps.Settings[setting];
+}
+
+// ValidVarName Function =========================================================
+// Uses ReservedDeps.RESERVED and validateName.
+
+/**
+ * Validates if the provided string is a valid variable name
+ *
+ * @param {string} varname Variable name
+ * @returns {boolean}
+ */
+function validVarName(varname) {
+ try {
+ validateName(varname);
+ return ReservedDeps.RESERVED.indexOf(varname) === -1;
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ return false;
+ }
+}
+
+// Reserved Function =============================================================
+// Uses ReservedDeps.RESERVED.
+
+/**
+ * Returns reserved variable names
+ *
+ * @param {boolean} [asArray] If true, returns as array; otherwise returns comma-separated string
+ * @returns {string | string[]}
+ */
+function reserved(asArray) {
+ if (asArray) {
+ return ReservedDeps.RESERVED;
+ }
+ return ReservedDeps.RESERVED.join(', ');
+}
+
+// Version Function ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses VersionDeps for dependency injection of _version and C (Core object).
+
+/**
+ * Dependency container for version function. Populated by the IIFE during initialization.
+ *
+ * @type {{
+ * _version: string;
+ * C: CoreType | null;
+ * }}
+ */
+const VersionDeps = {
+ get _version() {
+ return CoreDeps.version;
+ },
+ get C() {
+ return CoreDeps.core;
+ },
+};
+
+/**
+ * Get the version of nerdamer or a loaded add-on
+ *
+ * @param {string} [addOn] - The add-on being checked
+ * @returns {string} Returns the version of nerdamer
+ */
+function version(addOn) {
+ if (addOn) {
+ try {
+ return VersionDeps.C[addOn].version;
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ return `No module named ${addOn} found!`;
+ }
+ }
+ return CoreDeps.version;
+}
+
+// GetCore Function ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses VersionDeps.C which is already initialized with the Core object.
+
+/**
+ * Exports the nerdamer core functions and objects
+ *
+ * @returns {object} The Core object
+ */
+function getCore() {
+ return VersionDeps.C;
+}
+
+// Supported Function ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses SupportedDeps.functions which provides access to _.functions.
+
+/**
+ * Dependencies for the supported function. Initialized inside the IIFE.
+ *
+ * @type {{
+ * functions: object | null;
+ * }}
+ */
+const SupportedDeps = {
+ get functions() {
+ return CoreDeps.parser?.functions;
+ },
+};
+
+/**
+ * Returns an array of all supported function names
+ *
+ * @returns {string[]} Array of function names
+ */
+function supported() {
+ return keys(SupportedDeps.functions);
+}
+
+// GetConstant Function ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses GetConstantDeps.CONSTANTS which provides access to _.CONSTANTS.
+
+/**
+ * Dependencies for the getConstant function. Initialized inside the IIFE.
+ *
+ * @type {{
+ * CONSTANTS: object | null;
+ * }}
+ */
+const GetConstantDeps = {
+ get CONSTANTS() {
+ return CoreDeps.parser?.CONSTANTS;
+ },
+};
+
+/**
+ * Returns the value of a previously set constant
+ *
+ * @param {string} constant The name of the constant
+ * @returns {string} The string value of the constant
+ */
+function getConstant(constant) {
+ return String(GetConstantDeps.CONSTANTS[constant]);
+}
+
+// GetVar Function ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses GetVarDeps.VARS which provides access to VARS.
+
+/**
+ * Dependencies for the getVar function. Initialized inside the IIFE.
+ *
+ * @type {{
+ * VARS: Record<string, NerdamerSymbolType>;
+ * }}
+ */
+const GetVarDeps = {
+ get VARS() {
+ return CoreDeps.state.VARS;
+ },
+};
+
+/**
+ * Returns the value of a previously set variable
+ *
+ * @param {string} v The name of the variable
+ * @returns {NerdamerSymbolType | undefined} The value of the variable
+ */
+function getVar(v) {
+ return GetVarDeps.VARS[v];
+}
+
+/**
+ * Returns an object containing all stored variables, optionally formatted.
+ *
+ * @param {string} [output] Output format: 'object' (raw VARS), 'text' (default), or 'latex'
+ * @param {string | string[]} [option] Formatting option passed to text/latex methods
+ * @returns {object} Object with variable names as keys
+ */
+function getVars(output, option) {
+ output ||= 'text';
+ let result = {};
+ if (output === 'object') {
+ result = GetVarDeps.VARS;
+ } else {
+ for (const v in GetVarDeps.VARS) {
+ if (!Object.hasOwn(GetVarDeps.VARS, v)) {
+ continue;
+ }
+ if (output === 'latex') {
+ result[v] = GetVarDeps.VARS[v].latex(option);
+ } else if (output === 'text') {
+ result[v] = GetVarDeps.VARS[v].text(option);
+ }
+ }
+ }
+ return result;
+}
+
+// ConvertToLaTeX Function =======================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses ConvertToLaTeXDeps._ which provides access to the core object.
+
+/**
+ * Dependencies for core wrapper functions. Initialized inside the IIFE.
+ *
+ * @type {{
+ * _: ParserType | null;
+ * C: CoreType | null;
+ * }}
+ */
+const ConvertToLaTeXDeps = {
+ get _() {
+ return CoreDeps.parser;
+ },
+ get C() {
+ return CoreDeps.core;
+ },
+};
+
+/**
+ * Generates LaTeX from expression string
+ *
+ * @param {string} e
+ * @param {object} opt
+ * @returns {string}
+ */
+function convertToLaTeX(e, opt) {
+ return ConvertToLaTeXDeps._.toTeX(e, opt);
+}
+
+/**
+ * Returns the operator object for a given operator string
+ *
+ * @param {string} operator
+ * @returns {{ symbol: string; precedence: number; leftAssoc: boolean; operation?: Function } | undefined}
+ */
+function getOperator(operator) {
+ return ConvertToLaTeXDeps._.getOperator(operator);
+}
+
+/**
+ * Creates an alias for an operator
+ *
+ * @param {string} operator
+ * @param {string} withOperator
+ * @returns {void}
+ */
+function aliasOperator(operator, withOperator) {
+ ConvertToLaTeXDeps._.aliasOperator(operator, withOperator);
+}
+
+/**
+ * Sets an operator
+ *
+ * @param {string | { symbol: string; precedence?: number; leftAssoc?: boolean }} operator
+ * @param {Function} [action]
+ * @param {'over' | 'under'} [shift]
+ * @returns {void}
+ */
+function setOperator(operator, action, shift) {
+ ConvertToLaTeXDeps._.setOperator(operator, action, shift);
+}
+
+/**
+ * Adds a peeker function
+ *
+ * @param {string} name
+ * @param {Function} f
+ * @returns {void}
+ */
+function addPeeker(name, f) {
+ if (ConvertToLaTeXDeps._.peekers[name]) {
+ ConvertToLaTeXDeps._.peekers[name].push(f);
+ }
+}
+
+/**
+ * Removes a peeker function
+ *
+ * @param {string} name
+ * @param {Function} f
+ * @returns {void}
+ */
+function removePeeker(name, f) {
+ const peekers = ConvertToLaTeXDeps._.peekers[name];
+ if (peekers) {
+ const index = peekers.indexOf(f);
+ if (index !== -1) {
+ remove(peekers, index);
+ }
+ }
+}
+
+/**
+ * Returns the tree representation of an expression
+ *
+ * @param {string} expression
+ * @returns {object} Tree node representation
+ */
+function tree(expression) {
+ // The Parser's tree method is overloaded to accept both string and Token[]
+ // TypeScript definition only shows the string version, so we use type assertion
+ const tokens = ConvertToLaTeXDeps._.toRPN(ConvertToLaTeXDeps._.tokenize(expression));
+ // @ts-expect-error - tree method accepts Token[] at runtime but TypeScript types only show string overload
+ return ConvertToLaTeXDeps._.tree(tokens);
+}
+
+/**
+ * Parses an expression string into an array of symbols
+ *
+ * @param {string} e
+ * @returns {NerdamerSymbolType[]}
+ */
+function parse(e) {
+ return String(e)
+ .split(';')
+ .map(x => ConvertToLaTeXDeps._.parse(x));
+}
+
+/**
+ * Converts expression into rpn form
+ *
+ * @param {string} expression
+ * @returns {object[]}
+ */
+function rpn(expression) {
+ return ConvertToLaTeXDeps._.toRPN(ConvertToLaTeXDeps._.tokenize(expression));
+}
+
+/**
+ * Generates an HTML tree representation of an expression
+ *
+ * @param {string} expression
+ * @param {number} [indent]
+ * @returns {string}
+ */
+function htmlTree(expression, indent) {
+ const treeResult = tree(expression);
+
+ return (
+ `<div class="tree">\n` +
+ ` <ul>\n` +
+ ` <li>\n${treeResult.toHTML(3, indent)}\n` +
+ ` </li>\n` +
+ ` </ul>\n` +
+ `</div>`
+ );
+}
+
+/**
+ * Replaces an internal function with a new implementation
+ *
+ * @param {string} name The name of the function to replace
+ * @param {Function} fn A factory function that receives (existingFn, C) and returns the new function
+ * @param {number} [numArgs] Optional number of arguments (defaults to existing function's numArgs)
+ * @returns {void}
+ */
+function replaceFunction(name, fn, numArgs) {
+ const existing = ConvertToLaTeXDeps._.functions[name];
+ const newNumArgs = typeof numArgs === 'undefined' ? existing[1] : numArgs;
+ ConvertToLaTeXDeps._.functions[name] = /** @type {[Function, number] | [Function, number[]]} */ ([
+ fn(existing[0], ConvertToLaTeXDeps.C),
+ newNumArgs,
+ ]);
+}
+
+// Expressions Function ===========================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+
+/**
+ * Dependencies for expressions and related functions. Initialized inside the IIFE.
+ *
+ * @type {{
+ * EXPRESSIONS: ExpressionType[];
+ * USER_FUNCTIONS: string[];
+ * LaTeX: LaTeXInterface;
+ * text: (obj: unknown, option?: string | string[]) => string;
+ * functions: Record<string, [Function, number] | [Function, number[]] | [Function, number, object]>;
+ * Math2: Math2Interface;
+ * _: ParserType | null;
+ * Expression: ExpressionConstructor | null;
+ * }}
+ */
+const ExpressionsDeps = {
+ get EXPRESSIONS() {
+ return CoreDeps.state.EXPRESSIONS;
+ },
+ get USER_FUNCTIONS() {
+ return CoreDeps.state.USER_FUNCTIONS;
+ },
+ get LaTeX() {
+ return CoreDeps.classes.LaTeX;
+ },
+ get text() {
+ return CoreDeps.utils.text;
+ },
+ get functions() {
+ return CoreDeps.parser?.functions ?? {};
+ },
+ get Math2() {
+ return CoreDeps.classes.Math2;
+ },
+ get _() {
+ return CoreDeps.parser;
+ },
+ get Expression() {
+ return CoreDeps.classes.Expression;
+ },
+};
+
+/**
+ * Returns stored expressions as an array or object, optionally in LaTeX format.
+ *
+ * @param {boolean} [asObject] Return as object with 1-based indices as keys
+ * @param {boolean} [asLaTeX] Convert expressions to LaTeX
+ * @param {string | string[]} [option] Formatting option
+ * @returns {Record<number, string> | string[]}
+ */
+function expressions(asObject, asLaTeX, option) {
+ /** @type {Record<number, string> | string[]} */
+ const result = asObject ? {} : [];
+ for (let i = 0; i < ExpressionsDeps.EXPRESSIONS.length; i++) {
+ const eq = asLaTeX
+ ? ExpressionsDeps.LaTeX.latex(ExpressionsDeps.EXPRESSIONS[i], option)
+ : ExpressionsDeps.text(ExpressionsDeps.EXPRESSIONS[i], option);
+ asObject ? (result[i + 1] = eq) : /** @type {string[]} */ (result).push(eq);
+ }
+ return result;
+}
+
+/**
+ * Returns user-defined functions as an array or object.
+ *
+ * @param {boolean} [asObject] Return as object with 1-based indices as keys
+ * @param {string | string[]} [option] Formatting option
+ * @returns {Record<number, string> | string[]}
+ */
+function getFunctions(asObject, option) {
+ const result = asObject ? {} : [];
+ for (let i = 0; i < ExpressionsDeps.USER_FUNCTIONS.length; i++) {
+ let params;
+ let body;
+ const fnName = ExpressionsDeps.USER_FUNCTIONS[i];
+ const fnDef = ExpressionsDeps.functions[fnName][2];
+ if (fnDef) {
+ ({ params, body } = fnDef);
+ } else {
+ const fnString = ExpressionsDeps.Math2[fnName].toString();
+ [, params] = /\((?<params>.*?)\)/u.exec(fnString);
+ params = params.split(',').map(x => x.trim());
+ body = '{JavaScript}';
+ }
+ const fn = `${fnName}(${params.join(', ')})=${body}`;
+ const eq = ExpressionsDeps.text(fn, option);
+ asObject ? (result[i + 1] = eq) : result.push(eq);
+ }
+ return result;
+}
+
+/**
+ * Converts LaTeX to a nerdamer expression. Very basic at the moment - handles subscripts, superscripts, and fractions.
+ *
+ * @param {string} e LaTeX string to convert
+ * @returns {ExpressionType} Expression object
+ */
+function convertFromLaTeX(e) {
+ // Convert x_2a => x_2 a
+ e = e.replace(/_(?<char>[A-Za-z0-9])/gu, (...g) => `${g[0]} `);
+ // Convert x^2 => x^{2}
+ e = e.replace(/\^(?<char>[A-Za-z0-9])/gu, (...g) => `^{${g[1]}}`);
+ // Convert \frac12 => \frac{1}2
+ e = e.replace(/(?<cmd>\\[A-Za-z]+)(?<digit>\d)/gu, (...g) => `${g[1]}{${g[2]}}`);
+ // Convert \frac{1}2 => \frac{1}{2}
+ e = e.replace(/(?<cmd>\\[A-Za-z]+\{.*?\})(?<digit>\d)/gu, (...g) => `${g[1]}{${g[2]}}`);
+ const txt = ExpressionsDeps.LaTeX.parse(ExpressionsDeps._.tokenize(e));
+ return new ExpressionsDeps.Expression(ExpressionsDeps._.parse(txt));
+}
+
+// Chain Functions ===============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// These functions return libExports for method chaining.
+// Uses ChainDeps for dependency injection of libExports, VARS, and helper functions.
+
+/**
+ * Dependencies for chain functions. Initialized inside the IIFE with actual references.
+ *
+ * @type {{
+ * libExports: typeof nerdamer;
+ * VARS: Record<string, NerdamerSymbolType>;
+ * _clearFunctions: () => void;
+ * _initConstants: () => void;
+ * clear: (equationNumber: number | 'all' | 'last' | 'first', keepExpressionsFixed?: boolean) => typeof nerdamer;
+ * }}
+ */
+const ChainDeps = {
+ get libExports() {
+ return CoreDeps.libExports;
+ },
+ get VARS() {
+ return CoreDeps.state.VARS;
+ },
+ get _clearFunctions() {
+ return CoreDeps.utils._clearFunctions;
+ },
+ get _initConstants() {
+ return CoreDeps.parser?.initConstants ?? (() => {});
+ },
+ get clear() {
+ return clear;
+ },
+};
+
+/**
+ * Clears all user-defined variables
+ *
+ * @returns {typeof nerdamer} Returns the nerdamer object for chaining
+ */
+function clearVars() {
+ // Reset VARS to empty object - we need to clear the actual VARS object
+ for (const key in ChainDeps.VARS) {
+ if (Object.hasOwn(ChainDeps.VARS, key)) {
+ delete ChainDeps.VARS[key];
+ }
+ }
+ return ChainDeps.libExports;
+}
+
+/**
+ * Clears all added functions
+ *
+ * @returns {typeof nerdamer} Returns the nerdamer object for chaining
+ */
+function clearFunctions() {
+ ChainDeps._clearFunctions();
+ return ChainDeps.libExports;
+}
+
+/**
+ * Clears all user-defined constants
+ *
+ * BUG: The original implementation used `_.initConstants.bind(_)` which creates a bound function but does NOT call it.
+ * This means clearConstants() is a no-op and doesn't actually clear any constants. The test confirms this bug by
+ * expecting constants to persist after calling clearConstants().
+ *
+ * To actually clear constants, the code should be: `_.initConstants()` or `_.initConstants.call(_)` instead of
+ * `_.initConstants.bind(_)`.
+ *
+ * This bug is preserved for backwards compatibility - fixing it would be a breaking change. See issue #XX (TODO: file
+ * issue).
+ *
+ * @returns {typeof nerdamer} Returns the nerdamer object for chaining
+ */
+function clearConstants() {
+ // Original code was: _.initConstants.bind(_);
+ // This just creates a bound function but doesn't call it - confirmed bug.
+ // Preserving original (buggy) behavior for backwards compatibility.
+ return ChainDeps.libExports;
+}
+
+/**
+ * Alias for nerdamer.clear('all') - clears all stored expressions
+ *
+ * @returns {typeof nerdamer} Returns the nerdamer object for chaining
+ */
+function flush() {
+ ChainDeps.clear(/** @type {'all'} */ ('all'));
+ return ChainDeps.libExports;
+}
+
+/**
+ * Loads a custom loader function with nerdamer as `this` context
+ *
+ * @param {(this: typeof nerdamer) => void} loader - The loader function to call
+ * @returns {typeof nerdamer} Returns the nerdamer object for chaining
+ */
+function load(loader) {
+ loader.call(ChainDeps.libExports);
+ return ChainDeps.libExports;
+}
+
+// SetConstant Function ==========================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses SetConstantDeps for dependency injection.
+
+/**
+ * Dependencies for setConstant function. Initialized inside the IIFE with actual references.
+ *
+ * @type {{
+ * libExports: typeof nerdamer | null;
+ * CONSTANTS: Record<string, NerdamerSymbolType | string | number>;
+ * }}
+ */
+const SetConstantDeps = {
+ get libExports() {
+ return CoreDeps.libExports;
+ },
+ get CONSTANTS() {
+ return CoreDeps.state.CONSTANTS;
+ },
+};
+
+/**
+ * Set the value of a constant
+ *
+ * @param {string} constant - The name of the constant
+ * @param {number | 'delete' | ''} value - The value of the constant or 'delete' to remove
+ * @returns {typeof nerdamer} Returns the nerdamer object for chaining
+ */
+function setConstant(constant, value) {
+ validateName(constant);
+ if (!isReserved(constant)) {
+ // Fix for issue #127
+ if (value === 'delete' || value === '') {
+ delete SetConstantDeps.CONSTANTS[constant];
+ } else {
+ if (isNaN(/** @type {number} */ (value))) {
+ throw new NerdamerTypeError('Constant must be a number!');
+ }
+ SetConstantDeps.CONSTANTS[constant] = value;
+ }
+ }
+ return SetConstantDeps.libExports;
+}
+
+// SetVar Function ===============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses SetVarDeps for dependency injection.
+
+/**
+ * Dependencies for setVar function. Initialized inside the IIFE with actual references.
+ *
+ * @type {{
+ * libExports: typeof nerdamer | null;
+ * VARS: Record<string, NerdamerSymbolType>;
+ * CONSTANTS: Record<string, NerdamerSymbolType | string | number>;
+ * parse: (expression: ExpressionParam) => NerdamerSymbolType;
+ * isSymbol: (obj: unknown) => boolean;
+ * }}
+ */
+const SetVarDeps = {
+ get libExports() {
+ return CoreDeps.libExports;
+ },
+ get VARS() {
+ return CoreDeps.state.VARS;
+ },
+ get CONSTANTS() {
+ return CoreDeps.state.CONSTANTS;
+ },
+ get parse() {
+ const { parser } = CoreDeps;
+ return parser?.parse?.bind(parser) ?? (() => null);
+ },
+ get isSymbol() {
+ return CoreDeps.utils.isSymbol;
+ },
+};
+
+/**
+ * Set the value of a variable
+ *
+ * @param {string} v - Variable to be set
+ * @param {string | number | NerdamerSymbolType | 'delete'} val - Value of variable. This can be a variable expression
+ * or number
+ * @returns {typeof nerdamer} Returns the nerdamer object for chaining
+ */
+function setVar(v, val) {
+ validateName(v);
+ // Check if it's not already a constant
+ if (v in SetVarDeps.CONSTANTS) {
+ err(`Cannot set value for constant ${v}`);
+ }
+ if (val === 'delete' || val === '') {
+ delete SetVarDeps.VARS[v];
+ } else {
+ SetVarDeps.VARS[v] = /** @type {NerdamerSymbolType} */ (SetVarDeps.isSymbol(val) ? val : SetVarDeps.parse(val));
+ }
+ return SetVarDeps.libExports;
+}
+
+// SetFunction Function ==========================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses SetFunctionDeps for dependency injection.
+
+/**
+ * Dependencies for setFunction function. Initialized inside the IIFE with actual references.
+ *
+ * @type {{
+ * libExports: typeof nerdamer | null;
+ * _setFunction: (fnName: string | Function, fnParams?: string[], fnBody?: string) => boolean;
+ * }}
+ */
+const SetFunctionDeps = {
+ get libExports() {
+ return CoreDeps.libExports;
+ },
+ get _setFunction() {
+ return CoreDeps.utils._setFunction;
+ },
+};
+
+/**
+ * Set a custom function
+ *
+ * @example
+ * nerdamer.setFunction('f',['x'], 'x^2+2');
+ * OR nerdamer.setFunction('f(x)=x^2+2');
+ * OR function custom(x , y) {
+ * return x + y;
+ * }
+ * nerdamer.setFunction(custom);
+ *
+ * @param {string | Function} fnName - The name of the function
+ * @param {string[] | undefined} fnParams - A list containing the parameter name of the functions
+ * @param {string | undefined} fnBody - The body of the function
+ * @returns {typeof nerdamer} Returns nerdamer if succeeded and throws on fail
+ */
+function setFunction(fnName, fnParams, fnBody) {
+ if (!SetFunctionDeps._setFunction(fnName, fnParams, fnBody)) {
+ throw new Error('Failed to set function!');
+ }
+ return SetFunctionDeps.libExports;
+}
+
+// Clear Function ================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses ClearDeps for dependency injection.
+
+/**
+ * Dependencies for clear function. Initialized inside the IIFE with actual references.
+ *
+ * @type {{
+ * libExports: NerdamerType;
+ * EXPRESSIONS: ExpressionType[];
+ * }}
+ */
+const ClearDeps = {
+ get libExports() {
+ return CoreDeps.libExports;
+ },
+ get EXPRESSIONS() {
+ return CoreDeps.state.EXPRESSIONS;
+ },
+};
+
+/**
+ * Clear expressions from history
+ *
+ * @param {number | 'all' | 'last' | 'first'} equationNumber - The number of the equation to clear. If 'all' is supplied
+ * then all equations are cleared
+ * @param {boolean} [keepExpressionsFixed] - Use true if you don't want to keep EXPRESSIONS length fixed
+ * @returns {typeof nerdamer} Returns the nerdamer object for chaining
+ */
+function clear(equationNumber, keepExpressionsFixed = false) {
+ if (/** @type {unknown} */ (equationNumber) === 'all') {
+ ClearDeps.EXPRESSIONS.length = 0;
+ } else if (/** @type {unknown} */ (equationNumber) === 'last') {
+ ClearDeps.EXPRESSIONS.pop();
+ } else if (/** @type {unknown} */ (equationNumber) === 'first') {
+ ClearDeps.EXPRESSIONS.shift();
+ } else {
+ const index = equationNumber ? /** @type {number} */ (equationNumber) - 1 : ClearDeps.EXPRESSIONS.length;
+ keepExpressionsFixed === true
+ ? (ClearDeps.EXPRESSIONS[index] = undefined)
+ : remove(ClearDeps.EXPRESSIONS, index);
+ }
+ return ClearDeps.libExports;
+}
+
+// Register Function ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses RegisterDeps for dependency injection.
+
+/**
+ * Dependencies for the register function. Initialized inside the IIFE with actual references.
+ *
+ * @type {{
+ * libExports: typeof nerdamer;
+ * Settings: SettingsType;
+ * functions: Record<string, [Function, number] | [Function, number[]] | [Function, number, object]>;
+ * }}
+ */
+const RegisterDeps = {
+ get libExports() {
+ return CoreDeps.libExports;
+ },
+ get Settings() {
+ return CoreDeps.settings;
+ },
+ get functions() {
+ return CoreDeps.parser?.functions ?? {};
+ },
+};
+
+/**
+ * Register modules/addons with nerdamer
+ *
+ * @param {object | object[]} obj - The addon object or array of addon objects to register
+ * @returns {void}
+ */
+function register(obj) {
+ const core = RegisterDeps.libExports.getCore();
+
+ if (isArray(obj)) {
+ for (let i = 0; i < obj.length; i++) {
+ if (obj) {
+ register(obj[i]);
+ }
+ }
+ } else if (obj && RegisterDeps.Settings.exclude.indexOf(obj.name) === -1) {
+ // Make sure all the dependencies are available
+ if (obj.dependencies) {
+ for (let i = 0; i < obj.dependencies.length; i++) {
+ if (!core[obj.dependencies[i]]) {
+ throw new Error(format('{0} requires {1} to be loaded!', obj.name, obj.dependencies[i]));
+ }
+ }
+ }
+ // If no parent object is provided then the function does not have an address and cannot be called directly
+ const parentObj = obj.parent;
+ const fn = obj.build.call(core); // Call constructor to get function
+ if (parentObj) {
+ if (!core[parentObj]) {
+ core[obj.parent] = {};
+ }
+
+ const refObj = parentObj === 'nerdamer' ? RegisterDeps.libExports : core[parentObj];
+ // Attach the function to the core
+ refObj[obj.name] = fn;
+ }
+ if (obj.visible) {
+ RegisterDeps.functions[obj.name] = [fn, obj.numargs];
+ } // Make the function available
+ }
+}
+
+// Set Function ==================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses SettingsDeps for dependency injection.
+
+/**
+ * Dependencies for the set function. Initialized inside the IIFE with actual references.
+ *
+ * @type {{
+ * bigDec: DecimalStaticType;
+ * Settings: SettingsType;
+ * functions: Record<string, Function | [Function, number] | [Function, number[]] | [Function, number, object]>;
+ * symfunction: Function;
+ * NerdamerSymbol: SymbolConstructor;
+ * }}
+ */
+const SettingsDeps = {
+ get bigDec() {
+ return CoreDeps.ext.bigDec;
+ },
+ get Settings() {
+ return CoreDeps.settings;
+ },
+ get functions() {
+ return CoreDeps.parser?.functions ?? {};
+ },
+ get symfunction() {
+ return CoreDeps.utils.symfunction;
+ },
+ get NerdamerSymbol() {
+ return CoreDeps.classes.NerdamerSymbol;
+ },
+};
+
+/**
+ * Set the value of a setting
+ *
+ * @param {string | object} setting - The setting to be changed
+ * @param {boolean | number | string} [value] - The value to set
+ * @returns {void}
+ */
+function set(setting, value) {
+ // Current options:
+ // PARSE2NUMBER, suppress_errors
+ if (typeof setting === 'object' && setting !== null) {
+ const settingObj = /** @type {Partial<SettingsType>} */ (setting);
+ for (const x in settingObj) {
+ if (!Object.hasOwn(settingObj, x)) {
+ continue;
+ }
+ set(x, settingObj[x]);
+ }
+ }
+
+ const disallowed = ['SAFE'];
+ if (disallowed.indexOf(/** @type {string} */ (setting)) !== -1) {
+ err(`Cannot modify setting: ${setting}`);
+ }
+
+ if (setting === 'PRECISION') {
+ // @ts-expect-error - bigDec.set precision accepts number | string at runtime
+ SettingsDeps.bigDec.set({ precision: value });
+ SettingsDeps.Settings.PRECISION = /** @type {number} */ (value);
+
+ // Avoid that nerdamer puts out garbage after 21 decimal place
+ if (/** @type {number} */ (value) > 21) {
+ set('USE_BIG', true);
+ }
+ } else if (setting === 'USE_LN' && value === true) {
+ // Set log as LN
+ SettingsDeps.Settings.LOG = 'LN';
+ // Set log10 as log
+ SettingsDeps.Settings.LOG10 = 'log';
+ // Point the functions in the right direction
+ SettingsDeps.functions.log = SettingsDeps.Settings.LOG_FNS.log10; // Log is now log10
+ // the log10 function must be explicitly set
+ SettingsDeps.functions.log[0] = function log10Wrapper(x) {
+ if (x.isConstant()) {
+ return new SettingsDeps.NerdamerSymbol(Math.log10(x));
+ }
+ return SettingsDeps.symfunction(SettingsDeps.Settings.LOG10, [x]);
+ };
+ SettingsDeps.functions.LN = SettingsDeps.Settings.LOG_FNS.log; // LN is now log
+
+ // remove log10
+ delete SettingsDeps.functions.log10;
+ } else {
+ SettingsDeps.Settings[/** @type {string} */ (setting)] = value;
+ }
+}
+
+// UpdateAPI Function ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses UpdateAPIDeps for dependency injection.
+
+/**
+ * Dependencies for the updateAPI function. Initialized inside the IIFE with actual references.
+ *
+ * @type {{
+ * libExports: NerdamerType;
+ * functions: Record<string, [Function, number] | [Function, number[]] | [Function, number, object]>;
+ * parse: (expression: ExpressionParam) => NerdamerSymbolType;
+ * callfunction: Function;
+ * Expression: ExpressionConstructor;
+ * }}
+ */
+const UpdateAPIDeps = {
+ get libExports() {
+ return CoreDeps.libExports;
+ },
+ get functions() {
+ return CoreDeps.parser?.functions ?? {};
+ },
+ get parse() {
+ const { parser } = CoreDeps;
+ return parser?.parse?.bind(parser) ?? (() => null);
+ },
+ get callfunction() {
+ return CoreDeps.utils.callfunction;
+ },
+ get Expression() {
+ return CoreDeps.classes.Expression;
+ },
+};
+
+/**
+ * Makes internal functions available externally
+ *
+ * @param {boolean} [override] - Override the functions when calling updateAPI if it exists
+ * @returns {void}
+ */
+function updateAPI(override = false) {
+ // Map internal functions to external ones
+ const linker = function linker(fname) {
+ return function linkedFunction(...args) {
+ for (let i = 0; i < args.length; i++) {
+ args[i] = UpdateAPIDeps.parse(args[i]);
+ }
+ return new UpdateAPIDeps.Expression(block('PARSE2NUMBER', () => UpdateAPIDeps.callfunction(fname, args)));
+ };
+ };
+ // Perform the mapping
+ for (const x in UpdateAPIDeps.functions) {
+ if (!(x in UpdateAPIDeps.libExports) || override) {
+ UpdateAPIDeps.libExports[x] = linker(x);
+ }
+ }
+}
+
+// Err Function ==================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses ErrDeps for dependency injection of suppress_errors setting.
+
+/**
+ * Dependencies for the err function. Initialized inside the IIFE with actual Settings values.
+ *
+ * @type {{
+ * suppress_errors: boolean;
+ * }}
+ */
+const ErrDeps = {
+ get suppress_errors() {
+ return CoreDeps.settings?.suppress_errors ?? false;
+ },
+};
+
+/**
+ * Use this when errors are suppressible
+ *
+ * @param {string} msg
+ * @param {new (message?: string) => Error} [ErrorObj]
+ */
+function err(msg, ErrorObj = undefined) {
+ if (!ErrDeps.suppress_errors) {
+ if (ErrorObj) {
+ throw new ErrorObj(msg);
+ } else {
+ throw new Error(msg);
+ }
+ }
+}
+
+// IsPrime Function =============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses IsPrimeDeps for dependency injection of PRIMES_SET.
+
+/**
+ * Dependencies for the isPrime function. Initialized inside the IIFE with actual PRIMES_SET reference.
+ *
+ * @type {{
+ * PRIMES_SET: Record<number, boolean>;
+ * }}
+ */
+const IsPrimeDeps = {
+ get PRIMES_SET() {
+ return CoreDeps.ext.PRIMES_SET;
+ },
+};
+
+/**
+ * Checks if number is a prime number
+ *
+ * @param {number} n - The number to be checked
+ * @returns {boolean}
+ */
+function isPrime(n) {
+ if (n in IsPrimeDeps.PRIMES_SET) {
+ return true;
+ }
+ const q = Math.floor(Math.sqrt(n));
+ for (let i = 2; i <= q; i++) {
+ if (n % i === 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Timeout Functions ===============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses TimeoutDeps for shared state and dependency injection of Settings.TIMEOUT.
+
+/**
+ * Dependencies for the timeout functions. Contains shared state and is initialized inside the IIFE.
+ *
+ * @type {{
+ * starttime: number;
+ * timeout: number;
+ * TIMEOUT: number;
+ * }}
+ */
+const TimeoutDeps = {
+ starttime: 0,
+ timeout: 0,
+ get TIMEOUT() {
+ return CoreDeps.settings?.TIMEOUT ?? 800;
+ },
+};
+
+/** Arms the timeout mechanism with current time and timeout setting */
+function armTimeout() {
+ TimeoutDeps.starttime = Date.now();
+ TimeoutDeps.timeout = TimeoutDeps.TIMEOUT;
+}
+
+/** Disarms the timeout mechanism */
+function disarmTimeout() {
+ TimeoutDeps.starttime = 0;
+}
+
+/**
+ * Checks if timeout has been exceeded and throws if so
+ *
+ * @throws {Error} If timeout has been exceeded
+ */
+function checkTimeout() {
+ if (TimeoutDeps.starttime !== 0 && Date.now() > TimeoutDeps.starttime + TimeoutDeps.timeout) {
+ throw new Error('timeout');
+ }
+}
+
+// PrimeFactors Function ============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses PrimeFactorsDeps for dependency injection of PRIMES and PRIMES_SET.
+
+/**
+ * Dependencies for the primeFactors function. Initialized inside the IIFE.
+ *
+ * @type {{
+ * PRIMES: number[];
+ * PRIMES_SET: Record<number, boolean>;
+ * }}
+ */
+const PrimeFactorsDeps = {
+ get PRIMES() {
+ return CoreDeps.ext.PRIMES;
+ },
+ get PRIMES_SET() {
+ return CoreDeps.ext.PRIMES_SET;
+ },
+};
+
+/**
+ * Calculates prime factors for a number. It first checks if the number is a prime number. If it's not then it will
+ * calculate all the primes for that number.
+ *
+ * @param {number} num
+ * @returns {number[]}
+ */
+function primeFactors(num) {
+ checkTimeout();
+
+ if (isPrime(num)) {
+ return [num];
+ }
+
+ let l = num;
+ let i = 1;
+ const factors = [];
+ const epsilon = 2.2204460492503130808472633361816e-16;
+ while (i < l) {
+ checkTimeout();
+ const quotient = num / i;
+ const whole = Math.floor(quotient);
+ const remainder = quotient - whole;
+
+ if (remainder <= epsilon && i > 1) {
+ // If the prime wasn't found but calculated then save it and
+ // add it as a factor.
+ if (isPrime(i)) {
+ if (!PrimeFactorsDeps.PRIMES_SET[i]) {
+ PrimeFactorsDeps.PRIMES.push(i);
+ PrimeFactorsDeps.PRIMES_SET[i] = true;
+ }
+ factors.push(i);
+ }
+
+ // Check if the remainder is a prime
+ if (isPrime(whole)) {
+ factors.push(whole);
+ break;
+ }
+
+ l = whole;
+ }
+ i++;
+ }
+
+ return factors.sort((a, b) => a - b);
+}
+
+/**
+ * Generates prime numbers up to a specified number
+ *
+ * @param {number} upto
+ */
+function generatePrimes(upto) {
+ // Get the last prime in the array
+ const lastPrime = PrimeFactorsDeps.PRIMES[PrimeFactorsDeps.PRIMES.length - 1] || 2;
+ // No need to check if we've already encountered the number. Just check the cache.
+ for (let i = lastPrime; i < upto; i++) {
+ if (isPrime(i)) {
+ PrimeFactorsDeps.PRIMES.push(i);
+ }
+ PrimeFactorsDeps.PRIMES_SET[i] = true;
+ }
+}
+
+// Block Function ================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Dependencies are injected via BlockDeps which is set by the IIFE after initialization.
+
+/**
+ * Dependency container for block function. Populated by the IIFE during initialization.
+ *
+ * @type {{
+ * Settings: SettingsType;
+ * }}
+ */
+const BlockDeps = {
+ get Settings() {
+ return CoreDeps.settings;
+ },
+};
+
+/**
+ * Creates a temporary block in which one of the global settings is temporarily modified while the function is called.
+ * For instance if you want to parse directly to a number rather than have a symbolic answer for a period you would set
+ * PARSE2NUMBER to true in the block.
+ *
+ * @example
+ * block('PARSE2NUMBER', function(){//symbol being parsed to number}, true);
+ *
+ * @template T
+ * @param {string} setting - The setting being accessed
+ * @param {() => T} f
+ * @param {boolean} [opt] - The value of the setting in the block
+ * @param {unknown} [obj] - The obj of interest. Usually a NerdamerSymbol but could be any object
+ * @returns {T}
+ */
+function block(setting, f, opt = undefined, obj = undefined) {
+ const currentSetting = BlockDeps.Settings[setting];
+ BlockDeps.Settings[setting] = opt === undefined ? true : !!opt;
+ const retval = f.call(obj);
+ BlockDeps.Settings[setting] = currentSetting;
+ return retval;
+}
+
+// Evaluate Function ================================================================
+// Uses ParserDeps._ for parser access.
+
+/**
+ * As the name states. It forces evaluation of the expression
+ *
+ * @param {string | NerdamerSymbolType} symbol
+ * @param {Record<string, string | number | NerdamerSymbolType>} [o]
+ * @returns {NerdamerSymbolType}
+ */
+function evaluate(symbol, o = undefined) {
+ return block('PARSE2NUMBER', () => ParserDeps._.parse(symbol, o), true);
+}
+
+// Expression Class =================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Dependencies are accessed via CoreDeps for centralized management.
+
+/**
+ * Dependency accessor for Expression class. Uses CoreDeps as the single source of truth.
+ *
+ * @type {{
+ * EXPRESSIONS: ExpressionType[];
+ * Settings: SettingsType & { precision?: number };
+ * LaTeX: LaTeXInterface;
+ * text: Function;
+ * variables: Function;
+ * isVector: (x: unknown) => boolean;
+ * isSymbol: (x: unknown) => boolean;
+ * isExpression: (x: unknown) => boolean;
+ * isNumericSymbol: (x: unknown) => boolean;
+ * isFraction: (x: unknown) => boolean;
+ * isArray: (x: unknown) => boolean;
+ * _: ParserType;
+ * Build: BuildInterface;
+ * }}
+ */
+const ExpressionDeps = {
+ get EXPRESSIONS() {
+ return CoreDeps.state.EXPRESSIONS;
+ },
+ get Settings() {
+ return CoreDeps.settings;
+ },
+ get LaTeX() {
+ return CoreDeps.classes.LaTeX;
+ },
+ get text() {
+ return CoreDeps.utils.text;
+ },
+ get variables() {
+ return CoreDeps.utils.variables;
+ },
+ get isVector() {
+ return CoreDeps.utils.isVector;
+ },
+ get isSymbol() {
+ return CoreDeps.utils.isSymbol;
+ },
+ get isExpression() {
+ return CoreDeps.utils.isExpression;
+ },
+ get isNumericSymbol() {
+ return CoreDeps.utils.isNumericSymbol;
+ },
+ get isFraction() {
+ return CoreDeps.utils.isFraction;
+ },
+ get isArray() {
+ return CoreDeps.utils.isArray;
+ },
+ get _() {
+ return CoreDeps.parser;
+ },
+ get Build() {
+ return CoreDeps.classes.Build;
+ },
+};
+
+/**
+ * Wraps a symbol in an Expression for user-facing API.
+ *
+ * @implements {ExpressionType}
+ */
+class Expression {
+ /** @type {NerdamerSymbolType} */
+ symbol;
+
+ /** @param {NerdamerSymbolType} symbol */
+ constructor(symbol) {
+ // We don't want arrays wrapped
+ this.symbol = symbol;
+ }
+
+ /**
+ * Returns stored expression at index. For first index use 1 not 0.
+ *
+ * @param {number | string} expressionNumber
+ * @param {boolean} [_asType]
+ */
+ static getExpression(expressionNumber, _asType = undefined) {
+ if (expressionNumber === 'last' || !expressionNumber) {
+ expressionNumber = ExpressionDeps.EXPRESSIONS.length;
+ }
+ if (expressionNumber === 'first') {
+ expressionNumber = 1;
+ }
+ const index = Number(expressionNumber) - 1;
+ const expression = ExpressionDeps.EXPRESSIONS[index];
+ const retval = expression ? new Expression(/** @type {NerdamerSymbolType} */ (expression.symbol)) : expression;
+ return retval;
+ }
+
+ /**
+ * Returns the text representation of the expression
+ *
+ * @param {string} [opt] - Option of formatting numbers
+ * @param {number} [n] The number of significant figures
+ * @returns {string}
+ */
+ text(opt = 'decimals', n = undefined) {
+ n ||= ExpressionDeps.Settings.EXPRESSION_DECP;
+ const sym = /** @type {NerdamerSymbolType} */ (this.symbol);
+ if (sym.text_) {
+ return sym.text_(opt);
+ }
+
+ return ExpressionDeps.text(this.symbol, opt, undefined, n);
+ }
+
+ /**
+ * Returns the latex representation of the expression
+ *
+ * @param {OutputType} option - Option for formatting numbers
+ * @returns {string}
+ */
+ latex(option) {
+ if (this.symbol.latex) {
+ return this.symbol.latex(option);
+ }
+ return ExpressionDeps.LaTeX.latex(this.symbol, option);
+ }
+
+ /** @returns {number | string | DecimalType} */
+ valueOf() {
+ return this.symbol.valueOf();
+ }
+
+ /**
+ * Evaluates the expression and tries to reduce it to a number if possible. If an argument is given in the form of
+ * %{integer} it will evaluate that expression. Other than that it will just use it's own text and reparse
+ *
+ * @returns {ExpressionType}
+ */
+ evaluate(...args) {
+ // Don't evaluate an empty vector
+ if (
+ ExpressionDeps.isVector(this.symbol) &&
+ /** @type {VectorType} */ (/** @type {unknown} */ (this.symbol)).dimensions() === 0
+ ) {
+ return this;
+ }
+
+ const firstArg = args[0];
+ let expression;
+ let idx = 1;
+
+ // Enable getting of expressions using the % so for example %1 should get the first expression
+ if (typeof firstArg === 'string') {
+ // TODO Replace substr with slice, and test it
+ expression = firstArg.charAt(0) === '%' ? Expression.getExpression(firstArg.substr(1)).text() : firstArg;
+ } else if (firstArg instanceof Expression || ExpressionDeps.isSymbol(firstArg)) {
+ expression = firstArg.text();
+ } else {
+ expression = this.symbol.text();
+ idx--;
+ }
+
+ const subs = args[idx] || {};
+
+ const retval = new Expression(block('PARSE2NUMBER', () => ExpressionDeps._.parse(expression, subs), true));
+
+ return retval;
+ }
+
+ /**
+ * Converts a symbol to a JS function. Pass in an array of variables to use that order instead of the default
+ * alphabetical order
+ *
+ * @param {string[]} vars
+ * @returns {(...args: number[]) => number}
+ */
+ buildFunction(vars) {
+ return /** @type {(...args: number[]) => number} */ (ExpressionDeps.Build.build(this.symbol, vars));
+ }
+
+ /**
+ * Checks to see if the expression is just a plain old number
+ *
+ * @returns {boolean}
+ */
+ isNumber() {
+ return ExpressionDeps.isNumericSymbol(this.symbol);
+ }
+
+ /**
+ * Checks to see if the expression is infinity
+ *
+ * @returns {boolean}
+ */
+ isInfinity() {
+ return Math.abs(/** @type {number} */ (this.symbol.multiplier.valueOf())) === Infinity;
+ }
+
+ /**
+ * Checks to see if the expression contains imaginary numbers
+ *
+ * @returns {boolean}
+ */
+ isImaginary() {
+ return evaluate(ExpressionDeps._.parse(this.symbol)).isImaginary();
+ }
+
+ /**
+ * Returns all the variables in the expression
+ *
+ * @returns {Array}
+ */
+ variables() {
+ return ExpressionDeps.variables(this.symbol);
+ }
+
+ /** @returns {string} */
+ toString() {
+ try {
+ if (ExpressionDeps.isArray(this.symbol)) {
+ return `[${this.symbol.toString()}]`;
+ }
+ return this.symbol.toString();
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ return '';
+ }
+ }
+
+ /**
+ * Forces the symbol to be returned as a decimal
+ *
+ * @param {number} [prec]
+ * @returns {string}
+ */
+ toDecimal(prec) {
+ ExpressionDeps.Settings.precision = prec;
+ const dec = ExpressionDeps.text(this.symbol, 'decimals');
+ ExpressionDeps.Settings.precision = undefined;
+ return dec;
+ }
+
+ /**
+ * Checks to see if the expression is a fraction
+ *
+ * @returns {boolean}
+ */
+ isFraction() {
+ return ExpressionDeps.isFraction(this.symbol);
+ }
+
+ /**
+ * Checks to see if the symbol is a multivariate polynomial
+ *
+ * @returns {boolean}
+ */
+ isPolynomial() {
+ return this.symbol.isPoly();
+ }
+
+ /**
+ * Performs a substitution
+ *
+ * @param {string | NerdamerSymbolType} symbol
+ * @param {string | number | NerdamerSymbolType} forSymbol
+ * @returns {ExpressionType}
+ */
+ sub(symbol, forSymbol) {
+ return new Expression(this.symbol.sub(ExpressionDeps._.parse(symbol), ExpressionDeps._.parse(forSymbol)));
+ }
+
+ /**
+ * @param {string} otype
+ * @param {string | number | NerdamerSymbolType | ExpressionType} symbol
+ * @returns {ExpressionType}
+ */
+ operation(otype, symbol) {
+ /** @type {NerdamerSymbolType} */
+ let sym;
+ if (ExpressionDeps.isExpression(symbol)) {
+ sym = /** @type {ExpressionType} */ (symbol).symbol;
+ } else if (ExpressionDeps.isSymbol(symbol)) {
+ sym = /** @type {NerdamerSymbolType} */ (symbol);
+ } else {
+ sym = ExpressionDeps._.parse(/** @type {string | number} */ (symbol));
+ }
+ return new Expression(ExpressionDeps._[otype](this.symbol.clone(), sym.clone()));
+ }
+
+ /**
+ * @param {string | number | NerdamerSymbolType | ExpressionType} symbol
+ * @returns {ExpressionType}
+ */
+ add(symbol) {
+ return this.operation('add', symbol);
+ }
+
+ /**
+ * @param {string | number | NerdamerSymbolType | ExpressionType} symbol
+ * @returns {ExpressionType}
+ */
+ subtract(symbol) {
+ return this.operation('subtract', symbol);
+ }
+
+ /**
+ * @param {string | number | NerdamerSymbolType | ExpressionType} symbol
+ * @returns {ExpressionType}
+ */
+ multiply(symbol) {
+ return this.operation('multiply', symbol);
+ }
+
+ /**
+ * @param {string | number | NerdamerSymbolType | ExpressionType} symbol
+ * @returns {ExpressionType}
+ */
+ divide(symbol) {
+ return this.operation('divide', symbol);
+ }
+
+ /**
+ * @param {string | number | NerdamerSymbolType | ExpressionType} symbol
+ * @returns {ExpressionType}
+ */
+ pow(symbol) {
+ return this.operation('pow', symbol);
+ }
+
+ /** @returns {ExpressionType} */
+ expand() {
+ return new Expression(/** @type {NerdamerSymbolType} */ (ExpressionDeps._.expand(this.symbol)));
+ }
+
+ /**
+ * @param {Function} callback
+ * @param {boolean} [deep]
+ */
+ each(callback, deep) {
+ if (this.symbol.each) {
+ this.symbol.each(/** @type {(symbol: NerdamerSymbolType, key: string) => void} */ (callback), deep);
+ } else if (ExpressionDeps.isArray(this.symbol)) {
+ for (let idx = 0; idx < this.symbol.length; idx++) {
+ callback.call(this.symbol, this.symbol[idx], idx);
+ }
+ } else {
+ callback.call(this.symbol);
+ }
+ }
+
+ /**
+ * @param {string | number | NerdamerSymbolType} value
+ * @returns {boolean}
+ */
+ eq(value) {
+ if (!ExpressionDeps.isSymbol(value)) {
+ value = /** @type {NerdamerSymbolType} */ (ExpressionDeps._.parse(value));
+ }
+ try {
+ const d = /** @type {NerdamerSymbolType} */ (
+ ExpressionDeps._.subtract(this.symbol.clone(), /** @type {NerdamerSymbolType} */ (value))
+ );
+ return d.equals(0);
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * @param {string | number | NerdamerSymbolType} value
+ * @returns {boolean}
+ */
+ lt(value) {
+ if (!ExpressionDeps.isSymbol(value)) {
+ value = /** @type {NerdamerSymbolType} */ (ExpressionDeps._.parse(value));
+ }
+ try {
+ const d = evaluate(
+ /** @type {NerdamerSymbolType} */ (
+ ExpressionDeps._.subtract(this.symbol.clone(), /** @type {NerdamerSymbolType} */ (value))
+ )
+ );
+ return d.lessThan(0);
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * @param {string | number | NerdamerSymbolType} value
+ * @returns {boolean}
+ */
+ gt(value) {
+ if (!ExpressionDeps.isSymbol(value)) {
+ value = /** @type {NerdamerSymbolType} */ (ExpressionDeps._.parse(value));
+ }
+ try {
+ const d = evaluate(
+ /** @type {NerdamerSymbolType} */ (
+ ExpressionDeps._.subtract(this.symbol.clone(), /** @type {NerdamerSymbolType} */ (value))
+ )
+ );
+ return d.greaterThan(0);
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * @param {string | number | NerdamerSymbolType} value
+ * @returns {boolean}
+ */
+ gte(value) {
+ return this.gt(value) || this.eq(value);
+ }
+
+ /**
+ * @param {string | number | NerdamerSymbolType} value
+ * @returns {boolean}
+ */
+ lte(value) {
+ return this.lt(value) || this.eq(value);
+ }
+
+ /** @returns {ExpressionType} */
+ numerator() {
+ return new Expression(this.symbol.getNum());
+ }
+
+ /** @returns {ExpressionType} */
+ denominator() {
+ return new Expression(this.symbol.getDenom());
+ }
+
+ /**
+ * @param {string | string[]} f
+ * @returns {boolean}
+ */
+ hasFunction(f) {
+ return this.symbol.containsFunction(f);
+ }
+
+ /**
+ * @param {string} variable
+ * @returns {boolean}
+ */
+ contains(variable) {
+ return this.symbol.contains(variable);
+ }
+
+ /**
+ * Alias for latex
+ *
+ * @param {OutputType} option
+ * @returns {string}
+ */
+ toTeX(option) {
+ return this.latex(option);
+ }
+}
+
+// Assign Expression to CoreDeps immediately
+CoreDeps.classes.Expression = Expression;
+
+// Vector Class =====================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Dependencies are injected via VectorDeps which is set by the IIFE after initialization.
+
+/**
+ * Dependency container for Vector class. Populated by the IIFE during initialization. Only IIFE-scope values and
+ * forward-referenced values need injection.
+ *
+ * @type {{
+ * _: ParserType;
+ * Settings: { PRECISION: number };
+ * LaTeX: LaTeXInterface;
+ * NerdamerSymbol: SymbolConstructor;
+ * }}
+ */
+const VectorDeps = {
+ get _() {
+ return CoreDeps.parser;
+ },
+ get Settings() {
+ return CoreDeps.settings;
+ },
+ get LaTeX() {
+ return CoreDeps.classes.LaTeX;
+ },
+ get NerdamerSymbol() {
+ return CoreDeps.classes.NerdamerSymbol;
+ },
+};
+
+/**
+ * Vector class - Ported from Sylvester.js
+ *
+ * @implements {VectorType}
+ */
+class Vector {
+ /** @type {FracType} */
+ multiplier;
+
+ /** @type {(NerdamerSymbolType | VectorType | MatrixType)[]} */
+ elements;
+
+ /** @type {boolean | undefined} */
+ rowVector;
+
+ /**
+ * Custom marker for parser
+ *
+ * @type {true}
+ */
+ custom = true;
+
+ /**
+ * @param {VectorType | MatrixType | NerdamerSymbolType[] | NerdamerSymbolType | undefined} [v]
+ * @param {...NerdamerSymbolType} rest
+ */
+ constructor(v, ...rest) {
+ this.multiplier = new Frac(1);
+ if (isVector(v)) {
+ this.elements = /** @type {(NerdamerSymbol | Vector | Matrix)[]} */ (
+ /** @type {unknown[]} */ (v.elements)?.slice(0) ?? []
+ );
+ } else if (isArray(v)) {
+ this.elements = v.slice(0);
+ } else if (isMatrix(v)) {
+ if (v.elements.length === 1) {
+ this.elements = [...v.elements[0]];
+ this.rowVector = true;
+ } else if (v.elements.length > 1 && Array.isArray(v.elements[0]) && v.elements[0].length === 1) {
+ this.elements = v.elements.map(row => row[0]);
+ this.rowVector = false;
+ }
+ } else if (typeof v === 'undefined') {
+ this.elements = [];
+ } else {
+ this.elements = [v, ...rest];
+ }
+ }
+
+ /**
+ * Generates a pre-filled array
+ *
+ * @param {number} n
+ * @param {NerdamerSymbolType | number} [val]
+ * @returns {(NerdamerSymbolType | number)[]}
+ */
+ static arrayPrefill(n, val) {
+ const a = [];
+ val ||= 0;
+ for (let i = 0; i < n; i++) {
+ a[i] = val;
+ }
+ return a;
+ }
+
+ /**
+ * Generate a vector from an array
+ *
+ * @param {(NerdamerSymbolType | VectorType | MatrixType | string | number)[]} a
+ * @returns {VectorType}
+ */
+ static fromArray(a) {
+ const v = new Vector();
+ v.elements = /** @type {(NerdamerSymbolType | VectorType | MatrixType)[]} */ (a);
+ return v;
+ }
+
+ /**
+ * Convert a NerdamerSet to a Vector
+ *
+ * @param {SetType} nerdamerSet
+ * @returns {VectorType}
+ */
+ static fromSet(nerdamerSet) {
+ return Vector.fromArray(nerdamerSet.elements);
+ }
+
+ /**
+ * Returns element i of the vector
+ *
+ * @param {number} i
+ * @returns {NerdamerSymbolType | VectorType | MatrixType | null}
+ */
+ e(i) {
+ return i < 1 || i > this.elements.length ? null : this.elements[i - 1];
+ }
+
+ /**
+ * @param {number} i
+ * @param {NerdamerSymbolType | string | number} val
+ */
+ set(i, val) {
+ if (isSymbol(val)) {
+ this.elements[i] = val;
+ } else {
+ this.elements[i] = new VectorDeps.NerdamerSymbol(/** @type {string | number} */ (val));
+ }
+ }
+
+ /**
+ * Returns the number of elements the vector has
+ *
+ * @returns {number}
+ */
+ dimensions() {
+ return this.elements.length;
+ }
+
+ /**
+ * Returns the modulus ('length') of the vector
+ *
+ * @returns {NerdamerSymbolType}
+ */
+ modulus() {
+ return /** @type {NerdamerSymbolType} */ (
+ block(
+ 'SAFE',
+ () => VectorDeps._.pow(this.dot(this.clone()), new VectorDeps.NerdamerSymbol(0.5)),
+ undefined,
+ this
+ )
+ );
+ }
+
+ /**
+ * Returns true iff the vector is equal to the argument
+ *
+ * @param {VectorType | NerdamerSymbolType[]} vector
+ * @returns {boolean}
+ */
+ eql(vector) {
+ let n = this.elements.length;
+ const V = /** @type {NerdamerSymbolType[]} */ (/** @type {VectorType} */ (vector).elements || vector);
+ if (n !== V.length) {
+ return false;
+ }
+ do {
+ if (
+ Math.abs(/** @type {number} */ (VectorDeps._.subtract(this.elements[n - 1], V[n - 1]).valueOf())) >
+ VectorDeps.Settings.PRECISION
+ ) {
+ return false;
+ }
+ } while (--n);
+ return true;
+ }
+
+ /**
+ * Returns a clone of the vector
+ *
+ * @returns {VectorType}
+ */
+ clone() {
+ const V = new Vector();
+ const l = this.elements.length;
+ for (let i = 0; i < l; i++) {
+ // Rule: all items within the vector must have a clone method.
+ V.elements.push(this.elements[i].clone());
+ }
+ V.rowVector = this.rowVector;
+ return V;
+ }
+
+ /**
+ * @param {ExpandOptions} [options]
+ * @returns {this}
+ */
+ expand(options) {
+ this.elements = /** @type {NerdamerSymbolType[]} */ (this.elements.map(e => VectorDeps._.expand(e, options)));
+ return this;
+ }
+
+ /**
+ * Maps the vector to another vector according to the given function
+ *
+ * @param {(element: NerdamerSymbolType, index: number) => NerdamerSymbolType} fn
+ * @returns {VectorType}
+ */
+ map(fn) {
+ const elements = [];
+ this.each((x, i) => {
+ elements.push(fn(x, i));
+ });
+
+ return new Vector(elements);
+ }
+
+ /**
+ * Calls the iterator for each element of the vector in turn
+ *
+ * @param {Function} fn
+ */
+ each(fn) {
+ let n = this.elements.length;
+ const k = n;
+ let i;
+ do {
+ i = k - n;
+ fn(this.elements[i], i + 1);
+ } while (--n);
+ }
+
+ /**
+ * Returns a new vector created by normalizing the receiver
+ *
+ * @returns {VectorType}
+ */
+ toUnitVector() {
+ return block(
+ 'SAFE',
+ () => {
+ const r = this.modulus();
+ if (r.valueOf() === 0) {
+ return this.clone();
+ }
+ return this.map(x => /** @type {NerdamerSymbolType} */ (VectorDeps._.divide(x, r)));
+ },
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * Returns the angle between the vector and the argument (also a vector)
+ *
+ * @param {VectorType | NerdamerSymbolType[]} vector
+ * @returns {NerdamerSymbolType | null}
+ */
+ angleFrom(vector) {
+ return block(
+ 'SAFE',
+ () => {
+ const V = /** @type {NerdamerSymbolType[]} */ (/** @type {VectorType} */ (vector).elements || vector);
+ const n = this.elements.length;
+ if (n !== V.length) {
+ return null;
+ }
+ let dot = new VectorDeps.NerdamerSymbol(0);
+ let mod1 = new VectorDeps.NerdamerSymbol(0);
+ let mod2 = new VectorDeps.NerdamerSymbol(0);
+ // Work things out in parallel to save time
+ this.each((x, i) => {
+ dot = /** @type {NerdamerSymbolType} */ (VectorDeps._.add(dot, VectorDeps._.multiply(x, V[i - 1])));
+ mod1 = /** @type {NerdamerSymbolType} */ (VectorDeps._.add(mod1, VectorDeps._.multiply(x, x))); // Will not conflict in safe block
+ mod2 = /** @type {NerdamerSymbolType} */ (
+ VectorDeps._.add(mod2, VectorDeps._.multiply(V[i - 1], V[i - 1]))
+ ); // Will not conflict in safe block
+ });
+ mod1 = /** @type {NerdamerSymbolType} */ (VectorDeps._.pow(mod1, new VectorDeps.NerdamerSymbol(0.5)));
+ mod2 = /** @type {NerdamerSymbolType} */ (VectorDeps._.pow(mod2, new VectorDeps.NerdamerSymbol(0.5)));
+ const product = /** @type {NerdamerSymbolType} */ (VectorDeps._.multiply(mod1, mod2));
+ if (product.valueOf() === 0) {
+ return null;
+ }
+ /** @type {NerdamerSymbolType | number} */
+ let theta = /** @type {NerdamerSymbolType} */ (VectorDeps._.divide(dot, product));
+ const thetaVal = /** @type {number} */ (theta.valueOf());
+ if (thetaVal < -1) {
+ theta = -1;
+ }
+ if (thetaVal > 1) {
+ theta = 1;
+ }
+ return new VectorDeps.NerdamerSymbol(Math.acos(/** @type {number} */ (theta)));
+ },
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * Returns true iff the vector is parallel to the argument
+ *
+ * @param {VectorType | NerdamerSymbolType[]} vector
+ * @returns {boolean | null}
+ */
+ isParallelTo(vector) {
+ const angle = /** @type {number | null} */ (this.angleFrom(vector).valueOf());
+ return angle === null ? null : angle <= VectorDeps.Settings.PRECISION;
+ }
+
+ /**
+ * Returns true iff the vector is antiparallel to the argument
+ *
+ * @param {VectorType | NerdamerSymbolType[]} vector
+ * @returns {boolean | null}
+ */
+ isAntiparallelTo(vector) {
+ const angle = /** @type {number | null} */ (this.angleFrom(vector).valueOf());
+ return angle === null ? null : Math.abs(angle - Math.PI) <= VectorDeps.Settings.PRECISION;
+ }
+
+ /**
+ * Returns true iff the vector is perpendicular to the argument
+ *
+ * @param {VectorType | NerdamerSymbolType[]} vector
+ * @returns {boolean | null}
+ */
+ isPerpendicularTo(vector) {
+ const dot = this.dot(vector);
+ return dot === null
+ ? null
+ : Math.abs(/** @type {number} */ (/** @type {unknown} */ (dot))) <= VectorDeps.Settings.PRECISION;
+ }
+
+ /**
+ * Returns the result of adding the argument to the vector
+ *
+ * @param {VectorType | NerdamerSymbolType[]} vector
+ * @returns {VectorType | null}
+ */
+ add(vector) {
+ return block(
+ 'SAFE',
+ () => {
+ const V = /** @type {NerdamerSymbolType[]} */ (/** @type {VectorType} */ (vector).elements || vector);
+ if (this.elements.length !== V.length) {
+ return null;
+ }
+ return this.map((x, i) => /** @type {NerdamerSymbolType} */ (VectorDeps._.add(x, V[i - 1])));
+ },
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * Returns the result of subtracting the argument from the vector
+ *
+ * @param {VectorType | NerdamerSymbolType[]} vector
+ * @returns {VectorType | null}
+ */
+ subtract(vector) {
+ return block(
+ 'SAFE',
+ () => {
+ const V = /** @type {NerdamerSymbolType[]} */ (/** @type {VectorType} */ (vector).elements || vector);
+ if (this.elements.length !== V.length) {
+ return null;
+ }
+ return this.map((x, i) => /** @type {NerdamerSymbolType} */ (VectorDeps._.subtract(x, V[i - 1])));
+ },
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * Returns the result of multiplying the elements of the vector by the argument
+ *
+ * @param {NerdamerSymbolType} k
+ * @returns {VectorType}
+ */
+ multiply(k) {
+ return this.map(x => /** @type {NerdamerSymbolType} */ (VectorDeps._.multiply(x.clone(), k.clone())));
+ }
+
+ /**
+ * Alias for multiply
+ *
+ * @param {NerdamerSymbolType} k
+ * @returns {VectorType}
+ */
+ x(k) {
+ return this.multiply(k);
+ }
+
+ /**
+ * Returns the scalar product of the vector with the argument Both vectors must have equal dimensionality
+ *
+ * @param {VectorType | NerdamerSymbolType[]} vector
+ * @returns {NerdamerSymbolType | null}
+ */
+ dot(vector) {
+ return block(
+ 'SAFE',
+ () => {
+ const V = /** @type {NerdamerSymbolType[]} */ (/** @type {VectorType} */ (vector).elements || vector);
+ let product = new VectorDeps.NerdamerSymbol(0);
+ let n = this.elements.length;
+ if (n !== V.length) {
+ return null;
+ }
+ do {
+ product = /** @type {NerdamerSymbolType} */ (
+ VectorDeps._.add(product, VectorDeps._.multiply(this.elements[n - 1], V[n - 1]))
+ );
+ } while (--n);
+ return product;
+ },
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * Returns the vector product of the vector with the argument Both vectors must have dimensionality 3
+ *
+ * @param {VectorType | NerdamerSymbolType[]} vector
+ * @returns {VectorType | null}
+ */
+ cross(vector) {
+ const B = /** @type {NerdamerSymbolType[]} */ (/** @type {VectorType} */ (vector).elements || vector);
+ if (this.elements.length !== 3 || B.length !== 3) {
+ return null;
+ }
+ const rowVector = this.rowVector && /** @type {VectorType} */ (vector).rowVector;
+ const A = this.elements;
+ return block(
+ 'SAFE',
+ () => {
+ const result = new Vector([
+ /** @type {NerdamerSymbolType} */ (
+ VectorDeps._.subtract(VectorDeps._.multiply(A[1], B[2]), VectorDeps._.multiply(A[2], B[1]))
+ ),
+ /** @type {NerdamerSymbolType} */ (
+ VectorDeps._.subtract(VectorDeps._.multiply(A[2], B[0]), VectorDeps._.multiply(A[0], B[2]))
+ ),
+ /** @type {NerdamerSymbolType} */ (
+ VectorDeps._.subtract(VectorDeps._.multiply(A[0], B[1]), VectorDeps._.multiply(A[1], B[0]))
+ ),
+ ]);
+ result.rowVector = rowVector;
+ return result;
+ },
+ undefined,
+ this
+ );
+ }
+
+ /** @returns {this} */
+ toUnitMultiplier() {
+ return this;
+ }
+
+ /**
+ * Returns the (absolute) largest element of the vector
+ *
+ * @returns {NerdamerSymbolType | VectorType | MatrixType | number}
+ */
+ max() {
+ /** @type {NerdamerSymbolType | VectorType | MatrixType | number} */
+ let m = 0;
+ let n = this.elements.length;
+ const k = n;
+ let i;
+ do {
+ i = k - n;
+ const el = this.elements[i];
+ const elVal = /** @type {number} */ (el.valueOf());
+ const mVal = typeof m === 'number' ? m : /** @type {number} */ (m.valueOf());
+ if (Math.abs(elVal) > Math.abs(mVal)) {
+ m = el;
+ }
+ } while (--n);
+ return m;
+ }
+
+ /** @returns {NerdamerSymbolType} */
+ magnitude() {
+ let magnitude = new VectorDeps.NerdamerSymbol(0);
+ this.each(e => {
+ magnitude = /** @type {NerdamerSymbolType} */ (
+ VectorDeps._.add(magnitude, VectorDeps._.pow(e, new VectorDeps.NerdamerSymbol(2)))
+ );
+ });
+ return /** @type {NerdamerSymbolType} */ (VectorDeps._.sqrt(magnitude));
+ }
+
+ /**
+ * Returns the index of the first match found
+ *
+ * @param {NerdamerSymbolType | number} x
+ * @returns {number | null}
+ */
+ indexOf(x) {
+ let index = null;
+ let n = this.elements.length;
+ const k = n;
+ let i;
+ do {
+ i = k - n;
+ if (index === null && this.elements[i].valueOf() === x.valueOf()) {
+ index = i + 1;
+ }
+ } while (--n);
+ return index;
+ }
+
+ /**
+ * @param {unknown} x - Unused parameter
+ * @param {{ decimals?: boolean; decimalPlaces?: number }} [options]
+ * @returns {string}
+ */
+ text_(x, options) {
+ const result = text(
+ /** @type {NerdamerSymbolType} */ (/** @type {unknown} */ (this)),
+ /** @type {string | undefined} */ (options)
+ );
+ return (this.rowVector ? '[' : '') + result + (this.rowVector ? ']' : '');
+ }
+
+ /**
+ * @param {unknown} x - Unused parameter
+ * @param {{ decimals?: boolean; decimalPlaces?: number }} [options]
+ * @returns {string}
+ */
+ text(x, options) {
+ const result = text(
+ /** @type {NerdamerSymbolType} */ (/** @type {unknown} */ (this)),
+ /** @type {string | undefined} */ (options)
+ );
+ return (this.rowVector ? '[' : '') + result + (this.rowVector ? ']' : '');
+ }
+
+ /** @returns {string} */
+ toString() {
+ return this.text();
+ }
+
+ /**
+ * @param {OutputType} [option]
+ * @returns {string}
+ */
+ latex(option) {
+ const tex = [];
+ for (let i = 0; i < this.elements.length; i++) {
+ tex.push(VectorDeps.LaTeX.latex(this.elements[i], option));
+ }
+ return `[${tex.join(', ')}]`;
+ }
+}
+
+// Assign Vector to CoreDeps immediately
+CoreDeps.classes.Vector = Vector;
+
+// Matrix Class =====================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Uses module-scope values directly. MatrixDeps only provides the parser (_) from the IIFE.
+
+/**
+ * Dependency container for Matrix class. Populated by the IIFE during initialization.
+ *
+ * @type {{ _: ParserType; LaTeX: LaTeXInterface; NerdamerSymbol: SymbolConstructor }}
+ */
+const MatrixDeps = {
+ get _() {
+ return CoreDeps.parser;
+ },
+ get LaTeX() {
+ return CoreDeps.classes.LaTeX;
+ },
+ get NerdamerSymbol() {
+ return CoreDeps.classes.NerdamerSymbol;
+ },
+};
+
+/**
+ * Matrix class - Ported from Sylvester.js
+ *
+ * @implements {MatrixType}
+ */
+class Matrix {
+ /** @type {FracType} */
+ multiplier;
+
+ /** @type {(NerdamerSymbolType | VectorType | MatrixType)[][]} */
+ elements;
+
+ /**
+ * Custom marker for parser
+ *
+ * @type {true}
+ */
+ custom = true;
+
+ /** @param {...unknown} args */
+ constructor(...args) {
+ this.multiplier = new Frac(1);
+ const m = args;
+ const l = m.length;
+ let i;
+ /** @type {(NerdamerSymbolType | VectorType | MatrixType)[][]} */
+ const el = [];
+ if (isMatrix(m)) {
+ // If it's a matrix then make a clone
+ for (i = 0; i < l; i++) {
+ el.push(/** @type {(NerdamerSymbolType | VectorType | MatrixType)[]} */ (m[i]).slice(0));
+ }
+ } else {
+ let row;
+ let lw;
+ let rl;
+ for (i = 0; i < l; i++) {
+ row = m[i];
+ if (isVector(row)) {
+ row = row.elements;
+ }
+ if (!isArray(row)) {
+ row = [row];
+ }
+ rl = row.length;
+ if (lw && lw !== rl) {
+ err('Unable to create Matrix. Row dimensions do not match!');
+ }
+ el.push(/** @type {(NerdamerSymbolType | VectorType | MatrixType)[]} */ (row));
+ lw = rl;
+ }
+ }
+ this.elements = el;
+ }
+
+ /**
+ * @param {number} n
+ * @returns {MatrixType}
+ */
+ static identity(n) {
+ const m = new Matrix();
+ for (let i = 0; i < n; i++) {
+ m.elements.push([]);
+ for (let j = 0; j < n; j++) {
+ m.set(i, j, i === j ? new MatrixDeps.NerdamerSymbol(1) : new MatrixDeps.NerdamerSymbol(0));
+ }
+ }
+ return m;
+ }
+
+ /**
+ * @param {unknown[]} arr
+ * @returns {MatrixType}
+ */
+ static fromArray(arr) {
+ return new Matrix(...arr);
+ }
+
+ /**
+ * @param {number} rows
+ * @param {number} cols
+ * @returns {MatrixType}
+ */
+ static zeroMatrix(rows, cols) {
+ const m = new Matrix();
+ for (let i = 0; i < rows; i++) {
+ m.elements.push(
+ /** @type {(NerdamerSymbolType | VectorType | MatrixType)[]} */ (
+ Vector.arrayPrefill(cols, new MatrixDeps.NerdamerSymbol(0))
+ )
+ );
+ }
+ return m;
+ }
+
+ /**
+ * @param {number} row
+ * @param {number} column
+ * @returns {NerdamerSymbolType | VectorType | MatrixType | undefined}
+ */
+ get(row, column) {
+ if (!this.elements[row]) {
+ return undefined;
+ }
+ return this.elements[row][column];
+ }
+
+ /**
+ * @param {(element: NerdamerSymbolType) => NerdamerSymbolType} f
+ * @param {boolean} [rawValues]
+ * @returns {MatrixType}
+ */
+ map(f, rawValues) {
+ const M = new Matrix();
+ this.each((e, i, j) => {
+ M.set(i, j, f.call(M, e), rawValues);
+ });
+ return M;
+ }
+
+ /**
+ * @param {number} row
+ * @param {number} column
+ * @param {NerdamerSymbolType | VectorType | MatrixType | string | number} value
+ * @param {boolean} [raw]
+ */
+ set(row, column, value, raw) {
+ this.elements[row] ||= [];
+ if (raw || isSymbol(value)) {
+ this.elements[row][column] = /** @type {NerdamerSymbolType | VectorType | MatrixType} */ (value);
+ } else {
+ this.elements[row][column] = new MatrixDeps.NerdamerSymbol(
+ /** @type {string | number | FracType} */ (value)
+ );
+ }
+ }
+
+ /** @returns {number} */
+ cols() {
+ return this.elements[0].length;
+ }
+
+ /** @returns {number} */
+ rows() {
+ return this.elements.length;
+ }
+
+ /**
+ * @param {number} n
+ * @returns {(NerdamerSymbolType | VectorType | MatrixType)[]}
+ */
+ row(n) {
+ if (!n || n > this.cols()) {
+ return [];
+ }
+ return this.elements[n - 1];
+ }
+
+ /**
+ * @param {number} n
+ * @returns {(NerdamerSymbolType | VectorType | MatrixType)[]}
+ */
+ col(n) {
+ const nr = this.rows();
+ const col = [];
+ if (n > this.cols() || !n) {
+ return col;
+ }
+ for (let i = 0; i < nr; i++) {
+ col.push(this.elements[i][n - 1]);
+ }
+ return col;
+ }
+
+ /** @param {Function} fn */
+ eachElement(fn) {
+ const nr = this.rows();
+ const nc = this.cols();
+ let i;
+ let j;
+ for (i = 0; i < nr; i++) {
+ for (j = 0; j < nc; j++) {
+ fn.call(this, this.elements[i][j], i, j);
+ }
+ }
+ }
+
+ /**
+ * Alias for eachElement
+ *
+ * @param {Function} fn
+ */
+ each(fn) {
+ this.eachElement(fn);
+ }
+
+ /**
+ * Ported from Sylvester.js
+ *
+ * @returns {NerdamerSymbolType | null}
+ */
+ determinant() {
+ if (!this.isSquare()) {
+ return null;
+ }
+ const M = this.toRightTriangular();
+ let det = /** @type {NerdamerSymbolType} */ (M.elements[0][0]);
+ let n = M.elements.length - 1;
+ const k = n;
+ let i;
+ do {
+ i = k - n + 1;
+ det = /** @type {NerdamerSymbolType} */ (
+ MatrixDeps._.multiply(det, /** @type {NerdamerSymbolType} */ (M.elements[i][i]))
+ );
+ } while (--n);
+ return det;
+ }
+
+ /** @returns {boolean} */
+ isSquare() {
+ return this.elements.length === this.elements[0].length;
+ }
+
+ /** @returns {boolean} */
+ isSingular() {
+ const det = this.determinant();
+ return this.isSquare() && det !== null && det.multiplier.equals(0);
+ }
+
+ /**
+ * @param {MatrixType} m
+ * @returns {this}
+ */
+ augment(m) {
+ const r = this.rows();
+ const rr = m.rows();
+ if (r !== rr) {
+ err("Cannot augment matrix. Rows don't match.");
+ }
+ for (let i = 0; i < r; i++) {
+ this.elements[i] = this.elements[i].concat(m.elements[i]);
+ }
+
+ return this;
+ }
+
+ /** @returns {MatrixType} */
+ clone() {
+ const r = this.rows();
+ const c = this.cols();
+ const m = new Matrix();
+ for (let i = 0; i < r; i++) {
+ m.elements[i] = [];
+ for (let j = 0; j < c; j++) {
+ const symbol = this.elements[i][j];
+ m.elements[i][j] = isSymbol(symbol) ? symbol.clone() : symbol;
+ }
+ }
+ return m;
+ }
+
+ /** @returns {this} */
+ toUnitMultiplier() {
+ return this;
+ }
+
+ /**
+ * @param {ExpandOptions} [options]
+ * @returns {this}
+ */
+ expand(options) {
+ this.eachElement(e => MatrixDeps._.expand(e, options));
+ return this;
+ }
+
+ /**
+ * @param {Record<string, ExpressionParam>} [options]
+ * @returns {this}
+ */
+ evaluate(options) {
+ this.eachElement(e => MatrixDeps._.evaluate(e, options));
+ return this;
+ }
+
+ /**
+ * Ported from Sylvester.js
+ *
+ * @returns {MatrixType}
+ */
+ invert() {
+ if (!this.isSquare()) {
+ err('Matrix is not square!');
+ }
+ return block(
+ 'SAFE',
+ () => {
+ let ni = this.elements.length;
+ const ki = ni;
+ let i;
+ let j;
+ const imatrix = Matrix.identity(ni);
+ const M = this.augment(imatrix).toRightTriangular();
+ let np;
+ const kp = M.elements[0].length;
+ let p;
+ let els;
+ let divisor;
+ const inverseElements = [];
+ let newElement;
+ // Matrix is non-singular so there will be no zeros on the diagonal
+ // Cycle through rows from last to first
+ do {
+ i = ni - 1;
+ // First, normalise diagonal elements to 1
+ els = [];
+ np = kp;
+ inverseElements[i] = [];
+ divisor = M.elements[i][i];
+ do {
+ p = kp - np;
+ newElement = MatrixDeps._.divide(M.elements[i][p], divisor.clone());
+ els.push(newElement);
+ // Shuffle of the current row of the right hand side into the results
+ // array as it will not be modified by later runs through this loop
+ if (p >= ki) {
+ inverseElements[i].push(newElement);
+ }
+ } while (--np);
+ M.elements[i] = els;
+ // Then, subtract this row from those above it to
+ // give the identity matrix on the left hand side
+ for (j = 0; j < i; j++) {
+ els = [];
+ np = kp;
+ do {
+ p = kp - np;
+ els.push(
+ MatrixDeps._.subtract(
+ M.elements[j][p].clone(),
+ MatrixDeps._.multiply(M.elements[i][p].clone(), M.elements[j][i].clone())
+ )
+ );
+ } while (--np);
+ M.elements[j] = els;
+ }
+ } while (--ni);
+ return Matrix.fromArray(inverseElements);
+ },
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * Ported from Sylvester.js
+ *
+ * @returns {MatrixType}
+ */
+ toRightTriangular() {
+ return block(
+ 'SAFE',
+ () => {
+ const M = this.clone();
+ let els;
+ let fel;
+ let nel;
+ let n = this.elements.length;
+ const k = n;
+ let i;
+ let np;
+ const kp = this.elements[0].length;
+ let p;
+ do {
+ i = k - n;
+ fel = M.elements[i][i];
+ if (fel.valueOf() === 0) {
+ for (let j = i + 1; j < k; j++) {
+ nel = M.elements[j][i];
+ if (nel && nel.valueOf() !== 0) {
+ els = [];
+ np = kp;
+ do {
+ p = kp - np;
+ els.push(MatrixDeps._.add(M.elements[i][p].clone(), M.elements[j][p].clone()));
+ } while (--np);
+ M.elements[i] = els;
+ break;
+ }
+ }
+ }
+ fel = M.elements[i][i];
+ if (fel.valueOf() !== 0) {
+ for (let j = i + 1; j < k; j++) {
+ const multiplier = MatrixDeps._.divide(M.elements[j][i].clone(), M.elements[i][i].clone());
+ els = [];
+ np = kp;
+ do {
+ p = kp - np;
+ // Elements with column numbers up to an including the number
+ // of the row that we're subtracting can safely be set straight to
+ // zero, since that's the point of this routine and it avoids having
+ // to loop over and correct rounding errors later
+ els.push(
+ p <= i
+ ? new MatrixDeps.NerdamerSymbol(0)
+ : MatrixDeps._.subtract(
+ M.elements[j][p].clone(),
+ MatrixDeps._.multiply(M.elements[i][p].clone(), multiplier.clone())
+ )
+ );
+ } while (--np);
+ M.elements[j] = els;
+ }
+ }
+ } while (--n);
+
+ return M;
+ },
+ undefined,
+ this
+ );
+ }
+
+ /** @returns {MatrixType} */
+ transpose() {
+ const rows = this.elements.length;
+ const cols = this.elements[0].length;
+ const M = new Matrix();
+ let ni = cols;
+ let i;
+ let nj;
+ let j;
+
+ do {
+ i = cols - ni;
+ M.elements[i] = [];
+ nj = rows;
+ do {
+ j = rows - nj;
+ M.elements[i][j] = this.elements[j][i].clone();
+ } while (--nj);
+ } while (--ni);
+ return M;
+ }
+
+ /**
+ * Returns true if the matrix can multiply the argument from the left
+ *
+ * @param {MatrixType | unknown[]} matrix
+ * @returns {boolean}
+ */
+ canMultiplyFromLeft(matrix) {
+ const l = isMatrix(matrix) ? matrix.elements.length : matrix.length;
+ // This.columns should equal matrix.rows
+ return this.elements[0].length === l;
+ }
+
+ /**
+ * @param {MatrixType} matrix
+ * @returns {boolean}
+ */
+ sameSize(matrix) {
+ return this.rows() === matrix.rows() && this.cols() === matrix.cols();
+ }
+
+ /**
+ * @param {MatrixType | unknown[][]} matrix
+ * @returns {MatrixType | null}
+ */
+ multiply(matrix) {
+ return block(
+ 'SAFE',
+ () => {
+ const M = /** @type {MatrixType} */ (matrix).elements || /** @type {unknown[][]} */ (matrix);
+ if (!this.canMultiplyFromLeft(M)) {
+ const matrixTyped = /** @type {MatrixType} */ (matrix);
+ if (this.sameSize(matrixTyped)) {
+ const MM = new Matrix();
+ const rows = this.rows();
+ for (let i = 0; i < rows; i++) {
+ const e = MatrixDeps._.multiply(
+ new Vector(/** @type {NerdamerSymbolType[]} */ (this.elements[i])),
+ new Vector(/** @type {NerdamerSymbolType[]} */ (matrixTyped.elements[i]))
+ );
+ MM.elements[i] = /** @type {VectorType} */ (e).elements;
+ }
+ return MM;
+ }
+ return null;
+ }
+ let ni = this.elements.length;
+ const ki = ni;
+ let i;
+ let nj;
+ const kj = M[0].length;
+ let j;
+ const cols = this.elements[0].length;
+ const elements = [];
+ let sum;
+ let nc;
+ let c;
+ do {
+ i = ki - ni;
+ elements[i] = [];
+ nj = kj;
+ do {
+ j = kj - nj;
+ sum = new MatrixDeps.NerdamerSymbol(0);
+ nc = cols;
+ do {
+ c = cols - nc;
+ sum = MatrixDeps._.add(
+ sum,
+ MatrixDeps._.multiply(this.elements[i][c], /** @type {NerdamerSymbolType} */ (M[c][j]))
+ );
+ } while (--nc);
+ elements[i][j] = sum;
+ } while (--nj);
+ } while (--ni);
+ return Matrix.fromArray(elements);
+ },
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * @param {MatrixType} matrix
+ * @param {Function} [callback]
+ * @returns {MatrixType}
+ */
+ add(matrix, callback) {
+ const M = new Matrix();
+ if (this.sameSize(matrix)) {
+ this.eachElement((e, i, j) => {
+ let result = /** @type {NerdamerSymbolType} */ (
+ MatrixDeps._.add(e.clone(), matrix.elements[i][j].clone())
+ );
+ if (callback) {
+ result = callback.call(M, result, e, matrix.elements[i][j]);
+ }
+ M.set(i, j, result);
+ });
+ }
+ return M;
+ }
+
+ /**
+ * @param {MatrixType} matrix
+ * @param {Function} [callback]
+ * @returns {MatrixType}
+ */
+ subtract(matrix, callback) {
+ const M = new Matrix();
+ if (this.sameSize(matrix)) {
+ this.eachElement((e, i, j) => {
+ let result = /** @type {NerdamerSymbolType} */ (
+ MatrixDeps._.subtract(e.clone(), matrix.elements[i][j].clone())
+ );
+ if (callback) {
+ result = callback.call(M, result, e, matrix.elements[i][j]);
+ }
+ M.set(i, j, result);
+ });
+ }
+ return M;
+ }
+
+ /** @returns {this} */
+ negate() {
+ this.each(e => e.negate());
+ return this;
+ }
+
+ /** @returns {VectorType | MatrixType} */
+ toVector() {
+ if (this.rows() === 1 || this.cols() === 1) {
+ const v = new Vector();
+ v.elements = /** @type {(NerdamerSymbolType | VectorType | MatrixType)[]} */ (this.elements.flat());
+ return v;
+ }
+ return this;
+ }
+
+ /**
+ * @param {string} [newline]
+ * @param {boolean} [toDecimal]
+ * @returns {string}
+ */
+ toString(newline, toDecimal) {
+ const l = this.rows();
+ const s = [];
+ newline = newline === undefined ? '\n' : newline;
+ for (let i = 0; i < l; i++) {
+ s.push(
+ `[${this.elements[i]
+ .map(x => {
+ const v = toDecimal ? x.multiplier.toDecimal() : x.toString();
+ return x === undefined ? '' : v;
+ })
+ .join(',')}]`
+ );
+ }
+ return `matrix${inBrackets(s.join(','))}`;
+ }
+
+ /** @returns {string} */
+ text() {
+ return `matrix(${this.elements.map(row => `[${row.join(',')}]`)})`;
+ }
+
+ /**
+ * @param {OutputType} [option]
+ * @returns {string}
+ */
+ latex(option) {
+ const cols = this.cols();
+ const { elements } = this;
+ return format('\\begin{vmatrix}{0}\\end{vmatrix}', () => {
+ const tex = [];
+ for (const row in elements) {
+ if (!Object.hasOwn(elements, row)) {
+ continue;
+ }
+ const rowTex = [];
+ for (let i = 0; i < cols; i++) {
+ rowTex.push(MatrixDeps.LaTeX.latex(elements[row][i], option));
+ }
+ tex.push(rowTex.join(' & '));
+ }
+ return tex.join(' \\cr ');
+ });
+ }
+}
+
+// Assign Matrix to CoreDeps immediately
+CoreDeps.classes.Matrix = Matrix;
+
+// Build Object =================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Dependencies are injected via BuildDeps which is set by the IIFE after initialization.
+
+/**
+ * Dependency container for Build object. Populated by the IIFE during initialization. Contains IIFE-local values and
+ * forward-referenced values.
+ *
+ * @type {{
+ * _: ParserType;
+ * N: number;
+ * P: number;
+ * S: number;
+ * EX: number;
+ * FN: number;
+ * CB: number;
+ * Math2: Math2Interface;
+ * NerdamerSymbol: SymbolConstructor;
+ * }}
+ */
+const BuildDeps = {
+ get _() {
+ return CoreDeps.parser;
+ },
+ get N() {
+ return CoreDeps.groups.N;
+ },
+ get P() {
+ return CoreDeps.groups.P;
+ },
+ get S() {
+ return CoreDeps.groups.S;
+ },
+ get EX() {
+ return CoreDeps.groups.EX;
+ },
+ get FN() {
+ return CoreDeps.groups.FN;
+ },
+ get CB() {
+ return CoreDeps.groups.CB;
+ },
+ get Math2() {
+ return LateRefs.Math2;
+ },
+ get NerdamerSymbol() {
+ return CoreDeps.classes.NerdamerSymbol;
+ },
+};
+
+/** Build object for compiling mathematical expressions to JavaScript functions. */
+const Build = {
+ /** @type {Record<string, Record<string, Function | string | object> | Record<string, string>>} */
+ dependencies: {},
+ /**
+ * @type {Record<
+ * string,
+ * (
+ * symbol: NerdamerSymbolType,
+ * deps: [Record<string, string>, string]
+ * ) => [string, [Record<string, string>, string]]
+ * >}
+ */
+ reformat: {},
+ /** Initializes Build dependencies and reformat functions. Called once from IIFE after Math2 is available. */
+ initDependencies() {
+ const { Math2 } = BuildDeps;
+ this.dependencies = {
+ _rename: {
+ 'Math2.factorial': 'factorial',
+ },
+ factorial: {
+ 'Math2.gamma': Math2.gamma,
+ },
+ gamma_incomplete: {
+ 'Math2.factorial': Math2.factorial,
+ },
+ Li: {
+ 'Math2.Ei': Math2.Ei,
+ 'Math2.bigLog': Math2.bigLog,
+ Frac,
+ },
+ Ci: {
+ 'Math2.factorial': Math2.factorial,
+ },
+ Ei: {
+ 'Math2.factorial': Math2.factorial,
+ },
+ Si: {
+ 'Math2.factorial': Math2.factorial,
+ },
+ Shi: {
+ 'Math2.factorial': Math2.factorial,
+ },
+ Chi: {
+ isInt,
+ nround,
+ 'Math2.num_integrate': Math2.num_integrate,
+ },
+ factor: {
+ 'Math2.ifactor': Math2.ifactor,
+ NerdamerSymbol: BuildDeps.NerdamerSymbol,
+ },
+ num_integrate: {
+ 'Math2.simpson': Math2.simpson,
+ nround,
+ },
+ fib: {
+ even,
+ },
+ };
+ this.reformat = {
+ diff(symbol, deps) {
+ const v = symbol.args[1].toString();
+ const f = `let f = ${Build.build(symbol.args[0].toString(), [v])};`;
+ let diffStr = Math2.diff.toString();
+ if (!diffStr.startsWith('function') && !diffStr.startsWith('(') && !diffStr.startsWith('async')) {
+ diffStr = `function ${diffStr}`;
+ }
+ deps[1] += `let diff = ${diffStr};`;
+ deps[1] += f;
+ return [`diff(f)(${v})`, deps];
+ },
+ };
+ },
+ /**
+ * @param {string} f
+ * @returns {string}
+ */
+ getProperName(f) {
+ const map = {
+ continuedFraction: 'continuedFraction',
+ };
+ return map[f] || f;
+ },
+ /**
+ * Assumes that dependencies are at max 2 levels
+ *
+ * @param {string} f
+ * @param {[Record<string, string>, string]} [deps]
+ * @returns {[Record<string, string>, string]}
+ */
+ compileDependencies(f, deps) {
+ // Grab the predefined dependencies
+ const dependencies = Build.dependencies[f];
+
+ // The dependency string
+ let depString = deps && deps[1] ? deps[1] : '';
+
+ // The functions to be replaced
+ const replacements = deps && deps[0] ? deps[0] : {};
+
+ // Loop through them and add them to the list
+ for (const x in dependencies) {
+ if (typeof dependencies[x] === 'object') {
+ continue;
+ } // Skip object
+ const components = x.split('.'); // Math.f becomes f
+ // if the function isn't part of an object then reference the function itself
+ let depValue = dependencies[x];
+ // If it's a function, convert method shorthand to function expression
+ if (typeof depValue === 'function') {
+ let fnStr = depValue.toString();
+ // Handle ES6 method shorthand like "gamma(z) { ... }" -> "function gamma(z) { ... }"
+ if (!fnStr.startsWith('function') && !fnStr.startsWith('(') && !fnStr.startsWith('async')) {
+ fnStr = `function ${fnStr}`;
+ }
+ depValue = fnStr;
+ }
+ depString += `let ${components.length > 1 ? components[1] : components[0]}=${depValue};`;
+ replacements[x] = components.pop();
+ }
+
+ return [replacements, depString];
+ },
+ /**
+ * @param {NerdamerSymbolType} symbol
+ * @param {[Record<string, string>, string]} [dependencies]
+ * @returns {[Record<string, string>, string]}
+ */
+ getArgsDeps(symbol, dependencies) {
+ const { args } = symbol;
+ let deps = dependencies;
+ const processFn = function (x) {
+ if (x.group === BuildDeps.FN) {
+ deps = Build.compileDependencies(x.fname, deps);
+ }
+ };
+ for (let i = 0; i < args.length; i++) {
+ symbol.args[i].each(processFn);
+ }
+ return deps;
+ },
+ /**
+ * @param {NerdamerSymbolType | string} symbol
+ * @param {string[]} [argArray]
+ * @returns {(...args: number[]) => number}
+ */
+ build(symbol, argArray) {
+ // Module-scope values used directly: Math2, block, variables, inBrackets
+ // IIFE-local values from BuildDeps:
+ const { _, FN, N, S, P, EX, CB, NerdamerSymbol } = BuildDeps;
+
+ symbol = block('PARSE2NUMBER', () => _.parse(symbol), true);
+ let args = variables(symbol);
+ const supplements = [];
+ /** @type {[Record<string, string>, string]} */
+ let dependencies = [{}, ''];
+ const ftext = function (sym, xports) {
+ // Fix for #545 - Parentheses confuse build.
+ if (sym.fname === '') {
+ sym = NerdamerSymbol.unwrapPARENS(sym);
+ }
+ xports ||= [];
+ const c = [];
+ const { group } = sym;
+ let prefix = '';
+
+ const ftextComplex = function (grp) {
+ const d = grp === CB ? '*' : '+';
+ const cc = [];
+
+ for (const x in sym.symbols) {
+ if (!Object.hasOwn(sym.symbols, x)) {
+ continue;
+ }
+ const s = sym.symbols[x];
+ let ft = ftext(s, xports)[0];
+ // Wrap it in brackets if it's group PL or CP
+ if (s.isComposite()) {
+ ft = inBrackets(ft);
+ }
+ cc.push(ft);
+ }
+ let retval = cc.join(d);
+ retval = retval && !sym.multiplier.equals(1) ? inBrackets(retval) : retval;
+ return retval;
+ };
+ const ftextFunction = function (bn) {
+ let retval;
+ if (bn in Math) {
+ retval = `Math.${bn}`;
+ } else {
+ bn = Build.getProperName(bn);
+ if (supplements.indexOf(bn) === -1) {
+ // Make sure you're not adding the function twice
+ // Math2 functions aren't part of the standard javascript
+ // Math library and must be exported.
+ let fnStr = BuildDeps.Math2[bn].toString();
+ // Handle ES6 method shorthand like "factorial(x) { ... }" -> "function factorial(x) { ... }"
+ if (!fnStr.startsWith('function') && !fnStr.startsWith('(') && !fnStr.startsWith('async')) {
+ fnStr = `function ${fnStr}`;
+ }
+ xports.push(`let ${bn} = ${fnStr}; `);
+ supplements.push(bn);
+ }
+ retval = bn;
+ }
+ retval += inBrackets(sym.args.map(x => ftext(x, xports)[0]).join(','));
+
+ return retval;
+ };
+
+ // The multiplier
+ if (group === N) {
+ c.push(sym.multiplier.toDecimal());
+ } else if (sym.multiplier.equals(-1)) {
+ prefix = '-';
+ } else if (!sym.multiplier.equals(1)) {
+ c.push(sym.multiplier.toDecimal());
+ }
+ // The value
+ let value;
+
+ if (group === S || group === P) {
+ value = sym.value;
+ } else if (group === FN) {
+ dependencies = Build.compileDependencies(sym.fname, dependencies);
+ dependencies = Build.getArgsDeps(sym, dependencies);
+ if (Build.reformat[sym.fname]) {
+ const components = Build.reformat[sym.fname](sym, dependencies);
+ dependencies = components[1];
+ value = components[0];
+ } else {
+ value = ftextFunction(sym.fname);
+ }
+ } else if (group === EX) {
+ const pg = sym.previousGroup;
+ if (pg === N || pg === S) {
+ value = sym.value;
+ } else if (pg === FN) {
+ value = ftextFunction(sym.fname);
+ dependencies = Build.compileDependencies(sym.fname, dependencies);
+ dependencies = Build.getArgsDeps(sym, dependencies);
+ } else {
+ value = ftextComplex(sym.previousGroup);
+ }
+ } else {
+ value = ftextComplex(sym.group);
+ }
+
+ if (sym.group !== N && !sym.power.equals(1)) {
+ const pow = ftext(_.parse(sym.power));
+ xports.push(pow[1]);
+ value = `Math.pow${inBrackets(`${value},${pow[0]}`)}`;
+ }
+
+ if (value) {
+ c.push(prefix + value);
+ }
+
+ return [c.join('*'), xports.join('').replace(/\n+\s+/gu, ' ')];
+ };
+ if (argArray) {
+ // Fix for issue #546
+ // Disable argument checking since it's a bit presumptuous.
+ // Consider f(x) = 5; If I explicitely pass in an argument array contain x
+ // this check will fail and complain since the function doesn't contain x.
+ /*
+ for (let i = 0; i < args.length; i++) {
+ let arg = args[i];
+ if (argArray.indexOf(arg) === -1)
+ err(arg + ' not found in argument array');
+ }
+ */
+ args = argArray;
+ }
+
+ const fArray = ftext(symbol);
+
+ // Make all the substitutions;
+ for (const x in dependencies[0]) {
+ if (!Object.hasOwn(dependencies[0], x)) {
+ continue;
+ }
+ const alias = dependencies[0][x];
+ fArray[1] = fArray[1].replace(x, alias);
+ dependencies[1] = dependencies[1].replace(x, alias);
+ }
+
+ const f = /** @type {(...args: number[]) => number} */ (
+ // eslint-disable-next-line no-new-func
+ new Function(...args, `${(dependencies[1] || '') + fArray[1]} return ${fArray[0]};`)
+ );
+
+ return f;
+ },
+};
+
+// Assign Build to CoreDeps immediately
+CoreDeps.classes.Build = Build;
+
+// LaTeX Object =================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Dependencies are injected via LaTeXDeps which is set by the IIFE after initialization.
+
+/**
+ * Dependency container for LaTeX object. Populated by the IIFE during initialization. Contains IIFE-local values and
+ * forward-referenced values.
+ *
+ * @type {{
+ * _: ParserType;
+ * Settings: SettingsType;
+ * SQRT: string;
+ * ABS: string;
+ * PARENTHESIS: string;
+ * FACTORIAL: string;
+ * DOUBLEFACTORIAL: string;
+ * N: number;
+ * P: number;
+ * S: number;
+ * EX: number;
+ * FN: number;
+ * CB: number;
+ * CP: number;
+ * Parser: ParserConstructor | null;
+ * }}
+ */
+const LaTeXDeps = {
+ get _() {
+ return CoreDeps.parser;
+ },
+ get Settings() {
+ return CoreDeps.settings;
+ },
+ get SQRT() {
+ return CoreDeps.fnNames.SQRT;
+ },
+ get ABS() {
+ return CoreDeps.fnNames.ABS;
+ },
+ get PARENTHESIS() {
+ return CoreDeps.fnNames.PARENTHESIS;
+ },
+ get FACTORIAL() {
+ return CoreDeps.fnNames.FACTORIAL;
+ },
+ get DOUBLEFACTORIAL() {
+ return CoreDeps.fnNames.DOUBLEFACTORIAL;
+ },
+ get N() {
+ return CoreDeps.groups.N;
+ },
+ get P() {
+ return CoreDeps.groups.P;
+ },
+ get S() {
+ return CoreDeps.groups.S;
+ },
+ get EX() {
+ return CoreDeps.groups.EX;
+ },
+ get FN() {
+ return CoreDeps.groups.FN;
+ },
+ get CB() {
+ return CoreDeps.groups.CB;
+ },
+ get CP() {
+ return CoreDeps.groups.CP;
+ },
+ get Parser() {
+ return CoreDeps.classes.Parser;
+ },
+};
+
+/** LaTeX generator object for converting symbols to LaTeX notation. */
+const LaTeX = {
+ /** @type {ParserType | null} */
+ parser: null, // Initialized inside IIFE after Parser is created
+ space: '~',
+ dot: ' \\cdot ',
+
+ /**
+ * @param {NerdamerSymbolType | unknown[] | CollectionType} symbol
+ * @param {string} [option]
+ * @returns {string}
+ */
+ latex(symbol, option) {
+ const { _: parser, P: GROUP_P, CB: GROUP_CB } = LaTeXDeps;
+
+ // It might be an array
+ if (symbol && typeof symbol === 'object' && 'clone' in symbol && typeof symbol.clone === 'function') {
+ symbol = symbol.clone(); // Leave original as-is
+ }
+ if (symbol instanceof parser.classes.Collection) {
+ symbol = symbol.elements;
+ }
+
+ if (isArray(symbol)) {
+ const LaTeXArray = [];
+ for (let i = 0; i < symbol.length; i++) {
+ let sym = symbol[i];
+ // This way I can generate LaTeX on an array of strings.
+ if (!isSymbol(sym)) {
+ sym = parser.parse(
+ /** @type {string | number | NerdamerSymbolType | FracType | BigIntegerType} */ (sym)
+ );
+ }
+ LaTeXArray.push(this.latex(/** @type {NerdamerSymbolType | Collection | unknown[]} */ (sym), option));
+ }
+ return this.brackets(LaTeXArray.join(', '), 'square');
+ }
+ if (isMatrix(symbol)) {
+ let TeX = '\\begin{pmatrix}\n';
+ for (let i = 0; i < symbol.elements.length; i++) {
+ const rowTeX = [];
+ const e = symbol.elements[i];
+ for (let j = 0; j < e.length; j++) {
+ rowTeX.push(this.latex(/** @type {NerdamerSymbolType | Collection | unknown[]} */ (e[j]), option));
+ }
+ TeX += rowTeX.join(' & ');
+ if (i < symbol.elements.length - 1) {
+ TeX += '\\\\\n';
+ }
+ }
+ TeX += '\\end{pmatrix}';
+ return TeX;
+ }
+ if (isVector(symbol)) {
+ let TeX = '\\left[';
+ for (let i = 0; i < symbol.elements.length; i++) {
+ TeX += `${this.latex(symbol.elements[i], option)} ${i === symbol.elements.length - 1 ? '' : ',\\,'}`;
+ }
+ TeX += '\\right]';
+ return TeX;
+ }
+ if (isSet(symbol)) {
+ let TeX = '\\{';
+ for (let i = 0; i < symbol.elements.length; i++) {
+ TeX += `${this.latex(symbol.elements[i], option)} ${i === symbol.elements.length - 1 ? '' : ',\\,'}`;
+ }
+ TeX += '\\}';
+ return TeX;
+ }
+
+ symbol = symbol.clone();
+
+ const decimal = option === 'decimal' || option === 'decimals';
+ const { power } = symbol;
+ const invert = isNegative(/** @type {NerdamerSymbolType | FracType} */ (power));
+ const negative = symbol.multiplier.lessThan(0);
+
+ if (symbol.group === GROUP_P && decimal) {
+ const base = Number(symbol.value);
+ const exp = Number(/** @type {{ toDecimal: () => string }} */ (symbol.power).toDecimal());
+ const mult = Number(symbol.multiplier.toDecimal());
+ return String(mult * base ** exp);
+ }
+ symbol.multiplier = symbol.multiplier.abs();
+
+ // If the user wants the result in decimal format then return it as such by placing it at the top part
+ let mArray;
+
+ if (decimal) {
+ const m = String(symbol.multiplier.toDecimal());
+ // If(String(m) === '1' && !decimal) m = '';
+ mArray = [m, ''];
+ } else {
+ mArray = [symbol.multiplier.num, symbol.multiplier.den];
+ }
+ // Get the value as a two part array
+ const vArray = this.value(symbol, invert, option, negative);
+ let p;
+ // Make it all positive since we know whether to push the power to the numerator or denominator already.
+ if (invert) {
+ power.negate();
+ }
+ // The power is simple since it requires no additional formatting. We can get it to a
+ // string right away. pass in true to neglect unit powers
+ if (decimal) {
+ p = isSymbol(power) ? LaTeX.latex(power, option) : String(power.toDecimal());
+ if (String(p) === '1') {
+ p = '';
+ }
+ }
+ // Get the latex representation
+ else if (isSymbol(power)) {
+ p = this.latex(power, option);
+ }
+ // Get it as a fraction
+ else {
+ p = this.formatFrac(power, true);
+ }
+ // Use this array to specify if the power is getting attached to the top or the bottom
+ const pArray = ['', ''];
+ // Stick it to the top or the bottom. If it's negative then the power gets placed on the bottom
+ const index = invert ? 1 : 0;
+ pArray[index] = p;
+
+ // Special case group P and decimal
+ const retval = (negative ? '-' : '') + this.set(mArray, vArray, pArray, symbol.group === GROUP_CB);
+
+ return retval.replace(/\+-/giu, '-');
+ },
+ // Greek mapping
+ greek: {
+ alpha: '\\alpha',
+ beta: '\\beta',
+ gamma: '\\gamma',
+ delta: '\\delta',
+ epsilon: '\\epsilon',
+ zeta: '\\zeta',
+ eta: '\\eta',
+ theta: '\\theta',
+ iota: '\\iota',
+ kappa: '\\kappa',
+ lambda: '\\lambda',
+ mu: '\\mu',
+ nu: '\\nu',
+ xi: '\\xi',
+ omnikron: '\\omnikron',
+ pi: '\\pi',
+ rho: '\\rho',
+ sigma: '\\sigma',
+ tau: '\\tau',
+ upsilon: '\\upsilon',
+ phi: '\\phi',
+ chi: '\\chi',
+ psi: '\\psi',
+ omega: '\\omega',
+ Gamma: '\\Gamma',
+ Delta: '\\Delta',
+ Epsilon: '\\Epsilon',
+ Theta: '\\Theta',
+ Lambda: '\\Lambda',
+ Xi: '\\Xi',
+ Pi: '\\Pi',
+ Sigma: '\\Sigma',
+ Phi: '\\Phi',
+ Psi: '\\Psi',
+ Omega: '\\Omega',
+ },
+ symbols: {
+ arccos: '\\arccos',
+ cos: '\\cos',
+ csc: '\\csc',
+ exp: '\\exp',
+ ker: '\\ker',
+ limsup: '\\limsup',
+ min: '\\min',
+ sinh: '\\sinh',
+ arcsin: '\\arcsin',
+ cosh: '\\cosh',
+ deg: '\\deg',
+ gcd: '\\gcd',
+ lg: '\\lg',
+ ln: '\\ln',
+ Pr: '\\Pr',
+ sqrt: '\\sqrt',
+ sup: '\\sup',
+ arctan: '\\arctan',
+ cot: '\\cot',
+ det: '\\det',
+ hom: '\\hom',
+ lim: '\\lim',
+ log: '\\log',
+ LN: '\\LN',
+ sec: '\\sec',
+ tan: '\\tan',
+ arg: '\\arg',
+ coth: '\\coth',
+ dim: '\\dim',
+ inf: '\\inf',
+ liminf: '\\liminf',
+ max: '\\max',
+ sin: '\\sin',
+ tanh: '\\tanh',
+ },
+ /**
+ * Get the raw value of the symbol as an array
+ *
+ * @param {NerdamerSymbolType | unknown} symbol
+ * @param {boolean} inverted
+ * @param {string} [option]
+ * @param {boolean} [negative]
+ * @returns {string[]}
+ */
+ value(symbol, inverted, option, negative) {
+ const {
+ SQRT,
+ ABS,
+ PARENTHESIS,
+ FACTORIAL,
+ DOUBLEFACTORIAL,
+ FN: GROUP_FN,
+ S: GROUP_S,
+ P: GROUP_P,
+ N: GROUP_N,
+ CB: GROUP_CB,
+ CP: GROUP_CP,
+ EX: GROUP_EX,
+ } = LaTeXDeps;
+
+ const { group } = /** @type {NerdamerSymbolType} */ (symbol);
+ const { previousGroup } = /** @type {NerdamerSymbolType} */ (symbol);
+ const v = ['', ''];
+ const index = inverted ? 1 : 0;
+ /* If(group === N) // do nothing since we want to return top & bottom blank; */
+ if (/** @type {NerdamerSymbolType} */ (symbol).isInfinity) {
+ v[index] = '\\infty';
+ } else if (
+ group === GROUP_S ||
+ group === GROUP_P ||
+ previousGroup === GROUP_S ||
+ previousGroup === GROUP_P ||
+ previousGroup === GROUP_N
+ ) {
+ let value = this.formatSubscripts(/** @type {NerdamerSymbolType} */ (symbol).value);
+ if (value.replace) {
+ value = value.replace(/(?<prefix>.+)_$/u, '$1\\_');
+ }
+ // Split it so we can check for instances of alpha as well as alpha_b
+ const tVarray = String(value).split('_');
+ const greek = this.greek[tVarray[0]];
+ if (greek) {
+ tVarray[0] = greek;
+ value = tVarray.join('_');
+ }
+ const symbolEntry = this.symbols[tVarray[0]];
+ if (symbolEntry) {
+ tVarray[0] = symbolEntry;
+ value = tVarray.join('_');
+ }
+ v[index] = value;
+ } else if (group === GROUP_FN || previousGroup === GROUP_FN) {
+ const input = [];
+ const { fname } = /** @type {NerdamerSymbolType} */ (symbol);
+ // Collect the arguments
+ for (let i = 0; i < /** @type {NerdamerSymbolType} */ (symbol).args.length; i++) {
+ const arg = /** @type {NerdamerSymbolType} */ (symbol).args[i];
+ let item;
+ if (typeof arg === 'string') {
+ item = arg;
+ } else {
+ item = this.latex(arg, option);
+ }
+ input.push(item);
+ }
+
+ if (fname === SQRT) {
+ v[index] = `\\sqrt${this.braces(input.join(','))}`;
+ } else if (fname === ABS) {
+ v[index] = this.brackets(input.join(','), 'abs');
+ } else if (fname === PARENTHESIS) {
+ v[index] = this.brackets(input.join(','), 'parens');
+ } else if (fname === 'limit') {
+ v[index] = ` \\lim\\limits_{${input[1]} \\to ${input[2]}} ${input[0]}`;
+ } else if (fname === 'integrate') {
+ v[index] = `\\int${this.braces(input[0])}${this.braces(`d${input[1]}`)}`;
+ } else if (fname === 'defint') {
+ v[index] = `\\int\\limits_${this.braces(input[1])}^${this.braces(input[2])} ${input[0]} d${input[3]}`;
+ } else if (fname === FACTORIAL || fname === DOUBLEFACTORIAL) {
+ const arg = /** @type {NerdamerSymbolType} */ (symbol).args[0];
+ if (arg.power.equals(1) && (arg.isComposite() || arg.isCombination())) {
+ input[0] = this.brackets(input[0]);
+ }
+ v[index] = input[0] + (fname === FACTORIAL ? '!' : '!!');
+ } else if (fname === 'floor') {
+ v[index] = `\\left \\lfloor${this.braces(input[0])}\\right \\rfloor`;
+ } else if (fname === 'ceil') {
+ v[index] = `\\left \\lceil${this.braces(input[0])}\\right \\rceil`;
+ }
+ // Capture log(a, b)
+ else if (fname === LaTeXDeps.Settings.LOG && input.length > 1) {
+ v[index] =
+ `\\mathrm${this.braces(LaTeXDeps.Settings.LOG)}_${this.braces(input[1])}${this.brackets(input[0])}`;
+ }
+ // Capture log(a, b)
+ else if (fname === LaTeXDeps.Settings.LOG10) {
+ v[index] =
+ `\\mathrm${this.braces(LaTeXDeps.Settings.LOG)}_${this.braces('10')}${this.brackets(input[0])}`;
+ } else if (fname === LaTeXDeps.Settings.LOG2) {
+ v[index] =
+ `\\mathrm${this.braces(LaTeXDeps.Settings.LOG)}_${this.braces('2')}${this.brackets(input[0])}`;
+ } else if (fname === LaTeXDeps.Settings.LOG1P) {
+ v[index] = `\\ln${this.brackets(`1 + ${input[0]}`)}`;
+ } else if (fname === 'sum') {
+ const a = input[0];
+ const b = input[1];
+ const c = input[2];
+ const d = input[3];
+ v[index] = `\\sum\\limits_{${this.braces(b)}=${this.braces(c)}}^${this.braces(d)} ${this.braces(a)}`;
+ } else if (fname === 'product') {
+ const a = input[0];
+ const b = input[1];
+ const c = input[2];
+ const d = input[3];
+ v[index] = `\\prod\\limits_{${this.braces(b)}=${this.braces(c)}}^${this.braces(d)} ${this.braces(a)}`;
+ } else if (fname === 'nthroot') {
+ v[index] = `\\sqrt[${input[1]}]${this.braces(input[0])}`;
+ } else if (fname === 'mod') {
+ v[index] = `${input[0]} \\bmod ${input[1]}`;
+ } else if (fname === 'realpart') {
+ v[index] = `\\operatorname{Re}${this.brackets(input[0])}`;
+ } else if (fname === 'imagpart') {
+ v[index] = `\\operatorname{Im}${this.brackets(input[0])}`;
+ } else {
+ const name = fname === '' ? '' : `\\mathrm${this.braces(fname.replace(/_/gu, '\\_'))}`;
+ if (/** @type {NerdamerSymbolType} */ (symbol).isConversion) {
+ v[index] = name + this.brackets(input.join(''), 'parens');
+ } else {
+ v[index] = name + this.brackets(input.join(','), 'parens');
+ }
+ }
+ } else if (/** @type {NerdamerSymbolType} */ (symbol).isComposite()) {
+ const collected = /** @type {NerdamerSymbolType[]} */ (
+ /** @type {NerdamerSymbolType} */ (symbol).collectSymbols()
+ ).sort(
+ group === GROUP_CP || previousGroup === GROUP_CP
+ ? (x, y) => y.group - x.group
+ : (x, y) => {
+ const px = isSymbol(x.power) ? -1 : Number(x.power);
+ const py = isSymbol(y.power) ? -1 : Number(y.power);
+ return py - px;
+ }
+ );
+ const symbols = [];
+ const l = collected.length;
+ for (let i = 0; i < l; i++) {
+ symbols.push(LaTeX.latex(collected[i], option));
+ }
+ const value = symbols.join('+');
+
+ const typedSymbol = /** @type {NerdamerSymbolType} */ (symbol);
+ v[index] =
+ !(typedSymbol.isLinear() && typedSymbol.multiplier.equals(1)) || negative
+ ? this.brackets(value, 'parens')
+ : value;
+ } else if (group === GROUP_CB || previousGroup === GROUP_EX || previousGroup === GROUP_CB) {
+ if (group === GROUP_CB) {
+ /** @type {NerdamerSymbolType} */ (symbol).distributeExponent();
+ }
+ // This almost feels a little like cheating but I need to know if I should be wrapping the symbol
+ // in brackets or not. We'll do this by checking the value of the numerator and then comparing it
+ // to whether the symbol value is "simple" or not.
+ const denominator = [];
+ const numerator = [];
+ // Generate a profile
+ const denMap = [];
+ const numMap = [];
+ let numC = 0;
+ let denC = 0;
+ const setBrackets = function (container, map, counter) {
+ if (counter > 1 && map.length > 0) {
+ const l = map.length;
+ for (let idx = 0; idx < l; idx++) {
+ const mapIdx = map[idx];
+ const containerItem = container[mapIdx];
+ if (
+ !(
+ /^\\left\(.+\\right\)\^\{.+\}$/gu.test(containerItem) ||
+ /^\\left\(.+\\right\)$/gu.test(containerItem)
+ )
+ ) {
+ container[mapIdx] = LaTeX.brackets(containerItem, 'parens');
+ }
+ }
+ }
+ return container;
+ };
+
+ // Generate latex for each of them
+ /** @type {NerdamerSymbolType} */ (symbol).each(x => {
+ const isDenom = isNegative(x.power);
+ let laTex;
+
+ if (isDenom) {
+ laTex = LaTeX.latex(x.invert(), option);
+ denC++;
+ if (x.isComposite()) {
+ if (
+ !(/** @type {NerdamerSymbolType} */ (symbol).multiplier.den.equals(1)) &&
+ Math.abs(Number(x.power)) === 1
+ ) {
+ laTex = LaTeX.brackets(laTex, 'parens');
+ }
+ denMap.push(denominator.length); // Make a note of where the composite was found
+ }
+
+ denominator.push(laTex);
+ } else {
+ laTex = LaTeX.latex(x, option);
+ numC++;
+ if (x.isComposite()) {
+ if (
+ !(/** @type {NerdamerSymbolType} */ (symbol).multiplier.num.equals(1)) &&
+ Math.abs(Number(x.power)) === 1
+ ) {
+ laTex = LaTeX.brackets(laTex, 'parens');
+ }
+ numMap.push(numerator.length); // Make a note of where the composite was found
+ }
+ numerator.push(laTex);
+ }
+ });
+
+ // Apply brackets
+ setBrackets(numerator, numMap, numC);
+ v[0] = numerator.join(this.dot); // Collapse the numerator into one string
+
+ setBrackets(denominator, denMap, denC);
+ v[1] = denominator.join(this.dot);
+ }
+
+ return v;
+ },
+ /**
+ * @param {unknown[]} m
+ * @param {string[]} v
+ * @param {string[]} p
+ * @param {boolean} combinePower
+ * @returns {string}
+ */
+ set(m, v, p, combinePower) {
+ const isBracketed = function (str) {
+ return /^\\left\(.+\\right\)$/u.test(str);
+ };
+ // Format the power if it exists
+ p &&= this.formatP(p);
+ // Group CB will have to be wrapped since the power applies to both it's numerator and denominator
+ let tp;
+ if (combinePower) {
+ // POSSIBLE BUG: If powers for group CB format wrong, investigate this since I might have overlooked something
+ // the assumption is that in every case the denonimator should be empty when dealing with CB. I can't think
+ // of a case where this isn't true
+ tp = p[0];
+ p[0] = ''; // Temporarily make p blank
+ }
+
+ // Merge v and p. Not that v MUST be first since the order matters
+ v = this.merge(v, p);
+ let mn = m[0];
+ let md = m[1];
+ const vn = v[0];
+ const vd = v[1];
+ // Filters
+ // if the top has a variable but the numerator is one drop it
+ if (vn && Number(mn) === 1) {
+ mn = '';
+ }
+ // If denominator is 1 drop it always
+ if (Number(md) === 1) {
+ md = '';
+ }
+ // Prepare the top portion but check that it's not already bracketed. If it is then leave out the cdot
+ const top = this.join(
+ /** @type {string} */ (mn),
+ /** @type {string} */ (vn),
+ isBracketed(/** @type {string} */ (vn)) ? '' : this.dot
+ );
+
+ // Prepare the bottom portion but check that it's not already bracketed. If it is then leave out the cdot
+ const bottom = this.join(
+ /** @type {string} */ (md),
+ /** @type {string} */ (vd),
+ isBracketed(/** @type {string} */ (vd)) ? '' : this.dot
+ );
+ // Format the power if it exists
+ // make it a fraction if both top and bottom exists
+ if (top && bottom) {
+ let frac = this.frac(top, bottom);
+ if (combinePower && tp) {
+ frac = this.brackets(frac) + tp;
+ }
+ return frac;
+ }
+ // Otherwise only the top exists so return that
+
+ return top;
+ },
+ /**
+ * @param {string[]} a
+ * @param {string[]} b
+ * @returns {string[]}
+ */
+ merge(a, b) {
+ const r = [];
+ for (let i = 0; i < 2; i++) {
+ r[i] = a[i] + b[i];
+ }
+ return r;
+ },
+ /**
+ * Joins together two strings if both exist
+ *
+ * @param {string} n
+ * @param {string} d
+ * @param {string} glue
+ * @returns {string}
+ */
+ join(n, d, glue) {
+ if (!n && !d) {
+ return '';
+ }
+ if (n && !d) {
+ return n;
+ }
+ if (d && !n) {
+ return d;
+ }
+ return n + glue + d;
+ },
+ /**
+ * Places subscripts in braces for proper formatting
+ *
+ * @param {string} v
+ * @returns {string}
+ */
+ formatSubscripts(v) {
+ // Split it at the underscore
+ const arr = v.toString().split('_');
+
+ let name = '';
+
+ // Loop over all entries except the first one
+ while (arr.length > 1) {
+ // Wrap all in braces except for the last one
+ if (arr.length > 0) {
+ name = `_${this.braces(arr.pop() + name)}`;
+ }
+ }
+
+ return arr[0] + name;
+ },
+ /**
+ * @param {string[]} pArray
+ * @returns {string[]}
+ */
+ formatP(pArray) {
+ for (let i = 0; i < 2; i++) {
+ const p = pArray[i];
+ if (p) {
+ pArray[i] = `^${this.braces(p)}`;
+ }
+ }
+ return pArray;
+ },
+ /**
+ * Formats the fractions accordingly.
+ *
+ * @param {FracType} f
+ * @param {boolean} isPow
+ * @returns {string}
+ */
+ formatFrac(f, isPow) {
+ const n = f.num.toString();
+ const d = f.den.toString();
+ // No need to have x^1
+ if (isPow && n === '1' && d === '1') {
+ return '';
+ }
+ // No need to have x/1
+ if (d === '1') {
+ return n;
+ }
+ return this.frac(n, d);
+ },
+ /**
+ * @param {string} n
+ * @param {string} d
+ * @returns {string}
+ */
+ frac(n, d) {
+ return `\\frac${this.braces(n)}${this.braces(d)}`;
+ },
+ /**
+ * @param {string} e
+ * @returns {string}
+ */
+ braces(e) {
+ return `{${e}}`;
+ },
+ /**
+ * @param {string} e
+ * @param {string} [typ]
+ * @returns {string}
+ */
+ brackets(e, typ) {
+ typ ||= 'parens';
+ const bracketTypes = {
+ parens: ['(', ')'],
+ square: ['[', ']'],
+ brace: ['{', '}'],
+ abs: ['|', '|'],
+ angle: ['\\langle', '\\rangle'],
+ };
+ const bracket = bracketTypes[typ];
+ return `\\left${bracket[0]}${e}\\right${bracket[1]}`;
+ },
+ /**
+ * Removes extreneous tokens
+ *
+ * @param {LaTeXTokenType[]} tokens
+ * @returns {{ type: string; value: string }[] & { type?: string }}
+ */
+ filterTokens(tokens) {
+ /** @type {{ type: string; value: string }[] & { type?: string }} */
+ const filtered = /** @type {{ type: string; value: string }[] & { type?: string }} */ ([]);
+
+ // Copy over the type of the scope
+ if (isArray(tokens)) {
+ filtered.type = /** @type {{ type?: string }} */ (tokens).type;
+ }
+
+ // The items that need to be disposed
+ const d = ['\\', 'left', 'right', 'big', 'Big', 'large', 'Large'];
+ for (let i = 0, l = tokens.length; i < l; i++) {
+ const token = tokens[i];
+ const nextToken = tokens[i + 1];
+ if (token.value === '\\' && nextToken.value === '\\') {
+ filtered.push(token);
+ } else if (isArray(token)) {
+ filtered.push(
+ /** @type {{ type: string; value: string }} */ (
+ /** @type {unknown} */ (LaTeX.filterTokens(/** @type {LaTeXTokenType[]} */ (token)))
+ )
+ );
+ } else if (d.indexOf(token.value) === -1) {
+ filtered.push(token);
+ }
+ }
+ return filtered;
+ },
+ /**
+ * Parses tokens from LaTeX string. Does not do any error checking
+ *
+ * @param {unknown} rawTokens
+ * @returns {string}
+ */
+ parse(rawTokens) {
+ const { SQRT } = LaTeXDeps;
+
+ let i;
+ let l;
+ let retval = '';
+ const tokens = this.filterTokens(/** @type {LaTeXTokenType[]} */ (/** @type {unknown} */ (rawTokens)));
+ const replace = {
+ cdot: '',
+ times: '',
+ infty: 'Infinity',
+ };
+ // Get the next token
+ const next = function (n) {
+ return tokens[typeof n === 'undefined' ? ++i : (i += n)];
+ };
+ const parseNext = function () {
+ return LaTeX.parse(next());
+ };
+ const get = function (token) {
+ if (token in replace) {
+ return replace[token];
+ }
+ // A quirk with implicit multiplication forces us to check for *
+ if (token === '*' && tokens[i + 1].value === '&') {
+ next(2); // Skip this and the &
+ return ',';
+ }
+
+ if (token === '&') {
+ next();
+ return ','; // Skip the *
+ }
+ // If it's the end of a row, return the row separator
+ if (token === '\\') {
+ return '],[';
+ }
+ return token;
+ };
+
+ // Start parsing the tokens
+ for (i = 0, l = tokens.length; i < l; i++) {
+ const token = tokens[i];
+ // Fractions
+ if (token.value === 'frac') {
+ // Parse and wrap it in brackets
+ const n = parseNext();
+ const d = parseNext();
+ retval += `${n}/${d}`;
+ } else if (token.value in LaTeX.symbols) {
+ if (token.value === SQRT && tokens[i + 1].type === 'vector' && tokens[i + 2].type === 'NerdamerSet') {
+ const base = parseNext();
+ const expr = parseNext();
+ retval += `${expr}^${inBrackets(`1/${base}`)}`;
+ } else {
+ retval += token.value + parseNext();
+ }
+ } else if (token.value === 'int') {
+ const f = parseNext();
+ // Skip the comma
+ i++;
+ // Get the variable of integration
+ let dx = next().value;
+ dx = get(dx.substring(1, dx.length));
+ retval += `integrate${inBrackets(`${f},${dx}`)}`;
+ } else if (token.value === 'int_') {
+ const lower = parseNext(); // Lower
+ i++; // Skip the ^
+ let u = next().value; // Upper
+ // if it is in brackets
+ if (u === undefined) {
+ i--;
+ u = parseNext();
+ }
+ const f = parseNext(); // Function
+
+ // get the variable of integration
+ let dx = next().value;
+ // Skip the comma
+ if (dx === ',') {
+ dx = next().value;
+ }
+ // If 'd', skip
+ if (dx === 'differentialD') {
+ // Skip the *
+ i++;
+ dx = next().value;
+ }
+ if (dx === 'mathrm') {
+ // Skip the mathrm{d}
+ i++;
+ dx = next().value;
+ }
+ retval += `defint${inBrackets(`${f},${lower},${u},${dx}`)}`;
+ } else if (token.value && token.value.startsWith('int_')) {
+ // Var l = parseNext(); // lower
+ const intLower = token.value.replace('int_', '');
+ i++; // Skip the ^
+ let u = next().value; // Upper
+ // if it is in brackets
+ if (u === undefined) {
+ i--;
+ u = parseNext();
+ }
+ const f = parseNext(); // Function
+
+ // get the variable of integration
+ let dx = next().value;
+ // Skip the comma
+ if (dx === ',') {
+ dx = next().value;
+ }
+ // If 'd', skip
+ if (dx === 'differentialD') {
+ // Skip the *
+ i++;
+ dx = next().value;
+ }
+ if (dx === 'mathrm') {
+ // Skip the mathrm{d}
+ i++;
+ dx = next().value;
+ }
+ retval += `defint${inBrackets(`${f},${intLower},${u},${dx}`)}`;
+ } else if (token.value === 'mathrm') {
+ const f = tokens[++i][0].value;
+ retval += f + parseNext();
+ }
+ // Sum and product
+ else if (token.value === 'sum_' || token.value === 'prod_') {
+ const fn = token.value === 'sum_' ? 'sum' : 'product';
+ const nxt = next();
+ i++; // Skip the caret
+ const end = parseNext();
+ const f = parseNext();
+ retval += fn + inBrackets([f, get(nxt[0]), get(nxt[2]), get(end)].join(','));
+ } else if (token.value === 'lim_') {
+ const nxt = next();
+ retval += `limit${inBrackets([parseNext(), get(nxt[0]), get(nxt[2])].join(','))}`;
+ } else if (token.value === 'begin') {
+ const nxt = next();
+ if (Array.isArray(nxt)) {
+ const v = nxt[0].value;
+ if (v === 'matrix') {
+ // Start a matrix
+ retval += 'matrix([';
+ }
+ }
+ } else if (token.value === 'end') {
+ const nxt = next();
+ if (Array.isArray(nxt)) {
+ const v = nxt[0].value;
+ if (v === 'matrix') {
+ // End a matrix
+ retval += '])';
+ }
+ }
+ } else if (Array.isArray(token)) {
+ retval += get(LaTeX.parse(token));
+ } else {
+ retval += get(token.value.toString());
+ }
+ }
+
+ return inBrackets(retval);
+ },
+ /**
+ * Initializes the LaTeX parser with custom operators for LaTeX parsing. Called once from the IIFE after Parser is
+ * available.
+ */
+ initParser() {
+ const ParserClass = LaTeXDeps.Parser;
+ const keep = ['classes', 'setOperator', 'getOperators', 'getBrackets', 'tokenize', 'toRPN', 'tree', 'units'];
+ const parser = new ParserClass();
+ for (const x in parser) {
+ if (keep.indexOf(x) === -1) {
+ delete parser[x];
+ }
+ }
+ parser.setOperator({
+ precedence: 8,
+ operator: '\\',
+ action: 'slash',
+ prefix: true,
+ postfix: false,
+ leftAssoc: true,
+ operation(e) {
+ return e;
+ },
+ });
+ parser.setOperator({
+ precedence: 8,
+ operator: '\\,',
+ action: 'slash_comma',
+ prefix: true,
+ postfix: false,
+ leftAssoc: true,
+ operation(e) {
+ return e;
+ },
+ });
+ const brackets = parser.getBrackets();
+ brackets['{'].maps_to = undefined;
+ this.parser = parser;
+ },
+};
+
+// Assign LaTeX to CoreDeps immediately so VectorDeps/MatrixDeps getters work
+CoreDeps.classes.LaTeX = LaTeX;
+
+// Settings Object ==============================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Dependencies are injected via SettingsConstDeps which is set by the IIFE after initialization.
+
+/**
+ * Dependency container for Settings object constants. Populated by the IIFE during initialization.
+ *
+ * @type {{
+ * LONG_PI: string;
+ * LONG_E: string;
+ * }}
+ */
+const SettingsConstDeps = {
+ get LONG_PI() {
+ return CoreDeps.ext.LONG_PI;
+ },
+ get LONG_E() {
+ return CoreDeps.ext.LONG_E;
+ },
+};
+
+/** Configuration settings for nerdamer. */
+const Settings = {
+ // Enables/Disables call peekers. False means callPeekers are disabled and true means callPeekers are enabled.
+ callPeekers: false,
+
+ // The max number up to which to cache primes. Making this too high causes performance issues
+ init_primes: 1000,
+
+ /** @type {string[]} */
+ exclude: [],
+ // If you don't care about division by zero for example then this can be set to true.
+ // Has some nasty side effects so choose carefully.
+ suppress_errors: false,
+ // The global used to invoke the libary to parse to a number. Normally cos(9) for example returns
+ // cos(9) for convenience but parse to number will always try to return a number if set to true.
+ PARSE2NUMBER: false,
+ // This flag forces the a clone to be returned when add, subtract, etc... is called
+ SAFE: false,
+ // The symbol to use for imaginary symbols
+ IMAGINARY: 'i',
+ // The modules used to link numeric function holders
+ /** @type {(typeof Math | Record<string, Function>)[]} */
+ FUNCTION_MODULES: [Math],
+ // Allow certain characters
+ ALLOW_CHARS: ['π'],
+ // Allow nerdamer to convert multi-character variables
+ USE_MULTICHARACTER_VARS: true,
+ // Allow changing of power operator
+ POWER_OPERATOR: '^',
+ // Function catch regex
+ FUNCTION_REGEX: /^\s*(?<fnName>[a-z_][a-z0-9_]*)\((?<fnArgs>[a-z0-9_,\s]*)\)\s*:?=\s*(?<fnBody>.+)\s*$/iu,
+ // The variable validation regex
+ // VALIDATION_REGEX: /^[a-z_][a-z\d\_]*$/i
+ VALIDATION_REGEX:
+ /^[a-z_αAβBγΓδΔϵEζZηHθΘιIκKλΛμMνNξΞoOπΠρPσΣτTυϒϕΦχXψΨωΩ∞][0-9a-z_αAβBγΓδΔϵEζZηHθΘιIκKλΛμMνNξΞoOπΠρPσΣτTυϒϕΦχXψΨωΩ]*$/iu,
+ // The regex used to determine which characters should be included in implied multiplication
+ IMPLIED_MULTIPLICATION_REGEX:
+ /(?<coeff>[+\-/*]*[0-9]+)(?<vars>[a-z_αAβBγΓδΔϵEζZηHθΘιIκKλΛμMνNξΞoOπΠρPσΣτTυϒϕΦχXψΨωΩ]+[+\-/*]*)/giu,
+ // Aliases
+ ALIASES: {
+ π: 'pi',
+ '∞': 'Infinity',
+ },
+ POSITIVE_MULTIPLIERS: false,
+ // Cached items
+ /** @type {{ roots?: Record<string, number> }} */
+ CACHE: {},
+ // Print out warnings or not
+ SILENCE_WARNINGS: false,
+ // Precision
+ PRECISION: 21,
+ // The Expression defaults to this value for decimal places
+ EXPRESSION_DECP: 19,
+ // The text function defaults to this value for decimal places
+ DEFAULT_DECP: 16,
+ // Function mappings
+ VECTOR: 'vector',
+ PARENTHESIS: 'parens',
+ SQRT: 'sqrt',
+ ABS: 'abs',
+ FACTORIAL: 'factorial',
+ DOUBLEFACTORIAL: 'dfactorial',
+ // Reference pi and e - initialized via SettingsConstDeps inside IIFE
+ get LONG_PI() {
+ return SettingsConstDeps.LONG_PI;
+ },
+ get LONG_E() {
+ return SettingsConstDeps.LONG_E;
+ },
+ PI: Math.PI,
+ E: Math.E,
+ LOG: 'log',
+ LOG_LATEX: 'log',
+ LOG10: 'log10',
+ LOG10_LATEX: 'log_{10}',
+ LOG2: 'log2',
+ LOG2_LATEX: 'log_{2}',
+ LOG1P: 'log1p',
+ LOG1P_LATEX: 'ln\\left( 1 + {0} \\right)',
+ MAX_EXP: 200000,
+ // The number of scientific place to round to
+ SCIENTIFIC_MAX_DECIMAL_PLACES: 14,
+ // True if ints should not be converted to
+ SCIENTIFIC_IGNORE_ZERO_EXPONENTS: true,
+ // Exponent (absolute value) from which to switch from decimals to scientific in "decimals_or_scientific" mode
+ SCIENTIFIC_SWITCH_FROM_DECIMALS_MIN_EXPONENT: 7,
+ // No simplify() or solveFor() should take more ms than this
+ TIMEOUT: 800,
+ /** Initializes Settings.CACHE.roots with precomputed nth roots. Called once from IIFE during initialization. */
+ initCache() {
+ this.CACHE.roots = {};
+ const x = 40;
+ const y = 40;
+ for (let i = 2; i <= x; i++) {
+ for (let j = 2; j <= y; j++) {
+ const nthpow = nerdamerBigInt(i).pow(j);
+ this.CACHE.roots[`${nthpow}-${j}`] = i;
+ }
+ }
+ },
+};
+
+// Set Settings.CONST_HASH at module scope (previously in IIFE)
+Settings.CONST_HASH = CoreDeps.fnNames.CONST_HASH;
+
+// Initialize Settings.CACHE.roots at module scope
+Settings.initCache();
+
+// Populate LateRefs.Settings now that Settings is defined
+LateRefs.Settings = Settings;
+
+// Math2 Object ==================================================================
+// Extracted outside IIFE to enable proper TypeScript type inference.
+// Dependencies are injected via Math2Deps which is set by the IIFE after initialization.
+
+/**
+ * Dependency container for Math2 object. Populated by the IIFE during initialization.
+ *
+ * Note: bigInt is typed as BigIntegerStaticType which doesn't expose constructor in TypeScript, but supports 'new' at
+ * runtime. Type assertions are used at call sites.
+ *
+ * @type {{
+ * bigInt: BigIntegerStaticType;
+ * BIG_LOG_CACHE: string[];
+ * PRIMES: number[];
+ * NerdamerSymbol: SymbolConstructor;
+ * CB: number;
+ * P: number;
+ * }}
+ */
+const Math2Deps = {
+ get bigInt() {
+ return CoreDeps.ext.bigInt;
+ },
+ get BIG_LOG_CACHE() {
+ return CoreDeps.ext.BIG_LOG_CACHE;
+ },
+ get PRIMES() {
+ return CoreDeps.ext.PRIMES;
+ },
+ get NerdamerSymbol() {
+ return CoreDeps.classes.NerdamerSymbol;
+ },
+ get CB() {
+ return CoreDeps.groups.CB;
+ },
+ get P() {
+ return CoreDeps.groups.P;
+ },
+};
+
+/** Math utility functions for nerdamer. */
+const Math2 = {
+ csc(x) {
+ return 1 / Math.sin(x);
+ },
+ sec(x) {
+ return 1 / Math.cos(x);
+ },
+ cot(x) {
+ return 1 / Math.tan(x);
+ },
+ acsc(x) {
+ return Math.asin(1 / x);
+ },
+ asec(x) {
+ return Math.acos(1 / x);
+ },
+ acot(x) {
+ return Math.PI / 2 - Math.atan(x);
+ },
+ // https://gist.github.com/jiggzson/df0e9ae8b3b06ff3d8dc2aa062853bd8
+ erf(x) {
+ const t = 1 / (1 + 0.5 * Math.abs(x));
+ const result =
+ 1 -
+ t *
+ Math.exp(
+ -x * x -
+ 1.26551223 +
+ t *
+ (1.00002368 +
+ t *
+ (0.37409196 +
+ t *
+ (0.09678418 +
+ t *
+ (-0.18628806 +
+ t *
+ (0.27886807 +
+ t *
+ (-1.13520398 +
+ t *
+ (1.48851587 +
+ t *
+ (-0.82215223 +
+ t * 0.17087277))))))))
+ );
+ return x >= 0 ? result : -result;
+ },
+ diff(f) {
+ const h = 0.001;
+
+ const derivative = function (x) {
+ return (f(x + h) - f(x - h)) / (2 * h);
+ };
+
+ return derivative;
+ },
+ median(...values) {
+ values.sort((a, b) => a - b);
+
+ const half = Math.floor(values.length / 2);
+
+ if (values.length % 2) {
+ return values[half];
+ }
+
+ return (values[half - 1] + values[half]) / 2.0;
+ },
+ /*
+ * Reverses continued fraction calculation
+ * @param {obj} contd
+ * @returns {number}
+ */
+ fromContinued(contd) {
+ const arr = contd.fractions.slice();
+ let e = 1 / arr.pop();
+ for (let i = 0, l = arr.length; i < l; i++) {
+ e = 1 / (arr.pop() + e);
+ }
+ return contd.sign * (contd.whole + e);
+ },
+ /*
+ * Calculates continued fractions
+ * @param {number} n
+ * @param {number} x The number of places
+ * @returns {number}
+ */
+ continuedFraction(n, x) {
+ x ||= 20;
+ const sign = Math.sign(n); /* Store the sign*/
+ const absn = Math.abs(n); /* Get the absolute value of the number*/
+ const whole = Math.floor(absn); /* Get the whole*/
+ let ni = absn - whole; /* Subtract the whole*/
+ let c = 0; /* The counter to keep track of iterations*/
+ let done = false;
+ const epsilon = 1e-14;
+ const max = 1e7;
+ let e;
+ let w;
+ const retval = {
+ whole,
+ sign,
+ fractions: [],
+ };
+ /* Start calculating*/
+ while (!done && ni !== 0) {
+ /* Invert and get the whole*/
+ e = 1 / ni;
+ w = Math.floor(e);
+ if (w > max) {
+ /* This signals that we may have already gone too far*/
+ const d = Math2.fromContinued(retval) - n;
+ if (d <= Number.EPSILON) {
+ break;
+ }
+ }
+ /* Add to result*/
+ retval.fractions.push(w);
+ /* Move the ni to the decimal*/
+ ni = e - w;
+ /* Ni should always be a decimal. If we have a whole number then we're in the rounding errors*/
+ if (ni <= epsilon || c >= x - 1) {
+ done = true;
+ }
+ c++;
+ }
+ /* Cleanup 1/(n+1/1) = 1/(n+1) so just move the last digit one over if it's one*/
+ let idx = retval.fractions.length - 1;
+ if (retval.fractions[idx] === 1) {
+ retval.fractions.pop();
+ /* Increase the last one by one*/
+ retval.fractions[--idx]++;
+ }
+ return retval;
+ },
+ bigpow(n, p) {
+ if (!(n instanceof Frac)) {
+ n = Frac.create(n);
+ }
+ if (!(p instanceof Frac)) {
+ p = Frac.create(p);
+ }
+ const retval = new Frac(0);
+ if (p.isInteger()) {
+ retval.num = n.num.pow(p.toString());
+ retval.den = n.den.pow(p.toString());
+ } else {
+ const num = Frac.create(n.num ** p.num);
+ const den = Frac.create(n.den ** p.num);
+
+ retval.num = Math2.nthroot(num, p.den.toString());
+ retval.den = Math2.nthroot(den, p.den);
+ }
+ return retval;
+ },
+ // http://stackoverflow.com/questions/15454183/how-to-make-a-function-that-computes-the-factorial-for-numbers-with-decimals
+ gamma(z) {
+ const g = 7;
+ const gammaCoeffs = [
+ 0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313, -176.61502916214059,
+ 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7,
+ ];
+ if (z < 0.5) {
+ return Math.PI / (Math.sin(Math.PI * z) * Math2.gamma(1 - z));
+ }
+ z -= 1;
+
+ let x = gammaCoeffs[0];
+ for (let i = 1; i < g + 2; i++) {
+ x += gammaCoeffs[i] / (z + i);
+ }
+
+ const t = z + g + 0.5;
+ return Math.sqrt(2 * Math.PI) * t ** (z + 0.5) * Math.exp(-t) * x;
+ },
+ // Factorial
+ bigfactorial(x) {
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ let retval = new Math2Deps.bigInt(1);
+ for (let i = 2; i <= x; i++) {
+ retval = retval.times(i);
+ }
+ return new Frac(retval);
+ },
+ // https://en.wikipedia.org/wiki/Logarithm#Calculation
+ bigLog(x) {
+ const CACHE = Math2Deps.BIG_LOG_CACHE;
+ if (CACHE[x]) {
+ return Frac.quick.apply(null, CACHE[x].split('/'));
+ }
+ x = new Frac(x);
+ const n = 80;
+ let retval = new Frac(0);
+ const a = x.subtract(new Frac(1));
+ const b = x.add(new Frac(1));
+ for (let i = 0; i < n; i++) {
+ const t = new Frac(2 * i + 1);
+ const k = Math2.bigpow(a.divide(b), t);
+ const r = t.clone().invert().multiply(k);
+ retval = retval.add(r);
+ }
+ return retval.multiply(new Frac(2));
+ },
+ // The factorial function but using the big library instead
+ factorial(x) {
+ const isInteger = x % 1 === 0;
+
+ /* Factorial for negative integers is complex infinity according to Wolfram Alpha*/
+ if (isInteger && x < 0) {
+ return NaN;
+ }
+
+ if (!isInteger) {
+ return Math2.gamma(x + 1);
+ }
+
+ let retval = 1;
+ for (let i = 2; i <= x; i++) {
+ retval *= i;
+ }
+ return retval;
+ },
+ // Double factorial
+ // http://mathworld.wolfram.com/DoubleFactorial.html
+ dfactorial(x) {
+ /* The return value*/
+ /** @type {FracType | number} */
+ let r = new Frac(1);
+ if (isInt(x)) {
+ const isEven = x % 2 === 0;
+ /* If x = isEven then n = x/2 else n = (x-1)/2*/
+ const n = isEven ? x / 2 : (x + 1) / 2;
+ /* Start the loop*/
+ if (isEven) {
+ for (let i = 1; i <= n; i++) {
+ r = /** @type {FracType} */ (r).multiply(new Frac(2).multiply(new Frac(i)));
+ }
+ } else {
+ for (let i = 1; i <= n; i++) {
+ r = /** @type {FracType} */ (r).multiply(new Frac(2).multiply(new Frac(i)).subtract(new Frac(1)));
+ }
+ }
+ } else {
+ /* Not yet extended to bigNum*/
+ r =
+ 2 ** ((1 + 2 * x - Math.cos(Math.PI * x)) / 4) *
+ Math.PI ** ((Math.cos(Math.PI * x) - 1) / 4) *
+ Math2.gamma(1 + x / 2);
+ }
+
+ /* Done*/
+ return r;
+ },
+ GCD(...rest) {
+ const args = arrayUnique(rest.map(x => Math.abs(x))).sort();
+ let a = Math.abs(args.shift());
+ let n = args.length;
+
+ while (n-- > 0) {
+ let b = Math.abs(args.shift());
+ while (true) {
+ a %= b;
+ if (a === 0) {
+ a = b;
+ break;
+ }
+ b %= a;
+ if (b === 0) {
+ break;
+ }
+ }
+ }
+ return a;
+ },
+ QGCD(...args) {
+ let a = args[0];
+ for (let i = 1; i < args.length; i++) {
+ const b = args[i];
+ const sign = a.isNegative() && b.isNegative() ? -1 : 1;
+ a = b.gcd(a);
+ if (sign < 0) {
+ a.negate();
+ }
+ }
+ return a;
+ },
+ LCM(a, b) {
+ return (a * b) / Math2.GCD(a, b);
+ },
+ // Pow but with the handling of negative numbers
+ // http://stackoverflow.com/questions/12810765/calculating-cubic-root-for-negative-number
+ pow(b, e) {
+ if (b < 0) {
+ if (Math.abs(e) < 1) {
+ /* Nth root of a negative number is imaginary when n is even*/
+ if ((1 / e) % 2 === 0) {
+ return NaN;
+ }
+ return -(Math.abs(b) ** e);
+ }
+ }
+ return b ** e;
+ },
+ factor(n) {
+ n = Number(n);
+ const sign = Math.sign(n); /* Store the sign*/
+ /* move the number to absolute value*/
+ n = Math.abs(n);
+ const ifactors = Math2.ifactor(n);
+ let factors = new Math2Deps.NerdamerSymbol();
+ factors.symbols = {};
+ factors.group = Math2Deps.CB;
+ for (const x in ifactors) {
+ if (!Object.hasOwn(ifactors, x)) {
+ continue;
+ }
+ const factor = new Math2Deps.NerdamerSymbol(1);
+ factor.group = Math2Deps.P; /* Cheat a little*/
+ factor.value = x;
+ /** @type {NerdamerSymbolType} */
+ const powerSym = /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (new Math2Deps.NerdamerSymbol(ifactors[x]))
+ );
+ factor.power = powerSym;
+ factors.symbols[x] = factor;
+ }
+ factors.updateHash();
+
+ if (n === 1) {
+ factors = new Math2Deps.NerdamerSymbol(n);
+ }
+
+ /* Put back the sign*/
+ if (sign < 0) {
+ factors.negate();
+ }
+
+ return factors;
+ },
+ /**
+ * Uses trial division
+ *
+ * @param {number} n - The number being factored
+ * @param {object} factors - The factors object
+ * @returns {object}
+ */
+ sfactor(n, factors) {
+ factors ||= {};
+ const r = Math.floor(Math.sqrt(n));
+ const { PRIMES } = Math2Deps;
+ const lcprime = PRIMES[PRIMES.length - 1];
+ /* A one-time cost... Hopefully ... And don't bother for more than a million*/
+ /* takes too long*/
+ if (r > lcprime && n < 1e6) {
+ generatePrimes(r);
+ }
+ const l = PRIMES.length;
+ for (let i = 0; i < l; i++) {
+ const prime = PRIMES[i];
+ /* Trial division*/
+ while (n % prime === 0) {
+ n /= prime;
+ factors[prime] = (factors[prime] || 0) + 1;
+ }
+ }
+ if (n > 1) {
+ factors[n] = 1;
+ }
+ return factors;
+ },
+ /**
+ * Pollard's rho
+ *
+ * @param {number} num
+ * @returns {object}
+ */
+ ifactor(num) {
+ const { bigInt } = Math2Deps;
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ const input = new bigInt(num);
+ // Convert to bigInt for safety
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ let n = new bigInt(String(num));
+
+ if (n.equals(0)) {
+ return { 0: 1 };
+ }
+ const sign = n.isNegative() ? -1 : 1;
+ n = n.abs();
+ let factors = {}; /* Factor object being returned.*/
+ if (n.lt('65536')) {
+ /* Less than 2^16 just use trial division*/
+ factors = Math2.sfactor(n, factors);
+ } else {
+ const add = function (e) {
+ if (e.isPrime()) {
+ factors[e] = (factors[e] || 0) + 1;
+ } else {
+ factors = Math2.sfactor(e, factors);
+ }
+ };
+
+ try {
+ // NerdamerSet a safety
+ const max = 1e3;
+ const safetyCounter = { value: 0 };
+
+ const rho = function (c, currentN, safetyObj) {
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ let xf = new bigInt(c);
+ let cz = 2;
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ let x = new bigInt(c);
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ let factor = new bigInt(1);
+
+ while (factor.equals(1)) {
+ for (let i = 0; i <= cz && factor.equals(1); i++) {
+ // Trigger the safety
+ if (safetyObj.value++ > max) {
+ throw new Error('stopping');
+ }
+
+ x = x.pow(2).add(1).mod(currentN);
+ factor = bigInt.gcd(x.minus(xf).abs(), currentN);
+ }
+
+ cz *= 2;
+ xf = x;
+ }
+ if (factor.equals(currentN)) {
+ return rho(c + 1, currentN, safetyObj);
+ }
+ return factor;
+ };
+
+ while (!n.abs().equals(1)) {
+ if (n.isPrime()) {
+ add(n);
+ break;
+ } else {
+ const factor = rho(2, n, safetyCounter);
+ add(factor);
+ /* Divide out the factor*/
+ n = n.divide(factor);
+ }
+ }
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ // Reset factors
+ factors = {};
+ add(input);
+ }
+ }
+
+ /* Put the sign back*/
+ if (sign === -1) {
+ const sm = arrayMin(keys(factors).map(Number)); /*/ get the smallest number*/
+ factors[`-${sm}`] = factors[sm];
+ delete factors[sm];
+ }
+
+ return factors;
+ },
+ // Factors a number into rectangular box. If sides are primes that this will be
+ // their prime factors. e.g. 21 -> (7)(3), 133 -> (7)(19)
+ /**
+ * @param {number} n
+ * @param {number} [max]
+ * @returns {[number, number] | [number, number, number]}
+ */
+ boxfactor(n, max) {
+ max ||= 200; // Stop after this number of iterations
+ let c;
+ let r;
+ let d = Math.floor((5 / 12) * n); // The divisor
+ let i = 0; // Number of iterations
+ let safety = false;
+ while (true) {
+ c = Math.floor(n / d);
+ r = n % d;
+ if (r === 0) {
+ break;
+ } // We're done
+ if (safety) {
+ return /** @type {[number, number]} */ ([n, 1]);
+ }
+ d = Math.max(r, d - r);
+ i++;
+ safety = i > max;
+ }
+ return /** @type {[number, number, number]} */ ([c, d, i]);
+ },
+ fib(n) {
+ let sign = Math.sign(n);
+ n = Math.abs(n);
+ sign = even(n) ? sign : Math.abs(sign);
+ let a = 0;
+ let b = 1;
+ let f = 1;
+ for (let i = 2; i <= n; i++) {
+ f = a + b;
+ a = b;
+ b = f;
+ }
+ return f * sign;
+ },
+ mod(x, y) {
+ return x % y;
+ },
+ // http://mathworld.wolfram.com/IntegerPart.html
+ integer_part(x) {
+ const sign = Math.sign(x);
+ return sign * Math.floor(Math.abs(x));
+ },
+ simpson(f, a, b, step) {
+ const getValue = function (fn, x, side) {
+ let v = fn(x);
+ const d = 0.000000000001;
+ if (isNaN(v)) {
+ v = fn(side === 1 ? x + d : x - d);
+ }
+ return v;
+ };
+
+ step ||= 0.0001;
+ // Calculate the number of intervals
+ let n = Math.abs(Math.floor((b - a) / step));
+ // Simpson's rule requires an even number of intervals. If it's not then add 1
+ if (n % 2 !== 0) {
+ n++;
+ }
+ // Get the interval size
+ const dx = (b - a) / n;
+ // Get x0
+ let retval = getValue(f, a, 1);
+
+ // Get the middle part 4x1+2x2+4x3 ...
+ // but first set a flag to see if it's even or odd.
+ // The first one is odd so we start there
+ let isEvenIteration = false;
+ // Get x1
+ let xi = a + dx;
+ // The coefficient
+ let c;
+ let k;
+ // https://en.wikipedia.org/wiki/Simpson%27s_rule
+ for (let i = 1; i < n; i++) {
+ c = isEvenIteration ? 2 : 4;
+ k = c * getValue(f, xi, 1);
+ retval += k;
+ // Flip the even flag
+ isEvenIteration = !isEvenIteration;
+ // Increment xi
+ xi += dx;
+ }
+
+ // Add xn
+ return (retval + getValue(f, xi, 2)) * (dx / 3);
+ },
+ /**
+ * https://github.com/scijs/integrate-adaptive-simpson
+ *
+ * @param {Function} f - The function being integrated
+ * @param {number} a - Lower bound
+ * @param {number} b - Upper bound
+ * @param {number} tol - Step width
+ * @param {number} [maxdepth]
+ * @returns {number}
+ */
+ num_integrate(f, a, b, tol, maxdepth) {
+ if (maxdepth < 0) {
+ throw new Error('max depth cannot be negative');
+ }
+
+ /* This algorithm adapted from pseudocode in:*/
+ /* http://www.math.utk.edu/~ccollins/refs/Handouts/rich.pdf*/
+ function adsimp(fn, lo, hi, fa, fm, fb, V0, tolerance, maxDepth, depth, state) {
+ if (state.nanEncountered) {
+ return NaN;
+ }
+ const h = hi - lo;
+ const f1 = fn(lo + h * 0.25);
+ const f2 = fn(hi - h * 0.25);
+ /* Simple check for NaN:*/
+ if (isNaN(f1)) {
+ state.nanEncountered = true;
+ return undefined;
+ }
+ /* Simple check for NaN:*/
+ if (isNaN(f2)) {
+ state.nanEncountered = true;
+ return undefined;
+ }
+
+ const sl = (h * (fa + 4 * f1 + fm)) / 12;
+ const sr = (h * (fm + 4 * f2 + fb)) / 12;
+ const s2 = sl + sr;
+ const error = (s2 - V0) / 15;
+
+ if (state.maxDepthCount > 1000 * maxDepth) {
+ return undefined;
+ }
+
+ if (depth > maxDepth) {
+ state.maxDepthCount++;
+ return s2 + error;
+ }
+ if (Math.abs(error) < tolerance) {
+ return s2 + error;
+ }
+ const m = lo + h * 0.5;
+ const V1 = adsimp(fn, lo, m, fa, f1, fm, sl, tolerance * 0.5, maxDepth, depth + 1, state);
+ if (isNaN(V1)) {
+ state.nanEncountered = true;
+ return NaN;
+ }
+ const V2 = adsimp(fn, m, hi, fm, f2, fb, sr, tolerance * 0.5, maxDepth, depth + 1, state);
+
+ if (isNaN(V2)) {
+ state.nanEncountered = true;
+ return NaN;
+ }
+
+ return V1 + V2;
+ }
+
+ function integrate(fn, lo, hi, tolerance, maxDepth) {
+ const state = {
+ maxDepthCount: 0,
+ nanEncountered: false,
+ };
+
+ if (tolerance === undefined) {
+ tolerance = 1e-9;
+ }
+ if (maxDepth === undefined) {
+ /* Issue #458 - This was lowered because of performance issues. */
+ /* This was suspected from before but is now confirmed with this issue*/
+ maxDepth = 45;
+ }
+
+ const fa = fn(lo);
+ const fm = fn(0.5 * (lo + hi));
+ const fb = fn(hi);
+
+ const V0 = ((fa + 4 * fm + fb) * (hi - lo)) / 6;
+
+ const result = adsimp(fn, lo, hi, fa, fm, fb, V0, tolerance, maxDepth, 1, state);
+
+ if (state.maxDepthCount > 0) {
+ warn(
+ `integrate-adaptive-simpson: Warning: maximum recursion depth (${maxDepth}) reached ${
+ state.maxDepthCount
+ } times`
+ );
+ }
+
+ if (state.nanEncountered) {
+ throw new Error('Function does not converge over interval!');
+ }
+
+ return result;
+ }
+ /** @type {number} */
+ let retval;
+
+ try {
+ retval = integrate(f, a, b, tol, maxdepth);
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ /* Fallback to non-adaptive*/
+ return Math2.simpson(f, a, b);
+ }
+ return /** @type {number} */ (nround(retval, 12));
+ },
+ // https://en.wikipedia.org/wiki/Trigonometric_integral
+ // CosineIntegral
+ Ci(x) {
+ const n = 20;
+ /* Roughly Euler–Mascheroni*/
+ const g = 0.5772156649015329;
+ let sum = 0;
+ for (let i = 1; i < n; i++) {
+ /* Cache 2n*/
+ const n2 = 2 * i;
+ sum += ((-1) ** i * x ** n2) / (n2 * Math2.factorial(n2));
+ }
+ return Math.log(x) + g + sum;
+ },
+ /* SineIntegral*/
+ Si(x) {
+ const n = 20;
+ let sum = 0;
+ for (let i = 0; i < n; i++) {
+ const n2 = 2 * i;
+ sum += ((-1) ** i * x ** (n2 + 1)) / ((n2 + 1) * Math2.factorial(n2 + 1));
+ }
+ return sum;
+ },
+ /* ExponentialIntegral*/
+ Ei(x) {
+ if (Number(x) === 0) {
+ return -Infinity;
+ }
+ const n = 30;
+ const g = 0.5772156649015329; /* Roughly Euler–Mascheroni*/
+ let sum = 0;
+ for (let i = 1; i < n; i++) {
+ sum += x ** i / (i * Math2.factorial(i));
+ }
+ return g + Math.abs(Math.log(x)) + sum;
+ },
+ /* Hyperbolic Sine Integral*/
+ /* http://mathworld.wolfram.com/Shi.html*/
+ Shi(x) {
+ const n = 30;
+ let sum = 0;
+ let k;
+ let t;
+ for (let i = 0; i < n; i++) {
+ k = 2 * i;
+ t = k + 1;
+ sum += x ** t / (t * t * Math2.factorial(k));
+ }
+ return sum;
+ },
+ /* The cosine integral function*/
+ Chi(x) {
+ const dx = 0.001;
+ const g = 0.5772156649015329;
+ const f = function (t) {
+ return (Math.cosh(t) - 1) / t;
+ };
+ return (
+ Math.log(/** @type {number} */ (x)) +
+ g +
+ /** @type {number} */ (Math2.num_integrate(f, 0.002, /** @type {number} */ (x), dx))
+ );
+ },
+ /* The log integral*/
+ Li(x) {
+ return Math2.Ei(Math2.bigLog(x));
+ },
+ /* The gamma incomplete function*/
+ gamma_incomplete(n, xVal) {
+ const t = n - 1;
+ let sum = 0;
+ const x = xVal || 0;
+ for (let i = 0; i < t; i++) {
+ sum += x ** i / Math2.factorial(i);
+ }
+ return Math2.factorial(t) * Math.exp(-x) * sum;
+ },
+ /*
+ * Heaviside step function - Moved from Special.js (originally contributed by Brosnan Yuen)
+ * Specification : http://mathworld.wolfram.com/HeavisideStepFunction.html
+ * if x > 0 then 1
+ * if x == 0 then 1/2
+ * if x < 0 then 0
+ */
+ step(x) {
+ if (x > 0) {
+ return 1;
+ }
+ if (x < 0) {
+ return 0;
+ }
+ return 0.5;
+ },
+ /*
+ * Rectangle function - Moved from Special.js (originally contributed by Brosnan Yuen)
+ * Specification : http://mathworld.wolfram.com/RectangleFunction.html
+ * if |x| > 1/2 then 0
+ * if |x| == 1/2 then 1/2
+ * if |x| < 1/2 then 1
+ */
+ rect(x) {
+ const absX = Math.abs(x);
+ if (absX === 0.5) {
+ return absX;
+ }
+ if (absX > 0.5) {
+ return 0;
+ }
+ return 1;
+ },
+ /*
+ * Sinc function - Moved from Special.js (originally contributed by Brosnan Yuen)
+ * Specification : http://mathworld.wolfram.com/SincFunction.html
+ * if x == 0 then 1
+ * otherwise sin(x)/x
+ */
+ sinc(x) {
+ if (x.equals(0)) {
+ return 1;
+ }
+ return Math.sin(x) / x;
+ },
+ /*
+ * Triangle function - Moved from Special.js (originally contributed by Brosnan Yuen)
+ * Specification : http://mathworld.wolfram.com/TriangleFunction.html
+ * if |x| >= 1 then 0
+ * if |x| < then 1-|x|
+ */
+ tri(x) {
+ x = Math.abs(x);
+ if (x >= 1) {
+ return 0;
+ }
+ return 1 - x;
+ },
+ // https://en.wikipedia.org/wiki/Nth_root_algorithm
+ nthroot(A, n) {
+ /* Make sure the input is of type Frac*/
+ if (!(A instanceof Frac)) {
+ A = new Frac(A.toString());
+ }
+ if (!(n instanceof Frac)) {
+ n = new Frac(n.toString());
+ }
+ if (n.equals(1)) {
+ return A;
+ }
+ /* Begin algorithm*/
+ let xk = A.divide(new Frac(2)); /* X0*/
+ const e = new Frac(1e-15);
+ let dk;
+ let dk0;
+ let d0;
+ const a = n.clone().invert();
+ const b = n.subtract(new Frac(1));
+ do {
+ const powb = Math2.bigpow(xk, b);
+ let dkDec = a.multiply(A.divide(powb).subtract(xk)).toDecimal(25);
+ dk = Frac.create(dkDec);
+ if (d0) {
+ break;
+ }
+
+ xk = xk.add(dk);
+ /* Check to see if there's no change from the last xk*/
+ dkDec = dk.toDecimal();
+ d0 = dk0 ? dk0 === dkDec : false;
+ dk0 = dkDec;
+ } while (dk.abs().gte(e));
+
+ return xk;
+ },
+ /* https://gist.github.com/jiggzson/0c5b33cbcd7b52b36132b1e96573285f*/
+ /* Just the square root function but big :)*/
+ sqrt(n) {
+ if (!(n instanceof Frac)) {
+ n = new Frac(n);
+ }
+ let xn;
+ let d;
+ let ld;
+ let sameDelta;
+ let c = 0; /* Counter*/
+ let done = false;
+ const delta = new Frac(1e-20);
+ xn = n.divide(new Frac(2));
+ const safety = 1000;
+ do {
+ /* Break if we're not converging*/
+ if (c > safety) {
+ throw new Error(`Unable to calculate square root for ${n}`);
+ }
+ xn = xn.add(n.divide(xn)).divide(new Frac(2));
+ xn = new Frac(xn.decimal(30));
+ /* Get the difference from the true square*/
+ d = n.subtract(xn.multiply(xn));
+ /* If the square of the calculated number is close enough to the number*/
+ /* we're getting the square root or the last delta was the same as the new delta*/
+ /* then we're done*/
+ sameDelta = ld ? ld.equals(d) : false;
+ if (d.clone().abs().lessThan(delta) || sameDelta) {
+ done = true;
+ }
+ /* Store the calculated delta*/
+ ld = d;
+ c++; /* Increase the counter*/
+ } while (!done);
+
+ return xn;
+ },
+};
+
+// Register Math2 in Settings.FUNCTION_MODULES (before Parser instantiation)
+Settings.FUNCTION_MODULES.push(Math2);
+reserveNames(/** @type {object} */ (Math2));
+
+// Populate LateRefs.Math2 now that Math2 is defined
+LateRefs.Math2 = Math2;
+
+// IsSymbol Function ==============================================================
+/**
+ * Checks to see if the object provided is a NerdamerSymbol
+ *
+ * @param {unknown} obj
+ * @returns {obj is NerdamerSymbolType}
+ */
+function isSymbol(obj) {
+ return obj instanceof CoreDeps.classes.NerdamerSymbol;
+}
+
+// IsVector Function ==============================================================
+/**
+ * Checks to see if the object provided is a Vector
+ *
+ * @param {object} obj
+ * @returns {obj is VectorType}
+ */
+function isVector(obj) {
+ return obj instanceof Vector;
+}
+
+// IsMatrix Function ==============================================================
+/**
+ * Checks to see if the object provided is a Matrix
+ *
+ * @param {object} obj
+ * @returns {obj is MatrixType}
+ */
+function isMatrix(obj) {
+ return obj instanceof Matrix;
+}
+
+// IsExpression Function ===========================================================
+/**
+ * Checks to see if the object provided is an Expression
+ *
+ * @param {object} obj
+ * @returns {obj is ExpressionType}
+ */
+function isExpression(obj) {
+ return obj instanceof Expression;
+}
+
+// Variables Function ==============================================================
+/**
+ * Dependency container for variables function. Initialized inside the IIFE.
+ *
+ * @type {{
+ * EX: number;
+ * CP: number;
+ * CB: number;
+ * S: number;
+ * PL: number;
+ * FN: number;
+ * }}
+ */
+const VariablesDeps = {
+ get EX() {
+ return CoreDeps.groups.EX;
+ },
+ get CP() {
+ return CoreDeps.groups.CP;
+ },
+ get CB() {
+ return CoreDeps.groups.CB;
+ },
+ get S() {
+ return CoreDeps.groups.S;
+ },
+ get PL() {
+ return CoreDeps.groups.PL;
+ },
+ get FN() {
+ return CoreDeps.groups.FN;
+ },
+};
+
+/**
+ * This method traverses the symbol structure and grabs all the variables in a symbol. The variable names are then
+ * returned in alphabetical order.
+ *
+ * @param {NerdamerSymbolType | FracType} obj
+ * @param {boolean} poly
+ * @param {object} vars - An object containing the variables. Do not pass this in as it generated automatically. In the
+ * future this will be a Collector object.
+ * @returns {string[]} - An array containing variable names
+ */
+function variables(obj, poly = null, vars = null) {
+ vars ||= {
+ c: [],
+ add(value) {
+ if (this.c.indexOf(value) === -1 && isNaN(value)) {
+ this.c.push(value);
+ }
+ },
+ };
+
+ if (isSymbol(obj)) {
+ const { group } = obj;
+ const prevgroup = obj.previousGroup;
+ if (group === VariablesDeps.EX) {
+ variables(obj.power, poly, vars);
+ }
+
+ if (
+ group === VariablesDeps.CP ||
+ group === VariablesDeps.CB ||
+ prevgroup === VariablesDeps.CP ||
+ prevgroup === VariablesDeps.CB
+ ) {
+ for (const x in obj.symbols) {
+ if (!Object.hasOwn(obj.symbols, x)) {
+ continue;
+ }
+ variables(obj.symbols[x], poly, vars);
+ }
+ } else if (group === VariablesDeps.S || prevgroup === VariablesDeps.S) {
+ // Very crude needs fixing. TODO
+ if (!(obj.value === 'e' || obj.value === 'pi' || obj.value === Settings.IMAGINARY)) {
+ vars.add(obj.value);
+ }
+ } else if (group === VariablesDeps.PL || prevgroup === VariablesDeps.PL) {
+ variables(/** @type {NerdamerSymbolType | FracType} */ (firstObject(obj.symbols)), poly, vars);
+ } else if (group === VariablesDeps.EX) {
+ if (!isNaN(Number(obj.value))) {
+ vars.add(obj.value);
+ }
+ variables(obj.power, poly, vars);
+ } else if (group === VariablesDeps.FN && !poly && obj.args) {
+ for (let i = 0; i < obj.args.length; i++) {
+ variables(obj.args[i], poly, vars);
+ }
+ }
+ }
+
+ return vars.c.sort();
+}
+
+// GetCoeffs Function ==============================================================
+// Uses ParserDeps._ for parser access.
+
+/**
+ * Returns the coefficients of a symbol given a variable. Given ax^2+b^x+c, it divides each nth term by x^n.
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {NerdamerSymbolType} wrt
+ */
+function getCoeffs(symbol, wrt, _info) {
+ const coeffs = [];
+ // We loop through the symbols and stick them in their respective
+ // containers e.g. y*x^2 goes to index 2
+ symbol.each(term => {
+ let coeff;
+ let p;
+ if (term.contains(wrt)) {
+ // We want only the coefficient which in this case will be everything but the variable
+ // e.g. a*b*x -> a*b if the variable to solve for is x
+ coeff = term.stripVar(wrt);
+ const x = /** @type {NerdamerSymbolType} */ (ParserDeps._.divide(term.clone(), coeff.clone()));
+ p = /** @type {FracType} */ (x.power).toDecimal();
+ } else {
+ coeff = term;
+ p = 0;
+ }
+ const e = coeffs[p];
+ // If it exists just add it to it
+ coeffs[p] = e ? ParserDeps._.add(e, coeff) : coeff;
+ }, true);
+
+ for (let i = 0; i < coeffs.length; i++) {
+ coeffs[i] ||= new CoreDeps.classes.NerdamerSymbol(0);
+ }
+ // Fill the holes
+ return coeffs;
+}
+
+// Nroots Function =================================================================
+/**
+ * Dependency container for nroots function.
+ *
+ * @type {{
+ * _: ParserType;
+ * FN: number;
+ * P: number;
+ * N: number;
+ * NerdamerSymbol: SymbolConstructor;
+ * }}
+ */
+const NrootsDeps = {
+ get _() {
+ return CoreDeps.parser;
+ },
+ get FN() {
+ return CoreDeps.groups.FN;
+ },
+ get P() {
+ return CoreDeps.groups.P;
+ },
+ get N() {
+ return CoreDeps.groups.N;
+ },
+ get NerdamerSymbol() {
+ return CoreDeps.classes.NerdamerSymbol;
+ },
+};
+
+/**
+ * Gets nth roots of a number
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {VectorType}
+ */
+function nroots(symbol) {
+ let a;
+ let b;
+ let _roots;
+
+ if (symbol.group === NrootsDeps.FN && symbol.fname === '') {
+ a = NrootsDeps.NerdamerSymbol.unwrapPARENS(NrootsDeps._.parse(symbol).toLinear());
+ b = NrootsDeps._.parse(symbol.power);
+ } else if (symbol.group === NrootsDeps.P) {
+ a = NrootsDeps._.parse(symbol.value);
+ b = NrootsDeps._.parse(symbol.power);
+ }
+
+ if (a && b && a.group === NrootsDeps.N && b.group === NrootsDeps.N && a.multiplier.isNegative()) {
+ _roots = [];
+
+ const parts = NrootsDeps.NerdamerSymbol.toPolarFormArray(evaluate(symbol));
+ const r = parts[0];
+
+ // Var r = _.parse(a).abs().toString();
+
+ // https://en.wikipedia.org/wiki/De_Moivre%27s_formula
+ const x = NrootsDeps._.arg(a);
+ const n = b.multiplier.den.toString();
+ const p = b.multiplier.num.toString();
+
+ const formula = '(({0})^({1})*(cos({3})+({2})*sin({3})))^({4})';
+
+ for (let i = 0; i < Number(n); i++) {
+ const t = evaluate(NrootsDeps._.parse(format('(({0})+2*pi*({1}))/({2})', x, i, n))).multiplier.toDecimal();
+ _roots.push(evaluate(NrootsDeps._.parse(format(formula, r, n, Settings.IMAGINARY, t, p))));
+ }
+ return Vector.fromArray(_roots);
+ }
+ if (symbol.isConstant(true, true)) {
+ const sign = symbol.sign();
+ const x = evaluate(symbol.abs());
+ const root = NrootsDeps._.sqrt(x);
+
+ _roots = [root.clone(), root.negate()];
+
+ if (sign < 0) {
+ _roots = /** @type {NerdamerSymbolType[]} */ (
+ _roots.map(r => NrootsDeps._.multiply(r, NrootsDeps.NerdamerSymbol.imaginary()))
+ );
+ }
+ } else {
+ _roots = [/** @type {NerdamerSymbolType} */ (NrootsDeps._.parse(symbol))];
+ }
+
+ return Vector.fromArray(_roots);
+}
+
+// Compare Function ================================================================
+// Uses ParserDeps._ for parser access.
+
+/**
+ * Compares two symbols by evaluating them with random values for variables. This is useful for checking if two
+ * different representations are mathematically equivalent.
+ *
+ * @param {NerdamerSymbolType} sym1
+ * @param {NerdamerSymbolType} sym2
+ * @param {string[]} vars - An optional array of variables to use
+ * @returns {boolean}
+ */
+function compare(sym1, sym2, vars) {
+ const n = 5; // A random number between 1 and 5 is good enough
+ /** @type {Record<string, NerdamerSymbolType>} */
+ const scope = {}; // Scope object with random numbers generated using vars
+ let comparison;
+ for (let i = 0; i < vars.length; i++) {
+ scope[vars[i]] = /** @type {NerdamerSymbolType} */ (
+ new CoreDeps.classes.NerdamerSymbol(Math.floor(Math.random() * n) + 1)
+ );
+ }
+ block('PARSE2NUMBER', () => {
+ comparison = ParserDeps._.parse(sym1, scope).equals(ParserDeps._.parse(sym2, scope));
+ });
+ return comparison;
+}
+
+// IsFraction Function =============================================================
+/**
+ * Checks to see if a number or NerdamerSymbol is a fraction
+ *
+ * @param {number | string | NerdamerSymbolType} num
+ * @returns {boolean}
+ */
+function isFraction(num) {
+ if (isSymbol(num)) {
+ return isFraction(/** @type {NerdamerSymbolType} */ (num).multiplier.toDecimal());
+ }
+ return Number(num) % 1 !== 0;
+}
+
+// ArraySum Function ===============================================================
+// Uses ParserDeps._ for parser access.
+
+/**
+ * Returns the sum of an array
+ *
+ * @param {Array} arr
+ * @param {boolean} toNumber
+ * @returns {NerdamerSymbolType | number}
+ */
+function arraySum(arr, toNumber) {
+ /** @type {NerdamerSymbolType} */
+ let sum = /** @type {NerdamerSymbolType} */ (new CoreDeps.classes.NerdamerSymbol(0));
+ for (let i = 0; i < arr.length; i++) {
+ const x = arr[i];
+ // Convert to symbol if not
+ sum = /** @type {NerdamerSymbolType} */ (ParserDeps._.add(sum, isSymbol(x) ? x : ParserDeps._.parse(x)));
+ }
+
+ return toNumber ? Number(sum) : sum;
+}
+
+// AllConstants Function ===========================================================
+/**
+ * Checks if all arguments aren't just all numbers but if they are constants as well e.g. pi, e.
+ *
+ * @param {object} args
+ * @returns {boolean}
+ */
+function allConstants(args) {
+ for (let i = 0; i < args.length; i++) {
+ if (args[i].isPi() || args[i].isE()) {
+ continue;
+ }
+ if (!args[i].isConstant(true)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// FillHoles Function ==============================================================
+/**
+ * Fills holes in an array with zero symbol or generates one with n zeroes
+ *
+ * @param {Array} arr
+ * @param {number} n
+ */
+function fillHoles(arr, n) {
+ n ||= arr.length;
+ for (let i = 0; i < n; i++) {
+ const sym = arr[i];
+ if (!sym) {
+ arr[i] = new CoreDeps.classes.NerdamerSymbol(0);
+ }
+ }
+ return arr;
+}
+
+// IsNegative Function =============================================================
+/**
+ * @param {number | NerdamerSymbolType | FracType} obj
+ * @returns {boolean}
+ */
+function isNegative(obj) {
+ if (isSymbol(obj)) {
+ return obj.multiplier.lessThan(0);
+ }
+ if (typeof obj === 'object' && 'lessThan' in obj) {
+ return /** @type {FracType} */ (obj).lessThan(0);
+ }
+ return /** @type {number} */ (obj) < 0;
+}
+
+// Separate Function ===============================================================
+/**
+ * Dependency container for separate function.
+ *
+ * @type {{
+ * _: ParserType;
+ * S: number;
+ * FN: number;
+ * EX: number;
+ * ABS: string;
+ * }}
+ */
+const SeparateDeps = {
+ get _() {
+ return CoreDeps.parser;
+ },
+ get S() {
+ return CoreDeps.groups.S;
+ },
+ get FN() {
+ return CoreDeps.groups.FN;
+ },
+ get EX() {
+ return CoreDeps.groups.EX;
+ },
+ get ABS() {
+ return CoreDeps.fnNames.ABS;
+ },
+};
+
+/**
+ * Separates out the variables into terms of variables. e.g. x+y+x_y+sqrt(2)+pi returns {x: x, y: y, x y: x_y,
+ * constants: sqrt(2)+pi
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {Record<string, NerdamerSymbolType>} [o]
+ * @returns {Record<string, NerdamerSymbolType>}
+ * @throws {Error} For exponentials
+ */
+function separate(symbol, o) {
+ symbol = /** @type {NerdamerSymbolType} */ (SeparateDeps._.expand(symbol));
+ o ||= {};
+ const insert = function (key, sym) {
+ o[key] ||= new CoreDeps.classes.NerdamerSymbol(0);
+ o[key] = /** @type {NerdamerSymbolType} */ (SeparateDeps._.add(o[key], sym.clone()));
+ };
+ symbol.each(x => {
+ if (x.isConstant('all')) {
+ insert('constants', x);
+ } else if (x.group === SeparateDeps.S) {
+ insert(x.value, x);
+ } else if (x.group === SeparateDeps.FN && (x.fname === SeparateDeps.ABS || x.fname === '')) {
+ separate(x.args[0]);
+ } else if (x.group === SeparateDeps.EX || x.group === SeparateDeps.FN) {
+ // Todo: gm: this occurs with sqrt(a+1)
+ // Do nothing - skip EX and FN groups
+ } else {
+ insert(variables(x).join(' '), x);
+ }
+ });
+
+ return o;
+}
+
+// DecomposeFn Function ============================================================
+/**
+ * Dependency container for decomposeFn function.
+ *
+ * @type {{
+ * _: ParserType;
+ * CP: number;
+ * }}
+ */
+const DecomposeFnDeps = {
+ get _() {
+ return CoreDeps.parser;
+ },
+ get CP() {
+ return CoreDeps.groups.CP;
+ },
+};
+
+/**
+ * Breaks a function down into its parts wrt to a variable, mainly coefficients. Example: a*x^2+b wrt x
+ *
+ * @overload
+ * @param {NerdamerSymbolType} fn
+ * @param {string} wrt
+ * @param {true} asObj
+ * @returns {{ a: NerdamerSymbolType; x: NerdamerSymbolType; ax: NerdamerSymbolType; b: NerdamerSymbolType }}
+ */
+/**
+ * @overload
+ * @param {NerdamerSymbolType} fn
+ * @param {string} wrt
+ * @param {false} [asObj]
+ * @returns {NerdamerSymbolType[]}
+ */
+/**
+ * @param {NerdamerSymbolType} fn
+ * @param {string} wrt
+ * @param {boolean} [asObj]
+ */
+function decomposeFn(fn, wrt, asObj) {
+ wrt = String(wrt); // Convert to string
+ let ax;
+ let b;
+ if (fn.group === DecomposeFnDeps.CP) {
+ const t = /** @type {NerdamerSymbolType} */ (DecomposeFnDeps._.expand(fn.clone())).stripVar(wrt);
+ ax = DecomposeFnDeps._.subtract(fn.clone(), t.clone());
+ b = t;
+ } else {
+ ax = fn.clone();
+ }
+ const a = /** @type {NerdamerSymbolType} */ (ax).stripVar(wrt);
+ const x = DecomposeFnDeps._.divide(/** @type {NerdamerSymbolType} */ (ax).clone(), a.clone());
+ b ||= new CoreDeps.classes.NerdamerSymbol(0);
+ if (asObj) {
+ return {
+ a,
+ x,
+ ax,
+ b,
+ };
+ }
+ return [a, x, ax, b];
+}
+
+// Mix Function ====================================================================
+// Uses ParserDeps._ for parser access.
+
+/**
+ * Used to multiply two expressions in expanded form
+ *
+ * @param {NerdamerSymbolType} a
+ * @param {NerdamerSymbolType} b
+ */
+function mix(a, b, opt) {
+ // Flip them if b is a CP or PL and a is not
+ if ((b.isComposite() && !a.isComposite()) || (b.isLinear() && !a.isLinear())) {
+ [a, b] = [b, a];
+ }
+ // A temporary variable to hold the expanded terms
+ let t = new CoreDeps.classes.NerdamerSymbol(0);
+ if (a.isLinear()) {
+ a.each(x => {
+ // If b is not a PL or a CP then simply multiply it
+ if (!b.isComposite()) {
+ const term = /** @type {NerdamerSymbolType} */ (
+ ParserDeps._.multiply(ParserDeps._.parse(x), ParserDeps._.parse(b))
+ );
+ t = /** @type {NerdamerSymbolType} */ (ParserDeps._.add(t, ParserDeps._.expand(term, opt)));
+ }
+ // Otherwise multiply out each term.
+ else if (b.isLinear()) {
+ b.each(y => {
+ const term = /** @type {NerdamerSymbolType} */ (
+ ParserDeps._.multiply(ParserDeps._.parse(x), ParserDeps._.parse(y))
+ );
+ const expanded = /** @type {NerdamerSymbolType} */ (
+ ParserDeps._.expand(/** @type {NerdamerSymbolType} */ (ParserDeps._.parse(term)), opt)
+ );
+ t = /** @type {NerdamerSymbolType} */ (ParserDeps._.add(t, expanded));
+ }, true);
+ } else {
+ t = /** @type {NerdamerSymbolType} */ (
+ ParserDeps._.add(t, ParserDeps._.multiply(x, ParserDeps._.parse(b)))
+ );
+ }
+ }, true);
+ } else {
+ // Just multiply them together
+ t = /** @type {NerdamerSymbolType} */ (ParserDeps._.multiply(a, b));
+ }
+
+ // The expanded function is now t
+ return t;
+}
+
+// ConvertToVector Function ========================================================
+// Uses ParserDeps._ for parser access.
+
+/**
+ * Converts an array to a vector. Consider moving this to Vector.fromArray
+ *
+ * @param {string[] | string | NerdamerSymbolType | number | number[]} x
+ */
+function convertToVector(x) {
+ if (isArray(x)) {
+ const vector = new Vector([]);
+ for (let i = 0; i < x.length; i++) {
+ vector.elements.push(convertToVector(x[i]));
+ }
+ return vector;
+ }
+ // Ensure that a nerdamer ready object is returned
+ if (!isSymbol(x)) {
+ return ParserDeps._.parse(x);
+ }
+ return x;
+}
+
+// ArrayGetVariables Function ======================================================
+/**
+ * Gets all the variables in an array of Symbols
+ *
+ * @param {NerdamerSymbolType[]} arr
+ */
+function arrayGetVariables(arr) {
+ let vars = variables(arr[0], null, null);
+
+ // Get all variables
+ for (let i = 1, l = arr.length; i < l; i++) {
+ vars = vars.concat(variables(arr[i]));
+ }
+ // Remove duplicates
+ vars = arrayUnique(vars).sort();
+
+ // Done
+ return vars;
+}
+
+// GetU Function ===================================================================
+// Uses ReservedDeps.RESERVED for u-substitution variable tracking.
+
+/**
+ * Is used for u-substitution. Gets a suitable u for substitution. If for instance a is used in the symbol then it keeps
+ * going down the line until one is found that's not in use. If all letters are taken then it starts appending numbers.
+ * IMPORTANT! It assumes that the substitution will be undone before the user gets to interact with the object again.
+ *
+ * @param {NerdamerSymbolType} symbol
+ */
+function getU(symbol) {
+ // Start with u
+ const u = 'u'; // Start with u
+ let v = u; // Init with u
+ let c = 0; // Postfix number
+ const vars = variables(symbol);
+ // Make sure this variable isn't reserved and isn't in the variable list
+ while (!(ReservedDeps.RESERVED.indexOf(v) === -1 && vars.indexOf(v) === -1)) {
+ v = u + c++;
+ }
+ // Get an empty slot. It seems easier to just push but the
+ // problem is that we may have some which are created by clearU
+ for (
+ let i = 0, l = ReservedDeps.RESERVED.length;
+ i <= l;
+ i++ // Reserved cannot equals false or 0 so we can safely check for a falsy type
+ ) {
+ if (!ReservedDeps.RESERVED[i]) {
+ ReservedDeps.RESERVED[i] = v; // Reserve the variable
+ break;
+ }
+ }
+ return v;
+}
+
+// _setFunction Function ===========================================================
+/**
+ * Dependency container for _setFunction function.
+ *
+ * @type {{
+ * _: ParserType;
+ * C: CoreType;
+ * USER_FUNCTIONS: string[];
+ * }}
+ */
+const InternalSetFunctionDeps = {
+ get _() {
+ return CoreDeps.parser;
+ },
+ get C() {
+ return CoreDeps.core;
+ },
+ get USER_FUNCTIONS() {
+ return CoreDeps.state.USER_FUNCTIONS;
+ },
+};
+
+/**
+ * Is used to set a user defined function using the function assign operator and also is used to set a user defined
+ * JavaScript function using the function assign operator
+ *
+ * @param {string | Function} fnName
+ * @param {string[]} [fnParams]
+ * @param {string} [fnBody]
+ * @returns {boolean}
+ */
+function _setFunction(fnName, fnParams, fnBody) {
+ if (!fnParams) {
+ const fnNameType = typeof fnName;
+
+ // Option setFunction('f(x)=x^2+2'), setFunction('f(x):=x^2+2')
+ if (fnNameType === 'string') {
+ const fnNameStr = /** @type {string} */ (fnName);
+ if (!/:?=/u.test(fnNameStr)) {
+ return false;
+ }
+
+ const match = Settings.FUNCTION_REGEX.exec(fnNameStr);
+ if (!match) {
+ return false;
+ }
+ const [, fName, fParams, fBody] = match;
+ fnName = fName;
+ fnParams = fParams.split(',').map(arg => arg.trim());
+ fnBody = fBody;
+ }
+
+ // Option setFunction(function fox(x) { return x^2; })
+ else if (fnNameType === 'function') {
+ const jsFunction = /** @type {Function} */ (fnName);
+ const jsName = jsFunction.name;
+ validateName(jsName);
+ if (!isReserved(jsName)) {
+ InternalSetFunctionDeps.C.Math2[jsName] = jsFunction;
+ InternalSetFunctionDeps._.functions[jsName] = [undefined, jsFunction.length];
+
+ if (!InternalSetFunctionDeps.USER_FUNCTIONS.includes(jsName)) {
+ InternalSetFunctionDeps.USER_FUNCTIONS.push(jsName);
+ }
+ return true;
+ }
+ return false;
+ } else {
+ return false;
+ }
+ }
+
+ fnName = /** @type {string} */ (fnName).trim();
+ validateName(fnName);
+
+ // Option setFunction('f(x)', ['x'], 'x^2+2') or setFunction('f(x)=x^2+2'), setFunction('f(x):=x^2+2')
+ if (!isReserved(fnName)) {
+ fnParams ||= variables(InternalSetFunctionDeps._.parse(fnBody));
+ fnParams = fnParams.map(p => p.trim());
+ // The function gets set to PARSER.mapped function which is just
+ // a generic function call.
+ InternalSetFunctionDeps._.functions[/** @type {string} */ (fnName)] = [
+ InternalSetFunctionDeps._.mappedFunction,
+ fnParams.length,
+ {
+ name: fnName,
+ params: fnParams,
+ body: fnBody,
+ },
+ ];
+
+ if (!InternalSetFunctionDeps.USER_FUNCTIONS.includes(fnName)) {
+ InternalSetFunctionDeps.USER_FUNCTIONS.push(fnName);
+ }
+
+ return true;
+ }
+ return false;
+}
+
+// _clearFunctions Function ========================================================
+/**
+ * Dependency container for _clearFunctions function.
+ *
+ * @type {{
+ * _: ParserType;
+ * C: CoreType;
+ * USER_FUNCTIONS: string[];
+ * }}
+ */
+const ClearFunctionsDeps = {
+ get _() {
+ return CoreDeps.parser;
+ },
+ get C() {
+ return CoreDeps.core;
+ },
+ get USER_FUNCTIONS() {
+ return CoreDeps.state.USER_FUNCTIONS;
+ },
+};
+
+/** Clears all user defined functions */
+function _clearFunctions() {
+ for (const name of ClearFunctionsDeps.USER_FUNCTIONS) {
+ delete ClearFunctionsDeps.C.Math2[name];
+ delete ClearFunctionsDeps._.functions[name];
+ }
+}
+
+// ImportFunctions Function ========================================================
+// Uses ParserDeps._ for parser access.
+
+/**
+ * Provide a mechanism for accessing functions directly. Not yet complete!!! Some functions will return undefined. This
+ * can maybe just remove the function object at some point when all functions are eventually housed in the global
+ * function object. Returns ALL parser available functions. Parser.functions may not contain all functions
+ *
+ * @returns {import('./index').NerdamerCore.MathFunctions}
+ */
+function importFunctions() {
+ /** @type {import('./index').NerdamerCore.MathFunctions} */
+ const o = {};
+ for (const x in ParserDeps._.functions) {
+ if (!Object.hasOwn(ParserDeps._.functions, x)) {
+ continue;
+ }
+ o[x] = /** @type {any} */ (ParserDeps._.functions[x][0]);
+ }
+ return o;
+}
+
+// Text Function ==================================================================
+/**
+ * Dependency container for text function. These are initialized later once they're available inside the IIFE.
+ *
+ * @type {{
+ * bigInt: BigIntegerStaticType;
+ * isSymbol: Function;
+ * isVector: Function;
+ * N: number;
+ * P: number;
+ * S: number;
+ * FN: number;
+ * PL: number;
+ * CB: number;
+ * CP: number;
+ * EX: number;
+ * CUSTOM_OPERATORS: object;
+ * }}
+ */
+const TextDeps = {
+ get bigInt() {
+ return CoreDeps.ext.bigInt;
+ },
+ get isSymbol() {
+ return CoreDeps.utils.isSymbol;
+ },
+ get isVector() {
+ return CoreDeps.utils.isVector;
+ },
+ get N() {
+ return CoreDeps.groups.N;
+ },
+ get P() {
+ return CoreDeps.groups.P;
+ },
+ get S() {
+ return CoreDeps.groups.S;
+ },
+ get FN() {
+ return CoreDeps.groups.FN;
+ },
+ get PL() {
+ return CoreDeps.groups.PL;
+ },
+ get CB() {
+ return CoreDeps.groups.CB;
+ },
+ get CP() {
+ return CoreDeps.groups.CP;
+ },
+ get EX() {
+ return CoreDeps.groups.EX;
+ },
+ get CUSTOM_OPERATORS() {
+ return CoreDeps.state.CUSTOM_OPERATORS;
+ },
+};
+
+/**
+ * Convert an object to its text representation.
+ *
+ * @param {NerdamerSymbolType} obj
+ * @param {string} [option]
+ * @param {number} [useGroup]
+ * @param {number} [decp]
+ * @returns {string}
+ */
+function text(obj, option = undefined, useGroup = undefined, decp = undefined) {
+ const asHash = option === 'hash';
+ // Whether to wrap numbers in brackets
+ let wrapCondition;
+ const opt = asHash ? undefined : option;
+ const asDecimal = opt === 'decimal' || opt === 'decimals' || opt === 'decimals_or_scientific';
+
+ // Only set default decp for decimals_or_scientific mode, not for plain decimals.
+ // This preserves full valueOf() precision for internal operations.
+ //
+ // Background: When a NerdamerSymbol with a fractional multiplier (e.g., 1/3) is converted to
+ // a string via valueOf(), it becomes a decimal like "0.3333333333333333". If that
+ // decimal is then parsed back into a NerdamerSymbol, nerdamer uses a continued fractions
+ // algorithm (Fraction.fullConversion) to reconstruct the fraction. This algorithm
+ // finds the simplest fraction within epsilon (1e-30) of the decimal value.
+ //
+ // The precision matters:
+ // - 16 threes (0.3333333333333333): exactly equals JS's 1/3 in IEEE 754 → reconstructs to 1/3
+ // - 15 threes (0.333333333333333): differs by ~3.3e-16 → becomes 321685687669321/965057063007964
+ //
+ // Setting decp here would trigger toDecimal(16) which can truncate precision.
+ // By not setting decp for plain decimals mode, we preserve full valueOf() precision.
+ if (opt === 'decimals_or_scientific' && typeof decp === 'undefined') {
+ decp = Settings.DEFAULT_DECP;
+ }
+
+ function toString(fracObj, decimalPlaces) {
+ switch (option) {
+ case 'decimals':
+ case 'decimal':
+ wrapCondition ||= function (_str) {
+ return false;
+ };
+ if (decimalPlaces) {
+ return fracObj.toDecimal(decimalPlaces);
+ }
+ return fracObj.valueOf();
+ case 'recurring': {
+ wrapCondition ||= function (s) {
+ return s.indexOf("'") !== -1;
+ };
+
+ const str = fracObj.toString();
+ // Verify that the string is actually a fraction
+ const frac = /^-?\d+(?:\/\d+)?$/u.exec(str);
+ if (frac.length === 0) {
+ return str;
+ }
+
+ // Split the fraction into the numerator and denominator
+ const parts = frac[0].split('/');
+ let negative = false;
+ let m = Number(parts[0]);
+ if (m < 0) {
+ m = -m;
+ negative = true;
+ }
+ let n = Number(parts[1]);
+ n ||= 1;
+
+ // https://softwareengineering.stackexchange.com/questions/192070/what-is-a-efficient-way-to-find-repeating-decimal#comment743574_192081
+ /** @type {number | string} */
+ let quotient = Math.floor(m / n);
+ let c = 10 * (m - quotient * n);
+ quotient = `${quotient.toString()}.`;
+ while (c && c < n) {
+ c *= 10;
+ quotient += '0';
+ }
+ let digits = '';
+ const passed = [];
+ let i = 0;
+ while (true) {
+ if (typeof passed[c] !== 'undefined') {
+ const prefix = digits.slice(0, passed[c]);
+ const cycle = digits.slice(passed[c]);
+ const result = `${quotient + prefix}'${cycle}'`;
+ return (negative ? '-' : '') + result.replace("'0'", '').replace(/\.$/u, '');
+ }
+ const q = Math.floor(c / n);
+ const r = c - q * n;
+ passed[c] = i;
+ digits += q.toString();
+ i += 1;
+ c = 10 * r;
+ }
+ }
+ case 'mixed': {
+ wrapCondition ||= function (s) {
+ return s.indexOf('/') !== -1;
+ };
+
+ const str = fracObj.toString();
+ // Verify that the string is actually a fraction
+ const frac = /^-?\d+(?:\/\d+)?$/u.exec(str);
+ if (frac.length === 0) {
+ return str;
+ }
+
+ // Split the fraction into the numerator and denominator
+ const parts = frac[0].split('/');
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ const numer = new TextDeps.bigInt(parts[0]);
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ let denom = new TextDeps.bigInt(parts[1]);
+ if (denom.equals(0)) {
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ denom = new TextDeps.bigInt(1);
+ }
+
+ // Return the quotient plus the remainder
+ const divmod = numer.divmod(denom);
+ const { quotient } = divmod;
+ const { remainder } = divmod;
+ const operator = parts[0][0] === '-' || quotient.equals(0) || remainder.equals(0) ? '' : '+';
+ return (
+ (quotient.equals(0) ? '' : quotient.toString()) +
+ operator +
+ (remainder.equals(0) ? '' : `${remainder.toString()}/${parts[1]}`)
+ );
+ }
+ case 'scientific':
+ wrapCondition ||= function (_str) {
+ return false;
+ };
+ return new Scientific(fracObj.valueOf()).toString(Settings.SCIENTIFIC_MAX_DECIMAL_PLACES);
+ case 'decimals_or_scientific': {
+ wrapCondition ||= function (_str) {
+ return false;
+ };
+ const decimals = fracObj.valueOf();
+ const scientific = new Scientific(decimals);
+ if (Math.abs(scientific.exponent) >= Settings.SCIENTIFIC_SWITCH_FROM_DECIMALS_MIN_EXPONENT) {
+ return scientific.toString(Settings.SCIENTIFIC_MAX_DECIMAL_PLACES);
+ }
+ if (decimalPlaces) {
+ return fracObj.toDecimal(decimalPlaces);
+ }
+ return decimals;
+ }
+
+ default:
+ wrapCondition ||= function (s) {
+ return s.indexOf('/') !== -1;
+ };
+
+ return fracObj.toString();
+ }
+ }
+
+ // If the object is a symbol
+ if (TextDeps.isSymbol(obj)) {
+ /** @type {string | number} */
+ let multiplier = '';
+ let power = '';
+ let sign = '';
+ const group = obj.group || useGroup;
+ let { value } = obj;
+
+ // If the value is to be used as a hash then the power and multiplier need to be suppressed
+ if (!asHash) {
+ // Get multiplier as string. Don't pass decp here to preserve precision
+ // for internal operations - decp is only applied in the TextDeps.N case below.
+ let om = toString(obj.multiplier);
+ if (String(om) === '-1' && String(obj.multiplier) === '-1') {
+ sign = '-';
+ om = '1';
+ }
+ // Only add the multiplier if it's not 1
+ if (String(om) !== '1') {
+ multiplier = om;
+ }
+ // Use asDecimal to get the object back as a decimal
+ const p = obj.power ? toString(obj.power) : '';
+ // Only add the multiplier
+ if (String(p) !== '1') {
+ // Is it a symbol
+ if (isSymbol(p)) {
+ power = text(p, opt);
+ } else {
+ power = p;
+ }
+ }
+ }
+
+ switch (group) {
+ case TextDeps.N: {
+ multiplier = '';
+ // Handle numeric output with appropriate precision:
+ // - decimals_or_scientific: use toString with decp to trigger Scientific formatting
+ // - decimals with explicit decp: round to requested decimal places
+ // - otherwise: use default toString which preserves full precision via valueOf()
+ let m;
+ if (opt === 'decimals_or_scientific') {
+ m = toString(obj.multiplier, decp);
+ } else if (decp && asDecimal) {
+ m = obj.multiplier.toDecimal(decp);
+ } else {
+ m = toString(obj.multiplier);
+ }
+ // If it's numerical then all we need is the multiplier
+ value = String(obj.multiplier) === '-1' ? '1' : m;
+ power = '';
+ break;
+ }
+ case TextDeps.PL:
+ value = /** @type {NerdamerSymbolType[]} */ (obj.collectSymbols())
+ .map(x => {
+ let txt = text(x, opt, useGroup, decp);
+ if (txt === '0') {
+ txt = '';
+ }
+ return txt;
+ })
+ .sort()
+ .join('+')
+ .replace(/\+-/gu, '-');
+ break;
+ case TextDeps.CP:
+ value = /** @type {NerdamerSymbolType[]} */ (obj.collectSymbols())
+ .map(x => {
+ let txt = text(x, opt, useGroup, decp);
+ if (txt === '0') {
+ txt = '';
+ }
+ return txt;
+ })
+ .sort()
+ .join('+')
+ .replace(/\+-/gu, '-');
+ break;
+ case TextDeps.CB:
+ value = obj
+ .collectSymbols(symbol => {
+ const g = symbol.group;
+ // Both groups will already be in brackets if their power is greater than 1
+ // so skip it.
+ if (
+ (g === TextDeps.PL || g === TextDeps.CP) &&
+ symbol.power.equals(1) &&
+ symbol.multiplier.equals(1)
+ ) {
+ return inBrackets(text(symbol, opt));
+ }
+ return text(symbol, opt);
+ })
+ .join('*');
+ break;
+ case TextDeps.EX: {
+ const pg = obj.previousGroup;
+ const pwg = /** @type {NerdamerSymbolType} */ (obj.power).group;
+
+ // TextDeps.PL are the exception. It's simpler to just collect and set the value
+ if (pg === TextDeps.PL) {
+ value = obj.collectSymbols(text, opt).join('+').replace('+-', '-');
+ }
+ if (!(pg === TextDeps.N || pg === TextDeps.S || pg === TextDeps.FN) && !asHash) {
+ value = inBrackets(value);
+ }
+
+ if (
+ (pwg === TextDeps.CP ||
+ pwg === TextDeps.CB ||
+ pwg === TextDeps.PL ||
+ /** @type {NerdamerSymbolType} */ (obj.power).multiplier.toString() !== '1') &&
+ power
+ ) {
+ power = inBrackets(power);
+ }
+ break;
+ }
+ }
+
+ if (group === TextDeps.FN) {
+ value = obj.fname + inBrackets(obj.args.map(symbol => text(symbol, opt)).join(','));
+ }
+ // TODO: Needs to be more efficient. Maybe.
+ if (group === TextDeps.FN && obj.fname in TextDeps.CUSTOM_OPERATORS) {
+ let a = text(obj.args[0]);
+ let b = text(obj.args[1]);
+ if (obj.args[0].isComposite()) // Preserve the brackets
+ {
+ a = inBrackets(a);
+ }
+ if (obj.args[1].isComposite()) // Preserve the brackets
+ {
+ b = inBrackets(b);
+ }
+ value = a + TextDeps.CUSTOM_OPERATORS[obj.fname] + b;
+ }
+ // Wrap the power since / is less than ^
+ // TODO: introduce method call isSimple
+ const shouldWrapPower =
+ typeof wrapCondition === 'function' ? /** @type {Function} */ (wrapCondition)(power) : false;
+ if (power && group !== TextDeps.EX && shouldWrapPower) {
+ power = inBrackets(power);
+ }
+
+ // The following groups are held together by plus or minus. They can be raised to a power or multiplied
+ // by a multiplier and have to be in brackets to preserve the order of precedence
+ if (
+ ((group === TextDeps.CP || group === TextDeps.PL) &&
+ ((multiplier && String(multiplier) !== '1') || sign === '-')) ||
+ ((group === TextDeps.CB || group === TextDeps.CP || group === TextDeps.PL) &&
+ power &&
+ String(power) !== '1') ||
+ (!asHash && group === TextDeps.P && String(value) === '-1') ||
+ obj.fname === Settings.PARENTHESIS
+ ) {
+ value = inBrackets(value);
+ }
+
+ if (
+ decp &&
+ (option === 'decimal' || ((option === 'decimals' || option === 'decimals_or_scientific') && multiplier))
+ ) {
+ // Scientific notation? regular rounding would be the wrong decision here
+ if (multiplier.toString().includes('e')) {
+ if (option !== 'decimals_or_scientific') {
+ // ToPrecision can create extra digits, so we also
+ // convert it to string straight up and pick the shorter version
+ const numMult = Number(multiplier);
+ const m1 = numMult.toExponential();
+ const m2 = numMult.toPrecision(decp);
+ /** @type {string | number} */
+ multiplier = m1.length < m2.length ? m1 : m2;
+ }
+ } else {
+ multiplier = nround(Number(multiplier), decp);
+ }
+ }
+
+ // Add the sign back
+ let c = sign + multiplier;
+
+ const shouldWrapMult =
+ typeof wrapCondition === 'function' ? /** @type {Function} */ (wrapCondition)(multiplier) : false;
+ if (multiplier && shouldWrapMult) {
+ c = inBrackets(c);
+ }
+
+ if (Number(power) < 0) {
+ power = inBrackets(power);
+ }
+
+ // Add the multiplication back
+ if (multiplier) {
+ c = `${c}*`;
+ }
+
+ if (power) {
+ if (value === 'e' && Settings.E_TO_EXP) {
+ return `${c}exp${inBrackets(power)}`;
+ }
+ power = Settings.POWER_OPERATOR + power;
+ }
+
+ // This needs serious rethinking. Must fix
+ if (group === TextDeps.EX && value.charAt(0) === '-') {
+ value = inBrackets(value);
+ }
+
+ let cv = c + value;
+
+ if (obj.parens) {
+ cv = inBrackets(cv);
+ }
+
+ return cv + power;
+ }
+ if (TextDeps.isVector(obj)) {
+ const l = obj.elements.length;
+ const c = [];
+ for (let i = 0; i < l; i++) {
+ c.push(obj.elements[i].text(option));
+ }
+ return `[${c.join(',')}]`;
+ }
+ try {
+ return obj.toString();
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ return '';
+ }
+}
+
+/**
+ * Dependency container for NerdamerSymbol class. These are initialized later once they're available inside the IIFE.
+ *
+ * @type {{
+ * bigDec: DecimalStaticType;
+ * bigInt: BigIntegerStaticType;
+ * _: ParserType;
+ * N: number;
+ * P: number;
+ * S: number;
+ * FN: number;
+ * PL: number;
+ * CB: number;
+ * CP: number;
+ * EX: number;
+ * CONST_HASH: string;
+ * isSymbol: Function;
+ * text: Function;
+ * variables: Function;
+ * SQRT: string;
+ * PARENTHESIS: string;
+ * }}
+ */
+const NerdamerSymbolDeps = {
+ get bigDec() {
+ return CoreDeps.ext.bigDec;
+ },
+ get bigInt() {
+ return CoreDeps.ext.bigInt;
+ },
+ get _() {
+ return CoreDeps.parser;
+ },
+ get N() {
+ return CoreDeps.groups.N;
+ },
+ get P() {
+ return CoreDeps.groups.P;
+ },
+ get S() {
+ return CoreDeps.groups.S;
+ },
+ get FN() {
+ return CoreDeps.groups.FN;
+ },
+ get PL() {
+ return CoreDeps.groups.PL;
+ },
+ get CB() {
+ return CoreDeps.groups.CB;
+ },
+ get CP() {
+ return CoreDeps.groups.CP;
+ },
+ get EX() {
+ return CoreDeps.groups.EX;
+ },
+ get CONST_HASH() {
+ return CoreDeps.fnNames.CONST_HASH;
+ },
+ get isSymbol() {
+ return CoreDeps.utils.isSymbol;
+ },
+ get text() {
+ return CoreDeps.utils.text;
+ },
+ get variables() {
+ return CoreDeps.utils.variables;
+ },
+ get SQRT() {
+ return CoreDeps.fnNames.SQRT;
+ },
+ get PARENTHESIS() {
+ return CoreDeps.fnNames.PARENTHESIS;
+ },
+};
+
+/**
+ * NerdamerSymbol class - The core symbol class for mathematical expressions
+ *
+ * @implements {NerdamerSymbolType}
+ */
+class NerdamerSymbol {
+ /** @type {number} */
+ group;
+
+ /** @type {string} */
+ value;
+
+ /** @type {FracType} */
+ multiplier;
+
+ /** @type {FracType | NerdamerSymbolType} */
+ power;
+
+ /** @type {NerdamerSymbolType[] | undefined} */
+ args = undefined;
+
+ /** @type {string | undefined} */
+ fname = undefined;
+
+ /** @type {boolean | undefined} */
+ isImgSymbol = undefined;
+
+ /** @type {boolean | undefined} */
+ imaginary = undefined;
+
+ /** @type {boolean | undefined} */
+ isInfinity = undefined;
+
+ /** @param {string | number | FracType | object} obj */
+ constructor(obj) {
+ checkTimeout();
+
+ const isInfinity = obj === 'Infinity';
+ // Convert big numbers to a string
+ if (
+ typeof obj === 'object' &&
+ obj !== null &&
+ /** @type {DecimalType} */ (obj) instanceof NerdamerSymbolDeps.bigDec
+ ) {
+ obj = /** @type {DecimalType} */ (obj).toString();
+ }
+ // Define numeric symbols
+ const objStr = String(obj);
+ if (
+ /^(?<sign>-?\+?\d+)\.?\d*e?-?\+?\d*/iu.test(objStr) ||
+ (typeof obj === 'object' &&
+ obj !== null &&
+ /** @type {DecimalType} */ (obj) instanceof NerdamerSymbolDeps.bigDec)
+ ) {
+ this.group = NerdamerSymbolDeps.N;
+ this.value = NerdamerSymbolDeps.CONST_HASH;
+ this.multiplier = new Frac(obj);
+ }
+ // Define symbolic symbols
+ else {
+ this.group = NerdamerSymbolDeps.S;
+ validateName(obj);
+ this.value = obj;
+ this.multiplier = new Frac(1);
+ this.imaginary = obj === Settings.IMAGINARY;
+ this.isInfinity = isInfinity;
+ }
+
+ // As of 6.0.0 we switched to infinite precision so all objects have a power
+ // Although this is still redundant in constants, it simplifies the logic in
+ // other parts so we'll keep it
+ this.power = new Frac(1);
+ }
+
+ /**
+ * Returns vanilla imaginary symbol
+ *
+ * @returns {NerdamerSymbolType}
+ */
+ static imaginary() {
+ const s = new NerdamerSymbol(Settings.IMAGINARY);
+ s.imaginary = true;
+ return s;
+ }
+
+ /**
+ * Return nerdamer's representation of Infinity
+ *
+ * @param {number} negative -1 to return negative infinity
+ * @returns {NerdamerSymbolType}
+ */
+ static infinity(negative = undefined) {
+ const v = new NerdamerSymbol('Infinity');
+ if (negative === -1) {
+ v.negate();
+ }
+ return v;
+ }
+
+ /**
+ * Creates a shell symbol for a given group
+ *
+ * @param {number} group
+ * @param {string | number} [value]
+ * @returns {NerdamerSymbolType}
+ */
+ static shell(group, value) {
+ const symbol = new NerdamerSymbol(value);
+ symbol.group = group;
+ symbol.symbols = {};
+ symbol.length = 0;
+ return symbol;
+ }
+
+ /**
+ * Sqrt(x) -> x^(1/2)
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {boolean} [all]
+ * @returns {NerdamerSymbolType}
+ */
+ static unwrapSQRT(symbol, all) {
+ const p = symbol.power;
+ if (symbol.fname === Settings.SQRT && (symbol.isLinear() || all)) {
+ const t = symbol.args[0].clone();
+ // Power is Frac here since we're in a function context (not EX group)
+ t.power = /** @type {FracType} */ (t.power).multiply(new Frac(1 / 2));
+ t.multiplier = t.multiplier.multiply(symbol.multiplier);
+ symbol = t;
+ if (all) {
+ symbol.power = /** @type {FracType} */ (p).multiply(new Frac(1 / 2));
+ }
+ }
+
+ return symbol;
+ }
+
+ /**
+ * @param {NerdamerSymbolType} [a]
+ * @param {NerdamerSymbolType} [b]
+ * @returns {NerdamerSymbolType}
+ */
+ static hyp(a, b) {
+ a ||= new NerdamerSymbol(0);
+ b ||= new NerdamerSymbol(0);
+ const { _ } = NerdamerSymbolDeps;
+ return /** @type {NerdamerSymbolType} */ (
+ _.sqrt(
+ /** @type {NerdamerSymbolType} */ (
+ _.add(
+ _.pow(/** @type {NerdamerSymbolType} */ (a.clone()), new NerdamerSymbol(2)),
+ _.pow(/** @type {NerdamerSymbolType} */ (b.clone()), new NerdamerSymbol(2))
+ )
+ )
+ )
+ );
+ }
+
+ /**
+ * Converts to polar form array
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {[NerdamerSymbolType, NerdamerSymbolType]}
+ */
+ static toPolarFormArray(symbol) {
+ const re = symbol.realpart();
+ const im = symbol.imagpart();
+ const r = NerdamerSymbol.hyp(re, im);
+ const theta = re.equals(0)
+ ? /** @type {NerdamerSymbolType} */ (NerdamerSymbolDeps._.parse('pi/2'))
+ : /** @type {NerdamerSymbolType} */ (
+ NerdamerSymbolDeps._.trig.atan(
+ /** @type {NerdamerSymbolType} */ (NerdamerSymbolDeps._.divide(im, re))
+ )
+ );
+ return [r, theta];
+ }
+
+ /**
+ * Removes parentheses
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ static unwrapPARENS(symbol) {
+ if (symbol.fname === '') {
+ const r = symbol.args[0];
+ // Power.multiply: both powers should be Frac in parentheses context
+ r.power = /** @type {FracType} */ (r.power).multiply(/** @type {FracType} */ (symbol.power));
+ r.multiplier = r.multiplier.multiply(symbol.multiplier);
+ if (symbol.fname === '') {
+ return NerdamerSymbol.unwrapPARENS(r);
+ }
+ return r;
+ }
+ return symbol;
+ }
+
+ /**
+ * Quickly creates a NerdamerSymbol
+ *
+ * @param {string | number} value
+ * @param {number} [power]
+ * @returns {NerdamerSymbolType}
+ */
+ static create(value, power) {
+ power = power === undefined ? 1 : power;
+ const { _ } = NerdamerSymbolDeps;
+ return _.parse(`(${value})^(${power})`);
+ }
+ /** @returns {NerdamerSymbolType} */
+ pushMinus() {
+ const { _ } = NerdamerSymbolDeps;
+ /** @type {NerdamerSymbolType} */
+ let retval = this;
+ if (
+ (this.group === NerdamerSymbolDeps.CB ||
+ this.group === NerdamerSymbolDeps.CP ||
+ this.group === NerdamerSymbolDeps.PL) &&
+ this.multiplier.lessThan(0) &&
+ !even(this.power)
+ ) {
+ // Console.log();
+ // console.log("replacing "+this.text("fractions"))
+ retval = this.clone();
+ const m = retval.multiplier.clone();
+ m.negate();
+ // Console.log(" negated multiplier: "+m)
+ retval.toUnitMultiplier();
+
+ // Console.log(" unit main part: "+this)
+ for (const termkey in retval.symbols) {
+ if (!Object.hasOwn(retval.symbols, termkey)) {
+ continue;
+ }
+ retval.symbols[termkey] = retval.symbols[termkey].clone().negate();
+ // Console.log(" negated term: "+this.symbols[termkey])
+ if (retval.group === NerdamerSymbolDeps.CB) {
+ // Console.log(" is CB, breaking");
+ break;
+ }
+ }
+
+ // Console.log(" combined: "+retval.text("fractions"));
+ if (retval.length > 0) {
+ retval.each(c => c.pushMinus());
+ // Console.log(" result: "+this.text("fractions"));
+ }
+
+ // Console.log(" negated main part: "+retval)
+ retval = /** @type {NerdamerSymbolType} */ (_.parse(retval));
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.multiply(/** @type {NerdamerSymbolType} */ (_.parse(m)), retval)
+ );
+ }
+ return retval;
+ }
+
+ /**
+ * Gets nth root accounting for rounding errors
+ *
+ * @param {number} n
+ * @returns {NerdamerSymbolType}
+ */
+ getNth(n) {
+ const { _ } = NerdamerSymbolDeps;
+ // First calculate the root
+ const parsedN = /** @type {NerdamerSymbolType} */ (_.parse(String(n)));
+ const root = /** @type {NerdamerSymbolType} */ (
+ evaluate(
+ /** @type {NerdamerSymbolType} */ (
+ _.pow(/** @type {NerdamerSymbolType} */ (_.parse(this.multiplier)), parsedN.clone().invert())
+ )
+ )
+ );
+ // Round of any errors
+ const rounded = /** @type {NerdamerSymbolType} */ (
+ _.parse(nround(/** @type {number} */ (/** @type {unknown} */ (root))))
+ );
+ // Reverse the root
+ const e = /** @type {NerdamerSymbolType} */ (
+ evaluate(/** @type {NerdamerSymbolType} */ (_.pow(rounded, parsedN.clone())))
+ );
+ // If the rounded root equals the original number then we're good
+ if (e.equals(/** @type {NerdamerSymbolType} */ (_.parse(this.multiplier)))) {
+ return rounded;
+ }
+ // Otherwise return the unrounded version
+ return root;
+ }
+
+ /**
+ * Checks if symbol is to the nth power
+ *
+ * @returns {boolean}
+ */
+ isToNth(n) {
+ const { _ } = NerdamerSymbolDeps;
+ // Start by check in the multiplier for squareness
+ // First get the root but round it because currently we still depend
+ const root = this.getNth(n);
+ const nthMultiplier = isInt(root.multiplier.toDecimal());
+ let nthPower;
+
+ if (this.group === NerdamerSymbolDeps.CB) {
+ // Start by assuming that all will be square.
+ nthPower = true;
+ // All it takes is for one of the symbols to not have an even power
+ // e.g. x^n1*y^n2 requires that both n1 and n2 are even
+ this.each(x => {
+ const isNth = x.isToNth(n);
+
+ if (!isNth) {
+ nthPower = false;
+ }
+ });
+ } else {
+ // Check if the power is divisible by n if it's not a number.
+ nthPower = this.group === NerdamerSymbolDeps.N ? true : isInt(_.divide(_.parse(this.power), _.parse(n)));
+ }
+
+ return nthMultiplier && nthPower;
+ }
+
+ /**
+ * Checks if a symbol is square
+ *
+ * @returns {boolean}
+ */
+ isSquare() {
+ return this.isToNth(2);
+ }
+
+ /**
+ * Checks if a symbol is cube
+ *
+ * @returns {boolean}
+ */
+ isCube() {
+ return this.isToNth(3);
+ }
+
+ /**
+ * Checks if a symbol is a bare variable
+ *
+ * @returns {boolean}
+ */
+ isSimple() {
+ return this.power.equals(1) && this.multiplier.equals(1);
+ }
+
+ /**
+ * Simplifies the power of the symbol
+ *
+ * @returns {NerdamerSymbolType} A clone of the symbol
+ */
+ powSimp() {
+ const { _ } = NerdamerSymbolDeps;
+ if (this.group === NerdamerSymbolDeps.CB) {
+ const powers = [];
+ const sign = this.multiplier.sign();
+ this.each(x => {
+ const p = x.power;
+ // Why waste time if I can't do anything anyway
+ if (NerdamerSymbolDeps.isSymbol(p) || p.equals(1)) {
+ return;
+ }
+ powers.push(p);
+ });
+ if (powers.length === 0) {
+ return this.clone();
+ }
+ const min = new Frac(arrayMin(powers));
+
+ // Handle the coefficient
+ // handle the multiplier
+ // sign already declared above
+ const m = this.multiplier.clone().abs();
+ const mfactors = Math2.ifactor(/** @type {number} */ (m.valueOf()));
+ // If we have a multiplier of 6750 and a min of 2 then the factors are 5^3*5^3*2
+ // we can then reduce it to 2*3*5*(15)^2
+ let out_ = new Frac(1);
+ let in_ = new Frac(1);
+
+ for (const x in mfactors) {
+ if (!Object.hasOwn(mfactors, x)) {
+ continue;
+ }
+ let n = new Frac(mfactors[x]);
+ if (!n.lessThan(min)) {
+ n = n.divide(min).subtract(new Frac(1));
+ in_ = in_.multiply(new Frac(x)); // Move the factor inside the bracket
+ }
+
+ out_ = out_.multiply(
+ /** @type {NerdamerSymbolType} */ (_.parse(`${inBrackets(x)}^${inBrackets(n)}`)).multiplier
+ );
+ }
+ /** @type {NerdamerSymbolType} */
+ let t = new NerdamerSymbol(in_);
+ this.each(x => {
+ x = x.clone();
+ x.power = x.power.divide(min);
+ t = /** @type {NerdamerSymbolType} */ (_.multiply(t, /** @type {NerdamerSymbolType} */ (x)));
+ });
+
+ const xt = /** @type {NerdamerSymbolType} */ (_.symfunction(NerdamerSymbolDeps.PARENTHESIS, [t]));
+ xt.power = min;
+ xt.multiplier = sign < 0 ? out_.negate() : out_;
+
+ return xt;
+ }
+ return this.clone();
+ }
+
+ /**
+ * Checks to see if two functions are of equal value
+ *
+ * @param {string | number | NerdamerSymbolType} symbol
+ * @returns {boolean}
+ */
+ equals(symbol) {
+ /** @type {NerdamerSymbolType} */
+ let sym;
+ if (NerdamerSymbolDeps.isSymbol(symbol)) {
+ sym = /** @type {NerdamerSymbolType} */ (symbol);
+ } else {
+ sym = new NerdamerSymbol(symbol);
+ }
+ return (
+ this.value === sym.value &&
+ /** @type {FracType} */ (this.power).equals(/** @type {FracType} */ (sym.power)) &&
+ this.multiplier.equals(sym.multiplier) &&
+ this.group === sym.group
+ );
+ }
+
+ /** @returns {NerdamerSymbolType} */
+ abs() {
+ const e = this.clone();
+ e.multiplier.abs();
+ return e;
+ }
+
+ /**
+ * Greater than
+ *
+ * @param {string | number | NerdamerSymbolType} symbol
+ * @returns {boolean}
+ */
+ gt(symbol) {
+ if (!NerdamerSymbolDeps.isSymbol(symbol)) {
+ symbol = /** @type {NerdamerSymbolType} */ (/** @type {unknown} */ (new NerdamerSymbol(symbol)));
+ }
+ const sym = /** @type {NerdamerSymbolType} */ (symbol);
+ return this.isConstant() && sym.isConstant() && this.multiplier.greaterThan(sym.multiplier);
+ }
+
+ /**
+ * Greater than or equal
+ *
+ * @param {string | number | NerdamerSymbolType} symbol
+ * @returns {boolean}
+ */
+ gte(symbol) {
+ if (!NerdamerSymbolDeps.isSymbol(symbol)) {
+ symbol = new NerdamerSymbol(symbol);
+ }
+ const sym = /** @type {NerdamerSymbolType} */ (symbol);
+ return (
+ this.equals(sym) || (this.isConstant() && sym.isConstant() && this.multiplier.greaterThan(sym.multiplier))
+ );
+ }
+
+ /**
+ * Less than
+ *
+ * @param {string | number | NerdamerSymbolType} symbol
+ * @returns {boolean}
+ */
+ lt(symbol) {
+ if (!NerdamerSymbolDeps.isSymbol(symbol)) {
+ symbol = new NerdamerSymbol(symbol);
+ }
+ const sym = /** @type {NerdamerSymbolType} */ (symbol);
+ return this.isConstant() && sym.isConstant() && this.multiplier.lessThan(sym.multiplier);
+ }
+
+ /**
+ * Less than or equal
+ *
+ * @param {string | number | NerdamerSymbolType} symbol
+ * @returns {boolean}
+ */
+ lte(symbol) {
+ if (!NerdamerSymbolDeps.isSymbol(symbol)) {
+ symbol = new NerdamerSymbol(symbol);
+ }
+ const sym = /** @type {NerdamerSymbolType} */ (symbol);
+ return this.equals(sym) || (this.isConstant() && sym.isConstant() && this.multiplier.lessThan(sym.multiplier));
+ }
+
+ /**
+ * Because nerdamer doesn't group symbols by polynomials but rather a custom grouping method, this has to be
+ * reinserted in order to make use of most algorithms. This function checks if the symbol meets the criteria of a
+ * polynomial.
+ *
+ * @param {boolean} [multivariate]
+ * @returns {boolean}
+ */
+ isPoly(multivariate = false) {
+ const g = this.group;
+ const p = this.power;
+ // The power must be a integer so fail if it's not
+ if (!isInt(p) || Number(p) < 0) {
+ return false;
+ }
+ // Constants and first orders
+ if (g === NerdamerSymbolDeps.N || g === NerdamerSymbolDeps.S || this.isConstant(true)) {
+ return true;
+ }
+ const vars = NerdamerSymbolDeps.variables(this);
+ if (g === NerdamerSymbolDeps.CB && vars.length === 1) {
+ // The variable is assumed the only one that was found
+ const v = vars[0];
+ // If no variable then guess what!?!? We're done!!! We have a polynomial.
+ if (!v) {
+ return true;
+ }
+ for (const x in this.symbols) {
+ if (!Object.hasOwn(this.symbols, x)) {
+ continue;
+ }
+ const sym = this.symbols[x];
+ // Sqrt(x)
+ if (sym.group === NerdamerSymbolDeps.FN && !sym.args[0].isConstant()) {
+ return false;
+ }
+ if (!sym.contains(v) && !sym.isConstant(true)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ // PL groups. These only fail if a power is not an int
+ // this should handle cases such as x^2*t
+ if (this.isComposite() || (g === NerdamerSymbolDeps.CB && multivariate)) {
+ // Fail if we're not checking for multivariate polynomials
+ if (!multivariate && vars.length > 1) {
+ return false;
+ }
+ // Loop though the symbols and check if they qualify
+ for (const x in this.symbols) {
+ // We've already the symbols if we're not checking for multivariates at this point
+ // so we check the sub-symbols
+ if (!this.symbols[x].isPoly(multivariate)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+
+ /*
+ //all tests must have passed so we must be dealing with a polynomial
+ return true;
+ */
+ }
+ // Removes the requested variable from the symbol and returns the remainder
+ /**
+ * @param {string} x
+ * @param {boolean} [excludeX]
+ * @returns {NerdamerSymbolType}
+ */
+ stripVar(x, excludeX = false) {
+ const { _ } = NerdamerSymbolDeps;
+ /** @type {NerdamerSymbolType} */
+ let retval;
+ if ((this.group === NerdamerSymbolDeps.PL || this.group === NerdamerSymbolDeps.S) && this.value === x) {
+ retval = /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (new NerdamerSymbol(excludeX ? 0 : this.multiplier))
+ );
+ } else if (this.group === NerdamerSymbolDeps.CB && this.isLinear()) {
+ retval = new NerdamerSymbol(1);
+ this.each(s => {
+ if (!s.contains(x, true)) {
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.multiply(retval, /** @type {NerdamerSymbolType} */ (s.clone()))
+ );
+ }
+ });
+ retval.multiplier = retval.multiplier.multiply(this.multiplier);
+ } else if (this.group === NerdamerSymbolDeps.CP && !this.isLinear()) {
+ retval = new NerdamerSymbol(this.multiplier);
+ } else if (this.group === NerdamerSymbolDeps.CP && this.isLinear()) {
+ retval = new NerdamerSymbol(0);
+ this.each(s => {
+ if (!s.contains(x)) {
+ const t = s.clone();
+ t.multiplier = t.multiplier.multiply(this.multiplier);
+ retval = /** @type {NerdamerSymbolType} */ (_.add(retval, /** @type {NerdamerSymbolType} */ (t)));
+ }
+ });
+ // BIG TODO!!! It doesn't make much sense
+ if (retval.equals(0)) {
+ retval = /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (new NerdamerSymbol(this.multiplier))
+ );
+ }
+ } else if (
+ this.group === NerdamerSymbolDeps.EX &&
+ /** @type {NerdamerSymbolType} */ (this.power).contains(x, true)
+ ) {
+ retval = new NerdamerSymbol(this.multiplier);
+ } else if (this.group === NerdamerSymbolDeps.FN && this.contains(x)) {
+ retval = new NerdamerSymbol(this.multiplier);
+ } else // Wth? This should technically be the multiplier.
+ // Unfortunately this method wasn't very well thought out :`(.
+ // should be: retval = new NerdamerSymbol(this.multiplier);
+ // use: ((1+x^2)*sqrt(-1+x^2))^(-1) for correction.
+ // this will break a bunch of unit tests so be ready to for the long haul
+ {
+ retval = this.clone();
+ }
+
+ return retval;
+ }
+ // Returns symbol in array form with x as base e.g. a*x^2+b*x+c = [c, b, a].
+ toArray(v, arr) {
+ const { _ } = NerdamerSymbolDeps;
+ arr ||= {
+ arr: [],
+ add(x, idx) {
+ const e = this.arr[idx];
+ this.arr[idx] = e ? _.add(e, x) : x;
+ },
+ };
+ const g = this.group;
+
+ if (g === NerdamerSymbolDeps.S && this.contains(v)) {
+ arr.add(new NerdamerSymbol(this.multiplier), this.power);
+ } else if (g === NerdamerSymbolDeps.CB) {
+ const a = this.stripVar(v);
+ const x = /** @type {NerdamerSymbolType} */ (
+ _.divide(
+ /** @type {NerdamerSymbolType} */ (this.clone()),
+ /** @type {NerdamerSymbolType} */ (a.clone())
+ )
+ );
+ const p = x.isConstant() ? 0 : x.power;
+ arr.add(a, p);
+ } else if (g === NerdamerSymbolDeps.PL && this.value === v) {
+ this.each((x, p) => {
+ arr.add(x.stripVar(v), p);
+ });
+ } else if (g === NerdamerSymbolDeps.CP) {
+ // The logic: they'll be broken into symbols so e.g. (x^2+x)+1 or (a*x^2+b*x+c)
+ // each case is handled above
+ this.each(x => {
+ x.toArray(v, arr);
+ });
+ } else if (this.contains(v)) {
+ throw new NerdamerTypeError('Cannot convert to array! Exiting');
+ } else {
+ arr.add(this.clone(), 0); // It's just a constant wrt to v
+ }
+ // Fill the holes
+ arr = arr.arr; // Keep only the array since we don't need the object anymore
+ for (let i = 0; i < arr.length; i++) {
+ arr[i] ||= new NerdamerSymbol(0);
+ }
+ return arr;
+ }
+ // Checks to see if a symbol contans a function
+ hasFunc(v) {
+ const fnGroup = this.group === NerdamerSymbolDeps.FN || this.group === NerdamerSymbolDeps.EX;
+ if ((fnGroup && !v) || (fnGroup && this.contains(v))) {
+ return true;
+ }
+ if (this.symbols) {
+ for (const x in this.symbols) {
+ if (this.symbols[x].hasFunc(v)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ sub(a, b) {
+ const { _ } = NerdamerSymbolDeps;
+ a = NerdamerSymbolDeps.isSymbol(a) ? a.clone() : _.parse(a);
+ b = NerdamerSymbolDeps.isSymbol(b) ? b.clone() : _.parse(b);
+ if (a.group === NerdamerSymbolDeps.N || a.group === NerdamerSymbolDeps.P) {
+ err('Cannot substitute a number. Must be a variable');
+ }
+ let samePow = false;
+ const aIsUnitMultiplier = a.multiplier.equals(1);
+ let m = this.multiplier.clone();
+ let retval;
+ /*
+ * In order to make the substitution the bases have to first match take
+ * (x+1)^x -> (x+1)=y || x^2 -> x=y^6
+ * In both cases the first condition is that the bases match so we begin there
+ * Either both are PL or both are not PL but we cannot have PL and a non-PL group match
+ */
+ if (
+ this.value === a.value &&
+ ((this.group !== NerdamerSymbolDeps.PL && a.group !== NerdamerSymbolDeps.PL) ||
+ (this.group === NerdamerSymbolDeps.PL && a.group === NerdamerSymbolDeps.PL))
+ ) {
+ // We cleared the first hurdle but a subsitution may not be possible just yet
+ if (aIsUnitMultiplier || a.multiplier.equals(this.multiplier)) {
+ if (a.isLinear()) {
+ retval = b;
+ } else if (a.power.equals(this.power)) {
+ retval = b;
+ samePow = true;
+ }
+ if (a.multiplier.equals(this.multiplier)) {
+ m = new Frac(1);
+ }
+ }
+ }
+ // The next thing is to handle CB
+ else if (this.group === NerdamerSymbolDeps.CB || this.previousGroup === NerdamerSymbolDeps.CB) {
+ retval = new NerdamerSymbol(1);
+ this.each(x => {
+ const subbed = _.parse(x.sub(a, b)); // Parse it again for safety
+ retval = _.multiply(retval, subbed);
+ });
+ } else if (this.isComposite()) {
+ const symbol = this.clone();
+
+ if (a.isComposite() && symbol.isComposite() && symbol.isLinear() && a.isLinear()) {
+ const find = function (stack, needle) {
+ for (const x in stack.symbols) {
+ if (!Object.hasOwn(stack.symbols, x)) {
+ continue;
+ }
+ const sym = stack.symbols[x];
+ // If the symbol equals the needle or it's within the sub-symbols we're done
+ if ((sym.isComposite() && find(sym, needle)) || sym.equals(needle)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ // Go fish
+ for (const x in a.symbols) {
+ if (!Object.hasOwn(a.symbols, x)) {
+ continue;
+ }
+ if (!find(symbol, a.symbols[x])) {
+ return symbol.clone();
+ }
+ }
+ retval = _.add(_.subtract(symbol.clone(), a), b);
+ } else {
+ retval = new NerdamerSymbol(0);
+ symbol.each(x => {
+ retval = _.add(retval, x.sub(a, b));
+ });
+ }
+ } else if (this.group === NerdamerSymbolDeps.EX) {
+ // The parsed value could be a function so parse and sub
+ retval = _.parse(this.value).sub(a, b);
+ } else if (this.group === NerdamerSymbolDeps.FN) {
+ const nargs = [];
+ for (let i = 0; i < this.args.length; i++) {
+ /** @type {NerdamerSymbolType} */
+ let arg = this.args[i];
+ if (!NerdamerSymbolDeps.isSymbol(arg)) {
+ arg = _.parse(arg);
+ }
+ nargs.push(arg.sub(a, b));
+ }
+ retval = _.symfunction(this.fname, nargs);
+ }
+ // If we did manage a substitution
+ if (retval) {
+ if (!samePow) {
+ // Substitute the power
+ const p =
+ this.group === NerdamerSymbolDeps.EX
+ ? /** @type {NerdamerSymbolType} */ (/** @type {unknown} */ (this.power)).sub(a, b)
+ : _.parse(this.power);
+ // Now raise the symbol to that power
+ retval = _.pow(retval, p);
+ }
+
+ // Transfer the multiplier
+ retval.multiplier = retval.multiplier.multiply(m);
+
+ // Done
+ return retval;
+ }
+ // If all else fails
+ return this.clone();
+ }
+ isMonomial() {
+ if (this.group === NerdamerSymbolDeps.S) {
+ return true;
+ }
+ if (this.group === NerdamerSymbolDeps.CB) {
+ for (const x in this.symbols) {
+ if (this.symbols[x].group !== NerdamerSymbolDeps.S) {
+ return false;
+ }
+ }
+ } else {
+ return false;
+ }
+ return true;
+ }
+ isPi() {
+ return this.group === NerdamerSymbolDeps.S && this.value === 'pi';
+ }
+ sign() {
+ return this.multiplier.sign();
+ }
+ isE() {
+ return this.value === 'e';
+ }
+ isSQRT() {
+ return this.fname === NerdamerSymbolDeps.SQRT;
+ }
+ isConstant(checkAll, checkSymbols) {
+ if (checkSymbols && this.group === NerdamerSymbolDeps.CB) {
+ for (const x in this.symbols) {
+ if (this.symbols[x].isConstant(true)) {
+ return true;
+ }
+ }
+ }
+
+ if (checkAll === 'functions' && this.isComposite()) {
+ let isConstant = true;
+
+ this.each(x => {
+ if (!x.isConstant(checkAll, checkSymbols)) {
+ isConstant = false;
+ }
+ }, true);
+
+ return isConstant;
+ }
+
+ if (checkAll === 'all' && (this.isPi() || this.isE())) {
+ return true;
+ }
+
+ if (checkAll && this.group === NerdamerSymbolDeps.FN) {
+ for (let i = 0; i < this.args.length; i++) {
+ if (!this.args[i].isConstant(checkAll)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if (checkAll) {
+ return isNumericSymbol(this);
+ }
+ return this.value === NerdamerSymbolDeps.CONST_HASH;
+ }
+ // The symbols is imaginary if
+ // 1. n*i
+ // 2. a+b*i
+ // 3. a*i
+ isImaginary() {
+ if (this.imaginary) {
+ return true;
+ }
+ if (this.symbols) {
+ for (const x in this.symbols) {
+ if (this.symbols[x].isImaginary()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ /**
+ * Returns the real part of a symbol
+ *
+ * @returns {NerdamerSymbolType}
+ */
+ realpart() {
+ const { _ } = NerdamerSymbolDeps;
+ if (this.isConstant()) {
+ return this.clone();
+ }
+ if (this.imaginary) {
+ return new NerdamerSymbol(0);
+ }
+ if (this.isComposite()) {
+ /** @type {NerdamerSymbolType} */
+ let retval = new NerdamerSymbol(0);
+ this.each(x => {
+ retval = /** @type {NerdamerSymbolType} */ (_.add(retval, x.realpart()));
+ });
+ return retval;
+ }
+ if (this.isImaginary()) {
+ return new NerdamerSymbol(0);
+ }
+ return this.clone();
+ }
+ /*
+ * Return imaginary part of a symbol
+ * @returns {NerdamerSymbolType}
+ */
+ imagpart() {
+ const { _ } = NerdamerSymbolDeps;
+ if (this.group === NerdamerSymbolDeps.S && this.isImaginary()) {
+ /** @type {NerdamerSymbolType} */
+ let x = this;
+ // In S group, power is always Frac
+ if (/** @type {FracType} */ (this.power).isNegative()) {
+ x = this.clone();
+ x.power.negate();
+ x.multiplier.negate();
+ }
+ return new NerdamerSymbol(x.multiplier);
+ }
+ if (this.isComposite()) {
+ /** @type {NerdamerSymbolType} */
+ let retval = new NerdamerSymbol(0);
+ this.each(x => {
+ retval = /** @type {NerdamerSymbolType} */ (_.add(retval, x.imagpart()));
+ });
+ return retval;
+ }
+ if (this.group === NerdamerSymbolDeps.CB) {
+ return this.stripVar(Settings.IMAGINARY);
+ }
+ return new NerdamerSymbol(0);
+ }
+ isInteger() {
+ return this.isConstant() && this.multiplier.isInteger();
+ }
+ isLinear(wrt) {
+ if (wrt) {
+ if (this.isConstant()) {
+ return true;
+ }
+ // If this symbol doesn't contain the variable (including in exponents), it's constant with respect to it
+ if (!this.contains(wrt, true)) {
+ return true;
+ }
+ if (this.group === NerdamerSymbolDeps.S) {
+ if (this.value === wrt) {
+ return this.power.equals(1);
+ }
+ return true;
+ }
+
+ if (this.isComposite() && this.power.equals(1)) {
+ for (const x in this.symbols) {
+ if (!this.symbols[x].isLinear(wrt)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if (this.group === NerdamerSymbolDeps.CB) {
+ // If the variable doesn't exist in this term, it's constant wrt that variable, hence linear
+ if (!this.symbols[wrt]) {
+ return true;
+ }
+ return this.symbols[wrt].isLinear(wrt);
+ }
+ return false;
+ }
+ return this.power.equals(1);
+ }
+ /**
+ * Checks to see if a symbol has a function by a specified name or within a specified list
+ *
+ * @param {string | string[]} names
+ * @returns {boolean}
+ */
+ containsFunction(names) {
+ if (typeof names === 'string') {
+ names = [names];
+ }
+ if (this.group === NerdamerSymbolDeps.FN && names.indexOf(this.fname) !== -1) {
+ return true;
+ }
+ if (this.symbols) {
+ for (const x in this.symbols) {
+ if (this.symbols[x].containsFunction(names)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ /**
+ * Multiplies the current power by the given power
+ *
+ * @param {NerdamerSymbolType | FracType} p2
+ * @returns {NerdamerSymbolType}
+ */
+ multiplyPower(p2) {
+ const { _ } = NerdamerSymbolDeps;
+ // Leave out 1
+ if (this.group === NerdamerSymbolDeps.N && this.multiplier.equals(1)) {
+ return this;
+ }
+
+ /** @type {FracType | NerdamerSymbolType} */
+ let p1 = this.power;
+
+ if (
+ this.group !== NerdamerSymbolDeps.EX &&
+ NerdamerSymbolDeps.isSymbol(p2) &&
+ /** @type {NerdamerSymbolType} */ (p2).group === NerdamerSymbolDeps.N
+ ) {
+ const p = /** @type {NerdamerSymbolType} */ (p2).multiplier;
+ if (this.group === NerdamerSymbolDeps.N && !p.isInteger()) {
+ this.convert(NerdamerSymbolDeps.P);
+ }
+
+ this.power = /** @type {FracType} */ (p1.equals(1) ? p.clone() : /** @type {FracType} */ (p1).multiply(p));
+
+ if (this.group === NerdamerSymbolDeps.P && isInt(this.power)) {
+ // Bring it back to an N
+ this.value = String(Number(this.value) ** Number(this.power));
+ this.toLinear();
+ this.convert(NerdamerSymbolDeps.N);
+ }
+ } else {
+ if (this.group !== NerdamerSymbolDeps.EX) {
+ p1 = /** @type {FracType | NerdamerSymbolType} */ (/** @type {unknown} */ (new NerdamerSymbol(p1)));
+ this.convert(NerdamerSymbolDeps.EX);
+ }
+ /** @type {FracType | NerdamerSymbolType} */
+ const newPower = /** @type {FracType | NerdamerSymbolType} */ (
+ _.multiply(/** @type {NerdamerSymbolType} */ (p1), /** @type {NerdamerSymbolType} */ (p2))
+ );
+ /** @type {FracType | NerdamerSymbolType} */
+ this.power = newPower;
+ }
+
+ return this;
+ }
+ setPower(p, retainSign = false) {
+ // Leave out 1
+ if (this.group === NerdamerSymbolDeps.N && this.multiplier.equals(1)) {
+ return this;
+ }
+ if (this.group === NerdamerSymbolDeps.EX && !NerdamerSymbolDeps.isSymbol(p)) {
+ this.group = this.previousGroup;
+ delete this.previousGroup;
+ if (this.group === NerdamerSymbolDeps.N) {
+ this.multiplier = new Frac(this.value);
+ this.value = NerdamerSymbolDeps.CONST_HASH;
+ } else {
+ this.power = p;
+ }
+ } else {
+ let isSymbolic = false;
+ if (NerdamerSymbolDeps.isSymbol(p)) {
+ if (p.group === NerdamerSymbolDeps.N) {
+ // P should be the multiplier instead
+ p = p.multiplier;
+ } else {
+ isSymbolic = true;
+ }
+ }
+ const group = isSymbolic ? NerdamerSymbolDeps.EX : NerdamerSymbolDeps.P;
+ this.power = p;
+ if (this.group === NerdamerSymbolDeps.N && group) {
+ this.convert(group, retainSign);
+ }
+ }
+
+ return this;
+ }
+ /**
+ * Checks to see if symbol is located in the denominator
+ *
+ * @returns {boolean}
+ */
+ isInverse() {
+ if (this.group === NerdamerSymbolDeps.EX) {
+ return /** @type {NerdamerSymbolType} */ (this.power).multiplier.lessThan(0);
+ }
+ return Number(this.power) < 0;
+ }
+ /**
+ * Make a duplicate of a symbol by copying a predefined list of items. The name 'copy' would probably be a more
+ * appropriate name. to a new symbol
+ *
+ * @param {NerdamerSymbolType} [c]
+ * @returns {NerdamerSymbolType}
+ */
+ clone(c = undefined) {
+ /** @type {NerdamerSymbolType} */
+ const self = this;
+ const clone = c || new NerdamerSymbol(0);
+ // List of properties excluding power as this may be a symbol and would also need to be a clone.
+ const properties = [
+ 'value',
+ 'group',
+ 'length',
+ 'previousGroup',
+ 'imaginary',
+ 'fname',
+ 'args',
+ 'isInfinity',
+ 'scientific',
+ ];
+ const l = properties.length;
+ let i;
+ if (self.symbols) {
+ clone.symbols = {};
+ for (const x in self.symbols) {
+ if (!Object.hasOwn(self.symbols, x)) {
+ continue;
+ }
+ clone.symbols[x] = self.symbols[x].clone();
+ }
+ }
+
+ for (i = 0; i < l; i++) {
+ if (self[properties[i]] !== undefined) {
+ clone[properties[i]] = self[properties[i]];
+ }
+ }
+
+ clone.power = self.power.clone();
+ clone.multiplier = self.multiplier.clone();
+ // Add back the flag to track if this symbol is a conversion symbol
+ // These properties may be added by external modules (like units)
+ // Use type assertion to access dynamically added properties
+ const selfAny = /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (self));
+ const cloneAny = /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (clone));
+ if (selfAny.isConversion) {
+ cloneAny.isConversion = selfAny.isConversion;
+ }
+
+ if (selfAny.isUnit) {
+ cloneAny.isUnit = selfAny.isUnit;
+ }
+
+ return clone;
+ }
+ /**
+ * Converts a symbol multiplier to one.
+ *
+ * @param {boolean} [keepSign] Keep the multiplier as negative if the multiplier is negative and keepSign is true
+ */
+ toUnitMultiplier(keepSign = false) {
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ this.multiplier.num = new NerdamerSymbolDeps.bigInt(this.multiplier.num.isNegative() && keepSign ? -1 : 1);
+ // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
+ this.multiplier.den = new NerdamerSymbolDeps.bigInt(1);
+ return this;
+ }
+ /** Converts a NerdamerSymbol's power to one. */
+ toLinear() {
+ // Do nothing if it's already linear
+ if (this.power.equals(1)) {
+ return this;
+ }
+ this.setPower(new Frac(1));
+ return this;
+ }
+ /**
+ * Iterates over all the sub-symbols. If no sub-symbols exist then it's called on itself
+ *
+ * @param {Function} fn
+ * @param {boolean} [deep] If true it will itterate over the sub-symbols their symbols as well
+ */
+ each(fn, deep = false) {
+ if (this.symbols) {
+ for (const x in this.symbols) {
+ if (!Object.hasOwn(this.symbols, x)) {
+ continue;
+ }
+ const sym = this.symbols[x];
+ if (sym.group === NerdamerSymbolDeps.PL && deep) {
+ for (const y in sym.symbols) {
+ if (!Object.hasOwn(sym.symbols, y)) {
+ continue;
+ }
+ fn.call(x, sym.symbols[y], y);
+ }
+ } else {
+ fn.call(this, sym, x);
+ }
+ }
+ } else {
+ fn.call(this, this, this.value);
+ }
+ }
+ /**
+ * A numeric value to be returned for Javascript. It will try to return a number as far a possible but in case of a
+ * pure symbolic symbol it will just return its text representation. When Settings.USE_BIG is true, may return a
+ * Decimal instance for numeric symbols.
+ *
+ * @returns {string | number | DecimalType}
+ */
+ valueOf() {
+ if (this.group === NerdamerSymbolDeps.N) {
+ return this.multiplier.valueOf();
+ }
+ if (this.power.equals(0)) {
+ return 1;
+ }
+ if (this.multiplier.equals(0)) {
+ return 0;
+ }
+ return NerdamerSymbolDeps.text(this, 'decimals');
+ }
+ /**
+ * Checks to see if a symbols has a particular variable within it. Pass in true as second argument to include the
+ * power of exponentials which aren't check by default.
+ *
+ * @example
+ * let s = _.parse('x+y+z');
+ * s.contains('y');
+ * //returns true
+ *
+ * @param {string | NerdamerSymbolType} variable
+ * @param {boolean} [all]
+ * @returns {boolean}
+ */
+ contains(variable, all = false) {
+ // Contains expects a string
+ variable = String(variable);
+ const g = this.group;
+ if (this.value === variable) {
+ return true;
+ }
+ if (this.symbols) {
+ for (const x in this.symbols) {
+ if (this.symbols[x].contains(variable, all)) {
+ return true;
+ }
+ }
+ }
+ if (g === NerdamerSymbolDeps.FN || this.previousGroup === NerdamerSymbolDeps.FN) {
+ for (let i = 0; i < this.args.length; i++) {
+ if (this.args[i].contains(variable, all)) {
+ return true;
+ }
+ }
+ }
+
+ if (g === NerdamerSymbolDeps.EX) {
+ // Exit only if it does
+ if (all && /** @type {NerdamerSymbolType} */ (this.power).contains(variable, all)) {
+ return true;
+ }
+ if (this.value === variable) {
+ return true;
+ }
+ }
+
+ return this.value === variable;
+ }
+ /** Negates a symbols */
+ negate() {
+ this.multiplier.negate();
+ if (this.group === NerdamerSymbolDeps.CP || this.group === NerdamerSymbolDeps.PL) {
+ this.distributeMultiplier();
+ }
+ return this;
+ }
+ /**
+ * Inverts a symbol
+ *
+ * @param {boolean} [powerOnly]
+ * @param {boolean} [all]
+ */
+ invert(powerOnly = false, all = false) {
+ // Invert the multiplier
+ if (!powerOnly) {
+ this.multiplier = this.multiplier.invert();
+ }
+ // Invert the rest
+ if (NerdamerSymbolDeps.isSymbol(this.power)) {
+ this.power.negate();
+ } else if (this.group === NerdamerSymbolDeps.CB && all) {
+ this.each(x => x.invert());
+ } else if (this.power && this.group !== NerdamerSymbolDeps.N) {
+ this.power.negate();
+ }
+ return this;
+ }
+ /**
+ * Symbols of group CP or PL may have the multiplier being carried by the top level symbol at any given time e.g.
+ * 2*(x+y+z). This is convenient in many cases, however in some cases the multiplier needs to be carried
+ * individually e.g. 2_x+2_y+2*z. This method distributes the multiplier over the entire symbol
+ *
+ * @param {boolean} [all]
+ */
+ distributeMultiplier(all = false) {
+ // In CP/PL groups (not EX), power is Frac
+ const isOne = all ? /** @type {FracType} */ (this.power).absEquals(1) : this.power.equals(1);
+ if (this.symbols && isOne && this.group !== NerdamerSymbolDeps.CB && !this.multiplier.equals(1)) {
+ for (const x in this.symbols) {
+ if (!Object.hasOwn(this.symbols, x)) {
+ continue;
+ }
+ const s = this.symbols[x];
+ s.multiplier = s.multiplier.multiply(this.multiplier);
+ s.distributeMultiplier();
+ }
+ this.toUnitMultiplier();
+ }
+
+ return this;
+ }
+ /** This method expands the exponent over the entire symbol just like distributeMultiplier */
+ distributeExponent() {
+ const { _ } = NerdamerSymbolDeps;
+ if (!this.power.equals(1)) {
+ const p = this.power;
+ for (const x in this.symbols) {
+ if (!Object.hasOwn(this.symbols, x)) {
+ continue;
+ }
+ const s = this.symbols[x];
+ if (s.group === NerdamerSymbolDeps.EX) {
+ s.power = _.multiply(s.power, new NerdamerSymbol(p));
+ } else if (NerdamerSymbolDeps.isSymbol(this.symbols[x].power)) {
+ this.symbols[x].power = _.multiply(this.symbols[x].power, new NerdamerSymbol(p));
+ } else {
+ this.symbols[x].power = this.symbols[x].power.multiply(p);
+ }
+ }
+ this.toLinear();
+ }
+ return this;
+ }
+ /**
+ * This method will attempt to up-convert or down-convert one symbol from one group to another. Not all symbols are
+ * convertible from one group to another however. In that case the symbol will remain unchanged.
+ *
+ * @param {number} group
+ * @param {boolean} [imaginary]
+ */
+ convert(group, imaginary = undefined) {
+ if (group > NerdamerSymbolDeps.FN) {
+ // Make a clone of this symbol;
+ const cp = this.clone();
+
+ // Attach a symbols object and upgrade the group
+ this.symbols = {};
+
+ if (group === NerdamerSymbolDeps.CB) {
+ // Symbol of group CB hold symbols bound together through multiplication
+ // because of commutativity this multiplier can technically be anywhere within the group
+ // to keep track of it however it's easier to always have the top level carry it
+ cp.toUnitMultiplier();
+ } else {
+ // Reset the symbol
+ this.toUnitMultiplier();
+ }
+
+ if (this.group === NerdamerSymbolDeps.FN) {
+ cp.args = this.args;
+ delete this.args;
+ delete this.fname;
+ }
+
+ // The symbol may originate from the symbol i but this property no longer holds true
+ // after copying
+ if (this.isImgSymbol) {
+ delete this.isImgSymbol;
+ }
+
+ this.toLinear();
+ // Attach a clone of this symbol to the symbols object using its proper key
+ this.symbols[cp.keyForGroup(group)] = cp;
+ this.group = group;
+ // Objects by default don't have a length property. However, in order to keep track of the number
+ // of sub-symbols we have to impliment our own.
+ this.length = 1;
+ } else if (group === NerdamerSymbolDeps.EX) {
+ // 1^x is just one so check and make sure
+ if (!(this.group === NerdamerSymbolDeps.N && this.multiplier.equals(1))) {
+ if (this.group !== NerdamerSymbolDeps.EX) {
+ this.previousGroup = this.group;
+ }
+ if (this.group === NerdamerSymbolDeps.N) {
+ this.value = this.multiplier.num.toString();
+ this.toUnitMultiplier();
+ }
+ // Update the hash to reflect the accurate hash
+ else {
+ this.value = NerdamerSymbolDeps.text(this, 'hash');
+ }
+
+ this.group = NerdamerSymbolDeps.EX;
+ }
+ } else if (group === NerdamerSymbolDeps.N) {
+ const m = this.multiplier.toDecimal();
+ this.symbols &&= undefined;
+ new NerdamerSymbol(
+ this.group === NerdamerSymbolDeps.P ? Number(m) * Number(this.value) ** Number(this.power) : m
+ ).clone(this);
+ } else if (group === NerdamerSymbolDeps.P && this.group === NerdamerSymbolDeps.N) {
+ this.value = imaginary
+ ? this.multiplier.num.toString()
+ : String(Math.abs(Number(this.multiplier.num.toString())));
+ this.toUnitMultiplier(!imaginary);
+ this.group = NerdamerSymbolDeps.P;
+ }
+ return this;
+ }
+ /**
+ * This method is one of the principal methods to make it all possible. It performs cleanup and prep operations
+ * whenever a symbols is inserted. If the symbols results in a 1 in a CB (multiplication) group for instance it will
+ * remove the redundant symbol. Similarly in a symbol of group PL or CP (symbols glued by multiplication) it will
+ * remove any dangling zeroes from the symbol. It will also up-convert or down-convert a symbol if it detects that
+ * it's incorrectly grouped. It should be noted that this method is not called directly but rather by the 'attach'
+ * method for addition groups and the 'combine' method for multiplication groups.
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {string} action
+ */
+ insert(symbol, action) {
+ const { _ } = NerdamerSymbolDeps;
+ // This check can be removed but saves a lot of aggravation when trying to hunt down
+ // a bug. If left, you will instantly know that the error can only be between 2 symbols.
+ if (!NerdamerSymbolDeps.isSymbol(symbol)) {
+ err(`Object ${symbol} is not of type NerdamerSymbol!`);
+ }
+ if (this.symbols) {
+ const { group } = this;
+ if (group > NerdamerSymbolDeps.FN) {
+ const key = symbol.keyForGroup(group);
+ const existing = key in this.symbols ? this.symbols[key] : false; // Check if there's already a symbol there
+ if (action === 'add') {
+ const hash = key;
+ if (existing) {
+ // Add them together using the parser
+ this.symbols[hash] = _.add(existing, symbol);
+ // If the addition resulted in a zero multiplier remove it
+ if (this.symbols[hash].multiplier.equals(0)) {
+ delete this.symbols[hash];
+ this.length--;
+
+ if (this.length === 0) {
+ this.convert(NerdamerSymbolDeps.N);
+ this.multiplier = new Frac(0);
+ }
+ }
+ } else {
+ this.symbols[key] = symbol;
+ this.length++;
+ }
+ } else {
+ // Check if this is of group P and unwrap before inserting
+ if (symbol.group === NerdamerSymbolDeps.P && isInt(symbol.power)) {
+ symbol.convert(NerdamerSymbolDeps.N);
+ }
+
+ // Transfer the multiplier to the upper symbol but only if the symbol numeric
+ if (symbol.group === NerdamerSymbolDeps.EX) {
+ symbol.parens = symbol.multiplier.lessThan(0);
+ this.multiplier = this.multiplier.multiply(symbol.multiplier.clone().abs());
+ symbol.toUnitMultiplier(true);
+ } else {
+ this.multiplier = this.multiplier.multiply(symbol.multiplier);
+ symbol.toUnitMultiplier();
+ }
+
+ if (existing) {
+ // Remove because the symbol may have changed
+ symbol = /** @type {NerdamerSymbolType} */ (
+ _.multiply(/** @type {NerdamerSymbolType} */ (remove(this.symbols, key)), symbol)
+ );
+ if (symbol.isConstant()) {
+ this.multiplier = this.multiplier.multiply(symbol.multiplier);
+ symbol = new NerdamerSymbol(1); // The dirty work gets done down the line when it detects 1
+ }
+
+ this.length--;
+ // Clean up
+ }
+
+ // Don't insert the symbol if it's 1
+ if (!symbol.isOne(true)) {
+ this.symbols[key] = symbol;
+ this.length++;
+ } else if (symbol.multiplier.lessThan(0)) {
+ this.negate(); // Put back the sign
+ }
+ }
+
+ // Clean up
+ if (this.length === 0) {
+ this.convert(NerdamerSymbolDeps.N);
+ }
+ // Update the hash
+ if (this.group === NerdamerSymbolDeps.CP || this.group === NerdamerSymbolDeps.CB) {
+ this.updateHash();
+ }
+ }
+ }
+
+ return this;
+ }
+ /** The insert method for addition */
+ attach(symbol) {
+ if (isArray(symbol)) {
+ for (let i = 0; i < symbol.length; i++) {
+ this.insert(/** @type {NerdamerSymbolType} */ (symbol[i]), 'add');
+ }
+ return this;
+ }
+ return this.insert(symbol, 'add');
+ }
+ /** The insert method for multiplication */
+ combine(symbol) {
+ if (isArray(symbol)) {
+ for (let i = 0; i < symbol.length; i++) {
+ this.insert(/** @type {NerdamerSymbolType} */ (symbol[i]), 'multiply');
+ }
+ return this;
+ }
+ return this.insert(symbol, 'multiply');
+ }
+ /**
+ * This method should be called after any major "surgery" on a symbol. It updates the hash of the symbol for example
+ * if the fname of a function has changed it will update the hash of the symbol.
+ */
+ updateHash() {
+ if (this.group === NerdamerSymbolDeps.N) {
+ return;
+ }
+
+ if (this.group === NerdamerSymbolDeps.FN) {
+ let contents = '';
+ const { args } = this;
+ const isParens = this.fname === NerdamerSymbolDeps.PARENTHESIS;
+ for (let i = 0; i < args.length; i++) {
+ contents += (i === 0 ? '' : ',') + NerdamerSymbolDeps.text(args[i]);
+ }
+ const fnName = isParens ? '' : this.fname;
+ this.value = fnName + (isParens ? contents : inBrackets(contents));
+ } else if (!(this.group === NerdamerSymbolDeps.S || this.group === NerdamerSymbolDeps.PL)) {
+ this.value = NerdamerSymbolDeps.text(this, 'hash');
+ }
+ }
+ /**
+ * This function defines how every group in stored within a group of higher order think of it as the switchboard for
+ * the library. It defines the hashes for symbols.
+ *
+ * @param {number} group
+ */
+ keyForGroup(group) {
+ const g = this.group;
+ let key;
+
+ if (g === NerdamerSymbolDeps.N) {
+ key = this.value;
+ } else if (g === NerdamerSymbolDeps.S || g === NerdamerSymbolDeps.P) {
+ if (group === NerdamerSymbolDeps.PL) {
+ // In S/P groups, power is Frac
+ key = /** @type {FracType} */ (this.power).toDecimal();
+ } else {
+ key = this.value;
+ }
+ } else if (g === NerdamerSymbolDeps.FN) {
+ if (group === NerdamerSymbolDeps.PL) {
+ // In FN group, power is Frac
+ key = /** @type {FracType} */ (this.power).toDecimal();
+ } else {
+ key = NerdamerSymbolDeps.text(this, 'hash');
+ }
+ } else if (g === NerdamerSymbolDeps.PL) {
+ // If the order is reversed then we'll assume multiplication
+ // TODO: possible future dilemma
+ if (group === NerdamerSymbolDeps.CB) {
+ key = NerdamerSymbolDeps.text(this, 'hash');
+ } else if (group === NerdamerSymbolDeps.CP) {
+ if (this.power.equals(1)) {
+ key = this.value;
+ } else {
+ key =
+ inBrackets(NerdamerSymbolDeps.text(this, 'hash')) +
+ Settings.POWER_OPERATOR +
+ // In PL group, power is Frac
+ /** @type {FracType} */ (this.power).toDecimal();
+ }
+ } else if (group === NerdamerSymbolDeps.PL) {
+ key = this.power.toString();
+ } else {
+ key = this.value;
+ }
+ return key;
+ } else if (g === NerdamerSymbolDeps.CP) {
+ if (group === NerdamerSymbolDeps.CP) {
+ key = NerdamerSymbolDeps.text(this, 'hash');
+ }
+ if (group === NerdamerSymbolDeps.PL) {
+ // In CP group, power is Frac
+ key = /** @type {FracType} */ (this.power).toDecimal();
+ } else {
+ key = this.value;
+ }
+ } else if (g === NerdamerSymbolDeps.CB) {
+ if (group === NerdamerSymbolDeps.PL) {
+ // In CB group, power is Frac
+ key = /** @type {FracType} */ (this.power).toDecimal();
+ } else {
+ key = NerdamerSymbolDeps.text(this, 'hash');
+ }
+ } else if (g === NerdamerSymbolDeps.EX) {
+ if (group === NerdamerSymbolDeps.PL) {
+ // In EX group, power is NerdamerSymbol, use text()
+ key = NerdamerSymbolDeps.text(this.power);
+ } else {
+ key = NerdamerSymbolDeps.text(this, 'hash');
+ }
+ }
+
+ return key;
+ }
+ /**
+ * Symbols are typically stored in an object which works fine for most cases but presents a problem when the order
+ * of the symbols makes a difference. This function simply collects all the symbols and returns them as an array. If
+ * a function is supplied then that function is called on every symbol contained within the object.
+ *
+ * @param {(symbol: NerdamerSymbolType, opt?: string) => unknown} [fn]
+ * @param {string} [opt]
+ * @param {SortFn} [sortFn]
+ * @param {boolean} [expandSymbol]
+ * @returns {Array}
+ */
+ collectSymbols(fn, opt, sortFn, expandSymbol) {
+ let collected = [];
+ if (this.symbols) {
+ for (const x in this.symbols) {
+ if (!Object.hasOwn(this.symbols, x)) {
+ continue;
+ }
+ const symbol = this.symbols[x];
+ if (
+ expandSymbol &&
+ (symbol.group === NerdamerSymbolDeps.PL || symbol.group === NerdamerSymbolDeps.CP)
+ ) {
+ collected = collected.concat(symbol.collectSymbols());
+ } else {
+ collected.push(fn ? fn(symbol, opt) : symbol);
+ }
+ }
+ } else {
+ collected.push(this);
+ }
+ if (sortFn === null) {
+ sortFn = undefined;
+ } // WTF Firefox? Seriously?
+
+ return collected.sort(sortFn); // Sort hopefully gives us some sort of consistency
+ }
+
+ /**
+ * CollectSymbols but only for summands
+ *
+ * @param {(symbol: NerdamerSymbolType, opt?: string) => unknown} [fn]
+ * @param {string} [opt]
+ * @param {SortFn} [sortFn]
+ * @param {boolean} [expandSymbol]
+ * @returns {Array}
+ */
+ collectSummandSymbols(fn, opt, sortFn, expandSymbol) {
+ let collected = [];
+ if (!this.symbols || this.group === NerdamerSymbolDeps.CB) {
+ collected.push(this);
+ } else {
+ for (const x in this.symbols) {
+ if (!Object.hasOwn(this.symbols, x)) {
+ continue;
+ }
+ const symbol = this.symbols[x];
+ if (
+ expandSymbol &&
+ (symbol.group === NerdamerSymbolDeps.PL || symbol.group === NerdamerSymbolDeps.CP)
+ ) {
+ collected = collected.concat(symbol.collectSymbols());
+ } else {
+ collected.push(fn ? fn(symbol, opt) : symbol);
+ }
+ }
+ }
+ if (sortFn === null) {
+ sortFn = undefined;
+ } // WTF Firefox? Seriously?
+
+ return collected.sort(sortFn); // Sort hopefully gives us some sort of consistency
+ }
+ /**
+ * Returns the latex representation of the symbol
+ *
+ * @param {string} option
+ * @returns {string}
+ */
+ latex(option) {
+ return LaTeX.latex(this, option);
+ }
+ /**
+ * Returns the text representation of a symbol
+ *
+ * @param {string} [option]
+ * @returns {string}
+ */
+ text(option = undefined) {
+ return NerdamerSymbolDeps.text(this, option);
+ }
+ /**
+ * Checks if the function evaluates to 1. e.g. x^0 or 1 :)
+ *
+ * @param {boolean} [abs] Compares the absolute value
+ */
+ isOne(abs = false) {
+ const f = abs ? 'absEquals' : 'equals';
+ if (this.group === NerdamerSymbolDeps.N) {
+ return this.multiplier[f](1);
+ }
+ return this.power.equals(0);
+ }
+ isComposite() {
+ const g = this.group;
+ const pg = this.previousGroup;
+ return (
+ g === NerdamerSymbolDeps.CP ||
+ g === NerdamerSymbolDeps.PL ||
+ pg === NerdamerSymbolDeps.PL ||
+ pg === NerdamerSymbolDeps.CP
+ );
+ }
+ isCombination() {
+ const g = this.group;
+ const pg = this.previousGroup;
+ return g === NerdamerSymbolDeps.CB || pg === NerdamerSymbolDeps.CB;
+ }
+ lessThan(n) {
+ return this.multiplier.lessThan(n);
+ }
+ greaterThan(n) {
+ if (!NerdamerSymbolDeps.isSymbol(n)) {
+ n = new NerdamerSymbol(n);
+ }
+
+ // We can't tell for sure if a is greater than be if they're not both numbers
+ if (!this.isConstant(true) || !n.isConstant(true)) {
+ return false;
+ }
+
+ return this.multiplier.greaterThan(n.multiplier);
+ }
+ /**
+ * Get's the denominator of the symbol if the symbol is of class CB (multiplication) with other classes the symbol
+ * is either the denominator or not. Take x^-1+x^-2. If the symbol was to be mixed such as x+x^-2 then the symbol
+ * doesn't have have an exclusive denominator and has to be found by looking at the actual symbols themselves.
+ *
+ * @returns {NerdamerSymbolType}
+ */
+ getDenom() {
+ const { _ } = NerdamerSymbolDeps;
+ /** @type {NerdamerSymbolType | VectorType | MatrixType} */
+ let retval;
+ /** @type {NerdamerSymbolType} */
+ let symbol;
+ symbol = /** @type {NerdamerSymbolType} */ (this.clone());
+ // E.g. 1/(x*(x+1))
+ if (this.group === NerdamerSymbolDeps.CB && this.power.lessThan(0)) {
+ symbol = /** @type {NerdamerSymbolType} */ (_.expand(symbol));
+ }
+
+ // If the symbol already is the denominator... DONE!!!
+ if (
+ symbol.power.lessThan(0) ||
+ (symbol.group === NerdamerSymbolDeps.EX &&
+ /** @type {NerdamerSymbolType} */ (symbol.power).multiplier.lessThan(0))
+ ) {
+ const d = _.parse(symbol.multiplier.den);
+ retval = symbol.toUnitMultiplier();
+ retval.power.negate();
+ retval = _.multiply(d, retval); // Put back the coeff
+ } else if (symbol.group === NerdamerSymbolDeps.CB) {
+ retval = _.parse(symbol.multiplier.den);
+ for (const x in symbol.symbols) {
+ if (!Object.hasOwn(symbol.symbols, x)) {
+ continue;
+ }
+ const s = symbol.symbols[x];
+ if (
+ Number(s.power) < 0 ||
+ (s.group === NerdamerSymbolDeps.EX &&
+ /** @type {NerdamerSymbolType} */ (s.power).multiplier.lessThan(0))
+ ) {
+ retval = _.multiply(
+ /** @type {NerdamerSymbolType} */ (retval),
+ /** @type {NerdamerSymbolType} */ (symbol.symbols[x].clone().invert())
+ );
+ }
+ }
+ } else {
+ retval = _.parse(symbol.multiplier.den);
+ }
+ return /** @type {NerdamerSymbolType} */ (retval);
+ }
+ /** @returns {NerdamerSymbolType} */
+ getNum() {
+ const { _ } = NerdamerSymbolDeps;
+ /** @type {NerdamerSymbolType | VectorType | MatrixType} */
+ let retval;
+ /** @type {NerdamerSymbolType} */
+ let symbol;
+ symbol = /** @type {NerdamerSymbolType} */ (this.clone());
+ // E.g. 1/(x*(x+1))
+ if (symbol.group === NerdamerSymbolDeps.CB && symbol.power.lessThan(0)) {
+ symbol = /** @type {NerdamerSymbolType} */ (_.expand(symbol));
+ }
+ // If the symbol already is the denominator... DONE!!!
+ if (
+ (symbol.power.greaterThan(0) && symbol.group !== NerdamerSymbolDeps.CB) ||
+ (symbol.group === NerdamerSymbolDeps.EX &&
+ /** @type {NerdamerSymbolType} */ (symbol.power).multiplier.greaterThan(0))
+ ) {
+ retval = _.multiply(_.parse(symbol.multiplier.num), symbol.toUnitMultiplier());
+ } else if (symbol.group === NerdamerSymbolDeps.CB) {
+ retval = _.parse(symbol.multiplier.num);
+ symbol.each(x => {
+ if (
+ Number(x.power) > 0 ||
+ (x.group === NerdamerSymbolDeps.EX &&
+ /** @type {NerdamerSymbolType} */ (x.power).multiplier.greaterThan(0))
+ ) {
+ retval = _.multiply(
+ /** @type {NerdamerSymbolType} */ (retval),
+ /** @type {NerdamerSymbolType} */ (x.clone())
+ );
+ }
+ });
+ }
+ // Else if(symbol.group === NerdamerSymbolDeps.EX && this.previousGroup === NerdamerSymbolDeps.S) {
+ // retval = _.multiply(_.parse(symbol.multiplier.num), symbol.toUnitMultiplier());
+ // }
+ else {
+ retval = _.parse(symbol.multiplier.num);
+ }
+ return /** @type {NerdamerSymbolType} */ (retval);
+ }
+ toString() {
+ return this.text();
+ }
+}
+
+// Assign NerdamerSymbol to CoreDeps immediately
+CoreDeps.classes.NerdamerSymbol = NerdamerSymbol;
+
+// Parser Class =====================================================================
+// The Parser is the core mathematical expression parser for nerdamer. It uses a
+// modified Shunting-yard algorithm (http://en.wikipedia.org/wiki/Shunting-yard_algorithm).
+//
+// DEPENDENCY INJECTION:
+// The Parser relies on values that are only available inside the IIFE. These are
+// injected via ParserDeps, which the IIFE populates before Parser instantiation:
+//
+// 1. Symbol Group Constants (N, P, S, EX, FN, PL, CB, CP) - Symbol type classification
+// 2. Function Name Constants (SQRT, ABS, FACTORIAL, DOUBLEFACTORIAL, PARENTHESIS)
+// 3. bigDec - BigDecimal library for high-precision calculations
+// 4. PRIMES - Array of prime numbers for factorization
+// 5. VARS - Object storing user-defined variables
+
+/**
+ * The Parser class - core mathematical expression parser for nerdamer.
+ *
+ * This class is defined at module scope but instantiated inside the IIFE. The Parser destructures its dependencies from
+ * ParserDeps at construction time, which the IIFE has already populated with the correct values.
+ *
+ * @implements {ParserType}
+ */
+class Parser {
+ constructor() {
+ // Destructure dependencies from ParserDeps (populated by IIFE before instantiation)
+ const { N, P, S, EX, FN, PL, CB, CP } = ParserDeps;
+ const { SQRT, ABS, FACTORIAL, DOUBLEFACTORIAL, PARENTHESIS } = ParserDeps;
+ const { bigDec, PRIMES, VARS } = ParserDeps;
+
+ // Local reference to this parser instance for use in nested functions
+ /** @type {ParserType} */
+ const _parser = this;
+ const _ = _parser;
+ const bin = {};
+ const preprocessors = { names: [], actions: [] };
+
+ // Parser.classes ===============================================================
+ /** Slice class for representing array slices */
+ class Slice {
+ /** @type {NerdamerSymbolType | number} */
+ upper;
+ /** @type {NerdamerSymbolType | number} */
+ lower;
+
+ /**
+ * @param {NerdamerSymbolType | number} upper - Start of slice
+ * @param {NerdamerSymbolType | number} lower - End of slice
+ */
+ constructor(upper, lower) {
+ this.upper = upper;
+ this.lower = lower;
+ }
+
+ isConstant() {
+ const u = /** @type {NerdamerSymbolType} */ (this.upper);
+ const l = /** @type {NerdamerSymbolType} */ (this.lower);
+ return u.isConstant() && l.isConstant();
+ }
+
+ // Using 'getText' to avoid shadowing the outer 'text' function
+ text() {
+ return `${text(/** @type {NerdamerSymbolType} */ (this.upper))}:${text(/** @type {NerdamerSymbolType} */ (this.lower))}`;
+ }
+ }
+
+ /** Token class for representing parser tokens */
+ class Token {
+ static OPERATOR = 'OPERATOR';
+ static VARIABLE_OR_LITERAL = 'VARIABLE_OR_LITERAL';
+ static FUNCTION = 'FUNCTION';
+ static UNIT = 'UNIT';
+ static KEYWORD = 'KEYWORD';
+ static MAX_PRECEDENCE = 999;
+
+ /**
+ * @param {string} node - Token value
+ * @param {string} nodeType - Token type
+ * @param {number} [column] - Column position
+ */
+ constructor(node, nodeType, column) {
+ this.type = nodeType;
+ this.value = node;
+ if (column !== undefined) {
+ this.column = column + 1;
+ }
+ if (nodeType === Token.OPERATOR) {
+ // Copy everything over from the operator
+ // eslint-disable-next-line no-use-before-define -- operators is defined later but this function is only called after
+ const operator = operators[node];
+ for (const x in operator) {
+ if (!Object.hasOwn(operator, x)) {
+ continue;
+ }
+ this[x] = operator[x];
+ }
+ } else if (nodeType === Token.FUNCTION) {
+ this.precedence = Token.MAX_PRECEDENCE; // Leave enough room
+ this.leftAssoc = false;
+ }
+ }
+
+ /** @this {TokenType} */
+ toString() {
+ if (this.is_prefix) {
+ return `\`${this.value}`;
+ }
+ return this.value;
+ }
+ }
+
+ // Create link to classes
+ this.classes = {
+ Collection,
+ Slice,
+ Token,
+ };
+ // Parser.modules ===============================================================
+ // object for functions which handle complex number
+ const complex = {
+ prec: undefined,
+ cos(r, i) {
+ const re = _.parse(String(Math.cos(r) * Math.cosh(i)));
+ const im = _.parse(String(Math.sin(r) * Math.sinh(i)));
+ return _.subtract(re, _.multiply(im, NerdamerSymbol.imaginary()));
+ },
+ sin(r, i) {
+ const re = _.parse(String(Math.sin(r) * Math.cosh(i)));
+ const im = _.parse(String(Math.cos(r) * Math.sinh(i)));
+ return _.subtract(re, _.multiply(im, NerdamerSymbol.imaginary()));
+ },
+ tan(r, i) {
+ const re = _.parse(String(Math.sin(2 * r) / (Math.cos(2 * r) + Math.cosh(2 * i))));
+ const im = _.parse(String(Math.sinh(2 * i) / (Math.cos(2 * r) + Math.cosh(2 * i))));
+ return _.add(re, _.multiply(im, NerdamerSymbol.imaginary()));
+ },
+ sec(r, i) {
+ const t = this.removeDen(this.cos(r, i));
+ return _.subtract(t[0], _.multiply(t[1], NerdamerSymbol.imaginary()));
+ },
+ csc(r, i) {
+ const t = this.removeDen(this.sin(r, i));
+ return _.add(t[0], _.multiply(t[1], NerdamerSymbol.imaginary()));
+ },
+ cot(r, i) {
+ const t = this.removeDen(this.tan(r, i));
+ return _.subtract(t[0], _.multiply(t[1], NerdamerSymbol.imaginary()));
+ },
+ acos(r, i) {
+ const symbol = this.fromArray([r, i]);
+ const squared = _.pow(symbol.clone(), new NerdamerSymbol(2));
+ const sq = _.expand(squared); // Z*z
+ const a = _.multiply(sqrt(_.subtract(new NerdamerSymbol(1), sq)), NerdamerSymbol.imaginary());
+ const b = _.expand(_.add(symbol.clone(), a));
+ const c = log(b);
+ return _.expand(_.multiply(NerdamerSymbol.imaginary().negate(), c));
+ },
+ asin(r, i) {
+ return _.subtract(_.parse('pi/2'), this.acos(r, i));
+ },
+ atan(r, i) {
+ // Handle i and -i
+ if (r.equals(0) && (i.equals(1) || i.equals(-1))) {
+ // Just copy Wolfram Alpha for now. The parenthesis
+ return _.parse(`${NerdamerSymbol.infinity()}*${Settings.IMAGINARY}*${i}`);
+ }
+ const symbol = complex.fromArray([r, i]);
+ const a = _.expand(_.multiply(NerdamerSymbol.imaginary(), symbol.clone()));
+ const b = log(_.expand(_.subtract(new NerdamerSymbol(1), a.clone())));
+ const c = log(_.expand(_.add(new NerdamerSymbol(1), a.clone())));
+ return _.expand(
+ _.multiply(_.divide(NerdamerSymbol.imaginary(), new NerdamerSymbol(2)), _.subtract(b, c))
+ );
+ },
+ asec(r, i) {
+ const d = this.removeDen([r, i]);
+ d[1].negate();
+ return this.acos(...d);
+ },
+ acsc(r, i) {
+ const d = this.removeDen([r, i]);
+ d[1].negate();
+ return this.asin(...d);
+ },
+ acot(r, i) {
+ const d = this.removeDen([r, i]);
+ d[1].negate();
+ return this.atan(...d);
+ },
+ // Hyperbolic trig
+ cosh(r, i) {
+ const re = _.parse(String(Math.cosh(r) * Math.cos(i)));
+ const im = _.parse(String(Math.sinh(r) * Math.sin(i)));
+ return _.add(re, _.multiply(im, NerdamerSymbol.imaginary()));
+ },
+ sinh(r, i) {
+ const re = _.parse(String(Math.sinh(r) * Math.cos(i)));
+ const im = _.parse(String(Math.cosh(r) * Math.sin(i)));
+ return _.add(re, _.multiply(im, NerdamerSymbol.imaginary()));
+ },
+ tanh(r, i) {
+ const re = _.parse(String(Math.sinh(2 * r) / (Math.cos(2 * i) + Math.cosh(2 * r))));
+ const im = _.parse(String(Math.sin(2 * i) / (Math.cos(2 * i) + Math.cosh(2 * r))));
+ return _.subtract(re, _.multiply(im, NerdamerSymbol.imaginary()));
+ },
+ sech(r, i) {
+ const t = this.removeDen(this.cosh(r, i));
+ return _.subtract(t[0], _.multiply(t[1], NerdamerSymbol.imaginary()));
+ },
+ csch(r, i) {
+ const t = this.removeDen(this.sinh(r, i));
+ return _.subtract(t[0], _.multiply(t[1], NerdamerSymbol.imaginary()));
+ },
+ coth(r, i) {
+ const t = this.removeDen(this.tanh(r, i));
+ return _.add(t[0], _.multiply(t[1], NerdamerSymbol.imaginary()));
+ },
+ acosh(r, i) {
+ const z = this.fromArray([r, i]);
+ const a = sqrt(_.add(z.clone(), new NerdamerSymbol(1)));
+ const b = sqrt(_.subtract(z.clone(), new NerdamerSymbol(1)));
+ return _.expand(log(_.add(z, _.expand(_.multiply(a, b)))));
+ },
+ asinh(r, i) {
+ const z = this.fromArray([r, i]);
+ const a = sqrt(_.add(new NerdamerSymbol(1), _.expand(_.pow(z.clone(), new NerdamerSymbol(2)))));
+ return _.expand(log(_.add(z, a)));
+ },
+ atanh(r, i) {
+ const z = this.fromArray([r, i]);
+ const a = log(_.add(z.clone(), new NerdamerSymbol(1)));
+ const b = log(_.subtract(new NerdamerSymbol(1), z));
+ return _.expand(_.divide(_.subtract(a, b), new NerdamerSymbol(2)));
+ },
+ asech(r, i) {
+ const t = this.removeDen([r, i]);
+ t[1].negate();
+ return this.acosh(...t);
+ },
+ acsch(r, i) {
+ const t = this.removeDen([r, i]);
+ t[1].negate();
+ return this.asinh(...t);
+ },
+ acoth(r, i) {
+ const t = this.removeDen([r, i]);
+ t[1].negate();
+ return this.atanh(...t);
+ },
+ sqrt(symbol) {
+ const re = symbol.realpart();
+ const im = symbol.imagpart();
+ const h = NerdamerSymbol.hyp(re, im);
+ const a = _.add(re.clone(), h);
+ const d = sqrt(_.multiply(new NerdamerSymbol(2), a.clone()));
+ return _.add(_.divide(a.clone(), d.clone()), _.multiply(_.divide(im, d), NerdamerSymbol.imaginary()));
+ },
+ log(r, i) {
+ const re = log(NerdamerSymbol.hyp(r, i));
+ const phi = Settings.USE_BIG
+ ? new NerdamerSymbol(bigDec.atan2(i.multiplier.toDecimal(), r.multiplier.toDecimal()))
+ : Math.atan2(i, r);
+ const im = _.parse(phi);
+ return _.add(re, _.multiply(NerdamerSymbol.imaginary(), im));
+ },
+ erf(symbol, _n) {
+ // Do nothing for now. Revisit this in the future.
+ return _.symfunction('erf', [symbol]);
+
+ // N = n || 30;
+
+ // let f = function (R, I) {
+ // return block('PARSE2NUMBER', function () {
+ // let retval = new NerdamerSymbol(0);
+ // for(let i = 0; i < n; i++) {
+ // let a, b;
+ // a = _.parse(bigDec.exp(bigDec(i).toPower(2).neg().dividedBy(bigDec(n).pow(2).plus(bigDec(R).toPower(2).times(4)))));
+ // b = _.parse(format('2*({1})-e^(-(2*{0}*{1}*{2}))*(2*{1}*cosh({2}*{3})-{0}*{3}*sinh({3}*{2}))', Settings.IMAGINARY, R, I, i));
+ // retval = _.add(retval, _.multiply(a, b));
+ // }
+ // return _.multiply(retval, new NerdamerSymbol(2));
+ // }, true);
+ // };
+ // let re, im, a, b, c, k;
+ // re = symbol.realpart();
+ // im = symbol.imagpart();
+
+ // k = _.parse(format('(e^(-{0}^2))/pi', re));
+ // a = _.parse(format('(1-e^(-(2*{0}*{1}*{2})))/(2*{1})', Settings.IMAGINARY, re, im));
+ // b = f(re.toString(), im.toString());
+
+ // return _.add(_.parse(Math2.erf(re.toString())), _.multiply(k, _.add(a, b)));
+ },
+ removeDen(symbol) {
+ let r;
+ let i;
+ if (isArray(symbol)) {
+ r = symbol[0];
+ i = symbol[1];
+ } else {
+ r = symbol.realpart();
+ i = symbol.imagpart();
+ }
+
+ const den = r ** 2 + i ** 2;
+ const re = _.parse(String(r / den));
+ const im = _.parse(String(i / den));
+ return [re, im];
+ },
+ fromArray(arr) {
+ return _.add(arr[0], _.multiply(NerdamerSymbol.imaginary(), arr[1]));
+ },
+ evaluate(symbol, f) {
+ let re;
+ let im;
+
+ const signVal = symbol.power.sign();
+ // Remove it from under the denominator
+ symbol.power = symbol.power.abs();
+ // Expand
+ if (symbol.power.greaterThan(1)) {
+ symbol = _.expand(symbol);
+ }
+ // Remove the denominator
+ if (signVal < 0) {
+ const d = this.removeDen(symbol);
+ re = d[0];
+ im = d[1];
+ } else {
+ re = symbol.realpart();
+ im = symbol.imagpart();
+ }
+
+ if (re.isConstant('all') && im.isConstant('all')) {
+ return this[f](re, im);
+ }
+
+ return _.symfunction(f, [symbol]);
+ },
+ };
+ // Object for functions which handle trig
+ const trig = (this.trig = {
+ // Container for trigonometric function
+ cos(symbol) {
+ if (symbol.equals('pi') && symbol.multiplier.den.equals(2)) {
+ return new NerdamerSymbol(0);
+ }
+
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.equals(new NerdamerSymbol(Settings.PI / 2))) {
+ return new NerdamerSymbol(0);
+ }
+ if (symbol.isConstant()) {
+ if (Settings.USE_BIG) {
+ return new NerdamerSymbol(bigDec.cos(symbol.multiplier.toDecimal()));
+ }
+
+ return new NerdamerSymbol(Math.cos(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'cos');
+ }
+ }
+ if (symbol.equals(0)) {
+ return new NerdamerSymbol(1);
+ }
+
+ let retval;
+ let c = false;
+ const q = getQuadrant(symbol.multiplier.toDecimal());
+ const m = symbol.multiplier.abs();
+ symbol.multiplier = m;
+
+ if (symbol.isPi() && symbol.isLinear()) {
+ // Return for 1 or -1 for multiples of pi
+ if (isInt(m)) {
+ retval = new NerdamerSymbol(even(m) ? 1 : -1);
+ } else {
+ const _n = Number(m.num);
+ const d = Number(m.den);
+ if (d === 2) {
+ retval = new NerdamerSymbol(0);
+ } else if (d === 3) {
+ retval = _.parse('1/2');
+ c = true;
+ } else if (d === 4) {
+ retval = _.parse('1/sqrt(2)');
+ c = true;
+ } else if (d === 6) {
+ retval = _.parse('sqrt(3)/2');
+ c = true;
+ } else {
+ retval = _.symfunction('cos', [symbol]);
+ }
+ }
+ }
+
+ if (c && (q === 2 || q === 3)) {
+ retval.negate();
+ }
+
+ retval ||= _.symfunction('cos', [symbol]);
+
+ return retval;
+ },
+ sin(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ if (Number(symbol.multiplier.toDecimal()) % Math.PI === 0) {
+ return new NerdamerSymbol(0);
+ }
+
+ if (Settings.USE_BIG) {
+ return new NerdamerSymbol(bigDec.sin(symbol.multiplier.toDecimal()));
+ }
+
+ return new NerdamerSymbol(Math.sin(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'sin');
+ }
+ }
+
+ if (symbol.equals(0)) {
+ return new NerdamerSymbol(0);
+ }
+
+ let retval;
+ let c = false;
+ const q = getQuadrant(symbol.multiplier.toDecimal());
+ const signVal = symbol.multiplier.sign();
+ const m = symbol.multiplier.abs();
+ symbol.multiplier = m;
+ if (symbol.equals('pi')) {
+ retval = new NerdamerSymbol(0);
+ } else if (symbol.isPi() && symbol.isLinear()) {
+ // Return for 0 for multiples of pi
+ if (isInt(m)) {
+ retval = new NerdamerSymbol(0);
+ } else {
+ const _n = m.num;
+ const d = m.den;
+ if (d.equals(2)) {
+ retval = new NerdamerSymbol(1);
+ c = true;
+ } else if (d.equals(3)) {
+ retval = _.parse('sqrt(3)/2');
+ c = true;
+ } else if (d.equals(4)) {
+ retval = _.parse('1/sqrt(2)');
+ c = true;
+ } else if (d.equals(6)) {
+ retval = _.parse('1/2');
+ c = true;
+ } else {
+ retval = _.multiply(new NerdamerSymbol(signVal), _.symfunction('sin', [symbol]));
+ }
+ }
+ }
+
+ retval ||= _.multiply(new NerdamerSymbol(signVal), _.symfunction('sin', [symbol]));
+
+ if (c && (q === 3 || q === 4)) {
+ /** @type {NerdamerSymbolType} */ (retval).negate();
+ }
+
+ return retval;
+ },
+ tan(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (Number(symbol.multiplier.toDecimal()) % Math.PI === 0 && symbol.isLinear()) {
+ return new NerdamerSymbol(0);
+ }
+ if (symbol.isConstant()) {
+ if (Settings.USE_BIG) {
+ return new NerdamerSymbol(bigDec.tan(symbol.multiplier.toDecimal()));
+ }
+
+ return new NerdamerSymbol(Math.tan(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'tan');
+ }
+ }
+ let retval;
+ let c = false;
+ const q = getQuadrant(symbol.multiplier.toDecimal());
+ const m = symbol.multiplier;
+
+ symbol.multiplier = m;
+
+ if (symbol.isPi() && symbol.isLinear()) {
+ // Return 0 for all multiples of pi
+ if (isInt(m)) {
+ retval = new NerdamerSymbol(0);
+ } else {
+ const _n = m.num;
+ const d = m.den;
+ if (d.equals(2)) {
+ throw new UndefinedError(`tan is undefined for ${symbol.toString()}`);
+ } else if (d.equals(3)) {
+ retval = _.parse('sqrt(3)');
+ c = true;
+ } else if (d.equals(4)) {
+ retval = new NerdamerSymbol(1);
+ c = true;
+ } else if (d.equals(6)) {
+ retval = _.parse('1/sqrt(3)');
+ c = true;
+ } else {
+ retval = _.symfunction('tan', [symbol]);
+ }
+ }
+ }
+
+ retval ||= _.symfunction('tan', [symbol]);
+
+ if (c && (q === 2 || q === 4)) {
+ retval.negate();
+ }
+
+ return retval;
+ },
+ sec(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ if (Settings.USE_BIG) {
+ return new NerdamerSymbol(
+ new bigDec(1).dividedBy(bigDec.cos(symbol.multiplier.toDecimal()))
+ );
+ }
+
+ return new NerdamerSymbol(Math2.sec(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'sec');
+ }
+ return _.parse(format('1/cos({0})', symbol));
+ }
+
+ let retval;
+ let c = false;
+ const q = getQuadrant(symbol.multiplier.toDecimal());
+ const m = symbol.multiplier.abs();
+ symbol.multiplier = m;
+
+ if (symbol.isPi() && symbol.isLinear()) {
+ // Return for 1 or -1 for multiples of pi
+ if (isInt(m)) {
+ retval = new NerdamerSymbol(even(m) ? 1 : -1);
+ } else {
+ const _n = m.num;
+ const d = m.den;
+ if (d.equals(2)) {
+ throw new UndefinedError(`sec is undefined for ${symbol.toString()}`);
+ } else if (d.equals(3)) {
+ retval = new NerdamerSymbol(2);
+ c = true;
+ } else if (d.equals(4)) {
+ retval = _.parse('sqrt(2)');
+ c = true;
+ } else if (d.equals(6)) {
+ retval = _.parse('2/sqrt(3)');
+ c = true;
+ } else {
+ retval = _.symfunction('sec', [symbol]);
+ }
+ }
+ }
+
+ if (c && (q === 2 || q === 3)) {
+ retval.negate();
+ }
+
+ retval ||= _.symfunction('sec', [symbol]);
+
+ return retval;
+ },
+ csc(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ if (Settings.USE_BIG) {
+ return new NerdamerSymbol(
+ new bigDec(1).dividedBy(bigDec.sin(symbol.multiplier.toDecimal()))
+ );
+ }
+
+ return new NerdamerSymbol(Math2.csc(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'csc');
+ }
+ return _.parse(format('1/sin({0})', symbol));
+ }
+
+ let retval;
+ let c = false;
+ const q = getQuadrant(symbol.multiplier.toDecimal());
+ const signVal = symbol.multiplier.sign();
+ const m = symbol.multiplier.abs();
+
+ symbol.multiplier = m;
+
+ if (symbol.isPi() && symbol.isLinear()) {
+ // Return for 0 for multiples of pi
+ if (isInt(m)) {
+ throw new UndefinedError(`csc is undefined for ${symbol.toString()}`);
+ } else {
+ const _n = m.num;
+ const d = m.den;
+ if (d.equals(2)) {
+ retval = new NerdamerSymbol(1);
+ c = true;
+ } else if (d.equals(3)) {
+ retval = _.parse('2/sqrt(3)');
+ c = true;
+ } else if (d.equals(4)) {
+ retval = _.parse('sqrt(2)');
+ c = true;
+ } else if (d.equals(6)) {
+ retval = new NerdamerSymbol(2);
+ c = true;
+ } else {
+ retval = _.multiply(new NerdamerSymbol(signVal), _.symfunction('csc', [symbol]));
+ }
+ }
+ }
+
+ retval ||= _.multiply(new NerdamerSymbol(signVal), _.symfunction('csc', [symbol]));
+
+ if (c && (q === 3 || q === 4)) {
+ /** @type {NerdamerSymbolType} */ (retval).negate();
+ }
+
+ return retval;
+ },
+ cot(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (Number(symbol.multiplier.toDecimal()) % (Math.PI / 2) === 0) {
+ return new NerdamerSymbol(0);
+ }
+ if (symbol.isConstant()) {
+ if (Settings.USE_BIG) {
+ return new NerdamerSymbol(
+ new bigDec(1).dividedBy(bigDec.tan(symbol.multiplier.toDecimal()))
+ );
+ }
+
+ return new NerdamerSymbol(Math2.cot(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'cot');
+ }
+ return _.parse(format('1/tan({0})', symbol));
+ }
+ let retval;
+ let c = false;
+ const q = getQuadrant(symbol.multiplier.toDecimal());
+ const m = symbol.multiplier;
+
+ symbol.multiplier = m;
+
+ if (symbol.isPi() && symbol.isLinear()) {
+ // Return 0 for all multiples of pi
+ if (isInt(m)) {
+ throw new UndefinedError(`cot is undefined for ${symbol.toString()}`);
+ } else {
+ const _n = m.num;
+ const d = m.den;
+ if (d.equals(2)) {
+ retval = new NerdamerSymbol(0);
+ } else if (d.equals(3)) {
+ retval = _.parse('1/sqrt(3)');
+ c = true;
+ } else if (d.equals(4)) {
+ retval = new NerdamerSymbol(1);
+ c = true;
+ } else if (d.equals(6)) {
+ retval = _.parse('sqrt(3)');
+ c = true;
+ } else {
+ retval = _.symfunction('cot', [symbol]);
+ }
+ }
+ }
+
+ retval ||= _.symfunction('cot', [symbol]);
+
+ if (c && (q === 2 || q === 4)) {
+ retval.negate();
+ }
+
+ return retval;
+ },
+ acos(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ // Handle values in the complex domain
+ if (symbol.gt(1) || symbol.lt(-1)) {
+ const x = symbol.toString();
+ return expand(evaluate(`pi/2-asin(${x})`));
+ }
+ // Handle big numbers
+ if (Settings.USE_BIG) {
+ return new NerdamerSymbol(bigDec.acos(symbol.multiplier.toDecimal()));
+ }
+
+ return new NerdamerSymbol(Math.acos(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'acos');
+ }
+ }
+ return _.symfunction('acos', [symbol]);
+ },
+ asin(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ // Handle values in the complex domain
+ if (symbol.gt(1) || symbol.lt(-1)) {
+ const i = Settings.IMAGINARY;
+ const x = symbol.multiplier.toDecimal();
+ return expand(evaluate(`${i}*log(sqrt(1-${x}^2)-${i}*${x})`));
+ }
+ // Handle big numbers
+ if (Settings.USE_BIG) {
+ return new NerdamerSymbol(bigDec.asin(symbol.multiplier.toDecimal()));
+ }
+
+ return new NerdamerSymbol(Math.asin(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'asin');
+ }
+ }
+ return _.symfunction('asin', [symbol]);
+ },
+ atan(symbol) {
+ let retval;
+ if (symbol.equals(0)) {
+ retval = new NerdamerSymbol(0);
+ } else if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ // Handle big numbers
+ if (Settings.USE_BIG) {
+ return new NerdamerSymbol(bigDec.atan(symbol.multiplier.toDecimal()));
+ }
+
+ return new NerdamerSymbol(Math.atan(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'atan');
+ }
+ return _.symfunction('atan', [symbol]);
+ } else if (symbol.equals(-1)) {
+ retval = _.parse('-pi/4');
+ } else {
+ retval = _.symfunction('atan', [symbol]);
+ }
+ return retval;
+ },
+ asec(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.equals(0)) {
+ throw new OutOfFunctionDomainError('Input is out of the domain of sec!');
+ }
+ if (symbol.isConstant()) {
+ return trig.acos(symbol.invert());
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'asec');
+ }
+ }
+ return _.symfunction('asec', [symbol]);
+ },
+ acsc(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ return trig.asin(symbol.invert());
+ }
+
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'acsc');
+ }
+ }
+ return _.symfunction('acsc', [symbol]);
+ },
+ acot(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ return _.add(_.parse('pi/2'), trig.atan(symbol).negate());
+ }
+
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'acot');
+ }
+ }
+ return _.symfunction('acot', [symbol]);
+ },
+ atan2(a, b) {
+ if (a.equals(0) && b.equals(0)) {
+ throw new UndefinedError('atan2 is undefined for 0, 0');
+ }
+
+ if (Settings.PARSE2NUMBER && a.isConstant() && b.isConstant()) {
+ return new NerdamerSymbol(Math.atan2(a, b));
+ }
+ return _.symfunction('atan2', [a, b]);
+ },
+ });
+ // Object for functions which handle hyperbolic trig
+ const trigh = (this.trigh = {
+ // Container for hyperbolic trig function
+ cosh(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ return new NerdamerSymbol(Math.cosh(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'cosh');
+ }
+ }
+
+ return _.symfunction('cosh', [symbol]);
+ },
+ sinh(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ return new NerdamerSymbol(Math.sinh(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'sinh');
+ }
+ }
+
+ return _.symfunction('sinh', [symbol]);
+ },
+ tanh(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ return new NerdamerSymbol(Math.tanh(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'tanh');
+ }
+ }
+
+ return _.symfunction('tanh', [symbol]);
+ },
+ sech(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ return new NerdamerSymbol(Math.sech(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'sech');
+ }
+ return _.parse(format('1/cosh({0})', symbol));
+ }
+
+ return _.symfunction('sech', [symbol]);
+ },
+ csch(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ return new NerdamerSymbol(Math.csch(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'csch');
+ }
+ return _.parse(format('1/sinh({0})', symbol));
+ }
+
+ return _.symfunction('csch', [symbol]);
+ },
+ coth(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ return new NerdamerSymbol(Math.coth(symbol.valueOf()));
+ }
+ if (symbol.isImaginary()) {
+ return complex.evaluate(symbol, 'coth');
+ }
+ return _.parse(format('1/tanh({0})', symbol));
+ }
+
+ return _.symfunction('coth', [symbol]);
+ },
+ acosh(symbol) {
+ let retval;
+ if (Settings.PARSE2NUMBER && symbol.isImaginary()) {
+ retval = complex.evaluate(symbol, 'acosh');
+ } else if (Settings.PARSE2NUMBER) {
+ retval = evaluate(_.parse(format(`${Settings.LOG}(({0})+sqrt(({0})^2-1))`, symbol.toString())));
+ } else {
+ retval = _.symfunction('acosh', [symbol]);
+ }
+ return retval;
+ },
+ asinh(symbol) {
+ let retval;
+ if (Settings.PARSE2NUMBER && symbol.isImaginary()) {
+ retval = complex.evaluate(symbol, 'asinh');
+ } else if (Settings.PARSE2NUMBER) {
+ retval = evaluate(_.parse(format(`${Settings.LOG}(({0})+sqrt(({0})^2+1))`, symbol.toString())));
+ } else {
+ retval = _.symfunction('asinh', [symbol]);
+ }
+ return retval;
+ },
+ atanh(symbol) {
+ let retval;
+ if (Settings.PARSE2NUMBER && symbol.isImaginary()) {
+ retval = complex.evaluate(symbol, 'atanh');
+ } else if (Settings.PARSE2NUMBER) {
+ retval = evaluate(_.parse(format(`(1/2)*${Settings.LOG}((1+({0}))/(1-({0})))`, symbol.toString())));
+ } else {
+ retval = _.symfunction('atanh', [symbol]);
+ }
+ return retval;
+ },
+ asech(symbol) {
+ let retval;
+ if (Settings.PARSE2NUMBER && symbol.isImaginary()) {
+ retval = complex.evaluate(symbol, 'asech');
+ } else if (Settings.PARSE2NUMBER) {
+ retval = evaluate(
+ log(
+ _.add(
+ symbol.clone().invert(),
+ sqrt(_.subtract(_.pow(symbol, new NerdamerSymbol(-2)), new NerdamerSymbol(1)))
+ )
+ )
+ );
+ } else {
+ retval = _.symfunction('asech', [symbol]);
+ }
+ return retval;
+ },
+ acsch(symbol) {
+ let retval;
+ if (Settings.PARSE2NUMBER && symbol.isImaginary()) {
+ retval = complex.evaluate(symbol, 'acsch');
+ } else if (Settings.PARSE2NUMBER) {
+ retval = evaluate(_.parse(format(`${Settings.LOG}((1+sqrt(1+({0})^2))/({0}))`, symbol.toString())));
+ } else {
+ retval = _.symfunction('acsch', [symbol]);
+ }
+ return retval;
+ },
+ acoth(symbol) {
+ let retval;
+ if (Settings.PARSE2NUMBER && symbol.isImaginary()) {
+ retval = complex.evaluate(symbol, 'acoth');
+ } else if (Settings.PARSE2NUMBER) {
+ if (symbol.equals(1)) {
+ retval = NerdamerSymbol.infinity();
+ } else {
+ /** @type {NerdamerSymbolType} */
+ const logResult = /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (
+ log(
+ _.divide(
+ _.add(symbol.clone(), new NerdamerSymbol(1)),
+ _.subtract(symbol.clone(), new NerdamerSymbol(1))
+ )
+ )
+ )
+ );
+ retval = evaluate(
+ /** @type {NerdamerSymbolType} */ (_.divide(logResult, new NerdamerSymbol(2)))
+ );
+ }
+ } else {
+ retval = _.symfunction('acoth', [symbol]);
+ }
+ return retval;
+ },
+ });
+ // List of supported units
+ this.units = {};
+ // List all the supported operators
+ const operators = {
+ '\\': {
+ precedence: 8,
+ operator: '\\',
+ action: 'slash',
+ prefix: true,
+ postfix: false,
+ leftAssoc: true,
+ operation(e) {
+ return e; // Bypass the slash
+ },
+ },
+ '!!': {
+ precedence: 7,
+ operator: '!!',
+ action: 'dfactorial',
+ prefix: false,
+ postfix: true,
+ leftAssoc: true,
+ operation(e) {
+ return _.symfunction(Settings.DOUBLEFACTORIAL, [e]); // Wrap it in a factorial function
+ },
+ },
+ '!': {
+ precedence: 7,
+ operator: '!',
+ action: 'factorial',
+ prefix: false,
+ postfix: true,
+ leftAssoc: true,
+ operation(e) {
+ return _factorial(e); // Wrap it in a factorial function
+ },
+ },
+ '^': {
+ precedence: 6,
+ operator: '^',
+ action: 'pow',
+ prefix: false,
+ postfix: false,
+ leftAssoc: true,
+ },
+ '**': {
+ precedence: 6,
+ operator: '**',
+ action: 'pow',
+ prefix: false,
+ postfix: false,
+ leftAssoc: true,
+ },
+ '%': {
+ precedence: 4,
+ operator: '%',
+ action: 'percent',
+ prefix: false,
+ postfix: true,
+ leftAssoc: true,
+ overloaded: true,
+ overloadAction: 'mod',
+ overloadLeftAssoc: false,
+ operation(x) {
+ return _.divide(x, new NerdamerSymbol(100));
+ },
+ },
+ '*': {
+ precedence: 4,
+ operator: '*',
+ action: 'multiply',
+ prefix: false,
+ postfix: false,
+ leftAssoc: false,
+ },
+ '/': {
+ precedence: 4,
+ operator: '/',
+ action: 'divide',
+ prefix: false,
+ postfix: false,
+ leftAssoc: false,
+ },
+ '+': {
+ precedence: 3,
+ operator: '+',
+ action: 'add',
+ prefix: true,
+ postfix: false,
+ leftAssoc: false,
+ operation(x) {
+ return x;
+ },
+ },
+ plus: {
+ precedence: 3,
+ operator: 'plus',
+ action: 'add',
+ prefix: true,
+ postfix: false,
+ leftAssoc: false,
+ operation(x) {
+ return x;
+ },
+ },
+ '-': {
+ precedence: 3,
+ operator: '-',
+ action: 'subtract',
+ prefix: true,
+ postfix: false,
+ leftAssoc: false,
+ operation(x) {
+ return x.negate();
+ },
+ },
+ '=': {
+ precedence: 2,
+ operator: '=',
+ action: 'equals',
+ prefix: false,
+ postfix: false,
+ leftAssoc: false,
+ },
+ '==': {
+ precedence: 1,
+ operator: '==',
+ action: 'eq',
+ prefix: false,
+ postfix: false,
+ leftAssoc: false,
+ },
+ '<': {
+ precedence: 1,
+ operator: '<',
+ action: 'lt',
+ prefix: false,
+ postfix: false,
+ leftAssoc: false,
+ },
+ '<=': {
+ precedence: 1,
+ operator: '<=',
+ action: 'lte',
+ prefix: false,
+ postfix: false,
+ leftAssoc: false,
+ },
+ '>': {
+ precedence: 1,
+ operator: '>',
+ action: 'gt',
+ prefix: false,
+ postfix: false,
+ leftAssoc: false,
+ },
+ '=>': {
+ precedence: 1,
+ operator: '=>',
+ action: 'gte',
+ prefix: false,
+ postfix: false,
+ leftAssoc: false,
+ },
+ ',': {
+ precedence: 0,
+ operator: ',',
+ action: 'comma',
+ prefix: false,
+ postfix: false,
+ leftAssoc: false,
+ },
+ ':': {
+ precedence: 0,
+ operator: ',',
+ action: 'assign',
+ prefix: false,
+ postfix: false,
+ leftAssoc: false,
+ vectorFn: 'slice',
+ },
+ ':=': {
+ precedence: 0,
+ operator: ',',
+ action: 'functionAssign',
+ prefix: false,
+ postfix: false,
+ leftAssoc: true,
+ },
+ };
+ // Brackets
+ const brackets = {
+ '(': {
+ type: 'round',
+ id: 1,
+ is_open: true,
+ is_close: false,
+ },
+ ')': {
+ type: 'round',
+ id: 2,
+ is_open: false,
+ is_close: true,
+ },
+ '[': {
+ type: 'square',
+ id: 3,
+ is_open: true,
+ is_close: false,
+ maps_to: 'vector',
+ },
+ ']': {
+ type: 'square',
+ id: 4,
+ is_open: false,
+ is_close: true,
+ },
+ '{': {
+ type: 'curly',
+ id: 5,
+ is_open: true,
+ is_close: false,
+ maps_to: 'NerdamerSet',
+ },
+ '}': {
+ type: 'curly',
+ id: 6,
+ is_open: false,
+ is_close: true,
+ },
+ };
+ // Supported functions.
+ // Format: function_name: [mappedFunction, number_of_parameters]
+ /** @type {FunctionMapType} */
+ const functions = (this.functions = {
+ cos: [trig.cos, 1],
+ sin: [trig.sin, 1],
+ tan: [trig.tan, 1],
+ sec: [trig.sec, 1],
+ csc: [trig.csc, 1],
+ cot: [trig.cot, 1],
+ acos: [trig.acos, 1],
+ asin: [trig.asin, 1],
+ atan: [trig.atan, 1],
+ arccos: [trig.acos, 1],
+ arcsin: [trig.asin, 1],
+ arctan: [trig.atan, 1],
+ asec: [trig.asec, 1],
+ acsc: [trig.acsc, 1],
+ acot: [trig.acot, 1],
+ atan2: [trig.atan2, 2],
+ acoth: [trigh.acoth, 1],
+ asech: [trigh.asech, 1],
+ acsch: [trigh.acsch, 1],
+ sinh: [trigh.sinh, 1],
+ cosh: [trigh.cosh, 1],
+ tanh: [trigh.tanh, 1],
+ asinh: [trigh.asinh, 1],
+ sech: [trigh.sech, 1],
+ csch: [trigh.csch, 1],
+ coth: [trigh.coth, 1],
+ acosh: [trigh.acosh, 1],
+ atanh: [trigh.atanh, 1],
+ log10: [undefined, 1],
+ log2: [undefined, 1],
+ log1p: [undefined, 1],
+ exp: [exp, 1],
+ radians: [radians, 1],
+ degrees: [degrees, 1],
+ min: [min, -1],
+ max: [max, -1],
+ erf: [undefined, 1],
+ floor: [undefined, 1],
+ ceil: [undefined, 1],
+ trunc: [undefined, 1],
+ Si: [undefined, 1],
+ step: [undefined, 1],
+ rect: [undefined, 1],
+ sinc: [sinc, 1],
+ tri: [undefined, 1],
+ sign: [sign, 1],
+ Ci: [undefined, 1],
+ Ei: [undefined, 1],
+ Shi: [undefined, 1],
+ Chi: [undefined, 1],
+ Li: [undefined, 1],
+ fib: [undefined, 1],
+ fact: [_factorial, 1],
+ factorial: [_factorial, 1],
+ continuedFraction: [continuedFraction, [1, 2]],
+ dfactorial: [undefined, 1],
+ gamma_incomplete: [undefined, [1, 2]],
+ round: [round, [1, 2]],
+ scientific: [scientific, [1, 2]],
+ mod: [_mod, 2],
+ pfactor: [pfactor, 1],
+ vector: [vector, -1],
+ matrix: [matrix, -1],
+ NerdamerSet: [set, -1],
+ imatrix: [imatrix, -1],
+ parens: [parens, -1],
+ sqrt: [sqrt, 1],
+ cbrt: [cbrt, 1],
+ nthroot: [nthroot, 2],
+ log: [log, [1, 2]],
+ expand: [expandall, 1],
+ abs: [abs, 1],
+ invert: [invert, 1],
+ determinant: [determinant, 1],
+ size: [size, 1],
+ transpose: [transpose, 1],
+ dot: [dot, 2],
+ cross: [cross, 2],
+ vecget: [vecget, 2],
+ vecset: [vecset, 3],
+ vectrim: [vectrim, [1, 2]],
+ matget: [matget, 3],
+ matset: [matset, 4],
+ matgetrow: [matgetrow, 2],
+ matsetrow: [matsetrow, 3],
+ matgetcol: [matgetcol, 2],
+ matsetcol: [matsetcol, 3],
+ rationalize: [rationalize, 1],
+ IF: [IF, 3],
+ isIn: [isIn, 2],
+ // Imaginary support
+ realpart: [realpart, 1],
+ imagpart: [imagpart, 1],
+ conjugate: [conjugate, 1],
+ arg: [arg, 1],
+ polarform: [polarform, 1],
+ rectform: [rectform, 1],
+ sort: [sort, [1, 2]],
+ integer_part: [undefined, 1],
+ union: [union, 2],
+ contains: [contains, 2],
+ intersection: [intersection, 2],
+ difference: [difference, 2],
+ intersects: [intersects, 2],
+ isSubset: [isSubset, 2],
+ primes: [primes, 2],
+ // System support
+ print: [print, -1],
+ });
+
+ // Error handler
+ this.error = err;
+ // This function is used to comb through the function modules and find a function given its name
+ const findFunction = function (fname) {
+ const fmodules = Settings.FUNCTION_MODULES;
+ const l = fmodules.length;
+ for (let i = 0; i < l; i++) {
+ const fmodule = fmodules[i];
+ if (fname in fmodule) {
+ return fmodule[fname];
+ }
+ }
+ return err(`The function ${fname} is undefined!`);
+ };
+
+ /**
+ * This method gives the ability to override operators with new methods.
+ *
+ * @param {string} which
+ * @param {Function} withWhat
+ */
+ this.override = function override(which, withWhat) {
+ bin[which] ||= [];
+ bin[which].push(this[which]);
+ this[which] = withWhat;
+ };
+
+ /**
+ * Restores a previously overridden operator
+ *
+ * @param {string} what
+ */
+ this.restore = function restore(what) {
+ this[what] &&= bin[what].pop();
+ };
+
+ /**
+ * This method is supposed to behave similarly to the override method but it does not override the existing
+ * function rather it only extends it
+ *
+ * @param {string} what
+ * @param {Function} withWhat
+ * @param {boolean} forceCall
+ */
+ this.extend = function extend(what, withWhat, forceCall) {
+ const self = this;
+ const extended = this[what];
+ if (typeof extended === 'function' && typeof withWhat === 'function') {
+ const f = this[what];
+ this[what] = function extendedOp(a, b) {
+ if (isSymbol(a) && isSymbol(b) && !forceCall) {
+ return f.call(self, a, b);
+ }
+ return withWhat.call(self, a, b, f);
+ };
+ }
+ };
+
+ /**
+ * Generates library's representation of a function. It's a fancy way of saying a symbol with a few extras. The
+ * most important thing is that that it gives a fname and an args property to the symbols in addition to
+ * changing its group to FN
+ *
+ * @param {string} fnName
+ * @param {Array} params
+ * @returns {NerdamerSymbolType}
+ */
+ this.symfunction = function symfunction(fnName, params) {
+ // Call the proper function and return the result;
+ const f = new NerdamerSymbol(fnName);
+ f.group = FN;
+ if (typeof params === 'object') {
+ params = [].slice.call(params);
+ } // Ensure an array
+ f.args = params;
+ f.fname = fnName === PARENTHESIS ? '' : fnName;
+ f.updateHash();
+ return f;
+ };
+
+ /**
+ * An internal function call for the Parser. This will either trigger a real function call if it can do so or
+ * just return a symbolic representation of the function using symfunction.
+ *
+ * @param {string} fnName
+ * @param {Array} args
+ * @param {number} [allowedArgs]
+ * @returns {NerdamerSymbolType}
+ */
+ this.callfunction = function callfunction(fnName, args, allowedArgs = undefined) {
+ const fnSettings = functions[fnName];
+
+ if (!fnSettings) {
+ err(`Nerdamer currently does not support the function ${fnName}`);
+ }
+
+ const numAllowedArgs = fnSettings[1] || allowedArgs; // Get the number of allowed arguments
+ let fn = fnSettings[0]; // Get the mapped function
+ let retval;
+ // We want to be able to call apply on the arguments or create a symfunction. Both require
+ // an array so make sure to wrap the argument in an array.
+ if (!(args instanceof Array)) {
+ args = args === undefined ? [] : [args];
+ }
+
+ if (numAllowedArgs !== -1) {
+ const isArrayType = isArray(numAllowedArgs);
+ const minArgs = isArrayType ? numAllowedArgs[0] : numAllowedArgs;
+ const maxArgs = isArrayType ? numAllowedArgs[1] : numAllowedArgs;
+ const numArgs = args.length;
+
+ const errorMsg = `${fnName} requires a {0} of {1} arguments. {2} provided!`;
+
+ if (numArgs < minArgs) {
+ err(format(errorMsg, 'minimum', minArgs, numArgs));
+ }
+ if (numArgs > maxArgs) {
+ err(format(errorMsg, 'maximum', maxArgs, numArgs));
+ }
+ }
+
+ /*
+ * The following are very important to the how nerdamer constructs functions!
+ * Assumption 1 - if fn is undefined then handling of the function is purely numeric. This
+ * enables us to reuse Math, Math2, ..., any function from Settings.FUNCTIONS_MODULES entry
+ * Assumption 2 - if fn is defined then that function takes care of EVERYTHING including symbolics
+ * Assumption 3 - if the user calls symbolics on a function that returns a numeric value then
+ * they are expecting a symbolic output.
+ */
+ // check if arguments are all numers
+ const numericArgs = allNumbers(args);
+ // Big number support. Check if Big number is requested and the arguments are all numeric and, not imaginary
+ // if (Settings.USE_BIG && numericArgs) {
+ // retval = Big[fnName].apply(undefined, args);
+ // }
+ // else {
+ if (fn) {
+ // Call nerdamer function
+ // Remember assumption 2. The function is defined so it MUST handle all aspects including numeric values
+ retval = fn.apply(fnSettings[2], args);
+ } else {
+ // Call JS function
+ // Remember assumption 1. No function defined so it MUST be numeric in nature
+ fn = findFunction(fnName);
+ if (Settings.PARSE2NUMBER && numericArgs) {
+ retval = bigConvert(fn.apply(fn, args));
+ } else {
+ retval = _.symfunction(fnName, args);
+ }
+ }
+ // }
+
+ return retval;
+ };
+ /**
+ * Build a regex based on the operators currently loaded. These operators are to be ignored when substituting
+ * spaces for multiplication
+ */
+ this.operator_filter_regex = (function buildOperatorFilterRegex() {
+ // We only want the operators which are singular since those are the ones
+ // that nerdamer uses anyway
+ const ostr = `^\\${Object.keys(operators)
+ .filter(x => x.length === 1)
+ .join('\\')}`;
+ // Create a regex which captures all spaces between characters except those
+ // have an operator on one end
+ // Note: Cannot use 'u' flag because operator escapes like \! are invalid in Unicode mode
+ // eslint-disable-next-line require-unicode-regexp -- Dynamic regex with operator chars that have invalid Unicode escapes
+ return new RegExp(`([${ostr}])\\s+([${ostr}])`);
+ })();
+
+ /**
+ * Replaces nerdamer.setOperator
+ *
+ * @param {object} operator
+ * @param {Function} [action]
+ * @param {'over' | 'under'} [shift]
+ */
+ // eslint-disable-next-line no-shadow -- intentionally shadows outer setOperator for Parser method
+ this.setOperator = function setOperator(operator, action = undefined, shift = undefined) {
+ const name = operator.operator; // Take the name to be the symbol
+ operators[name] = operator;
+ if (action) {
+ this[operator.action] = action;
+ }
+ // Make the parser aware of the operator
+ _parser[name] = operator.operation;
+ // Make the action available to the parser if infix
+ if (!operator.action && !(operator.prefix || operator.postif)) {
+ operator.action = name;
+ }
+ // If this operator is exclusive then all successive operators should be shifted
+ if (shift === 'over' || shift === 'under') {
+ const { precedence } = operator;
+
+ for (const x in operators) {
+ if (!Object.hasOwn(operators, x)) {
+ continue;
+ }
+ const o = operators[x];
+ const condition = shift === 'over' ? o.precedence >= precedence : o.precedence > precedence;
+ if (condition) {
+ o.precedence++;
+ }
+ }
+ }
+ };
+
+ /**
+ * Gets an opererator by its symbol
+ *
+ * @param {string} operator
+ * @returns {object}
+ */
+ // eslint-disable-next-line no-shadow -- intentionally shadows outer getOperator for Parser method
+ this.getOperator = function getOperator(operator) {
+ return operators[operator];
+ };
+
+ // eslint-disable-next-line no-shadow -- intentionally shadows outer aliasOperator for Parser method
+ this.aliasOperator = function aliasOperator(o, n) {
+ const t = {};
+ const operator = operators[o];
+ // Copy everything over to the new operator
+ for (const x in operator) {
+ if (!Object.hasOwn(operator, x)) {
+ continue;
+ }
+ t[x] = operator[x];
+ }
+ // Update the symbol
+ t.operator = n;
+
+ this.setOperator(t);
+ };
+
+ /**
+ * Returns the list of operators. Caution! Can break parser!
+ *
+ * @returns {object}
+ */
+ this.getOperators = function getOperators() {
+ // Will replace this with some cloning action in the future
+ return operators;
+ };
+
+ this.getBrackets = function getBrackets() {
+ return brackets;
+ };
+ /*
+ * Preforms preprocessing on the string. Useful for making early modification before
+ * sending to the parser
+ * @param {string} e
+ * @param {ParserType} parser - The parser instance to use as context
+ */
+ const prepareExpression = function prepareExpression(e, parser) {
+ /*
+ * Since variables cannot start with a number, the assumption is made that when this occurs the
+ * user intents for this to be a coefficient. The multiplication symbol in then added. The same goes for
+ * a side-by-side close and open parenthesis
+ */
+ e = String(e);
+ // Apply preprocessors
+ for (let i = 0; i < preprocessors.actions.length; i++) {
+ e = preprocessors.actions[i].call(parser, e);
+ }
+
+ // E = e.split(' ').join('');//strip empty spaces
+ // replace multiple spaces with one space
+ e = e.replace(/\s+/gu, ' ');
+
+ // Only even bother to check if the string contains e. This regex is painfully slow and might need a better solution. e.g. hangs on (0.06/3650))^(365)
+ if (/e/giu.test(e)) {
+ // Negative numbers
+ e = e.replace(/-+\d+\.?\d*e\+?-?\d+/giu, x => scientificToDecimal(x));
+ // Positive numbers that are not part of an identifier
+ e = e.replace(/(?<![A-Za-z])\d+\.?\d*e\+?-?\d+/giu, x => scientificToDecimal(x));
+ }
+ // Replace scientific numbers
+
+ // allow omission of multiplication after coefficients
+ e =
+ e
+ .replace(Settings.IMPLIED_MULTIPLICATION_REGEX, (match, group1, group2, start, str) => {
+ const first = str.charAt(start);
+ let before = '';
+ let d = '*';
+ if (!first.match(/[+\-/*]/u)) {
+ before = str.charAt(start - 1);
+ }
+ if (before.match(/[a-z]/iu)) {
+ d = '';
+ }
+ return group1 + d + group2;
+ })
+ .replace(/(?<varname>[a-z0-9_]+)/giu, (match, a) => {
+ if (Settings.USE_MULTICHARACTER_VARS === false && !(a in functions)) {
+ if (!isNaN(a)) {
+ return a;
+ }
+ return a.split('').join('*');
+ }
+ return a;
+ })
+ // Allow omission of multiplication sign between brackets
+ .replace(/\)\(/gu, ')*(') || '0';
+ // Replace x(x+a) with x*(x+a)
+ while (true) {
+ const eOrg = e; // Store the original
+ e = e.replace(
+ /(?<prefix>[a-z0-9_]+)(?<open>\()|(?<close>\))(?<suffix>[a-z0-9]+)/giu,
+ (match, a, b, c, d) => {
+ const g1 = a || c;
+ const g2 = b || d;
+ if (g1 in functions) // Create a passthrough for functions
+ {
+ return g1 + g2;
+ }
+ return `${g1}*${g2}`;
+ }
+ );
+ // If the original equals the replace we're done
+ if (eOrg === e) {
+ break;
+ }
+ }
+ return e;
+ };
+ // Delay setting of constants until Settings is ready
+ this.initConstants = function initConstants() {
+ this.CONSTANTS = {
+ E: new NerdamerSymbol(Settings.E),
+ PI: new NerdamerSymbol(Settings.PI),
+ };
+ };
+ /*
+ * Debugging method used to better visualize vector and arrays
+ * @param {object | ScopeArrayType} o
+ * @returns {string}
+ */
+ this.prettyPrint = function prettyPrint(o) {
+ if (Array.isArray(o)) {
+ const arr = /** @type {ScopeArrayType} */ (o);
+ const s = arr.map(x => _.prettyPrint(x)).join(', ');
+ if (arr.type === 'vector') {
+ return `vector<${s}>`;
+ }
+ return `(${s})`;
+ }
+ return o.toString();
+ };
+ this.peekers = {
+ pre_operator: [],
+ post_operator: [],
+ pre_function: [],
+ post_function: [],
+ };
+
+ this.callPeekers = function callPeekers(name, ...rest) {
+ if (Settings.callPeekers) {
+ const peekers = this.peekers[name];
+ // Remove the first items and stringify
+ const args = rest.map(stringify);
+ // Call each one of the peekers
+ for (let i = 0; i < peekers.length; i++) {
+ peekers[i].apply(null, args);
+ }
+ }
+ };
+ /*
+ * Tokenizes the string
+ * @param {string} e
+ * @returns {Token[]}
+ */
+ this.tokenize = function tokenize(e) {
+ // Cast to String
+ e = String(e);
+ // Remove multiple white spaces and spaces at beginning and end of string
+ e = e.trim().replace(/\s+/gu, ' ');
+ // Remove spaces before and after brackets
+ for (const x in brackets) {
+ if (!Object.hasOwn(brackets, x)) {
+ continue;
+ }
+ const regex = new RegExp(brackets[x].is_close ? `\\s+\\${x}` : `\\${x}\\s+`, 'gu');
+ e = e.replace(regex, x);
+ }
+
+ let col = 0; // The column position
+ const L = e.length; // Expression length
+ let lpos = 0; // Marks beginning of next token
+ const tokens = []; // The tokens container
+ const scopes = [tokens]; // Initiate with the tokens as the highest scope
+ let target = scopes[0]; // The target to which the tokens are added. This can swing up or down
+ let depth = 0;
+ const openBrackets = [];
+ let hasSpace = false; // Marks if an open space character was found
+ let operatorStr; // Current operator string being processed
+ const SPACE = ' ';
+ const EMPTY_STRING = '';
+ const COMMA = ',';
+ const MINUS = '-';
+ const MULT = '*';
+ // Possible source of bug. Review
+ /*
+ //gets the next space
+ let next_space = function(from) {
+ for(let i=from; i<L; i++) {
+ if(e.charAt(i) === ' ')
+ return i;
+ }
+
+ return L; //assume the end of the string instead
+ };
+ */
+ /**
+ * Adds a scope to tokens
+ *
+ * @param {string} [scopeType]
+ * @param {number} [column]
+ * @returns {undefined}
+ */
+ const addScope = function (scopeType = undefined, column = undefined) {
+ /** @type {ScopeArrayType} */
+ const newScope = /** @type {ScopeArrayType} */ ([]); // Create a new scope
+ if (scopeType !== undefined) {
+ newScope.type = scopeType;
+ }
+ newScope.column = column; // Mark the column of the scope
+ scopes.push(newScope); // Add it to the list of scopes
+ target.push(newScope); // Add it to the tokens list since now it's a scope
+ target = newScope; // Point to it
+ depth++; // Go down one in scope
+ };
+ /**
+ * Goes up in scope by one
+ *
+ * @returns {undefined}
+ */
+ const goUp = function () {
+ scopes.pop(); // Remove the scope from the scopes stack
+ target = scopes[--depth]; // Point the above scope
+ };
+ /**
+ * Extracts all the operators from the expression string starting at postion startAt
+ *
+ * @param {number} startAt
+ * @returns {string}
+ */
+ const getOperatorStr = function (startAt) {
+ startAt = startAt === undefined ? col : startAt;
+ // Mark the end of the operator as the start since we're just going
+ // to be walking along the string
+ let end = startAt + 1;
+ // Just keep moving along
+ while (e.charAt(end++) in operators) {
+ // Intentionally empty - just advancing end pointer
+ }
+ // Remember that we started at one position ahead. The beginning operator is what triggered
+ // this function to be called in the first place. String.CharAt is zero based so we now
+ // have to correct two places. The initial increment + the extra++ at the end of end during
+ // the last iteration.
+ return e.substring(startAt, end - 1);
+ };
+ /**
+ * Breaks operator up in to several different operators as defined in operators
+ *
+ * @param {string} opStr
+ * @returns {Token[]}
+ */
+ const chunkify = function (opStr) {
+ const start = col - opStr.length; // Start of operator
+ const _operators = [];
+ let operator = opStr.charAt(0);
+ // Grab the largest possible chunks but start at 2 since we already know
+ // that the first character is an operator
+ const len = opStr.length;
+ let i;
+ for (i = 1; i < len; i++) {
+ const ch = opStr.charAt(i);
+ const o = operator + ch;
+ // Since the operator now is undefined then the last operator
+ // was the largest possible combination.
+ if (o in operators) {
+ operator = o; // Now the operator is the larger chunk
+ } else {
+ _operators.push(new Token(operator, Token.OPERATOR, start + i));
+ operator = ch;
+ }
+ }
+ // Add the last operator
+ _operators.push(new Token(operator, Token.OPERATOR, start + i));
+ return _operators;
+ };
+
+ /**
+ * Is used to add a token to the tokens array. Makes sure that no empty token is added
+ *
+ * @param {number} at
+ * @param {string} [token]
+ * @returns {undefined}
+ */
+ const addToken = function (at, token = undefined) {
+ // Grab the token if we're not supplied one
+ if (token === undefined) {
+ token = e.substring(lpos, at);
+ }
+ // Only add it if it's not an empty string
+ if (token in _.units) {
+ target.push(new Token(token, Token.UNIT, lpos));
+ } else if (token !== '') {
+ target.push(new Token(token, Token.VARIABLE_OR_LITERAL, lpos));
+ }
+ };
+ /**
+ * Adds a function to the output
+ *
+ * @param {string} f
+ * @returns {undefined}
+ */
+ const addFunction = function (f) {
+ target.push(new Token(f, Token.FUNCTION, lpos));
+ };
+ /**
+ * Tokens are found between operators so this marks the location of where the last token was found
+ *
+ * @param {number} position
+ * @returns {undefined}
+ */
+ const setLastPosition = function (position) {
+ lpos = position + 1;
+ };
+ /**
+ * When a operator is found and added, especially a combo operator, then the column location has to be
+ * adjusted to the end of the operator
+ *
+ * @returns {undefined}
+ */
+ const adjustColumnPosition = function () {
+ lpos = lpos + operatorStr.length - 2;
+ col = lpos - 1;
+ };
+ for (; col < L; col++) {
+ const ch = e.charAt(col);
+ if (ch in operators) {
+ addToken(col);
+ // Is the last token numeric?
+ const lastTokenIsNumeric = target[0] && isNumber(target[0]);
+ // Is this character multiplication?
+ const isMultiplication = lastTokenIsNumeric && ch === MULT;
+ // If we're in a new scope then go up by one but if the space
+ // is right befor an operator then it makes no sense to go up in scope
+ // consider sin -x. The last position = current position at the minus sign
+ // this means that we're going for sin(x) -x which is wrong
+ // Ignore comma since comma is still part of the existing scope.
+ if (hasSpace && lpos < col && !(ch === COMMA || isMultiplication)) {
+ hasSpace = false;
+ goUp();
+ }
+ // Mark the last position that a
+ setLastPosition(col + 1);
+ operatorStr = getOperatorStr(col);
+
+ adjustColumnPosition();
+ target.push(...chunkify(operatorStr));
+ } else if (ch in brackets) {
+ const bracket = brackets[ch];
+
+ if (bracket.is_open) {
+ // Mark the bracket
+ openBrackets.push([bracket, lpos]);
+ const f = e.substring(lpos, col);
+ if (f in functions) {
+ addFunction(f);
+ } else if (f !== '') {
+ // Assume multiplication
+ // TODO: Add the multiplication to stack
+ target.push(new Token(f, Token.VARIABLE_OR_LITERAL, lpos));
+ }
+ // Go down one in scope
+ addScope(bracket.maps_to, col);
+ } else if (bracket.is_close) {
+ // Get the matching bracket
+ const pair = openBrackets.pop();
+ // Throw errors accordingly
+ // missing open bracket
+ if (!pair) {
+ throw new ParityError(`Missing open bracket for bracket at: ${col + 1}`);
+ }
+ // Incorrect pair
+ else if (pair[0].id !== bracket.id - 1) {
+ throw new ParityError('Parity error');
+ }
+
+ addToken(col);
+ goUp();
+ }
+ setLastPosition(col);
+ } else if (ch === SPACE) {
+ const prev = e.substring(lpos, col); // Look back
+ let nxt = e.charAt(col + 1); // Look forward
+ if (hasSpace) {
+ if (prev in operators) {
+ target.push(new Token(prev, Token.OPERATOR, col));
+ } else {
+ addToken(undefined, prev);
+ // We're at the closing space
+ goUp(); // Go up in scope if we're at a space
+
+ // assume multiplication if it's not an operator except for minus
+ const isOperator = nxt in operators;
+
+ if ((isOperator && operators[nxt].value === MINUS) || !isOperator) {
+ target.push(new Token(MULT, Token.OPERATOR, col));
+ }
+ }
+ hasSpace = false; // Remove the space
+ } else {
+ // We're at the closing space
+ // check if it's a function
+ const f = e.substring(lpos, col);
+
+ if (f in functions) {
+ // There's no need to go up in scope if the next character is an operator
+ hasSpace = true; // Mark that a space was found
+ addFunction(f);
+ addScope();
+ } else if (f in operators) {
+ target.push(new Token(f, Token.OPERATOR, col));
+ } else {
+ addToken(undefined, f);
+ // Peek ahead to the next character
+ nxt = e.charAt(col + 1);
+
+ // If it's a number then add the multiplication operator to the stack but make sure that the next character
+ // is not an operator
+
+ if (
+ prev !== EMPTY_STRING &&
+ nxt !== EMPTY_STRING &&
+ !(prev in operators) &&
+ !(nxt in operators)
+ ) {
+ target.push(new Token(MULT, Token.OPERATOR, col));
+ }
+ }
+ // Possible source of bug. Review
+ /*
+ //space can mean multiplication so add the symbol if the is encountered
+ if(/\d+|\d+\.?\d*e[\+\-]*\d+/i.test(f)) {
+ let next = e.charAt(col+1);
+ let nextIsOperator = next in operators;
+ let ns = next_space(col+1);
+ let next_word = e.substring(col+1, ns);
+ //the next can either be a prefix operator or no operator
+ if((nextIsOperator && operators[next].prefix) || !(nextIsOperator || next_word in operators))
+ target.push(new Token('*', Token.OPERATOR, col));
+ }
+ */
+ }
+ setLastPosition(col); // Mark this location
+ }
+ }
+ // Check that all brackets were closed
+ if (openBrackets.length) {
+ const b = openBrackets.pop();
+ throw new ParityError(`Missing closed bracket for bracket at ${b[1] + 1}`);
+ }
+ // Add the last token
+ addToken(col);
+
+ return tokens;
+ };
+ /*
+ * Puts token array in Reverse Polish Notation
+ * @param {Token[]} tokens
+ * @returns {Token[]}
+ */
+ this.toRPN = function toRPN(tokens) {
+ const fn = tokens.type;
+ const l = tokens.length;
+ let i;
+ let e; // Current token being processed - also used for error reporting
+ const output = [];
+ const stack = [];
+ const prefixes = [];
+ const collapse = function collapse(target, destination) {
+ while (target.length) {
+ destination.push(target.pop());
+ }
+ };
+ // Mark all the prefixes and add them to the stack
+ for (i = 0; i < l; i++) {
+ const token = tokens[i];
+ if (token.type !== Token.OPERATOR) {
+ break;
+ }
+ if (!token.prefix) {
+ throw new OperatorError('Not a prefix operator');
+ }
+ token.is_prefix = true;
+ stack.push(token);
+ }
+ // Begin with remaining tokens
+ for (; i < l; i++) {
+ e = tokens[i];
+ if (e.type === Token.OPERATOR) {
+ const operator = e;
+
+ // Create the option for the operator being overloaded
+ if (operator.overloaded) {
+ const next = tokens[i + 1];
+ // If it's followed by a number or variable then we assume it's not a postfix operator
+ if (next && next.type === Token.VARIABLE_OR_LITERAL) {
+ operator.postfix = false;
+ // Override the original function with the overload function
+ operator.action = operator.overloadAction;
+ operator.leftAssoc = operator.overloadLeftAssoc;
+ }
+ }
+
+ // If the stack is not empty
+ while (stack.length) {
+ const last = stack[stack.length - 1];
+ // If (there is an operator at the top of the operator stack with greater precedence)
+ // or (the operator at the top of the operator stack has equal precedence and is left associative)) ~ wikipedia
+ // the !prefixes.length makes sure that the operator on stack isn't prematurely taken fromt he stack.
+ if (
+ !(
+ last.precedence > operator.precedence ||
+ (!operator.leftAssoc && last.precedence === operator.precedence)
+ )
+ ) {
+ break;
+ }
+ output.push(stack.pop());
+ }
+
+ // Change the behavior of the operator if it's a vector and we've been asked to do so
+ if ((fn === 'vector' || fn === 'set') && 'vectorFn' in operator) {
+ operator.action = operator.vectorFn;
+ }
+
+ // If the operator is a postfix operator then we're ready to go since it belongs
+ // to the preceding token. However the output cannot be empty. It must have either
+ // an operator or a variable/literal
+ if (operator.postfix) {
+ const previous = tokens[i - 1];
+ if (!previous) {
+ throw new OperatorError(`Unexpected prefix operator '${e.value}'! at ${e.column}`);
+ } else if (previous.type === Token.OPERATOR) {
+ // A postfix can only be followed by a postfix
+ if (!previous.postfix) {
+ throw new OperatorError(
+ `Unexpected prefix operator '${previous.value}'! at ${previous.column}`
+ );
+ }
+ }
+ } else {
+ // We must be at an infix so point the operator this
+ let nextIsOperator;
+ do {
+ // The first one is an infix operator all others have to be prefix operators so jump to the end
+ const next = tokens[i + 1]; // Take a look ahead
+ nextIsOperator = next ? next.type === Token.OPERATOR : false; // Check if it's an operator
+ if (nextIsOperator) {
+ // If it's not a prefix operator then it not in the right place
+ if (!next.prefix) {
+ throw new OperatorError(`A prefix operator was expected at ${next.column}`);
+ }
+ // Mark it as a confirmed prefix
+ next.is_prefix = true;
+ // Add it to the prefixes
+ prefixes.push(next);
+ i++;
+ }
+ } while (nextIsOperator);
+ }
+
+ // If it's a prefix it should be on a special stack called prefixes
+ // we do this to hold on to prefixes because of left associative operators.
+ // they belong to the variable/literal but if placed on either the stack
+ // or output there's no way of knowing this. I might be wrong so I welcome
+ // any discussion about this.
+
+ if (operator.is_prefix) // ADD ALL EXCEPTIONS FOR ADDING TO PREFIX STACK HERE. !!!
+ {
+ prefixes.push(operator);
+ } else {
+ stack.push(operator);
+ }
+ // Move the prefixes to the stack
+ while (prefixes.length) {
+ if (
+ operator.leftAssoc ||
+ (!operator.leftAssoc && prefixes[prefixes.length - 1].precedence >= operator.precedence)
+ ) // Revisit for commas
+ {
+ stack.push(prefixes.pop());
+ } else {
+ break;
+ }
+ }
+ } else if (e.type === Token.VARIABLE_OR_LITERAL) {
+ // Move prefixes to stack at beginning of scope
+ if (output.length === 0) {
+ collapse(prefixes, stack);
+ }
+ // Done with token
+ output.push(e);
+ const lastOnStack = stack[stack.length - 1];
+ // Then move all the prefixes to the output
+ if (!lastOnStack || !lastOnStack.leftAssoc) {
+ collapse(prefixes, output);
+ }
+ } else if (e.type === Token.FUNCTION) {
+ stack.push(e);
+ } else if (e.type === Token.UNIT) {
+ // If it's a unit it belongs on the stack since it's tied to the previous token
+ output.push(e);
+ }
+ // If it's an additonal scope then put that into RPN form
+ if (Array.isArray(e)) {
+ const scopeArr = /** @type {ScopeArrayType} */ (e);
+ output.push(this.toRPN(e));
+ if (scopeArr.type) {
+ output.push(new Token(scopeArr.type, Token.FUNCTION, scopeArr.column));
+ } // Since it's hidden it needs no column
+ }
+ }
+ // Collapse the remainder of the stack and prefixes to output
+ collapse(stack, output);
+ collapse(prefixes, output);
+
+ return output;
+ };
+ /*
+ * Parses the tokens
+ * @param {Tokens[]} rpn
+ * @param {object} substitutions
+ * @returns {NerdamerSymbolType}
+ */
+ // eslint-disable-next-line no-shadow -- rpn parameter name matches expected API
+ this.parseRPN = function parseRPN(rpn, substitutions) {
+ try {
+ // Default substitutions
+ substitutions ||= {};
+ // Prepare the substitutions.
+ // we first parse them out as-is
+ for (const x in substitutions) {
+ if (!Object.hasOwn(substitutions, x)) {
+ continue;
+ }
+ substitutions[x] = _.parse(substitutions[x], {});
+ }
+
+ // Although technically constants,
+ // pi and e are only available when evaluating the expression so add to the subs.
+ // Doing this avoids rounding errors
+ // link e and pi
+ if (Settings.PARSE2NUMBER) {
+ // Use the value provided if the individual for some strange reason prefers this.
+ // one reason could be to sub e but not pi or vice versa
+ if (!('e' in substitutions)) {
+ substitutions.e = new NerdamerSymbol(Settings.E);
+ }
+ if (!('pi' in substitutions)) {
+ substitutions.pi = new NerdamerSymbol(Settings.PI);
+ }
+ }
+
+ const Q = [];
+ let e; // Current RPN token being processed - also used for error reporting
+ for (let i = 0, l = rpn.length; i < l; i++) {
+ e = rpn[i];
+
+ // Arrays indicate a new scope so parse that out
+ if (Array.isArray(e)) {
+ e = this.parseRPN(e, substitutions);
+ }
+
+ if (e) {
+ if (e.type === Token.OPERATOR) {
+ if (e.is_prefix || e.postfix) // Resolve the operation assocated with the prefix
+ {
+ Q.push(e.operation(Q.pop()));
+ } else {
+ let b = Q.pop();
+ let a = Q.pop();
+ // Throw an error if the RH value is empty. This cannot be a postfix since we already checked
+ if (typeof a === 'undefined') {
+ throw new OperatorError(`${e} is not a valid postfix operator at ${e.column}`);
+ }
+
+ const isComma = e.action === 'comma';
+ // Convert Sets to Vectors on all operations at this point. Sets are only recognized functions or individually
+ if (a instanceof NerdamerSet && !isComma) {
+ a = Vector.fromSet(/** @type {SetType} */ (a));
+ }
+
+ if (b instanceof NerdamerSet && !isComma) {
+ b = Vector.fromSet(/** @type {SetType} */ (b));
+ }
+
+ // Call all the pre-operators
+ this.callPeekers('pre_operator', a, b, e);
+
+ const ans = _[e.action](a, b);
+
+ // Call all the pre-operators
+ this.callPeekers('post_operator', ans, a, b, e);
+
+ Q.push(ans);
+ }
+ } else if (e.type === Token.FUNCTION) {
+ let args = Q.pop();
+ const { parent } = args; // Make a note of the parent
+ if (!(args instanceof Collection)) {
+ args = Collection.create(args);
+ }
+ // The return value may be a vector. If it is then we check
+ // Q to see if there's another vector on the stack. If it is then
+ // we check if has elements. If it does then we know that we're dealing
+ // with an "getter" object and return the requested values
+
+ // call the function. This is the _.callfunction method in nerdamer
+ const fnName = e.value;
+ const fnArgs = args.getItems();
+
+ // Call the pre-function peekers
+ this.callPeekers('pre_function', fnName, fnArgs);
+
+ const ret = _.callfunction(fnName, fnArgs);
+
+ // Call the post-function peekers
+ this.callPeekers('post_function', ret, fnName, fnArgs);
+
+ const _last = Q[Q.length - 1];
+ const next = rpn[i + 1];
+ const _next_is_comma = next && next.type === Token.OPERATOR && next.value === ',';
+
+ // If(!next_is_comma && ret instanceof Vector && last && last.elements && !(last instanceof Collection)) {
+ // //remove the item from the queue
+ // let item = Q.pop();
+
+ // let getter = ret.elements[0];
+ // //check if it's symbolic. If so put it back and add the item to the stack
+ // if(!getter.isConstant()) {
+ // item.getter = getter;
+ // Q.push(item);
+ // Q.push(ret);
+ // }
+ // else if(getter instanceof Slice) {
+ // //if it's a Slice return the slice
+ // Q.push(Vector.fromArray(item.elements.slice(getter.upper, getter.lower)));
+ // }
+ // else {
+ // let index = Number(getter);
+ // let il = item.elements.length;
+ // //support for negative indices
+ // if(index < 0)
+ // index = il + index;
+ // //it it's still out of bounds
+ // if(index < 0 || index >= il) //index should no longer be negative since it's been reset above
+ // //range error
+ // throw new OutOfRangeError('Index out of range ' + (e.column + 1));
+
+ // let element = item.elements[index];
+ // //cyclic but we need to mark this for future reference
+ // item.getter = index;
+ // element.parent = item;
+
+ // Q.push(element);
+ // }
+ // }
+ // else {
+ // extend the parent reference
+ if (parent) {
+ ret.parent = parent;
+ }
+ Q.push(ret);
+ // }
+ } else {
+ let subbed;
+ const v = e.value;
+
+ if (v in Settings.ALIASES) {
+ e = _.parse(Settings.ALIASES[e]);
+ }
+ // Wrap it in a symbol if need be
+ else if (e.type === Token.VARIABLE_OR_LITERAL) {
+ e = new NerdamerSymbol(v);
+ } else if (e.type === Token.UNIT) {
+ /** @type {NerdamerSymbolType} */
+ const unitSymbol = /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (new NerdamerSymbol(v))
+ );
+ unitSymbol.isUnit = true;
+ e = unitSymbol;
+ }
+
+ // Make substitutions
+ // Always constants first. This avoids the being overridden
+ if (v in _.CONSTANTS) {
+ subbed = e;
+ e = new NerdamerSymbol(_.CONSTANTS[v]);
+ }
+ // Next substitutions. This allows declared variable to be overridden
+ // check if the values match to avoid erasing the multiplier.
+ // Example:/e = 3*a. substutiting a for a will wipe out the multiplier.
+ else if (v in substitutions && v !== substitutions[v].toString()) {
+ subbed = e;
+ e = substitutions[v].clone();
+ }
+ // Next declare variables
+ else if (v in VARS) {
+ subbed = e;
+ e = VARS[v].clone();
+ }
+ // Make notation of what it was before
+ if (subbed) {
+ e.subbed = subbed;
+ }
+
+ Q.push(e);
+ }
+ }
+ }
+
+ const retval = Q[0];
+
+ if (['undefined', 'string', 'number'].indexOf(typeof retval) !== -1) {
+ throw new UnexpectedTokenError('Unexpected token!');
+ }
+
+ return retval;
+ } catch (error) {
+ if (error.message === 'timeout') {
+ throw error;
+ }
+ // Rethrow non-parsing errors (TypeError, ReferenceError, etc.) as-is
+ // to preserve stack traces for debugging
+ if (error instanceof TypeError || error instanceof ReferenceError || error instanceof RangeError) {
+ throw error;
+ }
+ const rethrowErrors = [OutOfFunctionDomainError];
+ // Rethrow certain errors in the same class to preserve them
+ rethrowErrors.forEach(E => {
+ if (error instanceof E) {
+ const col = /** @type {{ column?: number }} */ (error).column;
+ throw new E(`${error.message}${col ? `: ${col}` : ''}`);
+ }
+ });
+
+ const errCol = /** @type {{ column?: number }} */ (error).column;
+ throw new ParseError(`${error.message}${errCol ? `: ${errCol}` : ''}`);
+ }
+ };
+ /**
+ * This is the method that triggers the parsing of the string. It generates a parse tree but processes it right
+ * away. The operator functions are called when their respective operators are reached. For instance
+ *
+ * - With cause this.add to be called with the left and right hand values. It works by walking along each
+ * character of the string and placing the operators on the stack and values on the output. When an operator
+ * having a lower order than the last is reached then the stack is processed from the last operator on the
+ * stack.
+ */
+
+ /** Node class for representing parse tree nodes */
+ class Node {
+ /** @param {{ type: string; value: string; left?: Node; right?: Node }} token */
+ constructor(token) {
+ this.type = token.type;
+ this.value = token.value;
+ // The incoming token may already be a Node type
+ this.left = token.left;
+ this.right = token.right;
+ }
+
+ toString() {
+ const left = this.left ? `${this.left.toString()}---` : '';
+ const right = this.right ? `---${this.right.toString()}` : '';
+ return `${left}(${this.value})${right}`;
+ }
+
+ toHTML(depth, indent) {
+ depth ||= 0;
+ indent = typeof indent === 'undefined' ? 4 : indent;
+ const tab = function tab(n) {
+ return ' '.repeat(indent * n);
+ };
+ let html = '';
+ const left = this.left
+ ? `${tab(depth + 1)}<li>\n${this.left.toHTML(depth + 2, indent)}${tab(depth + 1)}</li> \n`
+ : '';
+ const right = this.right
+ ? `${tab(depth + 1)}<li>\n${this.right.toHTML(depth + 2, indent)}${tab(depth + 1)}</li>\n`
+ : '';
+ html = `${tab(depth)}<div class="${this.type.toLowerCase()}"><span>${this.value}</span></div>${tab(depth)}\n`;
+ if (left || right) {
+ html += `${tab(depth)}<ul>\n${left}${right}${tab(depth)}</ul>\n`;
+ }
+ return html;
+ }
+ }
+
+ // eslint-disable-next-line no-shadow -- intentionally shadows outer tree for Parser method
+ this.tree = function tree(tokens) {
+ const Q = [];
+ for (let i = 0; i < tokens.length; i++) {
+ let e = tokens[i];
+ // Arrays indicate a new scope so parse that out
+ if (Array.isArray(e)) {
+ e = this.tree(e);
+ // If it's a comma then it's just arguments
+ Q.push(e);
+ continue;
+ }
+ if (e.type === Token.OPERATOR) {
+ if (e.is_prefix || e.postfix) {
+ // Prefixes go to the left, postfix to the right
+ const location = e.is_prefix ? 'left' : 'right';
+ const last = Q.pop();
+ e = new Node(e);
+ e[location] = last;
+ Q.push(e);
+ } else {
+ e = new Node(e);
+ e.right = Q.pop();
+ e.left = Q.pop();
+ Q.push(e);
+ }
+ } else if (e.type === Token.FUNCTION) {
+ e = new Node(e);
+ const args = Q.pop();
+ e.right = args;
+ if (e.value === 'object') {
+ // Check if Q has a value
+ let last = Q[Q.length - 1];
+ if (last) {
+ while (last.right) {
+ last = last.right;
+ }
+ last.right = e;
+ continue;
+ }
+ }
+
+ Q.push(e);
+ } else {
+ Q.push(new Node(e));
+ }
+ }
+
+ return Q[0];
+ };
+ // eslint-disable-next-line no-shadow -- intentionally shadows outer parse for Parser method
+ this.parse = function parse(e, substitutions) {
+ e = prepareExpression(e, this);
+ substitutions ||= {};
+ // Three passes but easier to debug
+ const tokens = this.tokenize(e);
+ const rpnTokens = this.toRPN(tokens);
+ return this.parseRPN(rpnTokens, substitutions);
+ };
+ /**
+ * TODO: Switch to Parser.tokenize for this method Reads a string into an array of Symbols and operators
+ *
+ * @param {string} expressionString
+ * @returns {Array}
+ */
+ this.toObject = function toObject(expressionString) {
+ const objectify = function objectify(tokens) {
+ const output = [];
+ for (let i = 0, l = tokens.length; i < l; i++) {
+ const token = tokens[i];
+ const v = token.value;
+ if (token.type === Token.VARIABLE_OR_LITERAL) {
+ output.push(new NerdamerSymbol(v));
+ } else if (token.type === Token.FUNCTION) {
+ // Jump ahead since the next object are the arguments
+ i++;
+ // Create a symbolic function and stick it on output
+ const f = _.symfunction(v, objectify(tokens[i]));
+ f.isConversion = true;
+ output.push(f);
+ } else if (token.type === Token.OPERATOR) {
+ output.push(v);
+ } else {
+ output.push(objectify(token));
+ }
+ }
+
+ return output;
+ };
+ return objectify(_.tokenize(expressionString));
+ };
+
+ // A helper method for toTeX
+ const chunkAtCommas = function chunkAtCommas(arr) {
+ let k = 0;
+ const chunks = [[]];
+ for (let j = 0, l = arr.length; j < l; j++) {
+ if (arr[j] === ',') {
+ k++;
+ chunks[k] = [];
+ } else {
+ chunks[k].push(arr[j]);
+ }
+ }
+ return chunks;
+ };
+
+ // Helper method for toTeX
+ const remBrackets = function (str) {
+ return str.replace(/^\\left\((?<inner>.+)\\right\)$/gu, (match, a) => {
+ if (a) {
+ return a;
+ }
+ return match;
+ });
+ };
+
+ const removeRedundantPowers = function (arr) {
+ // The filtered array
+ const narr = [];
+
+ while (arr.length) {
+ // Remove the element from the front
+ const e = arr.shift();
+ const next = arr[0];
+ const nextIsArray = isArray(next);
+ const nextIsMinus = next === '-';
+
+ // Remove redundant plusses
+ if (e === '^') {
+ if (next === '+') {
+ arr.shift();
+ } else if (nextIsArray && next[0] === '+') {
+ next.shift();
+ }
+
+ // Remove redundant parentheses
+ if (nextIsArray && next.length === 1) {
+ arr.unshift(arr.shift()[0]);
+ }
+ }
+
+ // Check if it's a negative power
+ if (e === '^' && ((nextIsArray && next[0] === '-') || nextIsMinus)) {
+ // If so:
+ // - Remove it from the new array, place a one and a division sign in that array and put it back
+ const last = narr.pop();
+ // Check if it's something multiplied by
+ const before = narr[narr.length - 1];
+ let beforeLast = '1';
+
+ if (before === '*') {
+ narr.pop();
+ // For simplicity we just pop it.
+ beforeLast = narr.pop();
+ }
+ // Implied multiplication
+ else if (isArray(before)) {
+ beforeLast = narr.pop();
+ }
+
+ narr.push(beforeLast, '/', last, e);
+
+ // Remove the negative sign from the power
+ if (nextIsArray) {
+ next.shift();
+ } else {
+ arr.shift();
+ }
+
+ // Remove it from the array so we don't end up with redundant parentheses if we can
+ if (nextIsArray && next.length === 1) {
+ narr.push(arr.shift()[0]);
+ }
+ } else {
+ narr.push(e);
+ }
+ }
+
+ return narr;
+ };
+ /*
+ * Convert expression or object to LaTeX
+ * @param {string} expressionOrObj
+ * @param {object} opt
+ * @returns {string}
+ */
+ this.toTeX = function toTeX(expressionOrObj, opt) {
+ opt ||= {};
+ // Add decimal option as per issue #579. Consider passing an object to Latex.latex as option instead of string
+ const decimals = opt.decimals === true ? 'decimals' : undefined;
+
+ let obj = typeof expressionOrObj === 'string' ? this.toObject(expressionOrObj) : expressionOrObj;
+ const TeX = [];
+ const cdot = typeof opt.cdot === 'undefined' ? '\\cdot' : opt.cdot; // NerdamerSet omit cdot to true by default
+
+ // Remove negative powers as per issue #570
+ obj = removeRedundantPowers(obj);
+
+ if (isArray(obj)) {
+ const nobj = [];
+ let a;
+ let b;
+ // First handle ^
+ for (let i = 0; i < obj.length; i++) {
+ a = obj[i];
+
+ if (obj[i + 1] === '^') {
+ b = obj[i + 2];
+ nobj.push(`${LaTeX.braces(this.toTeX([a]))}^${LaTeX.braces(this.toTeX([b]))}`);
+ i += 2;
+ } else {
+ nobj.push(a);
+ }
+ }
+ obj = nobj;
+ }
+
+ for (let i = 0, l = obj.length; i < l; i++) {
+ let e = obj[i];
+
+ // Convert * to cdot
+ if (e === '*') {
+ e = cdot;
+ }
+
+ if (isSymbol(e)) {
+ if (e.group === FN) {
+ const { fname } = e;
+ let f;
+
+ if (fname === SQRT) {
+ f = `\\sqrt${LaTeX.braces(this.toTeX(e.args))}`;
+ } else if (fname === ABS) {
+ f = LaTeX.brackets(this.toTeX(e.args), 'abs');
+ } else if (fname === PARENTHESIS) {
+ f = LaTeX.brackets(this.toTeX(e.args), 'parens');
+ } else if (fname === Settings.LOG) {
+ f = `\\${Settings.LOG_LATEX}\\left( ${this.toTeX(e.args)}\\right)`;
+ } else if (fname === Settings.LOG10) {
+ f = `\\${Settings.LOG10_LATEX}\\left( ${this.toTeX(e.args)}\\right)`;
+ } else if (fname === Settings.LOG2) {
+ f = `\\${Settings.LOG2_LATEX}\\left( ${this.toTeX(e.args)}\\right)`;
+ } else if (fname === Settings.LOG1P) {
+ f = `\\${format(Settings.LOG1P_LATEX, this.toTeX(e.args))}`;
+ } else if (fname === 'integrate') {
+ /* Retrive [Expression, x] */
+ const chunks = chunkAtCommas(e.args);
+ /* Build TeX */
+ const expr = LaTeX.braces(this.toTeX(chunks[0]));
+ const dx = this.toTeX(chunks[1]);
+ f = `\\int ${expr}\\, d${dx}`;
+ } else if (fname === 'defint') {
+ const chunks = chunkAtCommas(e.args);
+ const expr = LaTeX.braces(this.toTeX(chunks[0]));
+ const dx = this.toTeX(chunks[3]);
+ const lb = this.toTeX(chunks[1]);
+ const ub = this.toTeX(chunks[2]);
+ f = `\\int\\limits_{${lb}}^{${ub}} ${expr}\\, d${dx}`;
+ } else if (fname === 'diff') {
+ const chunks = chunkAtCommas(e.args);
+ let dx = '';
+ const expr = LaTeX.braces(this.toTeX(chunks[0]));
+ /* Handle cases: one argument provided, we need to guess the variable, and assume n = 1 */
+ if (chunks.length === 1) {
+ const vars = [];
+ for (let j = 0; j < chunks[0].length; j++) {
+ if (chunks[0][j].group === 3) {
+ vars.push(chunks[0][j].value);
+ }
+ }
+ vars.sort();
+ dx = vars.length > 0 ? `\\frac{d}{d ${vars[0]}}` : '\\frac{d}{d x}';
+ } else if (chunks.length === 2) {
+ /* If two arguments, we have expression and variable, we assume n = 1 */
+ dx = `\\frac{d}{d ${chunks[1]}}`;
+ } else {
+ /* If we have more than 2 arguments, we assume we've got everything */
+ dx = `\\frac{d^{${chunks[2]}}}{d ${this.toTeX(chunks[1])}^{${chunks[2]}}}`;
+ }
+
+ f = `${dx}\\left(${expr}\\right)`;
+ } else if (fname === 'sum' || fname === 'product') {
+ // Split e.args into 4 parts based on locations of , symbols.
+ const argSplit = [[], [], [], []];
+ let argIdx = 0;
+ for (let k = 0; k < e.args.length; k++) {
+ if (/** @type {string} */ (/** @type {unknown} */ (e.args[k])) === ',') {
+ argIdx++;
+ continue;
+ }
+ argSplit[argIdx].push(e.args[k]);
+ }
+ // Then build TeX string.
+ f =
+ (fname === 'sum' ? '\\sum_' : '\\prod_') +
+ LaTeX.braces(`${this.toTeX(argSplit[1])} = ${this.toTeX(argSplit[2])}`);
+ f += `^${LaTeX.braces(this.toTeX(argSplit[3]))}${LaTeX.braces(this.toTeX(argSplit[0]))}`;
+ } else if (fname === 'limit') {
+ const toTeXfn = this.toTeX.bind(this);
+ const parserRef = _;
+ const args = chunkAtCommas(e.args).map(x => {
+ if (Array.isArray(x)) {
+ return parserRef.toTeX(x.join(''));
+ }
+ return toTeXfn(String(x));
+ });
+ f = `\\lim_${LaTeX.braces(`${args[1]}\\to ${args[2]}`)} ${LaTeX.braces(args[0])}`;
+ } else if (fname === FACTORIAL || fname === DOUBLEFACTORIAL) {
+ f = this.toTeX(e.args) + (fname === FACTORIAL ? '!' : '!!');
+ } else {
+ f = LaTeX.latex(e, decimals);
+ // F = '\\mathrm'+LaTeX.braces(fname.replace(/_/g, '\\_')) + LaTeX.brackets(this.toTeX(e.args), 'parens');
+ }
+
+ TeX.push(f);
+ } else {
+ TeX.push(LaTeX.latex(e, decimals));
+ }
+ } else if (isArray(e)) {
+ TeX.push(LaTeX.brackets(this.toTeX(e)));
+ } else if (e === '/') {
+ TeX.push(LaTeX.frac(remBrackets(TeX.pop()), remBrackets(this.toTeX([obj[++i]]))));
+ } else {
+ TeX.push(e);
+ }
+ }
+
+ return TeX.join(' ');
+ };
+
+ // Parser.functions ==============================================================
+ /* Although parens is not a "real" function it is important in some cases when the
+ * symbol must carry parenthesis. Once set you don't have to worry about it anymore
+ * as the parser will get rid of it at the first opportunity
+ */
+ function parens(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ return symbol;
+ }
+ return _.symfunction('parens', [symbol]);
+ }
+
+ function abs(symbol) {
+ // |-∞| = ∞
+ if (symbol.isInfinity) {
+ return NerdamerSymbol.infinity();
+ }
+ if (symbol.multiplier.lessThan(0)) {
+ symbol.multiplier.negate();
+ }
+
+ if (symbol.isImaginary()) {
+ const re = symbol.realpart();
+ const im = symbol.imagpart();
+ if (re.isConstant() && im.isConstant()) {
+ return sqrt(_.add(_.pow(re, new NerdamerSymbol(2)), _.pow(im, new NerdamerSymbol(2))));
+ }
+ } else if (isNumericSymbol(symbol) || even(symbol.power)) {
+ return symbol;
+ }
+ // Together.math baseunits are presumed positive
+ else if (
+ isVariableSymbol(symbol) &&
+ typeof symbol.value === 'string' &&
+ symbol.value.startsWith('baseunit_')
+ ) {
+ return symbol;
+ }
+
+ if (symbol.isComposite()) {
+ const ms = [];
+ symbol.each(x => {
+ ms.push(x.multiplier);
+ });
+ const gcd = Math2.QGCD.apply(null, ms);
+ if (gcd.lessThan(0)) {
+ symbol.multiplier = symbol.multiplier.multiply(new Frac(-1));
+ symbol.distributeMultiplier();
+ }
+ }
+
+ // Convert |n*x| to n*|x|
+ const m = _.parse(symbol.multiplier);
+ symbol.toUnitMultiplier();
+
+ return _.multiply(m, _.symfunction(ABS, [symbol]));
+ }
+ /**
+ * The factorial function
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType | VectorType | MatrixType}
+ */
+ function _factorial(symbol) {
+ let retval;
+ if (isVector(symbol)) {
+ const V = new Vector();
+ symbol.each((x, i) => {
+ // I start at one.
+ V.set(
+ /** @type {number} */ (/** @type {unknown} */ (i)) - 1,
+ /** @type {NerdamerSymbolType} */ (_factorial(x))
+ );
+ });
+ return /** @type {VectorType} */ (V);
+ }
+ if (isMatrix(symbol)) {
+ const M = new Matrix();
+ symbol.each((x, i, j) => {
+ // I start at one.
+ M.set(i, j, /** @type {NerdamerSymbolType} */ (_factorial(x)));
+ });
+ return /** @type {MatrixType} */ (M);
+ }
+ if (Settings.PARSE2NUMBER && symbol.isConstant()) {
+ if (isInt(symbol)) {
+ retval = Math2.bigfactorial(symbol);
+ } else {
+ retval = Math2.gamma(symbol.multiplier.add(/** @type {FracType} */ (new Frac(1))).toDecimal());
+ }
+
+ retval = bigConvert(retval);
+ return retval;
+ }
+ if (symbol.isConstant()) {
+ const den = symbol.getDenom();
+ if (den.equals(2)) {
+ const num = symbol.getNum();
+ let a;
+ let b;
+ let n;
+
+ if (symbol.multiplier.isNegative()) {
+ n = /** @type {NerdamerSymbolType} */ (
+ _.subtract(num.negate(), new NerdamerSymbol(1))
+ ).multiplier.divide(new Frac(2));
+ a = /** @type {NerdamerSymbolType} */ (
+ _.pow(new NerdamerSymbol(-4), new NerdamerSymbol(n))
+ ).multiplier.multiply(Math2.bigfactorial(n));
+ b = Math2.bigfactorial(new Frac(2).multiply(n));
+ } else {
+ n = /** @type {NerdamerSymbolType} */ (_.add(num, new NerdamerSymbol(1))).multiplier.divide(
+ new Frac(2)
+ );
+ a = Math2.bigfactorial(new Frac(2).multiply(n));
+ b = /** @type {NerdamerSymbolType} */ (
+ _.pow(new NerdamerSymbol(4), new NerdamerSymbol(n))
+ ).multiplier.multiply(Math2.bigfactorial(n));
+ }
+ const c = a.divide(b);
+ return /** @type {NerdamerSymbolType | VectorType | MatrixType} */ (
+ _.multiply(_.parse('sqrt(pi)'), new NerdamerSymbol(c))
+ );
+ }
+ }
+ return /** @type {NerdamerSymbolType | VectorType | MatrixType} */ (_.symfunction(FACTORIAL, [symbol]));
+ }
+ /**
+ * Returns the continued fraction of a number
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {NerdamerSymbolType} n
+ * @returns {NerdamerSymbolType | Vector}
+ */
+ function continuedFraction(symbol, n) {
+ const _symbol = evaluate(symbol);
+ if (_symbol.isConstant()) {
+ const cf = Math2.continuedFraction(_symbol, n);
+ // Convert the fractions array to a new Vector
+ const fractions = Vector.fromArray(cf.fractions.map(x => new NerdamerSymbol(x)));
+ return Vector.fromArray([
+ new NerdamerSymbol(cf.sign),
+ new NerdamerSymbol(cf.whole),
+ /** @type {NerdamerSymbolType} */ (/** @type {unknown} */ (fractions)),
+ ]);
+ }
+ return _.symfunction('continuedFraction', [symbol, n]);
+ }
+ /**
+ * Returns the error function
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ function _erf(symbol) {
+ const _symbol = evaluate(symbol);
+
+ if (_symbol.isConstant()) {
+ return new NerdamerSymbol(Math2.erf(_symbol));
+ }
+ if (_symbol.isImaginary()) {
+ return complex.erf(symbol);
+ }
+ return _.symfunction('erf', [symbol]);
+ }
+ /**
+ * The mod function
+ *
+ * @param {NerdamerSymbolType} symbol1
+ * @param {NerdamerSymbolType} symbol2
+ * @returns {NerdamerSymbolType}
+ */
+ function _mod(symbol1, symbol2) {
+ if (symbol1.isConstant() && symbol2.isConstant()) {
+ const retval = new NerdamerSymbol(1);
+ retval.multiplier = retval.multiplier.multiply(symbol1.multiplier.mod(symbol2.multiplier));
+ return retval;
+ }
+ // Try to see if division has remainder of zero
+ const r = _.divide(symbol1.clone(), symbol2.clone());
+ if (isInt(r)) {
+ return new NerdamerSymbol(0);
+ }
+ return _.symfunction('mod', [symbol1, symbol2]);
+ }
+ /**
+ * A branghing function
+ *
+ * @param {boolean} condition
+ * @param {NerdamerSymbolType} a
+ * @param {NerdamerSymbolType} b
+ * @returns {NerdamerSymbolType}
+ */
+ function IF(condition, a, b) {
+ if (typeof condition !== 'boolean') {
+ if (isNumericSymbol(condition)) {
+ condition = !!Number(condition);
+ }
+ }
+ if (condition) {
+ return a;
+ }
+ return b;
+ }
+ /**
+ * @param {MatrixType | VectorType | SetType | CollectionType} obj
+ * @param {NerdamerSymbolType} item
+ * @returns {NerdamerSymbolType}
+ */
+ function isIn(obj, item) {
+ if (isMatrix(obj)) {
+ for (let i = 0, l = obj.rows(); i < l; i++) {
+ for (let j = 0, l2 = obj.cols(); j < l2; j++) {
+ const element = /** @type {NerdamerSymbolType} */ (obj.elements[i][j]);
+ if (element.equals(item)) {
+ return new NerdamerSymbol(1);
+ }
+ }
+ }
+ } else if (obj.elements) {
+ for (let i = 0, l = obj.elements.length; i < l; i++) {
+ if (/** @type {NerdamerSymbolType} */ (obj.elements[i]).equals(item)) {
+ return new NerdamerSymbol(1);
+ }
+ }
+ }
+
+ return new NerdamerSymbol(0);
+ }
+
+ /**
+ * A symbolic extension for sinc
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ function sinc(symbol) {
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant()) {
+ return new NerdamerSymbol(Math2.sinc(symbol));
+ }
+ return _.parse(format('sin({0})/({0})', symbol));
+ }
+ return _.symfunction('sinc', [symbol]);
+ }
+
+ /**
+ * A symbolic extension for exp. This will auto-convert all instances of exp(x) to e^x. Thanks @ Happypig375
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType | VectorType | MatrixType}
+ */
+ function exp(symbol) {
+ if (symbol.fname === Settings.LOG && symbol.isLinear()) {
+ return _.pow(symbol.args[0], NerdamerSymbol.create(symbol.multiplier.toString()));
+ }
+ return _.parse(format('e^({0})', symbol));
+ }
+
+ /**
+ * Converts value degrees to radians
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ function radians(symbol) {
+ return _.parse(format('({0})*pi/180', symbol));
+ }
+
+ /**
+ * Converts value from radians to degrees
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ function degrees(symbol) {
+ return _.parse(format('({0})*180/pi', symbol));
+ }
+
+ function _nroots(symbol) {
+ let a;
+ let b;
+ /** @type {(NerdamerSymbolType | VectorType | MatrixType)[]} */
+ let _roots;
+ if (symbol.group === FN && symbol.fname === '') {
+ a = NerdamerSymbol.unwrapPARENS(_.parse(symbol).toLinear());
+ b = _.parse(symbol.power);
+ } else if (symbol.group === P) {
+ a = _.parse(symbol.value);
+ b = _.parse(symbol.power);
+ }
+
+ if (a && b && a.group === N && b.group === N) {
+ _roots = [];
+ const _parts = NerdamerSymbol.toPolarFormArray(symbol);
+ const r = _.parse(a).abs().toString();
+ // https://en.wikipedia.org/wiki/De_Moivre%27s_formula
+ const x = arg(a).toString();
+ const n = b.multiplier.den.toString();
+ const p = b.multiplier.num.toString();
+
+ const formula = '(({0})^({1})*(cos({3})+({2})*sin({3})))^({4})';
+ for (let i = 0; i < Number(n); i++) {
+ const t = evaluate(_.parse(format('(({0})+2*pi*({1}))/({2})', x, i, n))).multiplier.toDecimal();
+ _roots.push(evaluate(_.parse(format(formula, r, n, Settings.IMAGINARY, t, p))));
+ }
+ return Vector.fromArray(/** @type {(string | number | NerdamerSymbolType)[]} */ (_roots));
+ }
+ if (symbol.isConstant(true)) {
+ const signVal = symbol.sign();
+ const x = evaluate(symbol.abs());
+ const root = _.sqrt(x);
+
+ _roots = [root.clone(), root.negate()];
+
+ if (signVal < 0) {
+ _roots = _roots.map(r => _.multiply(r, NerdamerSymbol.imaginary()));
+ }
+ } else {
+ _roots = [_.parse(symbol)];
+ }
+
+ return Vector.fromArray(/** @type {(string | number | NerdamerSymbolType)[]} */ (_roots));
+ }
+
+ /**
+ * Rationalizes a symbol
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType | VectorType | MatrixType}
+ */
+ function rationalize(symbol) {
+ if (symbol.isComposite()) {
+ /** @type {NerdamerSymbolType} */
+ let retval = new NerdamerSymbol(0);
+ let num;
+ let den;
+ let retnum;
+ let retden;
+ let a;
+ let b;
+ let n;
+ let d;
+ symbol.each(x => {
+ num = x.getNum();
+ den = x.getDenom();
+ retnum = retval.getNum();
+ retden = retval.getDenom();
+ a = _.multiply(den, retnum);
+ b = _.multiply(num, retden);
+ n = _.expand(_.add(a, b));
+ d = _.multiply(retden, den);
+ retval = /** @type {NerdamerSymbolType} */ (_.divide(n, d));
+ }, true);
+
+ return retval;
+ }
+ return symbol;
+ }
+
+ /**
+ * The square root function
+ *
+ * @param {string | number | NerdamerSymbolType | VectorType | MatrixType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ function sqrt(symbol) {
+ if (!isSymbol(symbol)) {
+ symbol = /** @type {NerdamerSymbolType} */ (_.parse(/** @type {string | number} */ (symbol)));
+ }
+
+ const original = _.symfunction('sqrt', [symbol]);
+
+ // Exit early for EX
+ if (symbol.group === EX) {
+ return _.symfunction(SQRT, [symbol]);
+ }
+
+ if (symbol.fname === '' && symbol.power.equals(1)) {
+ symbol = symbol.args[0];
+ }
+
+ const isNeg = symbol.multiplier.sign() < 0;
+
+ if (Settings.PARSE2NUMBER) {
+ if (symbol.isConstant() && !isNeg) {
+ return new NerdamerSymbol(bigDec.sqrt(symbol.multiplier.toDecimal()));
+ }
+ if (symbol.isImaginary()) {
+ return /** @type {NerdamerSymbolType} */ (complex.sqrt(symbol));
+ }
+ if (symbol.group === S) {
+ return _.symfunction('sqrt', [symbol]);
+ }
+ }
+
+ let img;
+ let retval;
+ const isConstant = symbol.isConstant();
+
+ if (symbol.group === CB && symbol.isLinear()) {
+ let m = sqrt(new NerdamerSymbol(symbol.multiplier));
+ for (const s in symbol.symbols) {
+ if (!Object.hasOwn(symbol.symbols, s)) {
+ continue;
+ }
+ const x = symbol.symbols[s];
+ m = /** @type {NerdamerSymbolType} */ (_.multiply(m, /** @type {NerdamerSymbolType} */ (sqrt(x))));
+ }
+
+ retval = m;
+ }
+ // If the symbol is already sqrt then it's that symbol^(1/4) and we can unwrap it
+ else if (symbol.fname === SQRT) {
+ const s = symbol.args[0];
+ const ms = symbol.multiplier;
+ s.setPower(/** @type {FracType} */ (symbol.power).multiply(new Frac(0.25)));
+ retval = s;
+ // Grab the multiplier
+ if (!ms.equals(1)) {
+ retval = _.multiply(sqrt(_.parse(ms)), retval);
+ }
+ }
+ // If the symbol is a fraction then we don't keep can unwrap it. For instance
+ // no need to keep sqrt(x^(1/3))
+ else if (!symbol.power.isInteger()) {
+ symbol.setPower(/** @type {FracType} */ (symbol.power).multiply(new Frac(0.5)));
+ retval = symbol;
+ } else if (Number(symbol.multiplier) < 0 && symbol.group === S) {
+ const a = _.parse(symbol.multiplier).negate();
+ const b = _.parse(symbol).toUnitMultiplier().negate();
+ retval = _.multiply(_.symfunction(Settings.SQRT, [b]), sqrt(a));
+ } else {
+ // Related to issue #401. Since sqrt(a)*sqrt(b^-1) relates in issues, we'll change the form
+ // to sqrt(a)*sqrt(b)^1 for better simplification
+ // the sign of the power
+ const signVal = symbol.power.sign();
+ // Remove the sign
+ symbol.power = symbol.power.abs();
+
+ // If the symbols is imagary then we place in the imaginary part. We'll return it
+ // as a product
+ if (isConstant && symbol.multiplier.lessThan(0)) {
+ img = NerdamerSymbol.imaginary();
+ symbol.multiplier = symbol.multiplier.abs();
+ }
+
+ let q = Number(symbol.multiplier.toDecimal());
+ const qa = Math.abs(q);
+ const t = Math.sqrt(qa);
+
+ let m;
+ // It's a perfect square so take the square
+ if (isInt(t)) {
+ m = new NerdamerSymbol(t);
+ } else if (isInt(q)) {
+ const factors = Math2.ifactor(q);
+ let tw = 1;
+ for (const x in factors) {
+ if (!Object.hasOwn(factors, x)) {
+ continue;
+ }
+ const n = factors[x];
+ const nn = n - (n % 2); // Get out the whole numbers
+ if (nn) {
+ // If there is a whole number ...
+ const w = Number(x) ** nn;
+ tw *= Number(x) ** (nn / 2); // Add to total wholes
+ q /= w; // Reduce the number by the wholes
+ }
+ }
+ m = _.multiply(_.symfunction(SQRT, [new NerdamerSymbol(q)]), new NerdamerSymbol(tw));
+ } else {
+ // Reduce the numerator and denominator using prime factorization
+ const c = [new NerdamerSymbol(symbol.multiplier.num), new NerdamerSymbol(symbol.multiplier.den)];
+ /** @type {NerdamerSymbolType[]} */
+ const r = [new NerdamerSymbol(1), new NerdamerSymbol(1)];
+ /** @type {NerdamerSymbolType[]} */
+ const sq = [new NerdamerSymbol(1), new NerdamerSymbol(1)];
+ // Capture _ to avoid no-loop-func warning
+ const parserRef = _;
+ for (let i = 0; i < 2; i++) {
+ const n = c[i];
+ // Get the prime factors and loop through each.
+ pfactor(n).each(factor => {
+ factor = NerdamerSymbol.unwrapPARENS(factor);
+ const b = factor.clone().toLinear();
+ const p = Number(factor.power);
+ // We'll consider it safe to use the native Number since 2^1000 is already a pretty huge number
+ const rem = p % 2; // Get the remainder. This will be 1 if 3 since sqrt(n^2) = n where n is positive
+ const w = (p - rem) / 2; // Get the whole numbers of n/2
+ r[i] = /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (
+ parserRef.multiply(r[i], parserRef.pow(b, new NerdamerSymbol(w)))
+ )
+ );
+ sq[i] = /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (
+ parserRef.multiply(sq[i], sqrt(parserRef.pow(b, new NerdamerSymbol(rem))))
+ )
+ );
+ });
+ }
+ m = _.divide(_.multiply(r[0], sq[0]), _.multiply(r[1], sq[1]));
+ }
+
+ // Strip the multiplier since we already took the sqrt
+ symbol = symbol.toUnitMultiplier(true);
+ // If the symbol is one just return one and not the sqrt function
+ if (symbol.isOne()) {
+ retval = symbol;
+ } else if (even(symbol.power.toString())) {
+ // Just raise it to the 1/2
+ retval = _.pow(symbol.clone(), new NerdamerSymbol(0.5));
+ } else {
+ retval = _.symfunction(SQRT, [symbol]);
+ }
+
+ // Put back the sign that was removed earlier
+ if (signVal < 0) {
+ /** @type {NerdamerSymbolType} */ (retval).power.negate();
+ }
+
+ if (m) {
+ retval = _.multiply(m, retval);
+ }
+
+ if (img) {
+ retval = _.multiply(img, retval);
+ }
+ }
+
+ if (isNegative && Settings.PARSE2NUMBER && retval.text() !== original.text()) {
+ return _.parse(/** @type {NerdamerSymbolType} */ (retval));
+ }
+
+ return /** @type {NerdamerSymbolType} */ (retval);
+ }
+
+ /**
+ * The cube root function
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType | VectorType | MatrixType}
+ */
+ function cbrt(symbol) {
+ if (!symbol.isConstant(true)) {
+ let retval;
+
+ const n = Number(symbol.power) / 3;
+ // Take the cube root of the multplier
+ const m = _.pow(_.parse(symbol.multiplier), new NerdamerSymbol(1 / 3));
+ // Strip the multiplier
+ const sym = symbol.toUnitMultiplier();
+
+ // Simplify the power
+ if (isInt(n)) {
+ retval = _.pow(sym.toLinear(), _.parse(String(n)));
+ } else if (sym.group === CB) {
+ retval = new NerdamerSymbol(1);
+ sym.each(x => {
+ retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, cbrt(x)));
+ });
+ } else {
+ retval = _.symfunction('cbrt', [sym]);
+ }
+
+ return _.multiply(m, retval);
+ }
+ return nthroot(symbol, new NerdamerSymbol(3));
+ }
+
+ function scientific(symbol, sigfigs) {
+ // Just set the flag and keep it moving. NerdamerSymbol.toString will deal with how to
+ // display this
+ symbol.scientific = sigfigs || 10;
+ return symbol;
+ }
+
+ /**
+ * @param {NerdamerSymbolType} num - The number being raised
+ * @param {NerdamerSymbolType} p - The exponent
+ * @param {number} [prec] - The precision wanted
+ * @param {boolean} [asbig] - True if a bigDecimal is wanted
+ * @returns {NerdamerSymbolType}
+ */
+ function nthroot(num, p, prec = undefined, asbig = undefined) {
+ // Clone p and convert to a number if possible
+ p = evaluate(_.parse(p));
+
+ // Cannot calculate if p = 0. nthroot(0, 0) => 0^(1/0) => undefined
+ if (p.equals(0)) {
+ throw new UndefinedError('Unable to calculate nthroots of zero');
+ }
+
+ // Stop computation if it negative and even since we have an imaginary result
+ if (Number(num) < 0 && even(p)) {
+ throw new Error('Cannot calculate nthroot of negative number for even powers');
+ }
+
+ // Return non numeric values unevaluated
+ if (!num.isConstant(true)) {
+ /** @type {NerdamerSymbolType[]} */
+ const symArgs = [num, p];
+ if (typeof prec !== 'undefined') {
+ symArgs.push(new NerdamerSymbol(prec));
+ }
+ if (typeof asbig !== 'undefined') {
+ symArgs.push(new NerdamerSymbol(asbig ? 1 : 0));
+ }
+ return _.symfunction('nthroot', symArgs);
+ }
+
+ // Evaluate numeric values
+ if (num.group !== N) {
+ num = evaluate(num);
+ }
+
+ // Default is to return a big value
+ if (typeof asbig === 'undefined') {
+ asbig = true;
+ }
+
+ prec ||= 25;
+
+ const signVal = num.sign();
+ let retval;
+ let ans;
+
+ if (signVal < 0) {
+ num = abs(num); // Remove the sign
+ }
+
+ if (isInt(num) && p.isConstant()) {
+ if (Number(num) < 18446744073709551616) {
+ // 2^64
+ ans = Frac.create(Number(num) ** (1 / Number(p)));
+ } else {
+ ans = Math2.nthroot(num, p);
+ }
+
+ if (asbig) {
+ retval = new NerdamerSymbol(ans);
+ } else {
+ retval = new NerdamerSymbol(ans.toDecimal(prec));
+ }
+
+ return /** @type {NerdamerSymbolType} */ (_.multiply(new NerdamerSymbol(signVal), retval));
+ }
+ return undefined;
+ }
+
+ function pfactor(symbol) {
+ // Fix issue #458 | nerdamer("sqrt(1-(3.3333333550520926e-7)^2)").evaluate().text()
+ // More Big Number issues >:(
+ if (symbol.greaterThan(9.999999999998891e41) || symbol.equals(-1)) {
+ return symbol;
+ }
+ // Fix issue #298
+ if (symbol.equals(Math.PI)) {
+ return new NerdamerSymbol(Math.PI);
+ }
+ // Evaluate the symbol to merge constants
+ symbol = evaluate(symbol.clone());
+
+ let retval;
+ if (symbol.isConstant()) {
+ retval = new NerdamerSymbol(1);
+ const m = symbol.toString();
+ if (isInt(m)) {
+ const factors = Math2.ifactor(m);
+ for (const factor in factors) {
+ if (!Object.hasOwn(factors, factor)) {
+ continue;
+ }
+ const p = factors[factor];
+ retval = _.multiply(
+ retval,
+ _.symfunction('parens', [new NerdamerSymbol(factor).setPower(new Frac(p))])
+ );
+ }
+ } else {
+ const n = pfactor(new NerdamerSymbol(symbol.multiplier.num));
+ const d = pfactor(new NerdamerSymbol(symbol.multiplier.den));
+ retval = _.multiply(_.symfunction('parens', [n]), _.symfunction('parens', [d]).invert());
+ }
+ } else {
+ retval = _.symfunction('pfactor', [symbol]);
+ }
+ return retval;
+ }
+
+ /**
+ * Get's the real part of a complex number. Return number if real
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ function realpart(symbol) {
+ return /** @type {NerdamerSymbolType} */ (symbol.realpart());
+ }
+
+ /**
+ * Get's the imaginary part of a complex number
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ function imagpart(symbol) {
+ return /** @type {NerdamerSymbolType} */ (symbol.imagpart());
+ }
+
+ /**
+ * Computes the conjugate of a complex number
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ function conjugate(symbol) {
+ const re = symbol.realpart();
+ const im = symbol.imagpart();
+ return /** @type {NerdamerSymbolType} */ (_.add(re, _.multiply(im.negate(), NerdamerSymbol.imaginary())));
+ }
+
+ /**
+ * Returns the arugment of a complex number
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ function arg(symbol) {
+ const re = symbol.realpart();
+ const im = symbol.imagpart();
+ if (re.isConstant() && im.isConstant()) {
+ // Right angles
+ if (im.equals(0) && re.equals(1)) {
+ return _.parse('0');
+ }
+ if (im.equals(1) && re.equals(0)) {
+ return _.parse('pi/2');
+ }
+ if (im.equals(0) && re.equals(-1)) {
+ return _.parse('pi');
+ }
+ if (im.equals(-1) && re.equals(0)) {
+ return _.parse('-pi/2');
+ }
+
+ // 45 degrees
+ if (im.equals(1) && re.equals(1)) {
+ return _.parse('pi/4');
+ }
+ if (im.equals(1) && re.equals(-1)) {
+ return _.parse('pi*3/4');
+ }
+ if (im.equals(-1) && re.equals(1)) {
+ return _.parse('-pi/4');
+ }
+ if (im.equals(-1) && re.equals(-1)) {
+ return _.parse('-pi*3/4');
+ }
+
+ // All the rest
+ return new NerdamerSymbol(Math.atan2(Number(im), Number(re)));
+ }
+ return _.symfunction('atan2', [im, re]);
+ }
+
+ /**
+ * Returns the polarform of a complex number
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ function polarform(symbol) {
+ const p = NerdamerSymbol.toPolarFormArray(symbol);
+ const theta = p[1];
+ const r = p[0];
+ const e = _.parse(format('e^({0}*({1}))', Settings.IMAGINARY, theta));
+ return /** @type {NerdamerSymbolType} */ (_.multiply(r, e));
+ }
+
+ /**
+ * Returns the rectangular form of a complex number. Does not work for symbolic coefficients
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @returns {NerdamerSymbolType}
+ */
+ function rectform(symbol) {
+ // TODO: e^((i*pi)/4)
+ const original = symbol.clone();
+ /**
+ * @typedef {{
+ * a: NerdamerSymbolType;
+ * x: NerdamerSymbolType;
+ * ax: NerdamerSymbolType;
+ * b: NerdamerSymbolType;
+ * }} RectformDecompose
+ */
+ try {
+ const f = /** @type {RectformDecompose} */ (decomposeFn(symbol, 'e', true));
+ const xPower = NerdamerSymbolDeps.isSymbol(f.x.power) ? f.x.power : _.parse(f.x.power);
+ const p = _.divide(/** @type {NerdamerSymbolType} */ (xPower), NerdamerSymbol.imaginary());
+ const q = evaluate(trig.tan(p));
+ const _s = _.pow(f.a, new NerdamerSymbol(2));
+ const d = /** @type {NerdamerSymbolType} */ (q.getDenom());
+ const n = /** @type {NerdamerSymbolType} */ (q.getNum());
+ const h = NerdamerSymbol.hyp(n, d);
+ // Check
+ if (h.equals(f.a)) {
+ return /** @type {NerdamerSymbolType} */ (_.add(d, _.multiply(NerdamerSymbol.imaginary(), n)));
+ }
+ return /** @type {NerdamerSymbolType} */ (original);
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ return /** @type {NerdamerSymbolType} */ (original);
+ }
+ }
+
+ function symMinMax(f, args) {
+ args.forEach(x => {
+ x.numVal = evaluate(x).multiplier;
+ });
+ let l;
+ let a;
+ let b;
+ let _a_val;
+ let _b_val;
+ while (true) {
+ l = args.length;
+ if (l < 2) {
+ return args[0];
+ }
+ a = args.pop();
+ b = args[l - 2];
+ if (f === 'min' ? a.numVal < b.numVal : a.numVal > b.numVal) {
+ args.pop();
+ args.push(a);
+ }
+ }
+ }
+
+ /**
+ * Returns maximum of a set of numbers
+ *
+ * @returns {NerdamerSymbolType}
+ */
+ function max(...args) {
+ if (allSame(args)) {
+ return args[0];
+ }
+ if (allNumbers(args)) {
+ return new NerdamerSymbol(Math.max.apply(null, args));
+ }
+ if (Settings.SYMBOLIC_MIN_MAX && allConstants(args)) {
+ return symMinMax('max', args);
+ }
+ return _.symfunction('max', args);
+ }
+
+ /**
+ * Returns minimum of a set of numbers
+ *
+ * @returns {NerdamerSymbolType}
+ */
+ function min(...args) {
+ if (allSame(args)) {
+ return args[0];
+ }
+ if (allNumbers(args)) {
+ return new NerdamerSymbol(Math.min.apply(null, args));
+ }
+ if (Settings.SYMBOLIC_MIN_MAX && allConstants(args)) {
+ return symMinMax('min', args);
+ }
+ return _.symfunction('min', args);
+ }
+
+ /**
+ * Returns the sign of a number
+ *
+ * @param {NerdamerSymbolType} x
+ * @returns {NerdamerSymbolType}
+ */
+ function sign(x) {
+ if (x.isConstant(true)) {
+ return new NerdamerSymbol(Math.sign(/** @type {number} */ (/** @type {unknown} */ (evaluate(x)))));
+ }
+ return _.symfunction('sign', [x]);
+ }
+
+ function sort(symbol, opt) {
+ opt = opt ? opt.toString() : 'asc';
+ const getval = function (e) {
+ if (e.group === N) {
+ return e.multiplier;
+ }
+ if (e.group === FN) {
+ if (e.fname === '') {
+ return getval(e.args[0]);
+ }
+ return e.fname;
+ }
+ if (e.group === S) {
+ return e.power;
+ }
+
+ return e.value;
+ };
+ const symbols = /** @type {NerdamerSymbolType[]} */ (
+ isVector(symbol) ? symbol.elements : symbol.collectSymbols()
+ );
+ return new Vector(
+ symbols.sort((a, b) => {
+ const aval = getval(a);
+ const bval = getval(b);
+ if (opt === 'desc') {
+ return bval - aval;
+ }
+ return aval - bval;
+ })
+ );
+ }
+
+ /**
+ * The log function
+ *
+ * @param {NerdamerSymbolType | VectorType | MatrixType} symbol
+ * @param {NerdamerSymbolType | VectorType | MatrixType} [base]
+ * @returns {NerdamerSymbolType}
+ */
+ function log(symbol, base = undefined) {
+ // Narrow types for internal use
+ const sym = /** @type {NerdamerSymbolType} */ (symbol);
+ const baseSymbol = base === undefined ? undefined : /** @type {NerdamerSymbolType} */ (base);
+
+ if (sym.equals(1)) {
+ return new NerdamerSymbol(0);
+ }
+
+ /** @type {NerdamerSymbolType | undefined} */
+ let retval;
+
+ if (sym.fname === SQRT && sym.multiplier.equals(1)) {
+ retval = /** @type {NerdamerSymbolType} */ (_.divide(log(sym.args[0]), new NerdamerSymbol(2)));
+
+ if (sym.power.sign() < 0) {
+ retval.negate();
+ }
+
+ // Exit early
+ return retval;
+ }
+
+ // Log(0) is undefined so complain
+ if (sym.equals(0)) {
+ throw new UndefinedError(`${Settings.LOG}(0) is undefined!`);
+ }
+
+ // Deal with imaginary values
+ if (sym.isImaginary()) {
+ return complex.evaluate(sym, Settings.LOG);
+ }
+
+ if (sym.isConstant() && typeof baseSymbol !== 'undefined' && baseSymbol.isConstant()) {
+ const logSym = Math.log(/** @type {number} */ (/** @type {unknown} */ (sym)));
+ const logBase = Math.log(/** @type {number} */ (/** @type {unknown} */ (baseSymbol)));
+ retval = new NerdamerSymbol(logSym / logBase);
+ } else if (
+ (sym.group === EX && /** @type {NerdamerSymbolType} */ (sym.power).multiplier.lessThan(0)) ||
+ sym.power.toString() === '-1'
+ ) {
+ sym.power.negate();
+ // Move the negative outside but keep the positive inside :)
+ retval = log(sym).negate();
+ } else if (sym.value === 'e' && sym.multiplier.equals(1)) {
+ const p = sym.power;
+ retval = isSymbol(p) ? /** @type {NerdamerSymbolType} */ (p) : new NerdamerSymbol(p);
+ } else if (sym.group === FN && sym.fname === 'exp') {
+ const s = sym.args[0];
+ if (sym.multiplier.equals(1)) {
+ retval = /** @type {NerdamerSymbolType} */ (_.multiply(s, new NerdamerSymbol(sym.power)));
+ } else {
+ retval = _.symfunction(Settings.LOG, [sym]);
+ }
+ } else if (Settings.PARSE2NUMBER && isNumericSymbol(sym)) {
+ // Parse for safety.
+ const numSym = /** @type {NerdamerSymbolType} */ (
+ _.parse(/** @type {string | number} */ (/** @type {unknown} */ (sym)))
+ );
+
+ let imgPart;
+ if (numSym.multiplier.lessThan(0)) {
+ numSym.negate();
+ imgPart = _.multiply(new NerdamerSymbol(Math.PI), new NerdamerSymbol('i'));
+ }
+
+ retval = new NerdamerSymbol(Math.log(/** @type {number} */ (numSym.multiplier.toDecimal())));
+
+ if (imgPart) {
+ retval = /** @type {NerdamerSymbolType} */ (_.add(retval, imgPart));
+ }
+ } else {
+ let s;
+ if (!sym.power.equals(1) && !sym.contains('e') && sym.multiplier.isOne()) {
+ s = sym.group === EX ? sym.power : new NerdamerSymbol(sym.power);
+ sym.toLinear();
+ }
+ // Log(a,a) = 1 since the base is allowed to be changed.
+ // This was pointed out by Happypig375 in issue #280
+ const args = typeof baseSymbol === 'undefined' ? [sym] : [sym, baseSymbol];
+ if (args.length > 1 && allSame(/** @type {NerdamerSymbolType[]} */ (args))) {
+ retval = new NerdamerSymbol(1);
+ } else {
+ retval = _.symfunction(Settings.LOG, args);
+ }
+
+ if (s) {
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.multiply(/** @type {NerdamerSymbolType} */ (s), retval)
+ );
+ }
+ }
+
+ return retval;
+ }
+
+ /**
+ * Round a number up to s decimal places
+ *
+ * @param {NerdamerSymbolType} x
+ * @param {NerdamerSymbolType | number} [s] - The number of decimal places
+ * @returns {NerdamerSymbolType}
+ */
+ function round(x, s) {
+ // Convert number to NerdamerSymbol if needed
+ if (typeof s === 'number') {
+ s = new NerdamerSymbol(s);
+ }
+ const sIsConstant = (s && s.isConstant()) || typeof s === 'undefined';
+ if (x.isConstant() && sIsConstant) {
+ let v;
+ let e;
+ let exponent;
+ /** @type {NerdamerSymbolType | string} */
+ v = x;
+ // Round the coefficient of then number but not the actual decimal value
+ // we know this because a negative number was passed
+ if (s && s.lessThan(0)) {
+ s = abs(s);
+ // Convert the number to exponential form
+ e = Number(x).toExponential().toString().split('e');
+ // Point v to the coefficient of then number
+ v = e[0];
+ // NerdamerSet the expontent
+ exponent = e[1];
+ }
+ // Round the number to the requested precision
+ const retval = new NerdamerSymbol(nround(Number(v), Number(s) || 0));
+ // If there's a exponent then put it back
+ return /** @type {NerdamerSymbolType} */ (
+ _.multiply(retval, _.pow(new NerdamerSymbol(10), new NerdamerSymbol(exponent || 0)))
+ );
+ }
+
+ const roundArgs = [x];
+ if (typeof s !== 'undefined') {
+ roundArgs.push(s);
+ }
+ return _.symfunction('round', roundArgs);
+ }
+
+ /**
+ * Gets the quadrant of the trig function
+ *
+ * @param {FracType} m
+ * @returns {number}
+ */
+ function getQuadrant(m) {
+ let v = Number(m) % 2;
+ let quadrant;
+
+ if (v < 0) {
+ v = 2 + v;
+ } // Put it in terms of pi
+
+ if (v >= 0 && v <= 0.5) {
+ quadrant = 1;
+ } else if (v > 0.5 && v <= 1) {
+ quadrant = 2;
+ } else if (v > 1 && v <= 1.5) {
+ quadrant = 3;
+ } else {
+ quadrant = 4;
+ }
+ return quadrant;
+ }
+
+ /*
+ * Serves as a bridge between numbers and bigNumbers
+ * @param {FracType|number} n
+ * @returns {NerdamerSymbolType}
+ */
+ function bigConvert(n) {
+ if (!isFinite(n)) {
+ const signVal = Math.sign(n);
+ const r = new NerdamerSymbol(String(Math.abs(n)));
+ r.multiplier = r.multiplier.multiply(new Frac(signVal));
+ return r;
+ }
+ if (isSymbol(n)) {
+ return n;
+ }
+ if (typeof n === 'number') {
+ try {
+ n = Frac.simple(n);
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ n = new Frac(n);
+ }
+ }
+
+ const symbol = new NerdamerSymbol(0);
+ symbol.multiplier = n;
+ return symbol;
+ }
+ function clean(symbol) {
+ // Handle functions with numeric values
+ // handle denominator within denominator
+ // handle trig simplifications
+ const g = symbol.group;
+ let retval;
+ // Now let's get to work
+ if (g === CP) {
+ const num = symbol.getNum();
+ const den = symbol.getDenom() || new NerdamerSymbol(1);
+ const p = Number(symbol.power);
+ /** @type {NerdamerSymbolType} */
+ let factor = new NerdamerSymbol(1);
+ if (Math.abs(p) === 1) {
+ den.each(x => {
+ if (x.group === CB) {
+ factor = /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (_.multiply(factor, clean(x.getDenom())))
+ );
+ } else if (x.power.lessThan(0)) {
+ factor = /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (_.multiply(factor, clean(x.clone().toUnitMultiplier())))
+ );
+ }
+ });
+
+ /** @type {NerdamerSymbolType} */
+ let newDen = new NerdamerSymbol(0);
+ // Now divide out the factor and add to new den
+ den.each(x => {
+ newDen = /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (_.add(_.divide(x, factor.clone()), newDen))
+ );
+ });
+
+ factor.invert(); // Invert so it can be added to the top
+ /** @type {NerdamerSymbolType | undefined} */
+ let newNum;
+ if (num.isComposite()) {
+ newNum = new NerdamerSymbol(0);
+ num.each(x => {
+ newNum = /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (_.add(_.multiply(clean(x), factor.clone()), newNum))
+ );
+ });
+ } else {
+ newNum = /** @type {NerdamerSymbolType} */ (_.multiply(factor, num));
+ }
+
+ retval = /** @type {NerdamerSymbolType} */ (_.divide(newNum, newDen));
+ }
+ } else if (g === CB) {
+ retval = new NerdamerSymbol(1);
+ symbol.each(x => {
+ retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, _.clean(x)));
+ });
+ } else if (g === FN) {
+ if (symbol.args.length === 1 && symbol.args[0].isConstant()) {
+ retval = block('PARSE2NUMBER', () => _.parse(symbol), true);
+ }
+ }
+
+ retval ||= symbol;
+
+ return retval;
+ }
+
+ /**
+ * A wrapper for the expand function
+ *
+ * @param {NerdamerSymbolType} symbol
+ * @param {ExpandOptions} [opt]
+ * @returns {NerdamerSymbolType}
+ */
+ function expandall(symbol, opt) {
+ opt ||= {
+ expand_denominator: true,
+ expand_functions: true,
+ };
+ return /** @type {NerdamerSymbolType} */ (expand(symbol, opt));
+ }
+ /**
+ * Expands a symbol
+ *
+ * @param {NerdamerSymbolType | VectorType | MatrixType} symbol
+ * @param {ExpandOptions} [opt]
+ * @returns {NerdamerSymbolType | VectorType | MatrixType}
+ */
+ // Old expand
+ function expand(symbol, opt) {
+ if (Array.isArray(symbol)) {
+ return /** @type {NerdamerSymbolType | VectorType | MatrixType} */ (
+ /** @type {unknown} */ (symbol.map(x => expand(x, opt)))
+ );
+ }
+ // Vector/Matrix have their own expand method - delegate to it
+ if ('expand' in symbol && typeof symbol.expand === 'function') {
+ return /** @type {VectorType | MatrixType} */ (symbol).expand(opt);
+ }
+ // From this point on, symbol is definitely a NerdamerSymbol
+ /** @type {NerdamerSymbolType} */
+ const sym = /** @type {NerdamerSymbolType} */ (symbol);
+ opt ||= {};
+ // Deal with parenthesis
+ if (sym.group === FN && sym.fname === '') {
+ const f = expand(sym.args[0], opt);
+ const x = expand(_.pow(f, _.parse(sym.power)), opt);
+ return /** @type {NerdamerSymbolType} */ (
+ _.multiply(_.parse(sym.multiplier), x)
+ ).distributeMultiplier();
+ }
+ // We cannot expand these groups so no need to waste time. Just return and be done.
+ if ([N, P, S].indexOf(sym.group) !== -1) {
+ return sym; // Nothing to do
+ }
+
+ const original = sym.clone();
+
+ // NerdamerSet up a try-catch block. If anything goes wrong then we simply return the original symbol
+ try {
+ // Store the power and multiplier
+ const m = sym.multiplier.toString();
+ const p = Number(sym.power);
+ let retval = sym;
+
+ // Handle (a+b)^2 | (x+x^2)^2
+ if (sym.isComposite() && isInt(sym.power) && p > 0) {
+ const n = p - 1;
+ // Strip the expression of it's multiplier and power. We'll call it f. The power will be p and the multiplier m.
+ /** @type {NerdamerSymbolType} */
+ let f = new NerdamerSymbol(0);
+
+ sym.each((/** @type {NerdamerSymbolType} */ x) => {
+ f = /** @type {NerdamerSymbolType} */ (_.add(f, expand(_.parse(x), opt)));
+ });
+
+ /** @type {NerdamerSymbolType} */
+ let expanded = _.parse(f);
+
+ for (let i = 0; i < n; i++) {
+ expanded = mix(expanded, f, opt);
+ }
+
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.multiply(_.parse(m), expanded)
+ ).distributeMultiplier();
+ } else if (sym.group === FN && opt.expand_functions === true) {
+ const args = [];
+ // Expand function the arguments
+ sym.args.forEach(x => {
+ args.push(expand(x, opt));
+ });
+ // Put back the power and multiplier
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.pow(_.symfunction(sym.fname, args), _.parse(sym.power))
+ );
+ retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, _.parse(sym.multiplier)));
+ } else if (sym.isComposite() && isInt(sym.power) && p < 0 && opt.expand_denominator === true) {
+ // Invert it. Expand it and then re-invert it.
+ const inverted = sym.invert();
+ retval = /** @type {NerdamerSymbolType} */ (expand(inverted, opt));
+ retval.invert();
+ } else if (sym.group === CB) {
+ const rank = function (s) {
+ switch (s.group) {
+ case CP:
+ return 0;
+ case PL:
+ return 1;
+ case CB:
+ return 2;
+ case FN:
+ return 3;
+ default:
+ return 4;
+ }
+ };
+ // Consider (a+b)(c+d). The result will be (a*c+a*d)+(b*c+b*d).
+ // We start by moving collecting the symbols. We want others>FN>CB>PL>CP
+ const symbols = /** @type {NerdamerSymbolType[]} */ (sym.collectSymbols())
+ .sort((a, b) => rank(b) - rank(a))
+ // Distribute the power to each symbol and expand
+ .map(s => {
+ const x = _.pow(s, _.parse(String(p)));
+ const e = /** @type {NerdamerSymbolType} */ (expand(x, opt));
+ return e;
+ });
+
+ /** @type {NerdamerSymbolType} */
+ let f = /** @type {NerdamerSymbolType} */ (symbols.pop());
+
+ // If the first symbols isn't a composite then we're done
+ if (f.isComposite() && f.isLinear()) {
+ symbols.forEach(s => {
+ f = /** @type {NerdamerSymbolType} */ (mix(f, s, opt));
+ });
+
+ // If f is of group PL or CP then we can expand some more
+ if (f.isComposite()) {
+ if (Number(f.power) > 1) {
+ f = /** @type {NerdamerSymbolType} */ (expand(_.pow(f, _.parse(f.power)), opt));
+ }
+ // Put back the multiplier
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.multiply(_.parse(m), f)
+ ).distributeMultiplier();
+ } else {
+ // Everything is expanded at this point so if it's still a CB
+ // then just return the symbol
+ retval = f;
+ }
+ } else {
+ // Just multiply back in the expanded form of each
+ retval = f;
+ symbols.forEach(s => {
+ retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, s));
+ });
+ // Put back the multiplier
+ retval = /** @type {NerdamerSymbolType} */ (
+ _.multiply(retval, _.parse(m))
+ ).distributeMultiplier();
+ }
+
+ // TODO: This exists solely as a quick fix for sqrt(11)*sqrt(33) not simplifying.
+ if (retval.group === CB) {
+ retval = _.parse(retval);
+ }
+ } else {
+ // Otherwise just return the expression
+ retval = sym;
+ }
+ // Final cleanup and return
+ return retval;
+ } catch (e) {
+ if (e.message === 'timeout') {
+ throw e;
+ }
+ return original;
+ }
+ }
+
+ /**
+ * Returns an identity matrix of nxn
+ *
+ * @param {number} n
+ * @returns {MatrixType}
+ */
+ function imatrix(n) {
+ return Matrix.identity(n);
+ }
+
+ /**
+ * Retrieves and item from a vector
+ *
+ * @param {VectorType} vec
+ * @param {NerdamerSymbolType} index
+ * @returns {VectorType | NerdamerSymbolType}
+ */
+ function vecget(vec, index) {
+ if (index.isConstant() && isInt(index)) {
+ return /** @type {NerdamerSymbolType | VectorType} */ (vec.elements[Number(index)]);
+ }
+ return _.symfunction('vecget', [/** @type {VectorType} */ (vec), index]);
+ }
+
+ /**
+ * Removes duplicates from a vector
+ *
+ * @param {VectorType} vec
+ * @param {number} tolerance
+ * @returns {VectorType}
+ */
+ function vectrim(vec, tolerance) {
+ tolerance = typeof tolerance === 'undefined' ? 1e-14 : tolerance;
+
+ vec = vec.clone();
+
+ tolerance = Number(tolerance);
+ // Place algebraic solutions first
+ vec.elements.sort(
+ (a, b) => /** @type {NerdamerSymbolType} */ (b).group - /** @type {NerdamerSymbolType} */ (a).group
+ );
+ // Depending on the start point we may have duplicates so we need to clean those up a bit.
+ // start by creating an object with the solution and the numeric value. This way we don't destroy algebraic values
+ vec.elements = removeDuplicates(vec.elements, (a, b) => {
+ const diff = Number(/** @type {NerdamerSymbolType} */ (_.subtract(evaluate(a), evaluate(b))).abs());
+ return diff <= tolerance;
+ });
+
+ return vec;
+ }
+
+ /**
+ * NerdamerSet a value for a vector at a given index
+ *
+ * @param {VectorType} vec
+ * @param {NerdamerSymbolType} index
+ * @param {NerdamerSymbolType} value
+ * @returns {VectorType | NerdamerSymbolType}
+ */
+ function vecset(vec, index, value) {
+ if (!index.isConstant) {
+ return _.symfunction('vecset', [
+ /** @type {VectorType} */ (/** @type {unknown} */ (vec)),
+ index,
+ value,
+ ]);
+ }
+ vec.elements[Number(index)] = value;
+ return vec;
+ }
+
+ /**
+ * @param {MatrixType} mat
+ * @param {NerdamerSymbolType} i
+ * @param {NerdamerSymbolType} j
+ * @returns {NerdamerSymbolType}
+ */
+ function matget(mat, i, j) {
+ if (i.isConstant() && j.isConstant()) {
+ return /** @type {NerdamerSymbolType} */ (mat.elements[Number(i)][Number(j)]);
+ }
+ return _.symfunction('matget', [/** @type {MatrixType} */ (mat), i, j]);
+ }
+
+ /**
+ * @param {MatrixType} mat
+ * @param {NerdamerSymbolType} i
+ * @returns {VectorType | NerdamerSymbolType}
+ */
+ function matgetrow(mat, i) {
+ if (i.isConstant()) {
+ return Vector.fromArray(/** @type {NerdamerSymbolType[]} */ (mat.elements[Number(i)]));
+ }
+ return _.symfunction('matgetrow', [/** @type {MatrixType} */ (mat), i]);
+ }
+
+ /**
+ * Sets a row in a matrix
+ *
+ * @param {MatrixType} mat
+ * @param {NerdamerSymbolType} i
+ * @param {VectorType} x
+ * @returns {MatrixType | NerdamerSymbolType}
+ */
+ function matsetrow(mat, i, x) {
+ // Handle symbolics
+ if (!i.isConstant()) {
+ return _.symfunction('matsetrow', [/** @type {MatrixType} */ (mat), i, /** @type {VectorType} */ (x)]);
+ }
+ if (mat.elements[Number(i)].length !== x.elements.length) {
+ throw new DimensionError('Matrix row must match row dimensions!');
+ }
+ const M = /** @type {MatrixType} */ (mat.clone());
+ M.elements[Number(i)] = x.clone().elements;
+ return M;
+ }
+
+ /**
+ * Gets a column from a matrix
+ *
+ * @param {MatrixType} mat
+ * @param {NerdamerSymbolType} colIndex
+ * @returns {MatrixType | NerdamerSymbolType}
+ */
+ function matgetcol(mat, colIndex) {
+ // Handle symbolics
+ if (!colIndex.isConstant()) {
+ return _.symfunction('matgetcol', [/** @type {MatrixType} */ (mat), colIndex]);
+ }
+ const colIndexNum = Number(colIndex);
+ /** @type {MatrixType} */
+ const M = Matrix.fromArray([]);
+ mat.each((x, i, j) => {
+ if (j === colIndexNum) {
+ M.elements.push([x.clone()]);
+ }
+ });
+ return M;
+ }
+
+ /**
+ * Sets a column in a matrix
+ *
+ * @param {MatrixType} mat
+ * @param {NerdamerSymbolType} j
+ * @param {MatrixType} col
+ * @returns {MatrixType | NerdamerSymbolType}
+ */
+ function matsetcol(mat, j, col) {
+ // Handle symbolics
+ if (!j.isConstant()) {
+ return _.symfunction('matsetcol', [
+ /** @type {MatrixType} */ (mat),
+ j,
+ /** @type {MatrixType} */ (col),
+ ]);
+ }
+ const jNum = Number(j);
+ if (mat.rows() !== col.elements.length) {
+ throw new DimensionError('Matrix column length must match number of rows!');
+ }
+ col.each(
+ /** @type {(element: NerdamerSymbolType, row: number, col: number) => void} */ (
+ /** @type {unknown} */ (
+ (/** @type {NerdamerSymbolType | VectorType | MatrixType} */ x, i) => {
+ mat.set(i - 1, jNum, /** @type {VectorType} */ (x).elements[0].clone());
+ }
+ )
+ )
+ );
+ return mat;
+ }
+
+ function matset(mat, i, j, value) {
+ mat.elements[i][j] = value;
+ return mat;
+ }
+
+ // The constructor for vectors
+ function vector(...args) {
+ return new Vector(args);
+ }
+
+ // The constructor for matrices
+ function matrix(...args) {
+ return Matrix.fromArray(args);
+ }
+
+ // The constructor for sets
+ // eslint-disable-next-line no-shadow -- intentionally shadows outer set for parser function
+ function set(...args) {
+ return NerdamerSet.fromArray(args);
+ }
+
+ function determinant(symbol) {
+ if (isMatrix(symbol)) {
+ return symbol.determinant();
+ }
+ return symbol;
+ }
+
+ function size(symbol) {
+ let retval;
+ if (isMatrix(symbol)) {
+ retval = [new NerdamerSymbol(symbol.cols()), new NerdamerSymbol(symbol.rows())];
+ } else if (isVector(symbol) || isSet(symbol)) {
+ retval = new NerdamerSymbol(symbol.elements.length);
+ } else {
+ err('size expects a matrix or a vector');
+ }
+ return retval;
+ }
+
+ function dot(vec1, vec2) {
+ if (isMatrix(vec1)) {
+ vec1 = new Vector(vec1);
+ }
+ if (isMatrix(vec2)) {
+ vec2 = new Vector(vec2);
+ }
+
+ if (isVector(vec1) && isVector(vec2)) {
+ return vec1.dot(vec2);
+ }
+
+ return _.multiply(vec1.clone(), vec2.clone());
+ // Err('function dot expects 2 vectors');
+ }
+
+ function cross(vec1, vec2) {
+ if (isMatrix(vec1)) {
+ vec1 = new Vector(vec1);
+ }
+ if (isMatrix(vec2)) {
+ vec2 = new Vector(vec2);
+ }
+
+ if (isVector(vec1) && isVector(vec2)) {
+ return vec1.cross(vec2);
+ }
+
+ return _.multiply(vec1.clone(), vec2.clone());
+ // Err('function cross expects 2 vectors');
+ }
+
+ function transpose(mat) {
+ if (isMatrix(mat)) {
+ return mat.transpose();
+ }
+ return err('function transpose expects a matrix');
+ }
+
+ function invert(mat) {
+ if (isMatrix(mat)) {
+ return mat.invert();
+ }
+ return err('invert expects a matrix');
+ }
+
+ // Basic set functions
+ function union(set1, set2) {
+ return set1.union(set2);
+ }
+
+ function intersection(set1, set2) {
+ return set1.intersection(set2);
+ }
+
+ function contains(set1, e) {
+ return set1.contains(e);
+ }
+
+ function difference(set1, set2) {
+ return set1.difference(set2);
+ }
+
+ function intersects(set1, set2) {
+ return new NerdamerSymbol(Number(set1.intersects(set2)));
+ }
+
+ function isSubset(set1, set2) {
+ return new NerdamerSymbol(Number(set1.isSubset(set2)));
+ }
+ function primes(a, b) {
+ b ??= a;
+ const primeList = PRIMES.slice(a, b).map(p => new NerdamerSymbol(p));
+ if (primeList.length === 1) {
+ return primeList[0];
+ }
+ if (primeList.length === 0) {
+ return new NerdamerSymbol(0);
+ }
+ return new Vector(primeList);
+ }
+
+ function print(...args) {
+ args.forEach(x => {
+ // eslint-disable-next-line no-console
+ console.log(x.toString());
+ });
+ }
+
+ function testSQRT(symbol) {
+ // Wrap the symbol in sqrt. This eliminates one more check down the line.
+ if (!isSymbol(symbol.power) && symbol.power.absEquals(0.5)) {
+ const signVal = symbol.power.sign();
+ // Don't devide the power directly. Notice the use of toString. This makes it possible
+ // to use a bigNumber library in the future
+ const retval = sqrt(symbol.group === P ? new NerdamerSymbol(symbol.value) : symbol.toLinear());
+ // Place back the sign of the power
+ if (signVal < 0) {
+ retval.invert();
+ }
+ return retval;
+ }
+ return symbol;
+ }
+
+ // Try to reduce a symbol by pulling its power
+ function testPow(symbol) {
+ if (symbol.group === P) {
+ const v = symbol.value;
+
+ const fct = primeFactors(v)[0];
+
+ // Safety
+ if (!fct) {
+ warn('Unable to compute prime factors. This should not happen. Please review and report.');
+ return symbol;
+ }
+
+ const n = new Frac(Math.log(v) / Math.log(fct));
+ const p = n.multiply(symbol.power);
+
+ // We don't want a more complex number than before
+ if (p.den > symbol.power.den) {
+ return symbol;
+ }
+
+ if (isInt(p)) {
+ symbol = new NerdamerSymbol(fct ** Number(p));
+ } else {
+ symbol = /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (new NerdamerSymbol(fct))
+ ).setPower(p);
+ }
+ }
+
+ return symbol;
+ }
+
+ // Link the functions to the parse so they're available outside of the library.
+ // This is strictly for convenience and may be deprecated.
+ this.expand = expand;
+ this.round = round;
+ this.clean = /** @type {ParserType['clean']} */ (clean);
+ this.sqrt = sqrt;
+ this.cbrt = cbrt;
+ this.abs = /** @type {ParserType['abs']} */ (abs);
+ this.log = log;
+ this.rationalize = /** @type {ParserType['rationalize']} */ (rationalize);
+ this.nthroot = /** @type {ParserType['nthroot']} */ (nthroot);
+ this.arg = /** @type {ParserType['arg']} */ (arg);
+ this.conjugate = /** @type {ParserType['conjugate']} */ (conjugate);
+ this.imagpart = /** @type {ParserType['imagpart']} */ (imagpart);
+ this.realpart = /** @type {ParserType['realpart']} */ (realpart);
+
+ // TODO:
+ // Utilize the function below instead of the linked function
+ this.getFunction = function getFunction(name) {
+ return functions[name][0];
+ };
+
+ // Parser.methods ===============================================================
+ this.addPreprocessor = function addPreprocessor(name, action, order, shiftCells) {
+ const { names } = preprocessors;
+ const { actions } = preprocessors;
+ if (typeof action !== 'function') // The person probably forgot to specify a name
+ {
+ throw new Error('Incorrect parameters. Function expected!');
+ }
+ if (!order) {
+ names.push(name);
+ actions.push(action);
+ } else if (shiftCells) {
+ names.splice(order, 0, name);
+ actions.splice(order, 0, action);
+ } else {
+ names[order] = name;
+ actions[order] = action;
+ }
+ };
+
+ /** @returns {Record<string, { order: number; action: Function }>} */
+ this.getPreprocessors = function getPreprocessors() {
+ /** @type {Record<string, { order: number; action: Function }>} */
+ const result = {};
+ for (let i = 0, l = preprocessors.names.length; i < l; i++) {
+ const name = preprocessors.names[i];
+ result[name] = {
+ order: i,
+ action: preprocessors.actions[i],
+ };
+ }
+ return result;
+ };
+
+ this.removePreprocessor = function removePreprocessor(name, shiftCells) {
+ const i = preprocessors.names.indexOf(name);
+ if (shiftCells) {
+ remove(preprocessors.names, i);
+ remove(preprocessors.actions, i);
+ } else {
+ preprocessors.names[i] = undefined;
+ preprocessors.actions[i] = undefined;
+ }
+ };
+
+ // The loader for functions which are not part of Math2
+ /** @this {{ params: string[]; body: string }} */
+ this.mappedFunction = function mappedFunction(...args) {
+ /** @type {Record<string, string>} */
+ const subs = {};
+ const { params } = this;
+
+ for (let i = 0; i < params.length; i++) {
+ subs[params[i]] = String(args[i]);
+ }
+
+ return _.parse(this.body, subs);
+ };
+ /**
+ * Adds two symbols
+ *
+ * @param {ArithmeticOperand} a
+ * @param {ArithmeticOperand} b
+ * @returns {ArithmeticOperand}
+ */
+ this.add = function add(a, b) {
+ let aIsSymbol = isSymbol(a);
+ let bIsSymbol = isSymbol(b);
+ // We're dealing with two symbols
+ if (aIsSymbol && bIsSymbol) {
+ // Cast to NerdamerSymbol since we've verified with isSymbol
+ /** @type {NerdamerSymbolType} */
+ let symA = /** @type {NerdamerSymbolType} */ (a);
+ /** @type {NerdamerSymbolType} */
+ let symB = /** @type {NerdamerSymbolType} */ (b);
+ // Forward the adding of symbols with units to the Unit module
+ if (symA.unit || symB.unit) {
+ return _.Unit.add(symA, symB);
+ }
+ // Handle Infinity
+ // https://www.encyclopediaofmath.org/index.php/Infinity
+ if (symA.isInfinity || symB.isInfinity) {
+ const aneg = symA.multiplier.lessThan(0);
+ const bneg = symB.multiplier.lessThan(0);
+
+ if (symA.isInfinity && symB.isInfinity && aneg !== bneg) {
+ throw new UndefinedError(`(${symA})+(${symB}) is not defined!`);
+ }
+
+ const inf = NerdamerSymbol.infinity();
+ if (bneg) {
+ inf.negate();
+ }
+ return inf;
+ }
+
+ if (symA.isComposite() && symA.isLinear() && symB.isComposite() && symB.isLinear()) {
+ symA.distributeMultiplier();
+ symB.distributeMultiplier();
+ // Fix for issue #606
+ if (symB.length > symA.length && symA.group === symB.group) {
+ [symA, symB] = [symB, symA];
+ }
+ }
+
+ // No need to waste time on zeroes
+ if (symA.multiplier.equals(0)) {
+ return symB;
+ }
+ if (symB.multiplier.equals(0)) {
+ return symA;
+ }
+
+ if (symA.isConstant() && symB.isConstant() && Settings.PARSE2NUMBER) {
+ const result = new NerdamerSymbol(
+ symA.multiplier.add(symB.multiplier).toDecimal(Settings.PRECISION)
+ );
+ return result;
+ }
+
+ let g1 = symA.group;
+ let g2 = symB.group;
+ let ap = symA.power.toString();
+ let bp = symB.power.toString();
+
+ // Always keep the greater group on the left.
+ if (g1 < g2 || (g1 === g2 && Number(ap) > Number(bp) && Number(bp) > 0)) {
+ return this.add(symB, symA);
+ }
+
+ /* Note to self: Please don't forget about this dilemma ever again. In this model PL and CB goes crazy
+ * because it doesn't know which one to prioritize. */
+ // correction to PL dilemma
+ if (g1 === CB && g2 === PL && symA.value === symB.value) {
+ // Swap
+ const t = symA;
+ symA = symB;
+ symB = t;
+ g1 = symA.group;
+ g2 = symB.group;
+ ap = symA.power.toString();
+ bp = symB.power.toString();
+ }
+
+ const powEQ = ap === bp;
+ let v1 = symA.value;
+ let v2 = symB.value;
+ const aIsComposite = symA.isComposite();
+ const bIsComposite = symB.isComposite();
+ let h1;
+ let h2;
+ let result;
+
+ if (aIsComposite) {
+ h1 = text(symA, 'hash');
+ }
+ if (bIsComposite) {
+ h2 = text(symB, 'hash');
+ }
+
+ if (g1 === CP && g2 === CP && symB.isLinear() && !symA.isLinear() && h1 !== h2) {
+ return this.add(symB, symA);
+ }
+
+ // PL & PL should compare hashes and not values e.g. compare x+x^2 with x+x^3 and not x with x
+ if (g1 === PL && g2 === PL) {
+ v1 = h1;
+ v2 = h2;
+ }
+
+ const PN = g1 === P && g2 === N;
+ const PNEQ = symA.value === symB.multiplier.toString();
+ const valEQ = v1 === v2 || (h1 === h2 && h1 !== undefined) || (PN && PNEQ);
+
+ // Equal values, equal powers
+ if (valEQ && powEQ && g1 === g2) {
+ // Make sure to convert N to something P can work with
+ if (PN) {
+ symB = symB.convert(P);
+ } // CL
+
+ // handle PL
+ if (g1 === PL && (g2 === S || g2 === P)) {
+ symA.distributeMultiplier();
+ result = symA.attach(symB);
+ } else {
+ result = symA; // CL
+ if (
+ symA.multiplier.isOne() &&
+ symB.multiplier.isOne() &&
+ g1 === CP &&
+ symA.isLinear() &&
+ symB.isLinear()
+ ) {
+ for (const s in symB.symbols) {
+ if (!Object.hasOwn(symB.symbols, s)) {
+ continue;
+ }
+ const x = symB.symbols[s];
+ result.attach(x);
+ }
+ } else {
+ result.multiplier = result.multiplier.add(symB.multiplier);
+ }
+ }
+ }
+ // Equal values uneven powers
+ else if (valEQ && g1 !== PL) {
+ // Break the tie for e.g. (x+1)+((x+1)^2+(x+1)^3)
+ if (g1 === CP && g2 === PL) {
+ symB.insert(symA, 'add');
+ result = symB;
+ } else {
+ result = NerdamerSymbol.shell(PL).attach([symA, symB]);
+ // Update the hash
+ result.value = g1 === PL ? h1 : v1;
+ }
+ } else if (aIsComposite && symA.isLinear()) {
+ let canIterate = g1 === g2;
+ const bothPL = g1 === PL && g2 === PL;
+
+ // We can only iterate group PL if they values match
+ if (bothPL) {
+ canIterate = symA.value === symB.value;
+ }
+ // Distribute the multiplier over the entire symbol
+ symA.distributeMultiplier();
+
+ if (symB.isComposite() && symB.isLinear() && canIterate) {
+ symB.distributeMultiplier();
+ // CL
+ for (const s in symB.symbols) {
+ if (!Object.hasOwn(symB.symbols, s)) {
+ continue;
+ }
+ const x = symB.symbols[s];
+ symA.attach(x);
+ }
+ result = symA;
+ }
+ // Handle cases like 2*(x+x^2)^2+2*(x+x^2)^3+4*(x+x^2)^2
+ else if ((bothPL && symA.value !== h2) || (g1 === PL && !valEQ)) {
+ result = NerdamerSymbol.shell(CP).attach([symA, symB]);
+ result.updateHash();
+ } else {
+ result = symA.attach(symB);
+ }
+ } else {
+ if (g1 === FN && symA.fname === SQRT && g2 !== EX && symB.power.equals(0.5)) {
+ const m = symB.multiplier.clone();
+ symB = sqrt(symB.toUnitMultiplier().toLinear());
+ symB.multiplier = m;
+ }
+ // Fix for issue #3 and #159
+ if (symA.length === 2 && symB.length === 2 && even(symA.power) && even(symB.power)) {
+ result = _.add(expand(symA), expand(symB));
+ } else {
+ result = NerdamerSymbol.shell(CP).attach([symA, symB]);
+ result.updateHash();
+ }
+ }
+
+ if (result.multiplier.equals(0)) {
+ result = new NerdamerSymbol(0);
+ }
+
+ // Make sure to remove unnecessary wraps
+ // At this point result is always a NerdamerSymbol
+ const symbolResult = /** @type {NerdamerSymbolType} */ (result);
+ if (symbolResult.length === 1) {
+ const m = symbolResult.multiplier;
+ const unwrapped = /** @type {NerdamerSymbolType} */ (firstObject(symbolResult.symbols));
+ unwrapped.multiplier = unwrapped.multiplier.multiply(m);
+ return unwrapped;
+ }
+
+ return result;
+ }
+ // Keep symbols to the right
+ if (bIsSymbol && !aIsSymbol) {
+ const tempOp = a;
+ a = b;
+ b = tempOp; // Swap
+ const tempBool = bIsSymbol;
+ bIsSymbol = aIsSymbol;
+ aIsSymbol = tempBool;
+ }
+
+ const bIsMatrix = isMatrix(b);
+
+ if (aIsSymbol && bIsMatrix) {
+ const M = new Matrix();
+ const bMatrix = /** @type {MatrixType} */ (b);
+ bMatrix.eachElement((e, i, j) => {
+ M.set(
+ i,
+ j,
+ /** @type {NerdamerSymbolType} */ (_.add(/** @type {NerdamerSymbolType} */ (a).clone(), e))
+ );
+ });
+
+ b = M;
+ } else if (isMatrix(a) && bIsMatrix) {
+ b = /** @type {MatrixType} */ (a).add(/** @type {MatrixType} */ (b));
+ } else if (aIsSymbol && isVector(b)) {
+ const bVec = /** @type {VectorType} */ (b);
+ bVec.each((el, i) => {
+ i--;
+ bVec.elements[i] = /** @type {NerdamerSymbolType} */ (
+ _.add(/** @type {NerdamerSymbolType} */ (a).clone(), bVec.elements[i])
+ );
+ });
+ } else if (isVector(a) && isVector(b)) {
+ const aVec = /** @type {VectorType} */ (a);
+ const bVec = /** @type {VectorType} */ (b);
+ bVec.each((el, i) => {
+ i--;
+ bVec.elements[i] = /** @type {NerdamerSymbolType} */ (_.add(aVec.elements[i], bVec.elements[i]));
+ });
+ } else if (isVector(a) && isMatrix(b)) {
+ // Try to convert a to a matrix
+ return /** @type {MatrixType} */ (_.add(/** @type {MatrixType} */ (b), /** @type {VectorType} */ (a)));
+ } else if (isMatrix(a) && isVector(b)) {
+ if (b.elements.length === a.rows()) {
+ const M = new Matrix();
+ const l = a.cols();
+ b.each((e, i) => {
+ const row = [];
+ if (isVector(e)) {
+ for (let j = 0; j < l; j++) {
+ row.push(_.add(a.elements[i - 1][j].clone(), e.elements[j].clone()));
+ }
+ } else {
+ for (let j = 0; j < l; j++) {
+ row.push(_.add(a.elements[i - 1][j].clone(), e.clone()));
+ }
+ }
+ M.elements.push(row);
+ });
+ return M;
+ }
+ err('Dimensions must match!');
+ }
+ return b;
+ };
+ /**
+ * Gets called when the parser finds the - operator. Not the prefix operator. See this.add
+ *
+ * @param {ArithmeticOperand} a
+ * @param {ArithmeticOperand} b
+ * @returns {ArithmeticOperand}
+ */
+ this.subtract = function subtract(a, b) {
+ const aIsSymbol = isSymbol(a);
+ const bIsSymbol = isSymbol(b);
+ let _t;
+
+ if (aIsSymbol && bIsSymbol) {
+ const aSymbol = /** @type {NerdamerSymbolType} */ (a);
+ const bSymbol = /** @type {NerdamerSymbolType} */ (b);
+ if (aSymbol.unit || bSymbol.unit) {
+ return _.Unit.subtract(aSymbol, bSymbol);
+ }
+ return this.add(aSymbol, bSymbol.negate());
+ }
+ if (bIsSymbol && isVector(a)) {
+ b = /** @type {VectorType} */ (
+ a.map(
+ x =>
+ /** @type {NerdamerSymbolType} */ (
+ _.subtract(x, /** @type {NerdamerSymbolType} */ (b).clone())
+ )
+ )
+ );
+ } else if (aIsSymbol && isVector(b)) {
+ b = /** @type {VectorType} */ (
+ b.map(
+ x =>
+ /** @type {NerdamerSymbolType} */ (
+ _.subtract(/** @type {NerdamerSymbolType} */ (a).clone(), x)
+ )
+ )
+ );
+ } else if ((isVector(a) && isVector(b)) || (isCollection(a) && isCollection(b))) {
+ if (a.dimensions() === b.dimensions()) {
+ // Both a and b are the same type (either Vector or Collection)
+ b = /** @type {VectorType} */ (
+ /** @type {VectorType | CollectionType} */ (a).subtract(
+ /** @type {VectorType & CollectionType} */ (b)
+ )
+ );
+ } else {
+ _.error('Unable to subtract vectors/collections. Dimensions do not match.');
+ }
+ } else if (isMatrix(a) && isVector(b)) {
+ if (b.elements.length === a.rows()) {
+ const M = new Matrix();
+ const l = a.cols();
+ b.each((e, i) => {
+ const row = [];
+ for (let j = 0; j < l; j++) {
+ row.push(_.subtract(a.elements[i - 1][j].clone(), e.clone()));
+ }
+ M.elements.push(row);
+ });
+ return M;
+ }
+ err('Dimensions must match!');
+ } else if (isVector(a) && isMatrix(b)) {
+ const M = b.clone().negate();
+ return /** @type {MatrixType} */ (_.add(/** @type {MatrixType} */ (M), a));
+ } else if (isMatrix(a) && isMatrix(b)) {
+ b = a.subtract(b);
+ } else if (isMatrix(a) && bIsSymbol) {
+ const M = new Matrix();
+ a.each((x, i, j) => {
+ M.set(i, j, /** @type {NerdamerSymbolType} */ (_.subtract(x, b.clone())));
+ });
+ b = M;
+ } else if (aIsSymbol && isMatrix(b)) {
+ const M = new Matrix();
+ b.each((x, i, j) => {
+ M.set(i, j, /** @type {NerdamerSymbolType} */ (_.subtract(a.clone(), x)));
+ });
+ b = M;
+ }
+ return b;
+ };
+ /**
+ * Gets called when the parser finds the * operator. See this.add
+ *
+ * @param {ArithmeticOperand} a
+ * @param {ArithmeticOperand} b
+ * @returns {ArithmeticOperand}
+ */
+ this.multiply = function multiply(a, b) {
+ let aIsSymbol = isSymbol(a);
+ let bIsSymbol = isSymbol(b);
+ // We're dealing with function assignment here
+ if (aIsSymbol && b instanceof Collection) {
+ /** @type {CollectionType} */ (b).elements.push(/** @type {NerdamerSymbolType} */ (a));
+ return /** @type {ArithmeticOperand} */ (/** @type {unknown} */ (b));
+ }
+ if (aIsSymbol && bIsSymbol) {
+ // Cast to NerdamerSymbol since we've verified with isSymbol
+ /** @type {NerdamerSymbolType} */
+ let symA = /** @type {NerdamerSymbolType} */ (a);
+ /** @type {NerdamerSymbolType} */
+ let symB = /** @type {NerdamerSymbolType} */ (b);
+ // If it has a unit then add it and return it right away.
+ if (symB.isUnit) {
+ const result = symA.clone();
+ symA.unit = symB;
+ return result;
+ }
+
+ // If it has units then just forward that problem to the unit module
+ if (symA.unit || symB.unit) {
+ return _.Unit.multiply(symA, symB);
+ }
+
+ // Handle Infinty
+ if (symA.isInfinity || symB.isInfinity) {
+ if (symA.equals(0) || symB.equals(0)) {
+ throw new UndefinedError(`${symA}*${symB} is undefined!`);
+ }
+ // X/infinity
+ if (symB.power.lessThan(0)) {
+ if (!symA.isInfinity) {
+ return new NerdamerSymbol(0);
+ }
+ throw new UndefinedError('Infinity/Infinity is not defined!');
+ }
+
+ const signVal = symA.multiplier.multiply(symB.multiplier).sign();
+ const inf = NerdamerSymbol.infinity();
+ if (symA.isConstant() || symB.isConstant() || (symA.isInfinity && symB.isInfinity)) {
+ if (signVal < 0) {
+ inf.negate();
+ }
+
+ return inf;
+ }
+ }
+
+ // The quickies
+ if (symA.multiplier.equals(0) || symB.multiplier.equals(0)) {
+ return new NerdamerSymbol(0);
+ }
+
+ if (symA.isOne()) {
+ return symB.clone();
+ }
+ if (symB.isOne()) {
+ return symA.clone();
+ }
+
+ // Now we know that neither is 0
+ if (symA.isConstant() && symB.isConstant() && Settings.PARSE2NUMBER) {
+ let retval;
+
+ // Check if either fraction has magnitude outside the precision range.
+ // If so, toDecimal() would lose significant digits, so we must use
+ // exact fraction arithmetic instead.
+ //
+ // The magnitude of a fraction num/den is approximately:
+ // log10(num) - log10(den) ≈ numDigits - denDigits
+ //
+ // With PRECISION decimal places, we can only represent numbers in
+ // the range [10^(-PRECISION), 10^(+PRECISION)] accurately.
+ // We use a buffer of 5 digits to ensure we have enough significant
+ // digits for accurate multiplication.
+ const aNumDigits = symA.multiplier.num.abs().toString().length;
+ const aDenDigits = symA.multiplier.den.toString().length;
+ const aMagnitude = aNumDigits - aDenDigits;
+
+ const bNumDigits = symB.multiplier.num.abs().toString().length;
+ const bDenDigits = symB.multiplier.den.toString().length;
+ const bMagnitude = bNumDigits - bDenDigits;
+
+ const magnitudeLimit = Settings.PRECISION - 5; // Need at least 5 significant digits
+ const needsExactArithmetic =
+ aMagnitude < -magnitudeLimit ||
+ aMagnitude > magnitudeLimit ||
+ bMagnitude < -magnitudeLimit ||
+ bMagnitude > magnitudeLimit;
+
+ if (needsExactArithmetic) {
+ // Use exact fraction arithmetic via bigDec to avoid precision loss
+ const anum = new bigDec(String(symA.multiplier.num));
+ const aden = new bigDec(String(symA.multiplier.den));
+ const bnum = new bigDec(String(symB.multiplier.num));
+ const bden = new bigDec(String(symB.multiplier.den));
+ retval = new NerdamerSymbol(anum.times(bnum).dividedBy(aden).dividedBy(bden).toFixed());
+ } else {
+ // Safe to use decimal approximation
+ const ad = new bigDec(symA.multiplier.toDecimal());
+ const bd = new bigDec(symB.multiplier.toDecimal());
+ const t = ad.times(bd).toFixed();
+ retval = new NerdamerSymbol(t);
+ }
+ return retval;
+ }
+
+ if (symB.group > symA.group && !(symB.group === CP)) {
+ return this.multiply(symB, symA);
+ }
+ // Correction for PL/CB dilemma
+ if (symA.group === CB && symB.group === PL && symA.value === symB.value) {
+ const t = symA;
+ symA = symB;
+ symB = t; // Swap
+ }
+
+ let g1 = symA.group;
+ const g2 = symB.group;
+ const bnum = symB.multiplier.num;
+ const bden = symB.multiplier.den;
+
+ if (
+ g1 === FN &&
+ symA.fname === SQRT &&
+ !symB.isConstant() &&
+ symA.args[0].value === symB.value &&
+ !symA.args[0].multiplier.lessThan(0)
+ ) {
+ // Unwrap sqrt
+ const aPow = symA.power;
+ const aMultiplier = _.parse(symA.multiplier);
+ symA = /** @type {NerdamerSymbolType} */ (_.multiply(aMultiplier, symA.args[0].clone()));
+ symA.setPower(new Frac(0.5).multiply(/** @type {FracType} */ (aPow)));
+ g1 = symA.group;
+ }
+ // Simplify n/sqrt(n). Being very specific
+ else if (
+ g1 === FN &&
+ symA.fname === SQRT &&
+ symA.multiplier.equals(1) &&
+ symA.power.equals(-1) &&
+ symB.isConstant() &&
+ symA.args[0].equals(symB)
+ ) {
+ symA = _.symfunction(SQRT, [symB.clone()]);
+ symB = new NerdamerSymbol(1);
+ }
+ let v1 = symA.value;
+ let v2 = symB.value;
+ /** @type {FracType} */
+ let signVal = /** @type {FracType} */ (/** @type {unknown} */ (new Frac(symA.sign())));
+ // Since P is just a morphed version of N we need to see if they relate
+ const ONN =
+ g1 === P &&
+ g2 === N &&
+ symB.multiplier.equals(/** @type {PowerValueType} */ (/** @type {unknown} */ (symA.value)));
+ // Don't multiply the multiplier of b since that's equal to the value of a
+ const m = ONN
+ ? new Frac(1).multiply(symA.multiplier).abs()
+ : symA.multiplier.multiply(symB.multiplier).abs();
+ let result = symA.clone().toUnitMultiplier();
+ symB = symB.clone().toUnitMultiplier(true);
+
+ // Further simplification of sqrt
+ if (g1 === FN && g2 === FN) {
+ const u = symA.args[0].clone();
+ const v = symB.args[0].clone();
+ if (symA.fname === SQRT && symB.fname === SQRT && symA.isLinear() && symB.isLinear()) {
+ const q = /** @type {NerdamerSymbolType} */ (_.divide(u, v)).invert();
+ if (q.gt(1) && isInt(q)) {
+ // B contains a factor a which can be moved to a
+ result = /** @type {NerdamerSymbolType} */ (
+ _.multiply(symA.args[0].clone(), sqrt(q.clone()))
+ );
+ symB = new NerdamerSymbol(1);
+ }
+ }
+ // Simplify factorial but only if
+ // 1 - It's division so b will have a negative power
+ // 2 - We're not dealing with factorials of numbers
+ else if (
+ symA.fname === FACTORIAL &&
+ symB.fname === FACTORIAL &&
+ !u.isConstant() &&
+ !v.isConstant() &&
+ Number(symB.power) < 0
+ ) {
+ // Assume that n = positive
+ const d = /** @type {NerdamerSymbolType} */ (_.subtract(u.clone(), v.clone()));
+
+ // If it's not numeric then we don't know if we can simplify so just return
+ if (d.isConstant()) {
+ // There will never be a case where d == 0 since this will already have
+ // been handled at the beginning of this function
+ /** @type {NerdamerSymbolType} */
+ let t = new NerdamerSymbol(1);
+ if (Number(d) < 0) {
+ // If d is negative then the numerator is larger so expand that
+ for (let i = 0, n = Math.abs(Number(d)); i <= n; i++) {
+ const s = _.add(u.clone(), new NerdamerSymbol(i));
+ t = /** @type {NerdamerSymbolType} */ (_.multiply(t, s));
+ }
+
+ result = /** @type {NerdamerSymbolType} */ (
+ _.multiply(
+ _.pow(u, new NerdamerSymbol(symA.power)),
+ _.pow(t, new NerdamerSymbol(symB.power))
+ )
+ );
+
+ symB = new NerdamerSymbol(1);
+ } else {
+ // Otherwise the denominator is larger so expand that
+ for (let i = 0, n = Math.abs(Number(d)); i <= n; i++) {
+ const s = _.add(v.clone(), new NerdamerSymbol(i));
+ t = /** @type {NerdamerSymbolType} */ (_.multiply(t, s));
+ }
+
+ result = /** @type {NerdamerSymbolType} */ (
+ _.multiply(
+ _.pow(t, new NerdamerSymbol(symA.power)),
+ _.pow(v, new NerdamerSymbol(symB.power))
+ )
+ );
+
+ symB = new NerdamerSymbol(1);
+ }
+ }
+ }
+ }
+
+ // If both are PL then their hashes have to match
+ if (v1 === v2 && g1 === PL && g1 === g2) {
+ v1 = symA.text('hash');
+ v2 = symB.text('hash');
+ }
+
+ // Same issue with (x^2+1)^x*(x^2+1)
+ // EX needs an exception when multiplying because it needs to recognize
+ // that (x+x^2)^x has the same hash as (x+x^2). The latter is kept as x
+ if (g2 === EX && symB.previousGroup === PL && g1 === PL) {
+ v1 = text(symA, 'hash', EX);
+ }
+
+ if (
+ (v1 === v2 || ONN) &&
+ !(g1 === PL && (g2 === S || g2 === P || g2 === FN)) &&
+ !(g1 === PL && g2 === CB)
+ ) {
+ const p1 = symA.power;
+ const p2 = symB.power;
+ const isSymbolP1 = isSymbol(p1);
+ const isSymbolP2 = isSymbol(p2);
+ const toEX = isSymbolP1 || isSymbolP2;
+ // TODO: this needs cleaning up
+ if (g1 === PL && g2 !== PL && symB.previousGroup !== PL && p1.equals(1)) {
+ result = new NerdamerSymbol(0);
+ symA.each(x => {
+ result = /** @type {NerdamerSymbolType} */ (_.add(result, _.multiply(x, symB.clone())));
+ }, true);
+ } else {
+ // Add the powers
+ if (toEX) {
+ result.power = /** @type {NerdamerSymbolType} */ (
+ _.add(
+ isSymbol(p1) ? p1 : new NerdamerSymbol(p1),
+ isSymbol(p2) ? p2 : new NerdamerSymbol(p2)
+ )
+ );
+ } else if (g1 === N) {
+ // Don't add powers for N
+ result.power = p1;
+ } else {
+ result.power = /** @type {FracType} */ (p1).add(/** @type {FracType} */ (p2));
+ }
+
+ // Eliminate zero power values and convert them to numbers
+ if (result.power.equals(0)) {
+ result = result.convert(N);
+ }
+
+ // Properly convert to EX
+ if (toEX) {
+ result.convert(EX);
+ }
+
+ // Take care of imaginaries
+ if (symA.imaginary && symB.imaginary) {
+ const isEven = even(Number(result.power) % 2);
+ if (isEven) {
+ result = new NerdamerSymbol(1);
+ m.negate();
+ }
+ }
+
+ // Cleanup: this causes the LaTeX generator to get confused as to how to render the symbol
+ if (result.group !== EX && result.previousGroup) {
+ result.previousGroup = undefined;
+ }
+ // The sign for b is floating around. Remember we are assuming that the odd variable will carry
+ // the sign but this isn't true if they're equals symbols
+ result.multiplier = result.multiplier.multiply(symB.multiplier);
+ }
+ } else if (g1 === CB && symA.isLinear()) {
+ if (g2 === CB) {
+ symB.distributeExponent();
+ }
+ if (g2 === CB && symB.isLinear()) {
+ for (const s in symB.symbols) {
+ if (!Object.hasOwn(symB.symbols, s)) {
+ continue;
+ }
+ const x = symB.symbols[s];
+ result = result.combine(x);
+ }
+ result.multiplier = result.multiplier.multiply(symB.multiplier);
+ } else {
+ result.combine(symB);
+ }
+ // The multiplier was already handled so nothing left to do
+ } else if (g1 === N) {
+ result = symB.clone().toUnitMultiplier(true);
+ } else if (g1 === CB) {
+ result.distributeExponent();
+ result.combine(symB);
+ } else if (!symB.isOne()) {
+ const bm = symB.multiplier.clone();
+ symB.toUnitMultiplier();
+ result = NerdamerSymbol.shell(CB).combine([result, symB]);
+ // Transfer the multiplier to the outside
+ result.multiplier = result.multiplier.multiply(bm);
+ }
+
+ if (result.group === P) {
+ const logV = Math.log(Number(result.value));
+ const n1 = Math.log(Number(bnum)) / logV;
+ const n2 = Math.log(Number(bden)) / logV;
+ const ndiv = Number(m.num) / Number(bnum);
+ const ddiv = Number(m.den) / Number(bden);
+ // We don't want to divide by zero no do we? Strange things happen.
+ if (n1 !== 0 && isInt(n1) && isInt(ndiv)) {
+ result.power = /** @type {FracType} */ (result.power).add(new Frac(n1));
+ m.num = m.num.divide(bnum);
+ }
+ if (n2 !== 0 && isInt(n2) && isInt(ddiv)) {
+ result.power = /** @type {FracType} */ (result.power).subtract(new Frac(n2));
+ m.den = m.den.divide(bden);
+ }
+ }
+
+ // Unpack CB if length is only one
+ if (result.length === 1) {
+ const t = result.multiplier;
+ // Transfer the multiplier
+ result = /** @type {NerdamerSymbolType} */ (firstObject(result.symbols));
+ result.multiplier = result.multiplier.multiply(t);
+ }
+
+ // Reduce square root
+ const ps = result.power.toString();
+ if (even(ps) && result.fname === SQRT) {
+ // Grab the sign of the symbol
+ signVal = signVal.multiply(
+ /** @type {FracType} */ (/** @type {unknown} */ (new Frac(result.sign())))
+ );
+ const p = /** @type {FracType} */ (result.power);
+ result = result.args[0];
+ result = /** @type {NerdamerSymbolType} */ (
+ _.multiply(new NerdamerSymbol(m), _.pow(result, new NerdamerSymbol(p.divide(new Frac(2)))))
+ );
+ // Flip it back to the correct sign
+ if (signVal.lessThan(0)) {
+ result.negate();
+ }
+ } else {
+ result.multiplier = result.multiplier.multiply(m).multiply(signVal);
+ if (result.group === CP && result.isImaginary()) {
+ result.distributeMultiplier();
+ }
+ }
+
+ // Back convert group P to a simpler group N if possible
+ if (result.group === P && isInt(/** @type {FracType} */ (result.power).toDecimal())) {
+ result = result.convert(N);
+ }
+
+ return result;
+ }
+ //* ***** Matrices & Vector *****//
+ if (bIsSymbol && !aIsSymbol) {
+ // Keep symbols to the right
+ const tempOp = a;
+ a = b;
+ b = tempOp; // Swap
+ const tempBool = bIsSymbol;
+ bIsSymbol = aIsSymbol;
+ aIsSymbol = tempBool;
+ }
+
+ const isMatrixB = isMatrix(b);
+ const isMatrixA = isMatrix(a);
+ if (aIsSymbol && isMatrixB) {
+ const M = new Matrix();
+ const bMatrix = /** @type {MatrixType} */ (b);
+ bMatrix.eachElement((e, row, col) => {
+ M.set(
+ row,
+ col,
+ /** @type {NerdamerSymbolType} */ (_.multiply(/** @type {NerdamerSymbolType} */ (a).clone(), e))
+ );
+ });
+
+ b = M;
+ } else if (isMatrixA && isMatrixB) {
+ b = /** @type {MatrixType} */ (a).multiply(/** @type {MatrixType} */ (b));
+ } else if (aIsSymbol && isVector(b)) {
+ const bVec = /** @type {VectorType} */ (b);
+ bVec.each((el, idx) => {
+ idx--;
+ bVec.elements[idx] = /** @type {NerdamerSymbolType} */ (
+ _.multiply(/** @type {NerdamerSymbolType} */ (a).clone(), bVec.elements[idx])
+ );
+ });
+ } else if (isVector(a) && isVector(b)) {
+ const aVec = /** @type {VectorType} */ (a);
+ const bVec = /** @type {VectorType} */ (b);
+ bVec.each((el, idx) => {
+ idx--;
+ bVec.elements[idx] = /** @type {NerdamerSymbolType} */ (
+ _.multiply(aVec.elements[idx], bVec.elements[idx])
+ );
+ });
+ } else if (isVector(a) && isMatrix(b)) {
+ // Try to convert a to a matrix
+ return this.multiply(b, a);
+ } else if (isMatrix(a) && isVector(b)) {
+ const aMatrix = /** @type {MatrixType} */ (a);
+ const bVec = /** @type {VectorType} */ (b);
+ if (bVec.elements.length === aMatrix.rows()) {
+ const M = new Matrix();
+ const l = aMatrix.cols();
+ bVec.each((e, idx) => {
+ const row = [];
+ for (let j = 0; j < l; j++) {
+ row.push(_.multiply(aMatrix.elements[idx - 1][j].clone(), e.clone()));
+ }
+ M.elements.push(row);
+ });
+ return M;
+ }
+ err('Dimensions must match!');
+ }
+
+ return b;
+ };
+ /**
+ * Gets called when the parser finds the / operator. See this.add
+ *
+ * @param {ArithmeticOperand} a
+ * @param {ArithmeticOperand} b
+ * @returns {ArithmeticOperand}
+ */
+ this.divide = function divide(a, b) {
+ const aIsSymbol = isSymbol(a);
+ const bIsSymbol = isSymbol(b);
+
+ if (aIsSymbol && bIsSymbol) {
+ // Cast to NerdamerSymbol since we've verified with isSymbol
+ const symA = /** @type {NerdamerSymbolType} */ (a);
+ const symB = /** @type {NerdamerSymbolType} */ (b);
+ // Forward to Unit division
+ if (symA.unit || symB.unit) {
+ return _.Unit.divide(symA, symB);
+ }
+ let result;
+ if (symB.equals(0)) {
+ throw new DivisionByZero('Division by zero not allowed!');
+ }
+
+ if (symA.isConstant() && symB.isConstant()) {
+ result = symA.clone();
+ result.multiplier = result.multiplier.divide(symB.multiplier);
+ } else {
+ symB.invert();
+ result = /** @type {NerdamerSymbolType} */ (_.multiply(symA, symB));
+ }
+ return result;
+ }
+ //* ****** Vectors & Matrices *********//
+ const isVectorA = isVector(a);
+ const isVectorB = isVector(b);
+ if (aIsSymbol && isVectorB) {
+ b = /** @type {VectorType} */ (b).map(
+ x => /** @type {NerdamerSymbolType} */ (_.divide(/** @type {NerdamerSymbolType} */ (a).clone(), x))
+ );
+ } else if (isVectorA && bIsSymbol) {
+ b = /** @type {VectorType} */ (a).map(
+ x => /** @type {NerdamerSymbolType} */ (_.divide(x, /** @type {NerdamerSymbolType} */ (b).clone()))
+ );
+ } else if (isVectorA && isVectorB) {
+ const aVec = /** @type {VectorType} */ (a);
+ if (aVec.dimensions() === /** @type {VectorType} */ (b).dimensions()) {
+ b = /** @type {VectorType} */ (b).map(
+ (x, i) => /** @type {NerdamerSymbolType} */ (_.divide(aVec.elements[--i], x))
+ );
+ } else {
+ _.error('Cannot divide vectors. Dimensions do not match!');
+ }
+ } else {
+ const isMatrixA = isMatrix(a);
+ const isMatrixB = isMatrix(b);
+ if (isMatrixA && bIsSymbol) {
+ const M = new Matrix();
+ /** @type {MatrixType} */ (a).eachElement((x, i, j) => {
+ M.set(
+ i,
+ j,
+ /** @type {NerdamerSymbolType} */ (
+ _.divide(x, /** @type {NerdamerSymbolType} */ (b).clone())
+ )
+ );
+ });
+ b = M;
+ } else if (aIsSymbol && isMatrixB) {
+ const M = new Matrix();
+ /** @type {MatrixType} */ (b).eachElement((x, i, j) => {
+ M.set(
+ i,
+ j,
+ /** @type {NerdamerSymbolType} */ (
+ _.divide(/** @type {NerdamerSymbolType} */ (a).clone(), x)
+ )
+ );
+ });
+ b = M;
+ } else if (isMatrixA && isMatrixB) {
+ const M = new Matrix();
+ const aMatrix = /** @type {MatrixType} */ (a);
+ const bMatrix = /** @type {MatrixType} */ (b);
+ if (aMatrix.rows() === bMatrix.rows() && aMatrix.cols() === bMatrix.cols()) {
+ aMatrix.eachElement((x, i, j) => {
+ M.set(i, j, /** @type {NerdamerSymbolType} */ (_.divide(x, bMatrix.elements[i][j])));
+ });
+ b = M;
+ } else {
+ _.error('Dimensions do not match!');
+ }
+ } else if (isMatrixA && isVectorB) {
+ const aMatrix = /** @type {MatrixType} */ (a);
+ const bVec = /** @type {VectorType} */ (b);
+ if (aMatrix.cols() === bVec.dimensions()) {
+ const M = new Matrix();
+ aMatrix.eachElement((x, i, j) => {
+ M.set(i, j, /** @type {NerdamerSymbolType} */ (_.divide(x, bVec.elements[i].clone())));
+ });
+ b = M;
+ } else {
+ _.error('Unable to divide matrix by vector.');
+ }
+ }
+ }
+ return b;
+ };
+ /**
+ * Gets called when the parser finds the ^ operator. See this.add
+ *
+ * @param {ArithmeticOperand} a
+ * @param {ArithmeticOperand} b
+ * @returns {ArithmeticOperand}
+ */
+ this.pow = function pow(a, b) {
+ const aIsSymbol = isSymbol(a);
+ const bIsSymbol = isSymbol(b);
+ if (aIsSymbol && bIsSymbol) {
+ // Cast to NerdamerSymbol since we've verified with isSymbol
+ const symA = /** @type {NerdamerSymbolType} */ (a);
+ const symB = /** @type {NerdamerSymbolType} */ (b);
+ // It has units then it's the Unit module's problem
+ if (symA.unit || symB.unit) {
+ return _.Unit.pow(symA, symB);
+ }
+
+ // Handle abs
+ if (symA.group === FN && symA.fname === ABS && even(symB)) {
+ const m = symA.multiplier.clone();
+ const raised = /** @type {NerdamerSymbolType} */ (_.pow(symA.args[0], symB));
+ raised.multiplier = m;
+ return raised;
+ }
+
+ // Handle infinity
+ if (symA.isInfinity || symB.isInfinity) {
+ if (symA.isInfinity && symB.isInfinity) {
+ throw new UndefinedError(`(${symA})^(${symB}) is undefined!`);
+ }
+
+ if (symA.isConstant() && symB.isInfinity) {
+ if (symA.equals(0)) {
+ if (symB.lessThan(0)) {
+ throw new UndefinedError('0^Infinity is undefined!');
+ }
+ return new NerdamerSymbol(0);
+ }
+ if (symA.equals(1)) {
+ throw new UndefinedError(`1^${symB.toString()} is undefined!`);
+ }
+ // A^-oo
+ if (symB.lessThan(0)) {
+ return new NerdamerSymbol(0);
+ }
+ // A^oo
+ if (!symA.lessThan(0)) {
+ return NerdamerSymbol.infinity();
+ }
+ }
+
+ if (symA.isInfinity && symB.isConstant()) {
+ if (symB.equals(0)) {
+ throw new UndefinedError(`${symA}^0 is undefined!`);
+ }
+ if (symB.lessThan(0)) {
+ return new NerdamerSymbol(0);
+ }
+ return /** @type {NerdamerSymbolType} */ (
+ _.multiply(NerdamerSymbol.infinity(), _.pow(new NerdamerSymbol(symA.sign()), symB.clone()))
+ );
+ }
+ }
+
+ const aIsZero = symA.equals(0);
+ const bIsZero = symB.equals(0);
+ if (aIsZero && bIsZero) {
+ throw new UndefinedError('0^0 is undefined!');
+ }
+
+ // Return 0 right away if possible
+ if (aIsZero && symB.isConstant() && symB.multiplier.greaterThan(0)) {
+ return new NerdamerSymbol(0);
+ }
+
+ if (bIsZero) {
+ return new NerdamerSymbol(1);
+ }
+
+ const bIsConstant = symB.isConstant();
+ const aIsConstant = symA.isConstant();
+ const bIsInt = symB.isInteger();
+ const m = symA.multiplier;
+ let result = symA.clone();
+
+ // 0^0, 1/0, etc. Complain.
+ if (aIsConstant && bIsConstant && symA.equals(0) && symB.lessThan(0)) {
+ throw new UndefinedError('Division by zero is not allowed!');
+ }
+
+ // Compute imaginary numbers right away
+ if (
+ Settings.PARSE2NUMBER &&
+ aIsConstant &&
+ bIsConstant &&
+ symA.sign() < 0 &&
+ evenFraction(/** @type {number} */ (/** @type {unknown} */ (symB)))
+ ) {
+ const k = Math.PI * Number(symB.multiplier.toDecimal());
+ const re = new NerdamerSymbol(Math.cos(k));
+ const im = /** @type {NerdamerSymbolType} */ (
+ _.multiply(NerdamerSymbol.imaginary(), new NerdamerSymbol(Math.sin(k)))
+ );
+ return _.add(re, im);
+ }
+
+ // Imaginary number under negative nthroot or to the n
+ if (
+ Settings.PARSE2NUMBER &&
+ symA.isImaginary() &&
+ bIsConstant &&
+ isInt(/** @type {number} */ (/** @type {unknown} */ (symB))) &&
+ !symB.lessThan(0)
+ ) {
+ let r;
+ let theta;
+ let nre;
+ let nim;
+ let phi;
+ const re = symA.realpart();
+ const im = symA.imagpart();
+ if (re.isConstant('all') && im.isConstant('all')) {
+ phi = Settings.USE_BIG
+ ? nerdamerBigDecimal
+ .atan2(im.multiplier.toDecimal(), re.multiplier.toDecimal())
+ .times(symB.toString())
+ : Math.atan2(Number(im.multiplier.toDecimal()), Number(re.multiplier.toDecimal())) *
+ Number(symB.multiplier.toDecimal());
+ theta = new NerdamerSymbol(phi);
+ r = /** @type {NerdamerSymbolType} */ (_.pow(NerdamerSymbol.hyp(re, im), symB));
+ nre = /** @type {NerdamerSymbolType} */ (_.multiply(r.clone(), _.trig.cos(theta.clone())));
+ nim = /** @type {NerdamerSymbolType} */ (_.multiply(r, _.trig.sin(theta)));
+ return _.add(nre, _.multiply(NerdamerSymbol.imaginary(), nim));
+ }
+ }
+
+ // Take care of the symbolic part
+ result.toUnitMultiplier();
+ let signVal;
+ // Simpifly sqrt
+ if (result.group === FN && result.fname === SQRT && !bIsConstant) {
+ const s = result.args[0];
+ s.multiplyPower(new NerdamerSymbol(0.5));
+ s.multiplier.multiply(result.multiplier);
+ s.multiplyPower(symB);
+ result = s;
+ } else {
+ signVal = m.sign();
+ // Handle cases such as (-a^3)^(1/4)
+ if (evenFraction(/** @type {number} */ (/** @type {unknown} */ (symB))) && signVal < 0) {
+ // Swaperoo
+ // First put the sign back on the symbol
+ result.negate();
+ // Wrap it in brackets
+ result = /** @type {NerdamerSymbolType} */ (_.symfunction(PARENTHESIS, [result]));
+ // Move the sign back the exterior and let nerdamer handle the rest
+ result.negate();
+ }
+
+ result.multiplyPower(symB);
+ }
+
+ let num;
+ let den;
+ if (aIsConstant && bIsConstant && Settings.PARSE2NUMBER) {
+ let c;
+ // Remove the sign
+ if (signVal < 0) {
+ symA.negate();
+ if (
+ symB.multiplier.den.equals(2)
+ ) // We know that the numerator has to be odd and therefore it's i
+ {
+ c = new NerdamerSymbol(Settings.IMAGINARY);
+ } else if (isInt(symB.multiplier)) {
+ if (even(symB.multiplier)) {
+ c = new NerdamerSymbol(1);
+ } else {
+ c = new NerdamerSymbol(-1);
+ }
+ } else if (even(symB.multiplier.den)) {
+ c = /** @type {NerdamerSymbolType} */ (
+ _.pow(_.symfunction(PARENTHESIS, [new NerdamerSymbol(signVal)]), symB.clone())
+ );
+ } else {
+ c = new NerdamerSymbol(
+ signVal ** /** @type {number} */ (/** @type {unknown} */ (symB.multiplier.num))
+ );
+ }
+ }
+
+ const _pow = Number(symA.multiplier.toDecimal()) ** Number(symB.multiplier.toDecimal());
+ if (_pow !== 0 || symA.multiplier.equals(0)) {
+ result = new NerdamerSymbol(_pow);
+ } else {
+ // Should not be here, must have underflowed precision
+ const ad = new bigDec(symA.multiplier.toDecimal());
+ const bd = new bigDec(symB.multiplier.toDecimal());
+ result = new NerdamerSymbol(ad.pow(bd).toFixed());
+ }
+ // Put the back sign
+ if (c) {
+ result = /** @type {NerdamerSymbolType} */ (_.multiply(result, c));
+ }
+ } else if (bIsInt && !m.equals(1)) {
+ const absB = symB.abs();
+ // Provide fall back to JS until big number implementation is improved
+ if (absB.gt(Settings.MAX_EXP)) {
+ if (symB.sign() < 0) {
+ return new NerdamerSymbol(0);
+ }
+ return NerdamerSymbol.infinity();
+ }
+ const p = /** @type {FracType} */ (symB.multiplier).toDecimal();
+ const sgn = Math.sign(Number(p));
+ const absP = Math.abs(Number(p));
+ const multiplier = new Frac(1);
+ multiplier.num = /** @type {BigIntegerType} */ (m.num).pow(Number(absP));
+ multiplier.den = /** @type {BigIntegerType} */ (m.den).pow(Number(absP));
+ if (sgn < 0) {
+ multiplier.invert();
+ }
+ // Multiplying is justified since after mulltiplyPower if it was of group P it will now be of group N
+ result.multiplier = result.multiplier.multiply(multiplier);
+ } else {
+ const aSignVal = symA.sign();
+ if (symB.isConstant() && symA.isConstant() && !symB.multiplier.den.equals(1) && aSignVal < 0) {
+ // We know the sign is negative so if the denominator for b == 2 then it's i
+ if (symB.multiplier.den.equals(2)) {
+ const i = new NerdamerSymbol(Settings.IMAGINARY);
+ symA.negate(); // Remove the sign
+ // if the power is negative then i is negative
+ if (symB.lessThan(0)) {
+ i.negate();
+ symB.negate(); // Remove the sign from the power
+ }
+ // Pull the power normally and put back the imaginary
+ result = /** @type {NerdamerSymbolType} */ (_.multiply(_.pow(symA, symB), i));
+ } else {
+ const aa = symA.clone();
+ aa.multiplier.negate();
+ result = /** @type {NerdamerSymbolType} */ (
+ _.pow(_.symfunction(PARENTHESIS, [new NerdamerSymbol(signVal)]), symB.clone())
+ );
+ const _a = /** @type {NerdamerSymbolType} */ (
+ _.pow(new NerdamerSymbol(aa.multiplier.num), symB.clone())
+ );
+ const _b = /** @type {NerdamerSymbolType} */ (
+ _.pow(new NerdamerSymbol(aa.multiplier.den), symB.clone())
+ );
+ const r = /** @type {NerdamerSymbolType} */ (_.divide(_a, _b));
+ result = /** @type {NerdamerSymbolType} */ (_.multiply(result, r));
+ }
+ } else if (Settings.PARSE2NUMBER && symB.isImaginary()) {
+ // 4^(i + 2) = e^(- (2 - 4 i) π n + (2 + i) log(4))
+
+ const re = symB.realpart();
+ const im = symB.imagpart();
+ /*
+ If(b.group === CP && false) {
+ let ex = _.pow(a.clone(), re);
+ let xi = _.multiply(_.multiply(ex.clone(), trig.sin(im.clone())), NerdamerSymbol.imaginary());
+ let xa = _.multiply(trig.cos(im), ex);
+ result = _.add(xi, xa);
+ }
+ else {
+ */
+ const aa = symA.clone().toLinear();
+ const a1 = /** @type {NerdamerSymbolType} */ (_.pow(aa.clone(), re));
+ const logA = /** @type {NerdamerSymbolType} */ (log(aa.clone()));
+ const b1 = /** @type {NerdamerSymbolType} */ (trig.cos(_.multiply(im.clone(), logA)));
+ const c1 = /** @type {NerdamerSymbolType} */ (
+ _.multiply(trig.sin(_.multiply(im, log(aa))), NerdamerSymbol.imaginary())
+ );
+ result = /** @type {NerdamerSymbolType} */ (_.multiply(a1, _.add(b1, c1)));
+ result = /** @type {NerdamerSymbolType} */ (_.expand(_.parse(result)));
+ /*
+ }
+ */
+ } else {
+ // B is a symbol
+ const negNum = symA.group === N && aSignVal < 0;
+ num = testSQRT(
+ /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (
+ new NerdamerSymbol(negNum ? Number(m.num) : Math.abs(Number(m.num)))
+ )
+ ).setPower(symB.clone())
+ );
+ den = testSQRT(
+ /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (new NerdamerSymbol(Number(m.den)))
+ )
+ .setPower(symB.clone())
+ .invert()
+ );
+
+ // Eliminate imaginary if possible
+ if (symA.imaginary) {
+ if (bIsInt) {
+ const s = Math.sign(/** @type {number} */ (/** @type {unknown} */ (symB)));
+ const p = abs(symB);
+ const n = p % 4;
+ result = new NerdamerSymbol(even(n) ? -1 : Settings.IMAGINARY);
+ if (n === 0 || (s < 0 && n === 1) || (s > 0 && n === 3)) {
+ result.negate();
+ }
+ } else {
+ // Assume i = sqrt(-1) -> (-1)^(1/2)
+ const nr = b.multiplier.multiply(Frac.quick(1, 2));
+ // The denominator denotes the power so raise to it. It will turn positive it round
+ const tn = (-1) ** /** @type {number} */ (/** @type {unknown} */ (nr.num));
+ result = even(nr.den)
+ ? /** @type {NerdamerSymbolType} */ (
+ /** @type {unknown} */ (new NerdamerSymbol(-1))
+ ).setPower(nr, true)
+ : new NerdamerSymbol(tn);
+ }
+ }
+ // Ensure that the sign is carried by the symbol and not the multiplier
+ // this enables us to check down the line if the multiplier can indeed be transferred
+ if (aSignVal < 0 && !negNum) {
+ result.negate();
+ }
+
+ // Retain the absolute value
+ if (bIsConstant && symA.group !== EX) {
+ const evenr = even(symB.multiplier.den);
+ const evenp = even(/** @type {FracType} */ (symA.power));
+ const n = /** @type {FracType} */ (result.power).toDecimal();
+ const evennp = even(n);
+ if (evenr && evenp && !evennp) {
+ if (n === '1' || n === '1') {
+ // Check for together.math baseunits
+ // don't have to wrap them in abs()
+ if (typeof result.value === 'string' && result.value.startsWith('baseunit_')) {
+ // Don't wrap baseunits in abs()
+ } else {
+ result = /** @type {NerdamerSymbolType} */ (_.symfunction(ABS, [result]));
+ }
+ } else if (isInt(n)) {
+ result = /** @type {NerdamerSymbolType} */ (
+ _.multiply(
+ _.symfunction(ABS, [result.clone().toLinear()]),
+ result.clone().setPower(new Frac(Number(n) - 1))
+ )
+ );
+ } else {
+ const p = /** @type {FracType} */ (result.power);
+ result = /** @type {NerdamerSymbolType} */ (
+ _.symfunction(ABS, [result.toLinear()])
+ ).setPower(p);
+ }
+ // Quick workaround. Revisit
+ if (Settings.POSITIVE_MULTIPLIERS && result.fname === ABS) {
+ result = result.args[0];
+ }
+ }
+ }
+ // Multiply out sqrt
+ if (symB.equals(2) && result.group === CB) {
+ /** @type {NerdamerSymbolType} */
+ let _result = new NerdamerSymbol(1);
+ result.each(sym => {
+ _result = /** @type {NerdamerSymbolType} */ (_.multiply(_result, _.pow(sym, symB)));
+ });
+ result = _result;
+ }
+ }
+ }
+
+ result = testSQRT(result);
+
+ // Don't multiply until we've tested the remaining symbol
+ if (num && den) {
+ result = /** @type {NerdamerSymbolType} */ (_.multiply(result, testPow(_.multiply(num, den))));
+ }
+
+ // Reduce square root
+ if (result.fname === SQRT) {
+ const isEX = result.group === EX;
+ const t = isEX
+ ? /** @type {NerdamerSymbolType} */ (result.power).multiplier.toString()
+ : /** @type {FracType} */ (result.power).toString();
+ if (even(t)) {
+ const pt = isEX
+ ? _.divide(/** @type {NerdamerSymbolType} */ (result.power), new NerdamerSymbol(2))
+ : new NerdamerSymbol(/** @type {FracType} */ (result.power).divide(new Frac(2)));
+ const resultMult = result.multiplier;
+ result = /** @type {NerdamerSymbolType} */ (_.pow(result.args[0], pt));
+ result.multiplier = result.multiplier.multiply(resultMult);
+ }
+ }
+ // Detect Euler's identity
+ else if (
+ !Settings.IGNORE_E &&
+ result.isE() &&
+ result.group === EX &&
+ /** @type {NerdamerSymbolType} */ (result.power).contains('pi') &&
+ /** @type {NerdamerSymbolType} */ (result.power).contains(Settings.IMAGINARY) &&
+ symB.group === CB
+ ) {
+ const theta = symB.stripVar(Settings.IMAGINARY);
+ result = /** @type {NerdamerSymbolType} */ (
+ _.add(trig.cos(theta), _.multiply(NerdamerSymbol.imaginary(), trig.sin(theta)))
+ );
+ }
+
+ return result;
+ }
+ if (isVector(a) && bIsSymbol) {
+ a = /** @type {VectorType} */ (a).map(
+ x => /** @type {NerdamerSymbolType} */ (_.pow(x, /** @type {NerdamerSymbolType} */ (b).clone()))
+ );
+ } else if (isMatrix(a) && bIsSymbol) {
+ const M = new Matrix();
+ a.eachElement((x, row, col) => {
+ M.set(row, col, /** @type {NerdamerSymbolType} */ (_.pow(x, b.clone())));
+ });
+ a = M;
+ } else if (aIsSymbol && isMatrix(b)) {
+ const M = new Matrix();
+ b.eachElement((x, row, col) => {
+ M.set(row, col, /** @type {NerdamerSymbolType} */ (_.pow(a.clone(), x)));
+ });
+ a = M;
+ }
+ return a;
+ };
+ // Gets called when the parser finds the , operator.
+ // Commas return a Collector object which is roughly an array
+ this.comma = function comma(a, b) {
+ if (!(a instanceof Collection)) {
+ a = Collection.create(a);
+ }
+ a.append(b);
+ return a;
+ };
+ // Link to modulus
+ this.mod = function mod(a, b) {
+ return _mod(a, b);
+ };
+ // Used to slice elements from arrays
+ this.slice = function slice(a, b) {
+ return new Slice(a, b);
+ };
+ // The equality setter
+ this.equals = function equals(a, b) {
+ // Equality can only be set for group S so complain it's not
+ if (a.group !== S && !a.isLinear()) {
+ err(`Cannot set equality for ${a.toString()}`);
+ }
+ VARS[a.value] = b.clone();
+ return b;
+ };
+ // Percent
+ this.percent = function percent(a) {
+ return _.divide(a, new NerdamerSymbol(100));
+ };
+ // NerdamerSet variable
+ this.assign = function assign(a, b) {
+ if (a instanceof Collection && b instanceof Collection) {
+ a.elements.map((x, i) => _.assign(x, b.elements[i]));
+ return Vector.fromArray(b.elements);
+ }
+ if (a.parent) {
+ // It's referring to the parent instead. The current item can be discarded
+ const e = a.parent;
+ e.elements[e.getter] = b;
+ delete e.getter;
+ return e;
+ }
+
+ if (a.group !== S) {
+ throw new NerdamerValueError(`Cannot complete operation. Incorrect LH value for ${a}`);
+ }
+ VARS[a.value] = b;
+ return b;
+ };
+ this.functionAssign = function functionAssign(a, b) {
+ const f = a.elements.pop();
+ return _setFunction(f, a.elements, b);
+ };
+ // Function to quickly convert bools to Symbols
+ const bool2Symbol = function bool2Symbol(x) {
+ return new NerdamerSymbol(x === true ? 1 : 0);
+ };
+ // Check for equality
+ this.eq = function eq(a, b) {
+ return bool2Symbol(a.equals(b));
+ };
+ // Checks for greater than
+ this.gt = function gt(a, b) {
+ return bool2Symbol(a.gt(b));
+ };
+ // Checks for greater than equal
+ this.gte = function gte(a, b) {
+ return bool2Symbol(a.gte(b));
+ };
+ // Checks for less than
+ this.lt = function lt(a, b) {
+ return bool2Symbol(a.lt(b));
+ };
+ // Checks for less than equal
+ this.lte = function lte(a, b) {
+ return bool2Symbol(a.lte(b));
+ };
+ // Wraps the factorial
+ this.factorial = function factorial(a) {
+ return this.symfunction(FACTORIAL, [a]);
+ };
+ // Wraps the double factorial
+ this.dfactorial = function dfactorial(a) {
+ return this.symfunction(DOUBLEFACTORIAL, [a]);
+ };
+ } // End constructor
+} // End class Parser
+
+// Utils ========================================================================
+// Utility functions exported as part of the nerdamer core.
+// All functions are already at module scope; Build.build uses a getter for lazy evaluation.
+// Note: CoreUtilsInterface is the base type - Algebra.js extends it with additional methods at runtime.
+/** @type {CoreUtilsInterface} */
+const Utils = {
+ allSame,
+ allNumeric,
+ arguments2Array,
+ armTimeout,
+ arrayAddSlices,
+ arrayClone,
+ arrayMax,
+ arrayMin,
+ arrayEqual,
+ arrayUnique,
+ err,
+ arrayGetVariables,
+ arraySum,
+ block,
+ checkTimeout,
+ clearU,
+ comboSort,
+ compare,
+ convertToVector,
+ customError,
+ customType,
+ decompose_fn: decomposeFn,
+ disarmTimeout,
+ each,
+ evaluate,
+ even,
+ evenFraction,
+ fillHoles,
+ firstObject,
+ format,
+ generatePrimes,
+ getCoeffs,
+ getU,
+ importFunctions,
+ inBrackets,
+ isArray,
+ isCollection,
+ isExpression,
+ isFraction,
+ isInt,
+ isMatrix,
+ isNegative,
+ isNumericSymbol,
+ isPrime,
+ isReserved,
+ isSet,
+ isSymbol,
+ isVariableSymbol,
+ isVector,
+ keys,
+ knownVariable,
+ nroots,
+ remove,
+ reserveNames,
+ range,
+ round: nround,
+ sameSign,
+ scientificToDecimal,
+ separate,
+ stringReplace,
+ text,
+ validateName,
+ variables,
+ warn,
+};
+
+// LibExports ===================================================================
+// Factory function to create the main nerdamer library function.
+// Extracted from IIFE to module scope for cleaner organization.
+// Uses CoreDeps for all dependencies, which are populated by the IIFE before calling.
+
+/**
+ * Creates the main nerdamer library entry point function. Must be called after CoreDeps.parser and CoreDeps.state are
+ * initialized.
+ *
+ * @returns {NerdamerType} The libExports function (callable with additional methods attached by attachLibExports)
+ */
+function createLibExports() {
+ /**
+ * @param {string} expression The expression to be evaluated
+ * @param {object} subs The object containing the variable values
+ * @param {string | string[]} option Additional options
+ * @param {number} location A specific location in the equation list to insert the evaluated expression
+ * @returns {ExpressionType | NerdamerType}
+ */
+ const libExports = /** @type {NerdamerType} */ (
+ function (expression, subs, option, location) {
+ armTimeout();
+ try {
+ const _ = CoreDeps.parser;
+ const exprs = CoreDeps.state.EXPRESSIONS;
+
+ // Initiate the numer flag
+ let numer = false;
+
+ // Is the user declaring a function? Try to add user function
+ if (typeof expression === 'string' || typeof expression === 'function') {
+ if (_setFunction(/** @type {string | Function} */ (expression))) {
+ return CoreDeps.libExports; // Return self-reference via CoreDeps
+ }
+ }
+
+ // Var variable, fn, args;
+ // Convert any expression passed in to a string
+ if (
+ expression !== null &&
+ typeof expression === 'object' &&
+ /** @type {ExpressionType} */ (expression) instanceof Expression
+ ) {
+ expression = /** @type {ExpressionType} */ (expression).toString();
+ }
+
+ // If it's still a NerdamerExpression (not Expression), convert to string
+ if (
+ expression !== null &&
+ typeof expression === 'object' &&
+ 'text' in expression &&
+ typeof expression.text === 'function'
+ ) {
+ expression = expression.text();
+ }
+
+ // Convert it to an array for simplicity
+ if (!isArray(option)) {
+ option = typeof option === 'undefined' ? [] : [option];
+ }
+
+ option.forEach(o => {
+ // Turn on the numer flag if requested
+ if (o === 'numer') {
+ numer = true;
+ return;
+ }
+ // Wrap it in a function if requested. This only holds true for
+ // functions that take a single argument which is the expression
+ const f = _.functions[o];
+ // If there's a function and it takes a single argument, then wrap
+ // the expression in it
+ if (f && f[1] === 1) {
+ expression = `${o}(${/** @type {string} */ (expression)})`;
+ }
+ });
+
+ const e = block(
+ 'PARSE2NUMBER',
+ () =>
+ _.parse(
+ /** @type {string | number | NerdamerSymbolType | FracType | BigIntegerType} */ (
+ expression
+ ),
+ subs
+ ),
+ numer || Settings.PARSE2NUMBER
+ );
+
+ const expr = new Expression(e);
+ if (location) {
+ exprs[location - 1] = expr;
+ } else {
+ exprs.push(expr);
+ }
+
+ return expr;
+ } finally {
+ disarmTimeout();
+ }
+ }
+ );
+
+ return libExports;
+}
+
+// Core Object ==================================================================
+// Factory function to create the C (core) object that contains all nerdamer internals.
+// Uses CoreDeps for parser reference.
+
+/**
+ * Creates the core object containing all nerdamer internals. Must be called after Parser is instantiated and
+ * CoreDeps.parser is set.
+ *
+ * @returns {CoreType} The core object
+ */
+function createCoreObject() {
+ const _ = CoreDeps.parser;
+ return {
+ groups: Groups,
+ NerdamerSymbol,
+ Expression,
+ Collection,
+ Frac,
+ Vector,
+ Matrix,
+ NerdamerSet,
+ Math2,
+ LaTeX,
+ Build,
+ // Utils is typed as CoreUtilsInterface here, but Algebra.js will add the remaining methods at runtime.
+ // Cast to UtilsInterface to satisfy the Core interface type.
+ Utils: /** @type {UtilsInterface} */ (/** @type {unknown} */ (Utils)),
+ PARSER: _,
+ Settings,
+ bigInt: nerdamerBigInt,
+ bigDec: nerdamerBigDecimal,
+ exceptions: CoreDeps.exceptions,
+ Solve: /** @type {Record<string, Function>} */ ({}),
+ Calculus: /** @type {Record<string, Function>} */ ({}),
+ Algebra: /** @type {Record<string, Function>} */ ({}),
+ Extra: /** @type {Record<string, Function>} */ ({}),
+ };
+}
+
+// Parser Finalization ==========================================================
+// Finalizes parser setup after instantiation.
+
+/**
+ * Finalizes parser setup - reserves names, initializes constants, sets error handler. Must be called after Parser is
+ * instantiated.
+ */
+function finalizeParser() {
+ const _ = CoreDeps.parser;
+ reserveNames(_.CONSTANTS);
+ reserveNames(_.functions);
+ _.initConstants();
+ _.error ||= err;
+ Settings.LOG_FNS = {
+ log: _.functions.log,
+ log10: _.functions.log10,
+ };
+}
+
+// Library Exports Attachment ===================================================
+// Attaches all public API methods to the libExports function.
+
+/**
+ * Attaches all public API methods to libExports.
+ *
+ * @param {NerdamerType} libExports - The main nerdamer function to attach methods to
+ */
+function attachLibExports(libExports) {
+ // Library exports (most functions defined outside IIFE with dependency injection)
+ libExports.rpn = rpn;
+ libExports.convertToLaTeX = convertToLaTeX;
+ libExports.convertFromLaTeX = convertFromLaTeX;
+ libExports.version = version;
+ libExports.getWarnings = getWarnings;
+ libExports.setConstant = setConstant;
+ libExports.getConstant = getConstant;
+ libExports.clearConstants = clearConstants;
+ libExports.setFunction = setFunction;
+ libExports.clearFunctions = clearFunctions;
+ libExports.getCore = getCore;
+ libExports.getExpression = libExports.getEquation = Expression.getExpression;
+ libExports.reserved = reserved;
+ libExports.clear = clear;
+ libExports.flush = flush;
+ libExports.expressions = expressions;
+ libExports.functions = getFunctions;
+ libExports.register = register;
+ libExports.validateName = validateName;
+ libExports.validVarName = validVarName;
+ libExports.supported = supported;
+ libExports.numEquations = libExports.numExpressions = numExpressions;
+ libExports.setVar = setVar;
+ libExports.getVar = getVar;
+ libExports.clearVars = clearVars;
+ libExports.load = load;
+ libExports.getVars = getVars;
+ libExports.set = set;
+ libExports.get = getSetting;
+ libExports.updateAPI = updateAPI;
+ libExports.replaceFunction = replaceFunction;
+ libExports.setOperator = setOperator;
+ libExports.getOperator = getOperator;
+ libExports.aliasOperator = aliasOperator;
+ libExports.tree = tree;
+ libExports.htmlTree = htmlTree;
+ libExports.addPeeker = addPeeker;
+ libExports.removePeeker = removePeeker;
+ libExports.parse = parse;
+}
+
+const nerdamer = (function initNerdamerCore() {
+ // ============================================================================
+ // Parser Initialization
+ // ============================================================================
+ // The IIFE's main purpose is to instantiate Parser and wire up parser-dependent values.
+ // Most dependencies are centralized in CoreDeps via getters at module scope.
+
+ // Create parser instance
+ /** @type {ParserType} */
+ const _ = /** @type {ParserType} */ (/** @type {unknown} */ (new Parser()));
+
+ // Set CoreDeps.parser immediately so all getters can access it
+ CoreDeps.parser = _;
+ CoreDeps.classes.Parser = /** @type {ParserConstructor} */ (/** @type {unknown} */ (Parser));
+
+ // Initialize LaTeX.parser
+ LaTeX.initParser();
+
+ // Initialize Build dependencies
+ Build.initDependencies();
+
+ // Finalize parser setup
+ finalizeParser();
+
+ // Create the core object
+ const C = createCoreObject();
+
+ // ============================================================================
+ // CoreDeps Finalization - Complete the centralized dependency registry
+ // ============================================================================
+ // Classes are already assigned at module scope. Only set runtime values here.
+ CoreDeps.core = C;
+ CoreDeps.classes.Math2 = C.Math2;
+
+ // Parser-bound utils (these need runtime parser binding)
+ CoreDeps.utils.symfunction = _.symfunction.bind(_);
+ CoreDeps.utils.callfunction = _.callfunction.bind(_);
+
+ // Complete state references that point to IIFE-local arrays/objects
+ // Note: EXPRESSIONS, VARS are the same arrays initialized in CoreDeps.state earlier
+ // CONSTANTS is only available after Parser is created
+ CoreDeps.state.CONSTANTS = _.CONSTANTS;
+
+ // LibExports ===================================================================
+ // Create libExports using the factory function (now that CoreDeps is populated)
+ const libExports = createLibExports();
+
+ // Complete CoreDeps with libExports
+ CoreDeps.libExports = libExports;
+
+ // Attach all public API methods to libExports
+ attachLibExports(libExports);
+
+ libExports.updateAPI();
+
+ return libExports; // Done
+})();
+
+if (typeof module !== 'undefined') {
+ module.exports = nerdamer;
+}
--- /dev/null
+{
+ "type": "commonjs"
+}
--- /dev/null
+declare module 'virtual:nerdamer' {
+ const code: string;
+ export default code;
+}
--- /dev/null
+import { beforeEach, describe, expect, it } from 'vitest';
+import { SandboxService } from '$lib/services/sandbox.service';
+import { SANDBOX_TOOL_NAME } from '$lib/constants';
+
+const run = (code: string, timeoutMs?: number) =>
+ SandboxService.executeTool(SANDBOX_TOOL_NAME, {
+ code,
+ ...(timeoutMs !== undefined ? { timeout_ms: timeoutMs } : {})
+ });
+
+describe('sandbox service', () => {
+ beforeEach(async () => {
+ const { settingsStore } = await import('$lib/stores/settings.svelte');
+ settingsStore.config = {
+ ...settingsStore.config,
+ symbolicMathEnabled: true
+ };
+ });
+
+ it('executes plain JavaScript', async () => {
+ const reply = await run('return 1 + 1;');
+ expect(reply.isError).toBe(false);
+ expect(reply.content).toContain('=> 2');
+ });
+
+ it('exposes nerdamer for symbolic computation', async () => {
+ const reply = await run("return nerdamer.diff('sin(x)/x', 'x').toString();");
+ expect(reply.isError).toBe(false);
+ expect(reply.content).toContain('cos(x)');
+ });
+
+ it('computes exact rational arithmetic', async () => {
+ const reply = await run("return nerdamer('1/3 + 1/6').toString();");
+ expect(reply.isError).toBe(false);
+ expect(reply.content).toContain('=> 1/2');
+ });
+
+ it('proves a polynomial identity symbolically', async () => {
+ const reply = await run(
+ "return nerdamer('expand((1+x*y)^3 - (1 + 3*x*y + 3*x^2*y^2 + x^3*y^3))').toString();"
+ );
+ expect(reply.isError).toBe(false);
+ expect(reply.content).toContain('=> 0');
+ });
+
+ it('blocks network egress in the worker via CSP', async () => {
+ const reply = await run(
+ "try { await fetch('https://example.com/'); return 'leaked'; } catch { return 'blocked'; }"
+ );
+ expect(reply.isError).toBe(false);
+ expect(reply.content).toContain('=> blocked');
+ });
+
+ it('enforces the timeout on runaway code', async () => {
+ const reply = await run('while (true) {}', 500);
+ expect(reply.isError).toBe(true);
+ expect(reply.content).toContain('timed out');
+ });
+});
".storybook/**/*.ts",
".storybook/**/*.svelte"
],
- "exclude": ["src/lib/services/sandbox-worker.js"]
+ "exclude": ["src/lib/services/sandbox-worker.js", "src/lib/vendors/**"]
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
import { splashScreenPlugin } from './scripts/vite-plugin-splash-screen';
import { buildInfoPlugin } from './scripts/vite-plugin-build-info';
import { relativizeBasePlugin } from './scripts/vite-plugin-relativize-base';
+import { nerdamerPlugin } from './scripts/vite-plugin-nerdamer';
import { playwright } from '@vitest/browser-playwright';
import { SVELTEKIT_PWA_OPTIONS } from './src/lib/constants/pwa';
SvelteKitPWA(SVELTEKIT_PWA_OPTIONS),
splashScreenPlugin(),
buildInfoPlugin(),
+ nerdamerPlugin(),
relativizeBasePlugin()
],