第8章 附录:我们的记号体系

一个恰当的记号体系至少应能被两人理解,其中一人可以是作者本人。

Abdus Salam (1950)。

我们采用了一种函数式数学记号,与Spivak在其《流形上的微积分》[40]中所使用的记号相近。函数式记号的使用避免了传统数学记号中的许多歧义——这些歧义在经典力学中会妨碍清晰的推理。函数式记号仔细区分了函数本身与函数应用于特定自变量时的值。在函数式记号中,数学表达式是無歧义且自包含的。

我们采用了一种泛型算术,其中基本的算术运算(如加法和乘法)被扩展到了广泛的数学类型上。因此,例如加法运算符 + 可以应用于数、数的元组、矩阵、函数等等。泛型算术将操作数学对象时常用的非正式实践加以形式化。

我们常常希望操作聚合性量——例如一个粒子集合的所有直角坐标的汇集——而无须显式地操作各个分量。张量算术提供了一种处理聚合对象的传统方式:用指标标记分量,并引入诸如求和约定之类的惯例来操作指标。我们引入了一种元组算术作为操作聚合量的另一种方式,它通常让我们避免用指标来标记分量。元组算术受张量算术启发,但更为一般:元组中并非所有分量都必须具有相同的大小或类型。

该数学记号与计算机语言Scheme[24]的表达式一一对应。Scheme 基于 -演算[13],直接支持函数的操作。我们为 Scheme 添加了符号、数值和泛型特性以支持我们的应用。关于 Scheme 的简单介绍,请参见 Scheme 附录。数学记号与 Scheme 之间的对应关系要求数学表达式是無歧义且自包含的。Scheme 为验证数学推导提供了即时反馈,并有助于探索系统的行为。

函数

给定自变量 x,函数 f 的值记为 f(x)。表达式 f(x) 表示函数在给定自变量处的值;当我们希望表示函数本身时,只写 f。函数可以接受多个自变量。例如,考虑这样一个函数,它给出平面上由直角坐标给定的两点之间的欧几里得距离:

在 Scheme 中我们可以这样写:

(define (d x1 y1 x2 y2)
  (sqrt (+ (square (- x2 x1)) (square (- y2 y1)))))

若一个函数的值域与另一个函数的定义域有重叠,则这两个函数可以复合。函数的复合通过将一个函数的输出传递给另一个函数的输入来构造。我们使用 o 运算来记两个函数的复合:

一个计算其自变量正弦值之立方的过程 h,可以通过复合过程 cubesin 来定义:

(define h (compose cube sin))

(h 2)
.7518269446689928

这与下式相同:

(cube (sin 2))
.7518269446689928

算术被扩展到了函数的操作上:通常的数学运算可以应用于函数。例如加法和乘法;如果两个函数接受相同类型的自变量且它们的值可以相加或相乘,那么我们可以对这两个函数进行加法或乘法:

一个将自变量的立方与自变量的正弦值相乘的过程 g

(define g (* cube sin))

(g 2)
7.274379414605454

(* (cube 2) (sin 2))
7.274379414605454

符号值

与通常的数学记号一样,算术被扩展以允许使用表示未知或不完全指定的数学对象的符号。这些符号被当作具有已知类型的值来操作。默认情况下,Scheme 符号被假定为表示一个实数。因此表达式 'a 是一个字面 Scheme 符号,表示一个未指定的实数:

