use crate::grid::Grid;
use crate::hash::{IntMap, IntSet};
use std::mem::MaybeUninit;
use std::ops::{Add, Sub};
use std::sync::Arc;
use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering};
#[derive(Clone, Copy, Debug)]
pub struct EncodedImage(u16);
impl EncodedImage {
const BIAS: i8 = 8;
const BITS: usize = 4;
const MASK: u16 = 2u16.pow(Self::BITS as u32) - 1; const ZERO: u16 = ((Self::BIAS as u16) & Self::MASK)
| (((Self::BIAS as u16) & Self::MASK) << Self::BITS)
| (((Self::BIAS as u16) & Self::MASK) << (2 * Self::BITS));
pub fn new(image: [i8; 3]) -> Self {
let mut encoded: u16 = 0;
image.iter().enumerate().for_each(|(i, img)| {
let biased = (img + Self::BIAS) as u16;
debug_assert!(biased <= Self::MASK, "Image out of encoding range");
encoded |= (biased & Self::MASK) << (i * Self::BITS);
});
Self(encoded)
}
pub fn decode(self) -> [i8; 3] {
let mut image = [0; 3];
image.iter_mut().enumerate().for_each(|(i, img)| {
let biased = (self.0 >> (i * Self::BITS)) & Self::MASK;
*img = (biased as i8) - Self::BIAS;
});
image
}
pub fn image_add(self, b: [i8; 3]) -> Self {
let a = self.decode();
Self::new([a[0] + b[0], a[1] + b[1], a[2] + b[2]])
}
pub fn is_zero(&self) -> bool {
self.0 == Self::ZERO
}
}
impl Add for EncodedImage {
type Output = Self;
fn add(self, rhs: Self) -> Self {
let a = self.decode();
let b = rhs.decode();
Self::new([a[0] + b[0], a[1] + b[1], a[2] + b[2]])
}
}
impl Sub for EncodedImage {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
let a = self.decode();
let b = rhs.decode();
Self::new([a[0] - b[0], a[1] - b[1], a[2] - b[2]])
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(transparent)]
pub struct EncodedAtom(pub u32);
impl EncodedAtom {
const SHIFT: usize = EncodedImage::BITS * 3;
const MASK: u32 = 2u32.pow(Self::SHIFT as u32) - 1;
const BITS: u32 = 20;
const MAX_ATOM: u32 = 2u32.pow(Self::BITS) - 1;
pub fn new(atom: u32, image: EncodedImage) -> Self {
debug_assert!(
atom < Self::MAX_ATOM,
"Atom Number out of EncodedAtom Range"
);
Self((atom << Self::SHIFT) | (image.0) as u32)
}
pub fn new_zero_image(atom: u32) -> Self {
Self((atom << Self::SHIFT) | (EncodedImage::ZERO) as u32)
}
pub fn atom_index(&self) -> u32 {
self.0 >> Self::SHIFT
}
pub fn image(&self) -> EncodedImage {
EncodedImage((self.0 & Self::MASK) as u16)
}
pub fn image_add(self, image: EncodedImage) -> Self {
Self::new(self.atom_index(), self.image() + image)
}
pub fn image_sub(self, image: EncodedImage) -> Self {
Self::new(self.atom_index(), self.image() - image)
}
pub fn decode_partial(self) -> (u32, EncodedImage) {
(self.atom_index(), self.image())
}
pub fn decode_full(self) -> (u32, [i8; 3]) {
(self.atom_index(), self.image().decode())
}
}
#[derive(Clone, Copy, Debug)]
pub struct EncodedWeight(u64);
impl EncodedWeight {
pub fn new(encoded_atom: EncodedAtom, weight: f32) -> Self {
Self((encoded_atom.0 as u64) | ((weight.to_bits() as u64) << 32))
}
pub fn decode(self) -> (EncodedAtom, f32) {
(
EncodedAtom(self.0 as u32),
f32::from_bits((self.0 >> 32) as u32),
)
}
}
pub enum Voxel {
Maxima(EncodedAtom),
Boundary(IntMap<EncodedAtom, f32>),
Vacuum,
}
pub struct BlockingVoxelMap {
weight_map: Arc<[MaybeUninit<Box<[EncodedWeight]>>]>,
voxel_map: Arc<[AtomicIsize]>,
pub grid: Grid,
weight_counter: AtomicUsize,
}
impl BlockingVoxelMap {
pub fn new(
grid: [usize; 3],
lattice: [[f64; 3]; 3],
voxel_origin: [f64; 3],
) -> Self {
let grid = Grid::new(grid, lattice, voxel_origin);
let size = grid.size.total;
let mut weight_map = Vec::with_capacity(size);
weight_map.resize_with(size, MaybeUninit::uninit);
let weight_map = Arc::from(weight_map.into_boxed_slice());
let mut voxel_map = Vec::with_capacity(size);
voxel_map.resize_with(size, || AtomicIsize::new(-1));
let voxel_map = Arc::from(voxel_map.into_boxed_slice());
let weight_counter = AtomicUsize::new(0);
Self {
weight_map,
voxel_map,
grid,
weight_counter,
}
}
pub fn weight_get(&self, i: isize) -> IntMap<EncodedAtom, f32> {
let i = -2 - i;
(unsafe { self.weight_map.get_unchecked(i as usize).assume_init_ref() })
.iter()
.map(|u| u.decode())
.collect()
}
pub fn maxima_get(&self, p: isize) -> isize {
loop {
match self.voxel_map[p as usize].load(Ordering::Acquire) {
-1 => std::thread::yield_now(),
x => break x,
}
}
}
pub fn voxel_get(&self, p: isize) -> Voxel {
let i = self.maxima_get(p);
match i.cmp(&-1) {
std::cmp::Ordering::Less => Voxel::Boundary(self.weight_get(i)),
std::cmp::Ordering::Equal => Voxel::Vacuum,
std::cmp::Ordering::Greater => Voxel::Maxima(EncodedAtom(i as u32)),
}
}
pub fn maxima_check(&self, p: isize) -> Option<isize> {
match self.voxel_map[p as usize].load(Ordering::Relaxed) {
-1 => None,
x => Some(x),
}
}
pub fn maxima_store(&self, p: isize, maxima: isize) {
self.voxel_map[p as usize].store(maxima, Ordering::Release);
}
pub fn weight_store(&self, p: isize, weights: Box<[EncodedWeight]>) {
let i = self.weight_counter.fetch_add(1, Ordering::Relaxed);
unsafe {
let ptr: *mut Box<[EncodedWeight]> =
self.weight_map.get_unchecked(i) as *const _ as *mut _;
ptr.write(weights)
}
self.maxima_store(p, -2 - (i as isize));
}
pub fn into_inner(self) -> (Vec<isize>, Vec<Box<[EncodedWeight]>>, Grid) {
(
self.voxel_map
.iter()
.map(|x| x.load(Ordering::Relaxed))
.collect(),
self.weight_map
.iter()
.take(self.weight_counter.into_inner())
.map(|mu| unsafe { mu.assume_init_read() })
.collect(),
self.grid,
)
}
}
pub struct VoxelMap {
pub voxel_map: Vec<isize>,
pub weight_map: Vec<Box<[EncodedWeight]>>,
pub grid: Grid,
}
impl VoxelMap {
pub fn new(
voxel_map: Vec<isize>,
weight_map: Vec<Box<[EncodedWeight]>>,
grid: Grid,
) -> Self {
Self {
voxel_map,
weight_map,
grid,
}
}
pub fn from_blocking_voxel_map(voxel_map: BlockingVoxelMap) -> Self {
let (voxel_map, weight_map, grid) = voxel_map.into_inner();
Self::new(voxel_map, weight_map, grid)
}
pub fn weight_iter(&self) -> std::slice::Iter<'_, Box<[EncodedWeight]>> {
self.weight_map.iter()
}
pub fn weight_len(&self) -> usize {
self.weight_map.len()
}
pub fn grid_get(&self) -> &Grid {
&self.grid
}
pub fn maxima_to_atom(&self, maxima: usize) -> usize {
maxima
}
pub fn maxima_to_voxel(&self, maxima: isize) -> Voxel {
match maxima.cmp(&-1) {
std::cmp::Ordering::Equal => Voxel::Vacuum,
std::cmp::Ordering::Greater => {
Voxel::Maxima(EncodedAtom(maxima as u32))
}
std::cmp::Ordering::Less => {
Voxel::Boundary(self.maxima_to_weight(maxima))
}
}
}
pub fn maxima_to_weight(&self, maxima: isize) -> IntMap<EncodedAtom, f32> {
self.weight_map[(-2 - maxima) as usize]
.iter()
.map(|ed| ed.decode())
.collect()
}
pub fn maxima_iter(&self) -> std::slice::Iter<'_, isize> {
self.voxel_map.iter()
}
pub fn maxima_len(&self) -> usize {
self.voxel_map.len()
}
pub fn maxima_chunks(
&self,
chunk_size: usize,
) -> std::slice::Chunks<'_, isize> {
self.voxel_map.chunks(chunk_size)
}
pub fn voxel_get(&self, p: isize) -> Voxel {
self.maxima_to_voxel(self.maxima_get(p))
}
pub fn maxima_get(&self, p: isize) -> isize {
self.voxel_map[p as usize]
}
pub fn volume_map(&self, volume_number: isize) -> Vec<Option<f64>> {
self.maxima_iter()
.map(|maxima| {
if *maxima == volume_number {
Some(1.0)
} else if *maxima < -1 {
let mut w = None;
for (m, weight) in
self.maxima_to_weight(*maxima).into_iter()
{
if (m.atom_index() as isize) == volume_number {
w = Some(weight as f64);
break;
}
}
w
} else {
None
}
})
.collect()
}
pub fn multi_volume_map(
&self,
volume_numbers: &IntSet<isize>,
) -> Vec<Option<f64>> {
self.maxima_iter()
.map(|maxima| {
if volume_numbers.contains(maxima) {
Some(1.0)
} else if *maxima < -1 {
let mut w = 0.0;
for (m, weight) in
self.maxima_to_weight(*maxima).into_iter()
{
if volume_numbers.contains(&(m.atom_index() as isize)) {
w += weight as f64;
}
}
Some(w)
} else {
None
}
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::IntSet;
#[test]
fn test_encoded_image_packing() {
let zero = EncodedImage::new([0, 0, 0]);
assert_eq!(zero.decode(), [0, 0, 0]);
assert!(zero.is_zero());
let max = EncodedImage::new([7, 7, 7]);
assert_eq!(max.decode(), [7, 7, 7]);
let min = EncodedImage::new([-8, -8, -8]);
assert_eq!(min.decode(), [-8, -8, -8]);
}
#[test]
fn test_encoded_image_arithmetic() {
let a = EncodedImage::new([1, 2, 3]);
let b = EncodedImage::new([-1, 0, 1]);
let sum = a + b;
assert_eq!(sum.decode(), [0, 2, 4]);
let sub = a - b;
assert_eq!(sub.decode(), [2, 2, 2]);
let helper = a.image_add([-1, -2, -3]);
assert!(helper.is_zero());
}
#[test]
#[should_panic(expected = "Image out of encoding range")]
fn test_encoded_image_out_of_bounds() {
EncodedImage::new([8, 0, 0]); }
#[test]
fn test_encoded_atom_round_trip() {
let atom_id = 12345;
let offset = EncodedImage::new([1, -1, 0]);
let encoded = EncodedAtom::new(atom_id, offset);
assert_eq!(encoded.atom_index(), atom_id);
assert_eq!(encoded.image().decode(), offset.decode());
let (id, img) = encoded.decode_full();
assert_eq!(id, atom_id);
assert_eq!(img, [1, -1, 0]);
}
#[test]
fn test_encoded_atom_zero_round_trip() {
let atom_id = 12345;
let encoded = EncodedAtom::new_zero_image(atom_id);
let (id, img) = encoded.decode_full();
assert_eq!(id, atom_id);
assert_eq!(img, [0, 0, 0]);
}
#[test]
fn test_encoded_atom_operations() {
let start = EncodedAtom::new(1, EncodedImage::new([0, 0, 0]));
let shift = EncodedImage::new([1, 1, 1]);
let moved = start.image_add(shift);
assert_eq!(moved.image().decode(), [1, 1, 1]);
assert_eq!(moved.atom_index(), 1);
let back = moved.image_sub(shift);
assert_eq!(back.image().decode(), [0, 0, 0]);
}
#[test]
fn test_voxel_map_full_flow() {
let grid_dims = [4, 4, 4];
let lattice = [[2.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 2.0]];
let voxel_origin = [0.0, 0.0, 0.0];
let b_map = BlockingVoxelMap::new(grid_dims, lattice, voxel_origin);
let atom1 = EncodedAtom::new(0, EncodedImage::new([0, 0, 0]));
let atom2 = EncodedAtom::new(1, EncodedImage::new([0, 0, 0]));
let w1 = EncodedWeight::new(atom1, 0.5);
let w2 = EncodedWeight::new(atom2, 0.5);
let weights = vec![w1, w2].into_boxed_slice();
b_map.maxima_store(0, atom1.0 as isize);
b_map.weight_store(1, weights);
let v_map = VoxelMap::from_blocking_voxel_map(b_map);
match v_map.voxel_get(0) {
Voxel::Maxima(a) => assert_eq!(a.atom_index(), 0),
_ => panic!("Voxel 0 should be Maxima"),
}
match v_map.voxel_get(1) {
Voxel::Boundary(weights) => {
assert!(
(weights.get(&atom1).unwrap() - 0.5).abs() < f32::EPSILON
);
assert!(
(weights.get(&atom2).unwrap() - 0.5).abs() < f32::EPSILON
);
}
_ => panic!("Voxel 1 should be Boundary"),
}
match v_map.voxel_get(2) {
Voxel::Vacuum => (),
_ => panic!("Voxel 2 should be Vacuum"),
}
}
#[test]
fn test_volume_map_generation() {
let grid = Grid::new(
[4, 4, 4],
[[2.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 2.0]],
[0.0; 3],
);
let atom1 = EncodedAtom::new(1, EncodedImage::new([0, 0, 0]));
let atom2 = EncodedAtom::new(2, EncodedImage::new([0, 0, 0]));
let w_box = vec![
EncodedWeight::new(atom1, 0.25),
EncodedWeight::new(atom2, 0.75),
]
.into_boxed_slice();
let voxel_data = vec![1, -2, -1];
let weight_data = vec![w_box];
let map = VoxelMap::new(voxel_data, weight_data, grid);
let volumes = map.volume_map(1);
assert_eq!(volumes[0], Some(1.0)); assert_eq!(volumes[1], Some(0.25)); assert_eq!(volumes[2], None);
let mut set = IntSet::default();
set.insert(1);
set.insert(2);
let multi_vol = map.multi_volume_map(&set);
assert_eq!(multi_vol[0], Some(1.0)); assert_eq!(multi_vol[1], Some(1.0)); assert_eq!(multi_vol[2], None);
}
}