vox_geometry_rust/
sph_system_data3.rs

1/*
2 * // Copyright (c) 2021 Feng Yang
3 * //
4 * // I am making my contributions/submissions to this project solely in my
5 * // personal capacity and am not conveying any rights to any intellectual
6 * // property of any third parties.
7 */
8
9use crate::particle_system_data3::*;
10use crate::vector3::Vector3D;
11use crate::sph_kernels3::*;
12use crate::point_neighbor_searcher3::PointNeighborSearcher3;
13use crate::bounding_box3::BoundingBox3D;
14use crate::point_generator3::PointGenerator3;
15use rayon::prelude::*;
16use crate::bcc_lattice_point_generator::BccLatticePointGenerator;
17use std::sync::{RwLock, Arc};
18
19///
20/// # 3-D SPH particle system data.
21///
22/// This class extends ParticleSystemData3 to specialize the data model for SPH.
23/// It includes density and pressure array as a default particle attribute, and
24/// it also contains SPH utilities such as interpolation operator.
25///
26pub struct SphSystemData3 {
27    /// Target density of this particle system in kg/m^3.
28    _target_density: f64,
29
30    /// Target spacing of this particle system in meters.
31    _target_spacing: f64,
32
33    /// Relative radius of SPH kernel.
34    /// SPH kernel radius divided by target spacing.
35    _kernel_radius_over_target_spacing: f64,
36
37    /// SPH kernel radius in meters.
38    _kernel_radius: f64,
39
40    _pressure_idx: usize,
41
42    _density_idx: usize,
43
44    _base_data: ParticleSystemData3,
45}
46
47impl SphSystemData3 {
48    /// Constructs empty SPH system.
49    pub fn new_default() -> SphSystemData3 {
50        return SphSystemData3 {
51            _target_density: crate::constants::K_WATER_DENSITY,
52            _target_spacing: 0.1,
53            _kernel_radius_over_target_spacing: 1.8,
54            _kernel_radius: 0.0,
55            _pressure_idx: 0,
56            _density_idx: 0,
57            _base_data: ParticleSystemData3::new(0),
58        };
59    }
60
61    /// Constructs SPH system data with given number of particles.
62    pub fn new(number_of_particles: usize) -> SphSystemData3 {
63        let mut data = SphSystemData3 {
64            _target_density: crate::constants::K_WATER_DENSITY,
65            _target_spacing: 0.1,
66            _kernel_radius_over_target_spacing: 1.8,
67            _kernel_radius: 0.0,
68            _pressure_idx: 0,
69            _density_idx: 0,
70            _base_data: ParticleSystemData3::new(number_of_particles),
71        };
72
73        data._density_idx = data._base_data.add_scalar_data(None);
74        data._pressure_idx = data._base_data.add_scalar_data(None);
75
76        data.set_target_spacing(0.1);
77
78        return data;
79    }
80}
81
82impl SphSystemData3 {
83    ///
84    /// \brief      Sets the radius.
85    ///
86    /// Sets the radius of the particle system. The radius will be interpreted
87    /// as target spacing.
88    ///
89    pub fn set_radius(&mut self, new_radius: f64) {
90        // Interpret it as setting target spacing
91        self.set_target_spacing(new_radius);
92    }
93
94    ///
95    /// \brief      Sets the mass of a particle.
96    ///
97    /// Setting the mass of a particle will change the target density.
98    ///
99    /// - parameter:  new_mass The new mass.
100    ///
101    pub fn set_mass(&mut self, new_mass: f64) {
102        let inc_ratio = new_mass / self._base_data.mass();
103        self._target_density *= inc_ratio;
104        self._base_data.set_mass(new_mass);
105    }
106
107    /// Returns the density array accessor (immutable).
108    pub fn densities(&self) -> &Vec<f64> {
109        return self._base_data.scalar_data_at(self._density_idx);
110    }
111
112    /// Returns the density array accessor (mutable).
113    pub fn densities_mut(&mut self) -> &mut Vec<f64> {
114        return self._base_data.scalar_data_at_mut(self._density_idx);
115    }
116
117    /// Returns the pressure array accessor (immutable).
118    pub fn pressures(&self) -> &Vec<f64> {
119        return self._base_data.scalar_data_at(self._pressure_idx);
120    }
121
122    /// Returns the pressure array accessor (mutable).
123    pub fn pressures_mut(&mut self) -> &mut Vec<f64> {
124        return self._base_data.scalar_data_at_mut(self._pressure_idx);
125    }
126
127    ///
128    /// \brief Updates the density array with the latest particle positions.
129    ///
130    /// This function updates the density array by recalculating each particle's
131    /// latest nearby particles' position.
132    ///
133    /// \warning You must update the neighbor searcher
134    /// (SphSystemData3::build_neighbor_searcher) before calling this function.
135    ///
136    pub fn update_densities(&mut self) {
137        let m = self._base_data.mass();
138
139        let mut density: Vec<f64> = self._base_data.positions().into_par_iter().map(|pos| {
140            let sum = self.sum_of_kernel_nearby(pos);
141            return m * sum;
142        }).collect();
143        self.densities_mut().clear();
144        self.densities_mut().append(&mut density);
145    }
146
147    /// Sets the target density of this particle system.
148    pub fn set_target_density(&mut self, target_density: f64) {
149        self._target_density = target_density;
150
151        self.compute_mass();
152    }
153
154    /// Returns the target density of this particle system.
155    pub fn target_density(&self) -> f64 {
156        return self._target_density;
157    }
158
159    ///
160    /// \brief Sets the target particle spacing in meters.
161    ///
162    /// Once this function is called, hash grid and density should be
163    /// updated using updateHashGrid() and update_densities).
164    ///
165    pub fn set_target_spacing(&mut self, spacing: f64) {
166        self._base_data.set_radius(spacing);
167
168        self._target_spacing = spacing;
169        self._kernel_radius = self._kernel_radius_over_target_spacing * self._target_spacing;
170
171        self.compute_mass();
172    }
173
174    /// Returns the target particle spacing in meters.
175    pub fn target_spacing(&self) -> f64 {
176        return self._target_spacing;
177    }
178
179    ///
180    /// \brief Sets the relative kernel radius.
181    ///
182    /// Sets the relative kernel radius compared to the target particle
183    /// spacing (i.e. kernel radius / target spacing).
184    /// Once this function is called, hash grid and density should
185    /// be updated using updateHashGrid() and update_densities).
186    ///
187    pub fn set_relative_kernel_radius(&mut self, relative_radius: f64) {
188        self._kernel_radius_over_target_spacing = relative_radius;
189        self._kernel_radius = self._kernel_radius_over_target_spacing * self._target_spacing;
190
191        self.compute_mass();
192    }
193
194    ///
195    /// \brief Returns the relative kernel radius.
196    ///
197    /// Returns the relative kernel radius compared to the target particle
198    /// spacing (i.e. kernel radius / target spacing).
199    ///
200    pub fn relative_kernel_radius(&self) -> f64 {
201        return self._kernel_radius_over_target_spacing;
202    }
203
204    ///
205    /// \brief Sets the absolute kernel radius.
206    ///
207    /// Sets the absolute kernel radius compared to the target particle
208    /// spacing (i.e. relative kernel radius * target spacing).
209    /// Once this function is called, hash grid and density should
210    /// be updated using updateHashGrid() and update_densities).
211    ///
212    pub fn set_kernel_radius(&mut self, kernel_radius: f64) {
213        self._kernel_radius = kernel_radius;
214        self._target_spacing = kernel_radius / self._kernel_radius_over_target_spacing;
215
216        self.compute_mass();
217    }
218
219    /// Returns the kernel radius in meters unit.
220    pub fn kernel_radius(&self) -> f64 {
221        return self._kernel_radius;
222    }
223    /// Returns sum of kernel function evaluation for each nearby particle.
224    pub fn sum_of_kernel_nearby(&self, origin: &Vector3D) -> f64 {
225        let mut sum = 0.0;
226        let kernel = SphStdKernel3::new(self._kernel_radius);
227        self._base_data.neighbor_searcher().read().unwrap().for_each_nearby_point(
228            origin, self._kernel_radius, &mut |_: usize, neighbor_position: &Vector3D| {
229                let dist = origin.distance_to(*neighbor_position);
230                sum += kernel.apply(dist);
231            });
232        return sum;
233    }
234
235    ///
236    /// \brief Returns interpolated value at given origin point.
237    ///
238    /// Returns interpolated scalar data from the given position using
239    /// standard SPH weighted average. The data array should match the
240    /// particle layout. For example, density or pressure arrays can be
241    /// used.
242    ///
243    /// \warning You must update the neighbor searcher
244    /// (SphSystemData3::build_neighbor_searcher) before calling this function.
245    ///
246    pub fn interpolate_scalar(&self, origin: &Vector3D,
247                              values: &Vec<f64>) -> f64 {
248        let mut sum = 0.0;
249        let d = self.densities();
250        let kernel = SphStdKernel3::new(self._kernel_radius);
251        let m = self._base_data.mass();
252
253        self._base_data.neighbor_searcher().read().unwrap().for_each_nearby_point(
254            origin, self._kernel_radius, &mut |i: usize, neighbor_position: &Vector3D| {
255                let dist = origin.distance_to(*neighbor_position);
256                let weight = m / d[i] * kernel.apply(dist);
257                sum += weight * values[i];
258            });
259
260        return sum;
261    }
262
263    ///
264    /// \brief Returns interpolated vector value at given origin point.
265    ///
266    /// Returns interpolated vector data from the given position using
267    /// standard SPH weighted average. The data array should match the
268    /// particle layout. For example, velocity or acceleration arrays can be
269    /// used.
270    ///
271    /// \warning You must update the neighbor searcher
272    /// (SphSystemData3::build_neighbor_searcher) before calling this function.
273    ///
274    pub fn interpolate_vec(&self, origin: &Vector3D,
275                           values: &Vec<Vector3D>) -> Vector3D {
276        let mut sum = Vector3D::new_default();
277        let d = self.densities();
278        let kernel = SphStdKernel3::new(self._kernel_radius);
279        let m = self._base_data.mass();
280
281        self._base_data.neighbor_searcher().read().unwrap().for_each_nearby_point(
282            origin, self._kernel_radius, &mut |i: usize, neighbor_position: &Vector3D| {
283                let dist = origin.distance_to(*neighbor_position);
284                let weight = m / d[i] * kernel.apply(dist);
285                sum += values[i] * weight;
286            });
287
288        return sum;
289    }
290
291    ///
292    /// Returns the gradient of the given values at i-th particle.
293    ///
294    /// \warning You must update the neighbor lists
295    /// (SphSystemData3::build_neighbor_lists) before calling this function.
296    ///
297    pub fn gradient_at(&self, i: usize,
298                       values: &Vec<f64>) -> Vector3D {
299        let mut sum = Vector3D::new_default();
300        let p = self._base_data.positions();
301        let d = self.densities();
302        let neighbors = &self._base_data.neighbor_lists()[i];
303        let origin = p[i];
304        let kernel = SphSpikyKernel3::new(self._kernel_radius);
305        let m = self._base_data.mass();
306
307        for j in neighbors {
308            let neighbor_position = p[*j];
309            let dist = origin.distance_to(neighbor_position);
310            if dist > 0.0 {
311                let dir = (neighbor_position - origin) / dist;
312                sum += kernel.gradient_dir(dist, &dir) * d[i] * m * (values[i] / crate::math_utils::square(d[i])
313                    + values[*j] / crate::math_utils::square(d[*j]));
314            }
315        }
316
317        return sum;
318    }
319
320    ///
321    /// Returns the laplacian of the given values at i-th particle.
322    ///
323    /// \warning You must update the neighbor lists
324    /// (SphSystemData3::build_neighbor_lists) before calling this function.
325    ///
326    pub fn laplacian_at_scalar(&self, i: usize,
327                               values: &Vec<f64>) -> f64 {
328        let mut sum = 0.0;
329        let p = self._base_data.positions();
330        let d = self.densities();
331        let neighbors = &self._base_data.neighbor_lists()[i];
332        let origin = p[i];
333        let kernel = SphSpikyKernel3::new(self._kernel_radius);
334        let m = self._base_data.mass();
335
336        for j in neighbors {
337            let neighbor_position = p[*j];
338            let dist = origin.distance_to(neighbor_position);
339            sum += m * (values[*j] - values[i]) / d[*j] * kernel.second_derivative(dist);
340        }
341
342        return sum;
343    }
344
345    ///
346    /// Returns the laplacian of the given values at i-th particle.
347    ///
348    /// \warning You must update the neighbor lists
349    /// (SphSystemData3::build_neighbor_lists) before calling this function.
350    ///
351    pub fn laplacian_at_vec(&self, i: usize,
352                            values: &Vec<Vector3D>) -> Vector3D {
353        let mut sum = Vector3D::new_default();
354        let p = self._base_data.positions();
355        let d = self.densities();
356        let neighbors = &self._base_data.neighbor_lists()[i];
357        let origin = p[i];
358        let kernel = SphSpikyKernel3::new(self._kernel_radius);
359        let m = self._base_data.mass();
360
361        for j in neighbors {
362            let neighbor_position = p[*j];
363            let dist = origin.distance_to(neighbor_position);
364            sum += (values[*j] - values[i]) * m / d[*j] * kernel.second_derivative(dist);
365        }
366
367        return sum;
368    }
369
370    /// Builds neighbor searcher with kernel radius.
371    pub fn build_neighbor_searcher(&mut self) {
372        self._base_data.build_neighbor_searcher(self._kernel_radius);
373    }
374
375    /// Builds neighbor lists with kernel radius.
376    pub fn build_neighbor_lists(&mut self) {
377        self._base_data.build_neighbor_lists(self._kernel_radius);
378    }
379
380    /// Computes the mass based on the target density and spacing.
381    fn compute_mass(&mut self) {
382        let mut points: Vec<Vector3D> = Vec::new();
383        let points_generator = BccLatticePointGenerator {};
384        let sample_bound = BoundingBox3D::new(
385            Vector3D::new(-1.5 * self._kernel_radius, -1.5 * self._kernel_radius, -1.5 * self._kernel_radius),
386            Vector3D::new(1.5 * self._kernel_radius, 1.5 * self._kernel_radius, 1.5 * self._kernel_radius));
387
388        points_generator.generate(&sample_bound, self._target_spacing, &mut points);
389
390        let mut max_number_density = 0.0;
391        let kernel = SphStdKernel3::new(self._kernel_radius);
392
393        for i in 0..points.len() {
394            let point = points[i];
395            let mut sum = 0.0;
396
397            for j in 0..points.len() {
398                let neighbor_point = points[j];
399                sum += kernel.apply(neighbor_point.distance_to(point));
400            }
401
402            max_number_density = f64::max(max_number_density, sum);
403        }
404
405        debug_assert!(max_number_density > 0.0);
406
407        let new_mass = self._target_density / max_number_density;
408        self._base_data.set_mass(new_mass);
409    }
410}
411
412impl SphSystemData3 {
413    /// Returns the radius of the particles.
414    pub fn radius(&self) -> f64 {
415        return self._base_data.radius();
416    }
417
418    /// Returns the mass of the particles.
419    pub fn mass(&self) -> f64 {
420        return self._base_data.mass();
421    }
422
423    /// Returns the position array (immutable).
424    pub fn positions(&self) -> &Vec<Vector3D> {
425        return self._base_data.positions();
426    }
427
428    /// Returns the position array (mutable).
429    pub fn positions_mut(&mut self) -> &mut Vec<Vector3D> {
430        return self._base_data.positions_mut();
431    }
432
433    /// Returns the velocity array (immutable).
434    pub fn velocities(&self) -> &Vec<Vector3D> {
435        return self._base_data.velocities();
436    }
437
438    /// Returns the velocity array (mutable).
439    pub fn velocities_mut(&mut self) -> &mut Vec<Vector3D> {
440        return self._base_data.velocities_mut();
441    }
442
443    /// Returns the force array (immutable).
444    pub fn forces(&self) -> &Vec<Vector3D> {
445        return self._base_data.forces();
446    }
447
448    /// Returns the force array (mutable).
449    pub fn forces_mut(&mut self) -> &mut Vec<Vector3D> {
450        return self._base_data.forces_mut();
451    }
452
453    /// Returns the number of particles.
454    pub fn number_of_particles(&self) -> usize {
455        return self._base_data.number_of_particles();
456    }
457
458    ///
459    /// \brief      Returns neighbor lists.
460    ///
461    /// This function returns neighbor lists which is available after calling
462    /// PointParallelHashGridSearcher2::build_neighbor_lists. Each list stores
463    /// indices of the neighbors.
464    ///
465    /// \return     Neighbor lists.
466    ///
467    pub fn neighbor_lists(&self) -> &Vec<Vec<usize>> {
468        return self._base_data.neighbor_lists();
469    }
470}
471
472/// Shared pointer for the SphSystemData3 type.
473pub type SphSystemData3Ptr = Arc<RwLock<SphSystemData3>>;