diff --git a/src/algorithms/edmonds_karp.rs b/src/algorithms/edmonds_karp.rs new file mode 100644 index 0000000..23a10d3 --- /dev/null +++ b/src/algorithms/edmonds_karp.rs @@ -0,0 +1,5 @@ +// Edmonds-Karp (also Ford-Fulkerson mit BFS statt DFS), + +//pub fn edmonds_karp(mut graph: StableGraph<(f32, f32), (u64, u64)>, source: NodeIndex, sink: NodeIndex) -> StableGraph<(f32, f32), (u64, u64)> { +// +//} \ No newline at end of file diff --git a/src/algorithms.rs b/src/algorithms/ford_fulkerson.rs similarity index 95% rename from src/algorithms.rs rename to src/algorithms/ford_fulkerson.rs index 20b208d..a210bec 100644 --- a/src/algorithms.rs +++ b/src/algorithms/ford_fulkerson.rs @@ -1,11 +1,9 @@ -use std::cmp::min; -use std::collections::VecDeque; - -use eframe::glow::FALSE; // Ford-Fulkerson mit DFS zur Berechnung des flusserhöhenden Pfades, // Edmonds-Karp (also Ford-Fulkerson mit BFS statt DFS), // Dinic // Goldberg-Tarjan (auch Preflow-Push oder Push-Relabel genannt). +use std::cmp::min; +use std::collections::VecDeque; use egui_graphs::{Graph, DefaultNodeShape, DefaultEdgeShape}; use petgraph::adj::EdgeIndex; use petgraph::data::Build; @@ -15,7 +13,6 @@ use petgraph::visit::{Dfs, EdgeRef, NodeRef, VisitMap, Visitable}; use petgraph::stable_graph::{StableGraph, NodeIndex}; use crate::random_generator::MaxflowProblem; -use crate::graph::{MaxflowNode, MaxflowEdge}; //mod random_generator; fn available_capacity(edge: EdgeReference<'_, (u64, u64)>) -> u64 { @@ -76,12 +73,12 @@ pub fn ford_fulkerson(mut graph: StableGraph<(f32, f32), (u64, u64)>, source: No graph } -pub struct FordFulkerson { - problem: MaxflowProblem, - flows: Graph<(), u64, Directed, u32, DefaultNodeShape, DefaultEdgeShape>, +//pub struct FordFulkerson { +// problem: MaxflowProblem, +// flows: Graph<(), u64, Directed, u32, DefaultNodeShape, DefaultEdgeShape>, // alertnatively, use a map from EdgeIndex to flow value //dfs: Dfs, -} +//} /* impl FordFulkerson { fn new(problem: MaxflowProblem) -> S/elf { diff --git a/src/algorithms/mod.rs b/src/algorithms/mod.rs new file mode 100644 index 0000000..36224ab --- /dev/null +++ b/src/algorithms/mod.rs @@ -0,0 +1,4 @@ +mod edmonds_karp; +mod ford_fulkerson; + +pub use ford_fulkerson::ford_fulkerson; \ No newline at end of file diff --git a/src/graph.rs b/src/graph.rs deleted file mode 100644 index 52e2239..0000000 --- a/src/graph.rs +++ /dev/null @@ -1,33 +0,0 @@ -//use egui_graphs::{Node, Edge}; -use petgraph::graph::{Node, Edge}; - -pub trait MaxflowNode { - fn visited(&self) -> bool; - fn set_visited(&mut self, b: bool); -} - -pub trait MaxflowEdge { - fn remaining_capacity(&self) -> u64; - fn set_capacity(&mut self, c: u64); -} - -/* impl MaxflowNode for Node { - fn visited(&self) -> bool { - *self.() - } - - fn set_visited(&mut self, b: bool) { - let payload = self.payload_mut(); - *payload = b; - } -} */ - -impl MaxflowEdge for Edge<(u64, u64)> { - fn remaining_capacity(&self) -> u64 { - self.weight.1 - self.weight.0 - } - - fn set_capacity(&mut self, c: u64) { - self.weight.0 = c; - } -} diff --git a/src/layout.rs b/src/layout.rs new file mode 100644 index 0000000..f60feeb --- /dev/null +++ b/src/layout.rs @@ -0,0 +1,355 @@ +use std::f32::consts::PI; + +use egui::{ + epaint::{CubicBezierShape, QuadraticBezierShape}, + Color32, Pos2, Stroke, Vec2, Shape, FontId, FontFamily, epaint::{TextShape} +}; + +use petgraph::{matrix_graph::Nullable, stable_graph::IndexType, EdgeType}; + +use egui_graphs::{ + DefaultEdgeShape, DisplayEdge, EdgeProps, + DefaultNodeShape, DisplayNode, NodeProps, + Node, DrawContext +}; + + +#[derive(Clone)] +pub struct CustomEdgeShape { + pub order: usize, + pub selected: bool, + + pub width: f32, + pub tip_size: f32, + pub tip_angle: f32, + pub curve_size: f32, + pub loop_size: f32, + pub label_text: String, +} + +impl From> for CustomEdgeShape { + fn from(props: EdgeProps) -> Self { + Self { + order: props.order, + selected: props.selected, + label_text: props.label, + + width: 1., + tip_size: 10., + tip_angle: std::f32::consts::TAU / 30., + curve_size: 15., + loop_size: 3., + } + } +} + +impl> + DisplayEdge for CustomEdgeShape +{ + // helper functions + // copied from: https://github.com/SeverinAlexB/pknames/blob/d042b6843ee503d92ae36742bab81b14574d9ad3/cli/src/visualization/edge_vis.rs + fn is_inside( + &self, + start: &Node, + end: &Node, + pos: egui::Pos2, + ) -> bool { + if start.id() == end.id() { + return is_inside_loop(start, self, pos); + } + + let pos_start = start.location(); + let pos_end = end.location(); + + if self.order == 0 { + return is_inside_line(pos_start, pos_end, pos, self); + } + + is_inside_curve(start, end, self, pos) + } + + fn shapes( + &mut self, + start: &Node, + end: &Node, + ctx: &DrawContext, + ) -> Vec { + // fixed properties + let edge_color = Color32::LIGHT_GRAY; + let label_color = Color32::DARK_GRAY; + //let label_visible = true; + //let style = ctx.ctx.style().visuals.widgets.inactive; + + // calculate coordinates + let dir = (end.location() - start.location()).normalized(); + let start_connector_point = start.display().closest_boundary_point(dir); + let end_connector_point = end.display().closest_boundary_point(-dir); + let tip_end = end_connector_point; + let edge_start = start_connector_point; + let edge_end = end_connector_point; + + // draw curved edge + let stroke_edge = Stroke::new(self.width * ctx.meta.zoom, edge_color); + let dir_perpendicular = Vec2::new(-dir.y, dir.x); + let center_point = (edge_start + edge_end.to_vec2()).to_vec2() / 2.; + let control_point = (center_point + dir_perpendicular * self.curve_size as f32).to_pos2(); + let tip_dir = (control_point-tip_end).normalized(); + //println!("edge_start: {:?} edge_end {:?} center_point {:?} dir_p {:?} control_point {:?}", edge_start, edge_end, center_point, dir_perpendicular, control_point); + + let arrow_tip_dir_1 = rotate_vector(tip_dir, self.tip_angle) * self.tip_size; + let arrow_tip_dir_2 = rotate_vector(tip_dir, -self.tip_angle) * self.tip_size; + let tip_start_1 = tip_end + arrow_tip_dir_1; + let tip_start_2 = tip_end + arrow_tip_dir_2; + + let edge_end_curved = point_between(tip_start_1, tip_start_2); + let line_curved = QuadraticBezierShape::from_points_stroke( + [ + ctx.meta.canvas_to_screen_pos(edge_start), + ctx.meta.canvas_to_screen_pos(control_point), + ctx.meta.canvas_to_screen_pos(edge_end_curved), + ], + false, + Color32::TRANSPARENT, + stroke_edge, + ); + + // draw tip/arrow + let stroke_tip = Stroke::new(0., edge_color); + let line_curved_tip = Shape::convex_polygon( + vec![ + ctx.meta.canvas_to_screen_pos(tip_end), + ctx.meta.canvas_to_screen_pos(tip_start_1), + ctx.meta.canvas_to_screen_pos(tip_start_2), + ], + edge_color, + stroke_tip, + ); + + + // draw label + let size = (::egui_graphs::node_size(start, dir) + egui_graphs::node_size(end, dir)) / 2.; + let galley = ctx.ctx.fonts(|f| { + f.layout_no_wrap( + self.label_text.clone(), + FontId::new(ctx.meta.canvas_to_screen_size(size), FontFamily::Monospace), + label_color, + ) + }); + + let flattened_curve = line_curved.flatten(None); + let median = *flattened_curve.get(flattened_curve.len() / 2).unwrap(); + + let label_width = galley.rect.width(); + let label_height = galley.rect.height(); + let pos = Pos2::new(median.x - label_width / 2., median.y - label_height); + + let label_shape = TextShape::new(pos, galley, label_color); + + vec![line_curved.into(), line_curved_tip, label_shape.into()] + } + + fn update(&mut self, state: &egui_graphs::EdgeProps) { + self.order = state.order; + self.selected = state.selected; + self.label_text = state.label.to_string(); + } + +} + +// Everything below is copied from: +// https://github.com/SeverinAlexB/pknames/blob/d042b6843ee503d92ae36742bab81b14574d9ad3/cli/src/visualization/edge_vis.rs +fn shape_looped( + node_size: f32, + node_center: Pos2, + stroke: Stroke, + e: &CustomEdgeShape, +) -> CubicBezierShape { + let center_horizon_angle = PI / 4.; + let y_intersect = node_center.y - node_size * center_horizon_angle.sin(); + + let edge_start = Pos2::new( + node_center.x - node_size * center_horizon_angle.cos(), + y_intersect, + ); + let edge_end = Pos2::new( + node_center.x + node_size * center_horizon_angle.cos(), + y_intersect, + ); + + let loop_size = node_size * (e.loop_size + e.order as f32); + + let control_point1 = Pos2::new(node_center.x + loop_size, node_center.y - loop_size); + let control_point2 = Pos2::new(node_center.x - loop_size, node_center.y - loop_size); + + CubicBezierShape::from_points_stroke( + [edge_end, control_point1, control_point2, edge_start], + false, + Color32::default(), + stroke, + ) +} + +fn shape_curved( + pos_start: Pos2, + pos_end: Pos2, + size_start: f32, + size_end: f32, + stroke: Stroke, + e: &CustomEdgeShape, +) -> QuadraticBezierShape { + let vec = pos_end - pos_start; + let dist: f32 = vec.length(); + let dir = vec / dist; + + let start_node_radius_vec = Vec2::new(size_start, size_start) * dir; + let end_node_radius_vec = Vec2::new(size_end, size_end) * dir; + + let tip_end = pos_start + vec - end_node_radius_vec; + + let edge_start = pos_start + start_node_radius_vec; + let edge_end = pos_end + end_node_radius_vec; + + let dir_perpendicular = Vec2::new(-dir.y, dir.x); + let center_point = (edge_start + tip_end.to_vec2()).to_vec2() / 2.0; + let control_point = + (center_point + dir_perpendicular * e.curve_size * e.order as f32).to_pos2(); + + QuadraticBezierShape::from_points_stroke( + [edge_start, control_point, edge_end], + false, + stroke.color, + stroke, + ) +} + +fn is_inside_loop>( + node: &Node, + e: &CustomEdgeShape, + pos: Pos2, +) -> bool { + let node_size = node_size(node); + + let shape = shape_looped(node_size, node.location(), Stroke::default(), e); + is_point_on_cubic_bezier_curve(pos, shape, e.width) +} + +fn is_inside_line(pos_start: Pos2, pos_end: Pos2, pos: Pos2, e: &CustomEdgeShape) -> bool { + let distance = distance_segment_to_point(pos_start, pos_end, pos); + distance <= e.width +} + +fn is_inside_curve< + N: Clone, + E: Clone, + Ty: EdgeType, + Ix: IndexType, + D: DisplayNode, +>( + node_start: &Node, + node_end: &Node, + e: &CustomEdgeShape, + pos: Pos2, +) -> bool { + let pos_start = node_start.location(); + let pos_end = node_end.location(); + + let size_start = node_size(node_start); + let size_end = node_size(node_end); + + let shape = shape_curved( + pos_start, + pos_end, + size_start, + size_end, + Stroke::default(), + e, + ); + is_point_on_quadratic_bezier_curve(pos, shape, e.width) +} + +fn node_size>( + node: &Node, +) -> f32 { + let left_dir = Vec2::new(-1., 0.); + let connector_left = node.display().closest_boundary_point(left_dir); + let connector_right = node.display().closest_boundary_point(-left_dir); + + (connector_right.x - connector_left.x) / 2. +} + +/// Returns the distance from line segment `a``b` to point `c`. +/// Adapted from https://stackoverflow.com/questions/1073336/circle-line-segment-collision-detection-algorithm +fn distance_segment_to_point(a: Pos2, b: Pos2, point: Pos2) -> f32 { + let ac = point - a; + let ab = b - a; + + let d = a + proj(ac, ab); + + let ad = d - a; + + let k = if ab.x.abs() > ab.y.abs() { + ad.x / ab.x + } else { + ad.y / ab.y + }; + + if k <= 0.0 { + return hypot2(point.to_vec2(), a.to_vec2()).sqrt(); + } else if k >= 1.0 { + return hypot2(point.to_vec2(), b.to_vec2()).sqrt(); + } + + hypot2(point.to_vec2(), d.to_vec2()).sqrt() +} + +/// Calculates the square of the Euclidean distance between vectors `a` and `b`. +fn hypot2(a: Vec2, b: Vec2) -> f32 { + (a - b).dot(a - b) +} + +/// Calculates the projection of vector `a` onto vector `b`. +fn proj(a: Vec2, b: Vec2) -> Vec2 { + let k = a.dot(b) / b.dot(b); + Vec2::new(k * b.x, k * b.y) +} + +fn is_point_on_cubic_bezier_curve(point: Pos2, curve: CubicBezierShape, width: f32) -> bool { + is_point_on_bezier_curve(point, curve.flatten(Option::new(10.0)), width) +} + +fn is_point_on_quadratic_bezier_curve( + point: Pos2, + curve: QuadraticBezierShape, + width: f32, +) -> bool { + is_point_on_bezier_curve(point, curve.flatten(Option::new(0.3)), width) +} + +fn is_point_on_bezier_curve(point: Pos2, curve_points: Vec, width: f32) -> bool { + let mut previous_point = None; + for p in curve_points { + if let Some(pp) = previous_point { + let distance = distance_segment_to_point(p, pp, point); + if distance < width { + return true; + } + } + previous_point = Some(p); + } + false +} + +/// rotates vector by angle +fn rotate_vector(vec: Vec2, angle: f32) -> Vec2 { + let cos = angle.cos(); + let sin = angle.sin(); + Vec2::new(cos * vec.x - sin * vec.y, sin * vec.x + cos * vec.y) +} + +/// finds point exactly in the middle between 2 points +fn point_between(p1: Pos2, p2: Pos2) -> Pos2 { + let base = p1 - p2; + let base_len = base.length(); + let dir = base / base_len; + p1 - (base_len / 2.) * dir +} diff --git a/src/main.rs b/src/main.rs index 3e49081..e74576a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,18 @@ use eframe::{run_native, App, CreationContext, NativeOptions, Frame}; use egui::{CentralPanel, SidePanel, Context}; -use egui_graphs::{GraphView, Graph, SettingsStyle, LayoutRandom, LayoutStateRandom}; +use egui_graphs::{DefaultEdgeShape, DefaultNodeShape, Graph, GraphView, LayoutRandom, LayoutStateRandom, SettingsStyle}; -use algorithms::ford_fulkerson; +use petgraph::Directed; use random_generator::MaxflowProblem; +use layout::CustomEdgeShape; +use crate::algorithms::ford_fulkerson; + mod random_generator; mod algorithms; -mod graph; +mod layout; pub struct MaxflowApp { - g: Graph<(f32, f32), (u64, u64)>, + g: Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape>, p: MaxflowProblem, node_count: u64, max_capacity: u64, @@ -26,7 +29,7 @@ impl App for MaxflowApp { fn update(&mut self, ctx: &Context, _: &mut Frame) { ctx.set_theme(egui::Theme::Light); CentralPanel::default().show(ctx, |ui| { - ui.add(&mut GraphView::<_, _, _, _, _, _, LayoutStateRandom, LayoutRandom>::new(&mut self.g).with_styles(&SettingsStyle::default().with_labels_always(true))); + ui.add(&mut GraphView::<_, _, _, _, _, CustomEdgeShape, LayoutStateRandom, LayoutRandom>::new(&mut self.g).with_styles(&SettingsStyle::default().with_labels_always(true))); }); SidePanel::right("right_panel") .min_width(200.) diff --git a/src/random_generator.rs b/src/random_generator.rs index 2a193cf..ca4151a 100644 --- a/src/random_generator.rs +++ b/src/random_generator.rs @@ -1,6 +1,6 @@ use petgraph::stable_graph::{StableGraph, NodeIndex}; use petgraph::{Directed}; -use egui_graphs::{Graph}; +use egui_graphs::{default_edge_transform, default_node_transform, to_graph_custom, DefaultEdgeShape, DefaultNodeShape, Graph}; use egui::Pos2; use rand::thread_rng; use rand::Rng; @@ -9,15 +9,18 @@ use itertools::Itertools; use geo::{Line, coord, line_intersection::{line_intersection, LineIntersection}}; use egui::Color32; +use crate::layout::CustomEdgeShape; + pub struct MaxflowProblem { - pub g: Graph<(), u64, Directed, u32, egui_graphs::DefaultNodeShape, egui_graphs::DefaultEdgeShape>, + pub g: StableGraph<(f32, f32), (u64, u64)>, pub s: NodeIndex, pub t: NodeIndex, } impl MaxflowProblem { pub fn new(num_nodes: u64, max_capacity: u64) -> Self { - let mut graph: Graph<_, u64> = Graph::new(StableGraph::default()); + // Node Type: (x,y) Edge Type: (current_flow, capacity) + let mut graph: StableGraph<(f32, f32), (u64, u64)> = StableGraph::default(); let mut nodes: Vec = Vec::new(); let mut rng = thread_rng(); @@ -25,30 +28,27 @@ impl MaxflowProblem { for _i in 0..num_nodes { let x: f32 = rng.gen_range(0.0..=400.0); let y: f32 = rng.gen_range(0.0..=300.0); - let pos = Pos2::new(x,y); - let n = graph.add_node_with_label_and_location((), String::new(), pos); + let n = graph.add_node((x,y)); nodes.push(n); } // select source (s) and sink (t) - let s = *nodes.choose(&mut rng).expect("no nodes found"); - let t = *nodes.choose(&mut rng).expect("no nodes found"); - graph.node_mut(s).expect("node index not found").set_label(String::from("s")); - graph.node_mut(t).expect("node index not found").set_label(String::from("t")); - graph.node_mut(s).expect("node index not found").set_color(Color32::RED); - graph.node_mut(t).expect("node index not found").set_color(Color32::GREEN); + let (s,t) = nodes.choose_multiple(&mut rng, 2).copied().collect_tuple::<(NodeIndex, NodeIndex)>().expect("not enough nodes"); // iterate over all possible edges let mut possible_edges: Vec<(NodeIndex, NodeIndex, f32)> = Vec::new(); for pair in nodes.clone().into_iter().combinations(2) { // calculate distance & append tuple (node_a, node_b, distance) to possible_edges - let distance = Self::distance(graph.node(pair[0]).expect("node index not found").location(), graph.node(pair[1]).expect("node index not found").location()); + let distance = Self::distance( + *graph.node_weight(pair[0]).expect("node index not found"), + *graph.node_weight(pair[1]).expect("node index not found") + ); + //let distance = Self::distance(graph.node(pair[0]).expect("node index not found").location(), graph.node(pair[1]).expect("node index not found").location()); possible_edges.push((pair[0], pair[1], distance)); } // sort mapping by distance possible_edges.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap()); - println!("possible_edges len: {}", possible_edges.len()); // insert the edge if possible let mut inserted_edges: Vec<(NodeIndex, NodeIndex)> = Vec::new(); @@ -57,10 +57,10 @@ impl MaxflowProblem { let mut intersects = false; for (node3, node4) in &inserted_edges { if Self::intersects( - graph.node(node1).expect("node index not found").location(), - graph.node(node2).expect("node index not found").location(), - graph.node(*node3).expect("node index not found").location(), - graph.node(*node4).expect("node index not found").location(), + *graph.node_weight(node1).expect("node index not found"), + *graph.node_weight(node2).expect("node index not found"), + *graph.node_weight(*node3).expect("node index not found"), + *graph.node_weight(*node4).expect("node index not found"), ) { intersects = true; break; @@ -71,29 +71,54 @@ impl MaxflowProblem { inserted_edges.push((node1, node2)); inserted_edges.push((node2, node1)); let capacity: u64 = rng.gen_range(1..=max_capacity); - graph.add_edge_with_label(node1, node2, capacity, capacity.to_string()); - graph.add_edge_with_label(node2, node1, capacity, capacity.to_string()); + graph.add_edge(node1, node2, (0, capacity)); + graph.add_edge(node2, node1, (0, capacity)); } } Self { g: graph, s: s, t: t } } + pub fn from(g: StableGraph<(f32, f32), (u64, u64)>, s: NodeIndex, t: NodeIndex) -> Self{ + Self {g: g, s: s, t: t} + } + // returns the eudclidean distance between two points - fn distance(a: Pos2, b: Pos2) -> f32 { - let delta_x = a.x - b.x; - let delta_y = a.y - b.y; + fn distance(a: (f32, f32), b: (f32, f32)) -> f32 { + let delta_x = a.0 - b.0; + let delta_y = a.1 - b.1; return f32::sqrt(delta_x.powf(2.) + delta_y.powf(2.)); } // checks if the lines between a-b and c-d intersect - fn intersects(a: Pos2, b: Pos2, c: Pos2, d: Pos2) -> bool { - let line1 = Line::new(coord! {x: a.x, y: a.y }, coord! {x: b.x, y: b.y } ); - let line2 = Line::new(coord! {x: c.x, y: c.y }, coord! {x: d.x, y: d.y } ); + fn intersects(a: (f32, f32), b: (f32, f32), c: (f32, f32), d: (f32, f32)) -> bool { + let line1 = Line::new(coord! {x: a.0, y: a.1 }, coord! {x: b.0, y: b.1 } ); + let line2 = Line::new(coord! {x: c.0, y: c.1 }, coord! {x: d.0, y: d.1 } ); match line_intersection(line1, line2) { // only return true for 'proper' intersections which are not at the end of a line Some(LineIntersection::SinglePoint { is_proper: true, .. }) => true, _ => false } } + + pub fn to_gui_graph(&self) -> Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape> { + let mut graph = to_graph_custom(&self.g, + |n| { + let (x, y) = *n.payload(); + default_node_transform(n); + n.set_location(Pos2::new(x, y)); + n.set_label(String::from("")); + }, + |e| { + let (flow, capacity): (u64, u64) = *e.payload(); + default_edge_transform(e); + e.set_label(format!("{flow}:{capacity}")); + } + ); + graph.node_mut(self.s).expect("node index not found").set_label(String::from("s")); + graph.node_mut(self.t).expect("node index not found").set_label(String::from("t")); + graph.node_mut(self.s).expect("node index not found").set_color(Color32::RED); + graph.node_mut(self.t).expect("node index not found").set_color(Color32::GREEN); + graph + } }