Online Formal Differential Machine with Scala

A new handmade online tool to differentiate to any order any common expression :

It's here : Click -> DIFFERENTIATOR

It uses Scala and especially its functionnal aspect : pattern matching, high order functions, Options ( equivalent to MayBe in Haskell )

Scala.Js allows to create dynamic webpages. The web part is rather short:

Scala
package example
 
import org.scalajs.dom
import dom.document
import dom.html
import scalajs.js.annotation.JSExport
import scalatags.JsDom.all._
//import org.scalajs.jquery.jQuery
import scala.scalajs.js.annotation.JSExportTopLevel
import scala.scalajs.js
import utils.Func 
import utils.Rpn // reading of Rpn expressions
import utils.Op  // manipulations of expression
 
object TutorialApp {
 
  // a function to append a text on the page
  def appendPar(targetNode: dom.Node, text: String): Unit = {
    val parNode = document.createElement("p")
    val textNode = document.createTextNode(text)
    parNode.appendChild(textNode)
    targetNode.appendChild(parNode)
  }
 
 
  // Javascript interactions 
  @JSExportTopLevel("addClickedMessage")
  def addClickedMessage(): Unit = {
    val expr:Option[utils.Func] =
      Rpn.rpnToFunc(document.getElementById("nodeExpr").asInstanceOf[html.Input].value.toString)
    val order:Int =
      document.getElementById("nodeOrder").asInstanceOf[html.Input].value.toInt
    val valev:Double =
      document.getElementById("nodeVal").asInstanceOf[html.Input].value.toDouble
    expr match {
      case Some(e) =>
        appendPar(document.body, "The expression is $" + Op.show(e) + "$")
        appendPar(document.body, "The derivative of order " + order.toString + " is " +
          "$" + Op.show(Op.derivn(e, order, Func.Var("x"))) + "$")
        Op.eval(Op.derivn(e, order, Func.Var("x")), valev) match {
          case Some(v) =>
            appendPar(document.body, "Whose value in " + valev.toString + " is " +
              "$" + v + "$")
          case _ => appendPar(document.body, "Expression unevaluable ")
         }
      case None =>  appendPar(document.body, "Expression mistyped ")
    }
    // reload MathJax for new expressions
    js.eval("MathJax.Hub.Queue([\"Typeset\",MathJax.Hub])")
  }
  // we need a main
  def main(args: Array[String]): Unit = {
  }
 }

Assiocated to a html file which could have been a lot shorter if created from the Scala file...TODO ;)

HTML
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Differentiator</title>
    <script type="text/x-mathjax-config">
      MathJax.Hub.Config({tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]}});
    </script>
    <script type="text/javascript" async
            src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
    </script>
  </head>
  <body>
 
    <h1>DIFFERENTIATOR</h1>
    <h2>Α simple formal "differentiator" written in Scala.Js</h2>
    <img src="https://www.scala-js.org/assets/badges/scalajs-1.0.0-M2.svg">
    <button onclick="window.location='http://informathix.tuxfamily.org/?q=node/192'">Back to Informathix</button>
 
    <br><br><br><br>
    <form method="post">
 
      Your expression to be differentiated (RPN form, variable is x):
      <br><br>
      <input type="text" placeholder="x 2 ^ 3 + log  gives : log(x^2 + 3)"
             name="expr" id="nodeExpr" size="40">
      <br><br>
 
      Order of differentiation:<br><br>
      <input type="text" name="order" id="nodeOrder"
             placeholder="2 to get the 2nd derivative" value="1">
      <br><br>
 
      Evaluated in:<br><br>
      <input type="text" name="valev" id="nodeVal"
             placeholder="1 to evaluate in 1" value="1">
      <br><br>
 
      <input type="button" onclick="addClickedMessage()"
             value="Differentiate!" id="click-me-button">
 
      <input type="reset" value="Reset" onClick="window.location.reload()">
    </form>
 
    
    <!-- Include Scala.js compiled code here -->
    <!-- Include JavaScript dependencies -->
    <script type="text/javascript" src="./target/scala-2.12/scala-js-tutorial-fastopt.js"></script>
    
  </body>
