runmat-meshing 0.6.0

Topology-first meshing pipeline orchestration for RunMat
Documentation
use std::collections::BTreeMap;

use runmat_meshing_core::contracts::{
    AnalysisFieldTopologyLocation, AnalysisMeshArtifact, ANALYSIS_MESH_BOUNDARY_EDGE_TOPOLOGY_ID,
    ANALYSIS_MESH_BOUNDARY_FACE_TOPOLOGY_ID, ANALYSIS_MESH_FIELD_TOPOLOGY_ID,
    TETRAHEDRON4_FIELD_ELEMENT_KIND, TRI3_FIELD_ELEMENT_KIND,
};

use super::FieldMappingError;

pub(super) fn validate_volume_scalar_to_boundary_face_topology(
    mesh: &AnalysisMeshArtifact,
) -> Result<(), FieldMappingError> {
    validate_field_topology_count(
        mesh,
        ANALYSIS_MESH_FIELD_TOPOLOGY_ID,
        AnalysisFieldTopologyLocation::VolumeElement,
        Some(TETRAHEDRON4_FIELD_ELEMENT_KIND),
        mesh.volume_elements.len(),
    )?;
    validate_field_topology_count(
        mesh,
        ANALYSIS_MESH_BOUNDARY_FACE_TOPOLOGY_ID,
        AnalysisFieldTopologyLocation::BoundaryFace,
        Some(TRI3_FIELD_ELEMENT_KIND),
        mesh.boundary_faces.len(),
    )
}

pub(super) fn validate_nodal_vector_to_boundary_node_topology(
    mesh: &AnalysisMeshArtifact,
) -> Result<(), FieldMappingError> {
    validate_field_topology_count(
        mesh,
        ANALYSIS_MESH_FIELD_TOPOLOGY_ID,
        AnalysisFieldTopologyLocation::Node,
        None,
        mesh.nodes.len(),
    )?;
    validate_field_topology_count(
        mesh,
        ANALYSIS_MESH_BOUNDARY_FACE_TOPOLOGY_ID,
        AnalysisFieldTopologyLocation::BoundaryFace,
        Some(TRI3_FIELD_ELEMENT_KIND),
        mesh.boundary_faces.len(),
    )?;
    validate_field_topology_count(
        mesh,
        ANALYSIS_MESH_BOUNDARY_EDGE_TOPOLOGY_ID,
        AnalysisFieldTopologyLocation::BoundaryEdge,
        None,
        mesh.boundary_edges.len(),
    )
}

pub(super) fn validate_nodal_vector_to_boundary_face_topology(
    mesh: &AnalysisMeshArtifact,
) -> Result<(), FieldMappingError> {
    validate_field_topology_count(
        mesh,
        ANALYSIS_MESH_FIELD_TOPOLOGY_ID,
        AnalysisFieldTopologyLocation::Node,
        None,
        mesh.nodes.len(),
    )?;
    validate_field_topology_count(
        mesh,
        ANALYSIS_MESH_BOUNDARY_FACE_TOPOLOGY_ID,
        AnalysisFieldTopologyLocation::BoundaryFace,
        Some(TRI3_FIELD_ELEMENT_KIND),
        mesh.boundary_faces.len(),
    )
}

fn validate_field_topology_count(
    mesh: &AnalysisMeshArtifact,
    topology_id: &str,
    location: AnalysisFieldTopologyLocation,
    element_kind: Option<&str>,
    actual_entity_count: usize,
) -> Result<(), FieldMappingError> {
    let same_location = mesh
        .field_topology
        .iter()
        .filter(|descriptor| {
            descriptor.topology_id == topology_id && descriptor.location == location
        })
        .collect::<Vec<_>>();
    if same_location.is_empty() {
        return Err(FieldMappingError::FieldTopologyMissing {
            topology_id: topology_id.to_string(),
            location,
            element_kind: element_kind.map(str::to_string),
        });
    }

    let descriptor = same_location
        .iter()
        .copied()
        .find(|descriptor| descriptor.element_kind.as_deref() == element_kind)
        .ok_or_else(|| FieldMappingError::FieldTopologyElementKindMismatch {
            topology_id: topology_id.to_string(),
            location,
            expected_element_kind: element_kind.unwrap_or("none").to_string(),
            actual_element_kind: same_location
                .first()
                .and_then(|descriptor| descriptor.element_kind.clone()),
        })?;

    if descriptor.entity_count != actual_entity_count {
        return Err(FieldMappingError::FieldTopologyCountMismatch {
            topology_id: topology_id.to_string(),
            location,
            expected_entity_count: descriptor.entity_count,
            actual_entity_count,
        });
    }

    Ok(())
}

pub(super) fn validate_nodal_vector_field<'a>(
    mesh: &'a AnalysisMeshArtifact,
    node_values: &'a [[f64; 3]],
) -> Result<BTreeMap<u32, [f64; 3]>, FieldMappingError> {
    if node_values.len() != mesh.nodes.len() {
        return Err(FieldMappingError::NodeVectorFieldLengthMismatch {
            node_value_count: node_values.len(),
            node_count: mesh.nodes.len(),
        });
    }
    for (node_index, value) in node_values.iter().enumerate() {
        for (component_index, component) in value.iter().enumerate() {
            if !component.is_finite() {
                return Err(FieldMappingError::NonFiniteNodeVectorValue {
                    node_index,
                    component_index,
                });
            }
        }
    }

    Ok(mesh
        .nodes
        .iter()
        .zip(node_values.iter().copied())
        .map(|(node, value)| (node.node_id, value))
        .collect())
}