SymPy-1-13-中文文档-三-

SymPy 1.13 中文文档(三)

原文:docs.sympy.org/latest/index.html

单变量代数系统的一个或一组不等式简化

原文: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) 

ooInfinity.)

如果每个不等式仅包含一个要简化的符号,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 

处理参数

你可以操作像 pq 这样由 diophantine() 自动生成的参数,将它们创建为符号。例如,要找到满足丢番图方程的特定值集合,可以通过以下方式替换参数的值

  1. 创建参数作为符号

  2. 使用 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} 

注意,需要对生成的参数 (pq) 包含 integer=True 假设,以便为它们提供数值。相反,对于原始方程中的符号 (a, b, 和 c),不需要包含 integer=True 假设,尽管这是一个好习惯。

要遍历解集,可以在嵌套循环中迭代参数 (pq) 的值:

>>> 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

原文:docs.sympy.org/latest/guides/logo.html

我们希望让您能够轻松地在下一篇学术论文、课程材料或演示中包含 SymPy 项目标识。

SymPy Logo

上述图像标识是标志的 SVG 版本。我们还有标志的 PNG 版本:

SymPy Logo

如果您想要一个没有“SymPy”文本的标识,我们也有:

SymPy Logo

注意:应优先选择文本版本,除非“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

原文:docs.sympy.org/latest/citing.html

要在出版物中引用 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 上找到

解释

原文:docs.sympy.org/latest/explanation/index.html

解释提供了对选择的 SymPy 特性进行深入讨论的内容。这些主题指南讨论了设计决策背后的动机、技术实现细节和具有观点的建议。

内容

  • 最佳实践

  • 注意事项和陷阱

  • 按类型解决输出

  • SymPy 特殊主题

  • 活跃弃用列表

  • 术语表

最佳实践

原文:docs.sympy.org/latest/explanation/best-practices.html

本页面概述了使用 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=Truepositive=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) 
    

    在第一个例子中,sinesin的打字错误)被解析为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 函数。这包括 numpyscipy 命名空间中的任何内容,以及来自其他 Python 库(如 matplotlib)的大多数函数。这些函数只设计用于处理具有数值值的 NumPy 数组。

  • 不要将 SymPy 表达式传递给 lambdify 函数。 与前一点类似,不应将 SymPy 表达式传递给使用 lambdify 创建的函数。实际上,lambdify 返回的函数就是 NumPy 函数,因此这里的情况完全相同。某些情况下,通过 lambdify() 创建的函数可能会与 SymPy 表达式一起工作,但这只是其工作方式的偶然结果。有关此现象更多详细信息,请参阅 lambdify() 文档的“how it works”部分。

  • 避免将 SymPy 表达式存储在 NumPy 数组中。 虽然技术上可以将 SymPy 表达式存储在 NumPy 数组中,但这样做通常是一个错误。表明这种情况发生的标志是如果 NumPy 数组的 dtypeobject(而不是像 float64int64 这样的数值类型)。

    就像在 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 的MatrixNDimArray类。

高级用法

谨慎比较和排序符号对象

在编写比较数值量的程序代码时要小心,可以直接使用不等式(<<=>>=)或间接使用像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_positiveTrue时才会运行该块,但当x.is_positiveNone时,您可能希望执行某些操作。

  • 返回一个分段函数结果。 如果函数的结果取决于不等式或其他布尔条件,您可以使用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 对象应始终满足以下不变量:

  1. all(isinstance(arg, Basic) for arg in args)

  2. 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) 

该函数通过递归遍历exprargs,并重建它,除了x的任何实例都被替换为y

很容易看出,如果参数不变量不成立,这个函数会如何破坏:

  1. 如果一个表达式的 args 不是Basic,它们在递归调用时将导致AttributeError失败,因为非Basic的 args 将没有.args.func属性。

  2. 如果一个表达式没有从它的args中重建出来,那么return exr.func(*newargs)这行代码将失败,即使在没有替换任何 args 的情况下,这应该有效地是一个无操作。

