det_read: incompatible shapes: lhs=[2], rhs=[3]
主线位置:直接计算主线和 traced 主线上的
tenferro-linalg,det/slogdet。各运算接受什么形状、0.8.0 为什么开始拒绝长方形矩阵,见 线性代数 的「det 只接受方阵」。
flowchart TB
a["2x3 矩阵"] --> det["det"]
det --> sq{"行数等于列数"}
sq -->|具体形状| e1["建图或调用时报错"]
sq -->|符号形状| e2["编译时约束失败"]
sq -->|方阵| lu["LU 再乘主元"]
a --> fix["A A^T 或取方块"]
fix --> lu
实验 crate exp-tenferro8/pit2-exp,bin p27_det,CPU(CpuEngine::serial()),debug,M2 Max、macOS 27、rustc 1.99.0。
现象
一个 2×3 矩阵,在四个面上求行列式:
let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], rect.clone())?;
a.det(&mut engine) // typed
TracedTensor::from_vec_col_major(vec![2, 3], rect.clone())?.det() // traced,具体形状
let sym = TracedTensor::input_symbolic_shape(DType::F64, 2)?;
GraphCompiler::new().compile_with_input_specs(&sym.det()?, &[(&sym, DType::F64, &[2, 3])])
rt.with_eager_session(&mut engine, |s| s.det(&x)) // eager
typed det [2,3]: Err(det_read: incompatible shapes: lhs=[2], rhs=[3])
Validation { op: "det_read", source: ShapeMismatch(IncompatibleShapes { lhs: [2], rhs: [3] }) }
typed slogdet [2,3]: Err(slogdet_read: incompatible shapes: lhs=[2], rhs=[3])
traced det [2,3]: Err(slogdet: incompatible shapes: lhs=[2], rhs=[3])
traced det on a symbolic placeholder: build Ok
compile with [2,3]: Err(extension family "tenferro-linalg.linalg.v1" shape constraint at instruction Some(1) failed: 2 (2) Equal 3 (3))
eager det [2,3]: Err(slogdet: incompatible shapes: lhs=[2], rhs=[3])
- 消息说「incompatible shapes」,
lhs=[2], rhs=[3]看上去像两个操作数的形状,其实是同一个矩阵的行数和列数。没有一处说「需要方阵」。 - traced 和 eager 调的是
det,报错写的是slogdet:它们的det由slogdet组合而成。 - 符号形状的占位符建图通过,绑定
[2, 3]编译时才失败,消息是一条符号约束2 (2) Equal 3 (3)。
出处
四个面共用 validate_square_matrix:先查秩,再比前两维(列主序的批量矩阵把行、列放在前两维),不等就返回 ShapeMismatch(IncompatibleShapes { lhs: [rows], rhs: [cols] })。复用「两个形状不兼容」这个变体,是上面措辞的来源。形状是符号时没法当场比,traced 的行列式元数据记下一条「行数等于列数」的符号约束,编译时代入绑定的形状检查。
这条检查是 PR #2329(首次进入 v0.8.0)加的。之前 det 和 slogdet 直接用能处理长方形矩阵的 LU、把前 min(rows, cols) 个主元乘起来,提交说明的原话是「a rectangular input returned a plausible but wrong scalar on every surface」。所以从 0.7.1 升上来,原来「能跑」的代码现在报错,那正是原来算错的地方。lu 本身仍然接受长方形矩阵。
修法
先想清楚要的是什么。长方形矩阵没有行列式;常见的意图有两种:
// 1. Gram 矩阵的行列式(体积的平方):det(A Aᵀ)
let gram = a.matmul(&a.transpose(&[1, 0], &mut engine)?, &mut engine)?;
println!("det(A A^T) = {:?}", gram.det(&mut engine)?.as_slice()?);
// 2. 某个方块的行列式:前两列
let sq = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], rect[..4].to_vec())?;
println!("det(A[:, :2]) = {:?}", sq.det(&mut engine)?.as_slice()?);
det(A A^T) = [13.99999999999968]
det(A[:, :2]) = [-2.0]
A = [[1, 3, 5], [2, 4, 7]],A Aᵀ = [[35, 49], [49, 69]],行列式 35·69 − 49² = 14;前两列 [[1, 3], [2, 4]] 的行列式是 −2。0.7.1 返回的「前 min(rows, cols) 个主元的乘积」依赖选主元的结果,两种意图都不保证对上(本页没有在 0.7.1 上重跑)。
怎么确认没中招
- 看到
det_read/slogdet_read/slogdet加incompatible shapes,先打印输入的形状,lhs、rhs就是行数和列数。 - 从 0.7.1 升级时,搜所有
det/slogdet的调用,确认输入是方阵;之前对长方形输入「算出来」的数都是错的。 - 符号形状的程序要编译一次真实的绑定,才能看到这条约束。
小结
det和slogdet只收方阵;2×3 输入报det_read: incompatible shapes: lhs=[2], rhs=[3],lhs、rhs是行数和列数,traced 和 eager 报成slogdet,符号形状在编译时报约束失败。- 检查是 v0.8.0 的 PR #2329 加的;0.7.1 对长方形输入返回前
min(rows, cols)个主元的乘积,一个看似合理的错值。 - 想要的如果是体积,用
det(A Aᵀ);如果是某个方块,先切出来。