oxiphysics_python/world_api/
sph.rs1#![allow(clippy::needless_range_loop)]
2#![allow(missing_docs)]
8
9#[derive(Debug, Clone)]
15#[allow(dead_code)]
16pub struct PySphConfig {
17 pub h: f64,
19 pub rest_density: f64,
21 pub stiffness: f64,
23 pub viscosity: f64,
25 pub gravity: [f64; 3],
27 pub particle_mass: f64,
29}
30
31impl PySphConfig {
32 pub fn water() -> Self {
34 Self {
35 h: 0.1,
36 rest_density: 1000.0,
37 stiffness: 200.0,
38 viscosity: 0.01,
39 gravity: [0.0, -9.81, 0.0],
40 particle_mass: 0.02,
41 }
42 }
43}
44
45impl Default for PySphConfig {
46 fn default() -> Self {
47 Self::water()
48 }
49}
50
51#[derive(Debug, Clone)]
53#[allow(dead_code)]
54pub struct PySphSim {
55 positions: Vec<[f64; 3]>,
57 velocities: Vec<[f64; 3]>,
59 densities: Vec<f64>,
61 pressures: Vec<f64>,
63 config: PySphConfig,
65 time: f64,
67}
68
69impl PySphSim {
70 pub fn new(config: PySphConfig) -> Self {
72 Self {
73 positions: Vec::new(),
74 velocities: Vec::new(),
75 densities: Vec::new(),
76 pressures: Vec::new(),
77 config,
78 time: 0.0,
79 }
80 }
81
82 pub fn add_particle(&mut self, pos: [f64; 3]) {
84 self.positions.push(pos);
85 self.velocities.push([0.0; 3]);
86 self.densities.push(self.config.rest_density);
87 self.pressures.push(0.0);
88 }
89
90 pub fn add_particle_block(
93 &mut self,
94 origin: [f64; 3],
95 nx: usize,
96 ny: usize,
97 nz: usize,
98 dx: f64,
99 ) {
100 for iz in 0..nz {
101 for iy in 0..ny {
102 for ix in 0..nx {
103 let pos = [
104 origin[0] + ix as f64 * dx,
105 origin[1] + iy as f64 * dx,
106 origin[2] + iz as f64 * dx,
107 ];
108 self.add_particle(pos);
109 }
110 }
111 }
112 }
113
114 pub fn particle_count(&self) -> usize {
116 self.positions.len()
117 }
118
119 pub fn position(&self, i: usize) -> Option<[f64; 3]> {
121 self.positions.get(i).copied()
122 }
123
124 pub fn velocity(&self, i: usize) -> Option<[f64; 3]> {
126 self.velocities.get(i).copied()
127 }
128
129 pub fn density(&self, i: usize) -> Option<f64> {
131 self.densities.get(i).copied()
132 }
133
134 pub fn pressure(&self, i: usize) -> Option<f64> {
136 self.pressures.get(i).copied()
137 }
138
139 pub fn velocities_mut(&mut self) -> &mut Vec<[f64; 3]> {
141 &mut self.velocities
142 }
143
144 pub fn smoothing_length(&self) -> f64 {
146 self.config.h
147 }
148
149 pub fn rest_density(&self) -> f64 {
151 self.config.rest_density
152 }
153
154 pub fn particle_mass(&self) -> f64 {
156 self.config.particle_mass
157 }
158
159 pub fn all_positions(&self) -> Vec<f64> {
161 self.positions
162 .iter()
163 .flat_map(|p| p.iter().copied())
164 .collect()
165 }
166
167 pub fn all_velocities(&self) -> Vec<f64> {
169 self.velocities
170 .iter()
171 .flat_map(|v| v.iter().copied())
172 .collect()
173 }
174
175 pub fn time(&self) -> f64 {
177 self.time
178 }
179
180 pub fn step(&mut self, dt: f64) {
187 let n = self.positions.len();
188 if n == 0 {
189 return;
190 }
191
192 let h = self.config.h;
193 let h2 = h * h;
194 let m = self.config.particle_mass;
195 let rho0 = self.config.rest_density;
196 let k = self.config.stiffness;
197 let mu = self.config.viscosity;
198 let g = self.config.gravity;
199
200 let poly6_coeff = 315.0 / (64.0 * std::f64::consts::PI * h.powi(9));
202 for i in 0..n {
203 let mut rho = 0.0f64;
204 for j in 0..n {
205 let dx = self.positions[i][0] - self.positions[j][0];
206 let dy = self.positions[i][1] - self.positions[j][1];
207 let dz = self.positions[i][2] - self.positions[j][2];
208 let r2 = dx * dx + dy * dy + dz * dz;
209 if r2 < h2 {
210 let diff = h2 - r2;
211 rho += m * poly6_coeff * diff * diff * diff;
212 }
213 }
214 self.densities[i] = rho.max(1e-3);
215 self.pressures[i] = k * (self.densities[i] - rho0);
216 }
217
218 let spiky_coeff = -45.0 / (std::f64::consts::PI * h.powi(6));
220 let visc_coeff = 45.0 / (std::f64::consts::PI * h.powi(6));
221 let mut forces = vec![[0.0f64; 3]; n];
222
223 for i in 0..n {
224 let mut fp = [0.0f64; 3];
225 let mut fv = [0.0f64; 3];
226 for j in 0..n {
227 if i == j {
228 continue;
229 }
230 let dx = self.positions[i][0] - self.positions[j][0];
231 let dy = self.positions[i][1] - self.positions[j][1];
232 let dz = self.positions[i][2] - self.positions[j][2];
233 let r2 = dx * dx + dy * dy + dz * dz;
234 if r2 >= h2 || r2 < 1e-20 {
235 continue;
236 }
237 let r = r2.sqrt();
238 let hr = h - r;
239 let pf = -m * (self.pressures[i] + self.pressures[j]) / (2.0 * self.densities[j])
241 * spiky_coeff
242 * hr
243 * hr
244 / r;
245 fp[0] += pf * dx;
246 fp[1] += pf * dy;
247 fp[2] += pf * dz;
248 let vf = mu * m / self.densities[j] * visc_coeff * hr;
250 fv[0] += vf * (self.velocities[j][0] - self.velocities[i][0]);
251 fv[1] += vf * (self.velocities[j][1] - self.velocities[i][1]);
252 fv[2] += vf * (self.velocities[j][2] - self.velocities[i][2]);
253 }
254 let rho_i = self.densities[i];
256 forces[i][0] = (fp[0] + fv[0]) / rho_i + g[0];
257 forces[i][1] = (fp[1] + fv[1]) / rho_i + g[1];
258 forces[i][2] = (fp[2] + fv[2]) / rho_i + g[2];
259 }
260
261 for i in 0..n {
263 self.velocities[i][0] += forces[i][0] * dt;
264 self.velocities[i][1] += forces[i][1] * dt;
265 self.velocities[i][2] += forces[i][2] * dt;
266 self.positions[i][0] += self.velocities[i][0] * dt;
267 self.positions[i][1] += self.velocities[i][1] * dt;
268 self.positions[i][2] += self.velocities[i][2] * dt;
269 }
270
271 self.time += dt;
272 }
273}