</html>

The RPN tools using Scala's Stack structure:

Scala
package utils
 
import java.io.{FileNotFoundException, PrintWriter, StringWriter}
import Func._
import scala.util.Try
 
object Rpn  {
 
  val op2 = Map("+" -> Add, "*" -> Mul, "^" -> Pow, "frac" -> Frac, "-" -> Sub)
  val op1 = Map("log" -> Log, "exp" -> Exp, "cos" -> Cos, "sin" -> Sin, "neg" -> Neg, "sqrt" -> Sqrt)
 
   def parseString(s: String): Option[Double] =
    Try { s.toDouble }.toOption
 
  def norm[T](v: T):Func =
  v match {
    case s if (s == "x") => Var("x")
    case f: Func => f
    case n: String => parseString(n) match {
      case Some(d) => Const(d)
      case None    => Param(n)
    }
  }
 
  def rpnToFunc(typed:String):Option[Func] = {
    val vals = typed.split(" ").filterNot(_ == "")
    val pile = new scala.collection.mutable.Stack[Func]
    for (value <- vals) {
      if (op1 contains value){
        val op = op1(value)
        val n = norm(pile.pop)
        pile.push(op(n))
      }
      else if (op2 contains value){
        val op = op2(value)
        val n2 = norm(pile.pop)
        val n1 = norm(pile.pop)
        pile.push(op(n1,n2))
      }
      else{
        pile.push(norm(value))
      }
    }
    val res = pile.pop
    if (pile.isEmpty) {Some(res)}
    else {None}
  }

And now, the big tool: how to deal with algebraic expressions...still need improvement!

Scala
package utils
 
sealed trait Func
 
object Func{
  case class Const(value: Double) extends Func
  case class Param(value: String) extends Func
  case class Var(value: String) extends Func
  case class Add(f1: Func, f2: Func) extends Func
  case class Mul(f1: Func, f2: Func) extends Func
  case class Pow(f1: Func, f2: Func) extends Func
  case class Log(f: Func) extends Func
  case class Exp(f: Func) extends Func
  case class Cos(f: Func) extends Func
  case class Sin(f: Func) extends Func
  case class Frac(f1:Func, f2:Func) extends Func
  case class Sub(f1:Func, f2:Func) extends Func
  case class Neg(f:Func) extends Func
  case class Sqrt(f:Func) extends Func
}
 
object Op {
 
  import Func._
  import scala.util.Try
 
  def add(f1:Func, f2: Func): Func  = 
    (f1, f2) match {
      case (Const(a), Const(b)) => Const(a + b)
      case (Add(Const(k1), f), Const(k2)) => add(Const(k1 + k2), f)
      case (Add(f, Const(k1)), Const(k2)) => add(Const(k1 + k2), f)
      case (Const(k2), Add(Const(k1), f)) => add(Const(k1 + k2), f)
      case (Const(k2), Add(f, Const(k1))) => add(Const(k1 + k2 ), f)
      case (Mul(u,v), Mul(w,z)) if v == z => mul(add(u,w),v)
      case (Mul(u,v), Mul(w,z)) if u == w => mul(u,add(v,z))
      case (Mul(u,v), Mul(w,z)) if u == z => mul(u,add(v,w))
      case (Mul(u,v), Neg(Mul(w,z))) if v == z => mul(add(u,neg(w)),v)
      case (Mul(u,v), Neg(Mul(w,z))) if u == w => mul(u,add(v,neg(z)))
      case (Mul(u,v), Neg(Mul(w,z))) if u == z => mul(u,add(v,neg(w)))
      case (Log(u),Log(v)) => log(mul(u,v))
      case (Frac(a,b),Frac(c,d)) => frac(add(mul(a,d),mul(b,c)), mul(b,d))
      case (Frac(a,b),Const(k)) => frac(add(a,mul(Const(k),b)), b)
      case (Const(k), Frac(a,b)) => frac(add(a,mul(Const(k),b)), b)
      case (f, Const(0)) => f
      case (Const(0), f) => f
      case (Const(a), Neg(Const(b))) => Const(a-b)
      case (Neg(Const(a)), (Const(b))) => Const(-a+b)
      case (u,v) if u == v => mul(Const(2),u)
      case _  => Add(simp(f1), simp(f2))
  }  
  
