use std::cmp::Ordering;
use std::collections::BinaryHeap;
use crate::field::{Coeffs, Entry, Fp, HeapEntry, Z2};
struct Rng(u64);
impl Rng {
fn new(seed: u64) -> Self {
Rng(seed | 1)
}
fn next_u64(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
fn below(&mut self, n: usize) -> usize {
(self.next_u64() % n as u64) as usize
}
}
fn normalize(d: f64) -> f64 {
if d == 0.0 { 0.0 } else { d }
}
fn arbitrary_nonnegative(rng: &mut Rng) -> f64 {
loop {
let d = f64::from_bits(rng.next_u64() & !(1u64 << 63));
if !d.is_nan() {
return normalize(d);
}
}
}
fn key_values() -> Vec<f64> {
let mut values = vec![
normalize(-0.0),
0.0,
f64::from_bits(1),
f64::MIN_POSITIVE / 2.0,
f64::MIN_POSITIVE,
1e-300,
1e-8,
0.5,
1.0,
1.0 + f64::EPSILON,
2.0,
1e8,
1e300,
f64::MAX,
f64::INFINITY,
];
let mut rng = Rng::new(0x6269_745f_6b65_7901);
values.extend((0..497).map(|_| arbitrary_nonnegative(&mut rng)));
values
}
#[test]
fn bit_order_matches_total_cmp_on_arbitrary_patterns() {
assert_eq!(normalize(-0.0f64).to_bits(), 0.0f64.to_bits());
assert_eq!(normalize(0.0f64).to_bits(), 0.0f64.to_bits());
let values = key_values();
assert!(
values.iter().any(|d| d.is_subnormal()),
"no subnormal drawn"
);
assert!(values.iter().any(|d| d.is_infinite()));
for &a in &values {
assert!(!a.is_nan() && a >= 0.0 && a.is_sign_positive(), "{a}");
for &b in &values {
assert_eq!(
a.to_bits().cmp(&b.to_bits()),
a.total_cmp(&b),
"bit order and total_cmp disagree on {a} and {b}"
);
}
}
}
#[derive(Debug, Clone, Copy)]
struct OldHeapEntry(Entry);
impl PartialEq for OldHeapEntry {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for OldHeapEntry {}
impl PartialOrd for OldHeapEntry {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for OldHeapEntry {
fn cmp(&self, other: &Self) -> Ordering {
other
.0
.diameter
.total_cmp(&self.0.diameter)
.then(self.0.payload.cmp(&other.0.payload))
}
}
fn old_pop_pivot_z2(heap: &mut BinaryHeap<OldHeapEntry>) -> Option<Entry> {
while let Some(top) = heap.pop() {
match heap.peek() {
Some(next) if next.0.payload == top.0.payload => {
heap.pop();
}
_ => return Some(top.0),
}
}
None
}
fn old_pop_pivot_fp(ops: &Fp, p: u64, heap: &mut BinaryHeap<OldHeapEntry>) -> Option<Entry> {
let mut acc: Option<(f64, u64, u64)> = None;
while let Some(&OldHeapEntry(top)) = heap.peek() {
let index = ops.index(top);
let coeff = ops.coeff(top);
match acc.as_mut() {
None => acc = Some((top.diameter, index, coeff)),
Some((_, _, c)) if *c == 0 => acc = Some((top.diameter, index, coeff)),
Some((_, i, _)) if index != *i => break,
Some((_, _, c)) => *c = (*c + coeff) % p,
}
heap.pop();
}
acc.filter(|&(_, _, c)| c != 0)
.map(|(diameter, index, coeff)| ops.pack(diameter, index, coeff))
}
fn random_column(
rng: &mut Rng,
palette: &[f64],
indices: usize,
p: u64,
len: usize,
) -> Vec<(f64, u64, u64)> {
let mut out = Vec::with_capacity(len);
for _ in 0..len {
let diameter = palette[rng.below(palette.len())];
let index = rng.below(indices) as u64;
let coeff = if p == 2 {
1
} else {
1 + rng.below(p as usize - 1) as u64
};
out.push((diameter, index, coeff));
}
out
}
fn trace_key(e: Entry) -> (u64, u64) {
(e.diameter.to_bits(), e.payload)
}
#[test]
fn heap_traces_agree_under_both_comparators() {
let palette: Vec<f64> = {
let mut rng = Rng::new(0x6269_745f_6b65_7902);
let mut v = vec![
0.0,
normalize(-0.0),
f64::from_bits(1),
f64::MIN_POSITIVE,
1.0,
1.0,
2.0,
2.0,
f64::MAX,
f64::INFINITY,
];
v.extend((0..10).map(|_| arbitrary_nonnegative(&mut rng)));
v
};
let mut rng = Rng::new(0x6269_745f_6b65_7903);
let mut popped = 0usize;
for p in [2u64, 3, 5, 7, 13, 32749] {
let ops = Fp::new(p);
for case in 0..200 {
let len = 1 + rng.below(64);
let indices = 1 + rng.below(8);
let column = random_column(&mut rng, &palette, indices, p, len);
let mut new_heap: BinaryHeap<HeapEntry> = BinaryHeap::new();
let mut old_heap: BinaryHeap<OldHeapEntry> = BinaryHeap::new();
for &(diameter, index, coeff) in &column {
let entry = if p == 2 {
Z2.pack(diameter, index, coeff)
} else {
ops.pack(diameter, index, coeff)
};
new_heap.push(HeapEntry::new(entry));
old_heap.push(OldHeapEntry(entry));
}
let mut new_trace = Vec::new();
let mut old_trace = Vec::new();
if p == 2 {
while let Some(e) = Z2.pop_pivot(&mut new_heap) {
new_trace.push(trace_key(e));
}
while let Some(e) = old_pop_pivot_z2(&mut old_heap) {
old_trace.push(trace_key(e));
}
} else {
while let Some(e) = ops.pop_pivot(&mut new_heap) {
new_trace.push(trace_key(e));
}
while let Some(e) = old_pop_pivot_fp(&ops, p, &mut old_heap) {
old_trace.push(trace_key(e));
}
}
assert_eq!(new_trace, old_trace, "p {p}, case {case}");
assert!(new_heap.is_empty() && old_heap.is_empty());
popped += new_trace.len();
}
}
assert!(popped > 10_000, "the traces were nearly empty: {popped}");
}