1use std::cmp::Ordering;
2
3use oorandom::Rand64;
4
5use crate::algorithms::poly_gcd::squarefree_part::poly_squarefree_part_local;
6use crate::computation::DontObserve;
7use crate::rings::poly::dense_poly::*;
8use crate::homomorphism::*;
9use crate::field::FieldStore;
10use crate::divisibility::{DivisibilityRing, DivisibilityRingStore};
11use crate::integer::*;
12use crate::ring::*;
13use crate::MAX_PROBABILISTIC_REPETITIONS;
14use crate::rings::float_complex::{Complex64, Complex64Base, Complex64El};
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.;
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>(poly_ring: P, f: &El<P>, poly_deg: usize, point: Complex64El, relative_error_point: f64) -> f64
27 where P: RingStore,
28 P::Type: PolyRing + DivisibilityRing,
29 <P::Type as RingExtension>::BaseRing: RingStore<Type = Complex64Base>
30{
31 let CC = Complex64::RING;
32 let mut current = point;
33 let mut current_relative_error = relative_error_point;
34 let mut total_error = 0.;
35 for i in 1..=poly_deg {
36 total_error += CC.abs(*poly_ring.coefficient_at(f, i)) * CC.abs(current) * current_relative_error;
37 current_relative_error += f64::EPSILON;
39 CC.mul_assign(&mut current, point);
40 }
41 return total_error;
42}
43
44fn bound_distance_to_root<P>(approx_root: Complex64El, CCX: P, poly: &El<P>, poly_deg: usize) -> Result<f64, PrecisionError>
45 where P: RingStore,
46 P::Type: PolyRing + DivisibilityRing,
47 <P::Type as RingExtension>::BaseRing: RingStore<Type = Complex64Base>
48{
49 let CC = Complex64::RING;
50 let f = poly;
51 let f_prime = derive_poly(&CCX, &f);
52
53 let approx_radius = (CC.abs(CCX.evaluate(&f, &approx_root, CC.identity())) + absolute_error_of_poly_eval(&CCX, &f, poly_deg, approx_root, 0.)) / CC.abs(CCX.evaluate(&f_prime, &approx_root, CC.identity()));
54 if !approx_radius.is_finite() {
55 return Err(PrecisionError);
56 }
57
58 let assume_radius = approx_radius * ASSUME_RADIUS_TO_APPROX_RADIUS_FACTOR;
59 let mut abs_taylor_series_coeffs = Vec::new();
61 let mut current = derive_poly(&CCX, &f_prime);
62 for i in 0..poly_deg.saturating_sub(1) {
63 CCX.inclusion().mul_assign_map(&mut current, CC.from_f64(1. / (i as f64 + 1.)));
64 abs_taylor_series_coeffs.push(CC.abs(CCX.evaluate(¤t, &approx_root, CC.identity())));
65 current = derive_poly(&CCX, ¤t);
66 }
67 let f_prime_bound = abs_taylor_series_coeffs.iter().enumerate()
68 .map(|(i, c)| {
69 c * assume_radius.powi(i as i32 + 1)
70 })
71 .sum::<f64>();
72
73 if f_prime_bound > CC.abs(CCX.evaluate(&f_prime, &approx_root, CC.identity())) * (ASSUME_RADIUS_TO_APPROX_RADIUS_FACTOR - 1.) / ASSUME_RADIUS_TO_APPROX_RADIUS_FACTOR {
77 return Err(PrecisionError);
78 }
79
80 return Ok(assume_radius);
81}
82
83fn newton_with_initial<P>(poly_ring: P, f: &El<P>, poly_deg: usize, initial: El<Complex64>) -> Result<(El<Complex64>, f64), PrecisionError>
84 where P: RingStore,
85 P::Type: PolyRing + DivisibilityRing,
86 <P::Type as RingExtension>::BaseRing: RingStore<Type = Complex64Base>
87{
88 let CC = Complex64::RING;
89 let f_prime = derive_poly(&poly_ring, f);
90 let mut current = initial;
91 for _ in 0..NEWTON_ITERATIONS {
92 current = CC.sub(current, CC.div(&poly_ring.evaluate(f, ¤t, CC.identity()), &poly_ring.evaluate(&f_prime, ¤t, CC.identity())));
93 }
94 return Ok((current, bound_distance_to_root(current, poly_ring, f, poly_deg)?));
95}
96
97fn find_approximate_complex_root_squarefree<P>(poly_ring: P, f: &El<P>, poly_deg: usize) -> Result<(El<Complex64>, f64), PrecisionError>
98 where P: RingStore,
99 P::Type: PolyRing + DivisibilityRing,
100 <P::Type as RingExtension>::BaseRing: RingStore<Type = Complex64Base>
101{
102 let mut rng = Rand64::new(1);
103 let CC = Complex64::RING;
104 assert!(poly_ring.terms(f).all(|(c, _): (&Complex64El, _)| CC.re(*c).is_finite() && CC.im(*c).is_finite()));
105 let f_prime = derive_poly(&poly_ring, f);
106
107 let (approx_root, approx_radius) = (0..MAX_PROBABILISTIC_REPETITIONS).map(|_| {
108 let starting_point_unscaled = CC.add(
109 CC.from_f64((rng.rand_u64() as i64) as f64 / i64::MAX as f64),
111 CC.mul(Complex64::I, CC.from_f64((rng.rand_u64() as i64) as f64 / i64::MAX as f64))
112 );
113 let scale = (rng.rand_u64() % (2 * NEWTON_MAX_SCALE as u64)) as i32 - NEWTON_MAX_SCALE as i32;
114 let starting_point = CC.mul(starting_point_unscaled, CC.from_f64(2.0f64.powi(scale)));
115
116 let mut current = starting_point;
117 for _ in 0..NEWTON_ITERATIONS {
118 current = CC.sub(current, CC.div(&poly_ring.evaluate(f, ¤t, CC.identity()), &poly_ring.evaluate(&f_prime, ¤t, CC.identity())));
119 }
120
121 let approx_radius = CC.abs(poly_ring.evaluate(f, ¤t, CC.identity())) / CC.abs(poly_ring.evaluate(&f_prime, ¤t, CC.identity()));
123
124 return (current, approx_radius);
125 }).min_by(|(_, r1), (_, r2)| match (r1.is_finite(), r2.is_finite()) {
126 (false, false) => Ordering::Equal,
127 (true, false) => Ordering::Less,
128 (false, true) => Ordering::Greater,
129 (true, true) => f64::total_cmp(r1, r2)
130 }).unwrap();
131 if !approx_radius.is_finite() || approx_radius < 0. {
132 return Err(PrecisionError);
133 }
134
135 return Ok((approx_root, bound_distance_to_root(approx_root, poly_ring, f, poly_deg)?));
136}
137
138#[stability::unstable(feature = "enable")]
148pub fn find_approximate_complex_root<P>(ZZX: P, f: &El<P>) -> Result<(El<Complex64>, f64), PrecisionError>
149 where P: RingStore,
150 P::Type: PolyRing + DivisibilityRing,
151 <<P::Type as RingExtension>::BaseRing as RingStore>::Type: IntegerRing
152{
153 assert!(ZZX.degree(f).unwrap_or(0) > 0);
154 let CC = Complex64::RING;
155 assert!(ZZX.checked_div(&poly_squarefree_part_local(&ZZX, ZZX.clone_el(f), DontObserve), f).is_some(), "polynomial must be square-free");
156 let CCX = DensePolyRing::new(CC, "X");
157 return find_approximate_complex_root_squarefree(&CCX, &CCX.lifted_hom(&ZZX, CC.can_hom(ZZX.base_ring()).unwrap()).map_ref(f), ZZX.degree(f).unwrap());
158}
159
160#[stability::unstable(feature = "enable")]
173pub fn find_all_approximate_complex_roots<P>(ZZX: P, poly: &El<P>) -> Result<Vec<(El<Complex64>, f64)>, PrecisionError>
174 where P: RingStore,
175 P::Type: PolyRing + DivisibilityRing,
176 <<P::Type as RingExtension>::BaseRing as RingStore>::Type: IntegerRing
177{
178 assert!(ZZX.degree(poly).unwrap_or(0) > 0);
179 let CC = Complex64::RING;
180 let ZZ_to_CC = CC.can_hom(ZZX.base_ring()).unwrap();
181 assert!(ZZX.checked_div(&poly_squarefree_part_local(&ZZX, ZZX.clone_el(poly), DontObserve), poly).is_some(), "polynomial must be square-free");
182 let CCX = DensePolyRing::new(CC, "X");
183 let ZZX_to_CCX = CCX.lifted_hom(&ZZX, ZZ_to_CC);
184
185 let d = ZZX.degree(poly).unwrap();
186 let f = ZZX_to_CCX.map_ref(poly);
187 let mut remaining_poly = CCX.clone_el(&f);
188 let mut result = Vec::new();
189 for i in 0..ZZX.degree(&poly).unwrap() {
190 let (next_root_initial, _) = find_approximate_complex_root_squarefree(&CCX, &remaining_poly, d - i)?;
191 let (next_root, distance) = newton_with_initial(&CCX, &f, d, next_root_initial)?;
192 if result.iter().any(|(prev_root, prev_distance)| CC.abs(CC.sub(*prev_root, next_root)) <= distance + prev_distance) {
193 return Err(PrecisionError);
194 }
195 result.push((next_root, distance));
196 let mut new_remaining_poly = CCX.zero();
197 for j in (1..=(d - i)).rev() {
198 let lc = *CCX.coefficient_at(&remaining_poly, j);
199 CCX.get_ring().add_assign_from_terms(&mut new_remaining_poly, [(lc, j - 1)]);
200 CCX.get_ring().add_assign_from_terms(&mut remaining_poly, [(CC.mul(lc, next_root), j - 1)]);
201 }
202 remaining_poly = new_remaining_poly;
203 }
204 result.sort_unstable_by(|(l, _), (r, _)| f64::total_cmp(&(CC.re(*l) + CC.im(*l) * 0.000001), &(CC.re(*r) + CC.im(*r) * 0.000001)));
206 return Ok(result);
207}
208
209#[cfg(test)]
210use crate::algorithms::cyclotomic::cyclotomic_polynomial;
211#[cfg(test)]
212use std::f64::consts::PI;
213#[cfg(test)]
214use crate::algorithms::eea::signed_gcd;
215#[cfg(test)]
216use crate::primitive_int::StaticRing;
217
218#[test]
219fn test_find_approximate_complex_root() {
220 let ZZ = BigIntRing::RING;
221 let ZZX = DensePolyRing::new(&ZZ, "X");
222 let CC = Complex64::RING;
223
224 let [f] = ZZX.with_wrapped_indeterminate(|X| [X.pow_ref(2) + 1]);
225 let (root, radius) = find_approximate_complex_root(&ZZX, &f).unwrap();
226 assert!(radius <= 0.000000001);
227 assert!(
228 CC.abs(CC.sub(Complex64::I, root)) <= radius ||
229 CC.abs(CC.add(Complex64::I, root)) <= radius
230 );
231
232 let [f] = ZZX.with_wrapped_indeterminate(|X| [100000000 * X.pow_ref(2) + 1]);
233 let (root, radius) = find_approximate_complex_root(&ZZX, &f).unwrap();
234 assert!(radius <= 0.000000001);
235 assert!(
236 CC.abs(CC.sub(CC.mul(Complex64::I, CC.from_f64(0.0001)), root)) <= radius ||
237 CC.abs(CC.add(CC.mul(Complex64::I, CC.from_f64(0.0001)), root)) <= radius
238 );
239
240 let f = cyclotomic_polynomial(&ZZX, 105);
241 let (root, radius) = find_approximate_complex_root(&ZZX, &f).unwrap();
242 assert!(radius <= 0.000000001);
243 let root_of_unity = |k, n| CC.exp(CC.mul(CC.from_f64(2.0 * PI * k as f64 / n as f64), Complex64::I));
244 assert!((0..105).filter(|k| signed_gcd(*k, 105, StaticRing::<i64>::RING) == 1).any(|k| CC.abs(CC.sub(root_of_unity(k, 105), root)) <= radius));
245
246 let [f] = ZZX.with_wrapped_indeterminate(|X| [X.pow_ref(4) - 6 * X.pow_ref(2) - 11]);
247 let (root, radius) = find_approximate_complex_root(&ZZX, &f).unwrap();
248 assert!(radius <= 0.000000001);
249 let expected = [
250 CC.from_f64(-2.7335207983477241859817441249894389670263693372196541057936155022),
251 CC.from_f64(2.7335207983477241859817441249894389670263693372196541057936155022),
252 CC.mul(CC.from_f64(-1.2133160985495821701841076107103126272177604441592580488679327999), Complex64::I),
253 CC.mul(CC.from_f64(1.2133160985495821701841076107103126272177604441592580488679327999), Complex64::I)
254 ];
255 assert!(expected.iter().any(|x| CC.abs(CC.sub(*x, root)) <= radius));
256}
257
258#[test]
259fn test_find_all_approximate_complex_roots() {
260 let ZZ = BigIntRing::RING;
261 let ZZX = DensePolyRing::new(&ZZ, "X");
262 let CC = Complex64::RING;
263
264 let [f] = ZZX.with_wrapped_indeterminate(|X| [X.pow_ref(3) - 7 * X + 100]);
265 let expected = [
266 CC.from_f64(-5.1425347350362689414102462079341122827700721701123021518405977272),
267 CC.add(
268 CC.from_f64(2.5712673675181344707051231039670561413850360850561510759202988636),
269 CC.mul(CC.from_f64(-3.5824918179656616775077885147170618458138064112538190024361795659), Complex64::I)
270 ),
271 CC.add(
272 CC.from_f64(2.5712673675181344707051231039670561413850360850561510759202988636),
273 CC.mul(CC.from_f64(3.5824918179656616775077885147170618458138064112538190024361795659), Complex64::I)
274 )
275 ];
276 let actual = find_all_approximate_complex_roots(&ZZX, &f).unwrap();
277 for (expected, (actual, dist)) in expected.iter().copied().zip(actual.iter().copied()) {
278 assert!(dist < 0.000000001);
279 assert!(CC.abs(CC.sub(actual, expected)) <= dist);
280 }
281
282 let root_of_unity = |k, n| CC.exp(CC.mul(CC.from_f64(2.0 * PI * k as f64 / n as f64), Complex64::I));
283 let f = cyclotomic_polynomial(&ZZX, 105);
284 let expected = (1..=52).rev().filter(|i| signed_gcd(*i, 105, StaticRing::<i64>::RING) == 1).flat_map(|i| [root_of_unity(105 - i, 105), root_of_unity(i, 105)]).chain([CC.one()]);
285 let actual = find_all_approximate_complex_roots(&ZZX, &f).unwrap();
286 assert_eq!(48, actual.len());
287 for (expected, (actual, dist)) in expected.zip(actual.iter().copied()) {
288 assert!(dist < 0.000000001);
289 assert!(CC.abs(CC.sub(actual, expected)) <= dist);
290 }
291}