1use polydat::compile::fusion::{DecomposedGraph, DecomposedWire, FusedNode};
12use xxhash_rust::xxh3::xxh3_64;
13
14pub use polydat::numeric::hash::splitmix64_u64;
15
16#[polydat::polydat_node(category = Hashing)]
27fn hash(input: u64) -> u64 {
28 splitmix64_u64(input)
29}
30
31#[polydat::polydat_node(category = Hashing)]
35fn splitmix64(input: u64) -> u64 {
36 splitmix64_u64(input)
37}
38
39#[polydat::polydat_node(category = Hashing)]
43fn scatter(input: u64) -> u64 {
44 splitmix64_u64(input)
45}
46
47#[polydat::polydat_node(category = Hashing)]
53fn xxhash3(input: u64) -> u64 {
54 xxh3_64(&input.to_le_bytes())
55}
56
57#[polydat::polydat_node(category = Hashing)]
61fn xxh3(input: u64) -> u64 {
62 xxh3_64(&input.to_le_bytes())
63}
64
65#[polydat::polydat_node(category = Hashing)]
76fn hash_range(input: u64, max: Const<u64>) -> u64 {
77 if *max == 0 {
78 0
79 } else {
80 splitmix64_u64(input) % *max
81 }
82}
83
84impl FusedNode for HashRange {
85 fn decomposed(&self) -> DecomposedGraph {
87 use crate::arithmetic::Mod;
88 let mut g = DecomposedGraph::new(1);
89 let h = g.add_node(Box::new(Hash::new()), vec![DecomposedWire::Input(0)]);
90 let m = g.add_node(
91 Box::new(Mod::new(self.max)),
92 vec![DecomposedWire::Node(h, 0)],
93 );
94 g.set_outputs(vec![DecomposedWire::Node(m, 0)]);
95 g
96 }
97}
98
99#[polydat::polydat_node(category = Hashing)]
109fn hash_interval(input: u64, min: Const<f64>, max: Const<f64>) -> f64 {
110 let h = splitmix64_u64(input);
111 let unit = (h as f64) / (u64::MAX as f64);
112 *min + unit * (*max - *min)
113}
114
115impl FusedNode for HashInterval {
116 fn decomposed(&self) -> DecomposedGraph {
118 use crate::lerp::Lerp;
119 use crate::sampling::icd::UnitInterval;
120 let mut g = DecomposedGraph::new(1);
121 let h = g.add_node(Box::new(Hash::new()), vec![DecomposedWire::Input(0)]);
122 let ui = g.add_node(
123 Box::new(UnitInterval::new()),
124 vec![DecomposedWire::Node(h, 0)],
125 );
126 let lerp = g.add_node(
127 Box::new(Lerp::new(self.min, self.max)),
128 vec![DecomposedWire::Node(ui, 0)],
129 );
130 g.set_outputs(vec![DecomposedWire::Node(lerp, 0)]);
131 g
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use polydat::ast::{PolydatNode, Value};
139
140 #[test]
141 fn hash_deterministic() {
142 let node = Hash::new();
143 let mut out = [Value::None];
144 node.eval(&[Value::U64(42)], &mut out);
145 let first = out[0].as_u64();
146 node.eval(&[Value::U64(42)], &mut out);
147 assert_eq!(
148 first,
149 out[0].as_u64(),
150 "same input must produce same output"
151 );
152 }
153
154 #[test]
155 fn hash_different_inputs_differ() {
156 let node = Hash::new();
157 let mut out1 = [Value::None];
158 let mut out2 = [Value::None];
159 node.eval(&[Value::U64(0)], &mut out1);
160 node.eval(&[Value::U64(1)], &mut out2);
161 assert_ne!(out1[0].as_u64(), out2[0].as_u64());
162 }
163
164 #[test]
165 fn hash_range_bounded() {
166 let node = HashRange::new(100);
167 let mut out = [Value::None];
168 for i in 0..1000 {
169 node.eval(&[Value::U64(i)], &mut out);
170 assert!(out[0].as_u64() < 100);
171 }
172 }
173
174 #[test]
175 fn hash_interval_bounded() {
176 let node = HashInterval::new(10.0, 20.0);
177 let mut out = [Value::None];
178 for i in 0..1000 {
179 node.eval(&[Value::U64(i)], &mut out);
180 let v = out[0].as_f64();
181 assert!((10.0..20.0).contains(&v), "got {v}");
182 }
183 }
184
185 #[test]
186 fn splitmix64_and_xxhash3_distinguishable() {
187 let sm = Splitmix64::new();
188 let xh = Xxhash3::new();
189 let mut out_sm = [Value::None];
190 let mut out_xh = [Value::None];
191 sm.eval(&[Value::U64(12345)], &mut out_sm);
192 xh.eval(&[Value::U64(12345)], &mut out_xh);
193 assert_ne!(out_sm[0].as_u64(), 0);
194 assert_ne!(out_xh[0].as_u64(), 0);
195 assert_ne!(out_sm[0].as_u64(), out_xh[0].as_u64());
196 }
197}
198
199use polydat::compile::fusion::{FusionPattern, FusionRule, FusionRuleRegistration};
206
207fn hash_mod_to_hash_range() -> FusionRule {
210 FusionRule {
211 name: "hash_mod_to_hash_range",
212 pattern: FusionPattern::node(
213 "mod",
214 vec![FusionPattern::node(
215 "hash",
216 vec![FusionPattern::any("x")],
217 "hash_node",
218 )],
219 "mod_node",
220 ),
221 replacement: |m| {
222 let max = m.const_u64("mod_node");
223 Box::new(HashRange::new(max))
224 },
225 input_bindings: &["x"],
226 }
227}
228
229fn hash_unit_lerp_to_hash_interval() -> FusionRule {
232 FusionRule {
233 name: "hash_unit_lerp_to_hash_interval",
234 pattern: FusionPattern::node(
235 "lerp",
236 vec![FusionPattern::node(
237 "unit_interval",
238 vec![FusionPattern::node(
239 "hash",
240 vec![FusionPattern::any("x")],
241 "hash_node",
242 )],
243 "ui_node",
244 )],
245 "lerp_node",
246 ),
247 replacement: |m| {
248 let consts = m.const_vec("lerp_node");
249 let lo = f64::from_bits(consts[0]);
250 let hi = f64::from_bits(consts[1]);
251 Box::new(HashInterval::new(lo, hi))
252 },
253 input_bindings: &["x"],
254 }
255}
256
257polydat::inventory::submit! {
258 FusionRuleRegistration { priority: 10, build: hash_mod_to_hash_range }
259}
260polydat::inventory::submit! {
261 FusionRuleRegistration { priority: 20, build: hash_unit_lerp_to_hash_interval }
262}