fastiron/simulation/
mct.rs

1//! Code for spatial computations of the simulation
2//!
3//! This module contains function used to compute and manipulate data related
4//! to a particle's coordinate and direction in the problem.
5
6use num::{one, zero, FromPrimitive};
7
8use crate::{
9    constants::CustomFloat,
10    data::mc_vector::MCVector,
11    geometry::{
12        facets::{MCGeneralPlane, MCNearestFacet},
13        mc_domain::MCMeshDomain,
14        N_FACETS_OUT, N_POINTS_INTERSEC, N_POINTS_PER_FACET,
15    },
16    particles::mc_particle::MCParticle,
17    utils::mc_rng_state::rng_sample,
18};
19
20/// Computes which facet of the specified cell is nearest to the specified
21/// coordinates.
22///
23/// The function uses the particle's direction to compute which facet is currently
24/// the closest to the particle as well as the distance to this facet. The result is
25/// used in order to assess which event the particle will undergo next, in this
26/// case, a facet crossing. See [MCNearestFacet] for more information.
27pub fn nearest_facet<T: CustomFloat>(
28    particle: &mut MCParticle<T>,
29    mesh: &MCMeshDomain<T>,
30) -> MCNearestFacet<T> {
31    let mut nearest_facet = mct_nf_3dg(particle, mesh);
32
33    nearest_facet.distance_to_facet = nearest_facet.distance_to_facet.max(zero());
34    assert!(nearest_facet.distance_to_facet <= T::huge_float());
35
36    nearest_facet
37}
38
39/// Generates a random coordinate inside a polyhedral cell.
40pub fn generate_coordinate_3dg<T: CustomFloat>(
41    seed: &mut u64,
42    mesh: &MCMeshDomain<T>,
43    cell_idx: usize,
44    cell_volume: T,
45) -> MCVector<T> {
46    let six: T = FromPrimitive::from_f64(6.0).unwrap();
47    let one: T = one();
48
49    let center: MCVector<T> = cell_position_3dg(mesh, cell_idx);
50
51    let which_volume = rng_sample::<T>(seed) * six * cell_volume;
52
53    let mut current_volume: T = zero();
54    let mut points = [MCVector::default(); N_POINTS_PER_FACET];
55
56    // find the facet to sample from
57    for facet_idx in 0..N_FACETS_OUT {
58        points = mesh.get_facet_coords(cell_idx, facet_idx);
59
60        let subvolume = compute_volume(&points, &center);
61        current_volume += subvolume;
62        if current_volume >= which_volume {
63            break;
64        }
65    }
66
67    // sample and adjust
68    let mut r1: T = rng_sample(seed);
69    let mut r2: T = rng_sample(seed);
70    let mut r3: T = rng_sample(seed);
71    if r1 + r2 > one {
72        r1 = one - r1;
73        r2 = one - r2;
74    }
75    if r2 + r3 > one {
76        let tmp = r3;
77        r3 = one - r1 - r2;
78        r2 = one - tmp;
79    } else if r1 + r2 + r3 > one {
80        let tmp = r3;
81        r3 = r1 + r2 + r3 - one;
82        r1 = one - r2 - tmp;
83    }
84    let r4: T = one - r1 - r2 - r3;
85
86    points[0] * r1 + points[1] * r2 + points[2] * r3 + center * r4
87}
88
89/// Returns a coordinate that represents the "center" of the cell.
90pub fn cell_position_3dg<T: CustomFloat>(mesh: &MCMeshDomain<T>, cell_idx: usize) -> MCVector<T> {
91    let mut coordinate: MCVector<T> = mesh.cell_connectivity[cell_idx]
92        .point
93        .map(|point_idx| mesh.node[point_idx])
94        .iter()
95        .sum();
96
97    coordinate /= FromPrimitive::from_usize(N_POINTS_INTERSEC).unwrap();
98
99    coordinate
100}
101
102/// Reflects a particle off a reflection-type boundary.
103///
104/// This function is called when a particle undergo a reflection event at the
105/// boundary of the problem. Note that the reflection does not result in a
106/// loss of energy.
107pub fn reflect_particle<T: CustomFloat>(particle: &mut MCParticle<T>, plane: &MCGeneralPlane<T>) {
108    let facet_normal: MCVector<T> = MCVector {
109        x: plane.a,
110        y: plane.b,
111        z: plane.c,
112    };
113
114    let two: T = FromPrimitive::from_f64(2.0).unwrap();
115    let dot: T = two * particle.direction.dot(&facet_normal);
116
117    if dot > zero() {
118        particle.direction -= facet_normal * dot;
119    }
120}
121
122// ==============================
123//       Private functions
124// ==============================
125
126fn mct_nf_3dg<T: CustomFloat>(
127    particle: &mut MCParticle<T>,
128    mesh: &MCMeshDomain<T>,
129) -> MCNearestFacet<T> {
130    let coords = particle.coordinate;
131    let direction = particle.direction;
132
133    let mut facet_coords: [MCVector<T>; N_POINTS_PER_FACET] = Default::default();
134    let mut iteration: usize = 0;
135    let mut move_factor: T = <T as FromPrimitive>::from_f64(0.5).unwrap() * T::small_float();
136
137    let tmp: T = FromPrimitive::from_f64(1e-16).unwrap();
138    let plane_tolerance: T =
139        tmp * (coords.x * coords.x + coords.y * coords.y + coords.z * coords.z);
140
141    let planes = &mesh.cell_geometry[particle.cell];
142    let mut distance_to_facet: [T; N_FACETS_OUT] = [T::huge_float(); N_FACETS_OUT];
143
144    loop {
145        // the link between distances and facet idx is made implicitly through
146        // array indexing
147        distance_to_facet.fill(T::huge_float());
148        distance_to_facet
149            .iter_mut()
150            .enumerate()
151            .zip(planes.iter())
152            .for_each(|((facet_idx, dist), plane)| {
153                let numerator: T = -one::<T>()
154                    * (plane.a * coords.x + plane.b * coords.y + plane.c * coords.z + plane.d);
155                let facet_normal_dot_dcos: T =
156                    plane.a * direction.x + plane.b * direction.y + plane.c * direction.z;
157
158                if (facet_normal_dot_dcos <= zero())
159                    | (numerator < zero()) & (numerator * numerator > plane_tolerance)
160                {
161                    return;
162                }
163
164                facet_coords = mesh.get_facet_coords(particle.cell, facet_idx);
165                let distance = numerator / facet_normal_dot_dcos;
166                let intersection_pt: MCVector<T> = coords + direction * distance;
167
168                if mct_nf_3dg_dist_to_segment(&intersection_pt, plane, &facet_coords) {
169                    *dist = distance;
170                }
171            });
172
173        let nearest_facet = mct_nf_compute_nearest(distance_to_facet);
174        let retry = check_nearest_validity(
175            particle,
176            mesh,
177            &mut iteration,
178            &mut move_factor,
179            &nearest_facet,
180        );
181
182        if !retry {
183            return nearest_facet;
184        }
185    }
186}
187
188/// Returns the volume defined by `v3v0`, `v3v1`, `v3v2` using
189/// vector operations.
190fn compute_volume<T: CustomFloat>(vertices: &[MCVector<T>], origin: &MCVector<T>) -> T {
191    assert_eq!(vertices.len(), N_POINTS_PER_FACET);
192    let tmp0 = vertices[0] - *origin;
193    let tmp1 = vertices[1] - *origin;
194    let tmp2 = vertices[2] - *origin;
195
196    tmp0.dot(&tmp1.cross(&tmp2)) // should be the same as original code
197}
198
199fn mct_nf_compute_nearest<T: CustomFloat>(
200    distance_to_facet: [T; N_FACETS_OUT],
201) -> MCNearestFacet<T> {
202    let huge_f: T = T::huge_float();
203    let mut nearest_facet: MCNearestFacet<T> = Default::default();
204    let mut nearest_negative_facet: MCNearestFacet<T> = MCNearestFacet {
205        distance_to_facet: -huge_f,
206        ..Default::default()
207    };
208
209    // determine the nearest facet
210    distance_to_facet
211        .iter()
212        .enumerate()
213        .filter(|(_, dist)| **dist > zero())
214        .for_each(|(facet_idx, dist)| {
215            if *dist <= nearest_facet.distance_to_facet {
216                nearest_facet.distance_to_facet = *dist;
217                nearest_facet.facet = facet_idx;
218            }
219        });
220
221    if nearest_facet.distance_to_facet != huge_f {
222        return nearest_facet;
223    }
224
225    distance_to_facet
226        .iter()
227        .enumerate()
228        .filter(|(_, dist)| **dist <= zero())
229        .for_each(|(facet_idx, dist)| {
230            if *dist > nearest_negative_facet.distance_to_facet {
231                nearest_negative_facet.distance_to_facet = *dist;
232                nearest_negative_facet.facet = facet_idx;
233            }
234        });
235
236    if nearest_negative_facet.distance_to_facet != -huge_f {
237        return nearest_negative_facet;
238    }
239
240    nearest_facet
241}
242
243fn check_nearest_validity<T: CustomFloat>(
244    particle: &mut MCParticle<T>,
245    mesh: &MCMeshDomain<T>,
246    iteration: &mut usize,
247    move_factor: &mut T,
248    nearest_facet: &MCNearestFacet<T>,
249) -> bool {
250    const MAX_ALLOWED_SEGMENTS: usize = 10000000;
251    const MAX_ITERATION: usize = 1000;
252    let max: T = FromPrimitive::from_usize(MAX_ALLOWED_SEGMENTS).unwrap();
253
254    let coord = &mut particle.coordinate;
255
256    if (nearest_facet.distance_to_facet == T::huge_float())
257        | ((particle.num_segments > max) & (nearest_facet.distance_to_facet <= zero()))
258    {
259        let two: T = FromPrimitive::from_f64(2.0).unwrap();
260        let threshold: T = FromPrimitive::from_f64(1.0e-2).unwrap();
261
262        // move coordinates towards cell center
263        let move_to = cell_position_3dg(mesh, particle.cell);
264        *coord += (move_to - *coord) * *move_factor;
265
266        // keep track of the movement
267        *iteration += 1;
268        *move_factor = threshold.min(*move_factor * two);
269
270        return *iteration != MAX_ITERATION;
271    }
272    false
273}
274
275fn mct_nf_3dg_dist_to_segment<T: CustomFloat>(
276    intersection_pt: &MCVector<T>,
277    plane: &MCGeneralPlane<T>,
278    facet_coords: &[MCVector<T>],
279) -> bool {
280    let pfive: T = FromPrimitive::from_f64(0.5).unwrap();
281    let bounding_box_tolerance: T = FromPrimitive::from_f64(1e-9).unwrap();
282
283    // if the point doesn't belong to the facet, returns huge_f
284    macro_rules! belongs_or_return {
285        ($axis: ident) => {
286            let below: bool = (facet_coords[0].$axis
287                > intersection_pt.$axis + bounding_box_tolerance)
288                & (facet_coords[1].$axis > intersection_pt.$axis + bounding_box_tolerance)
289                & (facet_coords[2].$axis > intersection_pt.$axis + bounding_box_tolerance);
290            let above: bool = (facet_coords[0].$axis
291                < intersection_pt.$axis - bounding_box_tolerance)
292                & (facet_coords[1].$axis < intersection_pt.$axis - bounding_box_tolerance)
293                & (facet_coords[2].$axis < intersection_pt.$axis - bounding_box_tolerance);
294            if below | above {
295                // doesn't belong
296                return false;
297            }
298        };
299    }
300
301    // scalar value of the cross product between AB & AC
302    macro_rules! ab_cross_ac {
303        ($ax: expr, $ay: expr, $bx: expr, $by: expr, $cx: expr, $cy: expr) => {
304            ($bx - $ax) * ($cy - $ay) - ($by - $ay) * ($cx - $ax)
305        };
306    }
307
308    let crosses = if plane.c.abs() > pfive {
309        belongs_or_return!(x);
310        belongs_or_return!(y);
311        // update cross; z elements
312        [
313            ab_cross_ac!(
314                facet_coords[0].x,
315                facet_coords[0].y,
316                facet_coords[1].x,
317                facet_coords[1].y,
318                intersection_pt.x,
319                intersection_pt.y
320            ),
321            ab_cross_ac!(
322                facet_coords[1].x,
323                facet_coords[1].y,
324                facet_coords[2].x,
325                facet_coords[2].y,
326                intersection_pt.x,
327                intersection_pt.y
328            ),
329            ab_cross_ac!(
330                facet_coords[2].x,
331                facet_coords[2].y,
332                facet_coords[0].x,
333                facet_coords[0].y,
334                intersection_pt.x,
335                intersection_pt.y
336            ),
337        ]
338    } else if plane.b.abs() > pfive {
339        belongs_or_return!(x);
340        belongs_or_return!(z);
341        // update cross; y elements
342        [
343            ab_cross_ac!(
344                facet_coords[0].z,
345                facet_coords[0].x,
346                facet_coords[1].z,
347                facet_coords[1].x,
348                intersection_pt.z,
349                intersection_pt.x
350            ),
351            ab_cross_ac!(
352                facet_coords[1].z,
353                facet_coords[1].x,
354                facet_coords[2].z,
355                facet_coords[2].x,
356                intersection_pt.z,
357                intersection_pt.x
358            ),
359            ab_cross_ac!(
360                facet_coords[2].z,
361                facet_coords[2].x,
362                facet_coords[0].z,
363                facet_coords[0].x,
364                intersection_pt.z,
365                intersection_pt.x
366            ),
367        ]
368    } else if plane.a.abs() > pfive {
369        belongs_or_return!(z);
370        belongs_or_return!(y);
371        // update cross; x elements
372        [
373            ab_cross_ac!(
374                facet_coords[0].y,
375                facet_coords[0].z,
376                facet_coords[1].y,
377                facet_coords[1].z,
378                intersection_pt.y,
379                intersection_pt.z
380            ),
381            ab_cross_ac!(
382                facet_coords[1].y,
383                facet_coords[1].z,
384                facet_coords[2].y,
385                facet_coords[2].z,
386                intersection_pt.y,
387                intersection_pt.z
388            ),
389            ab_cross_ac!(
390                facet_coords[2].y,
391                facet_coords[2].z,
392                facet_coords[0].y,
393                facet_coords[0].z,
394                intersection_pt.y,
395                intersection_pt.z
396            ),
397        ]
398    } else {
399        [zero(); 3]
400    };
401
402    let cross_tolerance: T = bounding_box_tolerance * (crosses[0] + crosses[1] + crosses[2]).abs();
403
404    if ((crosses[0] > -cross_tolerance)
405        & (crosses[1] > -cross_tolerance)
406        & (crosses[2] > -cross_tolerance))
407        | ((crosses[0] < cross_tolerance)
408            & (crosses[1] < cross_tolerance)
409            & (crosses[2] < cross_tolerance))
410    {
411        return true;
412    }
413    false
414}