将所有args实例转换为Basic通常意味着对类的输入调用_sympify(),这样它们就成为基本实例了。如果你想在类上存储一个字符串,你应该使用Symbolsympy.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 将是类的直接参数。然而,如果可能需要支持符号无限集合,例如IntegersRange,那么最好使用 args,因为这将不可能使用 *args 来实现。

避免在对象上存储额外属性

您可能希望创建一个自定义的 SymPy 对象的常见原因是您想在对象上存储额外的属性。然而,以简单的方式,即仅将数据作为 Python 属性存储在对象上,几乎总是不明智的做法。

SymPy 不希望对象在其 args 之外存储额外的数据。例如,这会破坏 == 检查,因为它只比较对象的 args。有关为什么覆盖 __eq__ 是一个坏主意,请参阅下面的不要覆盖 eq 部分。这一节和那一节是密切相关的。

通常情况下,根据您的具体情况,有更好的方法来完成您想要做的事情:

  • 将额外数据存储在对象的args中。 如果您想要存储的额外数据是对象数学描述的一部分,这是最佳方法。

    只要数据可以使用其他 SymPy 对象来表示,它就可以存储在 args 中。请注意,对象的 args 应该可以用于重新创建对象(例如,类似 YourObject(*instance.args) 的东西应该可以重新创建 instance)。

    此外,应该提到,如果您计划在args中存储任何额外内容,则不建议子类化SymbolSymbol的设计是围绕着没有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意味着ab是完全相同的对象。它们具有相同的类型和相同的 args。==不执行任何类型的数学相等检查。例如,

>>> x*(x - 1) == x**2 - x
False 

==始终返回布尔值TrueFalse。符号方程可以用Eq表示。

有几个原因造成了这种情况

  • 数学相等性检查可能非常昂贵,并且一般来说,计算上不可能确定

  • Python 本身在各处自动使用==,并假定它返回布尔值且计算成本低廉。例如,如果b是内置的 Python 容器如listdictset,那么a in b使用==[1]

  • SymPy 在内部各处都使用==,明确地和隐含地,例如通过in或字典键。这种使用隐含地假定==操作是结构化的。

实际上,结构相等性意味着如果a == bTrue,那么在所有意义上ab是相同的对象。这是因为所有的 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

原文:docs.sympy.org/latest/explanation/gotchas.html

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'] 

如果您希望控制变量的假设条件,请使用 Symbolsymbols()。请参阅下面的关键字参数。

最后,建议不要使用 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 

当将cossin项评估为 15 位精度并乘以大数时,它们给出了一个大约 15 位精度的大数,当 20 位大数被减去时,结果不为零;

有三件事可以帮助你获得更精确的表达式数值:

  1. 通过调用评估传递所需的替换。通过首先进行替换,可以防止 Float 值按需更新。通过在 evalf 调用中传递所需的替换,可以重新评估的能力是获得印象深刻更好的结果:
>>> big_trig_identity.n(2, {x: 0.1})

-0.e-91 
  1. 使用有理数,而不是浮点数。在评估过程中,有理数可以计算到任意精度,而浮点数一旦创建(默认为 15 位数字)就不能这样做。将 x 替换为表示 1/10 的有理数后,在进行评估之前,将上述-1.4e+3 的值与几乎为零的值进行比较:
>>> big_trig_identity.subs(x, S('1/10')).n(2)

0.e-91 
  1. 尝试简化表达式。在这种情况下,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 的名称asinacos等,而不是通常的arcsinarccos。使用上面符号描述的方法查看所有 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.orgSymPy 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>)

通过合并具有相似基数和指数的幂来简化表达式。

解释

如果 deepTrue,那么 powsimp() 还将简化函数的参数。默认情况下,deep 设置为 False

如果 forceTrue,则将合并基数而不检查假设,例如,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)) 

按类型解决输出

原文:docs.sympy.org/latest/explanation/solve_output.html

