SymPy-1-13-中文文档-三-
SymPy 1.13 中文文档(三)
单变量代数系统的一个或一组不等式简化
原文:
docs.sympy.org/latest/guides/solving/reduce-inequalities-algebraically.html
使用 SymPy 在单变量代数中简化一个或一组不等式。例如,简化 (x² < \pi),(x > 0) 将得到 (0 < x < \sqrt{\pi})。
注意
SymPy 目前仅能简化不等式中的一个符号(变量)。
SymPy 可以简化包含多个符号的系统,如果每个不等式只有一个符号。
考虑的替代方案
-
若要简化不等式中的多个符号,请尝试使用 SciPy 的
linprog()
-
要简化布尔表达式,请使用
as_set
例子
简化单变量代数不等式系统
reduce_inequalities()
接受要作为系统简化的不等式列表或元组:
>>> from sympy import symbols, reduce_inequalities, pi
>>> x = symbols('x')
>>> reduce_inequalities([x >= 0, x**2 <= pi], x)
(0 <= x) & (x <= sqrt(pi))
注意
虽然solve()
目前可以通过在内部调用reduce_inequalities()
来完成相同的功能,但该功能可能会在solve()
中被弃用或删除。因此,我们建议使用reduce_inequalities()
。
reduce_inequalities()
是顶层不等式简化函数,将在需要时内部调用任何其他低级不等式简化函数。
简化单变量代数不等式系统
如果只有一个不等式,可以选择排除列表结构,并将reduce_inequalities()
作为表达式传递给它:
>>> from sympy import symbols, reduce_inequalities, pi
>>> x = symbols('x')
>>> reduce_inequalities(x**2 <= pi, x)
(x <= sqrt(pi)) & (-sqrt(pi) <= x)
指南
在函数调用中包含要简化的变量
我们建议您将要简化的变量作为reduce_inequalities()
的第二个参数,以确保它对所需变量进行简化。
代数地减少一组不等式
您可以创建您的不等式,然后将系统简化为列表:
>>> from sympy import symbols, reduce_inequalities, pi
>>> x = symbols('x')
>>> reduce_inequalities([3*x >= 1, x**2 <= pi], x)
(1/3 <= x) & (x <= sqrt(pi))
使用结果
使用结果的常见方式是提取符号(变量)的边界。例如,对于 (0 < x < \sqrt{\pi}) 的解,您可能希望提取 (0) 和 (\sqrt{\pi})。
提取分解关系列表
您可以将通过 ^
(Or
) 或 &
(And
) 连接的一组关系分解为单个关系使用关系原子。使用canonical
将为每个关系放置顺序,使符号在左侧,因此您可以获取右侧rhs
以提取常数:
>>> from sympy import symbols, reduce_inequalities, pi
>>> from sympy.core.relational import Relational
>>> x = symbols('x')
>>> eq = reduce_inequalities([3*x >= 1, x**2 <= pi], x); eq
(1/3 <= x) & (x <= sqrt(pi))
>>> relations = [(i.lhs, i.rel_op, i.rhs) for i in [i.canonical for i in eq.atoms(Relational)]]
>>> relations_sorted = sorted(relations, key=lambda x: float(x[2])) # Sorting relations just to ensure consistent list order for docstring testing
>>> relations_sorted
[(x, '>=', 1/3), (x, '<=', sqrt(pi))]
提取关系元组
简化关系的args
(参数)是单独的关系,因此您可以从左侧或右侧的args
中提取常数:
>>> from sympy import symbols, reduce_inequalities, pi
>>> x = symbols('x')
>>> eq = reduce_inequalities([3*x >= 1, x**2 <= pi], x); eq
(1/3 <= x) & (x <= sqrt(pi))
>>> eq.args
(1/3 <= x, x <= sqrt(pi))
>>> constants = []
>>> for arg in eq.args:
... if arg.lhs == x:
... constants.append(arg.rhs)
... else:
... constants.append(arg.lhs)
>>> constants
[1/3, sqrt(pi)]
使用 SymPy 减少不等式的限制
SymPy 只能对感兴趣的每个不等式中的一个符号进行简化。
SymPy 目前只能针对给定不等式中感兴趣的一个符号(变量)进行简化。
>>> from sympy import reduce_inequalities, symbols
>>> x, y = symbols("x y")
>>> reduce_inequalities([x + y > 1, y > 0], [x, y])
Traceback (most recent call last):
...
NotImplementedError: inequality has more than one symbol of interest.
使用 SciPy 的linprog()
可以减少这个不等式系统。
SymPy 可以在系统中对超过一个符号进行简化,如果每个不等式只有一个感兴趣的符号。例如,以下不等式系统包含两个变量,(x) 和 (y)。SymPy 可以对 (x) 进行简化,并给出 (y) 的约束条件。
>>> from sympy import reduce_inequalities, symbols
>>> x, y = symbols("x y")
>>> reduce_inequalities([x + y > 1, y > 0], x)
(0 < y) & (y < oo) & (x > 1 - y)
(oo
是Infinity
.)
如果每个不等式仅包含一个要简化的符号,SymPy 可以为多个符号减少不等式集合:
>>> from sympy import reduce_inequalities, symbols
>>> x, y = symbols("x y")
>>> x_y_reduced = reduce_inequalities([x > 1, y > 0], [x, y]); x_y_reduced
(0 < y) & (1 < x) & (x < oo) & (y < oo)
请注意,这提供的数学洞察力仅限于分别减少不等式:
>>> from sympy import And
>>> x_reduced = reduce_inequalities(x > 1, x); x_reduced
(1 < x) & (x < oo)
>>> y_reduced = reduce_inequalities(y > 0, y); y_reduced
(0 < y) & (y < oo)
>>> And(x_reduced, y_reduced) == x_y_reduced
True
因此,解决此类不等式作为集合的好处可能只是方便性。
SymPy 能够解决的不等式类型限制
reduce_inequalities()
可以解决涉及要简化符号的幂或涉及另一个符号的不等式系统:
>>> from sympy import reduce_inequalities
>>> from sympy.abc import x, y
>>> reduce_inequalities([x ** 2 < 4, x > 0], x)
(0 < x) & (x < 2)
>>> reduce_inequalities([x < y, x > 0], x)
(0 < x) & (x < oo) & (x < y)
>>> reduce_inequalities([x ** 2 - y < 4, x > 0], x)
Traceback (most recent call last):
...
NotImplementedError: The inequality, -_y + x**2 - 4 < 0, cannot be solved using
solve_univariate_inequality.
并非所有周期函数的结果都会被返回
对于三角不等式返回的结果受其周期间隔的限制。reduce_inequalities()
试图返回足够的解,以便所有(无限多个)解都可以通过返回的解加上方程的整数倍的 periodicity()
(这里是 (2\pi))生成。
>>> from sympy import reduce_inequalities, cos
>>> from sympy.abc import x, y
>>> from sympy.calculus.util import periodicity
>>> reduce_inequalities([2*cos(x) < 1, x > 0], x)
(0 < x) & (x < oo) & (pi/3 < x) & (x < 5*pi/3)
>>> periodicity(2*cos(x), x)
2*pi
并非所有不等式系统都可以简化
无法满足的不等式系统
如果不等式系统具有不兼容的条件,例如 (x < 0) 和 (x > \pi),SymPy 将返回 False
:
>>> from sympy import symbols, reduce_inequalities, pi
>>> x = symbols('x')
>>> reduce_inequalities([x < 0, x > pi], x)
False
无法在解析上简化的不等式系统
SymPy 可能会反映您的不等式系统在代数(符号)上无法表达的解不存在,如返回诸如 NotImplementedError
的错误:
>>> from sympy import symbols, reduce_inequalities, cos
>>> x = symbols('x')
>>> reduce_inequalities([cos(x) - x > 0, x > 0], x)
Traceback (most recent call last):
...
NotImplementedError: The inequality, -x + cos(x) > 0, cannot be solved using solve_univariate_inequality.
因此,您可能需要使用 SciPy 的 linprog()
在数值上简化您的不等式。
可以在解析上简化的不等式,但 SymPy 无法简化的系统
请参阅上文的 使用 SymPy 进行不等式简化的限制。
报告 Bug
如果您在 diophantine()
中发现 Bug,请在 SymPy 邮件列表 上发布问题。在问题解决之前,您可以使用列在 考虑的替代方法 中的其他方法。
代数解二次不定方程
原文:
docs.sympy.org/latest/guides/solving/solve-diophantine-equation.html
使用 SymPy 来代数地解不定方程(找到多项式方程的整数解),如果可能,返回一个参数化的一般解。例如,解毕达哥拉斯定理 (a² + b² = c²) 得到 ((a=2pq, b=p²-q², c=p²+q²))。这里,(p) 和 (q) 是解中引入的新参数。(p) 和 (q) 可以取任意整数值来参数化完整的解集。更正式地说,(p,q \in \mathbb{Z}) 参数化了无限集合的毕达哥拉斯三元组。
考虑的替代方案
有几种寻找不定方程参数化一般解的替代方案。
-
数值替代方案:
-
Sage 的椭圆曲线命令可能能够找到每个变量的一组相对数值解
-
您可以测试显式整数值,例如使用值范围的嵌套 for 循环。这种方法效率低下,但如果您只对相对较小的解感兴趣,那么这是可以接受的。
-
-
solve()
将变量视为实数或复数,并仅解出一个变量的解,这会产生不同类型的解。例如,试图解(a² + b² = c²) 对(a), (b), 和(c) 只能揭示(a = \pm \sqrt{c²-b²})。
解二次不定方程的示例
下面是一个解不定方程的示例,特别是 (a² + b² = c²),使用diophantine()
:
>>> from sympy.solvers.diophantine import diophantine
>>> from sympy import symbols, Eq
>>> a, b, c = symbols("a, b, c", integer=True)
>>> my_syms = (a, b, c)
>>> pythag_eq = Eq(a**2 + b**2, c**2)
>>> # Solve Diophantine equation
>>> d = diophantine(pythag_eq, syms=my_syms)
>>> d
{(2*p*q, p**2 - q**2, p**2 + q**2)}
参考不定方程 API 参考,了解更多解各种类型不定方程的示例。
指导
不定方程可以被表达为等于零的表达式
如果您已经有一个等于零的表达式,您可以解决这个表达式。例如,将毕达哥拉斯方程表达为 (a² + b² - c²) 也是有效的:
>>> from sympy.solvers.diophantine import diophantine
>>> from sympy import symbols
>>> a, b, c = symbols("a, b, c", integer=True)
>>> my_syms = (a, b, c)
>>> pythag = a**2 + b**2 - c**2
>>> diophantine(pythag, syms=my_syms)
{(2*p*q, p**2 - q**2, p**2 + q**2)}
指定结果中符号的顺序
我们建议您指定结果中符号的顺序,以避免混淆。使用syms
参数,并将其传递给一个元组或符号列表,以确保结果按照该顺序排列,例如syms=my_syms
,如本页面上的示例所示。
限制条件
目前,可以使用 diophantine()
和 Diophantine 模块的其他辅助函数解决五种类型的丢番图方程。
-
线性丢番图方程:(a_1x_1 + a_2x_2 + \ldots + a_nx_n = b)
-
一般二元二次方程:(ax² + bxy + cy² + dx + ey + f = 0)
-
齐次三元二次方程:(ax² + by² + cz² + dxy + eyz + fzx = 0)
-
扩展勾股定理方程:(a_{1}x_{1}² + a_{2}x_{2}² + \ldots + a_{n}x_{n}² = a_{n+1}x_{n+1}²)
-
一般平方和:(x_{1}² + x_{2}² + \ldots + x_{n}² = k)
使用解决方案结果
从结果中提取表达式
diophantine()
返回结果作为一组元组,元组中的每个元素都是方程中变量的表达式。例如,对于勾股定理方程,结果是一个包含一个元组的集合,其中表达式对应于 (a, b, c)。也就是说,元组表示 a = 2*p*q, b = p**2 - q**2, c = p**2-q**2
。因为不能通过下标从集合中提取元素(这里是一个元组),所以可以创建一个符号-表达式对的字典,通过其符号提取表达式:
>>> from sympy.solvers.diophantine import diophantine
>>> from sympy import symbols
>>> a, b, c = symbols("a, b, c", integer=True)
>>> my_syms = (a, b, c)
>>> pythag = a**2 + b**2 - c**2
>>> solution, = diophantine(pythag, syms=my_syms)
>>> solution
(2*p*q, p**2 - q**2, p**2 + q**2)
>>> # Convert set to list
>>> solution_dict = dict(zip(my_syms, solution))
>>> solution_dict
{a: 2*p*q, b: p**2 - q**2, c: p**2 + q**2}
>>> # Extract an expression for one variable using its symbol, here a
>>> solution_dict[a]
2*p*q
不够优雅的方法是将集合转换为列表,然后对列表进行下标操作。忘记参数顺序是常见错误,因此此方法更容易出错:
>>> from sympy.solvers.diophantine import diophantine
>>> from sympy import symbols
>>> a, b, c, p, q = symbols("a, b, c, p, q", integer=True)
>>> my_syms = (a, b, c)
>>> pythag = a**2 + b**2 - c**2
>>> d = diophantine(pythag, syms=my_syms)
>>> d
{(2*p*q, p**2 - q**2, p**2 + q**2)}
>>> # Convert set to list
>>> solution_list = list(d)
>>> solution_list
[(2*p*q, p**2 - q**2, p**2 + q**2)]
>>> # Extract a tuple corresponding to a solution
>>> solution_first = solution_list[0]
>>> solution_first
(2*p*q, p**2 - q**2, p**2 + q**2)
>>> # Extract an expression for one variable using its order, here a is element number zero
>>> solution_first[0]
2*p*q
处理参数
你可以操作像 p
和 q
这样由 diophantine()
自动生成的参数,将它们创建为符号。例如,要找到满足丢番图方程的特定值集合,可以通过以下方式替换参数的值
-
创建参数作为符号
-
使用
subs()
替换它们的值。
在这里,我们将值集合表示为一个字典,将每个变量((a, b, c))与其示例值关联起来:
>>> from sympy.solvers.diophantine import diophantine
>>> from sympy import symbols
>>> my_syms = (a, b, c)
>>> pythag = a**2 + b**2 - c**2
>>> d = diophantine(pythag, syms=my_syms)
>>> solution_list = list(d)
>>> solution_list
[(2*p*q, p**2 - q**2, p**2 + q**2)]
>>> p, q = symbols("p, q", integer=True)
>>> # Substitute in values as the dictionary is created
>>> solution_p4q3 = dict(zip(my_syms, [var.subs({p:4, q:3}) for var in solution_list[0]]))
>>> solution_p4q3
{a: 24, b: 7, c: 25}
注意,需要对生成的参数 (p
和 q
) 包含 integer=True
假设,以便为它们提供数值。相反,对于原始方程中的符号 (a
, b
, 和 c
),不需要包含 integer=True
假设,尽管这是一个好习惯。
要遍历解集,可以在嵌套循环中迭代参数 (p
和 q
) 的值:
>>> from sympy.solvers.diophantine import diophantine
>>> from sympy import symbols
>>> a, b, c, p, q = symbols("a, b, c, p, q", integer=True)
>>> my_syms = (a, b, c)
>>> pythag = a**2 + b**2 - c**2
>>> d = diophantine(pythag, syms=my_syms)
>>> solution_list = list(d)
>>> # Iterate over the value of parameters p and q
>>> for p_val in range(-1,2):
... for q_val in range(-1,2):
... # Substitute in the values of p and q
... pythag_vals = dict(zip(my_syms, [var.subs({p:p_val, q:q_val}) for var in solution_list[0]]))
... # Print out the values of the generated parameters, and the Pythagorean triple a, b, c
... print(f"p: {p_val}, q: {q_val} -> {pythag_vals}")
p: -1, q: -1 -> {a: 2, b: 0, c: 2}
p: -1, q: 0 -> {a: 0, b: 1, c: 1}
p: -1, q: 1 -> {a: -2, b: 0, c: 2}
p: 0, q: -1 -> {a: 0, b: -1, c: 1}
p: 0, q: 0 -> {a: 0, b: 0, c: 0}
p: 0, q: 1 -> {a: 0, b: -1, c: 1}
p: 1, q: -1 -> {a: -2, b: 0, c: 2}
p: 1, q: 0 -> {a: 0, b: 1, c: 1}
p: 1, q: 1 -> {a: 2, b: 0, c: 2}
验证解决方案
您可以通过将其整数值代入原始方程(等于零的表达式)并检查结果是否为零来验证解是否正确,可以使用处理参数中的字典方法,或通过任何程序确定的值手动替换:
>>> from sympy.solvers.diophantine import diophantine
>>> from sympy import symbols
>>> a, b, c, p, q = symbols("a, b, c, p, q", integer=True)
>>> my_syms = (a, b, c)
>>> pythag = a**2 + b**2 - c**2
>>> d = diophantine(pythag, syms=my_syms)
>>> solution_list = list(d)
>>> solution_p4q3 = dict(zip(my_syms, [var.subs({p:4, q:3}) for var in solution_list[0]]))
>>> # Substitute values in using a dictionary
>>> pythag.subs({a: solution_p4q3[a], b: solution_p4q3[b], c: solution_p4q3[c]})
0
>>> # Manually substitute in values
>>> pythag.subs({a: 24, b: 7, c: 25})
0
程序化提取参数符号
如果您想程序化地获取一个解的自动生成参数集合,您可以使用以下代码:
>>> from sympy.solvers.diophantine import diophantine
>>> from sympy import symbols
>>> a, b, c, p, q = symbols("a, b, c, p, q", integer=True)
>>> my_syms = (a, b, c)
>>> pythag = a**2 + b**2 - c**2
>>> # Solve Diophantine equation
>>> solution, = diophantine(pythag, syms=my_syms)
>>> solution
(2*p*q, p**2 - q**2, p**2 + q**2)
>>> # Extract parameter symbols
>>> set().union(*(s.free_symbols for s in solution))
{p, q}
不是所有的方程都能解决
没有解的方程
有些丢番图方程无解,这种情况下 diophantine()
将返回一个空集合,set()
。例如,在表达式 (2x + 4y - 3)(我们将尝试将其设置为零)中,系数都是偶数((2) 和 (4)),因此项的和 ((2x + 4y)) 只能是偶数。然而,常数 (3) 是奇数,因此没有解决方案。
>>> from sympy.solvers.diophantine import diophantine
>>> from sympy import symbols
>>> x, y = symbols("x, y", integer=True)
>>> diophantine(2*x + 4*y - 3, syms=(x, y))
set()
报告错误
如果您发现 diophantine()
存在 bug,请在SymPy 邮件列表上发布问题。在问题解决之前,您可以使用考虑的替代方案中列出的其他方法。
SymPy Logo
我们希望让您能够轻松地在下一篇学术论文、课程材料或演示中包含 SymPy 项目标识。
上述图像标识是标志的 SVG 版本。我们还有标志的 PNG 版本:
如果您想要一个没有“SymPy”文本的标识,我们也有:
注意:应优先选择文本版本,除非“SymPy”名称已经单独存在。
如果您想要自己生成 SymPy 的官方标识集,可以通过首先安装所需的依赖项,然后运行:
$ cd doc
$ make logo # will be stored in the _build/logo subdirectory
通过使用本地 SymPy 副本中的sympy.svg
文件来生成图标。
还有一个 sympy/doc/generate_logos.py
脚本,允许在生成标识时使用更多样的选项。
所有标识的许可证与 SymPy 相同:BSD。更多信息,请参阅许可证文件。
引用 SymPy
要在出版物中引用 SymPy,请使用
Meurer A, Smith CP, Paprocki M, Čertík O, Kirpichev SB, Rocklin M, Kumar A,
Ivanov S, Moore JK, Singh S, Rathnayake T, Vig S, Granger BE, Muller RP,
Bonazzi F, Gupta H, Vats S, Johansson F, Pedregosa F, Curry MJ, Terrel AR,
Roučka Š, Saboo A, Fernando I, Kulal S, Cimrman R, Scopatz A. (2017) SymPy:
symbolic computing in Python. *PeerJ Computer Science* 3:e103
https://doi.org/10.7717/peerj-cs.103
LaTeX 用户的 BibTeX 条目是
@article{10.7717/peerj-cs.103,
title = {SymPy: symbolic computing in Python},
author = {Meurer, Aaron and Smith, Christopher P. and Paprocki, Mateusz and \v{C}ert\'{i}k, Ond\v{r}ej and Kirpichev, Sergey B. and Rocklin, Matthew and Kumar, AMiT and Ivanov, Sergiu and Moore, Jason K. and Singh, Sartaj and Rathnayake, Thilina and Vig, Sean and Granger, Brian E. and Muller, Richard P. and Bonazzi, Francesco and Gupta, Harsh and Vats, Shivam and Johansson, Fredrik and Pedregosa, Fabian and Curry, Matthew J. and Terrel, Andy R. and Rou\v{c}ka, \v{S}t\v{e}p\'{a}n and Saboo, Ashutosh and Fernando, Isuru and Kulal, Sumith and Cimrman, Robert and Scopatz, Anthony},
year = 2017,
month = jan,
keywords = {Python, Computer algebra system, Symbolics},
abstract = {
SymPy is an open source computer algebra system written in pure Python. It is built with a focus on extensibility and ease of use, through both interactive and programmatic applications. These characteristics have led SymPy to become a popular symbolic library for the scientific Python ecosystem. This paper presents the architecture of SymPy, a description of its features, and a discussion of select submodules. The supplementary material provide additional examples and further outline details of the architecture and features of SymPy.
},
volume = 3,
pages = {e103},
journal = {PeerJ Computer Science},
issn = {2376-5992},
url = {https://doi.org/10.7717/peerj-cs.103},
doi = {10.7717/peerj-cs.103}
}
SymPy 采用 BSD 许可证,因此您可以根据自己的喜好自由使用,无论是学术、商业、创建分支或衍生作品,只要在重新分发时复制 BSD 声明(有关详细信息,请参阅 LICENSE 文件)。话虽如此,尽管 SymPy 许可证不要求,但如果对您方便的话,请在使用时引用 SymPy,并考虑将所有更改贡献回来,这样我们可以将其整合,最终我们都将受益。
SymPy 开发团队成员名单列在GitHub 上的 AUTHORS 文件中。
一份列出了citing SymPy 的论文的列表可以在 Zotero 上找到。
解释
解释提供了对选择的 SymPy 特性进行深入讨论的内容。这些主题指南讨论了设计决策背后的动机、技术实现细节和具有观点的建议。
内容
-
最佳实践
-
注意事项和陷阱
-
按类型解决输出
-
SymPy 特殊主题
-
活跃弃用列表
-
术语表
最佳实践
本页面概述了使用 SymPy 的一些最佳实践。这些最佳实践将有助于避免使用 SymPy 时可能出现的一些常见错误和陷阱。
本页面主要关注适用于 SymPy 所有部分的一些最佳实践。适用于特定 SymPy 子模块或函数的最佳实践在这些特定函数的文档中进行了概述。
基本用法
定义符号
-
使用
symbols()
或Symbol()
定义符号。symbols()
函数是创建符号的最便捷方式。它支持一次创建一个或多个符号:>>> from sympy import symbols >>> x = symbols('x') >>> a, b, c = symbols('a b c')
另外,它支持向符号添加假设。
>>> i, j, k = symbols('i j k', integer=True)
并定义
Function
对象:>>> from sympy import Function >>> f, g, h = symbols('f g h', cls=Function)
它还支持快捷方式一次性定义多个编号符号:
>>> symbols('x:10') (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9)
Symbol()
构造函数也可以直接使用。与symbols()
不同,Symbol()
总是创建一个符号。如果要创建名称中带有不寻常字符的符号或者在程序中创建符号,则这是最佳选择。>>> from sympy import Symbol >>> x_y = Symbol('x y') # This creates a single symbol named 'x y'
var()
函数应避免使用,除非在交互式工作时。它的工作方式类似于symbols()
函数,但它会自动将符号名称注入到调用命名空间中。此函数仅设计用于交互式输入便捷,不建议用于程序化使用。不要使用
sympify()
或S()
创建符号。这样看似有效,但可能会出现问题:>>> from sympy import S >>> x = S("x") # DO NOT DO THIS
不过,
S()
/sympify()
不适用于创建符号。它们设计用于解析整个表达式。如果输入字符串无效,则该方法失败。如果字符串解析为较大表达式,则同样会失败:>>> # These both fail >>> x = S("0x") Traceback (most recent call last): ... SyntaxError: invalid syntax (<string>, line 1) >>> x = S("x+") Traceback (most recent call last): ... SyntaxError: invalid syntax (<string>, line 1)
任何 Python 字符串都可以用作有效的符号名称。
此外,下面的避免使用字符串输入部分中描述的所有问题也同样适用于此处。
-
在已知的情况下向符号添加假设。 假设 可通过向
symbols()
传递相关关键字来添加。最常见的假设是real=True
、positive=True
(或nonnegative=True
)和integer=True
。假设从不是必需的,但如果已知它们,建议始终包括它们,因为这将允许某些操作简化。如果未提供假设,则假定符号为一般复数,并且不会进行简化,除非它们对所有复数都成立。
例如:
>>> from sympy import integrate, exp, oo >>> a = symbols('a') # no assumptions >>> integrate(exp(-a*x), (x, 0, oo)) Piecewise((1/a, Abs(arg(a)) < pi/2), (Integral(exp(-a*x), (x, 0, oo)), True))
>>> a = symbols('a', positive=True) >>> integrate(exp(-a*x), (x, 0, oo)) 1/a
在这里,(\int_0^\infty e^{-ax},dx) 在
a
定义没有假设时会给出一个分段结果,因为积分只在a
为正时收敛。将a
设为正数可以消除这种分段。当您确实使用假设时,最佳实践是对每个符号名称始终使用相同的假设。SymPy 允许使用不同假设定义相同的符号名称,但这些符号将被视为不相等:
>>> z1 = symbols('z') >>> z2 = symbols('z', positive=True) >>> z1 == z2 False >>> z1 + z2 z + z
另请参见避免字符串输入和不要在 Python 函数中硬编码符号名称,了解有关定义符号的相关最佳实践。 ### 避免字符串输入
不要将字符串作为函数的输入。相反,使用符号(Symbols)和适当的 SymPy 函数来符号化地创建对象,并对它们进行操作。
不要
>>> from sympy import expand
>>> expand("(x**2 + x)/x")
x + 1
要
>>> from sympy import symbols
>>> x = symbols('x')
>>> expand((x**2 + x)/x)
x + 1
最好始终使用 Python 运算符显式地创建表达式,但有时您确实从字符串输入开始,例如从用户那里接受表达式。如果您确实有一个要开始的字符串,最好使用parse_expr()
显式解析它。最好尽早解析所有字符串,然后仅从那里进行符号操作。
>>> from sympy import parse_expr
>>> string_input = "(x**2 + x)/x"
>>> expr = parse_expr(string_input)
>>> expand(expr)
x + 1
原因
使用字符串作为 SymPy 函数输入存在许多缺点:
-
这是不符合 Python 风格的,会使代码难以阅读。参见Python 之禅“显式优于隐式”。
-
一般 SymPy 函数对字符串输入的支持大多是偶然的。这是因为这些函数在其输入上调用
sympify()
,以将 Python 的int
之类的东西转换为 SymPy 的Integer
。然而,sympify()
也会将字符串解析为 SymPy 表达式,除非使用strict=True
标志。一般 SymPy 函数(除了sympify()
或parse_expr()
之外)对字符串的自动解析可能会在未来的 SymPy 版本中消失(https://github.com/sympy/sympy/issues/11003)。 -
符号或函数名称中的拼写错误可能会被忽略。这是因为字符串中的所有未定义名称将自动解析为符号或函数。如果输入中有拼写错误,字符串仍将被正确解析,但输出将不符合预期。例如
>>> from sympy import expand_trig >>> expand_trig("sine(x + y)") sine(x + y)
与不使用字符串时得到的显式错误相比:
>>> from sympy import sin, symbols >>> x, y = symbols('x y') >>> expand_trig(sine(x + y)) # The typo is caught by a NameError Traceback (most recent call last): ... NameError: name 'sine' is not defined >>> expand_trig(sin(x + y)) sin(x)*cos(y) + sin(y)*cos(x)
在第一个例子中,
sine
(sin
的打字错误)被解析为Function("sine")
,看起来expand_trig
无法处理它。在第二种情况中,我们立即从未定义的名称sine
中获得错误,并且在修正我们的打字错误后,我们看到expand_trig
确实可以实现我们想要的效果。 -
当使用字符串输入时最大的陷阱来自于使用假设。在 SymPy 中,如果两个符号具有相同的名称但不同的假设,则认为它们是不相等的。
>>> z1 = symbols('z') >>> z2 = symbols('z', positive=True) >>> z1 == z2 False >>> z1 + z2 z + z
通常建议避免这样做,因为这可能导致混淆的表达式,如上面的例子(参见定义符号)。
然而,字符串输入始终会创建没有假设的符号。因此,如果您有一个带有假设的符号,并且稍后尝试使用其字符串版本,您将得到令人困惑的结果。
>>> from sympy import diff >>> z = symbols('z', positive=True) >>> diff('z**2', z) 0
这里的答案显然是错误的,但发生的情况是
"z**2"
中的z
被解析为没有假设的Symbol('z')
,而 SymPy 认为这是与z = Symbol('z', positive=True)
不同的符号,后者作为diff()
的第二个参数使用。因此,对于diff
而言,表达式是常数,结果为 0。这种情况特别糟糕,因为它通常不会导致任何错误。它会静默地给出“错误”的答案,因为 SymPy 将处理您认为相同但实际上不同的符号。这种情况可以通过不使用字符串输入来避免。
如果您正在解析字符串,并且希望其中的某些符号具有特定的假设,则应创建这些符号并将它们传递给
parse_expr()
的字典。例如:不要
>>> a, b, c = symbols('a b c', real=True) >>> # a, b, and c in expr are different symbols without assumptions >>> expr = parse_expr('a**2 + b - c') >>> expr.subs({a: 1, b: 1, c: 1}) # The substitution (apparently) doesn't work a**2 + b - c
做
>>> # a, b, and c are the same as the a, b, c with real=True defined above >>> expr = parse_expr('a**2 + b - c', {'a': a, 'b': b, 'c': c}) >>> expr.subs({a: 1, b: 1, c: 1}) 1
-
许多 SymPy 操作被定义为方法,而不是函数,也就是说,它们像
sympy_obj.method_name()
这样调用。由于这些方法尚未是 SymPy 对象,因此无法在字符串上工作。例如:>>> "x + 1".subs("x", "y") Traceback (most recent call last): ... AttributeError: 'str' object has no attribute 'subs'
与之相对:
>>> x, y = symbols('x y') >>> (x + 1).subs(x, y) y + 1
-
符号名称可以包含任何字符,包括 Python 不支持的内容。但如果使用字符串作为输入,则无法使用这些符号。例如
>>> from sympy import solve >>> solve('x_{2} - 1') ValueError: Error from parse_expr with transformed code: "Symbol ('x_' ){Integer (2 )}-Integer (1 )" ... SyntaxError: invalid syntax (<string>, line 1)
这不起作用,因为
x_{2}
不是有效的 Python 语法。但是将其用作符号名称是完全可能的:>>> x2 = symbols('x_{2}') >>> solve(x2 - 1, x2) [1]
实际上,上述情况是最好的情况,即出现错误。也有可能会得到一些意外的结果:
>>> solve('x¹_2 - 1') [-1, 1, -I, I, -1/2 - sqrt(3)*I/2, -1/2 + sqrt(3)*I/2, 1/2 - sqrt(3)*I/2, 1/2 + sqrt(3)*I/2, -sqrt(3)/2 - I/2, -sqrt(3)/2 + I/2, sqrt(3)/2 - I/2, sqrt(3)/2 + I/2]
这里发生的是,
x¹_2
被解析为x**12
(^
被转换为**
,而_
在 Python 的数值文字中被忽略)。如果我们创建一个 Symbol,那么符号名称的实际内容将被忽略。它始终表示为单个符号。
>>> x12 = symbols('x¹_2') >>> solve(x12 - 1, x12) [1]
-
如果使用字符串,语法错误将在运行该行时才会被捕获。如果您构建表达式,语法错误将立即被捕获。
-
代码编辑器中的语法高亮通常不会识别并对字符串内容进行颜色编码,而它可以识别 Python 表达式。
避免将表达式作为字符串进行操作
如果发现自己在符号表达式上进行大量字符串或正则表达式操作,则通常表示正在错误地使用 SymPy。最好直接使用诸如+
、-
、*
和/
以及 SymPy 的各种函数和方法直接构建表达式。基于字符串的操作可能会引入错误,迅速变得复杂,并丧失符号表达式结构的好处。
这是因为字符串中没有符号表达式的概念。对于 Python 来说,"(x + y)/z"
与"/x+)(y z "
并无二致,它们只是字符顺序不同的同一字符串。相比之下,SymPy 表达式确实了解它所代表的数学对象的类型。SymPy 有许多用于构建和操作表达式的方法和函数,它们都是作用于 SymPy 对象而非字符串的。
例如
不要
>>> expression_str = '+'.join([f'{i}*x_{i}' for i in range(10)])
>>> expr = parse_expr(expression_str)
>>> expr
x_1 + 2*x_2 + 3*x_3 + 4*x_4 + 5*x_5 + 6*x_6 + 7*x_7 + 8*x_8 + 9*x_9
做
>>> from sympy import Add, Symbol
>>> expr = Add(*[i*Symbol(f'x_{i}') for i in range(10)])
>>> expr
x_1 + 2*x_2 + 3*x_3 + 4*x_4 + 5*x_5 + 6*x_6 + 7*x_7 + 8*x_8 + 9*x_9
另请参阅避免向函数输入字符串的上一节。
精确有理数与浮点数
如果某个数值确切等于某个量,请避免将其定义为浮点数。
例如,
不要
>>> expression = x**2 + 0.5*x + 1
做
>>> from sympy import Rational
>>> expression = x**2 + Rational(1, 2)*x + 1
>>> expression = x**2 + x/2 + 1 # Equivalently
然而,并非说在 SymPy 中永远不应使用浮点数,只是如果已知更精确的值,则应优先使用。SymPy 确实支持任意精度浮点数,但某些操作可能性能不佳。
对于可以精确表示的非有理数,同样适用。例如,应避免使用math.pi
,而优先使用sympy.pi
,因为前者是对(\pi)的数值近似,而后者是(\pi)的精确表示(另请参见分离符号和数值代码下文;总体而言,在使用 SymPy 时应避免导入math
)。
不要
>>> import math
>>> import sympy
>>> math.pi
3.141592653589793
>>> sympy.sin(math.pi)
1.22464679914735e-16
做
>>> sympy.pi
pi
>>> sympy.sin(sympy.pi)
0
在这里,sympy.sin(math.pi)
并非严格等于 0,因为math.pi
并非严格等于(\pi)。
还应特别注意避免写integer/integer
,其中两个整数都是显式整数。这是因为 Python 会在 SymPy 能够解析之前将其评估为浮点值。
不要
>>> x + 2/7 # The exact value of 2/7 is lost
x + 0.2857142857142857
在这种情况下,可以使用Rational
创建有理数,或者如果想节省输入,则可以使用S()
缩写。
做
>>> from sympy import Rational, S
>>> x + Rational(2, 7)
x + 2/7
>>> x + S(2)/7 # Equivalently
x + 2/7
原因
对于已知的精确值,应优先选择而非浮点数,原因如下:
-
精确的符号值通常可以被符号化简或操作。浮点数表示对精确实数的近似,因此无法被精确简化。例如,在上面的例子中,
sin(math.pi)
不会产生0
,因为math.pi
并不是完全的(\pi)。它只是一个浮点数,用 15 位数近似了(\pi)(实际上是(\pi)的一个接近的有理近似,但不是精确的(\pi))。 -
如果存在浮点值,某些算法将无法计算结果,但如果值是有理数,则可以计算。这是因为有理数具有使这些算法更易处理的属性。例如,使用浮点数时,可能会出现一个数字应该为 0 的情况,但由于近似误差,实际上并不等于 0。
特别引人注目的例子是浮点数指数。例如,
>>> from sympy import factor >>> factor(x**2.0 - 1) x**2.0 - 1 >>> factor(x**2 - 1) (x - 1)*(x + 1)
-
SymPy 浮点数具有与使用有限精度浮点数近似导致的相同的有效位数损失和抵消问题:
>>> from sympy import expand >>> expand((x + 1.0)*(x - 1e-16)) # the coefficient of x should be slightly less than 1 x**2 + 1.0*x - 1.0e-16 >>> expand((x + 1)*(x - Rational('1e-16'))) # Using rational numbers gives the coefficient of x exactly x**2 + 9999999999999999*x/10000000000000000 - 1/10000000000000000
在许多情况下,可以通过仔细使用带有任意精度评估能力的
evalf
来避免这些问题。这通常涉及计算带有符号值的表达式,然后使用expr.evalf(subs=...)
稍后进行替换,或者通过使用比默认的 15 位高的精度开始使用Float
值:>>> from sympy import Float >>> expand((x + 1.0)*(x - Float('1e-16', 20))) x**2 + 0.9999999999999999*x - 1.0e-16
可以通过将Float
数传递给Rational
来将其转换为其精确有理等价物。或者,您可以使用nsimplify
来找到最好的有理近似值。这有时可以重现预期的数字,如果该数字应为有理数的话(尽管再次强调,最好一开始就使用有理数):
>>> from sympy import nsimplify
>>> Rational(0.7)
3152519739159347/4503599627370496
>>> nsimplify(0.7)
7/10
避免使用simplify()
simplify()
(不要与sympify()
混淆)被设计为一种通用的启发式算法。它在输入表达式上尝试各种简化算法,并根据某些度量返回看起来“最简”的结果。
simplify()
在交互使用中完全可以接受,您只需希望 SymPy 尽其所能处理表达式。但是,在程序化使用中,最好避免使用simplify()
,而是使用更多的定向简化函数(例如cancel()
,expand()
或collect()
)。
通常情况下,这样做有几个原因:
-
由于其启发式特性,
simplify()
可能会潜在地较慢,因为它尝试了许多不同的方法来找到最佳的简化。 -
经过
simplify()
处理后,表达式的形式可能无法保证。它可能实际上按您希望的任何度量标准变得“不那么简单”。相比之下,目标化简函数非常明确地说明了它们的行为和输出的保证。例如,-
factor()
将总是将多项式因式分解为不可约因子。 -
cancel()
将总是将有理函数转换为形式 (p/q),其中 (p) 和 (q) 是展开的多项式,没有公共因子。
每个函数的文档描述了它在输入表达式上的确切行为。
-
-
如果表达式包含意外形式或意外子表达式,目标化简不会执行意外操作。特别是如果应用简化函数时使用
deep=False
仅将简化应用于顶层表达式。
其他一些简化函数具有启发式特性,使用它们时也应格外小心。例如,trigsimp()
函数是一种针对三角函数的启发式方法,但sympy.simplify.fu
子模块中的例程允许应用特定的三角函数恒等式。
教程中的简化部分(simplify section of the tutorial)和简化模块参考(simplify module reference)列出了各种目标化简函数。
在某些情况下,您可能确切地知道要应用到表达式的简化操作,但可能没有确切的简化函数集来执行这些操作。发生这种情况时,您可以使用replace()
或一般地使用高级表达式操作创建您自己的目标简化。
不要在 Python 函数中硬编码符号名称
在函数定义中,而不是在内部硬编码Symbol
名称,请将符号作为函数的参数。
例如,考虑一个函数theta_operator
,它计算θ运算符 (\theta = zD_z):
不要
def theta_operator(expr):
z = symbols('z')
return z*expr.diff(z)
要
def theta_operator(expr, z):
return z*expr.diff(z)
硬编码的符号名称的缺点是需要所有表达式使用确切的符号名称。在上述示例中,不可能计算 (\theta = xD_x),因为它被硬编码为 (zD_z)。更糟糕的是,试图这样做会导致错误的结果,而不是错误,因为 x
被视为常量表达式:
>>> def theta_operator(expr):
... z = symbols('z')
... return z*expr.diff(z)
>>> theta_operator(x**2) # The expected answer is 2*x**2
0
这在函数接受任意用户输入时特别棘手,因为用户可能在他们的数学背景中使用不同的变量名。如果用户已经使用符号z
但作为常数,他们需要在能够使用函数之前使用subs
来交换这些内容。
另一个导致这种反模式问题的原因是,带有假设的符号被认为与没有假设的符号不相等。如果某人使用
>>> z = symbols('z', positive=True)
例如,为了使进一步简化成为可能(参见定义符号),硬编码的函数中使用 Symbol('z')
而没有假设将不起作用:
>>> theta_operator(z**2)
0
通过将符号作为函数的参数,如 theta_operator(expr, z)
,所有这些问题都可以解决。### 分离符号和数值代码
SymPy 与 Python 生态系统中的大多数其他库有所不同,它以符号方式运行,而其他库如 NumPy 则以数值方式运行。这两种范式差异足够大,因此最好尽可能将它们分开。
重要的是,SymPy 不设计与 NumPy 数组一起使用,反之亦然,NumPy 也不能直接使用 SymPy 对象。
>>> import numpy as np
>>> import sympy
>>> a = np.array([0., 1., 2.])
>>> sympy.sin(a)
Traceback (most recent call last):
...
AttributeError: 'ImmutableDenseNDimArray' object has no attribute 'as_coefficient'
>>> x = Symbol('x')
>>> np.sin(x) # NumPy functions do not know how to handle SymPy expressions
Traceback (most recent call last):
...
TypeError: loop of ufunc does not support argument 0 of type Symbol which has no callable sin method
如果您希望同时使用 SymPy 和 NumPy,应明确将 SymPy 表达式转换为 NumPy 函数,使用 lambdify()
。在 SymPy 中的典型工作流程是使用 SymPy 符号化地建模问题,然后使用 lambdify()
将结果转换为可以在 NumPy 数组上评估的数值函数。对于高级用例,lambdify()
/NumPy 可能不足以满足需求,可能需要使用 SymPy 更通用的 代码生成 例程来为其他快速数值语言(如 Fortran 或 C)生成代码。
>>> # First symbolically construct the expression you are interested in with SymPy
>>> from sympy import diff, sin, exp, lambdify, symbols
>>> x = symbols('x')
>>> expr = diff(sin(x)*exp(x**2), x)
>>> # Then convert it to a numeric function with lambdify()
>>> f = lambdify(x, expr)
>>> # Now use this function with NumPy
>>> import numpy as np
>>> a = np.linspace(0, 10)
>>> f(a)
[ 1.00000000e+00 1.10713341e+00 1.46699555e+00 ... -3.15033720e+44]
这些都是应该通常避免的反模式。
-
不要使用
import math
。 在使用 SymPy(或 NumPy)时几乎从不需要使用标准库math
模块。math
中的每个函数已经包含在 SymPy 中。SymPy 可以使用 evalf 进行数值计算,提供比math
更高的精度和准确性。更好的做法是,SymPy 默认会以符号方式计算。math
中的函数和常数是浮点数,存在不精确性。在可能的情况下,SymPy 始终更适合精确量。例如,>>> import math >>> math.pi # a float 3.141592653589793 >>> import sympy >>> sympy.sin(math.pi) 1.22464679914735e-16
sympy.sin(math.pi)
的结果不是你可能期望的0
,因为math.pi
只是 (\pi) 的近似值,精确到 16 位数字。另一方面,sympy.pi
确切 等于 (\pi),因为它是符号表示,所以能够给出精确的答案:>>> sympy.sin(sympy.pi) 0
因此,通常应该偏爱符号表示。但即使你确实需要一个浮点数,你最好使用 SymPy 的
evalf()
而不是math
。这样可以避免math
函数仅能操作float
对象而不能操作符号表达式的问题。>>> x = Symbol('x') >>> math.sin(x) Traceback (most recent call last): ... TypeError: Cannot convert expression to float
此外,SymPy 的
evalf()
比math
更精确,因为它使用任意精度算术,并允许您指定任意数量的位数。>>> sympy.sin(1).evalf(30) 0.841470984807896506652502321630 >>> math.sin(1) 0.8414709848078965
即使在使用 NumPy 时,应避免使用
math
。NumPy 函数比它们的math
等效函数更快,支持更大范围的数值类型,并且可以操作值数组,而math
函数一次只能操作一个标量。 -
不要将 SymPy 表达式传递给 NumPy 函数。 不应将 SymPy 表达式传递给 NumPy 函数。这包括
numpy
或scipy
命名空间中的任何内容,以及来自其他 Python 库(如matplotlib
)的大多数函数。这些函数只设计用于处理具有数值值的 NumPy 数组。 -
不要将 SymPy 表达式传递给 lambdify 函数。 与前一点类似,不应将 SymPy 表达式传递给使用
lambdify
创建的函数。实际上,lambdify
返回的函数就是 NumPy 函数,因此这里的情况完全相同。某些情况下,通过lambdify()
创建的函数可能会与 SymPy 表达式一起工作,但这只是其工作方式的偶然结果。有关此现象更多详细信息,请参阅lambdify()
文档的“how it works”部分。 -
避免将 SymPy 表达式存储在 NumPy 数组中。 虽然技术上可以将 SymPy 表达式存储在 NumPy 数组中,但这样做通常是一个错误。表明这种情况发生的标志是如果 NumPy 数组的
dtype
是object
(而不是像float64
或int64
这样的数值类型)。就像在 SymPy 进行符号计算时应避免使用 NumPy 一样,一旦计算转向 NumPy 的数值计算,就应停止使用 SymPy。
包含 SymPy 表达式的 NumPy 数组实际上面临与直接在 SymPy 表达式上调用 NumPy 函数相同的问题。它们不知道如何操作 SymPy 对象,因此会失败。即使 SymPy 对象都是 SymPy
Float
,情况也是如此。>>> import numpy as np >>> import sympy >>> a = np.asarray([sympy.Float(1.0), sympy.Float(0.0)]) # Do not do this >>> print(repr(a)) # Note that the dtype is 'object' array([1.00000000000000, 0.0], dtype=object) >>> np.sin(a) Traceback (most recent call last): ... TypeError: loop of ufunc does not support argument 0 of type Float which has no callable sin method
如果您正在这样做,您可能应该使用本机 NumPy 浮点数,或者如果确实希望存储一组 SymPy 表达式,则应使用 SymPy 的
Matrix
或NDimArray
类。
高级用法
谨慎比较和排序符号对象
在编写比较数值量的程序代码时要小心,可以直接使用不等式(<
、<=
、>
、>=
)或间接使用像sorted
之类的内容。问题在于如果不等式未知,则结果将是符号的,如
>>> x > 0
x > 0
如果对符号不等式调用bool()
将引发异常,由于其模棱两可性:
>>> bool(x > 0)
Traceback (most recent call last):
...
TypeError: cannot determine truth value of Relational
类似以下检查:
if x > 0:
...
如果仅针对数值x
进行测试,则可能运行得很好。但是如果x
可能是符号的,则上述代码是错误的。它将以TypeError: cannot determine truth value of Relational
失败。如果您曾看到此异常,则意味着此错误已经发生(有时错误出现在 SymPy 本身; 如果情况如此,请提交问题)。
当使用sorted
时,会出现完全相同的问题,因为它内部使用了>
。
>>> sorted([x, 0])
Traceback (most recent call last):
...
TypeError: cannot determine truth value of Relational
有几种方法可以解决此问题,选择正确的方法取决于您的操作目的:
-
禁止符号输入。 如果您的函数绝对不能处理符号输入,则可以明确禁止它们。这里的主要优点是向用户提供比
TypeError: cannot determine truth value of Relational
更易读的错误消息。可以使用is_number
属性来检查表达式是否可以通过evalf()
计算为特定数字。如果只想接受整数,可以检查isinstance(x, Integer)
(在调用sympify()
将 Python 整数转换为 SymPy 表达式后)。注意,is_integer
使用假设系统,即使对于如Symbol('x', integer=True)
之类的符号对象也可能为 True。 -
使用假设系统。 如果您支持符号输入,应使用假设系统来检查诸如
x > 0
之类的条件,例如使用x.is_positive
。在此过程中,您应始终注意布尔指导中使用的三值模糊逻辑的细微差别。也就是说,始终注意假设可能为None
,意味着其值未知且可能为真或假。例如,if x.is_positive: ...
仅在
x.is_positive
为True
时才会运行该块,但当x.is_positive
为None
时,您可能希望执行某些操作。 -
返回一个分段函数结果。 如果函数的结果取决于不等式或其他布尔条件,您可以使用
Piecewise
返回一个代表两种可能性的结果。在可能的情况下,这通常是首选,因为它提供了最大的灵活性。这是因为结果是以符号方式表示的,这意味着,例如,可以稍后替换符号的具体值,并且它将评估为特定情况,即使它与其他表达式组合在一起。例如,而不是
if x > 0: expr = 1 else: expr = 0
这可以用符号表示为
>>> from sympy import Piecewise, pprint >>> expr = Piecewise((1, x > 0), (0, True)) >>> pprint(expr, use_unicode=True) ⎧1 for x > 0 ⎨ ⎩0 otherwise >>> expr.subs(x, 1) 1 >>> expr.subs(x, -1) 0
-
使用
ordered()
将表达式排序为规范顺序。 如果您试图使用sorted
,因为您想要规范排序,但您并不特别关心排序是什么,您可以使用ordered
。>>> from sympy import ordered >>> list(ordered([x, 0])) [0, x]
或者,尝试编写函数的方式,使得结果的正确性不依赖于处理参数的顺序。
自定义 SymPy 对象
SymPy 设计为通过自定义类进行扩展,通常是通过对 Basic、Expr 或 Function 进行子类化。SymPy 本身的所有符号类都是这样编写的,并且这里的要点同样适用于用户定义的类。
关于如何编写Function
子类的深入指南,请参阅编写自定义函数指南。
Args 不变量
自定义 SymPy 对象应始终满足以下不变量:
-
all(isinstance(arg, Basic) for arg in args)
-
expr.func(*expr.args) == expr
第一个说所有 args 的元素应该是 Basic 的实例。第二个说一个表达式应该从它的args
重建(注意 func 通常与type(expr)
相同,尽管可能不总是)。
SymPy 在整个过程中假设这两个不变量,并且对于任何操作表达式的函数都是必不可少的。
例如,考虑这个简单的函数,这是xreplace()
的简化版本:
>>> def replace(expr, x, y):
... """Replace x with y in expr"""
... newargs = []
... for arg in expr.args:
... if arg == x:
... newargs.append(y)
... else:
... newargs.append(replace(arg, x, y))
... return expr.func(*newargs)
>>> replace(x + sin(x - 1), x, y)
y + sin(y - 1)
该函数通过递归遍历expr
的args
,并重建它,除了x
的任何实例都被替换为y
。
很容易看出,如果参数不变量不成立,这个函数会如何破坏:
-
如果一个表达式的 args 不是
Basic
,它们在递归调用时将导致AttributeError
失败,因为非Basic
的 args 将没有.args
或.func
属性。 -
如果一个表达式没有从它的
args
中重建出来,那么return exr.func(*newargs)
这行代码将失败,即使在没有替换任何 args 的情况下,这应该有效地是一个无操作。
将所有args
实例转换为Basic
通常意味着对类的输入调用_sympify()
,这样它们就成为基本实例了。如果你想在类上存储一个字符串,你应该使用Symbol
或sympy.core.symbols.Str
。
在某些情况下,一个类可能接受多种等价形式的 args。重要的是,存储在args
中的形式是可以用于重建类的方式之一。标准化args
是可以接受的,只要标准化形式被接受为输入。例如,Integral
总是将变量参数存储为一个元组,以便在内部处理时更容易,但这种形式也被类构造函数接受:
>>> from sympy import Integral
>>> expr = Integral(sin(x), x)
>>> expr.args # args are normalized
(sin(x), (x,))
>>> Integral(sin(x), (x,)) # Also accepted
Integral(sin(x), x)
请注意,大多数用户定义的自定义函数应该通过对Function
进行子类化来定义(参见编写自定义函数指南)。Function
类自动处理args
的两个不变量,因此如果你使用它,你不需要担心这个问题。 ### 避免过多的自动评估
在定义自定义函数时,避免做过多的自动评估(比如在eval
或__new__
方法中进行评估)。
一般来说,只有在快速的实例中才应该进行自动评估,而且这是没人希望不发生的事情。自动评估很难撤销。一个很好的经验法则是在显式的数值上进行评估(isinstance(x, Number)
),而且将其他一切保留为符号未评估。使用更高级的身份进行进一步简化应该在特定简化函数或doit
中完成(参见自定义函数指南列出的可以在 SymPy 对象上定义的常见简化例程)。
自定义函数指南对此进行了深入讨论(但请注意,这个准则适用于所有 SymPy 对象,而不仅仅是函数)。但简而言之,这样做的原因是,防止自动评估的唯一方法是使用evaluate=False
,这是很脆弱的。另外,代码总是写成假设由于自动评估而成立的不变量,这意味着使用evaluate=False
创建的表达式可能会导致代码产生错误的结果。这也意味着以后删除自动评估可能会很困难。
可能很昂贵的评估(比如应用符号身份)本身是不好的,因为可以使得创建一个没有做任何事情的表达式。这也适用于检查符号假设(比如x.is_integer
),因此在类构造函数中也应该避免这样做。
不要
class f(Function):
@classmethod
def eval(cls, x):
if x.is_integer: # Bad (checking general assumptions)
return 0
if isinstance(x, Add): # Bad (applying symbolic identities)
return Add(*[f(i) for i in x.args])
要
class f(Function):
@classmethod
def eval(cls, x):
if isinstance(x, Integer): # Good (only evaluating on explicit integers)
return 0
# Good (applying simplification on assumptions in doit())
def doit(self, deep=True, **hints):
x = self.args[0]
if deep:
x = x.doit(deep=deep, **hints)
if x.is_integer:
return S(0)
return self
# Good (applying symbolic identities inside of simplification functions)
def _eval_expand_func(self, **hints):
x = self.args[0]
if isinstance(x, Add):
return Add(*[f(i) for i in x.args])
return self
请注意,目前 SymPy 中并不是所有的类都很好地遵循这个准则,但这是我们正在改进的事情。
不要去除嵌套集合。
接受任意数量参数的函数和类应该直接接受这些参数,例如 f(*args)
,或者作为单个参数,例如 f(args)
。它们不应同时尝试支持两者。
原因在于这会使得嵌套集合的表示变得不可能。例如,考虑FiniteSet
类。它的构造方式类似于 FiniteSet(x, y, z)
(即使用 *args
)。
>>> from sympy import FiniteSet
>>> FiniteSet(1, 2, 3)
{1, 2, 3}
也许您也想支持 FiniteSet([1, 2, 3])
,以匹配内置的 set
。然而,这样做会使得无法表示包含单个 FiniteSet
的嵌套 FiniteSet
,例如 ({{1, 2, 3}}):
>>> FiniteSet(FiniteSet(1, 2, 3)) # We don't want this to be the same as {1, 2, 3}
FiniteSet({1, 2, 3})
关于使用 args
还是 *args
,如果只可能有有限数量的参数,通常使用 *args
更好,因为这样可以更容易地使用对象的 args,因为 obj.args
将是类的直接参数。然而,如果可能需要支持符号无限集合,例如Integers
或Range
,那么最好使用 args
,因为这将不可能使用 *args
来实现。
避免在对象上存储额外属性
您可能希望创建一个自定义的 SymPy 对象的常见原因是您想在对象上存储额外的属性。然而,以简单的方式,即仅将数据作为 Python 属性存储在对象上,几乎总是不明智的做法。
SymPy 不希望对象在其 args 之外存储额外的数据。例如,这会破坏 ==
检查,因为它只比较对象的 args
。有关为什么覆盖 __eq__
是一个坏主意,请参阅下面的不要覆盖 eq 部分。这一节和那一节是密切相关的。
通常情况下,根据您的具体情况,有更好的方法来完成您想要做的事情:
-
将额外数据存储在对象的
args
中。 如果您想要存储的额外数据是对象数学描述的一部分,这是最佳方法。只要数据可以使用其他 SymPy 对象来表示,它就可以存储在
args
中。请注意,对象的args
应该可以用于重新创建对象(例如,类似YourObject(*instance.args)
的东西应该可以重新创建instance
)。此外,应该提到,如果您计划在
args
中存储任何额外内容,则不建议子类化Symbol
。Symbol
的设计是围绕着没有args
的。最好是直接子类化Function
(参见编写自定义函数)或直接子类化Expr
。如果您只是想要有两个彼此不同的符号,最好的方法通常是给它们不同的名称。如果您关心它们的打印方式,可以在打印时用更规范的名称替换它们,或者使用自定义打印机。 -
单独存储关于对象的数据。如果额外的数据与对象的数学属性无直接关系,则这是最佳方法。
请记住,SymPy 对象是可哈希的,因此可以轻松用作字典键。因此,维护一个独立的
{object: extra_data}
对的字典非常简单。请注意,某些 SymPy API 已经允许重新定义它们在对象上操作的方式,而不影响对象本身。其中一个很好的例子是打印机,它允许定义自定义打印机,从而改变任何 SymPy 对象的打印方式而无需修改这些对象本身。像
lambdify()
和init_printing()
这样的函数允许传入自定义打印机。 -
使用不同子类表示属性。如果属性的可能值很少(例如布尔标志),这通常是一个好主意。通过使用一个共同的超类可以避免代码重复。
-
如果您要存储的数据是一个 Python 函数,最好将其作为类的方法使用。在许多情况下,该方法可能已经适合于现有的可重写 SymPy 方法集之一。如果您想定义如何对函数进行数值评估,可以使用
implemented_function()
。 -
通过修改对象的
func
表示信息。 这种解决方案比其他方案复杂得多,只有在必要时才应使用。在一些极端情况下,无法仅通过args
单独表示对象的每个数学方面。例如,由于限制args
应仅包含Basic
实例,在这种情况下,仍然可以通过使用与type(expr)
不同的自定义 func 来创建自定义的 SymPy 对象(在这种情况下,您将在func
上而不是在类上重写__eq__
)。然而,这种情况很少见。### 不要重写
__eq__
当构建自定义 SymPy 对象时,有时会有一种诱惑,即重写__eq__
来定义==
运算符的自定义逻辑。这几乎总是一个坏主意。自定义的 SymPy 类应该不定义__eq__
,而是使用Basic
超类中的默认实现。
在 SymPy 中,==
使用结构相等性来比较对象。也就是说,a == b
意味着a
和b
是完全相同的对象。它们具有相同的类型和相同的 args。==
不执行任何类型的数学相等检查。例如,
>>> x*(x - 1) == x**2 - x
False
==
始终返回布尔值True
或False
。符号方程可以用Eq
表示。
有几个原因造成了这种情况
-
数学相等性检查可能非常昂贵,并且一般来说,计算上不可能确定。
-
Python 本身在各处自动使用
==
,并假定它返回布尔值且计算成本低廉。例如,如果b
是内置的 Python 容器如list
、dict
或set
,那么a in b
使用==
。[1] -
SymPy 在内部各处都使用
==
,明确地和隐含地,例如通过in
或字典键。这种使用隐含地假定==
操作是结构化的。
实际上,结构相等性意味着如果a == b
为True
,那么在所有意义上a
和b
是相同的对象。这是因为所有的 SymPy 对象都是不可变的。当a == b
时,任何 SymPy 函数都可以自由地在任何子表达式中用b
替换a
。
Basic 上的默认__eq__
方法检查两个对象是否具有相同的类型和相同的args
。此外,SymPy 的许多部分隐含地假定如果两个对象相等,则它们具有相同的args
。因此,试图覆盖__eq__
以避免在其args
中存储某些标识信息并不是一个好主意。一个对象的args
应包含重新创建它所需的一切(请参阅 args)。请注意,对象的构造函数可以接受多种形式的参数,只要接受存储在args
中的形式即可(例如,某些 args 可以具有默认值是完全可以的)。
这里有一些你可能会想要覆盖__eq__
的原因及其首选替代方案的示例:
-
为了使
==
应用比纯结构相等更智能的相等检查。如上所述,这是一个坏主意,因为太多事情都隐含地假定==
只能结构上工作。相反,使用函数或方法来实现更智能的相等检查(例如,equals
方法)。另一种选择是定义一个规范化方法,将对象放入规范形式(例如,通过
doit
),以便例如,当数学上相等时x.doit() == y.doit()
为真。这并非总是可能的,因为并非每种类型的对象都具有可计算的规范形式,但在存在时这是一个方便的方法。 -
使
==
检查除表达式args
中存储的属性外的一些附加属性。有关为什么直接在 SymPy 对象上存储额外属性是一个坏主意以及最佳替代方案的更多详细信息,请参阅上文中的避免在对象上存储额外属性部分。 -
使
==
与某些非 SymPy 对象相等。最好扩展sympify
以能够将该对象转换为 SymPy 对象。如果另一个参数不是Basic
实例(例如,Integer(1) == int(1)
会返回True
),则默认的__eq__
实现将自动调用sympify
。对于您通过定义_sympy_
方法控制的对象和通过扩展converter
字典控制的对象,都可以扩展sympify
。有关更多详细信息,请参见sympify()
文档。
避免假设处理程序的无限递归
当在自定义函数上编写假设处理程序时,比如_eval_is_positive
(详见自定义函数指南中如何做此操作的详细信息),有两件重要的事情需要牢记:
首先,在假设处理器内部避免创建新表达式。你应该直接解析函数的参数。 这是因为创建新表达式本身可能会导致假设查询。这很容易导致无限递归。即使没有,创建一个可能会导致多个递归假设查询的新表达式,与直接查询所需属性相比,对性能影响很不利。
通常意味着使用as_independent()
或{meth}
~.as_coeff_mul`并直接检查表达式的`args`(参见自定义函数指南中的示例)。
其次,在假设处理器中不要对self
递归评估假设。假设处理器应仅检查self.args
上的假设。全局假设系统将自动处理不同假设之间的含义。
例如,你可能会被诱惑写出类似于以下的内容
# BAD
class f(Function):
def _eval_is_integer(self):
# Quick return if self is not real (do not do this).
if self.is_real is False:
return False
return self.args[0].is_integer
然而,检查if self.is_real is False
是完全不必要的。假设系统已经知道integer
蕴含real
,如果已经知道is_real
为 False,它将不会检查is_integer
。
如果以这种方式定义函数,将导致无限递归:
>>> class f(Function):
... def _eval_is_integer(self):
... if self.is_real is False:
... return False
... return self.args[0].is_integer
>>> f(x).is_real
Traceback (most recent call last):
...
RecursionError: maximum recursion depth exceeded while calling a Python object
相反,仅基于函数参数定义处理器:
# GOOD
class f(Function):
def _eval_is_integer(self):
return self.args[0].is_integer
gotchas 和 pitfalls
Introduction
SymPy 是在Python 编程语言下运行的,因此可能有些事情的行为与其他独立的计算机代数系统(如 Maple 或 Mathematica)不同。这些都是在使用 SymPy 时可能遇到的一些陷阱和问题。另请参阅入门教程、SymPy 文档的其余部分以及官方 Python 教程。
如果你已经熟悉 C 或 Java,你可能还想看看这个4 分钟的 Python 教程。
忽略例子中的 #doctest: +SKIP
。这与例子的内部测试有关。
等号 (=)
单等号
等号 (=
) 是赋值运算符,不是相等。如果要执行 (x = y),请使用 Eq(x, y)
表示相等。或者,所有表达式都假定为零,因此你可以只减去一边并使用 x - y
。
等号的正确使用是将表达式分配给变量。
例如:
>>> from sympy.abc import x, y
>>> a = x - y
>>> print(a)
x - y
双等号
双等号 (==
) 用于测试相等性。但是,这会精确地测试表达式,而不是符号上的相等性。例如:
>>> (x + 1)**2 == x**2 + 2*x + 1
False
>>> (x + 1)**2 == (x + 1)**2
True
如果要测试符号相等性,一种方法是从一个表达式中减去另一个表达式,并通过expand()
、simplify()
和trigsimp()
等函数进行处理,看方程是否简化为零。
>>> from sympy import simplify, cos, sin, expand
>>> simplify((x + 1)**2 - (x**2 + 2*x + 1))
0
>>> eq = sin(2*x) - 2*sin(x)*cos(x)
>>> simplify(eq)
0
>>> expand(eq, trig=True)
0
注意
另请参阅结构相等性和术语表中的[结构相等性]。
变量
变量分配不会创建表达式之间的关系
当你使用 =
进行赋值时,请记住在 Python 中,像大多数编程语言一样,如果你改变分配给它的值,变量本身不会改变。你正在输入的方程式使用在创建时存在的值来“填充”值,就像常规的 Python 定义一样。它们不会因后续的更改而改变。考虑以下情况:
>>> from sympy import Symbol
>>> a = Symbol('a') # Symbol, `a`, stored as variable "a"
>>> b = a + 1 # an expression involving `a` stored as variable "b"
>>> print(b)
a + 1
>>> a = 4 # "a" now points to literal integer 4, not Symbol('a')
>>> print(a)
4
>>> print(b) # "b" is still pointing at the expression involving `a`
a + 1
更改量 a
不会改变 b
;你不是在处理一组同时方程式。记住打印一个引用 SymPy 对象的变量时得到的字符串是在创建时给定的字符串;那个字符串不一定与你赋给它的变量相同。
>>> from sympy import var
>>> r, t, d = var('rate time short_life')
>>> d = r*t
>>> print(d)
rate*time
>>> r = 80
>>> t = 2
>>> print(d) # We haven't changed d, only r and t
rate*time
>>> d = r*t
>>> print(d) # Now d is using the current values of r and t
160
如果您需要具有相互依赖的变量,可以定义函数。使用 def
操作符。缩进函数体。有关定义函数的更多信息,请参阅 Python 文档。
>>> c, d = var('c d')
>>> print(c)
c
>>> print(d)
d
>>> def ctimesd():
... """
... This function returns whatever c is times whatever d is.
... """
... return c*d
...
>>> ctimesd()
c*d
>>> c = 2
>>> print(c)
2
>>> ctimesd()
2*d
如果定义了循环关系,将会出现 RuntimeError
。
>>> def a():
... return b()
...
>>> def b():
... return a()
...
>>> a()
Traceback (most recent call last):
File "...", line ..., in ...
compileflags, 1) in test.globs
File "<...>", line 1, in <module>
a()
File "<...>", line 2, in a
return b()
File "<...>", line 2, in b
return a()
File "<...>", line 2, in a
return b()
...
RuntimeError: maximum recursion depth exceeded
注意
另请参阅 Glossary 中的 不可变。
符号
符号是变量,与所有其他变量一样,使用前需要赋值。例如:
>>> import sympy
>>> z**2 # z is not defined yet
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'z' is not defined
>>> sympy.var('z') # This is the easiest way to define z as a standard symbol
z
>>> z**2
z**2
如果您使用 isympy,它会为您运行以下命令,为您提供一些默认的符号和函数。
>>> from __future__ import division
>>> from sympy import *
>>> x, y, z, t = symbols('x y z t')
>>> k, m, n = symbols('k m n', integer=True)
>>> f, g, h = symbols('f g h', cls=Function)
您还可以从 sympy.abc
导入常见符号名称。
>>> from sympy.abc import w
>>> w
w
>>> import sympy
>>> dir(sympy.abc)
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'Symbol', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'__builtins__', '__doc__', '__file__', '__name__', '__package__', '_greek',
'_latin', 'a', 'alpha', 'b', 'beta', 'c', 'chi', 'd', 'delta', 'e',
'epsilon', 'eta', 'f', 'g', 'gamma', 'h', 'i', 'iota', 'j', 'k', 'kappa',
'l', 'm', 'mu', 'n', 'nu', 'o', 'omega', 'omicron', 'p', 'phi', 'pi',
'psi', 'q', 'r', 'rho', 's', 'sigma', 't', 'tau', 'theta', 'u', 'upsilon',
'v', 'w', 'x', 'xi', 'y', 'z', 'zeta']
如果您希望控制变量的假设条件,请使用 Symbol
和 symbols()
。请参阅下面的关键字参数。
最后,建议不要使用 I
, E
, S
, N
, C
, O
, 或 Q
作为变量或符号名称,因为它们分别用于虚数单位 ((i)),自然对数的底 ((e)),sympify()
函数(见下文的符号表达式),数值评估(N()
相当于 evalf() ),大 O 表示法(如 (O(n\log{n}))),以及保存支持的查询键列表的假设对象(例如 Q.real
)。建议记住这些符号的助记符 OSINEQ
,以了解 SymPy 中默认定义的符号。或者更好的做法是,总是使用小写字母作为符号名称。Python 不会阻止您覆盖默认的 SymPy 名称或函数,因此请小心。
>>> cos(pi) # cos and pi are a built-in sympy names.
-1
>>> pi = 3 # Notice that there is no warning for overriding pi.
>>> cos(pi)
cos(3)
>>> def cos(x): # No warning for overriding built-in functions either.
... return 5*x
...
>>> cos(pi)
15
>>> from sympy import cos # reimport to restore normal behavior
要获得 SymPy 中所有默认名称的完整列表,请执行以下操作:
>>> import sympy
>>> dir(sympy)
# A big list of all default sympy names and functions follows.
# Ignore everything that starts and ends with __.
如果您已安装 IPython 并使用 isympy,您还可以按 TAB 键获取所有内置名称的列表并进行自动完成。另请参阅此页面,了解如何在常规 Python 控制台中实现 TAB 完成的技巧。
注意
参见 最佳实践 页面的 定义符号 部分。### 函数
类似 f(x)
的函数可以通过定义函数和变量来创建:
>>> from sympy import Function
>>> f = Function('f')
>>> x = Symbol('x')
>>> f(x)
f(x)
如果你将 f(x)
赋给 Python 变量 ( f ),你将失去复制和粘贴该函数或创建具有不同参数的函数的能力:Function('f')
可调用,但 Function('f')(x)
不可:
>>> f1 = Function('f1')
>>> f2 = Function('f2')('x')
>>> f1
f1
>>> f2
f2(x)
>>> f1(1)
f1(1)
>>> f2(1)
Traceback (most recent call last):
...
TypeError: 'f2' object is not callable
>>> f2.subs(x, 1)
f2(1)
``` ## 符号表达式
### Python 数字 vs. SymPy 数字
SymPy 使用其自己的整数、有理数和浮点数类,而不是默认的 Python `int` 和 `float` 类型,因为它允许更多的控制。但你必须小心。如果你输入一个只有数字的表达式,它将默认为 Python 表达式。使用`sympify()`函数,或者仅仅使用`S`,以确保某物是 SymPy 表达式。
```py
>>> 6.2 # Python float. Notice the floating point accuracy problems.
6.2000000000000002
>>> type(6.2) # <type 'float'> in Python 2.x, <class 'float'> in Py3k
<... 'float'>
>>> S(6.2) # SymPy Float has no such problems because of arbitrary precision.
6.20000000000000
>>> type(S(6.2))
<class 'sympy.core.numbers.Float'>
如果你在 SymPy 表达式中包含数字,它们将自动变为 SymPy 类型,但你应该注意一个陷阱。如果在 SymPy 表达式中执行 <数字>/<数字>
,Python 将在 SymPy 处理它们之前对这两个数字进行求值。解决方案是使用sympify()
其中一个数字,或使用Rational
。
>>> x**(1/2) # evaluates to x**0 or x**0.5
x**0.5
>>> x**(S(1)/2) # sympyify one of the ints
sqrt(x)
>>> x**Rational(1, 2) # use the Rational class
sqrt(x)
使用 1/2
的幂也可以使用 sqrt
的简写:
>>> sqrt(x) == x**Rational(1, 2)
True
如果两个整数没有直接用除号分开,那么你不必担心这个问题:
>>> x**(2*x/3)
x**(2*x/3)
注意
常见的错误是复制并重用打印的表达式。如果表达式中包含一个有理数
(即 <数字>/<数字>
),你将得到 Python 除法的结果,而不是 SymPy 的有理数。
>>> x = Symbol('x')
>>> print(solve(7*x -22, x))
[22/7]
>>> 22/7 # If we just copy and paste we get int 3 or a float
3.142857142857143
>>> # One solution is to just assign the expression to a variable
>>> # if we need to use it again.
>>> a = solve(7*x - 22, x)[0]
>>> a
22/7
另一个解决方案是在表达式周围加上引号,并通过 S()运行它(即,对其进行 sympify):
>>> S("22/7")
22/7
此外,如果你不使用 isympy,你可以使用 from __future__ import division
来防止 /
符号执行整数除法。
>>> from __future__ import division >>> 1/2 # With division imported it evaluates to a python float 0.5 >>> 1//2 # You can still achieve integer division with // 0
但要小心:现在你将收到浮点数,而不是你可能想要的有理数:
>>> x**(1/2) x**0.5
Rational
仅适用于数字/数字,并且仅适用于有理数。如果你想要一个包含符号或表达式的分数,只需使用 /
。如果你做数字/表达式或表达式/数字,则该数字将自动转换为 SymPy 数字。你只需小心处理数字/数字。
>>> Rational(2, x)
Traceback (most recent call last):
...
TypeError: invalid input: x
>>> 2/x
2/x
使用浮点数和有理数评估表达式
SymPy 跟踪Float
对象的精度。默认精度为 15 位小数。当计算涉及Float
的表达式时,结果将被表达为 15 位的精度,但这些位数(取决于计算中涉及的数字)可能不全是有效的;
首先要记住的问题是如何创建Float
:它是用一个值和一个精度创建的。精度表示在评估该Float
(或其出现在其中的表达式)时要使用多精确的值。
这些值可以作为字符串、整数、浮点数或有理数给出;
- 字符串和整数被解释为精确值;
>>> Float(100) 100.000000000000 >>> Float('100', 5) 100.00
- 为了使精度与数字的位数匹配,可以使用空字符串来表示精度;
>>> Float(100, '') 100. >>> Float('12.34') 12.3400000000000 >>> Float('12.34', '') 12.34
>>> s, r = [Float(j, 3) for j in ('0.25', Rational(1, 7))] >>> for f in [s, r]: ... print(f) 0.250 0.143
接下来,注意每一个值在 3 位数看起来都是正确的。但是如果我们尝试将它们评估到 20 位数,一个差异将变得明显:
0.25
(精度为 3)表示一个具有非重复二进制小数的数字;1/7
在二进制和十进制中是重复的 - 它不能在这些前三位数之外精确表示(正确的十进制是一个重复的 142857);>>> s.n(20) 0.25000000000000000000 >>> r.n(20) 0.14285278320312500000
重要的是要意识到,尽管
Float
以任意精度的十进制形式显示,但实际上它以二进制形式存储。一旦创建了Float
,其二进制信息就以给定的精度设置。该值的准确性不能后续更改;因此,对于 3 位数精度的 1/7,可以用二进制零填充,但这些不会使其成为 1/7 更准确的值;
如果涉及到不精确、低精度数字与高精度值的计算,则evalf
引擎将增加低精度值的精度,并获得不精确的结果。这是具有有限精度的计算特性;
>>> Float('0.1', 10) + Float('0.1', 3)
0.2000061035
尽管evalf
引擎尝试维持 10 位精度(因为那是最高表示的精度),但使用的 3 位精度限制了精度约为 4 位数 - 你看到的并非所有位数都是有效的。evalf
不试图跟踪有效数字的数量;
那个非常简单的涉及两个不同精度数字相加的表达式,希望能够帮助你理解为什么更复杂的表达式(比如可能没有简化的三角表达式)即使正确简化也不会评估为精确的零。考虑这个未简化的三角同一式,乘以一个大数:
>>> big = 12345678901234567890
>>> big_trig_identity = big*cos(x)**2 + big*sin(x)**2 - big*1
>>> abs(big_trig_identity.subs(x, .1).n(2)) > 1000
True
当将cos
和sin
项评估为 15 位精度并乘以大数时,它们给出了一个大约 15 位精度的大数,当 20 位大数被减去时,结果不为零;
有三件事可以帮助你获得更精确的表达式数值:
- 通过调用评估传递所需的替换。通过首先进行替换,可以防止 Float 值按需更新。通过在 evalf 调用中传递所需的替换,可以重新评估的能力是获得印象深刻更好的结果:
>>> big_trig_identity.n(2, {x: 0.1}) -0.e-91
- 使用有理数,而不是浮点数。在评估过程中,有理数可以计算到任意精度,而浮点数一旦创建(默认为 15 位数字)就不能这样做。将 x 替换为表示 1/10 的有理数后,在进行评估之前,将上述-1.4e+3 的值与几乎为零的值进行比较:
>>> big_trig_identity.subs(x, S('1/10')).n(2) 0.e-91
- 尝试简化表达式。在这种情况下,SymPy 将识别三角恒等式并将其简化为零,因此您甚至不需要数值评估它:
>>> big_trig_identity.simplify() 0
表达式的不可变性
在 SymPy 中,表达式是不可变的,不能通过原位操作修改。这意味着函数始终会返回一个对象,而原始表达式不会被修改。以下示例代码片段演示了这是如何工作的:
def main():
var('x y a b')
expr = 3*x + 4*y
print('original =', expr)
expr_modified = expr.subs({x: a, y: b})
print('modified =', expr_modified)
if __name__ == "__main__":
main()
输出显示subs()
函数已经用变量a
替换了变量x
,并且用变量b
替换了变量y
:
original = 3*x + 4*y
modified = 3*a + 4*b
subs()
函数不会修改原始表达式expr
。相反,它会返回表达式的修改副本。这个返回的对象存储在变量expr_modified
中。请注意,与 C/C++和其他高级语言不同,Python 不要求您在使用变量之前声明它。
数学运算符
SymPy 使用与 Python 相同的默认操作符。其中大多数,如 */+-
,都是标准的。除了在 Python 数字 vs SymPy 数字中讨论的整数除法之外,还应注意,隐含乘法是不允许的。每当需要乘以某些东西时,都需要使用 *
。此外,要提升某个值的幂,请使用 **
,而不是许多计算机代数系统使用的 ^
。括号 ()
可以按您通常预期的方式更改操作符优先级。
在isympy中,使用ipython shell:
>>> 2x
Traceback (most recent call last):
...
SyntaxError: invalid syntax
>>> 2*x
2*x
>>> (x + 1)² # This is not power. Use ** instead.
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for ^: 'Add' and 'int'
>>> (x + 1)**2
(x + 1)**2
>>> pprint(3 - x**(2*x)/(x + 1))
2*x
x
- ----- + 3
x + 1
反三角函数
SymPy 对于某些函数使用不同的名称,而不是大多数计算机代数系统所用的通用名称。特别是,反三角函数使用 Python 的名称asin
、acos
等,而不是通常的arcsin
和arccos
。使用上面符号描述的方法查看所有 SymPy 函数的名称。
Sqrt 不是一个函数
sqrt
函数不像指数函数 (exp
) 那样存在。sqrt(x)
用于表示 Pow(x, S(1)/2)
,因此如果要确定表达式中是否有平方根,expr.has(sqrt)
将无效。必须查找指数为一半的 Pow
(如果是分母,则为负一半,例如:
>>> (y + sqrt(x)).find(Wild('w')**S.Half)
{sqrt(x)}
>>> (y + 1/sqrt(x)).find(Wild('w')**-S.Half)
{1/sqrt(x)}
如果您对 sqrt
的任何幂次方感兴趣,则以下模式是合适的
>>> sq = lambda s: s.is_Pow and s.exp.is_Rational and s.exp.q == 2
>>> (y + sqrt(x)**3).find(sq)
{x**(3/2)}
特殊符号
符号 []
, {}
, =
, 和 ()
在 Python 中有特殊意义,因此在 SymPy 中也是如此。有关更多信息,请参阅上面链接的 Python 文档。
列表
方括号 []
表示列表。列表是一个容器,可以容纳任意数量的不同对象。列表可以包含任何内容,包括不同类型的项目。列表是可变的,这意味着可以在创建后更改列表的元素。还可以使用方括号访问列表或列表变量的项目。项目使用空间号进行编号。
注意
列表索引从 0 开始。
示例:
>>> a = [x, 1] # A simple list of two items
>>> a
[x, 1]
>>> a[0] # This is the first item
x
>>> a[0] = 2 # You can change values of lists after they have been created
>>> print(a)
[2, 1]
>>> print(solve(x**2 + 2*x - 1, x)) # Some functions return lists
[-1 + sqrt(2), -sqrt(2) - 1]
注意
有关列表和使用方括号访问列表元素的更多信息,请参阅 Python 文档。
字典
花括号 {}
表示字典,简称为 dict。字典是一个无序的非重复键值对列表。语法是 {key: value}
。可以使用方括号表示法访问键的值。
>>> d = {'a': 1, 'b': 2} # A dictionary.
>>> d
{'a': 1, 'b': 2}
>>> d['a'] # How to access items in a dict
1
>>> roots((x - 1)**2*(x - 2), x) # Some functions return dicts
{1: 2, 2: 1}
>>> # Some SymPy functions return dictionaries. For example,
>>> # roots returns a dictionary of root:multiplicity items.
>>> roots((x - 5)**2*(x + 3), x)
{-3: 1, 5: 2}
>>> # This means that the root -3 occurs once and the root 5 occurs twice.
注意
有关字典的更多信息,请参阅 Python 文档。
元组
括号 ()
除了改变运算符优先级和在函数调用中的使用(如 cos(x)
)外,还用于元组。tuple
和 list 完全相同,但不可变。这意味着创建后不能更改它们的值。在 SymPy 中通常不需要使用元组,但有时使用括号代替方括号可能更方便。
>>> t = (1, 2, x) # Tuples are like lists >>> t (1, 2, x) >>> t[0] 1 >>> t[0] = 4 # Except you cannot change them after they have been created Traceback (most recent call last): File "<console>", line 1, in <module> TypeError: 'tuple' object does not support item assignment
单元素元组与列表不同,必须在其中包含逗号:
>>> (x,) (x,)
没有逗号,单个表达式没有逗号不是元组:
>>> (x) x
integrate 将序列作为第二个参数,如果要带上限积分(列表或元组都可以使用):
>>> integrate(x**2, (x, 0, 1)) 1/3 >>> integrate(x**2, [x, 0, 1]) 1/3
注意
有关元组的更多信息,请参阅 Python 文档。
关键字参数
除了上述用法外,等号 (=
) 还用于将命名参数传递给函数。任何在参数列表中带有 key=value
的函数(请参阅下面如何查找此信息),则默认情况下将 key
设置为 value
。您可以通过在函数调用中使用等号更改键的值。还有一些带有参数列表中的 **
后面跟有名称的函数(通常是 **kwargs
或 **assumptions
),允许您添加任意数量的 key=value
对,它们将根据函数进行评估。
sqrt(x**2)
不会自动简化为 x,因为默认情况下假定 x 是复数,并且,例如,sqrt((-1)**2) == sqrt(1) == 1 != -1
:>>> sqrt(x**2) sqrt(x**2)
给 Symbols 添加假设是使用关键字参数的示例:
>>> x = Symbol('x', positive=True)
现在,平方根会简化,因为它知道
x >= 0
:>>> sqrt(x**2) x
powsimp
的默认参数为combine='all'
:>>> pprint(powsimp(x**n*x**m*y**n*y**m)) m + n (x*y)
将
combine
设置为默认值等同于不设置它。>>> pprint(powsimp(x**n*x**m*y**n*y**m, combine='all')) m + n (x*y)
非默认选项是
'exp'
,它结合指数…>>> pprint(powsimp(x**n*x**m*y**n*y**m, combine='exp')) m + n m + n x *y
…和‘base’,它合并基数。
>>> pprint(powsimp(x**n*x**m*y**n*y**m, combine='base')) m n (x*y) *(x*y)
注意
有关函数参数的更多信息,请参阅 Python 文档。
从 SymPy 中获取帮助
帮助()
尽管所有文档都可以在 docs.sympy.org 或 SymPy Wiki 上找到,但您也可以从运行 SymPy 的 Python 解释器内部获取函数信息。最简单的方法是执行 help(function)
或者在使用 ipython 时执行 function?
:
In [1]: help(powsimp) # help() works everywhere
In [2]: # But in ipython, you can also use ?, which is better because it
In [3]: # it gives you more information
In [4]: powsimp?
这些将为您提供 powsimp()
的函数参数和文档字符串。输出将类似于以下内容:
sympy.simplify.simplify.powsimp(expr, deep=False, combine='all', force=False, measure=<function count_ops>)
通过合并具有相似基数和指数的幂来简化表达式。
解释
如果 deep
是 True
,那么 powsimp() 还将简化函数的参数。默认情况下,deep
设置为 False
。
如果 force
是 True
,则将合并基数而不检查假设,例如,sqrt(x)sqrt(y) -> sqrt(xy),如果 x 和 y 都是负数,则不成立。
您可以通过更改 combine='base'
或 combine='exp'
使 powsimp() 仅组合基数或仅组合指数。默认情况下,combine='all'
,即两者都做。combine='base'
仅会组合:
a a a 2x x
x * y => (x*y) as well as things like 2 => 4
并且 combine='exp'
将仅组合
a b (a + b)
x * x => x
combine='exp'
严格只组合以前自动完成的指数方式。如果需要旧行为,请同时使用 deep=True
。
当 combine='all'
时,首先评估 'exp'。考虑下面的第一个例子,当可能涉及此问题时可能会出现歧义。这样做是为了完全组合类似第二个例子的内容。如果您希望首先组合 'base',请像这样执行 powsimp(powsimp(expr, combine='base'), combine='exp')。
例子
>>> from sympy import powsimp, exp, log, symbols
>>> from sympy.abc import x, y, z, n
>>> powsimp(x**y*x**z*y**z, combine='all')
x**(y + z)*y**z
>>> powsimp(x**y*x**z*y**z, combine='exp')
x**(y + z)*y**z
>>> powsimp(x**y*x**z*y**z, combine='base', force=True)
x**y*(x*y)**z
>>> powsimp(x**z*x**y*n**z*n**y, combine='all', force=True)
(n*x)**(y + z)
>>> powsimp(x**z*x**y*n**z*n**y, combine='exp')
n**(y + z)*x**(y + z)
>>> powsimp(x**z*x**y*n**z*n**y, combine='base', force=True)
(n*x)**y*(n*x)**z
>>> x, y = symbols('x y', positive=True)
>>> powsimp(log(exp(x)*exp(y)))
log(exp(x)*exp(y))
>>> powsimp(log(exp(x)*exp(y)), deep=True)
x + y
如果 combine='exp'
,则具有 Mul 基数的根号将被组合
>>> from sympy import sqrt
>>> x, y = symbols('x y')
两个根号会通过 Mul 自动连接:
>>> a=sqrt(x*sqrt(y))
>>> a*a**3 == a**4
True
但是,如果根号的整数次幂已经自动展开,则 Mul 不会连接结果因子:
>>> a**4 # auto expands to a Mul, no longer a Pow
x**2*y
>>> _*a # so Mul doesn't combine them
x**2*y*sqrt(x*sqrt(y))
>>> powsimp(_) # but powsimp will
(x*sqrt(y))**(5/2)
>>> powsimp(x*y*a) # but won't when doing so would violate assumptions
x*y*sqrt(x*sqrt(y))
按类型解决输出
solve()
函数的输出看起来非常笨重,因为它可能表现出任意返回六种不同类型的输出(除了引发错误)。这些原因是历史性的,倾向于人类互动而不是程序化使用。输出类型将取决于方程的类型(及其输入方式)以及提供的符号数量(及其提供方式)。
>>> from sympy import sqrt, exp, solve, Symbol, Eq >>> from sympy.abc import x, y, z, a, b
solve()
函数尝试找到尽可能多的符号值,使得给定的每个表达式等于零。输出的格式可以通过使用dict
或set
关键字来控制:>>> solve(x - 1, dict=True) [{x: 1}] >>> solve([x**2 - y, x + y - 6], set=True) ([x, y], {(-3, 9), (2, 4)})
下面的讨论说明了在不使用这些关键字时获得的输出的解释。
空列表
当没有解时,返回一个空列表。
>>> solve(sqrt(x) + 1) # or solve(sqrt(x) + 1, dict=True) [] >>> solve(sqrt(x) + 1, set=True) ([x], set())
值列表
当解决符号在上下文中明确时,给出一个值列表,因为 a)方程是单变量的或者 b)单个符号被指定为感兴趣的。
>>> solve(x**2 - 4) [-2, 2] >>> solve(x - y - 1, x) [y + 1]
单一字典
当方程作为列表传递并且所有符号在给定的方程中均为线性时,结果为单个字典,键为符号,值为这些符号的解。注意:如果对于指定的符号存在未确定系数的解,则会自动生成这样的系统以解决单个方程(不作为列表传递)。如果这不是预期的结果,请将表达式作为列表传递。
>>> solve([x + y - 2, x - y + 2], x, y) {x: 0, y: 2} >>> eq = a*x - 2*x + b - 5 >>> solve(eq, {a, b}) # undetermined coefficients {a: 2, b: 5} >>> solve([eq], {a, b}) # algebraic {a: -b/x + (2*x + 5)/x}
元组列表
列表中的每个元组按给定顺序给出符号的解。当 a)方程列表包含至少一个非线性方程或 b)符号列表按照明确定义的顺序给出时,使用此格式。(这也是在使用标志
set=True
时返回的集合中元组的格式。)>>> solve(x - 1, x, y) # more than one symbol [(1, y)] >>> solve([x**2], x) # list with nonlinear equation [(0,)] >>> solve([x**2 - 1], x) [(-1,), (1,)] >>> solve([x**2 - y, x - 3], x, y) # nonlinear and multiple symbols [(3, 9)]
字典列表
当表达式不是单变量或列表中存在非线性表达式且符号顺序可能会因 a)未传递符号或 b)符号被传递为集合而产生歧义时,返回字典列表。(这也是使用
dict=True
选择的格式。)>>> solve(x - y) [{x: y}] >>> solve([exp(x) - 1, x*(x - 1)]) [{x: 0}] >>> system = [x + y - z, x**2 - y + z, exp(z) + 1/x + 1/y - 2] >>> sol = solve(system[:2]); sol [{x: -1, y: z + 1}, {x: 0, y: z}]
字典仅包含与键不同的值。在上述最后一个示例中,字典中没有
z
的键,因为仅两个三个方程不足以确定其值。然而,这些解可以用于消除第三个方程中的变量,从而给出可以解决(可能是数值上的)以获得仅需猜测单个值而不是三个值的全解的单变量关系。>>> from sympy import nsolve >>> [system[-1].subs(s) for s in sol] [exp(z) - 3 + 1/(z + 1), exp(z) + zoo + 1/z] >>> z_eq = _[0] >>> zsol = nsolve(z_eq, 1); zsol 0.906425478894557 >>> sol0 = {k: v.subs(z, zsol) for k, v in sol[0].items()} >>> sol0[z] = zsol; sol0 {x: -1, y: 1.90642547889456, z: 0.906425478894557}
布尔或关系
当将一个与
Equality
不同的关系表达式作为要解析的表达式时,返回一个布尔表达式。可能会返回一个单个的(Equality)或更复杂的关系表达式。在这里使用solve()
相当于将方程组和符号传递给reduce_inequalities()
(并且dict
、set
和check
标志将被忽略)。>>> solve([x**2 > 4, x > 0]) (2 < x) & (x < oo)
>>> from sympy import Unequality as Ne >>> solve([x**2 - 4, Ne(x, -2)]) Eq(x, 2)
任何返回的(Equality)可以转换为字典:
>>> {_.lhs: _.rhs} {x: 2}
SymPy 专题
原文:
docs.sympy.org/latest/explanation/special_topics/index.html
此系列文档的目的是为了向 SymPy 的用户提供一些不完全是教程或比教程更长的主题。随着 SymPy 的发展和用户发现更多 SymPy 在更高级主题中的应用方式,这些文档希望填补一个空白。
-
有限差分逼近导数
-
介绍
-
使用 SymPy 矩阵的直接方法
-
-
SymPy 对象的分类
-
类
-
种类
-
集合和假设
-
函数
-
导数的有限差分逼近
原文:
docs.sympy.org/latest/explanation/special_topics/finite_diff_derivatives.html
引言
在数值分析和计算物理中,导数的有限差分逼近非常重要。在本教程中,我们展示如何使用 SymPy 计算不同精度的逼近值。希望这些笔记能对需要在某种语言中编写代码且需要高效生成各种逼近公式的研究人员有所帮助。
为了明确符号,我们首先声明:我们设想存在一个关于单变量 (x) 的连续函数 (F),其具有所需的所有导数。我们在实轴上均匀地以间隔 (h) 采样 (x) 值。在大多数情况下,我们希望 (h) 在某种意义上足够小。可以关于某一点 (x_{0}) 展开 (F(x)) 的泰勒级数展开式。令 (x = x_{0} + h)。那么泰勒展开式为
[F(x_{0}+h) = F(x_{0}) + \big(\frac{dF}{dx}\big){x{0}} * h + \frac{1}{2!} \big(\frac{d^{2}F }{dx^{2}}\big){x{0}}* h² + \frac{1}{3!} \big(\frac{d^{3}F }{dx^{3}}\big){x{0}}* h³ + ...]
为简化表示,我们现在定义一组系数 (c_{n}),其中
[c_{n} := \frac{1}{n!} \big(\frac{d^{n}F }{dx^{n}}\big){x{0}}.]
现在我们的级数形式如下:
[F(x_{0}+h) = F(x_{0}) + c_{1} * h + c_{2}* h² + c_{3}* h³ + ...]
我们将仅使用有限网格上的数值 (x_{i}),其中 (i) 从 (1,...,N),以及在这些网格点上对应的函数 (F) 的数值 (F_{i})。因此问题在于如何生成 (F) 的导数的近似值,条件是我们只能使用大小为 (N) 的有限对 ((x_{i},F_{i})) 的子集。
接下来使用 SymPy 进行操作,以制定给定阶数导数的逼近并评估其精度。首先,我们使用 SymPy 通过一种常见但效率较低的方法推导逼近值。稍后,我们将利用其他 SymPy 函数来更高效地完成这项工作。
使用 SymPy 矩阵的直接方法
如果我们令 (x_{0} = x_{i}),在 (x_{i+1}=x_{i}+ h) 处评估级数,并截断所有高于 (O(h¹)) 的项,我们可以解出单一系数 (c_{1}),从而获得一阶导数的近似值:
[\big(\frac{dF}{dx}\big){x{0}} \approx \frac{F_{i+1} - F_{i}}{h} + O(h)]
这里的 (O(h)) 是指 (h) 级数中的最低阶项。这确立了导数逼近是一阶精度的事实。换句话说,如果我们只能使用两对 ((x_{i},F_{i})) 和 ((x_{i+1},F_{i+1})),我们得到一个“一阶精度”的导数逼近。
除了 ((x_{i},F_{i})) 外,我们接下来使用 ((x_{i+1},F_{i+1})) 和 ((x_{i+2},F_{i+2})) 两个点。然后我们有两个方程:
[F_{i+1} = F_{i} + c_{1}* h + \frac{1}{2}c_{2}h² + \frac{1}{3!}c_{3}h³ + ...][F_{i+2} = F_{i} + c_{1}* (2h) + \frac{1}{2}c_{2}(2h)² + \frac{1}{3!}c_{3}(2h)³ + ...]
如果我们再次想要找到第一阶导数((c_{1})),我们可以通过消除涉及 (c_{2}) 的项来实现这一点。我们展示如何使用 SymPy 完成这一过程。
>>> from __future__ import print_function
>>> from sympy import *
>>> x, x0, h = symbols('x, x_0, h')
>>> Fi, Fip1, Fip2 = symbols('F_{i}, F_{i+1}, F_{i+2}')
>>> n = 3 # there are the coefficients c_0=Fi, c_1=dF/dx, c_2=d**2F/dx**2
>>> c = symbols('c:3')
>>> def P(x, x0, c, n):
... return sum( ((1/factorial(i))*c[i] * (x-x0)**i for i in range(n)) )
右手边的向量:
>>> R = Matrix([[Fi], [Fip1], [Fip2]])
现在我们构造一个矩阵,其中包含多项式 P 中 (c_i) 的系数。
在 (x_i) 处评估的 (c_i) 的系数:
>>> m11 = P(x0 , x0, c, n).diff(c[0])
>>> m12 = P(x0 , x0, c, n).diff(c[1])
>>> m13 = P(x0 , x0, c, n).diff(c[2])
在 (x_i + h) 处评估的 (c_i) 的系数:
>>> m21 = P(x0+h, x0, c, n).diff(c[0])
>>> m22 = P(x0+h, x0, c, n).diff(c[1])
>>> m23 = P(x0+h, x0, c, n).diff(c[2])
在 (x_i + 2*h) 处评估的 (c_i) 的系数:
>>> m31 = P(x0+2*h, x0, c, n).diff(c[0])
>>> m32 = P(x0+2*h, x0, c, n).diff(c[1])
>>> m33 = P(x0+2*h, x0, c, n).diff(c[2])
在这种情况下,系数矩阵是 3x3 的:
>>> M = Matrix([[m11, m12, m13], [m21, m22, m23], [m31, m32, m33]])
(c_i) 的三个方程的矩阵形式是 M*X = R:
解决方案通过直接反转 3x3 矩阵 M 获得:
>>> X = M.inv() * R
注意,所有三个系数构成了解。所需的第一阶导数是系数 (c_1),即 X[1]。
>>> print(together(X[1]))
(4*F_{i+1} - F_{i+2} - 3*F_{i})/(2*h)
计算另一个三点近似的第一阶导数是有益的,除了在 (x_i) 处居中近似,因此使用 (x_{i-1})、(x_{i}) 和 (x_{i+1}) 三个点。这里是使用“暴力方法”完成这个过程的方法:
>>> from __future__ import print_function
>>> from sympy import *
>>> x, x0, h = symbols('x, x_i, h')
>>> Fi, Fim1, Fip1 = symbols('F_{i}, F_{i-1}, F_{i+1}')
>>> n = 3 # there are the coefficients c_0=Fi, c_1=dF/h, c_2=d**2F/h**2
>>> c = symbols('c:3')
>>> # define a polynomial of degree n
>>> def P(x, x0, c, n):
... return sum( ((1/factorial(i))*c[i] * (x-x0)**i for i in range(n)) )
>>> # now we make a matrix consisting of the coefficients
>>> # of the c_i in the nth degree polynomial P
>>> # coefficients of c_i evaluated at x_i
>>> m11 = P(x0 , x0, c, n).diff(c[0])
>>> m12 = P(x0 , x0, c, n).diff(c[1])
>>> m13 = P(x0 , x0, c, n).diff(c[2])
>>> # coefficients of c_i evaluated at x_i - h
>>> m21 = P(x0-h, x0, c, n).diff(c[0])
>>> m22 = P(x0-h, x0, c, n).diff(c[1])
>>> m23 = P(x0-h, x0, c, n).diff(c[2])
>>> # coefficients of c_i evaluated at x_i + h
>>> m31 = P(x0+h, x0, c, n).diff(c[0])
>>> m32 = P(x0+h, x0, c, n).diff(c[1])
>>> m33 = P(x0+h, x0, c, n).diff(c[2])
>>> # matrix of the coefficients is 3x3 in this case
>>> M = Matrix([[m11, m12, m13], [m21, m22, m23], [m31, m32, m33]])
现在我们有了系数矩阵,接下来形成右侧并通过反转 (M) 解决:
>>> # matrix of the function values...actually a vector of right hand sides
>>> R = Matrix([[Fi], [Fim1], [Fip1]])
>>> # matrix form of the three equations for the c_i is M*X = R
>>> # solution directly inverting the 3x3 matrix M:
>>> X = M.inv() * R
>>> # note that all three coefficients make up the solution
>>> # the first derivative is coefficient c_1 which is X[1].
>>> print("The second-order accurate approximation for the first derivative is: ")
The second-order accurate approximation for the first derivative is:
>>> print(together(X[1]))
(F_{i+1} - F_{i-1})/(2*h)
这两个例子展示了如何使用 SymPy 直接找到二阶精确的一阶导数。第一个例子使用了 (x_i)、(x_{i+1}) 和 (x_{i+2}) 所有三点的 (x) 和 (F) 值,而第二个例子仅使用了 (x_{i-1}) 和 (x_{i+1}) 两点的 (x) 值,因此效率更高一些。
从这两个简单的例子中可以得出一个一般规则,即如果想要一阶导数精确到 (O(h^{n})),那么在逼近多项式中需要 n+1 个函数值(通过函数 (P(x,x0,c,n)) 提供)。
现在让我们评估中心差分结果的精确性,看看如何确定它确实是二阶的。为此,我们将 (dF/dx) 的结果代入更高阶多项式的展开式中,看看我们得到什么。为此,我们制作了一组八个系数 d,并用它们执行检查:
>>> d = symbols('c:8')
>>> dfdxcheck = (P(x0+h, x0, d, 8) - P(x0-h, x0, d, 8))/(2*h)
>>> print(simplify(dfdxcheck)) # so the appropriate cancellation of terms involving `h` happens
c1 + c3*h**2/6 + c5*h**4/120 + c7*h**6/5040
因此,我们看到导数确实是 (c_1),下一个阶数的级数为 (h²)。
然而,当试图生成高阶(如 6 或 8 阶)的导数近似时,通常很快会变得相当乏味,尽管这种方法确实有效,并且使用现有方法肯定比手动计算要少费力。
正如我们在上面的讨论中看到的,对于第一导数的简单中心近似只使用 ((x_{i},F_{i})) 对的两个点值。这在遇到域中的最后一个点时就会出现问题,比如在 (i=N) 处。由于我们的中心导数近似会使用点 ((x_{N+1},F_{N+1})) 处的数据,我们可以看到导数公式将无法工作。那么,该怎么办呢?嗯,处理这个问题的简单方法是为这最后一个点设计一个使用我们有值的点的不同公式。这就是所谓的向后差分公式。为了得到它,我们可以使用同样的直接方法,但现在使用三个点 ((x_{N},F_{N}))、((x_{N-1},F_{N-1})) 和 ((x_{N-2},F_{N-2})) 并在 ((x_{N},F_{N})) 处进行近似。以下是使用 SymPy 完成它的方法:
>>> from __future__ import print_function
>>> from sympy import *
>>> x, xN, h = symbols('x, x_N, h')
>>> FN, FNm1, FNm2 = symbols('F_{N}, F_{N-1}, F_{N-2}')
>>> n = 8 # there are the coefficients c_0=Fi, c_1=dF/h, c_2=d**2F/h**2
>>> c = symbols('c:8')
>>> # define a polynomial of degree d
>>> def P(x, x0, c, n):
... return sum( ((1/factorial(i))*c[i] * (x-x0)**i for i in range(n)) )
现在我们制作一个矩阵,其中包含 (c_i) 在第 (d) 次多项式 (P) 系数在 (x_i, x_{i-1}) 和 (x_{i+1}) 处的系数:
>>> m11 = P(xN , xN, c, n).diff(c[0])
>>> m12 = P(xN, xN, c, n).diff(c[1])
>>> m13 = P(xN , xN, c, n).diff(c[2])
>>> # coefficients of c_i evaluated at x_i - h
>>> m21 = P(xN-h, xN, c, n).diff(c[0])
>>> m22 = P(xN-h, xN, c, n).diff(c[1])
>>> m23 = P(xN-h, xN, c, n).diff(c[2])
>>> # coefficients of c_i evaluated at x_i + h
>>> m31 = P(xN-2*h, xN, c, n).diff(c[0])
>>> m32 = P(xN-2*h, xN, c, n).diff(c[1])
>>> m33 = P(xN-2*h, xN, c, n).diff(c[2])
接下来,我们构造 (3 \times 3) 系数矩阵:
>>> M = Matrix([[m11, m12, m13], [m21, m22, m23], [m31, m32, m33]])
>>> # matrix of the function values...actually a vector of right hand sides
>>> R = Matrix([[FN], [FNm1], [FNm2]])
然后我们反转 (M) 并写出 (3 \times 3) 系统的解。
三个方程式 (c_i) 的矩阵形式是 (M*C = R)。通过直接求逆 (M) 来获得解决方案:
>>> X = M.inv() * R
第一导数是系数 (c_1),即 (X[1])。因此,第一导数的二阶精确逼近是:
>>> print("The first derivative centered at the last point on the right is:")
The first derivative centered at the last point on the right is:
>>> print(together(X[1]))
(-4*F_{N-1} + F_{N-2} + 3*F_{N})/(2*h)
当然,我们可以为点集合左端的点 ((x_{1},F_{1})) 的导数值设计类似的公式,该公式以 ((x_{2},F_{2})) 和 ((x_{3},F_{3})) 的值为基础。
此外,我们注意到输出适合 Fortran、C 等格式的示例在上述示例中可能已经完成。
接下来,我们展示如何执行这些以及许多其他导数的离散化,但使用一种更有效的方法,最初由本特·弗恩伯格(Bengt Fornberg)提出,并已纳入 SymPy 中。
有限差分
有限差分权重
SymPy 对象的分类
原文:
docs.sympy.org/latest/explanation/special_topics/classification.html
SymPy 对象分类的几种方法。
类
就像 Python 中的任何其他对象一样,SymPy 表达式是一个类的实例。您可以使用内置的type()
函数获取对象的类,并使用isinstance()
函数进行检查。
>>> from sympy import Add
>>> from sympy.abc import x,y
>>> type(x + y)
<class 'sympy.core.add.Add'>
>>> isinstance(x + y, Add)
True
类仅代表对象的程序结构,并且不能区分它们之间的数学差异。例如,数字的积分和矩阵的积分都具有Integral
类,尽管前者是数字,后者是矩阵。
>>> from sympy import MatrixSymbol, Integral
>>> A = MatrixSymbol('A', 2, 2)
>>> type(Integral(1, x))
<class 'sympy.integrals.integrals.Integral'>
>>> type(Integral(A, x))
<class 'sympy.integrals.integrals.Integral'>
种类
种类指示表达式表示什么数学对象。您可以使用.kind
属性检索表达式的种类。
>>> Integral(1, x).kind
NumberKind
>>> Integral(A, x).kind
MatrixKind(NumberKind)
这个结果表明Integral(1, x)
是一个数字,而Integral(A, x)
是一个带有数字元素的矩阵。
由于类不能保证捕获这种差异,对象的种类非常重要。例如,如果您正在构建一个仅设计用于数字工作的函数或类,则应考虑使用NumberKind
过滤参数,以便用户不会轻易传递不受支持的对象,例如Integral(A, x)
。
出于性能考虑,种类系统中未实现集合论。例如,
NumberKind
不能区分实数和复数。>>> from sympy import pi, I >>> pi.kind NumberKind >>> I.kind NumberKind
SymPy 的
Set
和种类是不兼容的。>>> from sympy import S >>> from sympy.core.kind import NumberKind >>> S.Reals.is_subset(S.Complexes) True >>> S.Reals.is_subset(NumberKind) Traceback (most recent call last): ... ValueError: Unknown argument 'NumberKind'
集合和假设
如果您想以严格的数学方式对对象进行分类,您可能需要使用 SymPy 的集合和假设。
>>> from sympy import ask, Q
>>> S.One in S.Reals
True
>>> ask(Q.even(2*x), Q.odd(x))
True
更多信息请参见assumptions
模块和sets
模块。
函数
func
是对象的头,并且用于递归遍历表达式树。
>>> Add(x + y).func
<class 'sympy.core.add.Add'>
>>> Add(x + x).func
<class 'sympy.core.mul.Mul'>
>>> Q.even(x).func
<class 'sympy.assumptions.assume.AppliedPredicate'>
如您所见,生成的头可能是一个类,也可能是另一个 SymPy 对象。在使用此属性对对象进行分类时,请牢记这一点。详细信息请参见高级表达式操作。
活动弃用列表
原文:
docs.sympy.org/latest/explanation/active-deprecations.html
此页面列出了 SymPy 代码库中的所有活动弃用。请参阅弃用政策页面,了解 SymPy 的弃用政策说明以及贡献者如何弃用内容的说明。
特别是,SymPy 的弃用政策要求在包含弃用功能的首个主要发布版本之后至少保留1 year。此后,将可能从 SymPy 中移除弃用功能,需要更新代码以使用替代功能以继续工作。
在弃用期间,每当使用弃用功能时,将打印 SymPyDeprecationWarning
消息。建议用户更新其代码,使其不再使用弃用功能,具体方法如下所述,适用于每个给定的弃用。
消除 SymPy 弃用警告
要消除 SymPy 的弃用警告,请使用warnings
模块添加过滤器。例如:
import warnings
from sympy.utilities.exceptions import SymPyDeprecationWarning
warnings.filterwarnings(
# replace "ignore" with "error" to make the warning raise an exception.
# This useful if you want to test you aren't using deprecated code.
"ignore",
# message may be omitted to filter all SymPyDeprecationWarnings
message=r"(?s).*<regex matching the warning message>",
category=SymPyDeprecationWarning,
module=r"<regex matching your module>"
)
这里的(?s).*<regex matching the warning message>
是匹配警告消息的正则表达式。例如,要过滤有关sympy.printing
的警告,可以使用message=r"(?s).*sympy\.printing"
。前导的(?s).*
是因为警告模块会将message
与警告消息的开头匹配,而典型的警告消息跨越多行。
<regex matching your module>
应为与使用弃用代码的模块匹配的正则表达式。建议包括此内容,以避免将相同警告也应用于不相关的模块。
可以使用相同模式将 SymPyDeprecationWarning
替换为错误,以便测试不使用弃用代码。要执行此操作,请在上述示例中将 "ignore"
替换为 "error"
。您还可以省略 message
,以便将其应用于所有 SymPyDeprecationWarning
警告。
如果您使用 pytest,可以使用pytest 警告过滤功能来忽略 SymPyDeprecationWarning
或将其转换为错误。
注意
Python -W
flag 和 PYTHONWARNINGS
环境变量 无法用于过滤 SymPy 弃用警告(请参阅 Ned Batchelder 的此篇博文和 SymPy 的此问题了解详细信息)。您需要添加类似上述的 warnings
过滤器或使用 pytest 来过滤 SymPy 弃用警告。
版本 1.13
弃用机制 Body 类
sympy.physics.mechanics
模块中的 Body
类已被弃用。它最初用于支持关节框架,但由于既表示刚体又表示粒子而导致多种问题。Body
现已完全由 RigidBody
和 Particle
替代。以前,只需使用 Body
类即可创建简单的刚体或粒子:
>>> from sympy import symbols
>>> from sympy.physics.mechanics import Body
>>> Body("rigid_body")
rigid_body
>>> Body("particle", mass=symbols("m"))
particle
现在应使用 RigidBody
和 Particle
类创建:
>>> from sympy.physics.mechanics import RigidBody, Particle
>>> RigidBody("rigid_body")
rigid_body
>>> Particle("particle")
particle
``` ### 弃用的力学 JointsMethod
`sympy.physics.mechanics` 模块中的 `JointsMethod` 类已被弃用。它最初用于支持关节框架,但由于设计上的限制已被完全替换。以前,可以构建仅由体和关节组成的系统,然后由 `JointsMethod` 解析到后端,例如 `KanesMethod` 以形成运动方程。
```py
>>> from sympy import symbols
>>> from sympy.physics.mechanics import (
... Body, JointsMethod, PinJoint, PrismaticJoint)
>>> g, l = symbols("g l")
>>> wall = Body("wall")
>>> cart = Body("cart")
>>> pendulum = Body("Pendulum")
>>> slider = PrismaticJoint("s", wall, cart, joint_axis=wall.x)
>>> pin = PinJoint("j", cart, pendulum, joint_axis=cart.z,
... child_point=l * pendulum.y)
>>> pendulum.masscenter.set_vel(pendulum.frame, 0)
>>> cart.apply_force(-g * cart.mass * wall.y)
>>> pendulum.apply_force(-g * pendulum.mass * wall.y)
>>> method = JointsMethod(wall, slider, pin)
>>> method.form_eoms()
Matrix([
[ Pendulum_mass*l*u_j(t)**2*sin(q_j(t)) - Pendulum_mass*l*cos(q_j(t))*Derivative(u_j(t), t) - (Pendulum_mass + cart_mass)*Derivative(u_s(t), t)],
[-Pendulum_mass*g*l*sin(q_j(t)) - Pendulum_mass*l*cos(q_j(t))*Derivative(u_s(t), t) - (Pendulum_izz + Pendulum_mass*l**2)*Derivative(u_j(t), t)]])
JointsMethod
的替代方案是 System
,可用于形成相同小车摆杆的运动方程,如下所示:
>>> from sympy import symbols
>>> from sympy.physics.mechanics import (
... Particle, PinJoint, PrismaticJoint, RigidBody, System)
>>> g, l = symbols("g l")
>>> wall = RigidBody("wall")
>>> cart = RigidBody("cart")
>>> pendulum = RigidBody("Pendulum")
>>> slider = PrismaticJoint("s", wall, cart, joint_axis=wall.x)
>>> pin = PinJoint("j", cart, pendulum, joint_axis=cart.z,
... child_point=l * pendulum.y)
>>> system = System.from_newtonian(wall)
>>> system.add_joints(slider, pin)
>>> system.apply_uniform_gravity(-g * wall.y)
>>> system.form_eoms()
Matrix([
[ Pendulum_mass*l*u_j(t)**2*sin(q_j(t)) - Pendulum_mass*l*cos(q_j(t))*Derivative(u_j(t), t) - (Pendulum_mass + cart_mass)*Derivative(u_s(t), t)],
[-Pendulum_mass*g*l*sin(q_j(t)) - Pendulum_mass*l*cos(q_j(t))*Derivative(u_s(t), t) - (Pendulum_izz + Pendulum_mass*l**2)*Derivative(u_j(t), t)]])
``` ### 弃用的矩阵混合类
矩阵混合类已弃用。以前的 `Matrix` 类(又名 `MutableDenseMatrix`)通过继承层次结构创建,看起来像:
```py
class MatrixRequired:
class MatrixShaping(MatrixRequired):
class MatrixSpecial(MatrixRequired):
class MatrixProperties(MatrixRequired):
class MatrixOperations(MatrixRequired):
class MatrixArithmetic(MatrixRequired):
class MatrixCommon(
MatrixArithmetic,
MatrixOperations,
MatrixProperties,
MatrixSpecial,
MatrixShaping):
class MatrixDeterminant(MatrixCommon):
class MatrixReductions(MatrixDeterminant):
class MatrixSubspaces(MatrixReductions):
class MatrixEigen(MatrixSubspaces)
class MatrixCalculus(MatrixCommon):
class MatrixDeprecated(MatrixCommon):
class MatrixBase(MatrixDeprecated,
MatrixCalculus,
MatrixEigen,
MatrixCommon,
Printable):
class RepMatrix(MatrixBase):
class DenseMatrix(RepMatrix):
class MutableRepMatrix(RepMatrix):
class MutableDenseMatrix(DenseMatrix, MutableRepMatrix):
自 SymPy 1.13 起,所有类都已简化,以上 MatrixBase
的类被合并在一起,层次结构如下:
class MatrixBase(Printable):
class RepMatrix(MatrixBase):
class DenseMatrix(RepMatrix):
class MutableRepMatrix(RepMatrix):
class MutableDenseMatrix(DenseMatrix, MutableRepMatrix):
像 MatrixRequired
等矩阵混合类仍然可用,因为下游代码可能正在对这些类进行子类化,但这些类均已弃用,并将在未来版本的 SymPy 中移除。对这些类的子类化已被弃用,任何这样做的代码应修改为不再子类化它们。
使用 isinstance
与 MatrixCommon
这样的类也已被弃用,例如 isinstance(M, MatrixCommon)
。任何使用此方法的代码应改为使用 isinstance(M, Matrixbase)
,这也适用于先前的 SymPy 版本。
更一般地,导入 sympy.matrices.common
或 sympy.matrices.matrices
模块中的任何内容都已弃用,这些模块将在未来的 SymPy 发布中移除。
这一变更的原因是复杂的继承层次结构使得很难改进大多数用户的 Matrix
,同时仍提供可以作为子类的所有这些类。由于这些混合类不再作为 Matrix
的一部分使用,它们在 SymPy 中不再起任何作用,移除现在未使用的代码将简化代码库。### sympify()
中的字符串回退
sympify
函数过去会将未识别的对象转换为字符串并重试 sympification。这在 SymPy 1.6 中已弃用,并在 SymPy 1.13 中移除。
sympify()
的行为是,sympify(expr)
尝试各种方法将 expr
转换为 SymPy 对象。以前,如果所有这些方法都失败了,它会取 str(expr)
并尝试使用 parse_expr()
进行解析。这个字符串回退功能在 SymPy 1.6 中已弃用,并在 SymPy 1.13 中删除。
这种行为存在几个问题:
-
它可能会严重影响性能。例如,参见问题 #18056 和 #15416,在这些问题中,它导致了高达 100 倍的减速。问题在于 SymPy 函数会自动对其参数调用
sympify
。每当一个函数被传递一个sympify
不知道如何转换为 SymPy 对象的东西,例如一个 Python 函数类型,它会将字符串传递给parse_expr()
。这比默认发生的直接转换慢得多。这在库代码中使用sympify()
而不是_sympify()
(或等效的sympify(strict=True)
)时特别发生。在某个时候,对所有库代码使用strict=True
将成为默认设置,但这是一个更难的变更,参见 harder change to make。 -
使用
eval
可能会引起安全问题,因为字符串是被求值的,并且对象可以在其__repr__
中返回任意字符串。参见github.com/sympy/sympy/pull/12524
。 -
它一开始就不是很有用。仅仅因为一个对象的字符串形式可以解析为 SymPy 表达式并不意味着它应该以这种方式解析。这通常适用于自定义数值类型,但是一个对象的 repr 可以是任何东西。例如,如果一个对象的字符串形式看起来像一个有效的 Python 标识符,它将被解析为
Symbol
。
有很多方法可以使自定义对象在 sympify()
内部工作。
-
首先,如果一个对象旨在与其他 SymPy 表达式一起工作,它应该从
Basic
(或Expr
)继承。如果是这样,sympify()
将直接返回它,因为它已经是一个有效的 SymPy 对象。 -
对于您控制的对象,可以添加
_sympy_
方法。sympify docstring 中有一个示例。 -
对于您无法控制的对象,您可以向
sympy.core.sympify.converter
字典中添加自定义转换器。sympify()
的文档字符串中也有一个示例。 ### 弃用 DMP.rep 属性。
Poly
的内部类型是 DMP
类,之前可以用来作为多项式系数的列表访问:
>>> from sympy import symbols, Poly
>>> x = symbols('x')
>>> p = Poly(x**2 + 2*x + 3)
>>> p
Poly(x**2 + 2*x + 3, x, domain='ZZ')
>>> p.rep
DMP([1, 2, 3], ZZ)
>>> p.rep.rep
[1, 2, 3]
自 SymPy 1.13 版本开始,DMP
类型可以由以下两个子类之一实现:
-
DMP_Python
类似于之前的DMP
类型,并且其内部表示为列表的形式。 -
DUP_Flint
封装了来自 python-flint 的 Flint 多项式。
DUP_Flint
类型没有类似于 DMP_Python
的列表属性。访问 .rep
仍会生成一个列表,但现在会生成弃用警告。
不再使用 .rep
,而是使用返回等效列表的 DMP.to_list()
方法:
>>> p.rep.to_list()
[1, 2, 3]
.to_list()
方法在 SymPy 的早期版本中也是可用的,其行为没有改变。 ### 弃用 pkgdata 模块
sympy.utilities.pkdata
模块已经被废弃并将被移除。在 SymPy 中它已不再使用,也不适合任何下游代码使用。请使用标准库中的 importlib.resources
模块。 ### 弃用 Eq.rewrite(Add)
可以像 eq = Eq(x, y)
一样重写 eq.rewrite(Add)
以得到 x - y
已经被废弃,现在应该写成 eq.lhs - eq.rhs
。考虑到显式使用 lhs
和 rhs
的清晰度,不再认为需要替换属性/方法,并且将此功能包含在重写装置中导致期望布尔值的节点重写为表达式时失败。 ### 弃用标记,注释,填充,图类的矩形
包含用户提供的数值数据以添加到绘图上的 markers, annotations, fill, rectangles
属性已被弃用。新的实现将用户提供的数值数据保存到适当的数据系列中,可以轻松地由 MatplotlibBackend
处理。用户不应直接设置这些属性,而应将同名关键字参数传递给绘图函数。
支持的行为是将关键字参数传递给绘图函数,这对 SymPy 的所有版本(1.13 之前和之后)都适用:
p = plot(x,
markers=[{"args":[[0, 1], [0, 1]], "marker": "*", "linestyle": "none"}],
annotations=[{"text": "test", "xy": (0, 0)}],
fill={"x": [0, 1, 2, 3], "y1": [0, 1, 2, 3]},
rectangles=[{"xy": (0, 0), "width": 5, "height": 1}])
设置绘图对象的属性已被弃用,并将引发警告:
p = plot(x, show=False)
p.markers = [{"args":[[0, 1], [0, 1]], "marker": "*", "linestyle": "none"}]
p.annotations = [{"text": "test", "xy": (0, 0)}]
p.fill = {"x": [0, 1, 2, 3], "y1": [0, 1, 2, 3]}
p.rectangles = [{"xy": (0, 0), "width": 5, "height": 1}]
p.show()
引入此弃用的动机:Plot
类的实现表明,可以在 MatplotlibBackend
类中添加属性和硬编码的 if 语句来为用户提供更多功能,例如添加水平线、垂直线或条形图等。然而,这样做等于重复造轮子:绘图库已经实现了必要的 API。没有必要硬编码这些内容。绘图模块应该便于可视化符号表达式。添加自定义数值数据的最佳方法是检索由绘图模块创建的图,并使用特定绘图库的 API。例如:
# plot symbolic expression
p = plot(cos(x))
# retrieve Matplotlib's figure and axes object
fig, ax = p._backend.fig, p._backend.ax[0]
# add the desired numerical data using Matplotlib's API
ax.plot([0, 1, 2], [0, 1, -1], "*")
ax.axhline(0.5)
# visualize the figure
fig
``` ### 移动的力学函数
随着`sympy.physics.mechanics`模块中引入一些新对象如`Inertia`和负载对象,一些函数从`sympy.physics.mechanics.functions`已移动到新模块。这消除了一些循环导入错误,并使得通过函数名和模块名之间的对等性更容易导航源代码。以下函数已移动:
+ `inertia` 已经移动到 `sympy.physics.mechanics.inertia`
+ `inertia_of_point_mass` 已经移动到 `sympy.physics.mechanics.inertia`
+ `gravity` 已经移动到 `sympy.physics.mechanics.loads`
之前可以从 `sympy.physics.mechanics.functions` 导入函数:
```py
>>> from sympy.physics.mechanics.functions import inertia, inertia_of_point_mass, gravity
现在应该从 sympy.physics.mechanics
导入它们:
>>> from sympy.physics.mechanics import inertia, inertia_of_point_mass
>>> from sympy.physics.mechanics.loads import gravity
``` ### 带模数整数的有序比较如 `a < b`
SymPy 的`GF`域表示模数整数。以前可以用像 `a < b` 这样的有序比较来比较它们:
```py
>>> from sympy import GF
>>> F5 = GF(5)
>>> F5(2) < F5(3)
True
当设置的地面类型为flint
时,这将导致TypeError
。当地面类型不是flint
时,这些比较现在已弃用:它们仍然有效,但在使用时会给出弃用警告。
模数整数或有限域的有序比较是没有意义的,因为这些不是有序域:
>>> e = F5(4)
>>> e + 1 > e
False
``` ### `ModularInteger.to_int()` 方法
SymPy 的`GF`域用于模数整数,例如`GF(n)`是模数`n`的整数,并且可以像这样使用:
```py
>>> from sympy import GF
>>> K = GF(5)
>>> a = K(7)
>>> a
2 mod 5
模数整数域的元素有一个自 SymPy 1.13 版起已弃用的to_int()
方法:
>>> # this is deprecated:
>>> a.to_int()
2
相反,实现等效行为的首选方法是使用域上的方法(自 SymPy 1.13 版起添加)或者可能更好的是调用 int
:
>>> K.to_int(a)
2
>>> int(a)
2
这两种转换为int
的方法并不等效。域GF(p)
可以使用symmetric=True
或symmetric=False
定义。这种差异会影响to_int
方法的行为:
>>> KS = GF(5, symmetric=True)
>>> KU = GF(5, symmetric=False)
>>> [KS.to_int(KS(n)) for n in range(10)]
[0, 1, 2, -2, -1, 0, 1, 2, -2, -1]
>>> [KU.to_int(KU(n)) for n in range(10)]
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
>>> [int(KS(n)) for n in range(10)]
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
>>> [int(KU(n)) for n in range(10)]
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
所以如果 symmetric=True
(这是默认值),to_int
方法有时会返回负整数。如果 symmetric=False
或者使用 int(a)
方法,返回的结果总是非负整数。还要注意,int(a)
的行为在 SymPy 1.13 中已更改:在先前的版本中,它等同于 a.to_int()
。为了编写在所有 SymPy 版本中行为一致的代码,您可以:
-
使用
symmetric=False
并使用int(a)
。 -
定义一个函数,如
def to_int(K, a): if hasattr(K, 'to_int'): return K.to_int(a) else: return a.to_int()
这种改变的原因是,这样做可以使用 python-flint 的 nmod
作为 GF(p)
的元素的替代(更快)实现。不可能向 python-flint 的 nmod
类型添加 to_int
方法,或者通过在 nmod
实例中存储数据来捕获 symmetric=True/False
的等价物。弃用和移除 to_int
方法并改变 int
方法的行为意味着元素实例没有任何取决于域是否被视为“对称”的行为。相反,“对称”的概念现在纯粹是域对象自身的属性,而不是元素的属性,因此取决于这一点的 to_int
方法必须是一个域方法而不是元素方法。### 将 ntheory
中的符号函数移至 functions
ntheory
中的以下符号函数已经移动到 functions
:
-
sympy.ntheory.factor_.divisor_sigma
-
sympy.ntheory.factor_.primenu
-
sympy.ntheory.factor_.primeomega
-
sympy.ntheory.factor_.reduce_totient
-
sympy.ntheory.factor_.totient
-
sympy.ntheory.generate.primepi
-
sympy.partitions_.npartitions
-
sympy.ntheory.residue_ntheory.jacobi_symbol
-
sympy.ntheory.residue_ntheory.legendre_symbol
-
sympy.ntheory.residue_ntheory.mobius
从顶层导入这些函数的代码,比如 from sympy import mobius
仍然可以正常工作。但是从完全合格的模块导入这些函数的代码,比如 from sympy.ntheory import mobius
或者 from sympy.ntheory.residue_ntheory import mobius
现在会看到一个弃用警告。这些函数的新位置在 sympy.functions
中,但是导入它们的预期方式仍然是从顶层,比如 from sympy import mobius
。
ntheory
中的以下符号函数已经移动到 functions
,但无法在顶层导入。
sympy.ntheory.factor_.udivisor_sigma
以下函数从 functions
移动到 ntheory
,因为它们是数值函数。
-
sympy.functions.combinatorial.numbers.carmichael.is_carmichael
-
sympy.functions.combinatorial.numbers.carmichael.find_carmichael_numbers_in_range
-
sympy.functions.combinatorial.numbers.carmichael.find_first_n_carmichaels
如果你在使用这些函数,请从
>>> from sympy import carmichael
>>> carmichael.is_carmichael(561)
True
到
>>> from sympy import is_carmichael
>>> is_carmichael(561)
True
版本 1.12
ManagedProperties
元类
ManagedProperties
元类以前是Basic
的元类。现在Basic
不再使用元类,因此其元类只是type
。任何以前子类化Basic
并希望使用元类的代码都需要子类化ManagedProperties
以使用相关的元类。ManagedProperties
的唯一相关方法已移至Basic.__init_subclass__
。由于ManagedProperties
不再作为Basic
的元类使用,并且不再做任何有用的事情,因此此类代码现在可以仅仅子类化type
来代替任何元类。### 新关节坐标格式
泛化坐标和泛化速度的格式,即关节在sympy.physics.mechanics
模块中的类型和自动生成的名称,已经发生了变化。数据类型已从list
改变为Matrix
,与KanesMethod
中泛化坐标的类型相同。PinJoint
和PrismaticJoint
的泛化坐标和泛化速度的自动命名也已更改为q_<joint.name>
和u_<joint.name>
。以前,每个关节都有一个独特的模板来自动生成这些名称。### 新关节中间框架
sympy.physics.mechanics
模块中关节轴的定义已更改。现在,不再使用参数parent_axis
和child_axis
来自动确定关节轴和中间参考框架,而是关节现在同时使用了父体和子体的中间框架参数,即parent_interframe
和child_interframe
。这意味着您现在可以完全定义两个体的关节附着点和框架。此外,如果像PinJoint
这样的关节有特定的关节轴,例如旋转发生的轴线,则可以使用joint_axis
参数来指定此轴。此设置的优势在于可以更准确地定义从父体到子体的变换。
例如,假设您想要一个PinJoint
来使子体绕parent.z
轴和-child.z
轴旋转。以前指定这个关节的方式是:
>>> from sympy.physics.mechanics import Body, PinJoint
>>> parent, child = Body('parent'), Body('child')
>>> pin = PinJoint('pin', parent, child, parent_axis=parent.z,
... child_axis=-child.z)
>>> parent.dcm(child)
Matrix([
[-cos(q_pin(t)), -sin(q_pin(t)), 0],
[-sin(q_pin(t)), cos(q_pin(t)), 0],
[ 0, 0, -1]])
检查此矩阵时,您会注意到对于theta_pin = 0
,子体围绕parent.y
轴旋转(\pi)弧度。在新定义中,我们可以看到得到相同结果,但这次我们还指定了这个确切的旋转:
>>> from sympy import pi
>>> from sympy.physics.mechanics import Body, PinJoint, ReferenceFrame
>>> parent, child, = Body('parent'), Body('child')
>>> int_frame = ReferenceFrame('int_frame')
>>> int_frame.orient_axis(child.frame, child.y, pi)
>>> pin = PinJoint('pin', parent, child, joint_axis=parent.z,
... child_interframe=int_frame)
>>> parent.dcm(child)
Matrix([
[-cos(q_pin(t)), -sin(q_pin(t)), 0],
[-sin(q_pin(t)), cos(q_pin(t)), 0],
[ 0, 0, -1]])
但是,如果您喜欢废弃参数对齐框架的功能,那么您仍然可以通过向parent_interframe
和child_interframe
提供向量来使用此功能,然后这些向量将被定向,以便在中间框架中表示的关节轴与给定向量对齐:
>>> from sympy.physics.mechanics import Body, PinJoint
>>> parent, child = Body('parent'), Body('child')
>>> pin = PinJoint('pin', parent, child, parent_interframe=parent.z,
... child_interframe=-child.z)
>>> parent.dcm(child)
Matrix([
[-cos(q_pin(t)), -sin(q_pin(t)), 0],
[-sin(q_pin(t)), cos(q_pin(t)), 0],
[ 0, 0, -1]])
```### 关节附着点参数变更
在 `sympy.physics.mechanics` 中指定关节附着点的参数名已更改为 `parent_point` 和 `child_point`,即 `parent_joint_pos` 和 `child_joint_pos`。这是因为这些参数现在也可以是 `Point` 对象,因此它们可以与 `parent_point` 和 `child_point` 属性完全相同。
例如,假设您希望 `PinJoint` 在父级中被定位在 `parent.frame.x` 处相对于质心,在子级中为 `-child.frame.x`。以前指定这一点的方式是:
```py
>>> from sympy.physics.mechanics import Body, PinJoint
>>> parent, child = Body('parent'), Body('child')
>>> pin = PinJoint('pin', parent, child, parent_joint_pos=parent.frame.x,
... child_joint_pos=-child.frame.x)
>>> pin.parent_point.pos_from(parent.masscenter)
parent_frame.x
>>> pin.child_point.pos_from(child.masscenter)
- child_frame.x
现在你可以用同样的方式来处理
>>> from sympy.physics.mechanics import Body, PinJoint
>>> parent, child = Body('parent'), Body('child')
>>> pin = PinJoint('pin', parent, child, parent_point=parent.frame.x,
... child_point=-child.frame.x)
>>> pin.parent_point.pos_from(parent.masscenter)
parent_frame.x
>>> pin.child_point.pos_from(child.masscenter)
- child_frame.x
或者
>>> from sympy.physics.mechanics import Body, PinJoint, Point
>>> parent, child = Body('parent'), Body('child')
>>> parent_point = parent.masscenter.locatenew('parent_point', parent.frame.x)
>>> child_point = child.masscenter.locatenew('child_point', -child.frame.x)
>>> pin = PinJoint('pin', parent, child, parent_point=parent_point,
... child_point=child_point)
>>> pin.parent_point.pos_from(parent.masscenter)
parent_frame.x
>>> pin.child_point.pos_from(child.masscenter)
- child_frame.x
版本 1.11
模块 sympy.tensor.array.expressions.conv_*
重命名为 sympy.tensor.array.expressions.from_*
为了避免可能与模块同名函数的命名和制表完成冲突,sympy.tensor.array.expressions
中所有名称以 conv_*
开头的模块已重命名为 from_*
。 ### 新的 Mathematica 代码解析器
在模块 sympy.parsing.mathematica
中定义的旧 Mathematica 代码解析器已被弃用。应改用具有新的更全面解析器的 parse_mathematica
函数。
Mathematica 解析器的 parse_mathematica
函数中不可用的 additional_translations
参数。应在使用 SymPy 的 .replace()
或 .subs()
方法转换后,指定将 Mathematica 表达式转换为 SymPy 表达式的附加转换规则。如果翻译器无法识别 Mathematica 表达式的逻辑含义,则将返回类似 Mathematica 的完整形式,使用 SymPy 的 Function
对象来编码语法树的节点。
例如,假设您希望 F
是一个返回最大值乘以最小值的函数,以前指定此转换的方法是:
>>> from sympy.parsing.mathematica import mathematica
>>> mathematica('F[7,5,3]', {'F[*x]': 'Max(*x)*Min(*x)'})
21
现在你可以用同样的方式做到
>>> from sympy.parsing.mathematica import parse_mathematica
>>> from sympy import Function, Max, Min
>>> parse_mathematica("F[7,5,3]").replace(Function("F"), lambda *x: Max(*x)*Min(*x))
21
``` ### `carmichael` 中冗余的静态方法
在 `~.carmichael` 中的一些静态方法只是其他函数的包装器。例如,代替 `carmichael.is_perfect_square` 使用 `sympy.ntheory.primetest.is_square`,代替 `carmichael.is_prime` 使用 `~.isprime`。最后,`carmichael.divides` 可以替换为检查
```py
n % p == 0
``` ### 对 `HadamardProduct`、`MatAdd` 和 `MatMul` 的 `check` 参数
这个参数可以用来传递给 `~.HadamardProduct`、`~.MatAdd` 和 `~.MatMul` 的错误值,从而导致后续问题。`check` 参数将被移除,并且参数将始终被检查正确性,即参数是矩阵或矩阵符号。
## 版本 1.10
### 一些遍历函数已经移动
一些遍历函数已经移动。具体来说,这些函数
+ `bottom_up`
+ `interactive_traversal`
+ `postorder_traversal`
+ `preorder_traversal`
+ `use`
已移动到不同的 SymPy 子模块。
这些函数应该从顶级的 `sympy` 命名空间中使用,比如
```py
sympy.preorder_traversal
或者
from sympy import preorder_traversal
通常情况下,最终用户应该使用顶级 sympy
命名空间中存在的任何函数。如果一个名称在顶级命名空间中,不应依赖于其特定的 SymPy 子模块,因为由于内部重构,函数可能会移动。 ### sympy.core.trace
追踪对象 sympy.core.trace.Tr()
已经移至 sympy.physics.quantum.trace.Tr()
。这是因为它仅在 sympy.physics.quantum
子模块中使用,所以将其放在那里比放在核心模块中更合适。 ### sympy.core.compatibility
子模块
sympy.core.compatibility
子模块已经被弃用。
此子模块最初仅用于内部使用。由于 SymPy 不再支持 Python 2,因此此模块已不再必要,并且剩余的辅助函数已移至 SymPy 代码库中更方便的位置。
此模块中的一些函数现在可以从顶级 SymPy 命名空间中获取,即,
sympy.ordered
sympy.default_sort_key
或
from sympy import ordered, default_sort_key
通常情况下,最终用户应该使用顶级 sympy
命名空间中存在的任何函数。如果一个名称在顶级命名空间中,不应依赖于其特定的 SymPy 子模块,因为由于内部重构,函数可能会移动:
sympy.core.compatibility
中剩余的函数仅供内部 SymPy 使用,不应该被用户代码使用。
此外,这两个函数 ordered
和 default_sort_key
也曾在 sympy.utilities.iterables
中,但它们也已经从那里移动。
版本 1.9
expr_free_symbols
各种 SymPy 对象的 expr_free_symbols
属性已经被弃用。
expr_free_symbols
被设计为表示像 MatrixElement
和 Indexed
这样的索引对象作为自由符号。这旨在使自由符号的导数工作。然而,现在即使不使用该方法也能正常工作:
>>> from sympy import Indexed, MatrixSymbol, diff
>>> a = Indexed("A", 0)
>>> diff(a**2, a)
2*A[0]
>>> X = MatrixSymbol("X", 3, 3)
>>> diff(X[0, 0]**2, X[0, 0])
2*X[0, 0]
这是一个通用属性,旨在解决一个非常具体的问题,但它增加了不必要的抽象层。
-
对于已经具有结构化“非表达式”节点的对象,如果需要,可以直接专注于表达式节点,例如。
>>> from sympy import Derivative, symbols, Function >>> x = symbols('x') >>> f = Function('f') >>> Derivative(f(x), x).expr f(x)
引入此属性会在请求自由符号时鼓励不精确的思考,因为它允许从对象的特定节点获取符号而不必指定节点。
-
该属性被错误地添加到
AtomicExpr
,因此数字被返回为expr_free_symbols
:>>> S(2).expr_free_symbols 2
-
应用这个概念来定义
Subs.expr_free_symbols
是错误的:它增加了点的expr_free_symbols
但是点是一个Tuple
,因此没有添加任何内容。 -
它在代码库中除了在不同 iating
Subs
对象的上下文中未被使用外,这表明它并非通用用途,这也由以下事实确认: -
它是在未进行具体测试的情况下添加的,除了引入它的
Subs
对象的导数测试外。
更多讨论请参见问题#21494。 ### sympy.stats.sample(numsamples=n)
sympy.stats.sample()
的numsamples
参数已弃用。
numsamples
使得sample()
返回大小为numsamples
的列表,例如:
>>> from sympy.stats import Die, sample
>>> X = Die('X', 6)
>>> sample(X, numsamples=3)
[3, 2, 3]
然而,用户可以通过列表推导轻松实现此功能。
>>> [sample(X) for i in range(3)]
[5, 4, 3]
此外,它与size
参数重复,使得sample
返回一个具有给定形状的 NumPy 数组。
>>> sample(X, size=(3,))
array([6, 6, 1])
历史上,SymPy 1.7 中更改了sample
,使其返回迭代器而不是样本值。因此,添加了一个numsamples
参数来指定迭代器的长度。
然而,由于在问题#21563中讨论的混乱,这种新行为被撤销了。现在,如果需要迭代器,则应使用sample_iter
。因此,sample()
不再需要numsamples
参数。 ### sympy.polys.solvers.RawMatrix
RawMatrix
类已弃用。RawMatrix
类是Matrix
的子类,其使用域元素而不是Expr
作为矩阵的元素。这违反了Matrix
的关键内部不变量,并且这种子类化限制了对Matrix
类的改进。
SymPy 唯一文档化使用RawMatrix
类的部分是 Smith 正常形式代码,现在已更改为使用DomainMatrix
。建议任何使用RawMatrix
以前的 Smith 正常形式代码的人切换到使用问题#21402中显示的DomainMatrix
。稍后将添加更好的 Smith 正常形式 API。 ### 非Expr
对象在矩阵中
在 SymPy 1.8 及更早版本中,可以在Matrix
中放置非Expr
元素,并且矩阵元素可以是任意的 Python 对象:
>>> M = Matrix([[(1, 2), {}]])
这并不实用,实际上并不起作用,例如:
>>> M + M
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'Dict' and 'Dict'
允许此功能的主要原因是 SymPy 代码库中有许多Matrix
子类希望使用 polys 模块中的对象,例如:
-
RawMatrix
(见上文)在solve_lin_sys
中被使用,这是heurisch
的一部分,并且也被smith_normal_form
使用。NewMatrix
类使用域元素作为矩阵的元素,而不是Expr
。 -
NewMatrix
在holonomic
模块中被使用,并且还使用域元素作为矩阵的元素。 -
PolyMatrix
将Poly
和Expr
混合作为矩阵元素,并被risch
使用。
所有这些矩阵子类都以不同的方式损坏,并且引入了 DomainMatrix
(#20780, #20759, #20621, #19882, #18844) 提供了所有情况的更好解决方案。以前的 PR 已经移除了这些其他用例对 Matrix 的依赖 (#21441, #21427, #21402),现在 #21496 已经废弃了在 Matrix
中使用非Expr
元素的做法。
这一变更使得可以改进 Matrix 类的内部,但可能对一些下游用例产生影响,这些用例可能与 SymPy 代码库中使用非Expr
元素的 Matrix 使用方式类似。如果希望用类似域元素和域对象可以为其提供的元素替换使用 Matrix
的代码,请使用 DomainMatrix
。或者,如果目标只是打印支持,则可能可以使用 TableForm
。
没有清楚的建议可以在此处替换,除非了解更多关于用例的信息。如果不清楚如何更新您的代码,请提出问题或写信到我们的邮件列表,以便讨论。 ### 绘图对象的 get_segments
属性
Line2DBaseSeries
中实现的 get_segments
方法用于将 x
和 y
两个坐标列表转换为 Matplotlib 的 LineCollection
绘制线段所需的段列表。
由于段列表仅由 Matplotlib 需要(例如,Bokeh、Plotly、Mayavi、K3D 只需要坐标列表),因此这一改变已经移至 MatplotlibBackend
类内部。
注意之前,get_points()
方法总是返回均匀采样的点,这意味着当使用 get_points()
和 Matplotlib 绘图时,一些函数未能正确绘制。
要避免这个问题,可以使用get_segments()
方法,它使用自适应采样,并可以与 Matplotlib 的LineCollection
一起使用。但是,现在也可以使用get_points()
进行自适应采样。可以使用get_data()
方法。### sympy.physics.matrices
中的mdft
函数
sympy.physics.matrices.mdft()
函数已弃用。可以用sympy.matrices.expressions.fourier
中的DFT
类替换。
特别是,用DFT(n).as_explicit()
替换mdft(n)
。例如:
>>> from sympy.physics.matrices import mdft
>>> mdft(3) # DEPRECATED
Matrix([
[sqrt(3)/3, sqrt(3)/3, sqrt(3)/3],
[sqrt(3)/3, sqrt(3)*exp(-2*I*pi/3)/3, sqrt(3)*exp(2*I*pi/3)/3],
[sqrt(3)/3, sqrt(3)*exp(2*I*pi/3)/3, sqrt(3)*exp(-2*I*pi/3)/3]])
>>> from sympy.matrices.expressions.fourier import DFT
>>> DFT(3)
DFT(3)
>>> DFT(3).as_explicit()
Matrix([
[sqrt(3)/3, sqrt(3)/3, sqrt(3)/3],
[sqrt(3)/3, sqrt(3)*exp(-2*I*pi/3)/3, sqrt(3)*exp(2*I*pi/3)/3],
[sqrt(3)/3, sqrt(3)*exp(2*I*pi/3)/3, sqrt(3)*exp(-2*I*pi/3)/3]])
这种变化是因为sympy.physics
子模块只应包含与物理有关的内容,但离散傅立叶变换矩阵是一个更一般的数学概念,因此最好放在sympy.matrices
模块中。此外,DFT
类是一个矩阵表达式,这意味着它可以未评估并支持符号形状。### SparseMatrix._smat
和DenseMatrix._mat
私有属性
Matrix
的._mat
属性和SparseMatrix
的._smat
属性已弃用。
Matrix
和SparseMatrix
的内部表示已更改为#21626中的DomainMatrix
,因此不再可能将可变列表/字典暴露为突变Matrix
的一种方式。新的.flat()
方法可以使用,它返回一个新列表,不能用于突变Matrix
本身。可以使用.todok()
方法而不是._smat
。
请注意,这些属性在 SymPy 1.9 中已更改为返回只读副本,因此依赖于突变它们的任何代码将会失败。此外,这些属性在技术上始终是私有的(它们以下划线开头),因此用户代码在第一次使用它们时不应该真的使用它们。### Matrix 的 laplace_transform 与 noconds=False
在版本 1.9 之前,在一个带有noconds=False
(默认情况)的Matrix
上调用laplace_transform()
会导致元组的矩阵:
>>> from sympy import laplace_transform, symbols, eye
>>> t, z = symbols('t z')
>>> laplace_transform(eye(2), t, z)
Matrix([
[(1/z, 0, True), (0, 0, True)],
[ (0, 0, True), (1/z, 0, True)]])
但是,Matrix
仅设计用于与Expr
对象一起工作(见上面的 Matrix 中的非 Expr 对象)。
为了避免这种情况,可以使用noconds=True
来移除收敛条件。
>>> laplace_transform(eye(2), t, z, noconds=True)
Matrix([
[1/z, 0],
[ 0, 1/z]])
或者使用legacy_matrix=False
来返回新的行为,即在第一个参数中返回矩阵,并将收敛条件组合为整个矩阵的单一条件。
>>> laplace_transform(eye(2), t, z, legacy_matrix=False)
(Matrix([
[1/z, 0],
[ 0, 1/z]]), 0, True)
当此废弃项被移除时,legacy_matrix=False
行为将成为默认值,但标志将保留以确保兼容性。
版本 1.8
sympy.printing.theanocode
Theano已停止,并分支成一个名为Aesara的新项目。sympy.printing.theanocode
模块已重命名为sympy.printing.aesaracode
,并且所有对应的函数也已重命名(例如,theano_code
已重命名为aesara_code()
,TheanoPrinter
已重命名为AesaraPrinter
等)。 ### sympy.assumptions.handlers.AskHandler
和相关方法
Predicate
经历了重大设计变化。先前,其处理程序是AskHandler
类的列表,并通过add_handler()
和remove_handler()
函数进行注册。现在,其处理程序是一个multipledispatch
实例,并通过register()
或register_many()
方法进行注册。用户必须定义一个谓词类来引入新的谓词。
先前,处理程序是通过以下方式定义和注册的:
class AskPrimeHandler(AskHandler):
@staticmethod
def Integer(expr, assumptions):
return expr.is_prime
register_handler('prime', AskPrimeHandler)
应该更改为这样:
# Predicate definition.
# Not needed if you are registering the handler to existing predicate.
class PrimePredicate(Predicate):
name = 'prime'
Q.prime = PrimePredicate()
# Handler registration
@Q.prime.register(Integer)
def _(expr, assumptions):
return expr.is_prime
见 GitHub 问题#20209。
版本 1.7.1
使用RandomIndexedSymbol
调用sympy.stats.StochasticProcess.distribution
sympy.stats
的distribution
方法曾接受RandomIndexedSymbol
(即按时间戳索引的随机过程),但现在应仅在时间戳下调用。
例如,如果您有
>>> from sympy import symbols
>>> from sympy.stats import WienerProcess
>>> W = WienerProcess('W')
>>> t = symbols('t', positive=True)
以前这样可以工作
W.distribution(W(t)) # DEPRECATED
现在应该这样调用
>>> W.distribution(t)
NormalDistribution(0, sqrt(t))
这一更改是作为存储只有sympy.stats
中的Basic
对象的.args
的更改的一部分进行的。有关详情,请参见问题#20078。
版本 1.7
sympy.stats.DiscreteMarkovChain.absorbing_probabilities()
absorbing_probabilites
方法名称拼写错误。正确的拼写是absorbing_probabilities()
(“absorbing probabilities”)应该被使用。
函数sympy.utilities.misc.find_executable()
已被弃用。而应使用标准库中自 Python 3.3 起就存在的shutil.which()
函数,这更为强大。### sympy.diffgeom
中的可变属性
多个部分在sympy.diffgeom
中已更新,不再可变,这与 SymPy 其他部分使用的不可变设计更匹配。
-
传递给
CoordSystem
的符号名称字符串已被弃用。相反,您应该明确地传递带有适当假设的符号,例如,而不是CoordSystem(name, patch, ['x', 'y']) # DEPRECATED
使用
CoordSystem(name, patch, symbols('x y', real=True))
-
类似地,
names
关键字参数已重命名为symbols
,应为符号列表。 -
Manifold.patches
属性已被弃用。应该单独跟踪补丁。 -
Patch.coord_systems
属性已被弃用。应该单独跟踪坐标系。 -
CoordSystem.transforms
属性,CoordSystem.connect_to()
方法以及CoordSystem.coord_tuple_transform_to()
方法已被弃用。应使用CoordSystem
类构造函数的relations
关键字以及CoordSystem.transformation()
和CoordSystem.transform()
方法(参见CoordSystem
的文档字符串以获取示例)。###sympy.printing.pretty.stringpict.prettyForm
和sympy.printing.pretty.pretty_symbology.xstr
函数的unicode
参数和属性
sympy.printing.pretty.pretty_symbology.xstr
函数以及sympy.printing.pretty.stringpict.prettyForm
的unicode
参数和属性都是为了支持 Python 2 的 Unicode 行为而存在的。由于 Python 3 中 Unicode 字符串是默认的,这些不再需要。应将xstr()
替换为str()
,省略prettyForm
的unicode
参数,并用prettyForm.s
属性替换prettyForm.unicode
属性。### 将参数作为lambdify
的set
传递
传递函数参数作为lambdify
的集合已被弃用。应作为列表或元组传递它们。例如,而不是
lambdify({x, y}, x + 2*y) # WRONG
使用
lambdify((x, y), x + 2*y) # RIGHT
这是因为集合是无序的。 例如,在上面的示例中,lambidfy
无法知道它是否以{x, y}
或{y, x}
调用。 因此,当作为集合传递参数时,lambdify
必须猜测它们的顺序,如果猜测错误,将导致函数不正确。 ### 核心运算符不再接受非 Expr 参数
核心操作类Add
,Mul
和Pow
现在不能直接使用非Expr
子类的对象构造。
Expr
是所有表示标量数值数量的 SymPy 类的超类。 例如,sin
,Symbol
和Add
都是Expr
的子类。 但是,SymPy 中的许多对象不是Expr
,因为它们表示其他类型的数学对象。 例如,Set
,Poly
和Boolean
都不是Expr
。 这些对象在Add
,Mul
和Pow
内部没有数学意义,这些类专门用于表示标量复数的加法,乘法和指数运算。
可以手动构造这些类的一个对象,但通常会导致错误。 例如
Mul(1, Tuple(2)) # This is deprecated
可以工作并创建Tuple(2)
,但仅因为Mul
始终将 (1 \cdot x = x) 视为“欺骗”。 如果您尝试
Mul(2, Tuple(2)) # This is deprecated
它失败并引发异常
AttributeError: 'Tuple' object has no attribute 'as_coeff_Mul'
因为它尝试在Tuple
对象上调用Expr
的方法,而Tuple
对象没有所有Expr
方法(因为它不是Expr
的子类)。
如果要在非Expr
对象上使用+
,*
或**
操作,请直接使用运算符,而不是使用Mul
,Add
或Pow
。 如果需要函数版本,可以使用lambda
或operator
模块。
版本 1.6
各种 sympy.utilities
子模块已迁移
以下子模块已更名。
-
sympy.utilities.benchmarking
→sympy.testing.benchmarking
-
sympy.utilities.pytest
→sympy.testing.pytest
-
sympy.utilities.randtests
→sympy.core.random
-
sympy.utilities.runtests
→sympy.testing.runtests
-
sympy.utilities.tmpfiles
→sympy.testing.tmpfiles
###sympy.testing.randtest
sympy.testing.randtest
已经弃用。其中的函数已移至 sympy.core.random
。以下函数已移动。
-
sympy.testing.randtest.random_complex_number
→sympy.core.random.random_complex_number
-
sympy.testing.randtest.verify_numerically
→sympy.core.random.verify_numerically
-
sympy.testing.randtest.test_derivative_numerically
→sympy.core.random.test_derivative_numerically
-
sympy.testing.randtest._randrange
→sympy.core.random._randrange
-
sympy.testing.randtest._randint
→sympy.core.random._randint
### 在二进制操作中混合Poly
和非多项式表达式
在之前的 SymPy 版本中,Poly
是 Expr
的子类,但现在它已被更改为只是 Basic
的子类。这意味着某些以前与 Poly
一起工作的功能现在已弃用,因为它们仅设计用于与 Expr
对象一起使用。
这包括使用二进制操作组合 Poly
和 Expr
对象,例如
Poly(x)*sin(x) # DEPRECATED
要实现此功能,可以通过使用 Expr.as_poly()
将非 Poly
操作数显式转换为 Poly
,或者通过使用 Poly.as_expr()
将 Poly
操作数转换为 Expr
,具体取决于你想要的结果类型。 ### sympy.combinatorics.Permutation
的 print_cyclic
标志
sympy.combinatorics.Permutation
的 print_cyclic
属性控制排列打印为循环还是数组。可以通过设置 Permutation.print_cyclic = True
或 Permutation.print_cyclic = False
来实现。然而,这种控制打印方式的方法并不好,因为它是一个全局标志,而打印不应该依赖于全局行为。
相反,用户应该使用相应打印机的 perm_cyclic
标志。配置此项的最简单方法是在调用 init_printing()
时设置该标志,例如
>>> from sympy import init_printing
>>> init_printing(perm_cyclic=False) # Makes Permutation print in array form
>>> from sympy.combinatorics import Permutation
>>> Permutation(1, 2)(3, 4)
⎛0 1 2 3 4⎞
⎝0 2 1 4 3⎠
Permutation
的文档字符串详细介绍了 perm_cyclic
标志。### 使用 integrate
和 Poly
在之前的 SymPy 版本中,Poly
是 Expr
的子类,但现在已更改为仅是 Basic
的子类。这意味着某些以前与 Poly
一起工作的东西现在已经过时,因为它们只设计用于与 Expr
对象一起使用。
包括使用 Poly
调用 integrate()
或 Integral
。
要对 Poly
进行积分,使用 Poly.integrate()
方法。要将积分计算为 Expr
对象,请首先调用 Poly.as_expr()
方法。
另请参阅 在二元操作中混合多项式和非多项式表达式 上方。### 使用 Eq
参数创建不定积分 Integral
将 Eq()
对象传递给 integrate()
在积分为不定积分的情况下已经过时。这是因为如果 (f(x) = g(x)),那么一般来说 (\int f(x),dx = \int g(x),dx) 是不成立的,这是由于任意常数(integrate
不包括这些常数)。
如果要创建不定积分的等式,请明确使用 Eq(integrate(f(x), x), integrate(g(x), x))
。
如果已经有一个等式对象 eq
,可以使用 Eq(integrate(eq.lhs, x), integrate(eq.rhs, x))
。
版本 1.5
Tensor.fun_eval
和 Tensor.__call__
TensExpr.fun_eval
和 Tensor.__call__
(即调用张量来评估它)已经过时。应该使用 Tensor.substitute_indices()
方法。这一变更是因为 fun_eval
被认为是一个令人困惑的名称,并且使用函数评估被认为是既令人困惑又危险的。### TensorType
TensorType
类已弃用。请使用tensor_heads()
代替。TensorType
类除了更短地创建TensorHead
对象之外,没有其他用途。
另请参阅下面的 The tensorhead() function。 ### TensorIndexType
的dummy_fmt
参数
TensorIndexType
的dummy_fmt
关键字参数已弃用。设置dummy_fmt='L'
将导致_dummy_fmt='L_%d'
,这是令人困惑并且使用过时的字符串格式化。应改用dummy_name
。这个改变是因为dummy_name
是一个更清晰的名称。 ### TensorIndexType
的metric
参数
TensorIndexType
的metric
关键字参数已弃用。名称metric
在某些地方指“度量对称性”,在其他地方指“度量张量”,存在歧义。
应使用metric_symmetry
关键字或TensorIndexType.set_metric()
方法。 ### TensorIndexType
的get_kronecker_delta()
和get_epsilon()
方法
TensorIndexType
的get_kronecker_delta()
和get_epsilon()
方法已弃用。分别使用TensorIndexType.delta
和TensorIndexType.epsilon
属性。 ### tensorsymmetry()
函数
sympy.tensor
中的tensorsymmetry()
函数已经被弃用。请使用TensorSymmetry
类构造函数替代。
TensorSymmetry
优于tensorsymmetry()
,因为后者
-
没有额外的功能
-
涉及晦涩的 Young 表
-
不是
TensorSymmetry
类的成员 ###tensorhead()
函数
tensorhead()
函数已弃用,建议使用tensor_heads()
代替。tensor_heads()
与 SymPy 的其他命名(例如,Symbol
和symbols()
或TensorIndex
和tensor_indices()
)更一致。它也不使用 Young 表来表示对称性。 ### 集合的is_EmptySet
属性
Set 对象的is_EmptySet
属性已弃用。而是使用
from sympy import S
s is S.EmptySet
或者
s.is_empty
不同之处在于如果集合是否为空未知时,s.is_empty
可能返回None
。 ### ProductSet(iterable)
将单个可迭代对象作为ProductSet
的第一个参数已被弃用。应该使用ProductSet(*iterable)
创建产品集,或者作为每个单独的参数。例如
>>> from sympy import ProductSet
>>> sets = [{i} for i in range(3)]
>>> ProductSet(*sets)
ProductSet({0}, {1}, {2})
>>> ProductSet({1, 2}, {1})
ProductSet({1, 2}, {1})
之所以这样做是因为集合本身可以是可迭代的,而集合的集合也是允许的。但是,单个可迭代对象的产品集在数学上应该是该集合本身(或更确切地说,该集合的元素的一元组的集合)。自动去嵌套单个可迭代对象使得无法表示这种对象,并且在传递 1 个参数时,使得ProductSet
不能正确推广。另一方面,在旧代码路径中,如果第一个参数是集合,则对其进行不同处理与处理其他类型的可迭代对象(当前弃用的代码路径)会导致行为混乱。### 在sympy.physics.mechanics
中的set_potential_energy
方法
sympy.physics.mechanics.particle.Particle
和sympy.physics.mechanics.rigidbody.RigidBody
的set_potential_energy()
方法已被弃用。
相反,应该设置Particle.potential_energy
和RigidBody.potential_energy
属性来设置势能,例如
P.potential_energy = scalar
这一变更是为了更符合 Python 风格,使用@property
方法的设置器和获取器,而不是显式的set_
方法。### 在ConditionSet
中使用集合表示条件
在ConditionSet
中使用集合表示条件已被弃用。应该使用布尔值代替。这是因为条件在数学上是布尔值,而在此上下文中使用集合会引起歧义。
要修复此弃用问题,请替换
ConditionSet(symbol, set_condition)
使用
ConditionSet(symbol, And(*[Eq(lhs, 0) for lhs in set_condition]))
例如,
ConditionSet((x, y), {x + 1, x + y}, S.Reals) # DEPRECATED
会变成
ConditionSet((x, y), Eq(x + 1, 0) & Eq(x + y, 0), S.Reals)
```### `sympy.polys.multivariate_resultants.DixonResultant`的`max_degree`和`get_upper_degree`属性
`DixonResultant`的`max_degree`属性和`get_upper_degree()`方法已被弃用。详细信息请参见问题[#17749](https://github.com/sympy/sympy/pull/17749)。### 对于`Lambda`,第一个参数不是元组而是其他可迭代对象
使用非元组作为`Lambda`的第一个参数已被弃用。如果参数不是元组,请首先将其转换为元组,如`Lambda(tuple(args), expr)`。
这样做是因为`Lambda`能够支持通用的元组解包,例如
```py
>>> from sympy import Lambda, symbols
>>> x, y, z = symbols('x y z')
>>> f = Lambda((x, (y, z)), x + y + z)
>>> f(1, (2, 3))
6
``` ### `differentiate_finite` 的 `evaluate` 标志
`differentiate_finite()` 的 `evaluate` 标志已弃用。
`differentiate_finite(expr, x, evaluate=True)` 在计算差分之前会展开中间导数。但通常这不是你想要的,因为它不符合乘积法则。
如果你确实需要这种行为,你可以用以下方式模拟它:
```py
diff(expr, x).replace(
lambda arg: arg.is_Derivative,
lambda arg: arg.as_finite_difference())
参见问题讨论 #17881。
版本 1.4
TensorIndexType.data
和相关方法
TensorIndexType.data
属性已弃用,以及使用它的几个方法,包括 get_matrix()
,__getitem__()
(索引),__iter__()
(迭代),_components_data_full_destroy()
和 __pow__()
(**
)方法。在张量对象上存储数据是一种设计缺陷,并不符合 SymPy 的其余工作方式。
取而代之的是应该使用 TensExpr.replace_with_arrays()
方法。
术语表
这一页是 SymPy 文档中使用的各种术语的术语表。该术语表主要用于特定于 SymPy 的术语。有关更一般的 Python 术语,请参阅Python 术语表。数学术语仅在 SymPy 中具有特定含义时包含在此处。有关一般数学定义,请参考其他来源,如Wikipedia或MathWorld,以及特定 SymPy 函数文档中的参考资料。
反导数
函数 (f(x)) 关于 (x) 的一个反导数是一个函数 (F(x)),使得 (\frac{d}{dx}F(x) = f(x).) 有时也称为 (f(x)) 的“不定积分”,并写作 (\int f(x),dx.) 在 SymPy 中,可以用integrate()
计算反导数。请注意,某些来源称之为 (f(x)) 的“原函数”,但 SymPy 中不使用此术语,因为它不像“反导数”一样被普遍使用,并且“原函数”在数学和SymPy
中有其他含义。
args
SymPy 表达式的args
属性是用于创建它的顶级子表达式的元组。它们是用于创建表达式的类的参数。任何表达式的 args
可以通过 .args
属性获取。例如,(1 + x*y).args
是 (1, x*y)
,因为它等于 Add(1, x*y)
。args
与 func 一起完全定义了一个表达式。可以通过反复使用 .args
来遍历表达式树并提取 SymPy 表达式的任何子表达式。通过 func
和 args
,始终可以准确重建任何 SymPy 表达式,即 expr.func(*expr.args) == expr
对于任何 SymPy 表达式 expr
都是真实的。一个表达式的 args
可能是空元组 ()
,这意味着表达式是一个原子。
Assumptions
Assumptions 是对符号或表达式的一组谓词,定义其可以取的可能值集合。一些假设的示例包括 positive
、real
和 integer
。假设在逻辑上相互关联,例如,一个 integer
的假设自动意味着 real
。假设使用三值逻辑系统,其中谓词可以是 True
、False
或 None
。
假设可以是假设或查询。例如,一个符号 x
可能通过将其定义为 x = symbols('x', positive=True)
来假设为正数。然后可以在包含此符号的表达式上查询一个假设,如 (x + 1).is_real
,在这种情况下将返回 True
。
如果在符号上没有假设,则默认情况下假设符号是一般复数。设置假设是重要的,因为某些简化只在受限域内数学上成立,例如,(\sqrt{x²} = x) 对一般复数 (x) 不成立,但当 (x) 是正数时成立。除非表达式的所有允许值都符合其假设,否则 SymPy 函数不会对表达式执行操作。
SymPy 有两个独立的假设系统,它们彼此紧密相关。在第一个系统中,有时被称为“旧假设”,因为它更老,假设是在符号对象上假设并使用 is_*属性查询。在第二个系统中,有时被称为“新假设”,假设是使用像Q.positive
这样的单独谓词对象假设的,并使用ask()
函数查询。较新的假设系统能够支持更复杂的查询,但也不像较旧的那样发展得好。目前大多数 SymPy 用户应该偏好较旧的假设系统。
查看假设指南了解更多关于假设的细节。
原子
一个原子是一个表达式,其 args 是空元组()
。原子是表达式树的叶子节点。例如,如果一个函数使用递归来遍历表达式树使用args
,那么原子表达式将是递归的基本情况。
请注意,类Atom
有时被用作原子表达式的基类,但不要求原子表达式必须是这个类的子类。表达式要成为原子表达式的唯一要求是它的 args 为空。
自动简化
自动简化 指的是在类构造函数内部自动进行的任何简化。例如,在 Add
构造函数中,x + x
会自动简化为 2*x
。与手动 简化 不同,自动简化只能通过设置 evaluate=False
来禁用 (参见 未评估)。通常进行自动简化是为了使表达式 规范化。过度的自动简化是不鼓励的,因为这样做会使表达式无法表示为非简化形式,除非使用 evaluate=False
等技巧,而且在类构造函数中这样做通常是一件昂贵的事情。相比之下,通常更倾向于进行手动 简化/规范化。
基本
基本
是所有 SymPy 表达式的超类。它定义了 SymPy 表达式所需的基本方法,比如 args,func,equality,immutability,以及一些有用的表达式操作函数,比如 替换。大多数 SymPy 类将会作为更具体的 Basic
子类,比如 布尔,表达式,函数,或 矩阵 进行子类化。通常情况下,如果一个对象不是 Basic
实例,它就不能在 SymPy 函数中使用,除非可以通过 sympify() 转换为一个。
布尔
布尔
是 logic
模块中类的基类。Boolean
实例代表布尔代数中的逻辑谓词,并且可以被视为具有“真”或“假”值 (注意 Boolean
对象不使用 三值逻辑,而是使用 假设)。
绑定符号
表达式中的一个 symbol 如果是bound,则表示它不是 free。一个 bound 符号可以被新符号替换,得到的表达式仍然在数学上等价。例子包括定积分中的积分变量和Subs
中的替换变量。有时用 dummy 符号表示 bound 符号,但它们不总是Dummy
对象,而Dummy
对象也不总是 bound 符号。
规范形式
规范化
表达式通常可以用多种数学等价的方式写出。规范形式是表达式的一种单一写法,所有等价的表达式都可以转换为这种形式。将表达式放入规范形式称为规范化。通常规范形式是唯一的,并具有使其更易于处理的属性。例如,有理函数的常见规范形式是(\frac{p}{q}),其中(p)和(q)是无公因式的展开多项式。
代码生成
代码生成指的是将 SymPy 表达式转换为特定语言或库的代码,以便进行数值评估的过程。SymPy 支持几十种语言和库的代码生成,包括 C、C++、Fortran 和 NumPy。
核心
核心是包含所有 SymPy 对象使用的重要功能的子模块。这包括 Basic 和 Expr 基类,如Add
、Mul
和Pow
等类,以及假设。
哑元
dummy symbol 是一个符号,即使它与同名的其他 dummy 符号不相等,也会自动返回其自身。dummy 符号用于当函数需要返回带有新符号的表达式时,以避免意外与同名的 symbol 冲突。可以使用Dummy
创建 dummy 符号。
方程
方程是具有等号 (=) 的 expression。在 SymPy 中,方程使用Eq
类表示。方程不是使用==
运算符创建的。==
运算符执行两个表达式之间的结构相等性检查,并始终返回True
或False
。相比之下,符号方程可能是 unevaluated 的。方程被视为 booleans,因为它们在数学上表示一个谓词值,即真或假。
_eval_*
Basic 和 Expr 上的各种方法可以通过特殊的_eval_*
方法在子类中定义。例如,对象可以通过定义_eval_derivative
方法来定义在diff()
函数中如何处理它。使用的_eval_*
方法是替代重写方法本身,以便在基类上定义的方法在调度到_eval_*
方法之前进行预处理。
evalf
evalf
是每个 Expr 对象上的方法,用于将其评估为浮点数值,或者如果表达式包含 symbols,则将表达式的常数部分转换为数值。.n()
方法和N()
函数都是evalf
的简写。 evalf
代表“evaluate floating-point”。 evalf
在内部使用 mpmath 来对表达式进行任意精度评估。
评估
评估可以指:
-
将 expression 转换为数值的过程(见 evalf)
-
创建表达式时发生的自动简化过程(参见 Unevaluated)。
-
将表达式中的一个或多个 symbols 用数值或使用 substitution 替换的过程。
Expr
Expr
是所有代数 SymPy 表达式的超类。它本身是 Basic 的子类。可以在Add
、Mul
或Pow
中的 SymPy 表达式应该是Expr
的子类。并非所有 SymPy 类都是Expr
的子类,例如,布尔对象是 Basic,但不是Expr
,因为布尔表达式在像Add
或Mul
这样的类中没有数学意义。
表达式
任何 SymPy 对象,即任何 Basic 的实例,都可以称为表达式。有时,“表达式”一词保留给 Expr 对象,这些是代数表达式。表达式不应与方程混淆,后者是表示数学等式的特定类型的表达式。
表达树
表达树是树的表达式。每个表达式都是从较小的表达式构建而成的树。表达树的节点是表达式,每个节点的子节点是构成该表达式的直接子表达式。或者,可以将表达树视为一棵树,其中非叶节点是函数,叶节点是原子。例如,教程中展示了一个表达式树的示例。通过递归遍历 args,可以获得任何 SymPy 表达式的表达树。请注意,由于 SymPy 表达式是不可变的,并且严格按照结构相等性处理,因此也可以将表达树视为是DAG,其中相同的子表达式在图中只表示一次。
自由符号
表达式中的符号如果数学上依赖于该符号的值,则为自由。也就是说,如果该符号被替换为一个新符号,结果将是不同的表达式。不是自由的符号是绑定的。可以通过free_symbols
属性访问表达式的自由符号。
func
func
属性是 expression 的函数,可以通过expr.func
获取。这通常与type(expr)
相同,但在某些情况下可能会有所不同,因此在重建具有 args 的表达式时,应优先使用expr.func
而不是type(expr)
。每个 SymPy 表达式都可以使用func
和args
完全重建,即expr.func(*expr.args) == expr
对于任何 SymPy 表达式expr
都将始终为真。
函数
函数可能指:
-
数学函数,即将域中的值映射到范围中的某些内容。有时,包含 symbol 的 expression 在口语上称为“函数”,因为该符号可以使用 substitution 替换为值,从而 evaluating 表达式。这种用法是口语化的,因为必须使用
subs
方法来执行替换,而不是典型的 Python 函数调用语法,并且它不具体说明表达式是哪些变量的函数,因此通常应优先使用术语“expression”,除非某些内容确实是函数。可以使用Lambda
将表达式转换为可以使用 Pythonf(x)
语法调用的函数对象。 -
SymPy Function 类的一个实例。
-
Python 函数,即使用
def
关键字定义的函数。 Python 函数不是 symbolic,因为它们必须始终返回一个值,因此不能是 unevaluated。
Function
(类)
Function
是 SymPy 中符号函数的基类。这包括常见函数如sin()
和exp()
,特殊函数如zeta()
和hyper()
,以及积分函数如primepi()
和divisor_sigma()
。函数类总是符号化,这意味着当传递一个符号,如f(x)
时,它们通常保持未评估状态。并非所有符号表达式类都是Function
子类,例如,像Add
和Mul
这样的核心类不是Function
子类。
Function
也可以用于通过传递函数的字符串名称(如Function('f')
)创建一个未定义函数。
并非所有 SymPy 中的函数都是符号Function
类;有些只是始终返回值的 Python 函数。例如,大多数简化函数(如 simplify())无法以符号形式表示。
Immutable
在 Python 中,如果对象无法原地修改,则称其为不可变。为了改变一个不可变对象,必须创建一个新的对象。在 SymPy 中,所有的基础对象都是不可变的。这意味着所有操作表达式的函数都会返回一个新的表达式,并且不会改变原始对象。对表达式进行操作不会改变引用该表达式的其他对象或表达式。这也意味着任何两个相等的对象完全可以互换,并且可以被视为同一个对象,即使它们在内存中是两个不同的对象。不可变性使得更容易维护代码的心智模型,因为没有隐藏状态。SymPy 对象的不可变性也意味着它们是可哈希的,可以用作字典键。
交互式
交互 使用指的是在交互式 REPL 环境中使用 SymPy,例如 Python 提示符、isympy、IPython 或 Jupyter 笔记本。在交互式使用 SymPy 时,所有命令由用户实时输入,并显示所有中间结果。交互 使用与程序化 使用相对应,后者是指代码写入文件,然后作为脚本执行或作为较大 Python 库的一部分。一些 SymPy 习惯用法仅推荐在交互式使用时使用,当在程序化使用时被视为反模式。例如,在交互式使用 SymPy 时运行 from sympy import *
是方便的,但在程序化使用时通常不建议,应优先显式导入名称 import sympy
。
is_*
在 SymPy 中以 is_
开头并使用小写名称的属性查询对象的给定假设(注意:有少数属性是例外,因为它们不使用假设系统,请参阅假设指南)。例如,x.is_integer
将查询 x
的 integer
假设。使用大写名称的 is_*
属性测试对象是否是给定类的实例。有时相同名称将同时存在于小写和大写属性中,但它们代表不同的事物。例如,只有当 x
是 Integer
的实例时,x.is_Integer
才为 True
,而 x.is_integer
只有在 x
是假设系统中的 integer
时才为 True
,如 x = symbols('x', integer=True)
。一般建议不使用 is_Capitalized
属性。它们存在是为了历史目的,但是使用 isinstance()
可以达到同样的效果。另请参阅 Number。
isympy
isympy
是一个与 SymPy 一起提供的命令,它在命令行上启动一个交互会话,导入所有 SymPy 名称并启用打印功能。默认情况下,安装时使用 IPython。
类型
SymPy 对象的类型表示其代表的数学对象的种类。对象的类型可以通过 kind
属性访问。例如,NumberKind
代表复数,MatrixKind
代表其他某种矩阵,以及 BooleanKind
代表布尔谓词。SymPy 对象的类型与其 Python 类型不同,因为有时一个单一的 Python 类型可能代表许多不同种类的对象。例如,Matrix
可能是复数矩阵,也可能是某些其他值环中的对象矩阵。详见 SymPy 对象的分类页面关于类型的分类了解更多详情。
lamda
“Lamda” 只是希腊字母lambda
的另一种拼写方式。在 SymPy 中有时会使用这个拼写,因为在 Python 中 lambda
是一个保留关键字,所以表示 λ 的符号必须取别的名字。
lambdify()
lambdify()
是一个函数,将 SymPy 表达式转换为可以进行数值评估的 Python 函数,通常使用类似 NumPy 的数值库。
矩阵
矩阵 是 SymPy 用于表示矩阵的一组类。SymPy 有几个内部类用于表示矩阵,取决于矩阵是否是符号化的(MatrixExpr
)、显式的、可变的或不可变的、稠密的或稀疏的,以及底层元素的类型是什么,但通常统称为“矩阵”。
mpmath
mpmath 是一个纯 Python 库,用于任意精度数值计算。它是 SymPy 的一个硬依赖。mpmath 能够计算数值函数至任意精度位数。每当 SymPy 对表达式进行数值评估时(例如使用 evalf),mpmath 就在幕后发挥作用。
数值
数值 表示或算法直接操作数值输入。它与 符号 表示或算法相对,后者可以处理未求值形式的对象。通常数值算法与符号算法有很大不同。例如,数值求解常微分方程通常意味着使用像 Runge–Kutta 这样的算法来在给定初始条件下找到一组数值点,而符号求解常微分方程(例如使用 SymPy 的 dsolve()
)意味着数学上操纵常微分方程以生成一个 符号 方程,该方程表示解。符号常微分方程的解可能包含符号常数,这些符号常数可以表示任何数值。数值算法通常围绕浮点数引起的问题设计,如精度损失和数值稳定性,而符号算法则不涉及这些问题,因为它们可以精确计算。
大多数科学库,如 NumPy 或 SciPy,严格是数值的,意味着这些库中的函数只能操作特定的数值输入。它们无法处理 SymPy 表达式,因为它们的算法不是设计用于符号输入。SymPy 主要关注符号函数,将纯数值代码留给像 NumPy 这样的其他工具。然而,SymPy 通过工具如 代码生成 和 lambdify() 与数值库进行接口。
Number
Number 可以指 SymPy 中的两种东西:
-
类
Number
是显式数值(Integer
、Rational
和Float
)的基类。符号数值常量如pi
不是Number
的实例。 -
小写的 "number",例如
is_number
属性,指的是可以 evalfed 成显式Number
的任何 表达式。这包括像pi
这样的符号常数。注意,is_number
不是 假设 系统的一部分。
对于 is_Number
和 is_number
属性,这种区别非常重要。x.is_Number
将检查 x
是否是 Number
类的一个实例。
oo
oo
是 SymPy 表示正无穷大的对象。它以这种方式拼写,作为两个小写字母 O,因为它类似于符号(\infty)且易于输入。另见 zoo。
Polys
polys指的是sympy.polys
子模块,它实现了多项式操作的基本数据结构和算法。polys 是 SymPy 的关键部分(虽然通常不被认为是核心的一部分),因为许多基本的符号操作可以表示为对多项式的操作。SymPy 中的许多算法在内部使用多项式。例如,factor()
是对多项式因式分解算法的一种封装,这些算法在多项式中实现。polys 中的类使用高效的数据结构实现,并且不像 SymPy 中的其他类一样,不是 Basic 的子类。
打印
打印指的是将一个表达式转换为可以在屏幕上查看的形式。打印通常也用于指代代码生成。SymPy 有几个打印机,可以使用不同格式表示表达式。一些更常见的打印机是字符串打印机(str()
),漂亮的打印机(pprint()
),LaTeX 打印机(latex()
)和代码打印机。
关系
关系是一个表达式,它是符号 等式(比如(a=b)),或者一个象征着“小于”((a<b))的符号不等式。等式((=))和不等式((\neq))的关系是用Eq
和Ne
创建的。例如,Eq(x, 0)
表示(x=0)。这些应该用于代替==
或!=
,因为这些用于结构而不是象征性的相等。不等关系可以直接使用<
,<=
,>
和>=
来创建,比如x < 0
。
S
SymPy 中的S
对象有两个用途:
-
它将所有单例类作为属性保存。SymPy 中的一些特殊类被设计为单例化,意味着它们始终只有一个实例。这是一种优化方法,可以节省内存。例如,
Integer(0)
只有一个实例,可以通过S.Zero
获取。 -
它充当 sympify() 的缩写,即
S(a)
等同于sympify(a)
。这在将整数转换为 SymPy 整数以避免在表达式中使用 Python 整数进行除法时非常有用(参见 教程的注意事项部分)。
简化
简化(不要与 sympify 混淆)指的是将一个 表达式 转换为另一个在数学上等价但在某种意义上“更简单”的表达式的过程。“简单”的形容词实际上并不十分明确。什么算简单取决于具体的用例和个人审美观。
SymPy 函数 simplify()
根据启发式方法尝试各种简化算法,以找到表达式的“更简单”形式。如果你对“简化”想要什么并不是特别确定,它可能是一个不错的选择。但如果你对想要应用的简化有一个具体的想法,通常最好使用一个或多个目标化的 简化函数,这些函数对表达式应用非常具体的数学操作。
解决
求解器
对于 解 一个 方程 或方程组意味着找到一组 表达式,当给定的 符号 被它们替换时,方程(们)为真。例如,对于方程 (x² = 1) 关于 (x) 的解是集合 ({-1, 1})。SymPy 可以使用不同的 求解器 函数来解决不同类型的方程。例如,代数方程可以用 solve()
解决,微分方程可以用 dsolve()
解决,等等。
SymPy 通常使用“solve”和“solvers”来表示这种意义上的方程求解。它不用于“解决问题”的意义。例如,通常会更倾向于说“计算一个积分”或“评估一个积分”,而不是“解决一个积分”,来表示使用函数 integrate()
进行符号积分。
结构相等性
两个 SymPy 对象如果作为表达式相等,则它们被认为是结构相等的,即它们具有相同的表达式树。在 SymPy 中,两个结构相等的表达式被认为是相同的,因为所有 SymPy 表达式都是不可变的。结构相等可以通过==
操作符来检查,它总是返回True
或False
。符号等式可以用Eq
来表示。
通常,如果两个表达式是相同的类,并且(递归地)具有相同的 args,那么它们就是结构相等的。两个表达式可能在数学上是相同的,但在结构上不相等。例如,(x + 1)**2
和 x**2 + 2*x + 1
在数学上是相等的,但它们在结构上不相等,因为前者是一个 Pow
,其 args 包括一个Add
和一个Integer
,而后者是一个Add
,其 args 包括一个Pow
,一个Mul
和一个Integer
。
两个表面上不同的表达式,如果它们经过规范化后变成相同的形式,那么它们就是结构相等的。例如,x + y
和 y + x
结构上是相等的,因为Add
构造器会自动排序其参数,使它们变成相同的形式。
子表达式
子表达式 是一个包含在较大表达式中的表达式。子表达式出现在表达式树的某处。对于Add
和Mul
项,当确定什么是子表达式时,可以考虑交换律和结合律。例如,x + y
有时可能被认为是 x + y + z
的子表达式,即使Add(x, y)
的表达式树不是Add(x, y, z)
的直接子节点。
替换
替换是指用另一个表达式替换 expression 内部的 symbol 或 subexpression 的行为。在 SymPy 中有不同的方法执行替换,包括subs
、replace
和xreplace
。这些方法可能因是否只使用严格的 structural equality 或在确定子表达式在表达式中出现的位置时利用数学知识而有所不同。替换是将表达式视为数学 function 并在某点评估的标准方法。
符号
符号表示一个数学对象的表示,在运行时部分或完全未评估。它可能包括以命名的 symbolic constants 代替显式数值。符号表示通常与 numeric 表示形成对比。符号表达是数学上精确的,与通常舍入以适应浮点值的数值表示相对。符号表示数学对象的表达可能意识到这些对象的数学属性,并能够利用这些属性简化为等效的符号表达式。SymPy 的目标是表示和操作代表各种数学对象的符号表达式。
一些来源使用术语“分析解”或“闭合形式”来指代“符号”的概念,但这种术语在 SymPy 中不使用。如果在 SymPy 中使用,“分析”将指代解析函数的特性,在 SymPy 中 solve 仅指特定类型的符号操作。“闭合形式”在 SymPy 中通常指数学上的意义,而“符号”通常指的是数学概念如何实现的实现细节,并与相同数学概念的 numeric 实现形成对比。
Symbol
Symbol
是符号对象的类。符号代表表达式中的单个数学变量。Symbol
类是 Expr 的子类,是 atomic 的。一个Symbol
包含一个名称,可以是任何字符串,以及 assumptions。符号通常使用Symbol
构造函数或symbols()
函数定义。具有相同名称和假设的两个 Symbols 被认为是 equal 的。Symbols 通常被隐式地假设是彼此独立或常数。常量、变量和参数都由 Symbols 表示。在给定的 SymPy 函数中,通常通过 Symbols 的使用方式来区分它们。
sympify()
sympify()
(不要与simplify()混淆)是将非 SymPy 对象转换为 SymPy 对象的函数。sympify()
的结果将是 Basic 的一个实例。可以被sympified的对象包括本地 Python 数值类型如int
和float
,可以解析为 SymPy 表达式的字符串以及包含sympifiable对象的可迭代对象(详见sympify()
的文档了解更多信息)。
因为所有 SymPy 表达式必须是 Basic 的实例,所有 SymPy 函数和操作在其输入上会隐式调用sympify()
。例如,x + 1
会隐式调用sympify(1)
将 Python int
类型的1
转换为 SymPy 的Integer
。接受 SymPy 表达式的函数通常应该在其参数上调用sympify()
,以确保它们在输入不是 SymPy 类型时也能正常工作。
三值逻辑
三值逻辑是一种具有三个值 True
、False
和 None
的逻辑。有时它也被称为模糊逻辑,尽管在数学文献中这个术语也有不同的含义,因此“三值逻辑”更受推荐。True
和 False
的作用与通常的二值谓词逻辑相同。None
是一个额外的术语,表示“未知”、“不可计算”或“可以是 True 或 False”(从哲学上讲这些是不同的概念,但逻辑上它们完全相同)。None
的语义是在逻辑操作中吸收其他术语,每当如果将其替换为 True
或 False
结果将会不同时。例如,None OR False
是 None
,但 None OR True
是 True
,因为谓词是 True
,无论 None
“实际上”代表 True
还是 False
。在使用通常的 Python 逻辑运算符如 and
、or
和 not
时,必须小心处理三值逻辑,因为 None
为假。有关如何使用三值逻辑编码的更多详细信息,请参阅符号和模糊布尔值指南。
假设系统使用三值逻辑表示未知的假设。例如,如果在给定的假设下 x.is_positive
可能是 None
,因为 x
可以是正数或负数。请注意,由布尔子类定义的谓词逻辑表示标准的二值逻辑,而不是三值逻辑。
未定义函数
未定义函数是函数的一种,在其上没有定义数学属性。它始终保持未评估,例如 f(x)
。通过将函数的字符串名称传递给 Function
,例如 f = Function('f')
,可以创建未定义函数。在处理 ODEs 时,通常使用未定义函数。未定义函数还是制造符号的最简单方法,这些符号在数学上依赖于其他符号。例如,如果 f = Function('f')
和 x = Symbol('x')
,那么 SymPy 将知道 f(x)
取决于 x
,这意味着例如导数 diff(f(x), x)
不会被评估为 0
。
未评估
如果自动化简在创建表达式时被禁用,则表达式是未评估的。通常通过设置 evaluate=False
,使用 with evaluate(False)
或使用 UnevaluatedExpr
来完成这一点。虽然支持未评估表达式,但有时会导致意外行为,因为表达式未正确规范化。
术语未评估有时也用来表示当其参数是符号的时,表达式不会评估为特定值。
zoo
zoo
代表复无穷,即Riemann 球面的北极。它这样拼写的原因是,“z-oo”,其中“z”通常用于复变量的符号,而 oo 则是 SymPy 用于正无穷的符号。
API 参考
本节包含 SymPy 模块、函数、类和方法的摘要。sympy
核心子包中实现的所有函数和对象在下面都有文档。
基础
包含基础模块操作的描述。子类别包括:基础概念,操作,假设,函数,简化,微积分,求解器,以及其他一些子类别。
代码生成
包含生成可编译和可执行代码的方法描述。
逻辑
包含逻辑和集合模块的方法详细信息。
矩阵
讨论了矩阵、张量和向量模块的方法。
数论
文档了数论模块的方法。
物理
包含物理方法的文档。
实用工具
包含多个实用模块方法的文档字符串。子类别包括:交互式,解析,打印,测试,实用工具。
主题
包含多个模块的方法文档字符串。子类别包括:绘图,多项式,几何,范畴论,密码学,微分,Holonomic,李代数,和统计。
基础
目录
-
假设
-
询问
-
假设
-
细化
-
谓词
-
-
微积分
-
组合数学
-
分割
-
排列
-
置换群
-
多面体
-
普鲁弗序列
-
子集
-
格雷码
-
命名群
-
伽罗瓦群
-
群的数量
-
实用工具
-
群构造器
-
测试工具
-
张量标准化
-
有限呈现群
-
多环群
-
-
函数
-
初等
-
组合
-
枚举
-
特殊
-
-
积分
-
使用 Meijer G-函数计算积分
-
积分
-
-
级数
-
级数展开
-
序列
-
傅立叶级数
-
形式幂级数
-
序列极限
-
-
简化
-
简化
-
超几何展开
-
傅宏光的三角简化
-
-
求解器
-
丢番图方程
-
不等式求解器
-
常微分方程
-
偏微分方程
-
求解器
-
解集
-
-
abc
-
代数
-
具体
-
核心
-
离散
-
数值评估
-
数值计算
-
术语重写
假设
一个模块用于实现逻辑谓词和假设系统。
谓词
class sympy.assumptions.assume.Predicate(*args, **kwargs)
数学谓词的基类。它还充当未定义谓词对象的构造函数。
解释
谓词是返回布尔值的函数[1]。
谓词函数是对象,并且是谓词类的实例。当谓词应用于参数时,返回AppliedPredicate
实例。这仅包装参数并保持未评估状态。要获取应用谓词的真值,请使用函数ask
。
谓词的评估通过多重分派完成。您可以注册新的处理程序到谓词以支持新类型。
SymPy 中的每个谓词都可以通过Q
的属性访问。例如,Q.even
返回检查参数是否为偶数的谓词。
要定义一个可评估的谓词,必须子类化此类,创建其实例,并将其注册到Q
。之后,通过参数类型分派处理程序。
如果直接使用此类构造谓词,则将获得不能分派的UndefinedPredicate
。这在构建不需要评估的布尔表达式时很有用。
示例
应用和评估布尔值:
>>> from sympy import Q, ask
>>> ask(Q.prime(7))
True
您可以通过子类化和分派来定义新的谓词。这里,我们以性质素数[2]为例定义一个谓词。
>>> from sympy import Predicate, Integer
>>> class SexyPrimePredicate(Predicate):
... name = "sexyprime"
>>> Q.sexyprime = SexyPrimePredicate()
>>> @Q.sexyprime.register(Integer, Integer)
... def _(int1, int2, assumptions):
... args = sorted([int1, int2])
... if not all(ask(Q.prime(a), assumptions) for a in args):
... return False
... return args[1] - args[0] == 6
>>> ask(Q.sexyprime(5, 11))
True
直接构造返回UndefinedPredicate
,可以应用但不能分派。
>>> from sympy import Predicate, Integer
>>> Q.P = Predicate("P")
>>> type(Q.P)
<class 'sympy.assumptions.assume.UndefinedPredicate'>
>>> Q.P(1)
Q.P(1)
>>> Q.P.register(Integer)(lambda expr, assump: True)
Traceback (most recent call last):
...
TypeError: <class 'sympy.assumptions.assume.UndefinedPredicate'> cannot be dispatched.
参考文献
[R7]
zh.wikipedia.org/wiki/%E8%B0%93%E8%AF%8D
[R8]
zh.wikipedia.org/wiki/%E5%AF%86%E5%88%A9%E5%A5%87%E8%B0%93
eval(args, assumptions=True)
在给定假设下评估self(*args)
。
这仅使用直接解决方法,不使用逻辑推理。
handler = <dispatched AskPredicateHandler>
classmethod register(*types, **kwargs)
注册签名到处理程序。
classmethod register_many(*types, **kwargs)
向同一处理程序注册多个签名。
class sympy.assumptions.assume.AppliedPredicate(predicate, *args)
将Predicate
应用于参数所产生的表达式类。AppliedPredicate
仅仅包装其参数并保持未评估状态。要评估它,请使用ask()
函数。
示例
>>> from sympy import Q, ask
>>> Q.integer(1)
Q.integer(1)
function
属性返回谓词,arguments
属性返回参数元组。
>>> type(Q.integer(1))
<class 'sympy.assumptions.assume.AppliedPredicate'>
>>> Q.integer(1).function
Q.integer
>>> Q.integer(1).arguments
(1,)
可以使用ask
将应用的谓词评估为布尔值:
>>> ask(Q.integer(1))
True
property arg
返回此假设所用的表达式。
示例
>>> from sympy import Q, Symbol
>>> x = Symbol('x')
>>> a = Q.integer(x + 1)
>>> a.arg
x + 1
property arguments
返回应用于谓词的参数。
property function
返回谓词。
查询
查询用于询问关于表达式的信息。这的主要方法是ask()
:
sympy.assumptions.ask.ask(proposition, assumptions=True, context={})
使用假设评估命题的函数。
参数:
命题:布尔值
将评估为布尔值的命题。如果不是
AppliedPredicate
,它将被Q.is_true
包装。
假设:布尔值,可选
评估命题的局部假设。
context:AssumptionsContext,可选
默认假设用于评估proposition。默认情况下,这是
sympy.assumptions.global_assumptions
变量。
返回:
True
、False
或None
Raises:
TypeError:proposition或assumptions不是有效的逻辑表达式。
ValueError:假设不一致。
解释
如果可以确定真值,则此函数将评估proposition为True
或False
。否则,它将返回None
。
它应该从refine()
中区分出来,当应用于proposition时,简化参数为符号Boolean
而不是 Python 内置的True
、False
或None
。
语法
ask(proposition)
在全局假设上下文中评估proposition。
ask(proposition, assumptions)
在全局假设上下文中,根据assumptions评估proposition。
示例
>>> from sympy import ask, Q, pi
>>> from sympy.abc import x, y
>>> ask(Q.rational(pi))
False
>>> ask(Q.even(x*y), Q.even(x) & Q.integer(y))
True
>>> ask(Q.prime(4*x), Q.integer(x))
False
如果无法确定真值,则返回None
。
>>> print(ask(Q.odd(3*x))) # cannot determine unless we know x
None
如果假设不一致,则引发ValueError
。
>>> ask(Q.integer(x), Q.even(x) & Q.odd(x))
Traceback (most recent call last):
...
ValueError: inconsistent assumptions Q.even(x) & Q.odd(x)
注意事项
假设关系尚未实现(但),因此以下内容将不会给出有意义的结果。
>>> ask(Q.positive(x), x > 0)
然而,这还在进展中。
另请参阅
sympy.assumptions.refine.refine
使用假设简化。如果无法确定真值,则proposition不会简化为None
。
ask
的第二个可选参数应为涉及expr中对象假设的布尔表达式。有效值包括:
Q.integer(x)
Q.positive(x)
Q.integer(x) & Q.positive(x)
- 等等。
Q
是一个持有已知谓词的对象。
有关逻辑模块的文档,请参阅完整的有效布尔表达式列表。
您还可以定义上下文,这样您就不必每次都将该参数传递给函数ask()
。这可以通过使用模块sympy.assumptions
中的假设上下文管理器来完成。
>>> from sympy import *
>>> x = Symbol('x')
>>> y = Symbol('y')
>>> facts = Q.positive(x), Q.positive(y)
>>> with assuming(*facts):
... print(ask(Q.positive(2*x + y)))
True
内容
-
询问
-
假设
-
精化
-
谓词
性能改进
在涉及符号系数的查询中,使用逻辑推理。致力于改进可满足功能(sympy.logic.inference.satisfiable)应导致显著的速度改进。
一个 ask 中使用的逻辑推理可以用来加快进一步查询的速度,但当前系统未利用这一点。例如,可以实现真值维护系统(en.wikipedia.org/wiki/Truth_maintenance_system
)。
杂项
您可以在目录sympy/assumptions/tests/
中找到更多示例。
问
用于查询关于假设的 SymPy 对象的模块。
class sympy.assumptions.ask.AssumptionKeys
这个类包含了ask
系统中所有支持的键。它应该通过实例sympy.Q
来访问。
sympy.assumptions.ask.ask(proposition, assumptions=True, context={})
用于带有假设的命题评估的函数。
参数:
proposition:布尔值
将被评估为布尔值的命题。如果不是
AppliedPredicate
,它将被Q.is_true
包装。
assumptions:布尔值,可选
用于评估proposition的本地假设。
context:AssumptionsContext,可选
用于评估proposition的默认假设。默认情况下,这是
sympy.assumptions.global_assumptions
变量。
返回:
True
、False
或None
引发:
TypeError:proposition或assumptions不是有效的逻辑表达式。
ValueError:假设不一致。
解释
这个函数在能够确定真值时将命题评估为True
或False
。如果不能确定,将返回None
。
这应该与refine()
区分开来,当应用于命题时,它将简化参数为符号Boolean
,而不是 Python 内置的True
、False
或None
。
Syntax
ask(proposition)
在全局假设上评估proposition。
ask(proposition, assumptions)
在全局假设上评估proposition关于assumptions。
示例
>>> from sympy import ask, Q, pi
>>> from sympy.abc import x, y
>>> ask(Q.rational(pi))
False
>>> ask(Q.even(x*y), Q.even(x) & Q.integer(y))
True
>>> ask(Q.prime(4*x), Q.integer(x))
False
如果无法确定真值,将返回None
。
>>> print(ask(Q.odd(3*x))) # cannot determine unless we know x
None
如果假设不一致,将引发ValueError
。
>>> ask(Q.integer(x), Q.even(x) & Q.odd(x))
Traceback (most recent call last):
...
ValueError: inconsistent assumptions Q.even(x) & Q.odd(x)
注意事项
假设中的关系尚未实现(但是),因此以下内容不会给出有意义的结果。
>>> ask(Q.positive(x), x > 0)
然而,这还在进行中。
另请参阅
sympy.assumptions.refine.refine
使用假设进行简化。如果无法确定真值,命题不会简化为None
。
sympy.assumptions.ask.register_handler(key, handler)
在 ask 系统中注册一个处理程序。键必须是字符串,处理程序必须是继承自 AskHandler 类的类。
自版本 1.8 起已弃用:改用 multipledispatch 处理程序。参见Predicate
。
sympy.assumptions.ask.remove_handler(key, handler)
从 ask 系统中删除处理程序。
自版本 1.8 起已弃用:改用 multipledispatch 处理程序。参见Predicate
。
假设
实现谓词和假设上下文的模块。
class sympy.assumptions.assume.AppliedPredicate(predicate, *args)
通过将Predicate
应用于参数的表达式类。AppliedPredicate
仅仅包装其参数并保持未评估状态。要评估它,请使用ask()
函数。
示例
>>> from sympy import Q, ask
>>> Q.integer(1)
Q.integer(1)
function
属性返回谓词,arguments
属性返回参数元组。
>>> type(Q.integer(1))
<class 'sympy.assumptions.assume.AppliedPredicate'>
>>> Q.integer(1).function
Q.integer
>>> Q.integer(1).arguments
(1,)
应用谓词可以用ask
来评估为布尔值:
>>> ask(Q.integer(1))
True
property arg
返回此假设使用的表达式。
示例
>>> from sympy import Q, Symbol
>>> x = Symbol('x')
>>> a = Q.integer(x + 1)
>>> a.arg
x + 1
property arguments
返回应用于谓词的参数。
property function
返回谓词。
class sympy.assumptions.assume.AssumptionsContext
设置包含应用于ask()
函数的默认假设的集合。
解释
这用于表示全局假设,但您也可以使用此类创建自己的局部假设上下文。基本上它是 Python 集合的薄包装,因此请参阅其文档以获取高级用法。
示例
默认的假设上下文是global_assumptions
,最初是空的:
>>> from sympy import ask, Q
>>> from sympy.assumptions import global_assumptions
>>> global_assumptions
AssumptionsContext()
您可以添加默认的假设:
>>> from sympy.abc import x
>>> global_assumptions.add(Q.real(x))
>>> global_assumptions
AssumptionsContext({Q.real(x)})
>>> ask(Q.real(x))
True
并移除它们:
>>> global_assumptions.remove(Q.real(x))
>>> print(ask(Q.real(x)))
None
clear()
方法移除每一个假设:
>>> global_assumptions.add(Q.positive(x))
>>> global_assumptions
AssumptionsContext({Q.positive(x)})
>>> global_assumptions.clear()
>>> global_assumptions
AssumptionsContext()
另见
assuming
add(*assumptions)
添加假设。
class sympy.assumptions.assume.Predicate(*args, **kwargs)
数学谓词的基类。它还用作未定义谓词对象的构造函数。
解释
谓词是返回布尔值的函数 [1]。
谓词函数是对象,是谓词类的实例。当谓词应用于参数时,返回AppliedPredicate
实例。这仅仅是包装了参数并保持未评估状态。要获取应用谓词的真值,请使用函数ask
。
谓词的评估是通过多重分派完成的。您可以注册新的处理程序到谓词以支持新类型。
SymPy 中的每个谓词都可以通过Q
的属性访问。例如,Q.even
返回检查参数是否为偶数的谓词。
要定义一个可以评估的谓词,您必须子类化此类,并对其进行实例化,并将其注册到Q
。然后,按参数类型分派处理程序。
如果直接使用此类构造谓词,您将得到不能分派的UndefinedPredicate
。当您构建不需要评估的布尔表达式时,这是有用的。
示例
应用和评估为布尔值:
>>> from sympy import Q, ask
>>> ask(Q.prime(7))
True
您可以通过子类化和分派来定义新的谓词。在这里,我们定义一个性感素数 [2] 作为示例。
>>> from sympy import Predicate, Integer
>>> class SexyPrimePredicate(Predicate):
... name = "sexyprime"
>>> Q.sexyprime = SexyPrimePredicate()
>>> @Q.sexyprime.register(Integer, Integer)
... def _(int1, int2, assumptions):
... args = sorted([int1, int2])
... if not all(ask(Q.prime(a), assumptions) for a in args):
... return False
... return args[1] - args[0] == 6
>>> ask(Q.sexyprime(5, 11))
True
直接构造返回UndefinedPredicate
,可以应用但不能分派。
>>> from sympy import Predicate, Integer
>>> Q.P = Predicate("P")
>>> type(Q.P)
<class 'sympy.assumptions.assume.UndefinedPredicate'>
>>> Q.P(1)
Q.P(1)
>>> Q.P.register(Integer)(lambda expr, assump: True)
Traceback (most recent call last):
...
TypeError: <class 'sympy.assumptions.assume.UndefinedPredicate'> cannot be dispatched.
参考
[R5]
en.wikipedia.org/wiki/Predicate_%28mathematical_logic%29
[R6]
en.wikipedia.org/wiki/Sexy_prime
eval(args, assumptions=True)
在给定假设条件下评估self(*args)
。
这只使用直接解析方法,而不是逻辑推理。
handler = <dispatched AskPredicateHandler>
classmethod register(*types, **kwargs)
将签名注册到处理程序。
classmethod register_many(*types, **kwargs)
将多个签名注册到同一处理程序。
class sympy.assumptions.assume.UndefinedPredicate(name, handlers=None)
谓词无处理程序。
解释
此谓词是通过直接使用Predicate
进行构造生成的。它没有处理程序,通过 SAT 求解器对其进行参数评估。
示例
>>> from sympy import Predicate, Q
>>> Q.P = Predicate('P')
>>> Q.P.func
<class 'sympy.assumptions.assume.UndefinedPredicate'>
>>> Q.P.name
Str('P')
sympy.assumptions.assume.assuming(*assumptions)
假设的上下文管理器。
示例
>>> from sympy import assuming, Q, ask
>>> from sympy.abc import x, y
>>> print(ask(Q.integer(x + y)))
None
>>> with assuming(Q.integer(x), Q.integer(y)):
... print(ask(Q.integer(x + y)))
True