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