salva3d/solver/viscosity/
dfsph_viscosity.rs

1#[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
82/// Viscosity introduced with the Viscous DFSPH method.
83///
84/// This does not include any viscosity with boundaries so it can be useful to
85/// combine this with another viscosity model and include only its boundary part.
86pub struct DFSPHViscosity {
87    /// Minimum number of iterations that must be executed for viscosity resolution.
88    pub min_viscosity_iter: usize,
89    /// Maximum number of iterations that must be executed for viscosity resolution.
90    pub max_viscosity_iter: usize,
91    /// Maximum acceptable strain error (in percents).
92    ///
93    /// The viscosity solver will continue iterating until the strain error drops bellow this
94    /// threshold, or until the maximum number of iterations is reached.
95    pub max_viscosity_error: Real,
96    /// The viscosity coefficient.
97    pub viscosity_coefficient: Real,
98    betas: Vec<BetaMatrix<Real>>,
99    strain_rates: Vec<StrainRates>,
100}
101
102impl DFSPHViscosity {
103    /// Initialize a new DFSPH visocisity solver.
104    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                // Preconditionner.
162                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                        // Compute velocity change.
282                        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                //                println!(
314                //                    "Average viscosity error: {}, break after niters: {}, unstable: {}",
315                //                    avg_err,
316                //                    i,
317                //                    avg_err > last_err
318                //                );
319                break;
320            }
321
322            self.compute_accelerations(timestep, fluid_fluid_contacts, fluid, densities);
323        }
324    }
325
326    fn apply_permutation(&mut self, _: &[usize]) {}
327}