Mathjs: Symbolic sqrt?

Created on 14 Jul 2020  ยท  8Comments  ยท  Source: josdejong/mathjs

Is it possible to treat a square root as it is, e.g. โˆš5, without converting it to a number?

Specifically, I'd like to do the following:

sqrt(5) //=> โˆš5 object
sqrt(2) + sqrt(3) //=> โˆš2 + โˆš3
sqrt(2) + sqrt(2) //=> 2 * โˆš2
sqrt(3 * 3) //=> 3

I have read some examples and, like in the custom_datatatype.js example, I thought I could add a custom data type for โˆš. But I couldn't figure out how to return the value as it is of add, sub, etc.

If anyone knows how to do it right, please let me know!
Finally, thank you for such a wonderful library.

question

All 8 comments

@yasuhito thanks for your question. When you run for example math.parse('sqrt(2) + sqrt(3)'), you get back an expression tree, and you can perform operations on this tree like simplification, derivative, or custom transformations, and formatting to a string (or Latex output). Is that what you mean?

Docs:
https://mathjs.org/docs/expressions/parsing.html#parse
https://mathjs.org/docs/expressions/expression_trees.html

@josdejong thanks for the detailed information!
I pasted the code I wrote below to clarify what I want to achieve.
(I mainly used sqrt.js as a reference.)

My intention is that math.evaluate('sqrt(25)') will return 5 (same behavior as math.js), and math.evaluate('sqrt(5)') will return "โˆš5".

However, my implementation returns a string "โˆš5", which causes an error in further calculations.

I think what should be returned here is data of a type like "expression" or something.
If you have a way to do it correctly in mathjs, or sample code, it would be very helpful.

const sqrt = factory('sqrt', ['config', 'typed', 'Complex'], ({ config, typed, Complex }) => {
  return typed('sqrt', {
    number: _sqrtNumber,

    Complex: function (x) {
      return x.sqrt()
    },

    BigNumber: function (x) {
      if (!x.isNegative() || config.predictable) {
        const result = x.sqrt()
        if (Number.isInteger(result)) {
          return result
        } else {
          return `โˆš${x}`
        }
      } else {
        return _sqrtNumber(x.toNumber())
      }
    },
  })

  function _sqrtNumber (x) {
    if (isNaN(x)) {
      return NaN
    } else if (x >= 0 || config.predictable) {
      const result = Math.sqrt(x)
      if (Number.isInteger(result)) {
        return result
      } else {
        return `โˆš${x}`
      }
    } else {
      return new Complex(x, 0).sqrt()
    }
  }
})

this.math = create(evaluateDependencies)
this.math.import({ sqrt }, { override: true })

I'm not sure if it is a good idea to sometimes return a numeric result like 5 and sometimes a string like "โˆš5" when _evaluating_. If you want to do this in a real, symbolic way, you should use a solution based on manipulating a parsed expression tree.

What you can do is add a new rule to the math.simplify function. This rule can check whether a node in the expression is the function sqrt. If so, it can evaluate it, and when the result is an integer, return a new ConstantNode holding the integer result, and otherwise leave the node untouched.

You can extend the built-in rules as described in the documentation: https://mathjs.org/docs/reference/functions/simplify.html (existing rules are exposed via math.simplify.rules). Does that make sense?

@josdejong Thank you very much!!

I have added the function simplifyNode to math.simplify.rules as follows. As you told me, with this function โˆš25 becomes 5 and โˆš5 remains unchanged.

simplifyNode(node) {
  if (isFunctionNode(node) && node.name == 'sqrt') {
    const result = node.compile().evaluate()
    if (Number.isInteger(result)) {
      return new this.math.ConstantNode(result)
    }
  }
  return node
}

// ...

this.math.simplify.rules.unshift(
  this.simplifyNode.bind(this)
)

Unfortunately, the simplifyConstant later in math.simplify.rules seems to convert โˆš5 to a number (2.23606797749979).

Let me describe the lacking background information that explains why I am trying to do this.

