salva3d/solver/viscosity/
dfsph_viscosity.rs1#[cfg(feature = "parallel")]
2use rayon::prelude::*;
3
4use crate::geometry::ParticlesContacts;
5use crate::math::{Real, Vector, SPATIAL_DIM};
6use crate::object::{Boundary, Fluid};
7use crate::solver::NonPressureForce;
8use crate::TimestepManager;
9
10#[cfg(feature = "dim2")]
11type BetaMatrix<Real> = na::Matrix3<Real>;
12#[cfg(feature = "dim3")]
13type BetaMatrix<Real> = na::Matrix6<Real>;
14#[cfg(feature = "dim2")]
15type BetaGradientMatrix<Real> = na::Matrix3x2<Real>;
16#[cfg(feature = "dim3")]
17type BetaGradientMatrix<Real> = na::Matrix6x3<Real>;
18#[cfg(feature = "dim2")]
19type StrainRate<Real> = na::Vector3<Real>;
20#[cfg(feature = "dim3")]
21type StrainRate<Real> = na::Vector6<Real>;
22
23#[derive(Copy, Clone, Debug)]
24struct StrainRates {
25 target: StrainRate<Real>,
26 error: StrainRate<Real>,
27}
28
29impl StrainRates {
30 pub fn new() -> Self {
31 Self {
32 target: StrainRate::zeros(),
33 error: StrainRate::zeros(),
34 }
35 }
36}
37
38fn compute_strain_rate(gradient: &Vector<Real>, v_ji: &Vector<Real>) -> StrainRate<Real> {
39 let _2: Real = na::convert::<_, Real>(2.0f64);
40
41 #[cfg(feature = "dim3")]
42 return StrainRate::new(
43 _2 * v_ji.x * gradient.x,
44 _2 * v_ji.y * gradient.y,
45 _2 * v_ji.z * gradient.z,
46 v_ji.x * gradient.y + v_ji.y * gradient.x,
47 v_ji.x * gradient.z + v_ji.z * gradient.x,
48 v_ji.y * gradient.z + v_ji.z * gradient.y,
49 );
50
51 #[cfg(feature = "dim2")]
52 return StrainRate::new(
53 _2 * v_ji.x * gradient.x,
54 _2 * v_ji.y * gradient.y,
55 v_ji.x * gradient.y + v_ji.y * gradient.x,
56 );
57}
58
59fn compute_gradient_matrix(gradient: &Vector<Real>) -> BetaGradientMatrix<Real> {
60 let _2: Real = na::convert::<_, Real>(2.0f64);
61
62 #[cfg(feature = "dim2")]
63 #[rustfmt::skip]
64 return BetaGradientMatrix::new(
65 gradient.x * _2, na::zero::<Real>(),
66 na::zero::<Real>(), gradient.y * _2,
67 gradient.y, gradient.x,
68 );
69
70 #[cfg(feature = "dim3")]
71 #[rustfmt::skip]
72 return BetaGradientMatrix::new(
73 gradient.x * _2, na::zero::<Real>(), na::zero::<Real>(),
74 na::zero::<Real>(), gradient.y * _2, na::zero::<Real>(),
75 na::zero::<Real>(), na::zero::<Real>(), gradient.z * _2,
76 gradient.y, gradient.x, na::zero::<Real>(),
77 gradient.z, na::zero::<Real>(), gradient.x,
78 na::zero::<Real>(), gradient.z, gradient.y,
79 );
80}
81
82pub struct DFSPHViscosity {
87 pub min_viscosity_iter: usize,
89 pub max_viscosity_iter: usize,
91 pub max_viscosity_error: Real,
96 pub viscosity_coefficient: Real,
98 betas: Vec<BetaMatrix<Real>>,
99 strain_rates: Vec<StrainRates>,
100}
101
102impl DFSPHViscosity {
103 pub fn new(viscosity_coefficient: Real) -> Self {
105 assert!(
106 viscosity_coefficient >= na::zero::<Real>()
107 && viscosity_coefficient <= na::one::<Real>(),
108 "The viscosity coefficient must be between 0.0 and 1.0."
109 );
110
111 Self {
112 min_viscosity_iter: 1,
113 max_viscosity_iter: 50,
114 max_viscosity_error: na::convert::<_, Real>(0.01),
115 viscosity_coefficient,
116 betas: Vec::new(),
117 strain_rates: Vec::new(),
118 }
119 }
120
121 fn init(&mut self, fluid: &Fluid) {
122 if self.betas.len() != fluid.num_particles() {
123 self.betas
124 .resize(fluid.num_particles(), BetaMatrix::zeros());
125 self.strain_rates
126 .resize(fluid.num_particles(), StrainRates::new());
127 }
128 }
129
130 fn compute_betas(
131 &mut self,
132 fluid_fluid_contacts: &ParticlesContacts,
133 fluid: &Fluid,
134 densities: &[Real],
135 ) {
136 let _2: Real = na::convert::<_, Real>(2.0f64);
137
138 par_iter_mut!(self.betas)
139 .enumerate()
140 .for_each(|(i, beta_i)| {
141 let mut grad_sum = BetaGradientMatrix::zeros();
142 let mut squared_grad_sum = BetaMatrix::zeros();
143
144 for c in fluid_fluid_contacts
145 .particle_contacts(i)
146 .read()
147 .unwrap()
148 .iter()
149 {
150 if c.i_model == c.j_model {
151 let mat = compute_gradient_matrix(&c.gradient);
152 let grad_i = mat * (fluid.particle_mass(c.j) / (_2 * densities[c.i]));
153 squared_grad_sum += grad_i * grad_i.transpose() / densities[c.i];
154 grad_sum += grad_i;
155 }
156 }
157
158 let mut denominator =
159 squared_grad_sum + grad_sum * grad_sum.transpose() / densities[i];
160
161 let mut inv_diag = denominator.diagonal();
163 inv_diag.apply(|n| {
164 if n.abs() < na::convert::<_, Real>(1.0e-6) {
165 *n = na::one::<Real>();
166 } else {
167 *n = na::one::<Real>() / *n
168 }
169 });
170
171 for i in 0..SPATIAL_DIM {
172 denominator.column_mut(i).component_mul_assign(&inv_diag);
173 }
174
175 if SPATIAL_DIM == 3 {
176 if denominator.determinant().abs() < na::convert::<_, Real>(1.0e-6) {
177 *beta_i = BetaMatrix::zeros()
178 } else {
179 *beta_i = denominator
180 .try_inverse()
181 .unwrap_or_else(|| BetaMatrix::zeros())
182 }
183 }
184 let lu = denominator.lu();
185
186 if lu.determinant().abs() < na::convert::<_, Real>(1.0e-6) {
187 *beta_i = BetaMatrix::zeros();
188 } else {
189 *beta_i = lu.try_inverse().unwrap_or_else(|| BetaMatrix::zeros());
190 }
191
192 for i in 0..SPATIAL_DIM {
193 let mut col = beta_i.column_mut(i);
194 col *= inv_diag[i];
195 }
196 })
197 }
198
199 fn compute_strain_rates(
200 &mut self,
201 timestep: &TimestepManager,
202 fluid_fluid_contacts: &ParticlesContacts,
203 fluid: &Fluid,
204 densities: &[Real],
205 compute_error: bool,
206 ) -> Real {
207 let mut max_error = na::zero::<Real>();
208 let viscosity_coefficient = self.viscosity_coefficient;
209 let _2: Real = na::convert::<_, Real>(2.0f64);
210
211 let it = par_iter_mut!(self.strain_rates)
212 .enumerate()
213 .map(|(i, strain_rates_i)| {
214 let mut fluid_rate = StrainRate::zeros();
215
216 for c in fluid_fluid_contacts
217 .particle_contacts(i)
218 .read()
219 .unwrap()
220 .iter()
221 {
222 if c.i_model == c.j_model {
223 let v_i = fluid.velocities[c.i] + fluid.accelerations[c.i] * timestep.dt();
224 let v_j = fluid.velocities[c.j] + fluid.accelerations[c.j] * timestep.dt();
225 let v_ji = v_j - v_i;
226 let rate = compute_strain_rate(&c.gradient, &v_ji);
227
228 fluid_rate += rate * (fluid.particle_mass(c.j) / (_2 * densities[c.i]));
229 }
230 }
231
232 if compute_error {
233 strain_rates_i.error = fluid_rate - strain_rates_i.target;
234 strain_rates_i.error.lp_norm(1) / na::convert::<_, Real>(6.0f64)
235 } else {
236 strain_rates_i.target =
237 fluid_rate * (na::one::<Real>() - viscosity_coefficient);
238 na::zero::<Real>()
239 }
240 });
241
242 let err = par_reduce_sum!(na::zero::<Real>(), it);
243
244 let nparts = fluid.num_particles();
245 if nparts != 0 {
246 max_error = max_error.max(err / na::convert::<_, Real>(nparts as f64));
247 }
248
249 max_error
250 }
251
252 fn compute_accelerations(
253 &self,
254 timestep: &TimestepManager,
255 fluid_fluid_contacts: &ParticlesContacts,
256 fluid: &mut Fluid,
257 densities: &[Real],
258 ) {
259 let strain_rates = &self.strain_rates;
260 let betas = &self.betas;
261 let volumes = &fluid.volumes;
262 let density0 = fluid.density0;
263 let _2: Real = na::convert::<_, Real>(2.0);
264
265 par_iter_mut!(fluid.accelerations)
266 .enumerate()
267 .for_each(|(i, acceleration)| {
268 let ui = betas[i] * strain_rates[i].error / (densities[i] * densities[i]);
269
270 for c in fluid_fluid_contacts
271 .particle_contacts(i)
272 .read()
273 .unwrap()
274 .iter()
275 {
276 if c.i_model == c.j_model {
277 let uj = betas[c.j] * strain_rates[c.j].error
278 / (densities[c.j] * densities[c.j]);
279 let gradient = compute_gradient_matrix(&c.gradient);
280
281 let coeff = (ui + uj) * (volumes[c.j] * density0 / _2);
283 *acceleration +=
284 gradient.tr_mul(&coeff) * (volumes[c.i] * density0 * timestep.inv_dt());
285 }
286 }
287 })
288 }
289}
290
291impl NonPressureForce for DFSPHViscosity {
292 fn solve(
293 &mut self,
294 timestep: &TimestepManager,
295 _kernel_radius: Real,
296 fluid_fluid_contacts: &ParticlesContacts,
297 _fluid_boundaries_contacts: &ParticlesContacts,
298 fluid: &mut Fluid,
299 _boundaries: &[Boundary],
300 densities: &[Real],
301 ) {
302 self.init(fluid);
303
304 let _ = self.compute_betas(fluid_fluid_contacts, fluid, densities);
305
306 let _ = self.compute_strain_rates(timestep, fluid_fluid_contacts, fluid, densities, false);
307
308 for i in 0..self.max_viscosity_iter {
309 let avg_err =
310 self.compute_strain_rates(timestep, fluid_fluid_contacts, fluid, densities, true);
311
312 if avg_err <= self.max_viscosity_error && i >= self.min_viscosity_iter {
313 break;
320 }
321
322 self.compute_accelerations(timestep, fluid_fluid_contacts, fluid, densities);
323 }
324 }
325
326 fn apply_permutation(&mut self, _: &[usize]) {}
327}