数组是 Julia 的核心内容之一,而线性代数则是它真正发力的主场。虽然前面的数组笔记已经涉及了一些矩阵和广播,但如果不单独整理一篇,很多线性代数语义还是容易和“普通数组运算”混在一起。

概述

Julia 的线性代数体验和 Python/Numpy、MATLAB 既相似又不同:

  • 和 MATLAB 一样,矩阵乘法直接使用 *
  • 和 Python/Numpy 一样,元素访问写成 A[i, j]
  • 但 Julia 的标准库 LinearAlgebra 还引入了很多特殊矩阵类型和分解对象,它们不是简单的“二维数组”。

因此看 Julia 里的线性代数代码时,需要同时区分:

  • 这是普通数组操作;
  • 还是线性代数意义上的操作;
  • 结果是立即物化的数组,还是带语义的特殊对象。

LinearAlgebra 标准库

线性代数相关功能主要来自标准库:

1
using LinearAlgebra

它不是默认自动导入的,但属于 Julia 自带标准库,不需要额外安装。

向量与矩阵

创建

1
2
x = [1, 2, 3]          # Vector
A = [1 2 3; 4 5 6] # Matrix

这里要特别注意:Julia 的 Vector 是真正的一维数组,而不是 MATLAB 那种伪装成矩阵的行向量/列向量。

尺寸

1
2
3
4
size(x) # (3,)
size(A) # (2, 3)
ndims(x) # 1
ndims(A) # 2

基本线性代数运算

矩阵乘法

1
2
3
4
A = [1 2; 3 4]
B = [5 6; 7 8]

A * B

这和 MATLAB 相同,但和 Numpy 不同。Numpy 中 * 默认是逐元素乘法,Julia 里 * 默认就是线性代数乘法。

向量内积

常见做法:

1
2
3
4
x = [1, 2, 3]
y = [4, 5, 6]

dot(x, y) # 32

虽然也可以写某些转置乘法形式,但日常更推荐直接用 dot,语义最清楚。

逐元素运算

如果想做逐元素乘法,仍然要使用点语法:

1
2
x .* y
A .* B

不要把线性代数乘法和逐元素乘法混在一起。

转置与共轭转置

'

在 Julia 里:

1
A'

表示 adjoint,也就是实数情形下的转置、复数情形下的共轭转置。

这和 MATLAB 的 ' 非常接近。

transpose

如果只想做单纯转置,不希望共轭参与,更适合显式使用:

1
transpose(A)

对于复数矩阵,这一点尤其需要注意。

adjoint

也可以直接写:

1
adjoint(A)

这比单独写 ' 更明确。

单位矩阵与特殊矩阵

I

Julia 提供了一个很特别的单位矩阵对象:

1
I

它不是普通的稠密矩阵,而是 UniformScaling 对象。

例如:

1
2
A = [1 2; 3 4]
A + I

如果需要具体尺寸的矩阵,可以显式构造:

1
Matrix(I, 3, 3)

Diagonal

1
D = Diagonal([1, 2, 3])

这不是普通 Matrix,而是带结构信息的对角矩阵类型。

三角矩阵

1
2
U = UpperTriangular([1 2; 3 4])
L = LowerTriangular([1 2; 3 4])

这些类型保留了矩阵结构信息,后续求解和分解时往往更高效。

线性方程组

反斜杠 \

Julia 中求解线性方程组最常见的写法是:

1
2
3
4
A = [3.0 1.0; 1.0 2.0]
b = [9.0, 8.0]

x = A \ b

这表示求解:

1
A * x = b

这和 MATLAB 的 \ 写法非常接近。

重要的是:通常不建议先求逆再相乘。也就是不要轻易写成:

1
inv(A) * b

无论是数值稳定性还是性能,通常都不如直接 A \ b

范数与迹

norm

1
2
norm([3, 4]) # 5.0
norm(A)

默认通常是 2-范数或 Frobenius 范数,具体取决于对象类型。

tr

矩阵迹:

1
tr([1 2; 3 4]) # 5

行列式与秩

1
2
det(A)
rank(A)

这些接口都比较直接,但要注意:

  • 浮点数情形下,秩和奇异性判断都依赖数值阈值;
  • 不要把它们想成完全符号意义上的精确数学对象。

特征值与特征向量

1
2
vals = eigvals(A)
E = eigen(A)

eigen(A) 返回的不只是向量,还包括特征值和特征向量等信息。

例如:

1
2
E.values
E.vectors

矩阵分解

Julia 在线性代数里一个很有代表性的设计是:分解结果通常不是直接返回若干裸数组,而是返回带语义的分解对象。

LU 分解

1
F = lu(A)

可以访问:

1
2
3
F.L
F.U
F.p

QR 分解

1
F = qr(A)

Cholesky 分解

适用于对称正定矩阵:

1
F = cholesky(A)

SVD

1
2
3
4
F = svd(A)
F.U
F.S
F.Vt

这些对象风格和 MATLAB、Numpy 都略有不同,但一旦适应之后会觉得很自然,因为它们把“分解结果是一个对象”这件事表达得更完整。

结构矩阵与惰性语义

Julia 很强调“保留结构信息”。

例如:

  • 对角矩阵不是普通稠密矩阵;
  • 三角矩阵不是普通稠密矩阵;
  • 单位矩阵 I 甚至连尺寸都可以按上下文推断。

这意味着:

  • 代码语义更清晰;
  • 某些操作不需要立刻物化完整矩阵;
  • 性能和内存通常也更有优势。

广播与线性代数不要混淆

这是初学 Julia 线性代数时很容易犯错的地方。

例如:

1
2
3
4
5
A * B      # 线性代数乘法
A .* B # 逐元素乘法

A ^ 2 # 矩阵幂
A .^ 2 # 逐元素平方

这套区分和 MATLAB 很像,但如果来自 Python/Numpy,就必须反复提醒自己。

小结

Julia 的线性代数写法有几个地方很像 MATLAB,但真正用起来又比 MATLAB 更强调对象语义和结构信息。*\' 这些操作不能按普通数组运算去理解,LinearAlgebra 里很多结果也不只是“返回一个数组”,而是返回带结构的对象,这一点需要尽快适应。