use crate::differences::{DiffGraph, DiffNode};
use crate::dyadic::Dyadic;
use crate::error::Error;
use crate::graph::NodeId;
use crate::parse::Position;
const OVERFLOW_EXPONENT: i64 = 1022;
pub(crate) const MAX_DEGREE: u32 = 1022;
pub(crate) const MAX_COEFFICIENT_BITS: u64 = 1 << 26;
#[derive(Clone, Debug)]
pub(crate) struct NodeBound {
pub(crate) degree: u32,
pub(crate) m: Dyadic,
pub(crate) c: Dyadic,
pub(crate) alpha: Dyadic,
pub(crate) beta: Dyadic,
}
#[derive(Clone, Debug)]
pub struct FilterBound {
pub degree: u32,
pub epsilon_in_u: String,
pub kappa_exact: String,
pub products: u32,
nodes: Vec<NodeBound>,
root: NodeId,
d_min_exponent: i64,
d_max_exponent: i64,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FilterConstants {
pub degree: u32,
pub epsilon: f64,
pub kappa: f64,
pub d_min: f64,
pub d_max: f64,
}
pub fn filter_bound(
graph: &DiffGraph,
file: &str,
position: Position,
) -> Result<FilterBound, Error> {
if let Some(°ree) = graph.degrees.iter().filter(|&&g| g > MAX_DEGREE).max() {
return Err(Error::at(file, position, overflow_message(degree)));
}
if coefficient_bits(graph) > MAX_COEFFICIENT_BITS {
return Err(Error::at(
file,
position,
format!(
"the expression is too large for the error bound of the filter: its exact \
coefficients would take more than 2^{} bits in all; a long sum written as \
one chain is the usual cause, and the same terms summed in a balanced \
tree with `let` need far fewer",
MAX_COEFFICIENT_BITS.ilog2()
),
));
}
let u = Dyadic::pow2(-53);
let one = Dyadic::integer(1);
let one_plus_u = &one + &u;
let mut nodes: Vec<NodeBound> = Vec::with_capacity(graph.nodes.len());
for (id, node) in graph.nodes.iter().enumerate() {
let bound = match *node {
DiffNode::Leaf { .. } => NodeBound {
degree: 1,
m: one_plus_u.clone(),
c: one.clone(),
alpha: u.clone(),
beta: one.clone(),
},
DiffNode::Neg(x) => nodes[x].clone(),
DiffNode::Add(x, y) | DiffNode::Sub(x, y) => {
let (x, y) = (&nodes[x], &nodes[y]);
let c_sum = &x.c + &y.c;
NodeBound {
degree: x.degree,
m: &x.m + &y.m,
c: &c_sum * &one_plus_u,
alpha: &(&x.alpha + &y.alpha) + &(&u * &c_sum),
beta: &x.beta + &y.beta,
}
}
DiffNode::Mul(x, y) => {
let (x, y) = (&nodes[x], &nodes[y]);
let c_product = &x.c * &y.c;
NodeBound {
degree: x.degree + y.degree,
m: &x.m * &y.m,
c: &c_product * &one_plus_u,
alpha: &(&(&x.c * &y.alpha) + &(&y.m * &x.alpha)) + &(&u * &c_product),
beta: &(&x.c * &y.beta) + &(&y.m * &x.beta),
}
}
};
debug_assert_eq!(bound.degree, graph.degrees[id]);
nodes.push(bound);
}
let products = graph
.nodes
.iter()
.filter(|n| matches!(n, DiffNode::Mul(..)))
.count();
let root = &nodes[graph.root];
let degree = root.degree;
let products = u32::try_from(products).expect("fewer than 2^32 products");
let d_max_exponent = d_max_exponent(&nodes)
.map_err(|degree| Error::at(file, position, overflow_message(degree)))?;
let d_min_exponent = d_min_exponent(&nodes, root, products).ok_or_else(|| {
Error::at(
file,
position,
format!(
"the degree {degree} is too high for the floating-point filter: \
no scale of the inputs below 1 keeps its error bound clear of underflow"
),
)
})?;
Ok(FilterBound {
degree,
epsilon_in_u: (&root.alpha * &Dyadic::pow2(53)).fraction(),
kappa_exact: root.beta.fraction(),
products,
nodes,
root: graph.root,
d_min_exponent,
d_max_exponent,
})
}
fn coefficient_bits(graph: &DiffGraph) -> u64 {
let mut bits: Vec<u64> = Vec::with_capacity(graph.nodes.len());
for node in &graph.nodes {
bits.push(match *node {
DiffNode::Leaf { .. } => 55,
DiffNode::Neg(x) => bits[x],
DiffNode::Add(x, y) | DiffNode::Sub(x, y) => bits[x].max(bits[y]).saturating_add(55),
DiffNode::Mul(x, y) => bits[x].saturating_add(bits[y]).saturating_add(55),
});
}
bits.iter().fold(0u64, |total, &b| total.saturating_add(b))
}
fn overflow_message(degree: u32) -> String {
format!(
"the degree {degree} is too high for the floating-point filter: \
no scale of the inputs above 1 keeps every computed value below 2^{OVERFLOW_EXPONENT}"
)
}
impl FilterBound {
pub fn epsilon_is_u_times(&self, n: u64) -> bool {
let target = &Dyadic::integer(n) * &Dyadic::pow2(-53);
close_above(&self.nodes[self.root].alpha, &target)
}
pub fn kappa_is(&self, n: u64) -> bool {
close_above(&self.nodes[self.root].beta, &Dyadic::integer(n))
}
pub fn constants(&self) -> FilterConstants {
let inflate = &Dyadic::integer(1) + &Dyadic::pow2(-30);
let root = &self.nodes[self.root];
FilterConstants {
degree: self.degree,
epsilon: (&root.alpha * &inflate).round_up(),
kappa: (&root.beta * &inflate).round_up(),
d_min: Dyadic::pow2(self.d_min_exponent).round_up(),
d_max: Dyadic::pow2(self.d_max_exponent).round_up(),
}
}
}
fn d_max_exponent(nodes: &[NodeBound]) -> Result<i64, u32> {
let (j, degree) = nodes
.iter()
.map(|n| {
let j = (OVERFLOW_EXPONENT - n.c.ceil_log2()).div_euclid(i64::from(n.degree));
(j, n.degree)
})
.min()
.expect("a graph has a root");
if j >= 1 { Ok(j) } else { Err(degree) }
}
fn d_min_exponent(nodes: &[NodeBound], root: &NodeBound, products: u32) -> Option<i64> {
let epsilon = &root.alpha;
let c_max = nodes
.iter()
.map(|n| &n.c)
.max()
.expect("a graph has a root");
let factor = Dyadic::integer(u64::from(products) * u64::from(root.degree));
let underflow = &(&factor * c_max) * &Dyadic::pow2(-1074 + 32);
let normal = Dyadic::pow2(-1021);
let k = i64::from(root.degree);
let covers = |j: i64| {
let scaled = epsilon * &Dyadic::pow2(j * k);
scaled >= underflow && scaled >= normal
};
if !covers(-1) {
return None;
}
let mut j = -1;
while covers(j - 1) {
j -= 1;
}
Some(j)
}
fn close_above(value: &Dyadic, target: &Dyadic) -> bool {
let limit = target * &(&Dyadic::integer(1) + &Dyadic::pow2(-40));
target <= value && *value < limit
}
#[cfg(test)]
mod tests;