This algorithm is implemented in the Scala language, a characteristic feature of which is the use of case classes that are so well suited for writing a differentiation algorithm. In this article, it is planned to describe only a part of the program containing the algorithm for finding the derivative, since the development of a parser for mathematical expressions is another big topic,
worthy of a separate article
First, we describe the data structure in which the original mathematical function will be stored. We describe the MathAST treit :
sealed trait MathAST
sealed trait SingleToken extends MathAST{ val a: MathAST } sealed trait DoubleToken extends MathAST{ val a: MathAST val b: MathAST } sealed trait LeafToken extends MathAST
case object Pi extends LeafToken case object Exponenta extends LeafToken case class Sin(override val a: MathAST) extends SingleToken case class Cos(override val a: MathAST) extends SingleToken … case class Mul(override val a: MathAST, override val b: MathAST) extends DoubleToken case class Add(override val a: MathAST, override val b: MathAST) extends DoubleToken … case class Differentiate(f: MathAST, dx: Variable) extends MathAST case class Variable(name: String) extends LeafToken case class Constant(value: BigDecimal) extends LeafToken
The basis of which are the rules of differentiation and the table of derivatives .
We describe a recursive function, which will transform the original mathematical function into the resulting derivative function:
def differentiate(f: MathAST)(implicit dx: String): MathAST
In general, the algorithm is too abstract, so we will analyze it further in more detail.
The function took the input data - expression-function, which should be processed in accordance with the rules of differentiation
Binary expressions are marked with a DoubleToken trait . The operator is checked for compliance with one of the available operators ("+", "-", "*", "/", "^"):
case Add(a, b) => Add(differentiate(a), differentiate(b)) case Sub(a, b) => Sub(differentiate(a), differentiate(b)) …
As an example, we give the processing of the multiplication operation:
case Mul(a, b) => { val u = isDependsOnVar(a) val v = isDependsOnVar(b) if (u && !v) { Mul(differentiate(a), b) } else if (!u && v){ // c^u(x), c=const Mul(a, differentiate(b)) }else if (!u && !v){ // c^c, c=const Constant(BigDecimal(0)) }else Add(Mul(differentiate(a), b), Mul(a, differentiate(b))) }
SingleToken classes are handled as follows:
case e: SingleToken => val d = e match { case Sin(x) => Cos(x) case Cos(x) => Usub(Sin(x)) case Tg(x) => Div(Constant(BigDecimal(1)), Pow(Cos(x), Constant(BigDecimal(2)))) … } if (isLeaf(ea)) d else Mul(d, differentiate(ea))
It's about a variable or a constant.
case Variable(a) => if (a == dx) Constant(BigDecimal(1)) else Constant(BigDecimal(0)) case Constant(a) => Constant(BigDecimal(0)) case Pi | Exponenta => Constant(BigDecimal(0)) case _ => throw new AbstractEvaluateException("Differentiate: Wrong input data")
Finally, add the line:
case Differentiate(_f, _dx) => differentiate(_f)(_dx.name)
object Derivative { def apply(e: MathAST)(implicit dx: String): MathAST = differentiate(e) def differentiate(f: MathAST)(implicit dx: String): MathAST = f match { case Differentiate(_f, _dx) => differentiate(_f)(_dx.name) case Add(a, b) => Add(differentiate(a), differentiate(b)) case Sub(a, b) => Sub(differentiate(a), differentiate(b)) case Div(a, b) => Div(Sub(Mul(differentiate(a), b), Mul(a, differentiate(b))), Pow(b, Constant(BigDecimal(2)))) case Pow(a, b) => { val u = isDependsOnVar(a) val v = isDependsOnVar(b) if (u && !v) Mul(Mul(Pow(a, Sub(b, Constant(BigDecimal(1)))), differentiate(a)), b) // u(x)^c, c=const else if (!u && v){ // c^u(x), c=const Mul(Mul(Pow(a, b), differentiate(b)), Ln(a)) }else if(!u && !v){ Constant(BigDecimal(0)) }else Mul(Pow(a, Sub(b, Constant(BigDecimal(1)))), Add(Mul(b, differentiate(a)), Mul(Mul(a, Ln(a)), differentiate(b)))) } case Mul(a, b) => { val u = isDependsOnVar(a) val v = isDependsOnVar(b) if (u && !v) { Mul(differentiate(a), b) } else if (!u && v){ // c^u(x), c=const Mul(a, differentiate(b)) }else if (!u && !v){// c^c, c=const Constant(BigDecimal(0)) }else Add(Mul(differentiate(a), b), Mul(a, differentiate(b))) } case e: SingleToken => val d = e match { case Sin(x) => Cos(x) case Cos(x) => Usub(Sin(x)) case Tg(x) => Div(Constant(BigDecimal(1)), Pow(Cos(x), Constant(BigDecimal(2)))) case Ctg(x) => Usub(Div(Constant(BigDecimal(1)), Pow(Sin(x), Constant(BigDecimal(2))))) case Abs(x) => Div(x, Abs(x)) case Ln(x) => Div(Constant(BigDecimal(1)), x) case Sqrt(x) => Div(Constant(BigDecimal(1)), Mul(Constant(BigDecimal(2)), Sqrt(x))) case Usub(x) => Usub(differentiate(x)) case Arcsin(x) => Div(Constant(BigDecimal(1)), Sqrt(Sub(Constant(BigDecimal(1)), Pow(x, Constant(BigDecimal(2)))))) case Arccos(x) => Usub(Div(Constant(BigDecimal(1)), Sqrt(Sub(Constant(BigDecimal(1)), Pow(x, Constant(BigDecimal(2))))))) case Arctg(x) => Div(Constant(BigDecimal(1)), Sub(Constant(BigDecimal(1)), Pow(x, Constant(BigDecimal(2))))) case Arcctg(x) => Usub(Div(Constant(BigDecimal(1)), Sub(Constant(BigDecimal(1)), Pow(x, Constant(BigDecimal(2)))))) case _ => throw new AbstractEvaluateException("Differentiate: Unknown unary operator") } if (isLeaf(ea)) d else Mul(d, differentiate(ea)) case Variable(a) => if (a == dx) Constant(BigDecimal(1)) else Constant(BigDecimal(0)) case Constant(a) => Constant(BigDecimal(0)) case Pi | Exponenta => Constant(BigDecimal(0)) case _ => throw new AbstractEvaluateException("Differentiate: Wrong input data") } private def isLeaf(e: MathAST): Boolean = e match { case Variable(_) | Constant(_) => true case Pi | Exponenta => true case _ => false } private def isDependsOnVar(tree: MathAST)(implicit dx: String): Boolean = tree match{ case e: DoubleToken => (ea match { case Variable(name) => if(name == dx) true else false case _ => isDependsOnVar(ea) })||(eb match { case Variable(name) => if(name == dx) true else false case _ => isDependsOnVar(eb) }) case e: SingleToken => isDependsOnVar(ea) case Variable(name) => if(name == dx) true else false case _ => false } }
All source code can be downloaded on github , you can test the program online on the Derivatives Calculator site online , the application is implemented as a REST service and supplemented with expression simplification modules.
Source: https://habr.com/ru/post/309676/
All Articles