solve() 函数的输出看起来非常笨重,因为它可能表现出任意返回六种不同类型的输出(除了引发错误)。这些原因是历史性的,倾向于人类互动而不是程序化使用。输出类型将取决于方程的类型(及其输入方式)以及提供的符号数量(及其提供方式)。

>>> from sympy import sqrt, exp, solve, Symbol, Eq

>>> from sympy.abc import x, y, z, a, b 

solve() 函数尝试找到尽可能多的符号值,使得给定的每个表达式等于零。输出的格式可以通过使用dictset关键字来控制:

>>> 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()(并且dictsetcheck标志将被忽略)。

>>> 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 flagPYTHONWARNINGS 环境变量 无法用于过滤 SymPy 弃用警告(请参阅 Ned Batchelder 的此篇博文和 SymPy 的此问题了解详细信息)。您需要添加类似上述的 warnings 过滤器或使用 pytest 来过滤 SymPy 弃用警告。

版本 1.13

弃用机制 Body 类

sympy.physics.mechanics 模块中的 Body 类已被弃用。它最初用于支持关节框架,但由于既表示刚体又表示粒子而导致多种问题。Body 现已完全由 RigidBodyParticle 替代。以前,只需使用 Body 类即可创建简单的刚体或粒子:

>>> from sympy import symbols
>>> from sympy.physics.mechanics import Body
>>> Body("rigid_body")  
rigid_body
>>> Body("particle", mass=symbols("m"))  
particle 

现在应使用 RigidBodyParticle 类创建:

>>> 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 中移除。对这些类的子类化已被弃用,任何这样做的代码应修改为不再子类化它们。

使用 isinstanceMatrixCommon 这样的类也已被弃用,例如 isinstance(M, MatrixCommon)。任何使用此方法的代码应改为使用 isinstance(M, Matrixbase),这也适用于先前的 SymPy 版本。

更一般地,导入 sympy.matrices.commonsympy.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。考虑到显式使用 lhsrhs 的清晰度,不再认为需要替换属性/方法,并且将此功能包含在重写装置中导致期望布尔值的节点重写为表达式时失败。 ### 弃用标记,注释,填充,图类的矩形

包含用户提供的数值数据以添加到绘图上的 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=Truesymmetric=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 版本中行为一致的代码,您可以:

  1. 使用 symmetric=False 并使用 int(a)

  2. 定义一个函数,如

    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中泛化坐标的类型相同。PinJointPrismaticJoint的泛化坐标和泛化速度的自动命名也已更改为q_<joint.name>u_<joint.name>。以前,每个关节都有一个独特的模板来自动生成这些名称。### 新关节中间框架

sympy.physics.mechanics模块中关节轴的定义已更改。现在,不再使用参数parent_axischild_axis来自动确定关节轴和中间参考框架,而是关节现在同时使用了父体和子体的中间框架参数,即parent_interframechild_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_interframechild_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 使用,不应该被用户代码使用。

此外,这两个函数 ordereddefault_sort_key 也曾在 sympy.utilities.iterables 中,但它们也已经从那里移动。

版本 1.9

expr_free_symbols

各种 SymPy 对象的 expr_free_symbols 属性已经被弃用。

expr_free_symbols 被设计为表示像 MatrixElementIndexed 这样的索引对象作为自由符号。这旨在使自由符号的导数工作。然而,现在即使不使用该方法也能正常工作:

>>> 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] 

