Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test code #31

Merged
merged 18 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ indexmap = "2.0.0"
log = "0.4.19"
ordered-float = "3"
pico-args = { version = "0.5.0", features = ["eq-separator"] }
rand = "0.8.5"
walkdir = "2.4.0"

anyhow = "1.0.71"
coin_cbc = { version = "0.1.6", optional = true }
Expand Down
3 changes: 3 additions & 0 deletions src/extract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub mod greedy_dag;
#[cfg(feature = "ilp-cbc")]
pub mod ilp_cbc;

// Allowance for floating point values to be considered equal
pub const EPSILON_ALLOWANCE: f64 = 0.00001;

pub trait Extractor: Sync {
fn extract(&self, egraph: &EGraph, roots: &[ClassId]) -> ExtractionResult;

Expand Down
84 changes: 70 additions & 14 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,83 @@ use std::path::PathBuf;
pub type Cost = NotNan<f64>;
pub const INFINITY: Cost = unsafe { NotNan::new_unchecked(std::f64::INFINITY) };

fn main() {
env_logger::init();
#[derive(PartialEq, Eq)]
enum Optimal {
Tree,
DAG,
Neither,
}

struct ExtractorDetail {
extractor: Box<dyn Extractor>,
optimal: Optimal,
use_for_bench: bool,
}

let extractors: IndexMap<&str, Box<dyn Extractor>> = [
("bottom-up", extract::bottom_up::BottomUpExtractor.boxed()),
fn extractors() -> IndexMap<&'static str, ExtractorDetail> {
let extractors: IndexMap<&'static str, ExtractorDetail> = [
(
"faster-bottom-up",
extract::faster_bottom_up::FasterBottomUpExtractor.boxed(),
"bottom-up",
ExtractorDetail {
extractor: extract::bottom_up::BottomUpExtractor.boxed(),
optimal: Optimal::Tree,
use_for_bench: true,
},
),
(
"greedy-dag",
extract::greedy_dag::GreedyDagExtractor.boxed(),
"faster-bottom-up",
ExtractorDetail {
extractor: extract::faster_bottom_up::FasterBottomUpExtractor.boxed(),
optimal: Optimal::Tree,
use_for_bench: true,
},
),
(
/*(
"faster-greedy-dag",
extract::faster_greedy_dag::FasterGreedyDagExtractor.boxed(),
ExtractorDetail {
extractor: extract::faster_greedy_dag::FasterGreedyDagExtractor.boxed(),
optimal: Optimal::Neither,
use_for_bench: true,
},
),*/

/*(
"global-greedy-dag",
ExtractorDetail {
extractor: extract::global_greedy_dag::GlobalGreedyDagExtractor.boxed(),
optimal: Optimal::Neither,
use_for_bench: true,
},
),*/
#[cfg(feature = "ilp-cbc")]
(
"ilp-cbc-timeout",
ExtractorDetail {
extractor: extract::ilp_cbc::CbcExtractorWithTimeout::<10>.boxed(),
optimal: Optimal::DAG,
use_for_bench: true,
},
),
#[cfg(feature = "ilp-cbc")]
(
"global-greedy-dag",
extract::global_greedy_dag::GlobalGreedyDagExtractor.boxed(),
"ilp-cbc",
ExtractorDetail {
extractor: extract::ilp_cbc::CbcExtractor.boxed(),
optimal: Optimal::DAG,
use_for_bench: false, // takes >10 hours sometimes
},
),
]
.into_iter()
.collect();
return extractors;
}

fn main() {
env_logger::init();

let mut extractors = extractors();
extractors.retain(|_, ed| ed.use_for_bench);

let mut args = pico_args::Arguments::from_env();

Expand Down Expand Up @@ -71,13 +124,13 @@ fn main() {
.with_context(|| format!("Failed to parse {filename}"))
.unwrap();

let extractor = extractors
let ed = extractors
.get(extractor_name.as_str())
.with_context(|| format!("Unknown extractor: {extractor_name}"))
.unwrap();

let start_time = std::time::Instant::now();
let result = extractor.extract(&egraph, &egraph.root_eclasses);
let result = ed.extractor.extract(&egraph, &egraph.root_eclasses);
let us = start_time.elapsed().as_micros();

result.check(&egraph);
Expand All @@ -98,3 +151,6 @@ fn main() {
)
.unwrap();
}

#[cfg(test)]
mod test;
211 changes: 211 additions & 0 deletions src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
* Checks that no extractors produce better results than the extractors that produce optimal results.
* Checks that the extractions are valid.
*/

use super::*;

use crate::{extractors, Extractor, Optimal, EPSILON_ALLOWANCE};
pub type Cost = NotNan<f64>;
use egraph_serialize::{EGraph, Node, NodeId};
use ordered_float::NotNan;
use rand::Rng;

// generates a float between 0 and 1
fn generate_random_not_nan() -> NotNan<f64> {
let mut rng: rand::prelude::ThreadRng = rand::thread_rng();
let random_float: f64 = rng.gen();
NotNan::new(random_float).unwrap()
}

//make a random egraph that has a loop-free extraction.
pub fn generate_random_egraph() -> EGraph {
let mut rng = rand::thread_rng();
let core_node_count = rng.gen_range(1..100) as usize;
let extra_node_count = rng.gen_range(1..100);
let mut nodes: Vec<Node> = Vec::with_capacity(core_node_count + extra_node_count);
let mut eclass = 0;

let id2nid = |id: usize| -> NodeId { format!("node_{}", id).into() };

// Unless we do it explicitly, the costs are almost never equal to others' costs or zero:
let get_semi_random_cost = |nodes: &Vec<Node>| -> Cost {
let mut rng = rand::thread_rng();

if nodes.len() > 0 && rng.gen_bool(0.1) {
return nodes[rng.gen_range(0..nodes.len())].cost;
} else if rng.gen_bool(0.05) {
return Cost::default();
} else {
return generate_random_not_nan() * 100.0;
}
};

for i in 0..core_node_count {
let children: Vec<NodeId> = (0..i).filter(|_| rng.gen_bool(0.1)).map(id2nid).collect();

if rng.gen_bool(0.2) {
eclass += 1;
}

nodes.push(Node {
op: "operation".to_string(),
children: children,
eclass: eclass.to_string().clone().into(),
cost: get_semi_random_cost(&nodes),
});
}

// So far we have the nodes for a feasible egraph. Now we add some
// cycles to extra nodes - nodes that aren't required in the extraction.
for _ in 0..extra_node_count {
nodes.push(Node {
op: "operation".to_string(),
children: vec![],
eclass: rng.gen_range(0..eclass * 2 + 1).to_string().clone().into(),
cost: get_semi_random_cost(&nodes),
});
}

for i in core_node_count..nodes.len() {
for j in 0..nodes.len() {
if rng.gen_bool(0.05) {
nodes.get_mut(i).unwrap().children.push(id2nid(j));
}
}
}

let mut egraph = EGraph::default();

for i in 0..nodes.len() {
egraph.add_node(id2nid(i), nodes[i].clone());
}

// Set roots
for _ in 1..rng.gen_range(2..6) {
egraph.root_eclasses.push(
nodes
.get(rng.gen_range(0..core_node_count))
.unwrap()
.eclass
.clone(),
);
}

egraph
}

fn check_optimal_results<I: Iterator<Item = EGraph>>(egraphs: I) {
let mut optimal_dag: Vec<Box<dyn Extractor>> = Default::default();
let mut optimal_tree: Vec<Box<dyn Extractor>> = Default::default();
let mut others: Vec<Box<dyn Extractor>> = Default::default();

for (_, ed) in extractors().into_iter() {
match ed.optimal {
Optimal::DAG => optimal_dag.push(ed.extractor),
Optimal::Tree => optimal_tree.push(ed.extractor),
Optimal::Neither => others.push(ed.extractor),
}
}

for egraph in egraphs {
let mut optimal_dag_cost: Option<Cost> = None;

for e in &optimal_dag {
let extract = e.extract(&egraph, &egraph.root_eclasses);
extract.check(&egraph);
let dag_cost = extract.dag_cost(&egraph, &egraph.root_eclasses);
let tree_cost = extract.tree_cost(&egraph, &egraph.root_eclasses);
if optimal_dag_cost.is_none() {
optimal_dag_cost = Some(dag_cost);
continue;
}

assert!(
(dag_cost.into_inner() - optimal_dag_cost.unwrap().into_inner()).abs()
< EPSILON_ALLOWANCE
);

assert!(
tree_cost.into_inner() + EPSILON_ALLOWANCE > optimal_dag_cost.unwrap().into_inner()
);
}

let mut optimal_tree_cost: Option<Cost> = None;

for e in &optimal_tree {
let extract = e.extract(&egraph, &egraph.root_eclasses);
extract.check(&egraph);
let tree_cost = extract.tree_cost(&egraph, &egraph.root_eclasses);
if optimal_tree_cost.is_none() {
optimal_tree_cost = Some(tree_cost);
continue;
}

assert!(
(tree_cost.into_inner() - optimal_tree_cost.unwrap().into_inner()).abs()
< EPSILON_ALLOWANCE
);
}

if optimal_dag_cost.is_some() && optimal_tree_cost.is_some() {
assert!(optimal_dag_cost.unwrap() < optimal_tree_cost.unwrap() + EPSILON_ALLOWANCE);
}

for e in &others {
let extract = e.extract(&egraph, &egraph.root_eclasses);
extract.check(&egraph);
let tree_cost = extract.tree_cost(&egraph, &egraph.root_eclasses);
let dag_cost = extract.dag_cost(&egraph, &egraph.root_eclasses);

// The optimal tree cost should be <= any extractor's tree cost.
if optimal_tree_cost.is_some() {
assert!(optimal_tree_cost.unwrap() <= tree_cost + EPSILON_ALLOWANCE);
}

if optimal_dag_cost.is_some() {
// The optimal dag should be less <= any extractor's dag cost
assert!(optimal_dag_cost.unwrap() <= dag_cost + EPSILON_ALLOWANCE);
}
}
}
}

// Run on all the .json test files
#[test]
fn run_on_test_egraphs() {
use walkdir::WalkDir;

let egraphs = WalkDir::new("./test_data/")
.into_iter()
.filter_map(Result::ok)
.filter(|e| {
e.file_type().is_file()
&& e.path().extension().and_then(std::ffi::OsStr::to_str) == Some("json")
})
.map(|e| e.path().to_string_lossy().into_owned())
.map(|e| EGraph::from_json_file(e).unwrap());
check_optimal_results(egraphs);
}

#[test]
#[should_panic]
fn check_assert_enabled() {
assert!(false);
}

macro_rules! create_optimal_check_tests {
($($name:ident),*) => {
$(
#[test]
fn $name() {
let optimal_dag_found = extractors().into_iter().any(|(_, ed)| ed.optimal == Optimal::DAG);
let iterations = if optimal_dag_found { 100 } else { 10000 };
let egraphs = (0..iterations).map(|_| generate_random_egraph());
check_optimal_results(egraphs);
}
)*
}
}

create_optimal_check_tests!(check0, check1, check2, check3, check4, check5, check6, check7);
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions test_data/fuzz/19.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"nodes":{"node_0":{"op":"operation","children":[],"eclass":"0","cost":88.33907914523702},"node_1":{"op":"operation","children":[],"eclass":"0","cost":31.380094022709294},"node_2":{"op":"operation","children":[],"eclass":"0","cost":85.46008208334565},"node_3":{"op":"operation","children":[],"eclass":"1","cost":51.03798035678284},"node_4":{"op":"operation","children":[],"eclass":"1","cost":88.57609102193999},"node_5":{"op":"operation","children":["node_3","node_4"],"eclass":"1","cost":29.920743409811124},"node_6":{"op":"operation","children":[],"eclass":"1","cost":83.84156235361831},"node_7":{"op":"operation","children":[],"eclass":"1","cost":15.961085173271005},"node_8":{"op":"operation","children":["node_0","node_4"],"eclass":"2","cost":95.02774306310243},"node_9":{"op":"operation","children":[],"eclass":"2","cost":59.71792436293144},"node_10":{"op":"operation","children":["node_5"],"eclass":"2","cost":68.45922247445554},"node_11":{"op":"operation","children":["node_5","node_6","node_9"],"eclass":"2","cost":57.01097446465635},"node_12":{"op":"operation","children":["node_9"],"eclass":"2","cost":81.34398176692994},"node_13":{"op":"operation","children":["node_0","node_10"],"eclass":"2","cost":88.8278857769136},"node_14":{"op":"operation","children":["node_6"],"eclass":"2","cost":29.12956833073791},"node_15":{"op":"operation","children":["node_7"],"eclass":"2","cost":60.913906070481275},"node_16":{"op":"operation","children":["node_3","node_14"],"eclass":"2","cost":38.327931493858856},"node_17":{"op":"operation","children":[],"eclass":"2","cost":3.0666168877196975},"node_18":{"op":"operation","children":["node_3","node_8","node_13"],"eclass":"2","cost":90.71871650194228},"node_19":{"op":"operation","children":["node_17"],"eclass":"2","cost":36.90315552724963},"node_20":{"op":"operation","children":[],"eclass":"3","cost":68.74450717533955},"node_21":{"op":"operation","children":["node_15"],"eclass":"4","cost":86.4333128929997},"node_22":{"op":"operation","children":[],"eclass":"4","cost":39.135523596243125},"node_23":{"op":"operation","children":["node_0","node_2","node_22"],"eclass":"4","cost":68.33455667461268},"node_24":{"op":"operation","children":["node_2","node_4","node_5","node_7","node_10"],"eclass":"4","cost":48.63662876945013},"node_25":{"op":"operation","children":[],"eclass":"4","cost":5.275616481149159},"node_26":{"op":"operation","children":["node_22"],"eclass":"4","cost":48.82599038198296},"node_27":{"op":"operation","children":["node_7"],"eclass":"4","cost":49.69001156482923},"node_28":{"op":"operation","children":["node_5","node_12","node_20","node_24"],"eclass":"4","cost":7.036804972594057},"node_29":{"op":"operation","children":["node_17","node_28"],"eclass":"4","cost":70.54109393760133},"node_30":{"op":"operation","children":["node_6","node_7","node_14"],"eclass":"5","cost":95.93578126220126},"node_31":{"op":"operation","children":["node_5","node_13","node_25"],"eclass":"5","cost":54.28803013353224},"node_32":{"op":"operation","children":["node_7","node_10","node_30"],"eclass":"5","cost":3.572346212908928},"node_33":{"op":"operation","children":["node_0","node_10","node_15","node_24","node_32"],"eclass":"6","cost":84.31285228381701},"node_34":{"op":"operation","children":["node_1","node_3","node_16","node_22","node_23"],"eclass":"6","cost":6.515351494494381},"node_35":{"op":"operation","children":["node_1","node_13","node_22","node_32"],"eclass":"6","cost":33.036709109796256},"node_36":{"op":"operation","children":["node_24"],"eclass":"7","cost":90.6873521656599},"node_37":{"op":"operation","children":["node_1","node_2","node_8","node_29","node_31"],"eclass":"7","cost":1.8364613848407596},"node_38":{"op":"operation","children":["node_8","node_25","node_30","node_32","node_36","node_37"],"eclass":"7","cost":94.45614868004778},"node_39":{"op":"operation","children":["node_11","node_31","node_33"],"eclass":"7","cost":87.30408798371053},"node_40":{"op":"operation","children":["node_2","node_4","node_8","node_15"],"eclass":"7","cost":99.33533866173661},"node_41":{"op":"operation","children":["node_0","node_3","node_6","node_8","node_11","node_23","node_33","node_38"],"eclass":"7","cost":1.017173923360426},"node_42":{"op":"operation","children":["node_11"],"eclass":"7","cost":67.35391642646766},"node_43":{"op":"operation","children":["node_4","node_6","node_14","node_35","node_37","node_39"],"eclass":"7","cost":25.188737716223553},"node_44":{"op":"operation","children":["node_16","node_21","node_24"],"eclass":"7","cost":6.621741488453536},"node_45":{"op":"operation","children":["node_16","node_18","node_38"],"eclass":"7","cost":37.226773804762125},"node_46":{"op":"operation","children":["node_3","node_25","node_27"],"eclass":"7","cost":98.0170455983861},"node_47":{"op":"operation","children":["node_5","node_10","node_12","node_22","node_25","node_26","node_32","node_38","node_39","node_43"],"eclass":"8","cost":8.286875513283443},"node_48":{"op":"operation","children":["node_11","node_21","node_29"],"eclass":"9","cost":57.43141619732109},"node_49":{"op":"operation","children":["node_14","node_17","node_21","node_22","node_28","node_30"],"eclass":"9","cost":6.31613518850469},"node_50":{"op":"operation","children":["node_20","node_33","node_40"],"eclass":"9","cost":51.70111022535549},"node_51":{"op":"operation","children":["node_26","node_47"],"eclass":"9","cost":50.74398959092199},"node_52":{"op":"operation","children":["node_10","node_13","node_23","node_45","node_51"],"eclass":"9","cost":92.18725389709668},"node_53":{"op":"operation","children":["node_35","node_37","node_44"],"eclass":"9","cost":72.97669763885398},"node_54":{"op":"operation","children":["node_2","node_11","node_12","node_22","node_25","node_32","node_33","node_34"],"eclass":"9","cost":89.03357592761667},"node_55":{"op":"operation","children":["node_3","node_4","node_14","node_34","node_49"],"eclass":"9","cost":28.60041721917057}},"root_eclasses":["7","7","7"]}
File renamed without changes.
Loading