数值 Python: 符号计算
与基于数值数组的计算相比,符号计算是一种完全不同的计算模式。符号计算也被称为计算机代数系统(computer algebra systems,CAS),数学对象和表达式的表示被分析地操纵和转换。符号计算主要是将原则上需要使用笔和纸手动完成的计算,使用计算机自动进行分析计算。
分析和符号计算是科技计算领域的一个关键部分,即使对于只能用数值方法解决的问题(分析方法在许多实际问题中是不可行),在采用数值方法之前,它也能分析性的推动限制,从而产生很大不同。例如,可以减少最终需要解决的数值问题的复杂性或大小。换句话说,在使用数值方法之前先使用分析方法简化问题。
在科学 Python 环境中,符号计算的主要库是 SymPy(Symbolic Python)。 SymPy 完全用 Python 编写,为各种分析和符号问题提供了工具。
导入 SymPy
1 | In [1]: import sympy |
这里我们还调用了 sympy.init_printing
函数,它用于配置 SymPy 打印系统以显示良好格式化的数学表达式。在 IPython 中,它会使用 MathJax JavaScript 库来渲染 SymPy 表达式。
为了寻求方便和可读性,我们也假设以下常用符号是显式地从 SymPy 中导入到本地命名空间的。
1 | In [3]: from sympy import I, pi, oo |
符号
SymPy 的核心功能是将数学符号表示为 Python 对象。在 SymPy 库中,类 sympy.Symbol
可用于此目的。Symbol
的一个实例有一个名称和一组描述其特性的属性,以及用于查询这些特性和用于操纵符号对象的方法。一个符号本身并没有多少实际用途,但它们被当作表达式树中的节点,来表示代数表达式。
符号名是一个字符串,可以选择性地包含类 LaTeX 的标记,以使符号名在富显示系统(如, IPython)良好地展示。Symbol
对象的名称在创建时会被给定。在 SymPy 中有以下几种不同的方式创建符号,使用 sympy.Symbol
、sympy.symbols
和 sympy.var
。通常,希望将 SymPy 符号与具有相同名称(或具有紧密联系)的 Python 变量相关联。如:
1 | In [4]: x = sympy.Symbol("x") |
变量 x 现在表示一个抽象的数学符号 x,默认情况下知道的信息非常少。此时,x 可以表示例如实数、整数、复数、函数以及大量其他可能。在很多情况下,用这个抽象的、未指定的 Symbol 对象表示一个数学符号就足够了,但有时需要给 SymPy 更多的信息,以确切知道 Symbol 对象代表具体什么类型的符号。这可以帮助 SymPy 更有效地操纵分析表达式。我们可以添加各种假设,以缩小符号类型潜在的可能。下表总结了可以与 Symbol 类实例关联的一些常用假设的选择。
假设的关键字参数 | 属性 | 描述 |
---|---|---|
real, imaginary | is_real, is_imaginary | 指定一个符号代表实数或虚数 |
positive, negative | is_positive, is_negative | 指定一个符号代表正数或负数 |
integer | is_integer | 该符号代表整数 |
odd, even | is_odd, is_even | 该符号代表奇数或偶数 |
prime | is_prime | 该符号代表质数 |
finite, infinite | is_finite, is_infinite | 该符号代表有限或无限 |
1 | In [2]: y = sympy.Symbol("y", real=True) |
注意:如果知道符号是实数,is_real
返回 True
,如果知道符号不是实数,则返回 False
,如果不知道符号到底是实数还是非实数,那么返回 None
。
根据上表,创建新符号时最重要的是明确符号是实数还是虚数,以及是正数还是负数。
1 | In [8]: y = sympy.Symbol("y", positive=True) |
当数学符号表示的是整数而不仅仅是实数时,明确指定符号类型也是有用的。例如设置 integer=True
或 even=True
或 odd=True
。这可以让 SymPy 分析性地简化某些表达式和函数评估。例如:
1 | In [13]: n1 = sympy.Symbol("n") |
对于某些需要大量符号的数学问题,使用逐个指定每个符号会变得单调乏味。这时候需要使用 sympy.symbols
。
1 | In [24]: a, b, c = sympy.symbols("a, b, c", negative=True) |
数字
将数学符号表示为 Python 对象的目的是在数学表达式的表达式树中使用它们。为了能够做到这一点,我们还需要表示其他数学对象,如数字、函数和常量。
我们不能直接使用 Python 内置的整数 int
和浮点数 float
,
SymPy 提供了用于在 SymPy 框架内表示整数和浮点数的类 sympy.Integer
和 sympy.Float
。在使用 SymPy 时,这一区别非常重要。但是我们很少需要考虑到这一点,这是因为当遇到 SymPy 表达式时,SymPy 自动地提升了 Python 内置类型。下例展示了两者的不同之处。
1 | In [26]: i = sympy.Integer(19) |
要创建一个数字的 SymPy 表示,或者说任意表达式,我们也可以使用 sympy.sympify
函数。该函数接受各种输入并派生一个SymPy兼容表达式,并且不需要明确指定要创建什么类型的对象。 对于数字输入的简单情况,我们可以使用:
1 | In [3]: i, f = sympy.sympify(19), sympy.sympify(2.3) |
- 整数(Integer)
在上一节中,我们使用了 Integer 类来表示整数。值得指出的是,在 Symbol 实例设置 integer = True
与直接使用 Integer 实例是存在差异。integer = True
表示某个整数,Integer 实例表示特定的整数。对于这两种情况,is_integer
属性都为 True,但属性 is_Integer
,只对 Integer 实例为 True。 一般情况下,名称为 is_Name
的属性指示对象是否为 Name 类型,名称为 is_name
的属性指示对象是否满足已知条件。
1 | In [10]: n.is_integer, n.is_Integer, n.is_positive, n.is_Symbol |
SymPy 中的整数是任意的精度,这意味着它们没有固定的下限和上限,因此可以使用非常大的数字,如下所示:
1 | In [12]: i ** 50 |
- 浮点(Float)
在前面,我们也遇到过类型 sympy.Float
。与 Integer 类似,Float 也是任意精度的,这与 Python 的内置浮点类型和 NumPy 中的浮点类型不同。 这意味着任何 Float 都可以表示具有任意小的浮点数。当使用其构造函数创建 Float 实例时,有两个参数:第一个参数是 Python 浮点数或表示浮点数的字符串,第二个(可选)参数是精度(有效小数位数)。众所周知,实数 0.3 不能完全表示为正常的固定位大小的浮点数,当打印20个有效数字的 0.3 时,它被替换为 0.2999999999999999888977698。SymPy Float 对象可以表示实数 0.3 而不受浮点数的限制:
1 | In [16]: "%.25f" % 0.3 |
- 有理数(Rational)
有理数是两个整数 p, q
,的分数 p/q
。SymPy 使用类 sympy.Rational
表示这种类型的数。
1 | In [3]: sympy.Rational(11, 13) |
- 常数与特殊符号
SymPy 提供了多种数学常数和特殊对象的预定义符号,如下表所示:
数学符号 | SymPy 符号 | 描述 |
---|---|---|
$\pi$ |
sympy.pi | 圆的周长与直径之比 |
$e$ |
sympy.E | 自然对数的底 |
$\gamma$ |
sympy.EulerGamma | 欧拉常数 |
$i$ |
sympy.I | 虚数单位 |
$\infty$ |
sympy.oo | 无穷 |
- 函数
在 SymPy 中,可以使用 sympy.Function 创建表示函数的对象。SymPy 区分定义函数和未定义函数,以及应用(applied)函数和未应用函数。使用 Function 创建函数会产生未定义(抽象)、未应用函数,该函数仅仅具有一个名称,由于未定义其表达式或主体,因此无法对其进行求值。这样的函数可以表示具有任意个输入变量的任意函数。
1 | In [3]: x, y, z = sympy.symbols("x, y, z") |
这里我们还使用了 free_symbols
属性,它返回给定表达式(在这里是函数 g)中包含的一组唯一符号,以表明应用函数确实与一组特定的输入符号相关联。当我们考虑抽象函数的导数时这会很重要。未定义函数的一个重要应用是用于微分方程(或者说,函数的关系式是已知的,但函数本身是未知的)。
与未定义函数相比,已定义函数具有特定的实现,并且可以对所有有效的输入参数进行数值计算。可以通过子类化 sympy.Function
来定义这种类型的函数,但在大多数情况下,使用 SymPy 提供的数学函数就足够了。在 SymPy 全局命名空间(请参阅 sympy.functions.elementary
、sympy.functions.combinatorial
和 sympy.functions.special
模块及其子模块的文档)中有许多可用的 SymPy 内置标准数学函数。
1 | In [10]: sympy.sin |
当应用抽象符号(如 x)时,sin 函数保持未评估状态,但在某些情况下,会给出数值:
1 | In [14]: n = sympy.Symbol("n", integer=True) |
另一类 SymPy 函数是 lambda 函数或匿名函数。可以使用 sympy.Lambda
创建匿名函数:
1 | In [5]: h = sympy.Lambda(x, x**2) |
表达式
前一节介绍的各种符号是表示数学表达式所需的基本构件。在 SymPy 中,数学表达式被表示为树结构,树中叶是符号、节点是表示数学运算的类实例。这些类有用于基本算术运算的 Add
、Mul
和 Pow
以及用于分析数学运算的 Sum
、Product
、Integral
和 Derivative
。当然,还有许多其他的数学运算类。
考虑数学表达式 $1+2x^2+3x^3$
。为了在 SymPy 中表示这一点,我们只需要创建符号 x,然后将表达式写为 Python 代码:
1 | In [3]: x = sympy.Symbol("x") |
这里 expr 是 Add 的一个实例,子表达式为 1、2 * x ** 2 和 3 * x ** 3。下图显示了 expr 整个表达式树。
可以使用 args
属性显式遍历表达式树。
1 | In [6]: expr.args |
在 SymPy 的基本使用中,很少需要显式操作表达式树,但是当操作表达式的方法不够用时,使用 args 属性实现自己的函数来遍历和操作表达式树是有用的。
操纵表达式
操作表达式树是 SymPy 的主要工作之一,SymPy 为不同类型的转换提供了许多函数。总体思路是表达式树可以(通过化简和重写)在数学等价形式之间进行转换。SymPy 中的表达式被视为不可变对象,它不会修改传递过来的函数,而是创建一个新的函数。
化简
对一个数学表达式而言,最想要的操作是化简它。这可能也是最模糊的操作,因为从算法上确定一个表达式比其他的表达式对人类而言简单,是复杂的。而且,采用哪种方法来获取更简单的表达式,通常也是不明显的。尽管如此,任何 CAS 都提供了黑盒化简功能,SymPy 提供了 sympy.simplify
函数用于化简给定表达式。化简函数也可以通过 simplify
方法调用。
1 | In [6]: expr = 2 * (x**2 - x) - x*(x+1) |
一般地,sympy.simplify
会尝试不同的策略,它也会化简三角函数和幂函数。
1 | In [11]: expr = 2 * sympy.cos(x) * sympy.sin(x) |
对于三角函数和幂函数这些特定类型的化简,也可以用更具体的函数来执行,例如 sympy.trigsimp
和 sympy.powsimp
。这些函数仅执行其名称所指示的化简,并将表达式的其他部分保留其原始形式。下表给出了常见的化简函数。当已知精确的简化步骤时,最好使用具体的简化函数,因为它们的操作更加明确,并且不太可能在未来版本的 SymPy 中更改。而 sympy.simplify
函数依赖于未来可能会发生变化的启发式方法,因此对于特定的输入表达式会产生不同的结果。
函数 | 描述 |
---|---|
sympy.simplify |
尝试使用各种方法获取给定表达式的更简单形式 |
sympy.trigsimp |
尝试使用三角恒等式简化表达式 |
sympy.powsimp |
尝试使用幂法则简化表达式 |
sympy.compsimp |
简化组合表达式 |
sympy.ratsimp |
通过使用公分母来简化表达 |
展开
函数 sympy.expand
执行各种形式的展开,具体形式取决于可选关键字参数的值。默认情况下,该函数将和的乘积完全展开。例如,$(x+1)(x+2)$
被展开为 $x^2+3x+2$
:
1 | In [7]: expr = (x + 1) * (x + 2) |
一些可用的关键字参数包括 mul=True
展开乘积,trig=True
三角扩展。
1 | In [12]: sympy.sin(x + y).expand(trig = True) |
log=True
对数展开,
1 | In [14]: a, b = sympy.symbols("a, b", positive=True) |
complex=True
分离表达式的实部和虚部,
1 | In [16]: sympy.exp(I*a + b).expand(complex=True) |
power_base=True
和 power_exp=True
分别展开幂表达式的底和指数。
1 | In [17]: sympy.expand((a * b)**x, power_base=True) |
Factor, Collect, and Combine
sympy.factor
可以用来对代数表达式进行因式分解。在某种程度上,这是对 mul=True
的 sympy.expand
的反向操作:
1 | In [19]: sympy.factor(x**2 - 1) |
其他的一些相反操作有:sympy.trigsimp
、sympy.powsimp
和 sympy.logcombine
。
1 | In [22]: sympy.logcombine(sympy.log(a) - sympy.log(b)) |
在处理数学表达式时,通常需要对因子进行细粒度的控制。SymPy 函数 sympy.collect
因式分解包含给定符号或符号列表的项。例如,x+y+xyz
不能被完全因式分解,但能够部分的分解,使之包含 x 或 y 项:
1 | In [24]: expr = x + y + x*y*z |
Apart, Together, and Cancel
这里我们将要考虑的最后一种数学化简是重写分数。函数 sympy.apart
和 sympy.together
分别是将分数分解成多个部分和将多个分数组合成一个分数。
1 | In [4]: sympy.apart(1/(x**2 + 3*x + 2), x) |
替换
数学表达式操作的另一种常用形式是表达式内的符号或子表达式的替换。例如,我们可能想要进行变量替换,用 y 替换变量 x,或者用另一个表达式替换一个符号。在 SymPy 中有两种进行替换的方法:subs
和 replace
。通常情况下,subs 是最合适的选择,但在某些情况下,replace 可提供更强大的工具(例如,可基于通配符)。
1 | In [7]: (x + y).subs(x, y) |
当需要进行多次替换时,我们可以将字典作为第一个也是唯一的参数传递给 subs,这样可以将旧的符号或表达式映射到新的符号或表达式:
1 | In [11]: sympy.sin(x * z).subs({z: sympy.exp(y), x: y, sympy.sin: sympy.cos}) |
subs 方法的一个典型应用是用数字代替符号来进行数值计算。一种简单方法是定义一个字典,将符号转换为数字,并将该字典作为参数传递给 subs 方法。
1 | In [12]: expr = x * y + z **2 *x |
数值计算
可以使用 sympy.N
函数或 SymPy 表达式实例的 evalf
方法来计算 SymPy 表达式:
1 | In [5]: sympy.N(1+pi) |
sympy.N
和 evalf
方法都带有一个可选参数,该参数指定要计算表达式的有效位数。如上所示,SymPy 的多精度浮点数能够用来计算高达 50 位的 $\pi$
值。
当我们需要对一系列输入值进行数值计算时,原则上我们可以通过对这些数进行循环,然后逐个执行 evalf
调用,例如:
1 | In [8]: expr = sympy.sin(pi * x* sympy.exp(x)) |
但是,此方法相当低效,SymPy 使用函数 sympy.lambdify
为执行这类操作提供了更高效的方法。该函数将一组自由符号和一个表达式作为参数,并生成一个可高效评估表达式数值的函数。生成函数的参数数量与传递给 sympy.lambdify
第一个参数的自由符号的数量相同。
1 | In [10]: expr_func = sympy.lambdify(x, expr) |
请注意,函数 expr_func
需要数字(标量)值作为参数,所以我们不能将符号作为参数传递给此函数,这是严格的数值计算。前面的例子所中创建的 expr_func
是一个标量函数,与 NumPy 数组形式的矢量化输入不直接兼容。SymPy 也能够生成 NumPy 数组兼容的函数——通过将可选参数 numpy
作为第三个参数传递给 sympy.lambdify
就可创建一个接受 NumPy 数组作为输入的矢量化函数。这通常是计算具有大量输入参数的符号表达式的最有效的方法。
1 | In [12]: expr_func = sympy.lambdify(x, expr, 'numpy') |
这通常是从 SymPy 表达式中生成数据的有效方式。例如,用于绘图和其他面向数据应用。
数学计算
到目前为止,我们已经看过如何在 SymPy 中表示数学表达式,以及如何对这些表达式进行基本的简化和转换。有了这个框架,我们就可以准备探索符号微积分(符号分析),这是应用数学的基石,在整个科学和工程领域有大量的应用。微积分的核心概念是随着输入变量的变化而变化的函数。在本节中,我们将介绍如何计算 SymPy 中函数的导数和积分。
导数
函数的导数描述了它在给定点的变化率。在 SymPy 中,我们可以使用 sympy.diff
或者使用SymPy表达式实例的 diff
方法,来计算函数的导数。 这些函数将一个符号或多个符号作为参数,函数或表达式将针对该符号进行求导。为了表示抽象函数 $f(x)$
关于 x 的一阶导数,我们可以这样做
1 | In [16]: f = sympy.Function('f')(x) |
为了表示高阶导数,我们需要在 sympy.diff
的参数列表中重复符号 x,或者等价地通过在符号后指定一个整数作为参数,该参数定义了表达式中相应符号需要求导的次数:
1 | In [18]: sympy.diff(f, x, x) |
这种方法很容易扩展到多元函数:
1 | In [20]: g = sympy.Function('g')(x, y) |
迄今为止,这些例子只涉及未定义函数的求导。我们也可以计算已定义函数和表达式的导数。使用 sympy.diff
我们可以轻松计算任意数学表达式的导数。
1 | In [24]: expr = x**4 + x**3 + x**2 + x + 1 |
也能计算三角函数及其他复杂的数学表达式:
1 | In [29]: expr = sympy.sin(x * y) * sympy.cos(x / 2) |
请注意,在上述示例中,直接调用表达式 sympy.diff
会产生新的表达式。如果我们想要符号化地表示一个定义表达式的导数,可以创建类 sympy.Derivative
的实例:
1 | In [33]: d = sympy.Derivative(sympy.exp(sympy.cos(x)), x) |
然后可以通过调用 sympy.Ditivative
实例的 doit
方法来计算此表达式的导数形式。
1 | In [35]: d.doit() |
这种延迟计算模式贯穿整个 SymPy,当需要将结果表示为形式表达式进行简化或操纵时,这种操作十分有用。
积分
在 SymPy 中,使用函数 sympy.integrate
计算积分,使用 sympy.Integral
(与 sympy.Derviative
类似,可以通过调用 doit
方法显式计算)表示积分。SymPy 使用 sympy.integrate
函数处理定积分和不定积分。
如果只用表达式作为参数调用 sympy.integrate
函数,则计算不定积分。如果传递一个额外的元组参数,则计算定积分,元组形如 (x, a, b)
,其中 x 是积分变量,a 和 b 是积分限。对于单变量函数 f(x),使用下式计算不定积分和定积分:
1 | In [36]: a, b, x, y = sympy.symbols("a, b, x, y") |
当这些方法被应用到显式函数时,积分则进行相应地计算:
1 | In [40]: sympy.integrate(sympy.sin(x)) |
使用 SymPy 的无穷符号 oo,亦可使定积分的积分限包含正无穷或/和负无穷。
1 | In [42]: sympy.integrate(sympy.exp(-x ** 2), (x, 0, oo)) |
计算符号化积分通常十分困难,SymPy 不能给出积分的符号化结果。当 SymPy 不能计算一个积分时,会返回一个 sympy.Integral
实例,代表形式积分。
1 | In [45]: sympy.integrate(sympy.sin(x * sympy.cos(x))) |
使用 sympy.integrate
亦可计算多元表达式。对于多元表达式的不定积分,积分变量需要指定:
1 | In [46]: expr = sympy.sin(x * sympy.exp(y)) |
通过传递多个符号或者元组,我们可以计算多重积分:
1 | In [50]: sympy.integrate(expr, x, y) |
级数
级数展开是许多计算领域的重要工具。通过级数展开,可以将任意函数写成一个多项式,其系数由该函数的导数在展开所围绕的点处给出。通过将级数展开成 n 阶,就可以得到函数的第 n 阶近似。在 SymPy 中,可以使用 sympy.series
函数或 SymPy 表达式实例中的 series
方法来计算函数或表达式的级数展开。sympy.series
的第一个参数是要展开的函数或表达式,后面要计算展开的符号(对于单变量表达式和函数可以省略)。另外,还可以指定点来执行展开(使用 x0 关键字参数,默认 x0=0
)、指定展开阶数(使用 n 关键字参数,默认值 n=6
)、指定级数计算的方向,即从 x0 的下或上开始(使用 dir 关键字参数,默认为 dir='+'
)。
1 | In [52]: x = sympy.Symbol("x") |
指定在 $x_0$
处展开:
1 | In [58]: x0 = sympy.Symbol("x_0") |
这里我们也指定了 n=2
,要求级数只进行二阶展开。由于截断而产生的误差使用 $O(...)$
表示。在数值计算时,需要删除误差项,这时可以使用 removeO
方法:
1 | In [61]: f.series(x, x0, n=2).removeO() |
可以很容易地获得标准数学函数的展开:
1 | In [62]: sympy.cos(x).series() |
当然,任意表达式和函数亦可展开:
1 | In [66]: expr = sympy.cos(x) / (1 + sympy.sin(x * y)) |
极限
微积分的另一个重要工具是极限。可以使用 sympy.limit
函数计算极限:
1 | In [69]: sympy.limit(sympy.sin(x)/x, x, 0) |
也可以使用它计算符号极限:
1 | In [70]: f = sympy.Function('f') |
另一个更实际的例子是使用极限发现函数的渐近行为,考虑函数 $f(x) = (x^3-3x)/(2x-2)$
,当 x 增大,$f(x) \to px+q$
。这里我们使用 sympy.limit
计算 p、q。
1 | In [75]: expr = (x**2 - 3*x) / (2*x -2) |
和与积
和与积使用 sympy.Sum
与 sympy.Product
函数表示,使用 doit
方法计算。
1 | In [83]: n = sympy.symbols('n', integer=True) |
上式求和上限为无穷大,其结果显然是通过分析方法计算出来的。SymPy 能够提供这种类型的符号化结果。
1 | In [90]: x = sympy.Symbol("x") |
方程
方程求解是数学的一个基本部分,几乎在所有科学和技术领域都有应用,因此非常重要。SymPy 可以符号化地解各种各样的方程,即使原则上许多方程无解析解。如果一个方程或一个系统或方程组有解析解,那么 SymPy 很有可能找到解。如果没有,则提供数值解。
在 SymPy 中,我们可以使用函数 sympy.solve
来找满足方程的解,如:对于方程 $x^2+2x-3=0$
。
1 | In [94]: x = sympy.Symbol("x") |
sympy.solve
基于方程等于零的假设给出解。当包含多个符号时,必须指出需要求解的符号:
1 | In [97]: sympy.solve(a * x **2 + b * x + c, x) |
sympy.solve
也能求解三角表达式,
1 | In [98]: sympy.solve(sympy.sin(x) - sympy.cos(x), x) |
以及其解可以用特殊函数表示的等式:
1 | In [99]: sympy.solve(sympy.exp(x) + 2*x, x) |
然而,在处理一般方程时,即使是单变量情况下,遇到无代数解或者 SymPy 无法求解的方程也是常见的。在这些情况下,如果可以通过数值方法进行评估,SymPy 将返回一个形式解,如果没有方法可用于特定类型的方程,则会引发错误:
1 | In [100]: sympy.solve(x**5 -x**2 +1, x) |
1 | In [101]: sympy.solve(sympy.tan(x) + x, x) |
多变量方程的求解:
1 | In [102]: eq1 = x + 2*y - 1 |
在上式中使用了可选参数 dict=True
,字典格式可以很容易验证解是否符合条件:
1 | In [109]: [eq1.subs(sol).simplify() == 0 and eq2.subs(sol).simplify() == 0 for sol in sols] |
线性代数
线性代数是数学的另一个基本分支,在科学和技术计算中有重要应用。它涉及向量、向量空间以及向量空间之间的线性映射(这些映射可以表示为矩阵)。在 SymPy 中,我们可以使用 sympy.Matrix
类来符号化地表示向量和矩阵,其中元素可以用数字、符号甚至任意符号表达式来表示。 要创建一个包含数值的矩阵,可以将一个 Python 列表传递给 sympy.Matrix
:
1 | In [110]: sympy.Matrix([1, 2]) |
创建新的 sympy.Matrix
对象的另一种方法是传递行数、列数以及处理函数(该函数将行列索引作为参数,返回相应位置元素的值)作为参数:
1 | In [113]: sympy.Matrix(3, 4, lambda m, n: 10 * m + n) |
SymPy 矩阵强大之处在于它能将符号表达式作为元素,这也是它与 NumPy 数组的区别之处。
1 | In [114]: a, b, c, d = sympy.symbols("a, b, c, d") |
这样的矩阵也能进行计算:
1 | In [117]: M * M |
除了算术运算外,向量和矩阵上的许多标准线性代数运算也作为 sympy.Matrix 类的 SymPy 函数和方法实现了。下表给出了常用的线性代数相关函数的总结。SymPy 矩阵也可以使用索引和切片操作。
函数/方法 | 描述 |
---|---|
transpose/ T | 计算矩阵的转置 |
adjoint/ H | 计算矩阵的伴随阵 |
trace | 计算矩阵的迹(对角线元素的总和) |
det | 计算矩阵的行列式 |
inv | 计算矩阵的逆 |
LUdecomposition | 计算矩阵的 LU 分解 |
LUsolve | 使用 LU 分解法,求解形如 Mx=b 的等式,其中 x 是未知向量 |
QRdecomposition | 计算矩阵的 QR 分解 |
QRsolve | 使用 QR 分解法求解形如 Mx=b 的线性方程组,其中 x 是未知向量 |
diagonalize | 使矩阵 M 对角化 |
norm | 计算矩阵的范数 |
nullspace | 计算矩阵零空间的一组向量 |
rank | 计算矩阵的秩 |
singular_values | 计算矩阵的奇异值 |
solve | 求解形如 Mx=b 的线性方程组 |
考虑以下参数线性方程组,其不能直接使用纯数值方法解,但可以使用符号求解:
$$
\begin{array}{c}
x + py &= b_1, \
qx + y &= b_2,
\end{array}
$$
其矩阵形式:
$$
\begin{pmatrix}
1 &p \
q &1 \
\end{pmatrix}
\begin{pmatrix}
x \
y \
\end{pmatrix}
=
\begin{pmatrix}
b_1 \
b_2 \
\end{pmatrix}
$$
使用纯数值方法,在解这个问题之前,我们必须选择参数 p 和 q 的值。使用符号计算方法,我们可以直接计算解。使用 SymPy,我们可以简单地为未知变量和参数定义符号,并设置所需的矩阵对象:
1 | In [121]: p, q = sympy.symbols("p, q") |
使用 LUsolve
方法解该线性方程组:
1 | In [126]: x = M.LUsolve(b) |
也可以直接计算矩阵的逆,然后乘上向量 b:
1 | In [128]: x = M.inv() * b |
计算矩阵的逆矩阵比执行 LU 分解更困难,所以使用 LU 分解更有效率。对于较大的方程系统而言,这变得特别明显。在这里考虑了两种方法,解的符号表达式法,对于任何参数值都无需重新计算解,这是符号计算的优势。
本文翻译自:R. Johansson, Numerical Python.