  def mul(f1:Func, f2: Func): Func  = 
    (f1, f2) match {
      case (Const(l), Const(k)) => Const(k*l)
      case (Const(1), f) => f
      case (Const(0), _) => Const(0)
      case (f, Const(1)) => f
      case (_, Const(0)) => Const(0)
      case (Neg(u),Neg(v)) => mul(u,v)
      case (Const(k2), Mul(Const(k1), f)) => mul(Const(k1 * k2), f)
      case (Mul(Const(k1), f), Const(k2)) => mul(Const(k1 * k2), f)
      case (Mul(Const(k1),u), Mul(Const(k2),v)) => mul(Const(k1*k2),mul(u,v))
      case (u, Mul(Const(k), v)) => mul(Const(k), mul(u,v))
      case (Mul(Const(k), v),u) => mul(Const(k), mul(u,v))
      case (Mul(v, Const(k)),u) => mul(Const(k), mul(u,v))
      case (u,Mul(v, Const(k))) => mul(Const(k), mul(u,v))
      case (Frac(Const(1),b), Frac(Const(1),d)) =>
        frac(Const(1),mul(b,d))
      case (Const(k), Frac(Const(1),Mul(Const(l),u))) =>
        mul(frac(Const(k),Const(l)), frac(Const(1),u))
      //case (Frac(a,b),Const(k)) => frac(mul(Const(k),a),b)
      //case (Const(k),Frac(a,b)) => frac(mul(Const(k),a),b)
      case (Pow(u,n),Pow(v,k)) if u == v => pow(u,add(n,k))
      case (Pow(u,Const(n)),Frac(Const(1),Pow(v,Const(k))))
          if u == v =>{
            if (n >= k) {pow(u,add(Const(n),neg(Const(k))))}
            else {frac(Const(1),pow(u,add(Const(k),neg(Const(n)))))}
          }
      case (Frac(Const(1),Pow(v,Const(k))),Pow(u,Const(n)))
          if u == v =>{
            if (n >= k) {pow(u,add(Const(n),neg(Const(k))))}
            else {frac(Const(1),pow(u,add(Const(k),neg(Const(n)))))}
          }
      case (u,Frac(Const(1),Pow(v,Const(k))))
          if u == v =>{
           frac(Const(1),pow(u,add(Const(k),neg(Const(1)))))
          }
      case (Frac(Const(1),v),Pow(u,Const(n)))
          if u == v =>{
            pow(u,add(Const(n),neg(Const(1))))
          }
      case (Frac(Const(1),Pow(v,Const(k))),u)
          if u == v =>{
           frac(Const(1),pow(u,add(Const(k),neg(Const(1)))))
          }
      case (Pow(u,Const(n)),Frac(Const(1),v))
          if u == v =>{
            pow(u,add(Const(n),neg(Const(1))))
          }
      case (Pow(u,n),v) if u == v => pow(u,add(n,Const(1)))
      case (v,Pow(u,n)) if u == v => pow(u,add(n,Const(1)))
      case (v,u) if u == v => pow(u,Const(2))
      case (Exp(u), Exp(v)) => exp(add(u,v))
      case (Sqrt(u), Sqrt(v)) => sqrt(mul(u,v))
      case (u, Neg(v)) => neg(mul(u,v))
      case (Neg(u),v) => neg(mul(u,v))
      case (u,Const(k)) => mul(Const(k),u)
      case (u,Frac(Const(k),Const(l))) => mul(frac(Const(k),Const(l)),u)
      case _  => Mul(simp(f1), simp(f2))
    }
 
 
  def pow(f1:Func, f2: Func): Func  =
    (f1, f2) match {
      case (_, Const(0)) => Const(1)
      case (_, Const(1)) => f1
      case (Const(a), Const(b)) => Const(math.pow(a,b))
      case (Pow(u,n),p)  => pow(u,mul(n,p))
      case (u, Const(k)) if k <0 => frac(Const(1),pow(u,Const(-k)))
      case (Mul(Const(k),u),Const(n)) => mul(Const(math.pow(k,n)),pow(u,Const(n)))
      case (Mul(u,Const(k)),Const(n)) => mul(Const(math.pow(k,n)),pow(u,Const(n)))
      //case (Frac(a,b),n) => frac(pow(a,n),pow(b,n))
      case _ => Pow((f1), (f2))
    }
 
