向量 & 参考系¶
在 sympy.physics.vector
中,向量和参考系是动态系统的“构建块”。本文档将数学上描述这些内容,并描述如何使用该模块的代码。
向量¶
向量是一个具有大小(或长度)和方向的几何对象。在三维空间中的向量通常在纸上表示为:
向量代数¶
矢量代数是第一个要讨论的主题。
两个向量被称为相等当且仅当(iff)它们具有相同的大小和方向。
向量操作¶
向量可以进行多种代数运算:向量之间的加法、标量乘法和向量乘法。
基于平行四边形法则的向量加法。
向量加法也是可交换的:
标量乘法是向量和标量的乘积;结果是一个方向相同但大小按标量缩放的向量。注意,乘以 -1 等同于在垂直于向量的平面内围绕任意轴旋转向量 180 度。
单位向量是指其大小等于1的向量。给定任意向量 \(\mathbf{v}\) ,我们可以定义一个单位向量如下:
注意,每个向量都可以写成标量和单位向量的乘积。
在 sympy.physics.vector
中实现了三种向量积:点积、叉积和外积。
点积运算将两个向量映射为一个标量。它定义为:
其中 \(\theta\) 是 \(\mathbf{a}\) 和 \(\mathbf{b}\) 之间的角度。
两个单位向量的点积表示共同方向的大小;对于其他向量,它是共同方向的大小与两个向量大小的乘积。两个垂直向量的点积为零。下图显示了一些示例:
点积是可交换的:
两个向量的叉积向量乘法运算返回一个向量:
向量 \(\mathbf{c}\) 具有以下性质:它的方向垂直于 \(\mathbf{a}\) 和 \(\mathbf{b}\),其大小定义为 \(\Vert \mathbf{c} \Vert = \Vert \mathbf{a} \Vert \Vert \mathbf{b} \Vert \sin(\theta)`(其中 :math:\)theta` 是 \(\mathbf{a}\) 和 \(\mathbf{b}\) 之间的夹角),并且其方向由 \(\Vert \mathbf{a} \Vert \Vert \mathbf{b} \Vert\) 之间的右手定则确定。下图展示了这一点:
叉积具有以下性质:
它不具有交换性:
并且不是关联的:
两个平行向量的叉积将为零。
两个向量之间的外积在这里不会讨论,而是在惯性部分讨论(那是它被使用的地方)。其他有用的向量性质和关系包括:
替代表示¶
如果我们有三个不共面的单位向量 \(\mathbf{\hat{n}_x},\mathbf{\hat{n}_y},\mathbf{\hat{n}_z}\),我们可以将任意向量 \(\mathbf{a}\) 表示为 \(\mathbf{a} = a_x \mathbf{\hat{n}_x} + a_y \mathbf{\hat{n}_y} + a_z \mathbf{\hat{n}_z}\)。在这种情况下,\(\mathbf{\hat{n}_x},\mathbf{\hat{n}_y},\mathbf{\hat{n}_z}\) 被称为基。\(a_x, a_y, a_z\) 被称为度量数。通常,单位向量是相互垂直的,在这种情况下我们可以称它们为标准正交基,并且它们通常是右手系的。
要测试两个向量之间的相等性,现在我们可以执行以下操作。使用向量:
我们可以声称相等,如果: \(a_x = b_x, a_y = b_y, a_z = b_z\)。
向量加法对于相同的两个向量表示为:
乘法操作现在定义为:
要在给定的基底中写出一个向量,我们可以这样做:
示例¶
以下是这些操作的一些数值示例:
向量微积分¶
为了处理移动物体的向量微积分,我们必须引入参考系的概念。一个经典的例子是一列沿着轨道行驶的火车,你和你的朋友在车内。如果你们两个都坐着,你们之间的相对速度为零。但从火车外的观察者来看,你们都会有速度。
我们现在将对这个定义应用更多的严谨性。参考系是一个虚拟的“平台”,我们选择从它来观察矢量量。如果我们有一个参考系 \(\mathbf{N}\),矢量 \(\mathbf{a}\) 被认为是在参考系 \(\mathbf{N}\) 中固定的,如果从 \(\mathbf{N}\) 观察时它的任何属性都不会改变。我们通常会为每个参考系分配一个固定的标准正交基矢量集;\(\mathbf{N}\) 将拥有 \(\mathbf{\hat{n}_x}, \mathbf{\hat{n}_y}, \mathbf{\hat{n}_z}\) 作为其基矢量。
向量的导数¶
因此,一个在参考系中不固定的矢量,当从该参考系观察时,其性质会发生变化。微积分是研究变化的学科,为了处理固定和不固定在不同参考系中的矢量的特性,我们需要在我们的定义中更加明确。
在上图中,我们有向量 \(\mathbf{c,d,e,f}\)。如果要对 \(\mathbf{e}\) 关于 \(\theta\) 求导:
导数是什么并不清楚。如果你从框架 \(\mathbf{A}\) 观察,显然是非零的。如果你从框架 \(\mathbf{B}\) 观察,导数为零。因此,我们将引入框架作为导数符号的一部分:
以下是特定框架中向量导数的一些附加属性:
关联基向量集¶
我们现在需要定义两个不同参考系之间的关系;或者说如何将一个参考系的基向量与另一个参考系联系起来。我们可以使用方向余弦矩阵(DCM)来实现这一点。方向余弦矩阵将一个参考系的基向量与另一个参考系联系起来,具体如下:
当两个坐标系(例如,\(\mathbf{A}\) 和 \(\mathbf{B}\))最初对齐时,如果一个坐标系的所有基向量围绕一个与基向量对齐的轴旋转,我们称这两个坐标系通过一个简单的旋转相关联。下图展示了这一点:
上述旋转是关于 Z 轴的一个简单旋转,旋转角度为 \(\theta\)。注意,旋转后,基向量 \(\mathbf{\hat{a}_z}\) 和 \(\mathbf{\hat{b}_z}\) 仍然对齐。
这种旋转可以用以下方向余弦矩阵来表征:
关于X轴和Y轴的简单旋转定义为:
此处正方向的旋转将通过右手定则来定义。
方向余弦矩阵也涉及到基向量集合之间点积的定义。如果我们有两个相关基向量的参考系,它们的方向余弦矩阵可以定义为:
此外,方向余弦矩阵是正交的,即:
如果我们有参考系 \(\mathbf{A}\) 和 \(\mathbf{B}\),在这个例子中它们经历了简单的 z 轴旋转,旋转量为 \(\theta\),我们将有两组基向量。然后我们可以定义两个向量:\(\mathbf{a} = \mathbf{\hat{a}_x} + \mathbf{\hat{a}_y} + \mathbf{\hat{a}_z}\) 和 \(\mathbf{b} = \mathbf{\hat{b}_x} + \mathbf{\hat{b}_y} + \mathbf{\hat{b}_z}\)。如果我们希望在 \(\mathbf{A}\) 框架中表达 \(\mathbf{b}\),我们执行以下操作:
如果我们希望在 \(\mathbf{B}\) 中表达 \(\mathbf{a}\),我们这样做:
多帧导数¶
如果我们有参考系 \(\mathbf{A}\) 和 \(\mathbf{B}\),我们将有两组基向量。然后我们可以定义两个向量:\(\mathbf{a} = a_x\mathbf{\hat{a}_x} + a_y\mathbf{\hat{a}_y} + a_z\mathbf{\hat{a}_z}\) 和 \(\mathbf{b} = b_x\mathbf{\hat{b}_x} + b_y\mathbf{\hat{b}_y} + b_z\mathbf{\hat{b}_z}\)。如果我们想在参考系 \(\mathbf{A}\) 中对 \(\mathbf{b}\) 求导,我们必须首先将其在 \(\mathbf{A}\) 中表达,然后对测量数进行求导:
示例¶
向量微积分的一个例子:
在这个例子中,我们有两个物体,每个物体都有一个附带的参考系。我们假设 \(\theta\) 和 \(x\) 是时间的函数。我们希望知道向量 \(\mathbf{c}\) 在 \(\mathbf{A}\) 和 \(\mathbf{B}\) 参考系中的时间导数。
首先,我们需要定义 \(\mathbf{c}\);\(\mathbf{c}=x\mathbf{\hat{b}_x}+l\mathbf{\hat{b}_y}\)。这给出了在 \(\mathbf{B}\) 坐标系中的定义。现在我们可以进行以下操作:
要在 \(\mathbf{A}\) 框架中求导数,我们首先需要关联这两个框架:
现在我们可以做以下事情:
需要注意的是,这是在 \(\mathbf{A}\) 中 \(\mathbf{c}\) 的时间导数,并且是在 \(\mathbf{A}\) 坐标系中表示的。然而,我们也可以在 \(\mathbf{B}\) 坐标系中表示它,表达式仍然有效:
注意这两种形式在表达复杂性上的差异。它们是等价的,但其中一种要简单得多。这是一个极其重要的概念,因为以更复杂的形式定义向量会大大减慢运动方程的制定速度并增加其长度,有时甚至达到无法在屏幕上显示的程度。
使用向量和参考系¶
我们已经等到所有相关的数学关系都为向量和参考系定义之后才引入代码。这是因为向量的形成方式。在开始任何 sympy.physics.vector
中的问题时,第一步是定义一个参考系(记得先导入 sympy.physics.vector):
>>> from sympy.physics.vector import *
>>> N = ReferenceFrame('N')
现在我们已经创建了一个参考框架, \(\mathbf{N}\)。要访问任何基向量,首先需要创建一个参考框架。既然我们已经创建了一个表示 \(\mathbf{N}\) 的对象,我们就可以访问它的基向量:
>>> N.x
N.x
>>> N.y
N.y
>>> N.z
N.z
向量代数,在物理学中。¶
我们现在可以对这些向量进行基本的代数运算。:
>>> N.x == N.x
True
>>> N.x == N.y
False
>>> N.x + N.y
N.x + N.y
>>> 2 * N.x + N.y
2*N.x + N.y
记住,不要将标量与向量相加(N.x + 5
);这会引发错误。此时,我们将在向量中使用 SymPy 的 Symbol。处理符号时,请记得参考 SymPy 的陷阱和陷阱。:
>>> from sympy import Symbol, symbols
>>> x = Symbol('x')
>>> x * N.x
x*N.x
>>> x*(N.x + N.y)
x*N.x + x*N.y
在 sympy.physics.vector
中,向量乘法的多个接口已经在操作符级别、方法级别和函数级别上实现。向量点积可以如下工作:
>>> N.x.dot(N.x)
1
>>> N.x.dot(N.y)
0
>>> dot(N.x, N.x)
1
>>> dot(N.x, N.y)
0
“官方”接口是函数接口;这是所有示例中将使用的内容。这是为了避免属性方法并列在一起时产生的混淆,以及在操作符操作优先级的情况下。在 sympy.physics.vector
中用于向量乘法的操作符不具备正确的操作顺序;这可能导致错误。在使用操作符表示向量乘法时,需要小心使用括号。
叉积是这里将要讨论的另一种向量乘法。它提供了与点积类似的接口,并带有相同的警告。:
>>> N.x.cross(N.x)
0
>>> N.x.cross(N.z)
- N.y
>>> cross(N.x, N.y)
N.z
>>> cross(N.x, (N.y + N.z))
- N.y + N.z
向量可以进行两种额外的操作:将其归一化为长度1,以及获取其大小。这些操作如下:
>>> (N.x + N.y).normalize()
sqrt(2)/2*N.x + sqrt(2)/2*N.y
>>> (N.x + N.y).magnitude()
sqrt(2)
向量通常以矩阵形式表示,特别是在数值计算中。由于矩阵形式不包含向量所定义的参考系信息,因此您必须提供一个参考系来从向量中提取测量数值。有一个便捷函数可以完成此操作:
>>> (x * N.x + 2 * x * N.y + 3 * x * N.z).to_matrix(N)
Matrix([
[ x],
[2*x],
[3*x]])
向量微积分,在物理学中。¶
我们已经介绍了我们的第一个参考系。如果我们愿意,我们现在就可以在该参考系中求导:
>>> (x * N.x + N.y).diff(x, N)
N.x
SymPy 有一个 diff
函数,但它目前不支持 sympy.physics.vector
向量,因此请使用 Vector
的 diff
方法。这样做的原因是,在求一个 Vector
的导数时,除了需要指定对什么求导外,还必须指定参考系;而 SymPy 的 diff
函数不符合这种模式。
更复杂的情况出现在多个参考系中。如果我们引入第二个参考系,\(\mathbf{A}\),我们现在有两个参考系。注意,此时我们可以将 \(\mathbf{N}\) 和 \(\mathbf{A}\) 的分量相加,但不能进行向量乘法,因为两个参考系之间的关系尚未定义。:
>>> A = ReferenceFrame('A')
>>> A.x + N.x
N.x + A.x
如果我们想要进行向量乘法,首先我们必须定义一个方向。ReferenceFrame
的 orient
方法提供了这个功能。:
>>> A.orient(N, 'Axis', [x, N.y])
这通过一个简单的旋转,围绕Y轴,旋转量x,将 \(\mathbf{A}\) 坐标系相对于 \(\mathbf{N}\) 坐标系进行定向。这两个坐标系之间的方向余弦矩阵(DCM)可以通过 dcm
方法随时查看:A.dcm(N)
给出方向余弦矩阵 \(^{\mathbf{A}} \mathbf{C} ^{\mathbf{N}}\)。
其他更复杂的旋转类型包括体旋转、空间旋转、四元数和任意轴旋转。体旋转和空间旋转相当于连续进行3次简单的旋转,每次都是关于新坐标系中的一个基向量。以下是一个示例:
>>> N = ReferenceFrame('N')
>>> Bp = ReferenceFrame('Bp')
>>> Bpp = ReferenceFrame('Bpp')
>>> B = ReferenceFrame('B')
>>> q1,q2,q3 = symbols('q1 q2 q3')
>>> Bpp.orient(N,'Axis', [q1, N.x])
>>> Bp.orient(Bpp,'Axis', [q2, Bpp.y])
>>> B.orient(Bp,'Axis', [q3, Bp.z])
>>> N.dcm(B)
Matrix([
[ cos(q2)*cos(q3), -sin(q3)*cos(q2), sin(q2)],
[sin(q1)*sin(q2)*cos(q3) + sin(q3)*cos(q1), -sin(q1)*sin(q2)*sin(q3) + cos(q1)*cos(q3), -sin(q1)*cos(q2)],
[sin(q1)*sin(q3) - sin(q2)*cos(q1)*cos(q3), sin(q1)*cos(q3) + sin(q2)*sin(q3)*cos(q1), cos(q1)*cos(q2)]])
>>> B.orient(N,'Body',[q1,q2,q3],'XYZ')
>>> N.dcm(B)
Matrix([
[ cos(q2)*cos(q3), -sin(q3)*cos(q2), sin(q2)],
[sin(q1)*sin(q2)*cos(q3) + sin(q3)*cos(q1), -sin(q1)*sin(q2)*sin(q3) + cos(q1)*cos(q3), -sin(q1)*cos(q2)],
[sin(q1)*sin(q3) - sin(q2)*cos(q1)*cos(q3), sin(q1)*cos(q3) + sin(q2)*sin(q3)*cos(q1), cos(q1)*cos(q2)]])
空间方向类似于物体方向,但应用于从框架到物体的变换。物体和空间的旋转可以涉及两个或三个轴:’XYZ’ 有效,’YZX’、’ZXZ’、’YXY’ 等也有效。关键在于每个简单的旋转都是围绕与前一个不同的轴进行的;’ZZX’ 不能完全在三维空间中定向一组基向量。
有时,在一个步骤中创建一个新的参考框架并相对于现有框架进行定向会更方便。orientnew
方法允许这种功能,并且本质上封装了 orient
方法。在 orient
中可以做的所有事情,在 orientnew
中也可以做。:
>>> C = N.orientnew('C', 'Axis', [q1, N.x])
四元数(或欧拉参数)使用4个值来表征坐标系的方位。这和任意轴旋转在 orient
和 orientnew
方法的帮助中描述,或在参考文献 [Kane1983] 中描述。
最后,在开始多帧微积分运算之前,我们将介绍另一个 sympy.physics.vector
工具:dynamicsymbols
。dynamicsymbols
是一个快捷函数,用于在 SymPy 中创建未定义的时间函数。这种 ‘dynamicsymbol’ 的导数如下所示。:
>>> from sympy import diff
>>> q1, q2, q3 = dynamicsymbols('q1 q2 q3')
>>> diff(q1, Symbol('t'))
Derivative(q1(t), t)
上面的 ‘dynamicsymbol’ 打印不是很清晰;我们还将在这里介绍一些其他工具。对于非交互式会话,我们可以使用 vprint
而不是 print。:
>>> q1
q1(t)
>>> q1d = diff(q1, Symbol('t'))
>>> vprint(q1)
q1
>>> vprint(q1d)
q1'
对于交互式会话,使用 init_vprinting
。SymPy 的 vprint
、vpprint
和 latex
、vlatex
也有相应的模拟。:
>>> from sympy.physics.vector import init_vprinting
>>> init_vprinting(pretty_print=False)
>>> q1
q1
>>> q1d
q1'
在 sympy.physics.vector
中,应使用 ‘dynamicsymbol’ 来表示任何随时间变化的量,无论是坐标、变化的位置还是力。’dynamicsymbol’ 的主要用途是用于速度和坐标(关于这些内容,将在文档的运动学部分进行更多讨论)。
现在我们将使用一个 ‘dynamicsymbol’ 来定义我们新框架的方向,并且可以轻松地进行导数和时间导数。以下是一些示例。:
>>> N = ReferenceFrame('N')
>>> B = N.orientnew('B', 'Axis', [q1, N.x])
>>> (B.y*q2 + B.z).diff(q2, N)
B.y
>>> (B.y*q2 + B.z).dt(N)
(-q1' + q2')*B.y + q2*q1'*B.z
请注意,输出向量保持在它们被提供的相同框架中。对于由来自多个框架的基向量组成的向量分量,这一点仍然成立::
>>> (B.y*q2 + B.z + q2*N.x).diff(q2, N)
N.x + B.y
向量是如何编码的¶
以下是对 sympy.physics.vector
代码中如何定义向量的简短描述。这是为那些想了解更多关于 sympy.physics.vector
这部分工作原理的人提供的,不需要阅读即可使用此模块;除非你想了解此模块是如何实现的,否则不必阅读。
每个 Vector
的主要信息存储在 args
属性中,该属性存储了每个基准向量的三个测量数值,对于每个相关的基准框架。在创建 ReferenceFrame
之前,向量在代码中并不存在。此时,基准框架的 x
、y
和 z
属性是不可变的 Vector
,它们的测量数值分别为 [1,0,0]、[0,1,0] 和 [0,0,1],与该 ReferenceFrame
相关联。一旦这些向量可访问,就可以通过与基准向量进行代数运算来创建新向量。尽管如此,一个向量可以有来自多个框架的分量。这就是为什么 args
是一个列表;列表中的元素数量与向量分量中的唯一 ReferenceFrames
数量相同,即如果我们的新向量中有 A
和 B
框架的基准向量,args
的长度为 2;如果有 A
、B
和 C
框架的基准向量,args
的长度为三。
args
列表中的每个元素都是一个 2 元组;第一个元素是一个 SymPy Matrix``(这里存储了每组基向量的测量数值),第二个元素是一个 ``ReferenceFrame
,用于关联这些测量数值。
ReferenceFrame
存储了一些内容。首先,它存储了你在创建时提供的名称(name
属性)。它还存储了方向余弦矩阵,这些矩阵在创建时通过 orientnew
方法定义,或者在创建后通过 orient
方法调用。方向余弦矩阵由 SymPy 的 Matrix
表示,并且是字典的一部分,其中键是 ReferenceFrame
,值是 Matrix
;这些是双向设置的;也就是说,当你将 A
定向到 N
时,你正在设置 A
的方向字典以包含 N
及其 Matrix
,但你也在设置 N
的方向字典以包含 A
及其 ``Matrix``(该 DCM 是另一个的转置)。