use polydat::assembly::{GkAssembler, WireRef};
use polydat::nodes::arithmetic::{
AddU64, DivU64, Interleave, MixedRadix, ModU64,
};
use polydat::nodes::hash::{Hash64, HashRange};
fn build_hello_world() -> polydat::kernel::GkKernel {
let mut asm = GkAssembler::new(vec!["cycle".into()]);
asm.add_node("hashed", Box::new(Hash64::new()), vec![WireRef::input("cycle")]);
asm.add_node("user_id", Box::new(ModU64::new(1_000_000)), vec![WireRef::node("hashed")]);
asm.add_output("user_id", WireRef::node("user_id"));
asm.compile().unwrap()
}
#[test]
fn hello_world_bounded() {
let mut k = build_hello_world();
for cycle in 0..1000 {
k.set_inputs(&[cycle]);
let uid = k.pull("user_id").as_u64();
assert!(uid < 1_000_000, "cycle {cycle}: user_id={uid}");
}
}
#[test]
fn hello_world_deterministic() {
let mut k = build_hello_world();
k.set_inputs(&[42]);
let first = k.pull("user_id").as_u64();
k.set_inputs(&[42]);
assert_eq!(k.pull("user_id").as_u64(), first);
}
#[test]
fn hello_world_dispersed() {
let mut k = build_hello_world();
let mut vals = Vec::new();
for cycle in 0..100 {
k.set_inputs(&[cycle]);
vals.push(k.pull("user_id").as_u64());
}
let monotonic = vals.windows(2).all(|w| w[1] > w[0]);
assert!(!monotonic, "hash should disperse sequential inputs");
}
fn build_cartesian_space() -> polydat::kernel::GkKernel {
let mut asm = GkAssembler::new(vec!["cycle".into()]);
asm.add_node("decompose", Box::new(MixedRadix::new(vec![50, 200, 0])),
vec![WireRef::input("cycle")]);
asm.add_node("region_h", Box::new(Hash64::new()),
vec![WireRef::node_port("decompose", 0)]);
asm.add_node("region_code", Box::new(ModU64::new(10000)),
vec![WireRef::node("region_h")]);
asm.add_node("rs_interleave", Box::new(Interleave::new()),
vec![WireRef::node_port("decompose", 0), WireRef::node_port("decompose", 1)]);
asm.add_node("store_h", Box::new(Hash64::new()),
vec![WireRef::node("rs_interleave")]);
asm.add_node("store_code", Box::new(ModU64::new(100000)),
vec![WireRef::node("store_h")]);
asm.add_node("st_interleave", Box::new(Interleave::new()),
vec![WireRef::node("store_h"), WireRef::node_port("decompose", 2)]);
asm.add_node("tx_h", Box::new(Hash64::new()),
vec![WireRef::node("st_interleave")]);
asm.add_node("tx_id", Box::new(ModU64::new(1_000_000_000)),
vec![WireRef::node("tx_h")]);
asm.add_output("region", WireRef::node_port("decompose", 0));
asm.add_output("store", WireRef::node_port("decompose", 1));
asm.add_output("tx", WireRef::node_port("decompose", 2));
asm.add_output("region_code", WireRef::node("region_code"));
asm.add_output("store_code", WireRef::node("store_code"));
asm.add_output("tx_id", WireRef::node("tx_id"));
asm.compile().unwrap()
}
#[test]
fn cartesian_decomposition_covers_space() {
let mut k = build_cartesian_space();
for cycle in 0u64..50 {
k.set_inputs(&[cycle]);
assert_eq!(k.pull("region").as_u64(), cycle);
assert_eq!(k.pull("store").as_u64(), 0);
assert_eq!(k.pull("tx").as_u64(), 0);
}
k.set_inputs(&[50]);
assert_eq!(k.pull("region").as_u64(), 0);
assert_eq!(k.pull("store").as_u64(), 1);
}
#[test]
fn cartesian_tx_increments() {
let mut k = build_cartesian_space();
k.set_inputs(&[0]);
assert_eq!(k.pull("tx").as_u64(), 0);
k.set_inputs(&[10_000]);
assert_eq!(k.pull("tx").as_u64(), 1);
k.set_inputs(&[20_000]);
assert_eq!(k.pull("tx").as_u64(), 2);
}
#[test]
fn cartesian_codes_bounded() {
let mut k = build_cartesian_space();
for cycle in 0..500 {
k.set_inputs(&[cycle]);
assert!(k.pull("region_code").as_u64() < 10000);
assert!(k.pull("store_code").as_u64() < 100000);
assert!(k.pull("tx_id").as_u64() < 1_000_000_000);
}
}
fn build_shared_computation() -> polydat::kernel::GkKernel {
let mut asm = GkAssembler::new(vec!["cycle".into()]);
asm.add_node("user_h", Box::new(Hash64::new()),
vec![WireRef::input("cycle")]);
asm.add_node("user_id", Box::new(ModU64::new(10_000_000)),
vec![WireRef::node("user_h")]);
asm.add_node("user_bucket", Box::new(ModU64::new(64)),
vec![WireRef::node("user_h")]);
asm.add_node("user_shard", Box::new(ModU64::new(16)),
vec![WireRef::node("user_h")]);
asm.add_node("name_h", Box::new(Hash64::new()),
vec![WireRef::node("user_h")]);
asm.add_node("name_idx", Box::new(ModU64::new(50000)),
vec![WireRef::node("name_h")]);
asm.add_node("age_h", Box::new(Hash64::new()),
vec![WireRef::node("name_h")]);
asm.add_node("account_age_days", Box::new(ModU64::new(3650)),
vec![WireRef::node("age_h")]);
asm.add_output("user_id", WireRef::node("user_id"));
asm.add_output("user_bucket", WireRef::node("user_bucket"));
asm.add_output("user_shard", WireRef::node("user_shard"));
asm.add_output("name_idx", WireRef::node("name_idx"));
asm.add_output("account_age_days", WireRef::node("account_age_days"));
asm.compile().unwrap()
}
#[test]
fn shared_bucket_shard_consistent() {
let mut k = build_shared_computation();
for cycle in 0..500 {
k.set_inputs(&[cycle]);
let bucket = k.pull("user_bucket").as_u64();
let shard = k.pull("user_shard").as_u64();
assert_eq!(shard, bucket % 16,
"cycle {cycle}: shard={shard} != bucket%16={}", bucket % 16);
}
}
#[test]
fn shared_fields_bounded() {
let mut k = build_shared_computation();
for cycle in 0..500 {
k.set_inputs(&[cycle]);
assert!(k.pull("user_id").as_u64() < 10_000_000);
assert!(k.pull("user_bucket").as_u64() < 64);
assert!(k.pull("user_shard").as_u64() < 16);
assert!(k.pull("name_idx").as_u64() < 50000);
assert!(k.pull("account_age_days").as_u64() < 3650);
}
}
#[test]
fn shared_chained_hashes_differ() {
let mut k = build_shared_computation();
let mut all_same = true;
for cycle in 0..100 {
k.set_inputs(&[cycle]);
let uid = k.pull("user_id").as_u64();
let nidx = k.pull("name_idx").as_u64();
let age = k.pull("account_age_days").as_u64();
if uid != nidx as u64 || uid != age as u64 {
all_same = false;
break;
}
}
assert!(!all_same, "chained hashes should produce different field values");
}
fn build_multi_coordinate() -> polydat::kernel::GkKernel {
let mut asm = GkAssembler::new(vec!["cycle".into(), "thread".into()]);
asm.add_node("combined", Box::new(Interleave::new()),
vec![WireRef::input("cycle"), WireRef::input("thread")]);
asm.add_node("row_h", Box::new(Hash64::new()),
vec![WireRef::node("combined")]);
asm.add_node("thread_h", Box::new(Hash64::new()),
vec![WireRef::input("thread")]);
asm.add_node("partition", Box::new(ModU64::new(256)),
vec![WireRef::node("thread_h")]);
asm.add_node("row_key", Box::new(ModU64::new(1_000_000)),
vec![WireRef::node("row_h")]);
asm.add_node("value_h", Box::new(Hash64::new()),
vec![WireRef::node("row_h")]);
asm.add_node("value", Box::new(ModU64::new(1000)),
vec![WireRef::node("value_h")]);
asm.add_output("partition", WireRef::node("partition"));
asm.add_output("row_key", WireRef::node("row_key"));
asm.add_output("value", WireRef::node("value"));
asm.compile().unwrap()
}
#[test]
fn multi_coord_same_thread_stable_partition() {
let mut k = build_multi_coordinate();
k.set_inputs(&[0, 7]);
let p1 = k.pull("partition").as_u64();
k.set_inputs(&[100, 7]);
let p2 = k.pull("partition").as_u64();
k.set_inputs(&[99999, 7]);
let p3 = k.pull("partition").as_u64();
assert_eq!(p1, p2);
assert_eq!(p2, p3);
}
#[test]
fn multi_coord_different_threads_different_partitions() {
let mut k = build_multi_coordinate();
k.set_inputs(&[0, 0]);
let p0 = k.pull("partition").as_u64();
k.set_inputs(&[0, 1]);
let p1 = k.pull("partition").as_u64();
assert_ne!(p0, p1, "different threads should usually get different partitions");
}
#[test]
fn multi_coord_same_cycle_different_thread_different_row() {
let mut k = build_multi_coordinate();
k.set_inputs(&[100, 0]);
let r0 = k.pull("row_key").as_u64();
k.set_inputs(&[100, 1]);
let r1 = k.pull("row_key").as_u64();
assert_ne!(r0, r1, "interleave should make (cycle,thread) order-dependent");
}
#[test]
fn multi_coord_bounded() {
let mut k = build_multi_coordinate();
for cycle in 0..100 {
for thread in 0..8 {
k.set_inputs(&[cycle, thread]);
assert!(k.pull("partition").as_u64() < 256);
assert!(k.pull("row_key").as_u64() < 1_000_000);
assert!(k.pull("value").as_u64() < 1000);
}
}
}
fn build_hashing_provenance() -> polydat::kernel::GkKernel {
let mut asm = GkAssembler::new(vec!["cycle".into()]);
asm.add_node("decompose", Box::new(MixedRadix::new(vec![100, 0])),
vec![WireRef::input("cycle")]);
asm.add_node("tenant_h", Box::new(Hash64::new()),
vec![WireRef::node_port("decompose", 0)]);
asm.add_node("tenant_id", Box::new(ModU64::new(10000)),
vec![WireRef::node("tenant_h")]);
asm.add_node("td_interleave", Box::new(Interleave::new()),
vec![WireRef::node_port("decompose", 0), WireRef::node_port("decompose", 1)]);
asm.add_node("device_h", Box::new(Hash64::new()),
vec![WireRef::node("td_interleave")]);
asm.add_node("device_id", Box::new(ModU64::new(100000)),
vec![WireRef::node("device_h")]);
asm.add_node("field_a", Box::new(ModU64::new(1000)),
vec![WireRef::node("tenant_h")]);
asm.add_node("chain_1", Box::new(Hash64::new()),
vec![WireRef::node("tenant_h")]);
asm.add_node("field_b", Box::new(ModU64::new(1000)),
vec![WireRef::node("chain_1")]);
asm.add_node("chain_2", Box::new(Hash64::new()),
vec![WireRef::node("chain_1")]);
asm.add_node("field_c", Box::new(ModU64::new(1000)),
vec![WireRef::node("chain_2")]);
asm.add_output("tenant_id", WireRef::node("tenant_id"));
asm.add_output("device_id", WireRef::node("device_id"));
asm.add_output("field_a", WireRef::node("field_a"));
asm.add_output("field_b", WireRef::node("field_b"));
asm.add_output("field_c", WireRef::node("field_c"));
asm.compile().unwrap()
}
#[test]
fn provenance_direct_hash_bounded() {
let mut k = build_hashing_provenance();
for cycle in 0..500 {
k.set_inputs(&[cycle]);
assert!(k.pull("tenant_id").as_u64() < 10000);
}
}
#[test]
fn provenance_combined_hash_order_matters() {
let mut k = build_hashing_provenance();
k.set_inputs(&[1]);
let d1 = k.pull("device_id").as_u64();
k.set_inputs(&[2]);
let d2 = k.pull("device_id").as_u64();
assert_ne!(d1, d2);
}
#[test]
fn provenance_chained_hashes_produce_different_fields() {
let mut k = build_hashing_provenance();
let mut any_differ = false;
for cycle in 0..100 {
k.set_inputs(&[cycle]);
let a = k.pull("field_a").as_u64();
let b = k.pull("field_b").as_u64();
let c = k.pull("field_c").as_u64();
assert!(a < 1000);
assert!(b < 1000);
assert!(c < 1000);
if a != b || b != c {
any_differ = true;
}
}
assert!(any_differ, "chained hashes should produce distinct fields");
}
#[test]
fn provenance_same_tenant_same_fields() {
let mut k = build_hashing_provenance();
k.set_inputs(&[5]); let tid1 = k.pull("tenant_id").as_u64();
let a1 = k.pull("field_a").as_u64();
let b1 = k.pull("field_b").as_u64();
let c1 = k.pull("field_c").as_u64();
k.set_inputs(&[105]); let tid2 = k.pull("tenant_id").as_u64();
let a2 = k.pull("field_a").as_u64();
let b2 = k.pull("field_b").as_u64();
let c2 = k.pull("field_c").as_u64();
assert_eq!(tid1, tid2, "same tenant → same tenant_id");
assert_eq!(a1, a2, "same tenant → same field_a");
assert_eq!(b1, b2, "same tenant → same field_b");
assert_eq!(c1, c2, "same tenant → same field_c");
}
fn build_timeseries() -> polydat::kernel::GkKernel {
let mut asm = GkAssembler::new(vec!["cycle".into()]);
asm.add_node("decompose", Box::new(MixedRadix::new(vec![100, 1000, 0])),
vec![WireRef::input("cycle")]);
asm.add_node("tenant_h", Box::new(Hash64::new()),
vec![WireRef::node_port("decompose", 0)]);
asm.add_node("tenant_code", Box::new(ModU64::new(10000)),
vec![WireRef::node("tenant_h")]);
asm.add_node("td_interleave", Box::new(Interleave::new()),
vec![WireRef::node_port("decompose", 0), WireRef::node_port("decompose", 1)]);
asm.add_node("device_h", Box::new(Hash64::new()),
vec![WireRef::node("td_interleave")]);
asm.add_node("device_seq", Box::new(ModU64::new(100000)),
vec![WireRef::node("device_h")]);
asm.add_node("time_bucket", Box::new(DivU64::new(1000)),
vec![WireRef::node_port("decompose", 2)]);
asm.add_node("timestamp", Box::new(AddU64::new(1_710_000_000_000)),
vec![WireRef::node_port("decompose", 2)]);
asm.add_node("dr_interleave", Box::new(Interleave::new()),
vec![WireRef::node("device_h"), WireRef::node_port("decompose", 2)]);
asm.add_node("reading_h", Box::new(HashRange::new(1_000_000)),
vec![WireRef::node("dr_interleave")]);
asm.add_output("tenant", WireRef::node_port("decompose", 0));
asm.add_output("device", WireRef::node_port("decompose", 1));
asm.add_output("reading", WireRef::node_port("decompose", 2));
asm.add_output("tenant_code", WireRef::node("tenant_code"));
asm.add_output("device_seq", WireRef::node("device_seq"));
asm.add_output("time_bucket", WireRef::node("time_bucket"));
asm.add_output("timestamp", WireRef::node("timestamp"));
asm.add_output("reading_h", WireRef::node("reading_h"));
asm.compile().unwrap()
}
#[test]
fn timeseries_timestamp_tracks_reading() {
let mut k = build_timeseries();
for reading in 0u64..10 {
let cycle = reading * 100_000; k.set_inputs(&[cycle]);
assert_eq!(k.pull("reading").as_u64(), reading);
assert_eq!(k.pull("timestamp").as_u64(), 1_710_000_000_000 + reading);
}
}
#[test]
fn timeseries_same_tenant_across_devices() {
let mut k = build_timeseries();
k.set_inputs(&[5]); let tc_d0 = k.pull("tenant_code").as_u64();
k.set_inputs(&[105]); let tc_d1 = k.pull("tenant_code").as_u64();
assert_eq!(tc_d0, tc_d1, "same tenant across devices → same tenant_code");
}
#[test]
fn timeseries_different_tenant_same_device() {
let mut k = build_timeseries();
k.set_inputs(&[5]); let ds1 = k.pull("device_seq").as_u64();
k.set_inputs(&[6]); let ds2 = k.pull("device_seq").as_u64();
assert_ne!(ds1, ds2, "different tenant, same device → different device_seq");
}
#[test]
fn timeseries_time_bucket_groups_readings() {
let mut k = build_timeseries();
k.set_inputs(&[0]); assert_eq!(k.pull("reading").as_u64(), 0);
assert_eq!(k.pull("time_bucket").as_u64(), 0);
k.set_inputs(&[99_900_000]);
assert_eq!(k.pull("reading").as_u64(), 999);
assert_eq!(k.pull("time_bucket").as_u64(), 0);
k.set_inputs(&[100_000_000]);
assert_eq!(k.pull("reading").as_u64(), 1000);
assert_eq!(k.pull("time_bucket").as_u64(), 1); }