///|
// Injective over all UInt64 inputs; the finite input domain here excludes zero.
// Use the same integer formula in the independent minisketch driver.
fn identifier(index : Int32, seed : Int32) -> UInt64 {
(index.to_uint64() + 1UL + seed.to_uint64() * 1000003UL) *
6364136223846793005UL
}
///|
fn scenario(n : Int32, d : Int32, ratio : Int32, seed : Int32) -> Unit raise {
let width = if d == 0 { 1 } else { d * ratio / 2 }
let config = @reconcile.Config::new(width, seed=seed.to_uint64())
let build_start = @bench.monotonic_clock_start()
let a = @reconcile.Sketch::new(config)
let b = @reconcile.Sketch::new(config)
for i = 0; i < n; i = i + 1 {
a.insert(identifier(i, seed))
b.insert(identifier(i + d / 2, seed))
}
let build_us = @bench.monotonic_clock_end(build_start)
let decode_start = @bench.monotonic_clock_start()
let report = b.subtract(a).decode(budget=d + 1)
let decode_us = @bench.monotonic_clock_end(decode_start)
if report.complete() {
let negative = Array::makei(d / 2, fn(i) { identifier(i, seed) })
let positive = Array::makei(d / 2, fn(i) { identifier(n + i, seed) })
positive.sort()
negative.sort()
assert_eq(report.positive(), positive)
assert_eq(report.negative(), negative)
}
println(
"\{n},\{d},\{ratio},\{seed},\{width},\{report.complete()},\{report.residual_cells()},\{report.steps()},\{config.wire_bytes()},\{n.to_int64() * 8L},\{build_us},\{decode_us}",
)
}
///|
fn run() -> Unit raise {
if @env.args().contains("--sessions") {
session_matrix()
return
}
let full = @env.args().contains("--full")
let sizes = if full { [1000, 10000, 100000] } else { [1000, 10000] }
let differences = if full { [0, 2, 20, 200, 2000] } else { [0, 2, 20, 200] }
let seeds = if full { 100 } else { 10 }
println(
"members,differences,ratio_twice,seed,width,complete,residual,steps,frame_bytes,full_id_bytes,build_us,decode_us",
)
for n in sizes {
for d in differences {
for ratio in [1, 2, 4] {
for seed = 1; seed <= seeds; seed = seed + 1 {
scenario(n, d, ratio, seed)
}
}
}
}
// Extra one-way and high-difference cases are verified by the session and
// core property tests; this CSV isolates the declared balanced matrix.
}
///|
fn session_matrix() -> Unit raise {
println(
"members,differences,seed,budget,complete,reason,rounds,sent_bytes,received_bytes,steps,full_id_bytes",
)
for n in [1000, 10000] {
for d in [0, 2, 20, 200] {
for seed = 1; seed <= 10; seed = seed + 1 {
let a = @reconcile.Inventory::from_ids(
Array::makei(n, fn(i) { identifier(i, seed) }),
)
let b = @reconcile.Inventory::from_ids(
Array::makei(n, fn(i) { identifier(i + d / 2, seed) }),
)
for budget in [512L, 65536L] {
let initial = @reconcile.Config::new(1, seed=seed.to_uint64())
let session = @reconcile.Session::new(
a.snapshot(initial),
b.epoch(),
@reconcile.SessionLimits::new(max_bytes=budget, max_rounds=16),
)
for config = initial {
let source = a.snapshot(config)
match session.record_sent(source.encode()) {
Stopped(_) => break
_ => ()
}
match session.receive(source, b.snapshot(config).encode()) {
Awaiting(next) => continue next
_ => break
}
}
let (complete, reason) = match session.state() {
Complete(plan) => {
assert_eq(a.apply(plan).ids(), b.ids())
(true, "complete")
}
Stopped(reason) => (false, reason.label())
_ => fail("session loop must terminate")
}
println(
"\{n},\{d},\{seed},\{budget},\{complete},\{reason},\{session.rounds()},\{session.sent_bytes()},\{session.received_bytes()},\{session.decode_steps()},\{n.to_int64() * 8L}",
)
}
}
}
}
}
///|
fn main {
run() catch {
e => abort(e.to_string())
}
}