use std::cell::RefCell;
use super::aligned::AlignedBars;
use super::Method;
const SLOTS: usize = 1024;
type Key = [u64; 9];
#[derive(Clone, Copy)]
struct Entry {
occupied: bool,
key: Key,
value: f64,
method: Method,
}
thread_local! {
static TABLE: RefCell<Box<[Entry]>> = RefCell::new(
vec![
Entry {
occupied: false,
key: [0; 9],
value: 0.0,
method: Method::BarClosedForm,
};
SLOTS
]
.into_boxed_slice(),
);
}
fn key_of(bars: &AlignedBars) -> Key {
[
bars.length[0].to_bits(),
bars.length[1].to_bits(),
bars.width[0].to_bits(),
bars.width[1].to_bits(),
bars.height[0].to_bits(),
bars.height[1].to_bits(),
bars.offset[0].to_bits(),
bars.offset[1].to_bits(),
bars.offset[2].to_bits(),
]
}
fn slot(key: &Key) -> usize {
const MIX: u64 = 0x9e37_79b9_7f4a_7c15;
let mut hash = MIX;
for &word in key {
hash = (hash ^ word).wrapping_mul(MIX).rotate_left(29);
}
hash ^= hash >> 30;
hash = hash.wrapping_mul(0xbf58_476d_1ce4_e5b9);
hash ^= hash >> 27;
hash = hash.wrapping_mul(0x94d0_49bb_1331_11eb);
hash ^= hash >> 31;
hash as usize & (SLOTS - 1)
}
pub(super) fn aligned_integral(
bars: &AlignedBars,
evaluate: impl FnOnce(&AlignedBars) -> (f64, Method),
) -> (f64, Method) {
let key = key_of(bars);
let index = slot(&key);
let hit = TABLE.with_borrow(|table| {
let entry = &table[index];
(entry.occupied && entry.key == key).then_some((entry.value, entry.method))
});
if let Some(hit) = hit {
return hit;
}
let (value, method) = evaluate(bars);
TABLE.with_borrow_mut(|table| {
table[index] = Entry {
occupied: true,
key,
value,
method,
};
});
(value, method)
}
#[cfg(test)]
mod tests {
use super::*;
fn bars(index: usize) -> AlignedBars {
AlignedBars {
length: [-1.0, -1.0 - index as f64],
width: [-0.2, -0.3],
height: [-0.1, -0.1],
offset: [index as f64 * 1e-3, 0.5, 0.25],
}
}
#[test]
fn a_hit_returns_the_recorded_bits_and_method() {
let b = bars(7);
let first = aligned_integral(&b, |_| (std::f64::consts::PI, Method::AlignedQuadrature));
let second = aligned_integral(&b, |_| panic!("evaluated twice"));
assert_eq!(first, (std::f64::consts::PI, Method::AlignedQuadrature));
assert_eq!(second, first);
}
#[test]
fn keys_differing_only_in_the_last_two_words_spread_across_the_table() {
use std::collections::HashSet;
let mut slots = HashSet::new();
for dw in -7..=7 {
for dh in -3..=3 {
let bars = AlignedBars {
length: [10e-3, 10e-3],
width: [1e-3 / 8.0, 1e-3 / 8.0],
height: [35e-6 / 4.0, 35e-6 / 4.0],
offset: [0.0, dw as f64 * (1e-3 / 8.0), dh as f64 * (35e-6 / 4.0)],
};
slots.insert(slot(&key_of(&bars)));
}
}
assert!(
slots.len() >= 40,
"105 bundle-shaped keys used only {} of {SLOTS} slots",
slots.len()
);
}
#[test]
fn colliding_keys_never_answer_for_each_other() {
let expected = |index: usize| 1.0 + index as f64;
for round in 0..4 {
for index in 0..8 * SLOTS {
let got = aligned_integral(&bars(index), |b| {
assert_eq!(b.offset[0], index as f64 * 1e-3);
(expected(index), Method::AlignedQuadrature)
});
assert_eq!(got.0, expected(index), "round {round}, index {index}");
}
}
}
#[test]
fn every_field_is_part_of_the_key() {
let base = AlignedBars::same(-1.0, -0.2, -0.1);
let fields: [fn(&mut AlignedBars); 9] = [
|b| b.length[0] += 1e-9,
|b| b.length[1] += 1e-9,
|b| b.width[0] += 1e-9,
|b| b.width[1] += 1e-9,
|b| b.height[0] += 1e-9,
|b| b.height[1] += 1e-9,
|b| b.offset[0] += 1e-9,
|b| b.offset[1] += 1e-9,
|b| b.offset[2] += 1e-9,
];
let seed = aligned_integral(&base, |_| (1.0, Method::AlignedQuadrature));
assert_eq!(seed.0, 1.0);
for (which, nudge) in fields.iter().enumerate() {
let mut moved = base;
nudge(&mut moved);
let got = aligned_integral(&moved, |_| (2.0, Method::AlignedQuadrature));
assert_eq!(got.0, 2.0, "field {which} is missing from the key");
}
}
}