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