辞書式圧縮による数式の簡略化 (Mathematica)

###数式の簡略化
Mathematicaでは記号計算の結果、大変複雑な記号式が生成される場合があります。
`Simplify`,`FullSimplify`は数式を変形して簡単にしてくれますが、
これらは、数式の中から繰り返しでてくる部分を見つけ、それを変数で置き換えるということはやってくれません。
今回はそれをやってくれる関数を作ります。

例えば、四次方程式 $0 = a_4 x^4 + a_3 x^3 + a_2 x^2 + a_1 x^1 + a_0$ の解はたいへん煩雑なものになります。

```Mathematica
poly = Sum[Subscript[a, k] x^k, {k, 0, 4}];
sol = Solve[poly == 0, x];
MapIndexed[
  Style[TraditionalForm[Subscript[x, First[#2]] == #1], 20] &, 
  sol[[All, 1, 2]]] // Column
```
閲覧注意：[四次方程式の解の公式](https://upload.wikimedia.org/wikipedia/commons/c/cd/%E5%9B%9B%E6%AC%A1%E6%96%B9%E7%A8%8B%E5%BC%8F%E3%81%AE%E8%A7%A3%E3%81%AE%E5%85%AC%E5%BC%8F.png)

これをこれから紹介する関数`DictionaryCompress`を使うと次のように簡単化されます。

```Mathematica
poly = Sum[Subscript[c, k] x^k, {k, 0, 4}];
sol = Solve[poly == 0, x];
expr = x /. sol;
Column[{Column[
     MapIndexed[
      Style[TraditionalForm[Subscript[x, First[#2]] == #], 20] &, #[[
       1]]], Spacings -> 1],
    Column[Style[#, 20] & /@ #[[2]], Spacings -> 1, Frame -> All]
    }] &@DictionaryCompress[expr, 1, 1, 
  Framed[#, Background -> LightYellow] &]
```
![eq4c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178902/85fa4e44-6f52-2f2f-a1ac-fb87622d4535.png)

`DictionaryCompress`はこのようなコードです。

```Mathematica
DictionaryCompress[expr_, count_, size_, func_] := Module[
  {t, s, rule, updateRule},
  updateRule[ruleloc_] := Module[{rule1, rule2, rulertn},
    rulertn = (# //. Cases[ruleloc, Except[#]]) & /@ ruleloc;
    rule1 = 
     Select[rulertn, ((! FreeQ[#, Plus]) && (! NumericQ[#[[1]]])) &];
    rule2 = Complement[rulertn, rule1];
    rulertn = rule1 //. (Reverse /@ rule2);
    rulertn = 
     rulertn /. MapIndexed[ Last[#1] -> func @@ #2 &, rulertn];
    rulertn
    ];
  t = Tally@Level[expr, Depth[expr]];
  s = Sort[
    Select[{First@#, Last@#, Depth[First@#]} & /@ 
      t, (#[[2]] > count && #[[3]] > size) &], #1[[2]]*#1[[3]] < #2[[
        2]]*#2[[2]] &];
  rule = MapIndexed[First[#1] -> func @@ #2 &, s];
  rule = FixedPoint[updateRule, rule, 3];
  {
   expr //. rule, Reverse /@ rule
   }
  ]
```
###基本的なコード内容
数式はツリー構造で表されます。例えば三次方程式の解は

```Mathematica
poly = Sum[Subscript[a, k] x^k, {k, 0, 3}];
sol = Solve[poly == 0, x];
sol[[All, 1, 2]][[1]]
```
![eq3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178902/4eeffedb-1afa-56e1-f821-7d3456a6f367.png)
このような式ですが、これをツリー構造で表すと、

```Mathematica
sol[[All, 1, 2]][[1]]//TreeForm
```
![eq3tree.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178902/04509424-d112-202b-4aa0-76f5741451a3.png)
こうなります。

`Level`はこのようなツリー構造のサブツリーを取得し、リストとして返します。また`Tally`はリストを調べ、例えばaがn個あったらそれらをまとめて{a,n}という形にしてくれます。これらを組み合わせると、式に含まれる部分式の一覧表を作ることができます。

```Mathematica
expr=sol[[All, 1, 2]][[1]]]];
t=Tally[Level[expr,Depth@expr);
Column[#, Alignment -> Center, Dividers -> All] & /@ 
(
 {
  Style["Count : " <> ToString@Last[#], 20],
  Style["Size : " <> ToString@Depth[First[#]], 20],
  TreeForm@First[#]
 }& /@ t
) // Multicolumn
```
![etreetally.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178902/1727b3c2-7f92-0095-877a-01a760f43c64.png)
Countはもとの式にそのサブツリーが何回出現したか、Sizeはサブツリーの深さを表します。

一覧表ができたら、この中から、置き換えをする価値のある部分式を選びだします。出現頻度が高くサイズが大きいものがいいでしょう。CountとSizeに条件をつけて、その条件にあう部分式を`Select`で選びだします。

```Mathematica
s=Select[t,(Last[#]>3&&Depth[First[#]]>3)&];
Column[#, Alignment -> Center, Dividers -> All] & /@ 
(
 {
  Style["Count : " <> ToString@Last[#], 20],
  Style["Size : " <> ToString@Depth[First[#]], 20],
  TreeForm@First[#]
 }& /@ s
) // Multicolumn
```
![etreetally2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178902/7f50464d-b94e-4379-c7f5-c15dc6a49f90.png)
これから部分式置き換えのルールを作成します。

```Mathematica
rule=MapIndexed[First[#1] -> Framed[First@#2, Background -> LightYellow] &,s]
```
![rule.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178902/d7e33d5c-0f2f-218a-bc97-711cca6b38f5.png)
`ReplaceRepated`でできたルールをもとの式に適用します。`ReplaceRepated`は`Rule`をある式に対し、その式が変化しなくなるまで適用するというものです。
`ReplaceRepated`はシンタックスシュガー`//.`があり、これを使って簡単にかけます。
ちなみに、Mathemataicaでは`+`や`-`も`Plus`や`Subtract`のシンタクッスシュガーです。Mathematicaで見慣れない記号があったらそれはなにかのシンボルのシンタクッスシュガーです。

```
expr //. rule
```

![expr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178902/ff7c0224-c752-cff7-ba98-5e932843213a.png)

部分式置き換えができました！

###プラスアルファ
上のコードだと、まだ少し能力が弱いものになります。
そこで`DictionaryCompress`では、`updateRule`という関数でルールを更新する仕組みをつけてあります。まず部分式置き換えのルールをたくさん生成したら、生成されたルールをお互いに作用させあいます。そうするといくつかはよりコンパクトな新しい置き換えルールになるので、これを繰り返してルールを更新していくという仕組みをつけています。

また、サイズや出現頻度という構造的な条件とは別に、数学的な条件で置き換えにふさわしい部分式を選ぶこともしています。
この条件は「足し算を最低一個含み、数字ではない」という条件です。
詳しくはコード読んでください！ありがとうございました！