use crate::compile::fusion::{DecomposedGraph, DecomposedWire, FusedNode};
use crate::derive_support::Const;
use xxhash_rust::xxh3::xxh3_64;
#[inline(always)]
pub fn splitmix64_u64(mut x: u64) -> u64 {
x = x.wrapping_add(0x9e3779b97f4a7c15);
x = (x ^ (x >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
x = (x ^ (x >> 27)).wrapping_mul(0x94d049bb133111eb);
x ^ (x >> 31)
}
#[crate::polydat_node(category = Hashing)]
fn hash(input: u64) -> u64 {
splitmix64_u64(input)
}
#[crate::polydat_node(category = Hashing)]
fn splitmix64(input: u64) -> u64 {
splitmix64_u64(input)
}
#[crate::polydat_node(category = Hashing)]
fn scatter(input: u64) -> u64 {
splitmix64_u64(input)
}
#[crate::polydat_node(category = Hashing)]
fn xxhash3(input: u64) -> u64 {
xxh3_64(&input.to_le_bytes())
}
#[crate::polydat_node(category = Hashing)]
fn xxh3(input: u64) -> u64 {
xxh3_64(&input.to_le_bytes())
}
#[crate::polydat_node(category = Hashing)]
fn hash_range(input: u64, max: Const<u64>) -> u64 {
if *max == 0 {
0
} else {
splitmix64_u64(input) % *max
}
}
impl FusedNode for HashRange {
fn decomposed(&self) -> DecomposedGraph {
use crate::library::arithmetic::Mod;
let mut g = DecomposedGraph::new(1);
let h = g.add_node(Box::new(Hash::new()), vec![DecomposedWire::Input(0)]);
let m = g.add_node(Box::new(Mod::new(self.max)), vec![DecomposedWire::Node(h, 0)]);
g.set_outputs(vec![DecomposedWire::Node(m, 0)]);
g
}
}
#[crate::polydat_node(category = Hashing)]
fn hash_interval(input: u64, min: Const<f64>, max: Const<f64>) -> f64 {
let h = splitmix64_u64(input);
let unit = (h as f64) / (u64::MAX as f64);
*min + unit * (*max - *min)
}
impl FusedNode for HashInterval {
fn decomposed(&self) -> DecomposedGraph {
use crate::library::lerp::Lerp;
use crate::library::sampling::icd::UnitInterval;
let mut g = DecomposedGraph::new(1);
let h = g.add_node(Box::new(Hash::new()), vec![DecomposedWire::Input(0)]);
let ui = g.add_node(Box::new(UnitInterval::new()), vec![DecomposedWire::Node(h, 0)]);
let lerp = g.add_node(
Box::new(Lerp::new(self.min, self.max)),
vec![DecomposedWire::Node(ui, 0)],
);
g.set_outputs(vec![DecomposedWire::Node(lerp, 0)]);
g
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{PolydatNode, Value};
#[test]
fn hash_deterministic() {
let node = Hash::new();
let mut out = [Value::None];
node.eval(&[Value::U64(42)], &mut out);
let first = out[0].as_u64();
node.eval(&[Value::U64(42)], &mut out);
assert_eq!(first, out[0].as_u64(), "same input must produce same output");
}
#[test]
fn hash_different_inputs_differ() {
let node = Hash::new();
let mut out1 = [Value::None];
let mut out2 = [Value::None];
node.eval(&[Value::U64(0)], &mut out1);
node.eval(&[Value::U64(1)], &mut out2);
assert_ne!(out1[0].as_u64(), out2[0].as_u64());
}
#[test]
fn hash_range_bounded() {
let node = HashRange::new(100);
let mut out = [Value::None];
for i in 0..1000 {
node.eval(&[Value::U64(i)], &mut out);
assert!(out[0].as_u64() < 100);
}
}
#[test]
fn hash_interval_bounded() {
let node = HashInterval::new(10.0, 20.0);
let mut out = [Value::None];
for i in 0..1000 {
node.eval(&[Value::U64(i)], &mut out);
let v = out[0].as_f64();
assert!((10.0..20.0).contains(&v), "got {v}");
}
}
#[test]
fn splitmix64_and_xxhash3_distinguishable() {
let sm = Splitmix64::new();
let xh = Xxhash3::new();
let mut out_sm = [Value::None];
let mut out_xh = [Value::None];
sm.eval(&[Value::U64(12345)], &mut out_sm);
xh.eval(&[Value::U64(12345)], &mut out_xh);
assert_ne!(out_sm[0].as_u64(), 0);
assert_ne!(out_xh[0].as_u64(), 0);
assert_ne!(out_sm[0].as_u64(), out_xh[0].as_u64());
}
}