基本实现细节

坐标系与向量

目前,sympy.vector 能够处理笛卡尔(也称为矩形)、球形和其他曲线坐标系。

可以在 sympy.vector 中初始化一个三维笛卡尔坐标系。

>>> from sympy.vector import CoordSys3D
>>> N = CoordSys3D('N')

构造函数的字符串参数表示分配给系统的名称,主要用于打印目的。

一旦定义了坐标系(本质上是一个 CoordSys3D 实例),我们就可以访问其正交单位向量(即 \(\mathbf{\hat{i}}\)\(\mathbf{\hat{j}}\)\(\mathbf{\hat{k}}\) 向量)以及对应的坐标变量/基标量(即 \(\mathbf{x}\)\(\mathbf{y}\)\(\mathbf{z}\) 变量)。我们将在后面的章节中讨论坐标变量。

可以通过 ijk 属性分别访问 \(X\)\(Y\)\(Z\) 轴的基向量。

>>> N.i
N.i
>>> type(N.i)
<class 'sympy.vector.vector.BaseVector'>

如上所示,基向量都是名为 BaseVector 的类的实例。

当一个 BaseVector 乘以一个标量(本质上任何 SymPy Expr),我们得到一个 VectorMul - 一个基向量和标量的乘积。

>>> 3*N.i
3*N.i
>>> type(3*N.i)
<class 'sympy.vector.vector.VectorMul'>

添加 VectorMulBaseVectors 会导致 VectorAdd 的形成 - 当然,除了特殊情况。

>>> v = 2*N.i + N.j
>>> type(v)
<class 'sympy.vector.vector.VectorAdd'>
>>> v - N.j
2*N.i
>>> type(v - N.j)
<class 'sympy.vector.vector.VectorMul'>

零向量呢?它可以通过分配给类 Vectorzero 属性来访问。由于零向量的概念在考虑的坐标系中保持不变,我们在需要这种量时使用 Vector.zero

>>> from sympy.vector import Vector
>>> Vector.zero
0
>>> type(Vector.zero)
<class 'sympy.vector.vector.VectorZero'>
>>> N.i + Vector.zero
N.i
>>> Vector.zero == 2*Vector.zero
True

上面显示的所有类 - BaseVectorVectorMulVectorAddVectorZero 都是 Vector 的子类。

你永远不需要实例化 Vector 的任何子类的对象。使用分配给 CoordSys3D 实例的 BaseVector 实例和(如果需要) Vector.zero 作为构建块,任何类型的向量表达式都可以通过基本的数学运算符 +-*/ 来构造。

>>> v = N.i - 2*N.j
>>> v/3
1/3*N.i + (-2/3)*N.j
>>> v + N.k
N.i + (-2)*N.j + N.k
>>> Vector.zero/2
0
>>> (v/3)*4
4/3*N.i + (-8/3)*N.j

除了基本的数学运算外,还可以对 Vector 执行 dotcross 的向量运算。

>>> v1 = 2*N.i + 3*N.j - N.k
>>> v2 = N.i - 4*N.j + N.k
>>> v1.dot(v2)
-11
>>> v1.cross(v2)
(-1)*N.i + (-3)*N.j + (-11)*N.k
>>> v2.cross(v1)
N.i + 3*N.j + 11*N.k

&^ 运算符分别被重载为 dotcross 方法。

>>> v1 & v2
-11
>>> v1 ^ v2
(-1)*N.i + (-3)*N.j + (-11)*N.k

然而,这不是执行这些操作的推荐方式。使用原始方法使代码更清晰、更易于理解。

除了这些操作,还可以在 sympy.vector 中计算 Vector 实例的外积。稍后会详细介绍。

SymPy 对向量的操作

SymPy 的 simplifytrigsimpdifffactor 操作在 Vector 对象上工作,使用标准的 SymPy API。

本质上,这些方法作用于提供的向量表达式中的度量数(基向量的系数)。

