holos_tda/coverage/
model.rs1use std::collections::BTreeSet;
2
3use num_rational::BigRational;
4
5use crate::field::{MODULUS_LIMIT, is_prime};
6use crate::{Error, KineticEdgeKey, Result};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[non_exhaustive]
11pub struct CoverageLimits {
12 pub max_vertices: usize,
14 pub max_edges: usize,
16 pub max_triangles: usize,
18 pub max_matrix_entries: usize,
20 pub max_states: usize,
22 pub max_actions: usize,
24 pub max_failure_sets: usize,
26}
27
28impl Default for CoverageLimits {
29 fn default() -> Self {
30 Self {
31 max_vertices: 1_000_000,
32 max_edges: 20_000_000,
33 max_triangles: 1_000_000,
34 max_matrix_entries: 100_000_000,
35 max_states: 4_096,
36 max_actions: 65_536,
37 max_failure_sets: 10_000_000,
38 }
39 }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq)]
51pub struct PlanarCoverageModel {
52 broadcast_radius: f64,
53 sensing_radius: f64,
54}
55
56impl PlanarCoverageModel {
57 pub fn new(broadcast_radius: f64, sensing_radius: f64) -> Result<Self> {
62 if !broadcast_radius.is_finite()
63 || !sensing_radius.is_finite()
64 || broadcast_radius <= 0.0
65 || sensing_radius <= 0.0
66 {
67 return Err(Error::InvalidInput(
68 "coverage radii must be finite and positive".into(),
69 ));
70 }
71 let broadcast = rational(broadcast_radius);
72 let sensing = rational(sensing_radius);
73 if BigRational::from_integer(3.into()) * &sensing * sensing < broadcast.clone() * broadcast
74 {
75 return Err(Error::InvalidInput(
76 "coverage radii violate 3 * sensing_radius^2 >= broadcast_radius^2".into(),
77 ));
78 }
79 Ok(Self {
80 broadcast_radius,
81 sensing_radius,
82 })
83 }
84
85 pub fn broadcast_radius(self) -> f64 {
87 self.broadcast_radius
88 }
89
90 pub fn sensing_radius(self) -> f64 {
92 self.sensing_radius
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct CoverageFence {
103 vertices: Vec<usize>,
104}
105
106impl CoverageFence {
107 pub fn new(vertices: Vec<usize>) -> Result<Self> {
109 if vertices.len() < 3 {
110 return Err(Error::InvalidInput(
111 "coverage fence requires at least three vertices".into(),
112 ));
113 }
114 if vertices.iter().copied().collect::<BTreeSet<_>>().len() != vertices.len() {
115 return Err(Error::InvalidInput(
116 "coverage fence repeats a vertex".into(),
117 ));
118 }
119 let forward = rotate_to_minimum(&vertices);
120 let mut reversed = vertices;
121 reversed.reverse();
122 let reversed = rotate_to_minimum(&reversed);
123 Ok(Self {
124 vertices: forward.min(reversed),
125 })
126 }
127
128 pub fn vertices(&self) -> &[usize] {
130 &self.vertices
131 }
132
133 pub(crate) fn edges(&self) -> Vec<KineticEdgeKey> {
134 cycle_pairs(&self.vertices)
135 .map(|(u, v)| KineticEdgeKey::new(u, v))
136 .collect()
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
142pub struct CoverageTriangleTerm {
143 pub a: usize,
145 pub b: usize,
147 pub c: usize,
149 pub coefficient: u32,
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct CoverageEvaluation {
156 pub criterion_holds: bool,
158 pub witness: Vec<CoverageTriangleTerm>,
160 pub active_vertices: usize,
162 pub active_edges: usize,
164 pub active_triangles: usize,
166}
167
168pub(crate) fn cycle_pairs(vertices: &[usize]) -> impl Iterator<Item = (usize, usize)> + '_ {
169 vertices
170 .iter()
171 .copied()
172 .zip(vertices.iter().copied().cycle().skip(1))
173 .take(vertices.len())
174}
175
176fn rotate_to_minimum(values: &[usize]) -> Vec<usize> {
177 let position = values
178 .iter()
179 .enumerate()
180 .min_by_key(|(_, value)| **value)
181 .map(|(position, _)| position)
182 .unwrap_or(0);
183 values[position..]
184 .iter()
185 .chain(&values[..position])
186 .copied()
187 .collect()
188}
189
190pub(crate) fn validate_field(modulus: u32) -> Result<()> {
191 if u64::from(modulus) >= MODULUS_LIMIT || !is_prime(u64::from(modulus)) {
192 return Err(Error::InvalidInput(
193 "coverage modulus must be a supported prime".into(),
194 ));
195 }
196 Ok(())
197}
198
199fn rational(value: f64) -> BigRational {
200 BigRational::from_float(value).expect("validated finite f64 has an exact rational form")
201}