runmat-runtime 0.6.0

Core runtime for RunMat with builtins, BLAS/LAPACK integration, and execution APIs
Documentation
use runmat_runtime::geometry::{
    geometry_load_op, geometry_prep_for_analysis_op, GeometryPrepForAnalysisSpec,
    GeometryPrepProfile,
};
use runmat_runtime::operations::OperationContext;

const TRIANGLE_STL: &str = "solid tri\n  facet normal 0 0 1\n    outer loop\n      vertex 0 0 0\n      vertex 1 0 0\n      vertex 0 1 0\n    endloop\n  endfacet\nendsolid tri\n";
const SIMPLE_STEP: &str = "ISO-10303-21;\nHEADER;\nFILE_NAME('Assembly_A');\nENDSEC;\nDATA;\n#10=PRODUCT('Bracket_A','',(#1));\nENDSEC;\nEND-ISO-10303-21;\n";

#[test]
fn geometry_prep_for_analysis_is_deterministic_for_fixture_inputs() {
    let spec = GeometryPrepForAnalysisSpec {
        profile: GeometryPrepProfile::AnalysisReady,
        target_element_budget: 120_000,
    };

    let geometry = geometry_load_op(
        "/fixtures/tri.stl",
        TRIANGLE_STL.as_bytes(),
        OperationContext::new(None, None),
    )
    .expect("geometry load should succeed");
    let first = geometry_prep_for_analysis_op(
        &geometry.data,
        spec.clone(),
        OperationContext::new(Some("trace-prep-1".to_string()), None),
    )
    .expect("first prep should succeed");
    let second = geometry_prep_for_analysis_op(
        &geometry.data,
        spec,
        OperationContext::new(Some("trace-prep-2".to_string()), None),
    )
    .expect("second prep should succeed");

    assert_ne!(first.data.prep_artifact_id, second.data.prep_artifact_id);
    assert_eq!(first.data.prep, second.data.prep);
    assert!(!first.data.prep_artifact_id.is_empty());
    assert_eq!(
        first.data.prep.schema_version,
        "geometry-prep-for-analysis/v1"
    );
    assert!(first.data.prep.quality.min_scaled_jacobian >= 0.5);
    assert_eq!(first.data.prep.quality.inverted_element_count, 0);
}

#[test]
fn geometry_prep_for_analysis_preserves_region_mapping_stability() {
    let geometry = geometry_load_op(
        "/fixtures/tri.stl",
        TRIANGLE_STL.as_bytes(),
        OperationContext::new(None, None),
    )
    .expect("geometry load should succeed");
    let prep = geometry_prep_for_analysis_op(
        &geometry.data,
        GeometryPrepForAnalysisSpec::default(),
        OperationContext::new(None, None),
    )
    .expect("prep should succeed");

    assert!(!prep.data.prep.region_mappings.is_empty());
    for mapping in &prep.data.prep.region_mappings {
        assert!(!mapping.region_id.is_empty());
        assert!(!mapping.source_mesh_ids.is_empty());
        assert!(!mapping.prepared_mesh_ids.is_empty());
    }
}

#[test]
fn geometry_prep_for_analysis_rejects_metadata_only_step_regions() {
    let geometry = geometry_load_op(
        "/fixtures/assy.step",
        SIMPLE_STEP.as_bytes(),
        OperationContext::new(None, None),
    )
    .expect("metadata-only STEP load should succeed");
    let error = geometry_prep_for_analysis_op(
        &geometry.data,
        GeometryPrepForAnalysisSpec::default(),
        OperationContext::new(None, None),
    )
    .expect_err("analysis prep must reject a region with no mesh entity mapping");

    assert_eq!(error.error_code, "RM.GEOMETRY.PREP_FOR_ANALYSIS.FAILED");
    assert!(error
        .message
        .contains("region region_1 has no mesh entity mapping"));
}

#[test]
fn geometry_prep_for_analysis_adaptive_refine_is_deterministic_and_quality_oriented() {
    let geometry = geometry_load_op(
        "/fixtures/tri.stl",
        TRIANGLE_STL.as_bytes(),
        OperationContext::new(None, None),
    )
    .expect("geometry load should succeed");
    let spec = GeometryPrepForAnalysisSpec {
        profile: GeometryPrepProfile::AdaptiveRefine,
        target_element_budget: 4_000,
    };
    let first = geometry_prep_for_analysis_op(
        &geometry.data,
        spec.clone(),
        OperationContext::new(Some("trace-prep-adapt-1".to_string()), None),
    )
    .expect("first adaptive prep should succeed");
    let second = geometry_prep_for_analysis_op(
        &geometry.data,
        spec,
        OperationContext::new(Some("trace-prep-adapt-2".to_string()), None),
    )
    .expect("second adaptive prep should succeed");

    assert_eq!(first.data.prep, second.data.prep);
    assert!(first.data.prep.quality.min_scaled_jacobian >= 0.85);
    assert!(first.data.prep.quality.mean_aspect_ratio <= 2.0);
}