1#[cfg(feature = "mpfr")]
12use rug::ops::{AddAssignRound, DivAssignRound, Pow, SubAssignRound};
13
14use crate::domain::Domain;
15
16#[cfg(feature = "mpfr")]
18const BALL_PRECISION: u32 = 53;
19
20#[derive(Debug, Clone, PartialEq)]
25pub struct RealBall {
26 #[cfg(not(feature = "mpfr"))]
27 mid: f64,
28 #[cfg(not(feature = "mpfr"))]
29 rad: f64,
30 #[cfg(feature = "mpfr")]
31 mid: rug::Float,
32 #[cfg(feature = "mpfr")]
33 rad: rug::Float,
34}
35
36impl Eq for RealBall {}
37
38#[cfg(not(feature = "mpfr"))]
39impl Copy for RealBall {}
40
41impl RealBall {
42 #[cfg(not(feature = "mpfr"))]
46 pub fn new(mid: f64, rad: f64) -> Self {
47 Self {
48 mid,
49 rad: rad.max(0.0),
50 }
51 }
52
53 #[cfg(feature = "mpfr")]
57 pub fn new(mid: rug::Float, rad: rug::Float) -> Self {
58 let zero = mpfr_zero();
59 if rad < zero {
60 Self { mid, rad: zero }
61 } else {
62 Self { mid, rad }
63 }
64 }
65
66 #[cfg(not(feature = "mpfr"))]
68 pub fn from_f64(value: f64) -> Self {
69 Self::new(value, 0.0)
70 }
71
72 #[cfg(feature = "mpfr")]
74 pub fn from_f64(value: f64) -> Self {
75 Self::new(rug::Float::with_val(BALL_PRECISION, value), mpfr_zero())
76 }
77
78 #[cfg(not(feature = "mpfr"))]
80 pub fn mid(&self) -> f64 {
81 self.mid
82 }
83
84 #[cfg(feature = "mpfr")]
86 pub fn mid(&self) -> &rug::Float {
87 &self.mid
88 }
89
90 #[cfg(not(feature = "mpfr"))]
92 pub fn rad(&self) -> f64 {
93 self.rad
94 }
95
96 #[cfg(feature = "mpfr")]
98 pub fn rad(&self) -> &rug::Float {
99 &self.rad
100 }
101
102 #[cfg(not(feature = "mpfr"))]
104 pub fn lower(&self) -> f64 {
105 self.mid - self.rad
106 }
107
108 #[cfg(feature = "mpfr")]
110 pub fn lower(&self) -> rug::Float {
111 let mut lo = self.mid.clone();
112 lo.sub_assign_round(&self.rad, rug::float::Round::Down);
113 lo
114 }
115
116 #[cfg(not(feature = "mpfr"))]
118 pub fn upper(&self) -> f64 {
119 self.mid + self.rad
120 }
121
122 #[cfg(feature = "mpfr")]
124 pub fn upper(&self) -> rug::Float {
125 let mut hi = self.mid.clone();
126 hi.add_assign_round(&self.rad, rug::float::Round::Up);
127 hi
128 }
129
130 #[cfg(feature = "mpfr")]
132 pub fn precision(&self) -> u32 {
133 self.mid.prec()
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub struct RealBallDomain;
143
144impl Domain for RealBallDomain {
145 type Element = RealBall;
146
147 fn zero(&self) -> Self::Element {
148 RealBall::from_f64(0.0)
149 }
150
151 fn one(&self) -> Self::Element {
152 RealBall::from_f64(1.0)
153 }
154
155 fn add(&self, a: &Self::Element, b: &Self::Element) -> Self::Element {
156 #[cfg(not(feature = "mpfr"))]
157 {
158 RealBall::new(a.mid + b.mid, a.rad + b.rad)
159 }
160 #[cfg(feature = "mpfr")]
161 {
162 let mut mid = a.mid.clone();
163 let mut rad = a.rad.clone();
164 mid.add_assign_round(&b.mid, rug::float::Round::Down);
165 rad.add_assign_round(&b.rad, rug::float::Round::Up);
166 rad.add_assign_round(&rounding_unit(&mid), rug::float::Round::Up);
167 RealBall::new(mid, rad)
168 }
169 }
170
171 fn sub(&self, a: &Self::Element, b: &Self::Element) -> Self::Element {
172 #[cfg(not(feature = "mpfr"))]
173 {
174 RealBall::new(a.mid - b.mid, a.rad + b.rad)
175 }
176 #[cfg(feature = "mpfr")]
177 {
178 let mut mid = a.mid.clone();
179 let mut rad = a.rad.clone();
180 mid.sub_assign_round(&b.mid, rug::float::Round::Up);
181 rad.add_assign_round(&b.rad, rug::float::Round::Up);
182 rad.add_assign_round(&rounding_unit(&mid), rug::float::Round::Up);
183 RealBall::new(mid, rad)
184 }
185 }
186
187 fn neg(&self, a: &Self::Element) -> Self::Element {
188 #[cfg(not(feature = "mpfr"))]
189 {
190 RealBall::new(-a.mid, a.rad)
191 }
192 #[cfg(feature = "mpfr")]
193 {
194 RealBall::new(-a.mid.clone(), a.rad.clone())
195 }
196 }
197
198 fn mul(&self, a: &Self::Element, b: &Self::Element) -> Self::Element {
199 #[cfg(not(feature = "mpfr"))]
200 {
201 let a_lo = a.lower();
202 let a_hi = a.upper();
203 let b_lo = b.lower();
204 let b_hi = b.upper();
205 let p1 = a_lo * b_lo;
206 let p2 = a_lo * b_hi;
207 let p3 = a_hi * b_lo;
208 let p4 = a_hi * b_hi;
209 let lo = p1.min(p2).min(p3).min(p4);
210 let hi = p1.max(p2).max(p3).max(p4);
211 let mid = (lo + hi) / 2.0;
212 let rad = (hi - lo) / 2.0;
213 RealBall::new(mid, rad)
214 }
215 #[cfg(feature = "mpfr")]
216 {
217 let a_lo = a.lower();
218 let a_hi = a.upper();
219 let b_lo = b.lower();
220 let b_hi = b.upper();
221 let mut products = [
222 a_lo.clone() * b_lo.clone(),
223 a_lo * b_hi.clone(),
224 a_hi.clone() * b_lo,
225 a_hi * b_hi,
226 ];
227 for p in &mut products {
228 p.set_prec(BALL_PRECISION);
229 }
230 let lo = products.iter().cloned().reduce(mpfr_min).unwrap();
231 let hi = products.iter().cloned().reduce(mpfr_max).unwrap();
232 let mut mid = (lo.clone() + &hi) / 2u32;
233 mid.set_prec(BALL_PRECISION);
234 let mut rad = (hi - &lo).abs() / 2u32;
235 rad.set_prec(BALL_PRECISION);
236 rad.add_assign_round(&rounding_unit(&mid), rug::float::Round::Up);
237 RealBall::new(mid, rad)
238 }
239 }
240
241 fn div(&self, a: &Self::Element, b: &Self::Element) -> Option<Self::Element> {
242 let inv = self.inv(b)?;
243 Some(self.mul(a, &inv))
244 }
245
246 fn inv(&self, a: &Self::Element) -> Option<Self::Element> {
247 #[cfg(not(feature = "mpfr"))]
248 {
249 if a.lower() <= 0.0 && a.upper() >= 0.0 {
250 return None;
251 }
252 let lo = a.lower();
253 let hi = a.upper();
254 let inv_lo = 1.0 / hi;
255 let inv_hi = 1.0 / lo;
256 let mid = (inv_lo + inv_hi) / 2.0;
257 let rad = (inv_hi - inv_lo).abs() / 2.0;
258 Some(RealBall::new(mid, rad))
259 }
260 #[cfg(feature = "mpfr")]
261 {
262 let lo = a.lower();
263 let hi = a.upper();
264 if lo <= mpfr_zero() && hi >= mpfr_zero() {
265 return None;
266 }
267 let mut inv_lo = rug::Float::with_val(BALL_PRECISION, 1.0);
268 inv_lo.div_assign_round(&hi, rug::float::Round::Down);
269 let mut inv_hi = rug::Float::with_val(BALL_PRECISION, 1.0);
270 inv_hi.div_assign_round(&lo, rug::float::Round::Up);
271 let mut mid = (inv_lo.clone() + &inv_hi) / 2u32;
272 mid.set_prec(BALL_PRECISION);
273 let mut rad = (inv_hi - &inv_lo).abs() / 2u32;
274 rad.set_prec(BALL_PRECISION);
275 rad.add_assign_round(&rounding_unit(&mid), rug::float::Round::Up);
276 Some(RealBall::new(mid, rad))
277 }
278 }
279}
280
281#[cfg(feature = "mpfr")]
282fn mpfr_zero() -> rug::Float {
283 rug::Float::with_val(BALL_PRECISION, 0.0)
284}
285
286#[cfg(feature = "mpfr")]
288fn rounding_unit(x: &rug::Float) -> rug::Float {
289 let p = x.prec();
290 let mut u = rug::Float::with_val(p, 1.0);
291 let denom = rug::Float::with_val(p, 2.0).pow(p - 1);
292 u.div_assign_round(&denom, rug::float::Round::Up);
293 u
294}
295
296#[cfg(feature = "mpfr")]
298fn mpfr_min(a: rug::Float, b: rug::Float) -> rug::Float {
299 if a < b { a } else { b }
300}
301
302#[cfg(feature = "mpfr")]
304fn mpfr_max(a: rug::Float, b: rug::Float) -> rug::Float {
305 if a > b { a } else { b }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[cfg(not(feature = "mpfr"))]
313 #[test]
314 fn ball_default_addition_widens_radius() {
315 let domain = RealBallDomain;
316 let a = RealBall::from_f64(1.0);
317 let b = RealBall::from_f64(2.0);
318 let sum = domain.add(&a, &b);
319 assert!((sum.mid() - 3.0).abs() < 1e-12);
320 assert!((sum.rad() - 0.0).abs() < 1e-12);
321 }
322
323 #[cfg(not(feature = "mpfr"))]
324 #[test]
325 fn ball_default_multiplication_contains_true_product() {
326 let domain = RealBallDomain;
327 let a = RealBall::from_f64(2.0);
328 let b = RealBall::from_f64(3.0);
329 let prod = domain.mul(&a, &b);
330 assert!(prod.lower() <= 6.0 && 6.0 <= prod.upper());
331 }
332
333 #[cfg(not(feature = "mpfr"))]
334 #[test]
335 fn ball_default_inverse_excludes_zero() {
336 let domain = RealBallDomain;
337 let a = RealBall::from_f64(2.0);
338 let inv = domain.inv(&a).expect("ball does not contain zero");
339 let prod = domain.mul(&a, &inv);
340 assert!(prod.lower() <= 1.0 && 1.0 <= prod.upper());
341 }
342
343 #[cfg(not(feature = "mpfr"))]
344 #[test]
345 fn ball_default_inverse_of_zero_ball_is_none() {
346 let domain = RealBallDomain;
347 let a = RealBall::from_f64(0.0);
348 assert!(domain.inv(&a).is_none());
349 }
350
351 #[cfg(not(feature = "mpfr"))]
352 #[test]
353 fn ball_default_contains_zero_leads_to_none() {
354 let domain = RealBallDomain;
355 let a = RealBall::new(1.0, 0.0);
356 let b = RealBall::new(-0.05, 0.1);
357 assert!(domain.div(&a, &b).is_none());
358 }
359
360 #[cfg(feature = "mpfr")]
361 mod mpfr_tests {
362 use super::*;
363
364 #[test]
365 fn ball_mpfr_contains_exact_value() {
366 let domain = RealBallDomain;
367 let a = RealBall::from_f64(1.0);
368 let b = RealBall::from_f64(2.0);
369 let sum = domain.add(&a, &b);
370 let lo = sum.lower();
371 let hi = sum.upper();
372 assert!(lo <= rug::Float::with_val(BALL_PRECISION, 3.0));
373 assert!(hi >= rug::Float::with_val(BALL_PRECISION, 3.0));
374 }
375
376 #[test]
377 fn ball_mpfr_inverse_excludes_zero() {
378 let domain = RealBallDomain;
379 let a = RealBall::from_f64(2.0);
380 let inv = domain.inv(&a).expect("invertible");
381 let prod = domain.mul(&a, &inv);
382 let lo = prod.lower();
383 let hi = prod.upper();
384 assert!(lo <= rug::Float::with_val(BALL_PRECISION, 1.0));
385 assert!(hi >= rug::Float::with_val(BALL_PRECISION, 1.0));
386 }
387 }
388}