use crate::differences::{DiffGraph, DiffNode};
use crate::dyadic::Dyadic;
use crate::graph::{Graph, Node, NodeId};
use super::Leaves;
#[derive(Clone, Copy, Debug)]
enum Op {
Leaf,
Constant(i64),
Neg(NodeId),
Sum(NodeId, NodeId),
Product(NodeId, NodeId),
}
const MAX_OPERATIONS: u128 = 1 << 51;
pub(crate) const MAX_LENGTH: u64 = 1 << 32;
#[derive(Debug)]
pub(crate) struct Shape {
ops: Vec<Op>,
leaf_scale: i64,
pub(crate) lengths: Vec<usize>,
pub(crate) degrees: Vec<u64>,
operations: Vec<u128>,
}
impl Shape {
pub(crate) fn raw(graph: &Graph) -> Result<Self, String> {
let ops = graph
.nodes
.iter()
.map(|node| match *node {
Node::Coord { .. } => Ok(Op::Leaf),
Node::Int(n) => {
let magnitude = n.unsigned_abs();
if magnitude >> magnitude.trailing_zeros().min(63) < 1 << 53 {
Ok(Op::Constant(n))
} else {
Err(format!(
"the constant {n} is not exactly representable in f64, \
so it cannot be used in exact evaluation"
))
}
}
Node::Add(x, y) | Node::Sub(x, y) => Ok(Op::Sum(x, y)),
Node::Mul(x, y) => Ok(Op::Product(x, y)),
})
.collect::<Result<_, _>>()?;
Shape::new(ops, Leaves::One)
}
pub(crate) fn differences(graph: &DiffGraph, leaves: Leaves) -> Result<Self, String> {
let ops = graph
.nodes
.iter()
.map(|node| match *node {
DiffNode::Leaf { .. } => Op::Leaf,
DiffNode::Neg(x) => Op::Neg(x),
DiffNode::Add(x, y) | DiffNode::Sub(x, y) => Op::Sum(x, y),
DiffNode::Mul(x, y) => Op::Product(x, y),
})
.collect();
Shape::new(ops, leaves)
}
fn new(ops: Vec<Op>, leaves: Leaves) -> Result<Self, String> {
let (leaf_length, leaf_scale, leaf_operations): (u64, i64, u128) = match leaves {
Leaves::One => (1, 0, 0),
Leaves::Two => (2, 1, 1),
};
let mut lengths: Vec<u64> = Vec::with_capacity(ops.len());
let mut degrees: Vec<u64> = Vec::with_capacity(ops.len());
let mut operations: Vec<u128> = Vec::with_capacity(ops.len());
for op in &ops {
let (length, degree, own) = match *op {
Op::Leaf => (leaf_length, 1, leaf_operations),
Op::Constant(n) => (u64::from(n != 0), 0, 0),
Op::Neg(x) => (lengths[x], degrees[x], 0),
Op::Sum(x, y) => {
let length = lengths[x] + lengths[y];
(length, degrees[x].max(degrees[y]), 2 * u128::from(length))
}
Op::Product(x, y) => {
let (lx, ly) = (u128::from(lengths[x]), u128::from(lengths[y]));
let length = u64::try_from(2 * lx * ly).unwrap_or(u64::MAX);
let degree = degrees[x].saturating_add(degrees[y]);
(length, degree, 3 * lx * ly + 4 * lx * ly * (lx + ly))
}
};
if length >= MAX_LENGTH {
return Err(
"the expression is too large for exact evaluation: a temporary \
would need 2^32 components or more"
.to_string(),
);
}
let below = match *op {
Op::Leaf | Op::Constant(_) => 0,
Op::Neg(x) => operations[x],
Op::Sum(x, y) | Op::Product(x, y) => operations[x].saturating_add(operations[y]),
};
let count = below.saturating_add(own);
if count > MAX_OPERATIONS {
return Err(
"the expression is too large for exact evaluation: a temporary \
would take more than 2^51 floating-point operations"
.to_string(),
);
}
lengths.push(length);
degrees.push(degree);
operations.push(count);
}
let lengths = lengths
.into_iter()
.map(|n| usize::try_from(n).expect("a length below 2^32 fits in usize"))
.collect();
Ok(Shape {
ops,
leaf_scale,
lengths,
degrees,
operations,
})
}
pub(crate) fn operands(&self) -> impl Iterator<Item = NodeId> + '_ {
self.ops
.iter()
.filter_map(|op| match *op {
Op::Product(x, y) => Some([x, y]),
_ => None,
})
.flatten()
}
pub(crate) fn grids(&self, m: i64) -> Vec<Option<i64>> {
let mut grids: Vec<Option<i64>> = Vec::with_capacity(self.ops.len());
for op in &self.ops {
let grid = match *op {
Op::Leaf => Some(m - 52),
Op::Constant(n) => (n != 0).then(|| i64::from(n.trailing_zeros())),
Op::Neg(x) => grids[x],
Op::Sum(x, y) => match (grids[x], grids[y]) {
(Some(gx), Some(gy)) => Some(gx.min(gy)),
(grid, None) | (None, grid) => grid,
},
Op::Product(x, y) => grids[x].zip(grids[y]).map(|(gx, gy)| gx + gy),
};
grids.push(grid);
}
grids
}
pub(crate) fn magnitudes(&self, big_m: i64) -> Vec<Dyadic> {
let leaf = Dyadic::pow2(big_m + self.leaf_scale);
let mut magnitudes: Vec<Dyadic> = Vec::with_capacity(self.ops.len());
for op in &self.ops {
let magnitude = match *op {
Op::Leaf => leaf.clone(),
Op::Constant(n) => Dyadic::integer(n.unsigned_abs()),
Op::Neg(x) => magnitudes[x].clone(),
Op::Sum(x, y) => &magnitudes[x] + &magnitudes[y],
Op::Product(x, y) => &magnitudes[x] * &magnitudes[y],
};
magnitudes.push(magnitude);
}
magnitudes
}
pub(crate) fn component_bounds(&self, big_m: i64) -> Vec<Dyadic> {
let six_u = Dyadic::new(6, -53);
let one = Dyadic::integer(1);
self.magnitudes(big_m)
.iter()
.zip(&self.operations)
.map(|(magnitude, &operations)| {
let factor = &one + &(&six_u * &Dyadic::integer(operations));
magnitude * &factor
})
.collect()
}
}