1use std::fmt::Display;
8
9use ocas_domain::EuclideanDomain;
10
11use crate::dense::DenseUnivariatePolynomial;
12
13#[derive(Debug, Clone, PartialEq)]
15pub struct RootInterval {
16 pub low: f64,
18 pub high: f64,
20}
21
22impl<D: EuclideanDomain> DenseUnivariatePolynomial<D>
23where
24 D::Element: Display,
25{
26 pub fn sturm_sequence(&self) -> Vec<Self> {
31 let mut seq = Vec::new();
32 if self.is_zero() {
33 return seq;
34 }
35
36 seq.push(self.clone());
37 let deriv = self.derivative();
38 if deriv.is_zero() {
39 return seq;
40 }
41 seq.push(deriv);
42
43 loop {
44 let a = &seq[seq.len() - 2];
45 let b = &seq[seq.len() - 1];
46 if b.is_zero() {
47 break;
48 }
49 let rem = match a.pseudo_remainder(b) {
51 Some(r) => r,
52 None => break,
53 };
54 if rem.is_zero() {
55 break;
56 }
57 seq.push(rem.neg());
59 }
60
61 seq
62 }
63
64 pub fn eval_f64(&self, x: f64) -> f64 {
69 let mut result = 0.0;
70 for coeff in self.coeffs().iter().rev() {
71 result = result * x + coeff_value(coeff);
72 }
73 result
74 }
75
76 pub fn count_real_roots(&self) -> usize {
80 let seq = self.sturm_sequence();
81 if seq.len() < 2 {
82 return 0;
83 }
84 let neg_inf = count_sign_changes_at_infinity(&seq, true);
85 let pos_inf = count_sign_changes_at_infinity(&seq, false);
86 neg_inf.saturating_sub(pos_inf)
87 }
88
89 pub fn isolate_real_roots(&self) -> Vec<RootInterval> {
94 let seq = self.sturm_sequence();
95 if seq.len() < 2 {
96 return vec![];
97 }
98
99 let total_roots = self.count_real_roots();
100 if total_roots == 0 {
101 return vec![];
102 }
103
104 let m = root_bound(self);
106 let mut intervals = Vec::new();
107 let mut stack = vec![(-m, m)];
108
109 while let Some((lo, hi)) = stack.pop() {
110 if intervals.len() >= total_roots {
111 break;
112 }
113
114 let lo_signs = count_sign_changes(&seq, lo);
115 let hi_signs = count_sign_changes(&seq, hi);
116 let count = lo_signs.saturating_sub(hi_signs);
117
118 if count == 0 {
119 continue;
120 }
121 if count == 1 && (hi - lo) < 1e-10 {
122 intervals.push(RootInterval { low: lo, high: hi });
123 continue;
124 }
125 if hi - lo < 1e-12 {
126 if count == 1 {
127 intervals.push(RootInterval { low: lo, high: hi });
128 }
129 continue;
130 }
131
132 let mid = (lo + hi) / 2.0;
133 stack.push((lo, mid));
134 stack.push((mid, hi));
135 }
136
137 intervals
138 }
139
140 pub fn refine_root(&self, interval: &RootInterval, tol: f64) -> RootInterval {
142 let mut lo = interval.low;
143 let mut hi = interval.high;
144 let f_lo = self.eval_f64(lo);
145
146 if f_lo.abs() < 1e-15 {
147 return RootInterval { low: lo, high: lo };
148 }
149
150 while hi - lo > tol {
151 let mid = (lo + hi) / 2.0;
152 let f_mid = self.eval_f64(mid);
153 if f_mid.abs() < 1e-15 {
154 return RootInterval {
155 low: mid,
156 high: mid,
157 };
158 }
159 if f_lo * f_mid < 0.0 {
160 hi = mid;
161 } else {
162 lo = mid;
163 }
164 }
165
166 RootInterval { low: lo, high: hi }
167 }
168}
169
170fn count_sign_changes_at_infinity<D: EuclideanDomain>(
172 seq: &[DenseUnivariatePolynomial<D>],
173 at_neg_inf: bool,
174) -> usize
175where
176 D::Element: Display,
177{
178 let vals: Vec<f64> = seq
179 .iter()
180 .map(|p| {
181 if p.is_zero() {
182 return 0.0;
183 }
184 let deg = p.degree().unwrap_or(0);
185 let lc = coeff_value(p.leading_coeff().unwrap());
186 if at_neg_inf {
189 if deg % 2 == 0 { lc } else { -lc }
190 } else {
191 lc
192 }
193 })
194 .collect();
195 count_sign_changes_in_vals(&vals)
196}
197
198fn count_sign_changes<D: EuclideanDomain>(seq: &[DenseUnivariatePolynomial<D>], x: f64) -> usize
200where
201 D::Element: Display,
202{
203 let vals: Vec<f64> = seq.iter().map(|p| p.eval_f64(x)).collect();
204 count_sign_changes_in_vals(&vals)
205}
206
207fn count_sign_changes_in_vals(vals: &[f64]) -> usize {
208 let mut count = 0;
209 let mut prev_sign: Option<bool> = None;
210 for &v in vals {
211 if v == 0.0 {
212 continue;
213 }
214 let sign = v > 0.0;
215 if let Some(p) = prev_sign
216 && p != sign
217 {
218 count += 1;
219 }
220 prev_sign = Some(sign);
221 }
222 count
223}
224
225fn root_bound<D: EuclideanDomain>(p: &DenseUnivariatePolynomial<D>) -> f64
227where
228 D::Element: Display,
229{
230 if p.is_zero() || p.degree().is_none() {
231 return 1.0;
232 }
233 let coeffs = p.coeffs();
234 let lc = coeff_value(coeffs.last().unwrap()).abs();
235 let mut max_abs = 0.0f64;
236 for c in &coeffs[..coeffs.len() - 1] {
237 let v = coeff_value(c).abs();
238 if v > max_abs {
239 max_abs = v;
240 }
241 }
242 1.0 + max_abs / lc.max(1e-10)
243}
244
245fn coeff_value(elem: &(impl Display + ?Sized)) -> f64 {
247 let s = elem.to_string();
248 s.trim()
249 .parse::<f64>()
250 .unwrap_or_else(|_| s.trim().parse::<i64>().map(|v| v as f64).unwrap_or(0.0))
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256 use ocas_domain::{Integer, IntegerDomain};
257
258 fn i(n: i64) -> Integer {
259 Integer::from(n)
260 }
261
262 #[test]
263 fn count_roots_x2_minus_1() {
264 let d = IntegerDomain;
265 let p = DenseUnivariatePolynomial::from_coeffs(d, vec![i(-1), i(0), i(1)]);
266 assert_eq!(p.count_real_roots(), 2);
267 }
268
269 #[test]
270 fn count_roots_x2_plus_1() {
271 let d = IntegerDomain;
272 let p = DenseUnivariatePolynomial::from_coeffs(d, vec![i(1), i(0), i(1)]);
273 assert_eq!(p.count_real_roots(), 0);
274 }
275
276 #[test]
277 fn count_roots_perfect_square() {
278 let d = IntegerDomain;
279 let p = DenseUnivariatePolynomial::from_coeffs(d, vec![i(1), i(2), i(1)]);
281 assert_eq!(p.count_real_roots(), 1);
282 }
283
284 #[test]
285 fn isolate_roots_x2_minus_2() {
286 let d = IntegerDomain;
287 let p = DenseUnivariatePolynomial::from_coeffs(d, vec![i(-2), i(0), i(1)]);
288 let intervals = p.isolate_real_roots();
289 assert_eq!(intervals.len(), 2);
290 let refined = p.refine_root(&intervals[1], 1e-6);
292 let approx = (refined.low + refined.high) / 2.0;
293 assert!((approx.abs() - std::f64::consts::SQRT_2).abs() < 0.01);
294 }
295
296 #[test]
297 fn sturm_sequence_length() {
298 let d = IntegerDomain;
299 let p = DenseUnivariatePolynomial::from_coeffs(d, vec![i(-1), i(0), i(1)]);
300 let seq = p.sturm_sequence();
301 assert!(seq.len() >= 2);
302 }
303}