1use rand::Rng;
2use std::collections::HashSet;
3
4use crate::{
5 coord::{calc_distance, calc_distance_pbc, Coord},
6 error::Error,
7 grid::Grid,
8 sample::{gen_init_coord, get_active_index, NBallGen},
9};
10
11pub fn bridson<const D: usize>(
27 box_size: &Coord<D>,
28 rmin: f64,
29 num_attempts: usize,
30 use_pbc: bool,
31) -> Result<Vec<Coord<D>>, Error<D>> {
32 let mut rng = rand::thread_rng();
33
34 bridson_rng(box_size, rmin, num_attempts, use_pbc, &mut rng)
35}
36
37pub fn bridson_rng<R: Rng, const D: usize>(
41 box_size: &Coord<D>,
42 rmin: f64,
43 num_attempts: usize,
44 use_pbc: bool,
45 rng: &mut R,
46) -> Result<Vec<Coord<D>>, Error<D>> {
47 validate_rmin(rmin)?;
49 validate_box_size(box_size)?;
50
51 if box_size.is_empty() {
52 return Ok(vec![]);
53 }
54
55 let shape = get_grid_shape(rmin, box_size);
56 let mut grid: Grid<D> = Grid::new(&shape, box_size);
57
58 let mut sphere_gen = NBallGen::new(rmin);
59
60 let x0 = gen_init_coord(box_size, rng);
61 let grid_index = grid
62 .get_index_from_coord(&x0, use_pbc)
63 .ok_or(Error::GenCoordOutOfBounds(x0.clone()))?;
64
65 let mut active_inds = HashSet::new();
66 let mut samples: Vec<Coord<D>> = Vec::with_capacity(grid.data.len());
67
68 add_sample_to_list_and_grid(x0, grid_index, &mut samples, &mut active_inds, &mut grid);
69
70 while let Some(grid_index) = get_active_index(&active_inds, rng) {
71 let x0 =
72 get_sample_from_grid(grid_index, &samples, &grid).ok_or(Error::InvalidActiveList)?;
73
74 match get_sample_around(
75 x0,
76 &samples,
77 &grid,
78 num_attempts,
79 rmin,
80 use_pbc,
81 &mut sphere_gen,
82 rng,
83 ) {
84 Some(coord) => {
85 let sample_grid_index = grid
86 .get_index_from_coord(&coord, use_pbc)
87 .ok_or(Error::GenCoordOutOfBounds(coord.clone()))?;
88
89 add_sample_to_list_and_grid(
90 coord,
91 sample_grid_index,
92 &mut samples,
93 &mut active_inds,
94 &mut grid,
95 );
96 }
97 None => {
98 active_inds.remove(&grid_index);
99 }
100 }
101 }
102
103 Ok(samples)
104}
105
106fn add_sample_to_list_and_grid<const D: usize>(
107 coord: Coord<D>,
108 grid_index: usize,
109 samples: &mut Vec<Coord<D>>,
110 active_inds: &mut HashSet<usize>,
111 grid: &mut Grid<D>,
112) {
113 let sample_index = samples.len();
114
115 samples.push(coord);
116 active_inds.insert(grid_index);
117 grid.data[grid_index] = Some(sample_index);
118}
119
120fn get_sample_from_grid<'a, const D: usize>(
122 grid_index: usize,
123 samples: &'a [Coord<D>],
124 grid: &Grid<D>,
125) -> Option<&'a Coord<D>> {
126 grid.data
127 .get(grid_index)
128 .cloned()
129 .flatten()
130 .and_then(|sample_index| samples.get(sample_index))
131}
132
133fn get_sample_around<R: Rng, const D: usize>(
134 x0: &Coord<D>,
135 samples: &[Coord<D>],
136 grid: &Grid<D>,
137 num_attempts: usize,
138 rmin: f64,
139 use_pbc: bool,
140 sphere_gen: &mut NBallGen<D>,
141 rng: &mut R,
142) -> Option<Coord<D>> {
143 for _ in 0..num_attempts {
144 let x1 = sphere_gen.gen_around(x0, rng);
145
146 if check_if_coord_is_valid(&x1, samples, grid, rmin, use_pbc) {
147 return Some(x1);
148 }
149 }
150
151 None
152}
153
154fn check_if_coord_is_valid<const D: usize>(
155 coord: &Coord<D>,
156 samples: &[Coord<D>],
157 grid: &Grid<D>,
158 rmin: f64,
159 use_pbc: bool,
160) -> bool {
161 match grid.get_position_from_coord(coord) {
162 Some(position) => {
163 let index_ranges: Vec<(isize, isize)> = position
164 .iter()
165 .map(|&i| {
166 (
167 i - grid.num_adjacent as isize,
168 i + grid.num_adjacent as isize,
169 )
170 })
171 .collect();
172
173 let mut position_buf = Vec::with_capacity(position.len());
174
175 recurse_and_check(
176 &mut position_buf,
177 &index_ranges,
178 coord,
179 samples,
180 grid,
181 &position,
182 rmin,
183 use_pbc,
184 )
185 }
186 None => false,
187 }
188}
189
190fn recurse_and_check<const D: usize>(
192 position: &mut Vec<isize>,
193 index_ranges: &[(isize, isize)],
194 coord: &Coord<D>,
195 samples: &[Coord<D>],
196 grid: &Grid<D>,
197 original_position: &[isize],
198 rmin: f64,
199 use_pbc: bool,
200) -> bool {
201 match index_ranges.split_first() {
202 Some((&(imin, imax), tail)) => {
203 for i in imin..=imax {
204 position.push(i);
205
206 let result = if tail.is_empty() {
207 check_coord_at_position(coord, position.as_ref(), samples, grid, rmin, use_pbc)
208 } else {
209 recurse_and_check(
210 position,
211 tail,
212 coord,
213 samples,
214 grid,
215 original_position,
216 rmin,
217 use_pbc,
218 )
219 };
220
221 if !result {
224 return false;
225 }
226
227 position.pop();
228 }
229 }
230 None => (),
231 }
232
233 true
234}
235
236fn check_coord_at_position<const D: usize>(
244 coord: &Coord<D>,
245 grid_position: &[isize],
246 samples: &[Coord<D>],
247 grid: &Grid<D>,
248 rmin: f64,
249 use_pbc: bool,
250) -> bool {
251 match grid
252 .get_index(grid_position, use_pbc)
253 .and_then(|grid_index| get_sample_from_grid(grid_index, samples, grid))
254 {
255 Some(existing_coord) => {
256 let r = if use_pbc {
257 calc_distance_pbc(coord, existing_coord, &grid.pbc)
258 } else {
259 calc_distance(coord, existing_coord)
260 };
261
262 r >= rmin
263 }
264 None => true,
265 }
266}
267
268fn get_grid_shape<const D: usize>(rmin: f64, box_size: &[f64; D]) -> [usize; D] {
270 let max_bin_side_length = rmin / (D as f64).sqrt();
271 let mut shape = [0; D];
272
273 shape
274 .iter_mut()
275 .zip(box_size.iter())
276 .for_each(|(v, length)| {
277 let max_bin_size = length / max_bin_side_length;
278
279 *v = max_bin_size.ceil() as usize;
280 });
281
282 shape
283}
284
285fn validate_number(value: f64) -> bool {
290 value > 0.0 && value.is_finite()
291}
292
293fn validate_rmin<const D: usize>(rmin: f64) -> Result<(), Error<D>> {
294 if validate_number(rmin) {
295 Ok(())
296 } else {
297 Err(Error::InvalidRmin(rmin))
298 }
299}
300
301fn validate_box_size<const D: usize>(box_size: &Coord<D>) -> Result<(), Error<D>> {
302 for &value in box_size {
303 if !validate_number(value) {
304 return Err(Error::InvalidBoxSize {
305 value,
306 box_size: box_size.to_vec(),
307 });
308 }
309 }
310
311 Ok(())
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317
318 fn add_sample_at_grid_position<const D: usize>(
319 position: &[isize],
320 samples: &mut Vec<Coord<D>>,
321 grid: &mut Grid<D>,
322 ) {
323 use std::convert::TryInto;
324
325 let coord: Coord<D> = position
326 .iter()
327 .zip(grid.spacing.iter())
328 .map(|(&i, dx)| (i as f64 + 0.5) * dx)
329 .collect::<Vec<_>>()
330 .as_slice()
331 .try_into()
332 .unwrap();
333
334 let grid_index = grid.get_index(position, true).unwrap();
335 let mut buf = HashSet::new();
336
337 add_sample_to_list_and_grid(coord, grid_index, samples, &mut buf, grid);
338 }
339
340 #[test]
341 fn checking_coordinate_in_empty_grid_returns_true() {
342 let shape = [4, 4];
343 let size = [8.0, 8.0];
344
345 let coord = [3.0, 3.0]; let rmin = 2.0 * 2.0_f64.sqrt();
347
348 let grid = Grid::new(&shape, &size);
349
350 assert!(check_if_coord_is_valid(&coord, &[], &grid, rmin, true))
351 }
352
353 #[test]
354 fn checking_coordinate_in_grid_with_distant_coord_returns_true() {
355 let shape = [4, 4];
356 let size = [8.0, 8.0];
357
358 let coord = [3.0, 3.0]; let rmin = 2.0 * 2.0_f64.sqrt();
360
361 let mut grid = Grid::new(&shape, &size);
362 let mut samples = Vec::new();
363
364 add_sample_at_grid_position(&[3, 3], &mut samples, &mut grid);
365
366 assert!(check_if_coord_is_valid(&coord, &samples, &grid, rmin, true))
367 }
368
369 #[test]
370 fn checking_coordinate_uses_num_adjacent_for_its_search_space() {
371 let shape = [4, 4];
373 let size = [8.0, 8.0];
374
375 let coord = [1.0, 1.0]; let rmin = 2.0 * 2.0_f64.sqrt();
377
378 let mut grid = Grid::new(&shape, &size);
379
380 let mut samples = Vec::new();
381 add_sample_at_grid_position(&[3, 3], &mut samples, &mut grid);
382
383 grid.num_adjacent = 3;
385
386 samples[0] = [1.0, 2.0];
388
389 assert!(!check_if_coord_is_valid(
390 &coord, &samples, &grid, rmin, true
391 ))
392 }
393
394 #[test]
395 fn checking_coordinate_in_grid_with_close_coord_returns_false() {
396 let shape = [4, 4];
397 let size = [8.0, 8.0];
398
399 let coord = [3.0, 3.0]; let rmin = 2.0 * 2.0_f64.sqrt();
401
402 let mut grid = Grid::new(&shape, &size);
403 let mut samples = Vec::new();
404
405 add_sample_at_grid_position(&[2, 1], &mut samples, &mut grid);
406
407 assert!(!check_if_coord_is_valid(
408 &coord, &samples, &grid, rmin, true
409 ))
410 }
411
412 #[test]
413 fn checking_coordinate_in_grid_with_coord_in_adjacent_box_but_not_within_rmin_returns_true() {
414 let shape = [4, 4];
415 let size = [8.0, 8.0];
416
417 let coord = [2.01, 2.01]; let rmin = 2.0 * 2.0_f64.sqrt();
419
420 let mut grid = Grid::new(&shape, &size);
421 let mut samples = Vec::new();
422
423 add_sample_at_grid_position(&[2, 1], &mut samples, &mut grid);
424 samples[0] = [5.99, 3.99]; assert!(check_if_coord_is_valid(&coord, &samples, &grid, rmin, true))
427 }
428
429 #[test]
430 fn checking_coordinates_works_over_pbc_connected_cells() {
431 let shape = [4, 4];
432 let size = [8.0, 8.0];
433
434 let coord = [3.0, 0.0]; let rmin = 2.0 * 2.0_f64.sqrt();
436
437 let mut grid = Grid::new(&shape, &size);
438 let mut samples = Vec::new();
439
440 add_sample_at_grid_position(&[1, 3], &mut samples, &mut grid);
441
442 samples[0] = [3.0, 4.0]; assert!(check_if_coord_is_valid(&coord, &samples, &grid, rmin, true));
444
445 samples[0] = [3.0, 8.0]; assert!(!check_if_coord_is_valid(
447 &coord, &samples, &grid, rmin, true
448 ));
449 }
450
451 #[test]
456 fn non_positive_rmin_yields_error() {
457 let box_size = [5.0, 5.0];
458 let num_attempts = 10;
459
460 assert!(bridson(&box_size, 1.0, 10, true).is_ok());
461
462 let rmin_zero = 0.0;
463 let rmin_neg = -1.0;
464 let rmin_nan = f64::NAN;
465 let rmin_inf = f64::INFINITY;
466 let rmin_neg_inf = f64::NEG_INFINITY;
467
468 assert!(bridson(&box_size, rmin_zero, num_attempts, true)
469 .unwrap_err()
470 .is_invalid_rmin());
471
472 assert!(bridson(&box_size, rmin_neg, num_attempts, true)
473 .unwrap_err()
474 .is_invalid_rmin());
475
476 assert!(bridson(&box_size, rmin_nan, num_attempts, true)
477 .unwrap_err()
478 .is_invalid_rmin());
479
480 assert!(bridson(&box_size, rmin_inf, num_attempts, true)
481 .unwrap_err()
482 .is_invalid_rmin());
483
484 assert!(bridson(&box_size, rmin_neg_inf, num_attempts, true)
485 .unwrap_err()
486 .is_invalid_rmin());
487 }
488
489 #[test]
490 fn empty_box_size_yields_no_coords() {
491 assert!(bridson(&[], 1.0, 10, true).unwrap().is_empty());
492 }
493
494 #[test]
495 fn non_positive_box_size_yields_error() {
496 let rmin = 1.0;
497 let num_attempts = 10;
498
499 assert!(bridson(&[5.0, 5.0], rmin, num_attempts, true).is_ok());
500
501 assert!(bridson(&[-5.0, 5.0], rmin, num_attempts, true)
504 .unwrap_err()
505 .is_invalid_box_size());
506
507 assert!(bridson(&[5.0, -5.0], rmin, num_attempts, true)
508 .unwrap_err()
509 .is_invalid_box_size());
510
511 assert!(bridson(&[-5.0, -5.0], rmin, num_attempts, true)
512 .unwrap_err()
513 .is_invalid_box_size());
514
515 assert!(bridson(&[f64::NAN, 5.0], rmin, num_attempts, true)
516 .unwrap_err()
517 .is_invalid_box_size());
518
519 assert!(bridson(&[5.0, f64::NAN], rmin, num_attempts, true)
520 .unwrap_err()
521 .is_invalid_box_size());
522
523 assert!(bridson(&[f64::INFINITY, 5.0], rmin, num_attempts, true)
524 .unwrap_err()
525 .is_invalid_box_size());
526
527 assert!(bridson(&[5.0, f64::INFINITY], rmin, num_attempts, true)
528 .unwrap_err()
529 .is_invalid_box_size());
530
531 assert!(bridson(&[5.0, f64::NEG_INFINITY], rmin, num_attempts, true)
532 .unwrap_err()
533 .is_invalid_box_size());
534
535 assert!(bridson(&[f64::NEG_INFINITY, 5.0], rmin, num_attempts, true)
536 .unwrap_err()
537 .is_invalid_box_size());
538 }
539
540 #[test]
541 fn non_positive_num_attempts_works() {
542 let box_size = [5.0, 5.0];
543 let rmin = 1.0;
544
545 assert!(bridson(&box_size, rmin, 0, true).is_ok());
546 assert!(bridson(&box_size, rmin, 1, true).is_ok());
547 assert!(bridson(&box_size, rmin, 10, true).is_ok());
548 }
549}