@holgerengels/compute-engine
Version:
Symbolic computing and numeric evaluations for JavaScript and Node.js
618 lines (612 loc) • 22.1 kB
TypeScript
/* 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;
}