1use crate::data::atlas::Atlas;
4use crate::data::coordinates::Coordinates;
5use crate::data::refine::delta::SliceDelta;
6use crate::data::refine::sieved_array::SievedArray;
7use crate::data::section::Section;
8use crate::data::storage::{Storage, VecStorage};
9use crate::diagnostics::FvmCellDiagnostic;
10use crate::geometry::quality::{CellQuality, cell_quality_from_section};
11use crate::mesh_error::MeshSieveError;
12use crate::topology::anchors::TopologicalAnchors;
13use crate::topology::arrow::Polarity;
14use crate::topology::cell_type::CellType;
15use crate::topology::coarsen::{CoarsenEntity, CoarsenedTopology, coarsen_topology};
16use crate::topology::labels::LabelSet;
17use crate::topology::ownership::PointOwnership;
18use crate::topology::point::PointId;
19use crate::topology::refine::{
20 AnisotropicSplitHints, RefineOptions, RefinedMesh, collapse_to_cell_vertices,
21 refine_mesh_with_options,
22};
23use crate::topology::sieve::Sieve;
24use std::collections::HashMap;
25
26#[derive(Clone, Copy, Debug)]
28pub struct QualityMetrics {
29 pub aspect_ratio: f64,
31 pub min_angle_deg: f64,
33 pub jacobian_sign: f64,
35 pub cell_size: f64,
37}
38
39impl From<CellQuality> for QualityMetrics {
40 fn from(value: CellQuality) -> Self {
41 Self {
42 aspect_ratio: value.aspect_ratio,
43 min_angle_deg: value.min_angle_deg,
44 jacobian_sign: value.jacobian_sign,
45 cell_size: value.jacobian_sign.abs(),
46 }
47 }
48}
49
50pub fn evaluate_quality_metrics<S, Cs>(
52 sieve: &mut impl Sieve<Point = PointId>,
53 cell_types: &Section<CellType, S>,
54 coordinates: &Coordinates<f64, Cs>,
55) -> Result<Vec<(PointId, QualityMetrics)>, MeshSieveError>
56where
57 S: Storage<CellType>,
58 Cs: Storage<f64>,
59{
60 let collapsed = collapse_to_cell_vertices(sieve, cell_types)?;
61 let mut metrics = Vec::new();
62 for (cell, cell_slice) in cell_types.iter() {
63 if cell_slice.len() != 1 {
64 return Err(MeshSieveError::SliceLengthMismatch {
65 point: cell,
66 expected: 1,
67 found: cell_slice.len(),
68 });
69 }
70 let cell_type = cell_slice[0];
71 let vertices: Vec<_> = collapsed.cone_points(cell).collect();
72 let quality = cell_quality_from_section(cell_type, &vertices, coordinates)?;
73 metrics.push((cell, QualityMetrics::from(quality)));
74 }
75 Ok(metrics)
76}
77
78#[derive(Clone, Copy, Debug)]
80pub struct QualityThresholds {
81 pub refine_min_angle_deg: f64,
83 pub refine_max_aspect_ratio: f64,
85 pub refine_max_size: f64,
87 pub coarsen_min_angle_deg: f64,
89 pub coarsen_max_aspect_ratio: f64,
91 pub coarsen_min_size: f64,
93 pub check_geometry: bool,
95}
96
97impl Default for QualityThresholds {
98 fn default() -> Self {
99 Self {
100 refine_min_angle_deg: 20.0,
101 refine_max_aspect_ratio: 4.0,
102 refine_max_size: f64::INFINITY,
103 coarsen_min_angle_deg: 30.0,
104 coarsen_max_aspect_ratio: 2.0,
105 coarsen_min_size: 0.0,
106 check_geometry: false,
107 }
108 }
109}
110
111#[derive(Clone, Copy, Debug)]
113pub struct MetricTensor {
114 pub dimension: usize,
116 pub data: [f64; 6],
118}
119
120impl Default for MetricTensor {
121 fn default() -> Self {
122 Self::new_2d(1.0, 1.0, 0.0)
123 }
124}
125
126impl MetricTensor {
127 pub fn new_2d(m00: f64, m11: f64, m01: f64) -> Self {
129 Self {
130 dimension: 2,
131 data: [m00, m11, 0.0, m01, 0.0, 0.0],
132 }
133 }
134
135 pub fn new_3d(m00: f64, m11: f64, m22: f64, m01: f64, m02: f64, m12: f64) -> Self {
137 Self {
138 dimension: 3,
139 data: [m00, m11, m22, m01, m02, m12],
140 }
141 }
142
143 fn length_squared(&self, vector: &[f64]) -> Result<f64, MeshSieveError> {
144 match self.dimension {
145 2 => {
146 if vector.len() < 2 {
147 return Err(MeshSieveError::InvalidGeometry(
148 "metric tensor expects 2D vectors".into(),
149 ));
150 }
151 let (x, y) = (vector[0], vector[1]);
152 let [m00, m11, _, m01, _, _] = self.data;
153 Ok(m00 * x * x + m11 * y * y + 2.0 * m01 * x * y)
154 }
155 3 => {
156 if vector.len() < 3 {
157 return Err(MeshSieveError::InvalidGeometry(
158 "metric tensor expects 3D vectors".into(),
159 ));
160 }
161 let (x, y, z) = (vector[0], vector[1], vector[2]);
162 let [m00, m11, m22, m01, m02, m12] = self.data;
163 Ok(m00 * x * x
164 + m11 * y * y
165 + m22 * z * z
166 + 2.0 * (m01 * x * y + m02 * x * z + m12 * y * z))
167 }
168 _ => Err(MeshSieveError::InvalidGeometry(
169 "metric tensor dimension must be 2 or 3".into(),
170 )),
171 }
172 }
173}
174
175#[derive(Clone, Copy, Debug, PartialEq, Default)]
181pub struct MetricNormalizationControls {
182 pub target_complexity: Option<f64>,
184 pub gradation: Option<f64>,
186 pub hausdorff_number: Option<f64>,
188 pub min_anisotropy: Option<f64>,
190 pub max_anisotropy: Option<f64>,
192 pub min_magnitude: Option<f64>,
194 pub max_magnitude: Option<f64>,
196}
197
198impl MetricNormalizationControls {
199 pub fn is_identity(&self) -> bool {
201 self == &Self::default()
202 }
203}
204
205#[derive(Clone, Copy, Debug, PartialEq, Eq)]
207pub enum ExternalRemeshingBackend {
208 Triangle,
210 TetGen,
212 Gmsh,
214 Mmg,
216}
217
218impl ExternalRemeshingBackend {
219 pub fn is_feature_enabled(self) -> bool {
221 match self {
222 ExternalRemeshingBackend::Triangle => cfg!(feature = "triangle-support"),
223 ExternalRemeshingBackend::TetGen => cfg!(feature = "tetgen-support"),
224 ExternalRemeshingBackend::Gmsh => cfg!(feature = "gmsh-support"),
225 ExternalRemeshingBackend::Mmg => false,
226 }
227 }
228
229 pub fn cargo_feature(self) -> Option<&'static str> {
231 match self {
232 ExternalRemeshingBackend::Triangle => Some("triangle-support"),
233 ExternalRemeshingBackend::TetGen => Some("tetgen-support"),
234 ExternalRemeshingBackend::Gmsh => Some("gmsh-support"),
235 ExternalRemeshingBackend::Mmg => None,
236 }
237 }
238}
239
240#[derive(Clone, Copy, Debug, PartialEq, Eq)]
242pub enum MetricRemeshingBackend {
243 Internal,
245 External(ExternalRemeshingBackend),
247}
248
249impl Default for MetricRemeshingBackend {
250 fn default() -> Self {
251 Self::Internal
252 }
253}
254
255#[derive(Clone, Debug, Default, PartialEq, Eq)]
257pub struct AdaptationProvenanceMap {
258 pub old_to_new: Vec<(PointId, Vec<PointId>)>,
260 pub new_to_old: Vec<(PointId, Vec<PointId>)>,
262}
263
264impl AdaptationProvenanceMap {
265 fn from_refinement(refinement: &[(PointId, Vec<(PointId, Polarity)>)]) -> Self {
266 let mut old_to_new = Vec::new();
267 let mut new_to_old = Vec::new();
268 for (old, fines) in refinement {
269 let news: Vec<_> = fines.iter().map(|(fine, _)| *fine).collect();
270 for new_point in &news {
271 new_to_old.push((*new_point, vec![*old]));
272 }
273 old_to_new.push((*old, news));
274 }
275 Self {
276 old_to_new,
277 new_to_old,
278 }
279 }
280
281 fn from_coarsening(transfer: &[(PointId, Vec<(PointId, Polarity)>)]) -> Self {
282 let mut old_to_new_by_old: HashMap<PointId, Vec<PointId>> = HashMap::new();
283 let mut new_to_old = Vec::new();
284 for (new_coarse, old_fines) in transfer {
285 let olds: Vec<_> = old_fines.iter().map(|(old, _)| *old).collect();
286 for old in &olds {
287 old_to_new_by_old.entry(*old).or_default().push(*new_coarse);
288 }
289 new_to_old.push((*new_coarse, olds));
290 }
291 let mut old_to_new: Vec<_> = old_to_new_by_old.into_iter().collect();
292 old_to_new.sort_by_key(|(point, _)| *point);
293 Self {
294 old_to_new,
295 new_to_old,
296 }
297 }
298}
299
300#[derive(Clone, Copy, Debug)]
302pub struct MetricCellMetrics {
303 pub max_edge_length: f64,
305 pub min_edge_length: f64,
307 pub anisotropy_ratio: f64,
309}
310
311#[derive(Clone, Copy, Debug)]
313pub struct MetricThresholds {
314 pub refine_max_edge_length: f64,
316 pub coarsen_max_edge_length: f64,
318 pub split_edge_length: f64,
320 pub split_face_length: f64,
322 pub check_geometry: bool,
324}
325
326#[derive(Clone, Copy, Debug)]
328pub struct FvStabilityThresholds {
329 pub refine_non_orthogonality_deg: f64,
330 pub refine_skewness: f64,
331 pub refine_min_char_length: f64,
332 pub coarsen_non_orthogonality_deg: f64,
333 pub coarsen_skewness: f64,
334 pub coarsen_min_char_length: f64,
335}
336
337#[derive(Clone, Copy, Debug)]
338pub struct FvmQualityOutputs {
339 pub non_orthogonality_deg: f64,
340 pub skewness: f64,
341 pub characteristic_length: f64,
342}
343
344impl From<&FvmCellDiagnostic> for FvmQualityOutputs {
345 fn from(value: &FvmCellDiagnostic) -> Self {
346 Self {
347 non_orthogonality_deg: value.max_non_orthogonality_deg,
348 skewness: value.max_skewness,
349 characteristic_length: value.char_length,
350 }
351 }
352}
353
354impl Default for FvStabilityThresholds {
355 fn default() -> Self {
356 Self {
357 refine_non_orthogonality_deg: 75.0,
358 refine_skewness: 4.0,
359 refine_min_char_length: 1.0e-8,
360 coarsen_non_orthogonality_deg: 35.0,
361 coarsen_skewness: 1.5,
362 coarsen_min_char_length: 1.0e-4,
363 }
364 }
365}
366
367pub fn select_cells_from_fvm_diagnostics(
368 cell_diags: &[FvmCellDiagnostic],
369 thresholds: FvStabilityThresholds,
370) -> AdaptivitySelection {
371 let mut selection = AdaptivitySelection::default();
372 for d in cell_diags {
373 let q = FvmQualityOutputs::from(d);
374 if should_refine_from_fvm_quality(q, thresholds) {
375 selection.refine_cells.push(d.cell);
376 } else if should_coarsen_from_fvm_quality(q, thresholds) {
377 selection.coarsen_cells.push(d.cell);
378 }
379 }
380 selection
381}
382
383pub fn should_refine_from_fvm_quality(
384 quality: FvmQualityOutputs,
385 thresholds: FvStabilityThresholds,
386) -> bool {
387 quality.non_orthogonality_deg > thresholds.refine_non_orthogonality_deg
388 || quality.skewness > thresholds.refine_skewness
389 || quality.characteristic_length < thresholds.refine_min_char_length
390}
391
392pub fn should_coarsen_from_fvm_quality(
393 quality: FvmQualityOutputs,
394 thresholds: FvStabilityThresholds,
395) -> bool {
396 quality.non_orthogonality_deg <= thresholds.coarsen_non_orthogonality_deg
397 && quality.skewness <= thresholds.coarsen_skewness
398 && quality.characteristic_length >= thresholds.coarsen_min_char_length
399}
400
401impl Default for MetricThresholds {
402 fn default() -> Self {
403 Self {
404 refine_max_edge_length: 1.2,
405 coarsen_max_edge_length: 0.8,
406 split_edge_length: 1.0,
407 split_face_length: 1.0,
408 check_geometry: false,
409 }
410 }
411}
412
413#[derive(Clone, Debug, Default)]
415pub enum BoundaryRemeshingPolicy {
416 #[default]
418 PreserveBoundary,
419 RemeshBoundary,
421 PreserveLabeled(Vec<(String, i32)>),
423}
424
425impl BoundaryRemeshingPolicy {
426 fn preserves(&self, labels: Option<&LabelSet>, cell: PointId) -> bool {
427 match self {
428 BoundaryRemeshingPolicy::RemeshBoundary => false,
429 BoundaryRemeshingPolicy::PreserveBoundary => labels.is_some_and(|labels| {
430 labels
431 .iter()
432 .any(|(name, point, value)| point == cell && name == "boundary" && value != 0)
433 }),
434 BoundaryRemeshingPolicy::PreserveLabeled(strata) => labels.is_some_and(|labels| {
435 strata
436 .iter()
437 .any(|(name, value)| labels.get_label(cell, name) == Some(*value))
438 }),
439 }
440 }
441}
442
443#[derive(Clone, Debug, Default)]
445pub struct MetricAdaptationOptions {
446 pub boundary_policy: BoundaryRemeshingPolicy,
448 pub normalization: MetricNormalizationControls,
450 pub backend: MetricRemeshingBackend,
452}
453
454#[derive(Clone, Debug)]
456pub struct MetricSplitHint {
457 pub cell: PointId,
459 pub split_edges: Vec<[PointId; 2]>,
461 pub split_faces: Vec<Vec<PointId>>,
463}
464
465#[derive(Clone, Debug)]
466struct MetricCellEvaluation {
467 cell: PointId,
468 metrics: MetricCellMetrics,
469 edge_lengths: Vec<([PointId; 2], f64)>,
470 face_lengths: Vec<(Vec<PointId>, f64)>,
471}
472
473#[derive(Clone, Debug, Default)]
475pub struct AdaptivitySelection {
476 pub refine_cells: Vec<PointId>,
478 pub coarsen_cells: Vec<PointId>,
480}
481
482pub fn select_cells_for_adaptation(
484 metrics: &[(PointId, QualityMetrics)],
485 thresholds: QualityThresholds,
486) -> AdaptivitySelection {
487 let mut selection = AdaptivitySelection::default();
488 for (cell, quality) in metrics {
489 if quality.min_angle_deg < thresholds.refine_min_angle_deg
490 || quality.aspect_ratio > thresholds.refine_max_aspect_ratio
491 || quality.cell_size > thresholds.refine_max_size
492 {
493 selection.refine_cells.push(*cell);
494 } else if quality.min_angle_deg >= thresholds.coarsen_min_angle_deg
495 && quality.aspect_ratio <= thresholds.coarsen_max_aspect_ratio
496 && quality.cell_size < thresholds.coarsen_min_size
497 {
498 selection.coarsen_cells.push(*cell);
499 }
500 }
501 selection
502}
503
504fn polygon_edges(vertices: &[PointId]) -> Vec<[PointId; 2]> {
505 if vertices.len() < 2 {
506 return Vec::new();
507 }
508 let mut edges = Vec::with_capacity(vertices.len());
509 for i in 0..vertices.len() {
510 let next = (i + 1) % vertices.len();
511 edges.push([vertices[i], vertices[next]]);
512 }
513 edges
514}
515
516fn cell_edges(
517 cell_type: CellType,
518 vertices: &[PointId],
519) -> Result<Vec<[PointId; 2]>, MeshSieveError> {
520 let edges = match cell_type {
521 CellType::Triangle => vec![
522 [vertices[0], vertices[1]],
523 [vertices[1], vertices[2]],
524 [vertices[2], vertices[0]],
525 ],
526 CellType::Quadrilateral => vec![
527 [vertices[0], vertices[1]],
528 [vertices[1], vertices[2]],
529 [vertices[2], vertices[3]],
530 [vertices[3], vertices[0]],
531 ],
532 CellType::Polygon(_) => polygon_edges(vertices),
533 CellType::Tetrahedron => vec![
534 [vertices[0], vertices[1]],
535 [vertices[1], vertices[2]],
536 [vertices[2], vertices[0]],
537 [vertices[0], vertices[3]],
538 [vertices[1], vertices[3]],
539 [vertices[2], vertices[3]],
540 ],
541 CellType::Hexahedron => vec![
542 [vertices[0], vertices[1]],
543 [vertices[1], vertices[2]],
544 [vertices[2], vertices[3]],
545 [vertices[3], vertices[0]],
546 [vertices[4], vertices[5]],
547 [vertices[5], vertices[6]],
548 [vertices[6], vertices[7]],
549 [vertices[7], vertices[4]],
550 [vertices[0], vertices[4]],
551 [vertices[1], vertices[5]],
552 [vertices[2], vertices[6]],
553 [vertices[3], vertices[7]],
554 ],
555 CellType::Prism => vec![
556 [vertices[0], vertices[1]],
557 [vertices[1], vertices[2]],
558 [vertices[2], vertices[0]],
559 [vertices[3], vertices[4]],
560 [vertices[4], vertices[5]],
561 [vertices[5], vertices[3]],
562 [vertices[0], vertices[3]],
563 [vertices[1], vertices[4]],
564 [vertices[2], vertices[5]],
565 ],
566 CellType::Polyhedron => {
567 return Err(MeshSieveError::InvalidGeometry(
568 "metric evaluation does not support polyhedron edges".into(),
569 ));
570 }
571 _ => {
572 return Err(MeshSieveError::InvalidGeometry(format!(
573 "unsupported cell type: {cell_type:?}"
574 )));
575 }
576 };
577 Ok(edges)
578}
579
580fn cell_faces(
581 cell_type: CellType,
582 vertices: &[PointId],
583) -> Result<Vec<Vec<PointId>>, MeshSieveError> {
584 let faces = match cell_type {
585 CellType::Tetrahedron => vec![
586 vec![vertices[0], vertices[1], vertices[2]],
587 vec![vertices[0], vertices[1], vertices[3]],
588 vec![vertices[1], vertices[2], vertices[3]],
589 vec![vertices[2], vertices[0], vertices[3]],
590 ],
591 CellType::Hexahedron => vec![
592 vec![vertices[0], vertices[1], vertices[2], vertices[3]],
593 vec![vertices[4], vertices[5], vertices[6], vertices[7]],
594 vec![vertices[0], vertices[1], vertices[5], vertices[4]],
595 vec![vertices[1], vertices[2], vertices[6], vertices[5]],
596 vec![vertices[2], vertices[3], vertices[7], vertices[6]],
597 vec![vertices[3], vertices[0], vertices[4], vertices[7]],
598 ],
599 CellType::Prism => vec![
600 vec![vertices[0], vertices[1], vertices[2]],
601 vec![vertices[3], vertices[4], vertices[5]],
602 vec![vertices[0], vertices[1], vertices[4], vertices[3]],
603 vec![vertices[1], vertices[2], vertices[5], vertices[4]],
604 vec![vertices[2], vertices[0], vertices[3], vertices[5]],
605 ],
606 CellType::Polyhedron => {
607 return Err(MeshSieveError::InvalidGeometry(
608 "metric evaluation does not support polyhedron faces".into(),
609 ));
610 }
611 _ => Vec::new(),
612 };
613 Ok(faces)
614}
615
616fn metric_edge_length<Cs>(
617 tensor: MetricTensor,
618 coords: &Coordinates<f64, Cs>,
619 edge: [PointId; 2],
620) -> Result<f64, MeshSieveError>
621where
622 Cs: Storage<f64>,
623{
624 let a = coords.try_restrict(edge[0])?;
625 let b = coords.try_restrict(edge[1])?;
626 if a.len() != b.len() {
627 return Err(MeshSieveError::InvalidGeometry(
628 "edge coordinate dimensions do not match".into(),
629 ));
630 }
631 if a.len() != tensor.dimension {
632 return Err(MeshSieveError::InvalidGeometry(
633 "metric tensor dimension does not match coordinates".into(),
634 ));
635 }
636 let vector: Vec<f64> = b.iter().zip(a.iter()).map(|(bv, av)| bv - av).collect();
637 let length_sq = tensor.length_squared(&vector)?;
638 Ok(length_sq.max(0.0).sqrt())
639}
640
641fn evaluate_metric_cells<S, Cs, Ms>(
642 sieve: &mut impl Sieve<Point = PointId>,
643 cell_types: &Section<CellType, S>,
644 coordinates: &Coordinates<f64, Cs>,
645 metrics: &Section<MetricTensor, Ms>,
646) -> Result<Vec<MetricCellEvaluation>, MeshSieveError>
647where
648 S: Storage<CellType>,
649 Cs: Storage<f64>,
650 Ms: Storage<MetricTensor>,
651{
652 let collapsed = collapse_to_cell_vertices(sieve, cell_types)?;
653 let mut evaluations = Vec::new();
654 for (cell, cell_slice) in cell_types.iter() {
655 if cell_slice.len() != 1 {
656 return Err(MeshSieveError::SliceLengthMismatch {
657 point: cell,
658 expected: 1,
659 found: cell_slice.len(),
660 });
661 }
662 let tensor_slice = metrics.try_restrict(cell)?;
663 if tensor_slice.len() != 1 {
664 return Err(MeshSieveError::SliceLengthMismatch {
665 point: cell,
666 expected: 1,
667 found: tensor_slice.len(),
668 });
669 }
670 let tensor = tensor_slice[0];
671 let cell_type = cell_slice[0];
672 let vertices: Vec<_> = collapsed.cone_points(cell).collect();
673 let edges = cell_edges(cell_type, &vertices)?;
674 if edges.is_empty() {
675 return Err(MeshSieveError::InvalidGeometry(format!(
676 "cell {cell:?} has no edges for metric evaluation"
677 )));
678 }
679 let mut edge_lengths = Vec::with_capacity(edges.len());
680 for edge in edges {
681 let length = metric_edge_length(tensor, coordinates, edge)?;
682 edge_lengths.push((edge, length));
683 }
684 let mut max_edge = 0.0;
685 let mut min_edge = f64::INFINITY;
686 for (_, length) in &edge_lengths {
687 if *length > max_edge {
688 max_edge = *length;
689 }
690 if *length < min_edge {
691 min_edge = *length;
692 }
693 }
694 let anisotropy_ratio = if min_edge > 0.0 {
695 max_edge / min_edge
696 } else {
697 f64::INFINITY
698 };
699 let face_lengths = cell_faces(cell_type, &vertices)?
700 .into_iter()
701 .map(|face_vertices| {
702 let mut face_max = 0.0;
703 for edge in polygon_edges(&face_vertices) {
704 let length = metric_edge_length(tensor, coordinates, edge)?;
705 if length > face_max {
706 face_max = length;
707 }
708 }
709 Ok((face_vertices, face_max))
710 })
711 .collect::<Result<Vec<_>, MeshSieveError>>()?;
712 evaluations.push(MetricCellEvaluation {
713 cell,
714 metrics: MetricCellMetrics {
715 max_edge_length: max_edge,
716 min_edge_length: min_edge,
717 anisotropy_ratio,
718 },
719 edge_lengths,
720 face_lengths,
721 });
722 }
723 Ok(evaluations)
724}
725
726pub fn select_cells_for_metric_adaptation(
728 metrics: &[(PointId, MetricCellMetrics)],
729 thresholds: MetricThresholds,
730) -> AdaptivitySelection {
731 let mut selection = AdaptivitySelection::default();
732 for (cell, metric) in metrics {
733 if metric.max_edge_length > thresholds.refine_max_edge_length {
734 selection.refine_cells.push(*cell);
735 } else if metric.max_edge_length < thresholds.coarsen_max_edge_length {
736 selection.coarsen_cells.push(*cell);
737 }
738 }
739 selection
740}
741
742fn metric_split_hints(
743 evaluations: &[MetricCellEvaluation],
744 thresholds: MetricThresholds,
745) -> Vec<MetricSplitHint> {
746 let mut hints = Vec::new();
747 for evaluation in evaluations {
748 let split_edges: Vec<_> = evaluation
749 .edge_lengths
750 .iter()
751 .filter(|(_, length)| *length > thresholds.split_edge_length)
752 .map(|(edge, _)| *edge)
753 .collect();
754 let split_faces: Vec<_> = evaluation
755 .face_lengths
756 .iter()
757 .filter(|(_, length)| *length > thresholds.split_face_length)
758 .map(|(face, _)| face.clone())
759 .collect();
760 if !split_edges.is_empty() || !split_faces.is_empty() {
761 hints.push(MetricSplitHint {
762 cell: evaluation.cell,
763 split_edges,
764 split_faces,
765 });
766 }
767 }
768 hints
769}
770
771fn build_anisotropic_hints(hints: &[MetricSplitHint]) -> AnisotropicSplitHints {
772 let mut edge_splits: HashMap<PointId, Vec<[PointId; 2]>> = HashMap::new();
773 let mut face_splits: HashMap<PointId, Vec<Vec<PointId>>> = HashMap::new();
774 for hint in hints {
775 if !hint.split_edges.is_empty() {
776 edge_splits.insert(hint.cell, hint.split_edges.clone());
777 }
778 if !hint.split_faces.is_empty() {
779 face_splits.insert(hint.cell, hint.split_faces.clone());
780 }
781 }
782 AnisotropicSplitHints {
783 edge_splits,
784 face_splits,
785 }
786}
787
788fn normalized_metric_section<Ms>(
789 metric: &Section<MetricTensor, Ms>,
790 controls: MetricNormalizationControls,
791) -> Result<Section<MetricTensor, VecStorage<MetricTensor>>, MeshSieveError>
792where
793 Ms: Storage<MetricTensor>,
794{
795 let mut tensors: Vec<(PointId, MetricTensor)> = metric
796 .iter()
797 .map(|(point, slice)| {
798 if slice.len() == 1 {
799 Ok((point, slice[0]))
800 } else {
801 Err(MeshSieveError::SliceLengthMismatch {
802 point,
803 expected: 1,
804 found: slice.len(),
805 })
806 }
807 })
808 .collect::<Result<_, _>>()?;
809
810 for (_, tensor) in &mut tensors {
811 clamp_metric_tensor(tensor, controls);
812 }
813
814 if let Some(target) = controls
815 .target_complexity
816 .filter(|value| value.is_finite() && *value > 0.0)
817 {
818 let current: f64 = tensors
819 .iter()
820 .map(|(_, tensor)| metric_complexity_proxy(*tensor))
821 .sum();
822 if current > 0.0 && current.is_finite() {
823 let dimension = tensors
824 .first()
825 .map(|(_, tensor)| tensor.dimension)
826 .unwrap_or(2) as f64;
827 let scale = (target / current).powf(2.0 / dimension);
828 for (_, tensor) in &mut tensors {
829 scale_metric_tensor(tensor, scale);
830 clamp_metric_tensor(tensor, controls);
831 }
832 }
833 }
834
835 if let Some(gradation) = controls
836 .gradation
837 .filter(|value| value.is_finite() && *value >= 1.0)
838 && !tensors.is_empty()
839 {
840 let min_mag = tensors
841 .iter()
842 .map(|(_, tensor)| metric_magnitude_proxy(*tensor))
843 .filter(|value| *value > 0.0 && value.is_finite())
844 .fold(f64::INFINITY, f64::min);
845 if min_mag.is_finite() {
846 let max_allowed = min_mag * gradation;
847 for (_, tensor) in &mut tensors {
848 let magnitude = metric_magnitude_proxy(*tensor);
849 if magnitude > max_allowed && magnitude > 0.0 {
850 scale_metric_tensor(tensor, max_allowed / magnitude);
851 }
852 }
853 }
854 }
855
856 let mut atlas = Atlas::default();
857 for (point, _) in &tensors {
858 atlas.try_insert(*point, 1)?;
859 }
860 let mut out = Section::<MetricTensor, VecStorage<MetricTensor>>::new(atlas);
861 for (point, tensor) in tensors {
862 out.try_set(point, &[tensor])?;
863 }
864 Ok(out)
865}
866
867fn metric_magnitude_proxy(tensor: MetricTensor) -> f64 {
868 let dim = tensor.dimension.min(3);
869 if dim == 0 {
870 return 0.0;
871 }
872 (0..dim).map(|i| tensor.data[i].abs()).sum::<f64>() / dim as f64
873}
874
875fn metric_complexity_proxy(tensor: MetricTensor) -> f64 {
876 let dim = tensor.dimension.min(3);
877 if dim == 0 {
878 return 0.0;
879 }
880 let det_proxy = (0..dim)
881 .map(|i| tensor.data[i].abs().max(0.0))
882 .product::<f64>();
883 det_proxy.sqrt()
884}
885
886fn scale_metric_tensor(tensor: &mut MetricTensor, scale: f64) {
887 if scale.is_finite() && scale > 0.0 {
888 for value in &mut tensor.data {
889 *value *= scale;
890 }
891 }
892}
893
894fn clamp_metric_tensor(tensor: &mut MetricTensor, controls: MetricNormalizationControls) {
895 let dim = tensor.dimension.min(3);
896 for i in 0..dim {
897 if let Some(min) = controls.min_magnitude.filter(|value| value.is_finite()) {
898 tensor.data[i] = tensor.data[i].max(min);
899 }
900 if let Some(max) = controls.max_magnitude.filter(|value| value.is_finite()) {
901 tensor.data[i] = tensor.data[i].min(max);
902 }
903 }
904 if dim >= 2 {
905 let min_diag = (0..dim)
906 .map(|i| tensor.data[i].abs())
907 .fold(f64::INFINITY, f64::min);
908 let max_diag = (0..dim).map(|i| tensor.data[i].abs()).fold(0.0, f64::max);
909 if min_diag > 0.0 && max_diag.is_finite() {
910 if let Some(max_aniso) = controls
911 .max_anisotropy
912 .filter(|value| value.is_finite() && *value >= 1.0)
913 && max_diag / min_diag > max_aniso
914 {
915 let floor = max_diag / max_aniso;
916 for i in 0..dim {
917 if tensor.data[i].abs() < floor {
918 tensor.data[i] = floor.copysign(tensor.data[i]);
919 }
920 }
921 }
922 if let Some(min_aniso) = controls
923 .min_anisotropy
924 .filter(|value| value.is_finite() && *value >= 1.0)
925 && max_diag / min_diag < min_aniso
926 {
927 let ceiling = min_diag * min_aniso;
928 let max_idx = (0..dim)
929 .max_by(|a, b| {
930 tensor.data[*a]
931 .abs()
932 .partial_cmp(&tensor.data[*b].abs())
933 .unwrap()
934 })
935 .unwrap();
936 tensor.data[max_idx] = ceiling.copysign(tensor.data[max_idx]);
937 }
938 }
939 }
940}
941
942pub fn normalize_metric_section<Ms>(
944 metric: &Section<MetricTensor, Ms>,
945 controls: MetricNormalizationControls,
946) -> Result<Section<MetricTensor, VecStorage<MetricTensor>>, MeshSieveError>
947where
948 Ms: Storage<MetricTensor>,
949{
950 normalized_metric_section(metric, controls)
951}
952
953#[derive(Clone, Debug)]
955pub enum AdaptationAction<V> {
956 Refined {
958 mesh: RefinedMesh,
960 data: SievedArray<PointId, V>,
962 },
963 Coarsened {
965 mesh: CoarsenedTopology,
967 data: SievedArray<PointId, V>,
969 },
970 NoChange,
972}
973
974#[derive(Clone, Debug)]
976pub struct AdaptationResult<V> {
977 pub metrics: Vec<(PointId, QualityMetrics)>,
979 pub refine_cells: Vec<PointId>,
981 pub coarsen_cells: Vec<PointId>,
983 pub action: AdaptationAction<V>,
985}
986
987#[derive(Clone, Debug)]
989pub enum MetricAdaptationAction {
990 Refined { mesh: RefinedMesh },
992 Coarsened { mesh: CoarsenedTopology },
994 NoChange,
996}
997
998#[derive(Clone, Debug)]
1000pub struct MetricAdaptationResult<V> {
1001 pub metrics: Vec<(PointId, MetricCellMetrics)>,
1003 pub refine_cells: Vec<PointId>,
1005 pub coarsen_cells: Vec<PointId>,
1007 pub split_hints: Vec<MetricSplitHint>,
1009 pub action: MetricAdaptationAction,
1011 pub data: Option<SievedArray<PointId, V>>,
1013 pub provenance: AdaptationProvenanceMap,
1015}
1016
1017fn subset_cell_types<S>(
1018 cell_types: &Section<CellType, S>,
1019 cells: &[PointId],
1020) -> Result<Section<CellType, VecStorage<CellType>>, MeshSieveError>
1021where
1022 S: Storage<CellType>,
1023{
1024 let mut atlas = Atlas::default();
1025 for cell in cells {
1026 atlas.try_insert(*cell, 1)?;
1027 }
1028 let mut section = Section::<CellType, VecStorage<CellType>>::new(atlas);
1029 for cell in cells {
1030 let slice = cell_types.try_restrict(*cell)?;
1031 if slice.len() != 1 {
1032 return Err(MeshSieveError::SliceLengthMismatch {
1033 point: *cell,
1034 expected: 1,
1035 found: slice.len(),
1036 });
1037 }
1038 section.try_set(*cell, slice)?;
1039 }
1040 Ok(section)
1041}
1042
1043fn refined_data_atlas<V>(
1044 coarse: &SievedArray<PointId, V>,
1045 refinement: &[(PointId, Vec<(PointId, Polarity)>)],
1046) -> Result<Atlas, MeshSieveError> {
1047 let mut atlas = Atlas::default();
1048 for (coarse_cell, fine_cells) in refinement {
1049 let (_offset, len) = coarse
1050 .atlas()
1051 .get(*coarse_cell)
1052 .ok_or(MeshSieveError::SievedArrayPointNotInAtlas(*coarse_cell))?;
1053 for (fine_cell, _) in fine_cells {
1054 atlas.try_insert(*fine_cell, len)?;
1055 }
1056 }
1057 Ok(atlas)
1058}
1059
1060fn refine_data_with_transfer<V>(
1061 coarse: &SievedArray<PointId, V>,
1062 refinement: &[(PointId, Vec<(PointId, Polarity)>)],
1063) -> Result<SievedArray<PointId, V>, MeshSieveError>
1064where
1065 V: Clone + Default,
1066{
1067 let atlas = refined_data_atlas(coarse, refinement)?;
1068 let mut fine = SievedArray::<PointId, V>::new(atlas);
1069 fine.try_refine_with_sifter(coarse, refinement)?;
1070 Ok(fine)
1071}
1072
1073fn coarsened_data_atlas<V>(
1074 fine: &SievedArray<PointId, V>,
1075 transfer: &[(PointId, Vec<(PointId, Polarity)>)],
1076) -> Result<Atlas, MeshSieveError> {
1077 let mut atlas = Atlas::default();
1078 for (coarse_cell, fine_cells) in transfer {
1079 let first = fine_cells
1080 .first()
1081 .ok_or_else(|| MeshSieveError::InvalidGeometry("empty coarsen map".into()))?;
1082 let (_offset, len) = fine
1083 .atlas()
1084 .get(first.0)
1085 .ok_or(MeshSieveError::SievedArrayPointNotInAtlas(first.0))?;
1086 atlas.try_insert(*coarse_cell, len)?;
1087 }
1088 Ok(atlas)
1089}
1090
1091fn assemble_with_sifter<V>(
1092 fine: &SievedArray<PointId, V>,
1093 coarse: &mut SievedArray<PointId, V>,
1094 transfer: &[(PointId, Vec<(PointId, Polarity)>)],
1095) -> Result<(), MeshSieveError>
1096where
1097 V: Clone
1098 + Default
1099 + num_traits::FromPrimitive
1100 + core::ops::AddAssign
1101 + core::ops::Div<Output = V>,
1102{
1103 for (coarse_cell, fine_cells) in transfer {
1104 let coarse_len = coarse.try_get(*coarse_cell)?.len();
1105 let mut accum = vec![V::default(); coarse_len];
1106 let mut count = 0;
1107 for (fine_cell, polarity) in fine_cells {
1108 let slice = fine.try_get(*fine_cell)?;
1109 if slice.len() != accum.len() {
1110 return Err(MeshSieveError::SievedArraySliceLengthMismatch {
1111 point: *fine_cell,
1112 expected: accum.len(),
1113 found: slice.len(),
1114 });
1115 }
1116 let mut oriented = vec![V::default(); slice.len()];
1117 polarity.apply(slice, &mut oriented)?;
1118 for (acc, val) in accum.iter_mut().zip(oriented.iter()) {
1119 *acc += val.clone();
1120 }
1121 count += 1;
1122 }
1123 if count > 0 {
1124 let divisor: V = num_traits::FromPrimitive::from_usize(count)
1125 .ok_or(MeshSieveError::SievedArrayPrimitiveConversionFailure(count))?;
1126 for val in accum.iter_mut() {
1127 *val = val.clone() / divisor.clone();
1128 }
1129 coarse.try_set(*coarse_cell, &accum)?;
1130 }
1131 }
1132 Ok(())
1133}
1134
1135fn coarsen_data_with_transfer<V>(
1136 fine: &SievedArray<PointId, V>,
1137 transfer: &[(PointId, Vec<(PointId, Polarity)>)],
1138) -> Result<SievedArray<PointId, V>, MeshSieveError>
1139where
1140 V: Clone
1141 + Default
1142 + num_traits::FromPrimitive
1143 + core::ops::AddAssign
1144 + core::ops::Div<Output = V>,
1145{
1146 let atlas = coarsened_data_atlas(fine, transfer)?;
1147 let mut coarse = SievedArray::<PointId, V>::new(atlas);
1148 assemble_with_sifter(fine, &mut coarse, transfer)?;
1149 Ok(coarse)
1150}
1151
1152pub fn transfer_section_refinement<V>(
1154 coarse: &SievedArray<PointId, V>,
1155 refinement: &[(PointId, Vec<(PointId, Polarity)>)],
1156) -> Result<SievedArray<PointId, V>, MeshSieveError>
1157where
1158 V: Clone + Default,
1159{
1160 refine_data_with_transfer(coarse, refinement)
1161}
1162
1163pub fn transfer_section_coarsening<V>(
1165 fine: &SievedArray<PointId, V>,
1166 transfer: &[(PointId, Vec<(PointId, Polarity)>)],
1167) -> Result<SievedArray<PointId, V>, MeshSieveError>
1168where
1169 V: Clone
1170 + Default
1171 + num_traits::FromPrimitive
1172 + core::ops::AddAssign
1173 + core::ops::Div<Output = V>,
1174{
1175 coarsen_data_with_transfer(fine, transfer)
1176}
1177
1178pub fn transfer_labels_refinement(
1180 labels: &LabelSet,
1181 refinement: &[(PointId, Vec<(PointId, Polarity)>)],
1182) -> LabelSet {
1183 let mut out = labels.clone();
1184 for (coarse, fine_points) in refinement {
1185 for (name, point, value) in labels.iter() {
1186 if point == *coarse {
1187 for (fine, _) in fine_points {
1188 out.set_label(*fine, name, value);
1189 }
1190 }
1191 }
1192 }
1193 out
1194}
1195
1196pub fn transfer_cell_types_refinement<S>(
1198 cell_types: &Section<CellType, S>,
1199 refinement: &[(PointId, Vec<(PointId, Polarity)>)],
1200) -> Result<Section<CellType, VecStorage<CellType>>, MeshSieveError>
1201where
1202 S: Storage<CellType>,
1203{
1204 let mut atlas = Atlas::default();
1205 let mut entries = Vec::new();
1206 for (coarse, fine_points) in refinement {
1207 let slice = cell_types.try_restrict(*coarse)?;
1208 if slice.len() != 1 {
1209 return Err(MeshSieveError::SliceLengthMismatch {
1210 point: *coarse,
1211 expected: 1,
1212 found: slice.len(),
1213 });
1214 }
1215 for (fine, _) in fine_points {
1216 atlas.try_insert(*fine, 1)?;
1217 entries.push((*fine, slice[0]));
1218 }
1219 }
1220 let mut out = Section::<CellType, VecStorage<CellType>>::new(atlas);
1221 for (point, cell_type) in entries {
1222 out.try_set(point, &[cell_type])?;
1223 }
1224 Ok(out)
1225}
1226
1227pub fn transfer_ownership_refinement(
1229 ownership: &PointOwnership,
1230 refined: &RefinedMesh,
1231 my_rank: usize,
1232) -> Result<PointOwnership, MeshSieveError> {
1233 let mut out = ownership.clone();
1234 for (coarse, fine_points) in &refined.cell_refinement {
1235 let owner = ownership.owner_or_err(*coarse)?;
1236 for (fine, _) in fine_points {
1237 out.set_owner_min(*fine, owner, my_rank)?;
1238 for point in refined.sieve.cone_points(*fine) {
1239 if out.entry(point).is_none() {
1240 out.set_owner_min(point, owner, my_rank)?;
1241 }
1242 }
1243 }
1244 }
1245 Ok(out)
1246}
1247
1248pub fn transfer_constraints_from_anchors(
1250 anchors: &TopologicalAnchors,
1251 dofs_per_point: usize,
1252) -> crate::data::hanging_node_constraints::HangingNodeConstraints<f64> {
1253 crate::data::hanging_node_constraints::constraints_from_topological_anchors(
1254 anchors,
1255 dofs_per_point,
1256 )
1257}
1258
1259pub fn transfer_coordinates_refinement(
1261 refined: &RefinedMesh,
1262) -> Option<Coordinates<f64, VecStorage<f64>>> {
1263 refined.coordinates.clone()
1264}
1265
1266pub fn adapt_with_quality_and_transfer<S, Cs, V, C>(
1271 sieve: &mut impl Sieve<Point = PointId>,
1272 cell_types: &Section<CellType, S>,
1273 coordinates: &Coordinates<f64, Cs>,
1274 cell_data: &SievedArray<PointId, V>,
1275 coarsen_plan: C,
1276 thresholds: QualityThresholds,
1277) -> Result<AdaptationResult<V>, MeshSieveError>
1278where
1279 S: Storage<CellType>,
1280 Cs: Storage<f64>,
1281 V: Clone
1282 + Default
1283 + num_traits::FromPrimitive
1284 + core::ops::AddAssign
1285 + core::ops::Div<Output = V>,
1286 C: Fn(&[PointId]) -> Vec<CoarsenEntity>,
1287{
1288 let metrics = evaluate_quality_metrics(sieve, cell_types, coordinates)?;
1289 let selection = select_cells_for_adaptation(&metrics, thresholds);
1290 let refine_cells = selection.refine_cells.clone();
1291 let coarsen_cells = selection.coarsen_cells.clone();
1292
1293 if !refine_cells.is_empty() {
1294 let refined_section = subset_cell_types(cell_types, &refine_cells)?;
1295 let mut refined = refine_mesh_with_options(
1296 sieve,
1297 &refined_section,
1298 Some(coordinates),
1299 RefineOptions {
1300 check_geometry: thresholds.check_geometry,
1301 anisotropic_splits: None,
1302 },
1303 )?;
1304 materialize_unmodified_cells(sieve, cell_types, &mut refined, &refine_cells)?;
1305 let refined_data = refine_data_with_transfer(cell_data, &refined.cell_refinement)?;
1306 return Ok(AdaptationResult {
1307 metrics,
1308 refine_cells,
1309 coarsen_cells,
1310 action: AdaptationAction::Refined {
1311 mesh: refined,
1312 data: refined_data,
1313 },
1314 });
1315 }
1316
1317 if !coarsen_cells.is_empty() {
1318 let entities = coarsen_plan(&coarsen_cells);
1319 if entities.is_empty() {
1320 return Ok(AdaptationResult {
1321 metrics,
1322 refine_cells,
1323 coarsen_cells,
1324 action: AdaptationAction::NoChange,
1325 });
1326 }
1327 let coarsened = coarsen_topology(sieve, &entities)?;
1328 let coarsened_data = coarsen_data_with_transfer(cell_data, &coarsened.transfer_map)?;
1329 return Ok(AdaptationResult {
1330 metrics,
1331 refine_cells,
1332 coarsen_cells,
1333 action: AdaptationAction::Coarsened {
1334 mesh: coarsened,
1335 data: coarsened_data,
1336 },
1337 });
1338 }
1339
1340 Ok(AdaptationResult {
1341 metrics,
1342 refine_cells,
1343 coarsen_cells,
1344 action: AdaptationAction::NoChange,
1345 })
1346}
1347
1348fn materialize_unmodified_cells<S>(
1349 sieve: &mut impl Sieve<Point = PointId>,
1350 cell_types: &Section<CellType, S>,
1351 refined: &mut RefinedMesh,
1352 refined_cells: &[PointId],
1353) -> Result<(), MeshSieveError>
1354where
1355 S: Storage<CellType>,
1356{
1357 let refined_set: std::collections::HashSet<_> = refined_cells.iter().copied().collect();
1358 let collapsed = collapse_to_cell_vertices(sieve, cell_types)?;
1359 for (cell, cell_slice) in cell_types.iter() {
1360 if cell_slice.len() != 1 {
1361 return Err(MeshSieveError::SliceLengthMismatch {
1362 point: cell,
1363 expected: 1,
1364 found: cell_slice.len(),
1365 });
1366 }
1367 if refined_set.contains(&cell) {
1368 continue;
1369 }
1370 for vertex in collapsed.cone_points(cell) {
1371 refined.sieve.add_arrow(cell, vertex, ());
1372 }
1373 refined
1374 .cell_refinement
1375 .push((cell, vec![(cell, Polarity::Forward)]));
1376 }
1377 Ok(())
1378}
1379
1380fn filter_split_hints(hints: &[MetricSplitHint], cells: &[PointId]) -> Vec<MetricSplitHint> {
1381 let cell_set: std::collections::HashSet<_> = cells.iter().copied().collect();
1382 hints
1383 .iter()
1384 .filter(|hint| cell_set.contains(&hint.cell))
1385 .cloned()
1386 .collect()
1387}
1388
1389fn apply_boundary_policy(
1390 selection: &mut AdaptivitySelection,
1391 split_hints: &mut Vec<MetricSplitHint>,
1392 labels: Option<&LabelSet>,
1393 policy: &BoundaryRemeshingPolicy,
1394) {
1395 selection
1396 .refine_cells
1397 .retain(|cell| !policy.preserves(labels, *cell));
1398 selection
1399 .coarsen_cells
1400 .retain(|cell| !policy.preserves(labels, *cell));
1401 split_hints.retain(|hint| !policy.preserves(labels, hint.cell));
1402}
1403
1404pub fn adapt_with_metric_policy<S, Cs, Ms, C>(
1406 sieve: &mut impl Sieve<Point = PointId>,
1407 cell_types: &Section<CellType, S>,
1408 coordinates: &Coordinates<f64, Cs>,
1409 metric: &Section<MetricTensor, Ms>,
1410 labels: Option<&LabelSet>,
1411 coarsen_plan: C,
1412 thresholds: MetricThresholds,
1413 options: MetricAdaptationOptions,
1414) -> Result<MetricAdaptationResult<()>, MeshSieveError>
1415where
1416 S: Storage<CellType>,
1417 Cs: Storage<f64>,
1418 Ms: Storage<MetricTensor>,
1419 C: Fn(&[MetricSplitHint]) -> Vec<CoarsenEntity>,
1420{
1421 if let MetricRemeshingBackend::External(backend) = options.backend {
1422 let feature = backend
1423 .cargo_feature()
1424 .unwrap_or("no cargo feature is currently available");
1425 let availability = if backend.is_feature_enabled() {
1426 "the mesh-generation feature is enabled; invoke the corresponding meshgen remesher to obtain a MeshData round trip"
1427 } else {
1428 "enable the backend feature before invoking the corresponding meshgen remesher"
1429 };
1430 return Err(MeshSieveError::InvalidGeometry(format!(
1431 "external metric remeshing backend {backend:?} is selected through MetricRemeshingBackend; {availability}. Required feature: {feature}."
1432 )));
1433 }
1434 let normalized_metric = normalized_metric_section(metric, options.normalization)?;
1435 let evaluations = evaluate_metric_cells(sieve, cell_types, coordinates, &normalized_metric)?;
1436 let metrics: Vec<_> = evaluations
1437 .iter()
1438 .map(|eval| (eval.cell, eval.metrics))
1439 .collect();
1440 let mut split_hints = metric_split_hints(&evaluations, thresholds);
1441 let mut selection = select_cells_for_metric_adaptation(&metrics, thresholds);
1442 apply_boundary_policy(
1443 &mut selection,
1444 &mut split_hints,
1445 labels,
1446 &options.boundary_policy,
1447 );
1448 let refine_cells = selection.refine_cells.clone();
1449 let coarsen_cells = selection.coarsen_cells.clone();
1450
1451 if !refine_cells.is_empty() {
1452 let refined_section = subset_cell_types(cell_types, &refine_cells)?;
1453 let refine_hints = filter_split_hints(&split_hints, &refine_cells);
1454 let anisotropic = build_anisotropic_hints(&refine_hints);
1455 let mut refined = refine_mesh_with_options(
1456 sieve,
1457 &refined_section,
1458 Some(coordinates),
1459 RefineOptions {
1460 check_geometry: thresholds.check_geometry,
1461 anisotropic_splits: if anisotropic.is_empty() {
1462 None
1463 } else {
1464 Some(anisotropic)
1465 },
1466 },
1467 )?;
1468 materialize_unmodified_cells(sieve, cell_types, &mut refined, &refine_cells)?;
1469 return Ok(MetricAdaptationResult {
1470 metrics,
1471 refine_cells,
1472 coarsen_cells,
1473 split_hints,
1474 provenance: AdaptationProvenanceMap::from_refinement(&refined.cell_refinement),
1475 action: MetricAdaptationAction::Refined { mesh: refined },
1476 data: None,
1477 });
1478 }
1479
1480 if !coarsen_cells.is_empty() {
1481 let coarsen_hints = filter_split_hints(&split_hints, &coarsen_cells);
1482 let entities = coarsen_plan(&coarsen_hints);
1483 if !entities.is_empty() {
1484 let coarsened = coarsen_topology(sieve, &entities)?;
1485 return Ok(MetricAdaptationResult {
1486 metrics,
1487 refine_cells,
1488 coarsen_cells,
1489 split_hints,
1490 provenance: AdaptationProvenanceMap::from_coarsening(&coarsened.transfer_map),
1491 action: MetricAdaptationAction::Coarsened { mesh: coarsened },
1492 data: None,
1493 });
1494 }
1495 }
1496
1497 Ok(MetricAdaptationResult {
1498 metrics,
1499 refine_cells,
1500 coarsen_cells,
1501 split_hints,
1502 action: MetricAdaptationAction::NoChange,
1503 data: None,
1504 provenance: AdaptationProvenanceMap::default(),
1505 })
1506}
1507
1508pub fn adapt_with_metric<S, Cs, Ms, C>(
1513 sieve: &mut impl Sieve<Point = PointId>,
1514 cell_types: &Section<CellType, S>,
1515 coordinates: &Coordinates<f64, Cs>,
1516 metric: &Section<MetricTensor, Ms>,
1517 coarsen_plan: C,
1518 thresholds: MetricThresholds,
1519) -> Result<MetricAdaptationResult<()>, MeshSieveError>
1520where
1521 S: Storage<CellType>,
1522 Cs: Storage<f64>,
1523 Ms: Storage<MetricTensor>,
1524 C: Fn(&[MetricSplitHint]) -> Vec<CoarsenEntity>,
1525{
1526 let evaluations = evaluate_metric_cells(sieve, cell_types, coordinates, metric)?;
1527 let metrics: Vec<_> = evaluations
1528 .iter()
1529 .map(|eval| (eval.cell, eval.metrics))
1530 .collect();
1531 let split_hints = metric_split_hints(&evaluations, thresholds);
1532 let selection = select_cells_for_metric_adaptation(&metrics, thresholds);
1533 let refine_cells = selection.refine_cells.clone();
1534 let coarsen_cells = selection.coarsen_cells.clone();
1535
1536 if !refine_cells.is_empty() {
1537 let refined_section = subset_cell_types(cell_types, &refine_cells)?;
1538 let refine_hints = filter_split_hints(&split_hints, &refine_cells);
1539 let anisotropic = build_anisotropic_hints(&refine_hints);
1540 let mut refined = refine_mesh_with_options(
1541 sieve,
1542 &refined_section,
1543 Some(coordinates),
1544 RefineOptions {
1545 check_geometry: thresholds.check_geometry,
1546 anisotropic_splits: if anisotropic.is_empty() {
1547 None
1548 } else {
1549 Some(anisotropic)
1550 },
1551 },
1552 )?;
1553 materialize_unmodified_cells(sieve, cell_types, &mut refined, &refine_cells)?;
1554 return Ok(MetricAdaptationResult {
1555 metrics,
1556 refine_cells,
1557 coarsen_cells,
1558 split_hints,
1559 provenance: AdaptationProvenanceMap::from_refinement(&refined.cell_refinement),
1560 action: MetricAdaptationAction::Refined { mesh: refined },
1561 data: None,
1562 });
1563 }
1564
1565 if !coarsen_cells.is_empty() {
1566 let coarsen_hints = filter_split_hints(&split_hints, &coarsen_cells);
1567 let entities = coarsen_plan(&coarsen_hints);
1568 if entities.is_empty() {
1569 return Ok(MetricAdaptationResult {
1570 metrics,
1571 refine_cells,
1572 coarsen_cells,
1573 split_hints,
1574 action: MetricAdaptationAction::NoChange,
1575 data: None,
1576 provenance: AdaptationProvenanceMap::default(),
1577 });
1578 }
1579 let coarsened = coarsen_topology(sieve, &entities)?;
1580 return Ok(MetricAdaptationResult {
1581 metrics,
1582 refine_cells,
1583 coarsen_cells,
1584 split_hints,
1585 provenance: AdaptationProvenanceMap::from_coarsening(&coarsened.transfer_map),
1586 action: MetricAdaptationAction::Coarsened { mesh: coarsened },
1587 data: None,
1588 });
1589 }
1590
1591 Ok(MetricAdaptationResult {
1592 metrics,
1593 refine_cells,
1594 coarsen_cells,
1595 split_hints,
1596 action: MetricAdaptationAction::NoChange,
1597 data: None,
1598 provenance: AdaptationProvenanceMap::default(),
1599 })
1600}
1601
1602pub fn adapt_with_metric_and_transfer<S, Cs, Ms, V, C>(
1607 sieve: &mut impl Sieve<Point = PointId>,
1608 cell_types: &Section<CellType, S>,
1609 coordinates: &Coordinates<f64, Cs>,
1610 metric: &Section<MetricTensor, Ms>,
1611 cell_data: &SievedArray<PointId, V>,
1612 coarsen_plan: C,
1613 thresholds: MetricThresholds,
1614) -> Result<MetricAdaptationResult<V>, MeshSieveError>
1615where
1616 S: Storage<CellType>,
1617 Cs: Storage<f64>,
1618 Ms: Storage<MetricTensor>,
1619 V: Clone
1620 + Default
1621 + num_traits::FromPrimitive
1622 + core::ops::AddAssign
1623 + core::ops::Div<Output = V>,
1624 C: Fn(&[MetricSplitHint]) -> Vec<CoarsenEntity>,
1625{
1626 let evaluations = evaluate_metric_cells(sieve, cell_types, coordinates, metric)?;
1627 let metrics: Vec<_> = evaluations
1628 .iter()
1629 .map(|eval| (eval.cell, eval.metrics))
1630 .collect();
1631 let split_hints = metric_split_hints(&evaluations, thresholds);
1632 let selection = select_cells_for_metric_adaptation(&metrics, thresholds);
1633 let refine_cells = selection.refine_cells.clone();
1634 let coarsen_cells = selection.coarsen_cells.clone();
1635
1636 if !refine_cells.is_empty() {
1637 let refined_section = subset_cell_types(cell_types, &refine_cells)?;
1638 let refine_hints = filter_split_hints(&split_hints, &refine_cells);
1639 let anisotropic = build_anisotropic_hints(&refine_hints);
1640 let mut refined = refine_mesh_with_options(
1641 sieve,
1642 &refined_section,
1643 Some(coordinates),
1644 RefineOptions {
1645 check_geometry: thresholds.check_geometry,
1646 anisotropic_splits: if anisotropic.is_empty() {
1647 None
1648 } else {
1649 Some(anisotropic)
1650 },
1651 },
1652 )?;
1653 materialize_unmodified_cells(sieve, cell_types, &mut refined, &refine_cells)?;
1654 let refined_data = refine_data_with_transfer(cell_data, &refined.cell_refinement)?;
1655 return Ok(MetricAdaptationResult {
1656 metrics,
1657 refine_cells,
1658 coarsen_cells,
1659 split_hints,
1660 provenance: AdaptationProvenanceMap::from_refinement(&refined.cell_refinement),
1661 action: MetricAdaptationAction::Refined { mesh: refined },
1662 data: Some(refined_data),
1663 });
1664 }
1665
1666 if !coarsen_cells.is_empty() {
1667 let coarsen_hints = filter_split_hints(&split_hints, &coarsen_cells);
1668 let entities = coarsen_plan(&coarsen_hints);
1669 if entities.is_empty() {
1670 return Ok(MetricAdaptationResult {
1671 metrics,
1672 refine_cells,
1673 coarsen_cells,
1674 split_hints,
1675 action: MetricAdaptationAction::NoChange,
1676 data: None,
1677 provenance: AdaptationProvenanceMap::default(),
1678 });
1679 }
1680 let coarsened = coarsen_topology(sieve, &entities)?;
1681 let coarsened_data = coarsen_data_with_transfer(cell_data, &coarsened.transfer_map)?;
1682 return Ok(MetricAdaptationResult {
1683 metrics,
1684 refine_cells,
1685 coarsen_cells,
1686 split_hints,
1687 provenance: AdaptationProvenanceMap::from_coarsening(&coarsened.transfer_map),
1688 action: MetricAdaptationAction::Coarsened { mesh: coarsened },
1689 data: Some(coarsened_data),
1690 });
1691 }
1692
1693 Ok(MetricAdaptationResult {
1694 metrics,
1695 refine_cells,
1696 coarsen_cells,
1697 split_hints,
1698 action: MetricAdaptationAction::NoChange,
1699 data: None,
1700 provenance: AdaptationProvenanceMap::default(),
1701 })
1702}
1703
1704#[cfg(test)]
1705mod tests {
1706 use super::*;
1707
1708 #[test]
1709 fn fvm_threshold_selector_reduces_refine_pressure_over_passes() {
1710 let thresholds = FvStabilityThresholds::default();
1711 let pass0 = [
1712 FvmQualityOutputs {
1713 non_orthogonality_deg: 89.0,
1714 skewness: 5.0,
1715 characteristic_length: 1.0e-10,
1716 },
1717 FvmQualityOutputs {
1718 non_orthogonality_deg: 80.0,
1719 skewness: 2.5,
1720 characteristic_length: 1.0e-7,
1721 },
1722 FvmQualityOutputs {
1723 non_orthogonality_deg: 20.0,
1724 skewness: 0.9,
1725 characteristic_length: 1.0e-3,
1726 },
1727 ];
1728 let pass1 = [
1729 FvmQualityOutputs {
1730 non_orthogonality_deg: 74.0,
1731 skewness: 3.9,
1732 characteristic_length: 1.0e-6,
1733 },
1734 FvmQualityOutputs {
1735 non_orthogonality_deg: 68.0,
1736 skewness: 2.2,
1737 characteristic_length: 1.0e-5,
1738 },
1739 FvmQualityOutputs {
1740 non_orthogonality_deg: 19.0,
1741 skewness: 0.7,
1742 characteristic_length: 1.0e-3,
1743 },
1744 ];
1745 let pass2 = [
1746 FvmQualityOutputs {
1747 non_orthogonality_deg: 40.0,
1748 skewness: 1.2,
1749 characteristic_length: 1.0e-4,
1750 },
1751 FvmQualityOutputs {
1752 non_orthogonality_deg: 33.0,
1753 skewness: 1.1,
1754 characteristic_length: 1.0e-4,
1755 },
1756 FvmQualityOutputs {
1757 non_orthogonality_deg: 17.0,
1758 skewness: 0.5,
1759 characteristic_length: 1.0e-3,
1760 },
1761 ];
1762
1763 let refine0 = pass0
1764 .iter()
1765 .filter(|q| should_refine_from_fvm_quality(**q, thresholds))
1766 .count();
1767 let refine1 = pass1
1768 .iter()
1769 .filter(|q| should_refine_from_fvm_quality(**q, thresholds))
1770 .count();
1771 let refine2 = pass2
1772 .iter()
1773 .filter(|q| should_refine_from_fvm_quality(**q, thresholds))
1774 .count();
1775 assert!(refine0 >= refine1 && refine1 >= refine2);
1776 assert_eq!(refine2, 0);
1777 }
1778}