use std::collections::{BTreeMap, BTreeSet};
use pounce_common::exact::{add_is_exact, is_live, mul_is_exact};
type FormId = u32;
const NO_FORM: FormId = u32::MAX;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SquareTerm<'a> {
pub weight: f64,
pub coefs: &'a [(usize, f64)],
pub constant: f64,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct QuadraticStructure {
form_sup: Vec<u32>,
form_lin: Vec<u32>,
form_grad: Vec<u32>,
form_h: Vec<u32>,
constant: Vec<f64>,
sup: Vec<u32>,
sup_ptr: Vec<u32>,
col: Vec<u32>,
val: Vec<f64>,
lin_idx: Vec<u32>,
lin_val: Vec<f64>,
grad: Vec<u32>,
h_slot: Vec<u32>,
form_sq: Vec<u32>,
sq_w: Vec<f64>,
sq_d: Vec<f64>,
sq_ptr: Vec<u32>,
sq_idx: Vec<u32>,
sq_val: Vec<f64>,
form_of_row: Vec<FormId>,
obj_form: FormId,
}
#[derive(Clone, Copy)]
struct Neumaier {
sum: f64,
comp: f64,
}
impl Neumaier {
#[inline]
fn new() -> Self {
Self {
sum: 0.0,
comp: 0.0,
}
}
#[inline]
fn add(&mut self, t: f64) {
let y = self.sum + t;
self.comp += if self.sum.abs() >= t.abs() {
(self.sum - y) + t
} else {
(t - y) + self.sum
};
self.sum = y;
}
#[inline]
fn sum(self) -> f64 {
self.sum + self.comp
}
}
fn factored_hessian(squares: &[SquareTerm<'_>]) -> Option<BTreeMap<(usize, usize), f64>> {
let mut hess: BTreeMap<(usize, usize), (f64, bool)> = BTreeMap::new();
for t in squares {
debug_assert!(
t.coefs.windows(2).all(|w| w[0].0 < w[1].0),
"SquareTerm::coefs must be ascending and duplicate-free: the \
upper-triangle walk below depends on it",
);
let s = 2.0 * t.weight;
if !mul_is_exact(2.0, t.weight, s) {
return None;
}
for (a, &(i, bi)) in t.coefs.iter().enumerate() {
for &(j, bj) in &t.coefs[a..] {
let p = s * bi;
let c = p * bj;
let product_lost =
(is_live(s) && is_live(bi) && is_live(bj) && !is_live(c)) || !c.is_finite();
if product_lost {
return None;
}
let slot = hess.entry((i, j)).or_insert((0.0, false));
let (was, carried) = *slot;
let v = was + c;
let inexact = carried || !add_is_exact(was, c, v);
if was != 0.0 && !is_live(v) && inexact {
return None;
}
*slot = (v, inexact);
}
}
}
Some(
hess.into_iter()
.filter(|(_, (c, _))| is_live(*c))
.map(|(k, (c, _))| (k, c))
.collect(),
)
}
impl QuadraticStructure {
pub fn new(m: usize) -> Self {
QuadraticStructure {
form_sup: vec![0],
form_lin: vec![0],
form_grad: vec![0],
form_h: vec![0],
form_sq: vec![0],
sup_ptr: vec![0],
sq_ptr: vec![0],
form_of_row: vec![NO_FORM; m],
obj_form: NO_FORM,
..Self::default()
}
}
pub fn push_form(
&mut self,
hess: &BTreeMap<(usize, usize), f64>,
lin: &[(usize, f64)],
constant: f64,
) -> FormId {
self.push_matrix(hess);
self.push_linear(lin);
let f = self.constant.len();
let hs = &self.sup[self.form_sup[f] as usize..];
let ls = &self.lin_idx[self.form_lin[f] as usize..];
let (mut a, mut b) = (0usize, 0usize);
while a < hs.len() || b < ls.len() {
let take = match (hs.get(a), ls.get(b)) {
(Some(&x), Some(&y)) => {
if x < y {
a += 1;
x
} else if y < x {
b += 1;
y
} else {
a += 1;
b += 1;
x
}
}
(Some(&x), None) => {
a += 1;
x
}
(None, Some(&y)) => {
b += 1;
y
}
(None, None) => unreachable!("loop guard"),
};
self.grad.push(take);
}
self.form_grad.push(self.grad.len() as u32);
self.form_sq.push(self.sq_w.len() as u32);
self.finish_form(constant)
}
pub fn push_factored_form(
&mut self,
squares: &[SquareTerm<'_>],
lin: &[(usize, f64)],
constant: f64,
) -> Option<FormId> {
let hess = factored_hessian(squares)?;
self.push_matrix(&hess);
self.push_linear(lin);
let f = self.constant.len();
let mut sup: BTreeSet<u32> = squares
.iter()
.flat_map(|t| t.coefs.iter().map(|&(i, _)| i as u32))
.collect();
sup.extend(self.lin_idx[self.form_lin[f] as usize..].iter().copied());
self.grad.extend(sup);
self.form_grad.push(self.grad.len() as u32);
for t in squares {
self.sq_w.push(t.weight);
self.sq_d.push(t.constant);
for &(i, c) in t.coefs {
self.sq_idx.push(i as u32);
self.sq_val.push(c);
}
self.sq_ptr.push(self.sq_idx.len() as u32);
}
self.form_sq.push(self.sq_w.len() as u32);
Some(self.finish_form(constant))
}
fn push_matrix(&mut self, hess: &BTreeMap<(usize, usize), f64>) {
let mut rows: BTreeMap<u32, BTreeMap<u32, f64>> = BTreeMap::new();
for (&(i, j), &v) in hess {
debug_assert!(
i <= j,
"a form's Hessian is the upper triangle, got ({i}, {j})"
);
if v == 0.0 {
continue;
}
rows.entry(i as u32).or_default().insert(j as u32, v);
if i != j {
rows.entry(j as u32).or_default().insert(i as u32, v);
}
}
for (&r, cols) in &rows {
self.sup.push(r);
for (&c, &v) in cols {
self.col.push(c);
self.val.push(v);
}
self.sup_ptr.push(self.col.len() as u32);
}
self.form_sup.push(self.sup.len() as u32);
}
fn push_linear(&mut self, lin: &[(usize, f64)]) {
for &(i, c) in lin {
if c == 0.0 {
continue;
}
self.lin_idx.push(i as u32);
self.lin_val.push(c);
}
self.form_lin.push(self.lin_idx.len() as u32);
}
fn finish_form(&mut self, constant: f64) -> FormId {
let f = self.constant.len();
let n_lower = self.lower_triangle(f as FormId).count();
self.h_slot.resize(self.h_slot.len() + n_lower, NO_FORM);
self.form_h.push(self.h_slot.len() as u32);
self.constant.push(constant);
f as FormId
}
pub fn assign_row(&mut self, i: usize, f: FormId) {
self.form_of_row[i] = f;
}
pub fn assign_objective(&mut self, f: FormId) {
self.obj_form = f;
}
pub fn is_empty(&self) -> bool {
self.constant.is_empty()
}
pub fn len(&self) -> usize {
self.constant.len()
}
pub fn row_form(&self, i: usize) -> Option<FormId> {
match self.form_of_row.get(i).copied() {
Some(f) if f != NO_FORM => Some(f),
_ => None,
}
}
pub fn objective_form(&self) -> Option<FormId> {
(self.obj_form != NO_FORM).then_some(self.obj_form)
}
pub fn gradient_support(&self, f: FormId) -> &[u32] {
let f = f as usize;
&self.grad[self.form_grad[f] as usize..self.form_grad[f + 1] as usize]
}
pub fn lower_triangle(&self, f: FormId) -> impl Iterator<Item = (u32, u32, f64)> + '_ {
let f = f as usize;
let (lo, hi) = (self.form_sup[f] as usize, self.form_sup[f + 1] as usize);
(lo..hi).flat_map(move |k| {
let r = self.sup[k];
let (a, b) = (self.sup_ptr[k] as usize, self.sup_ptr[k + 1] as usize);
(a..b).filter_map(move |e| {
let c = self.col[e];
(c <= r).then(|| (r, c, self.val[e]))
})
})
}
pub fn bind_slots(&mut self, mut lookup: impl FnMut(u32, u32) -> usize) {
let mut k = 0usize;
for f in 0..self.constant.len() as FormId {
let entries: Vec<(u32, u32)> = self.lower_triangle(f).map(|(r, c, _)| (r, c)).collect();
for (r, c) in entries {
self.h_slot[k] = lookup(r, c) as u32;
k += 1;
}
}
debug_assert_eq!(k, self.h_slot.len(), "every entry gets a slot");
}
pub fn value(&self, f: FormId, x: &[f64]) -> f64 {
let fi = f as usize;
let quad = match self.squares_of(fi) {
Some(terms) => {
let mut acc = Neumaier::new();
for k in terms {
let l = self.affine(k, x);
acc.add(self.sq_w[k] * (l * l));
}
acc.sum()
}
None => {
let (lo, hi) = (self.form_sup[fi] as usize, self.form_sup[fi + 1] as usize);
let mut quad = Neumaier::new();
for k in lo..hi {
let r = self.sup[k] as usize;
let (a, b) = (self.sup_ptr[k] as usize, self.sup_ptr[k + 1] as usize);
let mut t = 0.0;
for e in a..b {
t += self.val[e] * x[self.col[e] as usize];
}
quad.add(x[r] * t);
}
0.5 * quad.sum()
}
};
let mut lin = Neumaier::new();
for e in self.form_lin[fi] as usize..self.form_lin[fi + 1] as usize {
lin.add(self.lin_val[e] * x[self.lin_idx[e] as usize]);
}
quad + lin.sum() + self.constant[fi]
}
fn squares_of(&self, fi: usize) -> Option<std::ops::Range<usize>> {
let (lo, hi) = (self.form_sq[fi] as usize, self.form_sq[fi + 1] as usize);
(lo != hi).then_some(lo..hi)
}
fn affine(&self, k: usize, x: &[f64]) -> f64 {
let mut l = self.sq_d[k];
for e in self.sq_ptr[k] as usize..self.sq_ptr[k + 1] as usize {
l += self.sq_val[e] * x[self.sq_idx[e] as usize];
}
l
}
pub fn add_gradient(&self, f: FormId, x: &[f64], w: f64, out: &mut [f64]) {
let fi = f as usize;
match self.squares_of(fi) {
Some(terms) => {
for k in terms {
let d = 2.0 * self.sq_w[k] * self.affine(k, x);
for e in self.sq_ptr[k] as usize..self.sq_ptr[k + 1] as usize {
out[self.sq_idx[e] as usize] += w * d * self.sq_val[e];
}
}
}
None => {
let (lo, hi) = (self.form_sup[fi] as usize, self.form_sup[fi + 1] as usize);
for k in lo..hi {
let r = self.sup[k] as usize;
let (a, b) = (self.sup_ptr[k] as usize, self.sup_ptr[k + 1] as usize);
let mut t = 0.0;
for e in a..b {
t += self.val[e] * x[self.col[e] as usize];
}
out[r] += w * t;
}
}
}
for e in self.form_lin[fi] as usize..self.form_lin[fi + 1] as usize {
out[self.lin_idx[e] as usize] += w * self.lin_val[e];
}
}
pub fn accumulate_hessian(&self, f: FormId, w: f64, values: &mut [f64]) {
let fi = f as usize;
let base = self.form_h[fi] as usize;
for (k, (_, _, v)) in self.lower_triangle(f).enumerate() {
let slot = self.h_slot[base + k] as usize;
values[slot] += w * v;
}
}
pub fn add_hessian_vector(&self, f: FormId, v: &[f64], w: f64, out: &mut [f64]) {
let fi = f as usize;
let (lo, hi) = (self.form_sup[fi] as usize, self.form_sup[fi + 1] as usize);
for k in lo..hi {
let r = self.sup[k] as usize;
let (a, b) = (self.sup_ptr[k] as usize, self.sup_ptr[k + 1] as usize);
let mut t = 0.0;
for e in a..b {
t += self.val[e] * v[self.col[e] as usize];
}
out[r] += w * t;
}
}
pub fn stored_entries(&self) -> usize {
self.val.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> (QuadraticStructure, FormId) {
let mut h = BTreeMap::new();
h.insert((0, 0), 6.0);
h.insert((0, 1), 5.0);
let mut qs = QuadraticStructure::new(1);
let f = qs.push_form(&h, &[(0, -2.0)], 7.0);
(qs, f)
}
#[test]
fn value_matches_the_polynomial() {
let (qs, f) = sample();
let x = [2.0, -3.0];
assert_eq!(qs.value(f, &x), -15.0);
}
#[test]
fn gradient_is_hx_plus_a() {
let (qs, f) = sample();
let x = [2.0, -3.0];
let mut g = [0.0; 2];
qs.add_gradient(f, &x, 1.0, &mut g);
assert_eq!(g, [-5.0, 10.0]);
qs.add_gradient(f, &x, 2.0, &mut g);
assert_eq!(g, [-15.0, 30.0]);
}
#[test]
fn the_diagonal_is_a_hessian_entry_not_a_polynomial_coefficient() {
let mut h = BTreeMap::new();
h.insert((0, 0), 6.0);
let mut qs = QuadraticStructure::new(0);
let f = qs.push_form(&h, &[], 0.0);
assert_eq!(qs.value(f, &[1.0]), 3.0);
let mut g = [0.0];
qs.add_gradient(f, &[1.0], 1.0, &mut g);
assert_eq!(g, [6.0]);
}
#[test]
fn lower_triangle_is_ascending_and_omits_the_upper_half() {
let (qs, f) = sample();
let got: Vec<(u32, u32, f64)> = qs.lower_triangle(f).collect();
assert_eq!(got, vec![(0, 0, 6.0), (1, 0, 5.0)]);
}
#[test]
fn hessian_scatters_through_the_bound_slots() {
let (mut qs, f) = sample();
let pattern = [(1u32, 0u32), (0, 0)];
qs.bind_slots(|r, c| {
pattern
.iter()
.position(|&p| p == (r, c))
.expect("entry in pattern")
});
let mut values = [0.0; 2];
qs.accumulate_hessian(f, 2.0, &mut values);
assert_eq!(values, [10.0, 12.0]);
}
#[test]
fn hessian_vector_product_agrees_with_the_dense_matrix() {
let (qs, f) = sample();
let v = [1.0, 2.0];
let mut out = [0.0; 2];
qs.add_hessian_vector(f, &v, 1.0, &mut out);
assert_eq!(out, [16.0, 5.0]);
}
#[test]
fn gradient_support_merges_the_two_sides() {
let mut h = BTreeMap::new();
h.insert((1, 3), 1.0);
let mut qs = QuadraticStructure::new(0);
let f = qs.push_form(&h, &[(0, 1.0), (3, 1.0)], 0.0);
assert_eq!(qs.gradient_support(f), &[0, 1, 3]);
}
#[test]
fn zero_coefficients_are_not_stored() {
let mut h = BTreeMap::new();
h.insert((0, 0), 0.0);
h.insert((0, 1), 4.0);
let mut qs = QuadraticStructure::new(0);
let f = qs.push_form(&h, &[(2, 0.0)], 0.0);
assert_eq!(qs.lower_triangle(f).count(), 1);
assert_eq!(qs.gradient_support(f), &[0, 1]);
}
#[test]
fn the_outer_row_sum_does_not_lose_small_terms() {
let mut h = BTreeMap::new();
h.insert((0, 0), 1e18);
for r in 1..=100usize {
h.insert((r, r), 2.0);
}
h.insert((101, 101), -1e18);
let mut qs = QuadraticStructure::new(0);
let f = qs.push_form(&h, &[], 0.0);
let x = [1.0; 102];
assert_eq!(qs.value(f, &x), 100.0);
}
#[test]
fn the_linear_sum_does_not_lose_small_terms() {
let mut lin = vec![(0usize, 1e18)];
lin.extend((1..=100usize).map(|i| (i, 2.0)));
lin.push((101, -1e18));
let mut qs = QuadraticStructure::new(0);
let f = qs.push_form(&BTreeMap::new(), &lin, 0.0);
let x = [1.0; 102];
assert_eq!(qs.value(f, &x), 200.0);
}
#[test]
fn an_empty_structure_reports_itself_as_one() {
let qs = QuadraticStructure::new(4);
assert!(qs.is_empty());
assert_eq!(qs.row_form(2), None);
assert_eq!(qs.objective_form(), None);
}
#[test]
fn a_factored_form_squares_the_residual_instead_of_expanding_it() {
let coefs = [(0usize, 1.0)];
let mut qs = QuadraticStructure::new(0);
let f = qs
.push_factored_form(
&[SquareTerm {
weight: 1.0,
coefs: &coefs,
constant: -500_000.0,
}],
&[],
0.0,
)
.expect("admitted");
let x = [500_000.0 + 1e-4];
let r = x[0] - 500_000.0;
assert_eq!(qs.value(f, &x), r * r);
let mut g = [0.0];
qs.add_gradient(f, &x, 1.0, &mut g);
assert_eq!(g, [2.0 * r]);
assert_eq!(qs.lower_triangle(f).collect::<Vec<_>>(), vec![(0, 0, 2.0)]);
}
#[test]
fn a_multi_term_factored_form_agrees_with_its_polynomial() {
let a = [(0usize, 1.0), (1usize, -1.0)];
let b = [(1usize, 1.0)];
let mut qs = QuadraticStructure::new(0);
let f = qs
.push_factored_form(
&[
SquareTerm {
weight: 2.0,
coefs: &a,
constant: 1.0,
},
SquareTerm {
weight: -1.0,
coefs: &b,
constant: 3.0,
},
],
&[(0, 5.0)],
7.0,
)
.expect("admitted");
let x = [1.5, -0.25];
let l1 = x[0] - x[1] + 1.0;
let l2 = x[1] + 3.0;
assert_eq!(qs.value(f, &x), 2.0 * l1 * l1 - l2 * l2 + 5.0 * x[0] + 7.0);
let mut g = [0.0; 2];
qs.add_gradient(f, &x, 1.0, &mut g);
assert_eq!(g[0], 4.0 * l1 + 5.0);
assert_eq!(g[1], -4.0 * l1 - 2.0 * l2);
assert_eq!(
qs.lower_triangle(f).collect::<Vec<_>>(),
vec![(0, 0, 4.0), (1, 0, -4.0), (1, 1, 2.0)]
);
}
#[test]
fn the_gradient_support_survives_a_cancelling_hessian() {
let p = [(0usize, 1.0), (1usize, 1.0)];
let m = [(0usize, 1.0), (1usize, -1.0)];
let mut qs = QuadraticStructure::new(0);
let f = qs
.push_factored_form(
&[
SquareTerm {
weight: 1.0,
coefs: &p,
constant: 0.0,
},
SquareTerm {
weight: -1.0,
coefs: &m,
constant: 0.0,
},
],
&[],
0.0,
)
.expect("admitted");
assert_eq!(qs.gradient_support(f), &[0, 1]);
assert_eq!(qs.lower_triangle(f).collect::<Vec<_>>(), vec![(1, 0, 4.0)]);
let x = [2.0, 3.0];
assert_eq!(qs.value(f, &x), 4.0 * x[0] * x[1]);
let mut g = [0.0; 2];
qs.add_gradient(f, &x, 1.0, &mut g);
assert_eq!(g, [4.0 * x[1], 4.0 * x[0]]);
}
#[test]
fn a_constant_square_is_value_only() {
let mut qs = QuadraticStructure::new(0);
let coefs = [(0usize, 1.0)];
let f = qs
.push_factored_form(
&[
SquareTerm {
weight: 3.0,
coefs: &[],
constant: 2.0,
},
SquareTerm {
weight: 1.0,
coefs: &coefs,
constant: 0.0,
},
],
&[],
0.0,
)
.expect("admitted");
assert_eq!(qs.gradient_support(f), &[0]);
assert_eq!(qs.value(f, &[4.0]), 3.0 * 4.0 + 16.0);
let mut g = [0.0];
qs.add_gradient(f, &[4.0], 1.0, &mut g);
assert_eq!(g, [8.0]);
}
#[test]
fn the_two_kinds_of_form_coexist() {
let (mut qs, expanded) = sample();
let coefs = [(0usize, 1.0)];
let factored = qs
.push_factored_form(
&[SquareTerm {
weight: 1.0,
coefs: &coefs,
constant: -1.0,
}],
&[],
0.0,
)
.expect("admitted");
let x = [2.0, -3.0];
assert_eq!(qs.value(expanded, &x), -15.0);
assert_eq!(qs.value(factored, &x), 1.0);
let plain = qs.push_form(&BTreeMap::from([((1, 1), 2.0)]), &[], 0.0);
assert_eq!(qs.value(plain, &x), 9.0);
assert_eq!(qs.value(factored, &x), 1.0);
let pattern = [(0u32, 0u32), (1, 0), (1, 1)];
qs.bind_slots(|r, c| {
pattern
.iter()
.position(|&p| p == (r, c))
.expect("in pattern")
});
let mut values = [0.0; 3];
qs.accumulate_hessian(factored, 1.0, &mut values);
assert_eq!(values, [2.0, 0.0, 0.0]);
qs.accumulate_hessian(plain, 1.0, &mut values);
assert_eq!(values, [2.0, 0.0, 2.0]);
}
#[test]
fn a_factored_forms_hessian_vector_product_agrees_with_its_matrix() {
let a = [(0usize, 1.0), (1usize, -1.0)];
let mut qs = QuadraticStructure::new(0);
let f = qs
.push_factored_form(
&[SquareTerm {
weight: 1.0,
coefs: &a,
constant: 4.0,
}],
&[],
0.0,
)
.expect("admitted");
let mut out = [0.0; 2];
qs.add_hessian_vector(f, &[1.0, 2.0], 1.0, &mut out);
assert_eq!(out, [-2.0, 2.0]);
}
#[test]
fn the_weight_multiplies_the_square_not_the_residual() {
let coefs = [(0usize, 1.0)];
let mut qs = QuadraticStructure::new(0);
let f = qs
.push_factored_form(
&[SquareTerm {
weight: 7.0,
coefs: &coefs,
constant: 0.0,
}],
&[],
0.0,
)
.expect("admitted");
let x = [1.1];
assert_eq!(qs.value(f, &x), 7.0 * (x[0] * x[0]));
assert_eq!(qs.value(f, &x), 8.47);
assert_ne!((7.0 * x[0]) * x[0], 8.47);
}
#[test]
fn the_factored_outer_sum_does_not_lose_small_terms_either() {
let coefs: Vec<[(usize, f64); 1]> = (0..102).map(|i| [(i, 1.0)]).collect();
let mut terms: Vec<SquareTerm<'_>> = Vec::new();
for (i, c) in coefs.iter().enumerate() {
let weight = match i {
0 => 1e18,
101 => -1e18,
_ => 2.0,
};
terms.push(SquareTerm {
weight,
coefs: c,
constant: 0.0,
});
}
let mut qs = QuadraticStructure::new(0);
let f = qs.push_factored_form(&terms, &[], 0.0).expect("admitted");
let x = [1.0; 102];
assert_eq!(qs.value(f, &x), 200.0);
}
#[test]
fn a_factored_form_that_loses_a_hessian_entry_is_refused() {
let big = (1u64 << 27) as f64;
let wide = [(0usize, big)];
let unit = [(0usize, 1.0), (1usize, 1.0)];
let terms = [
SquareTerm {
weight: 1.0,
coefs: &wide,
constant: 0.0,
},
SquareTerm {
weight: 1.0,
coefs: &unit,
constant: 0.0,
},
SquareTerm {
weight: -1.0,
coefs: &wide,
constant: 0.0,
},
];
let mut qs = QuadraticStructure::new(0);
assert!(
qs.push_factored_form(&terms, &[], 0.0).is_none(),
"a form that dropped a Hessian entry was admitted",
);
assert!(qs.is_empty(), "a refused form left state behind");
}
#[test]
fn the_refusal_does_not_need_a_negative_weight() {
let a = [(0usize, 1e9), (1usize, 1e9)];
let b = [(0usize, 1.0), (1usize, 1.0)];
let c = [(0usize, 1e9), (1usize, -1e9)];
let terms = [
SquareTerm {
weight: 1.0,
coefs: &a,
constant: 0.0,
},
SquareTerm {
weight: 1.0,
coefs: &b,
constant: 0.0,
},
SquareTerm {
weight: 1.0,
coefs: &c,
constant: 0.0,
},
];
let mut qs = QuadraticStructure::new(0);
assert!(qs.push_factored_form(&terms, &[], 0.0).is_none());
}
#[test]
fn an_exactly_cancelling_factored_form_is_still_admitted() {
let plus = [(0usize, 1.0), (1usize, 1.0)];
let minus = [(0usize, 1.0), (1usize, -1.0)];
let terms = [
SquareTerm {
weight: 1.0,
coefs: &plus,
constant: 0.0,
},
SquareTerm {
weight: -1.0,
coefs: &minus,
constant: 0.0,
},
];
let mut qs = QuadraticStructure::new(0);
let f = qs
.push_factored_form(&terms, &[], 0.0)
.expect("an exact cancellation is not a loss");
let x = [2.0, 3.0];
assert_eq!(qs.value(f, &x), 24.0);
assert_eq!(qs.gradient_support(f), &[0, 1]);
}
#[test]
fn a_weight_whose_double_overflows_is_refused() {
let coefs = [(0usize, 1.0)];
let terms = [SquareTerm {
weight: 1e308,
coefs: &coefs,
constant: 0.0,
}];
let mut qs = QuadraticStructure::new(0);
assert!(qs.push_factored_form(&terms, &[], 0.0).is_none());
}
#[test]
fn a_product_that_underflows_to_zero_is_refused() {
let coefs = [(0usize, 1e-200), (1usize, 1e-200)];
let terms = [SquareTerm {
weight: 1.0,
coefs: &coefs,
constant: 0.0,
}];
let mut qs = QuadraticStructure::new(0);
assert!(
qs.push_factored_form(&terms, &[], 0.0).is_none(),
"2·1·1e-200·1e-200 underflows to zero out of two nonzero \
factors, so the (0,1) entry would be missing from a map the \
tape still populates",
);
}
#[test]
fn a_subnormal_but_nonzero_product_is_still_admitted() {
let coefs = [(0usize, 1e-160), (1usize, 1e-160)];
let terms = [SquareTerm {
weight: 1.0,
coefs: &coefs,
constant: 0.0,
}];
let mut qs = QuadraticStructure::new(0);
assert!(
qs.push_factored_form(&terms, &[], 0.0).is_some(),
"a subnormal product is representable; refusing it would send \
ordinary tiny-coefficient models to the tape for nothing",
);
}
}