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$関数の引数は(真数、底)の順番であることに注意して下さい。
from sympy import log, simplify, N, Eq
x=2**(log(18,6))
y=3**((2+log(2,3))*log(2,6))
評価1
$x,y$と、等号評価「==」と、sympyの等号評価「equals」、「Eq」(simplify付き)の結果を表示します。
equalsは代数的に等しいかを評価し、Eqは2式が等しければTrueを返しますが基本的には(Falseであれば)等式そのものを返します。
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です。
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))))
評価2(simplify)
次に$x,y$をsimplifyで整理してみます。
#数式を整理する
x=simplify(x)
y=simplify(y)
ここで$y$の式は少し整理されましたが、やはりFalseとなりました。なお、equalsでNoneと表示されているのは判定不能だそうです。
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))))
評価3(差分を取ってからsimplify)
$x,y$をそれぞれ整理するよりも、差分を取ってから式を整理すると、上手く0になることもあるので試してみました。
#xとyの差分をとって、0になるか確かめる
x=x-y
y=0
やはりここでもFalse、Noneとなってしまいます。
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)
評価4(log_2 をとってから評価)
式が複雑過ぎて評価出来ない可能性があるので、少なくとも$x$の式を簡単にできるように$\log_2$を取ってみます。
#両辺のlog_2を取ると、途端に正解する
x=log(x,2)
y=log(y,2)
これでやっと、Trueと評価されました。ただし、「==」はFalseと評価されています。
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
評価5(log_2 をとり、simplifyをしてから評価)
評価4に加えて、さらに両辺をsiplifyで整理してから評価します。
#両辺のlog_2を取ると、途端に正解する
x=log(x,2)
y=log(y,2)
#数式を整理する
x=simplify(x)
y=simplify(y)
ついに、$x,y$の式が一致しました。
これで「==」もTrueを返します。
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
評価6(数値的一致を見る)
sympyのNを用いて数値計算します。
ここでは、一番最初に定義した$x,y$の式をそのまま計算させています。
#数値計算する
x=N(x)
y=N(y)
計算方法や順序によっては誤差が出るので、本来は完全に0と一致するとは限りませんが、今回は完全一致したようです。
x:: 3.05918465698377
y:: 3.05918465698377
x==y:: True
sympy.x.equals(y):: True
sympy.Eq(x,y).simplify():: True
Conclusion
前述のSummaryのとおりです
それでは良きPythonライフを!