diff --git a/tools/ui/.gitignore b/tools/ui/.gitignore index 0bb8c9b3c2..7cd35376e1 100644 --- a/tools/ui/.gitignore +++ b/tools/ui/.gitignore @@ -36,3 +36,7 @@ static/favicon* *storybook.log storybook-static *.code-workspace + +# Vitest browser mode failure artifacts +.vitest-attachments/ +tests/**/__screenshots__/ diff --git a/tools/ui/.prettierignore b/tools/ui/.prettierignore index 7bbdcf6a09..635cf99c7e 100644 --- a/tools/ui/.prettierignore +++ b/tools/ui/.prettierignore @@ -16,3 +16,6 @@ build/ /build/ /.svelte-kit/ test-results + +# Vendored third party sources, kept byte identical to upstream +src/lib/vendors/ diff --git a/tools/ui/eslint.config.js b/tools/ui/eslint.config.js index 54679a05be..fcbf7ee954 100644 --- a/tools/ui/eslint.config.js +++ b/tools/ui/eslint.config.js @@ -59,7 +59,8 @@ export default ts.config( '.svelte-kit/**', 'test-results/**', '.storybook/**/*', - 'src/lib/services/sandbox-worker.js' + 'src/lib/services/sandbox-worker.js', + 'src/lib/vendors/**' ] }, storybook.configs['flat/recommended'] diff --git a/tools/ui/scripts/vite-plugin-nerdamer.ts b/tools/ui/scripts/vite-plugin-nerdamer.ts new file mode 100644 index 0000000000..218c2fa233 --- /dev/null +++ b/tools/ui/scripts/vite-plugin-nerdamer.ts @@ -0,0 +1,49 @@ +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)};`; + } + }; +} diff --git a/tools/ui/src/lib/constants/sandbox.ts b/tools/ui/src/lib/constants/sandbox.ts index de49f05847..58242678d9 100644 --- a/tools/ui/src/lib/constants/sandbox.ts +++ b/tools/ui/src/lib/constants/sandbox.ts @@ -13,27 +13,44 @@ export const SANDBOX_EMPTY_OUTPUT = '(no output)'; 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); diff --git a/tools/ui/src/lib/constants/settings-keys.ts b/tools/ui/src/lib/constants/settings-keys.ts index fab343659d..265507a5b7 100644 --- a/tools/ui/src/lib/constants/settings-keys.ts +++ b/tools/ui/src/lib/constants/settings-keys.ts @@ -67,6 +67,7 @@ export const SETTINGS_KEYS = { 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' diff --git a/tools/ui/src/lib/constants/settings-registry.ts b/tools/ui/src/lib/constants/settings-registry.ts index f511b751de..742b5f5c67 100644 --- a/tools/ui/src/lib/constants/settings-registry.ts +++ b/tools/ui/src/lib/constants/settings-registry.ts @@ -724,6 +724,15 @@ const SETTINGS_REGISTRY: Record = { 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', diff --git a/tools/ui/src/lib/services/index.ts b/tools/ui/src/lib/services/index.ts index 386f740b8f..8704b1bd6c 100644 --- a/tools/ui/src/lib/services/index.ts +++ b/tools/ui/src/lib/services/index.ts @@ -276,7 +276,7 @@ export { MCPService } from './mcp.service'; * - **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'; diff --git a/tools/ui/src/lib/services/sandbox-harness.ts b/tools/ui/src/lib/services/sandbox-harness.ts index 27b05e24b4..40502121fb 100644 --- a/tools/ui/src/lib/services/sandbox-harness.ts +++ b/tools/ui/src/lib/services/sandbox-harness.ts @@ -1,14 +1,25 @@ +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 = ``; +} diff --git a/tools/ui/src/lib/services/sandbox-worker.js b/tools/ui/src/lib/services/sandbox-worker.js index 689b9211eb..838a97db19 100644 --- a/tools/ui/src/lib/services/sandbox-worker.js +++ b/tools/ui/src/lib/services/sandbox-worker.js @@ -21,7 +21,9 @@ self.onmessage = async (event) => { 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); diff --git a/tools/ui/src/lib/services/sandbox.service.ts b/tools/ui/src/lib/services/sandbox.service.ts index 36bf2d0203..f49a774a08 100644 --- a/tools/ui/src/lib/services/sandbox.service.ts +++ b/tools/ui/src/lib/services/sandbox.service.ts @@ -7,9 +7,32 @@ import { 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 = {}; + +/** + * 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 { + 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; @@ -45,20 +68,22 @@ export class SandboxService { * 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, signal?: AbortSignal ): Promise { 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 @@ -69,7 +94,7 @@ export class SandboxService { 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; diff --git a/tools/ui/src/lib/stores/tools.svelte.ts b/tools/ui/src/lib/stores/tools.svelte.ts index 53b6ad9ee7..bd69c4d30f 100644 --- a/tools/ui/src/lib/stores/tools.svelte.ts +++ b/tools/ui/src/lib/stores/tools.svelte.ts @@ -5,7 +5,7 @@ import { HealthCheckStatus, JsonSchemaType, ToolCallType, ToolSource } from '$li 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'; @@ -143,7 +143,9 @@ class ToolsStore { } get frontendTools(): OpenAIToolDefinition[] { - return config().jsSandboxEnabled ? [SANDBOX_TOOL_DEFINITION] : []; + return config().jsSandboxEnabled + ? [buildSandboxToolDefinition(!!config().symbolicMathEnabled)] + : []; } get customTools(): OpenAIToolDefinition[] { diff --git a/tools/ui/src/lib/vendors/big-integer/BigInteger.js b/tools/ui/src/lib/vendors/big-integer/BigInteger.js new file mode 100644 index 0000000000..87e43dfff5 --- /dev/null +++ b/tools/ui/src/lib/vendors/big-integer/BigInteger.js @@ -0,0 +1,1453 @@ +var bigInt = (function (undefined) { + "use strict"; + + var BASE = 1e7, + LOG_BASE = 7, + MAX_INT = 9007199254740992, + MAX_INT_ARR = smallToArray(MAX_INT), + DEFAULT_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz"; + + var supportsNativeBigInt = typeof BigInt === "function"; + + function Integer(v, radix, alphabet, caseSensitive) { + if (typeof v === "undefined") return Integer[0]; + if (typeof radix !== "undefined") return +radix === 10 && !alphabet ? parseValue(v) : parseBase(v, radix, alphabet, caseSensitive); + return parseValue(v); + } + + function BigInteger(value, sign) { + this.value = value; + this.sign = sign; + this.isSmall = false; + } + BigInteger.prototype = Object.create(Integer.prototype); + + function SmallInteger(value) { + this.value = value; + this.sign = value < 0; + this.isSmall = true; + } + SmallInteger.prototype = Object.create(Integer.prototype); + + function NativeBigInt(value) { + this.value = value; + } + NativeBigInt.prototype = Object.create(Integer.prototype); + + function isPrecise(n) { + return -MAX_INT < n && n < MAX_INT; + } + + function smallToArray(n) { // For performance reasons doesn't reference BASE, need to change this function if BASE changes + if (n < 1e7) + return [n]; + if (n < 1e14) + return [n % 1e7, Math.floor(n / 1e7)]; + return [n % 1e7, Math.floor(n / 1e7) % 1e7, Math.floor(n / 1e14)]; + } + + function arrayToSmall(arr) { // If BASE changes this function may need to change + trim(arr); + var length = arr.length; + if (length < 4 && compareAbs(arr, MAX_INT_ARR) < 0) { + switch (length) { + case 0: return 0; + case 1: return arr[0]; + case 2: return arr[0] + arr[1] * BASE; + default: return arr[0] + (arr[1] + arr[2] * BASE) * BASE; + } + } + return arr; + } + + function trim(v) { + var i = v.length; + while (v[--i] === 0); + v.length = i + 1; + } + + function createArray(length) { // function shamelessly stolen from Yaffle's library https://github.com/Yaffle/BigInteger + var x = new Array(length); + var i = -1; + while (++i < length) { + x[i] = 0; + } + return x; + } + + function truncate(n) { + if (n > 0) return Math.floor(n); + return Math.ceil(n); + } + + function add(a, b) { // assumes a and b are arrays with a.length >= b.length + var l_a = a.length, + l_b = b.length, + r = new Array(l_a), + carry = 0, + base = BASE, + sum, i; + for (i = 0; i < l_b; i++) { + sum = a[i] + b[i] + carry; + carry = sum >= base ? 1 : 0; + r[i] = sum - carry * base; + } + while (i < l_a) { + sum = a[i] + carry; + carry = sum === base ? 1 : 0; + r[i++] = sum - carry * base; + } + if (carry > 0) r.push(carry); + return r; + } + + function addAny(a, b) { + if (a.length >= b.length) return add(a, b); + return add(b, a); + } + + function addSmall(a, carry) { // assumes a is array, carry is number with 0 <= carry < MAX_INT + var l = a.length, + r = new Array(l), + base = BASE, + sum, i; + for (i = 0; i < l; i++) { + sum = a[i] - base + carry; + carry = Math.floor(sum / base); + r[i] = sum - carry * base; + carry += 1; + } + while (carry > 0) { + r[i++] = carry % base; + carry = Math.floor(carry / base); + } + return r; + } + + BigInteger.prototype.add = function (v) { + var n = parseValue(v); + if (this.sign !== n.sign) { + return this.subtract(n.negate()); + } + var a = this.value, b = n.value; + if (n.isSmall) { + return new BigInteger(addSmall(a, Math.abs(b)), this.sign); + } + return new BigInteger(addAny(a, b), this.sign); + }; + BigInteger.prototype.plus = BigInteger.prototype.add; + + SmallInteger.prototype.add = function (v) { + var n = parseValue(v); + var a = this.value; + if (a < 0 !== n.sign) { + return this.subtract(n.negate()); + } + var b = n.value; + if (n.isSmall) { + if (isPrecise(a + b)) return new SmallInteger(a + b); + b = smallToArray(Math.abs(b)); + } + return new BigInteger(addSmall(b, Math.abs(a)), a < 0); + }; + SmallInteger.prototype.plus = SmallInteger.prototype.add; + + NativeBigInt.prototype.add = function (v) { + return new NativeBigInt(this.value + parseValue(v).value); + } + NativeBigInt.prototype.plus = NativeBigInt.prototype.add; + + function subtract(a, b) { // assumes a and b are arrays with a >= b + var a_l = a.length, + b_l = b.length, + r = new Array(a_l), + borrow = 0, + base = BASE, + i, difference; + for (i = 0; i < b_l; i++) { + difference = a[i] - borrow - b[i]; + if (difference < 0) { + difference += base; + borrow = 1; + } else borrow = 0; + r[i] = difference; + } + for (i = b_l; i < a_l; i++) { + difference = a[i] - borrow; + if (difference < 0) difference += base; + else { + r[i++] = difference; + break; + } + r[i] = difference; + } + for (; i < a_l; i++) { + r[i] = a[i]; + } + trim(r); + return r; + } + + function subtractAny(a, b, sign) { + var value; + if (compareAbs(a, b) >= 0) { + value = subtract(a, b); + } else { + value = subtract(b, a); + sign = !sign; + } + value = arrayToSmall(value); + if (typeof value === "number") { + if (sign) value = -value; + return new SmallInteger(value); + } + return new BigInteger(value, sign); + } + + function subtractSmall(a, b, sign) { // assumes a is array, b is number with 0 <= b < MAX_INT + var l = a.length, + r = new Array(l), + carry = -b, + base = BASE, + i, difference; + for (i = 0; i < l; i++) { + difference = a[i] + carry; + carry = Math.floor(difference / base); + difference %= base; + r[i] = difference < 0 ? difference + base : difference; + } + r = arrayToSmall(r); + if (typeof r === "number") { + if (sign) r = -r; + return new SmallInteger(r); + } return new BigInteger(r, sign); + } + + BigInteger.prototype.subtract = function (v) { + var n = parseValue(v); + if (this.sign !== n.sign) { + return this.add(n.negate()); + } + var a = this.value, b = n.value; + if (n.isSmall) + return subtractSmall(a, Math.abs(b), this.sign); + return subtractAny(a, b, this.sign); + }; + BigInteger.prototype.minus = BigInteger.prototype.subtract; + + SmallInteger.prototype.subtract = function (v) { + var n = parseValue(v); + var a = this.value; + if (a < 0 !== n.sign) { + return this.add(n.negate()); + } + var b = n.value; + if (n.isSmall) { + return new SmallInteger(a - b); + } + return subtractSmall(b, Math.abs(a), a >= 0); + }; + SmallInteger.prototype.minus = SmallInteger.prototype.subtract; + + NativeBigInt.prototype.subtract = function (v) { + return new NativeBigInt(this.value - parseValue(v).value); + } + NativeBigInt.prototype.minus = NativeBigInt.prototype.subtract; + + BigInteger.prototype.negate = function () { + return new BigInteger(this.value, !this.sign); + }; + SmallInteger.prototype.negate = function () { + var sign = this.sign; + var small = new SmallInteger(-this.value); + small.sign = !sign; + return small; + }; + NativeBigInt.prototype.negate = function () { + return new NativeBigInt(-this.value); + } + + BigInteger.prototype.abs = function () { + return new BigInteger(this.value, false); + }; + SmallInteger.prototype.abs = function () { + return new SmallInteger(Math.abs(this.value)); + }; + NativeBigInt.prototype.abs = function () { + return new NativeBigInt(this.value >= 0 ? this.value : -this.value); + } + + + function multiplyLong(a, b) { + var a_l = a.length, + b_l = b.length, + l = a_l + b_l, + r = createArray(l), + base = BASE, + product, carry, i, a_i, b_j; + for (i = 0; i < a_l; ++i) { + a_i = a[i]; + for (var j = 0; j < b_l; ++j) { + b_j = b[j]; + product = a_i * b_j + r[i + j]; + carry = Math.floor(product / base); + r[i + j] = product - carry * base; + r[i + j + 1] += carry; + } + } + trim(r); + return r; + } + + function multiplySmall(a, b) { // assumes a is array, b is number with |b| < BASE + var l = a.length, + r = new Array(l), + base = BASE, + carry = 0, + product, i; + for (i = 0; i < l; i++) { + product = a[i] * b + carry; + carry = Math.floor(product / base); + r[i] = product - carry * base; + } + while (carry > 0) { + r[i++] = carry % base; + carry = Math.floor(carry / base); + } + return r; + } + + function shiftLeft(x, n) { + var r = []; + while (n-- > 0) r.push(0); + return r.concat(x); + } + + function multiplyKaratsuba(x, y) { + var n = Math.max(x.length, y.length); + + if (n <= 30) return multiplyLong(x, y); + n = Math.ceil(n / 2); + + var b = x.slice(n), + a = x.slice(0, n), + d = y.slice(n), + c = y.slice(0, n); + + var ac = multiplyKaratsuba(a, c), + bd = multiplyKaratsuba(b, d), + abcd = multiplyKaratsuba(addAny(a, b), addAny(c, d)); + + var product = addAny(addAny(ac, shiftLeft(subtract(subtract(abcd, ac), bd), n)), shiftLeft(bd, 2 * n)); + trim(product); + return product; + } + + // The following function is derived from a surface fit of a graph plotting the performance difference + // between long multiplication and karatsuba multiplication versus the lengths of the two arrays. + function useKaratsuba(l1, l2) { + return -0.012 * l1 - 0.012 * l2 + 0.000015 * l1 * l2 > 0; + } + + BigInteger.prototype.multiply = function (v) { + var n = parseValue(v), + a = this.value, b = n.value, + sign = this.sign !== n.sign, + abs; + if (n.isSmall) { + if (b === 0) return Integer[0]; + if (b === 1) return this; + if (b === -1) return this.negate(); + abs = Math.abs(b); + if (abs < BASE) { + return new BigInteger(multiplySmall(a, abs), sign); + } + b = smallToArray(abs); + } + if (useKaratsuba(a.length, b.length)) // Karatsuba is only faster for certain array sizes + return new BigInteger(multiplyKaratsuba(a, b), sign); + return new BigInteger(multiplyLong(a, b), sign); + }; + + BigInteger.prototype.times = BigInteger.prototype.multiply; + + function multiplySmallAndArray(a, b, sign) { // a >= 0 + if (a < BASE) { + return new BigInteger(multiplySmall(b, a), sign); + } + return new BigInteger(multiplyLong(b, smallToArray(a)), sign); + } + SmallInteger.prototype._multiplyBySmall = function (a) { + if (isPrecise(a.value * this.value)) { + return new SmallInteger(a.value * this.value); + } + return multiplySmallAndArray(Math.abs(a.value), smallToArray(Math.abs(this.value)), this.sign !== a.sign); + }; + BigInteger.prototype._multiplyBySmall = function (a) { + if (a.value === 0) return Integer[0]; + if (a.value === 1) return this; + if (a.value === -1) return this.negate(); + return multiplySmallAndArray(Math.abs(a.value), this.value, this.sign !== a.sign); + }; + SmallInteger.prototype.multiply = function (v) { + return parseValue(v)._multiplyBySmall(this); + }; + SmallInteger.prototype.times = SmallInteger.prototype.multiply; + + NativeBigInt.prototype.multiply = function (v) { + return new NativeBigInt(this.value * parseValue(v).value); + } + NativeBigInt.prototype.times = NativeBigInt.prototype.multiply; + + function square(a) { + //console.assert(2 * BASE * BASE < MAX_INT); + var l = a.length, + r = createArray(l + l), + base = BASE, + product, carry, i, a_i, a_j; + for (i = 0; i < l; i++) { + a_i = a[i]; + carry = 0 - a_i * a_i; + for (var j = i; j < l; j++) { + a_j = a[j]; + product = 2 * (a_i * a_j) + r[i + j] + carry; + carry = Math.floor(product / base); + r[i + j] = product - carry * base; + } + r[i + l] = carry; + } + trim(r); + return r; + } + + BigInteger.prototype.square = function () { + return new BigInteger(square(this.value), false); + }; + + SmallInteger.prototype.square = function () { + var value = this.value * this.value; + if (isPrecise(value)) return new SmallInteger(value); + return new BigInteger(square(smallToArray(Math.abs(this.value))), false); + }; + + NativeBigInt.prototype.square = function (v) { + return new NativeBigInt(this.value * this.value); + } + + function divMod1(a, b) { // Left over from previous version. Performs faster than divMod2 on smaller input sizes. + var a_l = a.length, + b_l = b.length, + base = BASE, + result = createArray(b.length), + divisorMostSignificantDigit = b[b_l - 1], + // normalization + lambda = Math.ceil(base / (2 * divisorMostSignificantDigit)), + remainder = multiplySmall(a, lambda), + divisor = multiplySmall(b, lambda), + quotientDigit, shift, carry, borrow, i, l, q; + if (remainder.length <= a_l) remainder.push(0); + divisor.push(0); + divisorMostSignificantDigit = divisor[b_l - 1]; + for (shift = a_l - b_l; shift >= 0; shift--) { + quotientDigit = base - 1; + if (remainder[shift + b_l] !== divisorMostSignificantDigit) { + quotientDigit = Math.floor((remainder[shift + b_l] * base + remainder[shift + b_l - 1]) / divisorMostSignificantDigit); + } + // quotientDigit <= base - 1 + carry = 0; + borrow = 0; + l = divisor.length; + for (i = 0; i < l; i++) { + carry += quotientDigit * divisor[i]; + q = Math.floor(carry / base); + borrow += remainder[shift + i] - (carry - q * base); + carry = q; + if (borrow < 0) { + remainder[shift + i] = borrow + base; + borrow = -1; + } else { + remainder[shift + i] = borrow; + borrow = 0; + } + } + while (borrow !== 0) { + quotientDigit -= 1; + carry = 0; + for (i = 0; i < l; i++) { + carry += remainder[shift + i] - base + divisor[i]; + if (carry < 0) { + remainder[shift + i] = carry + base; + carry = 0; + } else { + remainder[shift + i] = carry; + carry = 1; + } + } + borrow += carry; + } + result[shift] = quotientDigit; + } + // denormalization + remainder = divModSmall(remainder, lambda)[0]; + return [arrayToSmall(result), arrayToSmall(remainder)]; + } + + function divMod2(a, b) { // Implementation idea shamelessly stolen from Silent Matt's library http://silentmatt.com/biginteger/ + // Performs faster than divMod1 on larger input sizes. + var a_l = a.length, + b_l = b.length, + result = [], + part = [], + base = BASE, + guess, xlen, highx, highy, check; + while (a_l) { + part.unshift(a[--a_l]); + trim(part); + if (compareAbs(part, b) < 0) { + result.push(0); + continue; + } + xlen = part.length; + highx = part[xlen - 1] * base + part[xlen - 2]; + highy = b[b_l - 1] * base + b[b_l - 2]; + if (xlen > b_l) { + highx = (highx + 1) * base; + } + guess = Math.ceil(highx / highy); + do { + check = multiplySmall(b, guess); + if (compareAbs(check, part) <= 0) break; + guess--; + } while (guess); + result.push(guess); + part = subtract(part, check); + } + result.reverse(); + return [arrayToSmall(result), arrayToSmall(part)]; + } + + function divModSmall(value, lambda) { + var length = value.length, + quotient = createArray(length), + base = BASE, + i, q, remainder, divisor; + remainder = 0; + for (i = length - 1; i >= 0; --i) { + divisor = remainder * base + value[i]; + q = truncate(divisor / lambda); + remainder = divisor - q * lambda; + quotient[i] = q | 0; + } + return [quotient, remainder | 0]; + } + + function divModAny(self, v) { + var value, n = parseValue(v); + if (supportsNativeBigInt) { + return [new NativeBigInt(self.value / n.value), new NativeBigInt(self.value % n.value)]; + } + var a = self.value, b = n.value; + var quotient; + if (b === 0) throw new Error("Cannot divide by zero"); + if (self.isSmall) { + if (n.isSmall) { + return [new SmallInteger(truncate(a / b)), new SmallInteger(a % b)]; + } + return [Integer[0], self]; + } + if (n.isSmall) { + if (b === 1) return [self, Integer[0]]; + if (b == -1) return [self.negate(), Integer[0]]; + var abs = Math.abs(b); + if (abs < BASE) { + value = divModSmall(a, abs); + quotient = arrayToSmall(value[0]); + var remainder = value[1]; + if (self.sign) remainder = -remainder; + if (typeof quotient === "number") { + if (self.sign !== n.sign) quotient = -quotient; + return [new SmallInteger(quotient), new SmallInteger(remainder)]; + } + return [new BigInteger(quotient, self.sign !== n.sign), new SmallInteger(remainder)]; + } + b = smallToArray(abs); + } + var comparison = compareAbs(a, b); + if (comparison === -1) return [Integer[0], self]; + if (comparison === 0) return [Integer[self.sign === n.sign ? 1 : -1], Integer[0]]; + + // divMod1 is faster on smaller input sizes + if (a.length + b.length <= 200) + value = divMod1(a, b); + else value = divMod2(a, b); + + quotient = value[0]; + var qSign = self.sign !== n.sign, + mod = value[1], + mSign = self.sign; + if (typeof quotient === "number") { + if (qSign) quotient = -quotient; + quotient = new SmallInteger(quotient); + } else quotient = new BigInteger(quotient, qSign); + if (typeof mod === "number") { + if (mSign) mod = -mod; + mod = new SmallInteger(mod); + } else mod = new BigInteger(mod, mSign); + return [quotient, mod]; + } + + BigInteger.prototype.divmod = function (v) { + var result = divModAny(this, v); + return { + quotient: result[0], + remainder: result[1] + }; + }; + NativeBigInt.prototype.divmod = SmallInteger.prototype.divmod = BigInteger.prototype.divmod; + + + BigInteger.prototype.divide = function (v) { + return divModAny(this, v)[0]; + }; + NativeBigInt.prototype.over = NativeBigInt.prototype.divide = function (v) { + return new NativeBigInt(this.value / parseValue(v).value); + }; + SmallInteger.prototype.over = SmallInteger.prototype.divide = BigInteger.prototype.over = BigInteger.prototype.divide; + + BigInteger.prototype.mod = function (v) { + return divModAny(this, v)[1]; + }; + NativeBigInt.prototype.mod = NativeBigInt.prototype.remainder = function (v) { + return new NativeBigInt(this.value % parseValue(v).value); + }; + SmallInteger.prototype.remainder = SmallInteger.prototype.mod = BigInteger.prototype.remainder = BigInteger.prototype.mod; + + BigInteger.prototype.pow = function (v) { + var n = parseValue(v), + a = this.value, + b = n.value, + value, x, y; + if (b === 0) return Integer[1]; + if (a === 0) return Integer[0]; + if (a === 1) return Integer[1]; + if (a === -1) return n.isEven() ? Integer[1] : Integer[-1]; + if (n.sign) { + return Integer[0]; + } + if (!n.isSmall) throw new Error("The exponent " + n.toString() + " is too large."); + if (this.isSmall) { + if (isPrecise(value = Math.pow(a, b))) + return new SmallInteger(truncate(value)); + } + x = this; + y = Integer[1]; + while (true) { + if (b & 1 === 1) { + y = y.times(x); + --b; + } + if (b === 0) break; + b /= 2; + x = x.square(); + } + return y; + }; + SmallInteger.prototype.pow = BigInteger.prototype.pow; + + NativeBigInt.prototype.pow = function (v) { + var n = parseValue(v); + var a = this.value, b = n.value; + var _0 = BigInt(0), _1 = BigInt(1), _2 = BigInt(2); + if (b === _0) return Integer[1]; + if (a === _0) return Integer[0]; + if (a === _1) return Integer[1]; + if (a === BigInt(-1)) return n.isEven() ? Integer[1] : Integer[-1]; + if (n.isNegative()) return new NativeBigInt(_0); + var x = this; + var y = Integer[1]; + while (true) { + if ((b & _1) === _1) { + y = y.times(x); + --b; + } + if (b === _0) break; + b /= _2; + x = x.square(); + } + return y; + } + + BigInteger.prototype.modPow = function (exp, mod) { + exp = parseValue(exp); + mod = parseValue(mod); + if (mod.isZero()) throw new Error("Cannot take modPow with modulus 0"); + var r = Integer[1], + base = this.mod(mod); + if (exp.isNegative()) { + exp = exp.multiply(Integer[-1]); + base = base.modInv(mod); + } + while (exp.isPositive()) { + if (base.isZero()) return Integer[0]; + if (exp.isOdd()) r = r.multiply(base).mod(mod); + exp = exp.divide(2); + base = base.square().mod(mod); + } + return r; + }; + NativeBigInt.prototype.modPow = SmallInteger.prototype.modPow = BigInteger.prototype.modPow; + + function compareAbs(a, b) { + if (a.length !== b.length) { + return a.length > b.length ? 1 : -1; + } + for (var i = a.length - 1; i >= 0; i--) { + if (a[i] !== b[i]) return a[i] > b[i] ? 1 : -1; + } + return 0; + } + + BigInteger.prototype.compareAbs = function (v) { + var n = parseValue(v), + a = this.value, + b = n.value; + if (n.isSmall) return 1; + return compareAbs(a, b); + }; + SmallInteger.prototype.compareAbs = function (v) { + var n = parseValue(v), + a = Math.abs(this.value), + b = n.value; + if (n.isSmall) { + b = Math.abs(b); + return a === b ? 0 : a > b ? 1 : -1; + } + return -1; + }; + NativeBigInt.prototype.compareAbs = function (v) { + var a = this.value; + var b = parseValue(v).value; + a = a >= 0 ? a : -a; + b = b >= 0 ? b : -b; + return a === b ? 0 : a > b ? 1 : -1; + } + + BigInteger.prototype.compare = function (v) { + // See discussion about comparison with Infinity: + // https://github.com/peterolson/BigInteger.js/issues/61 + if (v === Infinity) { + return -1; + } + if (v === -Infinity) { + return 1; + } + + var n = parseValue(v), + a = this.value, + b = n.value; + if (this.sign !== n.sign) { + return n.sign ? 1 : -1; + } + if (n.isSmall) { + return this.sign ? -1 : 1; + } + return compareAbs(a, b) * (this.sign ? -1 : 1); + }; + BigInteger.prototype.compareTo = BigInteger.prototype.compare; + + SmallInteger.prototype.compare = function (v) { + if (v === Infinity) { + return -1; + } + if (v === -Infinity) { + return 1; + } + + var n = parseValue(v), + a = this.value, + b = n.value; + if (n.isSmall) { + return a == b ? 0 : a > b ? 1 : -1; + } + if (a < 0 !== n.sign) { + return a < 0 ? -1 : 1; + } + return a < 0 ? 1 : -1; + }; + SmallInteger.prototype.compareTo = SmallInteger.prototype.compare; + + NativeBigInt.prototype.compare = function (v) { + if (v === Infinity) { + return -1; + } + if (v === -Infinity) { + return 1; + } + var a = this.value; + var b = parseValue(v).value; + return a === b ? 0 : a > b ? 1 : -1; + } + NativeBigInt.prototype.compareTo = NativeBigInt.prototype.compare; + + BigInteger.prototype.equals = function (v) { + return this.compare(v) === 0; + }; + NativeBigInt.prototype.eq = NativeBigInt.prototype.equals = SmallInteger.prototype.eq = SmallInteger.prototype.equals = BigInteger.prototype.eq = BigInteger.prototype.equals; + + BigInteger.prototype.notEquals = function (v) { + return this.compare(v) !== 0; + }; + NativeBigInt.prototype.neq = NativeBigInt.prototype.notEquals = SmallInteger.prototype.neq = SmallInteger.prototype.notEquals = BigInteger.prototype.neq = BigInteger.prototype.notEquals; + + BigInteger.prototype.greater = function (v) { + return this.compare(v) > 0; + }; + NativeBigInt.prototype.gt = NativeBigInt.prototype.greater = SmallInteger.prototype.gt = SmallInteger.prototype.greater = BigInteger.prototype.gt = BigInteger.prototype.greater; + + BigInteger.prototype.lesser = function (v) { + return this.compare(v) < 0; + }; + NativeBigInt.prototype.lt = NativeBigInt.prototype.lesser = SmallInteger.prototype.lt = SmallInteger.prototype.lesser = BigInteger.prototype.lt = BigInteger.prototype.lesser; + + BigInteger.prototype.greaterOrEquals = function (v) { + return this.compare(v) >= 0; + }; + NativeBigInt.prototype.geq = NativeBigInt.prototype.greaterOrEquals = SmallInteger.prototype.geq = SmallInteger.prototype.greaterOrEquals = BigInteger.prototype.geq = BigInteger.prototype.greaterOrEquals; + + BigInteger.prototype.lesserOrEquals = function (v) { + return this.compare(v) <= 0; + }; + NativeBigInt.prototype.leq = NativeBigInt.prototype.lesserOrEquals = SmallInteger.prototype.leq = SmallInteger.prototype.lesserOrEquals = BigInteger.prototype.leq = BigInteger.prototype.lesserOrEquals; + + BigInteger.prototype.isEven = function () { + return (this.value[0] & 1) === 0; + }; + SmallInteger.prototype.isEven = function () { + return (this.value & 1) === 0; + }; + NativeBigInt.prototype.isEven = function () { + return (this.value & BigInt(1)) === BigInt(0); + } + + BigInteger.prototype.isOdd = function () { + return (this.value[0] & 1) === 1; + }; + SmallInteger.prototype.isOdd = function () { + return (this.value & 1) === 1; + }; + NativeBigInt.prototype.isOdd = function () { + return (this.value & BigInt(1)) === BigInt(1); + } + + BigInteger.prototype.isPositive = function () { + return !this.sign; + }; + SmallInteger.prototype.isPositive = function () { + return this.value > 0; + }; + NativeBigInt.prototype.isPositive = SmallInteger.prototype.isPositive; + + BigInteger.prototype.isNegative = function () { + return this.sign; + }; + SmallInteger.prototype.isNegative = function () { + return this.value < 0; + }; + NativeBigInt.prototype.isNegative = SmallInteger.prototype.isNegative; + + BigInteger.prototype.isUnit = function () { + return false; + }; + SmallInteger.prototype.isUnit = function () { + return Math.abs(this.value) === 1; + }; + NativeBigInt.prototype.isUnit = function () { + return this.abs().value === BigInt(1); + } + + BigInteger.prototype.isZero = function () { + return false; + }; + SmallInteger.prototype.isZero = function () { + return this.value === 0; + }; + NativeBigInt.prototype.isZero = function () { + return this.value === BigInt(0); + } + + BigInteger.prototype.isDivisibleBy = function (v) { + var n = parseValue(v); + if (n.isZero()) return false; + if (n.isUnit()) return true; + if (n.compareAbs(2) === 0) return this.isEven(); + return this.mod(n).isZero(); + }; + NativeBigInt.prototype.isDivisibleBy = SmallInteger.prototype.isDivisibleBy = BigInteger.prototype.isDivisibleBy; + + function isBasicPrime(v) { + var n = v.abs(); + if (n.isUnit()) return false; + if (n.equals(2) || n.equals(3) || n.equals(5)) return true; + if (n.isEven() || n.isDivisibleBy(3) || n.isDivisibleBy(5)) return false; + if (n.lesser(49)) return true; + // we don't know if it's prime: let the other functions figure it out + } + + function millerRabinTest(n, a) { + var nPrev = n.prev(), + b = nPrev, + r = 0, + d, t, i, x; + while (b.isEven()) b = b.divide(2), r++; + next: for (i = 0; i < a.length; i++) { + if (n.lesser(a[i])) continue; + x = bigInt(a[i]).modPow(b, n); + if (x.isUnit() || x.equals(nPrev)) continue; + for (d = r - 1; d != 0; d--) { + x = x.square().mod(n); + if (x.isUnit()) return false; + if (x.equals(nPrev)) continue next; + } + return false; + } + return true; + } + + // Set "strict" to true to force GRH-supported lower bound of 2*log(N)^2 + BigInteger.prototype.isPrime = function (strict) { + var isPrime = isBasicPrime(this); + if (isPrime !== undefined) return isPrime; + var n = this.abs(); + var bits = n.bitLength(); + if (bits <= 64) + return millerRabinTest(n, [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]); + var logN = Math.log(2) * bits.toJSNumber(); + var t = Math.ceil((strict === true) ? (2 * Math.pow(logN, 2)) : logN); + for (var a = [], i = 0; i < t; i++) { + a.push(bigInt(i + 2)); + } + return millerRabinTest(n, a); + }; + NativeBigInt.prototype.isPrime = SmallInteger.prototype.isPrime = BigInteger.prototype.isPrime; + + BigInteger.prototype.isProbablePrime = function (iterations, rng) { + var isPrime = isBasicPrime(this); + if (isPrime !== undefined) return isPrime; + var n = this.abs(); + var t = iterations === undefined ? 5 : iterations; + for (var a = [], i = 0; i < t; i++) { + a.push(bigInt.randBetween(2, n.minus(2), rng)); + } + return millerRabinTest(n, a); + }; + NativeBigInt.prototype.isProbablePrime = SmallInteger.prototype.isProbablePrime = BigInteger.prototype.isProbablePrime; + + BigInteger.prototype.modInv = function (n) { + var t = bigInt.zero, newT = bigInt.one, r = parseValue(n), newR = this.abs(), q, lastT, lastR; + while (!newR.isZero()) { + q = r.divide(newR); + lastT = t; + lastR = r; + t = newT; + r = newR; + newT = lastT.subtract(q.multiply(newT)); + newR = lastR.subtract(q.multiply(newR)); + } + if (!r.isUnit()) throw new Error(this.toString() + " and " + n.toString() + " are not co-prime"); + if (t.compare(0) === -1) { + t = t.add(n); + } + if (this.isNegative()) { + return t.negate(); + } + return t; + }; + + NativeBigInt.prototype.modInv = SmallInteger.prototype.modInv = BigInteger.prototype.modInv; + + BigInteger.prototype.next = function () { + var value = this.value; + if (this.sign) { + return subtractSmall(value, 1, this.sign); + } + return new BigInteger(addSmall(value, 1), this.sign); + }; + SmallInteger.prototype.next = function () { + var value = this.value; + if (value + 1 < MAX_INT) return new SmallInteger(value + 1); + return new BigInteger(MAX_INT_ARR, false); + }; + NativeBigInt.prototype.next = function () { + return new NativeBigInt(this.value + BigInt(1)); + } + + BigInteger.prototype.prev = function () { + var value = this.value; + if (this.sign) { + return new BigInteger(addSmall(value, 1), true); + } + return subtractSmall(value, 1, this.sign); + }; + SmallInteger.prototype.prev = function () { + var value = this.value; + if (value - 1 > -MAX_INT) return new SmallInteger(value - 1); + return new BigInteger(MAX_INT_ARR, true); + }; + NativeBigInt.prototype.prev = function () { + return new NativeBigInt(this.value - BigInt(1)); + } + + var powersOfTwo = [1]; + while (2 * powersOfTwo[powersOfTwo.length - 1] <= BASE) powersOfTwo.push(2 * powersOfTwo[powersOfTwo.length - 1]); + var powers2Length = powersOfTwo.length, highestPower2 = powersOfTwo[powers2Length - 1]; + + function shift_isSmall(n) { + return Math.abs(n) <= BASE; + } + + BigInteger.prototype.shiftLeft = function (v) { + var n = parseValue(v).toJSNumber(); + if (!shift_isSmall(n)) { + throw new Error(String(n) + " is too large for shifting."); + } + if (n < 0) return this.shiftRight(-n); + var result = this; + if (result.isZero()) return result; + while (n >= powers2Length) { + result = result.multiply(highestPower2); + n -= powers2Length - 1; + } + return result.multiply(powersOfTwo[n]); + }; + NativeBigInt.prototype.shiftLeft = SmallInteger.prototype.shiftLeft = BigInteger.prototype.shiftLeft; + + BigInteger.prototype.shiftRight = function (v) { + var remQuo; + var n = parseValue(v).toJSNumber(); + if (!shift_isSmall(n)) { + throw new Error(String(n) + " is too large for shifting."); + } + if (n < 0) return this.shiftLeft(-n); + var result = this; + while (n >= powers2Length) { + if (result.isZero() || (result.isNegative() && result.isUnit())) return result; + remQuo = divModAny(result, highestPower2); + result = remQuo[1].isNegative() ? remQuo[0].prev() : remQuo[0]; + n -= powers2Length - 1; + } + remQuo = divModAny(result, powersOfTwo[n]); + return remQuo[1].isNegative() ? remQuo[0].prev() : remQuo[0]; + }; + NativeBigInt.prototype.shiftRight = SmallInteger.prototype.shiftRight = BigInteger.prototype.shiftRight; + + function bitwise(x, y, fn) { + y = parseValue(y); + var xSign = x.isNegative(), ySign = y.isNegative(); + var xRem = xSign ? x.not() : x, + yRem = ySign ? y.not() : y; + var xDigit = 0, yDigit = 0; + var xDivMod = null, yDivMod = null; + var result = []; + while (!xRem.isZero() || !yRem.isZero()) { + xDivMod = divModAny(xRem, highestPower2); + xDigit = xDivMod[1].toJSNumber(); + if (xSign) { + xDigit = highestPower2 - 1 - xDigit; // two's complement for negative numbers + } + + yDivMod = divModAny(yRem, highestPower2); + yDigit = yDivMod[1].toJSNumber(); + if (ySign) { + yDigit = highestPower2 - 1 - yDigit; // two's complement for negative numbers + } + + xRem = xDivMod[0]; + yRem = yDivMod[0]; + result.push(fn(xDigit, yDigit)); + } + var sum = fn(xSign ? 1 : 0, ySign ? 1 : 0) !== 0 ? bigInt(-1) : bigInt(0); + for (var i = result.length - 1; i >= 0; i -= 1) { + sum = sum.multiply(highestPower2).add(bigInt(result[i])); + } + return sum; + } + + BigInteger.prototype.not = function () { + return this.negate().prev(); + }; + NativeBigInt.prototype.not = SmallInteger.prototype.not = BigInteger.prototype.not; + + BigInteger.prototype.and = function (n) { + return bitwise(this, n, function (a, b) { return a & b; }); + }; + NativeBigInt.prototype.and = SmallInteger.prototype.and = BigInteger.prototype.and; + + BigInteger.prototype.or = function (n) { + return bitwise(this, n, function (a, b) { return a | b; }); + }; + NativeBigInt.prototype.or = SmallInteger.prototype.or = BigInteger.prototype.or; + + BigInteger.prototype.xor = function (n) { + return bitwise(this, n, function (a, b) { return a ^ b; }); + }; + NativeBigInt.prototype.xor = SmallInteger.prototype.xor = BigInteger.prototype.xor; + + var LOBMASK_I = 1 << 30, LOBMASK_BI = (BASE & -BASE) * (BASE & -BASE) | LOBMASK_I; + function roughLOB(n) { // get lowestOneBit (rough) + // SmallInteger: return Min(lowestOneBit(n), 1 << 30) + // BigInteger: return Min(lowestOneBit(n), 1 << 14) [BASE=1e7] + var v = n.value, + x = typeof v === "number" ? v | LOBMASK_I : + typeof v === "bigint" ? v | BigInt(LOBMASK_I) : + v[0] + v[1] * BASE | LOBMASK_BI; + return x & -x; + } + + function integerLogarithm(value, base) { + if (base.compareTo(value) <= 0) { + var tmp = integerLogarithm(value, base.square(base)); + var p = tmp.p; + var e = tmp.e; + var t = p.multiply(base); + return t.compareTo(value) <= 0 ? { p: t, e: e * 2 + 1 } : { p: p, e: e * 2 }; + } + return { p: bigInt(1), e: 0 }; + } + + BigInteger.prototype.bitLength = function () { + var n = this; + if (n.compareTo(bigInt(0)) < 0) { + n = n.negate().subtract(bigInt(1)); + } + if (n.compareTo(bigInt(0)) === 0) { + return bigInt(0); + } + return bigInt(integerLogarithm(n, bigInt(2)).e).add(bigInt(1)); + } + NativeBigInt.prototype.bitLength = SmallInteger.prototype.bitLength = BigInteger.prototype.bitLength; + + function max(a, b) { + a = parseValue(a); + b = parseValue(b); + return a.greater(b) ? a : b; + } + function min(a, b) { + a = parseValue(a); + b = parseValue(b); + return a.lesser(b) ? a : b; + } + function gcd(a, b) { + a = parseValue(a).abs(); + b = parseValue(b).abs(); + if (a.equals(b)) return a; + if (a.isZero()) return b; + if (b.isZero()) return a; + var c = Integer[1], d, t; + while (a.isEven() && b.isEven()) { + d = min(roughLOB(a), roughLOB(b)); + a = a.divide(d); + b = b.divide(d); + c = c.multiply(d); + } + while (a.isEven()) { + a = a.divide(roughLOB(a)); + } + do { + while (b.isEven()) { + b = b.divide(roughLOB(b)); + } + if (a.greater(b)) { + t = b; b = a; a = t; + } + b = b.subtract(a); + } while (!b.isZero()); + return c.isUnit() ? a : a.multiply(c); + } + function lcm(a, b) { + a = parseValue(a).abs(); + b = parseValue(b).abs(); + return a.divide(gcd(a, b)).multiply(b); + } + function randBetween(a, b, rng) { + a = parseValue(a); + b = parseValue(b); + var usedRNG = rng || Math.random; + var low = min(a, b), high = max(a, b); + var range = high.subtract(low).add(1); + if (range.isSmall) return low.add(Math.floor(usedRNG() * range)); + var digits = toBase(range, BASE).value; + var result = [], restricted = true; + for (var i = 0; i < digits.length; i++) { + var top = restricted ? digits[i] + (i + 1 < digits.length ? digits[i + 1] / BASE : 0) : BASE; + var digit = truncate(usedRNG() * top); + result.push(digit); + if (digit < digits[i]) restricted = false; + } + return low.add(Integer.fromArray(result, BASE, false)); + } + + var parseBase = function (text, base, alphabet, caseSensitive) { + alphabet = alphabet || DEFAULT_ALPHABET; + text = String(text); + if (!caseSensitive) { + text = text.toLowerCase(); + alphabet = alphabet.toLowerCase(); + } + var length = text.length; + var i; + var absBase = Math.abs(base); + var alphabetValues = {}; + for (i = 0; i < alphabet.length; i++) { + alphabetValues[alphabet[i]] = i; + } + for (i = 0; i < length; i++) { + var c = text[i]; + if (c === "-") continue; + if (c in alphabetValues) { + if (alphabetValues[c] >= absBase) { + if (c === "1" && absBase === 1) continue; + throw new Error(c + " is not a valid digit in base " + base + "."); + } + } + } + base = parseValue(base); + var digits = []; + var isNegative = text[0] === "-"; + for (i = isNegative ? 1 : 0; i < text.length; i++) { + var c = text[i]; + if (c in alphabetValues) digits.push(parseValue(alphabetValues[c])); + else if (c === "<") { + var start = i; + do { i++; } while (text[i] !== ">" && i < text.length); + digits.push(parseValue(text.slice(start + 1, i))); + } + else throw new Error(c + " is not a valid character"); + } + return parseBaseFromArray(digits, base, isNegative); + }; + + function parseBaseFromArray(digits, base, isNegative) { + var val = Integer[0], pow = Integer[1], i; + for (i = digits.length - 1; i >= 0; i--) { + val = val.add(digits[i].times(pow)); + pow = pow.times(base); + } + return isNegative ? val.negate() : val; + } + + function stringify(digit, alphabet) { + alphabet = alphabet || DEFAULT_ALPHABET; + if (digit < alphabet.length) { + return alphabet[digit]; + } + return "<" + digit + ">"; + } + + function toBase(n, base) { + base = bigInt(base); + if (base.isZero()) { + if (n.isZero()) return { value: [0], isNegative: false }; + throw new Error("Cannot convert nonzero numbers to base 0."); + } + if (base.equals(-1)) { + if (n.isZero()) return { value: [0], isNegative: false }; + if (n.isNegative()) + return { + value: [].concat.apply([], Array.apply(null, Array(-n.toJSNumber())) + .map(Array.prototype.valueOf, [1, 0]) + ), + isNegative: false + }; + + var arr = Array.apply(null, Array(n.toJSNumber() - 1)) + .map(Array.prototype.valueOf, [0, 1]); + arr.unshift([1]); + return { + value: [].concat.apply([], arr), + isNegative: false + }; + } + + var neg = false; + if (n.isNegative() && base.isPositive()) { + neg = true; + n = n.abs(); + } + if (base.isUnit()) { + if (n.isZero()) return { value: [0], isNegative: false }; + + return { + value: Array.apply(null, Array(n.toJSNumber())) + .map(Number.prototype.valueOf, 1), + isNegative: neg + }; + } + var out = []; + var left = n, divmod; + while (left.isNegative() || left.compareAbs(base) >= 0) { + divmod = left.divmod(base); + left = divmod.quotient; + var digit = divmod.remainder; + if (digit.isNegative()) { + digit = base.minus(digit).abs(); + left = left.next(); + } + out.push(digit.toJSNumber()); + } + out.push(left.toJSNumber()); + return { value: out.reverse(), isNegative: neg }; + } + + function toBaseString(n, base, alphabet) { + var arr = toBase(n, base); + return (arr.isNegative ? "-" : "") + arr.value.map(function (x) { + return stringify(x, alphabet); + }).join(''); + } + + BigInteger.prototype.toArray = function (radix) { + return toBase(this, radix); + }; + + SmallInteger.prototype.toArray = function (radix) { + return toBase(this, radix); + }; + + NativeBigInt.prototype.toArray = function (radix) { + return toBase(this, radix); + }; + + BigInteger.prototype.toString = function (radix, alphabet) { + if (radix === undefined) radix = 10; + if (radix !== 10 || alphabet) return toBaseString(this, radix, alphabet); + var v = this.value, l = v.length, str = String(v[--l]), zeros = "0000000", digit; + while (--l >= 0) { + digit = String(v[l]); + str += zeros.slice(digit.length) + digit; + } + var sign = this.sign ? "-" : ""; + return sign + str; + }; + + SmallInteger.prototype.toString = function (radix, alphabet) { + if (radix === undefined) radix = 10; + if (radix != 10 || alphabet) return toBaseString(this, radix, alphabet); + return String(this.value); + }; + + NativeBigInt.prototype.toString = SmallInteger.prototype.toString; + + NativeBigInt.prototype.toJSON = BigInteger.prototype.toJSON = SmallInteger.prototype.toJSON = function () { return this.toString(); } + + BigInteger.prototype.valueOf = function () { + return parseInt(this.toString(), 10); + }; + BigInteger.prototype.toJSNumber = BigInteger.prototype.valueOf; + + SmallInteger.prototype.valueOf = function () { + return this.value; + }; + SmallInteger.prototype.toJSNumber = SmallInteger.prototype.valueOf; + NativeBigInt.prototype.valueOf = NativeBigInt.prototype.toJSNumber = function () { + return parseInt(this.toString(), 10); + } + + function parseStringValue(v) { + if (isPrecise(+v)) { + var x = +v; + if (x === truncate(x)) + return supportsNativeBigInt ? new NativeBigInt(BigInt(x)) : new SmallInteger(x); + throw new Error("Invalid integer: " + v); + } + var sign = v[0] === "-"; + if (sign) v = v.slice(1); + var split = v.split(/e/i); + if (split.length > 2) throw new Error("Invalid integer: " + split.join("e")); + if (split.length === 2) { + var exp = split[1]; + if (exp[0] === "+") exp = exp.slice(1); + exp = +exp; + if (exp !== truncate(exp) || !isPrecise(exp)) throw new Error("Invalid integer: " + exp + " is not a valid exponent."); + var text = split[0]; + var decimalPlace = text.indexOf("."); + if (decimalPlace >= 0) { + exp -= text.length - decimalPlace - 1; + text = text.slice(0, decimalPlace) + text.slice(decimalPlace + 1); + } + if (exp < 0) throw new Error("Cannot include negative exponent part for integers"); + text += (new Array(exp + 1)).join("0"); + v = text; + } + var isValid = /^([0-9][0-9]*)$/.test(v); + if (!isValid) throw new Error("Invalid integer: " + v); + if (supportsNativeBigInt) { + return new NativeBigInt(BigInt(sign ? "-" + v : v)); + } + var r = [], max = v.length, l = LOG_BASE, min = max - l; + while (max > 0) { + r.push(+v.slice(min, max)); + min -= l; + if (min < 0) min = 0; + max -= l; + } + trim(r); + return new BigInteger(r, sign); + } + + function parseNumberValue(v) { + if (supportsNativeBigInt) { + return new NativeBigInt(BigInt(v)); + } + if (isPrecise(v)) { + if (v !== truncate(v)) throw new Error(v + " is not an integer."); + return new SmallInteger(v); + } + return parseStringValue(v.toString()); + } + + function parseValue(v) { + if (typeof v === "number") { + return parseNumberValue(v); + } + if (typeof v === "string") { + return parseStringValue(v); + } + if (typeof v === "bigint") { + return new NativeBigInt(v); + } + return v; + } + // Pre-define numbers in range [-999,999] + for (var i = 0; i < 1000; i++) { + Integer[i] = parseValue(i); + if (i > 0) Integer[-i] = parseValue(-i); + } + // Backwards compatibility + Integer.one = Integer[1]; + Integer.zero = Integer[0]; + Integer.minusOne = Integer[-1]; + Integer.max = max; + Integer.min = min; + Integer.gcd = gcd; + Integer.lcm = lcm; + Integer.isInstance = function (x) { return x instanceof BigInteger || x instanceof SmallInteger || x instanceof NativeBigInt; }; + Integer.randBetween = randBetween; + + Integer.fromArray = function (digits, base, isNegative) { + return parseBaseFromArray(digits.map(parseValue), parseValue(base || 10), isNegative); + }; + + return Integer; +})(); + +// Node.js check +if (typeof module !== "undefined" && module.hasOwnProperty("exports")) { + module.exports = bigInt; +} + +//amd check +if (typeof define === "function" && define.amd) { + define( function () { + return bigInt; + }); +} diff --git a/tools/ui/src/lib/vendors/big-integer/LICENSE b/tools/ui/src/lib/vendors/big-integer/LICENSE new file mode 100644 index 0000000000..3ce22da38f --- /dev/null +++ b/tools/ui/src/lib/vendors/big-integer/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to diff --git a/tools/ui/src/lib/vendors/decimal.js/LICENCE.md b/tools/ui/src/lib/vendors/decimal.js/LICENCE.md new file mode 100644 index 0000000000..57740b9d4d --- /dev/null +++ b/tools/ui/src/lib/vendors/decimal.js/LICENCE.md @@ -0,0 +1,23 @@ +The MIT Licence. + +Copyright (c) 2025 Michael Mclaughlin + +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. + diff --git a/tools/ui/src/lib/vendors/decimal.js/decimal.js b/tools/ui/src/lib/vendors/decimal.js/decimal.js new file mode 100644 index 0000000000..23295c6acc --- /dev/null +++ b/tools/ui/src/lib/vendors/decimal.js/decimal.js @@ -0,0 +1,4951 @@ +;(function (globalScope) { + 'use strict'; + + + /*! + * decimal.js v10.6.0 + * An arbitrary-precision Decimal type for JavaScript. + * https://github.com/MikeMcl/decimal.js + * Copyright (c) 2025 Michael Mclaughlin + * MIT Licence + */ + + + // ----------------------------------- EDITABLE DEFAULTS ------------------------------------ // + + + // The maximum exponent magnitude. + // The limit on the value of `toExpNeg`, `toExpPos`, `minE` and `maxE`. + var EXP_LIMIT = 9e15, // 0 to 9e15 + + // The limit on the value of `precision`, and on the value of the first argument to + // `toDecimalPlaces`, `toExponential`, `toFixed`, `toPrecision` and `toSignificantDigits`. + MAX_DIGITS = 1e9, // 0 to 1e9 + + // Base conversion alphabet. + NUMERALS = '0123456789abcdef', + + // The natural logarithm of 10 (1025 digits). + LN10 = '2.3025850929940456840179914546843642076011014886287729760333279009675726096773524802359972050895982983419677840422862486334095254650828067566662873690987816894829072083255546808437998948262331985283935053089653777326288461633662222876982198867465436674744042432743651550489343149393914796194044002221051017141748003688084012647080685567743216228355220114804663715659121373450747856947683463616792101806445070648000277502684916746550586856935673420670581136429224554405758925724208241314695689016758940256776311356919292033376587141660230105703089634572075440370847469940168269282808481184289314848524948644871927809676271275775397027668605952496716674183485704422507197965004714951050492214776567636938662976979522110718264549734772662425709429322582798502585509785265383207606726317164309505995087807523710333101197857547331541421808427543863591778117054309827482385045648019095610299291824318237525357709750539565187697510374970888692180205189339507238539205144634197265287286965110862571492198849978748873771345686209167058', + + // Pi (1025 digits). + PI = '3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632789', + + + // The initial configuration properties of the Decimal constructor. + DEFAULTS = { + + // These values must be integers within the stated ranges (inclusive). + // Most of these values can be changed at run-time using the `Decimal.config` method. + + // The maximum number of significant digits of the result of a calculation or base conversion. + // E.g. `Decimal.config({ precision: 20 });` + precision: 20, // 1 to MAX_DIGITS + + // The rounding mode used when rounding to `precision`. + // + // ROUND_UP 0 Away from zero. + // ROUND_DOWN 1 Towards zero. + // ROUND_CEIL 2 Towards +Infinity. + // ROUND_FLOOR 3 Towards -Infinity. + // ROUND_HALF_UP 4 Towards nearest neighbour. If equidistant, up. + // ROUND_HALF_DOWN 5 Towards nearest neighbour. If equidistant, down. + // ROUND_HALF_EVEN 6 Towards nearest neighbour. If equidistant, towards even neighbour. + // ROUND_HALF_CEIL 7 Towards nearest neighbour. If equidistant, towards +Infinity. + // ROUND_HALF_FLOOR 8 Towards nearest neighbour. If equidistant, towards -Infinity. + // + // E.g. + // `Decimal.rounding = 4;` + // `Decimal.rounding = Decimal.ROUND_HALF_UP;` + rounding: 4, // 0 to 8 + + // The modulo mode used when calculating the modulus: a mod n. + // The quotient (q = a / n) is calculated according to the corresponding rounding mode. + // The remainder (r) is calculated as: r = a - n * q. + // + // UP 0 The remainder is positive if the dividend is negative, else is negative. + // DOWN 1 The remainder has the same sign as the dividend (JavaScript %). + // FLOOR 3 The remainder has the same sign as the divisor (Python %). + // HALF_EVEN 6 The IEEE 754 remainder function. + // EUCLID 9 Euclidian division. q = sign(n) * floor(a / abs(n)). Always positive. + // + // Truncated division (1), floored division (3), the IEEE 754 remainder (6), and Euclidian + // division (9) are commonly used for the modulus operation. The other rounding modes can also + // be used, but they may not give useful results. + modulo: 1, // 0 to 9 + + // The exponent value at and beneath which `toString` returns exponential notation. + // JavaScript numbers: -7 + toExpNeg: -7, // 0 to -EXP_LIMIT + + // The exponent value at and above which `toString` returns exponential notation. + // JavaScript numbers: 21 + toExpPos: 21, // 0 to EXP_LIMIT + + // The minimum exponent value, beneath which underflow to zero occurs. + // JavaScript numbers: -324 (5e-324) + minE: -EXP_LIMIT, // -1 to -EXP_LIMIT + + // The maximum exponent value, above which overflow to Infinity occurs. + // JavaScript numbers: 308 (1.7976931348623157e+308) + maxE: EXP_LIMIT, // 1 to EXP_LIMIT + + // Whether to use cryptographically-secure random number generation, if available. + crypto: false // true/false + }, + + + // ----------------------------------- END OF EDITABLE DEFAULTS ------------------------------- // + + + Decimal, inexact, noConflict, quadrant, + external = true, + + decimalError = '[DecimalError] ', + invalidArgument = decimalError + 'Invalid argument: ', + precisionLimitExceeded = decimalError + 'Precision limit exceeded', + cryptoUnavailable = decimalError + 'crypto unavailable', + tag = '[object Decimal]', + + mathfloor = Math.floor, + mathpow = Math.pow, + + isBinary = /^0b([01]+(\.[01]*)?|\.[01]+)(p[+-]?\d+)?$/i, + isHex = /^0x([0-9a-f]+(\.[0-9a-f]*)?|\.[0-9a-f]+)(p[+-]?\d+)?$/i, + isOctal = /^0o([0-7]+(\.[0-7]*)?|\.[0-7]+)(p[+-]?\d+)?$/i, + isDecimal = /^(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i, + + BASE = 1e7, + LOG_BASE = 7, + MAX_SAFE_INTEGER = 9007199254740991, + + LN10_PRECISION = LN10.length - 1, + PI_PRECISION = PI.length - 1, + + // Decimal.prototype object + P = { toStringTag: tag }; + + + // Decimal prototype methods + + + /* + * absoluteValue abs + * ceil + * clampedTo clamp + * comparedTo cmp + * cosine cos + * cubeRoot cbrt + * decimalPlaces dp + * dividedBy div + * dividedToIntegerBy divToInt + * equals eq + * floor + * greaterThan gt + * greaterThanOrEqualTo gte + * hyperbolicCosine cosh + * hyperbolicSine sinh + * hyperbolicTangent tanh + * inverseCosine acos + * inverseHyperbolicCosine acosh + * inverseHyperbolicSine asinh + * inverseHyperbolicTangent atanh + * inverseSine asin + * inverseTangent atan + * isFinite + * isInteger isInt + * isNaN + * isNegative isNeg + * isPositive isPos + * isZero + * lessThan lt + * lessThanOrEqualTo lte + * logarithm log + * [maximum] [max] + * [minimum] [min] + * minus sub + * modulo mod + * naturalExponential exp + * naturalLogarithm ln + * negated neg + * plus add + * precision sd + * round + * sine sin + * squareRoot sqrt + * tangent tan + * times mul + * toBinary + * toDecimalPlaces toDP + * toExponential + * toFixed + * toFraction + * toHexadecimal toHex + * toNearest + * toNumber + * toOctal + * toPower pow + * toPrecision + * toSignificantDigits toSD + * toString + * truncated trunc + * valueOf toJSON + */ + + + /* + * Return a new Decimal whose value is the absolute value of this Decimal. + * + */ + P.absoluteValue = P.abs = function () { + var x = new this.constructor(this); + if (x.s < 0) x.s = 1; + return finalise(x); + }; + + + /* + * Return a new Decimal whose value is the value of this Decimal rounded to a whole number in the + * direction of positive Infinity. + * + */ + P.ceil = function () { + return finalise(new this.constructor(this), this.e + 1, 2); + }; + + + /* + * Return a new Decimal whose value is the value of this Decimal clamped to the range + * delineated by `min` and `max`. + * + * min {number|string|bigint|Decimal} + * max {number|string|bigint|Decimal} + * + */ + P.clampedTo = P.clamp = function (min, max) { + var k, + x = this, + Ctor = x.constructor; + min = new Ctor(min); + max = new Ctor(max); + if (!min.s || !max.s) return new Ctor(NaN); + if (min.gt(max)) throw Error(invalidArgument + max); + k = x.cmp(min); + return k < 0 ? min : x.cmp(max) > 0 ? max : new Ctor(x); + }; + + + /* + * Return + * 1 if the value of this Decimal is greater than the value of `y`, + * -1 if the value of this Decimal is less than the value of `y`, + * 0 if they have the same value, + * NaN if the value of either Decimal is NaN. + * + */ + P.comparedTo = P.cmp = function (y) { + var i, j, xdL, ydL, + x = this, + xd = x.d, + yd = (y = new x.constructor(y)).d, + xs = x.s, + ys = y.s; + + // Either NaN or ±Infinity? + if (!xd || !yd) { + return !xs || !ys ? NaN : xs !== ys ? xs : xd === yd ? 0 : !xd ^ xs < 0 ? 1 : -1; + } + + // Either zero? + if (!xd[0] || !yd[0]) return xd[0] ? xs : yd[0] ? -ys : 0; + + // Signs differ? + if (xs !== ys) return xs; + + // Compare exponents. + if (x.e !== y.e) return x.e > y.e ^ xs < 0 ? 1 : -1; + + xdL = xd.length; + ydL = yd.length; + + // Compare digit by digit. + for (i = 0, j = xdL < ydL ? xdL : ydL; i < j; ++i) { + if (xd[i] !== yd[i]) return xd[i] > yd[i] ^ xs < 0 ? 1 : -1; + } + + // Compare lengths. + return xdL === ydL ? 0 : xdL > ydL ^ xs < 0 ? 1 : -1; + }; + + + /* + * Return a new Decimal whose value is the cosine of the value in radians of this Decimal. + * + * Domain: [-Infinity, Infinity] + * Range: [-1, 1] + * + * cos(0) = 1 + * cos(-0) = 1 + * cos(Infinity) = NaN + * cos(-Infinity) = NaN + * cos(NaN) = NaN + * + */ + P.cosine = P.cos = function () { + var pr, rm, + x = this, + Ctor = x.constructor; + + if (!x.d) return new Ctor(NaN); + + // cos(0) = cos(-0) = 1 + if (!x.d[0]) return new Ctor(1); + + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + Math.max(x.e, x.sd()) + LOG_BASE; + Ctor.rounding = 1; + + x = cosine(Ctor, toLessThanHalfPi(Ctor, x)); + + Ctor.precision = pr; + Ctor.rounding = rm; + + return finalise(quadrant == 2 || quadrant == 3 ? x.neg() : x, pr, rm, true); + }; + + + /* + * + * Return a new Decimal whose value is the cube root of the value of this Decimal, rounded to + * `precision` significant digits using rounding mode `rounding`. + * + * cbrt(0) = 0 + * cbrt(-0) = -0 + * cbrt(1) = 1 + * cbrt(-1) = -1 + * cbrt(N) = N + * cbrt(-I) = -I + * cbrt(I) = I + * + * Math.cbrt(x) = (x < 0 ? -Math.pow(-x, 1/3) : Math.pow(x, 1/3)) + * + */ + P.cubeRoot = P.cbrt = function () { + var e, m, n, r, rep, s, sd, t, t3, t3plusx, + x = this, + Ctor = x.constructor; + + if (!x.isFinite() || x.isZero()) return new Ctor(x); + external = false; + + // Initial estimate. + s = x.s * mathpow(x.s * x, 1 / 3); + + // Math.cbrt underflow/overflow? + // Pass x to Math.pow as integer, then adjust the exponent of the result. + if (!s || Math.abs(s) == 1 / 0) { + n = digitsToString(x.d); + e = x.e; + + // Adjust n exponent so it is a multiple of 3 away from x exponent. + if (s = (e - n.length + 1) % 3) n += (s == 1 || s == -2 ? '0' : '00'); + s = mathpow(n, 1 / 3); + + // Rarely, e may be one less than the result exponent value. + e = mathfloor((e + 1) / 3) - (e % 3 == (e < 0 ? -1 : 2)); + + if (s == 1 / 0) { + n = '5e' + e; + } else { + n = s.toExponential(); + n = n.slice(0, n.indexOf('e') + 1) + e; + } + + r = new Ctor(n); + r.s = x.s; + } else { + r = new Ctor(s.toString()); + } + + sd = (e = Ctor.precision) + 3; + + // Halley's method. + // TODO? Compare Newton's method. + for (;;) { + t = r; + t3 = t.times(t).times(t); + t3plusx = t3.plus(x); + r = divide(t3plusx.plus(x).times(t), t3plusx.plus(t3), sd + 2, 1); + + // TODO? Replace with for-loop and checkRoundingDigits. + if (digitsToString(t.d).slice(0, sd) === (n = digitsToString(r.d)).slice(0, sd)) { + n = n.slice(sd - 3, sd + 1); + + // The 4th rounding digit may be in error by -1 so if the 4 rounding digits are 9999 or 4999 + // , i.e. approaching a rounding boundary, continue the iteration. + if (n == '9999' || !rep && n == '4999') { + + // On the first iteration only, check to see if rounding up gives the exact result as the + // nines may infinitely repeat. + if (!rep) { + finalise(t, e + 1, 0); + + if (t.times(t).times(t).eq(x)) { + r = t; + break; + } + } + + sd += 4; + rep = 1; + } else { + + // If the rounding digits are null, 0{0,4} or 50{0,3}, check for an exact result. + // If not, then there are further digits and m will be truthy. + if (!+n || !+n.slice(1) && n.charAt(0) == '5') { + + // Truncate to the first rounding digit. + finalise(r, e + 1, 1); + m = !r.times(r).times(r).eq(x); + } + + break; + } + } + } + + external = true; + + return finalise(r, e, Ctor.rounding, m); + }; + + + /* + * Return the number of decimal places of the value of this Decimal. + * + */ + P.decimalPlaces = P.dp = function () { + var w, + d = this.d, + n = NaN; + + if (d) { + w = d.length - 1; + n = (w - mathfloor(this.e / LOG_BASE)) * LOG_BASE; + + // Subtract the number of trailing zeros of the last word. + w = d[w]; + if (w) for (; w % 10 == 0; w /= 10) n--; + if (n < 0) n = 0; + } + + return n; + }; + + + /* + * n / 0 = I + * n / N = N + * n / I = 0 + * 0 / n = 0 + * 0 / 0 = N + * 0 / N = N + * 0 / I = 0 + * N / n = N + * N / 0 = N + * N / N = N + * N / I = N + * I / n = I + * I / 0 = I + * I / N = N + * I / I = N + * + * Return a new Decimal whose value is the value of this Decimal divided by `y`, rounded to + * `precision` significant digits using rounding mode `rounding`. + * + */ + P.dividedBy = P.div = function (y) { + return divide(this, new this.constructor(y)); + }; + + + /* + * Return a new Decimal whose value is the integer part of dividing the value of this Decimal + * by the value of `y`, rounded to `precision` significant digits using rounding mode `rounding`. + * + */ + P.dividedToIntegerBy = P.divToInt = function (y) { + var x = this, + Ctor = x.constructor; + return finalise(divide(x, new Ctor(y), 0, 1, 1), Ctor.precision, Ctor.rounding); + }; + + + /* + * Return true if the value of this Decimal is equal to the value of `y`, otherwise return false. + * + */ + P.equals = P.eq = function (y) { + return this.cmp(y) === 0; + }; + + + /* + * Return a new Decimal whose value is the value of this Decimal rounded to a whole number in the + * direction of negative Infinity. + * + */ + P.floor = function () { + return finalise(new this.constructor(this), this.e + 1, 3); + }; + + + /* + * Return true if the value of this Decimal is greater than the value of `y`, otherwise return + * false. + * + */ + P.greaterThan = P.gt = function (y) { + return this.cmp(y) > 0; + }; + + + /* + * Return true if the value of this Decimal is greater than or equal to the value of `y`, + * otherwise return false. + * + */ + P.greaterThanOrEqualTo = P.gte = function (y) { + var k = this.cmp(y); + return k == 1 || k === 0; + }; + + + /* + * Return a new Decimal whose value is the hyperbolic cosine of the value in radians of this + * Decimal. + * + * Domain: [-Infinity, Infinity] + * Range: [1, Infinity] + * + * cosh(x) = 1 + x^2/2! + x^4/4! + x^6/6! + ... + * + * cosh(0) = 1 + * cosh(-0) = 1 + * cosh(Infinity) = Infinity + * cosh(-Infinity) = Infinity + * cosh(NaN) = NaN + * + * x time taken (ms) result + * 1000 9 9.8503555700852349694e+433 + * 10000 25 4.4034091128314607936e+4342 + * 100000 171 1.4033316802130615897e+43429 + * 1000000 3817 1.5166076984010437725e+434294 + * 10000000 abandoned after 2 minute wait + * + * TODO? Compare performance of cosh(x) = 0.5 * (exp(x) + exp(-x)) + * + */ + P.hyperbolicCosine = P.cosh = function () { + var k, n, pr, rm, len, + x = this, + Ctor = x.constructor, + one = new Ctor(1); + + if (!x.isFinite()) return new Ctor(x.s ? 1 / 0 : NaN); + if (x.isZero()) return one; + + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + Math.max(x.e, x.sd()) + 4; + Ctor.rounding = 1; + len = x.d.length; + + // Argument reduction: cos(4x) = 1 - 8cos^2(x) + 8cos^4(x) + 1 + // i.e. cos(x) = 1 - cos^2(x/4)(8 - 8cos^2(x/4)) + + // Estimate the optimum number of times to use the argument reduction. + // TODO? Estimation reused from cosine() and may not be optimal here. + if (len < 32) { + k = Math.ceil(len / 3); + n = (1 / tinyPow(4, k)).toString(); + } else { + k = 16; + n = '2.3283064365386962890625e-10'; + } + + x = taylorSeries(Ctor, 1, x.times(n), new Ctor(1), true); + + // Reverse argument reduction + var cosh2_x, + i = k, + d8 = new Ctor(8); + for (; i--;) { + cosh2_x = x.times(x); + x = one.minus(cosh2_x.times(d8.minus(cosh2_x.times(d8)))); + } + + return finalise(x, Ctor.precision = pr, Ctor.rounding = rm, true); + }; + + + /* + * Return a new Decimal whose value is the hyperbolic sine of the value in radians of this + * Decimal. + * + * Domain: [-Infinity, Infinity] + * Range: [-Infinity, Infinity] + * + * sinh(x) = x + x^3/3! + x^5/5! + x^7/7! + ... + * + * sinh(0) = 0 + * sinh(-0) = -0 + * sinh(Infinity) = Infinity + * sinh(-Infinity) = -Infinity + * sinh(NaN) = NaN + * + * x time taken (ms) + * 10 2 ms + * 100 5 ms + * 1000 14 ms + * 10000 82 ms + * 100000 886 ms 1.4033316802130615897e+43429 + * 200000 2613 ms + * 300000 5407 ms + * 400000 8824 ms + * 500000 13026 ms 8.7080643612718084129e+217146 + * 1000000 48543 ms + * + * TODO? Compare performance of sinh(x) = 0.5 * (exp(x) - exp(-x)) + * + */ + P.hyperbolicSine = P.sinh = function () { + var k, pr, rm, len, + x = this, + Ctor = x.constructor; + + if (!x.isFinite() || x.isZero()) return new Ctor(x); + + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + Math.max(x.e, x.sd()) + 4; + Ctor.rounding = 1; + len = x.d.length; + + if (len < 3) { + x = taylorSeries(Ctor, 2, x, x, true); + } else { + + // Alternative argument reduction: sinh(3x) = sinh(x)(3 + 4sinh^2(x)) + // i.e. sinh(x) = sinh(x/3)(3 + 4sinh^2(x/3)) + // 3 multiplications and 1 addition + + // Argument reduction: sinh(5x) = sinh(x)(5 + sinh^2(x)(20 + 16sinh^2(x))) + // i.e. sinh(x) = sinh(x/5)(5 + sinh^2(x/5)(20 + 16sinh^2(x/5))) + // 4 multiplications and 2 additions + + // Estimate the optimum number of times to use the argument reduction. + k = 1.4 * Math.sqrt(len); + k = k > 16 ? 16 : k | 0; + + x = x.times(1 / tinyPow(5, k)); + x = taylorSeries(Ctor, 2, x, x, true); + + // Reverse argument reduction + var sinh2_x, + d5 = new Ctor(5), + d16 = new Ctor(16), + d20 = new Ctor(20); + for (; k--;) { + sinh2_x = x.times(x); + x = x.times(d5.plus(sinh2_x.times(d16.times(sinh2_x).plus(d20)))); + } + } + + Ctor.precision = pr; + Ctor.rounding = rm; + + return finalise(x, pr, rm, true); + }; + + + /* + * Return a new Decimal whose value is the hyperbolic tangent of the value in radians of this + * Decimal. + * + * Domain: [-Infinity, Infinity] + * Range: [-1, 1] + * + * tanh(x) = sinh(x) / cosh(x) + * + * tanh(0) = 0 + * tanh(-0) = -0 + * tanh(Infinity) = 1 + * tanh(-Infinity) = -1 + * tanh(NaN) = NaN + * + */ + P.hyperbolicTangent = P.tanh = function () { + var pr, rm, + x = this, + Ctor = x.constructor; + + if (!x.isFinite()) return new Ctor(x.s); + if (x.isZero()) return new Ctor(x); + + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + 7; + Ctor.rounding = 1; + + return divide(x.sinh(), x.cosh(), Ctor.precision = pr, Ctor.rounding = rm); + }; + + + /* + * Return a new Decimal whose value is the arccosine (inverse cosine) in radians of the value of + * this Decimal. + * + * Domain: [-1, 1] + * Range: [0, pi] + * + * acos(x) = pi/2 - asin(x) + * + * acos(0) = pi/2 + * acos(-0) = pi/2 + * acos(1) = 0 + * acos(-1) = pi + * acos(1/2) = pi/3 + * acos(-1/2) = 2*pi/3 + * acos(|x| > 1) = NaN + * acos(NaN) = NaN + * + */ + P.inverseCosine = P.acos = function () { + var x = this, + Ctor = x.constructor, + k = x.abs().cmp(1), + pr = Ctor.precision, + rm = Ctor.rounding; + + if (k !== -1) { + return k === 0 + // |x| is 1 + ? x.isNeg() ? getPi(Ctor, pr, rm) : new Ctor(0) + // |x| > 1 or x is NaN + : new Ctor(NaN); + } + + if (x.isZero()) return getPi(Ctor, pr + 4, rm).times(0.5); + + // TODO? Special case acos(0.5) = pi/3 and acos(-0.5) = 2*pi/3 + + Ctor.precision = pr + 6; + Ctor.rounding = 1; + + // See https://github.com/MikeMcl/decimal.js/pull/217 + x = new Ctor(1).minus(x).div(x.plus(1)).sqrt().atan(); + + Ctor.precision = pr; + Ctor.rounding = rm; + + return x.times(2); + }; + + + /* + * Return a new Decimal whose value is the inverse of the hyperbolic cosine in radians of the + * value of this Decimal. + * + * Domain: [1, Infinity] + * Range: [0, Infinity] + * + * acosh(x) = ln(x + sqrt(x^2 - 1)) + * + * acosh(x < 1) = NaN + * acosh(NaN) = NaN + * acosh(Infinity) = Infinity + * acosh(-Infinity) = NaN + * acosh(0) = NaN + * acosh(-0) = NaN + * acosh(1) = 0 + * acosh(-1) = NaN + * + */ + P.inverseHyperbolicCosine = P.acosh = function () { + var pr, rm, + x = this, + Ctor = x.constructor; + + if (x.lte(1)) return new Ctor(x.eq(1) ? 0 : NaN); + if (!x.isFinite()) return new Ctor(x); + + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + Math.max(Math.abs(x.e), x.sd()) + 4; + Ctor.rounding = 1; + external = false; + + x = x.times(x).minus(1).sqrt().plus(x); + + external = true; + Ctor.precision = pr; + Ctor.rounding = rm; + + return x.ln(); + }; + + + /* + * Return a new Decimal whose value is the inverse of the hyperbolic sine in radians of the value + * of this Decimal. + * + * Domain: [-Infinity, Infinity] + * Range: [-Infinity, Infinity] + * + * asinh(x) = ln(x + sqrt(x^2 + 1)) + * + * asinh(NaN) = NaN + * asinh(Infinity) = Infinity + * asinh(-Infinity) = -Infinity + * asinh(0) = 0 + * asinh(-0) = -0 + * + */ + P.inverseHyperbolicSine = P.asinh = function () { + var pr, rm, + x = this, + Ctor = x.constructor; + + if (!x.isFinite() || x.isZero()) return new Ctor(x); + + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + 2 * Math.max(Math.abs(x.e), x.sd()) + 6; + Ctor.rounding = 1; + external = false; + + x = x.times(x).plus(1).sqrt().plus(x); + + external = true; + Ctor.precision = pr; + Ctor.rounding = rm; + + return x.ln(); + }; + + + /* + * Return a new Decimal whose value is the inverse of the hyperbolic tangent in radians of the + * value of this Decimal. + * + * Domain: [-1, 1] + * Range: [-Infinity, Infinity] + * + * atanh(x) = 0.5 * ln((1 + x) / (1 - x)) + * + * atanh(|x| > 1) = NaN + * atanh(NaN) = NaN + * atanh(Infinity) = NaN + * atanh(-Infinity) = NaN + * atanh(0) = 0 + * atanh(-0) = -0 + * atanh(1) = Infinity + * atanh(-1) = -Infinity + * + */ + P.inverseHyperbolicTangent = P.atanh = function () { + var pr, rm, wpr, xsd, + x = this, + Ctor = x.constructor; + + if (!x.isFinite()) return new Ctor(NaN); + if (x.e >= 0) return new Ctor(x.abs().eq(1) ? x.s / 0 : x.isZero() ? x : NaN); + + pr = Ctor.precision; + rm = Ctor.rounding; + xsd = x.sd(); + + if (Math.max(xsd, pr) < 2 * -x.e - 1) return finalise(new Ctor(x), pr, rm, true); + + Ctor.precision = wpr = xsd - x.e; + + x = divide(x.plus(1), new Ctor(1).minus(x), wpr + pr, 1); + + Ctor.precision = pr + 4; + Ctor.rounding = 1; + + x = x.ln(); + + Ctor.precision = pr; + Ctor.rounding = rm; + + return x.times(0.5); + }; + + + /* + * Return a new Decimal whose value is the arcsine (inverse sine) in radians of the value of this + * Decimal. + * + * Domain: [-Infinity, Infinity] + * Range: [-pi/2, pi/2] + * + * asin(x) = 2*atan(x/(1 + sqrt(1 - x^2))) + * + * asin(0) = 0 + * asin(-0) = -0 + * asin(1/2) = pi/6 + * asin(-1/2) = -pi/6 + * asin(1) = pi/2 + * asin(-1) = -pi/2 + * asin(|x| > 1) = NaN + * asin(NaN) = NaN + * + * TODO? Compare performance of Taylor series. + * + */ + P.inverseSine = P.asin = function () { + var halfPi, k, + pr, rm, + x = this, + Ctor = x.constructor; + + if (x.isZero()) return new Ctor(x); + + k = x.abs().cmp(1); + pr = Ctor.precision; + rm = Ctor.rounding; + + if (k !== -1) { + + // |x| is 1 + if (k === 0) { + halfPi = getPi(Ctor, pr + 4, rm).times(0.5); + halfPi.s = x.s; + return halfPi; + } + + // |x| > 1 or x is NaN + return new Ctor(NaN); + } + + // TODO? Special case asin(1/2) = pi/6 and asin(-1/2) = -pi/6 + + Ctor.precision = pr + 6; + Ctor.rounding = 1; + + x = x.div(new Ctor(1).minus(x.times(x)).sqrt().plus(1)).atan(); + + Ctor.precision = pr; + Ctor.rounding = rm; + + return x.times(2); + }; + + + /* + * Return a new Decimal whose value is the arctangent (inverse tangent) in radians of the value + * of this Decimal. + * + * Domain: [-Infinity, Infinity] + * Range: [-pi/2, pi/2] + * + * atan(x) = x - x^3/3 + x^5/5 - x^7/7 + ... + * + * atan(0) = 0 + * atan(-0) = -0 + * atan(1) = pi/4 + * atan(-1) = -pi/4 + * atan(Infinity) = pi/2 + * atan(-Infinity) = -pi/2 + * atan(NaN) = NaN + * + */ + P.inverseTangent = P.atan = function () { + var i, j, k, n, px, t, r, wpr, x2, + x = this, + Ctor = x.constructor, + pr = Ctor.precision, + rm = Ctor.rounding; + + if (!x.isFinite()) { + if (!x.s) return new Ctor(NaN); + if (pr + 4 <= PI_PRECISION) { + r = getPi(Ctor, pr + 4, rm).times(0.5); + r.s = x.s; + return r; + } + } else if (x.isZero()) { + return new Ctor(x); + } else if (x.abs().eq(1) && pr + 4 <= PI_PRECISION) { + r = getPi(Ctor, pr + 4, rm).times(0.25); + r.s = x.s; + return r; + } + + Ctor.precision = wpr = pr + 10; + Ctor.rounding = 1; + + // TODO? if (x >= 1 && pr <= PI_PRECISION) atan(x) = halfPi * x.s - atan(1 / x); + + // Argument reduction + // Ensure |x| < 0.42 + // atan(x) = 2 * atan(x / (1 + sqrt(1 + x^2))) + + k = Math.min(28, wpr / LOG_BASE + 2 | 0); + + for (i = k; i; --i) x = x.div(x.times(x).plus(1).sqrt().plus(1)); + + external = false; + + j = Math.ceil(wpr / LOG_BASE); + n = 1; + x2 = x.times(x); + r = new Ctor(x); + px = x; + + // atan(x) = x - x^3/3 + x^5/5 - x^7/7 + ... + for (; i !== -1;) { + px = px.times(x2); + t = r.minus(px.div(n += 2)); + + px = px.times(x2); + r = t.plus(px.div(n += 2)); + + if (r.d[j] !== void 0) for (i = j; r.d[i] === t.d[i] && i--;); + } + + if (k) r = r.times(2 << (k - 1)); + + external = true; + + return finalise(r, Ctor.precision = pr, Ctor.rounding = rm, true); + }; + + + /* + * Return true if the value of this Decimal is a finite number, otherwise return false. + * + */ + P.isFinite = function () { + return !!this.d; + }; + + + /* + * Return true if the value of this Decimal is an integer, otherwise return false. + * + */ + P.isInteger = P.isInt = function () { + return !!this.d && mathfloor(this.e / LOG_BASE) > this.d.length - 2; + }; + + + /* + * Return true if the value of this Decimal is NaN, otherwise return false. + * + */ + P.isNaN = function () { + return !this.s; + }; + + + /* + * Return true if the value of this Decimal is negative, otherwise return false. + * + */ + P.isNegative = P.isNeg = function () { + return this.s < 0; + }; + + + /* + * Return true if the value of this Decimal is positive, otherwise return false. + * + */ + P.isPositive = P.isPos = function () { + return this.s > 0; + }; + + + /* + * Return true if the value of this Decimal is 0 or -0, otherwise return false. + * + */ + P.isZero = function () { + return !!this.d && this.d[0] === 0; + }; + + + /* + * Return true if the value of this Decimal is less than `y`, otherwise return false. + * + */ + P.lessThan = P.lt = function (y) { + return this.cmp(y) < 0; + }; + + + /* + * Return true if the value of this Decimal is less than or equal to `y`, otherwise return false. + * + */ + P.lessThanOrEqualTo = P.lte = function (y) { + return this.cmp(y) < 1; + }; + + + /* + * Return the logarithm of the value of this Decimal to the specified base, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + * If no base is specified, return log[10](arg). + * + * log[base](arg) = ln(arg) / ln(base) + * + * The result will always be correctly rounded if the base of the log is 10, and 'almost always' + * otherwise: + * + * Depending on the rounding mode, the result may be incorrectly rounded if the first fifteen + * rounding digits are [49]99999999999999 or [50]00000000000000. In that case, the maximum error + * between the result and the correctly rounded result will be one ulp (unit in the last place). + * + * log[-b](a) = NaN + * log[0](a) = NaN + * log[1](a) = NaN + * log[NaN](a) = NaN + * log[Infinity](a) = NaN + * log[b](0) = -Infinity + * log[b](-0) = -Infinity + * log[b](-a) = NaN + * log[b](1) = 0 + * log[b](Infinity) = Infinity + * log[b](NaN) = NaN + * + * [base] {number|string|bigint|Decimal} The base of the logarithm. + * + */ + P.logarithm = P.log = function (base) { + var isBase10, d, denominator, k, inf, num, sd, r, + arg = this, + Ctor = arg.constructor, + pr = Ctor.precision, + rm = Ctor.rounding, + guard = 5; + + // Default base is 10. + if (base == null) { + base = new Ctor(10); + isBase10 = true; + } else { + base = new Ctor(base); + d = base.d; + + // Return NaN if base is negative, or non-finite, or is 0 or 1. + if (base.s < 0 || !d || !d[0] || base.eq(1)) return new Ctor(NaN); + + isBase10 = base.eq(10); + } + + d = arg.d; + + // Is arg negative, non-finite, 0 or 1? + if (arg.s < 0 || !d || !d[0] || arg.eq(1)) { + return new Ctor(d && !d[0] ? -1 / 0 : arg.s != 1 ? NaN : d ? 0 : 1 / 0); + } + + // The result will have a non-terminating decimal expansion if base is 10 and arg is not an + // integer power of 10. + if (isBase10) { + if (d.length > 1) { + inf = true; + } else { + for (k = d[0]; k % 10 === 0;) k /= 10; + inf = k !== 1; + } + } + + external = false; + sd = pr + guard; + num = naturalLogarithm(arg, sd); + denominator = isBase10 ? getLn10(Ctor, sd + 10) : naturalLogarithm(base, sd); + + // The result will have 5 rounding digits. + r = divide(num, denominator, sd, 1); + + // If at a rounding boundary, i.e. the result's rounding digits are [49]9999 or [50]0000, + // calculate 10 further digits. + // + // If the result is known to have an infinite decimal expansion, repeat this until it is clear + // that the result is above or below the boundary. Otherwise, if after calculating the 10 + // further digits, the last 14 are nines, round up and assume the result is exact. + // Also assume the result is exact if the last 14 are zero. + // + // Example of a result that will be incorrectly rounded: + // log[1048576](4503599627370502) = 2.60000000000000009610279511444746... + // The above result correctly rounded using ROUND_CEIL to 1 decimal place should be 2.7, but it + // will be given as 2.6 as there are 15 zeros immediately after the requested decimal place, so + // the exact result would be assumed to be 2.6, which rounded using ROUND_CEIL to 1 decimal + // place is still 2.6. + if (checkRoundingDigits(r.d, k = pr, rm)) { + + do { + sd += 10; + num = naturalLogarithm(arg, sd); + denominator = isBase10 ? getLn10(Ctor, sd + 10) : naturalLogarithm(base, sd); + r = divide(num, denominator, sd, 1); + + if (!inf) { + + // Check for 14 nines from the 2nd rounding digit, as the first may be 4. + if (+digitsToString(r.d).slice(k + 1, k + 15) + 1 == 1e14) { + r = finalise(r, pr + 1, 0); + } + + break; + } + } while (checkRoundingDigits(r.d, k += 10, rm)); + } + + external = true; + + return finalise(r, pr, rm); + }; + + + /* + * Return a new Decimal whose value is the maximum of the arguments and the value of this Decimal. + * + * arguments {number|string|bigint|Decimal} + * + P.max = function () { + Array.prototype.push.call(arguments, this); + return maxOrMin(this.constructor, arguments, -1); + }; + */ + + + /* + * Return a new Decimal whose value is the minimum of the arguments and the value of this Decimal. + * + * arguments {number|string|bigint|Decimal} + * + P.min = function () { + Array.prototype.push.call(arguments, this); + return maxOrMin(this.constructor, arguments, 1); + }; + */ + + + /* + * n - 0 = n + * n - N = N + * n - I = -I + * 0 - n = -n + * 0 - 0 = 0 + * 0 - N = N + * 0 - I = -I + * N - n = N + * N - 0 = N + * N - N = N + * N - I = N + * I - n = I + * I - 0 = I + * I - N = N + * I - I = N + * + * Return a new Decimal whose value is the value of this Decimal minus `y`, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + */ + P.minus = P.sub = function (y) { + var d, e, i, j, k, len, pr, rm, xd, xe, xLTy, yd, + x = this, + Ctor = x.constructor; + + y = new Ctor(y); + + // If either is not finite... + if (!x.d || !y.d) { + + // Return NaN if either is NaN. + if (!x.s || !y.s) y = new Ctor(NaN); + + // Return y negated if x is finite and y is ±Infinity. + else if (x.d) y.s = -y.s; + + // Return x if y is finite and x is ±Infinity. + // Return x if both are ±Infinity with different signs. + // Return NaN if both are ±Infinity with the same sign. + else y = new Ctor(y.d || x.s !== y.s ? x : NaN); + + return y; + } + + // If signs differ... + if (x.s != y.s) { + y.s = -y.s; + return x.plus(y); + } + + xd = x.d; + yd = y.d; + pr = Ctor.precision; + rm = Ctor.rounding; + + // If either is zero... + if (!xd[0] || !yd[0]) { + + // Return y negated if x is zero and y is non-zero. + if (yd[0]) y.s = -y.s; + + // Return x if y is zero and x is non-zero. + else if (xd[0]) y = new Ctor(x); + + // Return zero if both are zero. + // From IEEE 754 (2008) 6.3: 0 - 0 = -0 - -0 = -0 when rounding to -Infinity. + else return new Ctor(rm === 3 ? -0 : 0); + + return external ? finalise(y, pr, rm) : y; + } + + // x and y are finite, non-zero numbers with the same sign. + + // Calculate base 1e7 exponents. + e = mathfloor(y.e / LOG_BASE); + xe = mathfloor(x.e / LOG_BASE); + + xd = xd.slice(); + k = xe - e; + + // If base 1e7 exponents differ... + if (k) { + xLTy = k < 0; + + if (xLTy) { + d = xd; + k = -k; + len = yd.length; + } else { + d = yd; + e = xe; + len = xd.length; + } + + // Numbers with massively different exponents would result in a very high number of + // zeros needing to be prepended, but this can be avoided while still ensuring correct + // rounding by limiting the number of zeros to `Math.ceil(pr / LOG_BASE) + 2`. + i = Math.max(Math.ceil(pr / LOG_BASE), len) + 2; + + if (k > i) { + k = i; + d.length = 1; + } + + // Prepend zeros to equalise exponents. + d.reverse(); + for (i = k; i--;) d.push(0); + d.reverse(); + + // Base 1e7 exponents equal. + } else { + + // Check digits to determine which is the bigger number. + + i = xd.length; + len = yd.length; + xLTy = i < len; + if (xLTy) len = i; + + for (i = 0; i < len; i++) { + if (xd[i] != yd[i]) { + xLTy = xd[i] < yd[i]; + break; + } + } + + k = 0; + } + + if (xLTy) { + d = xd; + xd = yd; + yd = d; + y.s = -y.s; + } + + len = xd.length; + + // Append zeros to `xd` if shorter. + // Don't add zeros to `yd` if shorter as subtraction only needs to start at `yd` length. + for (i = yd.length - len; i > 0; --i) xd[len++] = 0; + + // Subtract yd from xd. + for (i = yd.length; i > k;) { + + if (xd[--i] < yd[i]) { + for (j = i; j && xd[--j] === 0;) xd[j] = BASE - 1; + --xd[j]; + xd[i] += BASE; + } + + xd[i] -= yd[i]; + } + + // Remove trailing zeros. + for (; xd[--len] === 0;) xd.pop(); + + // Remove leading zeros and adjust exponent accordingly. + for (; xd[0] === 0; xd.shift()) --e; + + // Zero? + if (!xd[0]) return new Ctor(rm === 3 ? -0 : 0); + + y.d = xd; + y.e = getBase10Exponent(xd, e); + + return external ? finalise(y, pr, rm) : y; + }; + + + /* + * n % 0 = N + * n % N = N + * n % I = n + * 0 % n = 0 + * -0 % n = -0 + * 0 % 0 = N + * 0 % N = N + * 0 % I = 0 + * N % n = N + * N % 0 = N + * N % N = N + * N % I = N + * I % n = N + * I % 0 = N + * I % N = N + * I % I = N + * + * Return a new Decimal whose value is the value of this Decimal modulo `y`, rounded to + * `precision` significant digits using rounding mode `rounding`. + * + * The result depends on the modulo mode. + * + */ + P.modulo = P.mod = function (y) { + var q, + x = this, + Ctor = x.constructor; + + y = new Ctor(y); + + // Return NaN if x is ±Infinity or NaN, or y is NaN or ±0. + if (!x.d || !y.s || y.d && !y.d[0]) return new Ctor(NaN); + + // Return x if y is ±Infinity or x is ±0. + if (!y.d || x.d && !x.d[0]) { + return finalise(new Ctor(x), Ctor.precision, Ctor.rounding); + } + + // Prevent rounding of intermediate calculations. + external = false; + + if (Ctor.modulo == 9) { + + // Euclidian division: q = sign(y) * floor(x / abs(y)) + // result = x - q * y where 0 <= result < abs(y) + q = divide(x, y.abs(), 0, 3, 1); + q.s *= y.s; + } else { + q = divide(x, y, 0, Ctor.modulo, 1); + } + + q = q.times(y); + + external = true; + + return x.minus(q); + }; + + + /* + * Return a new Decimal whose value is the natural exponential of the value of this Decimal, + * i.e. the base e raised to the power the value of this Decimal, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + */ + P.naturalExponential = P.exp = function () { + return naturalExponential(this); + }; + + + /* + * Return a new Decimal whose value is the natural logarithm of the value of this Decimal, + * rounded to `precision` significant digits using rounding mode `rounding`. + * + */ + P.naturalLogarithm = P.ln = function () { + return naturalLogarithm(this); + }; + + + /* + * Return a new Decimal whose value is the value of this Decimal negated, i.e. as if multiplied by + * -1. + * + */ + P.negated = P.neg = function () { + var x = new this.constructor(this); + x.s = -x.s; + return finalise(x); + }; + + + /* + * n + 0 = n + * n + N = N + * n + I = I + * 0 + n = n + * 0 + 0 = 0 + * 0 + N = N + * 0 + I = I + * N + n = N + * N + 0 = N + * N + N = N + * N + I = N + * I + n = I + * I + 0 = I + * I + N = N + * I + I = I + * + * Return a new Decimal whose value is the value of this Decimal plus `y`, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + */ + P.plus = P.add = function (y) { + var carry, d, e, i, k, len, pr, rm, xd, yd, + x = this, + Ctor = x.constructor; + + y = new Ctor(y); + + // If either is not finite... + if (!x.d || !y.d) { + + // Return NaN if either is NaN. + if (!x.s || !y.s) y = new Ctor(NaN); + + // Return x if y is finite and x is ±Infinity. + // Return x if both are ±Infinity with the same sign. + // Return NaN if both are ±Infinity with different signs. + // Return y if x is finite and y is ±Infinity. + else if (!x.d) y = new Ctor(y.d || x.s === y.s ? x : NaN); + + return y; + } + + // If signs differ... + if (x.s != y.s) { + y.s = -y.s; + return x.minus(y); + } + + xd = x.d; + yd = y.d; + pr = Ctor.precision; + rm = Ctor.rounding; + + // If either is zero... + if (!xd[0] || !yd[0]) { + + // Return x if y is zero. + // Return y if y is non-zero. + if (!yd[0]) y = new Ctor(x); + + return external ? finalise(y, pr, rm) : y; + } + + // x and y are finite, non-zero numbers with the same sign. + + // Calculate base 1e7 exponents. + k = mathfloor(x.e / LOG_BASE); + e = mathfloor(y.e / LOG_BASE); + + xd = xd.slice(); + i = k - e; + + // If base 1e7 exponents differ... + if (i) { + + if (i < 0) { + d = xd; + i = -i; + len = yd.length; + } else { + d = yd; + e = k; + len = xd.length; + } + + // Limit number of zeros prepended to max(ceil(pr / LOG_BASE), len) + 1. + k = Math.ceil(pr / LOG_BASE); + len = k > len ? k + 1 : len + 1; + + if (i > len) { + i = len; + d.length = 1; + } + + // Prepend zeros to equalise exponents. Note: Faster to use reverse then do unshifts. + d.reverse(); + for (; i--;) d.push(0); + d.reverse(); + } + + len = xd.length; + i = yd.length; + + // If yd is longer than xd, swap xd and yd so xd points to the longer array. + if (len - i < 0) { + i = len; + d = yd; + yd = xd; + xd = d; + } + + // Only start adding at yd.length - 1 as the further digits of xd can be left as they are. + for (carry = 0; i;) { + carry = (xd[--i] = xd[i] + yd[i] + carry) / BASE | 0; + xd[i] %= BASE; + } + + if (carry) { + xd.unshift(carry); + ++e; + } + + // Remove trailing zeros. + // No need to check for zero, as +x + +y != 0 && -x + -y != 0 + for (len = xd.length; xd[--len] == 0;) xd.pop(); + + y.d = xd; + y.e = getBase10Exponent(xd, e); + + return external ? finalise(y, pr, rm) : y; + }; + + + /* + * Return the number of significant digits of the value of this Decimal. + * + * [z] {boolean|number} Whether to count integer-part trailing zeros: true, false, 1 or 0. + * + */ + P.precision = P.sd = function (z) { + var k, + x = this; + + if (z !== void 0 && z !== !!z && z !== 1 && z !== 0) throw Error(invalidArgument + z); + + if (x.d) { + k = getPrecision(x.d); + if (z && x.e + 1 > k) k = x.e + 1; + } else { + k = NaN; + } + + return k; + }; + + + /* + * Return a new Decimal whose value is the value of this Decimal rounded to a whole number using + * rounding mode `rounding`. + * + */ + P.round = function () { + var x = this, + Ctor = x.constructor; + + return finalise(new Ctor(x), x.e + 1, Ctor.rounding); + }; + + + /* + * Return a new Decimal whose value is the sine of the value in radians of this Decimal. + * + * Domain: [-Infinity, Infinity] + * Range: [-1, 1] + * + * sin(x) = x - x^3/3! + x^5/5! - ... + * + * sin(0) = 0 + * sin(-0) = -0 + * sin(Infinity) = NaN + * sin(-Infinity) = NaN + * sin(NaN) = NaN + * + */ + P.sine = P.sin = function () { + var pr, rm, + x = this, + Ctor = x.constructor; + + if (!x.isFinite()) return new Ctor(NaN); + if (x.isZero()) return new Ctor(x); + + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + Math.max(x.e, x.sd()) + LOG_BASE; + Ctor.rounding = 1; + + x = sine(Ctor, toLessThanHalfPi(Ctor, x)); + + Ctor.precision = pr; + Ctor.rounding = rm; + + return finalise(quadrant > 2 ? x.neg() : x, pr, rm, true); + }; + + + /* + * Return a new Decimal whose value is the square root of this Decimal, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + * sqrt(-n) = N + * sqrt(N) = N + * sqrt(-I) = N + * sqrt(I) = I + * sqrt(0) = 0 + * sqrt(-0) = -0 + * + */ + P.squareRoot = P.sqrt = function () { + var m, n, sd, r, rep, t, + x = this, + d = x.d, + e = x.e, + s = x.s, + Ctor = x.constructor; + + // Negative/NaN/Infinity/zero? + if (s !== 1 || !d || !d[0]) { + return new Ctor(!s || s < 0 && (!d || d[0]) ? NaN : d ? x : 1 / 0); + } + + external = false; + + // Initial estimate. + s = Math.sqrt(+x); + + // Math.sqrt underflow/overflow? + // Pass x to Math.sqrt as integer, then adjust the exponent of the result. + if (s == 0 || s == 1 / 0) { + n = digitsToString(d); + + if ((n.length + e) % 2 == 0) n += '0'; + s = Math.sqrt(n); + e = mathfloor((e + 1) / 2) - (e < 0 || e % 2); + + if (s == 1 / 0) { + n = '5e' + e; + } else { + n = s.toExponential(); + n = n.slice(0, n.indexOf('e') + 1) + e; + } + + r = new Ctor(n); + } else { + r = new Ctor(s.toString()); + } + + sd = (e = Ctor.precision) + 3; + + // Newton-Raphson iteration. + for (;;) { + t = r; + r = t.plus(divide(x, t, sd + 2, 1)).times(0.5); + + // TODO? Replace with for-loop and checkRoundingDigits. + if (digitsToString(t.d).slice(0, sd) === (n = digitsToString(r.d)).slice(0, sd)) { + n = n.slice(sd - 3, sd + 1); + + // The 4th rounding digit may be in error by -1 so if the 4 rounding digits are 9999 or + // 4999, i.e. approaching a rounding boundary, continue the iteration. + if (n == '9999' || !rep && n == '4999') { + + // On the first iteration only, check to see if rounding up gives the exact result as the + // nines may infinitely repeat. + if (!rep) { + finalise(t, e + 1, 0); + + if (t.times(t).eq(x)) { + r = t; + break; + } + } + + sd += 4; + rep = 1; + } else { + + // If the rounding digits are null, 0{0,4} or 50{0,3}, check for an exact result. + // If not, then there are further digits and m will be truthy. + if (!+n || !+n.slice(1) && n.charAt(0) == '5') { + + // Truncate to the first rounding digit. + finalise(r, e + 1, 1); + m = !r.times(r).eq(x); + } + + break; + } + } + } + + external = true; + + return finalise(r, e, Ctor.rounding, m); + }; + + + /* + * Return a new Decimal whose value is the tangent of the value in radians of this Decimal. + * + * Domain: [-Infinity, Infinity] + * Range: [-Infinity, Infinity] + * + * tan(0) = 0 + * tan(-0) = -0 + * tan(Infinity) = NaN + * tan(-Infinity) = NaN + * tan(NaN) = NaN + * + */ + P.tangent = P.tan = function () { + var pr, rm, + x = this, + Ctor = x.constructor; + + if (!x.isFinite()) return new Ctor(NaN); + if (x.isZero()) return new Ctor(x); + + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + 10; + Ctor.rounding = 1; + + x = x.sin(); + x.s = 1; + x = divide(x, new Ctor(1).minus(x.times(x)).sqrt(), pr + 10, 0); + + Ctor.precision = pr; + Ctor.rounding = rm; + + return finalise(quadrant == 2 || quadrant == 4 ? x.neg() : x, pr, rm, true); + }; + + + /* + * n * 0 = 0 + * n * N = N + * n * I = I + * 0 * n = 0 + * 0 * 0 = 0 + * 0 * N = N + * 0 * I = N + * N * n = N + * N * 0 = N + * N * N = N + * N * I = N + * I * n = I + * I * 0 = N + * I * N = N + * I * I = I + * + * Return a new Decimal whose value is this Decimal times `y`, rounded to `precision` significant + * digits using rounding mode `rounding`. + * + */ + P.times = P.mul = function (y) { + var carry, e, i, k, r, rL, t, xdL, ydL, + x = this, + Ctor = x.constructor, + xd = x.d, + yd = (y = new Ctor(y)).d; + + y.s *= x.s; + + // If either is NaN, ±Infinity or ±0... + if (!xd || !xd[0] || !yd || !yd[0]) { + + return new Ctor(!y.s || xd && !xd[0] && !yd || yd && !yd[0] && !xd + + // Return NaN if either is NaN. + // Return NaN if x is ±0 and y is ±Infinity, or y is ±0 and x is ±Infinity. + ? NaN + + // Return ±Infinity if either is ±Infinity. + // Return ±0 if either is ±0. + : !xd || !yd ? y.s / 0 : y.s * 0); + } + + e = mathfloor(x.e / LOG_BASE) + mathfloor(y.e / LOG_BASE); + xdL = xd.length; + ydL = yd.length; + + // Ensure xd points to the longer array. + if (xdL < ydL) { + r = xd; + xd = yd; + yd = r; + rL = xdL; + xdL = ydL; + ydL = rL; + } + + // Initialise the result array with zeros. + r = []; + rL = xdL + ydL; + for (i = rL; i--;) r.push(0); + + // Multiply! + for (i = ydL; --i >= 0;) { + carry = 0; + for (k = xdL + i; k > i;) { + t = r[k] + yd[i] * xd[k - i - 1] + carry; + r[k--] = t % BASE | 0; + carry = t / BASE | 0; + } + + r[k] = (r[k] + carry) % BASE | 0; + } + + // Remove trailing zeros. + for (; !r[--rL];) r.pop(); + + if (carry) ++e; + else r.shift(); + + y.d = r; + y.e = getBase10Exponent(r, e); + + return external ? finalise(y, Ctor.precision, Ctor.rounding) : y; + }; + + + /* + * Return a string representing the value of this Decimal in base 2, round to `sd` significant + * digits using rounding mode `rm`. + * + * If the optional `sd` argument is present then return binary exponential notation. + * + * [sd] {number} Significant digits. Integer, 1 to MAX_DIGITS inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + */ + P.toBinary = function (sd, rm) { + return toStringBinary(this, 2, sd, rm); + }; + + + /* + * Return a new Decimal whose value is the value of this Decimal rounded to a maximum of `dp` + * decimal places using rounding mode `rm` or `rounding` if `rm` is omitted. + * + * If `dp` is omitted, return a new Decimal whose value is the value of this Decimal. + * + * [dp] {number} Decimal places. Integer, 0 to MAX_DIGITS inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + */ + P.toDecimalPlaces = P.toDP = function (dp, rm) { + var x = this, + Ctor = x.constructor; + + x = new Ctor(x); + if (dp === void 0) return x; + + checkInt32(dp, 0, MAX_DIGITS); + + if (rm === void 0) rm = Ctor.rounding; + else checkInt32(rm, 0, 8); + + return finalise(x, dp + x.e + 1, rm); + }; + + + /* + * Return a string representing the value of this Decimal in exponential notation rounded to + * `dp` fixed decimal places using rounding mode `rounding`. + * + * [dp] {number} Decimal places. Integer, 0 to MAX_DIGITS inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + */ + P.toExponential = function (dp, rm) { + var str, + x = this, + Ctor = x.constructor; + + if (dp === void 0) { + str = finiteToString(x, true); + } else { + checkInt32(dp, 0, MAX_DIGITS); + + if (rm === void 0) rm = Ctor.rounding; + else checkInt32(rm, 0, 8); + + x = finalise(new Ctor(x), dp + 1, rm); + str = finiteToString(x, true, dp + 1); + } + + return x.isNeg() && !x.isZero() ? '-' + str : str; + }; + + + /* + * Return a string representing the value of this Decimal in normal (fixed-point) notation to + * `dp` fixed decimal places and rounded using rounding mode `rm` or `rounding` if `rm` is + * omitted. + * + * As with JavaScript numbers, (-0).toFixed(0) is '0', but e.g. (-0.00001).toFixed(0) is '-0'. + * + * [dp] {number} Decimal places. Integer, 0 to MAX_DIGITS inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + * (-0).toFixed(0) is '0', but (-0.1).toFixed(0) is '-0'. + * (-0).toFixed(1) is '0.0', but (-0.01).toFixed(1) is '-0.0'. + * (-0).toFixed(3) is '0.000'. + * (-0.5).toFixed(0) is '-0'. + * + */ + P.toFixed = function (dp, rm) { + var str, y, + x = this, + Ctor = x.constructor; + + if (dp === void 0) { + str = finiteToString(x); + } else { + checkInt32(dp, 0, MAX_DIGITS); + + if (rm === void 0) rm = Ctor.rounding; + else checkInt32(rm, 0, 8); + + y = finalise(new Ctor(x), dp + x.e + 1, rm); + str = finiteToString(y, false, dp + y.e + 1); + } + + // To determine whether to add the minus sign look at the value before it was rounded, + // i.e. look at `x` rather than `y`. + return x.isNeg() && !x.isZero() ? '-' + str : str; + }; + + + /* + * Return an array representing the value of this Decimal as a simple fraction with an integer + * numerator and an integer denominator. + * + * The denominator will be a positive non-zero value less than or equal to the specified maximum + * denominator. If a maximum denominator is not specified, the denominator will be the lowest + * value necessary to represent the number exactly. + * + * [maxD] {number|string|bigint|Decimal} Maximum denominator. Integer >= 1 and < Infinity. + * + */ + P.toFraction = function (maxD) { + var d, d0, d1, d2, e, k, n, n0, n1, pr, q, r, + x = this, + xd = x.d, + Ctor = x.constructor; + + if (!xd) return new Ctor(x); + + n1 = d0 = new Ctor(1); + d1 = n0 = new Ctor(0); + + d = new Ctor(d1); + e = d.e = getPrecision(xd) - x.e - 1; + k = e % LOG_BASE; + d.d[0] = mathpow(10, k < 0 ? LOG_BASE + k : k); + + if (maxD == null) { + + // d is 10**e, the minimum max-denominator needed. + maxD = e > 0 ? d : n1; + } else { + n = new Ctor(maxD); + if (!n.isInt() || n.lt(n1)) throw Error(invalidArgument + n); + maxD = n.gt(d) ? (e > 0 ? d : n1) : n; + } + + external = false; + n = new Ctor(digitsToString(xd)); + pr = Ctor.precision; + Ctor.precision = e = xd.length * LOG_BASE * 2; + + for (;;) { + q = divide(n, d, 0, 1, 1); + d2 = d0.plus(q.times(d1)); + if (d2.cmp(maxD) == 1) break; + d0 = d1; + d1 = d2; + d2 = n1; + n1 = n0.plus(q.times(d2)); + n0 = d2; + d2 = d; + d = n.minus(q.times(d2)); + n = d2; + } + + d2 = divide(maxD.minus(d0), d1, 0, 1, 1); + n0 = n0.plus(d2.times(n1)); + d0 = d0.plus(d2.times(d1)); + n0.s = n1.s = x.s; + + // Determine which fraction is closer to x, n0/d0 or n1/d1? + r = divide(n1, d1, e, 1).minus(x).abs().cmp(divide(n0, d0, e, 1).minus(x).abs()) < 1 + ? [n1, d1] : [n0, d0]; + + Ctor.precision = pr; + external = true; + + return r; + }; + + + /* + * Return a string representing the value of this Decimal in base 16, round to `sd` significant + * digits using rounding mode `rm`. + * + * If the optional `sd` argument is present then return binary exponential notation. + * + * [sd] {number} Significant digits. Integer, 1 to MAX_DIGITS inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + */ + P.toHexadecimal = P.toHex = function (sd, rm) { + return toStringBinary(this, 16, sd, rm); + }; + + + /* + * Returns a new Decimal whose value is the nearest multiple of `y` in the direction of rounding + * mode `rm`, or `Decimal.rounding` if `rm` is omitted, to the value of this Decimal. + * + * The return value will always have the same sign as this Decimal, unless either this Decimal + * or `y` is NaN, in which case the return value will be also be NaN. + * + * The return value is not affected by the value of `precision`. + * + * y {number|string|bigint|Decimal} The magnitude to round to a multiple of. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + * 'toNearest() rounding mode not an integer: {rm}' + * 'toNearest() rounding mode out of range: {rm}' + * + */ + P.toNearest = function (y, rm) { + var x = this, + Ctor = x.constructor; + + x = new Ctor(x); + + if (y == null) { + + // If x is not finite, return x. + if (!x.d) return x; + + y = new Ctor(1); + rm = Ctor.rounding; + } else { + y = new Ctor(y); + if (rm === void 0) { + rm = Ctor.rounding; + } else { + checkInt32(rm, 0, 8); + } + + // If x is not finite, return x if y is not NaN, else NaN. + if (!x.d) return y.s ? x : y; + + // If y is not finite, return Infinity with the sign of x if y is Infinity, else NaN. + if (!y.d) { + if (y.s) y.s = x.s; + return y; + } + } + + // If y is not zero, calculate the nearest multiple of y to x. + if (y.d[0]) { + external = false; + x = divide(x, y, 0, rm, 1).times(y); + external = true; + finalise(x); + + // If y is zero, return zero with the sign of x. + } else { + y.s = x.s; + x = y; + } + + return x; + }; + + + /* + * Return the value of this Decimal converted to a number primitive. + * Zero keeps its sign. + * + */ + P.toNumber = function () { + return +this; + }; + + + /* + * Return a string representing the value of this Decimal in base 8, round to `sd` significant + * digits using rounding mode `rm`. + * + * If the optional `sd` argument is present then return binary exponential notation. + * + * [sd] {number} Significant digits. Integer, 1 to MAX_DIGITS inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + */ + P.toOctal = function (sd, rm) { + return toStringBinary(this, 8, sd, rm); + }; + + + /* + * Return a new Decimal whose value is the value of this Decimal raised to the power `y`, rounded + * to `precision` significant digits using rounding mode `rounding`. + * + * ECMAScript compliant. + * + * pow(x, NaN) = NaN + * pow(x, ±0) = 1 + + * pow(NaN, non-zero) = NaN + * pow(abs(x) > 1, +Infinity) = +Infinity + * pow(abs(x) > 1, -Infinity) = +0 + * pow(abs(x) == 1, ±Infinity) = NaN + * pow(abs(x) < 1, +Infinity) = +0 + * pow(abs(x) < 1, -Infinity) = +Infinity + * pow(+Infinity, y > 0) = +Infinity + * pow(+Infinity, y < 0) = +0 + * pow(-Infinity, odd integer > 0) = -Infinity + * pow(-Infinity, even integer > 0) = +Infinity + * pow(-Infinity, odd integer < 0) = -0 + * pow(-Infinity, even integer < 0) = +0 + * pow(+0, y > 0) = +0 + * pow(+0, y < 0) = +Infinity + * pow(-0, odd integer > 0) = -0 + * pow(-0, even integer > 0) = +0 + * pow(-0, odd integer < 0) = -Infinity + * pow(-0, even integer < 0) = +Infinity + * pow(finite x < 0, finite non-integer) = NaN + * + * For non-integer or very large exponents pow(x, y) is calculated using + * + * x^y = exp(y*ln(x)) + * + * Assuming the first 15 rounding digits are each equally likely to be any digit 0-9, the + * probability of an incorrectly rounded result + * P([49]9{14} | [50]0{14}) = 2 * 0.2 * 10^-14 = 4e-15 = 1/2.5e+14 + * i.e. 1 in 250,000,000,000,000 + * + * If a result is incorrectly rounded the maximum error will be 1 ulp (unit in last place). + * + * y {number|string|bigint|Decimal} The power to which to raise this Decimal. + * + */ + P.toPower = P.pow = function (y) { + var e, k, pr, r, rm, s, + x = this, + Ctor = x.constructor, + yn = +(y = new Ctor(y)); + + // Either ±Infinity, NaN or ±0? + if (!x.d || !y.d || !x.d[0] || !y.d[0]) return new Ctor(mathpow(+x, yn)); + + x = new Ctor(x); + + if (x.eq(1)) return x; + + pr = Ctor.precision; + rm = Ctor.rounding; + + if (y.eq(1)) return finalise(x, pr, rm); + + // y exponent + e = mathfloor(y.e / LOG_BASE); + + // If y is a small integer use the 'exponentiation by squaring' algorithm. + if (e >= y.d.length - 1 && (k = yn < 0 ? -yn : yn) <= MAX_SAFE_INTEGER) { + r = intPow(Ctor, x, k, pr); + return y.s < 0 ? new Ctor(1).div(r) : finalise(r, pr, rm); + } + + s = x.s; + + // if x is negative + if (s < 0) { + + // if y is not an integer + if (e < y.d.length - 1) return new Ctor(NaN); + + // Result is positive if x is negative and the last digit of integer y is even. + if ((y.d[e] & 1) == 0) s = 1; + + // if x.eq(-1) + if (x.e == 0 && x.d[0] == 1 && x.d.length == 1) { + x.s = s; + return x; + } + } + + // Estimate result exponent. + // x^y = 10^e, where e = y * log10(x) + // log10(x) = log10(x_significand) + x_exponent + // log10(x_significand) = ln(x_significand) / ln(10) + k = mathpow(+x, yn); + e = k == 0 || !isFinite(k) + ? mathfloor(yn * (Math.log('0.' + digitsToString(x.d)) / Math.LN10 + x.e + 1)) + : new Ctor(k + '').e; + + // Exponent estimate may be incorrect e.g. x: 0.999999999999999999, y: 2.29, e: 0, r.e: -1. + + // Overflow/underflow? + if (e > Ctor.maxE + 1 || e < Ctor.minE - 1) return new Ctor(e > 0 ? s / 0 : 0); + + external = false; + Ctor.rounding = x.s = 1; + + // Estimate the extra guard digits needed to ensure five correct rounding digits from + // naturalLogarithm(x). Example of failure without these extra digits (precision: 10): + // new Decimal(2.32456).pow('2087987436534566.46411') + // should be 1.162377823e+764914905173815, but is 1.162355823e+764914905173815 + k = Math.min(12, (e + '').length); + + // r = x^y = exp(y*ln(x)) + r = naturalExponential(y.times(naturalLogarithm(x, pr + k)), pr); + + // r may be Infinity, e.g. (0.9999999999999999).pow(-1e+40) + if (r.d) { + + // Truncate to the required precision plus five rounding digits. + r = finalise(r, pr + 5, 1); + + // If the rounding digits are [49]9999 or [50]0000 increase the precision by 10 and recalculate + // the result. + if (checkRoundingDigits(r.d, pr, rm)) { + e = pr + 10; + + // Truncate to the increased precision plus five rounding digits. + r = finalise(naturalExponential(y.times(naturalLogarithm(x, e + k)), e), e + 5, 1); + + // Check for 14 nines from the 2nd rounding digit (the first rounding digit may be 4 or 9). + if (+digitsToString(r.d).slice(pr + 1, pr + 15) + 1 == 1e14) { + r = finalise(r, pr + 1, 0); + } + } + } + + r.s = s; + external = true; + Ctor.rounding = rm; + + return finalise(r, pr, rm); + }; + + + /* + * Return a string representing the value of this Decimal rounded to `sd` significant digits + * using rounding mode `rounding`. + * + * Return exponential notation if `sd` is less than the number of digits necessary to represent + * the integer part of the value in normal notation. + * + * [sd] {number} Significant digits. Integer, 1 to MAX_DIGITS inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + */ + P.toPrecision = function (sd, rm) { + var str, + x = this, + Ctor = x.constructor; + + if (sd === void 0) { + str = finiteToString(x, x.e <= Ctor.toExpNeg || x.e >= Ctor.toExpPos); + } else { + checkInt32(sd, 1, MAX_DIGITS); + + if (rm === void 0) rm = Ctor.rounding; + else checkInt32(rm, 0, 8); + + x = finalise(new Ctor(x), sd, rm); + str = finiteToString(x, sd <= x.e || x.e <= Ctor.toExpNeg, sd); + } + + return x.isNeg() && !x.isZero() ? '-' + str : str; + }; + + + /* + * Return a new Decimal whose value is the value of this Decimal rounded to a maximum of `sd` + * significant digits using rounding mode `rm`, or to `precision` and `rounding` respectively if + * omitted. + * + * [sd] {number} Significant digits. Integer, 1 to MAX_DIGITS inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + * 'toSD() digits out of range: {sd}' + * 'toSD() digits not an integer: {sd}' + * 'toSD() rounding mode not an integer: {rm}' + * 'toSD() rounding mode out of range: {rm}' + * + */ + P.toSignificantDigits = P.toSD = function (sd, rm) { + var x = this, + Ctor = x.constructor; + + if (sd === void 0) { + sd = Ctor.precision; + rm = Ctor.rounding; + } else { + checkInt32(sd, 1, MAX_DIGITS); + + if (rm === void 0) rm = Ctor.rounding; + else checkInt32(rm, 0, 8); + } + + return finalise(new Ctor(x), sd, rm); + }; + + + /* + * Return a string representing the value of this Decimal. + * + * Return exponential notation if this Decimal has a positive exponent equal to or greater than + * `toExpPos`, or a negative exponent equal to or less than `toExpNeg`. + * + */ + P.toString = function () { + var x = this, + Ctor = x.constructor, + str = finiteToString(x, x.e <= Ctor.toExpNeg || x.e >= Ctor.toExpPos); + + return x.isNeg() && !x.isZero() ? '-' + str : str; + }; + + + /* + * Return a new Decimal whose value is the value of this Decimal truncated to a whole number. + * + */ + P.truncated = P.trunc = function () { + return finalise(new this.constructor(this), this.e + 1, 1); + }; + + + /* + * Return a string representing the value of this Decimal. + * Unlike `toString`, negative zero will include the minus sign. + * + */ + P.valueOf = P.toJSON = function () { + var x = this, + Ctor = x.constructor, + str = finiteToString(x, x.e <= Ctor.toExpNeg || x.e >= Ctor.toExpPos); + + return x.isNeg() ? '-' + str : str; + }; + + + // Helper functions for Decimal.prototype (P) and/or Decimal methods, and their callers. + + + /* + * digitsToString P.cubeRoot, P.logarithm, P.squareRoot, P.toFraction, P.toPower, + * finiteToString, naturalExponential, naturalLogarithm + * checkInt32 P.toDecimalPlaces, P.toExponential, P.toFixed, P.toNearest, + * P.toPrecision, P.toSignificantDigits, toStringBinary, random + * checkRoundingDigits P.logarithm, P.toPower, naturalExponential, naturalLogarithm + * convertBase toStringBinary, parseOther + * cos P.cos + * divide P.atanh, P.cubeRoot, P.dividedBy, P.dividedToIntegerBy, + * P.logarithm, P.modulo, P.squareRoot, P.tan, P.tanh, P.toFraction, + * P.toNearest, toStringBinary, naturalExponential, naturalLogarithm, + * taylorSeries, atan2, parseOther + * finalise P.absoluteValue, P.atan, P.atanh, P.ceil, P.cos, P.cosh, + * P.cubeRoot, P.dividedToIntegerBy, P.floor, P.logarithm, P.minus, + * P.modulo, P.negated, P.plus, P.round, P.sin, P.sinh, P.squareRoot, + * P.tan, P.times, P.toDecimalPlaces, P.toExponential, P.toFixed, + * P.toNearest, P.toPower, P.toPrecision, P.toSignificantDigits, + * P.truncated, divide, getLn10, getPi, naturalExponential, + * naturalLogarithm, ceil, floor, round, trunc + * finiteToString P.toExponential, P.toFixed, P.toPrecision, P.toString, P.valueOf, + * toStringBinary + * getBase10Exponent P.minus, P.plus, P.times, parseOther + * getLn10 P.logarithm, naturalLogarithm + * getPi P.acos, P.asin, P.atan, toLessThanHalfPi, atan2 + * getPrecision P.precision, P.toFraction + * getZeroString digitsToString, finiteToString + * intPow P.toPower, parseOther + * isOdd toLessThanHalfPi + * maxOrMin max, min + * naturalExponential P.naturalExponential, P.toPower + * naturalLogarithm P.acosh, P.asinh, P.atanh, P.logarithm, P.naturalLogarithm, + * P.toPower, naturalExponential + * nonFiniteToString finiteToString, toStringBinary + * parseDecimal Decimal + * parseOther Decimal + * sin P.sin + * taylorSeries P.cosh, P.sinh, cos, sin + * toLessThanHalfPi P.cos, P.sin + * toStringBinary P.toBinary, P.toHexadecimal, P.toOctal + * truncate intPow + * + * Throws: P.logarithm, P.precision, P.toFraction, checkInt32, getLn10, getPi, + * naturalLogarithm, config, parseOther, random, Decimal + */ + + + function digitsToString(d) { + var i, k, ws, + indexOfLastWord = d.length - 1, + str = '', + w = d[0]; + + if (indexOfLastWord > 0) { + str += w; + for (i = 1; i < indexOfLastWord; i++) { + ws = d[i] + ''; + k = LOG_BASE - ws.length; + if (k) str += getZeroString(k); + str += ws; + } + + w = d[i]; + ws = w + ''; + k = LOG_BASE - ws.length; + if (k) str += getZeroString(k); + } else if (w === 0) { + return '0'; + } + + // Remove trailing zeros of last w. + for (; w % 10 === 0;) w /= 10; + + return str + w; + } + + + function checkInt32(i, min, max) { + if (i !== ~~i || i < min || i > max) { + throw Error(invalidArgument + i); + } + } + + + /* + * Check 5 rounding digits if `repeating` is null, 4 otherwise. + * `repeating == null` if caller is `log` or `pow`, + * `repeating != null` if caller is `naturalLogarithm` or `naturalExponential`. + */ + function checkRoundingDigits(d, i, rm, repeating) { + var di, k, r, rd; + + // Get the length of the first word of the array d. + for (k = d[0]; k >= 10; k /= 10) --i; + + // Is the rounding digit in the first word of d? + if (--i < 0) { + i += LOG_BASE; + di = 0; + } else { + di = Math.ceil((i + 1) / LOG_BASE); + i %= LOG_BASE; + } + + // i is the index (0 - 6) of the rounding digit. + // E.g. if within the word 3487563 the first rounding digit is 5, + // then i = 4, k = 1000, rd = 3487563 % 1000 = 563 + k = mathpow(10, LOG_BASE - i); + rd = d[di] % k | 0; + + if (repeating == null) { + if (i < 3) { + if (i == 0) rd = rd / 100 | 0; + else if (i == 1) rd = rd / 10 | 0; + r = rm < 4 && rd == 99999 || rm > 3 && rd == 49999 || rd == 50000 || rd == 0; + } else { + r = (rm < 4 && rd + 1 == k || rm > 3 && rd + 1 == k / 2) && + (d[di + 1] / k / 100 | 0) == mathpow(10, i - 2) - 1 || + (rd == k / 2 || rd == 0) && (d[di + 1] / k / 100 | 0) == 0; + } + } else { + if (i < 4) { + if (i == 0) rd = rd / 1000 | 0; + else if (i == 1) rd = rd / 100 | 0; + else if (i == 2) rd = rd / 10 | 0; + r = (repeating || rm < 4) && rd == 9999 || !repeating && rm > 3 && rd == 4999; + } else { + r = ((repeating || rm < 4) && rd + 1 == k || + (!repeating && rm > 3) && rd + 1 == k / 2) && + (d[di + 1] / k / 1000 | 0) == mathpow(10, i - 3) - 1; + } + } + + return r; + } + + + // Convert string of `baseIn` to an array of numbers of `baseOut`. + // Eg. convertBase('255', 10, 16) returns [15, 15]. + // Eg. convertBase('ff', 16, 10) returns [2, 5, 5]. + function convertBase(str, baseIn, baseOut) { + var j, + arr = [0], + arrL, + i = 0, + strL = str.length; + + for (; i < strL;) { + for (arrL = arr.length; arrL--;) arr[arrL] *= baseIn; + arr[0] += NUMERALS.indexOf(str.charAt(i++)); + for (j = 0; j < arr.length; j++) { + if (arr[j] > baseOut - 1) { + if (arr[j + 1] === void 0) arr[j + 1] = 0; + arr[j + 1] += arr[j] / baseOut | 0; + arr[j] %= baseOut; + } + } + } + + return arr.reverse(); + } + + + /* + * cos(x) = 1 - x^2/2! + x^4/4! - ... + * |x| < pi/2 + * + */ + function cosine(Ctor, x) { + var k, len, y; + + if (x.isZero()) return x; + + // Argument reduction: cos(4x) = 8*(cos^4(x) - cos^2(x)) + 1 + // i.e. cos(x) = 8*(cos^4(x/4) - cos^2(x/4)) + 1 + + // Estimate the optimum number of times to use the argument reduction. + len = x.d.length; + if (len < 32) { + k = Math.ceil(len / 3); + y = (1 / tinyPow(4, k)).toString(); + } else { + k = 16; + y = '2.3283064365386962890625e-10'; + } + + Ctor.precision += k; + + x = taylorSeries(Ctor, 1, x.times(y), new Ctor(1)); + + // Reverse argument reduction + for (var i = k; i--;) { + var cos2x = x.times(x); + x = cos2x.times(cos2x).minus(cos2x).times(8).plus(1); + } + + Ctor.precision -= k; + + return x; + } + + + /* + * Perform division in the specified base. + */ + var divide = (function () { + + // Assumes non-zero x and k, and hence non-zero result. + function multiplyInteger(x, k, base) { + var temp, + carry = 0, + i = x.length; + + for (x = x.slice(); i--;) { + temp = x[i] * k + carry; + x[i] = temp % base | 0; + carry = temp / base | 0; + } + + if (carry) x.unshift(carry); + + return x; + } + + function compare(a, b, aL, bL) { + var i, r; + + if (aL != bL) { + r = aL > bL ? 1 : -1; + } else { + for (i = r = 0; i < aL; i++) { + if (a[i] != b[i]) { + r = a[i] > b[i] ? 1 : -1; + break; + } + } + } + + return r; + } + + function subtract(a, b, aL, base) { + var i = 0; + + // Subtract b from a. + for (; aL--;) { + a[aL] -= i; + i = a[aL] < b[aL] ? 1 : 0; + a[aL] = i * base + a[aL] - b[aL]; + } + + // Remove leading zeros. + for (; !a[0] && a.length > 1;) a.shift(); + } + + return function (x, y, pr, rm, dp, base) { + var cmp, e, i, k, logBase, more, prod, prodL, q, qd, rem, remL, rem0, sd, t, xi, xL, yd0, + yL, yz, + Ctor = x.constructor, + sign = x.s == y.s ? 1 : -1, + xd = x.d, + yd = y.d; + + // Either NaN, Infinity or 0? + if (!xd || !xd[0] || !yd || !yd[0]) { + + return new Ctor(// Return NaN if either NaN, or both Infinity or 0. + !x.s || !y.s || (xd ? yd && xd[0] == yd[0] : !yd) ? NaN : + + // Return ±0 if x is 0 or y is ±Infinity, or return ±Infinity as y is 0. + xd && xd[0] == 0 || !yd ? sign * 0 : sign / 0); + } + + if (base) { + logBase = 1; + e = x.e - y.e; + } else { + base = BASE; + logBase = LOG_BASE; + e = mathfloor(x.e / logBase) - mathfloor(y.e / logBase); + } + + yL = yd.length; + xL = xd.length; + q = new Ctor(sign); + qd = q.d = []; + + // Result exponent may be one less than e. + // The digit array of a Decimal from toStringBinary may have trailing zeros. + for (i = 0; yd[i] == (xd[i] || 0); i++); + + if (yd[i] > (xd[i] || 0)) e--; + + if (pr == null) { + sd = pr = Ctor.precision; + rm = Ctor.rounding; + } else if (dp) { + sd = pr + (x.e - y.e) + 1; + } else { + sd = pr; + } + + if (sd < 0) { + qd.push(1); + more = true; + } else { + + // Convert precision in number of base 10 digits to base 1e7 digits. + sd = sd / logBase + 2 | 0; + i = 0; + + // divisor < 1e7 + if (yL == 1) { + k = 0; + yd = yd[0]; + sd++; + + // k is the carry. + for (; (i < xL || k) && sd--; i++) { + t = k * base + (xd[i] || 0); + qd[i] = t / yd | 0; + k = t % yd | 0; + } + + more = k || i < xL; + + // divisor >= 1e7 + } else { + + // Normalise xd and yd so highest order digit of yd is >= base/2 + k = base / (yd[0] + 1) | 0; + + if (k > 1) { + yd = multiplyInteger(yd, k, base); + xd = multiplyInteger(xd, k, base); + yL = yd.length; + xL = xd.length; + } + + xi = yL; + rem = xd.slice(0, yL); + remL = rem.length; + + // Add zeros to make remainder as long as divisor. + for (; remL < yL;) rem[remL++] = 0; + + yz = yd.slice(); + yz.unshift(0); + yd0 = yd[0]; + + if (yd[1] >= base / 2) ++yd0; + + do { + k = 0; + + // Compare divisor and remainder. + cmp = compare(yd, rem, yL, remL); + + // If divisor < remainder. + if (cmp < 0) { + + // Calculate trial digit, k. + rem0 = rem[0]; + if (yL != remL) rem0 = rem0 * base + (rem[1] || 0); + + // k will be how many times the divisor goes into the current remainder. + k = rem0 / yd0 | 0; + + // Algorithm: + // 1. product = divisor * trial digit (k) + // 2. if product > remainder: product -= divisor, k-- + // 3. remainder -= product + // 4. if product was < remainder at 2: + // 5. compare new remainder and divisor + // 6. If remainder > divisor: remainder -= divisor, k++ + + if (k > 1) { + if (k >= base) k = base - 1; + + // product = divisor * trial digit. + prod = multiplyInteger(yd, k, base); + prodL = prod.length; + remL = rem.length; + + // Compare product and remainder. + cmp = compare(prod, rem, prodL, remL); + + // product > remainder. + if (cmp == 1) { + k--; + + // Subtract divisor from product. + subtract(prod, yL < prodL ? yz : yd, prodL, base); + } + } else { + + // cmp is -1. + // If k is 0, there is no need to compare yd and rem again below, so change cmp to 1 + // to avoid it. If k is 1 there is a need to compare yd and rem again below. + if (k == 0) cmp = k = 1; + prod = yd.slice(); + } + + prodL = prod.length; + if (prodL < remL) prod.unshift(0); + + // Subtract product from remainder. + subtract(rem, prod, remL, base); + + // If product was < previous remainder. + if (cmp == -1) { + remL = rem.length; + + // Compare divisor and new remainder. + cmp = compare(yd, rem, yL, remL); + + // If divisor < new remainder, subtract divisor from remainder. + if (cmp < 1) { + k++; + + // Subtract divisor from remainder. + subtract(rem, yL < remL ? yz : yd, remL, base); + } + } + + remL = rem.length; + } else if (cmp === 0) { + k++; + rem = [0]; + } // if cmp === 1, k will be 0 + + // Add the next digit, k, to the result array. + qd[i++] = k; + + // Update the remainder. + if (cmp && rem[0]) { + rem[remL++] = xd[xi] || 0; + } else { + rem = [xd[xi]]; + remL = 1; + } + + } while ((xi++ < xL || rem[0] !== void 0) && sd--); + + more = rem[0] !== void 0; + } + + // Leading zero? + if (!qd[0]) qd.shift(); + } + + // logBase is 1 when divide is being used for base conversion. + if (logBase == 1) { + q.e = e; + inexact = more; + } else { + + // To calculate q.e, first get the number of digits of qd[0]. + for (i = 1, k = qd[0]; k >= 10; k /= 10) i++; + q.e = i + e * logBase - 1; + + finalise(q, dp ? pr + q.e + 1 : pr, rm, more); + } + + return q; + }; + })(); + + + /* + * Round `x` to `sd` significant digits using rounding mode `rm`. + * Check for over/under-flow. + */ + function finalise(x, sd, rm, isTruncated) { + var digits, i, j, k, rd, roundUp, w, xd, xdi, + Ctor = x.constructor; + + // Don't round if sd is null or undefined. + out: if (sd != null) { + xd = x.d; + + // Infinity/NaN. + if (!xd) return x; + + // rd: the rounding digit, i.e. the digit after the digit that may be rounded up. + // w: the word of xd containing rd, a base 1e7 number. + // xdi: the index of w within xd. + // digits: the number of digits of w. + // i: what would be the index of rd within w if all the numbers were 7 digits long (i.e. if + // they had leading zeros) + // j: if > 0, the actual index of rd within w (if < 0, rd is a leading zero). + + // Get the length of the first word of the digits array xd. + for (digits = 1, k = xd[0]; k >= 10; k /= 10) digits++; + i = sd - digits; + + // Is the rounding digit in the first word of xd? + if (i < 0) { + i += LOG_BASE; + j = sd; + w = xd[xdi = 0]; + + // Get the rounding digit at index j of w. + rd = w / mathpow(10, digits - j - 1) % 10 | 0; + } else { + xdi = Math.ceil((i + 1) / LOG_BASE); + k = xd.length; + if (xdi >= k) { + if (isTruncated) { + + // Needed by `naturalExponential`, `naturalLogarithm` and `squareRoot`. + for (; k++ <= xdi;) xd.push(0); + w = rd = 0; + digits = 1; + i %= LOG_BASE; + j = i - LOG_BASE + 1; + } else { + break out; + } + } else { + w = k = xd[xdi]; + + // Get the number of digits of w. + for (digits = 1; k >= 10; k /= 10) digits++; + + // Get the index of rd within w. + i %= LOG_BASE; + + // Get the index of rd within w, adjusted for leading zeros. + // The number of leading zeros of w is given by LOG_BASE - digits. + j = i - LOG_BASE + digits; + + // Get the rounding digit at index j of w. + rd = j < 0 ? 0 : w / mathpow(10, digits - j - 1) % 10 | 0; + } + } + + // Are there any non-zero digits after the rounding digit? + isTruncated = isTruncated || sd < 0 || + xd[xdi + 1] !== void 0 || (j < 0 ? w : w % mathpow(10, digits - j - 1)); + + // The expression `w % mathpow(10, digits - j - 1)` returns all the digits of w to the right + // of the digit at (left-to-right) index j, e.g. if w is 908714 and j is 2, the expression + // will give 714. + + roundUp = rm < 4 + ? (rd || isTruncated) && (rm == 0 || rm == (x.s < 0 ? 3 : 2)) + : rd > 5 || rd == 5 && (rm == 4 || isTruncated || rm == 6 && + + // Check whether the digit to the left of the rounding digit is odd. + ((i > 0 ? j > 0 ? w / mathpow(10, digits - j) : 0 : xd[xdi - 1]) % 10) & 1 || + rm == (x.s < 0 ? 8 : 7)); + + if (sd < 1 || !xd[0]) { + xd.length = 0; + if (roundUp) { + + // Convert sd to decimal places. + sd -= x.e + 1; + + // 1, 0.1, 0.01, 0.001, 0.0001 etc. + xd[0] = mathpow(10, (LOG_BASE - sd % LOG_BASE) % LOG_BASE); + x.e = -sd || 0; + } else { + + // Zero. + xd[0] = x.e = 0; + } + + return x; + } + + // Remove excess digits. + if (i == 0) { + xd.length = xdi; + k = 1; + xdi--; + } else { + xd.length = xdi + 1; + k = mathpow(10, LOG_BASE - i); + + // E.g. 56700 becomes 56000 if 7 is the rounding digit. + // j > 0 means i > number of leading zeros of w. + xd[xdi] = j > 0 ? (w / mathpow(10, digits - j) % mathpow(10, j) | 0) * k : 0; + } + + if (roundUp) { + for (;;) { + + // Is the digit to be rounded up in the first word of xd? + if (xdi == 0) { + + // i will be the length of xd[0] before k is added. + for (i = 1, j = xd[0]; j >= 10; j /= 10) i++; + j = xd[0] += k; + for (k = 1; j >= 10; j /= 10) k++; + + // if i != k the length has increased. + if (i != k) { + x.e++; + if (xd[0] == BASE) xd[0] = 1; + } + + break; + } else { + xd[xdi] += k; + if (xd[xdi] != BASE) break; + xd[xdi--] = 0; + k = 1; + } + } + } + + // Remove trailing zeros. + for (i = xd.length; xd[--i] === 0;) xd.pop(); + } + + if (external) { + + // Overflow? + if (x.e > Ctor.maxE) { + + // Infinity. + x.d = null; + x.e = NaN; + + // Underflow? + } else if (x.e < Ctor.minE) { + + // Zero. + x.e = 0; + x.d = [0]; + // Ctor.underflow = true; + } // else Ctor.underflow = false; + } + + return x; + } + + + function finiteToString(x, isExp, sd) { + if (!x.isFinite()) return nonFiniteToString(x); + var k, + e = x.e, + str = digitsToString(x.d), + len = str.length; + + if (isExp) { + if (sd && (k = sd - len) > 0) { + str = str.charAt(0) + '.' + str.slice(1) + getZeroString(k); + } else if (len > 1) { + str = str.charAt(0) + '.' + str.slice(1); + } + + str = str + (x.e < 0 ? 'e' : 'e+') + x.e; + } else if (e < 0) { + str = '0.' + getZeroString(-e - 1) + str; + if (sd && (k = sd - len) > 0) str += getZeroString(k); + } else if (e >= len) { + str += getZeroString(e + 1 - len); + if (sd && (k = sd - e - 1) > 0) str = str + '.' + getZeroString(k); + } else { + if ((k = e + 1) < len) str = str.slice(0, k) + '.' + str.slice(k); + if (sd && (k = sd - len) > 0) { + if (e + 1 === len) str += '.'; + str += getZeroString(k); + } + } + + return str; + } + + + // Calculate the base 10 exponent from the base 1e7 exponent. + function getBase10Exponent(digits, e) { + var w = digits[0]; + + // Add the number of digits of the first word of the digits array. + for ( e *= LOG_BASE; w >= 10; w /= 10) e++; + return e; + } + + + function getLn10(Ctor, sd, pr) { + if (sd > LN10_PRECISION) { + + // Reset global state in case the exception is caught. + external = true; + if (pr) Ctor.precision = pr; + throw Error(precisionLimitExceeded); + } + return finalise(new Ctor(LN10), sd, 1, true); + } + + + function getPi(Ctor, sd, rm) { + if (sd > PI_PRECISION) throw Error(precisionLimitExceeded); + return finalise(new Ctor(PI), sd, rm, true); + } + + + function getPrecision(digits) { + var w = digits.length - 1, + len = w * LOG_BASE + 1; + + w = digits[w]; + + // If non-zero... + if (w) { + + // Subtract the number of trailing zeros of the last word. + for (; w % 10 == 0; w /= 10) len--; + + // Add the number of digits of the first word. + for (w = digits[0]; w >= 10; w /= 10) len++; + } + + return len; + } + + + function getZeroString(k) { + var zs = ''; + for (; k--;) zs += '0'; + return zs; + } + + + /* + * Return a new Decimal whose value is the value of Decimal `x` to the power `n`, where `n` is an + * integer of type number. + * + * Implements 'exponentiation by squaring'. Called by `pow` and `parseOther`. + * + */ + function intPow(Ctor, x, n, pr) { + var isTruncated, + r = new Ctor(1), + + // Max n of 9007199254740991 takes 53 loop iterations. + // Maximum digits array length; leaves [28, 34] guard digits. + k = Math.ceil(pr / LOG_BASE + 4); + + external = false; + + for (;;) { + if (n % 2) { + r = r.times(x); + if (truncate(r.d, k)) isTruncated = true; + } + + n = mathfloor(n / 2); + if (n === 0) { + + // To ensure correct rounding when r.d is truncated, increment the last word if it is zero. + n = r.d.length - 1; + if (isTruncated && r.d[n] === 0) ++r.d[n]; + break; + } + + x = x.times(x); + truncate(x.d, k); + } + + external = true; + + return r; + } + + + function isOdd(n) { + return n.d[n.d.length - 1] & 1; + } + + + /* + * Handle `max` (`n` is -1) and `min` (`n` is 1). + */ + function maxOrMin(Ctor, args, n) { + var k, y, + x = new Ctor(args[0]), + i = 0; + + for (; ++i < args.length;) { + y = new Ctor(args[i]); + + // NaN? + if (!y.s) { + x = y; + break; + } + + k = x.cmp(y); + + if (k === n || k === 0 && x.s === n) { + x = y; + } + } + + return x; + } + + + /* + * Return a new Decimal whose value is the natural exponential of `x` rounded to `sd` significant + * digits. + * + * Taylor/Maclaurin series. + * + * exp(x) = x^0/0! + x^1/1! + x^2/2! + x^3/3! + ... + * + * Argument reduction: + * Repeat x = x / 32, k += 5, until |x| < 0.1 + * exp(x) = exp(x / 2^k)^(2^k) + * + * Previously, the argument was initially reduced by + * exp(x) = exp(r) * 10^k where r = x - k * ln10, k = floor(x / ln10) + * to first put r in the range [0, ln10], before dividing by 32 until |x| < 0.1, but this was + * found to be slower than just dividing repeatedly by 32 as above. + * + * Max integer argument: exp('20723265836946413') = 6.3e+9000000000000000 + * Min integer argument: exp('-20723265836946411') = 1.2e-9000000000000000 + * (Math object integer min/max: Math.exp(709) = 8.2e+307, Math.exp(-745) = 5e-324) + * + * exp(Infinity) = Infinity + * exp(-Infinity) = 0 + * exp(NaN) = NaN + * exp(±0) = 1 + * + * exp(x) is non-terminating for any finite, non-zero x. + * + * The result will always be correctly rounded. + * + */ + function naturalExponential(x, sd) { + var denominator, guard, j, pow, sum, t, wpr, + rep = 0, + i = 0, + k = 0, + Ctor = x.constructor, + rm = Ctor.rounding, + pr = Ctor.precision; + + // 0/NaN/Infinity? + if (!x.d || !x.d[0] || x.e > 17) { + + return new Ctor(x.d + ? !x.d[0] ? 1 : x.s < 0 ? 0 : 1 / 0 + : x.s ? x.s < 0 ? 0 : x : 0 / 0); + } + + if (sd == null) { + external = false; + wpr = pr; + } else { + wpr = sd; + } + + t = new Ctor(0.03125); + + // while abs(x) >= 0.1 + while (x.e > -2) { + + // x = x / 2^5 + x = x.times(t); + k += 5; + } + + // Use 2 * log10(2^k) + 5 (empirically derived) to estimate the increase in precision + // necessary to ensure the first 4 rounding digits are correct. + guard = Math.log(mathpow(2, k)) / Math.LN10 * 2 + 5 | 0; + wpr += guard; + denominator = pow = sum = new Ctor(1); + Ctor.precision = wpr; + + for (;;) { + pow = finalise(pow.times(x), wpr, 1); + denominator = denominator.times(++i); + t = sum.plus(divide(pow, denominator, wpr, 1)); + + if (digitsToString(t.d).slice(0, wpr) === digitsToString(sum.d).slice(0, wpr)) { + j = k; + while (j--) sum = finalise(sum.times(sum), wpr, 1); + + // Check to see if the first 4 rounding digits are [49]999. + // If so, repeat the summation with a higher precision, otherwise + // e.g. with precision: 18, rounding: 1 + // exp(18.404272462595034083567793919843761) = 98372560.1229999999 (should be 98372560.123) + // `wpr - guard` is the index of first rounding digit. + if (sd == null) { + + if (rep < 3 && checkRoundingDigits(sum.d, wpr - guard, rm, rep)) { + Ctor.precision = wpr += 10; + denominator = pow = t = new Ctor(1); + i = 0; + rep++; + } else { + return finalise(sum, Ctor.precision = pr, rm, external = true); + } + } else { + Ctor.precision = pr; + return sum; + } + } + + sum = t; + } + } + + + /* + * Return a new Decimal whose value is the natural logarithm of `x` rounded to `sd` significant + * digits. + * + * ln(-n) = NaN + * ln(0) = -Infinity + * ln(-0) = -Infinity + * ln(1) = 0 + * ln(Infinity) = Infinity + * ln(-Infinity) = NaN + * ln(NaN) = NaN + * + * ln(n) (n != 1) is non-terminating. + * + */ + function naturalLogarithm(y, sd) { + var c, c0, denominator, e, numerator, rep, sum, t, wpr, x1, x2, + n = 1, + guard = 10, + x = y, + xd = x.d, + Ctor = x.constructor, + rm = Ctor.rounding, + pr = Ctor.precision; + + // Is x negative or Infinity, NaN, 0 or 1? + if (x.s < 0 || !xd || !xd[0] || !x.e && xd[0] == 1 && xd.length == 1) { + return new Ctor(xd && !xd[0] ? -1 / 0 : x.s != 1 ? NaN : xd ? 0 : x); + } + + if (sd == null) { + external = false; + wpr = pr; + } else { + wpr = sd; + } + + Ctor.precision = wpr += guard; + c = digitsToString(xd); + c0 = c.charAt(0); + + if (Math.abs(e = x.e) < 1.5e15) { + + // Argument reduction. + // The series converges faster the closer the argument is to 1, so using + // ln(a^b) = b * ln(a), ln(a) = ln(a^b) / b + // multiply the argument by itself until the leading digits of the significand are 7, 8, 9, + // 10, 11, 12 or 13, recording the number of multiplications so the sum of the series can + // later be divided by this number, then separate out the power of 10 using + // ln(a*10^b) = ln(a) + b*ln(10). + + // max n is 21 (gives 0.9, 1.0 or 1.1) (9e15 / 21 = 4.2e14). + //while (c0 < 9 && c0 != 1 || c0 == 1 && c.charAt(1) > 1) { + // max n is 6 (gives 0.7 - 1.3) + while (c0 < 7 && c0 != 1 || c0 == 1 && c.charAt(1) > 3) { + x = x.times(y); + c = digitsToString(x.d); + c0 = c.charAt(0); + n++; + } + + e = x.e; + + if (c0 > 1) { + x = new Ctor('0.' + c); + e++; + } else { + x = new Ctor(c0 + '.' + c.slice(1)); + } + } else { + + // The argument reduction method above may result in overflow if the argument y is a massive + // number with exponent >= 1500000000000000 (9e15 / 6 = 1.5e15), so instead recall this + // function using ln(x*10^e) = ln(x) + e*ln(10). + t = getLn10(Ctor, wpr + 2, pr).times(e + ''); + x = naturalLogarithm(new Ctor(c0 + '.' + c.slice(1)), wpr - guard).plus(t); + Ctor.precision = pr; + + return sd == null ? finalise(x, pr, rm, external = true) : x; + } + + // x1 is x reduced to a value near 1. + x1 = x; + + // Taylor series. + // ln(y) = ln((1 + x)/(1 - x)) = 2(x + x^3/3 + x^5/5 + x^7/7 + ...) + // where x = (y - 1)/(y + 1) (|x| < 1) + sum = numerator = x = divide(x.minus(1), x.plus(1), wpr, 1); + x2 = finalise(x.times(x), wpr, 1); + denominator = 3; + + for (;;) { + numerator = finalise(numerator.times(x2), wpr, 1); + t = sum.plus(divide(numerator, new Ctor(denominator), wpr, 1)); + + if (digitsToString(t.d).slice(0, wpr) === digitsToString(sum.d).slice(0, wpr)) { + sum = sum.times(2); + + // Reverse the argument reduction. Check that e is not 0 because, besides preventing an + // unnecessary calculation, -0 + 0 = +0 and to ensure correct rounding -0 needs to stay -0. + if (e !== 0) sum = sum.plus(getLn10(Ctor, wpr + 2, pr).times(e + '')); + sum = divide(sum, new Ctor(n), wpr, 1); + + // Is rm > 3 and the first 4 rounding digits 4999, or rm < 4 (or the summation has + // been repeated previously) and the first 4 rounding digits 9999? + // If so, restart the summation with a higher precision, otherwise + // e.g. with precision: 12, rounding: 1 + // ln(135520028.6126091714265381533) = 18.7246299999 when it should be 18.72463. + // `wpr - guard` is the index of first rounding digit. + if (sd == null) { + if (checkRoundingDigits(sum.d, wpr - guard, rm, rep)) { + Ctor.precision = wpr += guard; + t = numerator = x = divide(x1.minus(1), x1.plus(1), wpr, 1); + x2 = finalise(x.times(x), wpr, 1); + denominator = rep = 1; + } else { + return finalise(sum, Ctor.precision = pr, rm, external = true); + } + } else { + Ctor.precision = pr; + return sum; + } + } + + sum = t; + denominator += 2; + } + } + + + // ±Infinity, NaN. + function nonFiniteToString(x) { + // Unsigned. + return String(x.s * x.s / 0); + } + + + /* + * Parse the value of a new Decimal `x` from string `str`. + */ + function parseDecimal(x, str) { + var e, i, len; + + // TODO BigInt str: no need to check for decimal point, exponential form or leading zeros. + + // Decimal point? + if ((e = str.indexOf('.')) > -1) str = str.replace('.', ''); + + // Exponential form? + if ((i = str.search(/e/i)) > 0) { + + // Determine exponent. + if (e < 0) e = i; + e += +str.slice(i + 1); + str = str.substring(0, i); + } else if (e < 0) { + + // Integer. + e = str.length; + } + + // Determine leading zeros. + for (i = 0; str.charCodeAt(i) === 48; i++); + + // Determine trailing zeros. + for (len = str.length; str.charCodeAt(len - 1) === 48; --len); + str = str.slice(i, len); + + if (str) { + len -= i; + x.e = e = e - i - 1; + x.d = []; + + // Transform base + + // e is the base 10 exponent. + // i is where to slice str to get the first word of the digits array. + i = (e + 1) % LOG_BASE; + if (e < 0) i += LOG_BASE; + + if (i < len) { + if (i) x.d.push(+str.slice(0, i)); + for (len -= LOG_BASE; i < len;) x.d.push(+str.slice(i, i += LOG_BASE)); + str = str.slice(i); + i = LOG_BASE - str.length; + } else { + i -= len; + } + + for (; i--;) str += '0'; + x.d.push(+str); + + if (external) { + + // Overflow? + if (x.e > x.constructor.maxE) { + + // Infinity. + x.d = null; + x.e = NaN; + + // Underflow? + } else if (x.e < x.constructor.minE) { + + // Zero. + x.e = 0; + x.d = [0]; + // x.constructor.underflow = true; + } // else x.constructor.underflow = false; + } + } else { + + // Zero. + x.e = 0; + x.d = [0]; + } + + return x; + } + + + /* + * Parse the value of a new Decimal `x` from a string `str`, which is not a decimal value. + */ + function parseOther(x, str) { + var base, Ctor, divisor, i, isFloat, len, p, xd, xe; + + if (str.indexOf('_') > -1) { + str = str.replace(/(\d)_(?=\d)/g, '$1'); + if (isDecimal.test(str)) return parseDecimal(x, str); + } else if (str === 'Infinity' || str === 'NaN') { + if (!+str) x.s = NaN; + x.e = NaN; + x.d = null; + return x; + } + + if (isHex.test(str)) { + base = 16; + str = str.toLowerCase(); + } else if (isBinary.test(str)) { + base = 2; + } else if (isOctal.test(str)) { + base = 8; + } else { + throw Error(invalidArgument + str); + } + + // Is there a binary exponent part? + i = str.search(/p/i); + + if (i > 0) { + p = +str.slice(i + 1); + str = str.substring(2, i); + } else { + str = str.slice(2); + } + + // Convert `str` as an integer then divide the result by `base` raised to a power such that the + // fraction part will be restored. + i = str.indexOf('.'); + isFloat = i >= 0; + Ctor = x.constructor; + + if (isFloat) { + str = str.replace('.', ''); + len = str.length; + i = len - i; + + // log[10](16) = 1.2041... , log[10](88) = 1.9444.... + divisor = intPow(Ctor, new Ctor(base), i, i * 2); + } + + xd = convertBase(str, base, BASE); + xe = xd.length - 1; + + // Remove trailing zeros. + for (i = xe; xd[i] === 0; --i) xd.pop(); + if (i < 0) return new Ctor(x.s * 0); + x.e = getBase10Exponent(xd, xe); + x.d = xd; + external = false; + + // At what precision to perform the division to ensure exact conversion? + // maxDecimalIntegerPartDigitCount = ceil(log[10](b) * otherBaseIntegerPartDigitCount) + // log[10](2) = 0.30103, log[10](8) = 0.90309, log[10](16) = 1.20412 + // E.g. ceil(1.2 * 3) = 4, so up to 4 decimal digits are needed to represent 3 hex int digits. + // maxDecimalFractionPartDigitCount = {Hex:4|Oct:3|Bin:1} * otherBaseFractionPartDigitCount + // Therefore using 4 * the number of digits of str will always be enough. + if (isFloat) x = divide(x, divisor, len * 4); + + // Multiply by the binary exponent part if present. + if (p) x = x.times(Math.abs(p) < 54 ? mathpow(2, p) : Decimal.pow(2, p)); + external = true; + + return x; + } + + + /* + * sin(x) = x - x^3/3! + x^5/5! - ... + * |x| < pi/2 + * + */ + function sine(Ctor, x) { + var k, + len = x.d.length; + + if (len < 3) { + return x.isZero() ? x : taylorSeries(Ctor, 2, x, x); + } + + // Argument reduction: sin(5x) = 16*sin^5(x) - 20*sin^3(x) + 5*sin(x) + // i.e. sin(x) = 16*sin^5(x/5) - 20*sin^3(x/5) + 5*sin(x/5) + // and sin(x) = sin(x/5)(5 + sin^2(x/5)(16sin^2(x/5) - 20)) + + // Estimate the optimum number of times to use the argument reduction. + k = 1.4 * Math.sqrt(len); + k = k > 16 ? 16 : k | 0; + + x = x.times(1 / tinyPow(5, k)); + x = taylorSeries(Ctor, 2, x, x); + + // Reverse argument reduction + var sin2_x, + d5 = new Ctor(5), + d16 = new Ctor(16), + d20 = new Ctor(20); + for (; k--;) { + sin2_x = x.times(x); + x = x.times(d5.plus(sin2_x.times(d16.times(sin2_x).minus(d20)))); + } + + return x; + } + + + // Calculate Taylor series for `cos`, `cosh`, `sin` and `sinh`. + function taylorSeries(Ctor, n, x, y, isHyperbolic) { + var j, t, u, x2, + i = 1, + pr = Ctor.precision, + k = Math.ceil(pr / LOG_BASE); + + external = false; + x2 = x.times(x); + u = new Ctor(y); + + for (;;) { + t = divide(u.times(x2), new Ctor(n++ * n++), pr, 1); + u = isHyperbolic ? y.plus(t) : y.minus(t); + y = divide(t.times(x2), new Ctor(n++ * n++), pr, 1); + t = u.plus(y); + + if (t.d[k] !== void 0) { + for (j = k; t.d[j] === u.d[j] && j--;); + if (j == -1) break; + } + + j = u; + u = y; + y = t; + t = j; + i++; + } + + external = true; + t.d.length = k + 1; + + return t; + } + + + // Exponent e must be positive and non-zero. + function tinyPow(b, e) { + var n = b; + while (--e) n *= b; + return n; + } + + + // Return the absolute value of `x` reduced to less than or equal to half pi. + function toLessThanHalfPi(Ctor, x) { + var t, + isNeg = x.s < 0, + pi = getPi(Ctor, Ctor.precision, 1), + halfPi = pi.times(0.5); + + x = x.abs(); + + if (x.lte(halfPi)) { + quadrant = isNeg ? 4 : 1; + return x; + } + + t = x.divToInt(pi); + + if (t.isZero()) { + quadrant = isNeg ? 3 : 2; + } else { + x = x.minus(t.times(pi)); + + // 0 <= x < pi + if (x.lte(halfPi)) { + quadrant = isOdd(t) ? (isNeg ? 2 : 3) : (isNeg ? 4 : 1); + return x; + } + + quadrant = isOdd(t) ? (isNeg ? 1 : 4) : (isNeg ? 3 : 2); + } + + return x.minus(pi).abs(); + } + + + /* + * Return the value of Decimal `x` as a string in base `baseOut`. + * + * If the optional `sd` argument is present include a binary exponent suffix. + */ + function toStringBinary(x, baseOut, sd, rm) { + var base, e, i, k, len, roundUp, str, xd, y, + Ctor = x.constructor, + isExp = sd !== void 0; + + if (isExp) { + checkInt32(sd, 1, MAX_DIGITS); + if (rm === void 0) rm = Ctor.rounding; + else checkInt32(rm, 0, 8); + } else { + sd = Ctor.precision; + rm = Ctor.rounding; + } + + if (!x.isFinite()) { + str = nonFiniteToString(x); + } else { + str = finiteToString(x); + i = str.indexOf('.'); + + // Use exponential notation according to `toExpPos` and `toExpNeg`? No, but if required: + // maxBinaryExponent = floor((decimalExponent + 1) * log[2](10)) + // minBinaryExponent = floor(decimalExponent * log[2](10)) + // log[2](10) = 3.321928094887362347870319429489390175864 + + if (isExp) { + base = 2; + if (baseOut == 16) { + sd = sd * 4 - 3; + } else if (baseOut == 8) { + sd = sd * 3 - 2; + } + } else { + base = baseOut; + } + + // Convert the number as an integer then divide the result by its base raised to a power such + // that the fraction part will be restored. + + // Non-integer. + if (i >= 0) { + str = str.replace('.', ''); + y = new Ctor(1); + y.e = str.length - i; + y.d = convertBase(finiteToString(y), 10, base); + y.e = y.d.length; + } + + xd = convertBase(str, 10, base); + e = len = xd.length; + + // Remove trailing zeros. + for (; xd[--len] == 0;) xd.pop(); + + if (!xd[0]) { + str = isExp ? '0p+0' : '0'; + } else { + if (i < 0) { + e--; + } else { + x = new Ctor(x); + x.d = xd; + x.e = e; + x = divide(x, y, sd, rm, 0, base); + xd = x.d; + e = x.e; + roundUp = inexact; + } + + // The rounding digit, i.e. the digit after the digit that may be rounded up. + i = xd[sd]; + k = base / 2; + roundUp = roundUp || xd[sd + 1] !== void 0; + + roundUp = rm < 4 + ? (i !== void 0 || roundUp) && (rm === 0 || rm === (x.s < 0 ? 3 : 2)) + : i > k || i === k && (rm === 4 || roundUp || rm === 6 && xd[sd - 1] & 1 || + rm === (x.s < 0 ? 8 : 7)); + + xd.length = sd; + + if (roundUp) { + + // Rounding up may mean the previous digit has to be rounded up and so on. + for (; ++xd[--sd] > base - 1;) { + xd[sd] = 0; + if (!sd) { + ++e; + xd.unshift(1); + } + } + } + + // Determine trailing zeros. + for (len = xd.length; !xd[len - 1]; --len); + + // E.g. [4, 11, 15] becomes 4bf. + for (i = 0, str = ''; i < len; i++) str += NUMERALS.charAt(xd[i]); + + // Add binary exponent suffix? + if (isExp) { + if (len > 1) { + if (baseOut == 16 || baseOut == 8) { + i = baseOut == 16 ? 4 : 3; + for (--len; len % i; len++) str += '0'; + xd = convertBase(str, base, baseOut); + for (len = xd.length; !xd[len - 1]; --len); + + // xd[0] will always be be 1 + for (i = 1, str = '1.'; i < len; i++) str += NUMERALS.charAt(xd[i]); + } else { + str = str.charAt(0) + '.' + str.slice(1); + } + } + + str = str + (e < 0 ? 'p' : 'p+') + e; + } else if (e < 0) { + for (; ++e;) str = '0' + str; + str = '0.' + str; + } else { + if (++e > len) for (e -= len; e-- ;) str += '0'; + else if (e < len) str = str.slice(0, e) + '.' + str.slice(e); + } + } + + str = (baseOut == 16 ? '0x' : baseOut == 2 ? '0b' : baseOut == 8 ? '0o' : '') + str; + } + + return x.s < 0 ? '-' + str : str; + } + + + // Does not strip trailing zeros. + function truncate(arr, len) { + if (arr.length > len) { + arr.length = len; + return true; + } + } + + + // Decimal methods + + + /* + * abs + * acos + * acosh + * add + * asin + * asinh + * atan + * atanh + * atan2 + * cbrt + * ceil + * clamp + * clone + * config + * cos + * cosh + * div + * exp + * floor + * hypot + * ln + * log + * log2 + * log10 + * max + * min + * mod + * mul + * pow + * random + * round + * set + * sign + * sin + * sinh + * sqrt + * sub + * sum + * tan + * tanh + * trunc + */ + + + /* + * Return a new Decimal whose value is the absolute value of `x`. + * + * x {number|string|bigint|Decimal} + * + */ + function abs(x) { + return new this(x).abs(); + } + + + /* + * Return a new Decimal whose value is the arccosine in radians of `x`. + * + * x {number|string|bigint|Decimal} + * + */ + function acos(x) { + return new this(x).acos(); + } + + + /* + * Return a new Decimal whose value is the inverse of the hyperbolic cosine of `x`, rounded to + * `precision` significant digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} A value in radians. + * + */ + function acosh(x) { + return new this(x).acosh(); + } + + + /* + * Return a new Decimal whose value is the sum of `x` and `y`, rounded to `precision` significant + * digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} + * y {number|string|bigint|Decimal} + * + */ + function add(x, y) { + return new this(x).plus(y); + } + + + /* + * Return a new Decimal whose value is the arcsine in radians of `x`, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} + * + */ + function asin(x) { + return new this(x).asin(); + } + + + /* + * Return a new Decimal whose value is the inverse of the hyperbolic sine of `x`, rounded to + * `precision` significant digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} A value in radians. + * + */ + function asinh(x) { + return new this(x).asinh(); + } + + + /* + * Return a new Decimal whose value is the arctangent in radians of `x`, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} + * + */ + function atan(x) { + return new this(x).atan(); + } + + + /* + * Return a new Decimal whose value is the inverse of the hyperbolic tangent of `x`, rounded to + * `precision` significant digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} A value in radians. + * + */ + function atanh(x) { + return new this(x).atanh(); + } + + + /* + * Return a new Decimal whose value is the arctangent in radians of `y/x` in the range -pi to pi + * (inclusive), rounded to `precision` significant digits using rounding mode `rounding`. + * + * Domain: [-Infinity, Infinity] + * Range: [-pi, pi] + * + * y {number|string|bigint|Decimal} The y-coordinate. + * x {number|string|bigint|Decimal} The x-coordinate. + * + * atan2(±0, -0) = ±pi + * atan2(±0, +0) = ±0 + * atan2(±0, -x) = ±pi for x > 0 + * atan2(±0, x) = ±0 for x > 0 + * atan2(-y, ±0) = -pi/2 for y > 0 + * atan2(y, ±0) = pi/2 for y > 0 + * atan2(±y, -Infinity) = ±pi for finite y > 0 + * atan2(±y, +Infinity) = ±0 for finite y > 0 + * atan2(±Infinity, x) = ±pi/2 for finite x + * atan2(±Infinity, -Infinity) = ±3*pi/4 + * atan2(±Infinity, +Infinity) = ±pi/4 + * atan2(NaN, x) = NaN + * atan2(y, NaN) = NaN + * + */ + function atan2(y, x) { + y = new this(y); + x = new this(x); + var r, + pr = this.precision, + rm = this.rounding, + wpr = pr + 4; + + // Either NaN + if (!y.s || !x.s) { + r = new this(NaN); + + // Both ±Infinity + } else if (!y.d && !x.d) { + r = getPi(this, wpr, 1).times(x.s > 0 ? 0.25 : 0.75); + r.s = y.s; + + // x is ±Infinity or y is ±0 + } else if (!x.d || y.isZero()) { + r = x.s < 0 ? getPi(this, pr, rm) : new this(0); + r.s = y.s; + + // y is ±Infinity or x is ±0 + } else if (!y.d || x.isZero()) { + r = getPi(this, wpr, 1).times(0.5); + r.s = y.s; + + // Both non-zero and finite + } else if (x.s < 0) { + this.precision = wpr; + this.rounding = 1; + r = this.atan(divide(y, x, wpr, 1)); + x = getPi(this, wpr, 1); + this.precision = pr; + this.rounding = rm; + r = y.s < 0 ? r.minus(x) : r.plus(x); + } else { + r = this.atan(divide(y, x, wpr, 1)); + } + + return r; + } + + + /* + * Return a new Decimal whose value is the cube root of `x`, rounded to `precision` significant + * digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} + * + */ + function cbrt(x) { + return new this(x).cbrt(); + } + + + /* + * Return a new Decimal whose value is `x` rounded to an integer using `ROUND_CEIL`. + * + * x {number|string|bigint|Decimal} + * + */ + function ceil(x) { + return finalise(x = new this(x), x.e + 1, 2); + } + + + /* + * Return a new Decimal whose value is `x` clamped to the range delineated by `min` and `max`. + * + * x {number|string|bigint|Decimal} + * min {number|string|bigint|Decimal} + * max {number|string|bigint|Decimal} + * + */ + function clamp(x, min, max) { + return new this(x).clamp(min, max); + } + + + /* + * Configure global settings for a Decimal constructor. + * + * `obj` is an object with one or more of the following properties, + * + * precision {number} + * rounding {number} + * toExpNeg {number} + * toExpPos {number} + * maxE {number} + * minE {number} + * modulo {number} + * crypto {boolean|number} + * defaults {true} + * + * E.g. Decimal.config({ precision: 20, rounding: 4 }) + * + */ + function config(obj) { + if (!obj || typeof obj !== 'object') throw Error(decimalError + 'Object expected'); + var i, p, v, + useDefaults = obj.defaults === true, + ps = [ + 'precision', 1, MAX_DIGITS, + 'rounding', 0, 8, + 'toExpNeg', -EXP_LIMIT, 0, + 'toExpPos', 0, EXP_LIMIT, + 'maxE', 0, EXP_LIMIT, + 'minE', -EXP_LIMIT, 0, + 'modulo', 0, 9 + ]; + + for (i = 0; i < ps.length; i += 3) { + if (p = ps[i], useDefaults) this[p] = DEFAULTS[p]; + if ((v = obj[p]) !== void 0) { + if (mathfloor(v) === v && v >= ps[i + 1] && v <= ps[i + 2]) this[p] = v; + else throw Error(invalidArgument + p + ': ' + v); + } + } + + if (p = 'crypto', useDefaults) this[p] = DEFAULTS[p]; + if ((v = obj[p]) !== void 0) { + if (v === true || v === false || v === 0 || v === 1) { + if (v) { + if (typeof crypto != 'undefined' && crypto && + (crypto.getRandomValues || crypto.randomBytes)) { + this[p] = true; + } else { + throw Error(cryptoUnavailable); + } + } else { + this[p] = false; + } + } else { + throw Error(invalidArgument + p + ': ' + v); + } + } + + return this; + } + + + /* + * Return a new Decimal whose value is the cosine of `x`, rounded to `precision` significant + * digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} A value in radians. + * + */ + function cos(x) { + return new this(x).cos(); + } + + + /* + * Return a new Decimal whose value is the hyperbolic cosine of `x`, rounded to precision + * significant digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} A value in radians. + * + */ + function cosh(x) { + return new this(x).cosh(); + } + + + /* + * Create and return a Decimal constructor with the same configuration properties as this Decimal + * constructor. + * + */ + function clone(obj) { + var i, p, ps; + + /* + * The Decimal constructor and exported function. + * Return a new Decimal instance. + * + * v {number|string|bigint|Decimal} A numeric value. + * + */ + function Decimal(v) { + var e, i, t, + x = this; + + // Decimal called without new. + if (!(x instanceof Decimal)) return new Decimal(v); + + // Retain a reference to this Decimal constructor, and shadow Decimal.prototype.constructor + // which points to Object. + x.constructor = Decimal; + + if (isDecimalInstance(v)) { + x.s = v.s; + + if (external) { + if (!v.d || v.e > Decimal.maxE) { + + // Infinity. + x.e = NaN; + x.d = null; + } else if (v.e < Decimal.minE) { + + // Zero. + x.e = 0; + x.d = [0]; + } else { + x.e = v.e; + x.d = v.d.slice(); + } + } else { + x.e = v.e; + x.d = v.d ? v.d.slice() : v.d; + } + + return; + } + + t = typeof v; + + if (t === 'number') { + if (v === 0) { + x.s = 1 / v < 0 ? -1 : 1; + x.e = 0; + x.d = [0]; + return; + } + + if (v < 0) { + v = -v; + x.s = -1; + } else { + x.s = 1; + } + + // Fast path for small integers. + if (v === ~~v && v < 1e7) { + for (e = 0, i = v; i >= 10; i /= 10) e++; + + if (external) { + if (e > Decimal.maxE) { + x.e = NaN; + x.d = null; + } else if (e < Decimal.minE) { + x.e = 0; + x.d = [0]; + } else { + x.e = e; + x.d = [v]; + } + } else { + x.e = e; + x.d = [v]; + } + + return; + } + + // Infinity or NaN? + if (v * 0 !== 0) { + if (!v) x.s = NaN; + x.e = NaN; + x.d = null; + return; + } + + return parseDecimal(x, v.toString()); + } + + if (t === 'string') { + if ((i = v.charCodeAt(0)) === 45) { // minus sign + v = v.slice(1); + x.s = -1; + } else { + if (i === 43) v = v.slice(1); // plus sign + x.s = 1; + } + + return isDecimal.test(v) ? parseDecimal(x, v) : parseOther(x, v); + } + + if (t === 'bigint') { + if (v < 0) { + v = -v; + x.s = -1; + } else { + x.s = 1; + } + + return parseDecimal(x, v.toString()); + } + + throw Error(invalidArgument + v); + } + + Decimal.prototype = P; + + Decimal.ROUND_UP = 0; + Decimal.ROUND_DOWN = 1; + Decimal.ROUND_CEIL = 2; + Decimal.ROUND_FLOOR = 3; + Decimal.ROUND_HALF_UP = 4; + Decimal.ROUND_HALF_DOWN = 5; + Decimal.ROUND_HALF_EVEN = 6; + Decimal.ROUND_HALF_CEIL = 7; + Decimal.ROUND_HALF_FLOOR = 8; + Decimal.EUCLID = 9; + + Decimal.config = Decimal.set = config; + Decimal.clone = clone; + Decimal.isDecimal = isDecimalInstance; + + Decimal.abs = abs; + Decimal.acos = acos; + Decimal.acosh = acosh; // ES6 + Decimal.add = add; + Decimal.asin = asin; + Decimal.asinh = asinh; // ES6 + Decimal.atan = atan; + Decimal.atanh = atanh; // ES6 + Decimal.atan2 = atan2; + Decimal.cbrt = cbrt; // ES6 + Decimal.ceil = ceil; + Decimal.clamp = clamp; + Decimal.cos = cos; + Decimal.cosh = cosh; // ES6 + Decimal.div = div; + Decimal.exp = exp; + Decimal.floor = floor; + Decimal.hypot = hypot; // ES6 + Decimal.ln = ln; + Decimal.log = log; + Decimal.log10 = log10; // ES6 + Decimal.log2 = log2; // ES6 + Decimal.max = max; + Decimal.min = min; + Decimal.mod = mod; + Decimal.mul = mul; + Decimal.pow = pow; + Decimal.random = random; + Decimal.round = round; + Decimal.sign = sign; // ES6 + Decimal.sin = sin; + Decimal.sinh = sinh; // ES6 + Decimal.sqrt = sqrt; + Decimal.sub = sub; + Decimal.sum = sum; + Decimal.tan = tan; + Decimal.tanh = tanh; // ES6 + Decimal.trunc = trunc; // ES6 + + if (obj === void 0) obj = {}; + if (obj) { + if (obj.defaults !== true) { + ps = ['precision', 'rounding', 'toExpNeg', 'toExpPos', 'maxE', 'minE', 'modulo', 'crypto']; + for (i = 0; i < ps.length;) if (!obj.hasOwnProperty(p = ps[i++])) obj[p] = this[p]; + } + } + + Decimal.config(obj); + + return Decimal; + } + + + /* + * Return a new Decimal whose value is `x` divided by `y`, rounded to `precision` significant + * digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} + * y {number|string|bigint|Decimal} + * + */ + function div(x, y) { + return new this(x).div(y); + } + + + /* + * Return a new Decimal whose value is the natural exponential of `x`, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} The power to which to raise the base of the natural log. + * + */ + function exp(x) { + return new this(x).exp(); + } + + + /* + * Return a new Decimal whose value is `x` round to an integer using `ROUND_FLOOR`. + * + * x {number|string|bigint|Decimal} + * + */ + function floor(x) { + return finalise(x = new this(x), x.e + 1, 3); + } + + + /* + * Return a new Decimal whose value is the square root of the sum of the squares of the arguments, + * rounded to `precision` significant digits using rounding mode `rounding`. + * + * hypot(a, b, ...) = sqrt(a^2 + b^2 + ...) + * + * arguments {number|string|bigint|Decimal} + * + */ + function hypot() { + var i, n, + t = new this(0); + + external = false; + + for (i = 0; i < arguments.length;) { + n = new this(arguments[i++]); + if (!n.d) { + if (n.s) { + external = true; + return new this(1 / 0); + } + t = n; + } else if (t.d) { + t = t.plus(n.times(n)); + } + } + + external = true; + + return t.sqrt(); + } + + + /* + * Return true if object is a Decimal instance (where Decimal is any Decimal constructor), + * otherwise return false. + * + */ + function isDecimalInstance(obj) { + return obj instanceof Decimal || obj && obj.toStringTag === tag || false; + } + + + /* + * Return a new Decimal whose value is the natural logarithm of `x`, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} + * + */ + function ln(x) { + return new this(x).ln(); + } + + + /* + * Return a new Decimal whose value is the log of `x` to the base `y`, or to base 10 if no base + * is specified, rounded to `precision` significant digits using rounding mode `rounding`. + * + * log[y](x) + * + * x {number|string|bigint|Decimal} The argument of the logarithm. + * y {number|string|bigint|Decimal} The base of the logarithm. + * + */ + function log(x, y) { + return new this(x).log(y); + } + + + /* + * Return a new Decimal whose value is the base 2 logarithm of `x`, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} + * + */ + function log2(x) { + return new this(x).log(2); + } + + + /* + * Return a new Decimal whose value is the base 10 logarithm of `x`, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} + * + */ + function log10(x) { + return new this(x).log(10); + } + + + /* + * Return a new Decimal whose value is the maximum of the arguments. + * + * arguments {number|string|bigint|Decimal} + * + */ + function max() { + return maxOrMin(this, arguments, -1); + } + + + /* + * Return a new Decimal whose value is the minimum of the arguments. + * + * arguments {number|string|bigint|Decimal} + * + */ + function min() { + return maxOrMin(this, arguments, 1); + } + + + /* + * Return a new Decimal whose value is `x` modulo `y`, rounded to `precision` significant digits + * using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} + * y {number|string|bigint|Decimal} + * + */ + function mod(x, y) { + return new this(x).mod(y); + } + + + /* + * Return a new Decimal whose value is `x` multiplied by `y`, rounded to `precision` significant + * digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} + * y {number|string|bigint|Decimal} + * + */ + function mul(x, y) { + return new this(x).mul(y); + } + + + /* + * Return a new Decimal whose value is `x` raised to the power `y`, rounded to precision + * significant digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} The base. + * y {number|string|bigint|Decimal} The exponent. + * + */ + function pow(x, y) { + return new this(x).pow(y); + } + + + /* + * Returns a new Decimal with a random value equal to or greater than 0 and less than 1, and with + * `sd`, or `Decimal.precision` if `sd` is omitted, significant digits (or less if trailing zeros + * are produced). + * + * [sd] {number} Significant digits. Integer, 0 to MAX_DIGITS inclusive. + * + */ + function random(sd) { + var d, e, k, n, + i = 0, + r = new this(1), + rd = []; + + if (sd === void 0) sd = this.precision; + else checkInt32(sd, 1, MAX_DIGITS); + + k = Math.ceil(sd / LOG_BASE); + + if (!this.crypto) { + for (; i < k;) rd[i++] = Math.random() * 1e7 | 0; + + // Browsers supporting crypto.getRandomValues. + } else if (crypto.getRandomValues) { + d = crypto.getRandomValues(new Uint32Array(k)); + + for (; i < k;) { + n = d[i]; + + // 0 <= n < 4294967296 + // Probability n >= 4.29e9, is 4967296 / 4294967296 = 0.00116 (1 in 865). + if (n >= 4.29e9) { + d[i] = crypto.getRandomValues(new Uint32Array(1))[0]; + } else { + + // 0 <= n <= 4289999999 + // 0 <= (n % 1e7) <= 9999999 + rd[i++] = n % 1e7; + } + } + + // Node.js supporting crypto.randomBytes. + } else if (crypto.randomBytes) { + + // buffer + d = crypto.randomBytes(k *= 4); + + for (; i < k;) { + + // 0 <= n < 2147483648 + n = d[i] + (d[i + 1] << 8) + (d[i + 2] << 16) + ((d[i + 3] & 0x7f) << 24); + + // Probability n >= 2.14e9, is 7483648 / 2147483648 = 0.0035 (1 in 286). + if (n >= 2.14e9) { + crypto.randomBytes(4).copy(d, i); + } else { + + // 0 <= n <= 2139999999 + // 0 <= (n % 1e7) <= 9999999 + rd.push(n % 1e7); + i += 4; + } + } + + i = k / 4; + } else { + throw Error(cryptoUnavailable); + } + + k = rd[--i]; + sd %= LOG_BASE; + + // Convert trailing digits to zeros according to sd. + if (k && sd) { + n = mathpow(10, LOG_BASE - sd); + rd[i] = (k / n | 0) * n; + } + + // Remove trailing words which are zero. + for (; rd[i] === 0; i--) rd.pop(); + + // Zero? + if (i < 0) { + e = 0; + rd = [0]; + } else { + e = -1; + + // Remove leading words which are zero and adjust exponent accordingly. + for (; rd[0] === 0; e -= LOG_BASE) rd.shift(); + + // Count the digits of the first word of rd to determine leading zeros. + for (k = 1, n = rd[0]; n >= 10; n /= 10) k++; + + // Adjust the exponent for leading zeros of the first word of rd. + if (k < LOG_BASE) e -= LOG_BASE - k; + } + + r.e = e; + r.d = rd; + + return r; + } + + + /* + * Return a new Decimal whose value is `x` rounded to an integer using rounding mode `rounding`. + * + * To emulate `Math.round`, set rounding to 7 (ROUND_HALF_CEIL). + * + * x {number|string|bigint|Decimal} + * + */ + function round(x) { + return finalise(x = new this(x), x.e + 1, this.rounding); + } + + + /* + * Return + * 1 if x > 0, + * -1 if x < 0, + * 0 if x is 0, + * -0 if x is -0, + * NaN otherwise + * + * x {number|string|bigint|Decimal} + * + */ + function sign(x) { + x = new this(x); + return x.d ? (x.d[0] ? x.s : 0 * x.s) : x.s || NaN; + } + + + /* + * Return a new Decimal whose value is the sine of `x`, rounded to `precision` significant digits + * using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} A value in radians. + * + */ + function sin(x) { + return new this(x).sin(); + } + + + /* + * Return a new Decimal whose value is the hyperbolic sine of `x`, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} A value in radians. + * + */ + function sinh(x) { + return new this(x).sinh(); + } + + + /* + * Return a new Decimal whose value is the square root of `x`, rounded to `precision` significant + * digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} + * + */ + function sqrt(x) { + return new this(x).sqrt(); + } + + + /* + * Return a new Decimal whose value is `x` minus `y`, rounded to `precision` significant digits + * using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} + * y {number|string|bigint|Decimal} + * + */ + function sub(x, y) { + return new this(x).sub(y); + } + + + /* + * Return a new Decimal whose value is the sum of the arguments, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + * Only the result is rounded, not the intermediate calculations. + * + * arguments {number|string|bigint|Decimal} + * + */ + function sum() { + var i = 0, + args = arguments, + x = new this(args[i]); + + external = false; + for (; x.s && ++i < args.length;) x = x.plus(args[i]); + external = true; + + return finalise(x, this.precision, this.rounding); + } + + + /* + * Return a new Decimal whose value is the tangent of `x`, rounded to `precision` significant + * digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} A value in radians. + * + */ + function tan(x) { + return new this(x).tan(); + } + + + /* + * Return a new Decimal whose value is the hyperbolic tangent of `x`, rounded to `precision` + * significant digits using rounding mode `rounding`. + * + * x {number|string|bigint|Decimal} A value in radians. + * + */ + function tanh(x) { + return new this(x).tanh(); + } + + + /* + * Return a new Decimal whose value is `x` truncated to an integer. + * + * x {number|string|bigint|Decimal} + * + */ + function trunc(x) { + return finalise(x = new this(x), x.e + 1, 1); + } + + + // Create and configure initial Decimal constructor. + Decimal = clone(DEFAULTS); + Decimal.prototype.constructor = Decimal; + Decimal['default'] = Decimal.Decimal = Decimal; + + // Create the internal constants from their string values. + LN10 = new Decimal(LN10); + PI = new Decimal(PI); + + + // Export. + + + // AMD. + if (typeof define == 'function' && define.amd) { + define(function () { + return Decimal; + }); + + // Node and other environments that support module.exports. + } else if (typeof module != 'undefined' && module.exports) { + if (typeof Symbol == 'function' && typeof Symbol.iterator == 'symbol') { + P[Symbol['for']('nodejs.util.inspect.custom')] = P.toString; + P[Symbol.toStringTag] = 'Decimal'; + } + + module.exports = Decimal; + + // Browser. + } else { + if (!globalScope) { + globalScope = typeof self != 'undefined' && self && self.self == self ? self : window; + } + + noConflict = globalScope.Decimal; + Decimal.noConflict = function () { + globalScope.Decimal = noConflict; + return Decimal; + }; + + globalScope.Decimal = Decimal; + } +})(this); diff --git a/tools/ui/src/lib/vendors/nerdamer-prime/Algebra.js b/tools/ui/src/lib/vendors/nerdamer-prime/Algebra.js new file mode 100644 index 0000000000..4371baa230 --- /dev/null +++ b/tools/ui/src/lib/vendors/nerdamer-prime/Algebra.js @@ -0,0 +1,6213 @@ +/* + * 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} */ + 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 & { 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} */ + 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} 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 & { length: number }} [map] + */ + function MVTerm(coeff, terms, map) { + /** @type {FracType[]} */ + this.terms = terms || []; + /** @type {FracType} */ + this.coeff = coeff; + /** @type {(Record & { 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 | 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} [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} + */ + MVTerm.prototype.getRevMap = function getRevMap() { + if (this.revMap) { + return this.revMap; + } + /** @type {Record} */ + 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 & { length: number }} + */ + core.Utils.toMapObj = function toMapObj(arr) { + let c = 0; + /** @type {Record & { length: number }} */ + const o = /** @type {Record & { 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} [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} map + * @returns {Record} + */ + core.Utils.getFunctionsSubs = function getFunctionsSubs(map) { + /** @type {Record} */ + 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} */ + 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} */ + 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} */ + 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} */ + 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(); +})(); diff --git a/tools/ui/src/lib/vendors/nerdamer-prime/Calculus.js b/tools/ui/src/lib/vendors/nerdamer-prime/Calculus.js new file mode 100644 index 0000000000..f5aa22d413 --- /dev/null +++ b/tools/ui/src/lib/vendors/nerdamer-prime/Calculus.js @@ -0,0 +1,3487 @@ +/* + * 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} */ + 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} */ + 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} */ (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} */ + 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} */ (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} */ + 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} 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} */ + const upper = {}; + /** @type {Record} */ + 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} */ + 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(); +})(); diff --git a/tools/ui/src/lib/vendors/nerdamer-prime/Extra.js b/tools/ui/src/lib/vendors/nerdamer-prime/Extra.js new file mode 100644 index 0000000000..8984cdd462 --- /dev/null +++ b/tools/ui/src/lib/vendors/nerdamer-prime/Extra.js @@ -0,0 +1,926 @@ +/* + * 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} + */ + frequencyMap(arr) { + /** @type {Record} */ + 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} */ + 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; +} diff --git a/tools/ui/src/lib/vendors/nerdamer-prime/LICENSE b/tools/ui/src/lib/vendors/nerdamer-prime/LICENSE new file mode 100644 index 0000000000..1a62b421bd --- /dev/null +++ b/tools/ui/src/lib/vendors/nerdamer-prime/LICENSE @@ -0,0 +1,21 @@ +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. diff --git a/tools/ui/src/lib/vendors/nerdamer-prime/Solve.js b/tools/ui/src/lib/vendors/nerdamer-prime/Solve.js new file mode 100644 index 0000000000..7f7e424fab --- /dev/null +++ b/tools/ui/src/lib/vendors/nerdamer-prime/Solve.js @@ -0,0 +1,2370 @@ +/* + * 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} */ + 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} */ + 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} */ + 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} */ + 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} */ + 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(/(? 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(/(?"; + // 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} */ + 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} */ + 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} */ + 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(); +})(); diff --git a/tools/ui/src/lib/vendors/nerdamer-prime/all.js b/tools/ui/src/lib/vendors/nerdamer-prime/all.js new file mode 100644 index 0000000000..9f62f0be70 --- /dev/null +++ b/tools/ui/src/lib/vendors/nerdamer-prime/all.js @@ -0,0 +1,16 @@ +/* + * 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; diff --git a/tools/ui/src/lib/vendors/nerdamer-prime/constants.js b/tools/ui/src/lib/vendors/nerdamer-prime/constants.js new file mode 100644 index 0000000000..18f26ee104 --- /dev/null +++ b/tools/ui/src/lib/vendors/nerdamer-prime/constants.js @@ -0,0 +1,261 @@ +/* + * 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} */ +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, + }; +} diff --git a/tools/ui/src/lib/vendors/nerdamer-prime/nerdamer.core.js b/tools/ui/src/lib/vendors/nerdamer-prime/nerdamer.core.js new file mode 100644 index 0000000000..9fdcdff34d --- /dev/null +++ b/tools/ui/src/lib/vendors/nerdamer-prime/nerdamer.core.js @@ -0,0 +1,17838 @@ +/* + * 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} 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} */ +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; + * 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; + * CONSTANTS: Record; + * 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) => 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} */ ({}), + 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} [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(/^(?0*).*$/u); + return match ? match[1] : ''; + } + + /** @param {string} num */ + static removeLeadingZeroes(num) { + const match = num.match(/^0*(?.*)$/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(/\{(?\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['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 | 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} + */ +function knownVariable(variable, value) { + /** @type {Record} */ + 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} 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; + * }} + */ +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; + * }} + */ +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 ( + `
\n` + + `
    \n` + + `
  • \n${treeResult.toHTML(3, indent)}\n` + + `
  • \n` + + `
