SymPyのシンボル生成について色々触れてみる

以前の記事でもSymPyのシンボル生成についてある程度触れていましたが、その記事で漏れている点などもぼちぼちあったため再度記事にまとめておきます。

インストール手順などは今回はスキップするため、必要な方は以下の記事などをご確認ください。

https://qiita.com/simonritchie/items/44242479f0df86fffff5

※記事の執筆者は理系出身ではないため数学の知識などで粗い点はご容赦ください。

# 使うもの

- Python 3.9.0
- Jupyter (VS Code上のものを利用)
- sympy==1.8

# Symbolクラスによるシンボル単体の作成

一番シンプルなシンボル単体の生成法としてはSymbolクラスを使う形となります。第一引数には基本的に割り当てる変数名と同じ名前の文字列を指定します。

```py
from sympy import Symbol

x = Symbol('x')
y = Symbol('y')
```

# symbols関数による複数シンボルの一括作成

symbols関数で複数のシンボルを同時に作成することができます。シンボル名以外の設定が一緒な場合などに便利です。第一引数にはコンマ区切りもしくはスペース区切りで設定するシンボル名を指定します。

```py
from sympy import symbols

x, y, z = symbols('x,y,z')
```

上記の記述は以下のようにスペース区切りで指定しても同様の挙動をします。

```py
x, y, z = symbols('x y z')
```

# シンボルに下付き文字の数字と上付き文字を設定する

`シンボル名下付き文字の数字__上付き文字`といったように第一引数に指定すると、シンボルに下付き文字と上付き文字を設定することができます。たとえばxというシンボルに3という下付き文字と5という上付き文字を設定したければ`x3__5`という記述になります。

```py
x = Symbol('x3__5')
x
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/f706efbd-42af-d935-e50f-0452b2354b22.png)

なお、この書き方の場合は下付き文字は数字のみ受け付けられます。アルファベットなどを指定するとシンボル名の一部として扱われてしまいます。アンダースコア2つの後の上付き文字の指定は数字以外でも指定することができます。

```py
x = Symbol('xa__b')
x
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/d95dad30-9423-69d0-0c83-775748dacb58.png)

# アンダースコア1つで下付き文字、2つで上付き文字を設定する

下付き文字は直前にアンダースコア1つを付けて指定する書き方もできます。この書き方の場合前節であったような数字縛りなどはなくアルファベットの下付き文字などを設定できます。

```py
x = Symbol('x_a__b')
x
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/b2d98c5b-fb7c-105a-4a80-04c13229e50f.png)

# 上付き文字などをLaTeXで設定する

下付き文字はLaTeXでもアンダースコア1つなので変わりませんが、上付き文字はLaTeXと同じ記述でキャレットの記号（^）で指定することもできます。

```py
x = Symbol('x_1^2')
x
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/324aeb66-917b-7861-ba7d-dc3a4676363b.png)

# その他のシンボル設定をLaTeXで行う

他のシンボル設定もLaTeXの記法で行うことができます。\\の記号を使うことが多くなると思いますが、その場合にはraw文字としてのrの指定を文字列の前に設定しておくのが無難そうです。

```py
x = Symbol(r'\dot{\textrm{x}}_a^b')
x
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/9364def8-731e-af94-2438-4305560bcd4a.png)

# ギリシャ文字の文字列などは1つの文字に変換される

シンボル名に指定したギリシャ文字は単一のギリシャ文字のシンボルに変換されます。例えば`alpha`と指定すると`α`になります。

```py
alpha = Symbol('alpha')
alpha
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/cbe11eab-2a33-2c46-0a08-a21134e0818f.png)

日本語環境のIMEなど使われている場合はαなどは普通に変換できるでしょうからダイレクトにそれらの文字を指定しても変わりません。

```py
alpha = Symbol('α')
alpha
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/645ca7b9-bd5c-898c-1867-f5adf90356e7.png)

# 仮定の設定

シンボルにはassumption（仮定）の各種属性の設定が存在します。例えば「整数である」「素数である」「交換可能である」といったような各属性が該当します。

それぞれはSymbolクラスのコンストラクタ（もしくはsymbols関数など）でキーワード引数に真偽値で指定します。例えば「整数である」という仮定であれば`integer=True`と指定します。

```py
x = Symbol('x', integer=True)
```

各assumptionの設定値は`is_<assumption名>`というプロパティで参照できます。例えば整数かどうかであれば`is_integer`というプロパティ名でアクセスすることができます。

```py
>>> x.is_integer
True
```

なお、これらのプロパティの値は満たす場合にはTrue、満たさない場合にはFalse、どちらか分からない場合（設定が明示されていない場合など）にはNoneとなります。TrueとFalseだけでなくNoneのケースも発生するので注意してください。たとえば以下のように`integer`引数を指定せずにシンボルを作成してみると`is_integer`の値がNoneになっていることが確認できます。

```py
>>> x = Symbol('x')
>>> print(x.is_integer)
None
```

# 仮定の属性値の伝搬

各種仮定の属性を持ったシンボルを使って数式表現などを作った場合それらの属性は引き継がれたり値が変わったりするケースがあります。

例として「偶数である」という仮定を`even=True`という引数で設定してxのシンボルを作ってみます。

```py
>>> x = Symbol('x', even=True)
>>> x.is_even
True
```

このシンボルに2を加えて数式表現を作ってみます。

```py
expression = x + 2
expression
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/08138eea-9c25-ff14-4328-7a2fc44df1d5.png)