>>> from sympy.abc import a, b, c
>>> from sympy import sin, cos, trigsimp, diff
>>> v = (a*b + a*c + b**2 + b*c)*N.i + N.j
>>> v.factor()
((a + b)*(b + c))*N.i + N.j
>>> v = (sin(a)**2 + cos(a)**2)*N.i - (2*cos(b)**2 - 1)*N.k
>>> trigsimp(v)
N.i + (-cos(2*b))*N.k
>>> v.simplify()
N.i + (-cos(2*b))*N.k
>>> diff(v, b)
(4*sin(b)*cos(b))*N.k
>>> from sympy import Derivative
>>> Derivative(v, b).doit()
(4*sin(b)*cos(b))*N.k

Integral 也可以与 Vector 实例一起使用,类似于 Derivative

>>> from sympy import Integral
>>> v1 = a*N.i + sin(a)*N.j - N.k
>>> Integral(v1, a)
(Integral(a, a))*N.i + (Integral(sin(a), a))*N.j + (Integral(-1, a))*N.k
>>> Integral(v1, a).doit()
a**2/2*N.i + (-cos(a))*N.j + (-a)*N.k

如前所述,每个坐标系对应一个唯一的原点。点通常在 sympy.vector 中以 Point 类的形式实现。

要访问系统的原点,请使用 CoordSys3D 类的 origin 属性。

>>> from sympy.vector import CoordSys3D
>>> N = CoordSys3D('N')
>>> N.origin
N.origin
>>> type(N.origin)
<class 'sympy.vector.point.Point'>

你可以使用 Pointlocate_new 方法在空间中实例化新的点。参数包括新 Point 的名称(字符串),以及相对于 ‘父’ Point 的位置向量。

>>> from sympy.abc import a, b, c
>>> P = N.origin.locate_new('P', a*N.i + b*N.j + c*N.k)
>>> Q = P.locate_new('Q', -b*N.j)

类似于 Vector,用户永远不需要显式实例化一个 Point 对象。这是因为任何空间中的位置(尽管是相对的)都可以通过使用 CoordSys3Dorigin 作为参考,然后在其上使用 locate_new 以及后续的 Point 实例来指向。

一个 Point 相对于另一个 Point 的位置向量可以使用 position_wrt 方法计算。

>>> P.position_wrt(Q)
b*N.j
>>> Q.position_wrt(N.origin)
a*N.i + c*N.k

此外,可以以元组的形式获取 Point 相对于 CoordSys3D\(X\)\(Y\)\(Z\) 坐标。这是通过 express_coordinates 方法完成的。

>>> Q.express_coordinates(N)
(a, 0, c)

Dyadics

二元张量,或称二元张量,是由一对向量的并置形成的二阶张量。因此,向量的外积导致了二元张量的形成。二元张量已在 sympy.vector 模块中的 Dyadic 类中实现。

再次强调,你永远不需要实例化 Dyadic 对象。向量的外积可以使用 Vectorouter 方法来计算。| 运算符已经为 outer 重载。

>>> from sympy.vector import CoordSys3D
>>> N = CoordSys3D('N')
>>> N.i.outer(N.j)
(N.i|N.j)
>>> N.i|N.j
(N.i|N.j)

类似于 VectorDyadic 也有后续子类,如 BaseDyadicDyadicMulDyadicAdd。与 Vector 一样,可以从 Dyadic.zero 访问零对偶。

所有基本的数学运算也适用于 Dyadic

>>> dyad = N.i.outer(N.k)
>>> dyad*3
3*(N.i|N.k)
>>> dyad - dyad
0
>>> dyad + 2*(N.j|N.i)
(N.i|N.k) + 2*(N.j|N.i)

dotcross 也可以在 Dyadic 实例之间以及 DyadicVector 之间(反之亦然)工作 - 根据各自的数学定义。与 Vector 一样,&^ 已被重载用于 dotcross

>>> d = N.i.outer(N.j)
>>> d.dot(N.j|N.j)
(N.i|N.j)
>>> d.dot(N.i)
0
>>> d.dot(N.j)
N.i
>>> N.i.dot(d)
N.j
>>> N.k ^ d
(N.j|N.j)