1use std::cmp::Ordering;
2
3use oorandom::Rand64;
4
5use crate::MAX_PROBABILISTIC_REPETITIONS;
6use crate::algorithms::poly_gcd::squarefree_part::poly_squarefree_part_local;
7use crate::computation::DontObserve;
8use crate::divisibility::{DivisibilityRing, DivisibilityRingStore};
9use crate::field::FieldStore;
10use crate::homomorphism::*;
11use crate::integer::*;
12use crate::ring::*;
13use crate::rings::float_complex::{Complex64, Complex64Base, Complex64El};
14use crate::rings::poly::dense_poly::*;
15use crate::rings::poly::*;
16
17const NEWTON_MAX_SCALE: u32 = 10;
18const NEWTON_ITERATIONS: usize = 16;
19const ASSUME_RADIUS_TO_APPROX_RADIUS_FACTOR: f64 = 2.0;
20
21#[derive(Debug)]
22#[stability::unstable(feature = "enable")]
23pub struct PrecisionError;
24
25#[stability::unstable(feature = "enable")]
26pub fn absolute_error_of_poly_eval<P>(
27 poly_ring: P,
28 f: &El<P>,
29 poly_deg: usize,
30 point: Complex64El,
31 relative_error_point: f64,
32) -> f64
33where
34 P: RingStore,
35 P::Type: PolyRing + DivisibilityRing,
36 <P::Type as RingExtension>::BaseRing: RingStore<Type = Complex64Base>,
37{
38 let CC = Complex64::RING;
39 let mut current = point;
40 let mut current_relative_error = relative_error_point;
41 let mut total_error = 0.0;
42 for i in 1..=poly_deg {
43 total_error += CC.abs(*poly_ring.coefficient_at(f, i)) * CC.abs(current) * current_relative_error;
44 current_relative_error += f64::EPSILON;
47 CC.mul_assign(&mut current, point);
48 }
49 return total_error;
50}
51
52fn bound_distance_to_root<P>(
53 approx_root: Complex64El,
54 CCX: P,
55 poly: &El<P>,
56 poly_deg: usize,
57) -> Result<f64, PrecisionError>
58where
59 P: RingStore,
60 P::Type: PolyRing + DivisibilityRing,
61 <P::Type as RingExtension>::BaseRing: RingStore<Type = Complex64Base>,
62{
63 let CC = Complex64::RING;
64 let f = poly;
65 let f_prime = derive_poly(&CCX, f);
66
67 let approx_radius = (CC.abs(CCX.evaluate(f, &approx_root, CC.identity()))
68 + absolute_error_of_poly_eval(&CCX, f, poly_deg, approx_root, 0.0))
69 / CC.abs(CCX.evaluate(&f_prime, &approx_root, CC.identity()));
70 if !approx_radius.is_finite() {
71 return Err(PrecisionError);
72 }
73
74 let assume_radius = approx_radius * ASSUME_RADIUS_TO_APPROX_RADIUS_FACTOR;
75 let mut abs_taylor_series_coeffs = Vec::new();
78 let mut current = derive_poly(&CCX, &f_prime);
79 for i in 0..poly_deg.saturating_sub(1) {
80 CCX.inclusion()
81 .mul_assign_map(&mut current, CC.from_f64(1.0 / (i as f64 + 1.0)));
82 abs_taylor_series_coeffs.push(CC.abs(CCX.evaluate(¤t, &approx_root, CC.identity())));
83 current = derive_poly(&CCX, ¤t);
84 }
85 let f_prime_bound = abs_taylor_series_coeffs
86 .iter()
87 .enumerate()
88 .map(|(i, c)| c * assume_radius.powi(i as i32 + 1))
89 .sum::<f64>();
90
91 if f_prime_bound
97 > CC.abs(CCX.evaluate(&f_prime, &approx_root, CC.identity())) * (ASSUME_RADIUS_TO_APPROX_RADIUS_FACTOR - 1.0)
98 / ASSUME_RADIUS_TO_APPROX_RADIUS_FACTOR
99 {
100 return Err(PrecisionError);
101 }
102
103 return Ok(assume_radius);
104}
105
106fn newton_with_initial<P>(
107 poly_ring: P,
108 f: &El<P>,
109 poly_deg: usize,
110 initial: El<Complex64>,
111) -> Result<(El<Complex64>, f64), PrecisionError>
112where
113 P: RingStore,
114 P::Type: PolyRing + DivisibilityRing,
115 <P::Type as RingExtension>::BaseRing: RingStore<Type = Complex64Base>,
116{
117 let CC = Complex64::RING;
118 let f_prime = derive_poly(&poly_ring, f);
119 let mut current = initial;
120 for _ in 0..NEWTON_ITERATIONS {
121 current = CC.sub(
122 current,
123 CC.div(
124 &poly_ring.evaluate(f, ¤t, CC.identity()),
125 &poly_ring.evaluate(&f_prime, ¤t, CC.identity()),
126 ),
127 );
128 }
129 return Ok((current, bound_distance_to_root(current, poly_ring, f, poly_deg)?));
130}
131
132fn find_approximate_complex_root_squarefree<P>(
133 poly_ring: P,
134 f: &El<P>,
135 poly_deg: usize,
136) -> Result<(El<Complex64>, f64), PrecisionError>
137where
138 P: RingStore,
139 P::Type: PolyRing + DivisibilityRing,
140 <P::Type as RingExtension>::BaseRing: RingStore<Type = Complex64Base>,
141{
142 let mut rng = Rand64::new(1);
143 let CC = Complex64::RING;
144 assert!(
145 poly_ring
146 .terms(f)
147 .all(|(c, _): (&Complex64El, _)| CC.re(*c).is_finite() && CC.im(*c).is_finite())
148 );
149 let f_prime = derive_poly(&poly_ring, f);
150
151 let (approx_root, approx_radius) = (0..MAX_PROBABILISTIC_REPETITIONS)
152 .map(|_| {
153 let starting_point_unscaled = CC.add(
154 CC.from_f64((rng.rand_u64() as i64) as f64 / i64::MAX as f64),
156 CC.mul(
157 Complex64::I,
158 CC.from_f64((rng.rand_u64() as i64) as f64 / i64::MAX as f64),
159 ),
160 );
161 let scale = (rng.rand_u64() % (2 * NEWTON_MAX_SCALE as u64)) as i32 - NEWTON_MAX_SCALE as i32;
162 let starting_point = CC.mul(starting_point_unscaled, CC.from_f64(2.0f64.powi(scale)));
163
164 let mut current = starting_point;
165 for _ in 0..NEWTON_ITERATIONS {
166 current = CC.sub(
167 current,
168 CC.div(
169 &poly_ring.evaluate(f, ¤t, CC.identity()),
170 &poly_ring.evaluate(&f_prime, ¤t, CC.identity()),
171 ),
172 );
173 }
174
175 let approx_radius = CC.abs(poly_ring.evaluate(f, ¤t, CC.identity()))
177 / CC.abs(poly_ring.evaluate(&f_prime, ¤t, CC.identity()));
178
179 return (current, approx_radius);
180 })
181 .min_by(|(_, r1), (_, r2)| match (r1.is_finite(), r2.is_finite()) {
182 (false, false) => Ordering::Equal,
183 (true, false) => Ordering::Less,
184 (false, true) => Ordering::Greater,
185 (true, true) => f64::total_cmp(r1, r2),
186 })
187 .unwrap();
188 if !approx_radius.is_finite() || approx_radius < 0.0 {
189 return Err(PrecisionError);
190 }
191
192 return Ok((
193 approx_root,
194 bound_distance_to_root(approx_root, poly_ring, f, poly_deg)?,
195 ));
196}
197
198#[stability::unstable(feature = "enable")]
206pub fn find_approximate_complex_root<P>(ZZX: P, f: &El<P>) -> Result<(El<Complex64>, f64), PrecisionError>
207where
208 P: RingStore,
209 P::Type: PolyRing + DivisibilityRing,
210 <<P::Type as RingExtension>::BaseRing as RingStore>::Type: IntegerRing,
211{
212 assert!(ZZX.degree(f).unwrap_or(0) > 0);
213 let CC = Complex64::RING;
214 assert!(
215 ZZX.checked_div(&poly_squarefree_part_local(&ZZX, ZZX.clone_el(f), DontObserve), f)
216 .is_some(),
217 "polynomial must be square-free"
218 );
219 let CCX = DensePolyRing::new(CC, "X");
220 return find_approximate_complex_root_squarefree(
221 &CCX,
222 &CCX.lifted_hom(&ZZX, CC.can_hom(ZZX.base_ring()).unwrap()).map_ref(f),
223 ZZX.degree(f).unwrap(),
224 );
225}
226
227#[stability::unstable(feature = "enable")]
238pub fn find_all_approximate_complex_roots<P>(ZZX: P, poly: &El<P>) -> Result<Vec<(El<Complex64>, f64)>, PrecisionError>
239where
240 P: RingStore,
241 P::Type: PolyRing + DivisibilityRing,
242 <<P::Type as RingExtension>::BaseRing as RingStore>::Type: IntegerRing,
243{
244 assert!(ZZX.degree(poly).unwrap_or(0) > 0);
245 let CC = Complex64::RING;
246 let ZZ_to_CC = CC.can_hom(ZZX.base_ring()).unwrap();
247 assert!(
248 ZZX.checked_div(&poly_squarefree_part_local(&ZZX, ZZX.clone_el(poly), DontObserve), poly)
249 .is_some(),
250 "polynomial must be square-free"
251 );
252 let CCX = DensePolyRing::new(CC, "X");
253 let ZZX_to_CCX = CCX.lifted_hom(&ZZX, ZZ_to_CC);
254
255 let d = ZZX.degree(poly).unwrap();
256 let f = ZZX_to_CCX.map_ref(poly);
257 let mut remaining_poly = CCX.clone_el(&f);
258 let mut result = Vec::new();
259 for i in 0..ZZX.degree(poly).unwrap() {
260 let (next_root_initial, _) = find_approximate_complex_root_squarefree(&CCX, &remaining_poly, d - i)?;
261 let (next_root, distance) = newton_with_initial(&CCX, &f, d, next_root_initial)?;
262 if result
263 .iter()
264 .any(|(prev_root, prev_distance)| CC.abs(CC.sub(*prev_root, next_root)) <= distance + prev_distance)
265 {
266 return Err(PrecisionError);
267 }
268 result.push((next_root, distance));
269 let mut new_remaining_poly = CCX.zero();
270 for j in (1..=(d - i)).rev() {
271 let lc = *CCX.coefficient_at(&remaining_poly, j);
272 CCX.get_ring()
273 .add_assign_from_terms(&mut new_remaining_poly, [(lc, j - 1)]);
274 CCX.get_ring()
275 .add_assign_from_terms(&mut remaining_poly, [(CC.mul(lc, next_root), j - 1)]);
276 }
277 remaining_poly = new_remaining_poly;
278 }
279 result.sort_unstable_by(|(l, _), (r, _)| {
281 f64::total_cmp(&(CC.re(*l) + CC.im(*l) * 0.000001), &(CC.re(*r) + CC.im(*r) * 0.000001))
282 });
283 return Ok(result);
284}
285
286#[cfg(test)]
287use std::f64::consts::PI;
288
289#[cfg(test)]
290use crate::algorithms::cyclotomic::cyclotomic_polynomial;
291#[cfg(test)]
292use crate::algorithms::eea::signed_gcd;
293#[cfg(test)]
294use crate::primitive_int::StaticRing;
295
296#[test]
297fn test_find_approximate_complex_root() {
298 let ZZ = BigIntRing::RING;
299 let ZZX = DensePolyRing::new(&ZZ, "X");
300 let CC = Complex64::RING;
301
302 let [f] = ZZX.with_wrapped_indeterminate(|X| [X.pow_ref(2) + 1]);
303 let (root, radius) = find_approximate_complex_root(&ZZX, &f).unwrap();
304 assert!(radius <= 0.000000001);
305 assert!(CC.abs(CC.sub(Complex64::I, root)) <= radius || CC.abs(CC.add(Complex64::I, root)) <= radius);
306
307 let [f] = ZZX.with_wrapped_indeterminate(|X| [100000000 * X.pow_ref(2) + 1]);
308 let (root, radius) = find_approximate_complex_root(&ZZX, &f).unwrap();
309 assert!(radius <= 0.000000001);
310 assert!(
311 CC.abs(CC.sub(CC.mul(Complex64::I, CC.from_f64(0.0001)), root)) <= radius
312 || CC.abs(CC.add(CC.mul(Complex64::I, CC.from_f64(0.0001)), root)) <= radius
313 );
314
315 let f = cyclotomic_polynomial(&ZZX, 105);
316 let (root, radius) = find_approximate_complex_root(&ZZX, &f).unwrap();
317 assert!(radius <= 0.000000001);
318 let root_of_unity = |k, n| CC.exp(CC.mul(CC.from_f64(2.0 * PI * k as f64 / n as f64), Complex64::I));
319 assert!(
320 (0..105)
321 .filter(|k| signed_gcd(*k, 105, StaticRing::<i64>::RING) == 1)
322 .any(|k| CC.abs(CC.sub(root_of_unity(k, 105), root)) <= radius)
323 );
324
325 let [f] = ZZX.with_wrapped_indeterminate(|X| [X.pow_ref(4) - 6 * X.pow_ref(2) - 11]);
326 let (root, radius) = find_approximate_complex_root(&ZZX, &f).unwrap();
327 assert!(radius <= 0.000000001);
328 let expected = [
329 CC.from_f64(-2.7335207983477241859817441249894389670263693372196541057936155022),
330 CC.from_f64(2.7335207983477241859817441249894389670263693372196541057936155022),
331 CC.mul(
332 CC.from_f64(-1.2133160985495821701841076107103126272177604441592580488679327999),
333 Complex64::I,
334 ),
335 CC.mul(
336 CC.from_f64(1.2133160985495821701841076107103126272177604441592580488679327999),
337 Complex64::I,
338 ),
339 ];
340 assert!(expected.iter().any(|x| CC.abs(CC.sub(*x, root)) <= radius));
341}
342
343#[test]
344fn test_find_all_approximate_complex_roots() {
345 let ZZ = BigIntRing::RING;
346 let ZZX = DensePolyRing::new(&ZZ, "X");
347 let CC = Complex64::RING;
348
349 let [f] = ZZX.with_wrapped_indeterminate(|X| [X.pow_ref(3) - 7 * X + 100]);
350 let expected = [
351 CC.from_f64(-5.1425347350362689414102462079341122827700721701123021518405977272),
352 CC.add(
353 CC.from_f64(2.5712673675181344707051231039670561413850360850561510759202988636),
354 CC.mul(
355 CC.from_f64(-3.5824918179656616775077885147170618458138064112538190024361795659),
356 Complex64::I,
357 ),
358 ),
359 CC.add(
360 CC.from_f64(2.5712673675181344707051231039670561413850360850561510759202988636),
361 CC.mul(
362 CC.from_f64(3.5824918179656616775077885147170618458138064112538190024361795659),
363 Complex64::I,
364 ),
365 ),
366 ];
367 let actual = find_all_approximate_complex_roots(&ZZX, &f).unwrap();
368 for (expected, (actual, dist)) in expected.iter().copied().zip(actual.iter().copied()) {
369 assert!(dist < 0.000000001);
370 assert!(CC.abs(CC.sub(actual, expected)) <= dist);
371 }
372
373 let root_of_unity = |k, n| CC.exp(CC.mul(CC.from_f64(2.0 * PI * k as f64 / n as f64), Complex64::I));
374 let f = cyclotomic_polynomial(&ZZX, 105);
375 let expected = (1..=52)
376 .rev()
377 .filter(|i| signed_gcd(*i, 105, StaticRing::<i64>::RING) == 1)
378 .flat_map(|i| [root_of_unity(105 - i, 105), root_of_unity(i, 105)])
379 .chain([CC.one()]);
380 let actual = find_all_approximate_complex_roots(&ZZX, &f).unwrap();
381 assert_eq!(48, actual.len());
382 for (expected, (actual, dist)) in expected.zip(actual.iter().copied()) {
383 assert!(dist < 0.000000001);
384 assert!(CC.abs(CC.sub(actual, expected)) <= dist);
385 }
386}