本記事ではプログラミング言語『Python』の数式処理ライブラリ『SymPy』の使い方を紹介します。
数式変形をすることそれ自体も楽しいものですが、単純作業という一面があることも否めません。効率よく勉強するには面倒な計算はコンピュータに任せてしまうのも一つの方法でしょう。
数式を処理するソフトウェアとしてよく知られているのはMathematicaでしょうか?利用者も多いので、わからないことがあったときなどは、その充実したヘルプ機能に加えて、ネット検索によっても解決策が得られやすいという長所があります。しかしいかんせん有料(結構高い)なので個人利用で導入するのは気乗りしません。
ここではMathematicaの代替として、Pythonで使える数式処理ライブラリ『SymPy』の使い方を紹介します。といっても、いち利用者として具体例を扱いながら、習うより慣れろで使い方を学んだだけなので、ちょっと高度で便利な関数電卓の(自己流の)使い方を紹介します、という感覚の記事です。
- SymPy の使い方入門
SymPy の使い方入門
以下では具体的に Python(SymPy)の入力例を示します。
動作は JupyterNotebook で確認していますが、(もし問題があれば)適宜ご自分の動作環境のものに読み替えてください。
コピー&ペーストして計算を実行させたり、入力をいじったりして遊んでみてください。
SymPyライブラリの使い方の前に、Pythonの基本的な構文などを確認しておきたい方は以下の記事を参照してください。
SymPy ライブラリの読み込み
なにはともあれまずは SymPy ライブラリを読み込んでおきます:
import sympy
モジュロ演算(剰余演算)
11 % 2 # m % n で、 m を n で割った余りを出力します
他方、商の整数部分は
11 // 2
で与えられます。
冪乗・階乗・二重階乗・ルート
3**2
10の冪乗については次のようにしても入力できます:
1.2e2
ただしこのように入力した場合にはfloat型になります:
type(1.2e2)
また小数については
3e-3
とします。
sympy.factorial(10)
sympy.factorial2(10)
sympy.factorial2(9)
sympy.sqrt(50)
複素数
実部・虚部
sympy.re(2+sympy.I*3)
sympy.im(2+sympy.I*3)
絶対値
sympy.Abs(2+sympy.I*3)
偏角
sympy.arg(2+sympy.I*3)
数値の参照
数学定数や、特定の点での関数の値などを数値的にみたいときには
sympy.pi.evalf(10)
などとします。evalfの引数は表示する桁数を指定します。
代数的操作
変数を宣言する
sympy.var('x') # 変数 x の宣言
または
x = sympy.symbols('x')
としても x を変数として定義できます。
部分分数分解する
sympy.apart(1/(x**2-4*x+3))
sympy.apart(1/(x**6-1))
式を簡単化する・通分する
sympy.simplify((2*x-2)/((x-1)**2*(x-2)))
sympy.simplify(1/(x-1)**2+1/((x-1)*(x-2)))
式を簡単化・通分したうえで展開する
sympy.cancel((2*x-2)/((x-1)**2*(x-2)))
sympy.cancel(1/(x-1)**2+1/((x-1)*(x-2)))
式の真偽を判定する
0==sympy.sqrt(2)-2**(sympy.Rational(1,2))
0==sympy.sqrt(2)-2**(sympy.Rational(1,3))
sympy.factorial(50)==sympy.factorial2(50)*sympy.factorial2(49)
ただし数式を適当に処理しないとうまく判定しない場合も多いです:
x**2-2*x+1==(x-1)**2
x**2-2*x+1==sympy.expand((x-1)**2)
多項式から特定の項の係数を取り出す
sympy.expand((x+y)**6).coeff(x, 3) # expr.coeff(var, n) で数式 exp の変数 var の n 次の係数を表示
変数に値を代入する
2次式
で、 を代入してみます。
sympy.var('x, a, b, c') (a*x**2+b*x+c).subs(x, 3)
代入できるものは数字に限らず、文字式でも問題ありません。2次式
に、解の公式から得られる根の1つ
を代入して整理してみましょう。
sympy.expand((a*x**2+b*x+c).subs(x, (-b + sympy.sqrt(-4*a*c + b**2))/(2*a)))
もちろん期待通り、0が返ってきます。
総和を求める
k=sympy.symbols('k', integer = True) sympy.summation(k, (k, 1, 10) )
和の上限を文字にしても大丈夫です。
k, N=sympy.symbols('k N', integer = True) sympy.factor(sympy.summation(k, (k, 1, N) ))
sympy.factor(sympy.summation(k**3, (k, 1, N) ))
総乗を求める
k=sympy.symbols('k', integer = True) sympy.product(k, (k, 1, 10) )
sympy.factorial(10)==sympy.product(k, (k, 1, 10) )
変数の性質を規定する
整数であると宣言する
すでに上の総和を求めるさいの添字として、変数が整数であると宣言する書式を使っています。
k = sympy.symbols('k', integer = True)
実数であると宣言する
sympy.var('a, b', real = True) sympy.conjugate(a+b*sympy.I)
とくに何も宣言がなければ、文字は複素数として扱われてしまいます:
sympy.var('z')
sympy.conjugate(z)
非負であると宣言する
sympy.var('r', nonnegative = True) sympy.sqrt(r**2)
とくに何も宣言がなければ、絶対値などでケアが必要な場合に処理が進みません:
sympy.var('s') sympy.sqrt(s**2)
正であると宣言する
sympy.var('t', positive = True) sympy.Abs(t)
変数依存性を宣言する
from sympy.core.function import Function R = Function('R')(r, t)
したがって
sympy.diff(R, r)
によって
などという計算ができます。
初等関数
三角関数
正弦関数(サイン)
sympy.var('x')
sympy.sin(x)
余弦関数(コサイン)
sympy.cos(sympy.pi)
sympy.tan(sympy.pi/2)
正割関数(セカント)
sympy.sec(x)
余割関数(コセカント)
sympy.csc(x)
余接関数(コタンジェント)
sympy.cot(x)
指数関数
sympy.exp(1)
で与えられます。ただし は実数です。これをみてみます。左辺の実部は
sympy.var('theta', real = True) sympy.re(sympy.exp(sympy.I*theta))
より であることが、虚部は
sympy.im(sympy.exp(sympy.I*theta))
より であることが出力されます。
対数関数
sympy.log(sympy.E)
指数関数と対数関数は互いに逆関数の関係です。実際、正の引数 に対して
sympy.var('theta', real = True) sympy.exp(sympy.log(theta))
sympy.log(sympy.exp(theta))
より
が成り立ちます。しかし2つめの等号については対数関数の多価性をケアして、変数の性質を規定しない場合には勝手に処理されることはありません。
sympy.var('z')
sympy.log(sympy.exp(z))
三角関数(双曲線関数)の諸操作
引数の和を展開する(加法公式)
sympy.var('a, b') sympy.expand(sympy.sin(a+b), trig=True)
sympy.expand(sympy.cos(a+b), trig=True)
sympy.expand(sympy.tan(a+b), trig=True)
三角関数を簡単化する
sympy.trigsimp((sympy.sin(a))**2+(sympy.cos(a))**2)
sympy.trigsimp((sympy.sinh(a))**2+(sympy.cosh(a))**2)
方程式
代数方程式の根を求める
2次方程式
の解はSymPyの組み込み関数 solve で求めることができます。
sympy.var('x, a, b, c') sympy.solve (a*x**2+b*x+c, x) # sympy.solve(eq, var) で方程式 eq=0 を変数 varについて解いた根を表示
連立方程式を解く
組み込み関数 solve は連立方程式の場合にも使えます。
sympy.var('x, y') sympy.solve ([3*x+4*y-5, 2*x+3*y-3], [x, y]) # sympy.solve([eq1, eq2, ...], [var1, var2, ...]) で連立方程式 eq1=0, eq2=0, ... の解を表示
より詳しくは
pianofisica.hatenablog.com
で解説しています。
関数の定義・変数への代入
文字に式を割り当てる
sympy.var('x, y') u = x**2-y**2 v = 2*x*y
変数に値を代入する
u.subs(x,5)
v.subs(x,5).subs(y,3)
割り当てた文字を使う・四則演算
u+v
sympy.expand(u*v)
sympy.factor(u*v)
sympy.simplify(u/v)
割り当てた文字を使う・微分する
sympy.diff(v, x) # v の x についての1階偏微分係数
sympy.diff(v, y, 2) # u の y についての2階偏微分係数
0==sympy.diff(v, x, 1)-sympy.diff(v, x)
いま扱っている関数の具体形に対して
sympy.diff(u, x, 1)==sympy.diff(v, y, 1)
sympy.diff(u, y, 1)==-sympy.diff(v, x, 1)
より
という関係式が成り立ちます。これはCauchy-Riemann(コーシー・リーマン)の関係式と呼ばれ、複素関数論で基本となる関係式です。詳細は述べませんが、この関係式は以下のような考察から導かれます。いま2次元平面上の点 を複素数 とみなし、関数値の組 を同様に複素数 とみなしたとき、これを複素数 から複素数 へ移す関数 とみなすことができます。ここで変数変換 を考えたとき、ここで定めた関数 が によらない(正則である)ための必要十分条件としてCauchy-Riemann関係式が得られます。
割り当てた文字を使う・積分する
sympy.integrate(u, x) # u を x について不定積分
sympy.integrate(v, (x, 0, 6)) # v を x について 0 から 6 までの区間で定積分
いま経路に依存する線積分
を考えてみます。はじめに、経路 として、原点 から -軸上を の区間移動し、そのあと、-軸方向に の区間に沿って移動する経路をとってみましょう。つまり
に対して上記の積分を考えてみます。
sympy.integrate(u.subs(y, 0), (x, 0, 1))-sympy.integrate(v.subs(x, 1), (y, 0, 1))
より
次に、2つめの経路 としてを原点 から の直線上を の点まで移動してみます。つまり
を考えます。
sympy.integrate(u.subs(y, x), (x, 0, 1))-sympy.integrate(v.subs(x, y), (y, 0, 1))
より
さいごに3つめの経路 としてを原点 から -軸上を の区間移動し、そのあと、-軸方向に の区間に沿って移動する経路をとってみましょう。つまり
を考えます。すると
sympy.integrate(u.subs(y, 1), (x, 0, 1))-sympy.integrate(v.subs(x, 0), (y, 0, 1))
より、やはり
となります。まとめると、いま3つの異なる経路に対して線積分を考えその値を計算してみましたが、その値は積分経路によらないことがわかりました。もちろん3つしか確認していないので、数学的な証明ではありませんが、別な経路を考えて試してみてください。やはり同じ値になるはずです。実は、これは上で述べたCauchy-Riemannの関係式が成り立っていることに起因する性質です。これは「Cauchyの積分定理」として知られています。
関数を定義する
def f(x): return -sympy.log(1-x)/x
f(2)
関数を積分する
sympy.integrate(f(x), x)
ここで は二重対数関数(dilogarithm)と呼ばれる関数です。
あまり馴染みがないかもしれませんが、このすぐ後で簡単に紹介します。
Python(SymPy)で多重対数関数(polylogarithm) は
sympy.polylog(s,x)
と入力します。
関数をTaylor展開する
sympy.series(f(x), x, 0, 7) # f(x) を x について x=0 の周りで7次未満までTaylor展開
ちなみに、ここまでの計算から
がわかります。一般に、多重対数関数は逐次的に
で定義され、その のまわりでのTaylor展開は
で与えられます。たとえば
sympy.series(sympy.polylog(3,x), x, 0, 4)
などです。ちなみに、変数を と代入して の関数とみなすと
等式の右辺・左辺を取り出す
F = x+1 G = x**2+x-2 E = sympy.Eq(x+1, F/G) # sympy.Eq(exp1, exp2) で exp1=exp2 という等式を表す sympy.solve(E, x)
E.rhs # 等式 E の右辺
E.lhs # 等式 E の左辺
式の分母・分子を取り出す
sympy.denom(F/G) # F/G の分母
sympy.numer(F/G) # F/G の分子
極限・広義積分
行列
行列を入力する
sympy.var('a11, a12, a21, a22, b11, b12, b21, b22')
A = sympy.Matrix([
[a11,a12],
[a21,a22]])
B = sympy.Matrix([
[b11,b12],
[b21,b22]])
行列の要素を参照する
A[0,0]
Python ではインデックスのカウントがゼロから始まるので、数学でいうところの行列の (i,j)-成分が、[i-1,j-1]-成分と読み替える必要があります。慣れないうちは、この点に注意が必要です。
行列の和を求める
sympy.var('c, d')
c*A+d*B
行列の積を求める
A*B
行列の積は通常の数の積と同じ記号です。
行列の冪乗を求める
A**3
行列式を求める
A.det()
微分方程式
常微分方程式を解く
1階線形微分方程式
はPython(SymPy)の組み込み関数 dsolve を使って答えを得ることができます。
sympy.var('a, x') y = sympy.Function('y')(x) eq = sympy.Eq( sympy.diff(y, x), -a*y ) sympy.dsolve(eq)
より詳しくは
pianofisica.hatenablog.com
で解説しています。
特殊関数
Fourier級数
Fourier級数展開を求める
リスト
リストを作る・要素を参照する
S = list([1, 10, 100]) # リストの定義 S[0] # 要素の参照
S[0]+S[1]*S[2]
S[3] # 定義されていないものはエラー
リストの要素の和を求める
sum(S)
リストの要素の最大値・最小値を求める
max(S) min(S)
リストの要素の個数を求める
len(S)
リストを生成する(FOR文)
N_list = [] # 空のリストを準備する def n(k): return k**2 for i in range(10): N_list.append(n(i)) # リストの各要素に関数 n(i) を加える print(N_list)
数値計算
数式処理ライブラリであるSymPyは数値的な計算をするためにはあまり向いていません。数値計算については、以下の別な記事で取り上げていますので、あわせて読んでみてください。
方程式を数値的に解く
微分方程式を数値的に解く
数値的に積分を求める
条件分岐
Heaviside関数(階段関数、ステップ関数)
Heaviside関数は実軸上
と定義されます。これをPythonで自分で定義してみます:
def H(x): if x < 0: return 0 else: return 1
より詳しい条件分岐の例については、以下の記事を参照してください:
pianofisica.hatenablog.com
グラフを描く
x = sympy.symbols('x') sympy.plot(x**2)
変数の範囲を設定するには
x = sympy.symbols('x') sympy.plot(sympy.tanh(x), (x, -5, 5))
とし、複数の対象を同時に描きたい場合には
x = sympy.symbols('x') sympy.plot(sympy.log(x), sympy.sqrt(x), x, (x, -3, 3))
とします。より詳細は次の記事を読んでみてください:
pianofisica.hatenablog.com
直前の出力結果を参照する
直前の出力結果は" _ "(アンダースコア)で参照できます。いま行列Aの逆行列を出力して、それと行列Aとの積をとってみます。
sympy.var('a11, a12, a21, a22, b11, b12, b21, b22') A = sympy.Matrix([ [a11,a12], [a21,a22]]) A**(-1)
と入力したあと、その結果を使う場合に
_*A
などとします。
TeX形式で計算結果を出力する
E2=sympy.Eq(a*x**2+b*x+c, 0) sol=sympy.solve(E2, x) sympy.init_printing() display(sol[1]) # TeXでコンパイルして結果の数式を出力
print(sympy.latex(sol[0])) # TeXのソース形式で出力
SymPyのソース形式で出力する
sympy.var('x, y') exp1 = sympy.log((1+x)*(1+y)**2) exp2 = sympy.expand(exp1) sympy.print_python( exp2 )
定義した文字を初期化(無効化)する
del(x)
定義した文字をすべて初期化(無効化)する
%reset
さて、以上見てきたように SymPy で関数を扱うには、いろいろな箇所で sympy. という文字列をおまじないのようにつけなければいけません。これが面倒なようであれば、最初のライブラリの読み込みの段階で
from sympy import *
としておくという処方があります。
ただし、このとき、大文字のアイにはすでに虚数単位が割り当てられています:
I**2
このように、思いがけず使えない文字があったりするので、SymPy を上記のようにして読み込んだ場合には注意が必要になります。または
import sympy as sp
として sympy と入力していた箇所を sp と省略することができます。
今回学んだ事項の例題として、Bernoulli数(ベルヌーイ数)を具体的に求める問題を扱っているのが
です。その(数学的にもPyhton的にも)発展編としてBernoulli多項式(ベルヌーイ多項式)をPythonの計算を交えて解説しているのが
です。また、より実践的にSymPyを数式処理に活用しているのが次の記事です:
さらに、NumPy、Matplotlibライブラリを組み合わせた使い方についての解説は
でみてもらえます。本記事の続編、具体的な応用例として、是非あわせて読んでみてください。
キーワード:Python、SymPy、JupyterNotbook