这是一个通用属性,旨在解决一个非常具体的问题,但它增加了不必要的抽象层。

  1. 对于已经具有结构化“非表达式”节点的对象,如果需要,可以直接专注于表达式节点,例如。

    >>> from sympy import Derivative, symbols, Function
    >>> x = symbols('x')
    >>> f = Function('f')
    >>> Derivative(f(x), x).expr
    f(x) 
    

    引入此属性会在请求自由符号时鼓励不精确的思考,因为它允许从对象的特定节点获取符号而不必指定节点。

  2. 该属性被错误地添加到 AtomicExpr,因此数字被返回为 expr_free_symbols

    >>> S(2).expr_free_symbols 
    2 
    
  3. 应用这个概念来定义 Subs.expr_free_symbols 是错误的:它增加了点的 expr_free_symbols 但是点是一个 Tuple,因此没有添加任何内容。

  4. 它在代码库中除了在不同 iating Subs 对象的上下文中未被使用外,这表明它并非通用用途,这也由以下事实确认:

  5. 它是在未进行具体测试的情况下添加的,除了引入它的 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 模块中的对象,例如:

  1. RawMatrix(见上文)在solve_lin_sys中被使用,这是heurisch的一部分,并且也被smith_normal_form使用。NewMatrix类使用域元素作为矩阵的元素,而不是Expr

  2. NewMatrixholonomic模块中被使用,并且还使用域元素作为矩阵的元素。

  3. PolyMatrixPolyExpr混合作为矩阵元素,并被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 方法用于将 xy 两个坐标列表转换为 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._smatDenseMatrix._mat私有属性

Matrix._mat属性和SparseMatrix._smat属性已弃用。

MatrixSparseMatrix的内部表示已更改为#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.statsdistribution方法曾接受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.prettyFormsympy.printing.pretty.pretty_symbology.xstr函数的unicode参数和属性

sympy.printing.pretty.pretty_symbology.xstr函数以及sympy.printing.pretty.stringpict.prettyFormunicode参数和属性都是为了支持 Python 2 的 Unicode 行为而存在的。由于 Python 3 中 Unicode 字符串是默认的,这些不再需要。应将xstr()替换为str(),省略prettyFormunicode参数,并用prettyForm.s属性替换prettyForm.unicode属性。### 将参数作为lambdifyset传递

传递函数参数作为lambdify的集合已被弃用。应作为列表或元组传递它们。例如,而不是

lambdify({x, y}, x + 2*y) # WRONG 

使用

lambdify((x, y), x + 2*y) # RIGHT 

这是因为集合是无序的。 例如,在上面的示例中,lambidfy无法知道它是否以{x, y}{y, x}调用。 因此,当作为集合传递参数时,lambdify必须猜测它们的顺序,如果猜测错误,将导致函数不正确。 ### 核心运算符不再接受非 Expr 参数

核心操作类AddMulPow现在不能直接使用非Expr子类的对象构造。

Expr是所有表示标量数值数量的 SymPy 类的超类。 例如,sinSymbolAdd都是Expr的子类。 但是,SymPy 中的许多对象不是Expr,因为它们表示其他类型的数学对象。 例如,SetPolyBoolean都不是Expr。 这些对象在AddMulPow内部没有数学意义,这些类专门用于表示标量复数的加法,乘法和指数运算。

可以手动构造这些类的一个对象,但通常会导致错误。 例如

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对象上使用+***操作,请直接使用运算符,而不是使用MulAddPow。 如果需要函数版本,可以使用lambdaoperator模块。

版本 1.6

各种 sympy.utilities 子模块已迁移

以下子模块已更名。

  • sympy.utilities.benchmarkingsympy.testing.benchmarking

  • sympy.utilities.pytestsympy.testing.pytest

  • sympy.utilities.randtestssympy.core.random

  • sympy.utilities.runtestssympy.testing.runtests

  • sympy.utilities.tmpfilessympy.testing.tmpfiles ### sympy.testing.randtest

sympy.testing.randtest 已经弃用。其中的函数已移至 sympy.core.random。以下函数已移动。

  • sympy.testing.randtest.random_complex_numbersympy.core.random.random_complex_number

  • sympy.testing.randtest.verify_numericallysympy.core.random.verify_numerically

  • sympy.testing.randtest.test_derivative_numericallysympy.core.random.test_derivative_numerically

  • sympy.testing.randtest._randrangesympy.core.random._randrange

  • sympy.testing.randtest._randintsympy.core.random._randint ### 在二进制操作中混合 Poly 和非多项式表达式

