use serde::{Deserialize, Serialize};
use crate::common::ParticleSoa;
use crate::error::Result;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PackedParticles {
pub count: u32,
pub position_mass: Vec<f32>,
pub velocity: Vec<f32>,
pub density_pressure: Vec<f32>,
}
impl PackedParticles {
#[must_use]
pub fn from_soa(soa: &ParticleSoa) -> Self {
let n = soa.len();
debug_assert_eq!(soa.pos_x.len(), n, "pos_x length mismatch");
debug_assert_eq!(soa.pos_y.len(), n, "pos_y length mismatch");
debug_assert_eq!(soa.pos_z.len(), n, "pos_z length mismatch");
debug_assert_eq!(soa.vel_x.len(), n, "vel_x length mismatch");
debug_assert_eq!(soa.vel_y.len(), n, "vel_y length mismatch");
debug_assert_eq!(soa.vel_z.len(), n, "vel_z length mismatch");
debug_assert_eq!(soa.mass.len(), n, "mass length mismatch");
debug_assert_eq!(soa.density.len(), n, "density length mismatch");
debug_assert_eq!(soa.pressure.len(), n, "pressure length mismatch");
let mut position_mass = Vec::with_capacity(n * 4);
let mut velocity = Vec::with_capacity(n * 4);
let mut density_pressure = Vec::with_capacity(n * 2);
for i in 0..n {
position_mass.push(soa.pos_x[i] as f32);
position_mass.push(soa.pos_y[i] as f32);
position_mass.push(soa.pos_z[i] as f32);
position_mass.push(soa.mass[i] as f32);
velocity.push(soa.vel_x[i] as f32);
velocity.push(soa.vel_y[i] as f32);
velocity.push(soa.vel_z[i] as f32);
velocity.push(0.0);
density_pressure.push(soa.density[i] as f32);
density_pressure.push(soa.pressure[i] as f32);
}
Self {
count: n as u32,
position_mass,
velocity,
density_pressure,
}
}
pub fn unpack_density_pressure(&self, soa: &mut ParticleSoa) {
let n = self.count as usize;
for i in 0..n.min(soa.len()) {
soa.density[i] = f64::from(self.density_pressure[i * 2]);
soa.pressure[i] = f64::from(self.density_pressure[i * 2 + 1]);
}
}
pub fn unpack_velocity(&self, soa: &mut ParticleSoa) {
let n = self.count as usize;
for i in 0..n.min(soa.len()) {
soa.vel_x[i] = f64::from(self.velocity[i * 4]);
soa.vel_y[i] = f64::from(self.velocity[i * 4 + 1]);
soa.vel_z[i] = f64::from(self.velocity[i * 4 + 2]);
}
}
pub fn unpack_positions(&self, soa: &mut ParticleSoa) {
let n = self.count as usize;
for i in 0..n.min(soa.len()) {
soa.pos_x[i] = f64::from(self.position_mass[i * 4]);
soa.pos_y[i] = f64::from(self.position_mass[i * 4 + 1]);
soa.pos_z[i] = f64::from(self.position_mass[i * 4 + 2]);
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[non_exhaustive]
pub struct SphKernelParams {
pub smoothing_radius: f32,
pub rest_density: f32,
pub gas_constant: f32,
pub viscosity: f32,
pub gravity: [f32; 3],
pub dt: f32,
pub particle_count: u32,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[non_exhaustive]
pub struct GridKernelParams {
pub nx: u32,
pub ny: u32,
pub dx: f32,
pub dt: f32,
pub viscosity: f32,
pub buoyancy_alpha: f32,
pub ambient_density: f32,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ShallowKernelParams {
pub nx: u32,
pub ny: u32,
pub dx: f32,
pub dt: f32,
pub gravity: f32,
pub damping: f32,
pub dry_threshold: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ComputeOp {
SphDensity,
SphStep,
GridAdvect,
ShallowStep,
}
pub trait ComputeBackend {
fn supports(&self, op: ComputeOp) -> bool;
fn sph_density(&self, particles: &mut PackedParticles, params: &SphKernelParams) -> Result<()>;
fn sph_step(&self, particles: &mut PackedParticles, params: &SphKernelParams) -> Result<()>;
fn grid_advect(
&self,
dst: &mut [f32],
src: &[f32],
vx: &[f32],
vy: &[f32],
params: &GridKernelParams,
) -> Result<()>;
fn shallow_step(
&self,
height: &mut [f32],
vx: &mut [f32],
vy: &mut [f32],
ground: &[f32],
params: &ShallowKernelParams,
) -> Result<()>;
}
pub trait NeuralCorrector {
fn correct(
&self,
velocities: &[f32],
pressures: &[f32],
params: &GridKernelParams,
) -> Result<NeuralCorrection>;
}
#[derive(Debug, Clone)]
pub struct NeuralCorrection {
pub velocity_delta: Vec<f32>,
pub pressure_delta: Vec<f32>,
}
pub struct KernelDerivatives;
impl KernelDerivatives {
#[inline]
#[must_use]
pub fn dpoly6_dr2(r2: f64, h: f64) -> f64 {
let h2 = h * h;
if r2 > h2 {
return 0.0;
}
let h3 = h2 * h;
let h9 = h3 * h3 * h3;
let c = 315.0 / (64.0 * std::f64::consts::PI * h9);
let diff = h2 - r2;
-3.0 * c * diff * diff
}
#[inline]
#[must_use]
pub fn deos_drho(gas_constant: f64) -> f64 {
gas_constant
}
#[inline]
#[must_use]
pub fn dtait_drho(density: f64, rest_density: f64, speed_of_sound: f64, gamma: f64) -> f64 {
let rho0 = rest_density.max(1e-10);
let b = rho0 * speed_of_sound * speed_of_sound / gamma;
b * gamma * (density / rho0).powf(gamma - 1.0) / rho0
}
#[inline]
#[must_use]
pub fn dwendland_c2_dr(r: f64, h: f64) -> f64 {
crate::sph::kernel_wendland_c2_grad(r, h)
}
#[must_use]
pub fn autodiff_gradient(
f: impl Fn(&mut hisab::autodiff::Tape, &[hisab::autodiff::Var]) -> hisab::autodiff::Var,
x: &[f64],
) -> Vec<f64> {
hisab::autodiff::reverse_gradient(f, x)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::{FluidParticle, ParticleSoa};
use hisab::DVec3;
#[test]
fn test_packed_particles_roundtrip() {
let particles = vec![
FluidParticle::new(DVec3::new(1.0, 2.0, 3.0), 0.5),
FluidParticle::new(DVec3::new(4.0, 5.0, 6.0), 0.25),
];
let soa = ParticleSoa::from_aos(&particles);
let packed = PackedParticles::from_soa(&soa);
assert_eq!(packed.count, 2);
assert!((packed.position_mass[0] - 1.0).abs() < 1e-6);
assert!((packed.position_mass[1] - 2.0).abs() < 1e-6);
assert!((packed.position_mass[2] - 3.0).abs() < 1e-6);
assert!((packed.position_mass[3] - 0.5).abs() < 1e-6); assert!((packed.position_mass[4] - 4.0).abs() < 1e-6);
}
#[test]
fn test_packed_unpack_density() {
let particles = vec![FluidParticle::new(DVec3::ZERO, 1.0)];
let mut soa = ParticleSoa::from_aos(&particles);
let mut packed = PackedParticles::from_soa(&soa);
packed.density_pressure[0] = 1000.0;
packed.density_pressure[1] = 500.0;
packed.unpack_density_pressure(&mut soa);
assert!((soa.density[0] - 1000.0).abs() < 1e-3);
assert!((soa.pressure[0] - 500.0).abs() < 1e-3);
}
#[test]
fn test_packed_unpack_velocity() {
let particles = vec![FluidParticle::new(DVec3::ZERO, 1.0)];
let mut soa = ParticleSoa::from_aos(&particles);
let mut packed = PackedParticles::from_soa(&soa);
packed.velocity[0] = 1.5;
packed.velocity[1] = 2.5;
packed.velocity[2] = 3.5;
packed.unpack_velocity(&mut soa);
assert!((soa.vel_x[0] - 1.5).abs() < 1e-3);
assert!((soa.vel_y[0] - 2.5).abs() < 1e-3);
assert!((soa.vel_z[0] - 3.5).abs() < 1e-3);
}
#[test]
fn test_packed_empty() {
let soa = ParticleSoa::new();
let packed = PackedParticles::from_soa(&soa);
assert_eq!(packed.count, 0);
assert!(packed.position_mass.is_empty());
}
#[test]
fn test_sph_kernel_params() {
let params = SphKernelParams {
smoothing_radius: 0.05,
rest_density: 1000.0,
gas_constant: 2000.0,
viscosity: 0.001,
gravity: [0.0, -9.81, 0.0],
dt: 0.001,
particle_count: 100,
};
assert!(params.dt > 0.0);
assert_eq!(params.particle_count, 100);
}
#[test]
fn test_compute_op_variants() {
assert_ne!(ComputeOp::SphDensity, ComputeOp::SphStep);
assert_ne!(ComputeOp::GridAdvect, ComputeOp::ShallowStep);
}
}