  def isInt(a:Double):Boolean =
    a.toInt == a
 
  def gcd(a:Double, b:Double):Double = {
    if (b == 0.0) {a}
    else {gcd(math.abs(b), (math.abs(a).toInt % math.abs(b).toInt).toDouble)}
  }
 
  def frac(f1: Func, f2:Func): Func =
    (f1, f2) match {
      case (u,v) if u == v => Const(1)
      case (u, Const(1))   => u
      case (Const(a),Const(b)) if isInt(a) & isInt(b) =>
        {var g = gcd(a,b)
          if (g > 1) {Frac(Const(a/g),Const(b/g))}
          else {Frac(f1,f2)}
        }
      case (Neg(u),Neg(v)) => frac(u,v)
     // case (u, Frac(a,b)) => frac(mul(u,b),a)
     // case (Frac(a,b),u)  => frac(a,mul(u,b))
      case (Pow(u,n), Pow(v,k)) if u == v => pow(u, add(n, mul(Const(-1),k)))
      case (u, Pow(v,k)) if u == v => pow(u, add(Const(1), mul(Const(-1),k)))
      case (Pow(v,k),u) if u == v => pow(u, add(k, Const(-1)))
      case (Mul(Const(k),Pow(u,n)), Pow(v,l)) if u == v =>
        mul(Const(k),pow(u, add(n, mul(Const(-1),l))))
      case (Mul(Const(k),Pow(u,n)), v) if u == v =>
        mul(Const(k),pow(u, add(n, Const(-1))))
      case (Pow(u,n), Mul(Const(k),Pow(v,l))) if u == v =>
        frac(Const(1),mul(Const(k), pow(u, add(l, mul(Const(-1),n)))))
      case (Mul(Const(k),u), Pow(v,l)) if u == v =>
        frac(Const(k),pow(u, add(l, Const(-1))))
      case (u, Mul(Const(k),Pow(v,l))) if u == v =>
        frac(Const(1),mul(Const(k), pow(u, add(l, Const(-1)))))
      case (Neg(u),v) => neg(frac(u,v))
      case (u,Neg(v)) => neg(frac(u,v))
     // //
      //case (Mul(Const(k),u),v) => mul(Const(k),frac(u,v))
      case (Const(k),Mul(Const(l),v)) if k != 1 =>
        mul(frac(Const(k),Const(l)),frac(Const(1),v))
      case (Const(k),v) if k != 1 =>
        mul(Const(k),frac(Const(1),v))
      case (u,Mul(v,w)) if u == v => frac(Const(1),w)
      case (u,Mul(v,w)) if u == w => frac(Const(1),v)
      case (Mul(v,w),u) if u == v => w
      case (Mul(v,w),u) if u == w => v
      //case (Frac(Const(1),u),Mul(Const(k),Frac(Const(1),v))) =>
      //  mul(frac(Const(1),Const(k)),frac(v,u))
      // //case (u,v) => mul(u,pow(v,Const(-1))) */
      case (Mul(Const(k),u),Mul(Const(l),v)) => mul(frac(Const(k),Const(l)),frac(u,v))
      case (Mul(Const(k),u),v) => mul(Const(k),frac(u,v))
      case (Frac(a,b),Frac(c,d))  => frac(mul(a,d),mul(b,c))
      case _ => Frac(simp(f1),simp(f2))
         }
 
