portmatching 0.3.3

Fast Graph Matching for Port Graphs
Documentation
use std::{
    collections::BTreeSet,
    fs, io,
    path::{Path, PathBuf},
};

use portgraph::{dot::DotFormat, NodeIndex, PortGraph, PortOffset};
use portmatching::{
    matcher::{ManyMatcher, PatternMatch, PortMatcher},
    PatternID, Universe, UnweightedPattern,
};

const DBG_DUMP_FILES: bool = false;

fn valid_json_file(s: &str, pattern: &str) -> bool {
    s.starts_with(pattern) && s.ends_with(".json")
}

fn load_patterns(dir: &Path) -> io::Result<Vec<UnweightedPattern>> {
    let mut patterns = Vec::new();
    let mut all_patterns: Vec<_> = fs::read_dir(dir)?
        .filter_map(|entry| {
            let Ok(entry) = entry else { return None };
            let file_name = entry.file_name().to_str().unwrap().to_string();
            let path = entry.path();
            valid_json_file(&file_name, "pattern").then_some(path)
        })
        .collect();
    all_patterns.sort_unstable();
    for path in all_patterns {
        let (p, root): (PortGraph, NodeIndex) =
            serde_json::from_reader(fs::File::open(&path)?).unwrap();
        if DBG_DUMP_FILES {
            let mut path = path;
            path.set_extension("gv");
            fs::write(path, p.dot_string()).unwrap();
        }
        patterns.push(UnweightedPattern::from_rooted_portgraph(&p, root));
    }

    Ok(patterns)
}

fn load_graph(dir: &Path) -> io::Result<PortGraph> {
    for entry in fs::read_dir(dir)? {
        let Ok(entry) = entry else { continue };
        let file_name = entry.file_name().to_str().unwrap().to_string();
        let path = entry.path();
        if valid_json_file(&file_name, "graph") {
            let graph: PortGraph = serde_json::from_reader(fs::File::open(&path)?).unwrap();
            if DBG_DUMP_FILES {
                let mut path = path;
                path.set_extension("gv");
                fs::write(path, graph.dot_string()).unwrap();
            }
            return Ok(graph);
        }
    }

    Err(io::Error::new(io::ErrorKind::Other, "no file found"))
}

fn load_results(dir: &Path) -> io::Result<BTreeSet<PatternMatch<PatternID, NodeIndex>>> {
    for entry in fs::read_dir(dir)? {
        let Ok(entry) = entry else { continue };
        let file_name = entry.file_name().to_str().unwrap().to_string();
        let path = entry.path();
        if valid_json_file(&file_name, "results") {
            let res: BTreeSet<PatternMatch<PatternID, NodeIndex>> =
                serde_json::from_reader(fs::File::open(path)?).unwrap();
            return Ok(res);
        }
    }

    Err(io::Error::new(io::ErrorKind::Other, "no file found"))
}

fn test<'g, M, U>(
    matcher: &M,
    graph: &'g PortGraph,
    exp: &BTreeSet<PatternMatch<PatternID, NodeIndex>>,
) where
    M: PortMatcher<&'g PortGraph, NodeIndex, U, PNode = (), PEdge = (PortOffset, PortOffset)>,
    U: Universe,
{
    let many_matches: BTreeSet<_> = matcher.find_matches(graph).into_iter().collect();
    assert_eq!(&many_matches, exp);
}

#[test]
fn from_saved_patterns() {
    let testcases = ["0", "1", "2"];
    for test_name in testcases {
        println!("{test_name}...");
        let path: PathBuf = ["tests", "saved_patterns", test_name].iter().collect();
        let patterns = load_patterns(&path).unwrap();
        let graph = load_graph(&path).unwrap();
        let exp = load_results(&path).unwrap();

        let matcher = ManyMatcher::from_patterns(patterns.clone());
        if DBG_DUMP_FILES {
            let mut path = path.clone();
            path.push("trie.gv");
            fs::write(path, matcher.dot_string()).unwrap();
        }
        test(&matcher, &graph, &exp);
    }
}