はじめに
$\exp$, $\sin$などの初等関数を、たった1つの関数で表すことができる、ということで話題となってました。
そこで、どんなものかを試してみようという話です。
EMLとは
組み合わせれば色々な初等関数を表すことができるという関数EMLは、
$$
\mbox{EML}(x, y) = \exp(x) - \log(y)
$$
という関数です。例えば、$\mbox{EML}(1, 1)=e$は当たり前として、$\mbox{EML}(1, \mbox{EML}(\mbox{EML}(1, x), 1))=\log(x)$のようにEMLと1を組み合わせることであらかたの初等関数を表すことができると主張しています。
SymPyで実装する際の注意点
それでは、代数計算ライブラリであるSymPyでEMLを実装してみましょう。ただし、いくつかの注意点があります。まず、この論文では$\log(0)=-\infty$となることを期待しています。一方で、SymPyのlog(0)はS.ComplexInfinityになります。それだと都合が悪いので、ゼロの場合は負の無限大になる独自関数lnを定義します。
※なお、SymPyでもlnは定義されていますが、logと同じなのであまり意味はありません。
from sympy import log
class ln(log):
@classmethod
def eval(cls, x):
if x.is_zero:
return S.NegativeInfinity
return super().eval(x)
という感じで、ゼロなら$-\infty$を返し、それ以外はlogの値を返すようにします。さて、これを使って、EMLを実装しましょう。
from sympy import Function, exp, S
class EML(Function):
@classmethod
def eval(cls, x, y):
pass
def _eval_rewrite(self, rule, args, **hints):
if rule == exp:
return exp(args[0]) - ln(args[1])
これで、EML(1, 1).rewrite(exp)と書くと、$e$と評価してくれます。
一方で、変数を使う場合、更に考えなければいけないことは定義域です。ふつうに変数を宣言するとデフォルトでは複素数として考えてしまうため、期待する式変形ができません。そこで、変数は正の実数に制限した方が無難でしょう。
from sympy import symbols
x = symbols('x', real=True, positive=True)
EML(1, EML(EML(1, x), 1)).rewrite(exp).simplify() # ln(x)
これで、最低限遊べる状態になりました。
具体的に遊んでみる
$\sin$なども作れますが、かなりの複雑なEML式になるようで、ここでは論文に載っている例などを実装してみます。
EML(1, EML(EML(1, EML(x, 1)), 1)).rewrite(exp).simplify() # x
EML(1, EML(EML(1, 1), 1)).rewrite(exp).simplify() # 0
EML(1, EML(1, EML(EML(1, 1), 1))).rewrite(exp).simplify() # infty
EML(1, EML(1, EML(1, EML(EML(1, 1), 1)))).rewrite(exp).simplify() # -infty
EML(EML(1, EML(1, EML(1, EML(EML(1, 1), 1)))), EML(x, 1)).rewrite(exp).simplify() # -x
という感じで、単純な式を表すだけでも大層なEML式を書かないといけないで大変です。