@mathigon/hilbert
Advanced tools
Comparing version 0.6.2 to 0.6.3
@@ -167,7 +167,8 @@ 'use strict'; | ||
} | ||
const SPECIAL = new Set(['sin', 'cos', 'tan', 'sec', 'csc', 'cot', 'arcsin', | ||
'arccos', 'arctan', 'sinh', 'cosh', 'tanh', 'sech', 'csch', 'coth', 'exp', | ||
'log', 'ln', 'det', 'dim', 'mod', 'gcd', 'lcm', 'min', 'max']); | ||
const SPECIAL_FUNCTIONS = ['abs', 'round', 'floor', 'ceil', 'max', 'min', 'mod', | ||
'lcm', 'gcd', 'gcf', 'log', 'exp', 'ln', 'sqrt', 'root', 'sin', 'cos', 'tan', | ||
'sec', 'csc', 'cot', 'cosec', 'cotan', 'arcsin', 'arccos', 'arctan', 'sinh', | ||
'cosh', 'tanh', 'sech', 'csch', 'coth', 'cosech']; | ||
function isSpecialFunction(fn) { | ||
return SPECIAL.has(fn); | ||
return SPECIAL_FUNCTIONS.includes(fn); | ||
} | ||
@@ -208,2 +209,5 @@ const VOICE_STRINGS = { | ||
} | ||
interval(vars = {}) { | ||
return [this.evaluate(vars), this.evaluate(vars)]; | ||
} | ||
/** Substitutes a new expression for a variable. */ | ||
@@ -273,2 +277,11 @@ substitute(_vars = {}) { | ||
} | ||
interval(vars = {}) { | ||
var _a; | ||
const x = (_a = vars[this.i]) !== null && _a !== void 0 ? _a : CONSTANTS[this.i]; | ||
if (Array.isArray(x)) | ||
return x; | ||
if (typeof x === 'number') | ||
return [x, x]; | ||
throw ExprError.undefinedVariable(this.i); | ||
} | ||
toMathML() { | ||
@@ -345,2 +358,178 @@ const variant = isSpecialFunction(this.i) ? ' mathvariant="normal"' : ''; | ||
// ============================================================================= | ||
const WHOLE = [-Infinity, Infinity]; | ||
const EMPTY = [NaN, NaN]; | ||
const HALF_PI = Math.PI / 2; | ||
const TWO_PI = Math.PI * 2; | ||
const int = (a, b) => [a - Number.EPSILON, b + Number.EPSILON]; | ||
const range = (...args) => int(Math.min(...args), Math.max(...args)); | ||
const width = (a) => Math.abs(a[1] - a[0]); | ||
const isEmpty = (a) => isNaN(a[0]) || isNaN(a[1]); | ||
const isInfinite = (a) => !isFinite(a[0]) && a[0] === a[1]; | ||
const contains = (a, v) => fermat.isBetween(v, a[0] - Number.EPSILON, a[1] + Number.EPSILON); | ||
const hasZero = (a) => contains(a, 0); | ||
// ----------------------------------------------------------------------------- | ||
// Standard Evaluation | ||
const evaluate = { | ||
add: (...args) => args.reduce((a, b) => a + b, 0), | ||
sub: (...args) => (args.length > 1) ? args[0] - args[1] : -args[0], | ||
mul: (...args) => args.reduce((a, b) => a * b, 1), | ||
div: (a, b) => a / b, | ||
abs: (a) => Math.abs(a), | ||
round: (a) => Math.round(a), | ||
floor: (a) => Math.floor(a), | ||
ceil: (a) => Math.ceil(a), | ||
max: (...args) => Math.max(...args), | ||
min: (...args) => Math.min(...args), | ||
mod: (a, b) => a % b, | ||
lcm: (...args) => fermat.lcm(...args), | ||
gcd: (...args) => fermat.gcd(...args), | ||
gcf: (...args) => fermat.gcd(...args), | ||
sup: (a, b) => Math.pow(a, b), | ||
log: (a, b) => Math.log(a) / (b === undefined ? 1 : Math.log(b)), | ||
exp: (a) => Math.exp(a), | ||
ln: (a) => Math.log(a), | ||
sqrt: (a) => Math.sqrt(a), | ||
root: (a, b) => Math.pow(a, 1 / b), | ||
sin: (a) => Math.sin(a), | ||
cos: (a) => Math.cos(a), | ||
tan: (a) => Math.tan(a), | ||
sec: (a) => 1 / Math.cos(a), | ||
csc: (a) => 1 / Math.sin(a), | ||
cot: (a) => 1 / Math.tan(a), | ||
cosec: (a) => evaluate.csc(a), | ||
cotan: (a) => evaluate.cot(a), | ||
arcsin: (a) => Math.asin(a), | ||
arccos: (a) => Math.acos(a), | ||
arctan: (a) => Math.atan(a), | ||
sinh: (a) => Math.sinh(a), | ||
cosh: (a) => Math.cosh(a), | ||
tanh: (a) => Math.tanh(a), | ||
sech: (a) => 1 / Math.cosh(a), | ||
csch: (a) => 1 / Math.sinh(a), | ||
coth: (a) => 1 / Math.tanh(a), | ||
cosech: (a) => evaluate.csch(a), | ||
}; | ||
// ----------------------------------------------------------------------------- | ||
// Utility Functions | ||
/** Evaluate a^b */ | ||
function pow(a, b) { | ||
// If the base a is positive: | ||
if (a[0] > 0) { | ||
if (b[0] >= 0) | ||
return int(a[0] ** b[0], a[1] ** b[1]); | ||
return range(a[0] ** b[0], a[0] ** b[1], a[1] ** b[0], a[1] ** b[1]); | ||
} | ||
// If the exponent b is an integer: | ||
const k = b[0]; | ||
if (Number.isInteger(k) && k === b[1]) { | ||
if (k === 0) | ||
return [hasZero(a) ? 0 : 1, 1]; | ||
if (k % 2) | ||
return int(a[0] ** k, a[1] ** k); | ||
if (hasZero(a)) | ||
return [0, Math.max(a[0] ** k, a[1] ** k)]; | ||
return [a[1] ** k, a[0] ** k]; | ||
} | ||
return EMPTY; // TODO Implement this! | ||
} | ||
/** Shift an interval so that a[0] lies between 0 and 2 Pi. */ | ||
function intervalMod(a, m = TWO_PI) { | ||
const d = Math.floor(a[0] / m) * m; | ||
return [a[0] - d, a[1] - d]; | ||
} | ||
// ----------------------------------------------------------------------------- | ||
// Interval Evaluation | ||
const interval = { | ||
add: (a, b) => int(a[0] + b[0], a[1] + b[1]), | ||
sub: (a, b) => b !== undefined ? int(a[0] - b[1], a[1] - b[0]) : int(-a[1], -a[0]), | ||
mul: (a, b) => range(a[0] * b[0], a[0] * b[1], a[1] * b[0], a[1] * b[1]), | ||
div: (a, b) => hasZero(b) ? WHOLE : range(a[0] / b[0], a[0] / b[1], a[1] / b[0], a[1] / b[1]), | ||
abs: (a) => { | ||
if (hasZero(a)) | ||
return int(0, Math.max(-a[0], a[1])); | ||
return range(Math.abs(a[0]), Math.abs(a[1])); | ||
}, | ||
round: (a) => int(Math.round(a[0]), Math.round(a[1])), | ||
floor: (a) => int(Math.floor(a[0]), Math.floor(a[1])), | ||
ceil: (a) => int(Math.ceil(a[0]), Math.ceil(a[1])), | ||
max: (...args) => int(Math.max(...args.map(a => a[0])), Math.max(...args.map(a => a[1]))), | ||
min: (...args) => int(Math.min(...args.map(a => a[0])), Math.min(...args.map(a => a[1]))), | ||
mod: (a, b) => { | ||
if (isEmpty(a) || isEmpty(b)) | ||
return EMPTY; | ||
let n = a[0] / (a[0] < 0 ? b[0] : b[1]); | ||
n = (n < 0) ? Math.ceil(n) : Math.floor(n); | ||
return interval.sub(a, interval.mul(b, [n, n])); // a mod b = a - n * b | ||
}, | ||
lcm: (...args) => range(fermat.lcm(...args.map(a => a[0]))), | ||
gcd: (...args) => range(fermat.gcd(...args.map(a => a[0]))), | ||
gcf: (...args) => interval.gcd(...args), | ||
sup: (a, b) => pow(a, b), | ||
log: (a, b) => { | ||
if (b !== undefined) | ||
interval.div(interval.log(a), interval.log(b)); | ||
return int(a[0] <= 0 ? -Infinity : Math.log(a[0]), Math.log(a[1])); | ||
}, | ||
exp: (a) => pow([Math.E, Math.E], a), | ||
ln: (a) => interval.log(a), | ||
sqrt: (a) => pow(a, [0.5, 0.5]), | ||
root: (a, b) => pow(a, interval.div([1, 1], b)), | ||
sin: (a) => interval.cos(interval.sub(a, [HALF_PI, HALF_PI])), | ||
cos: (a) => { | ||
if (isEmpty(a) || isInfinite(a)) | ||
return EMPTY; | ||
if (width(a) >= TWO_PI - Number.EPSILON) | ||
return [-1, 1]; | ||
a = intervalMod(a); | ||
if (a[0] >= Math.PI) | ||
return interval.sub(interval.cos(interval.sub(a, [Math.PI, Math.PI]))); | ||
// Now we know that 0 <= a[0] < pi. | ||
if (a[1] <= Math.PI) | ||
return int(Math.cos(a[1]), Math.cos(a[0])); | ||
if (a[1] <= TWO_PI) | ||
return int(-1, Math.max(Math.cos(a[1]), Math.cos(a[0]))); | ||
return int(-1, 1); | ||
}, | ||
tan: (a) => { | ||
if (isEmpty(a) || isInfinite(a)) | ||
return EMPTY; | ||
a = intervalMod(a, Math.PI); | ||
if (a[0] >= HALF_PI) | ||
a = interval.sub(a, [Math.PI, Math.PI]); | ||
if (a[0] <= -HALF_PI || a[1] >= HALF_PI) | ||
return WHOLE; | ||
return int(Math.tan(a[0]), Math.tan(a[1])); | ||
}, | ||
sec: (a) => interval.div([1, 1], interval.cos(a)), | ||
csc: (a) => interval.div([1, 1], interval.sin(a)), | ||
cot: (a) => interval.div([1, 1], interval.tan(a)), | ||
cosec: (a) => interval.csc(a), | ||
cotan: (a) => interval.cot(a), | ||
arcsin: (a) => { | ||
if (isEmpty(a) || a[1] < -1 || a[0] > 1) | ||
return EMPTY; | ||
return int(a[0] <= -1 ? -HALF_PI : Math.asin(a[0]), a[1] >= 1 ? HALF_PI : Math.asin(a[1])); | ||
}, | ||
arccos: (a) => { | ||
if (isEmpty(a) || a[1] < -1 || a[0] > 1) | ||
return EMPTY; | ||
return int(a[1] >= 1 ? 0 : Math.acos(a[1]), a[0] <= -1 ? Math.PI : Math.acos(a[0])); | ||
}, | ||
arctan: (a) => int(Math.atan(a[0]), Math.atan(a[1])), | ||
sinh: (a) => int(Math.sinh(a[0]), Math.sinh(a[1])), | ||
cosh: (a) => { | ||
if (a[1] < 0) | ||
return int(Math.cosh(a[1]), Math.cosh(a[0])); | ||
if (a[0] > 0) | ||
return int(Math.cosh(a[0]), Math.cosh(a[1])); | ||
return int(1, Math.cosh(Math.max(-a[0], a[1]))); | ||
}, | ||
tanh: (a) => int(Math.tanh(a[0]), Math.tanh(a[1])), | ||
sech: (a) => interval.div([1, 1], interval.cosh(a)), | ||
csch: (a) => interval.div([1, 1], interval.sinh(a)), | ||
coth: (a) => interval.div([1, 1], interval.tanh(a)), | ||
cosech: (a) => interval.csch(a), | ||
}; | ||
// ============================================================================= | ||
const PRECEDENCE = core.words('+ − * × · / ÷ // sup sub subsup'); | ||
@@ -382,33 +571,38 @@ const SUBSUP = core.words('sub sup subsup'); | ||
return vars[this.fn](...args); | ||
switch (this.fn) { | ||
case '+': | ||
return args.reduce((a, b) => a + b, 0); | ||
case '−': | ||
return (args.length > 1) ? args[0] - args[1] : -args[0]; | ||
case '*': | ||
case '·': | ||
case '×': | ||
return args.reduce((a, b) => a * b, 1); | ||
case '/': | ||
return args[0] / args[1]; | ||
case 'sin': | ||
return Math.sin(args[0]); | ||
case 'cos': | ||
return Math.sin(args[0]); | ||
case 'tan': | ||
return Math.sin(args[0]); | ||
case 'log': | ||
return Math.log(args[0]) / Math.log(args[1] || Math.E); | ||
case 'sup': | ||
return Math.pow(args[0], args[1]); | ||
case 'sqrt': | ||
return Math.sqrt(args[0]); | ||
case 'root': | ||
return Math.pow(args[0], 1 / args[1]); | ||
case '(': | ||
return args[0]; | ||
// TODO Implement for all functions | ||
} | ||
if (this.fn === '+') | ||
return evaluate.add(...args); | ||
if (this.fn === '−') | ||
return evaluate.sub(...args); | ||
if (['*', '·', '×'].includes(this.fn)) | ||
return evaluate.mul(...args); | ||
if (this.fn === '/') | ||
return evaluate.div(...args); | ||
if (this.fn === 'sup') | ||
return evaluate.sup(...args); | ||
if (isSpecialFunction(this.fn)) | ||
return evaluate[this.fn](...args); | ||
if (this.fn === '(') | ||
return args[0]; | ||
throw ExprError.undefinedFunction(this.fn); | ||
} | ||
interval(vars = {}) { | ||
if (this.fn in vars) | ||
return core.repeat(this.evaluate(vars), 2); | ||
const args = this.args.map(a => a.interval(vars)); | ||
if (this.fn === '+') | ||
return interval.add(...args); | ||
if (this.fn === '−') | ||
return interval.sub(...args); | ||
if (['*', '·', '×'].includes(this.fn)) | ||
return interval.mul(...args); | ||
if (this.fn === '/') | ||
return interval.div(...args); | ||
if (this.fn === 'sup') | ||
return interval.sup(...args); | ||
if (isSpecialFunction(this.fn)) | ||
return interval[this.fn](...args); | ||
if (this.fn === '(') | ||
return args[0]; | ||
throw ExprError.undefinedFunction(this.fn); | ||
} | ||
substitute(vars = {}) { | ||
@@ -844,4 +1038,3 @@ return new ExprFunction(this.fn, this.args.map(a => a.substitute(vars))); | ||
} | ||
// Match comparison and division operators. | ||
findBinaryFunction(tokens, '= < > ≤ ≥'); | ||
// Match division operators. | ||
findBinaryFunction(tokens, '// ÷'); | ||
@@ -859,2 +1052,4 @@ // Match multiplication operators. | ||
tokens = findAssociativeFunction(tokens, '+'); | ||
// Match comparison operators. | ||
findBinaryFunction(tokens, '= < > ≤ ≥'); | ||
if (tokens.length > 1) | ||
@@ -914,2 +1109,3 @@ throw ExprError.invalidExpression(); | ||
exports.ExprError = ExprError; | ||
exports.ExprFunction = ExprFunction; | ||
exports.Expression = Expression; |
@@ -1,3 +0,3 @@ | ||
import { words, unique, flatten, isOneOf, join, last, cache } from '@mathigon/core'; | ||
import { nearlyEquals } from '@mathigon/fermat'; | ||
import { words, repeat, unique, flatten, isOneOf, join, last, cache } from '@mathigon/core'; | ||
import { lcm, gcd, isBetween, nearlyEquals } from '@mathigon/fermat'; | ||
@@ -163,7 +163,8 @@ // ============================================================================= | ||
} | ||
const SPECIAL = new Set(['sin', 'cos', 'tan', 'sec', 'csc', 'cot', 'arcsin', | ||
'arccos', 'arctan', 'sinh', 'cosh', 'tanh', 'sech', 'csch', 'coth', 'exp', | ||
'log', 'ln', 'det', 'dim', 'mod', 'gcd', 'lcm', 'min', 'max']); | ||
const SPECIAL_FUNCTIONS = ['abs', 'round', 'floor', 'ceil', 'max', 'min', 'mod', | ||
'lcm', 'gcd', 'gcf', 'log', 'exp', 'ln', 'sqrt', 'root', 'sin', 'cos', 'tan', | ||
'sec', 'csc', 'cot', 'cosec', 'cotan', 'arcsin', 'arccos', 'arctan', 'sinh', | ||
'cosh', 'tanh', 'sech', 'csch', 'coth', 'cosech']; | ||
function isSpecialFunction(fn) { | ||
return SPECIAL.has(fn); | ||
return SPECIAL_FUNCTIONS.includes(fn); | ||
} | ||
@@ -204,2 +205,5 @@ const VOICE_STRINGS = { | ||
} | ||
interval(vars = {}) { | ||
return [this.evaluate(vars), this.evaluate(vars)]; | ||
} | ||
/** Substitutes a new expression for a variable. */ | ||
@@ -269,2 +273,11 @@ substitute(_vars = {}) { | ||
} | ||
interval(vars = {}) { | ||
var _a; | ||
const x = (_a = vars[this.i]) !== null && _a !== void 0 ? _a : CONSTANTS[this.i]; | ||
if (Array.isArray(x)) | ||
return x; | ||
if (typeof x === 'number') | ||
return [x, x]; | ||
throw ExprError.undefinedVariable(this.i); | ||
} | ||
toMathML() { | ||
@@ -341,2 +354,178 @@ const variant = isSpecialFunction(this.i) ? ' mathvariant="normal"' : ''; | ||
// ============================================================================= | ||
const WHOLE = [-Infinity, Infinity]; | ||
const EMPTY = [NaN, NaN]; | ||
const HALF_PI = Math.PI / 2; | ||
const TWO_PI = Math.PI * 2; | ||
const int = (a, b) => [a - Number.EPSILON, b + Number.EPSILON]; | ||
const range = (...args) => int(Math.min(...args), Math.max(...args)); | ||
const width = (a) => Math.abs(a[1] - a[0]); | ||
const isEmpty = (a) => isNaN(a[0]) || isNaN(a[1]); | ||
const isInfinite = (a) => !isFinite(a[0]) && a[0] === a[1]; | ||
const contains = (a, v) => isBetween(v, a[0] - Number.EPSILON, a[1] + Number.EPSILON); | ||
const hasZero = (a) => contains(a, 0); | ||
// ----------------------------------------------------------------------------- | ||
// Standard Evaluation | ||
const evaluate = { | ||
add: (...args) => args.reduce((a, b) => a + b, 0), | ||
sub: (...args) => (args.length > 1) ? args[0] - args[1] : -args[0], | ||
mul: (...args) => args.reduce((a, b) => a * b, 1), | ||
div: (a, b) => a / b, | ||
abs: (a) => Math.abs(a), | ||
round: (a) => Math.round(a), | ||
floor: (a) => Math.floor(a), | ||
ceil: (a) => Math.ceil(a), | ||
max: (...args) => Math.max(...args), | ||
min: (...args) => Math.min(...args), | ||
mod: (a, b) => a % b, | ||
lcm: (...args) => lcm(...args), | ||
gcd: (...args) => gcd(...args), | ||
gcf: (...args) => gcd(...args), | ||
sup: (a, b) => Math.pow(a, b), | ||
log: (a, b) => Math.log(a) / (b === undefined ? 1 : Math.log(b)), | ||
exp: (a) => Math.exp(a), | ||
ln: (a) => Math.log(a), | ||
sqrt: (a) => Math.sqrt(a), | ||
root: (a, b) => Math.pow(a, 1 / b), | ||
sin: (a) => Math.sin(a), | ||
cos: (a) => Math.cos(a), | ||
tan: (a) => Math.tan(a), | ||
sec: (a) => 1 / Math.cos(a), | ||
csc: (a) => 1 / Math.sin(a), | ||
cot: (a) => 1 / Math.tan(a), | ||
cosec: (a) => evaluate.csc(a), | ||
cotan: (a) => evaluate.cot(a), | ||
arcsin: (a) => Math.asin(a), | ||
arccos: (a) => Math.acos(a), | ||
arctan: (a) => Math.atan(a), | ||
sinh: (a) => Math.sinh(a), | ||
cosh: (a) => Math.cosh(a), | ||
tanh: (a) => Math.tanh(a), | ||
sech: (a) => 1 / Math.cosh(a), | ||
csch: (a) => 1 / Math.sinh(a), | ||
coth: (a) => 1 / Math.tanh(a), | ||
cosech: (a) => evaluate.csch(a), | ||
}; | ||
// ----------------------------------------------------------------------------- | ||
// Utility Functions | ||
/** Evaluate a^b */ | ||
function pow(a, b) { | ||
// If the base a is positive: | ||
if (a[0] > 0) { | ||
if (b[0] >= 0) | ||
return int(a[0] ** b[0], a[1] ** b[1]); | ||
return range(a[0] ** b[0], a[0] ** b[1], a[1] ** b[0], a[1] ** b[1]); | ||
} | ||
// If the exponent b is an integer: | ||
const k = b[0]; | ||
if (Number.isInteger(k) && k === b[1]) { | ||
if (k === 0) | ||
return [hasZero(a) ? 0 : 1, 1]; | ||
if (k % 2) | ||
return int(a[0] ** k, a[1] ** k); | ||
if (hasZero(a)) | ||
return [0, Math.max(a[0] ** k, a[1] ** k)]; | ||
return [a[1] ** k, a[0] ** k]; | ||
} | ||
return EMPTY; // TODO Implement this! | ||
} | ||
/** Shift an interval so that a[0] lies between 0 and 2 Pi. */ | ||
function intervalMod(a, m = TWO_PI) { | ||
const d = Math.floor(a[0] / m) * m; | ||
return [a[0] - d, a[1] - d]; | ||
} | ||
// ----------------------------------------------------------------------------- | ||
// Interval Evaluation | ||
const interval = { | ||
add: (a, b) => int(a[0] + b[0], a[1] + b[1]), | ||
sub: (a, b) => b !== undefined ? int(a[0] - b[1], a[1] - b[0]) : int(-a[1], -a[0]), | ||
mul: (a, b) => range(a[0] * b[0], a[0] * b[1], a[1] * b[0], a[1] * b[1]), | ||
div: (a, b) => hasZero(b) ? WHOLE : range(a[0] / b[0], a[0] / b[1], a[1] / b[0], a[1] / b[1]), | ||
abs: (a) => { | ||
if (hasZero(a)) | ||
return int(0, Math.max(-a[0], a[1])); | ||
return range(Math.abs(a[0]), Math.abs(a[1])); | ||
}, | ||
round: (a) => int(Math.round(a[0]), Math.round(a[1])), | ||
floor: (a) => int(Math.floor(a[0]), Math.floor(a[1])), | ||
ceil: (a) => int(Math.ceil(a[0]), Math.ceil(a[1])), | ||
max: (...args) => int(Math.max(...args.map(a => a[0])), Math.max(...args.map(a => a[1]))), | ||
min: (...args) => int(Math.min(...args.map(a => a[0])), Math.min(...args.map(a => a[1]))), | ||
mod: (a, b) => { | ||
if (isEmpty(a) || isEmpty(b)) | ||
return EMPTY; | ||
let n = a[0] / (a[0] < 0 ? b[0] : b[1]); | ||
n = (n < 0) ? Math.ceil(n) : Math.floor(n); | ||
return interval.sub(a, interval.mul(b, [n, n])); // a mod b = a - n * b | ||
}, | ||
lcm: (...args) => range(lcm(...args.map(a => a[0]))), | ||
gcd: (...args) => range(gcd(...args.map(a => a[0]))), | ||
gcf: (...args) => interval.gcd(...args), | ||
sup: (a, b) => pow(a, b), | ||
log: (a, b) => { | ||
if (b !== undefined) | ||
interval.div(interval.log(a), interval.log(b)); | ||
return int(a[0] <= 0 ? -Infinity : Math.log(a[0]), Math.log(a[1])); | ||
}, | ||
exp: (a) => pow([Math.E, Math.E], a), | ||
ln: (a) => interval.log(a), | ||
sqrt: (a) => pow(a, [0.5, 0.5]), | ||
root: (a, b) => pow(a, interval.div([1, 1], b)), | ||
sin: (a) => interval.cos(interval.sub(a, [HALF_PI, HALF_PI])), | ||
cos: (a) => { | ||
if (isEmpty(a) || isInfinite(a)) | ||
return EMPTY; | ||
if (width(a) >= TWO_PI - Number.EPSILON) | ||
return [-1, 1]; | ||
a = intervalMod(a); | ||
if (a[0] >= Math.PI) | ||
return interval.sub(interval.cos(interval.sub(a, [Math.PI, Math.PI]))); | ||
// Now we know that 0 <= a[0] < pi. | ||
if (a[1] <= Math.PI) | ||
return int(Math.cos(a[1]), Math.cos(a[0])); | ||
if (a[1] <= TWO_PI) | ||
return int(-1, Math.max(Math.cos(a[1]), Math.cos(a[0]))); | ||
return int(-1, 1); | ||
}, | ||
tan: (a) => { | ||
if (isEmpty(a) || isInfinite(a)) | ||
return EMPTY; | ||
a = intervalMod(a, Math.PI); | ||
if (a[0] >= HALF_PI) | ||
a = interval.sub(a, [Math.PI, Math.PI]); | ||
if (a[0] <= -HALF_PI || a[1] >= HALF_PI) | ||
return WHOLE; | ||
return int(Math.tan(a[0]), Math.tan(a[1])); | ||
}, | ||
sec: (a) => interval.div([1, 1], interval.cos(a)), | ||
csc: (a) => interval.div([1, 1], interval.sin(a)), | ||
cot: (a) => interval.div([1, 1], interval.tan(a)), | ||
cosec: (a) => interval.csc(a), | ||
cotan: (a) => interval.cot(a), | ||
arcsin: (a) => { | ||
if (isEmpty(a) || a[1] < -1 || a[0] > 1) | ||
return EMPTY; | ||
return int(a[0] <= -1 ? -HALF_PI : Math.asin(a[0]), a[1] >= 1 ? HALF_PI : Math.asin(a[1])); | ||
}, | ||
arccos: (a) => { | ||
if (isEmpty(a) || a[1] < -1 || a[0] > 1) | ||
return EMPTY; | ||
return int(a[1] >= 1 ? 0 : Math.acos(a[1]), a[0] <= -1 ? Math.PI : Math.acos(a[0])); | ||
}, | ||
arctan: (a) => int(Math.atan(a[0]), Math.atan(a[1])), | ||
sinh: (a) => int(Math.sinh(a[0]), Math.sinh(a[1])), | ||
cosh: (a) => { | ||
if (a[1] < 0) | ||
return int(Math.cosh(a[1]), Math.cosh(a[0])); | ||
if (a[0] > 0) | ||
return int(Math.cosh(a[0]), Math.cosh(a[1])); | ||
return int(1, Math.cosh(Math.max(-a[0], a[1]))); | ||
}, | ||
tanh: (a) => int(Math.tanh(a[0]), Math.tanh(a[1])), | ||
sech: (a) => interval.div([1, 1], interval.cosh(a)), | ||
csch: (a) => interval.div([1, 1], interval.sinh(a)), | ||
coth: (a) => interval.div([1, 1], interval.tanh(a)), | ||
cosech: (a) => interval.csch(a), | ||
}; | ||
// ============================================================================= | ||
const PRECEDENCE = words('+ − * × · / ÷ // sup sub subsup'); | ||
@@ -378,33 +567,38 @@ const SUBSUP = words('sub sup subsup'); | ||
return vars[this.fn](...args); | ||
switch (this.fn) { | ||
case '+': | ||
return args.reduce((a, b) => a + b, 0); | ||
case '−': | ||
return (args.length > 1) ? args[0] - args[1] : -args[0]; | ||
case '*': | ||
case '·': | ||
case '×': | ||
return args.reduce((a, b) => a * b, 1); | ||
case '/': | ||
return args[0] / args[1]; | ||
case 'sin': | ||
return Math.sin(args[0]); | ||
case 'cos': | ||
return Math.sin(args[0]); | ||
case 'tan': | ||
return Math.sin(args[0]); | ||
case 'log': | ||
return Math.log(args[0]) / Math.log(args[1] || Math.E); | ||
case 'sup': | ||
return Math.pow(args[0], args[1]); | ||
case 'sqrt': | ||
return Math.sqrt(args[0]); | ||
case 'root': | ||
return Math.pow(args[0], 1 / args[1]); | ||
case '(': | ||
return args[0]; | ||
// TODO Implement for all functions | ||
} | ||
if (this.fn === '+') | ||
return evaluate.add(...args); | ||
if (this.fn === '−') | ||
return evaluate.sub(...args); | ||
if (['*', '·', '×'].includes(this.fn)) | ||
return evaluate.mul(...args); | ||
if (this.fn === '/') | ||
return evaluate.div(...args); | ||
if (this.fn === 'sup') | ||
return evaluate.sup(...args); | ||
if (isSpecialFunction(this.fn)) | ||
return evaluate[this.fn](...args); | ||
if (this.fn === '(') | ||
return args[0]; | ||
throw ExprError.undefinedFunction(this.fn); | ||
} | ||
interval(vars = {}) { | ||
if (this.fn in vars) | ||
return repeat(this.evaluate(vars), 2); | ||
const args = this.args.map(a => a.interval(vars)); | ||
if (this.fn === '+') | ||
return interval.add(...args); | ||
if (this.fn === '−') | ||
return interval.sub(...args); | ||
if (['*', '·', '×'].includes(this.fn)) | ||
return interval.mul(...args); | ||
if (this.fn === '/') | ||
return interval.div(...args); | ||
if (this.fn === 'sup') | ||
return interval.sup(...args); | ||
if (isSpecialFunction(this.fn)) | ||
return interval[this.fn](...args); | ||
if (this.fn === '(') | ||
return args[0]; | ||
throw ExprError.undefinedFunction(this.fn); | ||
} | ||
substitute(vars = {}) { | ||
@@ -840,4 +1034,3 @@ return new ExprFunction(this.fn, this.args.map(a => a.substitute(vars))); | ||
} | ||
// Match comparison and division operators. | ||
findBinaryFunction(tokens, '= < > ≤ ≥'); | ||
// Match division operators. | ||
findBinaryFunction(tokens, '// ÷'); | ||
@@ -855,2 +1048,4 @@ // Match multiplication operators. | ||
tokens = findAssociativeFunction(tokens, '+'); | ||
// Match comparison operators. | ||
findBinaryFunction(tokens, '= < > ≤ ≥'); | ||
if (tokens.length > 1) | ||
@@ -908,2 +1103,2 @@ throw ExprError.invalidExpression(); | ||
export { ExprElement, ExprError, Expression }; | ||
export { ExprElement, ExprError, ExprFunction, Expression }; |
@@ -10,1 +10,2 @@ // ============================================================================= | ||
export {ExprElement} from './src/elements'; | ||
export {ExprFunction} from './src/functions'; |
{ | ||
"name": "@mathigon/hilbert", | ||
"version": "0.6.2", | ||
"version": "0.6.3", | ||
"description": "JavaScript expression parsing, MathML rendering and CAS.", | ||
@@ -33,12 +33,12 @@ "keywords": [ | ||
"devDependencies": { | ||
"@rollup/plugin-typescript": "8.1.1", | ||
"@rollup/plugin-typescript": "8.2.1", | ||
"@types/tape": "4.13.0", | ||
"rollup": "2.38.2", | ||
"tape": "5.1.1", | ||
"rollup": "2.44.0", | ||
"tape": "5.2.2", | ||
"ts-node": "9.1.1", | ||
"tslib": "2.1.0", | ||
"typescript": "4.1.3", | ||
"@typescript-eslint/eslint-plugin": "4.14.1", | ||
"@typescript-eslint/parser": "4.14.1", | ||
"eslint": "7.19.0", | ||
"typescript": "4.2.3", | ||
"@typescript-eslint/eslint-plugin": "4.20.0", | ||
"@typescript-eslint/parser": "4.20.0", | ||
"eslint": "7.23.0", | ||
"eslint-config-google": "0.14.0", | ||
@@ -45,0 +45,0 @@ "eslint-plugin-import": "2.22.1" |
@@ -7,4 +7,3 @@ { | ||
"groupName": "lint", | ||
"automerge": true, | ||
"automergeType": "branch" | ||
"automerge": true | ||
}, { | ||
@@ -14,25 +13,20 @@ "packagePatterns": ["^@mathigon"], | ||
"schedule": ["at any time"], | ||
"automerge": true, | ||
"automergeType": "branch" | ||
"automerge": true | ||
}, { | ||
"packagePatterns": ["ts-node", "^typescript"], | ||
"groupName": "typescript", | ||
"automerge": true, | ||
"automergeType": "branch" | ||
"automerge": true | ||
}, { | ||
"packagePatterns": ["^@types/"], | ||
"groupName": "types", | ||
"automerge": true, | ||
"automergeType": "branch" | ||
"automerge": true | ||
}, { | ||
"packagePatterns": ["^rollup", "^@rollup"], | ||
"groupName": "rollup", | ||
"automerge": true, | ||
"automergeType": "branch" | ||
"automerge": true | ||
}, { | ||
"updateTypes": ["patch", "pin", "digest"], | ||
"groupName": "versions", | ||
"automerge": true, | ||
"automergeType": "branch" | ||
"automerge": true | ||
}] | ||
} |
@@ -8,2 +8,3 @@ // ============================================================================= | ||
import {Obj} from '@mathigon/core'; | ||
import {Interval} from './eval'; | ||
import {CONSTANTS, escape, isSpecialFunction, VOICE_STRINGS} from './symbols'; | ||
@@ -19,3 +20,3 @@ import {ExprError} from './errors'; | ||
export type CustomFunction = ((...args: number[]) => number); | ||
export type VarMap = Obj<number|CustomFunction>; | ||
export type VarMap = Obj<number|Interval|CustomFunction>; | ||
export type ExprMap = Obj<ExprElement>; | ||
@@ -35,2 +36,6 @@ export type MathMLMap = Obj<(...args: MathMLArgument[]) => string>; | ||
interval(vars: VarMap = {}): Interval { | ||
return [this.evaluate(vars), this.evaluate(vars)]; | ||
} | ||
/** Substitutes a new expression for a variable. */ | ||
@@ -114,2 +119,9 @@ substitute(_vars: ExprMap = {}): ExprElement { | ||
interval(vars: VarMap = {}): Interval { | ||
const x = vars[this.i] ?? CONSTANTS[this.i]; | ||
if (Array.isArray(x)) return x; | ||
if (typeof x === 'number') return [x, x]; | ||
throw ExprError.undefinedVariable(this.i); | ||
} | ||
toMathML() { | ||
@@ -116,0 +128,0 @@ const variant = isSpecialFunction(this.i) ? ' mathvariant="normal"' : ''; |
@@ -7,3 +7,4 @@ // ============================================================================= | ||
import {unique, flatten, words, isOneOf, join} from '@mathigon/core'; | ||
import {unique, flatten, words, isOneOf, join, repeat} from '@mathigon/core'; | ||
import {evaluate, interval, Interval} from './eval'; | ||
import {collapseTerm} from './parser'; | ||
@@ -50,34 +51,25 @@ import {BRACKETS, escape, isSpecialFunction, VOICE_STRINGS} from './symbols'; | ||
const args = this.args.map(a => a.evaluate(vars)); | ||
if (this.fn in vars) return (vars[this.fn] as CustomFunction)(...args); | ||
if (this.fn === '+') return evaluate.add(...args); | ||
if (this.fn === '−') return evaluate.sub(...args); | ||
if (['*', '·', '×'].includes(this.fn)) return evaluate.mul(...args); | ||
if (this.fn === '/') return evaluate.div(...args); | ||
if (this.fn === 'sup') return evaluate.sup(...args); | ||
if (isSpecialFunction(this.fn)) return evaluate[this.fn](...args); | ||
if (this.fn === '(') return args[0]; | ||
throw ExprError.undefinedFunction(this.fn); | ||
} | ||
switch (this.fn) { | ||
case '+': | ||
return args.reduce((a, b) => a + b, 0); | ||
case '−': | ||
return (args.length > 1) ? args[0] - args[1] : -args[0]; | ||
case '*': | ||
case '·': | ||
case '×': | ||
return args.reduce((a, b) => a * b, 1); | ||
case '/': | ||
return args[0] / args[1]; | ||
case 'sin': | ||
return Math.sin(args[0]); | ||
case 'cos': | ||
return Math.sin(args[0]); | ||
case 'tan': | ||
return Math.sin(args[0]); | ||
case 'log': | ||
return Math.log(args[0]) / Math.log(args[1] || Math.E); | ||
case 'sup': | ||
return Math.pow(args[0], args[1]); | ||
case 'sqrt': | ||
return Math.sqrt(args[0]); | ||
case 'root': | ||
return Math.pow(args[0], 1 / args[1]); | ||
case '(': | ||
return args[0]; | ||
// TODO Implement for all functions | ||
} | ||
interval(vars: VarMap = {}): Interval { | ||
if (this.fn in vars) return repeat(this.evaluate(vars), 2) as Interval; | ||
const args = this.args.map(a => a.interval(vars)); | ||
if (this.fn === '+') return interval.add(...args); | ||
if (this.fn === '−') return interval.sub(...args); | ||
if (['*', '·', '×'].includes(this.fn)) return interval.mul(...args); | ||
if (this.fn === '/') return interval.div(...args); | ||
if (this.fn === 'sup') return interval.sup(...args); | ||
if (isSpecialFunction(this.fn)) return interval[this.fn](...args); | ||
if (this.fn === '(') return args[0]; | ||
throw ExprError.undefinedFunction(this.fn); | ||
@@ -84,0 +76,0 @@ } |
@@ -277,4 +277,3 @@ // ============================================================================= | ||
// Match comparison and division operators. | ||
findBinaryFunction(tokens, '= < > ≤ ≥'); | ||
// Match division operators. | ||
findBinaryFunction(tokens, '// ÷'); | ||
@@ -296,4 +295,7 @@ | ||
// Match comparison operators. | ||
findBinaryFunction(tokens, '= < > ≤ ≥'); | ||
if (tokens.length > 1) throw ExprError.invalidExpression(); | ||
return tokens[0]; | ||
} |
@@ -135,9 +135,10 @@ // ============================================================================= | ||
const SPECIAL = new Set<string>( | ||
['sin', 'cos', 'tan', 'sec', 'csc', 'cot', 'arcsin', | ||
'arccos', 'arctan', 'sinh', 'cosh', 'tanh', 'sech', 'csch', 'coth', 'exp', | ||
'log', 'ln', 'det', 'dim', 'mod', 'gcd', 'lcm', 'min', 'max']); | ||
const SPECIAL_FUNCTIONS = ['abs', 'round', 'floor', 'ceil', 'max', 'min', 'mod', | ||
'lcm', 'gcd', 'gcf', 'log', 'exp', 'ln', 'sqrt', 'root', 'sin', 'cos', 'tan', | ||
'sec', 'csc', 'cot', 'cosec', 'cotan', 'arcsin', 'arccos', 'arctan', 'sinh', | ||
'cosh', 'tanh', 'sech', 'csch', 'coth', 'cosech'] as const; | ||
export type SpecialFunction = typeof SPECIAL_FUNCTIONS[number]; | ||
export function isSpecialFunction(fn: string) { | ||
return SPECIAL.has(fn); | ||
export function isSpecialFunction(fn: string): fn is SpecialFunction { | ||
return SPECIAL_FUNCTIONS.includes(fn as SpecialFunction); | ||
} | ||
@@ -144,0 +145,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
530521
27
14403