1use axiolid_core::Point2;
31use axiolid_exact::{certify, Arith, Dyadic, Interval, SignExpr};
32use axiolid_guarantees::Sign;
33
34#[derive(Debug, Clone, Copy, PartialEq)]
36pub struct EnclosingCircle {
37 pub centre: Point2,
39 pub radius: f64,
41}
42
43#[derive(Debug, Clone, PartialEq)]
45#[non_exhaustive]
46pub struct CircleEvidence {
47 pub support: Vec<usize>,
50 pub error: f64,
55}
56
57#[derive(Debug, Clone, PartialEq)]
59#[non_exhaustive]
60pub struct MinimumCircle {
61 pub circle: EnclosingCircle,
63 pub evidence: CircleEvidence,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69#[non_exhaustive]
70pub enum CircleError {
71 Empty,
73 NonFinite,
75}
76
77struct Diametral {
80 a: Point2,
81 b: Point2,
82 p: Point2,
83}
84
85impl SignExpr for Diametral {
86 fn sign_in<T: Arith>(&self) -> Option<Sign> {
87 let f = T::from_f64;
88 let (px, py) = (f(self.p.x), f(self.p.y));
89 let ax = px.sub(&f(self.a.x));
90 let ay = py.sub(&f(self.a.y));
91 let bx = px.sub(&f(self.b.x));
92 let by = py.sub(&f(self.b.y));
93 ax.mul(&bx).add(&ay.mul(&by)).sign()
94 }
95}
96
97struct Orient {
99 a: Point2,
100 b: Point2,
101 c: Point2,
102}
103
104impl SignExpr for Orient {
105 fn sign_in<T: Arith>(&self) -> Option<Sign> {
106 let f = T::from_f64;
107 let (ux, uy) = (f(self.b.x).sub(&f(self.a.x)), f(self.b.y).sub(&f(self.a.y)));
108 let (vx, vy) = (f(self.c.x).sub(&f(self.a.x)), f(self.c.y).sub(&f(self.a.y)));
109 ux.mul(&vy).sub(&uy.mul(&vx)).sign()
110 }
111}
112
113struct InCircle {
116 a: Point2,
117 b: Point2,
118 c: Point2,
119 p: Point2,
120}
121
122impl SignExpr for InCircle {
123 fn sign_in<T: Arith>(&self) -> Option<Sign> {
124 let f = T::from_f64;
125 let row = |q: Point2| {
126 let x = f(q.x).sub(&f(self.p.x));
127 let y = f(q.y).sub(&f(self.p.y));
128 let l = x.mul(&x).add(&y.mul(&y));
129 (x, y, l)
130 };
131 let (ax, ay, al) = row(self.a);
132 let (bx, by, bl) = row(self.b);
133 let (cx, cy, cl) = row(self.c);
134 let minor = |x1: &T, y1: &T, x2: &T, y2: &T| x1.mul(y2).sub(&y1.mul(x2));
135 al.mul(&minor(&bx, &by, &cx, &cy))
136 .sub(&bl.mul(&minor(&ax, &ay, &cx, &cy)))
137 .add(&cl.mul(&minor(&ax, &ay, &bx, &by)))
138 .sign()
139 }
140}
141
142fn sign<E: SignExpr>(e: &E) -> Sign {
144 certify(e).unwrap_or(Sign::Zero)
145}
146
147fn inside(points: &[Point2], support: &[usize], p: Point2) -> bool {
149 match *support {
150 [a] => points[a] == p,
151 [a, b] => {
152 sign(&Diametral {
153 a: points[a],
154 b: points[b],
155 p,
156 }) != Sign::Positive
157 }
158 [a, b, c] => {
159 let (a, b, c) = (points[a], points[b], points[c]);
160 let turn = sign(&Orient { a, b, c });
161 let side = sign(&InCircle { a, b, c, p });
162 side == Sign::Zero || side == turn
163 }
164 _ => unreachable!("a circle has one to three support points"),
165 }
166}
167
168fn visiting_order(n: usize) -> Vec<usize> {
172 let mut order: Vec<usize> = (0..n).collect();
173 let mut state: u64 = 0x9e37_79b9_7f4a_7c15;
174 let mut next = || {
175 state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
176 let mut z = state;
177 z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
178 z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
179 z ^ (z >> 31)
180 };
181 for i in (1..n).rev() {
182 let j = (next() % (i as u64 + 1)) as usize;
183 order.swap(i, j);
184 }
185 order
186}
187
188fn welzl(points: &[Point2]) -> Vec<usize> {
190 let order = visiting_order(points.len());
191 let mut support = vec![order[0]];
192 for i in 1..order.len() {
193 let pi = order[i];
194 if inside(points, &support, points[pi]) {
195 continue;
196 }
197 support = vec![pi];
199 for j in 0..i {
200 let pj = order[j];
201 if inside(points, &support, points[pj]) {
202 continue;
203 }
204 support = vec![pi, pj];
206 for &pk in &order[..j] {
207 if !inside(points, &support, points[pk]) {
208 support = vec![pi, pj, pk];
209 }
210 }
211 }
212 }
213 support
214}
215
216fn exact(x: f64) -> Dyadic {
217 Dyadic::from_f64(x)
218}
219
220fn centre_enclosure(points: &[Point2], support: &[usize]) -> [Interval; 2] {
222 match *support {
223 [a] => [Interval::point(points[a].x), Interval::point(points[a].y)],
224 [a, b] => {
225 let half = exact(0.5);
226 let mid = |s: f64, t: f64| exact(s).add(&exact(t)).mul(&half).enclosure();
227 [mid(points[a].x, points[b].x), mid(points[a].y, points[b].y)]
228 }
229 [a, b, c] => {
230 let (a, b, c) = (points[a], points[b], points[c]);
231 let (ux, uy) = (exact(b.x).sub(&exact(a.x)), exact(b.y).sub(&exact(a.y)));
232 let (vx, vy) = (exact(c.x).sub(&exact(a.x)), exact(c.y).sub(&exact(a.y)));
233 let uu = ux.mul(&ux).add(&uy.mul(&uy));
234 let vv = vx.mul(&vx).add(&vy.mul(&vy));
235 let d = ux.mul(&vy).sub(&uy.mul(&vx)).mul(&exact(2.0)).enclosure();
236 let nx = uu.mul(&vy).sub(&vv.mul(&uy)).enclosure();
237 let ny = vv.mul(&ux).sub(&uu.mul(&vx)).enclosure();
238 [
239 Interval::point(a.x).add(&nx.quotient(d)),
240 Interval::point(a.y).add(&ny.quotient(d)),
241 ]
242 }
243 _ => unreachable!("a circle has one to three support points"),
244 }
245}
246
247fn distance_bounds(p: Point2, centre: &[Interval; 2]) -> (f64, f64) {
250 let dx = Interval::point(p.x).sub(¢re[0]);
251 let dy = Interval::point(p.y).sub(¢re[1]);
252 let squared = dx.mul(&dx).add(&dy.mul(&dy));
253 let low = squared.lo().max(0.0).sqrt().next_down().max(0.0);
255 (low, squared.hi().sqrt().next_up())
256}
257
258pub fn minimum_enclosing_circle(points: &[Point2]) -> Result<MinimumCircle, CircleError> {
265 if points.is_empty() {
266 return Err(CircleError::Empty);
267 }
268 if !points.iter().all(|p| p.is_finite()) {
269 return Err(CircleError::NonFinite);
270 }
271 let mut support = welzl(points);
272 support.sort_unstable();
273 if let [a] = *support {
274 return Ok(MinimumCircle {
275 circle: EnclosingCircle {
276 centre: points[a],
277 radius: 0.0,
278 },
279 evidence: CircleEvidence {
280 support,
281 error: 0.0,
282 },
283 });
284 }
285 let enclosure = centre_enclosure(points, &support);
286 let mid = |i: Interval| i.lo() + 0.5 * (i.hi() - i.lo());
287 let centre = Point2::new(mid(enclosure[0]), mid(enclosure[1]));
288 let gap = |i: Interval, m: f64| (i.hi() - m).max(m - i.lo());
291 let centre_error = (gap(enclosure[0], centre.x) + gap(enclosure[1], centre.y)).next_up();
292 let (radius_low, radius_high) = distance_bounds(points[support[0]], &enclosure);
293 let mut radius = (radius_high + centre_error).next_up();
294 let at = [Interval::point(centre.x), Interval::point(centre.y)];
297 for &p in points {
298 radius = radius.max(distance_bounds(p, &at).1);
299 }
300 Ok(MinimumCircle {
301 circle: EnclosingCircle { centre, radius },
302 evidence: CircleEvidence {
303 support,
304 error: (radius - radius_low).next_up(),
305 },
306 })
307}