use std::collections::{BTreeMap, BTreeSet};
use rug::{Integer, Rational};
use crate::kernel::{ExprId, ExprPool};
use crate::poly::multipoly::MultiPoly;
use crate::poly::rational::{mpoly_exact_div, RationalFunction};
use super::profile::{self, Phase};
use super::ring::{self, is_unit, lcm, GenKind, NormanRing};
use super::solve;
use super::DeclineReason;
const MAX_UNKNOWNS: usize = 240;
const MAX_EQUATIONS: usize = 6000;
const MAX_DENOM_TERMS: usize = 2000;
struct Atom {
kind: AtomKind,
num: MultiPoly,
den: Den,
}
enum Den {
Rational,
Log(MultiPoly),
}
enum AtomKind {
Rational(Vec<u32>),
Log(MultiPoly),
}
pub(super) fn solve(
ring: &NormanRing,
f: &RationalFunction,
pool: &ExprPool,
) -> Result<ExprId, DeclineReason> {
let q = f.denom.clone();
let n = f.numer.clone();
let (monomials, logs) = profile::timed(Phase::AnsatzSetup, || {
let nv = ring.nvars();
let mut bounds = Vec::with_capacity(nv);
let mut count: usize = 1;
for i in 0..nv {
let b = (n.degree_in(i) + q.degree_in(i) + 1) as usize;
count = count.saturating_mul(b + 1);
if count > MAX_UNKNOWNS {
return Err(DeclineReason::TooLarge("ansatz monomial box"));
}
bounds.push(b);
}
let monomials = monomial_box(&bounds);
let logs = log_candidates(ring, &q)?;
if monomials.len() + logs.len() > MAX_UNKNOWNS {
return Err(DeclineReason::TooLarge("ansatz size"));
}
if monomials.is_empty() && logs.is_empty() {
return Err(DeclineReason::NoSolution);
}
Ok((monomials, logs))
})?;
let (atoms, rat_den) = profile::timed(Phase::AtomDerivs, || {
let q2 = q.clone() * q.clone();
let rat_den = ring.dden.clone() * q2;
let nq = ring.deriv_scaled(&q);
let nv = ring.nvars();
let dnq: Vec<MultiPoly> = (0..nv).map(|i| ring.dnum[i].clone() * q.clone()).collect();
let mut atoms: Vec<Atom> = Vec::with_capacity(monomials.len() + logs.len());
for m in &monomials {
let mut num = MultiPoly::zero(ring.vars.clone());
for (i, dq) in dnq.iter().enumerate() {
let e = m.get(i).copied().unwrap_or(0);
if e == 0 {
continue;
}
let mut shift = m.clone();
shift[i] = e - 1;
num = num + ring::shift_scale(dq, &shift, &Integer::from(e));
}
num = num - ring::shift_scale(&nq, m, &Integer::from(1));
atoms.push(Atom {
kind: AtomKind::Rational(m.clone()),
num,
den: Den::Rational,
});
}
for p in &logs {
atoms.push(Atom {
kind: AtomKind::Log(p.clone()),
num: ring.deriv_scaled(p),
den: Den::Log(ring.dden.clone() * p.clone()),
});
}
(atoms, rat_den)
});
let (columns, rhs_poly) = profile::timed(Phase::ClearDenoms, || {
let mut dens: Vec<&MultiPoly> = vec![&rat_den];
for a in &atoms {
if let Den::Log(d) = &a.den {
dens.push(d);
}
}
dens.push(&f.denom);
let mut common = rat_den.clone();
let mut scales: Option<Vec<MultiPoly>> = None;
for _ in 0..=dens.len() {
let mut built: Vec<MultiPoly> = Vec::with_capacity(dens.len());
let mut grew = false;
for d in &dens {
match mpoly_exact_div(&common, d) {
Some(s) => built.push(s),
None => {
common = lcm(&common, d).ok_or(DeclineReason::RingArithmetic)?;
if common.terms.len() > MAX_DENOM_TERMS {
return Err(DeclineReason::TooLarge("common denominator"));
}
grew = true;
break;
}
}
}
if !grew {
scales = Some(built);
break;
}
}
let scales = scales.ok_or(DeclineReason::RingArithmetic)?;
profile::record(|s| s.denom_terms = common.terms.len());
let rhs_scale = scales.last().expect("f.denom was pushed");
let mut columns: Vec<MultiPoly> = Vec::with_capacity(atoms.len());
let mut next_log = 1usize;
for a in &atoms {
let scale = match &a.den {
Den::Rational => &scales[0],
Den::Log(_) => {
let s = &scales[next_log];
next_log += 1;
s
}
};
columns.push(if is_unit(scale) {
a.num.clone()
} else {
a.num.clone() * scale.clone()
});
}
let rhs_poly = if is_unit(rhs_scale) {
f.numer.clone()
} else {
f.numer.clone() * rhs_scale.clone()
};
Ok((columns, rhs_poly))
})?;
let system = profile::timed(Phase::MatrixBuild, || {
let mut keys: BTreeSet<&Vec<u32>> = BTreeSet::new();
for c in &columns {
keys.extend(c.terms.keys());
}
keys.extend(rhs_poly.terms.keys());
profile::record(|s| {
s.equations = keys.len();
s.unknowns = columns.len();
});
if keys.len() > MAX_EQUATIONS {
return Err(DeclineReason::TooLarge("linear system"));
}
let index: BTreeMap<&Vec<u32>, usize> =
keys.iter().enumerate().map(|(i, k)| (*k, i)).collect();
let mut cells: Vec<Vec<(usize, Integer)>> = vec![Vec::new(); keys.len()];
for (j, c) in columns.iter().enumerate() {
for (k, v) in &c.terms {
let row = *index.get(k).expect("every column key is in `keys`");
cells[row].push((j, v.clone()));
}
}
let mut sys = solve::SparseSystem::new(columns.len());
for (i, k) in keys.iter().enumerate() {
let rhs = rhs_poly
.terms
.get(*k)
.cloned()
.unwrap_or_else(|| Integer::from(0));
sys.push_row(std::mem::take(&mut cells[i]), rhs);
}
Ok(sys)
})?;
let sol = match profile::timed(Phase::Solve, || system.solve()) {
solve::SolveOutcome::Solved(sol) => sol,
solve::SolveOutcome::Inconsistent => return Err(DeclineReason::NoSolution),
solve::SolveOutcome::GaveUp => return Err(DeclineReason::LinearSolver),
};
if sol.len() != atoms.len() {
return Err(DeclineReason::NoSolution);
}
profile::timed(Phase::Rebuild, || {
Ok(build_expression(ring, &atoms, &sol, &q, pool))
})
}
fn monomial_box(bounds: &[usize]) -> Vec<Vec<u32>> {
let mut out: Vec<Vec<u32>> = vec![Vec::new()];
for &b in bounds {
let mut next = Vec::with_capacity(out.len() * (b + 1));
for base in &out {
for e in 0..=b {
let mut v = base.clone();
v.push(e as u32);
next.push(v);
}
}
out = next;
}
for v in out.iter_mut() {
while v.last() == Some(&0) {
v.pop();
}
}
out.sort();
out.dedup();
out
}
fn log_candidates(ring: &NormanRing, q: &MultiPoly) -> Result<Vec<MultiPoly>, DeclineReason> {
let mut out: Vec<MultiPoly> = Vec::new();
let push = |p: MultiPoly, out: &mut Vec<MultiPoly>| {
if p.total_degree() == 0 {
return;
}
let neg = -p.clone();
if !out.iter().any(|g| *g == p || *g == neg) {
out.push(p);
}
};
let (_unit, factors) = q
.factor_irreducible()
.ok_or(DeclineReason::RingArithmetic)?;
for (f, _m) in factors {
if is_exp_monomial(ring, &f) {
continue;
}
push(f, &mut out);
}
push(ring.monomial(&[1]), &mut out);
for (i, k) in ring.kinds.iter().enumerate() {
if *k == GenKind::Log {
let idx = i + 1;
let mut e = vec![0u32; idx + 1];
e[idx] = 1;
push(ring.monomial(&e), &mut out);
}
}
Ok(out)
}
fn is_exp_monomial(ring: &NormanRing, p: &MultiPoly) -> bool {
if p.terms.len() != 1 {
return false;
}
let (exp, _) = p.terms.iter().next().expect("exactly one term");
let mut saw = false;
for (i, &e) in exp.iter().enumerate() {
if e == 0 {
continue;
}
if i == 0 || ring.kinds.get(i - 1) != Some(&GenKind::Exp) {
return false;
}
saw = true;
}
saw
}
fn scale(c: &Rational, e: ExprId, pool: &ExprPool) -> ExprId {
if *c == 1 {
return e;
}
let coeff = if *c.denom() == 1 {
pool.integer(c.numer().clone())
} else {
pool.rational(c.numer().clone(), c.denom().clone())
};
pool.mul(vec![coeff, e])
}
fn build_expression(
ring: &NormanRing,
atoms: &[Atom],
sol: &[Rational],
q: &MultiPoly,
pool: &ExprPool,
) -> ExprId {
let mut log_terms: Vec<ExprId> = Vec::new();
let mut lcm_den = rug::Integer::from(1);
for (a, c) in atoms.iter().zip(sol.iter()) {
if matches!(a.kind, AtomKind::Rational(_)) && *c != 0 {
lcm_den = lcm_den.lcm(c.denom());
}
}
let mut p_int = MultiPoly::zero(ring.vars.clone());
for (a, c) in atoms.iter().zip(sol.iter()) {
if *c == 0 {
continue;
}
match &a.kind {
AtomKind::Rational(exp) => {
let scaled = c.clone() * Rational::from(lcm_den.clone());
debug_assert_eq!(*scaled.denom(), 1);
let mut term = ring.monomial(exp);
for v in term.terms.values_mut() {
*v *= scaled.numer().clone();
}
p_int = p_int + term;
}
AtomKind::Log(p) => {
let arg = p.to_expr(pool);
let l = pool.func("log", vec![arg]);
log_terms.push(scale(c, l, pool));
}
}
}
let mut parts: Vec<ExprId> = Vec::new();
if !p_int.is_zero() {
let reduced = RationalFunction::new(p_int, q.clone());
let (n, d) = match reduced {
Ok(rf) => (rf.numer, rf.denom),
Err(_) => (MultiPoly::zero(ring.vars.clone()), ring.constant_poly(1)),
};
let n_expr = n.to_expr(pool);
let body = if is_unit(&d) {
n_expr
} else {
let inv = pool.pow(d.to_expr(pool), pool.integer(-1_i32));
pool.mul(vec![n_expr, inv])
};
let s = Rational::from((rug::Integer::from(1), lcm_den));
parts.push(scale(&s, body, pool));
}
parts.extend(log_terms);
let raw = match parts.len() {
0 => pool.integer(0_i32),
1 => parts[0],
_ => pool.add(parts),
};
crate::simplify::engine::simplify(raw, pool).value
}