UNPKG

btm-expressions

Version:

BTM (bowtie-math) is a math object model to enable parsing of mathematical expressions into a tree structure that can be manipulated, evaluated, and compared.

282 lines (248 loc) 8.65 kB
/*! * BTM JavaScript Library v@VERSION * https://github.com/dbrianwalton/BTM * * Copyright D. Brian Walton * Released under the MIT license (https://opensource.org/licenses/MIT) * * Date: @DATE */ /* *************************************************** * Define the Unary Expression -- defined by an operator and an input. * *************************************************** */ import { expression } from "./expression.js" import { create_scalar } from "./scalar_expr.js" import { binop_expr } from "./binop_expr.js" import { exprType, opPrec } from "./BTM_root.js" export class unop_expr extends expression { constructor(menv, op, input) { super(menv); this.type = exprType.unop; this.op = op; if (typeof input == 'undefined') input = new expression(menv); this.inputs = [input]; input.parent = this; switch (op) { case '+': this.prec = opPrec.multdiv; break; case '-': this.prec = opPrec.multdiv; break; case '/': this.prec = opPrec.power; break; default: alert("Unknown unary operator: '"+op+"'."); break; } } toString() { var theStr; var opStr; if (typeof this.inputs[0] == 'undefined') { opStr = '?'; } else { opStr = this.inputs[0].toString(); } if ((this.inputs[0].type >= exprType.unop && this.inputs[0].prec < this.prec) || (this.inputs[0].type == exprType.number && opStr.indexOf('/') >= 0 && opPrec.multdiv <= this.prec) ) { theStr = this.op + '(' + opStr + ')'; } else { theStr = this.op + opStr; } return(theStr); } // Return an array containing all tested equivalent strings. allStringEquivs() { var allInputs = this.inputs[0].allStringEquivs(); var retValue = []; for (var i in allInputs) { if (this.inputs[0].type >= exprType.unop && this.inputs[0].prec <= this.prec) { retValue[i] = this.op + '(' + allInputs[i] + ')'; } else { retValue[i] = this.op + allInputs[i]; } } return(retValue); } toTeX(showSelect) { var theStr; var opStr, theOp; if (typeof showSelect == 'undefined') { showSelect = false; } if (typeof this.inputs[0] == 'undefined') { opStr = '?'; } else { opStr = this.inputs[0].toTeX(showSelect); } theOp = this.op; if (theOp == '/') { theOp = '\\div '; if (showSelect && this.select) { theStr = "{\\color{red}" + this.op + "}" + '\\left({\\color{blue}' + opStr + '}\\right)'; } else { theStr = '\\frac{1}{' + opStr + '}'; } } else { if (showSelect && this.select) { theStr = "{\\color{red}" + this.op + "}" + '\\left({\\color{blue}' + opStr + '}\\right)'; } else if (this.inputs[0].type >= exprType.unop && this.inputs[0].prec <= this.prec && (this.inputs[0].type != exprType.unop || this.op != '-' || this.inputs[0].op != '/')) { theStr = theOp + '\\left(' + opStr + '\\right)'; } else { theStr = theOp + opStr; } } return(theStr); } toMathML() { var theStr; var opStr; if (typeof this.inputs[0] == 'undefined') { opStr = '?'; } else { opStr = this.inputs[0].toMathML(); } switch (this.op) { case '+': theStr = opStr; break; case '-': theStr = "<apply><minus/>" + opStr + "</apply>"; break; case '/': theStr = "<apply><divide/><cn>1</cn>" + opStr + "</apply>"; break; } return(theStr); } operateToTeX() { var opString = this.op; if (opString === '/') { opString = '\\div'; } return(opString); } evaluate(bindings) { var inputVal = this.inputs[0].evaluate(bindings); var retVal; if (inputVal == undefined) { return(undefined); } switch (this.op) { case '+': retVal = inputVal; break; case '-': retVal = -1*inputVal; break; case '/': // Even when divide by zero, we want to use this, since in the exception // we want the value to be Infinite and not undefined. retVal = 1/inputVal; break; default: alert("The unary operator '" + this.op + "' is not defined."); retVal = undefined; break; } return(retVal); } reduce() { var workExpr = super.reduce(); var newExpr = workExpr; if (workExpr.type == exprType.unop && workExpr.op == '-' && workExpr.inputs[0].isConstant() && workExpr.value()==0) { newExpr = create_scalar(this.menv, 0) } return(newExpr); } simplifyConstants() { var retVal; this.inputs[0] = this.inputs[0].simplifyConstants(); this.inputs[0].parent = this; if (this.inputs[0].type == exprType.number) { var theNumber = this.inputs[0].number; switch (this.op) { case '-': if (this.menv.options.negativeNumbers) { retVal = create_scalar(this.menv, theNumber.addInverse()); } else if (theNumber.value() == 0) { retVal = create_scalar(this.menv, 0); } else { retVal = this; } break; case '/': retVal = create_scalar(this.menv, theNumber.multInverse()); break; } } else { retVal = this; } return(retVal); } flatten() { return(new unop_expr(this.menv, this.op, this.inputs[0].flatten())); } copy() { return(new unop_expr(this.menv, this.op, this.inputs[0].copy())); } compose(bindings) { return(new unop_expr(this.menv, this.op, this.inputs[0].compose(bindings))); } derivative(ivar, varList) { var theDeriv; var uConst = this.inputs[0].isConstant(); if (uConst) { theDeriv = create_scalar(this.menv, 0); } else { switch (this.op) { case '+': theDeriv = this.inputs[0].derivative(ivar, varList); break; case '-': theDeriv = new unop_expr(this.menv, '-', this.inputs[0].derivative(ivar, varList)); if (theDeriv.isConstant()) { theDeriv = theDeriv.simplifyConstants(); } break; case '/': var denom = new binop_expr(this.menv, '*', this.inputs[0], this.inputs[0]); theDeriv = new unop_expr(this.menv, '-', new binop_expr(this.menv, '/', this.inputs[0].derivative(ivar, varList), denom)); break; default: console.log("The derivative of the unary operator '" + this.op + "' is not defined."); theDeriv = undefined; break; } } return(theDeriv); } match(expr, bindings) { var retValue = null; // Special named constants can not match expressions. if (this.isConstant() && expr.isConstant()) { var newExpr = expr.simplifyConstants(), newThis = this.simplifyConstants(); if (newExpr.toString() === newThis.toString() || newExpr.type == exprType.number && newThis.type == exprType.number && newThis.number.equal(newExpr.number)) { retValue = bindings; } } else { retValue = expression.prototype.match.call(this, expr, bindings); } return(retValue); } }