From 3001d129eb97818eb9242dc2abf561e12568c9be Mon Sep 17 00:00:00 2001 From: Ezrnest <1403718476@qq.com> Date: Fri, 20 Feb 2026 17:19:24 +0800 Subject: [PATCH 1/2] linear: add P0 PLU decomposition API and tests --- docs/EXACT_LINEAR_ALGEBRA_NEXT_STEPS.md | 91 +++++++++++++++++++ .../github/ezrnest/mathsymk/linear/Matrix.kt | 39 +++++++- .../ezrnest/mathsymk/linear/MatrixImpl.kt | 44 +++++++++ src/test/kotlin/linear/MatrixTest.kt | 32 ++++++- 4 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 docs/EXACT_LINEAR_ALGEBRA_NEXT_STEPS.md diff --git a/docs/EXACT_LINEAR_ALGEBRA_NEXT_STEPS.md b/docs/EXACT_LINEAR_ALGEBRA_NEXT_STEPS.md new file mode 100644 index 0000000..cee1856 --- /dev/null +++ b/docs/EXACT_LINEAR_ALGEBRA_NEXT_STEPS.md @@ -0,0 +1,91 @@ +# 精确线性代数(非浮点)下一步建议 + +> 目标:围绕整数、有理数、有限域、多项式环等模型,继续增强 `linear` 模块的可用性与可验证性。 + +## 1. 线性方程组能力补全 + +当前已有 `solveLinear(A, b)` 与 `solveHomo(A)`,下一步可补: + +- **可逆矩阵的多右端项求解批处理**: + - 对 `AX=B` 的每一列右端复用同一消元过程,避免重复消元。 +- **解结构标准化输出**: + - 明确区分唯一解 / 无解 / 仿射解空间; + - 输出“特解 + 零空间基”。 +- **可验证证书(certificate)**: + - 返回秩、主元列、自由变量索引,方便上层做证明式检查。 + +## 2. 分解算法增强(以精确代数为中心) + +在已有 LU / LDL / Smith / Hermite 能力上,建议优先做: + +- **PLU / LUP(带置换)**: + - 对一般矩阵更稳健,避免无主元时失败。 +- **分式自由(fraction-free)LU**: + - 在整数与多项式系数下控制中间表达式膨胀。 +- **分解结果的统一数据结构**: + - 例如 `LUResult(L, U, P, rank, pivots)`。 + +## 3. 标准形与模分解方向 + +你们已经有 Hermite/Smith 入口,这是一条很强的“符号(精确)线代”路线,可继续: + +- **Smith Normal Form 的变换矩阵输出**: + - 不只返回对角因子,也返回 `UAV = D` 中的 `U,V`(可逆)。 +- **子模/商模不变量的直接 API**: + - 例如由 Smith 因子直接构造有限阿贝尔群分解信息。 +- **整数矩阵同余分类支持**: + - 与现有 `toCongDiagForm` 配套,补更多不变量查询。 + +## 4. 特征多项式与最小多项式 + +目前有 `charPoly`,可继续构建完整谱工具链(依旧非浮点): + +- **最小多项式 `minimalPolynomial`**(Krylov/Frobenius 思路)。 +- **Cayley-Hamilton 证书接口**: + - 除返回 `p(A)=0` 的布尔结果,可返回验证用中间对象。 +- **有理标准形 / Frobenius 标准形**: + - 对域上线代尤其有价值,且完全精确。 + +## 5. 多项式矩阵与有理函数矩阵 + +在“非浮点”语境下非常实用: + +- **`Matrix>` 的行最简/列最简形**。 +- **多项式矩阵的 Smith 形(PID 条件下)**。 +- **有理函数矩阵的约化表示**(控制理论、系统理论会用到)。 + +## 6. 性能与复杂度改进(保持精确性) + +- **可选算法策略**:小规模用定义式/朴素法,大规模自动切换 Bareiss/分块。 +- **稀疏矩阵路径**:为大规模精确问题提供稀疏消元接口。 +- **缓存与结构共享**:重复子式、子矩阵视图和主元轨迹复用。 + +## 7. 测试与性质验证(最优先) + +建议新增“性质测试”而不只是样例测试: + +- **分解回代恒等式**:`A = LU`, `A = L D L^T`, `U A V = D`。 +- **秩-零度定理**:`dim ker(A) + rank(A) = n`。 +- **Smith 因子链条件**:`d_i | d_{i+1}`。 +- **跨模型一致性**: + - 同一整数矩阵在 `Z` 与 `Z/pZ` 投影后的秩关系(受模约束)。 + +## 8. API 体验优化建议 + +- 用统一命名:`toRowEchelon`, `toReducedRowEchelon`, `decompPLU`。 +- 为“要求域/整环/欧几里得整环”的函数加显式文档标签。 +- 增加失败原因类型化异常(主元不存在、模型不满足、维度不匹配)。 + +## 9. 建议的落地优先级(两周节奏示例) + +1. **P0**:PLU + 统一分解结果对象 + 补测试。 +2. **P1**:Smith 形返回 `(U,D,V)` 与不变量 API。 +3. **P2**:最小多项式 + 有理标准形(先小规模正确性版本)。 +4. **P3**:稀疏路径与策略切换(性能迭代)。 + +--- + +如果你们希望,我下一步可以直接按 `P0` 给出一版最小可合并实现清单: +- 要改哪些文件; +- 每个函数签名怎么定; +- 对应测试用例列表(先红后绿)。 diff --git a/src/main/kotlin/io/github/ezrnest/mathsymk/linear/Matrix.kt b/src/main/kotlin/io/github/ezrnest/mathsymk/linear/Matrix.kt index 0ba5ac0..c4a810b 100644 --- a/src/main/kotlin/io/github/ezrnest/mathsymk/linear/Matrix.kt +++ b/src/main/kotlin/io/github/ezrnest/mathsymk/linear/Matrix.kt @@ -437,6 +437,30 @@ interface Matrix : GenTuple { } } +/** + * Result of a PLU decomposition: + * + * ``` + * P * A = L * U + * ``` + * + * where + * - [P] is a permutation matrix, + * - [L] is lower triangular with unit diagonal, + * - [U] is upper triangular. + * + * [pivots] stores the pivot column indices and [rank] is its size. + */ +data class PLUDecompositionResult( + val P: Matrix, + val L: Matrix, + val U: Matrix, + val pivots: List, +) { + val rank: Int + get() = pivots.size +} + /** * Determines whether this matrix is the same shape as [y]. */ @@ -1381,6 +1405,19 @@ interface MatOverField : Algebra>, MatOverEUD { return MatrixImpl.decompLU(this, model) } + /** + * Returns the PLU decomposition of this matrix: + * + * ``` + * P * A = L * U + * ``` + * + * where `P` is a permutation matrix, `L` is unit lower triangular and `U` is upper triangular. + */ + fun Matrix.decompPLU(): PLUDecompositionResult { + return MatrixImpl.decompPLU(this, model) + } + /** * Returns the LDL decomposition of the given positive definite matrix: * ``` @@ -1564,4 +1601,4 @@ internal class MatOverFieldImpl(override val model: Field, row: Int, colum // require(ps.all { it in this }) // return MatrixImpl.product(ps, model) // } -//} \ No newline at end of file +//} diff --git a/src/main/kotlin/io/github/ezrnest/mathsymk/linear/MatrixImpl.kt b/src/main/kotlin/io/github/ezrnest/mathsymk/linear/MatrixImpl.kt index 17707b8..8944ea7 100644 --- a/src/main/kotlin/io/github/ezrnest/mathsymk/linear/MatrixImpl.kt +++ b/src/main/kotlin/io/github/ezrnest/mathsymk/linear/MatrixImpl.kt @@ -1245,6 +1245,50 @@ object MatrixImpl { } + /** + * Computes the PLU decomposition of a square matrix `A`, returning `(P, L, U)` such that + * + * ``` + * P * A = L * U + * ``` + * + * where `P` is a permutation matrix, `L` is lower triangular with unit diagonal, and `U` is upper triangular. + */ + fun decompPLU(m: Matrix, mc: Field): PLUDecompositionResult { + require(m.isSquare) { + "The matrix must be square!" + } + val n = m.row + val upper = AMatrix.copyOf(m) + val lower = zero(n, n, mc) + val permutation = identity(n, mc) + val pivots = mutableListOf() + + for (k in 0 until n) { + val pivotRow = (k until n).firstOrNull { !mc.isZero(upper[it, k]) } ?: continue + + if (pivotRow != k) { + upper.swapRow(k, pivotRow) + permutation.swapRow(k, pivotRow) + if (k > 0) { + lower.swapRow(k, pivotRow, 0, k) + } + } + + lower[k, k] = mc.one + pivots += k + for (i in (k + 1) until n) { + val lambda = mc.eval { + upper[i, k] / upper[k, k] + } + lower[i, k] = lambda + upper[i, k] = mc.zero + upper.mulAddRow(k, i, mc.negate(lambda), k + 1, model = mc) + } + } + return PLUDecompositionResult(permutation, lower, upper, pivots) + } + /** * Computes the LU decomposition of the given matrix `A` , returns a pair of matrices `(L,U)` such that * `A = LU`, where `L` is lower triangular with `1` as diagonal elements and `U` is upper triangular. diff --git a/src/test/kotlin/linear/MatrixTest.kt b/src/test/kotlin/linear/MatrixTest.kt index 0bf29d0..e7649ec 100644 --- a/src/test/kotlin/linear/MatrixTest.kt +++ b/src/test/kotlin/linear/MatrixTest.kt @@ -276,6 +276,36 @@ class MatrixTest { } } + @Test + fun decompPLUOfSquareMatrix() { + with(matZmod97) { + val rng = Random(13) + val n = 5 + val A = Matrix(n) { _, _ -> rng.nextInt(97) } + val result = A.decompPLU() + assertEquals(result.P * A, result.L * result.U) + assertEquals(result.rank, result.pivots.size) + } + } + + @Test + fun decompPLUHandlesZeroLeadingPivotByRowSwap() { + with(matZmod7) { + val A = Matrix.of( + 3, + 3, + 0, 1, 2, + 1, 2, 3, + 2, 4, 1 + ) + val result = A.decompPLU() + assertEquals(result.P * A, result.L * result.U) + // a row swap should have happened, so P is not identity + assertTrue(result.P != eye(3)) + assertEquals(3, result.rank) + } + } + @Test fun decompRankOfFullRankMatrix() { with(matZmod97) { @@ -386,4 +416,4 @@ class MatrixTest { } } -} \ No newline at end of file +} From 470766cbc519b1e855f336b90b64529bf923c957 Mon Sep 17 00:00:00 2001 From: Ezrnest <1403718476@qq.com> Date: Fri, 20 Feb 2026 17:23:13 +0800 Subject: [PATCH 2/2] build: remove default proxy and make toolchain version configurable --- build.gradle.kts | 10 ++++++++-- gradle.properties | 15 +++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9e4bc1c..a194a9c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,12 @@ plugins { group = "io.github.ezrnest" version = "0.0.2" + +val javaToolchainVersion = providers.gradleProperty("javaToolchainVersion") + .map(String::toInt) + .orElse(21) + .get() + repositories { maven { url = uri("https://maven.aliyun.com/repository/public/") @@ -34,14 +40,14 @@ tasks.test { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(25)) + languageVersion.set(JavaLanguageVersion.of(javaToolchainVersion)) } withJavadocJar() withSourcesJar() } kotlin { - jvmToolchain(25) + jvmToolchain(javaToolchainVersion) compilerOptions{ freeCompilerArgs.add("-jvm-default=no-compatibility") } diff --git a/gradle.properties b/gradle.properties index 48ae518..7929430 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,16 @@ kotlin.code.style=official -# comment out and set the following lines if you are using a proxy -systemProp.http.proxyHost=127.0.0.1 -systemProp.http.proxyPort=7890 -systemProp.https.proxyHost=127.0.0.1 -systemProp.https.proxyPort=7890 +javaToolchainVersion=21 +# Proxy settings are intentionally not enabled by default. +# If needed in your local environment, uncomment and configure the lines below: +# systemProp.http.proxyHost=127.0.0.1 +# systemProp.http.proxyPort=7890 +# systemProp.https.proxyHost=127.0.0.1 +# systemProp.https.proxyPort=7890 + ossrhUsername=yourUsername ossrhPassword=yourPassword signing.keyId=yourKeyId signing.password=yourKeyPassword signing.secretKeyRingFile=path/to/your/secring.gpg -signing.key=yourBase64EncodedPrivateKey \ No newline at end of file +signing.key=yourBase64EncodedPrivateKey