# Python 符號運算套件:SymPy
> 作者:王一哲 日期:2018/11/29
<br></br>
## 前言
SymPy 是 Python 的符號運算套件,可以幫助我們計算函數的微分、積分,而且套件裡已經內建了常用的常數和函數,例如圓周率$\pi = 3.14159265358979$、尤拉常數$E = 2.71828182845905$、正弦函數$\sin$。如果已經在電腦上安裝好 Python,接下來只要在文字介面中執行下的指令即可安裝 SymPy
```shell
pip install sympy
```
如果是 Windows 的用戶,可以使用 **Windows PowerShell(系統管理員)** 替這部電腦所有的使用者安裝,如果是 Linux 的用戶,在執行指令時可能需要在最前面加上 **sudo** 暫時取得管理者權限。如果只想要試用看看,可以到 SymPy 的官方網站按右上角的 **Online Shell** 使用線上版的 SymPy Live。
<br></br>
<img height="100%" width="100%" src="https://i.imgur.com/VWQZt0T.png" style="display: block; margin-left: auto; margin-right: auto;"/>
<div style="text-align:center">SymPy 官方網站</div>
<br></br>
<img height="100%" width="100%" src="https://i.imgur.com/JOF93JE.png" style="display: block; margin-left: auto; margin-right: auto;"/>
<div style="text-align:center">SymPy Live</div>
<br></br>
## 基本運算
如果想要引入 SymPy 函式庫,需要在程式碼的最上方加上
```python
from __future__ import division
from sympy import *
```
其中第一行是為了引入 SymPy 的除法型式,第二行才是引入 SymPy,兩行的順序不能顛倒,以下的例子會省略這兩行。
SymPy 的數字格式有 3 種:整數(Integer)、浮點數(Float)、有理數(Rational),其中有理數是以分數的型式顯示,例如
```python
print("Rational(2, 3) =", Rational(2, 3))
print("Rational(2) / 3) =", Rational(2) / 3)
print("2 / 3 =", 2 / 3)
```
輸出的結果為
```python
Rational(2, 3) = 2/3
Rational(2) / 3) = 2/3
2 / 3 = 0.6666666666666666
```
我們也可以把數字代入內建的函數中,輸出時會保留數學上的表達方式,如果要把數值計算出來,則要加上 **evalf()**,例如
$$\sin \left( \frac{\pi}{3} \right) = \frac{\sqrt{3}}{2} \approx 0.866025403784439$$
$$e^{-\infty} = \exp[-\infty] = 0$$
程式碼為
```python
print("sin(pi / 3) =", sin(pi / 3))
print("sin(pi / 3) =", sin(pi / 3).evalf())
print("exp(-oo) =", exp(-oo))
```
輸出的結果為
```python
sin(pi / 3) = sqrt(3)/2
sin(pi / 3) = 0.866025403784439
exp(-oo) = 0
```
我們也可以自訂函數,例如
$$f(x) = x^2 - 2x -8$$
$$f(x) = (x-4)(x+2) = 0 \Rightarrow x = 4 \vee -2$$
$$f(2) = 2^2 - 2 \times 2 -8 = -8$$
程式碼為
```python
x = symbols('x', communtative = True)
f = symbols('f', cls = Function)
f = x**2 - 2*x - 8
print("f(x) =", f)
print("roots of f(x):", roots(f))
print("roots of f(2):", f.evalf(subs = {x:2}))
```
我們用到以下的函式
1. **symbols**: 定義所用的符號,**communtative = True** 將符號設定為可交換,**cls = Function** 是將這些符號設定為函數的代號。
2. **roots**: 求多項式函數的根。
3. **subs = {x:2}**: 利用字典格式將函數中的 x 用 2 代入。
輸出的結果為
```python
f(x) = x**2 - 2*x - 8
roots of f(x): {4: 1, -2: 1}
roots of f(2): -8.00000000000000
```
我們也可以用 SymPy 解聯立方程式,使用的函式為 **solve**,語法為
```python
solve((方程式1, 方程式2), (變數1, 變數2))
```
以下列的二元一次聯立方程式為例
$$\begin{matrix}x + 2y = 8 \\ 2x - y = 6 \end{matrix} \Rightarrow x = 4, y = 2$$
程式碼為
```python
x, y = symbols('x y', communtative = True)
print(solve((x + 2*y - 8, 2*x - y - 6), (x, y)))
```
輸出為
```python
{x: 4, y: 2}
```
<br></br>
## 微分
如果要用 SymPy 計算微分,所用的函式為 **diff**,語法為
```python
函數名稱.diff()
diff(函數名稱, 變數)
```
兩種寫法的效果相同,但我比較傾向使用第二種寫法,能夠一看就知道是對誰微分。以多項式函數的微分為例:
```python
x = symbols('x', communtative = True)
f = symbols('f', cls = Function)
f = x**2 - 2*x - 8
print("f'(x) =", f.diff())
print("f'(x) =", diff(f, x))
```
輸出為
```python
f'(x) = 2*x - 2
```
如果以簡諧運動的位置 $x(t)$、速度 $v(t)$、加速度 $a(t)$ 為例:
$$x(t) = R \cos(\omega t + \phi)$$
$$v(t) = -R \omega \sin(\omega t + \phi)$$
$$x(t) = -R \omega^2 \cos(\omega t + \phi)$$
程式碼的寫法如下
```python
R, w, t, phi = symbols('R w t phi', commutative = True)
x, v, a = symbols('x v a', cls = Function)
x = R * cos(w * t + phi)
print("x(t) =", x)
v = diff(x, t)
a = diff(v, t)
print("v(t) =", v)
print("a(t) =", a)
```
輸出為
```python
x(t) = R*cos(phi + t*w)
v(t) = -R*w*sin(phi + t*w)
a(t) = -R*w**2*cos(phi + t*w)
```
<br></br>
## 積分
如果要用 SymPy 計算積分,所用的函式為 **integrate**,語法為
```python
integrate(函數名稱, 變數)
integrate(函數名稱, (變數, 積分下限, 積分上限))
```
以下以一些常見的函數為例:
$$ \int \sin(x) dx = -\cos(x) + c$$
$$ \int_0^{\pi} \sin(x) dx = \left [ -\cos(x) \right ]_0^{\pi} = -(-1) - (-1) = 2$$
$$ \int e^x dx = e^x + c$$
$$ \int_0^1 e^x dx = e^1 - e^0 = e - 1 \approx 1.71828182845905$$
$$ \int \frac{dx}{x} = \ln x+ c$$
$$ \int_2^3 \frac{dx}{x} = \ln 3 - \ln 2 = \ln \left( \frac{3}{2} \right) \approx 0.405465108108164$$
程式碼為
```python
x = symbols('x', communtative = True)
print(integrate(sin(x), x))
print(integrate(sin(x), (x, 0, pi)))
print(integrate(exp(x), x))
print(integrate(exp(x), (x, 0, 1)))
print(integrate(exp(x), (x, 0, 1)).evalf())
print(integrate(1 / x, x))
print(integrate(1 / x, (x, 2, 3)))
print(integrate(1 / x, (x, 2, 3)).simplify())
print(integrate(1 / x, (x, 2, 3)).evalf())
```
輸出為
```python
-cos(x)
2
exp(x)
-1 + E
1.71828182845905
log(x)
-log(2) + log(3)
-log(2) + log(3)
0.405465108108164
```
在倒數第2行的程式碼中,我們使用了 **simplify()**,用來化簡數學式子,但是在 Python Shell 中看不出效果,如果在 SymPy Live 中會將 -log(2) + log(3) 合併為 log(3 / 2)。還有一點,在SymPy 當中 $\log = \log_e = \ln$。
接下來我們試著處理更複雜的函數,例如前一篇文章〈[利用殼層定理推導電量均勻分布的球殼產生的電場](https://hackmd.io/s/SJHCp1OR7)〉當中的積分式
$$E_{out} = \int dE_r = \frac{kQ}{4r^2 R} \int_{r-R}^{r+R} \frac{r^2 + s^2 - R^2}{s^2} ds = \frac{kQ}{r^2}$$
$$E_{in} = \int dE_r = \frac{kQ}{4r^2 R} \int_{R-r}^{R+r} \frac{r^2 + s^2 - R^2}{s^2} ds = 0$$
使用的程式碼如下
```python
k, Q, r, R, s = symbols('k Q r R s', commutative = True)
dE = k * Q / (4 * r**2 * R) * ((r**2 + s**2 - R**2) / s**2)
E_out = integrate(dE, (s, r - R, r + R), manual = True)
E_in = integrate(dE, (s, R - r, R + r), manual = True).simplify()
print("E_out =", E_out)
print("E_in =", E_in)
```
我們在積分指令最後加上 **manual = True**,可以將計算結果盡量顯示為人類手算積分的表達方式。另外在 E_in 這行最後加上 **simplify()** 將計算結果化簡,否則答案不會是0,會變成
```python
E_in = Q*k/(2*r**2) - Q*k*(R - r - (-R + r)*(R + r)/(R - r))/(4*R*r**2)
```
<br></br>
## 結語
這是最近幾天為了做符號運算找到的使用方法,只運用到 SymPy 一點點的功能而已,有興趣的同學可以再上網搜尋更多的教學及範例。
<br></br>
## 參考資料
1. **SymPy 官方網站**: https://www.sympy.org/en/index.html
2. **SymPy Live**: https://live.sympy.org/
3. **SymPy Core**: https://docs.sympy.org/latest/modules/core.html
4. **Calculus**: https://docs.sympy.org/latest/tutorial/calculus.html
5. **Symbolic Integrals**: https://docs.sympy.org/latest/modules/integrals/integrals.html
---
###### tags:`Python`