  def log(f:Func): Func =
    f match {
      case Exp(u) => u
      case Pow(u,n) => mul(n,log(u))
      case Frac(Const(1),v) => neg(log(v))
     // case Mul(Const(k),v) => add(log(v),log(Const(k)))
      case _ => Log(simp(f))
    }
 
  def exp(f:Func): Func =
    f match {
      case Log(u) => u
      case _ => Exp(f)
    }
 
  def cos(f:Func): Func =
    Cos(f)
 
  def sin(f:Func): Func =
    Sin(f)
 
  def sqrt(f:Func): Func =
    f match {
      case Const(1) => Const(1)
      case Mul(Const(k),Var(x)) =>
        mul(sqrt(Const(k)),pow(Var(x),Const(0.5)))
      case Frac(Const(k),Var(x)) =>
        frac(sqrt(Const(k)),pow(Var(x),Const(0.5)))
      case _ => Sqrt(simp(f))
    }
 
  def neg(f:Func): Func =
    f match {
      //case Frac(u,v) => frac(neg(u),v)
      case Const(0) => Const(0)
      case Neg(u) => u
      case Mul(Neg(u),v) => mul(u,v)
      case Mul(v,Neg(u)) => mul(v,u)
      //case Const(k) => Const(-k)
      case _ => Neg((f))
    }
 
  def simp(f:Func): Func =
    f match{
      case  Const(v) => Const(v)
      case  Param(v) => Param(v)
      case  Var(v) => Var(v)
      case  Add(f1, f2) => add(f1,f2)
      case  Mul(f1, f2) => mul(f1,f2)
      case  Pow(f1, f2) => pow(f1,f2)
      case  Log(f) => log(f)
      case  Exp(f) => exp(f)
      case  Cos(f) => cos(f)
      case  Sin(f) => sin(f)
      case  Frac(f1, f2) => frac(f1,f2)
      case  Sub(f1, f2) => add(f1, neg(f2))
      case  Neg(f) => neg(f)
      case  Sqrt(f) => sqrt(f)
    }
 
     
  def deriv(f:Func, x:Func): Func =
    simp(f) match {
      case Neg(u)   => neg(deriv(u,x))
      case Var(_) => Const(1)
      case Const(_) | Param(_) => Const(0)
      case Add(u,v) => add(deriv(u,x), deriv(v,x))
      case Sub(u,v) => add(deriv(u,x), neg(deriv(v,x)))
      case Mul(Const(k),v) => mul(Const(k),deriv(v,x))
      case Mul(v,Const(k)) => mul(Const(k),deriv(v,x))
      case Mul(u,v) => add(mul(deriv(u,x),v), mul(deriv(v,x),u))
      case Pow(u,n) => mul(n, mul(deriv(u,x), pow(u, add(n, Const(-1)))))
      case Frac(u,v) => // deriv(mul(u, pow(v,Const(-1))),x)
        (u,v) match {
          case (z,Const(k)) => mul(frac(Const(1),Const(k)),deriv(z,x))
          case (Const(1),w) => neg(frac(deriv(w,x),pow(w,Const(2))))
          case _ => frac(add(mul(deriv(u,x),v), neg(mul(deriv(v,x),u))), pow(v,Const(2)))
        }
      case Log(u)   => mul(deriv(u,x), pow(u, Const(-1)))
      case Exp(u)   => mul(deriv(u,x), exp(u))
      case Cos(u)   => neg( mul(deriv(u,x), Sin(u)))
      case Sin(u)   => mul(deriv(u,x), Cos(u))
      case Sqrt(u)  => frac(deriv(u,x),mul(Const(2),Sqrt(u)))
      //case Var(y), Var(t)) => if (t == y) {Const(1)} else {Const(0)}
    }
 
  def derivn(f: Func, n: Int, x: Func): Func =
    if (n == 0) {simp(f)}
    else {derivn(simp(deriv(f,x)), n - 1, x)}
 
  def parseDouble(s: Double): Option[Double] =
    Try { s.toDouble }.toOption
 
