I/O
Mesh I/O
Sentinel.read_mesh_legacy Function
read_mesh_legacy(nod_file, elm_file)Read a legacy NLI mesh from .nod and .elm files and construct a Ferrite Grid.
Handles hex27 (27 nodes/element) and tet4 (4 nodes/element) meshes. Applies the Fortran-to-Ferrite node ordering permutation for hex27 elements.
For hex27 meshes, Ferrite's Hexahedron cell stores only 8 corner nodes. The full 27-node connectivity (in Ferrite order) is returned separately as full_connectivity for use in write_mesh_legacy and compute_gp_physical_coords.
Returns
Named tuple (grid, node_mtrltype, elem_mtrltype, full_connectivity):
grid::Grid— Ferrite grid object (8-node Hexahedron cells for hex27 meshes)node_mtrltype::Vector{Int}— material type tag per nodeelem_mtrltype::Vector{Int}— material type tag per elementfull_connectivity::Union{Nothing, Matrix{Int}}— full 27-node Ferrite-ordered connectivity (ne × 27), ornothingfor non-hex27 meshes
Sentinel.write_mesh_legacy Function
write_mesh_legacy(grid, stem; node_mtrltype, elem_mtrltype, full_connectivity)Write a Ferrite Grid to legacy NLI .nod and .elm files.
Writes stem.nod and stem.elm.
Arguments
grid— Ferrite Grid objectstem— output file stem (without extension)node_mtrltype— material type per node (default: all ones)elem_mtrltype— material type per element (default: all ones)full_connectivity— for hex27 meshes: the full 27-node Ferrite-ordered connectivity (ne × 27) fromread_mesh_legacy. Required for hex27 output since FerriteHexahedroncells only store 8 corner nodes.
Legacy File Formats
Sentinel.read_nod_file Function
read_nod_file(filename) -> (coords, mtrltype)Read a legacy .nod file containing node coordinates and material types.
Returns
coords::Matrix{Float64}:(nn, 3)matrix of[x, y, z]coordinatesmtrltype::Vector{Int}:(nn,)material type tag per node
Format
Each row: node_index x y z [mtrltype] (1-based, ASCII, free-format). The mtrltype column is optional; defaults to 1 if absent (e.g., 4-column material mesh files).
Sentinel.write_nod_file Function
write_nod_file(filename, coords, mtrltype)Write a legacy .nod file.
Arguments
filename: output file pathcoords::Matrix{Float64}:(nn, 3)node coordinatesmtrltype::Vector{Int}:(nn,)material type per node
Sentinel.read_elm_file Function
read_elm_file(filename) -> (connectivity, mtrltype, npe)Read a legacy .elm file containing element connectivity.
Returns
connectivity::Matrix{Int}:(ne, npe)node indices (1-based)mtrltype::Vector{Int}:(ne,)element material typenpe::Int: nodes per element (auto-detected from column count)
Format
Each row: elm_index n₁ n₂ … nₙₚₑ elmmtrltype (1-based, ASCII, free-format)
Sentinel.write_elm_file Function
write_elm_file(filename, connectivity, mtrltype)Write a legacy .elm file.
Arguments
filename: output file pathconnectivity::Matrix{Int}:(ne, npe)node indices (1-based)mtrltype::Vector{Int}:(ne,)element material type
Sentinel.read_dsp_file Function
read_dsp_file(filename) -> Matrix{ComplexF64}Read a legacy .dsp file containing complex displacement vectors.
Returns
disp::Matrix{ComplexF64}:(nn, 3)complex displacements[u, v, w]
Format
Each row: node_index Re(u) Im(u) Re(v) Im(v) Re(w) Im(w) (ASCII)
Sentinel.write_dsp_file Function
write_dsp_file(filename, disp)Write a legacy .dsp file.
Arguments
filename: output file pathdisp::Matrix{ComplexF64}:(nn, 3)complex displacements[u, v, w]
Sentinel.read_mtr_file Function
read_mtr_file(filename) -> Matrix{Float64}Read a legacy .mtr file containing material property values.
Note: In the Fortran convention, real and imaginary parts are stored in separate files (suffixed .RE. and .IM.). This function reads a single file (either real or imaginary part).
Returns
values::Matrix{Float64}:(np, nvpp)property values
Format
Each row: index val₁ val₂ … valₙᵥₚₚ (ASCII)
Sentinel.write_mtr_file Function
write_mtr_file(filename, values)Write a legacy .mtr file.
Arguments
filename: output file pathvalues::Matrix{Float64}:(np, nvpp)property values
Sentinel.read_pre_file Function
read_pre_file(filename) -> Matrix{ComplexF64}Read a legacy .pre file containing complex pressure values.
Returns
press::Matrix{ComplexF64}:(np, nppp)complex pressure values wherenpis number of elements (viscoelastic) or nodes (poroelastic) andnpppis pressure DOFs per point.
Format
Each row: index Re(p₁) Im(p₁) Re(p₂) Im(p₂) … (ASCII)
Sentinel.write_pre_file Function
write_pre_file(filename, press)Write a legacy .pre file.
Arguments
filename: output file pathpress::Matrix{ComplexF64}:(np, nppp)complex pressure values
Sentinel.read_bnd_file Function
read_bnd_file(filename) -> Vector{Int}Read a legacy .bnd file containing a list of boundary node IDs.
Returns
node_ids::Vector{Int}: sorted boundary node indices (1-based)
Format
Each row: seq_index node_id (1-based, ASCII)
Sentinel.write_bnd_file Function
write_bnd_file(filename, node_ids)Write a legacy .bnd file.
Arguments
filename: output file pathnode_ids: boundary node indices (will be sorted in output)
Sentinel.read_bcs_file Function
read_bcs_file(filename) -> Matrix{ComplexF64}Read a legacy .bcs file containing complex displacement boundary conditions.
Returns
bcs::Matrix{ComplexF64}:(n, 3)complex displacements[ux, uy, uz]wherenis the number of lines (typically total nodes in the mesh)
Format
Each row: node_id Re(ux) Im(ux) Re(uy) Im(uy) Re(uz) Im(uz) (ASCII)
Sentinel.write_bcs_file Function
write_bcs_file(filename, bcs)Write a legacy .bcs file.
Arguments
filename: output file pathbcs::Matrix{ComplexF64}:(n, 3)complex displacements[ux, uy, uz]
Sentinel.read_basis_file Function
read_basis_file(filename) -> Vector{Int}Read a .basis file containing per-property basis indices (1=nodal, 2=elemental).
Returns
basis::Vector{Int}: basis index per property
Format
Single line: basis₁ basis₂ … basisₙ (space-separated integers)
Sentinel.write_basis_file Function
write_basis_file(filename, basis)Write a .basis file.
Arguments
filename: output file pathbasis::Vector{Int}: basis index per property
Sentinel.read_meshind_file Function
read_meshind_file(filename) -> Matrix{Int}Read a .meshind file containing material mesh assignment indices.
Returns
meshind::Matrix{Int}:(2, numprop)— row 1 = real part mesh index, row 2 = imaginary part mesh index, per property
Format
Two rows of numprop space-separated integers each.
Sentinel.write_meshind_file Function
write_meshind_file(filename, meshind)Write a .meshind file.
Arguments
filename: output file pathmeshind::Matrix{Int}:(2, numprop)mesh indices
Material Mesh
Sentinel.MaterialMesh Type
MaterialMeshStructured hex8 material property mesh used for material parameterization. Not a Ferrite Grid — this is a simple structured grid used only for property interpolation via the GP2MTR mapping.
Fields
nn,ne: number of nodes and elementscoords::Matrix{Float64}: (nn, 3) node coordinatesconnectivity::Matrix{Int}: (ne, 8) element connectivity (Fortran hex8 ordering)origin::SVector{3,Float64}: mesh minimum corner (material mesh, may extend beyond disp mesh)res::SVector{3,Float64}: element size [dx, dy, dz]dims::NTuple{3,Int}: (nex, ney, nez) element counts per directionelnum::Array{Int,3}: (nex, ney, nez) → element number lookup
Sentinel.build_material_mesh Function
build_material_mesh(disp_grid, res8) -> MaterialMeshConstruct a structured hex8 material property mesh that overlaps the displacement mesh with the specified resolution.
Ports buildhex8mtrlmesh from MRE-Zone.f90 (lines 6819-6937).
Arguments
disp_grid: Ferrite Grid (displacement mesh)res8: element size [dx, dy, dz] as a 3-element vector
Sentinel.read_material_mesh Function
read_material_mesh(nod_file, elm_file) -> MaterialMeshRead a structured hex8 material mesh from legacy .nod/.elm files. Computes resolution and grid structure from the mesh geometry.
Assumes the mesh is a regular structured hex8 grid.
sourceGP2MTR Mapping
Sentinel.GP2MtrPoint Type
GP2MtrPointMapping from a single displacement Gauss point to its location on the material property mesh.
Ports the Fortran mtrlconvert type from FEmesh.f90.
Fields
mtrelm::Int: material mesh element containing this Gauss point (1-based)basis::SVector{8,Float64}: hex8 basis function values at the GP locationlocalcoords::SVector{3,Float64}: local (ξ,η,ζ) coords within mtrelm
Sentinel.build_gp2mtr Function
build_gp2mtr(disp_grid, mtrl_mesh::MaterialMesh; ngp::Int=3) -> GP2MtrBuild the Gauss-point-to-material-mesh mapping for all elements in the displacement mesh.
For each hex27 displacement element and each 3D Gauss point:
Computes physical GP coordinates using the axis-aligned hex27 formula
Finds containing hex8 material element via floor-division
Computes local (ξ,η,ζ) coordinates within the material element
Evaluates hex8 basis functions at those coordinates
Ports the gp2mtr builder from MRE-Zone.f90 (lines 6997-7069).
Arguments
disp_grid: Ferrite Grid (hex27 displacement mesh)mtrl_mesh: MaterialMesh (structured hex8 material mesh)ngp: number of Gauss points per direction (default 3)
Returns
GP2Mtr with mapping data indexed as gp2mtr.data[el, ig, jg, kg].
Sentinel.compute_gp_physical_coords Function
compute_gp_physical_coords(grid, el, xi_g, eta_g, zeta_g) -> (x, y, z)Compute physical coordinates of a Gauss point for an axis-aligned hexahedral element using its 8 corner nodes.
Equivalent to the Fortran formula (MRE-Zone.f90 line 7031) but works with Ferrite's 8-node Hexahedron cells instead of requiring all 27 nodes.
Ferrite Hexahedron corner ordering (VTK): 1(-,-,-), 2(+,-,-), 3(+,+,-), 4(-,+,-), 5(-,-,+), 6(+,-,+), 7(+,+,+), 8(-,+,+)
Formula: x_gp = center + ξ * half_width for each direction.
Sentinel.hex8_basis Function
hex8_basis(xi, eta, zeta) -> SVector{8, Float64}Evaluate the 8 trilinear hex8 basis functions at local coordinates (xi, eta, zeta).
Uses the Fortran hex8 node ordering (hex8.f90): 1:(-1,-1,-1), 2:(-1,+1,-1), 3:(+1,+1,-1), 4:(+1,-1,-1) 5:(-1,-1,+1), 6:(-1,+1,+1), 7:(+1,+1,+1), 8:(+1,-1,+1)
Each: P_a = (1/8)(1 + ξ_a·ξ)(1 + η_a·η)(1 + ζ_a·ζ)
Sentinel.hex8_basis_gradient Function
hex8_basis_gradient(xi, eta, zeta) -> SMatrix{8, 3, Float64}Evaluate the gradients of the 8 trilinear hex8 basis functions with respect to local coordinates (ξ, η, ζ). Returns an 8×3 matrix where row i gives [∂Nᵢ/∂ξ, ∂Nᵢ/∂η, ∂Nᵢ/∂ζ].
Uses the Fortran hex8 node ordering (same as hex8_basis).
Sentinel.interpolate_property_at_gp Function
interpolate_property_at_gp(gpt::GP2MtrPoint, values::AbstractMatrix,
connectivity::AbstractMatrix{Int}, val_idx::Int=1;
basis_type::Int=1) -> Float64Interpolate a single (real or imaginary) material property value at a Gauss point using the precomputed GP2MTR mapping.
Arguments
gpt: precomputed GP2MtrPoint for this Gauss pointvalues: property values matrix (npr × nvpp) or (npi × nvpp)connectivity: material mesh element connectivity (ne × 8)val_idx: which column ofvaluesto interpolatebasis_type: 1 = nodal (interpolate using hex8 basis), 2 = elemental (constant per element)
Returns
Interpolated property value at the Gauss point.
sourceSentinel.interpolate_complex_property_at_gp Function
interpolate_complex_property_at_gp(gpt_r::GP2MtrPoint, gpt_i::GP2MtrPoint,
prop::SingleProperty,
r_connectivity::AbstractMatrix{Int},
i_connectivity::AbstractMatrix{Int},
val_idx::Int=1) -> ComplexF64Interpolate a complex material property at a Gauss point. Real and imaginary parts may be on different material meshes with different GP2MTR mappings.
Returns
Scaled complex property value: scalar[1] * r_interp + i * scalar[2] * i_interp
Sentinel.gauss_points_1d Function
gauss_points_1d(n) -> SVector{n, Float64}Return the n-point Gauss-Legendre quadrature points on [-1, 1]. Matches the Fortran gaussinit routine.
sourceSentinel.gauss_weights_1d Function
gauss_weights_1d(n) -> SVector{n, Float64}Return the n-point Gauss-Legendre quadrature weights on [-1, 1]. Matches the Fortran gaussinit routine.
sourceMaterial Evaluation
Sentinel.powerlaw_complex Function
powerlaw_complex(θ₀_r, α_r, θ₀_i, α_i, ω, ω₀) -> ComplexF64Compute the complex material property using a power-law frequency model: θ*(ω) = (θ₀_r · (ω/ω₀)^α_r) + i·(θ₀_i · (ω/ω₀)^α_i)
This is used when multifreqind[prop] == true in the Fortran code.
Arguments
θ₀_r,α_r: real part amplitude and power-law exponentθ₀_i,α_i: imaginary part amplitude and power-law exponentω: current angular frequency [rad/s]ω₀: reference angular frequency rad/s
Sentinel.standard_complex Function
standard_complex(θ_r::Float64, θ_i::Float64,
scalar_r::Float64, scalar_i::Float64) -> ComplexF64Compute the complex material property using simple real/imaginary scaling: θ* = scalar_r · θ_r + i · scalar_i · θ_i
Used when multifreqind[prop] == false.
Sentinel.evaluate_property_at_gp Function
evaluate_property_at_gp(prop::SingleProperty, gp_elem::Int,
gp_basis::AbstractVector{Float64},
r_connectivity::AbstractMatrix{Int},
i_connectivity::AbstractMatrix{Int},
ω::Float64; powerlaw::Bool=false,
val_idx::Int=1) -> ComplexF64Evaluate a single material property at a Gauss point by interpolating from the material mesh using the precomputed basis function values and material mesh connectivity.
Arguments
prop: theSinglePropertyto evaluategp_elem: index of the material mesh element containing this Gauss pointgp_basis: basis function values at the Gauss point withingp_elem(8 values for hex8 material mesh)r_connectivity: material mesh element connectivity for real part (ne × 8)i_connectivity: material mesh element connectivity for imaginary part (ne × 8)ω: angular frequency [rad/s]powerlaw: iftrue, use power-law frequency scalingval_idx: which value column to interpolate (1 for standard)
Returns
Complex property value at the Gauss point.
sourceRunfile Parser
Sentinel.RunfileConfig Type
RunfileConfigStores all parsed data from an NLI .dat runfile. File paths are stored as strings (not loaded) — use setup_forward_problem to load everything.
Supports problemtype=0 (forward) with forwardtype=0 (simulation) and forwardtype=1 (parameter test).
Sentinel.parse_runfile Function
parse_runfile(filename) -> RunfileConfigParse an NLI .dat runfile and return a RunfileConfig with all settings.
Skips ! comment lines and blank lines. Processes data lines sequentially matching the Fortran readinitial / readforward read order.
Currently supports problemtype=0 (forward) with forwardtype=0 and 1.
Sentinel.write_runfile Function
write_runfile(filename, config::RunfileConfig)Write a .dat runfile from a RunfileConfig. The output matches the Fortran read order so that parse_runfile(write_runfile(f, c)) round-trips correctly.
Sentinel.setup_forward_problem Function
setup_forward_problem(config::RunfileConfig, basedir::AbstractString=".")Load all files referenced by a RunfileConfig and return a NamedTuple with the complete forward problem setup ready for assembly and solve.
Resolves relative paths against basedir.
Returns
Named tuple with fields:
grid: Ferrite Grid (hex27 elements stored as 8-node Hexahedron)dh: DofHandler with vector displacement fieldcv_disp: CellValues for scalar hex27 basis (27 shape functions)model: AbstractMaterialModel dispatch singletonmaterial: Material struct with loaded propertiesbcs: BoundaryConditions (populated for forwardtype=1)meshes: Vector{MaterialMesh} — material property meshesgp2mtrs: Vector{GP2Mtr} — Gauss-point-to-material-mesh mappingsomega: angular frequency ω = 2π·fK: pre-allocated global stiffness matrixfull_connectivity: ne×27 Ferrite-ordered connectivity (or nothing)config: the original RunfileConfigsolver: DirectSolver()
Inverse Runfile Parser & Setup
The inverse runfile parser, inverse problem setup, and zone-based solver are documented in Zone Decomposition.
Material Mesh Generation
Generate structured hex8 material meshes for inverse problems, matching the voxel grid used by Fortran MRE-Zone.
Sentinel.generate_regular_material_mesh Function
generate_regular_material_mesh(bbox_min, bbox_max, resolution) -> MaterialMeshGenerate a regular hex8 material property mesh covering the specified bounding box.
Arguments
bbox_min::SVector{3,Float64}: minimum corner of the bounding boxbbox_max::SVector{3,Float64}: maximum corner of the bounding boxresolution::SVector{3,Float64}: element size [dx, dy, dz]
Returns
A MaterialMesh with structured hex8 elements covering the bounding box, compatible with build_gp2mtr for GP-to-material-mesh mapping.