mathjs icon indicating copy to clipboard operation
mathjs copied to clipboard

Implicit multiplication inside of sqrt call parsed as FunctionNode

Open jackcohen5 opened this issue 4 years ago • 1 comments

Similar issue as https://github.com/josdejong/mathjs/issues/1035 but we're seeing this on the latest version of mathjs 9.5.0.

Minimal code to reproduce:

const {all, create} = require('mathjs')
const math = create(all)
math.evaluate(
  ['sqrt(p(1-n/N)/N)'],
  {
    N: '795',
    n: '219',
    p: '27.5',
  }
)

Results in: TypeError: fn is not a function

The equivalent expression sqrt(p*(1-n/N)/N) works as expected.

jackcohen5 avatar Oct 06 '21 15:10 jackcohen5

In applications that use the expression parsing strategy of a graphing calculator or spreadsheet (e.g. "2(x+1)" and "k(x+1)" both use implicit multiplication) you need to post-process math.parse. I followed the advice given in #2236 and used a transform to do this. Code tests ok so far, advice is welcome.

// test if node is FunctionNode with name of scope or constant symbol
function isConvertableNode(node, scopeSymbols = []) {
  const constants = ["e", "E", "pi", "Pi", "phi", "tau", "i"];
  if (node.isFunctionNode) {
    return (
      scopeSymbols.indexOf(node.name) >= 0 || constants.indexOf(node.name) >= 0
    );
  }
  return false;
}

// test if node contains child functions that need conversion
function containsConvertableNode(node, scopeSymbols = []) {
  let found = false;
  node.traverse(function (node) {
    if (isConvertableNode(node, scopeSymbols)) {
      found = true;
    }
  });
  return found;
}

function parseSpecial(expr, scopeSymbols = []) {
  let node = math.parse(expr);

  // transform stops after one change, iterate to transform all child nodes
  while (containsConvertableNode(node, scopeSymbols)) {
    node = node.transform((node) => {
      return isConvertableNode(node, scopeSymbols)
        ? new math.OperatorNode(
            "*",
            "multiply",
            [
              new math.SymbolNode(node.name),
              new math.ParenthesisNode(node.args[0]),
            ],
            true // implicit multiply
          )
        : node;
    });
  }
  return node;
}

// example
const expr = "sqrt(p(1-n/N)/N)";
const scope = { N: "795", n: "219", p: "27.5" };
const scopeSymbols = Object.keys(scope);
console.log(parseSpecial(expr, scopeSymbols).evaluate(scope));
// returns 0.15831076953511722

gsturr avatar Oct 20 '21 21:10 gsturr