use std::cell::RefCell;
use crate::kernel::{self, invert_columns};
#[derive(Clone, Copy, Debug)]
pub(crate) struct Obtuse {
v: [[f64; 3]; 4],
rel: [[f64; 8]; 3],
half_n2: [f64; 8],
gram: [[f64; 8]; 8],
inv: [[f64; 3]; 3],
idx: [u8; 3],
pub ok: bool,
}
impl PartialEq for Obtuse {
fn eq(&self, other: &Self) -> bool {
self.ok == other.ok && self.idx == other.idx && self.v == other.v && self.inv == other.inv
}
}
pub(crate) fn obtuse_superbasis(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> Obtuse {
let original = [
a,
b,
c,
kernel::scale(-1.0, kernel::add(kernel::add(a, b), c)),
];
let scale = (kernel::n2(a) + kernel::n2(b) + kernel::n2(c)).max(1.0);
let tol = 1e-10 * scale;
if already_obtuse(&original, tol) {
return finish(original);
}
let reduced = lagrange_reduce([a, b, c]);
let mut v = [
reduced[0],
reduced[1],
reduced[2],
kernel::scale(
-1.0,
kernel::add(kernel::add(reduced[0], reduced[1]), reduced[2]),
),
];
for budget in [64usize, 1000] {
let done = selling_steps(&mut v, tol, budget);
if done {
return finish(v);
}
}
unfinished(original)
}
fn unfinished(v: [[f64; 3]; 4]) -> Obtuse {
let (rel, half_n2, gram) = relevant_of(v);
Obtuse {
v,
rel,
half_n2,
gram,
inv: [[0.0; 3]; 3],
idx: [0, 1, 2],
ok: false,
}
}
fn already_obtuse(v: &[[f64; 3]; 4], tol: f64) -> bool {
for i in 0..3 {
for j in (i + 1)..4 {
if kernel::dot(v[i], v[j]) > tol {
return false;
}
}
}
true
}
type Relevant = ([[f64; 8]; 3], [f64; 8], [[f64; 8]; 8]);
fn relevant_of(v: [[f64; 3]; 4]) -> Relevant {
let vecs = [
v[0],
v[1],
v[2],
v[3],
kernel::add(v[0], v[1]),
kernel::add(v[0], v[2]),
kernel::add(v[0], v[3]),
[0.0; 3],
];
let mut rel = [[0.0; 8]; 3];
let mut half_n2 = [f64::INFINITY; 8];
let mut gram = [[0.0; 8]; 8];
for (k, r) in vecs.iter().enumerate() {
rel[0][k] = r[0];
rel[1][k] = r[1];
rel[2][k] = r[2];
if k < 7 {
half_n2[k] = 0.5 * kernel::n2(*r) * (1.0 + 1e-12);
}
for (j, q) in vecs.iter().enumerate() {
gram[k][j] = kernel::dot(*r, *q);
}
}
(rel, half_n2, gram)
}
fn lagrange_reduce(mut v: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
for _ in 0..24 {
for i in 1..3 {
let mut j = i;
while j > 0 && kernel::n2(v[j]) < kernel::n2(v[j - 1]) {
v.swap(j, j - 1);
j -= 1;
}
}
let mut changed = false;
for i in 1..3 {
for j in (0..i).rev() {
let lj = kernel::n2(v[j]);
if lj < 1e-30 {
continue;
}
let q = kernel::round_away(kernel::dot(v[i], v[j]) / lj);
if q != 0.0 {
v[i] = kernel::add(v[i], kernel::scale(-q, v[j]));
changed = true;
}
}
}
if !changed {
break;
}
}
v
}
fn selling_steps(v: &mut [[f64; 3]; 4], tol: f64, budget: usize) -> bool {
for _ in 0..budget {
let mut step_at = None;
'pairs: for i in 0..3 {
for j in (i + 1)..4 {
if kernel::dot(v[i], v[j]) > tol {
step_at = Some((i, j));
break 'pairs;
}
}
}
let Some((i, j)) = step_at else {
return true;
};
let step = v[i];
for (k, slot) in v.iter_mut().enumerate() {
if k != i && k != j {
*slot = kernel::add(*slot, step);
}
}
v[i] = kernel::scale(-1.0, step);
}
false
}
fn finish(v: [[f64; 3]; 4]) -> Obtuse {
let mut best_abs = 0.0;
let mut best: Option<([u8; 3], [[f64; 3]; 3])> = None;
for drop_at in 0..4 {
let mut idx = [0u8; 3];
let mut n = 0;
for t in 0..4 {
if t != drop_at {
idx[n] = t as u8;
n += 1;
}
}
let cols = [v[idx[0] as usize], v[idx[1] as usize], v[idx[2] as usize]];
if let Some((inv, det)) = invert_columns(cols) {
if det.abs() > best_abs {
best_abs = det.abs();
best = Some((idx, inv));
}
}
}
match best {
Some((idx, inv)) if best_abs > 1e-18 => {
let (rel, half_n2, gram) = relevant_of(v);
Obtuse {
v,
rel,
half_n2,
gram,
inv,
idx,
ok: true,
}
}
_ => unfinished(v),
}
}
pub(crate) fn closest_displacement(s: &Obtuse, y: [f64; 3]) -> [f64; 3] {
if s.ok {
slice(s, y)
} else {
shell(s.v[0], s.v[1], s.v[2], y)
}
}
pub(crate) struct CachedBasis {
h: [[f64; 3]; 3],
s: Obtuse,
engine_start: bool,
}
#[cfg_attr(not(all(target_arch = "x86_64", minimage_avx512)), allow(dead_code))]
pub(crate) struct Slicer<'a> {
pub(crate) rel: &'a [[f64; 8]; 3],
pub(crate) half_n2: &'a [f64; 8],
pub(crate) gram: &'a [[f64; 8]; 8],
pub(crate) inv: &'a [[f64; 3]; 3],
pub(crate) basis: [[f64; 3]; 3],
pub(crate) engine_start: bool,
}
fn engine_is_short(h: [[f64; 3]; 3]) -> bool {
let [a, b, c] = h;
let scale = (a[0].abs() + b[1].abs() + c[2].abs()).max(1.0);
let tol = 1e-10 * scale;
a[1].abs() <= tol
&& a[2].abs() <= tol
&& b[2].abs() <= tol
&& b[0].abs() <= 0.5 * a[0].abs() + tol
&& c[0].abs() <= 0.5 * a[0].abs() + tol
&& c[1].abs() <= 0.5 * b[1].abs() + tol
}
struct BasisCache {
slots: [Option<CachedBasis>; 4],
hand: usize,
}
thread_local! {
static BASIS: RefCell<BasisCache> = const {
RefCell::new(BasisCache {
slots: [None, None, None, None],
hand: 0,
})
};
}
pub(crate) fn closest_for(h: [[f64; 3]; 3], y: [f64; 3], wrapped: [f64; 3]) -> [f64; 3] {
with_basis(h, |b| b.closest(y, wrapped))
}
pub(crate) fn with_basis<R>(h: [[f64; 3]; 3], f: impl FnOnce(&CachedBasis) -> R) -> R {
BASIS.with(|cache| {
let mut cache = cache.borrow_mut();
let hit = cache
.slots
.iter()
.position(|slot| slot.as_ref().is_some_and(|slot| same_h(&slot.h, &h)));
let i = match hit {
Some(i) => i,
None => {
let i = cache.hand;
cache.hand = (i + 1) % cache.slots.len();
cache.slots[i] = Some(CachedBasis {
h,
s: obtuse_superbasis(h[0], h[1], h[2]),
engine_start: engine_is_short(h),
});
i
}
};
match &cache.slots[i] {
Some(slot) => f(slot),
None => unreachable!("slot {i} was just filled"),
}
})
}
impl CachedBasis {
#[inline(always)]
pub(crate) fn closest(&self, y: [f64; 3], wrapped: [f64; 3]) -> [f64; 3] {
if self.engine_start && self.s.ok {
descend(&self.s, wrapped)
} else {
closest_displacement(&self.s, y)
}
}
#[cfg_attr(not(all(target_arch = "x86_64", minimage_avx512)), allow(dead_code))]
pub(crate) fn slicer(&self) -> Option<Slicer<'_>> {
let s = &self.s;
s.ok.then(|| Slicer {
rel: &s.rel,
half_n2: &s.half_n2,
gram: &s.gram,
inv: &s.inv,
basis: [
s.v[s.idx[0] as usize],
s.v[s.idx[1] as usize],
s.v[s.idx[2] as usize],
],
engine_start: self.engine_start,
})
}
}
#[inline(always)]
fn same_h(a: &[[f64; 3]; 3], b: &[[f64; 3]; 3]) -> bool {
let mut same = true;
for (ca, cb) in a.iter().zip(b) {
for (x, y) in ca.iter().zip(cb) {
same &= x == y;
}
}
same
}
fn slice(s: &Obtuse, y: [f64; 3]) -> [f64; 3] {
descend(s, babai(s, y))
}
#[inline(always)]
fn babai(s: &Obtuse, y: [f64; 3]) -> [f64; 3] {
let c = kernel::mul(s.inv, y);
let b0 = s.v[s.idx[0] as usize];
let b1 = s.v[s.idx[1] as usize];
let b2 = s.v[s.idx[2] as usize];
let z0 = kernel::round_away(c[0]);
let z1 = kernel::round_away(c[1]);
let z2 = kernel::round_away(c[2]);
[
y[0] - (z0 * b0[0] + z1 * b1[0] + z2 * b2[0]),
y[1] - (z0 * b0[1] + z1 * b1[1] + z2 * b2[1]),
y[2] - (z0 * b0[2] + z1 * b1[2] + z2 * b2[2]),
]
}
#[inline(always)]
fn descend(s: &Obtuse, mut x: [f64; 3]) -> [f64; 3] {
let mut t = [0.0; 8];
for (k, slot) in t.iter_mut().enumerate() {
*slot = x[0] * s.rel[0][k] + x[1] * s.rel[1][k] + x[2] * s.rel[2][k];
}
for _ in 0..64 {
let (best, gain) = most_violated(&t, &s.half_n2);
let f = if gain > 0.0 {
if t[best] > 0.0 {
1.0
} else {
-1.0
}
} else {
0.0
};
x[0] -= f * s.rel[0][best];
x[1] -= f * s.rel[1][best];
x[2] -= f * s.rel[2][best];
for (slot, g) in t.iter_mut().zip(s.gram[best]) {
*slot -= f * g;
}
if !any_violated(&t, &s.half_n2) {
break;
}
}
x
}
#[inline(always)]
fn most_violated(t: &[f64; 8], h: &[f64; 8]) -> (usize, f64) {
let mut g = [0.0; 8];
for ((slot, tk), hk) in g.iter_mut().zip(t).zip(h) {
*slot = tk.abs() - hk;
}
let (i01, g01) = larger(0, g[0], 1, g[1]);
let (i23, g23) = larger(2, g[2], 3, g[3]);
let (i45, g45) = larger(4, g[4], 5, g[5]);
let (i03, g03) = larger(i01, g01, i23, g23);
let (i46, g46) = larger(i45, g45, 6, g[6]);
larger(i03, g03, i46, g46)
}
#[inline(always)]
fn any_violated(t: &[f64; 8], h: &[f64; 8]) -> bool {
let mut any = false;
for (tk, hk) in t.iter().zip(h) {
any |= tk.abs() > *hk;
}
any
}
#[inline(always)]
fn larger(i: usize, gi: f64, j: usize, gj: f64) -> (usize, f64) {
let take = usize::from(gj > gi).wrapping_neg();
(i ^ ((i ^ j) & take), if gj > gi { gj } else { gi })
}
fn shell(a: [f64; 3], b: [f64; 3], c: [f64; 3], y: [f64; 3]) -> [f64; 3] {
let mut best = y;
let mut best2 = kernel::n2(y);
for r in 0i32..=16 {
let lo = -r;
let hi = r;
for i in lo..=hi {
for j in lo..=hi {
for k in lo..=hi {
if r > 0 && i.abs() != r && j.abs() != r && k.abs() != r {
continue;
}
let p = [
f64::from(i) * a[0] + f64::from(j) * b[0] + f64::from(k) * c[0],
f64::from(i) * a[1] + f64::from(j) * b[1] + f64::from(k) * c[1],
f64::from(i) * a[2] + f64::from(j) * b[2] + f64::from(k) * c[2],
];
let d = [y[0] - p[0], y[1] - p[1], y[2] - p[2]];
let d2 = kernel::n2(d);
if d2 < best2 {
best2 = d2;
best = d;
}
}
}
}
}
best
}
#[cfg(test)]
mod tests {
use super::*;
fn brute(a: [f64; 3], b: [f64; 3], c: [f64; 3], y: [f64; 3]) -> f64 {
shell(a, b, c, y).iter().map(|e| e * e).sum()
}
#[test]
fn ortho_superbasis_is_obtuse() {
let a = [10.0, 0.0, 0.0];
let b = [0.0, 11.0, 0.0];
let c = [0.0, 0.0, 12.0];
let s = obtuse_superbasis(a, b, c);
assert!(s.ok);
let y = [25.0, -18.0, 3.0];
let d = closest_displacement(&s, y);
let d2 = kernel::n2(d);
assert!((d2 - brute(a, b, c, y)).abs() < 1e-8);
assert!((d2 - 50.0).abs() < 1e-8);
}
#[test]
fn lattice_vector_is_zero() {
let a = [1.0, 0.0, 0.0];
let b = [0.99, 0.01, 0.0];
let c = [0.0, 0.0, 1.0];
let s = obtuse_superbasis(a, b, c);
assert!(s.ok, "Selling finishes on the skewed cell");
let y = [0.02, -0.02, 0.0];
let d = closest_displacement(&s, y);
assert!(kernel::n2(d) < 1e-20, "2(a-b) is a lattice vector");
}
#[test]
fn near_parallel_finishes() {
let a = [1.0, 0.0, 0.0];
let b = [1.0 - 1e-4, 1e-4, 0.0];
let c = [0.2, -0.3, 1.5];
let s = obtuse_superbasis(a, b, c);
assert!(s.ok, "size reduction leaves a short Delone step");
let y = [2e-4, -2e-4, 0.0];
let d = closest_displacement(&s, y);
assert!(kernel::n2(d) < 1e-18, "2(a-b) is a lattice vector");
let probe = [3.5, -1.25, 4.0];
let got = kernel::n2(closest_displacement(&s, probe));
let oracle = brute(a, b, c, probe);
assert!(got <= oracle + 1e-6 * (1.0 + oracle));
}
#[test]
fn slicer_matches_a_reduced_shell_on_faces_and_skews() {
let mut state = 0xd1b5_4a32_d192_ed03u64;
let mut next = || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
(state >> 11) as f64 / (1u64 << 53) as f64 * 2.0 - 1.0
};
for cell in 0..300 {
let a = [3.0 + next(), next(), next()];
let mut b = [next(), 3.0 + next(), next()];
let c = [next(), next(), 3.0 + next()];
if cell % 3 == 0 {
let t = 1e-3 * (1.0 + next());
b = [a[0] + t * next(), a[1] + t * next(), a[2] + t];
}
if invert_columns([a, b, c]).is_none() {
continue;
}
let s = obtuse_superbasis(a, b, c);
assert!(s.ok);
let mut ys = Vec::new();
for _ in 0..30 {
ys.push([8.0 * next(), 8.0 * next(), 8.0 * next()]);
}
for k in 0..7 {
let r = [s.rel[0][k], s.rel[1][k], s.rel[2][k]];
for f in [0.5 - 1e-9, 0.5, 0.5 + 1e-9, -0.5] {
ys.push(kernel::scale(f, r));
}
}
for y in ys {
let got = kernel::n2(closest_displacement(&s, y));
let v = s.v;
let mut oracle = f64::INFINITY;
let base = slice(&s, y);
for i in -3i32..=3 {
for j in -3i32..=3 {
for k in -3i32..=3 {
let p = [
f64::from(i) * v[0][0]
+ f64::from(j) * v[1][0]
+ f64::from(k) * v[2][0],
f64::from(i) * v[0][1]
+ f64::from(j) * v[1][1]
+ f64::from(k) * v[2][1],
f64::from(i) * v[0][2]
+ f64::from(j) * v[1][2]
+ f64::from(k) * v[2][2],
];
let d = [base[0] - p[0], base[1] - p[1], base[2] - p[2]];
oracle = oracle.min(kernel::n2(d));
}
}
}
assert!(
got <= oracle + 1e-9 * (1.0 + oracle),
"cell {cell}: slicer {got} longer than shell {oracle}"
);
}
}
}
#[test]
fn random_cells_match_shell() {
let mut state = 0x1234_5678_9abc_def0u64;
let mut next = || {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
let u = (state >> 33) as f64 / f64::from(1u32 << 31);
u * 6.0 - 3.0
};
for _ in 0..40 {
let mut h = [[0.0; 3]; 3];
for col in &mut h {
*col = [next() + 4.0, next(), next()];
}
h[0][0] += 3.0;
h[1][1] += 3.0;
h[2][2] += 3.0;
if invert_columns(h).is_none() {
continue;
}
let s = obtuse_superbasis(h[0], h[1], h[2]);
assert!(s.ok);
let y = [next() * 8.0, next() * 8.0, next() * 8.0];
let got = kernel::n2(closest_displacement(&s, y));
let oracle = brute(h[0], h[1], h[2], y);
assert!(
got <= oracle + 1e-6 * (1.0 + oracle),
"closest {got} longer than shell {oracle}"
);
}
}
}