在之前的 SymPy 版本中,PolyExpr 的子类,但现在它已被更改为只是 Basic 的子类。这意味着某些以前与 Poly 一起工作的功能现在已弃用,因为它们仅设计用于与 Expr 对象一起使用。

这包括使用二进制操作组合 PolyExpr 对象,例如

Poly(x)*sin(x) # DEPRECATED 

要实现此功能,可以通过使用 Expr.as_poly() 将非 Poly 操作数显式转换为 Poly,或者通过使用 Poly.as_expr()Poly 操作数转换为 Expr,具体取决于你想要的结果类型。 ### sympy.combinatorics.Permutationprint_cyclic 标志

sympy.combinatorics.Permutationprint_cyclic 属性控制排列打印为循环还是数组。可以通过设置 Permutation.print_cyclic = TruePermutation.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 标志。### 使用 integratePoly

在之前的 SymPy 版本中,PolyExpr 的子类,但现在已更改为仅是 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_evalTensor.__call__

TensExpr.fun_evalTensor.__call__(即调用张量来评估它)已经过时。应该使用 Tensor.substitute_indices() 方法。这一变更是因为 fun_eval 被认为是一个令人困惑的名称,并且使用函数评估被认为是既令人困惑又危险的。### TensorType

TensorType类已弃用。请使用tensor_heads()代替。TensorType类除了更短地创建TensorHead对象之外,没有其他用途。

另请参阅下面的 The tensorhead() function。 ### TensorIndexTypedummy_fmt参数

TensorIndexTypedummy_fmt关键字参数已弃用。设置dummy_fmt='L'将导致_dummy_fmt='L_%d',这是令人困惑并且使用过时的字符串格式化。应改用dummy_name。这个改变是因为dummy_name是一个更清晰的名称。 ### TensorIndexTypemetric参数

TensorIndexTypemetric关键字参数已弃用。名称metric在某些地方指“度量对称性”,在其他地方指“度量张量”,存在歧义。

应使用metric_symmetry关键字或TensorIndexType.set_metric()方法。 ### TensorIndexTypeget_kronecker_delta()get_epsilon()方法

TensorIndexTypeget_kronecker_delta()get_epsilon()方法已弃用。分别使用TensorIndexType.deltaTensorIndexType.epsilon属性。 ### tensorsymmetry()函数

sympy.tensor中的tensorsymmetry()函数已经被弃用。请使用TensorSymmetry类构造函数替代。

TensorSymmetry优于tensorsymmetry(),因为后者

  1. 没有额外的功能

  2. 涉及晦涩的 Young 表

  3. 不是TensorSymmetry类的成员 ### tensorhead()函数

