bader/voxel_map.rs
1use crate::grid::Grid;
2use rustc_hash::FxHashSet;
3use std::cell::UnsafeCell;
4use std::ops::{Deref, DerefMut};
5use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
6
7/// Describes the state of the voxel.
8pub enum Voxel<'a> {
9 /// Contians the position of the voxel's maxima.
10 Maxima(usize),
11 /// Contians a vector of the maxima the current voxel contributes to and
12 /// their weights.
13 Boundary(&'a [f64]),
14 /// A voxel beneath the vacuum tolerance and not contributing to any maxima.
15 Vacuum,
16}
17
18/// A lock guard for write access to [`VoxelMap.weight_map`].
19pub struct Lock<'a> {
20 data: &'a BlockingVoxelMap,
21}
22
23unsafe impl<'a> Sync for Lock<'a> {}
24
25/// Deref only exposes the weight_map field of a [`VoxelMap`].
26impl<'a> Deref for Lock<'a> {
27 type Target = Vec<Box<[f64]>>;
28 fn deref(&self) -> &Vec<Box<[f64]>> {
29 unsafe { &*self.data.weight_map.get() }
30 }
31}
32
33/// DerefMut only exposes the weight_map field of a [`VoxelMap`].
34impl<'a> DerefMut for Lock<'a> {
35 fn deref_mut(&mut self) -> &mut Vec<Box<[f64]>> {
36 unsafe { &mut *self.data.weight_map.get() }
37 }
38}
39
40/// Make sure to free the lock when the struct is dropped.
41impl<'a> Drop for Lock<'a> {
42 fn drop(&mut self) {
43 self.data.lock.store(false, Ordering::SeqCst);
44 }
45}
46
47/// A structure for building and processing the map between voxel and maxima.
48/// Bader maxima are stored in the voxel_map whilst the contributing weights are
49/// stored in the weight_map. The weight_map is only written to once by each
50/// point and so once a value has been written it is safe to read by any thread.
51/// To check it has been written to `weight_get` monitors the state of corresponding
52/// voxel_map value. Writing to the map is acheived by acquiring the lock, noting
53/// the length of the weight_map, pushing the weight vector for voxel p to the
54/// weight_map, droping the write lock and then storing the index of the inserted
55/// vector using `weight_store`.
56///
57/// # Examples
58/// ```
59/// use bader::voxel_map::BlockingVoxelMap;
60///
61/// for p in 0..1isize {
62/// let voxel_map = BlockingVoxelMap::new(
63/// [2, 5, 2],
64/// [[2.0, 0.0, 0.0], [0.0, 5.0, 0.0], [0.0, 0.0, 2.0]],
65/// [0.0, 0.0, 0.0],
66/// );
67/// let i = {
68/// let mut weight = voxel_map.lock();
69/// (*weight).push(Vec::with_capacity(0).into());
70/// weight.len() - 1
71/// };
72/// voxel_map.weight_store(p, i)
73/// }
74/// ```
75pub struct BlockingVoxelMap {
76 weight_map: UnsafeCell<Vec<Box<[f64]>>>,
77 voxel_map: Vec<AtomicIsize>,
78 pub grid: Grid,
79 lock: AtomicBool,
80}
81
82unsafe impl Sync for BlockingVoxelMap {}
83
84impl BlockingVoxelMap {
85 /// Initialises a [`BlockingVoxelMap`] and the [`Grid`] that will faciliate movemoment around the
86 /// map.
87 pub fn new(
88 grid: [usize; 3],
89 lattice: [[f64; 3]; 3],
90 voxel_origin: [f64; 3],
91 ) -> Self {
92 let grid = Grid::new(grid, lattice, voxel_origin);
93 let size = grid.size.total;
94 // For mapping the the voxels
95 let weight_map =
96 UnsafeCell::new(Vec::<Box<[f64]>>::with_capacity(size));
97 let mut voxel_map = Vec::with_capacity(size);
98 voxel_map.resize_with(size, || AtomicIsize::new(-1));
99 let lock = AtomicBool::new(false);
100 // For post processing
101 Self {
102 weight_map,
103 voxel_map,
104 grid,
105 lock,
106 }
107 }
108
109 /// Retrieves the state of the voxel, p. This will lock until p has been stored
110 /// in the VoxelMap and then return either a `Voxel::Maxima` or `Voxel::Weight`.
111 /// Calling this on a voxel, p, that is below the vacuum_tolerance will deadlock
112 /// as a voxel is considered stored once voxel_map\[p\] > -1.
113 pub fn weight_get(&self, i: isize) -> &[f64] {
114 let i = -2 - i;
115 &(unsafe { &*self.weight_map.get() })[i as usize]
116 }
117
118 /// Atomic loading of voxel, p, from voxel_map blocks if maxima == -1
119 pub fn maxima_get(&self, p: isize) -> isize {
120 loop {
121 match self.voxel_map[p as usize].load(Ordering::Relaxed) {
122 -1 => (),
123 x => break x,
124 }
125 }
126 }
127
128 /// Check if a maxima is stored
129 pub fn maxima_check(&self, p: isize) -> Option<isize> {
130 match self.voxel_map[p as usize].load(Ordering::Relaxed) {
131 -1 => None,
132 x => Some(x),
133 }
134 }
135
136 /// Stores the maxima of voxel, p, in the voxel_map.
137 pub fn maxima_store(&self, p: isize, maxima: isize) {
138 self.voxel_map[p as usize].store(maxima, Ordering::Relaxed);
139 }
140
141 /// Stores the index of p's weight contributions in weight_map into the
142 /// weight_index.
143 pub fn weight_store(&self, p: isize, i: usize) {
144 self.maxima_store(p, -2 - (i as isize));
145 }
146
147 /// Locks the structure for write access unlock occurs when the returned
148 /// Lock is dropped.
149 pub fn lock(&self) -> Lock {
150 while self.lock.swap(true, Ordering::SeqCst) {}
151 Lock { data: self }
152 }
153
154 /// Extract the voxel map data.
155 pub fn into_inner(self) -> (Vec<isize>, Vec<Box<[f64]>>, Grid) {
156 (
157 self.voxel_map.into_iter().map(|x| x.into_inner()).collect(),
158 self.weight_map.into_inner(),
159 self.grid,
160 )
161 }
162}
163
164/// A VoxelMap for if the maxima stored are atomic indices.
165pub struct VoxelMap {
166 /// The vector mapping the voxel to a maxima.
167 pub voxel_map: Vec<isize>,
168 /// The vector containing the weights for boundary voxels.
169 pub weight_map: Vec<Box<[f64]>>,
170 /// The Grid used to navigate the VoxelMap.
171 pub grid: Grid,
172}
173
174impl VoxelMap {
175 /// Create a new [`VoxelMap`]
176 pub fn new(
177 voxel_map: Vec<isize>,
178 weight_map: Vec<Box<[f64]>>,
179 grid: Grid,
180 ) -> Self {
181 Self {
182 voxel_map,
183 weight_map,
184 grid,
185 }
186 }
187
188 /// Create a new [`VoxelMap`] from a [`BlockingVoxelMap`].
189 pub fn from_blocking_voxel_map(voxel_map: BlockingVoxelMap) -> Self {
190 let (voxel_map, weight_map, grid) = voxel_map.into_inner();
191 Self::new(voxel_map, weight_map, grid)
192 }
193
194 /// Produce an Iter over the boundary voxels.
195 pub fn weight_iter(&self) -> std::slice::Iter<'_, Box<[f64]>> {
196 self.weight_map.iter()
197 }
198
199 /// Get the length of the weight_map.
200 pub fn weight_len(&self) -> usize {
201 self.weight_map.len()
202 }
203
204 /// Get a refernce to the grid used by the VoxelMap.
205 pub fn grid_get(&self) -> &Grid {
206 &self.grid
207 }
208
209 /// Returns the atom associated with the point.
210 pub fn maxima_to_atom(&self, maxima: usize) -> usize {
211 maxima
212 }
213
214 /// Retrieval of the state of the voxel, p.
215 pub fn maxima_to_voxel(&self, maxima: isize) -> Voxel {
216 match maxima.cmp(&-1) {
217 std::cmp::Ordering::Equal => Voxel::Vacuum,
218 std::cmp::Ordering::Greater => Voxel::Maxima(maxima as usize),
219 std::cmp::Ordering::Less => {
220 Voxel::Boundary(self.maxima_to_weight(maxima))
221 }
222 }
223 }
224
225 /// Return a reference to the weights from the given maxima, Note: maxima here must be < -1.
226 pub fn maxima_to_weight(&self, maxima: isize) -> &[f64] {
227 &self.weight_map[(-2 - maxima) as usize]
228 }
229
230 /// Return an Iter over the maxima stored in the VoxelMap.
231 pub fn maxima_iter(&self) -> std::slice::Iter<'_, isize> {
232 self.voxel_map.iter()
233 }
234
235 /// Get the length of the voxel_map.
236 pub fn maxima_len(&self) -> usize {
237 self.voxel_map.len()
238 }
239
240 /// Return a Chunk over the maxima stored in the VoxelMap.
241 pub fn maxima_chunks(
242 &self,
243 chunk_size: usize,
244 ) -> std::slice::Chunks<'_, isize> {
245 self.voxel_map.chunks(chunk_size)
246 }
247
248 /// Retrieval of the state of the voxel, p.
249 pub fn voxel_get(&self, p: isize) -> Voxel {
250 self.maxima_to_voxel(self.maxima_get(p))
251 }
252
253 /// Return the stored maxima at point p.
254 pub fn maxima_get(&self, p: isize) -> isize {
255 self.voxel_map[p as usize]
256 }
257
258 /// Produce a mask for a specific volume number.
259 pub fn volume_map(&self, volume_number: isize) -> Vec<Option<f64>> {
260 self.maxima_iter()
261 .map(|maxima| {
262 if *maxima == volume_number {
263 Some(1.0)
264 } else if *maxima < -1 {
265 let mut w = None;
266 for weight in self.maxima_to_weight(*maxima).iter() {
267 let m = *weight as isize;
268 if m == volume_number {
269 w = Some(weight - m as f64);
270 break;
271 }
272 }
273 w
274 } else {
275 None
276 }
277 })
278 .collect()
279 }
280 /// Produce a mask for a collection volume numbers.
281 pub fn multi_volume_map(
282 &self,
283 volume_numbers: &FxHashSet<isize>,
284 ) -> Vec<Option<f64>> {
285 self.maxima_iter()
286 .map(|maxima| {
287 if volume_numbers.contains(maxima) {
288 Some(1.0)
289 } else if *maxima < -1 {
290 let mut w = 0.0;
291 for weight in self.maxima_to_weight(*maxima).iter() {
292 let m = *weight as isize;
293 if volume_numbers.contains(&m) {
294 w += weight - m as f64;
295 }
296 }
297 Some(w)
298 } else {
299 None
300 }
301 })
302 .collect()
303 }
304}