bentopy 0.2.0a10__cp313-cp313-manylinux_2_34_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. bentopy-0.2.0a10.data/scripts/bentopy-init +0 -0
  2. bentopy-0.2.0a10.data/scripts/bentopy-pack +0 -0
  3. bentopy-0.2.0a10.data/scripts/bentopy-render +0 -0
  4. bentopy-0.2.0a10.data/scripts/bentopy-solvate +0 -0
  5. bentopy-0.2.0a10.dist-info/METADATA +358 -0
  6. bentopy-0.2.0a10.dist-info/RECORD +58 -0
  7. bentopy-0.2.0a10.dist-info/WHEEL +5 -0
  8. bentopy-0.2.0a10.dist-info/entry_points.txt +4 -0
  9. bentopy-0.2.0a10.dist-info/licenses/LICENSE.txt +13 -0
  10. bentopy-0.2.0a10.dist-info/top_level.txt +8 -0
  11. check/check.py +128 -0
  12. core/config/bent/lexer.rs +338 -0
  13. core/config/bent/parser.rs +1180 -0
  14. core/config/bent/writer.rs +205 -0
  15. core/config/bent.rs +149 -0
  16. core/config/compartment_combinations.rs +300 -0
  17. core/config/legacy.rs +768 -0
  18. core/config.rs +362 -0
  19. core/mod.rs +4 -0
  20. core/placement.rs +100 -0
  21. core/utilities.rs +1 -0
  22. core/version.rs +32 -0
  23. init/example.bent +74 -0
  24. init/main.rs +235 -0
  25. mask/config.py +153 -0
  26. mask/mask.py +308 -0
  27. mask/utilities.py +38 -0
  28. merge/merge.py +175 -0
  29. pack/args.rs +77 -0
  30. pack/main.rs +121 -0
  31. pack/mask.rs +940 -0
  32. pack/session.rs +176 -0
  33. pack/state/combinations.rs +31 -0
  34. pack/state/compartment.rs +44 -0
  35. pack/state/mask.rs +196 -0
  36. pack/state/pack.rs +187 -0
  37. pack/state/segment.rs +72 -0
  38. pack/state/space.rs +98 -0
  39. pack/state.rs +440 -0
  40. pack/structure.rs +185 -0
  41. pack/voxelize.rs +85 -0
  42. render/args.rs +109 -0
  43. render/limits.rs +73 -0
  44. render/main.rs +12 -0
  45. render/render.rs +393 -0
  46. render/structure.rs +264 -0
  47. solvate/args.rs +324 -0
  48. solvate/convert.rs +25 -0
  49. solvate/cookies.rs +185 -0
  50. solvate/main.rs +177 -0
  51. solvate/placement.rs +380 -0
  52. solvate/solvate.rs +244 -0
  53. solvate/structure.rs +160 -0
  54. solvate/substitute.rs +113 -0
  55. solvate/water/martini.rs +409 -0
  56. solvate/water/models.rs +150 -0
  57. solvate/water/tip3p.rs +658 -0
  58. solvate/water.rs +115 -0
