指数と対数を組み合わせた等式の評価について

# Introduction
　正整数は素因数分解によって、たとえば $18 = 2 \times 3^2$ のように、素数の正整数乗の積として一意に表すことができます。
　一方、指数に実数を許すと、この表現は一意ではなくなります。実際、$18 = 2^{1+\log_2 3} \times 3 = 2 \times 3 \times 5^{\log_5 3}$ のように、無数の表し方が存在します。
　このように、指数に実数、特に対数（logarithm）を用いた場合に、2つの異なる表現が等しいかどうかを判定するプログラムを、PythonのSymPyを使って簡単に作ってみました。しかし、意外にも正しく評価できないケースがあったため、今回この記事を書くに至りました。

# Summary
以下では具体的な実例を扱いますが、全体像をつかんでいただくため、先にサマリーをまとめます。

- 指数と対数を組み合わせた等式が、正しく評価できない場合がありました
- 実例では前処理を工夫することで、正しく評価できる例を示しました
- ただし、これらは実例に対するadhocな前処理方法であり、すべての等式に適用できるわけではありません
- 等式評価を汎用的に行える便利なライブラリがあれば、ぜひ知りたいです

# Example
以下の$x,y$が等しいことを確かめよ

$x=2^{\log_6 18}$

$y=3^{(2+\log_3 2) \cdot \log_6 2}$

- コードはGoogle colaboratoryで作り、以下のGithubに置きました
https://github.com/wagyan/math/blob/master/ExponentsAndLogarithms.ipynb

## Codes

sympyを使っています。$\log$関数の引数は（真数、底）の順番であることに注意して下さい。

```Python
from sympy import log, simplify, N, Eq
x=2**(log(18,6))
y=3**((2+log(2,3))*log(2,6))
```

### 評価１

$x,y$と、等号評価「==」と、sympyの等号評価「equals」、「Eq」(simplify付き)の結果を表示します。

equalsは代数的に等しいかを評価し、Eqは2式が等しければTrueを返しますが基本的には（Falseであれば）等式そのものを返します。
```Python
print("x:: {}".format(x))
print("y:: {}".format(y))

print("x==y:: {}".format(x==y))
print("sympy.x.equals(y):: {}".format(x.equals(y)))
print("sympy.Eq(x,y).simplify():: {}".format(Eq(x,y).simplify()))
```

結果は、Falseです。

```Python
x:: 2**(log(3)/log(6) + 1)
y:: 3**((log(2)/log(3) + 2)*log(2)/log(6))
x==y:: False
sympy.x.equals(y):: False
sympy.Eq(x,y).simplify():: Eq(2**(log(3)/log(6) + 1), 3**(log(2)*log(18)/(log(3)*log(6))))
```

### 評価２（simplify）

次に$x,y$をsimplifyで整理してみます。
```Python
#数式を整理する
x=simplify(x)
y=simplify(y)
```
ここで$y$の式は少し整理されましたが、やはりFalseとなりました。なお、equalsでNoneと表示されているのは判定不能だそうです。

```Python
x:: 2**(log(3)/log(6) + 1)
y:: 3**(log(2)*log(18)/(log(3)*log(6)))
x==y:: False
sympy.x.equals(y):: None
sympy.Eq(x,y).simplify():: Eq(2**(log(3)/log(6) + 1), 3**(log(2)*log(18)/(log(3)*log(6))))
```

### 評価３（差分を取ってからsimplify）

$x,y$をそれぞれ整理するよりも、差分を取ってから式を整理すると、上手く0になることもあるので試してみました。

```Python
#xとyの差分をとって、0になるか確かめる
x=x-y
y=0
```

やはりここでもFalse、Noneとなってしまいます。

```Python
x:: -3**(log(2)*log(18)/(log(3)*log(6))) + 2**(log(18)/log(6))
y:: 0
x==y:: False
sympy.x.equals(y):: None
sympy.Eq(x,y).simplify():: Eq(-3**(log(2)*log(18)/(log(3)*log(6))) + 2**(log(3)/log(6) + 1), 0)
```

### 評価４（log_2 をとってから評価）

式が複雑過ぎて評価出来ない可能性があるので、少なくとも$x$の式を簡単にできるように$\log_2$を取ってみます。

```Python
#両辺のlog_2を取ると、途端に正解する
x=log(x,2)
y=log(y,2)
```

これでやっと、Trueと評価されました。ただし、「==」はFalseと評価されています。

```Python
x:: log(2**(log(3)/log(6) + 1))/log(2)
y:: log(3**((log(2)/log(3) + 2)*log(2)/log(6)))/log(2)
x==y:: False
sympy.x.equals(y):: True
sympy.Eq(x,y).simplify():: True
```

### 評価５（log_2 をとり、simplifyをしてから評価）

評価４に加えて、さらに両辺をsiplifyで整理してから評価します。

```Python
#両辺のlog_2を取ると、途端に正解する
x=log(x,2)
y=log(y,2)

#数式を整理する
x=simplify(x)
y=simplify(y)
```

ついに、$x,y$の式が一致しました。
これで「==」もTrueを返します。

```Python
x:: log(3)/log(6) + 1
y:: log(3)/log(6) + 1
x==y:: True
sympy.x.equals(y):: True
sympy.Eq(x,y).simplify():: True
```

### 評価６（数値的一致を見る）

sympyのNを用いて数値計算します。

ここでは、一番最初に定義した$x,y$の式をそのまま計算させています。

```Python
#数値計算する
x=N(x)
y=N(y)
```

計算方法や順序によっては誤差が出るので、本来は完全に0と一致するとは限りませんが、今回は完全一致したようです。

```Python
x:: 3.05918465698377
y:: 3.05918465698377
x==y:: True
sympy.x.equals(y):: True
sympy.Eq(x,y).simplify():: True
```

# Conclusion
前述の[Summary](#summary)のとおりです

それでは良きPythonライフを！