#![allow(dead_code)]
use ocas_domain::{Domain, Rational, RationalDomain};
use ocas_poly::DenseUnivariatePolynomial;
use super::rde::rde_solve;
use crate::tower::build::Tower;
use crate::tower::elem::KElem;
type DPoly = DenseUnivariatePolynomial<RationalDomain>;
const MAX_COUPLED_DIM: usize = 4;
pub(crate) fn solve_coupled_constant(
tower: &Tower,
a: &[Vec<Rational>],
b: &[KElem],
) -> Option<Vec<KElem>> {
let n = a.len();
if n == 0 || n > MAX_COUPLED_DIM || b.len() != n {
return None;
}
let nv = tower.n_vars();
let charpoly = char_poly_flv(a);
let roots = rational_roots_with_multiplicity(&charpoly)?;
let mut basis: Vec<Vec<Rational>> = Vec::new(); let mut lambdas: Vec<Rational> = Vec::new();
for (lambda, mult) in &roots {
let ns = nullspace(&sub_diag(a, lambda));
if ns.len() != *mult {
return None; }
for v in ns {
basis.push(v);
lambdas.push(lambda.clone());
}
}
let p_inv = inverse(&transpose_cols(&basis, n))?;
let mut ys: Vec<KElem> = Vec::with_capacity(n);
for (col, lambda) in lambdas.iter().enumerate() {
let _ = col;
let mut w = KElem::zero(nv);
for (j, bj) in b.iter().enumerate() {
w = w.add(&bj.mul_rational(&p_inv[col][j]));
}
let f = KElem::from_rational(lambda, nv);
ys.push(rde_solve(tower, 0, &f, &w)?);
}
let p = transpose_cols(&basis, n);
let mut out = Vec::with_capacity(n);
for row in &p {
let mut yi = KElem::zero(nv);
for (zj, pij) in ys.iter().zip(row.iter()) {
yi = yi.add(&zj.mul_rational(pij));
}
out.push(yi);
}
for (i, yi) in out.iter().enumerate() {
let dyi = crate::tower::build::tower_diff(yi, &[]);
let mut lhs = dyi;
for (j, yj) in out.iter().enumerate() {
lhs = lhs.add(&yj.mul_rational(&a[i][j]));
}
if !lhs.eq_cross(&b[i]) {
return None;
}
}
Some(out)
}
fn char_poly_flv(a: &[Vec<Rational>]) -> DPoly {
let dom = RationalDomain;
let n = a.len();
let mut cs: Vec<Rational> = vec![dom.one()];
let mut b_prev: Vec<Vec<Rational>> = (0..n)
.map(|i| {
(0..n)
.map(|j| if i == j { dom.one() } else { dom.zero() })
.collect()
})
.collect();
for k in 1..=n {
let ab = mat_mul(a, &b_prev);
let trace = (0..n).fold(dom.zero(), |acc, i| dom.add(&acc, &ab[i][i]));
let ck = dom.neg(&dom.mul(&trace, &Rational::new(1, k as i64)));
cs.push(ck.clone());
b_prev = ab;
for (i, row) in b_prev.iter_mut().enumerate() {
let v = dom.add(&row[i], &ck);
row[i] = v;
}
}
DPoly::from_coeffs(RationalDomain, cs.into_iter().rev().collect())
}
fn rational_roots_with_multiplicity(f: &DPoly) -> Option<Vec<(Rational, usize)>> {
let (roots, fully_split) = super::rational::rational_roots(f)?;
if !fully_split {
return None;
}
let dom = RationalDomain;
let mut out = Vec::new();
for r in roots {
let mut cur = f.clone();
let mut mult = 0usize;
loop {
let mut acc = dom.zero();
for c in cur.coeffs().iter().rev() {
acc = dom.add(&dom.mul(&acc, &r), c);
}
if !dom.is_zero(&acc) {
break;
}
let m = cur.coeffs().len() - 1;
let mut quo = vec![dom.zero(); m];
let mut carry = cur.coeffs()[m].clone();
for i in (1..=m).rev() {
quo[i - 1] = carry.clone();
if i >= 2 {
carry = dom.add(&cur.coeffs()[i - 1], &dom.mul(&carry, &r));
}
}
cur = DPoly::from_coeffs(RationalDomain, quo);
mult += 1;
}
out.push((r, mult));
}
Some(out)
}
fn sub_diag(a: &[Vec<Rational>], lambda: &Rational) -> Vec<Vec<Rational>> {
let dom = RationalDomain;
a.iter()
.enumerate()
.map(|(i, row)| {
row.iter()
.enumerate()
.map(|(j, v)| {
if i == j {
dom.sub(v, lambda)
} else {
v.clone()
}
})
.collect()
})
.collect()
}
fn rref(m: &[Vec<Rational>]) -> Vec<Vec<Rational>> {
let dom = RationalDomain;
let mut m: Vec<Vec<Rational>> = m.to_vec();
let rows = m.len();
let cols = m.first().map(|r| r.len()).unwrap_or(0);
let mut pivot_row = 0;
for col in 0..cols {
if pivot_row >= rows {
break;
}
let Some(piv) = (pivot_row..rows).find(|&r| !dom.is_zero(&m[r][col])) else {
continue;
};
m.swap(pivot_row, piv);
let inv = dom.inv(&m[pivot_row][col]).expect("nonzero pivot");
for v in &mut m[pivot_row] {
*v = dom.mul(v, &inv);
}
for r in 0..rows {
if r == pivot_row {
continue;
}
let factor = m[r][col].clone();
if dom.is_zero(&factor) {
continue;
}
let pr = m[pivot_row].clone();
m[r] = m[r]
.iter()
.zip(pr.iter())
.map(|(v, p)| dom.sub(v, &dom.mul(&factor, p)))
.collect();
}
pivot_row += 1;
}
m
}
fn nullspace(m: &[Vec<Rational>]) -> Vec<Vec<Rational>> {
let dom = RationalDomain;
let rows = m.len();
let cols = m.first().map(|r| r.len()).unwrap_or(0);
let r = rref(m);
let mut pivot_of_row = Vec::new();
let mut pivot_cols = Vec::new();
for row in &r {
let pc = row.iter().position(|v| !dom.is_zero(v));
pivot_of_row.push(pc);
if let Some(c) = pc {
pivot_cols.push(c);
}
}
let mut basis = Vec::new();
for free in 0..cols {
if pivot_cols.contains(&free) {
continue;
}
let mut v = vec![RationalDomain.zero(); cols];
v[free] = dom.one();
for (row, pc) in pivot_of_row.iter().enumerate() {
if let Some(c) = pc {
v[*c] = dom.neg(&r[row][free]);
}
}
basis.push(v);
}
let _ = rows;
basis
}
fn mat_mul(a: &[Vec<Rational>], b: &[Vec<Rational>]) -> Vec<Vec<Rational>> {
let dom = RationalDomain;
let n = a.len();
let m = b[0].len();
let inner = b.len();
(0..n)
.map(|i| {
(0..m)
.map(|j| {
(0..inner).fold(dom.zero(), |acc, k| {
dom.add(&acc, &dom.mul(&a[i][k], &b[k][j]))
})
})
.collect()
})
.collect()
}
fn transpose_cols(cols: &[Vec<Rational>], n: usize) -> Vec<Vec<Rational>> {
(0..n)
.map(|i| (0..cols.len()).map(|j| cols[j][i].clone()).collect())
.collect()
}
fn inverse(m: &[Vec<Rational>]) -> Option<Vec<Vec<Rational>>> {
let n = m.len();
let dom = RationalDomain;
let aug: Vec<Vec<Rational>> = m
.iter()
.enumerate()
.map(|(i, row)| {
row.iter()
.cloned()
.chain((0..n).map(|j| if i == j { dom.one() } else { dom.zero() }))
.collect()
})
.collect();
let r = rref(&aug);
for (i, row) in r.iter().enumerate() {
for (j, v) in row.iter().enumerate().take(n) {
let expect = if i == j { dom.one() } else { dom.zero() };
if *v != expect {
return None;
}
}
}
Some(r.iter().map(|row| row[n..].to_vec()).collect())
}
#[cfg(test)]
mod tests {
use ocas_atom::AtomArena;
use ocas_core::arena::Arena;
use ocas_domain::Rational;
use super::*;
use crate::tower::build::build_tower;
use crate::tower::convert::atom_to_rational_extended;
use ocas_atom::Symbol;
fn rat(p: i64, q: i64) -> Rational {
Rational::new(p, q)
}
fn kelem_of<'a>(ctx: &'a AtomArena<'a>, src: &str, n: usize) -> KElem {
let atom = ocas_parse::parse(ctx, src).expect("parse");
let x = ctx.var("x");
let rf = atom_to_rational_extended(atom, &[x], n).expect("rational");
KElem::new(rf.numerator, rf.denominator)
}
#[test]
fn coupled_distinct_rational_eigenvalues() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = ctx.var("x");
let tower = build_tower(&ctx, x, Symbol::new("x")).expect("tower");
let a = vec![vec![rat(0, 1), rat(1, 1)], vec![rat(2, 1), rat(1, 1)]];
let b = vec![KElem::zero(1), kelem_of(&ctx, "x", 1)];
let sol = solve_coupled_constant(&tower, &a, &b);
assert!(sol.is_some(), "the decoupled system must solve");
}
#[test]
fn coupled_declines_a_jordan_block() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = ctx.var("x");
let tower = build_tower(&ctx, x, Symbol::new("x")).expect("tower");
let a = vec![vec![rat(1, 1), rat(1, 1)], vec![rat(0, 1), rat(1, 1)]];
let b = vec![KElem::zero(1), kelem_of(&ctx, "x", 1)];
assert!(solve_coupled_constant(&tower, &a, &b).is_none());
}
#[test]
fn coupled_declines_irrational_eigenvalues() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = ctx.var("x");
let tower = build_tower(&ctx, x, Symbol::new("x")).expect("tower");
let a = vec![vec![rat(0, 1), rat(2, 1)], vec![rat(1, 1), rat(0, 1)]];
let b = vec![KElem::zero(1), kelem_of(&ctx, "x", 1)];
assert!(solve_coupled_constant(&tower, &a, &b).is_none());
}
}