use std::hash::{Hash, Hasher};
use num::BigUint;
use super::transform::fast_hilbert;
use super::permutation::Permutation;
#[derive(Debug, Clone)]
pub struct Point {
id : usize,
coordinates : Vec<u32>,
square_magnitude : u64
}
impl Point {
pub fn new(id : usize, coords : &[u32]) -> Point {
Point {
id,
coordinates : coords.iter().map(|c| c.clone()).collect(),
square_magnitude : coords.iter().fold(0_u64, |sum,coord| sum + (coord.clone() as u64) * (coord.clone() as u64))
}
}
pub fn permute(&self, permutation : &Permutation) -> Self {
Point::new(self.id, &permutation.apply(&self.coordinates))
}
pub fn dimensions(&self) -> usize {
self.coordinates.len()
}
pub fn get_id(&self) -> usize { self.id }
pub fn get_coordinates(&self) -> &Vec<u32> { &self.coordinates }
pub fn square_distance(&self, other_point : &Point) -> u64 {
let mut dot_product = 0_u64;
let full_loops = self.dimensions() / 8;
for batch in 0..full_loops {
let offset = batch * 8;
dot_product += self.coordinates[offset] as u64 * other_point.coordinates[offset] as u64;
dot_product += self.coordinates[offset + 1] as u64 * other_point.coordinates[offset + 1] as u64;
dot_product += self.coordinates[offset + 2] as u64 * other_point.coordinates[offset + 2] as u64;
dot_product += self.coordinates[offset + 3] as u64 * other_point.coordinates[offset + 3] as u64;
dot_product += self.coordinates[offset + 4] as u64 * other_point.coordinates[offset + 4] as u64;
dot_product += self.coordinates[offset + 5] as u64 * other_point.coordinates[offset + 5] as u64;
dot_product += self.coordinates[offset + 6] as u64 * other_point.coordinates[offset + 6] as u64;
dot_product += self.coordinates[offset + 7] as u64 * other_point.coordinates[offset + 7] as u64;
}
for dim in (full_loops * 8)..self.dimensions() {
dot_product += self.coordinates[dim] as u64 * other_point.coordinates[dim] as u64;
}
self.square_magnitude + other_point.square_magnitude - 2 * dot_product
}
pub fn square_distance_loop_unrolling(&self, other_point : &Point) -> u64 {
let mut dot_product = 0_u64;
let full_loops = self.dimensions() / 8;
for batch in 0..full_loops {
let offset = batch * 8;
dot_product += self.coordinates[offset] as u64 * other_point.coordinates[offset] as u64;
dot_product += self.coordinates[offset + 1] as u64 * other_point.coordinates[offset + 1] as u64;
dot_product += self.coordinates[offset + 2] as u64 * other_point.coordinates[offset + 2] as u64;
dot_product += self.coordinates[offset + 3] as u64 * other_point.coordinates[offset + 3] as u64;
dot_product += self.coordinates[offset + 4] as u64 * other_point.coordinates[offset + 4] as u64;
dot_product += self.coordinates[offset + 5] as u64 * other_point.coordinates[offset + 5] as u64;
dot_product += self.coordinates[offset + 6] as u64 * other_point.coordinates[offset + 6] as u64;
dot_product += self.coordinates[offset + 7] as u64 * other_point.coordinates[offset + 7] as u64;
}
for dim in (full_loops * 8)..self.dimensions() {
dot_product += self.coordinates[dim] as u64 * other_point.coordinates[dim] as u64;
}
self.square_magnitude + other_point.square_magnitude - 2 * dot_product
}
pub fn square_distance_no_loop_unrolling(&self, other_point : &Point) -> u64 {
let mut dot_product = 0_u64;
for dim in 0..self.dimensions() {
dot_product += self.coordinates[dim] as u64 * other_point.coordinates[dim] as u64;
}
self.square_magnitude + other_point.square_magnitude - 2 * dot_product
}
pub fn are_coordinates_identical(&self, other_point : &Point) -> bool {
self.square_magnitude == other_point.square_magnitude && self.coordinates == other_point.coordinates
}
pub fn hilbert_transform(&self, bits_per_dimension : usize) -> BigUint {
fast_hilbert::hilbert_index(&self.coordinates, bits_per_dimension, None)
}
fn inverse_hilbert_transform(id: usize, hilbert_index: &BigUint, bits_per_dimension : usize, dimensions : usize) -> Self {
let coordinates = fast_hilbert::hilbert_axes(hilbert_index, bits_per_dimension, dimensions);
Self::new(id, &coordinates)
}
pub fn new_from_hilbert_index(id: usize, hilbert_index: &BigUint, bits_per_dimension : usize, dimensions : usize) -> Self {
Self::inverse_hilbert_transform(id, hilbert_index, bits_per_dimension, dimensions)
}
pub fn hilbert_sort(points : &mut Vec<Point>, bits_per_dimension : usize) {
points.sort_by_cached_key(|point| point.hilbert_transform(bits_per_dimension));
}
pub fn hilbert_sort_permuted(points : &mut Vec<Point>, bits_per_dimension : usize, permutation : &Permutation) {
points.sort_by_cached_key(|point| point.permute(permutation).hilbert_transform(bits_per_dimension));
}
}
impl Hash for Point {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
self.square_magnitude.hash(state);
}
}
impl PartialEq for Point {
fn eq(&self, other: &Self) -> bool { self.id == other.id && self.square_magnitude == other.square_magnitude }
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use spectral::prelude::*;
extern crate rand;
use rand::thread_rng;
use rand::seq::SliceRandom;
use super::Point;
use crate::point_list;
#[test]
fn square_distance() {
let p1 = Point::new(0, &[3, 4, 5]);
let p2 = Point::new(0, &[0, 8, 10]);
asserting!("Square distance between points").that(&p1.square_distance(&p2)).is_equal_to(50);
}
#[test]
fn hilbert_sort() {
let expected_points = hilbert_ordered_2_dimensions_3_bits();
let mut actual_points = expected_points.clone();
let mut rng = thread_rng();
actual_points.shuffle(&mut rng);
asserting("Points not sorted by Hilbert Index").that(&actual_points).is_not_equal_to(expected_points.clone());
Point::hilbert_sort(&mut actual_points, 3);
asserting("Points sorted by Hilbert Index").that(&actual_points).is_equal_to(expected_points);
}
#[test]
fn point_to_hilbert_round_trip() {
let p1 = Point::new(0, &[3, 4, 5]);
let index = p1.hilbert_transform(5);
let p2 = Point::new_from_hilbert_index(0, &index, 5, 3);
asserting("Point to Hilbert round trip").that(&p1).is_equal_to(p2);
}
fn hilbert_ordered_2_dimensions_3_bits() -> Vec<Point> {
let point_data : Vec<Vec<i32>> = vec![
vec![0,0], vec![0,1], vec![1,1], vec![1,0], vec![2,0], vec![3,0],
vec![3,1], vec![2,1], vec![2,2], vec![3,2], vec![3,3], vec![2,3],
vec![1,3], vec![1,2], vec![0,2], vec![0,3], vec![0,4], vec![1,4],
vec![1,5], vec![0,5], vec![0,6], vec![0,7], vec![1,7], vec![1,6],
vec![2,6], vec![2,7], vec![3,7], vec![3,6], vec![3,5], vec![2,5],
vec![2,4], vec![3,4], vec![4,4], vec![5,4], vec![5,5], vec![4,5],
vec![4,6], vec![4,7], vec![5,7], vec![5,6], vec![6,6], vec![6,7],
vec![7,7], vec![7,6], vec![7,5], vec![6,5], vec![6,4], vec![7,4],
vec![7,3], vec![7,2], vec![6,2], vec![6,3], vec![5,3], vec![4,3],
vec![4,2], vec![5,2], vec![5,1], vec![4,1], vec![4,0], vec![5,0],
vec![6,0], vec![6,1], vec![7,1], vec![7,0]
];
point_list::make_points_i32(&point_data, 0, None, None).0
}
}