honeycomb_benches/
remesh.rs

1//! 2D remshing pipeline benchmark
2//!
3//! This benchmark execute a proxy-application of usual 2D remeshing pipelines. It is split into
4//! two parts:
5//!
6//! - initialization & first capture,
7//! - iterative remeshing.
8//!
9//! Time sampling is done on the second part. Currently only executes in sequential.
10
11use std::time::Instant;
12
13use honeycomb::{
14    kernels::{
15        grisubal::Clip,
16        remeshing::{
17            EdgeCollapseError, EdgeSwapError, capture_geometry, classify_capture, collapse_edge,
18            cut_inner_edge, cut_outer_edge, move_vertex_to_average, swap_edge,
19        },
20        triangulation::{TriangulateError, earclip_cell_countercw},
21        utils::{EdgeAnchor, FaceAnchor, VertexAnchor, is_orbit_orientation_consistent},
22    },
23    prelude::{CMap2, CoordsFloat, DartIdType, NULL_DART_ID, OrbitPolicy, SewError, Vertex2},
24    stm::{StmClosureResult, Transaction, abort, atomically, atomically_with_err, retry},
25};
26
27use crate::{cli::RemeshArgs, prof_start, prof_stop, utils::hash_file};
28
29pub fn bench_remesh<T: CoordsFloat>(args: RemeshArgs) -> CMap2<T> {
30    let input_map = args.input.to_str().unwrap();
31    let target_len = T::from(args.target_length).unwrap();
32
33    let n_threads = std::thread::available_parallelism()
34        .map(|n| n.get())
35        .unwrap_or(1);
36
37    // load map from file
38    let input_hash = hash_file(input_map).expect("E: could not compute input hash"); // file id for posterity
39
40    // -- capture via grid overlap
41    let mut instant = Instant::now();
42    let mut map: CMap2<T> = capture_geometry(
43        input_map,
44        [T::from(args.lx).unwrap(), T::from(args.ly).unwrap()],
45        Clip::from(args.clip),
46    )
47    .unwrap();
48    let capture_time = instant.elapsed();
49
50    // -- classification
51    instant = Instant::now();
52    classify_capture(&map).unwrap();
53    let classification_time = instant.elapsed();
54
55    // -- triangulation
56    instant = Instant::now();
57    prof_start!("HCBENCH_REMESH_TRIANGULATION");
58    let n_tot = map
59        .iter_faces()
60        .map(|id| (map.orbit(OrbitPolicy::Face, id as DartIdType).count() - 3) * 2)
61        .sum();
62    let start = map.allocate_used_darts(n_tot);
63    // use a prefix sum starting from the newly allocated darts to associate free darts to each face
64    map.iter_faces()
65        .scan(start, |state, f| {
66            // compute the number of dart needed to triangulate this face
67            let n_d = (map.orbit(OrbitPolicy::Face, f as DartIdType).count() - 3) * 2;
68            *state += n_d as DartIdType;
69            Some((f, n_d, *state - n_d as DartIdType))
70        })
71        .filter(|(_, n_d, _)| *n_d != 0)
72        .for_each(|(f, n_d, start)| {
73            let new_darts = (start..start + n_d as DartIdType).collect::<Vec<_>>();
74            let anchor = map.force_remove_attribute::<FaceAnchor>(f);
75            // make sure new edges are anchored
76            if let Some(a) = anchor {
77                atomically(|t| {
78                    for &d in &new_darts {
79                        map.write_attribute(t, d, EdgeAnchor::from(a))?;
80                    }
81                    Ok(())
82                });
83            }
84            while let Err(e) =
85                atomically_with_err(|t| earclip_cell_countercw(t, &map, f, &new_darts))
86            {
87                match e {
88                    TriangulateError::UndefinedFace(_) | TriangulateError::OpFailed(_) => continue,
89                    TriangulateError::NoEar => panic!("E: cannot triangulate the geometry capture"),
90                    TriangulateError::AlreadyTriangulated
91                    | TriangulateError::NonFannable
92                    | TriangulateError::NotEnoughDarts(_)
93                    | TriangulateError::TooManyDarts(_) => {
94                        unreachable!()
95                    }
96                }
97            }
98            // make sure new faces are anchored
99            if let Some(a) = anchor {
100                atomically(|t| {
101                    for &d in &new_darts {
102                        let fid = map.face_id_transac(t, d)?;
103                        map.write_attribute(t, fid, a)?;
104                    }
105                    Ok(())
106                });
107            }
108        });
109    let triangulation_time = instant.elapsed();
110    prof_stop!("HCBENCH_REMESH_TRIANGULATION");
111
112    // check that the mesh is triangular, consistently oriented and fully classified
113    debug_assert!(
114        map.iter_faces()
115            .all(|f| map.orbit(OrbitPolicy::Face, f as DartIdType).count() == 3)
116    );
117    debug_assert!(map.iter_faces().all(|f| {
118        map.orbit(OrbitPolicy::FaceLinear, f).count() == 3
119            && atomically(|t| check_tri_orientation(t, &map, f as DartIdType))
120    }));
121    debug_assert!(
122        map.iter_vertices()
123            .all(|v| map.force_read_attribute::<VertexAnchor>(v).is_some())
124    );
125    debug_assert!(
126        map.iter_edges()
127            .all(|e| map.force_read_attribute::<EdgeAnchor>(e).is_some())
128    );
129    debug_assert!(
130        map.iter_faces()
131            .all(|f| map.force_read_attribute::<FaceAnchor>(f).is_some())
132    );
133
134    // TODO: print the whole config / args
135    println!("| remesh benchmark");
136    println!("|-> input      : {input_map} (hash: {input_hash:#0x})");
137    println!(
138        "|-> backend    : {:?} with {n_threads} thread(s)",
139        args.backend
140    );
141    println!("|-> target size: {target_len:?}");
142    println!("|-> capture time  : {}ms", capture_time.as_millis());
143    println!(
144        "|-> triangulation time  : {}ms",
145        triangulation_time.as_millis()
146    );
147    println!(
148        "|-> classification time  : {}ms",
149        classification_time.as_millis()
150    );
151
152    println!(
153        "Round | Relax (tot, s) | Ret cond (s) | Batch compute (s) | Cut/collapse (s) | Swap (s)"
154    );
155    // -- main remeshing loop
156    // a. relax
157    // b. cut / collapse
158    // c. swap
159    // check for ending condition after each relax
160    prof_start!("HCBENCH_REMESH_MAINLOOP");
161    let mut n = 0;
162    let mut r;
163    loop {
164        print!("{:>5}", n);
165
166        // -- relax
167        prof_start!("HCBENCH_REMESH_RELAX");
168        instant = Instant::now();
169        r = 0;
170        loop {
171            map.iter_vertices()
172                .filter_map(|v| {
173                    let mut neigh = Vec::with_capacity(10);
174                    for d in map.orbit(OrbitPolicy::Vertex, v as DartIdType) {
175                        let b2d = map.beta::<2>(d);
176                        if b2d == NULL_DART_ID {
177                            return None; // filter out vertices on the boundary
178                        } else {
179                            neigh.push(map.vertex_id(b2d));
180                        }
181                    }
182                    Some((v, neigh))
183                })
184                .for_each(|(vid, neighbors)| {
185                    let _ = atomically_with_err(|t| {
186                        move_vertex_to_average(t, &map, vid, &neighbors)?;
187                        if !is_orbit_orientation_consistent(t, &map, vid)? {
188                            abort("E: resulting geometry is inverted")?;
189                        }
190                        Ok(())
191                    });
192                });
193
194            r += 1;
195            if r >= args.n_relax_rounds.get() {
196                break;
197            }
198        }
199        prof_stop!("HCBENCH_REMESH_RELAX");
200        print!(" | {:>14.6e}", instant.elapsed().as_secs_f64());
201
202        debug_assert!(
203            map.iter_faces()
204                .all(|f| { atomically(|t| check_tri_orientation(t, &map, f as DartIdType)) })
205        );
206
207        // -- check early return conds if enabled
208        if args.enable_er {
209            instant = Instant::now();
210            let n_e = map.iter_edges().count();
211            let n_e_outside_tol = map
212                .iter_edges()
213                .map(|e| {
214                    let (v1, v2) = (
215                        map.force_read_vertex(map.vertex_id(e as DartIdType))
216                            .unwrap(),
217                        map.force_read_vertex(map.vertex_id(map.beta::<1>(e as DartIdType)))
218                            .unwrap(),
219                    );
220                    (v2 - v1).norm()
221                })
222                .filter(|l| {
223                    (l.to_f64().unwrap() - args.target_length).abs() / args.target_length
224                        > args.target_tolerance
225                })
226                .count();
227            // if 95%+ edges are in the target length tolerance range, finish early
228            if ((n_e_outside_tol as f32 - n_e as f32).abs() / n_e as f32) < 0.05 {
229                print!(" | {:>12.6e}", instant.elapsed().as_millis());
230                print!(" | {:>17}", "n/a");
231                print!(" | {:>16}", "n/a");
232                println!(" | {:>8}", "n/a");
233                break;
234            }
235            print!(" | {:>12.6e}", instant.elapsed().as_secs_f64());
236        } else {
237            print!(" | {:>12}", "n/a");
238        }
239
240        // -- get edges to process
241        instant = Instant::now();
242        let edges_to_process = map.iter_edges().collect::<Vec<_>>();
243        print!(" | {:>17.6e}", instant.elapsed().as_secs_f64());
244
245        // -- cut / collapse
246        prof_start!("HCBENCH_REMESH_CC");
247        instant = Instant::now();
248        for e in edges_to_process {
249            if map.is_unused(e as DartIdType) {
250                // needed as some operations may remove some edges besides the one processed
251                continue;
252            }
253
254            // filter out
255            let (l, r) = (e as DartIdType, map.beta::<1>(e as DartIdType));
256            let diff = atomically(|t| compute_diff_to_target(t, &map, l, r, args.target_length));
257            if diff.abs() < args.target_tolerance {
258                // edge is within target length tolerance; skip the cut/process phase
259                continue;
260            }
261            let e = map.edge_id(e);
262            // process
263            if diff.is_sign_positive() {
264                // edge is longer than target length => cut
265                if map.is_i_free::<2>(e as DartIdType) {
266                    let nd = map.allocate_used_darts(3);
267                    let nds: [DartIdType; 3] = std::array::from_fn(|i| nd + i as DartIdType);
268                    while let Err(er) = atomically_with_err(|t| {
269                        cut_outer_edge(t, &map, e, nds)?;
270                        let new_vid = nds[0];
271                        if !is_orbit_orientation_consistent(t, &map, new_vid)? {
272                            abort(SewError::BadGeometry(1, nds[0], nds[2]))?;
273                        }
274                        Ok(())
275                    }) {
276                        match er {
277                            SewError::BadGeometry(1, _, _) => {
278                                for d in nds {
279                                    map.release_dart(d).expect("E: unreachable");
280                                }
281                                break;
282                            }
283                            SewError::BadGeometry(_, _, _)
284                            | SewError::FailedLink(_)
285                            | SewError::FailedAttributeOp(_) => continue,
286                        }
287                    }
288                } else {
289                    let nd = map.allocate_used_darts(6);
290                    let nds: [DartIdType; 6] = std::array::from_fn(|i| nd + i as DartIdType);
291                    while let Err(er) = atomically_with_err(|t| {
292                        cut_inner_edge(t, &map, e, nds)?;
293                        let new_vid = nds[0];
294                        if !is_orbit_orientation_consistent(t, &map, new_vid)? {
295                            abort(SewError::BadGeometry(1, nds[0], nds[3]))?;
296                        }
297                        Ok(())
298                    }) {
299                        match er {
300                            SewError::BadGeometry(1, _, _) => {
301                                for d in nds {
302                                    map.release_dart(d).expect("E: unreachable");
303                                }
304                                break;
305                            }
306                            SewError::BadGeometry(_, _, _)
307                            | SewError::FailedLink(_)
308                            | SewError::FailedAttributeOp(_) => continue,
309                        }
310                    }
311                }
312            } else {
313                // edge is shorter than target length => collapse
314                while let Err(er) = atomically_with_err(|t| collapse_edge(t, &map, e)) {
315                    match er {
316                        EdgeCollapseError::FailedCoreOp(SewError::BadGeometry(_, _, _))
317                        | EdgeCollapseError::NonCollapsibleEdge(_)
318                        | EdgeCollapseError::InvertedOrientation => break,
319                        EdgeCollapseError::FailedCoreOp(_)
320                        | EdgeCollapseError::FailedDartRelease(_)
321                        | EdgeCollapseError::BadTopology => continue,
322                        EdgeCollapseError::NullEdge => unreachable!(),
323                    }
324                }
325            }
326        }
327        prof_stop!("HCBENCH_REMESH_CC");
328        print!(" | {:>16.6e}", instant.elapsed().as_secs_f64());
329
330        // -- swap
331        prof_start!("HCBENCH_REMESH_SWAP");
332        instant = Instant::now();
333        for (e, diff) in map
334            .iter_edges()
335            .map(|e| {
336                let (l, r) = (e as DartIdType, map.beta::<1>(e as DartIdType));
337                let diff =
338                    atomically(|t| compute_diff_to_target(t, &map, l, r, args.target_length));
339                (e, diff)
340            })
341            .filter(|(_, diff)| diff.abs() > args.target_tolerance)
342        {
343            let (l, r) = (e as DartIdType, map.beta::<2>(e as DartIdType));
344            if r != NULL_DART_ID {
345                debug_assert!(
346                    map.force_read_attribute::<FaceAnchor>(map.face_id(l))
347                        .is_some()
348                );
349                debug_assert!(
350                    map.force_read_attribute::<FaceAnchor>(map.face_id(r))
351                        .is_some()
352                );
353                if let Err(er) = atomically_with_err(|t| {
354                    let (b0l, b0r) = (map.beta_transac::<0>(t, l)?, map.beta_transac::<0>(t, r)?);
355                    let new_diff = compute_diff_to_target(t, &map, b0l, b0r, args.target_length)?;
356
357                    // if the swap gets the edge length closer to target value, do it
358                    if new_diff.abs() < diff.abs() {
359                        swap_edge(t, &map, e)?;
360                    }
361
362                    // ensure the swap doesn't invert geometry
363                    if !check_tri_orientation(t, &map, l)? || !check_tri_orientation(t, &map, r)? {
364                        abort(EdgeSwapError::NotSwappable("swap inverts orientation"))?;
365                    }
366
367                    Ok(())
368                }) {
369                    match er {
370                        EdgeSwapError::NotSwappable(_) => {} // continue
371                        EdgeSwapError::NullEdge
372                        | EdgeSwapError::IncompleteEdge
373                        | EdgeSwapError::FailedCoreOp(_)
374                        | EdgeSwapError::BadTopology => unreachable!(),
375                    }
376                }
377
378                debug_assert!(
379                    map.force_read_attribute::<FaceAnchor>(map.face_id(l))
380                        .is_some()
381                );
382                debug_assert!(
383                    map.force_read_attribute::<FaceAnchor>(map.face_id(r))
384                        .is_some()
385                );
386            }
387        }
388        prof_stop!("HCBENCH_REMESH_SWAP");
389        println!(" | {:>8.6e}", instant.elapsed().as_secs_f64());
390
391        debug_assert!(map.iter_faces().all(|f| {
392            map.orbit(OrbitPolicy::FaceLinear, f).count() == 3
393                && atomically(|t| check_tri_orientation(t, &map, f as DartIdType))
394        }));
395
396        n += 1;
397        if n >= args.n_rounds.get() {
398            break;
399        }
400    }
401    prof_stop!("HCBENCH_REMESH_MAINLOOP");
402
403    debug_assert!(map.iter_faces().all(|f| {
404        map.orbit(OrbitPolicy::FaceLinear, f).count() == 3
405            && atomically(|t| check_tri_orientation(t, &map, f as DartIdType))
406    }));
407
408    map
409}
410
411#[inline]
412fn compute_diff_to_target<T: CoordsFloat>(
413    t: &mut Transaction,
414    map: &CMap2<T>,
415    l: DartIdType,
416    r: DartIdType,
417    target: f64,
418) -> StmClosureResult<f64> {
419    let (vid1, vid2) = (map.vertex_id_transac(t, l)?, map.vertex_id_transac(t, r)?);
420    let (v1, v2) =
421        if let (Some(v1), Some(v2)) = (map.read_vertex(t, vid1)?, map.read_vertex(t, vid2)?) {
422            (v1, v2)
423        } else {
424            retry()?
425        };
426    Ok(((v2 - v1).norm().to_f64().unwrap() - target) / target)
427}
428
429#[inline]
430fn check_tri_orientation<T: CoordsFloat>(
431    t: &mut Transaction,
432    map: &CMap2<T>,
433    d: DartIdType,
434) -> StmClosureResult<bool> {
435    let vid1 = map.vertex_id_transac(t, d)?;
436    let b1 = map.beta_transac::<1>(t, d)?;
437    let vid2 = map.vertex_id_transac(t, b1)?;
438    let b1b1 = map.beta_transac::<1>(t, b1)?;
439    let vid3 = map.vertex_id_transac(t, b1b1)?;
440    let v1 = map.read_vertex(t, vid1)?.unwrap();
441    let v2 = map.read_vertex(t, vid2)?.unwrap();
442    let v3 = map.read_vertex(t, vid3)?.unwrap();
443    Ok(Vertex2::cross_product_from_vertices(&v1, &v2, &v3) > T::zero())
444}