use super::homotopy::C64;
use crate::poly::groebner::GbPoly;
use rug::Rational;
use std::f64::consts::PI;
fn c64_new(re: f64, im: f64) -> C64 {
C64 { re, im }
}
fn c64_ln(z: C64) -> C64 {
let r = (z.re * z.re + z.im * z.im).sqrt();
let theta = z.im.atan2(z.re);
c64_new(r.ln(), theta)
}
fn c64_exp(z: C64) -> C64 {
let r = z.re.exp();
c64_new(r * z.im.cos(), r * z.im.sin())
}
fn rat_to_f64(r: &Rational) -> f64 {
r.numer().to_f64() / r.denom().to_f64()
}
#[derive(Clone, Debug)]
pub struct NewtonPolytope {
pub support: Vec<Vec<u32>>,
pub n_vars: usize,
}
impl NewtonPolytope {
pub fn from_poly(p: &GbPoly) -> Self {
NewtonPolytope {
support: p.terms.keys().cloned().collect(),
n_vars: p.n_vars,
}
}
fn support_2d(&self, i: usize, j: usize) -> Vec<[i32; 2]> {
self.support
.iter()
.map(|exp| {
[
exp.get(i).copied().unwrap_or(0) as i32,
exp.get(j).copied().unwrap_or(0) as i32,
]
})
.collect()
}
}
fn cross2(o: [i32; 2], a: [i32; 2], b: [i32; 2]) -> i64 {
(a[0] - o[0]) as i64 * (b[1] - o[1]) as i64 - (a[1] - o[1]) as i64 * (b[0] - o[0]) as i64
}
fn convex_hull_2d(pts: &[[i32; 2]]) -> Vec<[i32; 2]> {
if pts.len() <= 1 {
return pts.to_vec();
}
let mut sorted = pts.to_vec();
sorted.sort_by(|a, b| a[0].cmp(&b[0]).then(a[1].cmp(&b[1])));
sorted.dedup();
if sorted.len() <= 2 {
return sorted;
}
let mut lower: Vec<[i32; 2]> = Vec::new();
for &p in &sorted {
while lower.len() >= 2 && cross2(lower[lower.len() - 2], lower[lower.len() - 1], p) <= 0 {
lower.pop();
}
lower.push(p);
}
let mut upper: Vec<[i32; 2]> = Vec::new();
for &p in sorted.iter().rev() {
while upper.len() >= 2 && cross2(upper[upper.len() - 2], upper[upper.len() - 1], p) <= 0 {
upper.pop();
}
upper.push(p);
}
lower.pop();
upper.pop();
lower.extend(upper);
lower
}
fn twice_area_2d(hull: &[[i32; 2]]) -> i64 {
let n = hull.len();
if n < 3 {
return 0;
}
let mut area2: i64 = 0;
for i in 0..n {
let j = (i + 1) % n;
area2 += hull[i][0] as i64 * hull[j][1] as i64;
area2 -= hull[j][0] as i64 * hull[i][1] as i64;
}
area2.abs()
}
fn minkowski_sum_2d(p: &[[i32; 2]], q: &[[i32; 2]]) -> Vec<[i32; 2]> {
if p.is_empty() || q.is_empty() {
return Vec::new();
}
let start_p = p
.iter()
.enumerate()
.min_by_key(|(_, v)| (v[1], v[0]))
.unwrap()
.0;
let start_q = q
.iter()
.enumerate()
.min_by_key(|(_, v)| (v[1], v[0]))
.unwrap()
.0;
let np = p.len();
let nq = q.len();
let mut result = Vec::new();
let mut i = 0usize;
let mut j = 0usize;
let mut pi = start_p;
let mut qj = start_q;
while i < np || j < nq {
result.push([p[pi][0] + q[qj][0], p[pi][1] + q[qj][1]]);
let pnext = (pi + 1) % np;
let qnext = (qj + 1) % nq;
let ep = [p[pnext][0] - p[pi][0], p[pnext][1] - p[pi][1]];
let eq = [q[qnext][0] - q[qj][0], q[qnext][1] - q[qj][1]];
let cr = ep[0] as i64 * eq[1] as i64 - ep[1] as i64 * eq[0] as i64;
if cr > 0 || j == nq {
pi = pnext;
i += 1;
} else if cr < 0 || i == np {
qj = qnext;
j += 1;
} else {
pi = pnext;
qj = qnext;
i += 1;
j += 1;
}
}
result
}
pub fn mixed_volume_2d(p1: &NewtonPolytope, p2: &NewtonPolytope) -> usize {
let s1 = p1.support_2d(0, 1);
let s2 = p2.support_2d(0, 1);
let h1 = convex_hull_2d(&s1);
let h2 = convex_hull_2d(&s2);
let sum = minkowski_sum_2d(&h1, &h2);
let sum_hull = convex_hull_2d(&sum);
let a_sum = twice_area_2d(&sum_hull);
let a1 = twice_area_2d(&h1);
let a2 = twice_area_2d(&h2);
let mv2 = a_sum - a1 - a2; (mv2.max(0) / 2) as usize
}
pub fn bezout_number(polys: &[GbPoly]) -> usize {
polys
.iter()
.map(|p| {
p.terms
.keys()
.map(|e| e.iter().sum::<u32>())
.max()
.unwrap_or(1) as usize
})
.fold(1usize, |a, d| a.saturating_mul(d))
}
#[derive(Clone, Debug)]
struct Edge2D {
a: [i32; 2], b: [i32; 2], ev: [i32; 2],
}
fn hull_edges(hull: &[[i32; 2]]) -> Vec<Edge2D> {
let n = hull.len();
(0..n)
.map(|i| {
let a = hull[i];
let b = hull[(i + 1) % n];
Edge2D {
a,
b,
ev: [b[0] - a[0], b[1] - a[1]],
}
})
.collect()
}
fn edges_parallel(e1: &Edge2D, e2: &Edge2D) -> bool {
let cr = e1.ev[0] as i64 * e2.ev[1] as i64 - e1.ev[1] as i64 * e2.ev[0] as i64;
cr == 0
}
fn coeff_at(p: &GbPoly, exp: &[i32]) -> f64 {
let key: Vec<u32> = exp.iter().map(|&x| x.max(0) as u32).collect();
p.terms.get(&key).map(rat_to_f64).unwrap_or(1.0)
}
fn solve_binomial_cell(
edge1: &Edge2D,
edge2: &Edge2D,
poly1: &GbPoly,
poly2: &GbPoly,
) -> Vec<[C64; 2]> {
let ca = coeff_at(poly1, &edge1.a);
let cb = coeff_at(poly1, &edge1.b);
let cp = coeff_at(poly2, &edge2.a);
let cq = coeff_at(poly2, &edge2.b);
let ca = if ca.abs() < 1e-14 { 1.0 } else { ca };
let cb = if cb.abs() < 1e-14 { 1.0 } else { cb };
let cp = if cp.abs() < 1e-14 { 1.0 } else { cp };
let cq = if cq.abs() < 1e-14 { 1.0 } else { cq };
let rho1 = c64_new(-cb / ca, 0.0);
let rho2 = c64_new(-cq / cp, 0.0);
let d = [
(edge1.a[0] - edge1.b[0]) as i64,
(edge1.a[1] - edge1.b[1]) as i64,
];
let e = [
(edge2.a[0] - edge2.b[0]) as i64,
(edge2.a[1] - edge2.b[1]) as i64,
];
let det = d[0] * e[1] - d[1] * e[0];
if det == 0 {
return Vec::new();
}
let n_sols = det.unsigned_abs() as usize;
let mut out = Vec::with_capacity(n_sols);
let log_rho1 = c64_ln(rho1);
let log_rho2 = c64_ln(rho2);
for k in 0..n_sols {
let branch_im = 2.0 * PI * k as f64;
let lr1 = c64_new(log_rho1.re, log_rho1.im + branch_im);
let lr2 = log_rho2;
let det_c = det as f64;
let u1 = c64_new(
(e[1] as f64 * lr1.re - d[1] as f64 * lr2.re) / det_c,
(e[1] as f64 * lr1.im - d[1] as f64 * lr2.im) / det_c,
);
let u2 = c64_new(
(d[0] as f64 * lr2.re - e[0] as f64 * lr1.re) / det_c,
(d[0] as f64 * lr2.im - e[0] as f64 * lr1.im) / det_c,
);
out.push([c64_exp(u1), c64_exp(u2)]);
}
out
}
fn binomial_polys_for_cell(
edge1: &Edge2D,
edge2: &Edge2D,
poly1: &GbPoly,
poly2: &GbPoly,
) -> [GbPoly; 2] {
let a1: Vec<u32> = edge1.a.iter().map(|&x| x.max(0) as u32).collect();
let b1: Vec<u32> = edge1.b.iter().map(|&x| x.max(0) as u32).collect();
let p2: Vec<u32> = edge2.a.iter().map(|&x| x.max(0) as u32).collect();
let q2: Vec<u32> = edge2.b.iter().map(|&x| x.max(0) as u32).collect();
let ca = poly1.terms.get(&a1).cloned().unwrap_or(Rational::from(1));
let cb = poly1.terms.get(&b1).cloned().unwrap_or(Rational::from(1));
let cp = poly2.terms.get(&p2).cloned().unwrap_or(Rational::from(1));
let cq = poly2.terms.get(&q2).cloned().unwrap_or(Rational::from(1));
let g1 = GbPoly::monomial(a1, ca).add(&GbPoly::monomial(b1, cb));
let g2 = GbPoly::monomial(p2, cp).add(&GbPoly::monomial(q2, cq));
[g1, g2]
}
#[cfg(test)]
pub(crate) fn polyhedral_starts_2d(poly1: &GbPoly, poly2: &GbPoly) -> (Vec<[C64; 2]>, usize) {
let np1 = NewtonPolytope::from_poly(poly1);
let np2 = NewtonPolytope::from_poly(poly2);
let s1 = np1.support_2d(0, 1);
let s2 = np2.support_2d(0, 1);
let h1 = convex_hull_2d(&s1);
let h2 = convex_hull_2d(&s2);
let edges1 = hull_edges(&h1);
let edges2 = hull_edges(&h2);
let mv = mixed_volume_2d(&np1, &np2);
let mut starts: Vec<[C64; 2]> = Vec::with_capacity(mv);
for e1 in &edges1 {
for e2 in &edges2 {
if edges_parallel(e1, e2) {
let sols = solve_binomial_cell(e1, e2, poly1, poly2);
starts.extend(sols);
}
}
}
(starts, mv)
}
pub(crate) fn polyhedral_cell_iter(
poly1: &GbPoly,
poly2: &GbPoly,
) -> Vec<(Vec<GbPoly>, Vec<Vec<C64>>)> {
let np1 = NewtonPolytope::from_poly(poly1);
let np2 = NewtonPolytope::from_poly(poly2);
let s1 = np1.support_2d(0, 1);
let s2 = np2.support_2d(0, 1);
let h1 = convex_hull_2d(&s1);
let h2 = convex_hull_2d(&s2);
let edges1 = hull_edges(&h1);
let edges2 = hull_edges(&h2);
let mut cells: Vec<(Vec<GbPoly>, Vec<Vec<C64>>)> = Vec::new();
for e1 in &edges1 {
for e2 in &edges2 {
if edges_parallel(e1, e2) {
let sols = solve_binomial_cell(e1, e2, poly1, poly2);
if !sols.is_empty() {
let gbpolys = binomial_polys_for_cell(e1, e2, poly1, poly2);
let starts: Vec<Vec<C64>> =
sols.into_iter().map(|s| vec![s[0], s[1]]).collect();
cells.push((gbpolys.to_vec(), starts));
}
}
}
}
cells
}
pub fn mixed_volume(polys: &[GbPoly]) -> Option<usize> {
if polys.len() != 2 {
return None;
}
let np1 = NewtonPolytope::from_poly(&polys[0]);
let np2 = NewtonPolytope::from_poly(&polys[1]);
if np1.n_vars != 2 || np2.n_vars != 2 {
return None;
}
Some(mixed_volume_2d(&np1, &np2))
}
pub fn should_use_polyhedral(polys: &[GbPoly]) -> bool {
if polys.len() != 2 {
return false;
}
let mv = match mixed_volume(polys) {
Some(v) => v,
None => return false,
};
let bez = bezout_number(polys);
mv > 0 && mv < bez
}
#[cfg(test)]
mod tests {
use super::*;
fn make_poly(terms: &[([u32; 2], i64)]) -> GbPoly {
use rug::Rational;
let mut p = GbPoly::zero(2);
for &(exp, coeff) in terms {
p = p.add(&GbPoly::monomial(
vec![exp[0], exp[1]],
Rational::from(coeff),
));
}
p
}
#[test]
fn convex_hull_triangle() {
let pts = [[0, 0], [1, 0], [0, 1]];
let hull = convex_hull_2d(&pts);
assert_eq!(twice_area_2d(&hull), 1, "area of unit right triangle = 1/2");
}
#[test]
fn minkowski_sum_unit_segments() {
let p = vec![[0, 0], [1, 0]];
let q = vec![[0, 0], [0, 1]];
let sum = minkowski_sum_2d(&p, &q);
let hull = convex_hull_2d(&sum);
assert_eq!(twice_area_2d(&hull), 2, "unit square area = 1");
}
#[test]
fn mixed_volume_line_segments() {
let p1 = NewtonPolytope {
support: vec![vec![0, 0], vec![2, 0]],
n_vars: 2,
};
let p2 = NewtonPolytope {
support: vec![vec![0, 0], vec![0, 2]],
n_vars: 2,
};
let mv = mixed_volume_2d(&p1, &p2);
assert!(mv >= 1, "MV should be positive: got {mv}");
}
#[test]
fn bezout_vs_mv_for_linear_system() {
let p1 = make_poly(&[([1, 0], 1), ([0, 1], 1), ([0, 0], -1)]);
let p2 = make_poly(&[([1, 0], 1), ([0, 1], -1)]);
let bez = bezout_number(&[p1.clone(), p2.clone()]);
let mv = mixed_volume(&[p1, p2]).unwrap();
assert_eq!(bez, 1);
assert_eq!(mv, 1);
}
#[test]
fn polyhedral_starts_smoke() {
let p1 = make_poly(&[([2, 0], 1), ([0, 0], -1)]);
let p2 = make_poly(&[([0, 2], 1), ([0, 0], -1)]);
let (starts, mv) = polyhedral_starts_2d(&p1, &p2);
assert!(mv >= 1, "MV ≥ 1 for non-trivial system");
assert!(!starts.is_empty(), "should have at least one start point");
}
}