ifc_lite_geometry/
void_index.rs1use ifc_lite_core::{EntityDecoder, EntityScanner};
15use rustc_hash::FxHashMap;
16
17#[derive(Debug, Clone)]
22pub struct VoidIndex {
23 host_to_voids: FxHashMap<u32, Vec<u32>>,
25 void_to_host: FxHashMap<u32, u32>,
27 relationship_count: usize,
29}
30
31impl VoidIndex {
32 pub fn new() -> Self {
34 Self {
35 host_to_voids: FxHashMap::default(),
36 void_to_host: FxHashMap::default(),
37 relationship_count: 0,
38 }
39 }
40
41 pub fn from_content(content: &str, decoder: &mut EntityDecoder) -> Self {
53 let mut index = Self::new();
54 let mut scanner = EntityScanner::new(content);
55
56 while let Some((_id, type_name, start, end)) = scanner.next_entity() {
57 if type_name == "IFCRELVOIDSELEMENT" {
59 if let Ok(entity) = decoder.decode_at(start, end) {
60 if let (Some(host_id), Some(void_id)) = (entity.get_ref(4), entity.get_ref(5)) {
67 index.add_relationship(host_id, void_id);
68 }
69 }
70 }
71 }
72
73 index
74 }
75
76 pub fn add_relationship(&mut self, host_id: u32, void_id: u32) {
78 self.host_to_voids.entry(host_id).or_default().push(void_id);
79 self.void_to_host.insert(void_id, host_id);
80 self.relationship_count += 1;
81 }
82
83 pub fn get_voids(&self, host_id: u32) -> &[u32] {
91 self.host_to_voids
92 .get(&host_id)
93 .map(|v| v.as_slice())
94 .unwrap_or(&[])
95 }
96
97 pub fn get_host(&self, void_id: u32) -> Option<u32> {
105 self.void_to_host.get(&void_id).copied()
106 }
107
108 pub fn has_voids(&self, host_id: u32) -> bool {
110 self.host_to_voids
111 .get(&host_id)
112 .map(|v| !v.is_empty())
113 .unwrap_or(false)
114 }
115
116 pub fn void_count(&self, host_id: u32) -> usize {
118 self.host_to_voids
119 .get(&host_id)
120 .map(|v| v.len())
121 .unwrap_or(0)
122 }
123
124 pub fn host_count(&self) -> usize {
126 self.host_to_voids.len()
127 }
128
129 pub fn total_relationships(&self) -> usize {
131 self.relationship_count
132 }
133
134 pub fn iter(&self) -> impl Iterator<Item = (u32, &[u32])> {
136 self.host_to_voids.iter().map(|(k, v)| (*k, v.as_slice()))
137 }
138
139 pub fn hosts_with_voids(&self) -> Vec<u32> {
141 self.host_to_voids.keys().copied().collect()
142 }
143
144 pub fn is_void(&self, entity_id: u32) -> bool {
146 self.void_to_host.contains_key(&entity_id)
147 }
148
149 pub fn is_host_with_voids(&self, entity_id: u32) -> bool {
151 self.host_to_voids.contains_key(&entity_id)
152 }
153}
154
155impl Default for VoidIndex {
156 fn default() -> Self {
157 Self::new()
158 }
159}
160
161#[derive(Debug, Clone)]
163pub struct VoidStatistics {
164 pub hosts_with_voids: usize,
166 pub total_voids: usize,
168 pub max_voids_per_host: usize,
170 pub avg_voids_per_host: f64,
172 pub hosts_with_many_voids: usize,
174}
175
176impl VoidStatistics {
177 pub fn from_index(index: &VoidIndex) -> Self {
179 let hosts_with_voids = index.host_count();
180 let total_voids = index.total_relationships();
181
182 let max_voids_per_host = index
183 .host_to_voids
184 .values()
185 .map(|v| v.len())
186 .max()
187 .unwrap_or(0);
188
189 let avg_voids_per_host = if hosts_with_voids > 0 {
190 total_voids as f64 / hosts_with_voids as f64
191 } else {
192 0.0
193 };
194
195 let hosts_with_many_voids = index.host_to_voids.values().filter(|v| v.len() > 10).count();
196
197 Self {
198 hosts_with_voids,
199 total_voids,
200 max_voids_per_host,
201 avg_voids_per_host,
202 hosts_with_many_voids,
203 }
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_void_index_basic() {
213 let mut index = VoidIndex::new();
214
215 index.add_relationship(100, 200);
217 index.add_relationship(100, 201);
218 index.add_relationship(101, 202);
219
220 assert_eq!(index.get_voids(100), &[200, 201]);
222 assert_eq!(index.get_voids(101), &[202]);
223 assert!(index.get_voids(999).is_empty());
224
225 assert_eq!(index.get_host(200), Some(100));
227 assert_eq!(index.get_host(202), Some(101));
228 assert_eq!(index.get_host(999), None);
229
230 assert_eq!(index.void_count(100), 2);
232 assert_eq!(index.void_count(101), 1);
233 assert_eq!(index.host_count(), 2);
234 assert_eq!(index.total_relationships(), 3);
235 }
236
237 #[test]
238 fn test_void_index_has_voids() {
239 let mut index = VoidIndex::new();
240 index.add_relationship(100, 200);
241
242 assert!(index.has_voids(100));
243 assert!(!index.has_voids(999));
244 }
245
246 #[test]
247 fn test_void_index_is_void() {
248 let mut index = VoidIndex::new();
249 index.add_relationship(100, 200);
250
251 assert!(index.is_void(200));
252 assert!(!index.is_void(100));
253 assert!(!index.is_void(999));
254 }
255
256 #[test]
257 fn test_void_index_hosts_with_voids() {
258 let mut index = VoidIndex::new();
259 index.add_relationship(100, 200);
260 index.add_relationship(101, 201);
261 index.add_relationship(102, 202);
262
263 let hosts = index.hosts_with_voids();
264 assert_eq!(hosts.len(), 3);
265 assert!(hosts.contains(&100));
266 assert!(hosts.contains(&101));
267 assert!(hosts.contains(&102));
268 }
269
270 #[test]
271 fn test_void_statistics() {
272 let mut index = VoidIndex::new();
273
274 index.add_relationship(100, 200);
276 index.add_relationship(100, 201);
277 index.add_relationship(100, 202);
278
279 index.add_relationship(101, 203);
281
282 let stats = VoidStatistics::from_index(&index);
283
284 assert_eq!(stats.hosts_with_voids, 2);
285 assert_eq!(stats.total_voids, 4);
286 assert_eq!(stats.max_voids_per_host, 3);
287 assert!((stats.avg_voids_per_host - 2.0).abs() < 0.01);
288 assert_eq!(stats.hosts_with_many_voids, 0);
289 }
290
291 #[test]
292 fn test_void_statistics_many_voids() {
293 let mut index = VoidIndex::new();
294
295 for i in 0..15 {
297 index.add_relationship(100, 200 + i);
298 }
299
300 let stats = VoidStatistics::from_index(&index);
301 assert_eq!(stats.hosts_with_many_voids, 1);
302 }
303}