use crate::differences::DiffGraph;
use crate::dyadic::Dyadic;
use crate::error::Error;
use crate::graph::Graph;
use crate::parse::Position;
use super::shape::{MAX_LENGTH, Op, difference_ops, raw_ops};
use super::{ExactPlan, ExactRange, OVERFLOW_EXPONENT, domain, first_failure};
pub const MAX_COORD_COMPONENTS: usize = 16;
const MAX_OPERATIONS: u128 = 1 << 50;
pub fn exact_plan_spans_translated(
graph: &DiffGraph,
components: usize,
file: &str,
position: Position,
operand_min: f64,
operand_max: f64,
) -> Result<ExactPlan, Error> {
let arena = Arena::new(difference_ops(graph), components, true);
let (low, high) = domain(operand_min, operand_max);
arena
.plan(low, high)
.map_err(|message| Error::at(file, position, message))
}
pub fn exact_plan_spans_untranslated(
graph: &Graph,
components: usize,
file: &str,
position: Position,
operand_min: f64,
operand_max: f64,
) -> Result<ExactPlan, Error> {
let (low, high) = domain(operand_min, operand_max);
raw_ops(graph)
.and_then(|ops| Arena::new(ops, components, false).plan(low, high))
.map_err(|message| Error::at(file, position, message))
}
struct Arena {
ops: Vec<Op>,
components: u128,
translated: bool,
}
impl Arena {
fn new(ops: Vec<Op>, components: usize, translated: bool) -> Arena {
assert!(components > 0, "a coordinate has at least one component");
let components = u128::try_from(components).expect("a usize fits in u128");
Arena {
ops,
components,
translated,
}
}
fn plan(&self, low: i64, high: i64) -> Result<ExactPlan, String> {
let min = first_failure(low, high, |m| !self.grids_fit(m, low));
let at = min.min(high);
let max = first_failure(at, high, |big_m| self.fit(at, big_m, high).is_some()) - 1;
if min > high || max < min {
let degree = self.operand_degree();
return Err(format!(
"no input range keeps every exact product inside the operand domain \
[2^{low}, 2^{high}] of the exact arithmetic for coordinates of up to {} \
components: a product operand of degree {degree} needs nonzero components \
of magnitude at least 2^{min}, and keeping every operand within the domain \
and every value below 2^{OVERFLOW_EXPONENT} needs components of at most \
2^{max}",
self.components
));
}
let lengths = self.fit(min, max, high).expect("the range fits");
let too_large = || {
"the expression is too large for exact evaluation: its temporaries \
would need 2^32 components or more in all"
.to_string()
};
let total = lengths
.iter()
.try_fold(0u128, |sum, &n| sum.checked_add(n))
.filter(|&total| total < u128::from(MAX_LENGTH))
.ok_or_else(too_large)?;
Ok(ExactPlan {
range: ExactRange {
min: Dyadic::pow2(min).round_up(),
max: Dyadic::pow2(max).round_up(),
},
lengths: lengths
.iter()
.map(|&n| usize::try_from(n).expect("below 2^32"))
.collect(),
total: usize::try_from(total).expect("below 2^32"),
})
}
fn operand_degree(&self) -> u64 {
let mut degrees: Vec<u64> = Vec::with_capacity(self.ops.len());
let mut highest = 0;
for op in &self.ops {
let degree = match *op {
Op::Leaf => 1,
Op::Constant(_) => 0,
Op::Neg(x) => degrees[x],
Op::Sum(x, y) => degrees[x].max(degrees[y]),
Op::Product(x, y) => {
highest = highest.max(degrees[x]).max(degrees[y]);
degrees[x].saturating_add(degrees[y])
}
};
degrees.push(degree);
}
highest
}
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.saturating_add(gy)),
};
grids.push(grid);
}
grids
}
fn grids_fit(&self, m: i64, low: i64) -> bool {
let grids = self.grids(m);
self.ops.iter().all(|op| match *op {
Op::Product(x, y) => [x, y]
.into_iter()
.all(|n| grids[n].is_none_or(|g| g >= low)),
_ => true,
})
}
fn fit(&self, m: i64, big_m: i64, high: i64) -> Option<Vec<u128>> {
self.fit_with_counts(m, big_m, high)
.map(|(lengths, _)| lengths)
}
fn fit_with_counts(&self, m: i64, big_m: i64, high: i64) -> Option<(Vec<u128>, Vec<u128>)> {
let grids = self.grids(m);
let (leaf_length, leaf_operations) = if self.translated {
(2 * self.components, 4 * self.components)
} else {
(self.components, 0)
};
let leaf = &Dyadic::integer(leaf_length) * &Dyadic::pow2(big_m);
let zero = Dyadic::integer(0);
let overflow = Dyadic::pow2(OVERFLOW_EXPONENT);
let operand_max = Dyadic::pow2(high);
let six_u = Dyadic::new(6, -53);
let one = Dyadic::integer(1);
let n = self.ops.len();
let mut magnitudes: Vec<Dyadic> = Vec::with_capacity(n);
let mut bounds: Vec<Dyadic> = Vec::with_capacity(n);
let mut lengths: Vec<u128> = Vec::with_capacity(n);
let mut operations: Vec<u128> = Vec::with_capacity(n);
for (node, op) in self.ops.iter().enumerate() {
let magnitude = match *op {
Op::Leaf => leaf.clone(),
Op::Constant(c) => Dyadic::integer(c.unsigned_abs()),
Op::Neg(x) => magnitudes[x].clone(),
Op::Sum(x, y) => &magnitudes[x] + &magnitudes[y],
Op::Product(x, y) => {
if !(bounds[x] <= operand_max && bounds[y] <= operand_max) {
return None;
}
&magnitudes[x] * &magnitudes[y]
}
};
if magnitude >= overflow {
return None;
}
let structural = match *op {
Op::Leaf => leaf_length,
Op::Constant(c) => u128::from(c != 0),
Op::Neg(x) => lengths[x],
Op::Sum(x, y) => lengths[x].saturating_add(lengths[y]),
Op::Product(x, y) => lengths[x].saturating_mul(lengths[y]).saturating_mul(2),
};
let length = match grids[node] {
None => 0,
Some(_) if magnitude == zero => 0,
Some(grid) => {
let bits = magnitude.ceil_log2().saturating_sub(grid).saturating_add(1);
structural.min(u128::try_from(bits).unwrap_or(0))
}
};
let own = match *op {
Op::Leaf => leaf_operations,
Op::Constant(_) => 0,
Op::Neg(x) => lengths[x].saturating_mul(2),
Op::Sum(x, y) => lengths[x].saturating_add(lengths[y]).saturating_mul(2),
Op::Product(x, y) => {
let (s, t) = (lengths[x].min(lengths[y]), lengths[x].max(lengths[y]));
s.saturating_mul(t.saturating_mul(7).saturating_add(length.saturating_mul(2)))
}
};
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 None;
}
let bound = &magnitude * &(&one + &(&six_u * &Dyadic::integer(count)));
if bound >= overflow {
return None;
}
magnitudes.push(magnitude);
bounds.push(bound);
lengths.push(length);
operations.push(count);
}
Some((lengths, operations))
}
}
#[cfg(test)]
mod tests;