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}