runmat-meshing-core 0.6.0

Deterministic meshing preparation contracts for RunMat
Documentation
use serde::{Deserialize, Serialize};

use super::{StageEvidence, TopologyEntityId};

pub const TETRAHEDRON_OPTIMIZATION_LOCAL_RECONNECTION_ATTEMPT_COUNT: &str =
    "optimization_local_reconnection_attempts";
pub const TETRAHEDRON_OPTIMIZATION_LOCAL_RECONNECTION_ACCEPTED_COUNT: &str =
    "optimization_local_reconnection_accepted";
pub const TETRAHEDRON_OPTIMIZATION_LOCAL_RECONNECTION_REJECTED_COUNT: &str =
    "optimization_local_reconnection_rejected";
pub const TETRAHEDRON_OPTIMIZATION_LOCAL_RECONNECTION_BUDGET_LIMIT_COUNT: &str =
    "optimization_local_reconnection_budget_limited";
pub const TETRAHEDRON_OPTIMIZATION_LOCAL_RECONNECTION_REJECTION_PREFIX: &str =
    "optimization_local_reconnection_rejected_";
pub const TETRAHEDRON_OPTIMIZATION_INTERIOR_SMOOTHING_ATTEMPT_COUNT: &str =
    "optimization_interior_smoothing_attempts";
pub const TETRAHEDRON_OPTIMIZATION_INTERIOR_SMOOTHING_ACCEPTED_COUNT: &str =
    "optimization_interior_smoothing_accepted";
pub const TETRAHEDRON_OPTIMIZATION_INTERIOR_SMOOTHING_REJECTED_COUNT: &str =
    "optimization_interior_smoothing_rejected";
pub const TETRAHEDRON_OPTIMIZATION_INTERIOR_SMOOTHING_BUDGET_LIMIT_COUNT: &str =
    "optimization_interior_smoothing_budget_limited";
pub const TETRAHEDRON_OPTIMIZATION_INTERIOR_SMOOTHING_REJECTION_PREFIX: &str =
    "optimization_interior_smoothing_rejected_";
pub const TETRAHEDRON_OPTIMIZATION_BOUNDARY_SMOOTHING_ATTEMPT_COUNT: &str =
    "optimization_boundary_smoothing_attempts";
pub const TETRAHEDRON_OPTIMIZATION_BOUNDARY_SMOOTHING_ACCEPTED_COUNT: &str =
    "optimization_boundary_smoothing_accepted";
pub const TETRAHEDRON_OPTIMIZATION_BOUNDARY_SMOOTHING_REJECTED_COUNT: &str =
    "optimization_boundary_smoothing_rejected";
pub const TETRAHEDRON_OPTIMIZATION_BOUNDARY_SMOOTHING_BUDGET_LIMIT_COUNT: &str =
    "optimization_boundary_smoothing_budget_limited";
pub const TETRAHEDRON_OPTIMIZATION_BOUNDARY_SMOOTHING_REJECTION_PREFIX: &str =
    "optimization_boundary_smoothing_rejected_";
pub const TETRAHEDRON_OPTIMIZATION_SLIVER_REMOVAL_ATTEMPT_COUNT: &str =
    "optimization_sliver_removal_attempts";
pub const TETRAHEDRON_OPTIMIZATION_SLIVER_REMOVAL_ACCEPTED_COUNT: &str =
    "optimization_sliver_removal_accepted";
pub const TETRAHEDRON_OPTIMIZATION_SLIVER_REMOVAL_REJECTED_COUNT: &str =
    "optimization_sliver_removal_rejected";
pub const TETRAHEDRON_OPTIMIZATION_SLIVER_REMOVAL_BUDGET_LIMIT_COUNT: &str =
    "optimization_sliver_removal_budget_limited";
pub const TETRAHEDRON_OPTIMIZATION_SLIVER_REMOVAL_REJECTION_PREFIX: &str =
    "optimization_sliver_removal_rejected_";
pub const TETRAHEDRON_UNTANGLING_PASS_COUNT: &str = "untangling_passes";
pub const TETRAHEDRON_UNTANGLING_INITIAL_NEAR_SINGULAR_COUNT: &str =
    "untangling_initial_near_singular";
pub const TETRAHEDRON_UNTANGLING_FINAL_NEAR_SINGULAR_COUNT: &str = "untangling_final_near_singular";
pub const TETRAHEDRON_UNTANGLING_RELOCATED_SEED_COUNT: &str = "untangling_relocated_seeds";
pub const TETRAHEDRON_UNTANGLING_REJECTION_PREFIX: &str = "untangling_rejected_";
pub const TETRAHEDRON_EXACT_QUALITY_REPAIR_PASS_COUNT: &str = "exact_quality_repair_passes";
pub const TETRAHEDRON_EXACT_QUALITY_SEED_STAR_RELOCATION_COUNT: &str =
    "exact_quality_seed_star_relocations";
pub const TETRAHEDRON_EXACT_QUALITY_UNREPAIRED_TOTAL_COUNT: &str = "exact_quality_unrepaired_total";
pub const TETRAHEDRON_EXACT_QUALITY_UNREPAIRED_INTERIOR_SEED_COUNT: &str =
    "exact_quality_unrepaired_interior_seeds";
pub const TETRAHEDRON_EXACT_QUALITY_REPAIR_REJECTION_PREFIX: &str =
    "exact_quality_repair_rejected_";

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TetrahedronMesh {
    pub mesh_id: String,
    pub tetrahedron_generation_family: String,
    #[serde(default)]
    pub nodes: Vec<TetrahedronMeshNode>,
    #[serde(default)]
    pub elements: Vec<Tetrahedron4Element>,
    #[serde(default)]
    pub boundary_faces: Vec<TetrahedronBoundaryFace>,
    pub recovery_complete: bool,
    pub quality_optimized: bool,
    pub evidence: StageEvidence,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TetrahedronMeshNode {
    pub node_id: TopologyEntityId,
    pub coordinates_m: [f64; 3],
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Tetrahedron4Element {
    pub element_id: TopologyEntityId,
    pub node_ids: [TopologyEntityId; 4],
    pub material_region_id: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TetrahedronBoundaryFace {
    pub face_id: TopologyEntityId,
    pub node_ids: [TopologyEntityId; 3],
    pub source_face_id: TopologyEntityId,
    #[serde(default)]
    pub source_edge_ids: [Option<TopologyEntityId>; 3],
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn tetrahedron_mesh_deserialization_requires_generation_family() {
        let err = serde_json::from_value::<TetrahedronMesh>(serde_json::json!({
            "mesh_id": "missing_generation_family",
            "nodes": [],
            "elements": [],
            "boundary_faces": [],
            "recovery_complete": false,
            "quality_optimized": false,
            "evidence": {
                "stage": "tetrahedron_mesh",
                "status": "complete",
                "entity_counts": {},
                "checks": []
            }
        }))
        .expect_err("serialized Tetrahedron meshes must name their generation family");

        assert!(err
            .to_string()
            .contains("missing field `tetrahedron_generation_family`"));
    }
}