1use super::functions::*;
5
6#[allow(dead_code)]
7#[derive(Debug, Clone)]
8pub struct IntrinsicVolume {
9 pub ambient_dimension: usize,
10 pub index: usize,
11 pub formula: String,
12}
13#[allow(dead_code)]
14impl IntrinsicVolume {
15 pub fn new(n: usize, i: usize) -> Self {
16 let formula = format!("V_{i}(K) = C(n,{i}) × (mixed volume of K and B^n)");
17 IntrinsicVolume {
18 ambient_dimension: n,
19 index: i,
20 formula,
21 }
22 }
23 pub fn kinematic_formula(&self) -> String {
24 format!("Kinematic formula: ∫ χ(K∩gL) dg = Σ_k c_{{n,k}} V_{{n-k}}(K) V_k(L)")
25 }
26 pub fn steiner_formula_contribution(&self) -> String {
27 format!(
28 "Steiner: V(K + εB) = sum_k eps^k C(n,k) V_(n-k)(K), n={}, i={}",
29 self.ambient_dimension, self.index
30 )
31 }
32}
33#[allow(dead_code)]
34#[derive(Debug, Clone)]
35pub struct LogConcaveSequence {
36 pub coefficients: Vec<f64>,
37 pub is_log_concave: bool,
38 pub is_ultra_log_concave: bool,
39 pub has_real_roots_conjecture: bool,
40}
41#[allow(dead_code)]
42impl LogConcaveSequence {
43 pub fn new(coeffs: Vec<f64>) -> Self {
44 let lc = Self::check_log_concave(&coeffs);
45 LogConcaveSequence {
46 is_ultra_log_concave: false,
47 coefficients: coeffs,
48 is_log_concave: lc,
49 has_real_roots_conjecture: false,
50 }
51 }
52 fn check_log_concave(c: &[f64]) -> bool {
53 let n = c.len();
54 for i in 1..n.saturating_sub(1) {
55 if c[i] * c[i] < c[i - 1] * c[i + 1] {
56 return false;
57 }
58 }
59 true
60 }
61 pub fn with_ultra_log_concave(mut self) -> Self {
62 self.is_ultra_log_concave = true;
63 self
64 }
65 pub fn mason_conjecture_statement(&self) -> String {
66 "Mason's conjecture: independence numbers of matroids form ultra-log-concave sequences"
67 .to_string()
68 }
69 pub fn branden_huh_lorentzian_connection(&self) -> String {
70 "Brändén-Huh (2020): Lorentzian polynomials → log-concavity of matroid sequences"
71 .to_string()
72 }
73}
74#[allow(dead_code)]
75#[derive(Debug, Clone)]
76pub struct ZonotopeData {
77 pub generators: Vec<Vec<f64>>,
78 pub ambient_dim: usize,
79 pub num_generators: usize,
80}
81#[allow(dead_code)]
82impl ZonotopeData {
83 pub fn new(generators: Vec<Vec<f64>>) -> Self {
84 let n = generators.first().map_or(0, |g| g.len());
85 let k = generators.len();
86 ZonotopeData {
87 generators,
88 ambient_dim: n,
89 num_generators: k,
90 }
91 }
92 pub fn num_faces_upper_bound(&self) -> usize {
93 let k = self.num_generators;
94 let n = self.ambient_dim;
95 let mut count = 2;
96 for j in 0..n.min(k) {
97 let mut c = 1usize;
98 for i in 0..j {
99 c = c * (k - 1 - i) / (i + 1);
100 }
101 count += 2 * c;
102 }
103 count
104 }
105 pub fn volume_formula(&self) -> String {
106 format!(
107 "Vol(Z) = sum over n-subsets of |det(g_{{i1}},...,g_{{in}})| (n={})",
108 self.ambient_dim
109 )
110 }
111 pub fn is_centrally_symmetric(&self) -> bool {
112 true
113 }
114 pub fn tilings_of_space_description(&self) -> String {
115 "Zonotopes tile space if and only if the generators form a totally unimodular matrix"
116 .to_string()
117 }
118}
119#[derive(Debug, Clone)]
121pub struct Zonotope {
122 pub centre: Vec<f64>,
124 pub generators: Vec<Vec<f64>>,
126}
127impl Zonotope {
128 pub fn new(centre: Vec<f64>, generators: Vec<Vec<f64>>) -> Self {
130 Self { centre, generators }
131 }
132 pub fn contains_approx(&self, x: &[f64]) -> bool {
135 let d = self.centre.len();
136 let diff: Vec<f64> = x
137 .iter()
138 .zip(self.centre.iter())
139 .map(|(xi, ci)| xi - ci)
140 .collect();
141 let sum_abs: Vec<f64> = (0..d)
142 .map(|j| self.generators.iter().map(|g| g[j].abs()).sum::<f64>())
143 .collect();
144 diff.iter()
145 .zip(sum_abs.iter())
146 .all(|(di, &si)| di.abs() <= si + 1e-10)
147 }
148 pub fn volume_2d(&self) -> f64 {
151 if self.generators.len() < 2 {
152 return 0.0;
153 }
154 let mut vol = 0.0_f64;
155 let m = self.generators.len();
156 for i in 0..m {
157 for j in (i + 1)..m {
158 let gi = &self.generators[i];
159 let gj = &self.generators[j];
160 if gi.len() >= 2 && gj.len() >= 2 {
161 let cross = (gi[0] * gj[1] - gi[1] * gj[0]).abs();
162 vol += cross;
163 }
164 }
165 }
166 4.0 * vol
167 }
168 pub fn num_vertices_upper_bound(&self) -> usize {
170 let m = self.generators.len();
171 let d = self.centre.len();
172 if d == 0 || m == 0 {
173 return 1;
174 }
175 2 * (0..d)
176 .map(|k| binomial(m.saturating_sub(1), k))
177 .sum::<usize>()
178 }
179}
180#[derive(Debug, Clone)]
182pub struct VPolytope {
183 pub vertices: Vec<Vec<f64>>,
185 pub dim: usize,
187}
188impl VPolytope {
189 pub fn new(vertices: Vec<Vec<f64>>) -> Self {
191 let dim = vertices.first().map_or(0, |v| v.len());
192 Self { vertices, dim }
193 }
194 pub fn vertices(&self) -> &[Vec<f64>] {
196 &self.vertices
197 }
198 pub fn f_vector(&self) -> Vec<usize> {
201 vec![self.vertices.len()]
202 }
203 pub fn is_simplicial(&self) -> bool {
205 !self.vertices.is_empty() && self.vertices.len() == self.dim + 1
206 }
207}
208#[derive(Debug, Clone)]
210pub struct ConvexFunction {
211 pub dim: usize,
213}
214impl ConvexFunction {
215 pub fn new(dim: usize) -> Self {
217 Self { dim }
218 }
219 pub fn check_convexity<F>(&self, f: F, x: &[f64], y: &[f64], lambda: f64) -> bool
221 where
222 F: Fn(&[f64]) -> f64,
223 {
224 let mid: Vec<f64> = x
225 .iter()
226 .zip(y.iter())
227 .map(|(xi, yi)| lambda * xi + (1.0 - lambda) * yi)
228 .collect();
229 f(&mid) <= lambda * f(x) + (1.0 - lambda) * f(y) + 1e-10
230 }
231 pub fn subgradient<F>(&self, f: F, x: &[f64], eps: f64) -> Vec<f64>
233 where
234 F: Fn(&[f64]) -> f64,
235 {
236 let fx = f(x);
237 let mut grad = vec![0.0_f64; x.len()];
238 for i in 0..x.len() {
239 let mut xp = x.to_vec();
240 xp[i] += eps;
241 grad[i] = (f(&xp) - fx) / eps;
242 }
243 grad
244 }
245}
246#[derive(Debug, Clone)]
248pub struct FacePoset {
249 pub f_vec: Vec<usize>,
251}
252impl FacePoset {
253 pub fn new(f_vec: Vec<usize>) -> Self {
255 Self { f_vec }
256 }
257 pub fn f_vector(&self) -> &[usize] {
259 &self.f_vec
260 }
261 pub fn euler_characteristic(&self) -> i64 {
263 self.f_vec
264 .iter()
265 .enumerate()
266 .map(|(k, &fk)| if k % 2 == 0 { fk as i64 } else { -(fk as i64) })
267 .sum()
268 }
269}
270#[derive(Debug, Clone)]
272pub struct TilingTheory {
273 pub dim: usize,
275 pub lattice: Vec<Vec<f64>>,
277}
278impl TilingTheory {
279 pub fn new(dim: usize, lattice: Vec<Vec<f64>>) -> Self {
281 Self { dim, lattice }
282 }
283 pub fn is_valid_lattice(&self) -> bool {
285 if self.lattice.len() < 2 || self.lattice[0].len() < 2 {
286 return !self.lattice.is_empty();
287 }
288 let det = self.lattice[0][0] * self.lattice[1][1] - self.lattice[0][1] * self.lattice[1][0];
289 det.abs() > 1e-12
290 }
291 pub fn fundamental_volume_2d(&self) -> f64 {
293 if self.lattice.len() < 2 || self.lattice[0].len() < 2 {
294 return 0.0;
295 }
296 (self.lattice[0][0] * self.lattice[1][1] - self.lattice[0][1] * self.lattice[1][0]).abs()
297 }
298}
299#[derive(Debug, Clone)]
307pub struct HellyTheoremChecker {
308 pub dim: usize,
310 pub boxes: Vec<(Vec<f64>, Vec<f64>)>,
312}
313impl HellyTheoremChecker {
314 pub fn new(dim: usize) -> Self {
316 Self {
317 dim,
318 boxes: Vec::new(),
319 }
320 }
321 pub fn add_box(&mut self, lo: Vec<f64>, hi: Vec<f64>) {
323 self.boxes.push((lo, hi));
324 }
325 fn boxes_intersect(lo1: &[f64], hi1: &[f64], lo2: &[f64], hi2: &[f64]) -> bool {
327 lo1.iter()
328 .zip(hi1.iter())
329 .zip(lo2.iter())
330 .zip(hi2.iter())
331 .all(|(((l1, h1), l2), h2)| l1 <= h2 && l2 <= h1)
332 }
333 pub fn all_intersect(&self) -> bool {
336 if self.boxes.is_empty() {
337 return true;
338 }
339 let d = self.dim;
340 let mut lo = vec![f64::NEG_INFINITY; d];
341 let mut hi = vec![f64::INFINITY; d];
342 for (bl, bh) in &self.boxes {
343 for j in 0..d.min(bl.len()).min(bh.len()) {
344 if bl[j] > lo[j] {
345 lo[j] = bl[j];
346 }
347 if bh[j] < hi[j] {
348 hi[j] = bh[j];
349 }
350 }
351 }
352 lo.iter().zip(hi.iter()).all(|(l, h)| l <= h)
353 }
354 pub fn verify_helly_condition_1d(&self) -> bool {
357 let n = self.boxes.len();
358 for i in 0..n {
359 for j in (i + 1)..n {
360 let (li, hi_) = &self.boxes[i];
361 let (lj, hj) = &self.boxes[j];
362 if !Self::boxes_intersect(li, hi_, lj, hj) {
363 return false;
364 }
365 }
366 }
367 true
368 }
369 pub fn fraction_pairwise_intersecting(&self) -> f64 {
373 let n = self.boxes.len();
374 if n < 2 {
375 return 1.0;
376 }
377 let total = n * (n - 1) / 2;
378 let mut count = 0_usize;
379 for i in 0..n {
380 for j in (i + 1)..n {
381 let (li, hi_) = &self.boxes[i];
382 let (lj, hj) = &self.boxes[j];
383 if Self::boxes_intersect(li, hi_, lj, hj) {
384 count += 1;
385 }
386 }
387 }
388 count as f64 / total as f64
389 }
390 pub fn has_radon_partition_1d(points: &[f64]) -> bool {
395 if points.len() < 3 {
396 return false;
397 }
398 let mut sorted = points.to_vec();
399 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
400 true
401 }
402}
403#[derive(Debug, Clone)]
409pub struct JohnEllipsoidApprox {
410 pub centre: Vec<f64>,
412 pub axes: Vec<f64>,
414}
415impl JohnEllipsoidApprox {
416 pub fn from_points(points: &[Vec<f64>]) -> Option<Self> {
419 if points.is_empty() {
420 return None;
421 }
422 let d = points[0].len();
423 let n = points.len() as f64;
424 let mut centre = vec![0.0_f64; d];
425 for p in points {
426 for j in 0..d {
427 centre[j] += p[j];
428 }
429 }
430 for c in centre.iter_mut() {
431 *c /= n;
432 }
433 let mut axes = vec![0.0_f64; d];
434 for p in points {
435 for j in 0..d {
436 axes[j] += (p[j] - centre[j]).powi(2);
437 }
438 }
439 for a in axes.iter_mut() {
440 *a = (*a / n).sqrt().max(1e-12);
441 }
442 Some(Self { centre, axes })
443 }
444 pub fn contains(&self, x: &[f64]) -> bool {
446 let dist2: f64 = x
447 .iter()
448 .zip(self.centre.iter())
449 .zip(self.axes.iter())
450 .map(|((xi, ci), ai)| ((xi - ci) / ai).powi(2))
451 .sum();
452 dist2 <= 1.0 + 1e-10
453 }
454 pub fn volume(&self) -> f64 {
457 let d = self.axes.len();
458 let prod: f64 = self.axes.iter().product();
459 match d {
460 1 => 2.0 * prod,
461 2 => std::f64::consts::PI * prod,
462 3 => 4.0 / 3.0 * std::f64::consts::PI * prod,
463 _ => {
464 let half_d = d as f64 / 2.0;
465 let kappa = std::f64::consts::PI.powf(half_d) / stirling_gamma(half_d + 1.0);
466 kappa * prod
467 }
468 }
469 }
470 pub fn johns_outer_bound(&self) -> Vec<f64> {
473 let d = self.axes.len() as f64;
474 self.axes.iter().map(|&a| a * d).collect()
475 }
476}
477#[derive(Debug, Clone)]
479pub struct MixedVolume {
480 pub dim: usize,
482}
483impl MixedVolume {
484 pub fn new(dim: usize) -> Self {
486 Self { dim }
487 }
488 pub fn estimate_volume_monte_carlo(
490 &self,
491 vertices: &[Vec<f64>],
492 n_samples: usize,
493 seed: u64,
494 ) -> f64 {
495 if vertices.is_empty() || self.dim == 0 {
496 return 0.0;
497 }
498 let mut lo = vec![f64::INFINITY; self.dim];
499 let mut hi = vec![f64::NEG_INFINITY; self.dim];
500 for v in vertices {
501 for j in 0..self.dim {
502 if v[j] < lo[j] {
503 lo[j] = v[j];
504 }
505 if v[j] > hi[j] {
506 hi[j] = v[j];
507 }
508 }
509 }
510 let bbox_vol: f64 = lo.iter().zip(hi.iter()).map(|(l, h)| h - l).product();
511 let mut rng_state = seed;
512 let mut inside = 0_usize;
513 for _ in 0..n_samples {
514 rng_state = rng_state
515 .wrapping_mul(6364136223846793005)
516 .wrapping_add(1442695040888963407);
517 let point: Vec<f64> = (0..self.dim)
518 .map(|j| {
519 rng_state = rng_state
520 .wrapping_mul(6364136223846793005)
521 .wrapping_add(1442695040888963407);
522 let t = (rng_state >> 33) as f64 / (u32::MAX as f64);
523 lo[j] + t * (hi[j] - lo[j])
524 })
525 .collect();
526 if is_in_convex_hull(vertices, &point) {
527 inside += 1;
528 }
529 }
530 bbox_vol * inside as f64 / n_samples as f64
531 }
532 pub fn check_brunn_minkowski(&self, vol_a: f64, vol_b: f64, vol_apb: f64) -> bool {
534 let n = self.dim as f64;
535 let lhs = vol_apb.powf(1.0 / n);
536 let rhs = vol_a.powf(1.0 / n) + vol_b.powf(1.0 / n);
537 lhs >= rhs - 1e-10
538 }
539 pub fn check_isoperimetric_2d(area: f64, perimeter: f64) -> bool {
541 4.0 * std::f64::consts::PI * area <= perimeter * perimeter + 1e-10
542 }
543}
544#[allow(dead_code)]
545#[derive(Debug, Clone)]
546pub struct ConvexValuation {
547 pub name: String,
548 pub is_continuous: bool,
549 pub is_translation_invariant: bool,
550 pub is_rotation_invariant: bool,
551 pub homogeneity_degree: Option<usize>,
552}
553#[allow(dead_code)]
554impl ConvexValuation {
555 pub fn euler_characteristic() -> Self {
556 ConvexValuation {
557 name: "Euler characteristic χ".to_string(),
558 is_continuous: true,
559 is_translation_invariant: true,
560 is_rotation_invariant: true,
561 homogeneity_degree: Some(0),
562 }
563 }
564 pub fn volume(dim: usize) -> Self {
565 ConvexValuation {
566 name: format!("Volume V_{}", dim),
567 is_continuous: true,
568 is_translation_invariant: true,
569 is_rotation_invariant: true,
570 homogeneity_degree: Some(dim),
571 }
572 }
573 pub fn surface_area() -> Self {
574 ConvexValuation {
575 name: "Surface area S".to_string(),
576 is_continuous: true,
577 is_translation_invariant: true,
578 is_rotation_invariant: true,
579 homogeneity_degree: Some(1),
580 }
581 }
582 pub fn hadwiger_theorem(&self) -> String {
583 "Hadwiger: any continuous, rigid-motion invariant valuation on K^n = linear combo of V_0,...,V_n"
584 .to_string()
585 }
586 pub fn mcmullen_decomposition(&self) -> String {
587 format!(
588 "McMullen: {} decomposes into homogeneous components of degrees 0..n",
589 self.name
590 )
591 }
592}
593#[derive(Debug, Clone)]
595pub struct ConvexSet {
596 pub dim: usize,
598 pub vertices: Vec<Vec<f64>>,
600}
601impl ConvexSet {
602 pub fn new(dim: usize, vertices: Vec<Vec<f64>>) -> Self {
604 Self { dim, vertices }
605 }
606 pub fn projection(&self, x: &[f64]) -> Vec<f64> {
609 self.vertices
610 .iter()
611 .min_by(|a, b| {
612 let da: f64 = a.iter().zip(x).map(|(ai, xi)| (ai - xi).powi(2)).sum();
613 let db: f64 = b.iter().zip(x).map(|(bi, xi)| (bi - xi).powi(2)).sum();
614 da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
615 })
616 .cloned()
617 .unwrap_or_else(|| vec![0.0; self.dim])
618 }
619 pub fn is_polyhedral(&self, a: &[Vec<f64>], b: &[f64]) -> bool {
621 self.vertices.iter().all(|v| {
622 a.iter().zip(b.iter()).all(|(row, &bi)| {
623 let dot: f64 = row.iter().zip(v.iter()).map(|(aij, vj)| aij * vj).sum();
624 dot <= bi + 1e-10
625 })
626 })
627 }
628 pub fn support_function(&self, y: &[f64]) -> f64 {
630 self.vertices
631 .iter()
632 .map(|v| v.iter().zip(y.iter()).map(|(vi, yi)| vi * yi).sum::<f64>())
633 .fold(f64::NEG_INFINITY, f64::max)
634 }
635 pub fn minkowski_sum(&self, other: &ConvexSet) -> ConvexSet {
637 let mut sums = Vec::new();
638 for a in &self.vertices {
639 for b in &other.vertices {
640 let s: Vec<f64> = a.iter().zip(b.iter()).map(|(ai, bi)| ai + bi).collect();
641 sums.push(s);
642 }
643 }
644 ConvexSet {
645 dim: self.dim,
646 vertices: sums,
647 }
648 }
649}
650#[derive(Debug, Clone)]
652pub struct DelaunayTriangulation {
653 pub sites: Vec<Vec<f64>>,
655 pub triangles: Vec<[usize; 3]>,
657}
658impl DelaunayTriangulation {
659 pub fn new(sites: Vec<Vec<f64>>, triangles: Vec<[usize; 3]>) -> Self {
661 Self { sites, triangles }
662 }
663 pub fn check_delaunay_property(&self) -> bool {
666 for &[i, j, k] in &self.triangles {
667 let p = &self.sites[i];
668 let q = &self.sites[j];
669 let r = &self.sites[k];
670 if p.len() < 2 || q.len() < 2 || r.len() < 2 {
671 continue;
672 }
673 let ax = p[0] - r[0];
674 let ay = p[1] - r[1];
675 let bx = q[0] - r[0];
676 let by = q[1] - r[1];
677 let d = 2.0 * (ax * by - ay * bx);
678 if d.abs() < 1e-12 {
679 continue;
680 }
681 let ux = (by * (ax * ax + ay * ay) - ay * (bx * bx + by * by)) / d;
682 let uy = (ax * (bx * bx + by * by) - bx * (ax * ax + ay * ay)) / d;
683 let cx = r[0] + ux;
684 let cy = r[1] + uy;
685 let rad2 = ux * ux + uy * uy;
686 for (m, s) in self.sites.iter().enumerate() {
687 if m == i || m == j || m == k || s.len() < 2 {
688 continue;
689 }
690 let dx = s[0] - cx;
691 let dy = s[1] - cy;
692 if dx * dx + dy * dy < rad2 - 1e-10 {
693 return false;
694 }
695 }
696 }
697 true
698 }
699}
700#[allow(dead_code)]
701#[derive(Debug, Clone)]
702pub struct LorentzianPolynomial {
703 pub degree: usize,
704 pub num_variables: usize,
705 pub is_strictly_lorentzian: bool,
706 pub is_m_convex: bool,
707}
708#[allow(dead_code)]
709impl LorentzianPolynomial {
710 pub fn new(deg: usize, nvars: usize) -> Self {
711 LorentzianPolynomial {
712 degree: deg,
713 num_variables: nvars,
714 is_strictly_lorentzian: false,
715 is_m_convex: false,
716 }
717 }
718 pub fn hessian_is_psd(&self) -> bool {
719 true
720 }
721 pub fn implies_log_concavity(&self) -> bool {
722 true
723 }
724 pub fn connection_to_hodge_theory(&self) -> String {
725 "Adiprasito-Huh-Katz: Hodge theory for combinatorial geometries → log-concavity".to_string()
726 }
727}
728#[allow(dead_code)]
729#[derive(Debug, Clone)]
730pub struct LatticePolytope {
731 pub vertices: Vec<Vec<i64>>,
732 pub dimension: usize,
733 pub is_reflexive: bool,
734 pub ehrhart_polynomial: Vec<i64>,
735}
736#[allow(dead_code)]
737impl LatticePolytope {
738 pub fn new(vertices: Vec<Vec<i64>>) -> Self {
739 let dim = vertices.first().map_or(0, |v| v.len());
740 LatticePolytope {
741 vertices,
742 dimension: dim,
743 is_reflexive: false,
744 ehrhart_polynomial: vec![],
745 }
746 }
747 pub fn simplex(dim: usize) -> Self {
748 let mut verts = vec![vec![0i64; dim]];
749 for i in 0..dim {
750 let mut e = vec![0i64; dim];
751 e[i] = 1;
752 verts.push(e);
753 }
754 LatticePolytope {
755 vertices: verts,
756 dimension: dim,
757 is_reflexive: dim == 1,
758 ehrhart_polynomial: vec![1; dim + 1],
759 }
760 }
761 pub fn num_lattice_points(&self, dilation: usize) -> usize {
762 let t = dilation;
763 let d = self.dimension;
764 let mut num = 1usize;
765 for i in 0..d {
766 num = num * (t + d - i) / (i + 1);
767 }
768 num
769 }
770 pub fn ehrhart_reciprocity(&self, dilation: i64) -> i64 {
771 let _t = dilation.unsigned_abs();
772 if dilation < 0 {
773 -(self.dimension as i64)
774 } else {
775 self.dimension as i64
776 }
777 }
778 pub fn volume_from_ehrhart(&self) -> f64 {
779 let d = self.dimension;
780 let factorial: usize = (1..=d).product();
781 self.vertices.len() as f64 / factorial as f64
782 }
783}
784#[derive(Debug, Clone)]
786pub struct VoronoiDiagram {
787 pub sites: Vec<Vec<f64>>,
789}
790impl VoronoiDiagram {
791 pub fn new(sites: Vec<Vec<f64>>) -> Self {
793 Self { sites }
794 }
795 pub fn nearest_neighbor(&self, q: &[f64]) -> usize {
797 self.sites
798 .iter()
799 .enumerate()
800 .min_by(|(_, a), (_, b)| {
801 let da: f64 = a.iter().zip(q).map(|(ai, qi)| (ai - qi).powi(2)).sum();
802 let db: f64 = b.iter().zip(q).map(|(bi, qi)| (bi - qi).powi(2)).sum();
803 da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
804 })
805 .map(|(i, _)| i)
806 .unwrap_or(0)
807 }
808 pub fn lloyd_iteration(&self, samples: &[Vec<f64>]) -> Vec<Vec<f64>> {
811 let d = self.sites.first().map_or(0, |v| v.len());
812 let mut sums = vec![vec![0.0_f64; d]; self.sites.len()];
813 let mut counts = vec![0_usize; self.sites.len()];
814 for q in samples {
815 let idx = self.nearest_neighbor(q);
816 for j in 0..d {
817 sums[idx][j] += q[j];
818 }
819 counts[idx] += 1;
820 }
821 sums.iter()
822 .zip(counts.iter())
823 .map(|(s, &c)| {
824 if c == 0 {
825 s.clone()
826 } else {
827 s.iter().map(|&v| v / c as f64).collect()
828 }
829 })
830 .collect()
831 }
832}
833#[derive(Debug, Clone)]
838pub struct MinkowskiSumComputer {
839 pub dim: usize,
841}
842impl MinkowskiSumComputer {
843 pub fn new(dim: usize) -> Self {
845 Self { dim }
846 }
847 pub fn compute(&self, a: &[Vec<f64>], b: &[Vec<f64>]) -> Vec<Vec<f64>> {
850 let mut result = Vec::with_capacity(a.len() * b.len());
851 for va in a {
852 for vb in b {
853 let s: Vec<f64> = va.iter().zip(vb.iter()).map(|(ai, bi)| ai + bi).collect();
854 result.push(s);
855 }
856 }
857 result
858 }
859 pub fn dilate(&self, vertices: &[Vec<f64>], t: f64) -> Vec<Vec<f64>> {
861 vertices
862 .iter()
863 .map(|v| v.iter().map(|xi| xi * t).collect())
864 .collect()
865 }
866 pub fn translate(&self, vertices: &[Vec<f64>], v: &[f64]) -> Vec<Vec<f64>> {
868 vertices
869 .iter()
870 .map(|vert| vert.iter().zip(v.iter()).map(|(xi, vi)| xi + vi).collect())
871 .collect()
872 }
873 pub fn estimate_sum_volume(&self, a: &[Vec<f64>], b: &[Vec<f64>], n_samples: usize) -> f64 {
876 let sum_verts = self.compute(a, b);
877 if sum_verts.is_empty() || self.dim == 0 {
878 return 0.0;
879 }
880 let mv = MixedVolume::new(self.dim);
881 mv.estimate_volume_monte_carlo(&sum_verts, n_samples, 12345)
882 }
883 pub fn support_function_sum(&self, a: &[Vec<f64>], b: &[Vec<f64>], y: &[f64]) -> f64 {
885 let ha = a
886 .iter()
887 .map(|v| v.iter().zip(y.iter()).map(|(vi, yi)| vi * yi).sum::<f64>())
888 .fold(f64::NEG_INFINITY, f64::max);
889 let hb = b
890 .iter()
891 .map(|v| v.iter().zip(y.iter()).map(|(vi, yi)| vi * yi).sum::<f64>())
892 .fold(f64::NEG_INFINITY, f64::max);
893 ha + hb
894 }
895}
896#[derive(Debug, Clone)]
898pub struct PowerDiagram {
899 pub weighted_sites: Vec<(Vec<f64>, f64)>,
901}
902impl PowerDiagram {
903 pub fn new(weighted_sites: Vec<(Vec<f64>, f64)>) -> Self {
905 Self { weighted_sites }
906 }
907 pub fn power_distance(&self, q: &[f64], site_idx: usize) -> f64 {
909 let (ref p, w) = self.weighted_sites[site_idx];
910 let dist2: f64 = p
911 .iter()
912 .zip(q.iter())
913 .map(|(pi, qi)| (pi - qi).powi(2))
914 .sum();
915 dist2 - w
916 }
917 pub fn nearest_power_neighbor(&self, q: &[f64]) -> usize {
919 (0..self.weighted_sites.len())
920 .min_by(|&a, &b| {
921 self.power_distance(q, a)
922 .partial_cmp(&self.power_distance(q, b))
923 .unwrap_or(std::cmp::Ordering::Equal)
924 })
925 .unwrap_or(0)
926 }
927}
928#[derive(Debug, Clone)]
930pub struct CrossPolytope {
931 pub dim: usize,
933}
934impl CrossPolytope {
935 pub fn new(dim: usize) -> Self {
937 Self { dim }
938 }
939 pub fn vertices(&self) -> Vec<Vec<f64>> {
941 let mut verts = Vec::with_capacity(2 * self.dim);
942 for i in 0..self.dim {
943 let mut pos = vec![0.0_f64; self.dim];
944 pos[i] = 1.0;
945 verts.push(pos);
946 let mut neg = vec![0.0_f64; self.dim];
947 neg[i] = -1.0;
948 verts.push(neg);
949 }
950 verts
951 }
952 pub fn contains(&self, x: &[f64]) -> bool {
954 x.iter().map(|xi| xi.abs()).sum::<f64>() <= 1.0 + 1e-10
955 }
956 pub fn volume(&self) -> f64 {
958 let pow2 = 2.0_f64.powi(self.dim as i32);
959 let factorial: f64 = (1..=self.dim).map(|k| k as f64).product();
960 pow2 / factorial
961 }
962 pub fn f_vector(&self) -> Vec<usize> {
964 let n = self.dim;
965 (0..n)
966 .map(|k| {
967 let binom = binomial(n, k + 1);
968 (1_usize << (k + 1)) * binom
969 })
970 .collect()
971 }
972}
973#[derive(Debug, Clone)]
979pub struct MixedVolumeEstimator {
980 pub dim: usize,
982}
983impl MixedVolumeEstimator {
984 pub fn new(dim: usize) -> Self {
986 Self { dim }
987 }
988 pub fn intrinsic_volume_hypercube(&self, j: usize) -> f64 {
993 if j > self.dim {
994 return 0.0;
995 }
996 binomial(self.dim, j) as f64
997 }
998 pub fn quermassintegral_hypercube(&self, j: usize) -> f64 {
1001 self.intrinsic_volume_hypercube(j)
1002 }
1003 pub fn mean_width_hypercube(&self) -> f64 {
1006 if self.dim == 0 {
1007 return 0.0;
1008 }
1009 (self.dim as f64).sqrt()
1010 }
1011 pub fn steiner_coefficients_hypercube(&self) -> Vec<f64> {
1014 (0..=self.dim)
1015 .rev()
1016 .map(|j| binomial(self.dim, j) as f64)
1017 .collect()
1018 }
1019 pub fn check_brunn_minkowski(&self, vol_a: f64, vol_b: f64, vol_sum: f64) -> bool {
1022 if self.dim == 0 {
1023 return true;
1024 }
1025 let n = self.dim as f64;
1026 let lhs = vol_sum.powf(1.0 / n);
1027 let rhs = vol_a.powf(1.0 / n) + vol_b.powf(1.0 / n);
1028 lhs >= rhs - 1e-10
1029 }
1030 pub fn check_alexandrov_fenchel_2d(&self, hk: f64, hl: f64, hkl: f64) -> bool {
1034 hkl * hkl >= hk * hl - 1e-10
1035 }
1036}
1037#[derive(Debug, Clone)]
1039pub struct HPolytope {
1040 pub a: Vec<Vec<f64>>,
1042 pub b: Vec<f64>,
1044 pub dim: usize,
1046}
1047impl HPolytope {
1048 pub fn new(a: Vec<Vec<f64>>, b: Vec<f64>) -> Self {
1050 let dim = a.first().map_or(0, |r| r.len());
1051 Self { a, b, dim }
1052 }
1053 pub fn contains(&self, x: &[f64]) -> bool {
1055 self.a.iter().zip(self.b.iter()).all(|(row, &bi)| {
1056 let dot: f64 = row.iter().zip(x.iter()).map(|(aij, xj)| aij * xj).sum();
1057 dot <= bi + 1e-10
1058 })
1059 }
1060 pub fn facets(&self) -> Vec<(Vec<f64>, f64)> {
1062 self.a
1063 .iter()
1064 .zip(self.b.iter())
1065 .map(|(r, &bi)| (r.clone(), bi))
1066 .collect()
1067 }
1068 pub fn is_simple(&self) -> bool {
1071 self.a.len() >= self.dim
1072 }
1073}
1074#[derive(Debug, Clone)]
1076pub struct CircumscribedSphere {
1077 pub centre: Vec<f64>,
1079 pub radius: f64,
1081}
1082impl CircumscribedSphere {
1083 pub fn compute(points: &[Vec<f64>]) -> Option<Self> {
1086 if points.is_empty() {
1087 return None;
1088 }
1089 let d = points[0].len();
1090 let mut centre = vec![0.0_f64; d];
1091 for p in points {
1092 for j in 0..d {
1093 centre[j] += p[j];
1094 }
1095 }
1096 let n = points.len() as f64;
1097 for c in centre.iter_mut() {
1098 *c /= n;
1099 }
1100 let radius = points
1101 .iter()
1102 .map(|p| {
1103 p.iter()
1104 .zip(centre.iter())
1105 .map(|(pi, ci)| (pi - ci).powi(2))
1106 .sum::<f64>()
1107 .sqrt()
1108 })
1109 .fold(0.0_f64, f64::max);
1110 Some(Self { centre, radius })
1111 }
1112 pub fn contains(&self, p: &[f64]) -> bool {
1114 let dist2: f64 = p
1115 .iter()
1116 .zip(self.centre.iter())
1117 .map(|(pi, ci)| (pi - ci).powi(2))
1118 .sum();
1119 dist2 <= self.radius * self.radius + 1e-10
1120 }
1121}
1122#[derive(Debug, Clone)]
1127pub struct HadwigerNumber {
1128 pub dim: usize,
1130}
1131impl HadwigerNumber {
1132 pub fn new(dim: usize) -> Self {
1134 Self { dim }
1135 }
1136 pub fn upper_bound(&self) -> u64 {
1138 if self.dim == 0 {
1139 return 1;
1140 }
1141 (1_u64 << self.dim).saturating_sub(if self.dim >= 2 { 2 } else { 0 })
1142 }
1143 pub fn cross_polytope_exact(&self) -> u64 {
1145 (1_u64 << (self.dim + 1)).saturating_sub(2)
1146 }
1147}