Skip to content

Commit

Permalink
generalize find_path for many inputs and outputs
Browse files Browse the repository at this point in the history
  • Loading branch information
jku20 committed Jun 10, 2024
1 parent 2641b54 commit a3b9db2
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 73 deletions.
154 changes: 81 additions & 73 deletions fud2/fud-core/src/exec/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ use cranelift_entity::{PrimaryMap, SecondaryMap};
use rand::distributions::{Alphanumeric, DistString};
use std::{collections::HashMap, error::Error, ffi::OsStr, fmt::Display};

#[derive(PartialEq)]
enum Destination {
State(StateRef),
Op(OpRef),
}

type FileData = HashMap<&'static str, &'static [u8]>;

/// A Driver encapsulates a set of States and the Operations that can transform between them. It
Expand All @@ -25,93 +19,107 @@ pub struct Driver {
}

impl Driver {
/// Find a chain of Operations from the `start` state to the `end`, which may be a state or the
/// final operation in the chain. Include with each operation the list of used output states.
fn find_path_segment(
/// Return parents ops of a given state
fn parent_ops(&self, state: StateRef) -> Vec<OpRef> {
self.ops
.iter()
.filter_map(|(op_ref, op)| {
if op.output.contains(&state) {
Some(op_ref)
} else {
None
}
})
.collect()
}

fn find_reversed_path(
&self,
start: StateRef,
end: Destination,
start: &[StateRef],
end: &[StateRef],
through: &[OpRef],
visited: &mut SecondaryMap<StateRef, bool>,
) -> Option<Vec<(OpRef, Vec<StateRef>)>> {
// Our start state is the input.
let mut visited = SecondaryMap::<StateRef, bool>::new();
visited[start] = true;

// Build the incoming edges for each vertex.
let mut breadcrumbs = SecondaryMap::<StateRef, Option<OpRef>>::new();

// Breadth-first search.
let mut state_queue: Vec<StateRef> = vec![start];
while !state_queue.is_empty() {
let cur_state = state_queue.remove(0);

// Finish when we reach the goal vertex.
if end == Destination::State(cur_state) {
break;
let mut path: Vec<(OpRef, Vec<StateRef>)> = vec![];
for &output in end.iter().filter(|s| !start.contains(s)) {
// visit the state because it can only be decided upon once
visited[output] = true;

// select and order ops
let ops = self.parent_ops(output);
let mut reordered_ops = vec![];
for &op in through {
if ops.contains(&op) {
reordered_ops.push(op);
}
}

// Traverse any edge from the current state to an unvisited state.
for (op_ref, op) in self.ops.iter() {
if op.input[0] == cur_state && !visited[op.output[0]] {
state_queue.push(op.output[0]);
visited[op.output[0]] = true;
breadcrumbs[op.output[0]] = Some(op_ref);
for op in ops {
if !reordered_ops.contains(&op) {
reordered_ops.push(op);
}
}

// Finish when we reach the goal edge.
if end == Destination::Op(op_ref) {
// recurse to find path to current point
let mut segment = None;
for op in reordered_ops {
if let Some(p) = self.find_reversed_path(
start,
&self.ops[op].input,
through,
visited,
) {
segment = Some((p, op));
break;
}
}
}

// Traverse the breadcrumbs backward to build up the path back from output to input.
let mut op_path: Vec<(OpRef, Vec<StateRef>)> = vec![];
let mut cur_state = match end {
Destination::State(state) => state,
Destination::Op(op) => {
op_path.push((op, vec![self.ops[op].output[0]]));
self.ops[op].input[0]
}
};
while cur_state != start {
match breadcrumbs[cur_state] {
Some(op) => {
op_path.push((op, vec![self.ops[op].output[0]]));
cur_state = self.ops[op].input[0];
match segment {
Some((s, o)) => {
let mut in_path = false;
for (op, states) in path.iter_mut() {
if *op == o {
states.push(output);
in_path = true;
break;
}
}
if !in_path {
path.push((o, vec![output]));
}
path.extend(s);
}
None => return None,
}
}
op_path.reverse();

Some(op_path)
Some(path)
}

/// Find a chain of operations from the `start` state to the `end` state, passing through each
/// `through` operation in order. Include with each operation the list of used output states.
pub fn find_path(
&self,
start: StateRef,
end: StateRef,
start: &[StateRef],
end: &[StateRef],
through: &[OpRef],
) -> Option<Vec<(OpRef, Vec<StateRef>)>> {
let mut cur_state = start;
let mut op_path: Vec<(OpRef, Vec<StateRef>)> = vec![];

// Build path segments through each through required operation.
for op in through {
let segment =
self.find_path_segment(cur_state, Destination::Op(*op))?;
op_path.extend(segment);
cur_state = self.ops[*op].output[0];
}

// Build the final path segment to the destination state.
let segment =
self.find_path_segment(cur_state, Destination::State(end))?;
op_path.extend(segment);

Some(op_path)
let path = self.find_reversed_path(
start,
end,
through,
&mut SecondaryMap::new(),
);
// reverse the path
path.map(|mut p| {
p.reverse();
p
})
// make sure we actually go through everything we are trying to go through
// the algo just does it's best to satisfy the through constraint, but doesn't guarrentee it
.filter(|p| {
through
.iter()
.all(|t| p.iter().map(|(o, _)| o).any(|o| o == t))
})
}

/// Generate a filename with an extension appropriate for the given State.
Expand All @@ -132,7 +140,7 @@ impl Driver {
pub fn plan(&self, req: Request) -> Option<Plan> {
// Find a path through the states.
let path =
self.find_path(req.start_state, req.end_state, &req.through)?;
self.find_path(&[req.start_state], &[req.end_state], &req.through)?;

let mut steps: Vec<(OpRef, Utf8PathBuf)> = vec![];

Expand Down
86 changes: 86 additions & 0 deletions fud2/fud-core/tests/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use fud_core::DriverBuilder;

#[test]
fn find_path_simple_graph_test() {
let mut bld = DriverBuilder::new("fud2");
let s1 = bld.state("s1", &[]);
let s2 = bld.state("s2", &[]);
let t1 = bld.op("t1", &[], s1, s2, |_, _, _| Ok(()));
let driver = bld.build();
assert_eq!(
Some(vec![(t1, vec![s2])]),
driver.find_path(&[s1], &[s2], &[])
);
assert_eq!(Some(vec![]), driver.find_path(&[s1], &[s1], &[]));
}

#[test]
fn find_path_multi_op_graph() {
let mut bld = DriverBuilder::new("fud2");
let s1 = bld.state("s1", &[]);
let s2 = bld.state("s2", &[]);
let s3 = bld.state("s3", &[]);
let t1 = bld.op("t1", &[], s1, s3, |_, _, _| Ok(()));
let _ = bld.op("t2", &[], s2, s3, |_, _, _| Ok(()));
let driver = bld.build();
assert_eq!(
Some(vec![(t1, vec![s3])]),
driver.find_path(&[s1], &[s3], &[])
);
}

#[test]
fn find_path_multi_path_graph() {
let mut bld = DriverBuilder::new("fud2");
let s1 = bld.state("s1", &[]);
let s2 = bld.state("s2", &[]);
let s3 = bld.state("s3", &[]);
let s4 = bld.state("s4", &[]);
let s5 = bld.state("s5", &[]);
let s6 = bld.state("s6", &[]);
let s7 = bld.state("s7", &[]);
let t1 = bld.op("t1", &[], s1, s3, |_, _, _| Ok(()));
let t2 = bld.op("t2", &[], s2, s3, |_, _, _| Ok(()));
let _ = bld.op("t3", &[], s3, s4, |_, _, _| Ok(()));
let t4 = bld.op("t4", &[], s3, s5, |_, _, _| Ok(()));
let t5 = bld.op("t5", &[], s3, s5, |_, _, _| Ok(()));
let _ = bld.op("t6", &[], s6, s7, |_, _, _| Ok(()));
let driver = bld.build();
assert_eq!(
Some(vec![(t1, vec![s3]), (t4, vec![s5])]),
driver.find_path(&[s1], &[s5], &[])
);
assert_eq!(
Some(vec![(t1, vec![s3]), (t5, vec![s5])]),
driver.find_path(&[s1], &[s5], &[t5])
);
assert_eq!(None, driver.find_path(&[s6], &[s5], &[]));
assert_eq!(None, driver.find_path(&[s1], &[s5], &[t2]));
}

#[test]
fn find_path_only_state_graph() {
let mut bld = DriverBuilder::new("fud2");
let s1 = bld.state("s1", &[]);
let driver = bld.build();
assert_eq!(Some(vec![]), driver.find_path(&[s1], &[s1], &[]));
}

#[test]
fn find_path_cycle_graph() {
let mut bld = DriverBuilder::new("fud2");
let s1 = bld.state("s1", &[]);
let s2 = bld.state("s2", &[]);
let t1 = bld.op("t1", &[], s1, s2, |_, _, _| Ok(()));
let t2 = bld.op("t2", &[], s2, s1, |_, _, _| Ok(()));
let driver = bld.build();
assert_eq!(Some(vec![]), driver.find_path(&[s1], &[s1], &[]));
assert_eq!(
Some(vec![(t1, vec![s2])]),
driver.find_path(&[s1], &[s2], &[])
);
assert_eq!(
Some(vec![(t2, vec![s1])]),
driver.find_path(&[s2], &[s1], &[])
);
}

0 comments on commit a3b9db2

Please sign in to comment.