feanor_math/rings/
float_complex.rs

1use std::f64::EPSILON;
2use std::f64::consts::PI;
3
4use crate::homomorphism::CanHomFrom;
5use crate::pid::{EuclideanRing, PrincipalIdealRing};
6use crate::field::Field;
7use crate::integer::{IntegerRingStore, IntegerRing};
8use crate::impl_eq_based_self_iso;
9use crate::ring::*;
10use crate::divisibility::{DivisibilityRing, Domain};
11
12use super::float_real::Real64;
13use super::rational::RationalFieldBase;
14
15///
16/// An approximate implementation of the complex numbers `C`, using 64 bit floating
17/// point numbers.
18/// 
19/// # Warning
20/// 
21/// Since floating point numbers do not exactly represent the complex numbers, and this crate follows
22/// a mathematically precise approach, we cannot provide any function related to equality.
23/// In particular, `Complex64Base.eq_el(a, b)` is not supported, and will panic. 
24/// Hence, this ring has only limited use within this crate, and is currently only used for
25/// floating-point FFTs. 
26/// 
27#[derive(Clone, Copy, PartialEq)]
28pub struct Complex64Base;
29
30#[derive(Clone, Copy)]
31pub struct Complex64El(f64, f64);
32
33///
34/// [`RingStore`] corresponding to [`Complex64Base`]
35/// 
36pub type Complex64 = RingValue<Complex64Base>;
37
38impl Complex64 {
39
40    pub const RING: Self = RingValue::from(Complex64Base);
41    pub const I: Complex64El = Complex64El(0., 1.);
42}
43
44impl Complex64Base {
45
46    pub fn abs(&self, Complex64El(re, im): Complex64El) -> f64 {
47        (re * re + im * im).sqrt()
48    }
49
50    pub fn conjugate(&self, Complex64El(re, im): Complex64El) -> Complex64El {
51        Complex64El(re, -im)
52    }
53
54    pub fn exp(&self, Complex64El(exp_re, exp_im): Complex64El) -> Complex64El {
55        let angle = exp_im;
56        let abs = exp_re.exp();
57        Complex64El(abs * angle.cos(), abs * angle.sin())
58    }
59
60    pub fn closest_gaussian_int(&self, Complex64El(re, im): Complex64El) -> (i64, i64) {
61        (re.round() as i64, im.round() as i64)
62    }
63
64    pub fn ln_main_branch(&self, Complex64El(re, im): Complex64El) -> Complex64El {
65        Complex64El(self.abs(Complex64El(re, im)).ln(), im.atan2(re))
66    }
67
68    pub fn is_absolute_approx_eq(&self, lhs: Complex64El, rhs: Complex64El, absolute_threshold: f64) -> bool {
69        self.abs(self.sub(lhs, rhs)) < absolute_threshold
70    }
71
72    pub fn is_relative_approx_eq(&self, lhs: Complex64El, rhs: Complex64El, relative_limit: f64) -> bool {
73        self.is_absolute_approx_eq(lhs, rhs, self.abs(lhs) * relative_limit)
74    }
75
76    pub fn is_approx_eq(&self, lhs: Complex64El, rhs: Complex64El, precision: u64) -> bool {
77        let scaled_precision = precision as f64 * EPSILON;
78        if self.is_absolute_approx_eq(lhs, self.zero(), scaled_precision) {
79            self.is_absolute_approx_eq(rhs, self.zero(), scaled_precision)
80        } else {
81            self.is_relative_approx_eq(lhs, rhs, scaled_precision)
82        }
83    }
84
85    pub fn from_f64(&self, x: f64) -> Complex64El {
86        Complex64El(x, 0.)
87    }
88
89    pub fn root_of_unity(&self, i: i64, n: i64) -> Complex64El {
90        self.exp(self.mul(self.from_f64((i as f64 / n as f64) * (2. * PI)), Complex64::I))
91    }
92
93    pub fn re(&self, Complex64El(re, _im): Complex64El) -> f64 {
94        re
95    }
96
97    pub fn im(&self, Complex64El(_re, im): Complex64El) -> f64 {
98        im
99    }
100}
101
102impl Complex64 {
103    
104    pub fn abs(&self, val: Complex64El) -> f64 { self.get_ring().abs(val) }
105
106    pub fn conjugate(&self, val: Complex64El) -> Complex64El { self.get_ring().conjugate(val) }
107
108    pub fn exp(&self, exp: Complex64El) -> Complex64El { self.get_ring().exp(exp) }
109
110    pub fn closest_gaussian_int(&self, val: Complex64El) -> (i64, i64) { self.get_ring().closest_gaussian_int(val) }
111
112    pub fn ln_main_branch(&self, val: Complex64El) -> Complex64El { self.get_ring().ln_main_branch(val) }
113
114    pub fn is_absolute_approx_eq(&self, lhs: Complex64El, rhs: Complex64El, absolute_threshold: f64) -> bool { self.get_ring().is_absolute_approx_eq(lhs, rhs, absolute_threshold) }
115
116    pub fn is_relative_approx_eq(&self, lhs: Complex64El, rhs: Complex64El, relative_limit: f64) -> bool { self.get_ring().is_relative_approx_eq(lhs, rhs, relative_limit) }
117
118    pub fn is_approx_eq(&self, lhs: Complex64El, rhs: Complex64El, precision: u64) -> bool { self.get_ring().is_approx_eq(lhs, rhs, precision) }
119
120    pub fn from_f64(&self, x: f64) -> Complex64El { self.get_ring().from_f64(x) }
121
122    pub fn root_of_unity(&self, i: i64, n: i64) -> Complex64El { self.get_ring().root_of_unity(i, n) }
123
124    pub fn re(&self, x: Complex64El) -> f64 { self.get_ring().re(x) }
125
126    pub fn im(&self, x: Complex64El) -> f64 { self.get_ring().im(x) }
127}
128
129impl RingBase for Complex64Base {
130 
131    type Element = Complex64El;
132    
133    fn clone_el(&self, val: &Self::Element) -> Self::Element {
134        *val
135    }
136
137    fn add_assign(&self, Complex64El(lhs_re, lhs_im): &mut Self::Element, Complex64El(rhs_re, rhs_im): Self::Element) {
138        *lhs_re += rhs_re;
139        *lhs_im += rhs_im;
140    }
141
142    fn negate_inplace(&self, Complex64El(re, im): &mut Self::Element) {
143        *re = -*re;
144        *im = -*im;
145    }
146
147    fn mul_assign(&self, Complex64El(lhs_re, lhs_im): &mut Self::Element, Complex64El(rhs_re, rhs_im): Self::Element) {
148        let new_im = *lhs_re * rhs_im + *lhs_im * rhs_re;
149        *lhs_re = *lhs_re * rhs_re - *lhs_im * rhs_im;
150        *lhs_im = new_im;
151    }
152
153    fn from_int(&self, value: i32) -> Self::Element {
154        Complex64El(value as f64, 0.)
155    }
156    
157    fn eq_el(&self, _: &Self::Element, _: &Self::Element) -> bool {
158        panic!("Cannot provide equality on approximate rings")
159    }
160
161    fn pow_gen<R: IntegerRingStore>(&self, x: Self::Element, power: &El<R>, integers: R) -> Self::Element 
162        where R::Type: IntegerRing
163    {
164        self.exp(self.mul(self.ln_main_branch(x), Complex64El(integers.to_float_approx(power), 0.)))
165    }
166
167    fn is_commutative(&self) -> bool { true }
168
169    fn is_noetherian(&self) -> bool { true }
170
171    fn is_approximate(&self) -> bool { true }
172
173    fn dbg_within<'a>(&self, Complex64El(re, im): &Self::Element, out: &mut std::fmt::Formatter<'a>, env: EnvBindingStrength) -> std::fmt::Result {
174        if env >= EnvBindingStrength::Product {
175            write!(out, "({} + {}i)", re, im)
176        } else {
177            write!(out, "{} + {}i", re, im)
178        }
179    }
180
181    fn dbg<'a>(&self, value: &Self::Element, out: &mut std::fmt::Formatter<'a>) -> std::fmt::Result {
182        self.dbg_within(value, out, EnvBindingStrength::Weakest)
183    }
184    
185    fn characteristic<I: IntegerRingStore + Copy>(&self, ZZ: I) -> Option<El<I>>
186        where I::Type: IntegerRing
187    {
188        Some(ZZ.zero())
189    }
190}
191
192impl_eq_based_self_iso!{ Complex64Base }
193
194impl Domain for Complex64Base {}
195
196impl DivisibilityRing for Complex64Base {
197
198    fn checked_left_div(&self, lhs: &Self::Element, rhs: &Self::Element) -> Option<Self::Element> {
199        let abs_sqr = self.abs(*rhs) * self.abs(*rhs);
200        let Complex64El(res_re, res_im) =  self.mul(*lhs, self.conjugate(*rhs));
201        return Some(Complex64El(res_re / abs_sqr, res_im / abs_sqr));
202    }
203}
204
205impl PrincipalIdealRing for Complex64Base {
206
207    fn checked_div_min(&self, lhs: &Self::Element, rhs: &Self::Element) -> Option<Self::Element> {
208        self.checked_left_div(lhs, rhs)
209    }
210    
211    fn extended_ideal_gen(&self, _lhs: &Self::Element, _rhs: &Self::Element) -> (Self::Element, Self::Element, Self::Element) {
212        panic!("Since Complex64 is only approximate, this cannot be implemented properly")
213    }
214}
215
216impl EuclideanRing for Complex64Base {
217
218    fn euclidean_div_rem(&self, _lhs: Self::Element, _rhs: &Self::Element) -> (Self::Element, Self::Element) {
219        panic!("Since Complex64 is only approximate, this cannot be implemented properly")
220    }
221
222    fn euclidean_deg(&self, _: &Self::Element) -> Option<usize> {
223        panic!("Since Complex64 is only approximate, this cannot be implemented properly")
224    }
225}
226
227impl Field for Complex64Base {
228    
229    fn div(&self, lhs: &Self::Element, rhs: &Self::Element) -> Self::Element {
230        self.checked_left_div(lhs, rhs).unwrap()
231    }
232}
233
234impl RingExtension for Complex64Base {
235
236    type BaseRing = Real64;
237
238    fn base_ring<'a>(&'a self) -> &'a Self::BaseRing {
239        &Real64::RING
240    }
241
242    fn from(&self, x: El<Self::BaseRing>) -> Self::Element {
243        self.from_f64(x)
244    }
245
246    fn mul_assign_base(&self, lhs: &mut Self::Element, rhs: &El<Self::BaseRing>) {
247        lhs.0 *= *rhs;
248        lhs.1 *= *rhs;
249    }
250}
251
252impl<I: ?Sized + IntegerRing> CanHomFrom<I> for Complex64Base {
253    
254    type Homomorphism = ();
255
256    fn has_canonical_hom(&self, _from: &I) -> Option<Self::Homomorphism> {
257        Some(())
258    }
259
260    fn map_in(&self, from: &I, el: <I as RingBase>::Element, hom: &Self::Homomorphism) -> Self::Element {
261        self.map_in_ref(from, &el, hom)
262    }
263
264    fn map_in_ref(&self, from: &I, el: &<I as RingBase>::Element, _hom: &Self::Homomorphism) -> Self::Element {
265        self.from_f64(from.to_float_approx(el))
266    }
267}
268
269impl<I> CanHomFrom<RationalFieldBase<I>> for Complex64Base
270    where I: IntegerRingStore,
271        I::Type: IntegerRing
272{    
273    type Homomorphism = <Self as CanHomFrom<I::Type>>::Homomorphism;
274
275    fn has_canonical_hom(&self, from: &RationalFieldBase<I>) -> Option<Self::Homomorphism> {
276        self.has_canonical_hom(from.base_ring().get_ring())
277    }
278
279    fn map_in(&self, from: &RationalFieldBase<I>, el: <RationalFieldBase<I> as RingBase>::Element, hom: &Self::Homomorphism) -> Self::Element {
280        self.map_in_ref(from, &el, hom)
281    }
282
283    fn map_in_ref(&self, from: &RationalFieldBase<I>, el: &<RationalFieldBase<I> as RingBase>::Element, hom: &Self::Homomorphism) -> Self::Element {
284        self.div(&self.map_in_ref(from.base_ring().get_ring(), from.num(el), hom), &self.map_in_ref(from.base_ring().get_ring(), from.den(el), hom))
285    }
286}
287
288#[test]
289fn test_pow() {
290    let CC = Complex64::RING;
291    let i = Complex64::I;
292    assert!(CC.is_approx_eq(CC.negate(i), CC.pow(i, 3), 1));
293    assert!(!CC.is_approx_eq(CC.negate(i), CC.pow(i, 1024 + 3), 1));
294    assert!(CC.is_approx_eq(CC.negate(i), CC.pow(i, 1024 + 3), 100));
295    assert!(CC.is_approx_eq(CC.exp(CC.mul(CC.from_f64(std::f64::consts::PI / 4.), i)), CC.mul(CC.add(CC.one(), i), CC.from_f64(2f64.powf(-0.5))), 1));
296
297    let seventh_root_of_unity = CC.exp(CC.mul(i, CC.from_f64(2. * std::f64::consts::PI / 7.)));
298    assert!(CC.is_approx_eq(CC.pow(seventh_root_of_unity, 7 * 100 + 1), seventh_root_of_unity, 1000));
299}
300
301#[test]
302fn test_mul() {
303    let CC = Complex64::RING;
304    let i = Complex64::I;
305    assert!(CC.is_approx_eq(CC.mul(i, i), CC.from_f64(-1.), 1));
306    assert!(CC.is_approx_eq(CC.mul(i, CC.negate(i)), CC.from_f64(1.), 1));
307    assert!(CC.is_approx_eq(CC.mul(CC.add(i, CC.one()), i), CC.sub(i, CC.one()), 1));
308}