tensorhead()函数已弃用,建议使用tensor_heads()代替。tensor_heads()与 SymPy 的其他命名(例如,Symbolsymbols()TensorIndextensor_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.Particlesympy.physics.mechanics.rigidbody.RigidBodyset_potential_energy()方法已被弃用。

相反,应该设置Particle.potential_energyRigidBody.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() 方法。

术语表

原文:docs.sympy.org/latest/explanation/glossary.html

这一页是 SymPy 文档中使用的各种术语的术语表。该术语表主要用于特定于 SymPy 的术语。有关更一般的 Python 术语,请参阅Python 术语表。数学术语仅在 SymPy 中具有特定含义时包含在此处。有关一般数学定义,请参考其他来源,如WikipediaMathWorld,以及特定 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 表达式的任何子表达式。通过 funcargs,始终可以准确重建任何 SymPy 表达式,即 expr.func(*expr.args) == expr 对于任何 SymPy 表达式 expr 都是真实的。一个表达式的 args 可能是空元组 (),这意味着表达式是一个原子。

Assumptions

Assumptions 是对符号或表达式的一组谓词,定义其可以取的可能值集合。一些假设的示例包括 positiverealinteger。假设在逻辑上相互关联,例如,一个 integer 的假设自动意味着 real。假设使用三值逻辑系统,其中谓词可以是 TrueFalseNone

假设可以是假设查询。例如,一个符号 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 基类,如AddMulPow等类,以及假设。

哑元

dummy symbol 是一个符号,即使它与同名的其他 dummy 符号不相等,也会自动返回其自身。dummy 符号用于当函数需要返回带有新符号的表达式时,以避免意外与同名的 symbol 冲突。可以使用Dummy创建 dummy 符号。

方程

方程是具有等号 (=) 的 expression。在 SymPy 中,方程使用Eq类表示。方程不是使用==运算符创建的。==运算符执行两个表达式之间的结构相等性检查,并始终返回TrueFalse。相比之下,符号方程可能是 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 的子类。可以在AddMulPow中的 SymPy 表达式应该是Expr的子类。并非所有 SymPy 类都是Expr的子类,例如,布尔对象是 Basic,但不是Expr,因为布尔表达式在像AddMul这样的类中没有数学意义。

表达式

任何 SymPy 对象,即任何 Basic 的实例,都可以称为表达式。有时,“表达式”一词保留给 Expr 对象,这些是代数表达式。表达式不应与方程混淆,后者是表示数学等式的特定类型的表达式。

表达树

表达树的表达式。每个表达式都是从较小的表达式构建而成的树。表达树的节点是表达式,每个节点的子节点是构成该表达式的直接子表达式。或者,可以将表达树视为一棵树,其中非叶节点是函数,叶节点是原子。例如,教程中展示了一个表达式树的示例。通过递归遍历 args,可以获得任何 SymPy 表达式的表达树。请注意,由于 SymPy 表达式是不可变的,并且严格按照结构相等性处理,因此也可以将表达树视为是DAG,其中相同的子表达式在图中只表示一次。

自由符号

表达式中的符号如果数学上依赖于该符号的值,则为自由。也就是说,如果该符号被替换为一个新符号,结果将是不同的表达式。不是自由的符号是绑定的。可以通过free_symbols属性访问表达式的自由符号。

func

func属性是 expression 的函数,可以通过expr.func获取。这通常与type(expr)相同,但在某些情况下可能会有所不同,因此在重建具有 args 的表达式时,应优先使用expr.func而不是type(expr)。每个 SymPy 表达式都可以使用funcargs完全重建,即expr.func(*expr.args) == expr对于任何 SymPy 表达式expr都将始终为真。

函数

函数可能指:

  • 数学函数,即将域中的值映射到范围中的某些内容。有时,包含 symbol 的 expression 在口语上称为“函数”,因为该符号可以使用 substitution 替换为值,从而 evaluating 表达式。这种用法是口语化的,因为必须使用subs方法来执行替换,而不是典型的 Python 函数调用语法,并且它不具体说明表达式是哪些变量的函数,因此通常应优先使用术语“expression”,除非某些内容确实是函数。可以使用Lambda将表达式转换为可以使用 Python f(x)语法调用的函数对象。

  • SymPy Function 类的一个实例。

  • Python 函数,即使用def关键字定义的函数。 Python 函数不是 symbolic,因为它们必须始终返回一个值,因此不能是 unevaluated。

Function(类)

Function是 SymPy 中符号函数的基类。这包括常见函数如sin()exp(),特殊函数如zeta()hyper(),以及积分函数如primepi()divisor_sigma()。函数类总是符号化,这意味着当传递一个符号,如f(x)时,它们通常保持未评估状态。并非所有符号表达式类都是Function子类,例如,像AddMul这样的核心类不是Function子类。

Function也可以用于通过传递函数的字符串名称(如Function('f'))创建一个未定义函数。

并非所有 SymPy 中的函数都是符号Function类;有些只是始终返回值的 Python 函数。例如,大多数简化函数(如 simplify())无法以符号形式表示。

Immutable

在 Python 中,如果对象无法原地修改,则称其为不可变。为了改变一个不可变对象,必须创建一个新的对象。在 SymPy 中,所有的基础对象都是不可变的。这意味着所有操作表达式的函数都会返回一个新的表达式,并且不会改变原始对象。对表达式进行操作不会改变引用该表达式的其他对象或表达式。这也意味着任何两个相等的对象完全可以互换,并且可以被视为同一个对象,即使它们在内存中是两个不同的对象。不可变性使得更容易维护代码的心智模型,因为没有隐藏状态。SymPy 对象的不可变性也意味着它们是可哈希的,可以用作字典键。

交互式

交互 使用指的是在交互式 REPL 环境中使用 SymPy,例如 Python 提示符、isympy、IPythonJupyter 笔记本。在交互式使用 SymPy 时,所有命令由用户实时输入,并显示所有中间结果。交互 使用与程序化 使用相对应,后者是指代码写入文件,然后作为脚本执行或作为较大 Python 库的一部分。一些 SymPy 习惯用法仅推荐在交互式使用时使用,当在程序化使用时被视为反模式。例如,在交互式使用 SymPy 时运行 from sympy import * 是方便的,但在程序化使用时通常不建议,应优先显式导入名称 import sympy

is_*

在 SymPy 中以 is_ 开头并使用小写名称的属性查询对象的给定假设(注意:有少数属性是例外,因为它们不使用假设系统,请参阅假设指南)。例如,x.is_integer 将查询 xinteger 假设。使用大写名称的 is_* 属性测试对象是否是给定类的实例。有时相同名称将同时存在于小写和大写属性中,但它们代表不同的事物。例如,只有当 xInteger 的实例时,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 是显式数值(IntegerRationalFloat)的基类。符号数值常量如 pi 不是 Number 的实例。

  • 小写的 "number",例如 is_number 属性,指的是可以 evalfed 成显式 Number 的任何 表达式。这包括像 pi 这样的符号常数。注意,is_number 不是 假设 系统的一部分。

对于 is_Numberis_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))的关系是用EqNe创建的。例如,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 表达式都是不可变的。结构相等可以通过==操作符来检查,它总是返回TrueFalse。符号等式可以用Eq来表示。

