吉文斯旋转
吉文斯旋转(Givens rotation)只在两个坐标方向中作平面旋转,其他坐标保持不变。它常用于逐个消去矩阵中的元素,尤其适合只需更新少数行或保留稀疏结构的场合。二维约定为 与通常正角度逆时针的旋转矩阵相比,这个符号约定的角度方向相反;选定约定后,消元公式必须保持一致。
消去一个数的参数
给定实数 ,希望 。若 ,取 则第一分量为 ,第二分量为 ;同时 ,直接相乘给出 和 。若 ,可取 ,避免除零。
以 为例,。旋转角为顺时针 ,并非将第二个分量直接删掉;总长度必须由首分量五承接。
一次两行更新
若 左乘矩阵的第 行,则对每一列 ,新元素为 计算第二行时必须仍使用旧行值,否则会把刚修改过的元素又代回,得到另一种运算。一次两行更新对有 列的矩阵耗时 。
取 ,用主例的 , 于是 ,其中 。第二列的两项分别是 与 。行列式核对也成立:,,因为旋转行列式是一。
可运行参数构造
直接先算 可能使平方溢出;先得到原尺度范数再相除,也可能受次正规数的舍入影响。以下代码先用最大绝对分量缩放,由缩放后的范数计算方向参数;再用 math.hypot 单独计算原尺度目标。输入两个有限实数,返回 ,不改写输入;若目标范数溢出,抛出 OverflowError。只处理常见实旋转;复数消元需使用酉旋转的共轭规则。
import math
def givens(a, b):
a, b = float(a), float(b)
if not (math.isfinite(a) and math.isfinite(b)):
raise ValueError("require finite real inputs")
scale = max(abs(a), abs(b))
if scale == 0:
return 1.0, 0.0, 0.0
aa, bb = a / scale, b / scale
scaled_norm = math.hypot(aa, bb)
c, s = aa / scaled_norm, bb / scaled_norm
r = math.hypot(a, b)
if not math.isfinite(r):
raise OverflowError("rotation norm is not representable")
return c, s, r
c, s, r = givens(3, 4)
print(c, s, r) # 0.6 0.8 5.0
print(c * 3 + s * 4) # 5.0
print(-s * 3 + c * 4) # approximately 0.0
参数构造是常数时间和空间;在高维中只更新指定两行,不必显式形成整张旋转矩阵。稠密QR分解需要多次旋转,总成本仍可达到 ,不能把单次旋转的成本当作整次分解的成本。
方向与目标尺度应分开理解。若 都是最小正浮点数 math.ulp(0.0),本函数仍给出约 ; 因次正规刻度舍入成最小正数。若反过来用这个已舍入的 相除,就会错误地得到 。极小输出的逐项乘加同样会舍入,不能据此要求每个浮点等式逐位精确;旋转方向的单位圆条件仍应在通常的浮点相对精度内成立。
变式取 ,得到 ,第二分量被正确消去;若 ,本约定得到 。不同软件也可能允许负的 以满足其他连续性约定,比较参数时应先比较它们实现的等式,而非只看符号是否相同。
参考文献
LAPACK作者,DLARTG源码文档,平面旋转定义;本文Python实现采用非负r约定,边界符号可能不同。 Sheldon Axler,Linear Algebra Done Right,第四版,§7D(正交变换与QR分解);本文例子与图解独立构造。