use super::functions::*;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FailureCriterion {
PlasticStrain(f64),
PrincipalStress(f64),
Damage(f64),
VolumeFraction(f64),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MassLumping {
RowSum,
Hrz,
NodalVolume,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum HourglassType {
Stiffness,
Viscous,
Combined,
}
pub struct ContactDetectionFem {
pub enforcement: ContactEnforcement,
pub penalty_stiffness: f64,
pub augmentation: f64,
pub friction_coeff: f64,
pub pairs: Vec<ContactPair>,
pub search_radius: f64,
}
impl ContactDetectionFem {
pub fn new(
enforcement: ContactEnforcement,
penalty_stiffness: f64,
friction_coeff: f64,
) -> Self {
Self {
enforcement,
penalty_stiffness,
augmentation: penalty_stiffness,
friction_coeff,
pairs: Vec::new(),
search_radius: 1e-3,
}
}
pub fn node_to_segment_gap_2d(
slave: [f64; 2],
master_a: [f64; 2],
master_b: [f64; 2],
) -> (f64, f64, [f64; 2]) {
let ab = [master_b[0] - master_a[0], master_b[1] - master_a[1]];
let len2 = ab[0] * ab[0] + ab[1] * ab[1];
let t = if len2 > 1e-30 {
let as_ = [slave[0] - master_a[0], slave[1] - master_a[1]];
(as_[0] * ab[0] + as_[1] * ab[1]) / len2
} else {
0.5
};
let t_clamp = t.clamp(0.0, 1.0);
let proj = [master_a[0] + t_clamp * ab[0], master_a[1] + t_clamp * ab[1]];
let diff = [slave[0] - proj[0], slave[1] - proj[1]];
let _dist = (diff[0] * diff[0] + diff[1] * diff[1]).sqrt();
let seg_len = len2.sqrt().max(1e-30);
let normal = [-ab[1] / seg_len, ab[0] / seg_len];
let gap = diff[0] * normal[0] + diff[1] * normal[1];
(gap, t_clamp, normal)
}
pub fn node_to_segment_gap_3d(
slave: [f64; 3],
master_a: [f64; 3],
master_b: [f64; 3],
) -> (f64, f64, [f64; 3]) {
let ab = [
master_b[0] - master_a[0],
master_b[1] - master_a[1],
master_b[2] - master_a[2],
];
let len2 = ab[0] * ab[0] + ab[1] * ab[1] + ab[2] * ab[2];
let t = if len2 > 1e-30 {
let as_ = [
slave[0] - master_a[0],
slave[1] - master_a[1],
slave[2] - master_a[2],
];
(as_[0] * ab[0] + as_[1] * ab[1] + as_[2] * ab[2]) / len2
} else {
0.5
};
let t_clamp = t.clamp(0.0, 1.0);
let proj = [
master_a[0] + t_clamp * ab[0],
master_a[1] + t_clamp * ab[1],
master_a[2] + t_clamp * ab[2],
];
let diff = [slave[0] - proj[0], slave[1] - proj[1], slave[2] - proj[2]];
let dist = (diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2]).sqrt();
let normal = if dist > 1e-30 {
[diff[0] / dist, diff[1] / dist, diff[2] / dist]
} else {
[0.0, 1.0, 0.0]
};
(dist, t_clamp, normal)
}
pub fn penalty_force(&self, gap: f64) -> f64 {
if gap < 0.0 {
-self.penalty_stiffness * gap
} else {
0.0
}
}
pub fn augmented_lagrangian_force(&self, gap: f64, lambda: f64) -> f64 {
let trial = lambda + self.augmentation * gap;
if trial < 0.0 { -trial } else { 0.0 }
}
pub fn detect_node_to_segment(
&mut self,
node_positions: &[f64],
segments: &[[usize; 2]],
slave_nodes: &[usize],
) {
self.pairs.clear();
for &sn in slave_nodes {
let sp = [
node_positions[sn * 3],
node_positions[sn * 3 + 1],
node_positions[sn * 3 + 2],
];
for &seg in segments {
let ma = [
node_positions[seg[0] * 3],
node_positions[seg[0] * 3 + 1],
node_positions[seg[0] * 3 + 2],
];
let mb = [
node_positions[seg[1] * 3],
node_positions[seg[1] * 3 + 1],
node_positions[seg[1] * 3 + 2],
];
let (gap, _t, normal) = Self::node_to_segment_gap_3d(sp, ma, mb);
if gap < self.search_radius {
let mut pair = ContactPair::new(sn, seg);
pair.gap = gap;
pair.normal = normal;
self.pairs.push(pair);
}
}
}
}
pub fn assemble_contact_forces(&self, force: &mut [f64]) {
for pair in &self.pairs {
let f = match self.enforcement {
ContactEnforcement::Penalty => self.penalty_force(pair.gap),
ContactEnforcement::Lagrange => {
if pair.gap < 0.0 {
pair.lambda
} else {
0.0
}
}
ContactEnforcement::AugmentedLagrangian => {
self.augmented_lagrangian_force(pair.gap, pair.lambda)
}
};
let sn = pair.slave_node;
for dir in 0..3 {
force[sn * 3 + dir] += f * pair.normal[dir];
}
for (k, mn) in pair.master_segment.iter().enumerate() {
let _ = k;
for dir in 0..3 {
force[mn * 3 + dir] -= 0.5 * f * pair.normal[dir];
}
}
}
}
pub fn update_lagrange_multipliers(&mut self) {
for pair in &mut self.pairs {
if pair.gap < 0.0 {
pair.lambda += self.augmentation * pair.gap;
pair.lambda = pair.lambda.max(0.0);
}
}
}
}
pub struct WavePropagationFem {
pub wave_type: WaveType,
pub youngs_modulus: f64,
pub shear_modulus: f64,
pub density: f64,
pub poisson_ratio: f64,
pub characteristic_size: f64,
}
impl WavePropagationFem {
pub fn new(
wave_type: WaveType,
youngs_modulus: f64,
density: f64,
poisson_ratio: f64,
characteristic_size: f64,
) -> Self {
let shear_modulus = youngs_modulus / (2.0 * (1.0 + poisson_ratio));
Self {
wave_type,
youngs_modulus,
shear_modulus,
density,
poisson_ratio,
characteristic_size,
}
}
pub fn p_wave_speed(&self) -> f64 {
let k = self.youngs_modulus / (3.0 * (1.0 - 2.0 * self.poisson_ratio));
let g = self.shear_modulus;
((k + 4.0 * g / 3.0) / self.density).sqrt()
}
pub fn s_wave_speed(&self) -> f64 {
(self.shear_modulus / self.density).sqrt()
}
pub fn bar_wave_speed(&self) -> f64 {
(self.youngs_modulus / self.density).sqrt()
}
pub fn rayleigh_wave_speed(&self) -> f64 {
let nu = self.poisson_ratio;
let cs = self.s_wave_speed();
cs * (0.862 + 1.14 * nu) / (1.0 + nu)
}
pub fn dispersion_curve(&self, frequencies: &[f64]) -> WaveResult {
let c0 = match self.wave_type {
WaveType::Longitudinal => self.bar_wave_speed(),
WaveType::Transverse => self.s_wave_speed(),
WaveType::Flexural => self.bar_wave_speed(),
WaveType::Rayleigh => self.rayleigh_wave_speed(),
};
let nu = self.poisson_ratio;
let r = self.characteristic_size;
let n = frequencies.len();
let mut wave_numbers = vec![0.0; n];
let mut phase_velocities = vec![0.0; n];
let mut group_velocities = vec![0.0; n];
let mut dispersion_param = vec![0.0; n];
for (i, &f) in frequencies.iter().enumerate() {
let omega = 2.0 * std::f64::consts::PI * f;
let k = match self.wave_type {
WaveType::Longitudinal => {
let k0 = omega / c0;
let denom = (1.0 + nu * nu * k0 * k0 * r * r).sqrt();
omega / (c0 * denom)
}
WaveType::Transverse => omega / c0,
WaveType::Flexural => {
let ei = self.youngs_modulus * r * r * r * r / 12.0;
(omega * omega * self.density / ei).sqrt().sqrt()
}
WaveType::Rayleigh => omega / c0,
};
let cp = if k > 1e-30 { omega / k } else { c0 };
let cg = match self.wave_type {
WaveType::Flexural => 2.0 * cp,
_ => cp,
};
wave_numbers[i] = k;
phase_velocities[i] = cp;
group_velocities[i] = cg;
dispersion_param[i] = (cp - c0).abs() / c0;
}
WaveResult {
frequencies: frequencies.to_vec(),
phase_velocities,
group_velocities,
wave_numbers,
dispersion_parameter: dispersion_param,
}
}
pub fn reflection_transmission(&self, density2: f64, wave_speed2: f64) -> (f64, f64) {
let z1 = self.density * self.bar_wave_speed();
let z2 = density2 * wave_speed2;
if (z1 + z2).abs() < 1e-30 {
return (0.0, 1.0);
}
let r = (z2 - z1) / (z2 + z1);
let t = 2.0 * z2 / (z2 + z1);
(r, t)
}
pub fn arrival_time(&self, distance: f64) -> f64 {
distance / self.bar_wave_speed()
}
pub fn hopkinson_pulse(&self, time: f64, amplitude: f64, duration: f64) -> f64 {
if (0.0..=duration).contains(&time) {
amplitude
} else {
0.0
}
}
pub fn fem_dispersion(element_size: f64, wave_speed: f64, frequency: f64) -> f64 {
let omega = 2.0 * std::f64::consts::PI * frequency;
let xi = omega * element_size / wave_speed;
if xi <= 2.0 {
let k_fem = (2.0 / element_size) * (xi / 2.0).asin();
let k_exact = omega / wave_speed;
if k_exact > 1e-30 {
k_fem / k_exact
} else {
1.0
}
} else {
f64::NAN
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BlastModel {
UserDefined,
Friedlander,
ConWep,
Triangular,
}
#[derive(Debug, Clone)]
pub struct HourglassControl {
pub hg_type: HourglassType,
pub stiffness_coeff: f64,
pub viscosity_coeff: f64,
pub n_elements: usize,
pub hourglass_energy: Vec<f64>,
}
impl HourglassControl {
pub const HOURGLASS_GAMMA: [[f64; 8]; 4] = [
[1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0],
[1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0],
[1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0],
[-1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0],
];
pub fn new(
hg_type: HourglassType,
stiffness_coeff: f64,
viscosity_coeff: f64,
n_elements: usize,
) -> Self {
Self {
hg_type,
stiffness_coeff,
viscosity_coeff,
n_elements,
hourglass_energy: vec![0.0; n_elements],
}
}
pub fn compute_mode_amplitudes(&self, node_displacements: &[[f64; 3]; 8]) -> [[f64; 3]; 4] {
let mut amplitudes = [[0.0f64; 3]; 4];
for (mode, gamma) in Self::HOURGLASS_GAMMA.iter().enumerate() {
for dir in 0..3 {
amplitudes[mode][dir] = gamma
.iter()
.zip(node_displacements.iter())
.map(|(&g, nd)| g * nd[dir])
.sum();
}
}
amplitudes
}
pub fn stiffness_forces(
&self,
q_modes: &[[f64; 3]; 4],
element_modulus: f64,
element_volume: f64,
) -> [[f64; 3]; 8] {
let k_hg = self.stiffness_coeff * element_modulus * element_volume.cbrt();
let mut forces = [[0.0f64; 3]; 8];
for (mode, gamma) in Self::HOURGLASS_GAMMA.iter().enumerate() {
for dir in 0..3 {
let f_mode = -k_hg * q_modes[mode][dir];
for (node, &g) in gamma.iter().enumerate() {
forces[node][dir] += f_mode * g;
}
}
}
forces
}
pub fn viscous_forces(
&self,
q_dot_modes: &[[f64; 3]; 4],
density: f64,
wave_speed: f64,
element_volume: f64,
) -> [[f64; 3]; 8] {
let c_hg = self.viscosity_coeff * density * wave_speed * element_volume.powf(1.0 / 3.0);
let mut forces = [[0.0f64; 3]; 8];
for (mode, gamma) in Self::HOURGLASS_GAMMA.iter().enumerate() {
for dir in 0..3 {
let f_mode = -c_hg * q_dot_modes[mode][dir];
for (node, &g) in gamma.iter().enumerate() {
forces[node][dir] += f_mode * g;
}
}
}
forces
}
pub fn combined_forces(
&self,
q_modes: &[[f64; 3]; 4],
q_dot_modes: &[[f64; 3]; 4],
element_modulus: f64,
density: f64,
wave_speed: f64,
element_volume: f64,
) -> [[f64; 3]; 8] {
let f_stiff = self.stiffness_forces(q_modes, element_modulus, element_volume);
let f_visc = self.viscous_forces(q_dot_modes, density, wave_speed, element_volume);
let mut total = [[0.0f64; 3]; 8];
for node in 0..8 {
for dir in 0..3 {
total[node][dir] = f_stiff[node][dir] + f_visc[node][dir];
}
}
total
}
pub fn update_hourglass_energy(
&mut self,
element_idx: usize,
q_modes: &[[f64; 3]; 4],
k_hg: f64,
) {
let energy: f64 = q_modes
.iter()
.flat_map(|q| q.iter())
.map(|q| 0.5 * k_hg * q * q)
.sum();
self.hourglass_energy[element_idx] = energy;
}
pub fn total_hourglass_energy(&self) -> f64 {
self.hourglass_energy.iter().sum()
}
pub fn is_hourglass_acceptable(&self, total_strain_energy: f64, threshold: f64) -> bool {
let hg_energy = self.total_hourglass_energy();
if total_strain_energy > 1e-30 {
hg_energy / total_strain_energy < threshold
} else {
true
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum WaveType {
Longitudinal,
Transverse,
Flexural,
Rayleigh,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ContactEnforcement {
Penalty,
Lagrange,
AugmentedLagrangian,
}
pub struct ExplicitFemSolver {
pub config: ExplicitFemConfig,
pub mass: Vec<f64>,
pub external_force: Vec<f64>,
pub internal_force: Vec<f64>,
pub damping: Vec<f64>,
pub state: ExplicitState,
pub fixed_dof: Vec<bool>,
pub energy_history: Vec<(f64, f64, f64, f64)>,
}
impl ExplicitFemSolver {
pub fn new(n_dof: usize, config: ExplicitFemConfig) -> Self {
Self {
config,
mass: vec![1.0; n_dof],
external_force: vec![0.0; n_dof],
internal_force: vec![0.0; n_dof],
damping: vec![0.0; n_dof],
state: ExplicitState::new(n_dof),
fixed_dof: vec![false; n_dof],
energy_history: Vec::new(),
}
}
pub fn critical_time_step(&self) -> f64 {
self.config.cfl_factor * self.config.min_element_length / self.config.wave_speed
}
pub fn assemble_lumped_mass(&mut self, element_masses: &[(Vec<usize>, f64)]) {
for m in self.mass.iter_mut() {
*m = 0.0;
}
for (dofs, em) in element_masses {
let share = em / dofs.len() as f64;
for &d in dofs {
self.mass[d] += share;
}
}
}
pub fn apply_hrz_correction(&mut self, element_dofs: &[Vec<usize>], consistent_traces: &[f64]) {
for (dofs, &trace) in element_dofs.iter().zip(consistent_traces.iter()) {
let diag_sum: f64 = dofs.iter().map(|&d| self.mass[d]).sum();
if diag_sum > 1e-30 {
let scale = trace / diag_sum;
for &d in dofs {
self.mass[d] *= scale;
}
}
}
}
pub fn step(&mut self, dt: f64, internal_force_fn: impl Fn(&[f64]) -> Vec<f64>) {
let n = self.state.n_dof();
let mut vel_half = vec![0.0; n];
for (i, vh) in vel_half.iter_mut().enumerate().take(n) {
if !self.fixed_dof[i] {
*vh = self.state.velocity[i] + 0.5 * dt * self.state.acceleration[i];
}
}
for (i, &vh) in vel_half.iter().enumerate().take(n) {
if !self.fixed_dof[i] {
self.state.displacement[i] += dt * vh;
}
}
self.internal_force = internal_force_fn(&self.state.displacement);
for (i, &vh) in vel_half.iter().enumerate().take(n) {
if !self.fixed_dof[i] && self.mass[i] > 1e-30 {
let f_net = self.external_force[i] - self.internal_force[i] - self.damping[i] * vh;
self.state.acceleration[i] = f_net / self.mass[i];
} else {
self.state.acceleration[i] = 0.0;
}
}
for (i, &vh) in vel_half.iter().enumerate().take(n) {
if !self.fixed_dof[i] {
self.state.velocity[i] = vh + 0.5 * dt * self.state.acceleration[i];
}
}
self.state.time += dt;
self.state.step += 1;
if self.config.check_energy {
self.record_energy();
}
}
fn record_energy(&mut self) {
let ke: f64 = self
.state
.velocity
.iter()
.zip(self.mass.iter())
.map(|(v, m)| 0.5 * m * v * v)
.sum();
let ie: f64 = self
.internal_force
.iter()
.zip(self.state.displacement.iter())
.map(|(f, u)| f * u)
.sum::<f64>()
.abs()
* 0.5;
self.energy_history.push((self.state.time, ke, ie, ke + ie));
}
pub fn kinetic_energy(&self) -> f64 {
self.state
.velocity
.iter()
.zip(self.mass.iter())
.map(|(v, m)| 0.5 * m * v * v)
.sum()
}
pub fn fix_dofs(&mut self, dofs: &[usize]) {
for &d in dofs {
self.fixed_dof[d] = true;
self.state.displacement[d] = 0.0;
self.state.velocity[d] = 0.0;
self.state.acceleration[d] = 0.0;
}
}
pub fn set_initial_velocity(&mut self, dof: usize, value: f64) {
self.state.velocity[dof] = value;
}
pub fn set_external_force(&mut self, dof: usize, value: f64) {
self.external_force[dof] = value;
}
pub fn estimate_stable_dt_from_stiffness(
&self,
element_stiffness_max: f64,
element_mass_min: f64,
) -> f64 {
if element_stiffness_max > 0.0 && element_mass_min > 0.0 {
self.config.cfl_factor * (2.0 * element_mass_min / element_stiffness_max).sqrt()
} else {
self.critical_time_step()
}
}
pub fn run_until(&mut self, end_time: f64, internal_force_fn: impl Fn(&[f64]) -> Vec<f64>) {
let dt = self.critical_time_step();
while self.state.time < end_time {
let dt_use = dt.min(end_time - self.state.time);
self.step(dt_use, &internal_force_fn);
}
}
}
pub struct BlastLoadingFem {
pub model: BlastModel,
pub params: BlastParameters,
pub surface_nodes: Vec<usize>,
pub node_normals: Vec<[f64; 3]>,
pub tributary_areas: Vec<f64>,
pub pressure_history: Vec<(f64, f64)>,
pub response_history: Vec<(f64, f64)>,
}
impl BlastLoadingFem {
pub fn new(model: BlastModel, params: BlastParameters) -> Self {
Self {
model,
params,
surface_nodes: Vec::new(),
node_normals: Vec::new(),
tributary_areas: Vec::new(),
pressure_history: Vec::new(),
response_history: Vec::new(),
}
}
pub fn pressure_at(&self, t: f64) -> f64 {
match self.model {
BlastModel::Friedlander => {
let p0 = self.params.peak_pressure;
let td = self.params.duration;
let b = self.params.decay_coeff;
if t < 0.0 || t > td {
0.0
} else {
p0 * (1.0 - t / td) * (-b * t / td).exp()
}
}
BlastModel::Triangular => {
let p0 = self.params.peak_pressure;
let td = self.params.duration;
if t < 0.0 || t > td {
0.0
} else {
p0 * (1.0 - t / td)
}
}
BlastModel::ConWep => {
let r = self.params.standoff;
let w = self.params.charge_mass;
let z = r / w.cbrt();
let p_mpa = if z < 0.5 {
67.36 / (z * z * z)
} else {
0.84 + 3.35 / z + 0.66 / (z * z)
};
let p0 = p_mpa * 1e6;
let td = self.params.duration;
if t < 0.0 || t > td {
0.0
} else {
p0 * (1.0 - t / td) * (-b_coeff(z) * t / td).exp()
}
}
BlastModel::UserDefined => interpolate_table(&self.params.table, t),
}
}
pub fn assemble_blast_forces(&self, time: f64, force: &mut [f64]) {
let p = self.pressure_at(time);
for (idx, &node) in self.surface_nodes.iter().enumerate() {
let area = if idx < self.tributary_areas.len() {
self.tributary_areas[idx]
} else {
1.0
};
let normal = if idx < self.node_normals.len() {
self.node_normals[idx]
} else {
[0.0, 1.0, 0.0]
};
for dir in 0..3 {
force[node * 3 + dir] += p * area * normal[dir];
}
}
}
pub fn record_state(&mut self, time: f64, max_displacement: f64) {
let p = self.pressure_at(time);
self.pressure_history.push((time, p));
self.response_history.push((time, max_displacement));
}
pub fn compute_impulse(&self) -> f64 {
if self.pressure_history.len() < 2 {
return 0.0;
}
self.pressure_history
.windows(2)
.map(|w| {
let dt = w[1].0 - w[0].0;
0.5 * (w[0].1 + w[1].1) * dt
})
.sum()
}
pub fn dynamic_load_factor(&self, static_displacement: f64) -> f64 {
let max_disp = self
.response_history
.iter()
.map(|(_, d)| d.abs())
.fold(0.0_f64, f64::max);
if static_displacement.abs() > 1e-30 {
max_disp / static_displacement.abs()
} else {
0.0
}
}
pub fn reflected_pressure(&self, time: f64, p_atm: f64) -> f64 {
let p_i = self.pressure_at(time);
let gamma = 1.4;
let cr = 2.0 + (gamma + 1.0) / 2.0 * (p_i / p_atm);
cr * p_i
}
}
#[derive(Debug, Clone)]
pub struct ExplicitState {
pub displacement: Vec<f64>,
pub velocity: Vec<f64>,
pub acceleration: Vec<f64>,
pub time: f64,
pub step: u64,
}
impl ExplicitState {
pub fn new(n_dof: usize) -> Self {
Self {
displacement: vec![0.0; n_dof],
velocity: vec![0.0; n_dof],
acceleration: vec![0.0; n_dof],
time: 0.0,
step: 0,
}
}
pub fn n_dof(&self) -> usize {
self.displacement.len()
}
}
#[derive(Debug, Clone)]
pub struct BlastParameters {
pub peak_pressure: f64,
pub duration: f64,
pub decay_coeff: f64,
pub standoff: f64,
pub charge_mass: f64,
pub table: Vec<(f64, f64)>,
}
#[derive(Debug, Clone)]
pub struct ElementErosionState {
pub eroded: bool,
pub damage: f64,
pub plastic_strain: f64,
pub max_principal_stress: f64,
pub volume_ratio: f64,
pub mass: f64,
pub eroded_at_step: u64,
}
impl ElementErosionState {
pub fn new(mass: f64) -> Self {
Self {
eroded: false,
damage: 0.0,
plastic_strain: 0.0,
max_principal_stress: 0.0,
volume_ratio: 1.0,
mass,
eroded_at_step: u64::MAX,
}
}
pub fn should_erode(&self, criterion: &FailureCriterion) -> bool {
match criterion {
FailureCriterion::PlasticStrain(eps_f) => self.plastic_strain >= *eps_f,
FailureCriterion::PrincipalStress(sigma_f) => self.max_principal_stress >= *sigma_f,
FailureCriterion::Damage(d_f) => self.damage >= *d_f,
FailureCriterion::VolumeFraction(v_f) => self.volume_ratio <= *v_f,
}
}
}
#[derive(Debug, Clone)]
pub struct ContactPair {
pub slave_node: usize,
pub master_segment: [usize; 2],
pub gap: f64,
pub normal: [f64; 3],
pub pressure: f64,
pub lambda: f64,
}
impl ContactPair {
pub fn new(slave_node: usize, master_segment: [usize; 2]) -> Self {
Self {
slave_node,
master_segment,
gap: 0.0,
normal: [0.0, 1.0, 0.0],
pressure: 0.0,
lambda: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct ExplicitFemConfig {
pub cfl_factor: f64,
pub lumping: MassLumping,
pub wave_speed: f64,
pub min_element_length: f64,
pub bulk_viscosity: f64,
pub check_energy: bool,
}
pub struct ErosionAlgorithm {
pub criterion: FailureCriterion,
pub states: Vec<ElementErosionState>,
pub total_eroded_mass: f64,
pub n_eroded: usize,
pub conserve_mass: bool,
pub debris_positions: Vec<[f64; 3]>,
pub debris_velocities: Vec<[f64; 3]>,
pub debris_masses: Vec<f64>,
}
impl ErosionAlgorithm {
pub fn new(criterion: FailureCriterion, n_elements: usize, conserve_mass: bool) -> Self {
Self {
criterion,
states: vec![ElementErosionState::new(0.0); n_elements],
total_eroded_mass: 0.0,
n_eroded: 0,
conserve_mass,
debris_positions: Vec::new(),
debris_velocities: Vec::new(),
debris_masses: Vec::new(),
}
}
pub fn set_element_mass(&mut self, element_idx: usize, mass: f64) {
self.states[element_idx].mass = mass;
}
pub fn update_element(
&mut self,
element_idx: usize,
damage: f64,
plastic_strain: f64,
max_principal_stress: f64,
volume_ratio: f64,
step: u64,
) -> bool {
let state = &mut self.states[element_idx];
if state.eroded {
return false;
}
state.damage = damage;
state.plastic_strain = plastic_strain;
state.max_principal_stress = max_principal_stress;
state.volume_ratio = volume_ratio;
if state.should_erode(&self.criterion) {
state.eroded = true;
state.eroded_at_step = step;
self.total_eroded_mass += state.mass;
self.n_eroded += 1;
true
} else {
false
}
}
pub fn redistribute_mass_to_nodes(
&self,
element_idx: usize,
node_masses: &mut [f64],
element_nodes: &[usize],
) {
if !self.conserve_mass {
return;
}
let state = &self.states[element_idx];
let share = state.mass / element_nodes.len() as f64;
for &node in element_nodes {
node_masses[node] += share;
}
}
pub fn spawn_debris(
&mut self,
element_idx: usize,
node_positions: &[f64],
node_velocities: &[f64],
element_nodes: &[usize],
) {
if !self.states[element_idx].eroded {
return;
}
let n = element_nodes.len() as f64;
let pos = element_nodes.iter().fold([0.0f64; 3], |mut acc, &ni| {
acc[0] += node_positions[ni * 3] / n;
acc[1] += node_positions[ni * 3 + 1] / n;
acc[2] += node_positions[ni * 3 + 2] / n;
acc
});
let vel = element_nodes.iter().fold([0.0f64; 3], |mut acc, &ni| {
acc[0] += node_velocities[ni * 3] / n;
acc[1] += node_velocities[ni * 3 + 1] / n;
acc[2] += node_velocities[ni * 3 + 2] / n;
acc
});
self.debris_positions.push(pos);
self.debris_velocities.push(vel);
self.debris_masses.push(self.states[element_idx].mass);
}
pub fn erosion_fraction(&self) -> f64 {
if self.states.is_empty() {
return 0.0;
}
self.n_eroded as f64 / self.states.len() as f64
}
pub fn eroded_elements(&self) -> Vec<usize> {
self.states
.iter()
.enumerate()
.filter(|(_, s)| s.eroded)
.map(|(i, _)| i)
.collect()
}
pub fn active_elements(&self) -> Vec<usize> {
self.states
.iter()
.enumerate()
.filter(|(_, s)| !s.eroded)
.map(|(i, _)| i)
.collect()
}
}
#[derive(Debug, Clone)]
pub struct WaveResult {
pub frequencies: Vec<f64>,
pub phase_velocities: Vec<f64>,
pub group_velocities: Vec<f64>,
pub wave_numbers: Vec<f64>,
pub dispersion_parameter: Vec<f64>,
}