通常,如果两个表达式是相同的类,并且(递归地)具有相同的 args,那么它们就是结构相等的。两个表达式可能在数学上是相同的,但在结构上不相等。例如,(x + 1)**2x**2 + 2*x + 1 在数学上是相等的,但它们在结构上不相等,因为前者是一个 Pow,其 args 包括一个Add 和一个Integer,而后者是一个Add,其 args 包括一个Pow,一个Mul 和一个Integer

两个表面上不同的表达式,如果它们经过规范化后变成相同的形式,那么它们就是结构相等的。例如,x + yy + x 结构上是相等的,因为Add 构造器会自动排序其参数,使它们变成相同的形式。

子表达式

子表达式 是一个包含在较大表达式中的表达式。子表达式出现在表达式树的某处。对于AddMul项,当确定什么是子表达式时,可以考虑交换律和结合律。例如,x + y 有时可能被认为是 x + y + z 的子表达式,即使Add(x, y)的表达式树不是Add(x, y, z)的直接子节点。

替换

替换是指用另一个表达式替换 expression 内部的 symbol 或 subexpression 的行为。在 SymPy 中有不同的方法执行替换,包括subsreplacexreplace。这些方法可能因是否只使用严格的 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 数值类型如intfloat,可以解析为 SymPy 表达式的字符串以及包含sympifiable对象的可迭代对象(详见sympify()的文档了解更多信息)。

因为所有 SymPy 表达式必须是 Basic 的实例,所有 SymPy 函数和操作在其输入上会隐式调用sympify()。例如,x + 1会隐式调用sympify(1)将 Python int类型的1转换为 SymPy 的Integer。接受 SymPy 表达式的函数通常应该在其参数上调用sympify(),以确保它们在输入不是 SymPy 类型时也能正常工作。

三值逻辑

