use crate::differences::{DiffGraph, DiffNode};
use crate::graph::{Graph, Node, NodeId};
use crate::range::Leaves;
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum Scalar {
Coordinate { point: usize, axis: u8 },
Local(NodeId),
Constant(f64),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum Step {
Difference {
node: NodeId,
axis: u8,
minuend: usize,
subtrahend: usize,
},
NegateScalar { node: NodeId, x: Scalar },
Zero { node: NodeId },
FromF64 { node: NodeId, x: Scalar },
FromTwoDiff { node: NodeId, a: Scalar, b: Scalar },
FromTwoProduct { node: NodeId, a: Scalar, b: Scalar },
Scale { node: NodeId, e: NodeId, b: Scalar },
Product { node: NodeId, x: NodeId, y: NodeId },
Sum { node: NodeId, x: NodeId, y: NodeId },
Diff { node: NodeId, x: NodeId, y: NodeId },
Negate { node: NodeId, x: NodeId },
}
#[derive(Clone, Debug)]
pub(crate) struct Program {
pub(crate) steps: Vec<Step>,
pub(crate) capacities: Vec<usize>,
pub(crate) root: NodeId,
pub(crate) coordinates: Vec<(usize, u8)>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Value {
Scalar(Scalar),
Expansion(NodeId),
}
#[derive(Clone, Copy)]
enum Op {
Coordinate {
point: usize,
axis: u8,
},
Constant(i64),
Leaf {
axis: u8,
minuend: usize,
subtrahend: usize,
},
Neg(NodeId),
Add(NodeId, NodeId),
Sub(NodeId, NodeId),
Mul(NodeId, NodeId),
}
impl Program {
pub(crate) fn raw(graph: &Graph, lengths: &[usize]) -> Program {
let ops = graph.nodes.iter().map(|node| match *node {
Node::Coord { point, axis } => Op::Coordinate { point, axis },
Node::Int(n) => Op::Constant(n),
Node::Sub(x, y) if graph.nodes[x] == Node::Int(0) => Op::Neg(y),
Node::Add(x, y) => Op::Add(x, y),
Node::Sub(x, y) => Op::Sub(x, y),
Node::Mul(x, y) => Op::Mul(x, y),
});
build(ops.collect(), graph.root, lengths, Leaves::One)
}
pub(crate) fn differences(graph: &DiffGraph, leaves: Leaves, lengths: &[usize]) -> Program {
let ops = graph.nodes.iter().map(|node| match *node {
DiffNode::Leaf {
axis,
minuend,
subtrahend,
} => Op::Leaf {
axis,
minuend,
subtrahend,
},
DiffNode::Neg(x) => Op::Neg(x),
DiffNode::Add(x, y) => Op::Add(x, y),
DiffNode::Sub(x, y) => Op::Sub(x, y),
DiffNode::Mul(x, y) => Op::Mul(x, y),
});
build(ops.collect(), graph.root, lengths, leaves)
}
pub(crate) fn product_scratch(&self) -> (usize, usize) {
self.steps
.iter()
.filter_map(|step| match *step {
Step::Product { node, y, .. } => {
Some((2 * self.capacities[y], self.capacities[node]))
}
_ => None,
})
.fold((0, 0), |(s, a), (ns, na)| (s.max(ns), a.max(na)))
}
pub(crate) fn footprint(&self) -> usize {
let buffers: usize = self
.steps
.iter()
.filter_map(Step::expansion)
.map(|node| self.capacities[node])
.sum();
let (scaled, spare) = self.product_scratch();
buffers + scaled + spare
}
}
impl Step {
pub(crate) fn expansion(&self) -> Option<NodeId> {
match *self {
Step::Difference { .. } | Step::NegateScalar { .. } => None,
Step::Zero { node }
| Step::FromF64 { node, .. }
| Step::FromTwoDiff { node, .. }
| Step::FromTwoProduct { node, .. }
| Step::Scale { node, .. }
| Step::Product { node, .. }
| Step::Sum { node, .. }
| Step::Diff { node, .. }
| Step::Negate { node, .. } => Some(node),
}
}
}
fn build(ops: Vec<Op>, root: NodeId, lengths: &[usize], leaves: Leaves) -> Program {
let mut b = Builder {
steps: Vec::new(),
values: Vec::with_capacity(ops.len()),
expansions: vec![false; ops.len()],
capacities: lengths.to_vec(),
coordinates: Vec::new(),
};
for (node, op) in ops.iter().enumerate() {
let value = match *op {
Op::Coordinate { point, axis } => {
b.coordinate(point, axis);
Value::Scalar(Scalar::Coordinate { point, axis })
}
Op::Constant(n) => Value::Scalar(Scalar::Constant(n as f64)),
Op::Leaf {
axis,
minuend,
subtrahend,
} => match leaves {
Leaves::One => {
b.steps.push(Step::Difference {
node,
axis,
minuend,
subtrahend,
});
Value::Scalar(Scalar::Local(node))
}
Leaves::Two => {
b.coordinate(minuend, axis);
b.coordinate(subtrahend, axis);
let coordinate = |point| Scalar::Coordinate { point, axis };
b.expansion(Step::FromTwoDiff {
node,
a: coordinate(minuend),
b: coordinate(subtrahend),
})
}
},
Op::Neg(x) => match b.values[x] {
Value::Scalar(Scalar::Constant(c)) => Value::Scalar(Scalar::Constant(-c)),
Value::Scalar(s) => {
b.steps.push(Step::NegateScalar { node, x: s });
Value::Scalar(Scalar::Local(node))
}
Value::Expansion(e) => b.expansion(Step::Negate { node, x: e }),
},
Op::Add(x, y) => {
let (x, y) = (b.materialize(x), b.materialize(y));
b.expansion(Step::Sum { node, x, y })
}
Op::Sub(x, y) => {
let (x, y) = (b.materialize(x), b.materialize(y));
b.expansion(Step::Diff { node, x, y })
}
Op::Mul(x, y) => b.product(node, x, y),
};
b.values.push(value);
}
let root = b.materialize(root);
b.coordinates.sort_unstable();
Program {
steps: b.steps,
capacities: b.capacities,
root,
coordinates: b.coordinates,
}
}
struct Builder {
steps: Vec<Step>,
values: Vec<Value>,
expansions: Vec<bool>,
capacities: Vec<usize>,
coordinates: Vec<(usize, u8)>,
}
impl Builder {
fn coordinate(&mut self, point: usize, axis: u8) {
if !self.coordinates.contains(&(point, axis)) {
self.coordinates.push((point, axis));
}
}
fn expansion(&mut self, step: Step) -> Value {
let node = step.expansion().expect("a step that writes an expansion");
self.steps.push(if self.capacities[node] == 0 {
Step::Zero { node }
} else {
step
});
Value::Expansion(node)
}
fn materialize(&mut self, node: NodeId) -> NodeId {
match self.values[node] {
Value::Expansion(e) => e,
Value::Scalar(x) => {
if !self.expansions[node] {
self.expansions[node] = true;
self.expansion(Step::FromF64 { node, x });
}
node
}
}
}
fn product(&mut self, node: NodeId, x: NodeId, y: NodeId) -> Value {
if self.capacities[node] == 0 {
return self.expansion(Step::Zero { node });
}
match (self.values[x], self.values[y]) {
(Value::Scalar(a), Value::Scalar(b)) => {
self.expansion(Step::FromTwoProduct { node, a, b })
}
(Value::Expansion(e), Value::Scalar(b)) | (Value::Scalar(b), Value::Expansion(e)) => {
self.expansion(Step::Scale { node, e, b })
}
(Value::Expansion(x), Value::Expansion(y)) => {
let (x, y) = if self.capacities[x] <= self.capacities[y] {
(x, y)
} else {
(y, x)
};
self.expansion(Step::Product { node, x, y })
}
}
}
}