  def parseOp2(o1:Option[Double], o2:Option[Double], op: (Double,Double) => Double): Option[Double] =
    (o1,o2) match {
      case (Some(d1), Some(d2)) => parseDouble(op(d1,d2))
      case _ => None
    }
 
  def parseOp1(o1:Option[Double], op: Double => Double): Option[Double] =
    o1 match {
      case Some(d1) => parseDouble(op(d1))
      case _ => None
    }
 
  def eval(f: Func, t: Double): Option[Double] =
    simp(f) match {
      case Param(_)  => None
      case Var(_)    => parseDouble(t)
      case Const(k)  => parseDouble(k)
      case Mul(u, v) => parseOp2(eval(u,t),eval(v,t),(x,y)=>x*y)
      case Add(u, v) => parseOp2(eval(u,t),eval(v,t),(x,y)=>x+y)
      case Sub(u, v) => parseOp2(eval(u,t),eval(v,t),(x,y)=>x-y)
      case Frac(u, v)=> parseOp2(eval(u,t),eval(v,t),(x,y)=>x/y)
      case Pow(u, n) => parseOp2(eval(u,t),eval(n,t),math.pow)
      case Log(u)    => parseOp1(eval(u,t), math.log)
      case Cos(u)    => parseOp1(eval(u,t), math.cos)
      case Sin(u)    => parseOp1(eval(u,t), math.sin)
      case Exp(u)    => parseOp1(eval(u,t), math.exp)
      case Sqrt(u)   => parseOp1(eval(u,t), math.sqrt)
      case Neg(u)    => parseOp1(eval(u,t), x => -x)
      case _         => None
    }
  
  def show(f: Func): String =
    simp(f) match {
      case Const(k) => k.toString()
      case Var(x)   => x
      case Param(k) => k
      case Mul(Const(-1),u) => "-" + show(u)
      case Mul(u,Const(-1)) => "-" + show(u)
      case Add(u,Neg(v)) => "\\left(" + show(u) + "-" + show(v) + "\\right)"
      case Add(u,Mul(Neg(v),w)) => "\\left(" + show(u) + "-" + show(mul(v,w)) + "\\right)"
      case Add(u,Mul(Mul(Const(-1),v),w)) => "\\left(" + show(u) + "-" + show(mul(v,w)) + "\\right)"
      case Add(u,v) => "\\left(" + show(u) + "+" + show(v) + "\\right)"
      case Sub(u,v) => "\\left(" + show(u) + " - " + show(v) + "\\right)"
      case Mul(u,v) =>  show(u) + "\\cdot " + show(v)
      case Log(u) => u match {
        case Exp(v) => show(v)
        case _      => "\\log\\left(" + show(u) + "\\right)"}
      case Exp(u) => u match {
        case Log(v) => show(v)
        case _ => "{\\rm e}^{" + show(u) + "}"}
      case Cos(u) => "\\cos\\left(" + show(u) + "\\right)"
      case Sin(u) => "\\sin\\left(" + show(u) + "\\right)"
      case Sqrt(u) => u match {
        case Pow(v,Const(2)) => "|" + show(v) + "|"
        case _               => "\\sqrt{" + show(u) + "}"}
      case Neg(u) => "=\\left(" + show(u) + "\\right)"
      case Frac(u,v) => "\\dfrac{" + show(u) + "}{" + show(v) + "}"
      case Pow(u,v) =>
        (u,v) match {
          case (w,Const(0.5)) => w match {
            case Pow(z,Const(2)) => "|" + show(z) + "|"
            case _ => "\\sqrt{" + show(u) + "}"}
          case (w,Const(2)) => w match {
            case Sqrt(z) => show(z)
            case Pow(y,Const(0.5)) => show(y)
            case Const(_)|Var(_)|Param(_) => show(u) + "^2"
            case _ => "\\left(" + show(u) + "\\right)^{2}"}
          case (Const(_)|Var(_)|Param(_),_) => show(u) + "^{" + show(v) + "}"
          case _ =>  "\\left(" + show(u) + "\\right)^{" + show(v) + "}"
        }
    }
}