#![allow(missing_docs)]
use super::lbm::{PyLbmConfig, PyLbmGrid};
use super::sph::{PySphConfig, PySphSim};
use crate::md_api::{PyMdConfig, PyMdSimulation};
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct PyMdBinding {
sim: PyMdSimulation,
}
impl PyMdBinding {
pub fn new_default() -> Self {
Self {
sim: PyMdSimulation::new(PyMdConfig::default()),
}
}
pub fn new(config: PyMdConfig) -> Self {
Self {
sim: PyMdSimulation::new(config),
}
}
#[allow(clippy::too_many_arguments)]
pub fn add_atom(
&mut self,
x: f64,
y: f64,
z: f64,
vx: f64,
vy: f64,
vz: f64,
atom_type: u32,
) -> usize {
let idx = self.sim.add_atom([x, y, z], atom_type);
self.sim.set_velocity(idx, [vx, vy, vz]);
idx
}
pub fn atom_count(&self) -> usize {
self.sim.atom_count()
}
pub fn step(&mut self, dt: f64) {
self.sim.step(dt);
}
pub fn run_steps(&mut self, n_steps: u32, dt: f64) {
for _ in 0..n_steps {
self.sim.step(dt);
}
}
pub fn get_positions_flat(&self) -> Vec<f64> {
self.sim.all_positions()
}
pub fn get_velocities_flat(&self) -> Vec<f64> {
self.sim.all_velocities()
}
pub fn get_energies(&self) -> [f64; 3] {
let ke = self.sim.kinetic_energy();
let pe = self.sim.potential_energy();
[ke, pe, ke + pe]
}
pub fn get_temperature(&self) -> f64 {
self.sim.temperature()
}
pub fn elapsed_time(&self) -> f64 {
self.sim.time()
}
pub fn step_count(&self) -> u64 {
self.sim.step_count()
}
pub fn set_thermostat_enabled(&mut self, enabled: bool) {
self.sim.set_thermostat(enabled);
}
pub fn set_target_temperature(&mut self, temp: f64) {
self.sim.set_target_temperature(temp);
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct PyLbmBinding {
grid: PyLbmGrid,
}
impl PyLbmBinding {
pub fn new(width: usize, height: usize, viscosity: f64) -> Self {
let config = PyLbmConfig::new(width, height, viscosity);
Self {
grid: PyLbmGrid::new(&config),
}
}
pub fn from_config(config: PyLbmConfig) -> Self {
Self {
grid: PyLbmGrid::new(&config),
}
}
pub fn grid_width(&self) -> usize {
self.grid.width()
}
pub fn grid_height(&self) -> usize {
self.grid.height()
}
pub fn cell_count(&self) -> usize {
self.grid.width() * self.grid.height()
}
pub fn step(&mut self) {
self.grid.step();
}
pub fn run_steps(&mut self, n_steps: u32) {
for _ in 0..n_steps {
self.grid.step();
}
}
pub fn get_density_flat(&self) -> Vec<f64> {
self.grid.density_field()
}
pub fn get_velocity_flat(&self) -> Vec<f64> {
self.grid.velocity_field()
}
pub fn get_speed_flat(&self) -> Vec<f64> {
let vel = self.grid.velocity_field();
vel.chunks(2)
.map(|c| (c[0] * c[0] + c[1] * c[1]).sqrt())
.collect()
}
pub fn mean_density(&self) -> f64 {
let rho = self.get_density_flat();
if rho.is_empty() {
return 0.0;
}
rho.iter().sum::<f64>() / rho.len() as f64
}
pub fn max_speed(&self) -> f64 {
self.get_speed_flat()
.iter()
.cloned()
.fold(f64::NEG_INFINITY, f64::max)
.max(0.0)
}
pub fn step_count(&self) -> u64 {
self.grid.step_count()
}
}
use crate::fem_api::{PyFemDirichletBC, PyFemMaterial, PyFemMesh, PyFemSolveResult, PyFemSolver};
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct PyFemBinding {
mesh: PyFemMesh,
solver: PyFemSolver,
last_result: Option<PyFemSolveResult>,
}
impl PyFemBinding {
pub fn new() -> Self {
Self {
mesh: PyFemMesh::new(),
solver: PyFemSolver::new(),
last_result: None,
}
}
pub fn add_node(&mut self, x: f64, y: f64) -> usize {
self.mesh.add_node(x, y)
}
pub fn add_tri_element(&mut self, n0: usize, n1: usize, n2: usize) -> usize {
self.mesh.add_element(n0, n1, n2)
}
#[allow(clippy::too_many_arguments)]
pub fn add_tri_element_with_material(
&mut self,
n0: usize,
n1: usize,
n2: usize,
material_id: usize,
thickness: f64,
) -> usize {
self.mesh
.add_element_with_material(n0, n1, n2, material_id, thickness)
}
pub fn add_material(&mut self, material: PyFemMaterial) -> usize {
self.mesh.add_material(material)
}
pub fn node_count(&self) -> usize {
self.mesh.nodes.len()
}
pub fn element_count(&self) -> usize {
self.mesh.elements.len()
}
pub fn pin_node(&mut self, node: usize) {
self.mesh.pin_node(node);
}
pub fn add_dirichlet_bc(&mut self, bc: PyFemDirichletBC) {
self.mesh.add_dirichlet_bc(bc);
}
pub fn apply_nodal_force(&mut self, node: usize, fx: f64, fy: f64) {
self.mesh.apply_nodal_force(node, fx, fy);
}
pub fn solve(&mut self) -> bool {
match self.solver.solve(&self.mesh) {
Some(result) => {
self.last_result = Some(result);
true
}
None => false,
}
}
pub fn get_displacement_flat(&self) -> Vec<f64> {
match &self.last_result {
Some(r) => r.displacements.clone(),
None => Vec::new(),
}
}
pub fn get_stress_flat(&self) -> Vec<f64> {
match &self.last_result {
Some(r) => r.von_mises_stress.clone(),
None => Vec::new(),
}
}
pub fn max_von_mises_stress(&self) -> f64 {
match &self.last_result {
Some(r) if !r.von_mises_stress.is_empty() => r.max_von_mises().max(0.0),
_ => 0.0,
}
}
pub fn last_solve_converged(&self) -> bool {
self.last_result
.as_ref()
.map(|r| r.converged)
.unwrap_or(false)
}
}
impl Default for PyFemBinding {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct PySphBinding {
sim: PySphSim,
}
impl PySphBinding {
pub fn new_water() -> Self {
Self {
sim: PySphSim::new(PySphConfig::water()),
}
}
pub fn from_config(config: PySphConfig) -> Self {
Self {
sim: PySphSim::new(config),
}
}
pub fn add_particle_at(&mut self, x: f64, y: f64, z: f64) -> usize {
let idx = self.sim.particle_count();
self.sim.add_particle([x, y, z]);
idx
}
pub fn add_particle(&mut self, x: f64, y: f64, z: f64, vx: f64, vy: f64, vz: f64) -> usize {
let idx = self.sim.particle_count();
self.sim.add_particle([x, y, z]);
if let Some(vel) = self.sim.velocities_mut().get_mut(idx) {
*vel = [vx, vy, vz];
}
idx
}
pub fn particle_count(&self) -> usize {
self.sim.particle_count()
}
pub fn step(&mut self, dt: f64) {
self.sim.step(dt);
}
pub fn run_steps(&mut self, n_steps: u32, dt: f64) {
for _ in 0..n_steps {
self.sim.step(dt);
}
}
pub fn get_positions_flat(&self) -> Vec<f64> {
self.sim.all_positions()
}
pub fn get_velocities_flat(&self) -> Vec<f64> {
self.sim.all_velocities()
}
pub fn get_densities_flat(&self) -> Vec<f64> {
(0..self.sim.particle_count())
.filter_map(|i| self.sim.density(i))
.collect()
}
pub fn get_pressures_flat(&self) -> Vec<f64> {
(0..self.sim.particle_count())
.filter_map(|i| self.sim.pressure(i))
.collect()
}
pub fn get_speeds_flat(&self) -> Vec<f64> {
let vels = self.sim.all_velocities();
vels.chunks(3)
.map(|c| (c[0] * c[0] + c[1] * c[1] + c[2] * c[2]).sqrt())
.collect()
}
pub fn mean_density(&self) -> f64 {
let n = self.sim.particle_count();
if n == 0 {
return 0.0;
}
let sum: f64 = (0..n).filter_map(|i| self.sim.density(i)).sum();
sum / n as f64
}
pub fn max_speed(&self) -> f64 {
self.get_speeds_flat()
.iter()
.cloned()
.fold(f64::NEG_INFINITY, f64::max)
.max(0.0)
}
pub fn total_kinetic_energy(&self) -> f64 {
let mass = self.sim.particle_mass();
let vels = self.sim.all_velocities();
vels.chunks(3)
.map(|c| 0.5 * mass * (c[0] * c[0] + c[1] * c[1] + c[2] * c[2]))
.sum()
}
pub fn smoothing_length(&self) -> f64 {
self.sim.smoothing_length()
}
pub fn rest_density(&self) -> f64 {
self.sim.rest_density()
}
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub struct MaterialProperties {
pub name: String,
pub density: f64,
pub youngs_modulus: f64,
pub poisson_ratio: f64,
pub tensile_strength: f64,
pub viscosity: f64,
pub yield_strength: f64,
}
impl MaterialProperties {
pub fn steel() -> Self {
Self {
name: "steel".into(),
density: 7850.0,
youngs_modulus: 200e9,
poisson_ratio: 0.3,
tensile_strength: 400e6,
viscosity: 0.0,
yield_strength: 250e6,
}
}
pub fn aluminium() -> Self {
Self {
name: "aluminium".into(),
density: 2700.0,
youngs_modulus: 69e9,
poisson_ratio: 0.33,
tensile_strength: 310e6,
viscosity: 0.0,
yield_strength: 276e6,
}
}
pub fn concrete() -> Self {
Self {
name: "concrete".into(),
density: 2400.0,
youngs_modulus: 30e9,
poisson_ratio: 0.2,
tensile_strength: 3e6,
viscosity: 0.0,
yield_strength: 0.0,
}
}
pub fn rubber() -> Self {
Self {
name: "rubber".into(),
density: 1100.0,
youngs_modulus: 0.01e9,
poisson_ratio: 0.49,
tensile_strength: 20e6,
viscosity: 0.0,
yield_strength: 0.0,
}
}
pub fn water() -> Self {
Self {
name: "water".into(),
density: 998.0,
youngs_modulus: 0.0,
poisson_ratio: 0.0,
tensile_strength: 0.0,
viscosity: 1.002e-3,
yield_strength: 0.0,
}
}
pub fn air() -> Self {
Self {
name: "air".into(),
density: 1.204,
youngs_modulus: 0.0,
poisson_ratio: 0.0,
tensile_strength: 0.0,
viscosity: 1.825e-5,
yield_strength: 0.0,
}
}
pub fn titanium() -> Self {
Self {
name: "titanium".into(),
density: 4430.0,
youngs_modulus: 114e9,
poisson_ratio: 0.34,
tensile_strength: 950e6,
viscosity: 0.0,
yield_strength: 880e6,
}
}
pub fn lookup(name: &str) -> Option<Self> {
match name.to_lowercase().as_str() {
"steel" => Some(Self::steel()),
"aluminium" | "aluminum" => Some(Self::aluminium()),
"concrete" => Some(Self::concrete()),
"rubber" => Some(Self::rubber()),
"water" => Some(Self::water()),
"air" => Some(Self::air()),
"titanium" => Some(Self::titanium()),
_ => None,
}
}
pub fn p_wave_speed(&self) -> Option<f64> {
if self.youngs_modulus < 1.0 || self.density < 1e-6 {
return None;
}
let nu = self.poisson_ratio;
let e = self.youngs_modulus;
let rho = self.density;
let c = ((e * (1.0 - nu)) / (rho * (1.0 + nu) * (1.0 - 2.0 * nu))).sqrt();
Some(c)
}
pub fn s_wave_speed(&self) -> Option<f64> {
if self.youngs_modulus < 1.0 || self.density < 1e-6 {
return None;
}
let nu = self.poisson_ratio;
let e = self.youngs_modulus;
let rho = self.density;
let g = e / (2.0 * (1.0 + nu));
Some((g / rho).sqrt())
}
}
#[allow(dead_code)]
pub fn py_query_material(name: &str) -> Vec<f64> {
match MaterialProperties::lookup(name) {
Some(m) => vec![
m.density,
m.youngs_modulus,
m.poisson_ratio,
m.tensile_strength,
m.viscosity,
m.yield_strength,
],
None => Vec::new(),
}
}
#[allow(dead_code)]
pub fn py_p_wave_speed(name: &str) -> f64 {
MaterialProperties::lookup(name)
.and_then(|m| m.p_wave_speed())
.unwrap_or(0.0)
}
#[allow(dead_code)]
pub fn py_s_wave_speed(name: &str) -> f64 {
MaterialProperties::lookup(name)
.and_then(|m| m.s_wave_speed())
.unwrap_or(0.0)
}