honeycomb_core/cmap/dim3/basic_ops.rs
1//! Basic operations implementation
2//!
3//! This module contains code used to implement basic operations of combinatorial maps, such as
4//! (but not limited to):
5//!
6//! - Dart addition / insertion / removal
7//! - Beta function interfaces
8//! - i-cell computations
9
10use std::cell::RefCell;
11use std::collections::{HashSet, VecDeque};
12
13use crate::attributes::UnknownAttributeStorage;
14use crate::cmap::{
15 CMap3, DartIdType, EdgeIdType, FaceIdType, NULL_DART_ID, VertexIdType, VolumeIdType,
16};
17use crate::geometry::CoordsFloat;
18use crate::stm::{StmClosureResult, StmError, Transaction, atomically};
19
20// use thread local hashset and queue for orbit traversal of ID comp.
21// not applied to orbit currently bc they are lazily onsumed, and therefore require dedicated
22// instances to be robust
23thread_local! {
24 static AUXILIARIES: RefCell<(VecDeque<DartIdType>, HashSet<DartIdType>)> = RefCell::new((VecDeque::with_capacity(10), HashSet::with_capacity(10)));
25}
26
27/// **Dart-related methods**
28impl<T: CoordsFloat> CMap3<T> {
29 // --- read
30
31 /// Return the current number of darts.
32 #[must_use = "unused return value"]
33 pub fn n_darts(&self) -> usize {
34 self.unused_darts.len()
35 }
36
37 /// Return the current number of unused darts.
38 #[must_use = "unused return value"]
39 pub fn n_unused_darts(&self) -> usize {
40 self.unused_darts.iter().filter(|v| v.read_atomic()).count()
41 }
42
43 // --- edit
44
45 /// Add a new free dart to the map.
46 ///
47 /// # Return
48 ///
49 /// Returns the ID of the new dart.
50 pub fn add_free_dart(&mut self) -> DartIdType {
51 let new_id = self.n_darts() as DartIdType;
52 self.betas.extend(1);
53 self.unused_darts.extend(1);
54 self.vertices.extend(1);
55 self.attributes.extend_storages(1);
56 new_id
57 }
58
59 /// Add `n_darts` new free darts to the map.
60 ///
61 /// # Return
62 ///
63 /// Returns the ID of the first new dart. Other IDs are in the range `ID..ID+n_darts`.
64 pub fn add_free_darts(&mut self, n_darts: usize) -> DartIdType {
65 let new_id = self.n_darts() as DartIdType;
66 self.betas.extend(n_darts);
67 self.unused_darts.extend(n_darts);
68 self.vertices.extend(n_darts);
69 self.attributes.extend_storages(n_darts);
70 new_id
71 }
72
73 /// Insert a new free dart into the map.
74 ///
75 /// This method attempts to reuse an unused dart slot if available; otherwise, it adds a new one.
76 ///
77 /// # Return
78 ///
79 /// Returns the ID of the inserted dart.
80 pub fn insert_free_dart(&mut self) -> DartIdType {
81 if let Some((new_id, _)) = self
82 .unused_darts
83 .iter()
84 .enumerate()
85 .find(|(_, u)| u.read_atomic())
86 {
87 atomically(|trans| self.unused_darts[new_id as DartIdType].write(trans, false));
88 new_id as DartIdType
89 } else {
90 self.add_free_dart()
91 }
92 }
93
94 /// Remove a free dart from the map.
95 ///
96 /// The removed dart identifier is added to the list of free darts. This way of proceeding is
97 /// necessary as the structure relies on dart indexing for encoding data, making reordering of
98 /// any sort extremely costly.
99 ///
100 /// # Arguments
101 ///
102 /// - `dart_id: DartIdType` -- Identifier of the dart to remove.
103 ///
104 /// # Panics
105 ///
106 /// This method may panic if:
107 /// - the dart is not free for all *i*,
108 /// - the dart is already marked as unused.
109 pub fn remove_free_dart(&mut self, dart_id: DartIdType) {
110 assert!(self.is_free(dart_id)); // all beta images are 0
111 assert!(!atomically(|t| self.remove_free_dart_transac(t, dart_id)));
112 }
113
114 #[allow(clippy::missing_errors_doc)]
115 /// Transactionally remove a free dart from the map.
116 ///
117 /// The removed dart identifier is added to the list of free dart. This way of proceeding is
118 /// necessary as the structure relies on darts indexing for encoding data, making reordering of
119 /// any sort extremely costly.
120 ///
121 /// # Arguments
122 ///
123 /// - `dart_id: DartIdentifier` -- Identifier of the dart to remove.
124 ///
125 /// # Return / Errors
126 ///
127 /// This method return a boolean indicating whether the art was already unused or not.
128 ///
129 /// This method is meant to be called in a context where the returned `Result` is used to
130 /// validate the transaction passed as argument. Errors should not be processed manually,
131 /// only processed via the `?` operator.
132 pub fn remove_free_dart_transac(
133 &self,
134 t: &mut Transaction,
135 dart_id: DartIdType,
136 ) -> StmClosureResult<bool> {
137 self.unused_darts[dart_id].replace(t, true)
138 }
139}
140
141/// **Beta-related methods**
142impl<T: CoordsFloat> CMap3<T> {
143 // --- read
144
145 /// Return β<sub>`I`</sub>(`dart_id`).
146 ///
147 /// # Errors
148 ///
149 /// This method is meant to be called in a context where the returned `Result` is used to
150 /// validate the transaction passed as argument. Errors should not be processed manually,
151 /// only processed via the `?` operator.
152 ///
153 /// # Panics
154 ///
155 /// The method will panic if `I` is not 0, 1, 2, or 3.
156 pub fn beta_transac<const I: u8>(
157 &self,
158 trans: &mut Transaction,
159 dart_id: DartIdType,
160 ) -> StmClosureResult<DartIdType> {
161 assert!(I < 4);
162 self.betas[(I, dart_id)].read(trans)
163 }
164
165 /// Return β<sub>`i`</sub>(`dart_id`).
166 ///
167 /// # Errors
168 ///
169 /// This method is meant to be called in a context where the returned `Result` is used to
170 /// validate the transaction passed as argument. Errors should not be processed manually,
171 /// only processed via the `?` operator.
172 ///
173 /// # Panics
174 ///
175 /// The method will panic if `i` is not 0, 1, 2, or 3.
176 pub fn beta_rt_transac(
177 &self,
178 trans: &mut Transaction,
179 i: u8,
180 dart_id: DartIdType,
181 ) -> StmClosureResult<DartIdType> {
182 assert!(i < 4);
183 match i {
184 0 => self.beta_transac::<0>(trans, dart_id),
185 1 => self.beta_transac::<1>(trans, dart_id),
186 2 => self.beta_transac::<2>(trans, dart_id),
187 3 => self.beta_transac::<3>(trans, dart_id),
188 _ => unreachable!(),
189 }
190 }
191
192 /// Return β<sub>`I`</sub>(`dart_id`).
193 ///
194 /// # Panics
195 ///
196 /// The method will panic if `I` is not 0, 1, 2, or 3.
197 #[must_use = "unused return value"]
198 pub fn beta<const I: u8>(&self, dart_id: DartIdType) -> DartIdType {
199 assert!(I < 4);
200 self.betas[(I, dart_id)].read_atomic()
201 }
202
203 /// Return β<sub>`i`</sub>(`dart_id`).
204 ///
205 /// # Panics
206 ///
207 /// The method will panic if `i` is not 0, 1, 2, or 3.
208 #[must_use = "unused return value"]
209 pub fn beta_rt(&self, i: u8, dart_id: DartIdType) -> DartIdType {
210 assert!(i < 4);
211 match i {
212 0 => self.beta::<0>(dart_id),
213 1 => self.beta::<1>(dart_id),
214 2 => self.beta::<2>(dart_id),
215 3 => self.beta::<3>(dart_id),
216 _ => unreachable!(),
217 }
218 }
219
220 /// Check if a given dart is `I`-free.
221 ///
222 /// # Return
223 ///
224 /// The method returns:
225 /// - `true` if β<sub>`I`</sub>(`dart_id`) = `NULL_DART_ID`,
226 /// - `false` otherwise.
227 ///
228 /// # Panics
229 ///
230 /// The function will panic if *I* is not 0, 1, 2, or 3.
231 #[must_use = "unused return value"]
232 pub fn is_i_free<const I: u8>(&self, dart_id: DartIdType) -> bool {
233 self.beta::<I>(dart_id) == NULL_DART_ID
234 }
235
236 /// Check if a given dart is free for all `i`.
237 ///
238 /// # Return
239 ///
240 /// Returns `true` if the dart is 0-free, 1-free, 2-free, **and** 3-free.
241 #[must_use = "unused return value"]
242 pub fn is_free(&self, dart_id: DartIdType) -> bool {
243 self.beta::<0>(dart_id) == NULL_DART_ID
244 && self.beta::<1>(dart_id) == NULL_DART_ID
245 && self.beta::<2>(dart_id) == NULL_DART_ID
246 && self.beta::<3>(dart_id) == NULL_DART_ID
247 }
248}
249
250/// **I-cell-related methods**
251impl<T: CoordsFloat> CMap3<T> {
252 /// Compute the ID of the vertex a given dart is part of.
253 ///
254 /// This corresponds to the minimum dart ID among darts composing the 0-cell orbit.
255 #[must_use = "unused return value"]
256 pub fn vertex_id(&self, dart_id: DartIdType) -> VertexIdType {
257 atomically(|t| self.vertex_id_transac(t, dart_id))
258 }
259
260 /// Compute the ID of the vertex a given dart is part of, transactionally.
261 ///
262 /// This corresponds to the minimum dart ID among darts composing the 0-cell orbit.
263 ///
264 /// # Errors
265 ///
266 /// This method is meant to be called in a context where the returned `Result` is used to
267 /// validate the transaction passed as argument. Errors should not be processed manually,
268 /// only processed via the `?` operator.
269 pub fn vertex_id_transac(
270 &self,
271 trans: &mut Transaction,
272 dart_id: DartIdType,
273 ) -> Result<VertexIdType, StmError> {
274 AUXILIARIES.with(|t| {
275 let (pending, marked) = &mut *t.borrow_mut();
276 // clear from previous computations
277 pending.clear();
278 marked.clear();
279 // initialize
280 pending.push_front(dart_id);
281 marked.insert(NULL_DART_ID); // we don't want to include the null dart in the orbit
282
283 let mut min = dart_id;
284
285 while let Some(d) = pending.pop_front() {
286 if marked.insert(d) {
287 min = min.min(d);
288 let (b0, b2, b3) = (
289 self.beta_transac::<0>(trans, d)?, // ?
290 self.beta_transac::<2>(trans, d)?,
291 self.beta_transac::<3>(trans, d)?,
292 );
293 pending.push_back(self.beta_transac::<1>(trans, b3)?);
294 pending.push_back(self.beta_transac::<3>(trans, b2)?);
295 pending.push_back(self.beta_transac::<1>(trans, b2)?);
296 pending.push_back(self.beta_transac::<3>(trans, b0)?); // ?
297 pending.push_back(self.beta_transac::<2>(trans, b0)?); // ?
298 }
299 }
300
301 Ok(min)
302 })
303 }
304
305 /// Compute the ID of the edge a given dart is part of.
306 ///
307 /// This corresponds to the minimum dart ID among darts composing the 1-cell orbit.
308 #[must_use = "unused return value"]
309 pub fn edge_id(&self, dart_id: DartIdType) -> EdgeIdType {
310 atomically(|t| self.edge_id_transac(t, dart_id))
311 }
312
313 /// Compute the ID of the edge a given dart is part of.
314 ///
315 /// This corresponds to the minimum dart ID among darts composing the 1-cell orbit.
316 ///
317 /// # Errors
318 ///
319 /// This method is meant to be called in a context where the returned `Result` is used to
320 /// validate the transaction passed as argument. Errors should not be processed manually,
321 /// only processed via the `?` operator.
322 pub fn edge_id_transac(
323 &self,
324 trans: &mut Transaction,
325 dart_id: DartIdType,
326 ) -> Result<EdgeIdType, StmError> {
327 AUXILIARIES.with(|t| {
328 let (pending, marked) = &mut *t.borrow_mut();
329 // clear from previous computations
330 pending.clear();
331 marked.clear();
332 // initialize
333 pending.push_back(dart_id);
334 marked.insert(NULL_DART_ID);
335
336 let mut min = dart_id;
337
338 while let Some(d) = pending.pop_front() {
339 if marked.insert(d) {
340 min = min.min(d);
341 for im in [
342 self.beta_transac::<2>(trans, d)?,
343 self.beta_transac::<3>(trans, d)?,
344 ] {
345 pending.push_back(im);
346 }
347 }
348 }
349
350 Ok(min)
351 })
352 }
353
354 /// Compute the ID of the face a given dart is part of.
355 ///
356 /// This corresponds to the minimum dart ID among darts composing the 2-cell orbit.
357 #[must_use = "unused return value"]
358 pub fn face_id(&self, dart_id: DartIdType) -> FaceIdType {
359 atomically(|t| self.face_id_transac(t, dart_id))
360 }
361
362 /// Compute the ID of the face a given dart is part of.
363 ///
364 /// This corresponds to the minimum dart ID among darts composing the 2-cell orbit.
365 ///
366 /// # Errors
367 ///
368 /// This method is meant to be called in a context where the returned `Result` is used to
369 /// validate the transaction passed as argument. Errors should not be processed manually,
370 /// only processed via the `?` operator.
371 pub fn face_id_transac(
372 &self,
373 trans: &mut Transaction,
374 dart_id: DartIdType,
375 ) -> Result<FaceIdType, StmError> {
376 AUXILIARIES.with(|t| {
377 let (_pending, marked) = &mut *t.borrow_mut();
378 // clear from previous computations
379 marked.clear();
380 // initialize
381 marked.insert(NULL_DART_ID); // we don't want to include the null dart in the orbit
382
383 let b3_dart_id = self.beta_transac::<3>(trans, dart_id)?;
384 let (mut lb, mut rb) = (dart_id, b3_dart_id);
385 let mut min = if rb == NULL_DART_ID { lb } else { lb.min(rb) };
386
387 while marked.insert(lb) || marked.insert(rb) {
388 (lb, rb) = (
389 self.beta_transac::<1>(trans, lb)?,
390 self.beta_transac::<0>(trans, rb)?,
391 );
392 if lb != NULL_DART_ID {
393 min = min.min(lb);
394 }
395 if rb != NULL_DART_ID {
396 min = min.min(rb);
397 }
398 }
399 // face is open, we need to iterate in the other direction
400 if lb == NULL_DART_ID || rb == NULL_DART_ID {
401 (lb, rb) = (
402 self.beta_transac::<0>(trans, dart_id)?,
403 self.beta_transac::<1>(trans, b3_dart_id)?,
404 );
405 while marked.insert(lb) || marked.insert(rb) {
406 (lb, rb) = (
407 self.beta_transac::<0>(trans, lb)?,
408 self.beta_transac::<1>(trans, rb)?,
409 );
410 if lb != NULL_DART_ID {
411 min = min.min(lb);
412 }
413 if rb != NULL_DART_ID {
414 min = min.min(rb);
415 }
416 }
417 }
418
419 Ok(min)
420 })
421 }
422
423 /// Compute the ID of the volume a given dart is part of.
424 ///
425 /// This corresponds to the minimum dart ID among darts composing the 3-cell orbit.
426 #[must_use = "unused return value"]
427 pub fn volume_id(&self, dart_id: DartIdType) -> VolumeIdType {
428 atomically(|t| self.volume_id_transac(t, dart_id))
429 }
430
431 /// Compute the ID of the volume a given dart is part of.
432 ///
433 /// This corresponds to the minimum dart ID among darts composing the 3-cell orbit.
434 ///
435 /// # Errors
436 ///
437 /// This method is meant to be called in a context where the returned `Result` is used to
438 /// validate the transaction passed as argument. Errors should not be processed manually,
439 /// only processed via the `?` operator.
440 pub fn volume_id_transac(
441 &self,
442 trans: &mut Transaction,
443 dart_id: DartIdType,
444 ) -> Result<VolumeIdType, StmError> {
445 AUXILIARIES.with(|t| {
446 let (pending, marked) = &mut *t.borrow_mut();
447 // clear from previous computations
448 pending.clear();
449 marked.clear();
450 // initialize
451 pending.push_front(dart_id);
452 marked.insert(NULL_DART_ID); // we don't want to include the null dart in the orbit
453
454 let mut min = dart_id;
455
456 while let Some(d) = pending.pop_front() {
457 if marked.insert(d) {
458 min = min.min(d);
459 pending.push_back(self.beta_transac::<1>(trans, d)?);
460 pending.push_back(self.beta_transac::<0>(trans, d)?); // ?
461 pending.push_back(self.beta_transac::<2>(trans, d)?);
462 }
463 }
464
465 Ok(min)
466 })
467 }
468
469 /// Return an iterator over IDs of all the map's vertices.
470 pub fn iter_vertices(&self) -> impl Iterator<Item = VertexIdType> + '_ {
471 (1..self.n_darts() as DartIdType)
472 .zip(self.unused_darts.iter().skip(1))
473 .filter_map(
474 |(d, unused)| {
475 if unused.read_atomic() { None } else { Some(d) }
476 },
477 )
478 .filter_map(|d| {
479 let vid = self.vertex_id(d);
480 if d == vid { Some(vid) } else { None }
481 })
482 }
483
484 /// Return an iterator over IDs of all the map's edges.
485 pub fn iter_edges(&self) -> impl Iterator<Item = EdgeIdType> + '_ {
486 (1..self.n_darts() as DartIdType)
487 .zip(self.unused_darts.iter().skip(1))
488 .filter_map(
489 |(d, unused)| {
490 if unused.read_atomic() { None } else { Some(d) }
491 },
492 )
493 .filter_map(|d| {
494 let eid = self.edge_id(d);
495 if d == eid { Some(eid) } else { None }
496 })
497 }
498
499 /// Return an iterator over IDs of all the map's faces.
500 pub fn iter_faces(&self) -> impl Iterator<Item = FaceIdType> + '_ {
501 (1..self.n_darts() as DartIdType)
502 .zip(self.unused_darts.iter().skip(1))
503 .filter_map(
504 |(d, unused)| {
505 if unused.read_atomic() { None } else { Some(d) }
506 },
507 )
508 .filter_map(|d| {
509 let fid = self.face_id(d);
510 if d == fid { Some(fid) } else { None }
511 })
512 }
513
514 /// Return an iterator over IDs of all the map's volumes.
515 pub fn iter_volumes(&self) -> impl Iterator<Item = VolumeIdType> + '_ {
516 (1..self.n_darts() as DartIdType)
517 .zip(self.unused_darts.iter().skip(1))
518 .filter_map(
519 |(d, unused)| {
520 if unused.read_atomic() { None } else { Some(d) }
521 },
522 )
523 .filter_map(|d| {
524 let vid = self.volume_id(d);
525 if d == vid { Some(vid) } else { None }
526 })
527 }
528}