愛想モルフィズム

I saw what I am

SymPy 備忘録 #1 sympy.poly.polytools 周辺

研究でSymPyを使っているのですが,だいぶ癖が強いと感じたので,個人的躓きポイントをまとめます.

github

github.com

Official documentation

www.sympy.org

Poly class

多項式を扱う基本的なクラスはPolyです.これは sympy/poly/polytools.py 内で定義されています.多項式の定義は

>>> from sympy import *
>>> x, y = symbols('x, y') # define variables
>>> f = Poly(x + 2 * y)
Poly(x + 2*y, x, y, domain=ZZ)
>>> f.degree()
1

というふうに行います.4行目の中盤の"x,y"は式の変数を表し,内部処理的にはこれはgensというlistになっています.poly()メソッドを使っても同じようにできます.

>>> f = poly(x + 2*y)
>>> f
Poly(x + 2*y, x, y, domain='ZZ')

単純に f = x + 2 * y とすると,Add オブジェクトになります.Add オブジェクトには degree() メソッドがないのでエラーになります.

>>> g = x + 2 * y
>>> g
x + 2 * y
>>> type(g)
<class 'sympy.core.add.Add'>
>>> g.degree()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Add' object has no attribute 'degree'

さて,Poly.__new__() は引数によってオブジェクトの生成法が異なるようですが,大体は式( Expr のサブクラスのオブジェクト)を元に生成することが多いと思いますが,Poly 自体は Basic のサブクラスであり,プレーンな式の情報を得るためには as_expr() メソッドを使います.

>>> f.as_expr()
x + 2*y

subs method

Poly クラスには代入を行う subs() というメソッドがあります.より正確には,それは Basic クラス内で実装されています.subs() に dict で変数と値を入れるとそれぞれの変数に値を代入した値が返されます.

>>> f.subs({x: 1, y: 1})
3

クセつよポイントはここからです. x 2x を代入したいと思ったとき,

>>> f.subs({x: 2 * x})
Poly((2*x) + 2*y, 2*x, y, domain='ZZ')

こうしてしまうと,変数が  2x y の Poly オブジェクトが帰ってきます.正確ではない解決方法としては,

>>> poly(f.as_expr().subs({x: 2 * x}))
Poly(2*x + 2*y, x, y, domain='ZZ')

とすれば良いでしょう.

LT method

Polyは先頭項 (Leading Term) を返す LT() というメソッドがあります.

>>> f = poly(x**2 + 2 * x + 1)
>>> f
Poly(x**2 + 2*x + 1, x, domain='ZZ')
>>> LT(f)
x**2

クセつよポイントとしては,Poly オブジェクトではなく Pow オブジェクトが帰ってくることです.

>>> m = LT(f)
>>> m
x**2
>>> type(m)
<class 'sympy.core.power.Pow'>

つまり,この先頭項をいじったり,次数を求めたりするときに注意が必要です.

>>> m.degree()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Pow' object has no attribute 'degree'

単純に式変形だけで事が済んでしまう計算の場合は Expr のまま行い,degree() や LT() といったPoly のメソッドを組み合わせたい場合は意味不明なネストが必要になります.必要でないのであれば教えて下さいお願いします.

domain option

Poly オブジェクトは生成時に何も指定しない場合は整係数多項式になります.これをいじる場合は,生成時に domain オプションで指定します.

>>> f = poly(x + 2 * y, domain='FF(5)')
>>> f
Poly(x + 2*y, x, y, modulus=5)
>>> a = symbols('a')
>>> f = poly(x + 2 * a * y, domain='ZZ[a]')
>>> f
Poly(x + 2*a*y, x, y, domain='ZZ[a]')

上の例では  \mathbb{F}_5 上の多項式を定義しています.他に指定できる domain は sympy/polys/polyoptions.py に記述されています.domainオプションに文字列を渡して,それを正規表現で合致したら対応するオブジェクトを返すという実装になっており,それ以外は突っぱねられます.

>>> f = poly(x + 2 * a * y, domain='FF(5)[a]/(a**2 - 2)')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>  
sympy.polys.polyerrors.OptionError: expected a valid domain specification, got FF(5)[a]/(a**2 - 2)

上の例では,有限体上の多項式環の剰余環上での多項式を定義しようとしてエラーになっています.ちなみに今の自分の研究は,pythonで(SymPyで)これを実装することがテーマの1つになっています.

もし地道にやろうと思えば,この domain オプション周辺を書き直す必要があり,めちゃくちゃめんどくさそうです.ただ,SymPy 的にはそのやり方が正しいはずです.

Conclusion

  • Poly は Basic のサブクラス
  • 単純な式は Expr およびそのサブクラス
  • ごっちゃになりがちなので気をつける

SymPy使ってるとほんまにイライラする.査読よろしくお願いします.

f:id:dafuyafu:20200721050813p:plain
記事の内容とは全く関係のないDiana香澄