使用 sympy 写公式
sympy.abc
以前定义变量的常用方法
x, y = Symbol('x y')
实际上,在 sympy.abc
中已经定义好了
from sympy.abc import x, y
化简、分解、合成、展开
https://docs.sympy.org/latest/tutorials/intro-tutorial/simplification.html
以下函数,如果没有使用字符串输入公式,则说明此函数不可以使用字符串作参数。
-
simplify()
或S()
S()
是simplify()
内置的一种简单写法 -
expand()
expand('(x + 2) * (x - 3)')
\[\begin{align*} x^{2} - x - 6 \end{align*} \] -
factor()
factor('x**3 - x**2 + x - 1')
\[\begin{align*} \left(x - 1\right) \left(x^{2} + 1\right) \end{align*} \] -
collect()
collect('x*y + x - 3 + 2*x**2 - z*x**2 + x**3', 'x') collect('x*y + x - 3 + 2*x**2 - z*x**2 + x**3', 'x').coeff('x', 2)
\[\begin{align*} &x^{3} + x^{2} \cdot \left(2 - z\right) + x \left(y + 1\right) - 3\\ &\qquad\qquad\qquad2-z \end{align*} \] -
cancel()
cancel('(x*y**2 - 2*x*y*z + x*z**2 + y**2 - 2*y*z + z**2)/(x**2 - 1)')
\[\begin{align*} \frac{y^{2} - 2 y z + z^{2}}{x - 1} \end{align*} \] -
apart()
apart('(4*x**3 + 21*x**2 + 10*x + 12)/(x**4 + 5*x**3 + 5*x**2 + 4*x)')
\[\begin{align*} \frac{2 x - 1}{x^{2} + x + 1} - \frac{1}{x + 4} + \frac{3}{x} \end{align*} \] -
fraction()
fraction('2*x**(-y)') # (2, x**y)
及其变种,这些变种可能改变变量的定义域,所以被单独作为一个函数,有些函数需要加上参数 force=True
才会工作。
-
radsimp()
移除分母中的根号
radsimp(S('1/(y*r2 + x*r2 + a*r5 + b*r5)').subs({'r2':sqrt(2), 'r5':sqrt(5)}))
\[\begin{align*} \frac{\sqrt{5} a + \sqrt{5} b - \sqrt{2} x - \sqrt{2} y}{5 a^{2} + 10 a b + 5 b^{2} - 2 x^{2} - 4 x y - 2 y^{2}} \end{align*} \] -
ratsimp()
合成带分母的项
ratsimp('1/x + 1/y + y')
\[\begin{align*} y + \frac{x + y}{x y} \end{align*} \] -
collect_sqrt()
对根号起作用的
collect()
collect_sqrt(sqrt(2) * x + sqrt(2) * y)
-
collect_const()
对常数起作用的
collect()
collect_const(2*x - 2*y - 2*z, 2) collect_const(2*x - 2*y - 2*z, -2)
- `trigsimp()`
~~~python
S('sin(x)*tan(x)/sec(x)')
trigsimp('sin(x)*tan(x)/sec(x)')
trigsimp('sin(x)*cos(y) + sin(y)*cos(x)')
-
expand_trig()
-
powsimp()
-
expand_power_exp()
-
powdenest()
-
expand_log()
expand_log(ln(x*y), force=True)
-
logcombine()
logcombine(n*log(z), force=True)
-
expand_func()
-
hyperexpand()
-
combsimp()
-
gammasimp()
使用字符串写公式
不使用字符串写公式的情况
from sympy import gamma, Symbol
from sympy.abc import a, b
a_b, a, b = symbols('a_b a b')
2 * gamma(a_b ** 2) + a * b
如果使用字符串书写公式,则可以像在 markdown 中一样,这样写不用导入(import)函数和变量,不过也要注意
- 环境中已经存在的变量不会被使用;
- 部分符号的含义发生了变化,比如
^
代表异或或者指数; - 还是要显式地写出乘法
*
; - 字母后面用或不用
_
连接的字母、数字,会被视为一体,比如'r_23caax'
、'r23caax'
或者'r2_3ca_ax'
都会被看作(不同的)符号,这些符号显示出来是一样的,但实际上是不同的符号。转化为 LaTeX 时_
会作为空格处理。
from sympy import S
S('2*gamma(a_b^2) + a*b')
S('2*gamma(a_b^2) + a*b').subs('a_b', 2)
Sympy 中绝大部分函数都识别字符串,并且将其自发地转化为相应的函数或者变量,比如 'gamma'
就被转化为了函数 \(\Gamma(x) := \int^{\infty}_{0} t^{x-1} e^{-t} \mathrm{d}t\)。
小数和分数
写公式的时候经常遇到,小数精度问题。
a = (1 + 0.4 * z) ** 4 * (1 - 0.2 * z) ** 2
cancel(a)
式中 \(z^5\) 的系数实际上是 0,但由于精度原因变成了一个极小的数,\(z^3\) 的系数也被加上了一个极小的数。
解决的方法之一就是将公式中的所有小数转化为有理数的形式,可以使用类 Rational
来达成这一目的。
a = (1 + Rational(4, 10) * z) ** 4 * (1 - Rational(2, 10) * z) ** 2
cancel(a)
但这种方法非常麻烦,因为每个小数都要计算分子分母作为参数,当然也可以使用字符串做参数 Rational('0.4')
,但麻烦程度相差不大。
实际上可以使用 nsimplify()
或者 simplify(rational=True)
来达成。比如
from sympy.abc import z
a = nsimplify((1 + 0.4 * z) ** 4 * (1 - 0.2 * z) ** 2)
# a = S((1 + 0.4 * z) ** 4 * (1 - 0.2 * z) ** 2, rational=True)
cancel(a)
结合前面使用字符串来书写公式的思想
a = nsimplify('(1 + 0.4 * z) ** 4 * (1 - 0.2 * z) ** 2')
cancel(a)
这个时候如果想要小数表示,就可以使用 evalf()
函数,可以加上参数 n
保留有效数字,比如 evalf(n=6)
表示保留 6 位有效数字。
a = nsimplify('(1 + 0.4 * z) ** 4 * (1 - 0.2 * z) ** 2')
cancel(a).evalf()
将 Sympy 中的公式用 LaTeX 表示
使用 latex()
即可
a = nsimplify('(1 + 0.4 * z) ** 4 * (1 - 0.2 * z) ** 2')
print(latex(a))
# \left(1 - \frac{z}{5}\right)^{2} \left(\frac{2 z}{5} + 1\right)^{4}
print(latex(S('r_23caax * r23_caax * r2_3ca_ax')))
# r_{23 caax} r_{2 3ca ax} r_{23caax}