pack/mask.rs ADDED
@@ -0,0 +1,940 @@
1
+ use std::ops::{BitAndAssign, BitOrAssign, Not};
2
+
3
+ use glam::{I64Vec3, IVec3, U64Vec3};
4
+ use rayon::iter::{IntoParallelIterator, ParallelIterator};
5
+
6
+ pub type Dimensions = [u64; 3]; // TODO: Make into usize? Rather awkward in places right now.
7
+ pub type Position = Dimensions;
8
+
9
+ type Backing = u8;
10
+ const BACKING_BITS: usize = Backing::BITS as usize;
11
+
12
+ /// A 3-dimensional bitmap-backed `Mask` type that is used to represent voxelized spaces.
13
+ ///
14
+ /// Invariant: A `Mask` has non-zero dimensions.
15
+ #[derive(Debug, Clone)]
16
+ pub struct Mask {
17
+ dimensions: [usize; 3],
18
+ pub(crate) backings: Box<[Backing]>,
19
+ }
20
+
21
+ impl Mask {
22
+ pub fn new(dimensions: Dimensions) -> Self {
23
+ Self::fill::<false>(dimensions)
24
+ }
25
+
26
+ pub fn fill<const VALUE: bool>(dimensions: Dimensions) -> Self {
27
+ let dimensions = dimensions.map(|v| v as usize);
28
+ // Invariant: A `Mask` has non-zero dimensions.
29
+ assert!(
30
+ dimensions.iter().all(|&v| v > 0),
31
+ "a mask must have non-zero dimensions"
32
+ );
33
+ let n: usize = dimensions.iter().product();
34
+ let n_backings = n.div_ceil(BACKING_BITS);
35
+ let value = if VALUE { Backing::MAX } else { Backing::MIN };
36
+ Self {
37
+ dimensions,
38
+ backings: vec![value; n_backings].into_boxed_slice(),
39
+ }
40
+ }
41
+
42
+ /// # Panics
43
+ ///
44
+ /// If the provided `dimensions` are not compatible with the number of `cells`, this function
45
+ /// will panic.
46
+ pub fn from_cells(dimensions: Dimensions, cells: &[bool]) -> Self {
47
+ assert_eq!(
48
+ dimensions.iter().product::<u64>() as usize,
49
+ cells.len(),
50
+ "the number of cells must match the dimensions"
51
+ );
52
+
53
+ Self::from_cells_iter(dimensions, cells.iter().copied())
54
+ }
55
+
56
+ /// An internal function for setting up a [`Mask`] from an iterator of `bool`s.
57
+ ///
58
+ /// Providing an iterator that is not exactly the correct length is not acceptable behavior but
59
+ /// is not unsafe.
60
+ fn from_cells_iter(dimensions: Dimensions, cells: impl Iterator<Item = bool>) -> Self {
61
+ let mut mask = Self::new(dimensions);
62
+
63
+ for lin_idx in cells
64
+ .enumerate()
65
+ .filter_map(|(i, v)| if v { Some(i) } else { None })
66
+ {
67
+ mask.set_linear_unchecked::<true>(lin_idx)
68
+ }
69
+
70
+ mask
71
+ }
72
+
73
+ pub const fn dimensions(&self) -> Dimensions {
74
+ let [w, h, d] = self.dimensions;
75
+ [w as u64, h as u64, d as u64]
76
+ }
77
+
78
+ /// Return the number of cells that are equal to `VALUE`.
79
+ pub fn count<const VALUE: bool>(&self) -> usize {
80
+ // If we're counting the `false` cells, we may overcount when there are trailing bits in
81
+ // the last backing.
82
+ let overshoot = if VALUE {
83
+ 0
84
+ } else {
85
+ self.n_backings() * BACKING_BITS - self.n_cells()
86
+ };
87
+ let uncorrected: usize = self
88
+ .backings
89
+ .iter()
90
+ .map(|backing| {
91
+ if VALUE {
92
+ backing.count_ones() as usize
93
+ } else {
94
+ backing.count_zeros() as usize
95
+ }
96
+ })
97
+ .sum();
98
+ uncorrected - overshoot
99
+ }
100
+
101
+ const fn n_cells(&self) -> usize {
102
+ let [w, h, d] = self.dimensions;
103
+ w * h * d
104
+ }
105
+
106
+ pub(crate) const fn n_backings(&self) -> usize {
107
+ self.backings.len()
108
+ }
109
+
110
+ const fn backing_idx(&self, lin_idx: usize) -> (usize, usize) {
111
+ (lin_idx / BACKING_BITS, lin_idx % BACKING_BITS)
112
+ }
113
+
114
+ const fn contains(&self, idx: Position) -> bool {
115
+ let [x, y, z] = idx;
116
+ let [w, h, d] = self.dimensions();
117
+ x < w && y < h && z < d
118
+ }
119
+
120
+ pub const fn spatial_idx(&self, mut lin_idx: usize) -> Option<Position> {
121
+ if lin_idx >= self.n_cells() {
122
+ return None;
123
+ }
124
+
125
+ let [w, h, _] = self.dimensions;
126
+ let x = lin_idx % w;
127
+ lin_idx /= w;
128
+ let y = lin_idx % h;
129
+ lin_idx /= h;
130
+ let z = lin_idx;
131
+
132
+ Some([x as u64, y as u64, z as u64])
133
+ }
134
+
135
+ const fn linear_idx(&self, idx: Position) -> usize {
136
+ let [x, y, z] = idx;
137
+ let [w, h, _] = self.dimensions;
138
+ let lin_idx = x as usize + y as usize * w + z as usize * w * h;
139
+ debug_assert!(lin_idx < self.n_cells());
140
+ lin_idx
141
+ }
142
+
143
+ const fn get_linear_unchecked(&self, lin_idx: usize) -> bool {
144
+ debug_assert!(lin_idx < self.n_cells());
145
+ let (backing_idx, bit_idx) = self.backing_idx(lin_idx);
146
+ self.backings[backing_idx] >> bit_idx & 1 != 0
147
+ }
148
+
149
+ /// Check whether the cell at the specified `lin_idx` is `VALUE`.
150
+ const fn query_linear_unchecked<const VALUE: bool>(&self, lin_idx: usize) -> bool {
151
+ debug_assert!(lin_idx < self.n_cells());
152
+ let (backing_idx, bit_idx) = self.backing_idx(lin_idx);
153
+ let backing = self.backings[backing_idx];
154
+ // FIXME: This seems convoluted for what the truth table actually looks like.
155
+ if VALUE {
156
+ match backing {
157
+ 0 => false,
158
+ Backing::MAX => true,
159
+ _ => backing >> bit_idx & 1 != 0,
160
+ }
161
+ } else {
162
+ match backing {
163
+ 0 => true,
164
+ Backing::MAX => false,
165
+ _ => backing >> bit_idx & 1 == 0,
166
+ }
167
+ }
168
+ }
169
+
170
+ fn set_linear_unchecked<const VALUE: bool>(&mut self, lin_idx: usize) {
171
+ debug_assert!(lin_idx < self.n_cells());
172
+ let (backing_idx, bit_idx) = self.backing_idx(lin_idx);
173
+ if VALUE {
174
+ self.backings[backing_idx] |= 1 << bit_idx;
175
+ } else {
176
+ self.backings[backing_idx] &= !(1 << bit_idx);
177
+ }
178
+ }
179
+
180
+ #[allow(dead_code)]
181
+ pub const fn get(&self, idx: Position) -> Option<bool> {
182
+ if self.contains(idx) {
183
+ let lin_idx = self.linear_idx(idx);
184
+ Some(self.get_linear_unchecked(lin_idx))
185
+ } else {
186
+ None
187
+ }
188
+ }
189
+
190
+ pub const fn get_periodic(&self, idx: Position) -> bool {
191
+ let lin_idx = self.linear_idx(normalize_periodic(idx, self.dimensions()));
192
+ self.get_linear_unchecked(lin_idx)
193
+ }
194
+
195
+ /// # Panics
196
+ ///
197
+ /// If `idx` is not within the dimensions of this `Mask`, this function will panic.
198
+ pub fn set(&mut self, idx: Position, value: bool) {
199
+ assert!(
200
+ self.contains(idx),
201
+ "the provided `idx` must be within the dimensions of this mask"
202
+ );
203
+ let lin_idx = self.linear_idx(idx);
204
+ if value {
205
+ self.set_linear_unchecked::<true>(lin_idx)
206
+ } else {
207
+ self.set_linear_unchecked::<false>(lin_idx)
208
+ }
209
+ }
210
+
211
+ pub fn set_periodic(&mut self, idx: Position, value: bool) {
212
+ let lin_idx = self.linear_idx(normalize_periodic(idx, self.dimensions()));
213
+ if value {
214
+ self.set_linear_unchecked::<true>(lin_idx)
215
+ } else {
216
+ self.set_linear_unchecked::<false>(lin_idx)
217
+ }
218
+ }
219
+
220
+ /// Return an [`Iterator`] over all linear indices where the cell is equal to `VALUE`.
221
+ pub fn linear_indices_where<const VALUE: bool>(&self) -> impl Iterator<Item = usize> + '_ {
222
+ (0..self.n_cells()).filter(|&lin_idx| self.query_linear_unchecked::<VALUE>(lin_idx))
223
+ }
224
+
225
+ /// Return an [`Iterator`] over all indices where the cell is equal to `VALUE`.
226
+ pub fn indices_where<const VALUE: bool>(&self) -> impl Iterator<Item = Position> + '_ {
227
+ let [w, h, d] = self.dimensions();
228
+ (0..d).flat_map(move |z| {
229
+ (0..h).flat_map(move |y| {
230
+ (0..w).filter_map(move |x| {
231
+ let idx = [x, y, z];
232
+ let lin_idx = self.linear_idx(idx);
233
+ if self.query_linear_unchecked::<VALUE>(lin_idx) {
234
+ Some(idx)
235
+ } else {
236
+ None
237
+ }
238
+ })
239
+ })
240
+ })
241
+ }
242
+
243
+ /// Apply some `mask` onto this [`Mask`].
244
+ ///
245
+ /// This is essentially an `or-assign` operation between the `self` and the provided `mask`.
246
+ ///
247
+ /// # Panics
248
+ ///
249
+ /// If the dimensions of the stamped and stamping mask are different, this function will panic.
250
+ pub fn apply_mask(&mut self, mask: &Mask) {
251
+ assert_eq!(
252
+ self.dimensions(),
253
+ mask.dimensions(),
254
+ "the dimensions of both masks must be identical"
255
+ );
256
+ // For good measure, so the compiler gets it.
257
+ assert_eq!(self.n_backings(), mask.n_backings()); // FIXME: Is this one necessary?
258
+
259
+ self.backings
260
+ .iter_mut()
261
+ .zip(mask.backings.iter())
262
+ .for_each(|(s, &m)| *s |= m);
263
+ }
264
+
265
+ pub fn merge_mask(&mut self, mask: &Mask) {
266
+ assert_eq!(
267
+ self.dimensions(),
268
+ mask.dimensions(),
269
+ "the dimensions of both masks must be identical"
270
+ );
271
+ // For good measure, so the compiler gets it.
272
+ assert_eq!(self.n_backings(), mask.n_backings()); // FIXME: Is this one necessary?
273
+
274
+ self.backings
275
+ .iter_mut()
276
+ .zip(mask.backings.iter())
277
+ .for_each(|(s, &m)| *s &= m);
278
+ }
279
+
280
+ pub fn apply_function(&mut self, f: impl Fn(Position) -> bool) {
281
+ let [w, h, d] = self.dimensions();
282
+ let mut lin_idx = 0;
283
+ for z in 0..d {
284
+ for y in 0..h {
285
+ for x in 0..w {
286
+ if f([x, y, z]) {
287
+ self.set_linear_unchecked::<true>(lin_idx);
288
+ } else {
289
+ self.set_linear_unchecked::<false>(lin_idx);
290
+ }
291
+ lin_idx += 1;
292
+ }
293
+ }
294
+ }
295
+ }
296
+
297
+ /// Return whether any of the [`Mask`] cells have the provided `VALUE`.
298
+ pub fn any<const VALUE: bool>(&self) -> bool {
299
+ (0..self.n_cells()).any(|lin_idx| self.query_linear_unchecked::<VALUE>(lin_idx))
300
+ }
301
+
302
+ // TODO: Periodicity?
303
+ /// Grow the voxels in the [`Mask`] in _x_, _y_, and _z_ directions.
304
+ pub fn grow_approx(&mut self) {
305
+ const OFFSETS: [I64Vec3; 6] = [
306
+ I64Vec3::X,
307
+ I64Vec3::NEG_X,
308
+ I64Vec3::Y,
309
+ I64Vec3::NEG_Y,
310
+ I64Vec3::Z,
311
+ I64Vec3::NEG_Z,
312
+ ];
313
+
314
+ let old = self.clone();
315
+ let dimensions = self.dimensions();
316
+ let [w, h, d] = dimensions;
317
+ let offset_masks = OFFSETS.into_par_iter().map(|offset| {
318
+ let mut mask = Mask::new(dimensions);
319
+ for z in 0..d {
320
+ let oz = z as i64 + offset.z;
321
+ if oz < 0 || oz >= d as i64 {
322
+ continue;
323
+ }
324
+ let oz = oz as u64;
325
+
326
+ for y in 0..h {
327
+ let oy = y as i64 + offset.y;
328
+ if oy < 0 || oy >= h as i64 {
329
+ continue;
330
+ }
331
+ let oy = oy as u64;
332
+
333
+ for x in 0..w {
334
+ let ox = x as i64 + offset.x;
335
+ if ox < 0 || ox >= w as i64 {
336
+ continue;
337
+ }
338
+ let ox = ox as u64;
339
+
340
+ let offset_lin_idx = ox + oy * w + oz * w * h;
341
+ if old.query_linear_unchecked::<true>(offset_lin_idx as usize) {
342
+ let lin_idx = x + y * w + z * w * h;
343
+ mask.set_linear_unchecked::<true>(lin_idx as usize)
344
+ }
345
+ }
346
+ }
347
+ }
348
+
349
+ mask
350
+ });
351
+
352
+ *self |= offset_masks.reduce(
353
+ || Mask::new(dimensions),
354
+ |mut acc, mask| {
355
+ acc |= mask;
356
+ acc
357
+ },
358
+ );
359
+ }
360
+ }
361
+
362
+ /// Take an `idx` that may fall outside of the `dimensions` and return a normalized [`Position`].
363
+ // NOTE: It turns out that when compiled with '-C target-cpu=native', this unrolled operation
364
+ // behaved very poorly. That led to poor performance, as this function is very hot.
365
+ // Inlining this function helped.
366
+ #[inline(always)]
367
+ const fn normalize_periodic(idx: Position, dimensions: Dimensions) -> Position {
368
+ [
369
+ idx[0] % dimensions[0],
370
+ idx[1] % dimensions[1],
371
+ idx[2] % dimensions[2],
372
+ ]
373
+ }
374
+
375
+ mod blocks {
376
+ use super::{Dimensions, IVec3, U64Vec3};
377
+
378
+ type Block = Vec<U64Vec3>;
379
+
380
+ pub struct Blocks {
381
+ radius: u64,
382
+ size: [usize; 3],
383
+ blocks: Vec<Block>,
384
+ }
385
+
386
+ impl Blocks {
387
+ pub fn from_iter(
388
+ radius: u64,
389
+ dimensions: Dimensions,
390
+ positions: impl Iterator<Item = U64Vec3>,
391
+ ) -> Self {
392
+ let size = dimensions.map(|v| (v / radius) as usize + 1);
393
+ let [w, h, _] = size;
394
+ let mut blocks = vec![Vec::new(); size.iter().product()];
395
+ for pos in positions {
396
+ let idx = pos / radius;
397
+ let [x, y, z] = idx.to_array().map(|v| v as usize);
398
+ let lin_idx = x + y * w + z * w * h;
399
+ blocks[lin_idx].push(pos)
400
+ }
401
+
402
+ Self {
403
+ radius,
404
+ size,
405
+ blocks,
406
+ }
407
+ }
408
+
409
+ pub fn nearby(&self, position: U64Vec3) -> impl Iterator<Item = U64Vec3> + '_ {
410
+ let [w, h, d] = self.size;
411
+ let block_idx = (position / self.radius).as_ivec3();
412
+ [
413
+ IVec3::new(0, 0, 0),
414
+ IVec3::new(-1, 0, 0),
415
+ IVec3::new(1, 0, 0),
416
+ IVec3::new(0, -1, 0),
417
+ IVec3::new(0, 1, 0),
418
+ IVec3::new(0, 0, -1),
419
+ IVec3::new(0, 0, 1),
420
+ IVec3::new(-1, -1, -1),
421
+ IVec3::new(-1, -1, 0),
422
+ IVec3::new(-1, -1, 1),
423
+ IVec3::new(-1, 0, -1),
424
+ IVec3::new(-1, 0, 1),
425
+ IVec3::new(-1, 1, -1),
426
+ IVec3::new(-1, 1, 0),
427
+ IVec3::new(-1, 1, 1),
428
+ IVec3::new(0, -1, -1),
429
+ IVec3::new(0, -1, 1),
430
+ IVec3::new(0, 1, -1),
431
+ IVec3::new(0, 1, 1),
432
+ IVec3::new(1, -1, -1),
433
+ IVec3::new(1, -1, 0),
434
+ IVec3::new(1, -1, 1),
435
+ IVec3::new(1, 0, -1),
436
+ IVec3::new(1, 0, 1),
437
+ IVec3::new(1, 1, -1),
438
+ IVec3::new(1, 1, 0),
439
+ IVec3::new(1, 1, 1),
440
+ ]
441
+ .into_iter()
442
+ .map(move |offset| block_idx + offset)
443
+ .filter(move |block_idx| {
444
+ (0..w as i32).contains(&block_idx.x)
445
+ && (0..h as i32).contains(&block_idx.y)
446
+ && (0..d as i32).contains(&block_idx.z)
447
+ })
448
+ .flat_map(move |block_idx| {
449
+ let [x, y, z] = block_idx.to_array().map(|v| v as usize);
450
+ let lin_idx = x + y * w + z * w * h;
451
+ &self.blocks[lin_idx]
452
+ })
453
+ .copied()
454
+ }
455
+ }
456
+ }
457
+
458
+ // TODO: Reconsider whether we want to use this function or `distance_mask_grow`.
459
+ // TODO: Add a periodic counterpart or setting.
460
+ #[allow(dead_code)]
461
+ pub fn distance_mask(thing: &Mask, radius: f32) -> Mask {
462
+ let dimensions = thing.dimensions();
463
+
464
+ assert!(radius >= 0.0);
465
+ let positions = thing.indices_where::<false>().map(U64Vec3::from_array);
466
+ let thing_blocks = blocks::Blocks::from_iter(radius as u64, dimensions, positions);
467
+
468
+ let radius_squared = radius.powi(2) as i64;
469
+ let [w, h, d] = dimensions;
470
+ let start = std::time::Instant::now();
471
+ let slices = (0..d).into_par_iter().map(|z| {
472
+ let mut slice = Mask::new(dimensions);
473
+ for y in 0..h {
474
+ for x in 0..w {
475
+ let pos = U64Vec3::from_array([x, y, z]);
476
+ let posi = pos.as_i64vec3();
477
+ let mut nearby = thing_blocks.nearby(pos);
478
+ if nearby.any(|t| {
479
+ let distance_squared = t.as_i64vec3().distance_squared(posi);
480
+ distance_squared < radius_squared
481
+ }) {
482
+ let lin_idx = x + y * w + z * w * h;
483
+ slice.set_linear_unchecked::<true>(lin_idx as usize)
484
+ }
485
+ }
486
+ }
487
+
488
+ slice
489
+ });
490
+
491
+ let mask = slices.reduce(
492
+ || Mask::new(dimensions),
493
+ |mut acc, slice| {
494
+ acc |= slice;
495
+ acc
496
+ },
497
+ );
498
+
499
+ eprintln!("DISTANCE MASK took {:.3} s", start.elapsed().as_secs_f32());
500
+
501
+ mask
502
+ }
503
+
504
+ // TODO: Add a periodic counterpart or setting.
505
+ /// Create a distance mask by growing the starting point by `radius` voxel steps.
506
+ pub fn distance_mask_grow(mask: &Mask, radius: u64) -> Mask {
507
+ let mut mask = !mask.clone();
508
+
509
+ for _ in 0..radius {
510
+ mask.grow_approx()
511
+ }
512
+
513
+ !mask
514
+ }
515
+
516
+ impl BitOrAssign for Mask {
517
+ fn bitor_assign(&mut self, rhs: Self) {
518
+ assert_eq!(
519
+ self.dimensions(),
520
+ rhs.dimensions(),
521
+ "the dimensions of both masks must be identical"
522
+ );
523
+ // For good measure, so the compiler gets it.
524
+ assert_eq!(self.n_backings(), rhs.n_backings()); // FIXME: Is this one necessary?
525
+
526
+ self.backings
527
+ .iter_mut()
528
+ .zip(rhs.backings.iter())
529
+ .for_each(|(s, &m)| *s |= m);
530
+ }
531
+ }
532
+
533
+ impl BitAndAssign for Mask {
534
+ // TODO: Perhaps introduce a macro to set up functions like this?
535
+ fn bitand_assign(&mut self, rhs: Self) {
536
+ assert_eq!(
537
+ self.dimensions(),
538
+ rhs.dimensions(),
539
+ "the dimensions of both masks must be identical"
540
+ );
541
+ // For good measure, so the compiler gets it.
542
+ assert_eq!(self.n_backings(), rhs.n_backings()); // FIXME: Is this one necessary?
543
+
544
+ self.backings
545
+ .iter_mut()
546
+ .zip(rhs.backings.iter())
547
+ .for_each(|(s, &m)| *s &= m);
548
+ }
549
+ }
550
+
551
+ impl Not for Mask {
552
+ type Output = Self;
553
+
554
+ fn not(mut self) -> Self::Output {
555
+ self.backings.iter_mut().for_each(|b| *b = !*b);
556
+ self
557
+ }
558
+ }
559
+
560
+ #[cfg(test)]
561
+ mod tests {
562
+ use super::*;
563
+
564
+ #[test]
565
+ fn create() {
566
+ let _ = Mask::new([10, 20, 30]);
567
+ }
568
+
569
+ #[test]
570
+ #[should_panic]
571
+ fn create_zero() {
572
+ let _ = Mask::new([0, 0, 0]);
573
+ }
574
+
575
+ #[test]
576
+ fn from_cells() {
577
+ let cells = [
578
+ false, false, false, true, true, true, false, false, false, false, true, false, true,
579
+ true, true, false, false, true, false, false, false, true, true, true, true, true,
580
+ true,
581
+ ];
582
+ let _ = Mask::from_cells([3, 3, 3], &cells);
583
+ }
584
+
585
+ #[test]
586
+ #[should_panic]
587
+ fn from_cells_under() {
588
+ let cells = [
589
+ false, false, false, true, true, true, false, false, false, false, true, false, true,
590
+ true, true, false, false, true, false, false, false, true, true, true,
591
+ ];
592
+ let _ = Mask::from_cells([3, 3, 3], &cells);
593
+ }
594
+
595
+ #[test]
596
+ #[should_panic]
597
+ fn from_cells_over() {
598
+ let cells = [
599
+ false, false, false, true, true, true, false, false, false, false, true, false, true,
600
+ true, true, false, false, true, false, false, false, true, true, true, true, true,
601
+ true, true, true, false, true,
602
+ ];
603
+ let _ = Mask::from_cells([3, 3, 3], &cells);
604
+ }
605
+
606
+ #[test]
607
+ fn dimensions() {
608
+ let dimensions = [5172, 1312, 161];
609
+ let mask = Mask::new(dimensions);
610
+ assert_eq!(mask.dimensions(), dimensions);
611
+ }
612
+
613
+ #[test]
614
+ fn count() {
615
+ let mut mask = Mask::new([172, 1312, 161]);
616
+
617
+ mask.set([123, 456, 78], true);
618
+ mask.set([123, 784, 56], true);
619
+ mask.set([78, 1236, 45], true);
620
+
621
+ assert_eq!(mask.count::<true>(), 3);
622
+ assert_eq!(mask.count::<false>(), mask.n_cells() - mask.count::<true>());
623
+
624
+ mask.set([78, 1236, 45], false);
625
+ assert_eq!(mask.count::<true>(), 2);
626
+ assert_eq!(mask.count::<false>(), mask.n_cells() - mask.count::<true>());
627
+ }
628
+
629
+ #[test]
630
+ fn n_cells() {
631
+ let mask = Mask::new([123, 456, 789]);
632
+ assert_eq!(mask.n_cells(), 123 * 456 * 789);
633
+ }
634
+
635
+ #[test]
636
+ fn n_backings() {
637
+ let mask = Mask::new([123, 456, 789]);
638
+ assert_eq!(mask.n_backings(), mask.n_cells().div_ceil(BACKING_BITS));
639
+ }
640
+
641
+ #[test]
642
+ fn backing_idx() {
643
+ let mask = Mask::new([123, 456, 789]);
644
+ assert_eq!(mask.backing_idx(0), (0, 0));
645
+ assert_eq!(mask.backing_idx(1), (0, 1));
646
+ assert_eq!(mask.backing_idx(2), (0, 2));
647
+ assert_eq!(mask.backing_idx(8), (8 / BACKING_BITS, 8 % BACKING_BITS));
648
+ }
649
+
650
+ #[test]
651
+ fn contains() {
652
+ let mask = Mask::new([123, 456, 789]);
653
+ assert!(!mask.contains([123, 456, 789]));
654
+ assert!(!mask.contains([122, 456, 789]));
655
+ assert!(!mask.contains([122, 455, 789]));
656
+ assert!(mask.contains([122, 455, 788]));
657
+ assert!(mask.contains([0, 0, 0]));
658
+ assert!(!mask.contains([12345, 0, 0]));
659
+ }
660
+
661
+ #[test]
662
+ fn spatial_idx() {
663
+ let mask = Mask::new([100, 100, 100]);
664
+ assert_eq!(mask.spatial_idx(0), Some([0, 0, 0]));
665
+ assert_eq!(mask.spatial_idx(99), Some([99, 0, 0]));
666
+ assert_eq!(mask.spatial_idx(100), Some([0, 1, 0]));
667
+ assert_eq!(mask.spatial_idx(101), Some([1, 1, 0]));
668
+ assert_eq!(mask.spatial_idx(1000000 - 1), Some([99, 99, 99]));
669
+ assert!(mask.spatial_idx(1000000).is_none());
670
+ assert!(mask.spatial_idx(12345678).is_none());
671
+ }
672
+
673
+ #[test]
674
+ fn linear_idx() {
675
+ let mask = Mask::new([100, 100, 100]);
676
+ assert_eq!(mask.linear_idx([0, 0, 0]), 0);
677
+ assert_eq!(mask.linear_idx([99, 0, 0]), 99);
678
+ assert_eq!(mask.linear_idx([0, 1, 0]), 100);
679
+ assert_eq!(mask.linear_idx([1, 1, 0]), 101);
680
+ assert_eq!(mask.linear_idx([99, 99, 99]), 1000000 - 1);
681
+ }
682
+
683
+ #[test]
684
+ #[should_panic]
685
+ fn linear_idx_panic() {
686
+ let mask = Mask::new([100, 100, 100]);
687
+ assert_eq!(mask.linear_idx([100, 100, 100]), 1000000); // FIXME: Reconsider this behavior.
688
+ }
689
+
690
+ #[test]
691
+ fn query_linear_unchecked() {
692
+ let cells = [
693
+ false, false, false, true, true, true, false, false, false, false, true, false, true,
694
+ true, true, false, false, true, false, false, false, true, true, true, true, true,
695
+ true,
696
+ ];
697
+ let mask = Mask::from_cells([3, 3, 3], &cells);
698
+
699
+ assert!(mask.query_linear_unchecked::<false>(0));
700
+ assert!(mask.query_linear_unchecked::<true>(13));
701
+ assert!(mask.query_linear_unchecked::<false>(7));
702
+ assert!(mask.query_linear_unchecked::<true>(12));
703
+ }
704
+
705
+ #[test]
706
+ fn set_linear_unchecked() {
707
+ let cells = [
708
+ false, false, false, true, true, true, false, false, false, false, true, false, true,
709
+ true, true, false, false, true, false, false, false, true, true, true, true, true,
710
+ true,
711
+ ];
712
+ let mut mask = Mask::from_cells([3, 3, 3], &cells);
713
+
714
+ // Was false, set false.
715
+ assert!(mask.query_linear_unchecked::<false>(0));
716
+ mask.set_linear_unchecked::<false>(0);
717
+ assert!(mask.query_linear_unchecked::<false>(0));
718
+
719
+ // Was true, set false.
720
+ assert!(mask.query_linear_unchecked::<true>(13));
721
+ mask.set_linear_unchecked::<false>(13);
722
+ assert!(mask.query_linear_unchecked::<false>(13));
723
+
724
+ // Was false, set true.
725
+ assert!(mask.query_linear_unchecked::<false>(8));
726
+ mask.set_linear_unchecked::<true>(8);
727
+ assert!(mask.query_linear_unchecked::<true>(8));
728
+
729
+ // Was true, set true.
730
+ assert!(mask.query_linear_unchecked::<true>(12));
731
+ mask.set_linear_unchecked::<true>(12);
732
+ assert!(mask.query_linear_unchecked::<true>(12));
733
+
734
+ // Was true, set false.
735
+ assert!(mask.query_linear_unchecked::<true>(17));
736
+ mask.set_linear_unchecked::<false>(17);
737
+ assert!(mask.query_linear_unchecked::<false>(17));
738
+ }
739
+
740
+ #[test]
741
+ fn get() {
742
+ let cells = [
743
+ false, false, false, true, true, true, false, false, false, false, true, false, true,
744
+ true, true, false, false, true, false, false, false, true, true, true, true, true,
745
+ true,
746
+ ];
747
+ let mut mask = Mask::from_cells([3, 3, 3], &cells);
748
+
749
+ assert_eq!(mask.get([0, 0, 0]), Some(false));
750
+ assert_eq!(mask.get([1, 1, 1]), Some(true));
751
+ assert_eq!(mask.get([2, 2, 2]), Some(true));
752
+ assert_eq!(mask.get([2, 2, 3]), None);
753
+ assert_eq!(mask.get([3, 3, 3]), None);
754
+ assert_eq!(mask.get([2, 4, 2]), None);
755
+
756
+ mask.set([1, 1, 1], true);
757
+ assert_eq!(mask.get([1, 1, 1]), Some(true));
758
+ }
759
+
760
+ #[test]
761
+ fn set() {
762
+ let cells = [
763
+ false, false, false, true, true, true, false, false, false, false, true, false, true,
764
+ true, true, false, false, true, false, false, false, true, true, true, true, true,
765
+ true,
766
+ ];
767
+ let mut mask = Mask::from_cells([3, 3, 3], &cells);
768
+
769
+ // Was false, set false.
770
+ assert_eq!(mask.get([0, 0, 0]), Some(false));
771
+ mask.set([0, 0, 0], false);
772
+ assert_eq!(mask.get([0, 0, 0]), Some(false));
773
+
774
+ // Was true, set true.
775
+ assert_eq!(mask.get([0, 1, 0]), Some(true));
776
+ mask.set([0, 1, 0], true);
777
+ assert_eq!(mask.get([0, 1, 0]), Some(true));
778
+
779
+ // Was false, set true.
780
+ assert_eq!(mask.get([1, 2, 0]), Some(false));
781
+ mask.set([1, 2, 0], true);
782
+ assert_eq!(mask.get([1, 2, 0]), Some(true));
783
+
784
+ // Was true, set false.
785
+ assert_eq!(mask.get([2, 2, 2]), Some(true));
786
+ mask.set([2, 2, 2], false);
787
+ assert_eq!(mask.get([2, 2, 2]), Some(false));
788
+ }
789
+
790
+ #[test]
791
+ fn indices_where() {
792
+ let cells = [
793
+ false, false, false, true, true, true, false, false, false, false, true, false, true,
794
+ true, true, false, false, true, false, false, false, true, true, true, true, true,
795
+ true,
796
+ ];
797
+ let mask = Mask::from_cells([3, 3, 3], &cells);
798
+
799
+ let where_false = [
800
+ [0, 0, 0],
801
+ [1, 0, 0],
802
+ [2, 0, 0],
803
+ [0, 2, 0],
804
+ [1, 2, 0],
805
+ [2, 2, 0],
806
+ [0, 0, 1],
807
+ [2, 0, 1],
808
+ [0, 2, 1],
809
+ [1, 2, 1],
810
+ [0, 0, 2],
811
+ [1, 0, 2],
812
+ [2, 0, 2],
813
+ ];
814
+
815
+ assert_eq!(
816
+ mask.indices_where::<false>().collect::<Vec<_>>(),
817
+ where_false
818
+ );
819
+
820
+ let where_true = [
821
+ [0, 1, 0],
822
+ [1, 1, 0],
823
+ [2, 1, 0],
824
+ [1, 0, 1],
825
+ [0, 1, 1],
826
+ [1, 1, 1],
827
+ [2, 1, 1],
828
+ [2, 2, 1],
829
+ [0, 1, 2],
830
+ [1, 1, 2],
831
+ [2, 1, 2],
832
+ [0, 2, 2],
833
+ [1, 2, 2],
834
+ [2, 2, 2],
835
+ ];
836
+
837
+ assert_eq!(mask.indices_where::<true>().collect::<Vec<_>>(), where_true);
838
+ }
839
+
840
+ #[test]
841
+ fn linear_indices_where() {
842
+ let cells = [
843
+ false, false, false, true, true, true, false, false, false, false, true, false, true,
844
+ true, true, false, false, true, false, false, false, true, true, true, true, true,
845
+ true,
846
+ ];
847
+ let mask = Mask::from_cells([3, 3, 3], &cells);
848
+
849
+ let where_false = [0, 1, 2, 6, 7, 8, 9, 11, 15, 16, 18, 19, 20];
850
+
851
+ assert_eq!(
852
+ mask.linear_indices_where::<false>().collect::<Vec<_>>(),
853
+ where_false
854
+ );
855
+
856
+ let where_true = [3, 4, 5, 10, 12, 13, 14, 17, 21, 22, 23, 24, 25, 26];
857
+
858
+ assert_eq!(
859
+ mask.linear_indices_where::<true>().collect::<Vec<_>>(),
860
+ where_true
861
+ );
862
+ }
863
+
864
+ #[test]
865
+ fn apply_mask() {
866
+ let cells = [
867
+ false, false, false, true, true, true, false, false, false, false, true, false, true,
868
+ true, true, false, false, true, false, false, false, true, true, true, true, true,
869
+ true,
870
+ ];
871
+ let mut base = Mask::from_cells([3, 3, 3], &cells);
872
+ assert!(base.query_linear_unchecked::<false>(0));
873
+ assert!(base.query_linear_unchecked::<false>(1));
874
+
875
+ let mut mask = base.clone();
876
+ mask.set([1, 0, 0], true);
877
+ assert!(mask.query_linear_unchecked::<false>(0));
878
+ assert!(mask.query_linear_unchecked::<true>(1));
879
+
880
+ // Mask the base.
881
+ base.apply_mask(&mask);
882
+ // And now base should have that same bit set and be identical aside from that.
883
+ assert!(base.query_linear_unchecked::<false>(0));
884
+ assert!(base.query_linear_unchecked::<true>(1));
885
+ }
886
+
887
+ #[test]
888
+ fn merge_mask() {
889
+ let cells = [
890
+ false, false, false, true, true, true, false, false, false, false, true, false, true,
891
+ true, true, false, false, true, false, false, false, true, true, true, true, true,
892
+ true,
893
+ ];
894
+ let mut base = Mask::from_cells([3, 3, 3], &cells);
895
+ assert!(base.query_linear_unchecked::<false>(0));
896
+ assert!(base.query_linear_unchecked::<false>(1));
897
+
898
+ let mut mask = base.clone();
899
+ mask.set([1, 0, 0], true);
900
+ assert!(mask.query_linear_unchecked::<false>(0));
901
+ assert!(mask.query_linear_unchecked::<true>(1));
902
+
903
+ // Mask the base.
904
+ base.merge_mask(&mask);
905
+ // And now base and mask should be identical again.
906
+ assert!(base.query_linear_unchecked::<false>(0));
907
+ assert!(base.query_linear_unchecked::<false>(1));
908
+ }
909
+
910
+ #[test]
911
+ fn grow() {
912
+ let mut cells = [false; 27];
913
+ cells[cells.len() / 2] = true; // Set the center cell.
914
+ let mut mask = Mask::from_cells([3, 3, 3], &cells);
915
+
916
+ assert_eq!(mask.count::<true>(), 1);
917
+
918
+ // After this, we should have a 3d cross.
919
+ mask.grow_approx();
920
+ assert_eq!(mask.count::<true>(), 1 + 6);
921
+ assert_eq!(mask.get([0, 0, 0]), Some(false));
922
+ assert_eq!(mask.get([1, 0, 0]), Some(false));
923
+ assert_eq!(mask.get([2, 0, 0]), Some(false));
924
+ assert_eq!(mask.get([0, 1, 0]), Some(false));
925
+ assert_eq!(mask.get([1, 1, 0]), Some(true));
926
+ assert_eq!(mask.get([2, 1, 0]), Some(false));
927
+ assert_eq!(mask.get([0, 1, 1]), Some(true));
928
+ assert_eq!(mask.get([1, 1, 1]), Some(true));
929
+ assert_eq!(mask.get([2, 1, 1]), Some(true));
930
+ assert_eq!(mask.get([2, 2, 2]), Some(false));
931
+
932
+ // Now we have a cube where only the corners are not occupied.
933
+ mask.grow_approx();
934
+ assert_eq!(mask.count::<true>(), mask.n_cells() - 8);
935
+
936
+ // Finally, this will fill up the whole mask.
937
+ mask.grow_approx();
938
+ assert_eq!(mask.count::<true>(), mask.n_cells());
939
+ }
940
+ }