I'm trying to implement a quantum computer simulator, running in the browser, using math.js. In a quantum simulation, every computation is a product of a matrix and a vector. The vector, by the way, is the tensor product of the state of the qubits, and the matrix represents the operations (gates) on the qubits.

My implementation doesn't use eval(expr) or simplify(expr) (i.e., doesn't go through the string), but instead finds the product by constructing matrices and vectors directly like this:

// Apply a H gate to a qubit state

// the H gate
const H = matrix([
  [divide(1, sqrt(2)), divide(1, sqrt(2))],
  [divide(1, sqrt(2)), divide(-1, sqrt(2))],
])

// psi: qubit state vector
return multiply(H, this.psi)

The result of such a calculation often returns a value like 1/โˆš2. My original motivation was that I wanted to get the same "1/โˆš2" as the result of hand calculations, not an immediate value of 0.7071067811865475.

Would math.simplify.rules still work if I don't use eval(expr) or simplify(expr)? If you have a better way to achieve what I want to doo, I'd appreciate it if you could let me know. Thank you in advance ๐Ÿ™

@yasuhito you could try another lib like https://nerdamer.com/demo or my lib while it is not supported here.

@yasuhito you could try another lib like https://nerdamer.com/demo or my lib while it is not supported here.

Thanks for the link Viktor, I hadn't heard about Nerdamer before, it looks great! I've linked to it from the downloads page.

@yasuhito The reason is that there is a built-in rule which evaluates functions containing constants, also for sqrt. This is the simplifyConstant rule: simplify.js#L300. So, what you need is create your own version of this function, which first checks the special sqrt rule, and otherwise fallback to the old behavior. Here a full example:

const { simplify, isFunctionNode, isInteger, ConstantNode } = require('mathjs')

const rules = simplify.rules.map(rule => {
  if (typeof rule === 'function' && rule.name === 'simplifyConstant') {
    // replace the built-in simplifyConstant with our own extended version
    const simplifyConstant = rule

    return function customSimplifyConstant(node) {
      if (isFunctionNode(node) && node.name === 'sqrt') {
        const result = node.compile().evaluate({})
        if (isInteger(result)) {
          // return the integer result (for example `sqrt(25)`
          return new ConstantNode(result)
        } else {
          // do not replace sqrt functions not resulting in an integer result, like `sqrt(5)`
          return node
        }
      }
      else {
        // fall back on the original behavior
        return simplifyConstant(node, {})
      }
    }
  }
  else {
    // leave all other built-in rules untouched
    return rule
  }
})

function simplifyAndPrint(expression) {
  console.log(`${expression}=${simplify(expression, rules)}`)
}

simplifyAndPrint('2 + 5')     // 7
simplifyAndPrint('sqrt(25)')  // 5
simplifyAndPrint('sqrt(5)')   // sqrt(5)

You're fully flexible in which rules you do or do not want to apply with the function simplify ๐Ÿ˜„

@josdejong Thank you, sir! The code you gave me did indeed make the examples such as โˆš5 work correctly. math.js' API is very elegant.

Finally, could you tell me one more thing about the simplify() function over matrices? I tried to parse() and simplify() the following formula m, but it gave me the following error:

const m = "multiply(matrix([[1 / sqrt(2), 1 / sqrt(2)], [1 / sqrt(2), -1 / sqrt(2)]]), multiply(identity(2), matrix([[1], [0]])))"

const f = math.parse(m)
math.simplify(f, myRules) // => Error: Unimplemented node type in simplifyConstant: ArrayNode

Is there any way to get around this error? The formula m is supposed to return a vector (1/โˆš2, 1/โˆš2), so for me I want to get each element symbolically. I did a search and #1913 seems to be relevant.

That's good to hear ๐Ÿ‘

About simplifying expressions with matrices: that is an open feature request, see #1913. Help is welcome :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

balagge picture balagge  ยท  5Comments

adamazing picture adamazing  ยท  4Comments

lwirtz picture lwirtz  ยท  3Comments

Lakedaemon picture Lakedaemon  ยท  5Comments

zzzgit picture zzzgit  ยท  4Comments