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