use std::fmt::{self, Debug, Formatter};
use super::hyperbolic_geometry::HyperbolicPoint;
use crate::constants;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct GeometricSignature {
hash: String,
level: u32,
position_signature: Vec<i32>,
}
impl GeometricSignature {
pub fn new(hash: String, level: u32, position_signature: Vec<i32>) -> Self {
Self {
hash,
level,
position_signature,
}
}
pub fn hash(&self) -> &str {
&self.hash
}
pub fn level(&self) -> u32 {
self.level
}
pub fn position_signature(&self) -> &[i32] {
&self.position_signature
}
pub fn stub(unique_id: &str) -> Self {
Self {
hash: unique_id.to_string(),
level: 0,
position_signature: Vec::new(),
}
}
pub fn is_stub(&self) -> bool {
self.position_signature.is_empty()
}
pub fn unique_id(&self) -> String {
if self.position_signature.is_empty() {
return self.hash.clone();
}
use sha3::{Sha3_256, Digest as _};
let mut hasher = Sha3_256::new();
hasher.update(self.level.to_le_bytes());
for &v in &self.position_signature {
hasher.update(v.to_le_bytes());
}
hex::encode(&hasher.finalize()[..16])
}
}
impl Debug for GeometricSignature {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "GeometricSignature(hash={}, level={})",
&self.hash[0..8], self.level)
}
}
impl GeometricSignature {
pub fn embedded(point: &HyperbolicPoint, dimension: usize, level: u32) -> Self {
let position_signature: Vec<i32> = (0..dimension)
.map(|i| constants::quantize_position(point.coords()[i]))
.collect();
let mut signature = Self {
hash: String::new(),
level,
position_signature,
};
signature.hash = signature.unique_id();
signature
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hyperbolic_geometry::PoincareDisk;
#[test]
fn embedded_signature_carries_level_and_position() {
let disk = PoincareDisk::new(2);
let point = disk.point_from_f32_slice(&[0.5, 0.0]);
let signature = GeometricSignature::embedded(&point, 2, 3);
assert_eq!(signature.level(), 3);
assert_eq!(signature.position_signature().len(), 2);
assert!(!signature.is_stub());
assert_eq!(signature.hash(), signature.unique_id());
}
#[test]
fn distinct_positions_get_distinct_ids() {
let disk = PoincareDisk::new(2);
let a = GeometricSignature::embedded(&disk.point_from_f32_slice(&[0.5, 0.0]), 2, 1);
let b = GeometricSignature::embedded(&disk.point_from_f32_slice(&[0.0, 0.5]), 2, 1);
assert_ne!(a.unique_id(), b.unique_id());
}
#[test]
fn same_position_at_different_levels_gets_distinct_ids() {
let disk = PoincareDisk::new(2);
let point = disk.point_from_f32_slice(&[0.25, 0.25]);
let a = GeometricSignature::embedded(&point, 2, 1);
let b = GeometricSignature::embedded(&point, 2, 2);
assert_ne!(a.unique_id(), b.unique_id());
}
#[test]
fn stub_signature_is_its_own_id() {
let stub = GeometricSignature::stub("data-only-node");
assert!(stub.is_stub());
assert_eq!(stub.unique_id(), "data-only-node");
}
}