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}