(print-expression
 ((compose cube sin) 'a))
(expt (sin a) 3)

过程 print-expression 对表达式进行简化,去除类型标签,并以可读的形式显示。我们可以使用该简化器来验证三角恒等式:

(print-expression 
  ((- (+ (square sin) (square cos)) 1) 'a))
0

正如能够操作符号数很有用一样,能够操作符号函数也很有用。过程 literal-function 创建一个过程,其行为像一个除了名称之外没有其他属性的函数。默认情况下,一个字面函数被定义为接受一个实自变量并产生一个实数值。例如,我们可能希望使用一个函数 f : __P2__ --> __P3__

(print-expression 
  ((literal-function 'f) 'x))
(f x)

(print-expression 
  ((compose (literal-function 'f) (literal-function 'g)) 'x))
(f (g x))

我们还可以创建接受多个(可能具有结构的)自变量并返回结构化值的字面函数。例如,要表示一个名为 g 的字面函数,它接受两个实自变量并返回一个实值(g : __P2__ × __P3__ --> __P4__),我们可以写:

(define g (literal-function 'g (-> (X Real Real) Real)))

(print-expression (g 'x 'y))
(g x y)

我们可以在任何可以使用同类型显式函数的地方使用这样的字面函数。

有一整套语言用于描述字面函数的类型,包括自变量的个数、自变量的类型以及值的类型。这里我们用表达式 (-> (X Real Real) Real) 描述了一个将实数对映射到实数的函数。稍后我们将引入结构化自变量和值,并展示字面函数处理这些情况的扩展。

元组

有两种元组:up 元组和 down 元组。我们将元组写为其分量的有序列表;如果是上元组,用圆括号分隔;如果是下元组,用方括号分隔。例如,速度分量 v0、v1 和 v2 的上元组 v

动量分量 p0、p1 和 p2 的下元组 p

上元组的分量通常用上标标识。下元组的分量通常用下标标识。我们在引用元组元素时使用从零开始的索引。这种记号遵循张量算术中的通常惯例。

在 Scheme 中,我们使用构造函数 updown 来创建元组:

(define v (up 'v^0 'v^1 'v^2))

(print-expression v)
(up v^0 v^1 v^2)

(define p (down 'p_0 'p_1 'p_2))

(print-expression p)
(down p_0 p_1 p_2)

元组算术与通常的张量算术不同之处在于,元组的分量本身也可以是元组,并且不同分量不必具有相同的结构。例如,相空间状态的元组结构 s

它是一个由时间、坐标和动量组成的上元组。时间 t 没有子结构。坐标是由坐标分量 xy 组成的上元组。动量是由动量分量 pxpy 组成的下元组。在 Scheme 中这写为:

(define s (up 't (up 'x 'y) (down 'p_x 'p_y)))

为了引用元组结构中的分量,存在选择子函数,例如:

选择子上整数下标的序列描述了访问目标分量的存取链。

过程 component 是通用的选择子过程,它实现了选择子函数 Iz

((component 0 1) (up (up 'a 'b) (up 'c 'd)))
b

要访问元组的分量,我们还可以使用选择子过程 ref,它接受一个元组和一个索引,并返回元组中指定位置的元素:

(ref (up 'a 'b 'c) 1)
b

我们在所有地方都使用从零开始的索引。过程 ref 可用于访问元组树的任何子结构:

(ref (up (up 'a 'b) (up 'c 'd)) 0 1)
b

两个长度相同的上元组可以逐元素相加或相减,若分量可兼容相加则产生一个上元组。类似地,两个长度相同的下元组可以逐元素相加或相减,若分量可兼容相加则产生一个下元组。

任何元组都可以乘以一个数,方法是将每个分量乘以该数。当然,数也可以相乘。可兼容相加的元组构成一个向量空间。

为方便起见,我们将元组的平方定义为元组各分量平方之和。元组可以相乘,如下文所述,但元组的平方并非元组与其自身的乘积。

元组乘法的含义取决于元组的结构。如果两个元组类型相反、长度相同,且对应元素具有以下性质,则称它们可兼容缩并:要么它们都是元组且可兼容缩并,要么其中之一不是元组。如果两个元组可兼容缩并,那么泛型乘法被解释为缩并:结果是元组对应分量乘积之和。例如,上文公式 (8.4) 和 (8.5) 中引入的 pv 可兼容缩并;其乘积为

因此,可兼容缩并的元组的乘积是一个内积。使用上面定义的元组 pv 我们得到

(print-expression (* p v))
(+ (* p_0 v^0) (* p_1 v^1) (* p_2 v^2))

元组的缩并是可交换的:pv = vp。注意:可兼容缩并的元组的乘法通常不是结合律的。例如,令 u = ( 5, 2 ), v = ( 11, 13 ), g = [ [ 3, 5 ], [ 7, 9 ] ]。那么 u (g v) = 964,而 (u g) v = 878。表达式 u g v 是歧义的。本书不会出现具有这种歧义的表达式。

两个不可兼容缩并的结构相乘的规则很简单。如果 AB 不可兼容缩并,乘积 AB 是一个类型为 B 的元组,其分量为 AB 的分量的乘积。同样的规则递归地应用于分量的乘法。因此,如果 B = ( B0, B1, B2 ),则 AB 的乘积为

如果 AC 不可兼容缩并且 C = [ C0, C1, C2 ],则乘积为

元组结构可以用来表示线性变换。例如,通常用矩阵表示的旋转

可以表示为元组结构:1

这样的元组与表示向量的上元组可兼容缩并。因此,例如:

两个表示线性变换的元组(尽管不可兼容缩并)也可以相乘。这种情况下,乘积表示线性变换的复合。例如,表示两个旋转的元组之乘积为

表示线性变换的元组的乘法是结合的,但通常不可交换,正如变换的复合是结合的但通常不可交换一样。

导数

函数 f 的导数是一个函数,记为 Df。我们的记号惯例是 D 是一个高优先级算子。因此 D 先作用于相邻的函数,然后再进行任何其他应用:Df(x) 与 (Df)(x) 相同。高阶导数通过对导数算子取幂来描述。因此函数 fn 阶导数记为 Dn f

用于产生函数导数的 Scheme 过程名为 Dsin 过程的导数是一个计算 cos 的过程:

(define derivative-of-sine (D sin))

(print-expression (derivative-of-sine 'x))
(cos x)

函数 f 的导数是函数 Df,该函数在特定自变量处的值是一个量,可以乘以自变量增量 x 以得到 f 值增量的线性近似:

例如,令 f 为将自变量立方的函数(f(x) = x3);那么 Df 是这样一个函数,它返回其自变量的平方的三倍(Df(y) = 3y2)。所以 f(5) = 125 且 Df(5) = 75。f 在自变量 x + x 处的值为

以及

因此 Df(x) 乘以 x 给出了 f(x + x) 中关于 x 的线性项,当 x 很小时,它提供了 f(x + x) - f(x) 的良好近似。

复合函数的导数遵循链式法则:

因此在 x 处,

导数是算子。算子类似于函数,但算子的乘法被解释为复合,而函数的乘法是值的乘法(见公式 8.3)。如果 D 是一个普通函数,那么乘法规则将意味着 D2 f 就是 Df 与其自身的乘积,这不是我们想要的。算术被扩展以允许操作算子。一个典型的算子是 (D + 1)(D - 1) = D2 - 1,它将一个函数减去其二阶导数。其中的 1 充当恒等算子:当与算子进行算术组合时,一个数被视为一个算子,将其输入乘以该数。这样的算子可以在 Scheme 中构造和使用如下:

(print-expression
  (((* (- D 1) (+ D 1)) (literal-function 'f)) 'x))
(+ (((expt D 2) f) x) (* -1 (f x)))

多自变量函数的导数

导数可以推广到接受多个自变量的函数。多自变量实值函数的导数是一个对象,它与自变量增量元组的缩并给出了函数值增量的线性近似。

多自变量函数可以视为一个以上元组为自变量的函数。因此,一个增量自变量元组是一个上元组,其分量对应每个自变量位置。这种函数的导数是一个下元组,其中包含函数关于每个自变量位置的偏导数。

假设我们有一个两个实自变量的实值函数 g,我们希望近似 gx, y 处的值的增量。如果自变量增量为元组 ( x, y ),我们计算:

使用上面定义的双自变量字面函数 g,我们有:

(print-expression ((D g) 'x 'y))
(down (((partial 0) g) x y) (((partial 1) g) x y))

一般而言,偏导数只是多自变量(或具有结构的自变量,或两者兼有;见下文)函数的导数的分量。因此,一个函数的偏导数是分量选择子与该函数导数的复合。确实:

具体来说,如果

那么

并且将自变量改变 x y 时增量的一阶近似为

复合函数的偏导数也遵循链式法则:

因此,如果 x 是一个自变量元组,那么

数学记号通常不区分多自变量函数和以元组为自变量的函数。令 h((x, y)) = g(x, y)。函数 h(接受一个由 xy 组成的元组作为自变量)与函数 g(接受自变量 xy)不加区分。我们同时使用这两种方式来定义多自变量函数。两种函数的导数都与自变量增量元组可兼容缩并。Scheme 在这里提供了便利:

(define (h s)
  (g (ref s 0) (ref s 1)))

(print-expression
 (h (up 'x 'y)))
(g x y)

(print-expression ((D g) 'x 'y))
(down (((partial 0) g) x y) (((partial 1) g) x y))

(print-expression ((D h) (up 'x 'y)))
(down (((partial 0) g) x y) (((partial 1) g) x y))

相空间状态函数是时间、坐标和动量的函数。令 H 为这样的函数。H 的值为 H(t, ( x, y ), [ px , py ] ),其中时间为 t,坐标为 ( x, y ),动量为 [ px , py ]。令 s 为如 (8.6) 中的相空间状态元组:

H 在自变量元组 s 处的值为 H(s)。我们同时使用这两种方式来书写 H 的值。

我们常常通过用分号标示自变量元组的边界、用逗号分隔其分量来表示包含元组的多自变量函数。如果 H 是一个相空间状态函数,自变量为 t、( x, y ) 和 [ px, py ],我们可以写为 H(t; x, y ; px, py)。这种记法丢失了上/下的区分,但我们的分号-逗号记法很方便且相当无歧义。

H 的导数是一个函数,它产生一个对象,该对象可以与自变量结构的增量缩并以产生函数值的增量。该导数是由三个偏导数组成的下元组。第一个偏导数是关于数值自变量的偏导数。第二个偏导数是一个下元组,包含关于上元组自变量每个分量的偏导数。第三个偏导数是一个上元组,包含关于下元组自变量每个分量的偏导数:

其中 1,0 表示函数关于第二个自变量(索引 1)的第一个分量(索引 0)的偏导数,以此类推。确实,对于任何函数 F 和存取链 z,有 z F = Iz o D F。因此,如果我们令 s 为一个增量相空间状态元组,

那么

注意:关于不同结构化自变量的偏导数算子通常不可交换。

在 Scheme 中我们必须做出明确的选择。我们通常假定相空间状态函数是以元组为自变量的函数。例如,

(define H
  (literal-function 'H 
    (-> (UP Real (UP Real Real) (DOWN Real Real)) Real)))

(print-expression
 (H s))
(H (up t (up x y) (down p_x p_y)))

(print-expression
 ((D H) s))
(down
 (((partial 0) H) (up t (up x y) (down p_x p_y)))
 (down (((partial 1 0) H) (up t (up x y) (down p_x p_y)))
       (((partial 1 1) H) (up t (up x y) (down p_x p_y))))
 (up (((partial 2 0) H) (up t (up x y) (down p_x p_y)))
     (((partial 2 1) H) (up t (up x y) (down p_x p_y)))))

结构化结果

有些函数产生结构化输出。输出为元组的函数等价于一个元组,其每个分量函数产生输出元组的一个分量。

例如,一个接受一个数值自变量并产生一个输出结构的函数可用于描述空间中的曲线。下面这个函数描述了三维空间中绕 z 轴的一条螺旋路径:

导数就是函数各分量导数的上元组:

在 Scheme 中我们可以写

(define (helix t)
  (up (cos t) (sin t) t))

或者直接

(define helix (up cos sin identity))

其导数就是函数各分量导数的上元组:

(print-expression ((D helix) 't))
(up (* -1 (sin t)) (cos t) 1)

一般地,产生结构化输出的函数被处理为函数的结构化组合,每个分量对应一个函数。接受结构化输入并产生结构化输出的函数的导数是一个对象,当与输入增量结构缩并时,它产生输出增量的线性近似。因此,如果我们按如下方式定义函数 g

那么 g 的导数为

在 Scheme 中:

(define (g x y)
  (up (square (+ x y)) (cube (- y x)) (exp (+ x y))))

(print-expression ((D g) 'x 'y))
(down (up (+ (* 2 x) (* 2 y))
          (+ (* -3 (expt x 2)) (* 6 x y) (* -3 (expt y 2)))
          (* (exp y) (exp x)))
      (up (+ (* 2 x) (* 2 y))
          (+ (* 3 (expt x 2)) (* -6 x y) (* 3 (expt y 2)))
          (* (exp y) (exp x))))

练习 8.1. 链式法则 令 F(x, y) = x2 y3, G(x, y) = (F(x, y), y), H(x, y) = F(F(x, y), y),因此 H = F o G

a. 计算 0 F(x, y) 和 1 F(x, y)。

b. 计算 0 F(F(x, y), y) 和 1 F(F(x, y), y)。

c. 计算 0 G(x, y) 和 1 G(x, y)。

d. 计算 DF(a, b), DG(3, 5) 和 DH(3a2, 5b3)。

练习 8.2. 计算导数 我们可以通过多种方式将多自变量函数表示为过程,具体取决于我们希望如何使用它们。最简单的方式是将过程的自变量与函数的自变量等同起来。

例如,我们可以按如下方式编写练习 8.1 中出现的函数的实现:

(define (f x y)
  (* (square x) (cube y)))
(define (g x y)
  (up (f x y) y))
(define (h x y)
  (f (f x y) y))

在这种选择下,将一个接受多个自变量的函数(如 f)与一个产生这些自变量元组的函数(如 g)进行复合是笨拙的。另一种方式是,我们可以将函数自变量表示为元组数据结构的槽位,这样与产生此类数据结构的函数的复合就变得容易了。然而,这种选择要求过程构建和分解结构。

例如,我们可以按如下方式定义实现上述函数的过程:

(define (f v) 
  (let ((x (ref v 0))
        (y (ref v 1)))
    (* (square x) (cube y))))
(define (g v) 
  (let ((x (ref v 0))
        (y (ref v 1)))
    (up (f v) y)))
(define h (compose f g))

使用计算机重复练习 8.1。探索多自变量函数的两种实现。


1 元组结构的分量排列方式并不像矩阵记号中那样具有特定含义:我们同样可以将这个元组写为 [ ( cos, sin ), ( - sin, cos ) ]。