use super::{bond_based::BondBasedSolver, PeridynamicState};
pub fn velocity_verlet_step(
state: &mut PeridynamicState,
forces_old: &[[f64; 3]],
forces_new: &[[f64; 3]],
dt: f64,
masses: &[f64],
) {
let n = state.n_particles;
debug_assert_eq!(forces_old.len(), n);
debug_assert_eq!(forces_new.len(), n);
debug_assert_eq!(masses.len(), n);
for i in 0..n {
let inv_m = 1.0 / masses[i];
for d in 0..3 {
state.velocities[i][d] += 0.5 * forces_old[i][d] * inv_m * dt;
}
for d in 0..3 {
state.displacements[i][d] += state.velocities[i][d] * dt;
}
for d in 0..3 {
state.velocities[i][d] += 0.5 * forces_new[i][d] * inv_m * dt;
}
}
}
pub fn adaptive_timestep(solver: &BondBasedSolver) -> f64 {
solver.cfl_timestep()
}
#[derive(Debug, Clone)]
pub struct DirichletBC {
pub particles: Vec<usize>,
pub displacement: [f64; 3],
}
impl DirichletBC {
pub fn new(particles: Vec<usize>, displacement: [f64; 3]) -> Self {
Self {
particles,
displacement,
}
}
pub fn apply(&self, state: &mut PeridynamicState) {
for &i in &self.particles {
state.displacements[i] = self.displacement;
state.velocities[i] = [0.0, 0.0, 0.0];
}
}
}
#[derive(Debug, Clone)]
pub struct BodyForce {
pub acceleration: [f64; 3],
pub target_particles: Option<Vec<usize>>,
}
impl BodyForce {
pub fn new_uniform(acceleration: [f64; 3]) -> Self {
Self {
acceleration,
target_particles: None,
}
}
pub fn new_targeted(acceleration: [f64; 3], particles: Vec<usize>) -> Self {
Self {
acceleration,
target_particles: Some(particles),
}
}
pub fn apply(&self, forces: &mut [[f64; 3]], masses: &[f64]) {
let n = forces.len();
match &self.target_particles {
None => {
for i in 0..n {
for d in 0..3 {
forces[i][d] += masses[i] * self.acceleration[d];
}
}
}
Some(indices) => {
for &i in indices {
if i < n {
for d in 0..3 {
forces[i][d] += masses[i] * self.acceleration[d];
}
}
}
}
}
}
}
pub fn surface_correction_factors(
neighbor_list: &crate::pde::peridynamics::neighbor_list::NeighborList,
volumes: &[f64],
horizon: f64,
) -> Vec<f64> {
let n = neighbor_list.n_particles();
let v_sphere = (4.0 / 3.0) * std::f64::consts::PI * horizon.powi(3);
let mut beta = vec![1.0_f64; n];
for i in 0..n {
let v_actual: f64 = neighbor_list.neighbors[i].iter().map(|&j| volumes[j]).sum();
if v_actual > f64::EPSILON {
beta[i] = v_sphere / v_actual;
}
}
beta
}
pub struct PdIntegrator {
pub solver: BondBasedSolver,
pub dirichlet_bcs: Vec<DirichletBC>,
pub body_forces: Vec<BodyForce>,
pub time: f64,
pub step_count: usize,
}
impl PdIntegrator {
pub fn new(solver: BondBasedSolver) -> Self {
let mut integrator = Self {
solver,
dirichlet_bcs: Vec::new(),
body_forces: Vec::new(),
time: 0.0,
step_count: 0,
};
let f0 = integrator.solver.compute_forces();
integrator.solver.prev_forces = f0;
integrator
}
pub fn add_dirichlet_bc(&mut self, bc: DirichletBC) {
self.dirichlet_bcs.push(bc);
}
pub fn add_body_force(&mut self, bf: BodyForce) {
self.body_forces.push(bf);
}
pub fn step(&mut self, dt: f64) {
let n = self.solver.state.n_particles;
{
let forces_old = self.solver.prev_forces.clone();
for i in 0..n {
let inv_m = 1.0 / self.solver.masses[i];
for d in 0..3 {
self.solver.state.velocities[i][d] += 0.5 * forces_old[i][d] * inv_m * dt;
}
}
}
for i in 0..n {
for d in 0..3 {
self.solver.state.displacements[i][d] += self.solver.state.velocities[i][d] * dt;
}
}
for bc in &self.dirichlet_bcs {
bc.apply(&mut self.solver.state);
}
let mut forces_new = self.solver.compute_forces();
for bf in &self.body_forces {
bf.apply(&mut forces_new, &self.solver.masses);
}
for i in 0..n {
let inv_m = 1.0 / self.solver.masses[i];
for d in 0..3 {
self.solver.state.velocities[i][d] += 0.5 * forces_new[i][d] * inv_m * dt;
}
}
for bc in &self.dirichlet_bcs {
bc.apply(&mut self.solver.state);
}
self.solver.prev_forces = forces_new;
self.time += dt;
self.step_count += 1;
}
pub fn suggested_timestep(&self) -> f64 {
adaptive_timestep(&self.solver)
}
pub fn damage_field(&self) -> Vec<f64> {
self.solver.damage_field()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pde::peridynamics::{
bond_based::{BondBasedConfig, BondBasedSolver},
neighbor_list::NeighborList,
};
fn simple_grid_2x2x2(dx: f64) -> (Vec<[f64; 3]>, Vec<f64>) {
let mut pos = Vec::new();
for i in 0_usize..2 {
for j in 0_usize..2 {
for k in 0_usize..2 {
pos.push([i as f64 * dx, j as f64 * dx, k as f64 * dx]);
}
}
}
let vol = vec![dx * dx * dx; pos.len()];
(pos, vol)
}
#[test]
fn test_ten_steps_no_nan() {
let (pos, vol) = simple_grid_2x2x2(0.05);
let config = BondBasedConfig {
horizon: 0.08,
..Default::default()
};
let solver = BondBasedSolver::new(pos, vol, config);
let mut integrator = PdIntegrator::new(solver);
let dt = integrator.suggested_timestep() * 0.5;
let dt = if dt > 0.0 { dt } else { 1e-5 };
for _ in 0..10 {
integrator.step(dt);
}
for i in 0..integrator.solver.state.n_particles {
let p = integrator.solver.state.current_position(i);
for &c in &p {
assert!(!c.is_nan(), "NaN in position after 10 steps");
}
}
}
#[test]
fn test_dirichlet_bc_particles_dont_move() {
let (pos, vol) = simple_grid_2x2x2(0.05);
let config = BondBasedConfig {
horizon: 0.08,
..Default::default()
};
let solver = BondBasedSolver::new(pos, vol, config);
let mut integrator = PdIntegrator::new(solver);
integrator.add_dirichlet_bc(DirichletBC::new(vec![0], [0.0, 0.0, 0.0]));
integrator.add_body_force(BodyForce::new_targeted([1.0, 0.0, 0.0], vec![1]));
let dt = 1e-5;
for _ in 0..20 {
integrator.step(dt);
}
let u0 = integrator.solver.state.displacements[0];
for (d, &c) in u0.iter().enumerate() {
assert!(
c.abs() < 1e-14,
"constrained particle 0 displacement[{d}] = {c} should be 0"
);
}
}
#[test]
fn test_surface_correction_factors_interior_close_to_one() {
let dx = 0.1_f64;
let horizon = 0.25_f64; let mut pos = Vec::new();
let n = 5_usize;
for i in 0..n {
for j in 0..n {
for k in 0..n {
pos.push([i as f64 * dx, j as f64 * dx, k as f64 * dx]);
}
}
}
let vol = vec![dx * dx * dx; pos.len()];
let nl = NeighborList::build(&pos, horizon);
let beta = surface_correction_factors(&nl, &vol, horizon);
let center = 2 * 25 + 2 * 5 + 2;
assert!(
beta[center] >= 0.5 && beta[center] <= 2.5,
"interior beta[{center}] = {} seems unreasonable",
beta[center]
);
for (i, &b) in beta.iter().enumerate() {
assert!(b > 0.0, "beta[{i}] = {b} must be positive");
}
}
#[test]
fn test_damage_field_all_in_unit_interval() {
let (pos, vol) = simple_grid_2x2x2(0.05);
let config = BondBasedConfig {
horizon: 0.08,
critical_stretch: 0.005,
..Default::default()
};
let solver = BondBasedSolver::new(pos, vol, config);
let mut integrator = PdIntegrator::new(solver);
integrator.solver.state.displacements[0][0] = 0.05;
let _ = integrator.solver.compute_forces();
let damage = integrator.damage_field();
for (i, &d) in damage.iter().enumerate() {
assert!((0.0..=1.0).contains(&d), "damage[{i}] = {d} out of [0,1]");
}
}
}