use super::{
Meta, MetaGraph,
error::{InvalidInputIndex, InvalidOutputIndex, NodeConnsError, TooManyConns},
push_eval_neighbors, push_reachable,
};
use crate::node;
use petgraph::visit::{EdgeRef, IntoEdgeReferences};
use std::{
collections::{BTreeMap, HashMap, HashSet},
fmt, ops,
};
#[derive(Debug, thiserror::Error)]
pub enum NodeInputsError {
#[error(transparent)]
TooManyInputs(#[from] TooManyConns),
#[error(transparent)]
InvalidIndex(#[from] InvalidInputIndex),
}
#[derive(Debug, thiserror::Error)]
pub enum NodeOutputsError {
#[error(transparent)]
TooManyOutputs(#[from] TooManyConns),
#[error(transparent)]
InvalidIndex(#[from] InvalidOutputIndex),
}
#[derive(Debug)]
pub struct Flow {
pub nested: FlowGraph,
pub push: BTreeMap<(node::Id, node::Conns), FlowGraph>,
pub pull: BTreeMap<(node::Id, node::Conns), FlowGraph>,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Block(pub Vec<NodeConf>);
pub type FlowGraph = petgraph::stable_graph::StableDiGraph<Block, Branch>;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Branch {
pub ix: usize,
pub conns: node::Conns,
}
pub type NodeConfGraph = petgraph::graphmap::DiGraphMap<NodeConf, Branch>;
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct NodeConf {
pub id: node::Id,
pub conns: NodeConns,
}
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct NodeConns {
pub inputs: node::Conns,
pub outputs: node::Conns,
}
impl Flow {
pub fn from_meta(meta: &Meta) -> Result<Self, NodeConnsError> {
let push = meta
.push
.iter()
.flat_map(|(&n, connss)| {
connss
.iter()
.map(move |conns| ((n, *conns), push_eval_flow_graph(meta, n, conns)))
})
.map(|(k, r)| r.map(|v| (k, v)))
.collect::<Result<_, _>>()?;
let pull = meta
.pull
.iter()
.flat_map(|(&n, connss)| {
connss
.iter()
.map(move |conns| ((n, *conns), pull_eval_flow_graph(meta, n, conns)))
})
.map(|(k, r)| r.map(|v| (k, v)))
.collect::<Result<_, _>>()?;
let nested = flow_graph(
meta,
meta.inlets
.iter()
.map(|&n| (n, node::Conns::connected(1).unwrap())),
meta.outlets
.iter()
.map(|&n| (n, node::Conns::connected(1).unwrap())),
)?;
Ok(Self { nested, push, pull })
}
}
impl fmt::Debug for NodeConf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({:?}: {:?})", self.id, self.conns)
}
}
impl fmt::Debug for NodeConns {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "([{}], [{}])", self.inputs, self.outputs)
}
}
impl ops::Deref for Block {
type Target = Vec<NodeConf>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl ops::DerefMut for Block {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<NodeInputsError> for NodeConnsError {
fn from(e: NodeInputsError) -> Self {
match e {
NodeInputsError::TooManyInputs(e) => NodeConnsError::TooManyConns(e),
NodeInputsError::InvalidIndex(e) => NodeConnsError::InvalidInputIndex(e),
}
}
}
impl From<NodeOutputsError> for NodeConnsError {
fn from(e: NodeOutputsError) -> Self {
match e {
NodeOutputsError::TooManyOutputs(e) => NodeConnsError::TooManyConns(e),
NodeOutputsError::InvalidIndex(e) => NodeConnsError::InvalidOutputIndex(e),
}
}
}
pub fn flow_graph(
meta: &Meta,
push: impl IntoIterator<Item = (node::Id, node::Conns)>,
pull: impl IntoIterator<Item = (node::Id, node::Conns)>,
) -> Result<FlowGraph, NodeConnsError> {
let order: Vec<_> = super::eval_order(&meta.graph, push, pull).collect();
let included: HashSet<_> = order.iter().copied().collect();
let mg = reachable_subgraph(&meta.graph, &included);
let conf_graph = node_conf_graph(meta, &mg, None)?;
Ok(flow_graph_from_conf_graph(&conf_graph))
}
fn push_eval_flow_graph(
meta: &Meta,
n: node::Id,
conns: &node::Conns,
) -> Result<FlowGraph, NodeConnsError> {
flow_graph(meta, Some((n, *conns)), std::iter::empty())
}
fn pull_eval_flow_graph(
meta: &Meta,
n: node::Id,
conns: &node::Conns,
) -> Result<FlowGraph, NodeConnsError> {
flow_graph(meta, std::iter::empty(), Some((n, *conns)))
}
fn reachable_subgraph(g: &MetaGraph, reachable: &HashSet<node::Id>) -> MetaGraph {
g.all_edges()
.filter(|(a, b, _)| reachable.contains(a) && reachable.contains(b))
.map(|(a, b, w)| (a, b, w.clone()))
.collect()
}
fn flow_graph_from_conf_graph(cg: &NodeConfGraph) -> FlowGraph {
let mut g = FlowGraph::with_capacity(cg.node_count(), cg.edge_count());
let mut visited = HashMap::with_capacity(cg.node_count());
for (a, b, &branch) in cg.all_edges() {
let na = *visited
.entry(a)
.or_insert_with(|| g.add_node(Block(vec![a])));
let nb = *visited
.entry(b)
.or_insert_with(|| g.add_node(Block(vec![b])));
g.add_edge(na, nb, branch);
}
flow_graph_edge_contraction(&mut g);
g
}
fn flow_graph_edge_contraction(g: &mut FlowGraph) {
let mut edges: Vec<_> = g.edge_references().map(|e_ref| e_ref.id()).collect();
while let Some(e) = edges.pop() {
let (src, dst) = g.edge_endpoints(e).unwrap();
let mergeable = g.edges_directed(src, petgraph::Outgoing).take(2).count() == 1
&& g.edges_directed(dst, petgraph::Incoming).take(2).count() == 1;
if !mergeable {
continue;
}
let (src_blk, dst_blk) = g.index_twice_mut(src, dst);
src_blk.0.append(&mut dst_blk.0);
let new_edges: Vec<_> = g
.edges_directed(dst, petgraph::Outgoing)
.map(|e_ref| (e_ref.target(), *e_ref.weight()))
.collect();
for (new_dst, w) in new_edges {
let new_e = g.add_edge(src, new_dst, w);
edges.push(new_e);
}
g.remove_node(dst);
}
}
fn node_inputs(
g: &MetaGraph,
n: node::Id,
n_inputs: usize,
) -> Result<node::Conns, NodeInputsError> {
let mut inputs = node::Conns::unconnected(n_inputs).map_err(|_| TooManyConns(n_inputs))?;
for e_ref in g.edges_directed(n, petgraph::Incoming) {
for (edge, _kind) in e_ref.weight() {
let index = edge.input.0 as usize;
inputs
.set(index, true)
.map_err(|_| InvalidInputIndex { index, n_inputs })?;
}
}
Ok(inputs)
}
fn node_outputs(
g: &MetaGraph,
n: node::Id,
n_outputs: usize,
) -> Result<node::Conns, NodeOutputsError> {
let mut outputs = node::Conns::unconnected(n_outputs).map_err(|_| TooManyConns(n_outputs))?;
for e_ref in g.edges_directed(n, petgraph::Outgoing) {
for (edge, _kind) in e_ref.weight() {
let index = edge.output.0 as usize;
outputs
.set(index, true)
.map_err(|_| InvalidOutputIndex { index, n_outputs })?;
}
}
Ok(outputs)
}
fn node_conf_graph(
meta: &Meta,
mg: &MetaGraph,
mut from_branch: Option<(NodeConns, Branch)>,
) -> Result<NodeConfGraph, NodeConnsError> {
let mut g = NodeConfGraph::new();
let mut topo = petgraph::visit::Topo::new(mg);
let mut last = None;
while let Some(n) = topo.next(mg) {
let n_inputs = meta.inputs.get(&n).copied().unwrap_or(0);
let n_outputs = meta.outputs.get(&n).copied().unwrap_or(0);
let inputs = node_inputs(mg, n, n_inputs)?;
let outputs = node_outputs(mg, n, n_outputs)?;
let conns = NodeConns { inputs, outputs };
let mut conf = NodeConf { id: n, conns };
if let Some(prev) = last {
let branch = from_branch.take().map(|(_, b)| b).unwrap_or_else(|| {
let conns = outputs;
Branch { ix: 0, conns }
});
g.add_edge(prev, conf, branch);
} else {
if let Some((conns, _branch)) = from_branch {
conf.conns = conns;
}
g.add_node(conf);
}
if let (Some(branches), true) = (meta.branches.get(&n), last.is_some()) {
for (ix, branch) in branches.iter().enumerate() {
let nbs = push_eval_neighbors(mg, n, branch);
let reachable: HashSet<_> = push_reachable(mg, n, &nbs).collect();
let sub_mg = reachable_subgraph(mg, &reachable);
let branch = Branch { ix, conns: *branch };
let sub_ncg = node_conf_graph(meta, &sub_mg, Some((conns, branch)))?;
g.extend(sub_ncg.all_edges().map(|(a, b, &w)| (a, b, w)));
}
break;
}
last = Some(conf);
}
Ok(g)
}