Scalaの微分関数の解析計算

はじめに



このアルゴリズムはScala言語で実装されており、その特徴はcase-classesの使用であるため、微分アルゴリズムの記述に適しています。 この記事では、微分アルゴリズムを含むプログラムの一部のみを説明する予定です。これは、数式のパーサーの開発も大きなトピックであるためです。

別の記事に値する







準備する



最初に、元の数学関数が保存されるデータ構造について説明します。 MathAST 特性について説明しましょう。







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
      
      





SingleTokenは、sin(a)、-a、ln(a)などの単項演算子のケースクラスを実装します。

DoubleTokenはバイナリ演算子を記述します:a + b、a ^ bなど。

LeafToken-ツリーの「葉」、つまり 定数、変数、および予約済みの定数名(Pi番号と指数)。



演算子とトークンのクラス/オブジェクトについて説明します。



 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
      
      





Differentiateクラスに注意してください。 このクラスには特別なシグネチャがあります。fは元の関数、 dxは微分が発生する変数です。



これで、例えば、関数をとるなど、計算のツリーの形で数学関数を表すすべてのものがあります。 、形式は次のとおりです。



Mul(定数(BigDecimal(2))、Pow(x、定数(BigDecimal(2))))



もちろん、ユーザーが入力した通常の文字列から式ツリーを取得するには、パーサーを記述する必要がありますが、前述のように、これは別のトピックです。 私は、プログラムがscala.util.parsing.combinatorパッケージからParsers トレイトを継承するその場しのぎのパーサーを使用しているとしか言えません。







微分アルゴリズム



これは微分規則と導関数の表に基づいています



元の数学関数を結果の微分関数に変換する再帰関数を説明します。







 def differentiate(f: MathAST)(implicit dx: String): MathAST
      
      





(微分が発生する)変数の名前を含むdx引数は暗黙的にマークされます。これにより、コンパイラーがそれを行っても、再帰呼び出しに渡さないようにできます。



式-ソース関数f(x)MathAST形式)は再帰関数の入力に渡され 、戻り値は微分関数です 同じ形式で。



注1 :式には、バイナリ、単項、またはトークンを使用できます。

注2 :演算子は、「+」、「-」、「^」、「*」、「/」、「abs」、「sin」、「cos」、「tg」、「ctg」、「 ln "、" arcsin "、" arccos "、" arctg "、" arcctg "、"( "、") "。

注3 :入力および出力データは、MathAST-tree-expressionの形式で表示されます。







一般的なアルゴリズム



一般に、アルゴリズムは抽象的すぎるため、以下で詳細に分析します。







  1. 再帰関数は、データを入力として受け取り、 パターンマッチングを使用して、データのタイプに応じて必要なアクションを実行します。



  2. この関数は、入力式の導関数を計算し、結果式を返します。 引数aおよび/またはbが定数または変数ではなく、複雑な関数u(x)であることが判明した可能性があります。

    次に、導関数u '(x)を再帰的に計算する必要があります。 return [differentiate(u(x))] -新しいデータでステップ1に進みます-u(x)



  3. データが正しくない場合は、エラーメッセージを返します。


仕事の原理と数学的抽象化との関係の詳細



関数は、入力としてデータを受け取りました-微分の規則に従って処理されるべき式関数







バイナリ式の場合



バイナリ式は、DoubleToken traitでマークされます 。 演算子は、使用可能な演算子のいずれか( "+"、 "-"、 "*"、 "/"、 "^")に準拠しているかどうかがチェックされます。







 case Add(a, b) => Add(differentiate(a), differentiate(b)) case Sub(a, b) => Sub(differentiate(a), differentiate(b)) …
      
      









  1. 演算子が「+」の場合: [differentiate(a)+ distinct(b)]を返します。



  2. 演算子が「-」の場合: [differentiate(a)-differentiate(b)]を返します。



  3. 演算子「*」の場合:乗算がより複雑な場合、オペランドaおよびbは定数または変数(合計4つの組み合わせ: u(x)* cu(x)* v(x)c * cc * u(x) )。

    この関数は、4つのオプションのどれがキャッチされたかを分析し、微分ルールNo. 1、No。3、No。5を使用して式を返します。

    オペランドの1つが複合関数の場合。 たとえば、 a = u(x)b = v(x)の場合、 [differentiate(a)* b + a * distinct(b))]を返します。

    isDependsOnVarプライベートメソッドは、部分式が微分される変数に依存するかどうかをチェックします。



  4. 演算子が「/」の場合:アクションは演算子「*」の場合と同じです。



  5. べき乗演算子が「^」の場合:式には、 u(x)^ cu(x)^ v(x)c ^ cc ^ u(x)の 4つのバリアントもあります。

    3番目のケースは最も自明ではありません: u(x)^ v(x) 、そのような式の導関数を見つけるには、対数とそのプロパティを使用する必要があります(対数手順の説明はこの記事の範囲を超えているため、すぐに既製の式を提供します-No. 6)。



    残りの3つのケースでは、標準の表式が使用されます。


例として、乗算演算の処理を示します。







 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クラスは次のように処理されます。







 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))
      
      





オペレーターは、使用可能なオペレーターのいずれか(「sin」、「-」、「cos」、...)に準拠しているかどうかチェックされます。

たとえば、「sin」演算子は、 [cos(a)]を返します。a = xの場合、aが複素関数u(x)の場合、 [cos(a)* distinct(a)]を返します。



他の演算子では、複雑な関数の微分の規則と導関数を取るための表の規則を使用して、同様のアクションが発生します。



それとは別に、 abs演算子-モジュールはテーブルにないため、考慮する必要があります。









isLeafプライベートメソッド 、関数が複素数であるかどうかを調べます。最初の場合は、現在の結果にネストされた関数の導関数を乗算して返す必要があり、2番目の場合は単に現在の結果を返します。







1つのトークン



変数または定数に関するものです







 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")
      
      









  1. 誤ったデータを入力し、エラーメッセージを表示して終了します。
  2. 変数(微分が実行されるxのように )の場合、 [1]を返します。
  3. 定数の場合、 [0]を返します。


最後に、次の行を追加します。







 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 } }
      
      









おわりに



すべてのソースコードはgithubにダウンロードできます。ウェブサイトDerivative Calculator onlineでプログラムをオンラインでテストできます。アプリケーションはRESTサービスの形式で作成され、式簡略化モジュールで補完されます。







参照資料






All Articles