\n` + + `
` + ); +} + +/** + * 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; + * 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 | string[]} + */ +function expressions(asObject, asLaTeX, option) { + /** @type {Record | 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 | 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] = /\((?.*?)\)/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(/_(?[A-Za-z0-9])/gu, (...g) => `${g[0]} `); + // Convert x^2 => x^{2} + e = e.replace(/\^(?[A-Za-z0-9])/gu, (...g) => `^{${g[1]}}`); + // Convert \frac12 => \frac{1}2 + e = e.replace(/(?\\[A-Za-z]+)(?\d)/gu, (...g) => `${g[1]}{${g[2]}}`); + // Convert \frac{1}2 => \frac{1}{2} + e = e.replace(/(?\\[A-Za-z]+\{.*?\})(?\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; + * _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; + * }} + */ +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; + * CONSTANTS: Record; + * 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; + * }} + */ +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; + * 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} */ (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; + * 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; + * }} + */ +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; + * }} + */ +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} [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} [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 | Record>} */ + dependencies: {}, + /** + * @type {Record< + * string, + * ( + * symbol: NerdamerSymbolType, + * deps: [Record, string] + * ) => [string, [Record, 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]} [deps] + * @returns {[Record, 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]} [dependencies] + * @returns {[Record, 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]} */ + 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(/(?.+)_$/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)[]} */ + 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*(?[a-z_][a-z0-9_]*)\((?[a-z0-9_,\s]*)\)\s*:?=\s*(?.+)\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: + /(?[+\-/*]*[0-9]+)(?[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 }} */ + 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} */ + 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} [o] + * @returns {Record} + * @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 ( + /^(?-?\+?\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} */ (/** @type {unknown} */ (self)); + const cloneAny = /** @type {Record} */ (/** @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(/(? 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(/(?[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( + /(?[a-z0-9_]+)(?\()|(?\))(?[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 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)}
  • \n${this.left.toHTML(depth + 2, indent)}${tab(depth + 1)}
  • \n` + : ''; + const right = this.right + ? `${tab(depth + 1)}
  • \n${this.right.toHTML(depth + 2, indent)}${tab(depth + 1)}
  • \n` + : ''; + html = `${tab(depth)}
    ${this.value}
    ${tab(depth)}\n`; + if (left || right) { + html += `${tab(depth)}
      \n${left}${right}${tab(depth)}
    \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\((?.+)\\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} */ + this.getPreprocessors = function getPreprocessors() { + /** @type {Record} */ + 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} */ + 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} */ ({}), + Calculus: /** @type {Record} */ ({}), + Algebra: /** @type {Record} */ ({}), + Extra: /** @type {Record} */ ({}), + }; +} + +// 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; +} diff --git a/tools/ui/src/lib/vendors/package.json b/tools/ui/src/lib/vendors/package.json new file mode 100644 index 0000000000..a0df0c8677 --- /dev/null +++ b/tools/ui/src/lib/vendors/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/tools/ui/src/virtual-nerdamer.d.ts b/tools/ui/src/virtual-nerdamer.d.ts new file mode 100644 index 0000000000..433fb2b3bd --- /dev/null +++ b/tools/ui/src/virtual-nerdamer.d.ts @@ -0,0 +1,4 @@ +declare module 'virtual:nerdamer' { + const code: string; + export default code; +} diff --git a/tools/ui/tests/client/sandbox.service.svelte.test.ts b/tools/ui/tests/client/sandbox.service.svelte.test.ts new file mode 100644 index 0000000000..8f2e3df20a --- /dev/null +++ b/tools/ui/tests/client/sandbox.service.svelte.test.ts @@ -0,0 +1,59 @@ +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'); + }); +}); diff --git a/tools/ui/tsconfig.json b/tools/ui/tsconfig.json index 51f55971e9..60047e995b 100644 --- a/tools/ui/tsconfig.json +++ b/tools/ui/tsconfig.json @@ -25,7 +25,7 @@ ".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 // diff --git a/tools/ui/vite.config.ts b/tools/ui/vite.config.ts index fbb7dc4c3a..4c1e9724a4 100644 --- a/tools/ui/vite.config.ts +++ b/tools/ui/vite.config.ts @@ -9,6 +9,7 @@ import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; 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'; @@ -46,6 +47,7 @@ export default defineConfig({ SvelteKitPWA(SVELTEKIT_PWA_OPTIONS), splashScreenPlugin(), buildInfoPlugin(), + nerdamerPlugin(), relativizeBasePlugin() ],