runmat-meshing-size 0.6.0

Sizing field query service for RunMat meshing
Documentation
use crate::adaptive::{
    build_refinement_markers_from_samples, RefinementIndicatorSample, RefinementMarkerError,
    RefinementMarkerOptions,
};

#[test]
fn refinement_markers_are_deterministic_and_create_sizing_samples() {
    let samples = vec![
        RefinementIndicatorSample {
            entity_id: "tetrahedron_low".to_string(),
            position_m: [0.0, 0.0, 0.0],
            indicator_value: 0.2,
            current_size_m: 0.08,
        },
        RefinementIndicatorSample {
            entity_id: "tetrahedron_b".to_string(),
            position_m: [1.0, 0.0, 0.0],
            indicator_value: 1.0,
            current_size_m: 0.06,
        },
        RefinementIndicatorSample {
            entity_id: "tetrahedron_a".to_string(),
            position_m: [0.0, 1.0, 0.0],
            indicator_value: 1.0,
            current_size_m: 0.04,
        },
        RefinementIndicatorSample {
            entity_id: "tetrahedron_mid".to_string(),
            position_m: [0.0, 0.0, 1.0],
            indicator_value: 0.5,
            current_size_m: 0.1,
        },
    ];

    let (markers, sizing) = build_refinement_markers_from_samples(
        &samples,
        "structural.stress_gradient",
        RefinementMarkerOptions {
            max_markers: 3,
            min_relative_value: 0.5,
            target_size_scale: 0.4,
        },
    )
    .expect("marker generation should succeed");

    assert_eq!(
        markers
            .iter()
            .map(|marker| marker.entity_id.as_str())
            .collect::<Vec<_>>(),
        vec!["tetrahedron_a", "tetrahedron_b", "tetrahedron_mid"]
    );
    assert_eq!(markers[0].weight, 1.0);
    assert_eq!(markers[2].weight, 0.5);
    assert_eq!(sizing.samples.len(), 3);
    assert_eq!(sizing.samples[0].target_size_m, 0.016);
    assert_eq!(
        sizing.samples[0].reason.as_deref(),
        Some("structural.stress_gradient")
    );
}

#[test]
fn refinement_markers_filter_nonfinite_and_empty_samples() {
    let samples = vec![
        RefinementIndicatorSample {
            entity_id: "nan".to_string(),
            position_m: [0.0, 0.0, 0.0],
            indicator_value: f64::NAN,
            current_size_m: 0.08,
        },
        RefinementIndicatorSample {
            entity_id: "zero".to_string(),
            position_m: [0.0, 0.0, 0.0],
            indicator_value: 0.0,
            current_size_m: 0.08,
        },
        RefinementIndicatorSample {
            entity_id: "bad_size".to_string(),
            position_m: [0.0, 0.0, 0.0],
            indicator_value: 1.0,
            current_size_m: 0.0,
        },
    ];

    let (markers, sizing) = build_refinement_markers_from_samples(
        &samples,
        "structural.stress_gradient",
        RefinementMarkerOptions::default(),
    )
    .expect("invalid samples should be filtered, not fail the batch");

    assert!(markers.is_empty());
    assert!(sizing.samples.is_empty());
}

#[test]
fn refinement_marker_options_are_validated() {
    let sample = [RefinementIndicatorSample {
        entity_id: "tetrahedron".to_string(),
        position_m: [0.0, 0.0, 0.0],
        indicator_value: 1.0,
        current_size_m: 0.1,
    }];

    assert_eq!(
        build_refinement_markers_from_samples(
            &sample,
            "reason",
            RefinementMarkerOptions {
                max_markers: 0,
                ..RefinementMarkerOptions::default()
            }
        ),
        Err(RefinementMarkerError::InvalidMaxMarkers)
    );
    assert_eq!(
        build_refinement_markers_from_samples(
            &sample,
            "reason",
            RefinementMarkerOptions {
                min_relative_value: 1.5,
                ..RefinementMarkerOptions::default()
            }
        ),
        Err(RefinementMarkerError::InvalidMinRelativeValue)
    );
    assert_eq!(
        build_refinement_markers_from_samples(
            &sample,
            "reason",
            RefinementMarkerOptions {
                target_size_scale: 1.0,
                ..RefinementMarkerOptions::default()
            }
        ),
        Err(RefinementMarkerError::InvalidTargetSizeScale)
    );
}