三值逻辑是一种具有三个值 TrueFalseNone 的逻辑。有时它也被称为模糊逻辑,尽管在数学文献中这个术语也有不同的含义,因此“三值逻辑”更受推荐。TrueFalse 的作用与通常的二值谓词逻辑相同。None 是一个额外的术语,表示“未知”、“不可计算”或“可以是 True 或 False”(从哲学上讲这些是不同的概念,但逻辑上它们完全相同)。None 的语义是在逻辑操作中吸收其他术语,每当如果将其替换为 TrueFalse 结果将会不同时。例如,None OR FalseNone,但 None OR TrueTrue,因为谓词是 True,无论 None “实际上”代表 True 还是 False。在使用通常的 Python 逻辑运算符如 andornot 时,必须小心处理三值逻辑,因为 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 参考

原文:docs.sympy.org/latest/reference/index.html

本节包含 SymPy 模块、函数、类和方法的摘要。sympy核心子包中实现的所有函数和对象在下面都有文档。

基础

包含基础模块操作的描述。子类别包括:基础概念操作假设函数简化微积分求解器,以及其他一些子类别。

代码生成

包含生成可编译和可执行代码的方法描述。

逻辑

包含逻辑集合模块的方法详细信息。

矩阵

讨论了矩阵、张量和向量模块的方法。

数论

文档了数论模块的方法。

物理

包含物理方法的文档。

实用工具

包含多个实用模块方法的文档字符串。子类别包括:交互式解析打印测试实用工具

主题

包含多个模块的方法文档字符串。子类别包括:绘图多项式几何范畴论密码学微分Holonomic李代数,和统计

基础

原文:docs.sympy.org/latest/reference/public/basics/index.html

目录

  • 假设

    • 询问

    • 假设

    • 细化

    • 谓词

  • 微积分

  • 组合数学

    • 分割

    • 排列

    • 置换群

    • 多面体

    • 普鲁弗序列

    • 子集

    • 格雷码

    • 命名群

    • 伽罗瓦群

    • 群的数量

    • 实用工具

    • 群构造器

    • 测试工具

    • 张量标准化

    • 有限呈现群

    • 多环群

  • 函数

    • 初等

    • 组合

    • 枚举

    • 特殊

  • 积分

    • 使用 Meijer G-函数计算积分

    • 积分

  • 级数

    • 级数展开

    • 序列

    • 傅立叶级数

    • 形式幂级数

    • 序列极限

  • 简化

    • 简化

    • 超几何展开

    • 傅宏光的三角简化

  • 求解器

    • 丢番图方程

    • 不等式求解器

    • 常微分方程

    • 偏微分方程

    • 求解器

    • 解集

  • abc

  • 代数

  • 具体

  • 核心

  • 离散

  • 数值评估

  • 数值计算

  • 术语重写

假设

原文:docs.sympy.org/latest/modules/assumptions/index.html

一个模块用于实现逻辑谓词和假设系统。

谓词

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变量。

返回:

TrueFalseNone

Raises:

TypeErrorpropositionassumptions不是有效的逻辑表达式。

ValueError:假设不一致。

解释

如果可以确定真值,则此函数将评估propositionTrueFalse。否则,它将返回None

它应该从refine()中区分出来,当应用于proposition时,简化参数为符号Boolean而不是 Python 内置的TrueFalseNone

语法

  • 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/中找到更多示例。

原文:docs.sympy.org/latest/modules/assumptions/ask.html

用于查询关于假设的 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变量。

返回:

TrueFalseNone

引发:

TypeErrorpropositionassumptions不是有效的逻辑表达式。

ValueError:假设不一致。

解释

这个函数在能够确定真值时将命题评估为TrueFalse。如果不能确定,将返回None

这应该与refine()区分开来,当应用于命题时,它将简化参数为符号Boolean,而不是 Python 内置的TrueFalseNone

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

假设

原文:docs.sympy.org/latest/modules/assumptions/assume.html

实现谓词和假设上下文的模块。

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 
posted @ 2024-06-27 17:19  绝不原创的飞龙  阅读(46)  评论(0编辑  收藏  举报