Skip to content
Snippets Groups Projects
Commit 9fd75bf0 authored by Valerian Wintner's avatar Valerian Wintner
Browse files

(basic) Validity-check of input.

parent ae6a218a
No related branches found
No related tags found
No related merge requests found
- extract nodes and edges from original graph -- done
- add custom attribute for:
- color
- starting position
- goal position -- done
- run algorithm on custom data structure (map of sets?) -- done
- cli -- done
- different executable for displaying the graph, and for running the strategy-check -- done, all in 1
- validity-check of input.
- use map/set of &str instead of String, to avoid copying it
\ No newline at end of file
/*
* A simple graph structure, using
*/
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use std::rc::Rc;
use crate::graph::*;
use std::{collections::HashSet, rc::Rc};
use tracing::debug;
#[derive(PartialOrd, Ord, Clone, Debug)]
pub struct RedNode {
pub id: Rc<String>,
pub counter: Rc<RefCell<u32>>,
}
impl PartialEq for RedNode {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.counter == other.counter
}
}
impl Eq for RedNode {}
impl Hash for RedNode {
fn hash<H>(&self, state: &mut H)
where
H: std::hash::Hasher,
{
self.id.hash(state);
}
}
pub type GameNode = Rc<String>;
pub type GameNodes = Vec<GameNode>;
pub type Predecessors = HashMap<Rc<String>, HashSet<RedNode>>;
pub type Successors = HashMap<Rc<String>, HashSet<Rc<String>>>;
pub type WinningPositions = HashSet<Rc<String>>;
pub type AdditionalWinning = HashSet<Rc<String>>;
/*
* Insert this node into the winning positions, and also all red nodes that counted down to 0 (recursively) due to being predecessors of an inserted node.
*/
/// Insert this node into the winning positions, and also all red nodes that counted down to 0 (recursively) due to being predecessors of an inserted node.
fn insert_node(
node: Rc<String>,
additional_winning: &mut AdditionalWinning,
......@@ -66,9 +28,7 @@ fn insert_node(
}
}
/**
* Calculates new winning positions, iterative step
*/
/// Calculates new winning positions, iterative step.
pub fn pre(
greens: GameNodes,
winning_positions: &WinningPositions,
......@@ -90,6 +50,8 @@ pub fn pre(
}
(remaining_greens, additional_winning)
}
/// Calculates new winning positions.
pub fn winning_positions(
greens: &[GameNode],
final_positions: &WinningPositions,
......
use dot_generator::*;
use dot_structures::*;
use graphviz_rust::attributes::*;
use graphviz_rust::cmd::{CommandArg, Format};
use graphviz_rust::printer::PrinterContext;
use graphviz_rust::*;
use graphviz_rust::{exec, parse};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::fs::{self, File};
use std::hash::Hash;
use std::io::Write;
use std::rc::Rc;
/*********************************
* Types and enums, structs
*********************************/
pub type GameNode = Rc<String>;
pub type GameNodes = Vec<GameNode>;
pub type GreenNodes = Vec<GameNode>;
pub type RedNodes = Vec<GameNode>;
pub type Predecessors = HashMap<Rc<String>, HashSet<RedNode>>;
pub type Successors = HashMap<Rc<String>, HashSet<Rc<String>>>;
pub type WinningPositions = HashSet<Rc<String>>;
pub type AcceptingPositions = HashSet<Rc<String>>;
pub type AdditionalWinning = HashSet<Rc<String>>;
enum Colour {
Red,
Green,
}
/// A representation of a red node, with a mutable, shared counter. This counter will be initialized with the out-degree of the node, and then reduced when a successor is added to the winning set of nodes.
#[derive(PartialOrd, Ord, Clone, Debug)]
pub struct RedNode {
pub id: Rc<String>,
pub counter: Rc<RefCell<u32>>,
}
impl PartialEq for RedNode {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.counter == other.counter
}
}
impl Eq for RedNode {}
impl Hash for RedNode {
fn hash<H>(&self, state: &mut H)
where
H: std::hash::Hasher,
{
self.id.hash(state);
}
}
/*********************************
* Graph-related functions
*********************************/
/// Read a graph from a file.
pub fn read_graph(in_file: std::path::PathBuf) -> Graph {
let input = fs::read_to_string(in_file).expect("Could not read input-file");
parse(&input).expect("Input-graph is malformed")
}
/// Build the necessary inputs from the deserialized graph.
pub fn build_inputs(
g: &Graph,
) -> Result<
(
GreenNodes,
RedNodes,
AcceptingPositions,
Successors,
Predecessors,
),
String,
> {
let mut greens = HashSet::new();
let mut reds = HashSet::new();
let mut final_positions = HashSet::new();
let mut succ: Successors = HashMap::new();
let mut pred: Predecessors = HashMap::new();
let stmts = match g {
Graph::DiGraph { stmts, .. } => stmts,
Graph::Graph { stmts, .. } => stmts,
};
for e in stmts {
if let Stmt::Node(n) = e {
let node_id = Rc::new(node_id(n).to_string());
let mut had_player = false;
for attr in &n.attributes {
if let Attribute(Id::Plain(name), Id::Plain(value)) = attr {
had_player |= match (name.as_str(), value.as_str()) {
("player", "green") => {
greens.insert(node_id.clone());
true
}
("player", "red") => {
reds.insert(node_id.clone());
true
}
("state", "final") => {
final_positions.insert(node_id.clone());
false
}
_ => false,
};
}
}
if !had_player {
return Err(format!(
"Node {} did not define a player (green, red).",
node_id
));
}
} else if let Stmt::Edge(e) = e {
let mut add_vertices = |v1: &Vertex, v2: &Vertex| -> Result<(), String> {
if let Vertex::N(n1) = v1 {
if let Vertex::N(n2) = v2 {
let n1_id = id_value(&n1.0);
let n2_id = id_value(&n2.0);
let n1 =
greens
.get(n1_id)
.or_else(|| reds.get(n1_id))
.ok_or_else(|| {
format!("Node {} did not define a player (green, red).", n1_id)
})?;
let n2 =
greens
.get(n2_id)
.or_else(|| reds.get(n2_id))
.ok_or_else(|| {
format!("Node {} in the edge {}->{} did not define a player (green, red). Add a node to the list of nodes with this attribute above.", n2_id, n1_id, n2_id)
})?;
let successors = succ.entry(n1.clone()).or_default();
successors.insert(n2.clone());
}
}
Ok(())
};
match &e.ty {
// a -> b
EdgeTy::Pair(v1, v2) => add_vertices(v1, v2)?,
// a -> b -> c -> d -> ...
EdgeTy::Chain(vertices) => {
for (v1, v2) in Iterator::zip(vertices.iter(), vertices.iter().skip(1)) {
add_vertices(v1, v2)?;
}
}
}
}
}
// Finally, add the predecessors. Here, the count is needed, initialized with the #outgoing_edges for red nodes.
for red in reds.iter() {
if let Some(successors) = &succ.get(red) {
let pre_node = RedNode {
id: red.clone(),
counter: Rc::new(RefCell::new(successors.len() as u32)),
};
for successor in successors.iter() {
let predecessors = pred.entry(successor.clone()).or_default();
predecessors.insert(pre_node.clone());
}
}
}
let mut greens: Vec<Rc<String>> = greens.into_iter().collect();
greens.sort();
let mut reds: Vec<Rc<String>> = reds.into_iter().collect();
reds.sort();
Ok((greens, reds, final_positions, succ, pred))
}
/// Builds the output-graph ready for serialization/plotting.
pub fn build_graph(
reds: &[GameNode],
greens: &[GameNode],
finals: &AcceptingPositions,
succ: &Successors,
winning_positions: Option<&WinningPositions>,
) -> Graph {
let mut stmts = Vec::new();
let reds = reds.iter().map(|r| (r.clone(), Colour::Red));
let greens = greens.iter().map(|g| (g.clone(), Colour::Green));
let mut nodes = reds.chain(greens).collect::<Vec<(Rc<String>, Colour)>>();
nodes.sort_by_key(|c| c.0.clone());
for (id, colour) in nodes {
let is_winning = if let Some(winning_positions) = winning_positions {
winning_positions.contains(&id)
} else {
false
};
let node = match colour {
Colour::Red => {
let mut node = node!(id;
NodeAttributes::shape(shape::square),
NodeAttributes::fillcolor(if is_winning { // needs ugly copy+paste here because colour does not implement clone or copy-trait
color_name::orange
} else {
color_name::red4
}),
NodeAttributes::style("filled".to_string()));
if finals.contains(&id) {
node.attributes.push(NodeAttributes::peripheries(2));
let colour = if is_winning {
"\"orange1:white:orange1\""
} else {
"\"red4:white:red4\""
};
node.attributes.push(attr!("color", colour));
} else {
node.attributes.push(NodeAttributes::color(if is_winning {
color_name::orange
} else {
color_name::red4
}));
}
node
}
Colour::Green => {
let mut node = node!(id;
NodeAttributes::shape(shape::diamond),
NodeAttributes::fillcolor(if is_winning {
color_name::orange
} else {
color_name::green4
}),
NodeAttributes::style("filled".to_string()));
if finals.contains(&id) {
node.attributes.push(NodeAttributes::peripheries(2));
let colour = if is_winning {
"\"orange1:white:orange1\""
} else {
"\"green4:white:green4\""
};
node.attributes.push(attr!("color", colour));
} else {
node.attributes.push(NodeAttributes::color(if is_winning {
color_name::orange
} else {
color_name::green4
}));
}
node
}
};
stmts.push(Stmt::Node(node));
}
for (id, successors) in succ {
for successor in successors {
let edge = edge!(node_id!(id) => node_id!(successor));
stmts.push(Stmt::Edge(edge));
}
}
stmts.push(stmt!(attr!("rankdir", "LR")));
Graph::DiGraph {
id: id!("output_graph"),
strict: true,
stmts,
}
}
/// Plots a graph in svg-format to a file.
pub fn output(g: &Graph, path: std::path::PathBuf) {
let graph_svg = exec(
g.clone(),
PrinterContext::default(),
vec![CommandArg::Format(Format::Svg)],
)
.expect("Could not create svg using graphviz.");
let mut file = File::create(path).expect("Could not create output-file");
file.write_all(graph_svg.as_bytes())
.expect("Could not write output-file");
}
/*********************************
* Private helpers
*********************************/
fn node_id(node: &Node) -> &String {
id_value(&node.id.0)
}
#[allow(clippy::needless_borrow)]
fn id_value(id: &Id) -> &String {
match id {
Id::Html(v) => &v,
Id::Escaped(v) => &v,
Id::Plain(v) => &v,
Id::Anonymous(v) => &v,
}
}
mod arena;
mod graph;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::fs::{self, File};
use std::io::Write;
use std::rc::Rc;
use crate::graph::*;
use clap::Parser;
use dot_generator::*;
use dot_structures::*;
use graphviz_rust::attributes::*;
use graphviz_rust::cmd::{CommandArg, Format};
use graphviz_rust::printer::PrinterContext;
use graphviz_rust::*;
use graphviz_rust::{exec, parse};
use tracing::{debug, info};
use tracing_subscriber::fmt;
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use crate::arena::*;
#[derive(Parser, Debug)]
#[clap(about)]
struct Args {
......@@ -61,10 +47,11 @@ fn main() {
}
info!("Reading graph from in-file...");
let input = fs::read_to_string(args.in_file).expect("Could not read infile!");
let g = parse(&input).unwrap();
let g = read_graph(args.in_file);
let (greens, reds, final_positions, succ, pred) = build_inputs(&g);
info!("Extracting graph from input-format...");
let (greens, reds, final_positions, succ, pred) =
build_inputs(&g).expect("Error extracting graph-info");
debug!("Green nodes: {:?}", greens);
debug!("Red nodes: {:?}", reds);
debug!("Final positions: {:?}", final_positions);
......@@ -96,202 +83,3 @@ fn main() {
println!("Winning positions are: {}", winners.join(", "));
}
}
fn node_id(node: &Node) -> &String {
id_value(&node.id.0)
}
#[allow(clippy::needless_borrow)]
fn id_value(id: &Id) -> &String {
match id {
Id::Html(v) => &v,
Id::Escaped(v) => &v,
Id::Plain(v) => &v,
Id::Anonymous(v) => &v,
}
}
fn build_inputs(
g: &Graph,
) -> (
GameNodes,
GameNodes,
WinningPositions,
Successors,
Predecessors,
) {
let mut greens = HashSet::new();
let mut reds = HashSet::new();
let mut final_positions = HashSet::new();
let mut succ: Successors = HashMap::new();
let mut pred: Predecessors = HashMap::new();
let stmts = match g {
Graph::DiGraph { stmts, .. } => stmts,
Graph::Graph { stmts, .. } => stmts,
};
for e in stmts {
if let Stmt::Node(n) = e {
let node_id = Rc::new(node_id(n).to_string());
for attr in &n.attributes {
if let Attribute(Id::Plain(name), Id::Plain(value)) = attr {
match (name.as_str(), value.as_str()) {
("player", "green") => greens.insert(node_id.clone()),
("player", "red") => reds.insert(node_id.clone()),
("state", "final") => final_positions.insert(node_id.clone()),
_ => false,
};
}
}
} else if let Stmt::Edge(e) = e {
let mut add_vertices = |v1: &Vertex, v2: &Vertex| {
if let Vertex::N(n1) = v1 {
if let Vertex::N(n2) = v2 {
let n1 = Rc::new(id_value(&n1.0).to_string());
let n2 = Rc::new(id_value(&n2.0).to_string());
let successors = succ.entry(n1).or_default();
successors.insert(n2);
}
}
};
match &e.ty {
// a -> b
EdgeTy::Pair(v1, v2) => add_vertices(v1, v2),
// a -> b -> c -> d -> ...
EdgeTy::Chain(vertices) => {
for (v1, v2) in Iterator::zip(vertices.iter(), vertices.iter().skip(1)) {
add_vertices(v1, v2)
}
}
}
}
}
// Finally, add the predecessors. Here, the count is needed, initialized with the #outgoing_edges for red nodes.
for red in reds.iter() {
if let Some(successors) = &succ.get(red) {
let pre_node = RedNode {
id: red.clone(),
counter: Rc::new(RefCell::new(successors.len() as u32)),
};
for successor in successors.iter() {
let predecessors = pred.entry(successor.clone()).or_default();
predecessors.insert(pre_node.clone());
}
}
}
let mut greens: Vec<Rc<String>> = greens.into_iter().collect();
greens.sort();
let mut reds: Vec<Rc<String>> = reds.into_iter().collect();
reds.sort();
(greens, reds, final_positions, succ, pred)
}
enum Colour {
Red,
Green,
}
fn build_graph(
reds: &[GameNode],
greens: &[GameNode],
finals: &WinningPositions,
succ: &Successors,
winning_positions: Option<&WinningPositions>,
) -> Graph {
let mut stmts = Vec::new();
let reds = reds.iter().map(|r| (r.clone(), Colour::Red));
let greens = greens.iter().map(|g| (g.clone(), Colour::Green));
let mut nodes = reds.chain(greens).collect::<Vec<(Rc<String>, Colour)>>();
nodes.sort_by_key(|c| c.0.clone());
for (id, colour) in nodes {
let is_winning = if let Some(winning_positions) = winning_positions {
winning_positions.contains(&id)
} else {
false
};
let node = match colour {
Colour::Red => {
let mut node = node!(id;
NodeAttributes::shape(shape::square),
NodeAttributes::fillcolor(if is_winning { // needs ugly copy+paste here because colour does not implement clone or copy-trait
color_name::orange
} else {
color_name::red4
}),
NodeAttributes::style("filled".to_string()));
if finals.contains(&id) {
node.attributes.push(NodeAttributes::peripheries(2));
let colour = if is_winning {
"\"orange1:white:orange1\""
} else {
"\"red4:white:red4\""
};
node.attributes.push(attr!("color", colour));
} else {
node.attributes.push(NodeAttributes::color(if is_winning {
color_name::orange
} else {
color_name::red4
}));
}
node
}
Colour::Green => {
let mut node = node!(id;
NodeAttributes::shape(shape::diamond),
NodeAttributes::fillcolor(if is_winning {
color_name::orange
} else {
color_name::green4
}),
NodeAttributes::style("filled".to_string()));
if finals.contains(&id) {
node.attributes.push(NodeAttributes::peripheries(2));
let colour = if is_winning {
"\"orange1:white:orange1\""
} else {
"\"green4:white:green4\""
};
node.attributes.push(attr!("color", colour));
} else {
node.attributes.push(NodeAttributes::color(if is_winning {
color_name::orange
} else {
color_name::green4
}));
}
node
}
};
stmts.push(Stmt::Node(node));
}
for (id, successors) in succ {
for successor in successors {
let edge = edge!(node_id!(id) => node_id!(successor));
stmts.push(Stmt::Edge(edge));
}
}
stmts.push(stmt!(attr!("rankdir", "LR")));
Graph::DiGraph {
id: id!("output_graph"),
strict: true,
stmts,
}
}
fn output(g: &Graph, path: std::path::PathBuf) {
let graph_svg = exec(
g.clone(),
PrinterContext::default(),
vec![CommandArg::Format(Format::Svg)],
)
.unwrap();
let mut file = File::create(path).unwrap();
file.write_all(graph_svg.as_bytes()).unwrap();
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment