runmat-meshing 0.6.0

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

use runmat_meshing_core::contracts::AnalysisMeshArtifact;

mod error;
mod types;
mod validation;

pub use error::FieldMappingError;
pub use types::{BoundaryFaceScalarValue, BoundaryFaceVectorValue, BoundaryNodeVectorValue};

use validation::{
    validate_nodal_vector_field, validate_nodal_vector_to_boundary_face_topology,
    validate_nodal_vector_to_boundary_node_topology,
    validate_volume_scalar_to_boundary_face_topology,
};

pub fn map_volume_scalar_field_to_boundary_faces(
    mesh: &AnalysisMeshArtifact,
    element_values: &[f64],
) -> Result<Vec<BoundaryFaceScalarValue>, FieldMappingError> {
    validate_volume_scalar_to_boundary_face_topology(mesh)?;
    if element_values.len() != mesh.volume_elements.len() {
        return Err(FieldMappingError::ElementFieldLengthMismatch {
            element_value_count: element_values.len(),
            volume_element_count: mesh.volume_elements.len(),
        });
    }
    for (element_index, value) in element_values.iter().enumerate() {
        if !value.is_finite() {
            return Err(FieldMappingError::NonFiniteElementValue { element_index });
        }
    }

    let element_values_by_id = mesh
        .volume_elements
        .iter()
        .zip(element_values.iter().copied())
        .map(|(element, value)| (element.element_id.as_str(), value))
        .collect::<BTreeMap<_, _>>();

    mesh.boundary_faces
        .iter()
        .map(|face| {
            if face.adjacent_volume_element_ids.is_empty() {
                return Err(FieldMappingError::BoundaryFaceMissingAdjacentVolume {
                    face_id: face.face_id.clone(),
                });
            }
            let mut value_sum = 0.0_f64;
            for volume_element_id in &face.adjacent_volume_element_ids {
                let Some(value) = element_values_by_id
                    .get(volume_element_id.as_str())
                    .copied()
                else {
                    return Err(FieldMappingError::BoundaryFaceReferencesUnknownVolume {
                        face_id: face.face_id.clone(),
                        volume_element_id: volume_element_id.clone(),
                    });
                };
                value_sum += value;
            }
            Ok(BoundaryFaceScalarValue {
                face_id: face.face_id.clone(),
                value: value_sum / face.adjacent_volume_element_ids.len() as f64,
            })
        })
        .collect()
}

pub fn map_nodal_vector_field_to_boundary_nodes(
    mesh: &AnalysisMeshArtifact,
    node_values: &[[f64; 3]],
) -> Result<Vec<BoundaryNodeVectorValue>, FieldMappingError> {
    validate_nodal_vector_to_boundary_node_topology(mesh)?;
    let node_values_by_id = validate_nodal_vector_field(mesh, node_values)?;
    let mut boundary_node_ids = BTreeMap::<u32, ()>::new();

    for face in &mesh.boundary_faces {
        for node_id in &face.node_ids {
            if !node_values_by_id.contains_key(node_id) {
                return Err(FieldMappingError::BoundaryFaceReferencesUnknownNode {
                    face_id: face.face_id.clone(),
                    node_id: *node_id,
                });
            }
            boundary_node_ids.insert(*node_id, ());
        }
    }
    for edge in &mesh.boundary_edges {
        for node_id in edge.node_ids {
            if !node_values_by_id.contains_key(&node_id) {
                return Err(FieldMappingError::BoundaryEdgeReferencesUnknownNode {
                    edge_id: edge.edge_id.clone(),
                    node_id,
                });
            }
            boundary_node_ids.insert(node_id, ());
        }
    }

    Ok(boundary_node_ids
        .keys()
        .map(|node_id| BoundaryNodeVectorValue {
            node_id: *node_id,
            value: node_values_by_id[node_id],
        })
        .collect())
}

pub fn map_nodal_vector_field_to_boundary_faces(
    mesh: &AnalysisMeshArtifact,
    node_values: &[[f64; 3]],
) -> Result<Vec<BoundaryFaceVectorValue>, FieldMappingError> {
    validate_nodal_vector_to_boundary_face_topology(mesh)?;
    let node_values_by_id = validate_nodal_vector_field(mesh, node_values)?;

    mesh.boundary_faces
        .iter()
        .map(|face| {
            if face.node_ids.is_empty() {
                return Err(FieldMappingError::BoundaryFaceHasNoNodes {
                    face_id: face.face_id.clone(),
                });
            }
            let mut value_sum = [0.0_f64; 3];
            for node_id in &face.node_ids {
                let Some(value) = node_values_by_id.get(node_id).copied() else {
                    return Err(FieldMappingError::BoundaryFaceReferencesUnknownNode {
                        face_id: face.face_id.clone(),
                        node_id: *node_id,
                    });
                };
                for component in 0..3 {
                    value_sum[component] += value[component];
                }
            }
            let node_count = face.node_ids.len() as f64;
            Ok(BoundaryFaceVectorValue {
                face_id: face.face_id.clone(),
                value: [
                    value_sum[0] / node_count,
                    value_sum[1] / node_count,
                    value_sum[2] / node_count,
                ],
            })
        })
        .collect()
}

#[cfg(test)]
mod tests;