Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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/")
Expand Down Expand Up @@ -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")
}
Expand Down
91 changes: 91 additions & 0 deletions docs/EXACT_LINEAR_ALGEBRA_NEXT_STEPS.md
Original file line number Diff line number Diff line change
@@ -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<Polynomial<T>>` 的行最简/列最简形**。
- **多项式矩阵的 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` 给出一版最小可合并实现清单:
- 要改哪些文件;
- 每个函数签名怎么定;
- 对应测试用例列表(先红后绿)。
15 changes: 9 additions & 6 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -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
signing.key=yourBase64EncodedPrivateKey
39 changes: 38 additions & 1 deletion src/main/kotlin/io/github/ezrnest/mathsymk/linear/Matrix.kt
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,30 @@ interface Matrix<T> : GenTuple<T> {
}
}

/**
* 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<T>(
val P: Matrix<T>,
val L: Matrix<T>,
val U: Matrix<T>,
val pivots: List<Int>,
) {
val rank: Int
get() = pivots.size
}

/**
* Determines whether this matrix is the same shape as [y].
*/
Expand Down Expand Up @@ -1381,6 +1405,19 @@ interface MatOverField<T> : Algebra<T, Matrix<T>>, MatOverEUD<T> {
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<T>.decompPLU(): PLUDecompositionResult<T> {
return MatrixImpl.decompPLU(this, model)
}

/**
* Returns the LDL decomposition of the given positive definite matrix:
* ```
Expand Down Expand Up @@ -1564,4 +1601,4 @@ internal class MatOverFieldImpl<T>(override val model: Field<T>, row: Int, colum
// require(ps.all { it in this })
// return MatrixImpl.product(ps, model)
// }
//}
//}
44 changes: 44 additions & 0 deletions src/main/kotlin/io/github/ezrnest/mathsymk/linear/MatrixImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T> decompPLU(m: Matrix<T>, mc: Field<T>): PLUDecompositionResult<T> {
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<Int>()

for (k in 0 until n) {
val pivotRow = (k until n).firstOrNull { !mc.isZero(upper[it, k]) } ?: continue
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve PLU invariants when a pivot column is all zero

In decompPLU, the early continue when column k has no non-zero candidate skips initialization of lower[k,k] and abandons pivot tracking for that step. On rank-deficient inputs with a zero leading column but non-zero entries later in the row (for example [[0,1],[0,0]]), this yields P=I and U=A but leaves row k of L zeroed, so P*A != L*U, and pivots/rank underreport the matrix rank. This should handle zero-pivot columns without breaking the decomposition identity.

Useful? React with 👍 / 👎.


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.
Expand Down
32 changes: 31 additions & 1 deletion src/test/kotlin/linear/MatrixTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -386,4 +416,4 @@ class MatrixTest {
}

}
}
}