honeycomb_core/cmap/dim3/orbits.rs
1//! Orbit implementation
2//!
3//! This module contains all code used to model orbits, a notion defined
4//! along the structure of combinatorial maps.
5
6use std::collections::VecDeque;
7
8use rustc_hash::FxHashSet as HashSet;
9
10use crate::cmap::{CMap3, DartIdType, NULL_DART_ID, OrbitPolicy, try_from_fn};
11use crate::geometry::CoordsFloat;
12use crate::stm::{StmClosureResult, Transaction};
13
14impl<T: CoordsFloat> CMap3<T> {
15 /// Generic orbit implementation.
16 ///
17 /// # Arguments
18 /// - `opolicy: OrbitPolicy` -- Policy used by the orbit for the BFS.
19 /// - `dart_id: DartIdentifier` -- Dart of which the structure will compute the orbit.
20 ///
21 /// # The search algorithm
22 ///
23 /// The search algorithm used to establish the list of dart included in the orbit is a
24 /// [Breadth-First Search algorithm][WIKIBFS]. This means that:
25 ///
26 /// - we look at the images of the current dart through all beta functions,
27 /// adding those to a queue, before moving on to the next dart.
28 /// - we apply the beta functions in their specified order; This guarantees a consistent and
29 /// predictable result.
30 ///
31 /// # Performance
32 ///
33 /// Currently, orbits use two dynamically allocated structures for computation: a `VecDeque`,
34 /// and a `HashSet`. There is a possibility to use static thread-local instances to avoid
35 /// ephemeral allocations, but [it would require a guard mechanism][PR].
36 ///
37 /// [WIKIBFS]: https://en.wikipedia.org/wiki/Breadth-first_search
38 /// [PR]: https://github.com/LIHPC-Computational-Geometry/honeycomb/pull/293
39 #[allow(clippy::needless_for_each, clippy::too_many_lines)]
40 #[rustfmt::skip]
41 pub fn orbit(
42 &self,
43 opolicy: OrbitPolicy,
44 dart_id: DartIdType,
45 ) -> impl Iterator<Item = DartIdType> {
46 let mut pending = VecDeque::new();
47 let mut marked: HashSet<DartIdType> = HashSet::default();
48 pending.push_back(dart_id);
49 marked.insert(NULL_DART_ID);
50 marked.insert(dart_id); // we're starting here, so we mark it beforehand
51
52 // FIXME: move the match block out of the iterator
53 std::iter::from_fn(move || {
54 if let Some(d) = pending.pop_front() {
55 let mut check = |d: DartIdType| {
56 if marked.insert(d) {
57 pending.push_back(d);
58 }
59 };
60 // compute the next images
61 match opolicy {
62 // B3oB2, B1oB3, B1oB2, B3oB0, B2oB0
63 OrbitPolicy::Vertex => {
64 [
65 self.beta::<1>(self.beta::<2>(d)), // b1(b2(d))
66 self.beta::<2>(self.beta::<0>(d)), // b2(b0(d))
67 self.beta::<1>(self.beta::<3>(d)), // b1(b3(d))
68 self.beta::<3>(self.beta::<0>(d)), // b3(b0(d))
69 self.beta::<2>(self.beta::<3>(d)), // b2(b3(d))
70 self.beta::<3>(self.beta::<2>(d)), // b3(b2(d))
71 ]
72 .into_iter()
73 .for_each(check);
74 }
75 // B3oB2, B1oB3, B1oB2
76 OrbitPolicy::VertexLinear => {
77 [
78 self.beta::<1>(self.beta::<2>(d)), // b1(b2(d))
79 self.beta::<1>(self.beta::<3>(d)), // b1(b3(d))
80 self.beta::<2>(self.beta::<3>(d)), // b2(b3(d))
81 ]
82 .into_iter()
83 .for_each(check);
84 }
85 // B2, B3
86 OrbitPolicy::Edge => {
87 [
88 self.beta::<2>(d),
89 self.beta::<3>(d),
90 ]
91 .into_iter()
92 .for_each(check);
93 }
94 // B1, B0, B3
95 OrbitPolicy::Face => {
96 [
97 self.beta::<1>(d),
98 self.beta::<0>(d),
99 self.beta::<3>(d),
100 ]
101 .into_iter()
102 .for_each(check);
103 }
104 // B1, B3
105 OrbitPolicy::FaceLinear => {
106 [
107 self.beta::<1>(d),
108 self.beta::<3>(d),
109 ]
110 .into_iter()
111 .for_each(check);
112 }
113 // B1, B0, B2
114 OrbitPolicy::Volume => {
115 [
116 self.beta::<1>(d),
117 self.beta::<0>(d),
118 self.beta::<2>(d),
119 ]
120 .into_iter()
121 .for_each(check);
122 }
123 // B1, B2
124 OrbitPolicy::VolumeLinear => {
125 [
126 self.beta::<1>(d),
127 self.beta::<2>(d),
128 ]
129 .into_iter()
130 .for_each(check);
131 }
132 OrbitPolicy::Custom(beta_slice) => {
133 for beta_id in beta_slice {
134 let im = self.beta_rt(*beta_id, d);
135 check(im);
136 }
137 }
138 }
139
140 return Some(d);
141 }
142 None // queue is empty, we're done
143 })
144 }
145
146 /// Generic orbit transactional implementation.
147 #[allow(clippy::needless_for_each, clippy::too_many_lines)]
148 #[rustfmt::skip]
149 pub fn orbit_tx(
150 &self,
151 t: &mut Transaction,
152 opolicy: OrbitPolicy,
153 dart_id: DartIdType,
154 ) -> impl Iterator<Item = StmClosureResult<DartIdType>> {
155 let mut pending = VecDeque::new();
156 let mut marked: HashSet<DartIdType> = HashSet::default();
157 pending.push_back(dart_id);
158 marked.insert(NULL_DART_ID);
159 marked.insert(dart_id); // we're starting here, so we mark it beforehand
160
161 // FIXME: move the match block out of the iterator
162 try_from_fn(move || {
163 if let Some(d) = pending.pop_front() {
164 let mut check = |d: DartIdType| {
165 if marked.insert(d) {
166 pending.push_back(d);
167 }
168 };
169 // compute the next images
170 match opolicy {
171 // B1oB2, B2oB0, B1oB3, B3oB0, B2oB3, B3oB2
172 OrbitPolicy::Vertex => {
173 let (b0, b2, b3) = (
174 self.beta_tx::<0>(t, d)?,
175 self.beta_tx::<2>(t, d)?,
176 self.beta_tx::<3>(t, d)?,
177 );
178 [
179 self.beta_tx::<1>(t, b2)?, // b1(b2(d))
180 self.beta_tx::<2>(t, b0)?, // b2(b0(d))
181 self.beta_tx::<1>(t, b3)?, // b1(b3(d))
182 self.beta_tx::<3>(t, b0)?, // b3(b0(d))
183 self.beta_tx::<2>(t, b3)?, // b2(b3(d))
184 self.beta_tx::<3>(t, b2)?, // b3(b2(d))
185 ]
186 .into_iter()
187 .for_each(check);
188 }
189 // B2oB3, B1oB3, B1oB2
190 OrbitPolicy::VertexLinear => {
191 let (b2, b3) =
192 (self.beta_tx::<2>(t, d)?, self.beta_tx::<3>(t, d)?);
193 [
194 self.beta_tx::<1>(t, b2)?, // b1(b2(d))
195 self.beta_tx::<1>(t, b3)?, // b1(b3(d))
196 self.beta_tx::<2>(t, b3)?, // b2(b3(d))
197 ]
198 .into_iter()
199 .for_each(check);
200 }
201 // B2, B3
202 OrbitPolicy::Edge => {
203 [self.beta_tx::<2>(t, d)?, self.beta_tx::<3>(t, d)?]
204 .into_iter()
205 .for_each(check);
206 }
207 // B1, B0, B3
208 OrbitPolicy::Face => {
209 [
210 self.beta_tx::<1>(t, d)?,
211 self.beta_tx::<0>(t, d)?,
212 self.beta_tx::<3>(t, d)?,
213 ]
214 .into_iter()
215 .for_each(check);
216 }
217 // B1, B3
218 OrbitPolicy::FaceLinear => {
219 [
220 self.beta_tx::<1>(t, d)?,
221 self.beta_tx::<3>(t, d)?,
222 ]
223 .into_iter()
224 .for_each(check);
225 }
226 // B1, B0, B2
227 OrbitPolicy::Volume => {
228 [
229 self.beta_tx::<1>(t, d)?,
230 self.beta_tx::<0>(t, d)?,
231 self.beta_tx::<2>(t, d)?,
232 ]
233 .into_iter()
234 .for_each(check);
235 }
236 // B1, B2
237 OrbitPolicy::VolumeLinear => {
238 [self.beta_tx::<1>(t, d)?, self.beta_tx::<2>(t, d)?]
239 .into_iter()
240 .for_each(check);
241 }
242 OrbitPolicy::Custom(beta_slice) => {
243 for beta_id in beta_slice {
244 let im = self.beta_rt_tx(t, *beta_id, d)?;
245 check(im);
246 }
247 }
248 }
249
250 return Ok(Some(d));
251 }
252 Ok(None) // queue is empty, we're done
253 })
254 }
255
256 /// Return the orbit defined by a dart and its `I`-cell.
257 ///
258 /// # Usage
259 ///
260 /// The returned item can be iterated upon to retrieve all dart members of the cell. Note that
261 /// **the dart passed as an argument is included as the first element of the returned orbit**.
262 ///
263 /// # Panics
264 ///
265 /// The method will panic if *I* is not 0, 1, 2, or 3.
266 #[must_use = "unused return value"]
267 pub fn i_cell<const I: u8>(&self, dart_id: DartIdType) -> impl Iterator<Item = DartIdType> {
268 assert!(I < 4);
269 match I {
270 0 => self.orbit(OrbitPolicy::Vertex, dart_id),
271 1 => self.orbit(OrbitPolicy::Edge, dart_id),
272 2 => self.orbit(OrbitPolicy::Face, dart_id),
273 3 => self.orbit(OrbitPolicy::Volume, dart_id),
274 _ => unreachable!(),
275 }
276 }
277}