元が「偶数である」というシンボルに対して2を加える数式の場合必ず偶数になることは分かります。そのためその数式のis_evenプロパティの値はTrueのまま伝搬していることが確認できます。

```py
>>> expression.is_even
True
```

逆に1を指定してみると結果の数式表現のis_evenプロパティはFalseになっていることを確認できます。

```py
>>> expression = x + 1
>>> expression.is_even
False
```

また、直接は設定していない仮定の属性でも同時に設定されることがあります。たとえばシンボルが「偶数ある」という仮定が設定されていれば「奇数ではない」という条件も成り立ちます。以下のようにis_oddを確認するとFalseとなっていることが分かります。

```py
>>> x.is_odd
False
```

# 主な仮定の属性値の一覧

前節まででis_evenやis_integerなどに触れてきましたが、他にも色々な属性があり一覧にすると主に以下のような仮定の属性がSymPyに存在します。それぞれevenやintegerのキーワード引数でコンストラクタで指定したりis_の形式の属性でアクセスすることができます。

| 属性名 | 属性の説明 |
| :--- | :--- |
| is_integer | 整数かどうか。 |
| is_odd | 奇数かどうか。 |
| is_even | 偶数かどうか。 |
| is_zero | 0かどうか。 |
| is_nonzero | 0以外かどうか。 |
| is_positive | 正の値かどうか。 |
| is_nonpositive | 正の値以外かどうか。 |
| is_negative | 負の値かどうか。 |
| is_nonnegative | 負の値以外かどうか。 |
| is_prime | 素数かどうか。 |
| is_complex | 複素数かどうか。  |
| is_real | 実数かどうか。 |
| is_imaginary | 虚数かどうか。 |
| is_rational | 有理数かどうか。 |
| is_irrational | 無理数かどうか。 |
| is_algebraic | 代数的数かどうか。 |
| is_transendental | 超越数かどうか。 |
| is_commutative | 可換（交換可能）かどうか。 |
| is_hermitian | エルミート作用素かどうか。 |
| is_antihermitian | エルミート作用素以外かどうか。 |

# symbols関数での返却値の型の変動

symbols関数ではコンマやスペースなどを含まずに単一のシンボル名のみを指定すると返却値はSymbolクラスを使ったときと同じように単一のシンボルが返ってきます。

```py
from sympy import symbols

x = symbols('x')
x
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/fc4de5cf-7cdf-9a00-cb40-e2676e5f71f5.png)

一方で前節までで触れたようにコンマやスペースを含めると返却値はタプルとなります（返却値側もx, yといったようにコンマ区切りなどで設定するか、一旦タプルのままで受け取る必要があります）。

```py
from sympy import symbols

x, y = symbols('x,y')
```

このように引数に応じて返却値の型が変わるので指定を動的に変える場合などは注意が必要です。

# symbols関数の返却値をタプルに固定する

単一のシンボル名をsymbols関数の引数に指定した場合でも返却値をタプルに固定したい場合にはseq引数にTrueを指定します。引数のシンボル名の指定を問わず単一の型となるので扱いがシンプルになるケースがあるかもしれません。

```py
tuple_val = symbols('x', seq=True)
print(tuple_val)
print(type(tuple_val))
```

```
(x,)
<class 'tuple'>
```

# symbols関数にコンマやスペース区切りの文字列でなはくタプルを指定する

symbols関数にはコンマ区切りなどの文字列ではなくそのままシンボル名を格納しタプルを指定することもできます。挙動はコンマ区切りの文字列などで指定した際の同じです。

```py
x, y, z = symbols(('x', 'y', 'z'))
```

# symbols関数にリストや集合を指定した場合にはそのままリストや集合になる

symbols関数にリストや集合（set）で値を指定した場合にはタプルの時のように各シンボルに展開されることはなくそのままリストや集合の値で返ってきます。ただしリストなどに含まれる値はSymPyのシンボルになっています。

```py
xyz = symbols(['x', 'y', 'z'])
print(xyz)
print(type(xyz))
print(type(xyz[0]))
```

```
[x, y, z]
<class 'list'>
<class 'sympy.core.symbol.Symbol'>
```

# スライス等のような記法で複数のシンボルの生成を行う

symbols関数ではPythonのリストやNumPyでのスライスのような記法で複数の連続したシンボルの生成処理を行うことができます。それぞれ書き方のパターンがあるため以降の節で触れていきます。

## 0から特定の値までの範囲の下付き文字を含んだシンボルを生成する

`<シンボル名>:<終点の位置の番号>`という形式でsymbols関数の引数に文字列で指定すると0～終点の位置の番号 - 1までの範囲の下付き文字のついた複数のシンボルを得ることができます。例えば`'x:3'`と指定すると$x_0$, $x_1$, $x_2$の3つのシンボルを得ることができます。

```py
from IPython.display import display
x0, x1, x2 = symbols('x:3')
display(x0, x1, x2)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/c98c9a10-6357-36a4-a1bd-222efeb4cade.png)


