UNPKG

@holgerengels/compute-engine

Version:

Symbolic computing and numeric evaluations for JavaScript and Node.js

618 lines (612 loc) 22.1 kB
/* 0.26.0-alpha2 */ import { Complex } from 'complex-esm'; import { Decimal } from 'decimal.js'; import { Expression, MathJsonIdentifier, MathJsonNumber } from '../math-json/types'; import type { LibraryCategory, LatexDictionaryEntry, LatexString, ParseLatexOptions } from './latex-syntax/public'; import { AssumeResult, BoxedFunctionDefinition, BoxedSymbolDefinition, IComputeEngine, IdentifierDefinitions, ExpressionMapInterface, RuntimeScope, Scope, SymbolDefinition, BoxedRuleSet, Rule, ComputeEngineStats, Metadata, FunctionDefinition, BoxedSubstitution, AssignValue, AngularUnit, CanonicalOptions } from './public'; import { Rational } from './numerics/rationals'; import { type IndexedLatexDictionary } from './latex-syntax/dictionary/definitions'; import { BoxedExpression, BoxedRule, EvaluateOptions, SemiBoxedExpression } from './boxed-expression/public'; import './boxed-expression/serialize.ts'; import { ExactNumericValueData, NumericValue, NumericValueData } from './numeric-value/public'; import { Type, TypeString } from '../common/type/types'; import { OneOf } from '../common/one-of'; import { BigNum } from './numerics/bignum'; /** * * To use the Compute Engine, create a `ComputeEngine` instance: * * ```js * ce = new ComputeEngine(); * ``` * * If using a mathfield, use the default Compute Engine instance from the * `MathfieldElement` class: * * ```js * ce = MathfieldElement.computeEngine * ``` * * Use the instance to create boxed expressions with `ce.parse()` and `ce.box()`. * * ```js * const ce = new ComputeEngine(); * * let expr = ce.parse("e^{i\\pi}"); * console.log(expr.N().latex); * // ➔ "-1" * * expr = ce.box(["Expand", ["Power", ["Add", "a", "b"], 2]]); * console.log(expr.evaluate().latex); * // ➔ "a^2 + 2ab + b^2" * ``` * * @category Compute Engine * */ export declare class ComputeEngine implements IComputeEngine { readonly True: BoxedExpression; readonly False: BoxedExpression; readonly Pi: BoxedExpression; readonly E: BoxedExpression; readonly Nothing: BoxedExpression; readonly Zero: BoxedExpression; readonly One: BoxedExpression; readonly Half: BoxedExpression; readonly NegativeOne: BoxedExpression; readonly Two: BoxedExpression; readonly I: BoxedExpression; readonly NaN: BoxedExpression; readonly PositiveInfinity: BoxedExpression; readonly NegativeInfinity: BoxedExpression; readonly ComplexInfinity: BoxedExpression; /** The symbol separating the whole part of a number from its fractional * part in a LaTeX string. * * Commonly a period (`.`) in English, but a comma (`,`) in many European * languages. For the comma, use `"{,}"` so that the spacing is correct. * * Note that this is a LaTeX string and is used when parsing or serializing * LaTeX. MathJSON always uses a period. * * */ decimalSeparator: LatexString; /** @internal */ _BIGNUM_NAN: Decimal; /** @internal */ _BIGNUM_ZERO: Decimal; /** @internal */ _BIGNUM_ONE: Decimal; /** @internal */ _BIGNUM_TWO: Decimal; /** @internal */ _BIGNUM_HALF: Decimal; /** @internal */ _BIGNUM_PI: Decimal; /** @internal */ _BIGNUM_NEGATIVE_ONE: Decimal; /** @internal */ private _precision; /** @ internal */ private _angularUnit; /** @internal */ private _tolerance; /** @internal */ private _bignumTolerance; private _negBignumTolerance; /** @internal */ private _cache; /** @internal */ private _stats; /** @internal */ private _cost?; /** @internal */ private _commonSymbols; /** @internal */ private _commonNumbers; /** * The current scope. * * A **scope** stores the definition of symbols and assumptions. * * Scopes form a stack, and definitions in more recent * scopes can obscure definitions from older scopes. * * The `ce.context` property represents the current scope. * */ context: RuntimeScope | null; /** * Generation. * * The generation is incremented each time the context changes. * It is used to invalidate caches. * @internal */ generation: number; /** In strict mode (the default) the Compute Engine performs * validation of domains and signature and may report errors. * * These checks may impact performance * * When strict mode is off, results may be incorrect or generate JavaScript * errors if the input is not valid. * */ strict: boolean; /** Absolute time beyond which evaluation should not proceed. * @internal */ deadline?: number; /** * Return identifier tables suitable for the specified categories, or `"all"` * for all categories (`"arithmetic"`, `"algebra"`, etc...). * * An identifier table defines how the symbols and function names in a * MathJSON expression should be interpreted, i.e. how to evaluate and * manipulate them. * */ /** @internal */ private _latexDictionaryInput; /** @internal */ _indexedLatexDictionary: IndexedLatexDictionary; /** @internal */ _bignum: Decimal.Constructor; static getStandardLibrary(categories?: LibraryCategory[] | LibraryCategory | 'all'): readonly IdentifierDefinitions[]; /** * Return a LaTeX dictionary suitable for the specified category, or `"all"` * for all categories (`"arithmetic"`, `"algebra"`, etc...). * * A LaTeX dictionary is needed to translate between LaTeX and MathJSON. * * Each entry in the dictionary indicate how a LaTeX token (or string of * tokens) should be parsed into a MathJSON expression. * * For example an entry can define that the `\pi` LaTeX token should map to the * symbol `"Pi"`, or that the token `-` should map to the function * `["Negate",...]` when in a prefix position and to the function * `["Subtract", ...]` when in an infix position. * * Furthermore, the information in each dictionary entry is used to serialize * the LaTeX string corresponding to a MathJSON expression. * * Use with `ce.latexDictionary` to set the dictionary. You can complement * it with your own definitions, for example with: * * ```ts * ce.latexDictionary = [ * ...ce.getLatexDictionary("all"), * { * kind: "function", * identifierTrigger: "concat", * parse: "Concatenate" * } * ]; * ``` */ static getLatexDictionary(domain?: LibraryCategory | 'all'): readonly Readonly<LatexDictionaryEntry>[]; /** * Construct a new `ComputeEngine` instance. * * Identifier tables define functions and symbols (in `options.ids`). * If no table is provided the MathJSON Standard Library is used (`ComputeEngine.getStandardLibrary()`) * * The LaTeX syntax dictionary is defined in `options.latexDictionary`. * * The order of the dictionaries matter: the definitions from the later ones * override the definitions from earlier ones. The first dictionary should * be the `'core'` dictionary which include some basic definitions such * as domains (`Booleans`, `Numbers`, etc...) that are used by later * dictionaries. * * * @param options.precision Specific how many digits of precision * for the numeric calculations. Default is 300. * * @param options.tolerance If the absolute value of the difference of two * numbers is less than `tolerance`, they are considered equal. Used by * `chop()` as well. */ constructor(options?: { ids?: readonly IdentifierDefinitions[]; precision?: number | 'machine'; tolerance?: number | 'auto'; }); get latexDictionary(): Readonly<LatexDictionaryEntry[]>; set latexDictionary(dic: Readonly<LatexDictionaryEntry[]>); get indexedLatexDictionary(): IndexedLatexDictionary; /** After the configuration of the engine has changed, clear the caches * so that new values can be recalculated. * * This needs to happen for example when the numeric precision changes. * * @internal */ reset(): void; /** @internal */ _register(_expr: BoxedExpression): void; /** @internal */ _unregister(_expr: BoxedExpression): void; /** @internal */ get stats(): ComputeEngineStats; get precision(): number; /** The precision, or number of significant digits, of numeric * calculations when the numeric mode is `"auto"` or `"bignum"`. * * To make calculations using more digits, at the cost of expanded memory * usage and slower computations, set the `precision` higher. * * If the numeric mode is not `"auto"` or `"bignum"`, it is set to `"auto"`. * * Trigonometric operations are accurate for precision up to 1,000. * */ set precision(p: number | 'machine' | 'auto'); /** * The unit used for angles in trigonometric functions. * * Default is `"rad"` (radians). */ get angularUnit(): AngularUnit; set angularUnit(u: AngularUnit); /** @experimental */ get timeLimit(): number; /** @experimental */ get iterationLimit(): number; /** @experimental */ get recursionLimit(): number; get tolerance(): number; /** * Values smaller than the tolerance are considered to be zero for the * purpose of comparison, i.e. if `|b - a| <= tolerance`, `b` is considered * equal to `a`. */ set tolerance(val: number | 'auto'); /** Replace a number that is close to 0 with the exact integer 0. * * How close to 0 the number has to be to be considered 0 is determined by {@linkcode tolerance}. */ chop(n: number): number; chop(n: Decimal): Decimal | 0; chop(n: Complex): Complex | 0; /** Create an arbitrary precision number. * * The return value is an object with methods to perform arithmetic * operations: * - `toNumber()`: convert to a JavaScript `number` with potential loss of precision * - `add()` * - `sub()` * - `neg()` (unary minus) * - `mul()` * - `div()` * - `pow()` * - `sqrt()` (square root) * - `cbrt()` (cube root) * - `exp()` (e^x) * - `log()` * - `ln()` (natural logarithm) * - `mod()` * - `abs()` * - `ceil()` * - `floor()` * - `round()` * - `equals()` * - `gt()` * - `gte()` * - `lt()` * - `lte()` * * - `cos()` * - `sin()` * - `tanh()` * - `acos()` * - `asin()` * - `atan()` * - `cosh()` * - `sinh()` * - `acosh()` * - `asinh()` * - `atanh()` * * - `isFinite()` * - `isInteger()` * - `isNaN()` * - `isNegative()` * - `isPositive()` * - `isZero()` * - `sign()` (1, 0 or -1) * */ bignum(a: Decimal.Value | bigint): Decimal; /** Create a complex number. * The return value is an object with methods to perform arithmetic * operations: * - `re` (real part, as a JavaScript `number`) * - `im` (imaginary part, as a JavaScript `number`) * - `add()` * - `sub()` * - `neg()` (unary minus) * - `mul()` * - `div()` * - `pow()` * - `sqrt()` (square root) * - `exp()` (e^x) * - `log()` * - `ln()` (natural logarithm) * - `mod()` * - `abs()` * - `ceil()` * - `floor()` * - `round()` * - `arg()` the angle of the complex number * - `inverse()` the inverse of the complex number 1/z * - `conjugate()` the conjugate of the complex number * - `equals()` * * - `cos()` * - `sin()` * - `tanh()` * - `acos()` * - `asin()` * - `atan()` * - `cosh()` * - `sinh()` * - `acosh()` * - `asinh()` * - `atanh()` * * - `isFinite()` * - `isNaN()` * - `isZero()` * - `sign()` (1, 0 or -1) */ complex(a: number | Decimal | Complex, b?: number | Decimal): Complex; /** * * Create a Numeric Value. * * @internal */ _numericValue(value: number | bigint | OneOf<[BigNum | NumericValueData | ExactNumericValueData | Complex]>): NumericValue; /** * The cost function is used to determine the "cost" of an expression. For example, when simplifying an expression, the simplification that results in the lowest cost is chosen. */ get costFunction(): (expr: BoxedExpression) => number; set costFunction(fn: ((expr: BoxedExpression) => number) | undefined); /** * Return a matching symbol definition, starting with the current * scope and going up the scope chain. Prioritize finding a match by * wikidata, if provided. */ lookupSymbol(symbol: string, wikidata?: string, scope?: RuntimeScope): undefined | BoxedSymbolDefinition; /** * Return the definition for a function with this operator name. * * Start looking in the current context, than up the scope chain. * * This is a very rough lookup, since it doesn't account for the domain * of the argument or the codomain. However, it is useful during parsing * to differentiate between symbols that might represent a function application, e.g. `f` vs `x`. */ lookupFunction(name: MathJsonIdentifier, scope?: RuntimeScope | null): undefined | BoxedFunctionDefinition; /** * Associate a new definition to a symbol in the current context. * * If a definition existed previously, it is replaced. * * * For internal use. Use `ce.declare()` instead. * * @internal */ defineSymbol(name: string, def: SymbolDefinition): BoxedSymbolDefinition; _defineSymbol(name: string, def: SymbolDefinition): BoxedSymbolDefinition; /** * Associate a new FunctionDefinition to a function in the current context. * * If a definition existed previously, it is replaced. * * For internal use. Use `ce.declare()` instead. * * @internal */ defineFunction(name: string, def: FunctionDefinition): BoxedFunctionDefinition; _defineFunction(name: string, def: FunctionDefinition): BoxedFunctionDefinition; /** * * Create a new scope and add it to the top of the scope stack * */ pushScope(scope?: Partial<Scope>): IComputeEngine; /** Remove the most recent scope from the scope stack, and set its * parent scope as the current scope. */ popScope(): IComputeEngine; /** Set the current scope, return the previous scope. */ swapScope(scope: RuntimeScope | null): RuntimeScope | null; /** @internal */ _printScope(options?: { details?: boolean; maxDepth?: number; }, scope?: RuntimeScope | null, depth?: number): RuntimeScope | null; /** * Reset the value of any identifiers that have been assigned a value * in the current scope. * @internal */ resetContext(): void; /** * Declare an identifier: specify their type and other attributes, * including optionally a value. * * Once the type of an identifier has been declared, it cannot be changed. * The type information is used to calculate the canonical form of * expressions and ensure they are valid. If the type could be changed * after the fact, previously valid expressions could become invalid. * * Set the type to `any` type for a very generic type, or use `unknown` * if the type is not known yet. If `unknown`, the type will be inferred * based on usage. * * An identifier can be redeclared with a different type, but only if * the type of the identifier was inferred. If the domain was explicitly * set, the identifier cannot be redeclared. * */ declare(id: string, def: Type | TypeString | OneOf<[SymbolDefinition, FunctionDefinition]>): IComputeEngine; declare(identifiers: { [id: string]: Type | TypeString | OneOf<[SymbolDefinition, FunctionDefinition]>; }): IComputeEngine; /** Assign a value to an identifier in the current scope. * Use `undefined` to reset the identifier to no value. * * The identifier should be a valid MathJSON identifier * not a LaTeX string. * * The identifier can take the form "f(x, y") to create a function * with two parameters, "x" and "y". * * If the id was not previously declared, assigning a value will declare it. * Its type is inferred from the value. * * To more precisely define the type of the identifier, use `ce.declare()` * instead, which allows you to specify the type, value and other * attributes of the identifier. */ assign(id: string, value: AssignValue): IComputeEngine; assign(ids: { [id: string]: AssignValue; }): IComputeEngine; /** * Same as assign(), but for internal use: * - skips validity checks * - does not auto-declare * - if assigning to a function, must pass a JS function * * @internal */ _assign(id: string, value: BoxedExpression | ((ops: ReadonlyArray<BoxedExpression>, options: EvaluateOptions & { engine: IComputeEngine; }) => BoxedExpression | undefined)): void; /** * Return false if the execution should stop. * * This can occur if: * - an error has been signaled * - the time limit or memory limit has been exceeded * * @internal */ shouldContinueExecution(): boolean; /** @internal */ checkContinueExecution(): void; /** @internal */ cache<T>(cacheName: string, build: () => T, purge?: (t: T) => T | undefined): T; /** Return a boxed expression from a number, string or semiboxed expression. * Calls `ce.function()`, `ce.number()` or `ce.symbol()` as appropriate. */ box(expr: NumericValue | SemiBoxedExpression, options?: { canonical?: CanonicalOptions; structural?: boolean; }): BoxedExpression; function(name: string, ops: ReadonlyArray<BoxedExpression> | ReadonlyArray<Expression>, options?: { metadata?: Metadata; canonical: CanonicalOptions; structural: boolean; }): BoxedExpression; /** * * Shortcut for `this.box(["Error",...])`. * * The result is canonical. */ error(message: string | string[], where?: string): BoxedExpression; typeError(expected: Type, actual: undefined | Type, where?: string): BoxedExpression; /** * Add a`["Hold"]` wrapper to `expr. */ hold(expr: BoxedExpression): BoxedExpression; /** Shortcut for `this.box(["Tuple", ...])` * * The result is canonical. */ tuple(...elements: ReadonlyArray<number>): BoxedExpression; tuple(...elements: ReadonlyArray<BoxedExpression>): BoxedExpression; string(s: string, metadata?: Metadata): BoxedExpression; /** Return a boxed symbol */ symbol(name: string, options?: { metadata?: Metadata; canonical?: CanonicalOptions; }): BoxedExpression; /** * This function tries to avoid creating a boxed number if `num` corresponds * to a common value for which we have a shared instance (-1, 0, NaN, etc...) */ number(value: number | bigint | string | NumericValue | MathJsonNumber | Decimal | Complex | Rational, options?: { metadata: Metadata; canonical: CanonicalOptions; }): BoxedExpression; rules(rules: Rule | ReadonlyArray<Rule | BoxedRule> | BoxedRuleSet | undefined | null, options?: { canonical?: boolean; }): BoxedRuleSet; getRuleSet(id?: string): BoxedRuleSet | undefined; /** * Return a function expression, but the caller is responsible for making * sure that the arguments are canonical. * * Unlike ce.function(), the operator of the result is the name argument. * Calling this function directly is potentially unsafe, as it bypasses * the canonicalization of the arguments. * * For example: * * - `ce._fn('Multiply', [1, 'x'])` returns `['Multiply', 1, 'x']` as a canonical expression, even though it doesn't follow the canonical form * - `ce.function('Multiply', [1, 'x']` returns `'x'` which is the correct canonical form * * @internal */ _fn(name: MathJsonIdentifier, ops: ReadonlyArray<BoxedExpression>, options?: Metadata & { canonical?: boolean; }): BoxedExpression; /** * Parse a string of LaTeX and return a corresponding `BoxedExpression`. * * If the `canonical` option is set to `true`, the result will be canonical * */ parse(latex: null, options?: Partial<ParseLatexOptions> & { canonical?: CanonicalOptions; }): null; parse(latex: LatexString, options?: Partial<ParseLatexOptions> & { canonical?: CanonicalOptions; }): BoxedExpression; get assumptions(): ExpressionMapInterface<boolean>; /** * Return a list of all the assumptions that match a pattern. * * ```js * ce.assume(['Element', 'x', 'PositiveIntegers'); * ce.ask(['Greater', 'x', '_val']) * // -> [{'val': 0}] * ``` */ ask(pattern: BoxedExpression): BoxedSubstitution[]; /** * Answer a query based on the current assumptions. * */ verify(_query: BoxedExpression): boolean; /** * Add an assumption. * * Note that the assumption is put into canonical form before being added. * * Returns: * - `contradiction` if the new assumption is incompatible with previous * ones. * - `tautology` if the new assumption is redundant with previous ones. * - `ok` if the assumption was successfully added to the assumption set. * * */ assume(predicate: BoxedExpression): AssumeResult; /** Remove all assumptions about one or more symbols */ forget(symbol: undefined | string | string[]): void; }