## 特定の始点位置から特定の最終位置までの下付き文字を含んだシンボルを生成する

`<シンボル名><始点位置の番号>:<終点の位置の番号>`という形でsymbols関数の引数に文字列を指定すると始点の位置の番号～終点の位置の番号 - 1の範囲の下付き文字のついた複数のシンボルを得ることができます。

例えば`'x3:6'`という指定をすれば$x_3$、$x_4$、$x_5$という3つのシンボルを得ることができます。

```py
from sympy import symbols
from IPython.display import display

x3, x4, x5 = symbols('x3:6')
display(x3, x4, x5)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/a9a230d1-2d1e-4ace-979f-c504d111265f.png)

## コンマ区切りなどでの複数のシンボルの指定と範囲指定を組み合わせる

symbols関数はコンマ区切りなどで複数のシンボルを生成できるとは以前の節で触れましたが、この使い方は範囲を指定して下付き文字を設定した場合も有効です。

例えば`'x1:3,y3:6'`と指定することで$x_1$, $x_2$, $y_3$, $y_4$, $y_5$といったようにxとyそれぞれのシンボルを生成することができます。

```py
x1, x2, y3, y4, y5 = symbols('x1:3,y3:6')
display(x1, x2, y3, y4, y5)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/89fa4ad5-d0a3-87a1-de95-1efa6315f044.png)

## 括弧を付けることで行列やテンソルの表現ができる

`'<シンボル名><通常の範囲指定の番号>(<2次元目（列）の範囲指定の番号>)'`といったように`()`の括弧を付けることで行列の表現ができます。

例えば`'x3:5'`という指定であれば$x_3$, $x_4$という2つのベクトル的な値になりますが、ここに`(1:4)`という指定を追加して`'x3:5(1:4)'`と書けばすれば$x_{31}$, $x_{32}$, $x_{33}$, $x_{41}$, $x_{42}$, $x_{43}$といったように2次元目のインデックスが付与される形で各シンボルが生成されます。

```py
tuple_value = symbols('x3:5(1:4)')
tuple_value
```

```
(x31, x32, x33, x41, x42, x43)
```

```py
display(tuple_value[0], tuple_value[-1])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/31be3336-3beb-3383-73a8-37679e9e53d2.png)


この括弧の記述はどんどん繋げて3階のテンソル、4階のテンソル・・・としていくことも可能です（$x_{312}$, $x_{433}$といったように）。

```py
tuple_value = symbols('x3:5(1:4)(2:4)')
tuple_value
```

```
(x312, x313, x322, x323, x332, x333, x412, x413, x422, x423, x432, x433)
```

## アルファベットの範囲を指定してシンボルを生成する

`'<始点シンボル名>:<終点シンボル名>'`という記述でアルファベットの範囲でシンボルを生成することができます。例えば`'x:z'`とすればx, y, zの3つのシンボルが生成されます。数値の範囲の終点と異なりこちらの場合は終点の指定も含まれます。

```py
x, y, z = symbols('x:z')
display(x, y, z)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/b7a939d3-ea0e-bcd7-a8d7-6c2838bdec16.png)

## タプルで範囲を指定すると2次元のタプルになって返ってくる

引数を文字列ではなくタプルで指定し、タプルの値を範囲指定をした文字列で指定すると2次元のタプルになって返ってきます。

```py
tuples = symbols(('x1:3', 'y2:4'))
tuples
```

```
((x1, x2), (y2, y3))
```

```py
x1 = tuples[0][0]
y2 = tuples[1][0]
display(x1, y2)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/c56ca828-f482-75c4-69ed-7bab5ff1ad79.png)


# 参考文献・参考サイトまとめ

https://www.amazon.com/dp/B09HJ1WZ7K/

https://www.amazon.com/dp/1593276400/

https://qiita.com/simonritchie/items/44242479f0df86fffff5