From d45dc1775086071c4129656735af816965b56546 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Mon, 28 Mar 2022 07:39:36 -0700 Subject: [PATCH 01/37] First setup of planar layout for retworkx --- retworkx/__init__.py | 26 ++++++++++++++++++++ src/layout/mod.rs | 41 ++++++++++++++++++++++++++++++++ src/layout/planar.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ 4 files changed, 125 insertions(+) create mode 100644 src/layout/planar.rs diff --git a/retworkx/__init__.py b/retworkx/__init__.py index 460111643..7f55b49d0 100644 --- a/retworkx/__init__.py +++ b/retworkx/__init__.py @@ -1104,6 +1104,32 @@ def _graph_complement(graph): return graph_complement(graph) +@functools.singledispatch +def planar_layout(graph, scale=1.0, center=None): + """Generate a planar layout + + :param PyGraph|PyDiGraph graph: The graph to generate the layout for + :param float|None scale: Scale factor for positions.If scale is ``None``, + no re-scaling is performed. (``default=1.0``) + :param tuple center: An optional center position. This is a 2 tuple of two + ``float`` values for the center position + + :returns: The planar layout of the graph. + :rtype: Pos2DMapping + """ + raise TypeError("Invalid Input Type %s for graph" % type(graph)) + + +@planar_layout.register(PyDiGraph) +def _digraph_planar_layout(graph, scale=1.0, center=None): + return digraph_planar_layout(graph, scale=1.0, center=center) + + +@planar_layout.register(PyGraph) +def _graph_planar_layout(graph, scale=1.0, center=None): + return graph_planar_layout(graph, scale=1.0, center=center) + + @functools.singledispatch def random_layout(graph, center=None, seed=None): """Generate a random layout diff --git a/src/layout/mod.rs b/src/layout/mod.rs index ba6f66982..8003d4e81 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -12,6 +12,7 @@ mod bipartite; mod circular; +mod planar; mod random; mod shell; mod spiral; @@ -193,6 +194,46 @@ pub fn digraph_spring_layout( ) } +/// Generate a planar layout +/// +/// :param PyGraph graph: The graph to generate the layout for +/// :param float|None scale: Scale factor for positions.If scale is ``None``, +/// no re-scaling is performed. (``default=1.0``) +/// :param tuple center: An optional center position. This is a 2 tuple of two +/// ``float`` values for the center position +/// +/// :returns: The planar layout of the graph. +/// :rtype: Pos2DMapping +#[pyfunction] +#[pyo3(text_signature = "(graph, / scale=1.0, center=None)")] +pub fn graph_planar_layout( + graph: &graph::PyGraph, + scale: Option, + center: Option<[f64; 2]>, +) -> Pos2DMapping { + planar::planar_layout(&graph.graph, scale, center) +} + +/// Generate a planar layout +/// +/// :param PyDiGraph graph: The graph to generate the layout for +/// :param float|None scale: Scale factor for positions.If scale is ``None``, +/// no re-scaling is performed. (``default=1.0``) +/// :param tuple center: An optional center position. This is a 2 tuple of two +/// ``float`` values for the center position +/// +/// :returns: The planar layout of the graph. +/// :rtype: Pos2DMapping +#[pyfunction] +#[pyo3(text_signature = "(graph, / scale=1.0, center=None)")] +pub fn digraph_planar_layout( + graph: &digraph::PyDiGraph, + scale: Option, + center: Option<[f64; 2]>, +) -> Pos2DMapping { + planar::planar_layout(&graph.graph, scale, center) +} + /// Generate a random layout /// /// :param PyGraph graph: The graph to generate the layout for diff --git a/src/layout/planar.rs b/src/layout/planar.rs new file mode 100644 index 000000000..65b5315d0 --- /dev/null +++ b/src/layout/planar.rs @@ -0,0 +1,56 @@ +use petgraph::EdgeType; + +use crate::iterators::Pos2DMapping; +use crate::StablePyGraph; + +pub fn planar_layout( + graph: &StablePyGraph, + scale: Option, + center: Option<[f64; 2]>, +) -> Pos2DMapping { + + match scale { + Some(scale) => scale, + None => 1.0, + }; + println!("HELLO WORLD!"); + Pos2DMapping { + pos_map: graph + .node_indices() + .map(|n| { + let random_tuple: [f64; 2] = [5.0, 5.0]; + match center { + Some(center) => ( + n.index(), + [random_tuple[0] + center[0], random_tuple[1] + center[1]], + ), + None => (n.index(), random_tuple), + } + }) + .collect(), + } +} + +// import numpy as np +// +// if dim != 2: +// raise ValueError("can only handle 2 dimensions") +// +// G, center = _process_params(G, center, dim) +// +// if len(G) == 0: +// return {} +// +// if isinstance(G, nx.PlanarEmbedding): +// embedding = G +// else: +// is_planar, embedding = nx.check_planarity(G) +// if not is_planar: +// raise nx.NetworkXException("G is not planar.") +// pos = nx.combinatorial_embedding_to_pos(embedding) +// node_list = list(embedding) +// pos = np.row_stack([pos[x] for x in node_list]) +// pos = pos.astype(np.float64) +// pos = rescale_layout(pos, scale=scale) + center +// return dict(zip(node_list, pos)) +// \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4a35418d1..31308837a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -378,6 +378,8 @@ fn retworkx(py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(digraph_core_number))?; m.add_wrapped(wrap_pyfunction!(graph_complement))?; m.add_wrapped(wrap_pyfunction!(digraph_complement))?; + m.add_wrapped(wrap_pyfunction!(graph_planar_layout))?; + m.add_wrapped(wrap_pyfunction!(digraph_planar_layout))?; m.add_wrapped(wrap_pyfunction!(graph_random_layout))?; m.add_wrapped(wrap_pyfunction!(digraph_random_layout))?; m.add_wrapped(wrap_pyfunction!(graph_bipartite_layout))?; From c0f7cd93aed44fedcc7367177fbc3afe404eb9f5 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 20 Apr 2022 14:04:07 -0700 Subject: [PATCH 02/37] Add PlanarEmbedding structure --- src/layout/mod.rs | 6 +- src/layout/planar.rs | 193 ++++++++++++++++++++++++++++++++----------- 2 files changed, 150 insertions(+), 49 deletions(-) diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 8003d4e81..9e3b4d8f1 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -207,11 +207,12 @@ pub fn digraph_spring_layout( #[pyfunction] #[pyo3(text_signature = "(graph, / scale=1.0, center=None)")] pub fn graph_planar_layout( + py: Python, graph: &graph::PyGraph, scale: Option, center: Option<[f64; 2]>, ) -> Pos2DMapping { - planar::planar_layout(&graph.graph, scale, center) + planar::planar_layout(py, &graph.graph, scale, center) } /// Generate a planar layout @@ -227,11 +228,12 @@ pub fn graph_planar_layout( #[pyfunction] #[pyo3(text_signature = "(graph, / scale=1.0, center=None)")] pub fn digraph_planar_layout( + py: Python, graph: &digraph::PyDiGraph, scale: Option, center: Option<[f64; 2]>, ) -> Pos2DMapping { - planar::planar_layout(&graph.graph, scale, center) + planar::planar_layout(py, &graph.graph, scale, center) } /// Generate a random layout diff --git a/src/layout/planar.rs b/src/layout/planar.rs index 65b5315d0..b1f363a8c 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -1,56 +1,155 @@ -use petgraph::EdgeType; +use petgraph::{EdgeType, Directed}; +use petgraph::prelude::*; +use pyo3::{PyObject}; +use pyo3::prelude::*; +use super::spring::{recenter, rescale, Point}; use crate::iterators::Pos2DMapping; use crate::StablePyGraph; +use crate::connected_components; +use retworkx_core::dictmap::*; pub fn planar_layout( + py: Python, graph: &StablePyGraph, scale: Option, - center: Option<[f64; 2]>, + center: Option, ) -> Pos2DMapping { - match scale { - Some(scale) => scale, - None => 1.0, - }; - println!("HELLO WORLD!"); - Pos2DMapping { - pos_map: graph - .node_indices() - .map(|n| { - let random_tuple: [f64; 2] = [5.0, 5.0]; - match center { - Some(center) => ( - n.index(), - [random_tuple[0] + center[0], random_tuple[1] + center[1]], - ), - None => (n.index(), random_tuple), - } - }) - .collect(), - } -} - -// import numpy as np -// -// if dim != 2: -// raise ValueError("can only handle 2 dimensions") -// -// G, center = _process_params(G, center, dim) -// -// if len(G) == 0: -// return {} -// -// if isinstance(G, nx.PlanarEmbedding): -// embedding = G -// else: -// is_planar, embedding = nx.check_planarity(G) -// if not is_planar: -// raise nx.NetworkXException("G is not planar.") -// pos = nx.combinatorial_embedding_to_pos(embedding) -// node_list = list(embedding) -// pos = np.row_stack([pos[x] for x in node_list]) -// pos = pos.astype(np.float64) -// pos = rescale_layout(pos, scale=scale) + center -// return dict(zip(node_list, pos)) -// \ No newline at end of file + let node_num = graph.node_count(); + if node_num == 0 { + return Pos2DMapping { + pos_map: DictMap::new(), + }; + } + let mut pos: Vec = Vec::with_capacity(node_num); + let mut planar = PlanarEmbedding::new(); + + if !is_planar(graph) { + return Pos2DMapping { + pos_map: DictMap::new(), + }; + } else { + create_embedding(graph, &mut planar.embedding); + combinitorial_embedding_to_pos(&planar.embedding, &mut pos); + + if let Some(scale) = scale { + rescale(&mut pos, scale, (0..node_num).collect()); + } + if let Some(center) = center { + recenter(&mut pos, center); + } + Pos2DMapping { + pos_map: planar.embedding.node_indices().map(|n| n.index()).zip(pos).collect(), + } + } +} + +pub fn is_planar( + graph: &StablePyGraph, +) -> bool { + true +} + +pub fn create_embedding( + graph: &StablePyGraph, + embedding: &mut StablePyGraph, +) -> bool { + // DEBUG CODE FOR TESTING BASIC EMBEDDING + for v in graph.node_indices() { + if v.index() < 5 { + println!("GRAPH {:?} {}", v, graph[v].clone()); + embedding.add_node(graph[v].clone()); + } else { + break; + } + } + true +} + +pub fn combinitorial_embedding_to_pos ( + embedding: &StablePyGraph, + pos: &mut Vec, +){ + if embedding.node_count() < 4 { + *pos = [[0.0, 0.0], [2.0, 0.0], [1.0, 1.0]].to_vec() + } + let outer_face = triangulate_embedding(&embedding, true); + + // DEBUG: Need proper value for pos + *pos = [[0.4, 0.5]].to_vec() +} + +pub fn triangulate_embedding ( + embedding: &StablePyGraph, + fully_triangulate: bool, +) -> Vec { + if embedding.node_count() <= 1 { + return embedding.node_indices().map(|n| n).collect::>(); + } + let component_nodes = connected_components(embedding); + let outer_face = embedding.node_indices().map(|n| n).collect::>(); + println!("DFLT {:?}", outer_face); + outer_face +} + +struct PlanarEmbedding { + embedding: StablePyGraph, +} + +impl Default for PlanarEmbedding { + fn default () -> Self { + PlanarEmbedding { + embedding: StablePyGraph::::new(), + } + } +} +impl PlanarEmbedding { + pub fn new () -> Self { + PlanarEmbedding { + embedding: StablePyGraph::::new(), + } + } + + fn neighbors_cw_order (&self, v: NodeIndex) + { + } + + fn check_structure (&self) + { + } + + fn add_half_edge_ccw ( + &self, + start_node: NodeIndex, + end_node: NodeIndex, + ref_neighbor: Option + ){ + } + + fn add_half_edge_cw ( + &self, + start_node: NodeIndex, + end_node: NodeIndex, + ref_neighbor: Option + ){ + let weight: PyObject = "abc"; + self.embedding.add_edge(start_node, end_node, weight); + if !ref_neighbor.is_none() { + self.embedding.add_edge(start_node, end_node, weight); + } + } + + fn add_half_edge_first (&self, start_node: NodeIndex, end_node: NodeIndex) + { + } + + fn next_face_half_edge (&self, v: NodeIndex, w: NodeIndex) + { + } + + fn traverse_face (&self, v: NodeIndex, w: NodeIndex, mark_half_edges: bool) + { + } + +} From fab350ad72d26fd05c7a1aaa5e1d46fd0537ca6f Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sat, 23 Apr 2022 15:07:08 -0700 Subject: [PATCH 03/37] Add embedding functions --- src/layout/planar.rs | 103 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 83 insertions(+), 20 deletions(-) diff --git a/src/layout/planar.rs b/src/layout/planar.rs index b1f363a8c..a9db1a071 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -1,14 +1,76 @@ use petgraph::{EdgeType, Directed}; use petgraph::prelude::*; -use pyo3::{PyObject}; use pyo3::prelude::*; use super::spring::{recenter, rescale, Point}; use crate::iterators::Pos2DMapping; use crate::StablePyGraph; +use crate::Graph; use crate::connected_components; use retworkx_core::dictmap::*; +pub struct CwCcw { + cw: Option, + ccw: Option, +} + +impl Default for CwCcw { + fn default() -> Self { + CwCcw { cw: None, ccw: None } + } +} + +impl CwCcw { + fn new(cw: T, ccw: T) -> Self { + CwCcw { + cw: Some(cw), + ccw: Some(ccw), + } + } + + fn cw_is_empty(&self) -> bool { + self.cw.is_none() + } + + fn ccw_is_empty(&self) -> bool { + self.ccw.is_none() + } + + fn cw_unwrap(self) -> T { + self.cw.unwrap() + } + + fn ccw_unwrap(self) -> T { + self.ccw.unwrap() + } + + fn cw_as_ref(&mut self) -> Option<&T> { + self.cw.as_ref() + } + + fn ccw_as_ref(&mut self) -> Option<&T> { + self.ccw.as_ref() + } + + fn cw_as_mut(&mut self) -> Option<&mut T> { + self.cw.as_mut() + } + + fn cc_as_mut(&mut self) -> Option<&mut T> { + self.ccw.as_mut() + } +} + +pub struct FirstNbr { + first_nbr: Option, +} + +impl Default for FirstNbr { + fn default() -> Self { + FirstNbr { first_nbr: None } + } +} + pub fn planar_layout( py: Python, graph: &StablePyGraph, @@ -53,22 +115,22 @@ pub fn is_planar( pub fn create_embedding( graph: &StablePyGraph, - embedding: &mut StablePyGraph, + embedding: &mut Graph, Directed>, ) -> bool { // DEBUG CODE FOR TESTING BASIC EMBEDDING - for v in graph.node_indices() { - if v.index() < 5 { - println!("GRAPH {:?} {}", v, graph[v].clone()); - embedding.add_node(graph[v].clone()); - } else { - break; - } - } + // for v in graph.node_indices() { + // if v.index() < 5 { + // println!("GRAPH {:?} {}", v, graph[v].clone()); + // embedding.add_node(graph[v].clone()); + // } else { + // break; + // } + // } true } pub fn combinitorial_embedding_to_pos ( - embedding: &StablePyGraph, + embedding: &Graph, Directed>, pos: &mut Vec, ){ if embedding.node_count() < 4 { @@ -81,33 +143,33 @@ pub fn combinitorial_embedding_to_pos ( } pub fn triangulate_embedding ( - embedding: &StablePyGraph, + embedding: &Graph, Directed>, fully_triangulate: bool, ) -> Vec { if embedding.node_count() <= 1 { return embedding.node_indices().map(|n| n).collect::>(); } - let component_nodes = connected_components(embedding); + //let component_nodes = connected_components(embedding); let outer_face = embedding.node_indices().map(|n| n).collect::>(); println!("DFLT {:?}", outer_face); outer_face } struct PlanarEmbedding { - embedding: StablePyGraph, + embedding: Graph::, Directed>, } impl Default for PlanarEmbedding { fn default () -> Self { PlanarEmbedding { - embedding: StablePyGraph::::new(), + embedding: Graph::, Directed>::new(), } } } impl PlanarEmbedding { pub fn new () -> Self { PlanarEmbedding { - embedding: StablePyGraph::::new(), + embedding: Graph::, Directed>::new(), } } @@ -128,15 +190,16 @@ impl PlanarEmbedding { } fn add_half_edge_cw ( - &self, + &mut self, start_node: NodeIndex, end_node: NodeIndex, ref_neighbor: Option ){ - let weight: PyObject = "abc"; - self.embedding.add_edge(start_node, end_node, weight); + let mut cw_weight = CwCcw::::default(); + let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); if !ref_neighbor.is_none() { - self.embedding.add_edge(start_node, end_node, weight); + self.embedding[new_edge].cw = Some(end_node.clone()); + //self.embedding.update_edge(start_node, end_node, upd_weight); } } From e770d14408daafe443eb47eb4cac8371472e458b Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 29 Apr 2022 08:43:57 -0700 Subject: [PATCH 04/37] Add update_edge_weight --- src/layout/planar.rs | 100 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 18 deletions(-) diff --git a/src/layout/planar.rs b/src/layout/planar.rs index a9db1a071..e4c781cee 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -56,21 +56,44 @@ impl CwCcw { self.cw.as_mut() } - fn cc_as_mut(&mut self) -> Option<&mut T> { + fn ccw_as_mut(&mut self) -> Option<&mut T> { self.ccw.as_mut() } } -pub struct FirstNbr { - first_nbr: Option, +pub struct FirstNbr { + first_nbr: Option, } -impl Default for FirstNbr { +impl Default for FirstNbr { fn default() -> Self { FirstNbr { first_nbr: None } } } +impl FirstNbr { + fn new(first_nbr: T) -> Self { + FirstNbr { + first_nbr: Some(first_nbr), + } + } + + fn first_nbr_is_empty(&self) -> bool { + self.first_nbr.is_none() + } + + fn first_nbr_unwrap(self) -> T { + self.first_nbr.unwrap() + } + + fn first_nbr_as_ref(&mut self) -> Option<&T> { + self.first_nbr.as_ref() + } + + fn first_nbr_as_mut(&mut self) -> Option<&mut T> { + self.first_nbr.as_mut() + } +} pub fn planar_layout( py: Python, graph: &StablePyGraph, @@ -115,7 +138,7 @@ pub fn is_planar( pub fn create_embedding( graph: &StablePyGraph, - embedding: &mut Graph, Directed>, + embedding: &mut Graph, CwCcw, Directed>, ) -> bool { // DEBUG CODE FOR TESTING BASIC EMBEDDING // for v in graph.node_indices() { @@ -130,7 +153,7 @@ pub fn create_embedding( } pub fn combinitorial_embedding_to_pos ( - embedding: &Graph, Directed>, + embedding: &Graph, CwCcw, Directed>, pos: &mut Vec, ){ if embedding.node_count() < 4 { @@ -143,7 +166,7 @@ pub fn combinitorial_embedding_to_pos ( } pub fn triangulate_embedding ( - embedding: &Graph, Directed>, + embedding: &Graph, CwCcw, Directed>, fully_triangulate: bool, ) -> Vec { if embedding.node_count() <= 1 { @@ -156,20 +179,20 @@ pub fn triangulate_embedding ( } struct PlanarEmbedding { - embedding: Graph::, Directed>, + embedding: Graph::, CwCcw, Directed>, } impl Default for PlanarEmbedding { fn default () -> Self { PlanarEmbedding { - embedding: Graph::, Directed>::new(), + embedding: Graph::, CwCcw, Directed>::new(), } } } impl PlanarEmbedding { pub fn new () -> Self { PlanarEmbedding { - embedding: Graph::, Directed>::new(), + embedding: Graph::, CwCcw, Directed>::new(), } } @@ -181,25 +204,55 @@ impl PlanarEmbedding { { } - fn add_half_edge_ccw ( - &self, + fn add_half_edge_cw ( + &mut self, start_node: NodeIndex, end_node: NodeIndex, - ref_neighbor: Option + ref_nbr: Option ){ + let cw_weight = CwCcw::::default(); + let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); + if ref_nbr.is_none() { + // The start node has no neighbors + //cw_weight.cw = Some(end_node.clone()); + //self.embedding.update_edge(start_node, end_node, cw_weight); + self.update_edge_weight(start_node, end_node, end_node, false); + // self.embedding[new_edge].ccw = Some(end_node.clone()); + // self.embedding[start_node].first_nbr = Some(end_node.clone()); + // return + } + // // if ref_nbr not in self[start_node] error + // let ref_nbr_edge = self.embedding.find_edge(start_node, ref_nbr.unwrap().clone()); + // let cw_ref_node = ref_nbr_edge.unwrap().cw; + // let cw_ref_edge = self.embedding.find_edge(start_node, cw_ref_node); + + // self.embedding[ref_nbr_edge].cw = Some(end_node.clone()); + // self.embedding[new_edge].cw = Some(cw_ref_node.clone()); + // self.embedding[cw_ref_edge].ccw = Some(end_node.clone()); + // self.embedding[new_edge].ccw = ref_nbr.unwrap().clone(); } - fn add_half_edge_cw ( + fn add_half_edge_ccw ( &mut self, start_node: NodeIndex, end_node: NodeIndex, - ref_neighbor: Option + ref_nbr: Option ){ let mut cw_weight = CwCcw::::default(); let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); - if !ref_neighbor.is_none() { + if !ref_nbr.is_none() { self.embedding[new_edge].cw = Some(end_node.clone()); - //self.embedding.update_edge(start_node, end_node, upd_weight); + self.embedding[new_edge].ccw = Some(end_node.clone()); + self.embedding[start_node].first_nbr = Some(end_node.clone()); + return + } else { + return + //let ccw_ref = self.embedding.find_edge(start_node, ref_nbr.unwrap().clone()).ccw; + //self.add_half_edge_cw(start_node, end_node, ccw_ref); + + //if ref_nbr == self.embedding[start_node].first_nbr { + // self.embedding[start_node].first_nbr = Some(end_node.clone()); + //} } } @@ -214,5 +267,16 @@ impl PlanarEmbedding { fn traverse_face (&self, v: NodeIndex, w: NodeIndex, mark_half_edges: bool) { } - + fn update_edge_weight(&mut self, v: NodeIndex, w: NodeIndex, new_value: NodeIndex, cw: bool) { + //let found_weight = self.embedding.edge_weight_mut(self.embedding.find_edge(v, w).unwrap()).unwrap(); + let found_edge = self.embedding.find_edge(v, w); + let found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()).unwrap().clone(); + + if cw { + found_weight.cw = Some(new_value.clone()); + } else { + found_weight.ccw = Some(new_value.clone()); + } + self.embedding.update_edge(v, w, found_weight); + } } From fe6b0dbe17f87d002ca5c9381bd36c2acb8cf7c4 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 4 May 2022 07:05:10 -0700 Subject: [PATCH 05/37] More PlanarEmbedding functions --- src/layout/planar.rs | 71 +++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/src/layout/planar.rs b/src/layout/planar.rs index e4c781cee..69d7f7bc6 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -211,25 +211,25 @@ impl PlanarEmbedding { ref_nbr: Option ){ let cw_weight = CwCcw::::default(); + let first_nbr = FirstNbr::::default(); let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); if ref_nbr.is_none() { // The start node has no neighbors - //cw_weight.cw = Some(end_node.clone()); - //self.embedding.update_edge(start_node, end_node, cw_weight); + self.update_edge_weight(start_node, end_node, end_node, true); self.update_edge_weight(start_node, end_node, end_node, false); - // self.embedding[new_edge].ccw = Some(end_node.clone()); - // self.embedding[start_node].first_nbr = Some(end_node.clone()); - // return + self.embedding[start_node].first_nbr = Some(end_node); + return } - // // if ref_nbr not in self[start_node] error - // let ref_nbr_edge = self.embedding.find_edge(start_node, ref_nbr.unwrap().clone()); - // let cw_ref_node = ref_nbr_edge.unwrap().cw; - // let cw_ref_edge = self.embedding.find_edge(start_node, cw_ref_node); - - // self.embedding[ref_nbr_edge].cw = Some(end_node.clone()); - // self.embedding[new_edge].cw = Some(cw_ref_node.clone()); - // self.embedding[cw_ref_edge].ccw = Some(end_node.clone()); - // self.embedding[new_edge].ccw = ref_nbr.unwrap().clone(); + // if ref_nbr not in self[start_node] error + let ref_nbr_node = ref_nbr.unwrap(); + let cw_ref_edge = self.embedding.find_edge(start_node, ref_nbr_node).unwrap(); + let cw_ref_node = self.embedding.edge_weight_mut(cw_ref_edge).unwrap().cw.unwrap(); + + // Alter half-edge data structures + self.update_edge_weight(start_node, ref_nbr_node, end_node, true); + self.update_edge_weight(start_node, end_node, cw_ref_node, true); + self.update_edge_weight(start_node, cw_ref_node, end_node, false); + self.update_edge_weight(start_node, end_node, ref_nbr_node, false); } fn add_half_edge_ccw ( @@ -238,26 +238,31 @@ impl PlanarEmbedding { end_node: NodeIndex, ref_nbr: Option ){ - let mut cw_weight = CwCcw::::default(); - let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); - if !ref_nbr.is_none() { - self.embedding[new_edge].cw = Some(end_node.clone()); - self.embedding[new_edge].ccw = Some(end_node.clone()); - self.embedding[start_node].first_nbr = Some(end_node.clone()); - return + if ref_nbr.is_none() { + let cw_weight = CwCcw::::default(); + let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); + self.update_edge_weight(start_node, end_node, end_node, true); + self.update_edge_weight(start_node, end_node, end_node, false); + self.embedding[start_node].first_nbr = Some(end_node); } else { - return - //let ccw_ref = self.embedding.find_edge(start_node, ref_nbr.unwrap().clone()).ccw; - //self.add_half_edge_cw(start_node, end_node, ccw_ref); - - //if ref_nbr == self.embedding[start_node].first_nbr { - // self.embedding[start_node].first_nbr = Some(end_node.clone()); - //} + let ref_nbr_node = ref_nbr.unwrap(); + let ccw_ref_edge = self.embedding.find_edge(start_node, ref_nbr_node).unwrap(); + let ccw_ref_node = self.embedding.edge_weight_mut(ccw_ref_edge).unwrap().ccw; + self.add_half_edge_cw(start_node, end_node, ccw_ref_node); + if ref_nbr == self.embedding[start_node].first_nbr { + self.embedding[start_node].first_nbr = Some(end_node); + } } } fn add_half_edge_first (&self, start_node: NodeIndex, end_node: NodeIndex) { + if self.embedding.contains(start_node) && !self.embedding[start_node].first_nbr.is_none() { + let ref_node = self.embedding[start_node].first_nbr.unwrap(); + } else { + let ref_node = Some(None); + } + self.add_half_edge_ccw(start_node, end_node, ref_node); } fn next_face_half_edge (&self, v: NodeIndex, w: NodeIndex) @@ -267,16 +272,14 @@ impl PlanarEmbedding { fn traverse_face (&self, v: NodeIndex, w: NodeIndex, mark_half_edges: bool) { } - fn update_edge_weight(&mut self, v: NodeIndex, w: NodeIndex, new_value: NodeIndex, cw: bool) { - //let found_weight = self.embedding.edge_weight_mut(self.embedding.find_edge(v, w).unwrap()).unwrap(); + fn update_edge_weight(&mut self, v: NodeIndex, w: NodeIndex, new_node: NodeIndex, cw: bool) { let found_edge = self.embedding.find_edge(v, w); - let found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()).unwrap().clone(); + let mut found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()).unwrap(); if cw { - found_weight.cw = Some(new_value.clone()); + found_weight.cw = Some(new_node); } else { - found_weight.ccw = Some(new_value.clone()); + found_weight.ccw = Some(new_node); } - self.embedding.update_edge(v, w, found_weight); } } From 64895a24c81bfe7ca218dccacac03fa8c7f995ef Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sat, 7 May 2022 07:26:12 -0700 Subject: [PATCH 06/37] Cleanup PlanarEmbedding --- src/layout/planar.rs | 114 +++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 59 deletions(-) diff --git a/src/layout/planar.rs b/src/layout/planar.rs index 69d7f7bc6..ed94abb97 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -1,22 +1,26 @@ -use petgraph::{EdgeType, Directed}; use petgraph::prelude::*; +use petgraph::{Directed, EdgeType}; +use petgraph::visit::NodeIndexable; use pyo3::prelude::*; use super::spring::{recenter, rescale, Point}; +use crate::connected_components; use crate::iterators::Pos2DMapping; -use crate::StablePyGraph; use crate::Graph; -use crate::connected_components; +use crate::StablePyGraph; use retworkx_core::dictmap::*; -pub struct CwCcw { +pub struct CwCcw { cw: Option, ccw: Option, } -impl Default for CwCcw { +impl Default for CwCcw { fn default() -> Self { - CwCcw { cw: None, ccw: None } + CwCcw { + cw: None, + ccw: None, + } } } @@ -65,7 +69,7 @@ pub struct FirstNbr { first_nbr: Option, } -impl Default for FirstNbr { +impl Default for FirstNbr { fn default() -> Self { FirstNbr { first_nbr: None } } @@ -100,7 +104,6 @@ pub fn planar_layout( scale: Option, center: Option, ) -> Pos2DMapping { - let node_num = graph.node_count(); if node_num == 0 { return Pos2DMapping { @@ -125,14 +128,17 @@ pub fn planar_layout( recenter(&mut pos, center); } Pos2DMapping { - pos_map: planar.embedding.node_indices().map(|n| n.index()).zip(pos).collect(), + pos_map: planar + .embedding + .node_indices() + .map(|n| n.index()) + .zip(pos) + .collect(), } } } -pub fn is_planar( - graph: &StablePyGraph, -) -> bool { +pub fn is_planar(graph: &StablePyGraph) -> bool { true } @@ -140,22 +146,14 @@ pub fn create_embedding( graph: &StablePyGraph, embedding: &mut Graph, CwCcw, Directed>, ) -> bool { - // DEBUG CODE FOR TESTING BASIC EMBEDDING - // for v in graph.node_indices() { - // if v.index() < 5 { - // println!("GRAPH {:?} {}", v, graph[v].clone()); - // embedding.add_node(graph[v].clone()); - // } else { - // break; - // } - // } + true } -pub fn combinitorial_embedding_to_pos ( +pub fn combinitorial_embedding_to_pos( embedding: &Graph, CwCcw, Directed>, pos: &mut Vec, -){ +) { if embedding.node_count() < 4 { *pos = [[0.0, 0.0], [2.0, 0.0], [1.0, 1.0]].to_vec() } @@ -165,7 +163,7 @@ pub fn combinitorial_embedding_to_pos ( *pos = [[0.4, 0.5]].to_vec() } -pub fn triangulate_embedding ( +pub fn triangulate_embedding( embedding: &Graph, CwCcw, Directed>, fully_triangulate: bool, ) -> Vec { @@ -179,52 +177,42 @@ pub fn triangulate_embedding ( } struct PlanarEmbedding { - embedding: Graph::, CwCcw, Directed>, + embedding: Graph, CwCcw, Directed>, } impl Default for PlanarEmbedding { - fn default () -> Self { + fn default() -> Self { PlanarEmbedding { embedding: Graph::, CwCcw, Directed>::new(), } } } impl PlanarEmbedding { - pub fn new () -> Self { + pub fn new() -> Self { PlanarEmbedding { embedding: Graph::, CwCcw, Directed>::new(), } } - fn neighbors_cw_order (&self, v: NodeIndex) - { - } - - fn check_structure (&self) - { - } - - fn add_half_edge_cw ( + fn add_half_edge_cw( &mut self, start_node: NodeIndex, end_node: NodeIndex, - ref_nbr: Option - ){ + ref_nbr: Option, + ) { let cw_weight = CwCcw::::default(); - let first_nbr = FirstNbr::::default(); let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); if ref_nbr.is_none() { // The start node has no neighbors + let first_nbr = FirstNbr::::default(); self.update_edge_weight(start_node, end_node, end_node, true); self.update_edge_weight(start_node, end_node, end_node, false); self.embedding[start_node].first_nbr = Some(end_node); - return + return; } // if ref_nbr not in self[start_node] error let ref_nbr_node = ref_nbr.unwrap(); - let cw_ref_edge = self.embedding.find_edge(start_node, ref_nbr_node).unwrap(); - let cw_ref_node = self.embedding.edge_weight_mut(cw_ref_edge).unwrap().cw.unwrap(); - + let cw_ref_node = self.get_edge_weight(start_node, ref_nbr_node, true); // Alter half-edge data structures self.update_edge_weight(start_node, ref_nbr_node, end_node, true); self.update_edge_weight(start_node, end_node, cw_ref_node, true); @@ -232,12 +220,12 @@ impl PlanarEmbedding { self.update_edge_weight(start_node, end_node, ref_nbr_node, false); } - fn add_half_edge_ccw ( + fn add_half_edge_ccw( &mut self, start_node: NodeIndex, end_node: NodeIndex, - ref_nbr: Option - ){ + ref_nbr: Option, + ) { if ref_nbr.is_none() { let cw_weight = CwCcw::::default(); let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); @@ -246,8 +234,7 @@ impl PlanarEmbedding { self.embedding[start_node].first_nbr = Some(end_node); } else { let ref_nbr_node = ref_nbr.unwrap(); - let ccw_ref_edge = self.embedding.find_edge(start_node, ref_nbr_node).unwrap(); - let ccw_ref_node = self.embedding.edge_weight_mut(ccw_ref_edge).unwrap().ccw; + let ccw_ref_node = Some(self.get_edge_weight(start_node, ref_nbr_node, false)); self.add_half_edge_cw(start_node, end_node, ccw_ref_node); if ref_nbr == self.embedding[start_node].first_nbr { self.embedding[start_node].first_nbr = Some(end_node); @@ -255,23 +242,22 @@ impl PlanarEmbedding { } } - fn add_half_edge_first (&self, start_node: NodeIndex, end_node: NodeIndex) - { - if self.embedding.contains(start_node) && !self.embedding[start_node].first_nbr.is_none() { - let ref_node = self.embedding[start_node].first_nbr.unwrap(); + fn add_half_edge_first(&mut self, start_node: NodeIndex, end_node: NodeIndex) { + let ref_node: Option = if self.embedding.node_bound() >= start_node.index() + && !self.embedding[start_node].first_nbr.is_none() + { + self.embedding[start_node].first_nbr } else { - let ref_node = Some(None); - } + None + }; self.add_half_edge_ccw(start_node, end_node, ref_node); } - fn next_face_half_edge (&self, v: NodeIndex, w: NodeIndex) - { + fn next_face_half_edge(&self, v: NodeIndex, w: NodeIndex) -> (NodeIndex, NodeIndex) { + let new_node = self.get_edge_weight(v, w, false); + (w, new_node) } - fn traverse_face (&self, v: NodeIndex, w: NodeIndex, mark_half_edges: bool) - { - } fn update_edge_weight(&mut self, v: NodeIndex, w: NodeIndex, new_node: NodeIndex, cw: bool) { let found_edge = self.embedding.find_edge(v, w); let mut found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()).unwrap(); @@ -282,4 +268,14 @@ impl PlanarEmbedding { found_weight.ccw = Some(new_node); } } + fn get_edge_weight(&self, v: NodeIndex, w: NodeIndex, cw: bool) -> NodeIndex { + let found_edge = self.embedding.find_edge(v, w); + let found_weight = self.embedding.edge_weight(found_edge.unwrap()).unwrap(); + + if cw { + found_weight.cw.unwrap() + } else { + found_weight.ccw.unwrap() + } + } } From a648320854652b377d894397a7f4d52a17507661 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 13 May 2022 07:27:25 -0700 Subject: [PATCH 07/37] Load lr_planar Create embedding.rs Add embedding functions to embedding.rs --- retworkx-core/src/lib.rs | 1 + retworkx-core/src/planar/embedding.rs | 194 +++++++ retworkx-core/src/planar/lr_planar.rs | 701 ++++++++++++++++++++++++++ retworkx-core/src/planar/mod.rs | 19 + retworkx-core/tests/test_planar.rs | 268 ++++++++++ src/layout/mod.rs | 22 +- src/layout/planar.rs | 263 +--------- src/lib.rs | 2 +- 8 files changed, 1215 insertions(+), 255 deletions(-) create mode 100644 retworkx-core/src/planar/embedding.rs create mode 100644 retworkx-core/src/planar/lr_planar.rs create mode 100644 retworkx-core/src/planar/mod.rs create mode 100644 retworkx-core/tests/test_planar.rs diff --git a/retworkx-core/src/lib.rs b/retworkx-core/src/lib.rs index 72af648ef..da7b89229 100644 --- a/retworkx-core/src/lib.rs +++ b/retworkx-core/src/lib.rs @@ -72,6 +72,7 @@ pub mod centrality; pub mod connectivity; /// Module for maximum weight matching algorithmss pub mod max_weight_matching; +pub mod planar; pub mod shortest_path; pub mod traversal; // These modules define additional data structures diff --git a/retworkx-core/src/planar/embedding.rs b/retworkx-core/src/planar/embedding.rs new file mode 100644 index 000000000..100659be0 --- /dev/null +++ b/retworkx-core/src/planar/embedding.rs @@ -0,0 +1,194 @@ +use petgraph::prelude::*; +use petgraph::Directed; +use petgraph::visit::{GraphBase, NodeIndexable}; +use petgraph::graph::Graph; +use std::hash::Hash; +use std::fmt::Debug; + +use crate::dictmap::*; +use crate::planar::is_planar; +use crate::planar::lr_planar::LRState; + +pub type Point = [f64; 2]; + +pub struct CwCcw { + cw: Option, + ccw: Option, +} + +impl Default for CwCcw { + fn default() -> Self { + CwCcw { + cw: None, + ccw: None, + } + } +} + +impl CwCcw { + fn new(cw: T, ccw: T) -> Self { + CwCcw { + cw: Some(cw), + ccw: Some(ccw), + } + } +} + +pub struct FirstNbr { + first_nbr: Option, +} + +impl Default for FirstNbr { + fn default() -> Self { + FirstNbr { first_nbr: None } + } +} + +impl FirstNbr { + fn new(first_nbr: T) -> Self { + FirstNbr { + first_nbr: Some(first_nbr), + } + } +} + +pub struct PlanarEmbedding { + pub embedding: Graph, CwCcw, Directed>, +} + +impl Default for PlanarEmbedding { + fn default() -> Self { + PlanarEmbedding { + embedding: Graph::, CwCcw, Directed>::new(), + } + } +} +impl PlanarEmbedding { + fn new() -> Self { + PlanarEmbedding { + embedding: Graph::, CwCcw, Directed>::new(), + } + } + + fn add_half_edge_cw( + &mut self, + start_node: NodeIndex, + end_node: NodeIndex, + ref_nbr: Option, + ) { + let cw_weight = CwCcw::::default(); + let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); + if ref_nbr.is_none() { + // The start node has no neighbors + let first_nbr = FirstNbr::::default(); + self.update_edge_weight(start_node, end_node, end_node, true); + self.update_edge_weight(start_node, end_node, end_node, false); + self.embedding[start_node].first_nbr = Some(end_node); + return; + } + // if ref_nbr not in self[start_node] error + let ref_nbr_node = ref_nbr.unwrap(); + let cw_ref_node = self.get_edge_weight(start_node, ref_nbr_node, true); + // Alter half-edge data structures + self.update_edge_weight(start_node, ref_nbr_node, end_node, true); + self.update_edge_weight(start_node, end_node, cw_ref_node, true); + self.update_edge_weight(start_node, cw_ref_node, end_node, false); + self.update_edge_weight(start_node, end_node, ref_nbr_node, false); + } + + fn add_half_edge_ccw( + &mut self, + start_node: NodeIndex, + end_node: NodeIndex, + ref_nbr: Option, + ) { + if ref_nbr.is_none() { + let cw_weight = CwCcw::::default(); + let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); + self.update_edge_weight(start_node, end_node, end_node, true); + self.update_edge_weight(start_node, end_node, end_node, false); + self.embedding[start_node].first_nbr = Some(end_node); + } else { + let ref_nbr_node = ref_nbr.unwrap(); + let ccw_ref_node = Some(self.get_edge_weight(start_node, ref_nbr_node, false)); + self.add_half_edge_cw(start_node, end_node, ccw_ref_node); + if ref_nbr == self.embedding[start_node].first_nbr { + self.embedding[start_node].first_nbr = Some(end_node); + } + } + } + + fn add_half_edge_first(&mut self, start_node: NodeIndex, end_node: NodeIndex) { + let ref_node: Option = if self.embedding.node_bound() >= start_node.index() + && !self.embedding[start_node].first_nbr.is_none() + { + self.embedding[start_node].first_nbr + } else { + None + }; + self.add_half_edge_ccw(start_node, end_node, ref_node); + } + + fn next_face_half_edge(&self, v: NodeIndex, w: NodeIndex) -> (NodeIndex, NodeIndex) { + let new_node = self.get_edge_weight(v, w, false); + (w, new_node) + } + + fn update_edge_weight(&mut self, v: NodeIndex, w: NodeIndex, new_node: NodeIndex, cw: bool) { + let found_edge = self.embedding.find_edge(v, w); + let mut found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()).unwrap(); + + if cw { + found_weight.cw = Some(new_node); + } else { + found_weight.ccw = Some(new_node); + } + } + fn get_edge_weight(&self, v: NodeIndex, w: NodeIndex, cw: bool) -> NodeIndex { + let found_edge = self.embedding.find_edge(v, w); + let found_weight = self.embedding.edge_weight(found_edge.unwrap()).unwrap(); + + if cw { + found_weight.cw.unwrap() + } else { + found_weight.ccw.unwrap() + } + } +} + +pub fn create_embedding( + planar_emb: &PlanarEmbedding, + lr_state: &LRState, +) -> Vec where ::NodeId: Hash + Eq, ::NodeId: Debug { + println!("ROOTS {:?}", lr_state.roots); + + //let mut planar_emb = PlanarEmbedding::default(); + + let mut pos = combinatorial_embedding_to_pos(&planar_emb); + pos +} + +fn combinatorial_embedding_to_pos( + planar_emb: &PlanarEmbedding, +) -> Vec { + let mut pos: Vec = Vec::with_capacity(planar_emb.embedding.node_count()); + if planar_emb.embedding.node_count() < 4 { + pos = [[0.0, 0.0], [2.0, 0.0], [1.0, 1.0]].to_vec() + } + let outer_face = triangulate_embedding(&planar_emb, true); + + pos +} + +fn triangulate_embedding( + planar_emb: &PlanarEmbedding, + fully_triangulate: bool, +) -> Vec { + if planar_emb.embedding.node_count() <= 1 { + return planar_emb.embedding.node_indices().map(|n| n).collect::>(); + } + //let component_nodes = connected_components(embedding); + let outer_face = planar_emb.embedding.node_indices().map(|n| n).collect::>(); + println!("DFLT {:?}", outer_face); + outer_face +} diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs new file mode 100644 index 000000000..cbd41f7fb --- /dev/null +++ b/retworkx-core/src/planar/lr_planar.rs @@ -0,0 +1,701 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +use std::cmp::Ordering; +use std::hash::Hash; +use std::vec::IntoIter; + +use hashbrown::{hash_map::Entry, HashMap}; +use petgraph::{ + visit::{ + EdgeCount, EdgeRef, GraphBase, GraphProp, IntoEdges, IntoNodeIdentifiers, NodeCount, + Visitable, + }, + Undirected, +}; + +use crate::traversal::{depth_first_search, DfsEvent}; + +type Edge = (::NodeId, ::NodeId); + +fn insert_or_min(xs: &mut HashMap, key: K, val: V) +where + K: Hash + Eq, + V: Ord + Copy, +{ + xs.entry(key) + .and_modify(|e| { + if val < *e { + *e = val; + } + }) + .or_insert(val); +} + +fn edges_filtered_and_sorted_by( + graph: G, + a: G::NodeId, + filter: P, + compare: F, +) -> IntoIter> +where + G: IntoEdges, + P: Fn(&Edge) -> bool, + F: Fn(&Edge) -> K, + K: Ord, +{ + let mut edges = graph + .edges(a) + .filter_map(|edge| { + let e = (edge.source(), edge.target()); + if filter(&e) { + Some(e) + } else { + None + } + }) + .collect::>(); + edges.sort_by_key(compare); + // Remove parallel edges since they do *not* affect whether a graph is planar. + edges.dedup(); + edges.into_iter() +} + +fn is_target(edge: Option<&Edge>, v: G::NodeId) -> Option<&Edge> { + edge.filter(|e| e.1 == v) +} + +#[derive(Clone, Copy, PartialEq, PartialOrd)] +struct Interval { + inner: Option<(T, T)>, +} + +impl Default for Interval { + fn default() -> Self { + Interval { inner: None } + } +} + +impl Interval { + fn new(low: T, high: T) -> Self { + Interval { + inner: Some((low, high)), + } + } + + fn is_empty(&self) -> bool { + self.inner.is_none() + } + + fn unwrap(self) -> (T, T) { + self.inner.unwrap() + } + + fn low(&self) -> Option<&T> { + match self.inner { + Some((ref low, _)) => Some(low), + None => None, + } + } + + fn high(&self) -> Option<&T> { + match self.inner { + Some((_, ref high)) => Some(high), + None => None, + } + } + + fn as_ref(&mut self) -> Option<&(T, T)> { + self.inner.as_ref() + } + + fn as_mut(&mut self) -> Option<&mut (T, T)> { + self.inner.as_mut() + } + + fn as_mut_low(&mut self) -> Option<&mut T> { + match self.inner { + Some((ref mut low, _)) => Some(low), + None => None, + } + } +} + +impl Interval<(T, T)> +where + T: Copy + Hash + Eq, +{ + /// Returns ``true`` if the interval conflicts with ``edge``. + fn conflict(&self, lr_state: &LRState, edge: Edge) -> bool + where + G: GraphBase, + { + match self.inner { + Some((_, ref h)) => lr_state.lowpt.get(h) > lr_state.lowpt.get(&edge), + _ => false, + } + } +} + +#[derive(Clone, Copy, PartialEq, PartialOrd)] +struct ConflictPair { + left: Interval, + right: Interval, +} + +impl Default for ConflictPair { + fn default() -> Self { + ConflictPair { + left: Interval::default(), + right: Interval::default(), + } + } +} + +impl ConflictPair { + fn new(left: Interval, right: Interval) -> Self { + ConflictPair { left, right } + } + + fn swap(&mut self) { + std::mem::swap(&mut self.left, &mut self.right) + } + + fn is_empty(&self) -> bool { + self.left.is_empty() && self.right.is_empty() + } +} + +impl ConflictPair<(T, T)> +where + T: Copy + Hash + Eq, +{ + /// Returns the lowest low point of a conflict pair. + fn lowest(&self, lr_state: &LRState) -> usize + where + G: GraphBase, + { + match (self.left.low(), self.right.low()) { + (Some(l_low), Some(r_low)) => lr_state.lowpt[l_low].min(lr_state.lowpt[r_low]), + (Some(l_low), None) => lr_state.lowpt[l_low], + (None, Some(r_low)) => lr_state.lowpt[r_low], + (None, None) => std::usize::MAX, + } + } +} + +enum Sign { + Plus, + Minus, +} + +/// Similar to ``DfsEvent`` plus an extra event ``FinishEdge`` +/// that indicates that we have finished processing an edge. +enum LRTestDfsEvent { + Finish(N), + TreeEdge(N, N), + BackEdge(N, N), + FinishEdge(N, N), +} + +// An error: graph is *not* planar. +struct NonPlanar {} + +pub struct LRState +where + G::NodeId: Hash + Eq, +{ + graph: G, + /// roots of the DFS forest. + pub roots: Vec, + /// distnace from root. + height: HashMap, + /// parent edge. + eparent: HashMap>, + /// height of lowest return point. + lowpt: HashMap, usize>, + /// height of next-to-lowest return point. Only used to check if an edge is chordal. + lowpt_2: HashMap, usize>, + /// next back edge in traversal with lowest return point. + lowpt_edge: HashMap, Edge>, + /// proxy for nesting order ≺ given by twice lowpt (plus 1 if chordal). + nesting_depth: HashMap, usize>, + /// stack for conflict pairs. + stack: Vec>>, + /// marks the top conflict pair when an edge was pushed in the stack. + stack_emarker: HashMap, ConflictPair>>, + /// edge relative to which side is defined. + eref: HashMap, Edge>, + /// side of edge, or modifier for side of reference edge. + side: HashMap, Sign>, +} + +impl LRState +where + G: GraphBase + NodeCount + EdgeCount + IntoEdges + Visitable, + G::NodeId: Hash + Eq, +{ + fn new(graph: G) -> Self { + let num_nodes = graph.node_count(); + let num_edges = graph.edge_count(); + + LRState { + graph, + roots: Vec::new(), + height: HashMap::with_capacity(num_nodes), + eparent: HashMap::with_capacity(num_edges), + lowpt: HashMap::with_capacity(num_edges), + lowpt_2: HashMap::with_capacity(num_edges), + lowpt_edge: HashMap::with_capacity(num_edges), + nesting_depth: HashMap::with_capacity(num_edges), + stack: Vec::new(), + stack_emarker: HashMap::with_capacity(num_edges), + eref: HashMap::with_capacity(num_edges), + side: graph + .edge_references() + .map(|e| ((e.source(), e.target()), Sign::Plus)) + .collect(), + } + } + + fn lr_orientation_visitor(&mut self, event: DfsEvent) { + match event { + DfsEvent::Discover(v, _) => { + if let Entry::Vacant(entry) = self.height.entry(v) { + entry.insert(0); + self.roots.push(v); + } + } + DfsEvent::TreeEdge(v, w, _) => { + let ei = (v, w); + let v_height = self.height[&v]; + let w_height = v_height + 1; + + self.eparent.insert(w, ei); + self.height.insert(w, w_height); + // now initialize low points. + self.lowpt.insert(ei, v_height); + self.lowpt_2.insert(ei, w_height); + } + DfsEvent::BackEdge(v, w, _) => { + // do *not* consider ``(v, w)`` as a back edge if ``(w, v)`` is a tree edge. + if Some(&(w, v)) != self.eparent.get(&v) { + let ei = (v, w); + self.lowpt.insert(ei, self.height[&w]); + self.lowpt_2.insert(ei, self.height[&v]); + } + } + DfsEvent::Finish(v, _) => { + for edge in self.graph.edges(v) { + let w = edge.target(); + let ei = (v, w); + + // determine nesting depth. + let low = match self.lowpt.get(&ei) { + Some(val) => *val, + None => + // if ``lowpt`` does *not* contain edge ``(v, w)``, it means + // that it's *not* a tree or a back edge so we skip it since + // it's oriented in the reverse direction. + { + continue + } + }; + + if self.lowpt_2[&ei] < self.height[&v] { + // if it's chordal, add one. + self.nesting_depth.insert(ei, 2 * low + 1); + } else { + self.nesting_depth.insert(ei, 2 * low); + } + + // update lowpoints of parent edge. + if let Some(e_par) = self.eparent.get(&v) { + match self.lowpt[&ei].cmp(&self.lowpt[e_par]) { + Ordering::Less => { + self.lowpt_2 + .insert(*e_par, self.lowpt[e_par].min(self.lowpt_2[&ei])); + self.lowpt.insert(*e_par, self.lowpt[&ei]); + } + Ordering::Greater => { + insert_or_min(&mut self.lowpt_2, *e_par, self.lowpt[&ei]); + } + _ => { + let val = self.lowpt_2[&ei]; + insert_or_min(&mut self.lowpt_2, *e_par, val); + } + } + } + } + } + _ => {} + } + } + + fn lr_testing_visitor(&mut self, event: LRTestDfsEvent) -> Result<(), NonPlanar> { + match event { + LRTestDfsEvent::TreeEdge(v, w) => { + let ei = (v, w); + if let Some(&last) = self.stack.last() { + self.stack_emarker.insert(ei, last); + } + } + LRTestDfsEvent::BackEdge(v, w) => { + let ei = (v, w); + if let Some(&last) = self.stack.last() { + self.stack_emarker.insert(ei, last); + } + self.lowpt_edge.insert(ei, ei); + let c_pair = ConflictPair::new(Interval::default(), Interval::new(ei, ei)); + self.stack.push(c_pair); + } + LRTestDfsEvent::FinishEdge(v, w) => { + let ei = (v, w); + if self.lowpt[&ei] < self.height[&v] { + // ei has return edge + let e_par = self.eparent[&v]; + let val = self.lowpt_edge[&ei]; + + match self.lowpt_edge.entry(e_par) { + Entry::Occupied(_) => { + self.add_constraints(ei, e_par)?; + } + Entry::Vacant(o) => { + o.insert(val); + } + } + } + } + LRTestDfsEvent::Finish(v) => { + if let Some(&e) = self.eparent.get(&v) { + let u = e.0; + self.remove_back_edges(u); + + // side of ``e = (u, v)` is side of a highest return edge + if self.lowpt[&e] < self.height[&u] { + if let Some(top) = self.stack.last() { + let e_high = match (top.left.high(), top.right.high()) { + (Some(hl), Some(hr)) => { + if self.lowpt[hl] > self.lowpt[hr] { + hl + } else { + hr + } + } + (Some(hl), None) => hl, + (None, Some(hr)) => hr, + _ => { + // Otherwise ``top`` would be empty, but we don't push + // empty conflict pairs in stack. + unreachable!() + } + }; + self.eref.insert(e, *e_high); + } + } + } + } + } + + Ok(()) + } + + fn until_top_of_stack_hits_emarker(&mut self, ei: Edge) -> Option>> { + if let Some(&c_pair) = self.stack.last() { + if self.stack_emarker[&ei] != c_pair { + return self.stack.pop(); + } + } + + None + } + + fn until_top_of_stack_is_conflicting(&mut self, ei: Edge) -> Option>> { + if let Some(c_pair) = self.stack.last() { + if c_pair.left.conflict(self, ei) || c_pair.right.conflict(self, ei) { + return self.stack.pop(); + } + } + + None + } + + /// Unify intervals ``pi``, ``qi``. + /// + /// Interval ``qi`` must be non - empty and contain edges + /// with smaller lowpt than interval ``pi``. + fn union_intervals(&mut self, pi: &mut Interval>, qi: Interval>) { + match pi.as_mut_low() { + Some(p_low) => { + let (q_low, q_high) = qi.unwrap(); + self.eref.insert(*p_low, q_high); + *p_low = q_low; + } + None => { + *pi = qi; + } + } + } + + /// Adding constraints associated with edge ``ei``. + fn add_constraints(&mut self, ei: Edge, e: Edge) -> Result<(), NonPlanar> { + let mut c_pair = ConflictPair::>::default(); + + // merge return edges of ei into ``c_pair.right``. + while let Some(mut q_pair) = self.until_top_of_stack_hits_emarker(ei) { + if !q_pair.left.is_empty() { + q_pair.swap(); + + if !q_pair.left.is_empty() { + return Err(NonPlanar {}); + } + } + + // We call unwrap since ``q_pair`` was in stack and + // ``q_pair.right``, ``q_pair.left`` can't be both empty + // since we don't push empty conflict pairs in stack. + let qr_low = q_pair.right.low().unwrap(); + if self.lowpt[qr_low] > self.lowpt[&e] { + // merge intervals + self.union_intervals(&mut c_pair.right, q_pair.right); + } else { + // make consinsent + self.eref.insert(*qr_low, self.lowpt_edge[&e]); + } + } + + // merge conflicting return edges of e1, . . . , ei−1 into ``c_pair.left``. + while let Some(mut q_pair) = self.until_top_of_stack_is_conflicting(ei) { + if q_pair.right.conflict(self, ei) { + q_pair.swap(); + + if q_pair.right.conflict(self, ei) { + return Err(NonPlanar {}); + } + } + + // merge interval below lowpt(ei) into ``c_pair.right``. + if let Some((qr_low, qr_high)) = q_pair.right.as_ref() { + if let Some(pr_low) = c_pair.right.as_mut_low() { + self.eref.insert(*pr_low, *qr_high); + *pr_low = *qr_low; + } + }; + self.union_intervals(&mut c_pair.left, q_pair.left); + } + + if !c_pair.is_empty() { + self.stack.push(c_pair); + } + + Ok(()) + } + + fn until_lowest_top_of_stack_has_height( + &mut self, + v: G::NodeId, + ) -> Option>> { + if let Some(c_pair) = self.stack.last() { + if c_pair.lowest(self) == self.height[&v] { + return self.stack.pop(); + } + } + + None + } + + fn follow_eref_until_is_target(&self, edge: Edge, v: G::NodeId) -> Option> { + let mut res = Some(&edge); + while let Some(b) = is_target::(res, v) { + res = self.eref.get(b); + } + + res.copied() + } + + /// Trim back edges ending at parent v. + fn remove_back_edges(&mut self, v: G::NodeId) { + // drop entire conflict pairs. + while let Some(c_pair) = self.until_lowest_top_of_stack_has_height(v) { + if let Some(pl_low) = c_pair.left.low() { + self.side.insert(*pl_low, Sign::Minus); + } + } + + // one more conflict pair to consider. + if let Some(mut c_pair) = self.stack.pop() { + // trim left interval. + if let Some((pl_low, pl_high)) = c_pair.left.as_mut() { + match self.follow_eref_until_is_target(*pl_high, v) { + Some(val) => { + *pl_high = val; + } + None => { + // just emptied. + // We call unwrap since right interval cannot be empty for otherwise + // the entire conflict pair had been removed. + let pr_low = c_pair.right.low().unwrap(); + self.eref.insert(*pl_low, *pr_low); + self.side.insert(*pl_low, Sign::Minus); + c_pair.left = Interval::default(); + } + } + } + + // trim right interval + if let Some((pr_low, ref mut pr_high)) = c_pair.right.as_mut() { + match self.follow_eref_until_is_target(*pr_high, v) { + Some(val) => { + *pr_high = val; + } + None => { + // just emptied. + // We call unwrap since left interval cannot be empty for otherwise + // the entire conflict pair had been removed. + let pl_low = c_pair.left.low().unwrap(); + self.eref.insert(*pr_low, *pl_low); + self.side.insert(*pr_low, Sign::Minus); + c_pair.right = Interval::default(); + } + }; + } + + if !c_pair.is_empty() { + self.stack.push(c_pair); + } + } + } +} + +/// Visits the DFS - oriented tree that we have pre-computed +/// and stored in ``lr_state``. We traverse the edges of +/// a node in nesting depth order. Events are emitted at points +/// of interest and should be handled by ``visitor``. +fn lr_visit_ordered_dfs_tree( + lr_state: &mut LRState, + v: G::NodeId, + mut visitor: F, +) -> Result<(), E> +where + G: GraphBase + IntoEdges, + G::NodeId: Hash + Eq, + F: FnMut(&mut LRState, LRTestDfsEvent) -> Result<(), E>, +{ + let mut stack: Vec<(G::NodeId, IntoIter>)> = vec![( + v, + edges_filtered_and_sorted_by( + lr_state.graph, + v, + // if ``lowpt`` does *not* contain edge ``e = (v, w)``, it means + // that it's *not* a tree or a back edge so we skip it since + // it's oriented in the reverse direction. + |e| lr_state.lowpt.contains_key(e), + // we sort edges based on nesting depth order. + |e| lr_state.nesting_depth[e], + ), + )]; + + while let Some(elem) = stack.last_mut() { + let v = elem.0; + let adjacent_edges = &mut elem.1; + let mut next = None; + + for (v, w) in adjacent_edges { + if Some(&(v, w)) == lr_state.eparent.get(&w) { + // tree edge + visitor(lr_state, LRTestDfsEvent::TreeEdge(v, w))?; + next = Some(w); + break; + } else { + // back edge + visitor(lr_state, LRTestDfsEvent::BackEdge(v, w))?; + visitor(lr_state, LRTestDfsEvent::FinishEdge(v, w))?; + } + } + + match next { + Some(w) => stack.push(( + w, + edges_filtered_and_sorted_by( + lr_state.graph, + w, + |e| lr_state.lowpt.contains_key(e), + |e| lr_state.nesting_depth[e], + ), + )), + None => { + stack.pop(); + visitor(lr_state, LRTestDfsEvent::Finish(v))?; + if let Some(&(u, v)) = lr_state.eparent.get(&v) { + visitor(lr_state, LRTestDfsEvent::FinishEdge(u, v))?; + } + } + } + } + + Ok(()) +} + +/// Check if an undirected graph is planar. +/// +/// A graph is planar iff it can be drawn in a plane without any edge +/// intersections. +/// +/// The planarity check algorithm is based on the +/// Left-Right Planarity Test: +/// +/// [`Ulrik Brandes: The Left-Right Planarity Test (2009)`](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.217.9208) +/// +/// # Example: +/// ```rust +/// use retworkx_core::petgraph::graph::UnGraph; +/// use retworkx_core::planar::is_planar; +/// +/// let grid = UnGraph::<(), ()>::from_edges(&[ +/// // row edges +/// (0, 1), (1, 2), (3, 4), (4, 5), (6, 7), (7, 8), +/// // col edges +/// (0, 3), (3, 6), (1, 4), (4, 7), (2, 5), (5, 8), +/// ]); +/// assert!(is_planar(&grid)) +/// ``` +pub fn is_planar(graph: G) -> (bool, LRState) +where + G: GraphProp + + NodeCount + + EdgeCount + + IntoEdges + + IntoNodeIdentifiers + + Visitable, + G::NodeId: Hash + Eq, +{ + let mut state = LRState::new(graph); + + // Dfs orientation phase + depth_first_search(graph, graph.node_identifiers(), |event| { + state.lr_orientation_visitor(event) + }); + + println!("LR before roots"); + // Left - Right partition. + for v in state.roots.clone() { + let res = lr_visit_ordered_dfs_tree(&mut state, v, |state, event| { + state.lr_testing_visitor(event) + }); + if res.is_err() { + return (false, state); + } + } + println!("LR true"); + + (true, state) +} diff --git a/retworkx-core/src/planar/mod.rs b/retworkx-core/src/planar/mod.rs new file mode 100644 index 000000000..e2349aee6 --- /dev/null +++ b/retworkx-core/src/planar/mod.rs @@ -0,0 +1,19 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +//! Module for planar graphs. + +pub mod lr_planar; +pub mod embedding; + +pub use lr_planar::is_planar; +pub use embedding::PlanarEmbedding; diff --git a/retworkx-core/tests/test_planar.rs b/retworkx-core/tests/test_planar.rs new file mode 100644 index 000000000..a2fe3d130 --- /dev/null +++ b/retworkx-core/tests/test_planar.rs @@ -0,0 +1,268 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +//! Test module for planar graphs. + +use retworkx_core::petgraph::graph::UnGraph; +use retworkx_core::planar::is_planar; + +#[test] +fn test_simple_planar_graph() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (1, 2), + (2, 3), + (3, 4), + (4, 6), + (6, 7), + (7, 1), + (1, 5), + (5, 2), + (2, 4), + (4, 5), + (5, 7), + ]); + let res = is_planar(&graph); + assert!(res) +} + +#[test] +fn test_planar_grid_3_3_graph() { + let graph = UnGraph::<(), ()>::from_edges(&[ + // row edges + (0, 1), + (1, 2), + (3, 4), + (4, 5), + (6, 7), + (7, 8), + // col edges + (0, 3), + (3, 6), + (1, 4), + (4, 7), + (2, 5), + (5, 8), + ]); + let res = is_planar(&graph); + assert!(res) +} + +#[test] +fn test_planar_with_self_loop() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (1, 1), + (2, 2), + (3, 3), + (4, 4), + (5, 5), + (1, 2), + (1, 3), + (1, 5), + (2, 5), + (2, 4), + (3, 4), + (3, 5), + (4, 5), + ]); + let res = is_planar(&graph); + assert!(res) +} + +#[test] +fn test_goldner_harary_planar_graph() { + // test goldner-harary graph (a maximal planar graph) + let graph = UnGraph::<(), ()>::from_edges(&[ + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (1, 7), + (1, 8), + (1, 10), + (1, 11), + (2, 3), + (2, 4), + (2, 6), + (2, 7), + (2, 9), + (2, 10), + (2, 11), + (3, 4), + (4, 5), + (4, 6), + (4, 7), + (5, 7), + (6, 7), + (7, 8), + (7, 9), + (7, 10), + (8, 10), + (9, 10), + (10, 11), + ]); + let res = is_planar(&graph); + assert!(res) +} + +#[test] +fn test_multiple_components_planar_graph() { + let graph = UnGraph::<(), ()>::from_edges(&[(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)]); + let res = is_planar(&graph); + assert!(res) +} + +#[test] +fn test_planar_multi_graph() { + let graph = UnGraph::<(), ()>::from_edges(&[(0, 1), (0, 1), (0, 1), (1, 2), (2, 0)]); + let res = is_planar(&graph); + assert!(res) +} + +#[test] +fn test_k3_3_non_planar() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (0, 3), + (0, 4), + (0, 5), + (1, 3), + (1, 4), + (1, 5), + (2, 3), + (2, 4), + (2, 5), + ]); + let res = is_planar(&graph); + assert_eq!(res, false) +} + +#[test] +fn test_k5_non_planar() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 2), + (1, 3), + (1, 4), + (2, 3), + (2, 4), + (3, 4), + ]); + let res = is_planar(&graph); + assert_eq!(res, false) +} + +#[test] +fn test_multiple_components_non_planar() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 2), + (1, 3), + (1, 4), + (2, 3), + (2, 4), + (3, 4), + (6, 7), + (7, 8), + (8, 6), + ]); + let res = is_planar(&graph); + assert_eq!(res, false) +} + +#[test] +fn test_non_planar() { + // tests a graph that has no subgraph directly isomorphic to K5 or K3_3. + let graph = UnGraph::<(), ()>::from_edges(&[ + (1, 5), + (1, 6), + (1, 7), + (2, 6), + (2, 3), + (3, 5), + (3, 7), + (4, 5), + (4, 6), + (4, 7), + ]); + let res = is_planar(&graph); + assert_eq!(res, false) +} + +#[test] +fn test_planar_graph1() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (3, 10), + (2, 13), + (1, 13), + (7, 11), + (0, 8), + (8, 13), + (0, 2), + (0, 7), + (0, 10), + (1, 7), + ]); + let res = is_planar(&graph); + assert!(res) +} + +#[test] +fn test_non_planar_graph2() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (1, 2), + (4, 13), + (0, 13), + (4, 5), + (7, 10), + (1, 7), + (0, 3), + (2, 6), + (5, 6), + (7, 13), + (4, 8), + (0, 8), + (0, 9), + (2, 13), + (6, 7), + (3, 6), + (2, 8), + ]); + let res = is_planar(&graph); + assert_eq!(res, false) +} + +#[test] +fn test_non_planar_graph3() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (0, 7), + (3, 11), + (3, 4), + (8, 9), + (4, 11), + (1, 7), + (1, 13), + (1, 11), + (3, 5), + (5, 7), + (1, 3), + (0, 4), + (5, 11), + (5, 13), + ]); + let res = is_planar(&graph); + assert_eq!(res, false) +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 9e3b4d8f1..93215e26f 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -207,12 +207,11 @@ pub fn digraph_spring_layout( #[pyfunction] #[pyo3(text_signature = "(graph, / scale=1.0, center=None)")] pub fn graph_planar_layout( - py: Python, graph: &graph::PyGraph, scale: Option, center: Option<[f64; 2]>, ) -> Pos2DMapping { - planar::planar_layout(py, &graph.graph, scale, center) + planar::planar_layout(&graph.graph, scale, center) } /// Generate a planar layout @@ -225,16 +224,15 @@ pub fn graph_planar_layout( /// /// :returns: The planar layout of the graph. /// :rtype: Pos2DMapping -#[pyfunction] -#[pyo3(text_signature = "(graph, / scale=1.0, center=None)")] -pub fn digraph_planar_layout( - py: Python, - graph: &digraph::PyDiGraph, - scale: Option, - center: Option<[f64; 2]>, -) -> Pos2DMapping { - planar::planar_layout(py, &graph.graph, scale, center) -} +// #[pyfunction] +// #[pyo3(text_signature = "(graph, / scale=1.0, center=None)")] +// pub fn digraph_planar_layout( +// graph: &digraph::PyDiGraph, +// scale: Option, +// center: Option<[f64; 2]>, +// ) -> Pos2DMapping { +// planar::planar_layout(&graph.graph, scale, center) +// } /// Generate a random layout /// diff --git a/src/layout/planar.rs b/src/layout/planar.rs index ed94abb97..1780128db 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -1,7 +1,4 @@ use petgraph::prelude::*; -use petgraph::{Directed, EdgeType}; -use petgraph::visit::NodeIndexable; -use pyo3::prelude::*; use super::spring::{recenter, rescale, Point}; use crate::connected_components; @@ -9,117 +6,41 @@ use crate::iterators::Pos2DMapping; use crate::Graph; use crate::StablePyGraph; use retworkx_core::dictmap::*; +use retworkx_core::planar::{PlanarEmbedding, is_planar, create_embedding}; +// use retworkx_core::planar::is_planar; +// use retworkx_core::planar::create_embedding; -pub struct CwCcw { - cw: Option, - ccw: Option, -} - -impl Default for CwCcw { - fn default() -> Self { - CwCcw { - cw: None, - ccw: None, - } - } -} - -impl CwCcw { - fn new(cw: T, ccw: T) -> Self { - CwCcw { - cw: Some(cw), - ccw: Some(ccw), - } - } - - fn cw_is_empty(&self) -> bool { - self.cw.is_none() - } - - fn ccw_is_empty(&self) -> bool { - self.ccw.is_none() - } - - fn cw_unwrap(self) -> T { - self.cw.unwrap() - } - - fn ccw_unwrap(self) -> T { - self.ccw.unwrap() - } - - fn cw_as_ref(&mut self) -> Option<&T> { - self.cw.as_ref() - } - - fn ccw_as_ref(&mut self) -> Option<&T> { - self.ccw.as_ref() - } - - fn cw_as_mut(&mut self) -> Option<&mut T> { - self.cw.as_mut() - } - - fn ccw_as_mut(&mut self) -> Option<&mut T> { - self.ccw.as_mut() - } -} - -pub struct FirstNbr { - first_nbr: Option, -} - -impl Default for FirstNbr { - fn default() -> Self { - FirstNbr { first_nbr: None } - } -} - -impl FirstNbr { - fn new(first_nbr: T) -> Self { - FirstNbr { - first_nbr: Some(first_nbr), - } - } - - fn first_nbr_is_empty(&self) -> bool { - self.first_nbr.is_none() - } - - fn first_nbr_unwrap(self) -> T { - self.first_nbr.unwrap() - } - - fn first_nbr_as_ref(&mut self) -> Option<&T> { - self.first_nbr.as_ref() - } - - fn first_nbr_as_mut(&mut self) -> Option<&mut T> { - self.first_nbr.as_mut() - } -} -pub fn planar_layout( - py: Python, - graph: &StablePyGraph, +pub fn planar_layout( + graph: &StablePyGraph, scale: Option, center: Option, ) -> Pos2DMapping { + let node_num = graph.node_count(); + println!("NODE NUM {}", node_num); if node_num == 0 { return Pos2DMapping { pos_map: DictMap::new(), }; } - let mut pos: Vec = Vec::with_capacity(node_num); - let mut planar = PlanarEmbedding::new(); - if !is_planar(graph) { + println!("before is"); + let (its_planar, lr_state) = is_planar(graph); + + if !its_planar { + println!("is false"); return Pos2DMapping { pos_map: DictMap::new(), }; } else { - create_embedding(graph, &mut planar.embedding); - combinitorial_embedding_to_pos(&planar.embedding, &mut pos); + println!("is true"); + + let mut planar_emb = PlanarEmbedding::default(); + planar_emb.embedding = Graph::with_capacity(node_num, 0); + println!("ROOTS {:?}", lr_state.roots); + let mut pos = create_embedding(&planar_emb, &lr_state); + + println!("after emb to pos {:?}", pos); if let Some(scale) = scale { rescale(&mut pos, scale, (0..node_num).collect()); @@ -128,7 +49,7 @@ pub fn planar_layout( recenter(&mut pos, center); } Pos2DMapping { - pos_map: planar + pos_map: planar_emb .embedding .node_indices() .map(|n| n.index()) @@ -137,145 +58,3 @@ pub fn planar_layout( } } } - -pub fn is_planar(graph: &StablePyGraph) -> bool { - true -} - -pub fn create_embedding( - graph: &StablePyGraph, - embedding: &mut Graph, CwCcw, Directed>, -) -> bool { - - true -} - -pub fn combinitorial_embedding_to_pos( - embedding: &Graph, CwCcw, Directed>, - pos: &mut Vec, -) { - if embedding.node_count() < 4 { - *pos = [[0.0, 0.0], [2.0, 0.0], [1.0, 1.0]].to_vec() - } - let outer_face = triangulate_embedding(&embedding, true); - - // DEBUG: Need proper value for pos - *pos = [[0.4, 0.5]].to_vec() -} - -pub fn triangulate_embedding( - embedding: &Graph, CwCcw, Directed>, - fully_triangulate: bool, -) -> Vec { - if embedding.node_count() <= 1 { - return embedding.node_indices().map(|n| n).collect::>(); - } - //let component_nodes = connected_components(embedding); - let outer_face = embedding.node_indices().map(|n| n).collect::>(); - println!("DFLT {:?}", outer_face); - outer_face -} - -struct PlanarEmbedding { - embedding: Graph, CwCcw, Directed>, -} - -impl Default for PlanarEmbedding { - fn default() -> Self { - PlanarEmbedding { - embedding: Graph::, CwCcw, Directed>::new(), - } - } -} -impl PlanarEmbedding { - pub fn new() -> Self { - PlanarEmbedding { - embedding: Graph::, CwCcw, Directed>::new(), - } - } - - fn add_half_edge_cw( - &mut self, - start_node: NodeIndex, - end_node: NodeIndex, - ref_nbr: Option, - ) { - let cw_weight = CwCcw::::default(); - let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); - if ref_nbr.is_none() { - // The start node has no neighbors - let first_nbr = FirstNbr::::default(); - self.update_edge_weight(start_node, end_node, end_node, true); - self.update_edge_weight(start_node, end_node, end_node, false); - self.embedding[start_node].first_nbr = Some(end_node); - return; - } - // if ref_nbr not in self[start_node] error - let ref_nbr_node = ref_nbr.unwrap(); - let cw_ref_node = self.get_edge_weight(start_node, ref_nbr_node, true); - // Alter half-edge data structures - self.update_edge_weight(start_node, ref_nbr_node, end_node, true); - self.update_edge_weight(start_node, end_node, cw_ref_node, true); - self.update_edge_weight(start_node, cw_ref_node, end_node, false); - self.update_edge_weight(start_node, end_node, ref_nbr_node, false); - } - - fn add_half_edge_ccw( - &mut self, - start_node: NodeIndex, - end_node: NodeIndex, - ref_nbr: Option, - ) { - if ref_nbr.is_none() { - let cw_weight = CwCcw::::default(); - let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); - self.update_edge_weight(start_node, end_node, end_node, true); - self.update_edge_weight(start_node, end_node, end_node, false); - self.embedding[start_node].first_nbr = Some(end_node); - } else { - let ref_nbr_node = ref_nbr.unwrap(); - let ccw_ref_node = Some(self.get_edge_weight(start_node, ref_nbr_node, false)); - self.add_half_edge_cw(start_node, end_node, ccw_ref_node); - if ref_nbr == self.embedding[start_node].first_nbr { - self.embedding[start_node].first_nbr = Some(end_node); - } - } - } - - fn add_half_edge_first(&mut self, start_node: NodeIndex, end_node: NodeIndex) { - let ref_node: Option = if self.embedding.node_bound() >= start_node.index() - && !self.embedding[start_node].first_nbr.is_none() - { - self.embedding[start_node].first_nbr - } else { - None - }; - self.add_half_edge_ccw(start_node, end_node, ref_node); - } - - fn next_face_half_edge(&self, v: NodeIndex, w: NodeIndex) -> (NodeIndex, NodeIndex) { - let new_node = self.get_edge_weight(v, w, false); - (w, new_node) - } - - fn update_edge_weight(&mut self, v: NodeIndex, w: NodeIndex, new_node: NodeIndex, cw: bool) { - let found_edge = self.embedding.find_edge(v, w); - let mut found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()).unwrap(); - - if cw { - found_weight.cw = Some(new_node); - } else { - found_weight.ccw = Some(new_node); - } - } - fn get_edge_weight(&self, v: NodeIndex, w: NodeIndex, cw: bool) -> NodeIndex { - let found_edge = self.embedding.find_edge(v, w); - let found_weight = self.embedding.edge_weight(found_edge.unwrap()).unwrap(); - - if cw { - found_weight.cw.unwrap() - } else { - found_weight.ccw.unwrap() - } - } -} diff --git a/src/lib.rs b/src/lib.rs index cb69a0505..3a942f747 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -381,7 +381,7 @@ fn retworkx(py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(graph_complement))?; m.add_wrapped(wrap_pyfunction!(digraph_complement))?; m.add_wrapped(wrap_pyfunction!(graph_planar_layout))?; - m.add_wrapped(wrap_pyfunction!(digraph_planar_layout))?; + //m.add_wrapped(wrap_pyfunction!(digraph_planar_layout))?; m.add_wrapped(wrap_pyfunction!(graph_random_layout))?; m.add_wrapped(wrap_pyfunction!(digraph_random_layout))?; m.add_wrapped(wrap_pyfunction!(graph_bipartite_layout))?; From a9fedab68f977d4a4d8784bb1feaad4a42d1948f Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 20 May 2022 13:54:30 -0700 Subject: [PATCH 08/37] Finalize structure and start lr_state changes --- retworkx-core/src/planar/embedding.rs | 33 +++++++++++++++++++++++---- retworkx-core/src/planar/lr_planar.rs | 5 ++++ retworkx-core/src/planar/mod.rs | 2 ++ src/layout/planar.rs | 14 ++++-------- 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/retworkx-core/src/planar/embedding.rs b/retworkx-core/src/planar/embedding.rs index 100659be0..c57f79a70 100644 --- a/retworkx-core/src/planar/embedding.rs +++ b/retworkx-core/src/planar/embedding.rs @@ -3,6 +3,7 @@ use petgraph::Directed; use petgraph::visit::{GraphBase, NodeIndexable}; use petgraph::graph::Graph; use std::hash::Hash; +use hashbrown::hash_map::HashMap; use std::fmt::Debug; use crate::dictmap::*; @@ -159,24 +160,33 @@ impl PlanarEmbedding { pub fn create_embedding( planar_emb: &PlanarEmbedding, lr_state: &LRState, -) -> Vec where ::NodeId: Hash + Eq, ::NodeId: Debug { +) where ::NodeId: Hash + Eq, ::NodeId: Debug { println!("ROOTS {:?}", lr_state.roots); //let mut planar_emb = PlanarEmbedding::default(); - let mut pos = combinatorial_embedding_to_pos(&planar_emb); - pos } -fn combinatorial_embedding_to_pos( +pub fn combinatorial_embedding_to_pos( planar_emb: &PlanarEmbedding, ) -> Vec { let mut pos: Vec = Vec::with_capacity(planar_emb.embedding.node_count()); if planar_emb.embedding.node_count() < 4 { - pos = [[0.0, 0.0], [2.0, 0.0], [1.0, 1.0]].to_vec() + let default_pos = [[0.0, 0.0], [2.0, 0.0], [1.0, 1.0]].to_vec(); + pos = planar_emb.embedding + .node_indices() + .map(|n| default_pos[n.index()]) + .collect(); } let outer_face = triangulate_embedding(&planar_emb, true); + let right_t_child = HashMap::::new(); + let left_t_child = HashMap::::new(); + let delta_x = HashMap::::new(); + let y_coord = HashMap::::new(); + + let node_list = canonical_ordering(&planar_emb, outer_face); + pos } @@ -192,3 +202,16 @@ fn triangulate_embedding( println!("DFLT {:?}", outer_face); outer_face } + +fn canonical_ordering( + planar_emb: &PlanarEmbedding, + outer_face: Vec, +) -> Vec { + if planar_emb.embedding.node_count() <= 1 { + return planar_emb.embedding.node_indices().map(|n| n).collect::>(); + } + //let component_nodes = connected_components(embedding); + let outer_face = planar_emb.embedding.node_indices().map(|n| n).collect::>(); + println!("DFLT {:?}", outer_face); + outer_face +} diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index cbd41f7fb..1a040565a 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -21,6 +21,8 @@ use petgraph::{ Visitable, }, Undirected, + Directed, + graph::Graph, }; use crate::traversal::{depth_first_search, DfsEvent}; @@ -237,6 +239,8 @@ where eref: HashMap, Edge>, /// side of edge, or modifier for side of reference edge. side: HashMap, Sign>, + DG: Graph<(), (), Directed>, + } impl LRState @@ -264,6 +268,7 @@ where .edge_references() .map(|e| ((e.source(), e.target()), Sign::Plus)) .collect(), + DG: Graph::with_capacity(num_nodes, 0), } } diff --git a/retworkx-core/src/planar/mod.rs b/retworkx-core/src/planar/mod.rs index e2349aee6..404545fba 100644 --- a/retworkx-core/src/planar/mod.rs +++ b/retworkx-core/src/planar/mod.rs @@ -17,3 +17,5 @@ pub mod embedding; pub use lr_planar::is_planar; pub use embedding::PlanarEmbedding; +pub use embedding::create_embedding; +pub use embedding::combinatorial_embedding_to_pos; diff --git a/src/layout/planar.rs b/src/layout/planar.rs index 1780128db..a92690c8f 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -6,9 +6,7 @@ use crate::iterators::Pos2DMapping; use crate::Graph; use crate::StablePyGraph; use retworkx_core::dictmap::*; -use retworkx_core::planar::{PlanarEmbedding, is_planar, create_embedding}; -// use retworkx_core::planar::is_planar; -// use retworkx_core::planar::create_embedding; +use retworkx_core::planar::{PlanarEmbedding, is_planar, create_embedding, combinatorial_embedding_to_pos}; pub fn planar_layout( graph: &StablePyGraph, @@ -17,30 +15,26 @@ pub fn planar_layout( ) -> Pos2DMapping { let node_num = graph.node_count(); - println!("NODE NUM {}", node_num); if node_num == 0 { return Pos2DMapping { pos_map: DictMap::new(), }; } - println!("before is"); let (its_planar, lr_state) = is_planar(graph); if !its_planar { - println!("is false"); return Pos2DMapping { pos_map: DictMap::new(), }; } else { - println!("is true"); let mut planar_emb = PlanarEmbedding::default(); planar_emb.embedding = Graph::with_capacity(node_num, 0); - println!("ROOTS {:?}", lr_state.roots); - let mut pos = create_embedding(&planar_emb, &lr_state); - println!("after emb to pos {:?}", pos); + create_embedding(&planar_emb, &lr_state); + + let mut pos = combinatorial_embedding_to_pos(&planar_emb); if let Some(scale) = scale { rescale(&mut pos, scale, (0..node_num).collect()); From c0ac5e9911a0d9d86fda46f9bbb4307bf2195481 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Mon, 23 May 2022 11:09:59 -0700 Subject: [PATCH 09/37] Implementing dfs_embedding --- retworkx-core/src/planar/embedding.rs | 59 ++++++++++++++++++++++++++- retworkx-core/src/planar/lr_planar.rs | 33 ++++++++++----- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/retworkx-core/src/planar/embedding.rs b/retworkx-core/src/planar/embedding.rs index c57f79a70..f4cf741f1 100644 --- a/retworkx-core/src/planar/embedding.rs +++ b/retworkx-core/src/planar/embedding.rs @@ -5,6 +5,8 @@ use petgraph::graph::Graph; use std::hash::Hash; use hashbrown::hash_map::HashMap; use std::fmt::Debug; +use std::ops::IndexMut; +use petgraph::adj::IndexType; use crate::dictmap::*; use crate::planar::is_planar; @@ -161,9 +163,62 @@ pub fn create_embedding( planar_emb: &PlanarEmbedding, lr_state: &LRState, ) where ::NodeId: Hash + Eq, ::NodeId: Debug { - println!("ROOTS {:?}", lr_state.roots); + //println!("ROOTS {:?}", lr_state.roots); - //let mut planar_emb = PlanarEmbedding::default(); + for node in lr_state.dir_graph.node_indices() { + for edge in lr_state.dir_graph.edges(node) { + println!("Edge {:?}, {:?}", edge.source(), edge.target()); + } + } + let mut ordered_adjs: HashMap> = HashMap::new(); + for v in lr_state.dir_graph.node_indices() { + ordered_adjs.insert(v, lr_state.dir_graph.edges(v).map(|e| e.target()).collect()); + } + for x in ordered_adjs { + println!("ordered {:?}", x); + } + for x in &lr_state.nesting_depth { + println!("nesting {:?}", x); + } + let mut emb_nesting: HashMap<(NodeIndex, NodeIndex), usize> = HashMap::new(); + emb_nesting = lr_state.nesting_depth + .iter() + .map(|(a, b)| (a.index(), b.index())) + .collect(); + for v in ordered_adjs { + ordered_adjs[&v.0].sort_by_key(|n| lr_state.nesting_depth[(&v.0, n)]); + } + for x in ordered_adjs { + println!("ordered {:?}", x); + } + // for v in self.DG: # sort the adjacency lists by nesting depth + // # note: this sorting leads to non linear time + // self.ordered_adjs[v] = sorted( + // self.DG[v], key=lambda x: self.nesting_depth[(v, x)] + // ) + // for e in self.DG.edges: + // self.nesting_depth[e] = self.sign(e) * self.nesting_depth[e] + + // self.embedding.add_nodes_from(self.DG.nodes) + // for v in self.DG: + // # sort the adjacency lists again + // self.ordered_adjs[v] = sorted( + // self.DG[v], key=lambda x: self.nesting_depth[(v, x)] + // ) + // # initialize the embedding + // previous_node = None + // for w in self.ordered_adjs[v]: + // self.embedding.add_half_edge_cw(v, w, previous_node) + // previous_node = w + + // # Free no longer used variables + // self.DG = None + // self.nesting_depth = None + // self.ref = None + + // # compute the complete embedding + // for v in self.roots: + // self.dfs_embedding(v) } diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index 1a040565a..fa5087b6a 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -18,11 +18,11 @@ use hashbrown::{hash_map::Entry, HashMap}; use petgraph::{ visit::{ EdgeCount, EdgeRef, GraphBase, GraphProp, IntoEdges, IntoNodeIdentifiers, NodeCount, - Visitable, + Visitable, NodeIndexable, }, Undirected, Directed, - graph::Graph, + graph::{Graph, NodeIndex}, }; use crate::traversal::{depth_first_search, DfsEvent}; @@ -218,7 +218,7 @@ where { graph: G, /// roots of the DFS forest. - pub roots: Vec, + roots: Vec, /// distnace from root. height: HashMap, /// parent edge. @@ -230,7 +230,7 @@ where /// next back edge in traversal with lowest return point. lowpt_edge: HashMap, Edge>, /// proxy for nesting order ≺ given by twice lowpt (plus 1 if chordal). - nesting_depth: HashMap, usize>, + pub nesting_depth: HashMap, usize>, /// stack for conflict pairs. stack: Vec>>, /// marks the top conflict pair when an edge was pushed in the stack. @@ -239,20 +239,20 @@ where eref: HashMap, Edge>, /// side of edge, or modifier for side of reference edge. side: HashMap, Sign>, - DG: Graph<(), (), Directed>, - + /// directed graph used to build the embedding + pub dir_graph: Graph<(), (), Directed>, } impl LRState where - G: GraphBase + NodeCount + EdgeCount + IntoEdges + Visitable, + G: GraphBase + NodeCount + EdgeCount + IntoNodeIdentifiers + NodeIndexable + IntoEdges + Visitable, G::NodeId: Hash + Eq, { fn new(graph: G) -> Self { let num_nodes = graph.node_count(); let num_edges = graph.edge_count(); - LRState { + let mut lr_state = LRState { graph, roots: Vec::new(), height: HashMap::with_capacity(num_nodes), @@ -268,8 +268,12 @@ where .edge_references() .map(|e| ((e.source(), e.target()), Sign::Plus)) .collect(), - DG: Graph::with_capacity(num_nodes, 0), - } + dir_graph: Graph::with_capacity(num_nodes, 0), + }; + for node in graph.node_identifiers() { + lr_state.dir_graph.add_node(()); + }; + lr_state } fn lr_orientation_visitor(&mut self, event: DfsEvent) { @@ -281,6 +285,7 @@ where } } DfsEvent::TreeEdge(v, w, _) => { + self.dir_graph.add_edge(NodeIndex::new(self.graph.to_index(v)), NodeIndex::new(self.graph.to_index(w)), ()); let ei = (v, w); let v_height = self.height[&v]; let w_height = v_height + 1; @@ -294,6 +299,7 @@ where DfsEvent::BackEdge(v, w, _) => { // do *not* consider ``(v, w)`` as a back edge if ``(w, v)`` is a tree edge. if Some(&(w, v)) != self.eparent.get(&v) { + self.dir_graph.add_edge(NodeIndex::new(self.graph.to_index(v)), NodeIndex::new(self.graph.to_index(w)), ()); let ei = (v, w); self.lowpt.insert(ei, self.height[&w]); self.lowpt_2.insert(ei, self.height[&v]); @@ -679,6 +685,7 @@ where + NodeCount + EdgeCount + IntoEdges + + NodeIndexable + IntoNodeIdentifiers + Visitable, G::NodeId: Hash + Eq, @@ -702,5 +709,11 @@ where } println!("LR true"); + for node in state.dir_graph.node_indices() { + for edge in state.dir_graph.edges(node) { + println!("Edge {:?}, {:?}", edge.source(), edge.target()); + } + } + (true, state) } From 969db1331e3e162387d6b1edaf66bddc38b64fc5 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sat, 28 May 2022 20:32:54 -0700 Subject: [PATCH 10/37] Debugging embedding --- retworkx-core/src/planar/embedding.rs | 159 ++++++++++++++++++-------- retworkx-core/src/planar/lr_planar.rs | 41 ++++--- retworkx-core/src/planar/mod.rs | 8 +- src/layout/planar.rs | 8 +- 4 files changed, 146 insertions(+), 70 deletions(-) diff --git a/retworkx-core/src/planar/embedding.rs b/retworkx-core/src/planar/embedding.rs index f4cf741f1..8b4b8c281 100644 --- a/retworkx-core/src/planar/embedding.rs +++ b/retworkx-core/src/planar/embedding.rs @@ -1,12 +1,10 @@ +use hashbrown::hash_map::HashMap; +use petgraph::graph::Graph; use petgraph::prelude::*; -use petgraph::Directed; use petgraph::visit::{GraphBase, NodeIndexable}; -use petgraph::graph::Graph; -use std::hash::Hash; -use hashbrown::hash_map::HashMap; +use petgraph::Directed; use std::fmt::Debug; -use std::ops::IndexMut; -use petgraph::adj::IndexType; +use std::hash::Hash; use crate::dictmap::*; use crate::planar::is_planar; @@ -159,51 +157,113 @@ impl PlanarEmbedding { } } -pub fn create_embedding( - planar_emb: &PlanarEmbedding, - lr_state: &LRState, -) where ::NodeId: Hash + Eq, ::NodeId: Debug { - //println!("ROOTS {:?}", lr_state.roots); +fn id_to_index(graph: G, node_id: G::NodeId) -> NodeIndex { + NodeIndex::new(graph.to_index(node_id)) +} + +fn index_to_id(graph: G, node_index: NodeIndex) -> G::NodeId { + graph.from_index(node_index.index()) +} +pub fn create_embedding( + planar_emb: &mut PlanarEmbedding, + lr_state: &LRState, +) where + ::NodeId: Hash + Eq, + ::NodeId: Debug, +{ for node in lr_state.dir_graph.node_indices() { for edge in lr_state.dir_graph.edges(node) { println!("Edge {:?}, {:?}", edge.source(), edge.target()); } } - let mut ordered_adjs: HashMap> = HashMap::new(); + + let mut ordered_adjs: Vec> = Vec::new(); + for v in lr_state.dir_graph.node_indices() { - ordered_adjs.insert(v, lr_state.dir_graph.edges(v).map(|e| e.target()).collect()); + ordered_adjs.push(lr_state.dir_graph.edges(v).map(|e| e.target()).collect()); + + let first_nbr = FirstNbr::::default(); + planar_emb.embedding.add_node(first_nbr); } - for x in ordered_adjs { + for x in &ordered_adjs { println!("ordered {:?}", x); } for x in &lr_state.nesting_depth { println!("nesting {:?}", x); } - let mut emb_nesting: HashMap<(NodeIndex, NodeIndex), usize> = HashMap::new(); - emb_nesting = lr_state.nesting_depth - .iter() - .map(|(a, b)| (a.index(), b.index())) - .collect(); - for v in ordered_adjs { - ordered_adjs[&v.0].sort_by_key(|n| lr_state.nesting_depth[(&v.0, n)]); + + for v in lr_state.dir_graph.node_indices() { + let mut prev_node: Option = None; + for w in &ordered_adjs[v.index()] { + //println!("v {:?} w {:?} prev {:?}", v, *w, prev_node); + planar_emb.add_half_edge_cw(v, *w, prev_node); + prev_node = Some(*w) + } } - for x in ordered_adjs { - println!("ordered {:?}", x); + + //println!("roots {:?}", &lr_state.roots); + let mut left_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); + let mut right_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); + let mut idx: Vec = vec![0; ordered_adjs.len()]; + //println!("First idx {:?}", idx); + //idx.push(vec![0; ordered_adjs.len()]); + + for v_id in lr_state.roots.iter() { + let v = id_to_index(&lr_state.graph, *v_id); + println!("second v {:?} v index {:?} ord {:?} idx {:?}", v, v.index(), ordered_adjs[v.index()], idx); + + let mut dfs_stack: Vec = vec![v]; + //println!("idx {:?}", idx); + + println!("lr eparent {:?}", lr_state.eparent); + while dfs_stack.len() > 0 { + let v = dfs_stack.pop().unwrap(); + //println!("v {:?} {:?}", v, idx); + let idx2 = idx[v.index()]; + for (w_pos, w) in ordered_adjs[v.index()][idx2..].iter().enumerate() { + //println!("w {:?} {:?}", w, idx); + let w_id = index_to_id(&lr_state.graph, *w); + println!("third v {:?} vindex {:?} w {:?} w_id {:?} w_pos {:?} idx {:?} ", v, v.index(), *w, w_id, w_pos, idx); + idx[v.index()] += 1; + + let ei = (v, w); + let (mut v1, mut v2) = (NodeIndex::new(0), NodeIndex::new(0)); + if lr_state.eparent.contains_key(&w_id) { + let e_id = lr_state.eparent[&w_id]; + (v1, v2) = ( + id_to_index(&lr_state.graph, e_id.0), + id_to_index(&lr_state.graph, e_id.1), + ); + + if ei == (v1, &v2) { + planar_emb.add_half_edge_first(*w, v); + left_ref.entry(v).or_insert(*w); + right_ref.entry(v).or_insert(*w); + dfs_stack.push(v); + dfs_stack.push(*w); + break; + } else { + planar_emb.add_half_edge_cw(*w, v, Some(right_ref[w])); + } + } + } + } } + // for v in self.DG: # sort the adjacency lists by nesting depth // # note: this sorting leads to non linear time // self.ordered_adjs[v] = sorted( - // self.DG[v], key=lambda x: self.nesting_depth[(v, x)] + // self.DG[v], key=lambda x: self.nesting_depth2[(v, x)] // ) // for e in self.DG.edges: - // self.nesting_depth[e] = self.sign(e) * self.nesting_depth[e] + // self.nesting_depth2[e] = self.sign(e) * self.nesting_depth2[e] // self.embedding.add_nodes_from(self.DG.nodes) // for v in self.DG: // # sort the adjacency lists again // self.ordered_adjs[v] = sorted( - // self.DG[v], key=lambda x: self.nesting_depth[(v, x)] + // self.DG[v], key=lambda x: self.nesting_depth2[(v, x)] // ) // # initialize the embedding // previous_node = None @@ -211,24 +271,17 @@ pub fn create_embedding( // self.embedding.add_half_edge_cw(v, w, previous_node) // previous_node = w - // # Free no longer used variables - // self.DG = None - // self.nesting_depth = None - // self.ref = None - // # compute the complete embedding // for v in self.roots: // self.dfs_embedding(v) - } -pub fn combinatorial_embedding_to_pos( - planar_emb: &PlanarEmbedding, -) -> Vec { +pub fn combinatorial_embedding_to_pos(planar_emb: &PlanarEmbedding) -> Vec { let mut pos: Vec = Vec::with_capacity(planar_emb.embedding.node_count()); if planar_emb.embedding.node_count() < 4 { let default_pos = [[0.0, 0.0], [2.0, 0.0], [1.0, 1.0]].to_vec(); - pos = planar_emb.embedding + pos = planar_emb + .embedding .node_indices() .map(|n| default_pos[n.index()]) .collect(); @@ -245,28 +298,38 @@ pub fn combinatorial_embedding_to_pos( pos } -fn triangulate_embedding( - planar_emb: &PlanarEmbedding, - fully_triangulate: bool, -) -> Vec { +fn triangulate_embedding(planar_emb: &PlanarEmbedding, fully_triangulate: bool) -> Vec { if planar_emb.embedding.node_count() <= 1 { - return planar_emb.embedding.node_indices().map(|n| n).collect::>(); + return planar_emb + .embedding + .node_indices() + .map(|n| n) + .collect::>(); } //let component_nodes = connected_components(embedding); - let outer_face = planar_emb.embedding.node_indices().map(|n| n).collect::>(); + let outer_face = planar_emb + .embedding + .node_indices() + .map(|n| n) + .collect::>(); println!("DFLT {:?}", outer_face); outer_face } -fn canonical_ordering( - planar_emb: &PlanarEmbedding, - outer_face: Vec, -) -> Vec { +fn canonical_ordering(planar_emb: &PlanarEmbedding, outer_face: Vec) -> Vec { if planar_emb.embedding.node_count() <= 1 { - return planar_emb.embedding.node_indices().map(|n| n).collect::>(); + return planar_emb + .embedding + .node_indices() + .map(|n| n) + .collect::>(); } //let component_nodes = connected_components(embedding); - let outer_face = planar_emb.embedding.node_indices().map(|n| n).collect::>(); + let outer_face = planar_emb + .embedding + .node_indices() + .map(|n| n) + .collect::>(); println!("DFLT {:?}", outer_face); outer_face } diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index fa5087b6a..f46091a7b 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -16,13 +16,12 @@ use std::vec::IntoIter; use hashbrown::{hash_map::Entry, HashMap}; use petgraph::{ + graph::{Graph, NodeIndex}, visit::{ EdgeCount, EdgeRef, GraphBase, GraphProp, IntoEdges, IntoNodeIdentifiers, NodeCount, - Visitable, NodeIndexable, + NodeIndexable, Visitable, }, - Undirected, - Directed, - graph::{Graph, NodeIndex}, + Directed, Undirected, }; use crate::traversal::{depth_first_search, DfsEvent}; @@ -195,7 +194,7 @@ where } } -enum Sign { +pub enum Sign { Plus, Minus, } @@ -216,13 +215,13 @@ pub struct LRState where G::NodeId: Hash + Eq, { - graph: G, + pub graph: G, /// roots of the DFS forest. - roots: Vec, + pub roots: Vec, /// distnace from root. height: HashMap, /// parent edge. - eparent: HashMap>, + pub eparent: HashMap>, /// height of lowest return point. lowpt: HashMap, usize>, /// height of next-to-lowest return point. Only used to check if an edge is chordal. @@ -238,14 +237,20 @@ where /// edge relative to which side is defined. eref: HashMap, Edge>, /// side of edge, or modifier for side of reference edge. - side: HashMap, Sign>, + pub side: HashMap, Sign>, /// directed graph used to build the embedding pub dir_graph: Graph<(), (), Directed>, } impl LRState where - G: GraphBase + NodeCount + EdgeCount + IntoNodeIdentifiers + NodeIndexable + IntoEdges + Visitable, + G: GraphBase + + NodeCount + + EdgeCount + + IntoNodeIdentifiers + + NodeIndexable + + IntoEdges + + Visitable, G::NodeId: Hash + Eq, { fn new(graph: G) -> Self { @@ -270,9 +275,9 @@ where .collect(), dir_graph: Graph::with_capacity(num_nodes, 0), }; - for node in graph.node_identifiers() { + for _ in graph.node_identifiers() { lr_state.dir_graph.add_node(()); - }; + } lr_state } @@ -285,7 +290,11 @@ where } } DfsEvent::TreeEdge(v, w, _) => { - self.dir_graph.add_edge(NodeIndex::new(self.graph.to_index(v)), NodeIndex::new(self.graph.to_index(w)), ()); + self.dir_graph.add_edge( + NodeIndex::new(self.graph.to_index(v)), + NodeIndex::new(self.graph.to_index(w)), + (), + ); let ei = (v, w); let v_height = self.height[&v]; let w_height = v_height + 1; @@ -299,7 +308,11 @@ where DfsEvent::BackEdge(v, w, _) => { // do *not* consider ``(v, w)`` as a back edge if ``(w, v)`` is a tree edge. if Some(&(w, v)) != self.eparent.get(&v) { - self.dir_graph.add_edge(NodeIndex::new(self.graph.to_index(v)), NodeIndex::new(self.graph.to_index(w)), ()); + self.dir_graph.add_edge( + NodeIndex::new(self.graph.to_index(v)), + NodeIndex::new(self.graph.to_index(w)), + (), + ); let ei = (v, w); self.lowpt.insert(ei, self.height[&w]); self.lowpt_2.insert(ei, self.height[&v]); diff --git a/retworkx-core/src/planar/mod.rs b/retworkx-core/src/planar/mod.rs index 404545fba..49f47b276 100644 --- a/retworkx-core/src/planar/mod.rs +++ b/retworkx-core/src/planar/mod.rs @@ -12,10 +12,10 @@ //! Module for planar graphs. -pub mod lr_planar; pub mod embedding; +pub mod lr_planar; -pub use lr_planar::is_planar; -pub use embedding::PlanarEmbedding; -pub use embedding::create_embedding; pub use embedding::combinatorial_embedding_to_pos; +pub use embedding::create_embedding; +pub use embedding::PlanarEmbedding; +pub use lr_planar::{is_planar, LRState}; diff --git a/src/layout/planar.rs b/src/layout/planar.rs index a92690c8f..ae5f08255 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -6,14 +6,15 @@ use crate::iterators::Pos2DMapping; use crate::Graph; use crate::StablePyGraph; use retworkx_core::dictmap::*; -use retworkx_core::planar::{PlanarEmbedding, is_planar, create_embedding, combinatorial_embedding_to_pos}; +use retworkx_core::planar::{ + combinatorial_embedding_to_pos, create_embedding, is_planar, LRState, PlanarEmbedding, +}; pub fn planar_layout( graph: &StablePyGraph, scale: Option, center: Option, ) -> Pos2DMapping { - let node_num = graph.node_count(); if node_num == 0 { return Pos2DMapping { @@ -28,11 +29,10 @@ pub fn planar_layout( pos_map: DictMap::new(), }; } else { - let mut planar_emb = PlanarEmbedding::default(); planar_emb.embedding = Graph::with_capacity(node_num, 0); - create_embedding(&planar_emb, &lr_state); + create_embedding(&mut planar_emb, &lr_state); let mut pos = combinatorial_embedding_to_pos(&planar_emb); From b0def5a371bacf58449a915bbc71ff670b183b75 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sun, 29 May 2022 16:16:44 -0700 Subject: [PATCH 11/37] Finish phase 1 creating embedding --- retworkx-core/src/planar/embedding.rs | 69 ++++++++++----------------- retworkx-core/src/planar/lr_planar.rs | 31 ++++++------ src/layout/planar.rs | 9 +++- 3 files changed, 50 insertions(+), 59 deletions(-) diff --git a/retworkx-core/src/planar/embedding.rs b/retworkx-core/src/planar/embedding.rs index 8b4b8c281..209da4a32 100644 --- a/retworkx-core/src/planar/embedding.rs +++ b/retworkx-core/src/planar/embedding.rs @@ -8,10 +8,11 @@ use std::hash::Hash; use crate::dictmap::*; use crate::planar::is_planar; -use crate::planar::lr_planar::LRState; +use crate::planar::lr_planar::{LRState, Sign::Plus}; pub type Point = [f64; 2]; +#[derive(Debug)] pub struct CwCcw { cw: Option, ccw: Option, @@ -35,6 +36,7 @@ impl CwCcw { } } +#[derive(Debug)] pub struct FirstNbr { first_nbr: Option, } @@ -145,6 +147,7 @@ impl PlanarEmbedding { found_weight.ccw = Some(new_node); } } + fn get_edge_weight(&self, v: NodeIndex, w: NodeIndex, cw: bool) -> NodeIndex { let found_edge = self.embedding.find_edge(v, w); let found_weight = self.embedding.edge_weight(found_edge.unwrap()).unwrap(); @@ -172,11 +175,11 @@ pub fn create_embedding( ::NodeId: Hash + Eq, ::NodeId: Debug, { - for node in lr_state.dir_graph.node_indices() { - for edge in lr_state.dir_graph.edges(node) { - println!("Edge {:?}, {:?}", edge.source(), edge.target()); - } - } + // for node in lr_state.dir_graph.node_indices() { + // for edge in lr_state.dir_graph.edges(node) { + // println!("Edge {:?}, {:?}", edge.source(), edge.target()); + // } + // } let mut ordered_adjs: Vec> = Vec::new(); @@ -186,6 +189,8 @@ pub fn create_embedding( let first_nbr = FirstNbr::::default(); planar_emb.embedding.add_node(first_nbr); } + + for x in &ordered_adjs { println!("ordered {:?}", x); } @@ -193,50 +198,46 @@ pub fn create_embedding( println!("nesting {:?}", x); } + for v in lr_state.dir_graph.node_indices() { let mut prev_node: Option = None; for w in &ordered_adjs[v.index()] { - //println!("v {:?} w {:?} prev {:?}", v, *w, prev_node); planar_emb.add_half_edge_cw(v, *w, prev_node); prev_node = Some(*w) } } - //println!("roots {:?}", &lr_state.roots); let mut left_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); let mut right_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); let mut idx: Vec = vec![0; ordered_adjs.len()]; - //println!("First idx {:?}", idx); - //idx.push(vec![0; ordered_adjs.len()]); for v_id in lr_state.roots.iter() { let v = id_to_index(&lr_state.graph, *v_id); println!("second v {:?} v index {:?} ord {:?} idx {:?}", v, v.index(), ordered_adjs[v.index()], idx); let mut dfs_stack: Vec = vec![v]; - //println!("idx {:?}", idx); println!("lr eparent {:?}", lr_state.eparent); while dfs_stack.len() > 0 { let v = dfs_stack.pop().unwrap(); - //println!("v {:?} {:?}", v, idx); let idx2 = idx[v.index()]; for (w_pos, w) in ordered_adjs[v.index()][idx2..].iter().enumerate() { - //println!("w {:?} {:?}", w, idx); let w_id = index_to_id(&lr_state.graph, *w); println!("third v {:?} vindex {:?} w {:?} w_id {:?} w_pos {:?} idx {:?} ", v, v.index(), *w, w_id, w_pos, idx); idx[v.index()] += 1; let ei = (v, w); - let (mut v1, mut v2) = (NodeIndex::new(0), NodeIndex::new(0)); + let ei_id = (index_to_id(&lr_state.graph, v), index_to_id(&lr_state.graph, *w)); + //let (mut v1, mut v2) = (NodeIndex::default(), NodeIndex::default()); if lr_state.eparent.contains_key(&w_id) { - let e_id = lr_state.eparent[&w_id]; - (v1, v2) = ( - id_to_index(&lr_state.graph, e_id.0), - id_to_index(&lr_state.graph, e_id.1), + let parent_id = lr_state.eparent[&w_id]; + let (v1, v2) = ( + id_to_index(&lr_state.graph, parent_id.0), + id_to_index(&lr_state.graph, parent_id.1), ); if ei == (v1, &v2) { + println!("in ei {:?}", ei); planar_emb.add_half_edge_first(*w, v); left_ref.entry(v).or_insert(*w); right_ref.entry(v).or_insert(*w); @@ -244,36 +245,18 @@ pub fn create_embedding( dfs_stack.push(*w); break; } else { - planar_emb.add_half_edge_cw(*w, v, Some(right_ref[w])); + if lr_state.side[&ei_id] == Plus { + planar_emb.add_half_edge_cw(*w, v, Some(right_ref[w])); + } else { + planar_emb.add_half_edge_ccw(*w, v, Some(left_ref[w])); + left_ref.entry(*w).or_insert(v); + } + println!("in else {:?}", ei); } } } } } - - // for v in self.DG: # sort the adjacency lists by nesting depth - // # note: this sorting leads to non linear time - // self.ordered_adjs[v] = sorted( - // self.DG[v], key=lambda x: self.nesting_depth2[(v, x)] - // ) - // for e in self.DG.edges: - // self.nesting_depth2[e] = self.sign(e) * self.nesting_depth2[e] - - // self.embedding.add_nodes_from(self.DG.nodes) - // for v in self.DG: - // # sort the adjacency lists again - // self.ordered_adjs[v] = sorted( - // self.DG[v], key=lambda x: self.nesting_depth2[(v, x)] - // ) - // # initialize the embedding - // previous_node = None - // for w in self.ordered_adjs[v]: - // self.embedding.add_half_edge_cw(v, w, previous_node) - // previous_node = w - - // # compute the complete embedding - // for v in self.roots: - // self.dfs_embedding(v) } pub fn combinatorial_embedding_to_pos(planar_emb: &PlanarEmbedding) -> Vec { diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index f46091a7b..e0e31549b 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -194,6 +194,7 @@ where } } +#[derive(PartialEq)] pub enum Sign { Plus, Minus, @@ -253,7 +254,7 @@ where + Visitable, G::NodeId: Hash + Eq, { - fn new(graph: G) -> Self { + pub fn new(graph: G) -> Self { let num_nodes = graph.node_count(); let num_edges = graph.edge_count(); @@ -692,7 +693,7 @@ where /// ]); /// assert!(is_planar(&grid)) /// ``` -pub fn is_planar(graph: G) -> (bool, LRState) +pub fn is_planar(graph: G, lr_state: &mut LRState) -> bool where G: GraphProp + NodeCount @@ -703,30 +704,30 @@ where + Visitable, G::NodeId: Hash + Eq, { - let mut state = LRState::new(graph); - - // Dfs orientation phase + // let lr_state = match state { + // Some(state) => state.unwrap(), + // None => &mut LRState::new(graph), + // }; + // // Dfs orientation phase depth_first_search(graph, graph.node_identifiers(), |event| { - state.lr_orientation_visitor(event) + lr_state.lr_orientation_visitor(event) }); - println!("LR before roots"); // Left - Right partition. - for v in state.roots.clone() { - let res = lr_visit_ordered_dfs_tree(&mut state, v, |state, event| { - state.lr_testing_visitor(event) + for v in lr_state.roots.clone() { + let res = lr_visit_ordered_dfs_tree(lr_state, v, |lr_state, event| { + lr_state.lr_testing_visitor(event) }); if res.is_err() { - return (false, state); + return false; } } - println!("LR true"); - for node in state.dir_graph.node_indices() { - for edge in state.dir_graph.edges(node) { + for node in lr_state.dir_graph.node_indices() { + for edge in lr_state.dir_graph.edges(node) { println!("Edge {:?}, {:?}", edge.source(), edge.target()); } } - (true, state) + true } diff --git a/src/layout/planar.rs b/src/layout/planar.rs index ae5f08255..4ae33974a 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -15,6 +15,7 @@ pub fn planar_layout( scale: Option, center: Option, ) -> Pos2DMapping { + let node_num = graph.node_count(); if node_num == 0 { return Pos2DMapping { @@ -22,7 +23,8 @@ pub fn planar_layout( }; } - let (its_planar, lr_state) = is_planar(graph); + let mut lr_state = LRState::new(graph); + let its_planar = is_planar(graph, &mut lr_state); if !its_planar { return Pos2DMapping { @@ -34,6 +36,11 @@ pub fn planar_layout( create_embedding(&mut planar_emb, &lr_state); + for node in planar_emb.embedding.node_indices() { + println!("emb node {:?}", planar_emb.embedding[node]); + println!("emb edges {:?}", planar_emb.embedding.edges(node)); + } + let mut pos = combinatorial_embedding_to_pos(&planar_emb); if let Some(scale) = scale { From 41c46cbfb54d8f281b8fa226ebd5e33ec3ec5762 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Tue, 31 May 2022 08:25:40 -0700 Subject: [PATCH 12/37] Work on sign and cleanup. Ready to start embedding_to_pos --- retworkx-core/src/planar/embedding.rs | 119 ++++++++++++++++++++------ retworkx-core/src/planar/lr_planar.rs | 15 ++-- retworkx-core/src/planar/mod.rs | 2 +- src/layout/planar.rs | 15 ++-- 4 files changed, 111 insertions(+), 40 deletions(-) diff --git a/retworkx-core/src/planar/embedding.rs b/retworkx-core/src/planar/embedding.rs index 209da4a32..4dd5e99ee 100644 --- a/retworkx-core/src/planar/embedding.rs +++ b/retworkx-core/src/planar/embedding.rs @@ -1,14 +1,15 @@ use hashbrown::hash_map::HashMap; +use petgraph::graph::Edge; use petgraph::graph::Graph; use petgraph::prelude::*; use petgraph::visit::{GraphBase, NodeIndexable}; use petgraph::Directed; +use rayon::prelude::*; use std::fmt::Debug; use std::hash::Hash; +use std::ops::Mul; -use crate::dictmap::*; -use crate::planar::is_planar; -use crate::planar::lr_planar::{LRState, Sign::Plus}; +use crate::planar::lr_planar::{LRState, Sign}; pub type Point = [f64; 2]; @@ -67,7 +68,7 @@ impl Default for PlanarEmbedding { } } impl PlanarEmbedding { - fn new() -> Self { + pub fn new() -> Self { PlanarEmbedding { embedding: Graph::, CwCcw, Directed>::new(), } @@ -80,10 +81,9 @@ impl PlanarEmbedding { ref_nbr: Option, ) { let cw_weight = CwCcw::::default(); - let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); + self.embedding.add_edge(start_node, end_node, cw_weight); if ref_nbr.is_none() { // The start node has no neighbors - let first_nbr = FirstNbr::::default(); self.update_edge_weight(start_node, end_node, end_node, true); self.update_edge_weight(start_node, end_node, end_node, false); self.embedding[start_node].first_nbr = Some(end_node); @@ -107,7 +107,7 @@ impl PlanarEmbedding { ) { if ref_nbr.is_none() { let cw_weight = CwCcw::::default(); - let new_edge = self.embedding.add_edge(start_node, end_node, cw_weight); + self.embedding.add_edge(start_node, end_node, cw_weight); self.update_edge_weight(start_node, end_node, end_node, true); self.update_edge_weight(start_node, end_node, end_node, false); self.embedding[start_node].first_nbr = Some(end_node); @@ -174,6 +174,9 @@ pub fn create_embedding( ) where ::NodeId: Hash + Eq, ::NodeId: Debug, + // These are needed for par_sort + G: std::marker::Sync, + ::NodeId: Sync, { // for node in lr_state.dir_graph.node_indices() { // for edge in lr_state.dir_graph.edges(node) { @@ -188,16 +191,31 @@ pub fn create_embedding( let first_nbr = FirstNbr::::default(); planar_emb.embedding.add_node(first_nbr); + // Change the sign for nesting_depth + // for e in lr_state.dir_graph.edges(v) { + // lr_state.nesting_depth[e] = sign(e, &lr_state.eref, &lr_state.side) * lr_state.nesting_depth[&e]; + // } } + // for x in &ordered_adjs { + // println!("ordered {:?}", x); + // } + // for x in &lr_state.nesting_depth { + // println!("nesting {:?}", x); + // } + //lr_state.nesting_depth.iter().enumerate().map(|(e, val)| (e, val * lr_state.sign(e)) - for x in &ordered_adjs { - println!("ordered {:?}", x); - } - for x in &lr_state.nesting_depth { - println!("nesting {:?}", x); + for (v, adjs) in ordered_adjs.iter_mut().enumerate() { + adjs.par_sort_by_key(|x| { + lr_state.nesting_depth[&( + index_to_id(&lr_state.graph, NodeIndex::new(v)), + index_to_id(&lr_state.graph, *x), + )] + }); } - + // for x in &ordered_adjs { + // println!("ordered 2222 {:?}", x); + // } for v in lr_state.dir_graph.node_indices() { let mut prev_node: Option = None; @@ -213,22 +231,38 @@ pub fn create_embedding( for v_id in lr_state.roots.iter() { let v = id_to_index(&lr_state.graph, *v_id); - println!("second v {:?} v index {:?} ord {:?} idx {:?}", v, v.index(), ordered_adjs[v.index()], idx); + // println!( + // "second v {:?} v index {:?} ord {:?} idx {:?}", + // v, + // v.index(), + // ordered_adjs[v.index()], + // idx + // ); let mut dfs_stack: Vec = vec![v]; - println!("lr eparent {:?}", lr_state.eparent); + // println!("lr eparent {:?}", lr_state.eparent); while dfs_stack.len() > 0 { let v = dfs_stack.pop().unwrap(); let idx2 = idx[v.index()]; for (w_pos, w) in ordered_adjs[v.index()][idx2..].iter().enumerate() { let w_id = index_to_id(&lr_state.graph, *w); - println!("third v {:?} vindex {:?} w {:?} w_id {:?} w_pos {:?} idx {:?} ", v, v.index(), *w, w_id, w_pos, idx); + // println!( + // "third v {:?} vindex {:?} w {:?} w_id {:?} w_pos {:?} idx {:?} ", + // v, + // v.index(), + // *w, + // w_id, + // w_pos, + // idx + // ); idx[v.index()] += 1; let ei = (v, w); - let ei_id = (index_to_id(&lr_state.graph, v), index_to_id(&lr_state.graph, *w)); - //let (mut v1, mut v2) = (NodeIndex::default(), NodeIndex::default()); + let ei_id = ( + index_to_id(&lr_state.graph, v), + index_to_id(&lr_state.graph, *w), + ); if lr_state.eparent.contains_key(&w_id) { let parent_id = lr_state.eparent[&w_id]; let (v1, v2) = ( @@ -237,7 +271,7 @@ pub fn create_embedding( ); if ei == (v1, &v2) { - println!("in ei {:?}", ei); + // println!("in ei {:?}", ei); planar_emb.add_half_edge_first(*w, v); left_ref.entry(v).or_insert(*w); right_ref.entry(v).or_insert(*w); @@ -245,21 +279,58 @@ pub fn create_embedding( dfs_stack.push(*w); break; } else { - if lr_state.side[&ei_id] == Plus { + if lr_state.side[&ei_id] == Sign::Plus { planar_emb.add_half_edge_cw(*w, v, Some(right_ref[w])); } else { planar_emb.add_half_edge_ccw(*w, v, Some(left_ref[w])); left_ref.entry(*w).or_insert(v); } - println!("in else {:?}", ei); + // println!("in else {:?}", ei); } } } } } + pub fn sign( + edge: Edge, + eref: &mut HashMap, Edge>, + side: &mut HashMap, Sign>, + ) -> i32 + where + G: GraphBase, + Edge: Hash + Eq + Copy, + { + let mut dfs_stack: Vec> = vec![edge]; + let mut old_ref: HashMap, Edge> = + HashMap::with_capacity(eref.len()); + + // """Resolve the relative side of an edge to the absolute side.""" + + let mut e: Edge = edge; + let mut side_final: i32 = 1; + while dfs_stack.len() > 0 { + e = dfs_stack.pop().unwrap(); + + if eref.contains_key(&e) { + dfs_stack.push(e); + dfs_stack.push(eref[&e]); + *old_ref.get_mut(&e).unwrap() = eref[&e]; + eref.remove(&e); + } else { + if side[&e] == side[&old_ref[&e]] { + *side.get_mut(&e).unwrap() = Sign::Plus; + side_final = 1; + } else { + *side.get_mut(&e).unwrap() = Sign::Minus; + side_final = -1; + } + } + } + side_final + } } -pub fn combinatorial_embedding_to_pos(planar_emb: &PlanarEmbedding) -> Vec { +pub fn embedding_to_pos(planar_emb: &PlanarEmbedding) -> Vec { let mut pos: Vec = Vec::with_capacity(planar_emb.embedding.node_count()); if planar_emb.embedding.node_count() < 4 { let default_pos = [[0.0, 0.0], [2.0, 0.0], [1.0, 1.0]].to_vec(); @@ -295,7 +366,7 @@ fn triangulate_embedding(planar_emb: &PlanarEmbedding, fully_triangulate: bool) .node_indices() .map(|n| n) .collect::>(); - println!("DFLT {:?}", outer_face); + // println!("DFLT {:?}", outer_face); outer_face } @@ -313,6 +384,6 @@ fn canonical_ordering(planar_emb: &PlanarEmbedding, outer_face: Vec) .node_indices() .map(|n| n) .collect::>(); - println!("DFLT {:?}", outer_face); + // println!("DFLT {:?}", outer_face); outer_face } diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index e0e31549b..b9882a12b 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -13,6 +13,7 @@ use std::cmp::Ordering; use std::hash::Hash; use std::vec::IntoIter; +use std::ops::Mul; use hashbrown::{hash_map::Entry, HashMap}; use petgraph::{ @@ -195,7 +196,7 @@ where } #[derive(PartialEq)] -pub enum Sign { +pub enum Sign{ Plus, Minus, } @@ -236,7 +237,7 @@ where /// marks the top conflict pair when an edge was pushed in the stack. stack_emarker: HashMap, ConflictPair>>, /// edge relative to which side is defined. - eref: HashMap, Edge>, + pub eref: HashMap, Edge>, /// side of edge, or modifier for side of reference edge. pub side: HashMap, Sign>, /// directed graph used to build the embedding @@ -723,11 +724,11 @@ where } } - for node in lr_state.dir_graph.node_indices() { - for edge in lr_state.dir_graph.edges(node) { - println!("Edge {:?}, {:?}", edge.source(), edge.target()); - } - } + // for node in lr_state.dir_graph.node_indices() { + // for edge in lr_state.dir_graph.edges(node) { + // println!("Edge {:?}, {:?}", edge.source(), edge.target()); + // } + // } true } diff --git a/retworkx-core/src/planar/mod.rs b/retworkx-core/src/planar/mod.rs index 49f47b276..e9e869e88 100644 --- a/retworkx-core/src/planar/mod.rs +++ b/retworkx-core/src/planar/mod.rs @@ -15,7 +15,7 @@ pub mod embedding; pub mod lr_planar; -pub use embedding::combinatorial_embedding_to_pos; pub use embedding::create_embedding; +pub use embedding::embedding_to_pos; pub use embedding::PlanarEmbedding; pub use lr_planar::{is_planar, LRState}; diff --git a/src/layout/planar.rs b/src/layout/planar.rs index 4ae33974a..4d4cc2936 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -7,7 +7,7 @@ use crate::Graph; use crate::StablePyGraph; use retworkx_core::dictmap::*; use retworkx_core::planar::{ - combinatorial_embedding_to_pos, create_embedding, is_planar, LRState, PlanarEmbedding, + create_embedding, embedding_to_pos, is_planar, LRState, PlanarEmbedding, }; pub fn planar_layout( @@ -15,7 +15,6 @@ pub fn planar_layout( scale: Option, center: Option, ) -> Pos2DMapping { - let node_num = graph.node_count(); if node_num == 0 { return Pos2DMapping { @@ -31,17 +30,17 @@ pub fn planar_layout( pos_map: DictMap::new(), }; } else { - let mut planar_emb = PlanarEmbedding::default(); + let mut planar_emb = PlanarEmbedding::new(); planar_emb.embedding = Graph::with_capacity(node_num, 0); create_embedding(&mut planar_emb, &lr_state); - for node in planar_emb.embedding.node_indices() { - println!("emb node {:?}", planar_emb.embedding[node]); - println!("emb edges {:?}", planar_emb.embedding.edges(node)); - } + // for node in planar_emb.embedding.node_indices() { + // println!("emb node {:?}", planar_emb.embedding[node]); + // println!("emb edges {:?}", planar_emb.embedding.edges(node)); + // } - let mut pos = combinatorial_embedding_to_pos(&planar_emb); + let mut pos = embedding_to_pos(&planar_emb); if let Some(scale) = scale { rescale(&mut pos, scale, (0..node_num).collect()); From 4ee70c61ef268c9e80441cc298e6f65a976f300c Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Thu, 2 Jun 2022 20:37:49 -0700 Subject: [PATCH 13/37] Do triangulate code --- retworkx-core/src/planar/embedding.rs | 149 ++++++++++++++++++-------- retworkx-core/src/planar/lr_planar.rs | 3 +- src/layout/planar.rs | 3 +- 3 files changed, 106 insertions(+), 49 deletions(-) diff --git a/retworkx-core/src/planar/embedding.rs b/retworkx-core/src/planar/embedding.rs index 4dd5e99ee..9678654ae 100644 --- a/retworkx-core/src/planar/embedding.rs +++ b/retworkx-core/src/planar/embedding.rs @@ -1,4 +1,5 @@ use hashbrown::hash_map::HashMap; +use hashbrown::HashSet; use petgraph::graph::Edge; use petgraph::graph::Graph; use petgraph::prelude::*; @@ -7,19 +8,20 @@ use petgraph::Directed; use rayon::prelude::*; use std::fmt::Debug; use std::hash::Hash; -use std::ops::Mul; + +use crate::connectivity::connected_components; use crate::planar::lr_planar::{LRState, Sign}; pub type Point = [f64; 2]; -#[derive(Debug)] -pub struct CwCcw { +#[derive(Debug, Clone)] +pub struct CwCcw { cw: Option, ccw: Option, } -impl Default for CwCcw { +impl Default for CwCcw { fn default() -> Self { CwCcw { cw: None, @@ -28,32 +30,42 @@ impl Default for CwCcw { } } -impl CwCcw { +impl CwCcw { fn new(cw: T, ccw: T) -> Self { CwCcw { cw: Some(cw), ccw: Some(ccw), } } + fn clone(&self) -> Self { + CwCcw { + cw: self.cw.clone(), + ccw: self.ccw.clone(), + } + } } -#[derive(Debug)] -pub struct FirstNbr { +#[derive(Debug, Clone)] +pub struct FirstNbr { first_nbr: Option, } -impl Default for FirstNbr { +impl Default for FirstNbr { fn default() -> Self { FirstNbr { first_nbr: None } } } -impl FirstNbr { +impl FirstNbr { fn new(first_nbr: T) -> Self { FirstNbr { first_nbr: Some(first_nbr), } } + + fn clone(&self) -> Option { + self.first_nbr.clone() + } } pub struct PlanarEmbedding { @@ -74,6 +86,27 @@ impl PlanarEmbedding { } } + fn clone(&self) -> Graph, CwCcw, Directed> { + self.embedding.clone() + } + + fn neighbors_cw_order(&mut self, v: NodeIndex) -> Vec { + let mut nbrs: Vec = vec![]; + let mut first_nbr = self.embedding[v].first_nbr; + if first_nbr.is_none() { + return nbrs + } + let start_node = first_nbr.unwrap(); + nbrs.push(start_node); + + let mut current_node = self.get_edge_weight(v, start_node, true); + while start_node != current_node { + nbrs.push(current_node); + current_node = self.get_edge_weight(v, current_node, true); + } + nbrs + } + fn add_half_edge_cw( &mut self, start_node: NodeIndex, @@ -158,6 +191,11 @@ impl PlanarEmbedding { found_weight.ccw.unwrap() } } + + fn connect_components(&mut self, v: NodeIndex, w: NodeIndex) { + self.add_half_edge_first(v, w); + self.add_half_edge_first(w, v); + } } fn id_to_index(graph: G, node_id: G::NodeId) -> NodeIndex { @@ -301,8 +339,7 @@ pub fn create_embedding( Edge: Hash + Eq + Copy, { let mut dfs_stack: Vec> = vec![edge]; - let mut old_ref: HashMap, Edge> = - HashMap::with_capacity(eref.len()); + let mut old_ref: HashMap, Edge> = HashMap::with_capacity(eref.len()); // """Resolve the relative side of an edge to the absolute side.""" @@ -330,60 +367,82 @@ pub fn create_embedding( } } -pub fn embedding_to_pos(planar_emb: &PlanarEmbedding) -> Vec { +pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { let mut pos: Vec = Vec::with_capacity(planar_emb.embedding.node_count()); if planar_emb.embedding.node_count() < 4 { let default_pos = [[0.0, 0.0], [2.0, 0.0], [1.0, 1.0]].to_vec(); - pos = planar_emb + return planar_emb .embedding .node_indices() .map(|n| default_pos[n.index()]) .collect(); } - let outer_face = triangulate_embedding(&planar_emb, true); + let outer_face = triangulate_embedding(planar_emb); let right_t_child = HashMap::::new(); let left_t_child = HashMap::::new(); let delta_x = HashMap::::new(); let y_coord = HashMap::::new(); - let node_list = canonical_ordering(&planar_emb, outer_face); + let node_list = canonical_ordering(planar_emb, outer_face); pos } -fn triangulate_embedding(planar_emb: &PlanarEmbedding, fully_triangulate: bool) -> Vec { - if planar_emb.embedding.node_count() <= 1 { - return planar_emb - .embedding - .node_indices() - .map(|n| n) - .collect::>(); +fn triangulate_embedding( + planar_emb: &mut PlanarEmbedding, +) -> Vec { + let component_sets = connected_components(&planar_emb.embedding); + + for i in 0..(component_sets.len() - 1) { + let v1 = component_sets[i].iter().next().unwrap(); + let v2 = component_sets[i + 1].iter().next().unwrap(); + planar_emb.connect_components(*v1, *v2); } - //let component_nodes = connected_components(embedding); - let outer_face = planar_emb - .embedding - .node_indices() - .map(|n| n) - .collect::>(); - // println!("DFLT {:?}", outer_face); + let mut outer_face = vec![]; + let mut face_list = vec![]; + let mut edges_counted:HashSet = HashSet::new(); + + for v in planar_emb.embedding.node_indices() { + for w in planar_emb.neighbors_cw_order(v) { + let new_face = make_bi_connected(planar_emb, v, w, &mut edges_counted); + let new_len = new_face.len(); + if new_len > 0 { + face_list.push(new_face.clone()); + if new_len > outer_face.len() { + outer_face = new_face; + } + } + } + } + for face in face_list { + if face != outer_face { + triangulate_face(planar_emb, face[0], face[1]); + } + } + println!("outer_face {:?}", outer_face); outer_face } -fn canonical_ordering(planar_emb: &PlanarEmbedding, outer_face: Vec) -> Vec { - if planar_emb.embedding.node_count() <= 1 { - return planar_emb - .embedding - .node_indices() - .map(|n| n) - .collect::>(); - } - //let component_nodes = connected_components(embedding); - let outer_face = planar_emb - .embedding - .node_indices() - .map(|n| n) - .collect::>(); - // println!("DFLT {:?}", outer_face); - outer_face +fn triangulate_face(planar_emb: &PlanarEmbedding, v1: NodeIndex, v2: NodeIndex) { + () +} + +fn make_bi_connected( + planar_emb: &PlanarEmbedding, + start_node: NodeIndex, + end_node: NodeIndex, + edges_counted: &mut HashSet, +) -> Vec { + vec![NodeIndex::new(0), NodeIndex::new(1)] +} + +fn canonical_ordering( + planar_emb: &mut PlanarEmbedding, + outer_face: Vec, +) -> Vec<(NodeIndex, Vec)> { + vec![( + NodeIndex::new(0), + vec![NodeIndex::new(1), NodeIndex::new(2)], + )] } diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index b9882a12b..cf767ee7b 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -13,7 +13,6 @@ use std::cmp::Ordering; use std::hash::Hash; use std::vec::IntoIter; -use std::ops::Mul; use hashbrown::{hash_map::Entry, HashMap}; use petgraph::{ @@ -196,7 +195,7 @@ where } #[derive(PartialEq)] -pub enum Sign{ +pub enum Sign { Plus, Minus, } diff --git a/src/layout/planar.rs b/src/layout/planar.rs index 4d4cc2936..9b6bc43c4 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -1,7 +1,6 @@ use petgraph::prelude::*; use super::spring::{recenter, rescale, Point}; -use crate::connected_components; use crate::iterators::Pos2DMapping; use crate::Graph; use crate::StablePyGraph; @@ -40,7 +39,7 @@ pub fn planar_layout( // println!("emb edges {:?}", planar_emb.embedding.edges(node)); // } - let mut pos = embedding_to_pos(&planar_emb); + let mut pos = embedding_to_pos(&mut planar_emb); if let Some(scale) = scale { rescale(&mut pos, scale, (0..node_num).collect()); From 1240f760b6b326e18e82b2a5f70534757ee58a51 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sat, 18 Jun 2022 10:58:57 -0700 Subject: [PATCH 14/37] Move embedding to retworkx --- retworkx-core/src/planar/lr_planar.rs | 12 +- retworkx-core/src/planar/mod.rs | 4 - .../src/planar => src/layout}/embedding.rs | 313 ++++++++++++------ src/layout/mod.rs | 1 + src/layout/planar.rs | 10 +- 5 files changed, 228 insertions(+), 112 deletions(-) rename {retworkx-core/src/planar => src/layout}/embedding.rs (55%) diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index cf767ee7b..54705d3b7 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -693,7 +693,7 @@ where /// ]); /// assert!(is_planar(&grid)) /// ``` -pub fn is_planar(graph: G, lr_state: &mut LRState) -> bool +pub fn is_planar(graph: G, state: Option<&mut LRState>) -> bool where G: GraphProp + NodeCount @@ -704,10 +704,12 @@ where + Visitable, G::NodeId: Hash + Eq, { - // let lr_state = match state { - // Some(state) => state.unwrap(), - // None => &mut LRState::new(graph), - // }; + let mut lr_state = LRState::new(graph); + let lr_state = match state { + Some(state) => state, + None => &mut lr_state, + }; + // // Dfs orientation phase depth_first_search(graph, graph.node_identifiers(), |event| { lr_state.lr_orientation_visitor(event) diff --git a/retworkx-core/src/planar/mod.rs b/retworkx-core/src/planar/mod.rs index e9e869e88..d6fe1f4e6 100644 --- a/retworkx-core/src/planar/mod.rs +++ b/retworkx-core/src/planar/mod.rs @@ -12,10 +12,6 @@ //! Module for planar graphs. -pub mod embedding; pub mod lr_planar; -pub use embedding::create_embedding; -pub use embedding::embedding_to_pos; -pub use embedding::PlanarEmbedding; pub use lr_planar::{is_planar, LRState}; diff --git a/retworkx-core/src/planar/embedding.rs b/src/layout/embedding.rs similarity index 55% rename from retworkx-core/src/planar/embedding.rs rename to src/layout/embedding.rs index 9678654ae..f8ef24750 100644 --- a/retworkx-core/src/planar/embedding.rs +++ b/src/layout/embedding.rs @@ -9,19 +9,19 @@ use rayon::prelude::*; use std::fmt::Debug; use std::hash::Hash; -use crate::connectivity::connected_components; - -use crate::planar::lr_planar::{LRState, Sign}; +use retworkx_core::connectivity::connected_components; +use crate::StablePyGraph; +use retworkx_core::planar::lr_planar::{LRState, Sign}; pub type Point = [f64; 2]; -#[derive(Debug, Clone)] -pub struct CwCcw { +#[derive(Debug)] +pub struct CwCcw { cw: Option, ccw: Option, } -impl Default for CwCcw { +impl Default for CwCcw { fn default() -> Self { CwCcw { cw: None, @@ -30,42 +30,32 @@ impl Default for CwCcw { } } -impl CwCcw { +impl CwCcw { fn new(cw: T, ccw: T) -> Self { CwCcw { cw: Some(cw), ccw: Some(ccw), } } - fn clone(&self) -> Self { - CwCcw { - cw: self.cw.clone(), - ccw: self.ccw.clone(), - } - } } -#[derive(Debug, Clone)] -pub struct FirstNbr { +#[derive(Debug)] +pub struct FirstNbr { first_nbr: Option, } -impl Default for FirstNbr { +impl Default for FirstNbr { fn default() -> Self { FirstNbr { first_nbr: None } } } -impl FirstNbr { +impl FirstNbr { fn new(first_nbr: T) -> Self { FirstNbr { first_nbr: Some(first_nbr), } } - - fn clone(&self) -> Option { - self.first_nbr.clone() - } } pub struct PlanarEmbedding { @@ -86,24 +76,34 @@ impl PlanarEmbedding { } } - fn clone(&self) -> Graph, CwCcw, Directed> { - self.embedding.clone() - } - fn neighbors_cw_order(&mut self, v: NodeIndex) -> Vec { let mut nbrs: Vec = vec![]; - let mut first_nbr = self.embedding[v].first_nbr; + let first_nbr = self.embedding[v].first_nbr; + println!("first_nbr in nbrs_cw_order {:?}", first_nbr); if first_nbr.is_none() { - return nbrs + // v has no neighbors + return nbrs; } let start_node = first_nbr.unwrap(); + println!("start_node in nbrs_cw_order {:?}", start_node); nbrs.push(start_node); - let mut current_node = self.get_edge_weight(v, start_node, true); - while start_node != current_node { - nbrs.push(current_node); - current_node = self.get_edge_weight(v, current_node, true); + let mut node = self.get_edge_weight(v, start_node, true); + if let Some(mut current_node) = node { + //println!("current_node 1 {:?}", current_node); + + while start_node != current_node { + nbrs.push(current_node); + node = self.get_edge_weight(v, current_node, true); + current_node = match node { + Some(node) => node, + None => break, + }; + //println!("current_node 2 {:?}", current_node); + } } + println!("nbrs 1 in nbrs_cw_order {:?}", nbrs); + nbrs } @@ -124,12 +124,18 @@ impl PlanarEmbedding { } // if ref_nbr not in self[start_node] error let ref_nbr_node = ref_nbr.unwrap(); - let cw_ref_node = self.get_edge_weight(start_node, ref_nbr_node, true); - // Alter half-edge data structures - self.update_edge_weight(start_node, ref_nbr_node, end_node, true); - self.update_edge_weight(start_node, end_node, cw_ref_node, true); - self.update_edge_weight(start_node, cw_ref_node, end_node, false); - self.update_edge_weight(start_node, end_node, ref_nbr_node, false); + // DEBUG + if self.embedding.find_edge(start_node, ref_nbr_node).is_none() { + println!("NO REF NBR in ADD CW {:?} {:?}", start_node, ref_nbr_node); + } + let cw_ref = self.get_edge_weight(start_node, ref_nbr_node, true); + if let Some(cw_ref_node) = cw_ref { + // Alter half-edge data structures + self.update_edge_weight(start_node, ref_nbr_node, end_node, true); + self.update_edge_weight(start_node, end_node, cw_ref_node, true); + self.update_edge_weight(start_node, cw_ref_node, end_node, false); + self.update_edge_weight(start_node, end_node, ref_nbr_node, false); + } } fn add_half_edge_ccw( @@ -139,6 +145,7 @@ impl PlanarEmbedding { ref_nbr: Option, ) { if ref_nbr.is_none() { + // Start node has no neighbors let cw_weight = CwCcw::::default(); self.embedding.add_edge(start_node, end_node, cw_weight); self.update_edge_weight(start_node, end_node, end_node, true); @@ -146,9 +153,10 @@ impl PlanarEmbedding { self.embedding[start_node].first_nbr = Some(end_node); } else { let ref_nbr_node = ref_nbr.unwrap(); - let ccw_ref_node = Some(self.get_edge_weight(start_node, ref_nbr_node, false)); + let ccw_ref_node = self.get_edge_weight(start_node, ref_nbr_node, false); self.add_half_edge_cw(start_node, end_node, ccw_ref_node); if ref_nbr == self.embedding[start_node].first_nbr { + // Update first neighbor self.embedding[start_node].first_nbr = Some(end_node); } } @@ -156,7 +164,7 @@ impl PlanarEmbedding { fn add_half_edge_first(&mut self, start_node: NodeIndex, end_node: NodeIndex) { let ref_node: Option = if self.embedding.node_bound() >= start_node.index() - && !self.embedding[start_node].first_nbr.is_none() + && self.embedding[start_node].first_nbr.is_some() { self.embedding[start_node].first_nbr } else { @@ -166,29 +174,59 @@ impl PlanarEmbedding { } fn next_face_half_edge(&self, v: NodeIndex, w: NodeIndex) -> (NodeIndex, NodeIndex) { - let new_node = self.get_edge_weight(v, w, false); - (w, new_node) + //println!("next v {:?} w {:?}", v, w); + let new_node = self.get_edge_weight(w, v, false); + //println!("new node {:?}", new_node); + // FIX THIS + // + // + if new_node.is_none() { + println!("NEW NODE NONE in next_face {:?} {:?} {:?}", new_node, v, w); + return (w, v); + } + (w, new_node.unwrap()) } fn update_edge_weight(&mut self, v: NodeIndex, w: NodeIndex, new_node: NodeIndex, cw: bool) { let found_edge = self.embedding.find_edge(v, w); - let mut found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()).unwrap(); + if found_edge.is_none() { + return; + } + let found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()); //.unwrap(); + if found_weight.is_none() { + return; + } if cw { - found_weight.cw = Some(new_node); + found_weight.unwrap().cw = Some(new_node); } else { - found_weight.ccw = Some(new_node); + found_weight.unwrap().ccw = Some(new_node); } } - fn get_edge_weight(&self, v: NodeIndex, w: NodeIndex, cw: bool) -> NodeIndex { + fn get_edge_weight(&self, v: NodeIndex, w: NodeIndex, cw: bool) -> Option { + println!("GET EDGE v w {:?} {:?}", v, w); let found_edge = self.embedding.find_edge(v, w); - let found_weight = self.embedding.edge_weight(found_edge.unwrap()).unwrap(); + // FIX THIS + // + // + if found_edge.is_none() { + return None; + } + println!("GET EDGE find edge{:?}", found_edge); + let found_weight = self.embedding.edge_weight(found_edge.unwrap()); //.unwrap(); + //println!("after found weight {:?}", found_weight); + if found_weight.is_none() { + println!("GET EDGE Weight is none {:?}", found_weight); + return None; + // } else { + // let found_weight = found_weight.unwrap(); + } if cw { - found_weight.cw.unwrap() + found_weight.unwrap().cw } else { - found_weight.ccw.unwrap() + found_weight.unwrap().ccw } } @@ -206,21 +244,22 @@ fn index_to_id(graph: G, node_index: NodeIndex) -> graph.from_index(node_index.index()) } -pub fn create_embedding( +pub fn create_embedding( planar_emb: &mut PlanarEmbedding, - lr_state: &LRState, -) where - ::NodeId: Hash + Eq, - ::NodeId: Debug, - // These are needed for par_sort - G: std::marker::Sync, - ::NodeId: Sync, + lr_state: &mut LRState<&StablePyGraph>, +) +// where +// ::NodeId: Hash + Eq, +// ::NodeId: Debug, +// // These are needed for par_sort +// G: std::marker::Sync, +// ::NodeId: Sync, { - // for node in lr_state.dir_graph.node_indices() { - // for edge in lr_state.dir_graph.edges(node) { - // println!("Edge {:?}, {:?}", edge.source(), edge.target()); - // } - // } + for node in lr_state.dir_graph.node_indices() { + for edge in lr_state.dir_graph.edges(node) { + println!("Edge {:?}, {:?}", edge.source(), edge.target()); + } + } let mut ordered_adjs: Vec> = Vec::new(); @@ -229,19 +268,26 @@ pub fn create_embedding( let first_nbr = FirstNbr::::default(); planar_emb.embedding.add_node(first_nbr); + let mut nesting_depth: HashMap<(NodeIndex, NodeIndex), i32> = + HashMap::with_capacity(lr_state.nesting_depth.len()); // Change the sign for nesting_depth - // for e in lr_state.dir_graph.edges(v) { - // lr_state.nesting_depth[e] = sign(e, &lr_state.eref, &lr_state.side) * lr_state.nesting_depth[&e]; - // } + for e in lr_state.dir_graph.edges(v) { + let edge: (NodeIndex, NodeIndex) = (e.source(), e.target()); + //let edge_base = (index_to_id(&lr_state.graph, edge.source()), index_to_id(&lr_state.graph, edge.target())); + let signed_depth: i32 = lr_state.nesting_depth[&edge] as i32; + nesting_depth.insert(edge, + sign(edge, &mut lr_state.eref, &mut lr_state.side) * signed_depth); + //lr_state.nesting_depth[&edge]; + } + } + for x in &ordered_adjs { + println!("ordered {:?}", x); + } + for x in &lr_state.nesting_depth { + println!("nesting {:?}", x); } - // for x in &ordered_adjs { - // println!("ordered {:?}", x); - // } - // for x in &lr_state.nesting_depth { - // println!("nesting {:?}", x); - // } - //lr_state.nesting_depth.iter().enumerate().map(|(e, val)| (e, val * lr_state.sign(e)) + //lr_state.nesting_depth.iter().enumerate().map(|(e, val)| (e, val * sign(e))); for (v, adjs) in ordered_adjs.iter_mut().enumerate() { adjs.par_sort_by_key(|x| { @@ -251,9 +297,9 @@ pub fn create_embedding( )] }); } - // for x in &ordered_adjs { - // println!("ordered 2222 {:?}", x); - // } + for x in &ordered_adjs { + println!("ordered 2222 {:?}", x); + } for v in lr_state.dir_graph.node_indices() { let mut prev_node: Option = None; @@ -262,6 +308,9 @@ pub fn create_embedding( prev_node = Some(*w) } } + for v in planar_emb.embedding.node_indices() { + println!("11111 embedding node {:?} {:?}", v, planar_emb.embedding[v]); + } let mut left_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); let mut right_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); @@ -329,22 +378,27 @@ pub fn create_embedding( } } } - pub fn sign( - edge: Edge, - eref: &mut HashMap, Edge>, - side: &mut HashMap, Sign>, - ) -> i32 - where - G: GraphBase, - Edge: Hash + Eq + Copy, - { - let mut dfs_stack: Vec> = vec![edge]; - let mut old_ref: HashMap, Edge> = HashMap::with_capacity(eref.len()); + + for v in planar_emb.embedding.node_indices() { + println!("22222 embedding node {:?} {:?}", v, planar_emb.embedding[v]); + } + pub fn sign( + edge: (NodeIndex, NodeIndex), + eref: &mut HashMap<(NodeIndex, NodeIndex), (NodeIndex, NodeIndex)>, + side: &mut HashMap<(NodeIndex, NodeIndex), Sign>, + ) -> i32 { + let mut dfs_stack: Vec<(NodeIndex, NodeIndex)> = vec![edge]; + let mut old_ref: HashMap<(NodeIndex, NodeIndex), (NodeIndex, NodeIndex)> = + HashMap::with_capacity(eref.len()); // """Resolve the relative side of an edge to the absolute side.""" - let mut e: Edge = edge; + let mut e: (NodeIndex, NodeIndex) = edge; let mut side_final: i32 = 1; + // for e in side { + // println!("side {} {}", e.0.0.index(), e.0.1.index()); + // } + //return side_final; while dfs_stack.len() > 0 { e = dfs_stack.pop().unwrap(); @@ -354,11 +408,19 @@ pub fn create_embedding( *old_ref.get_mut(&e).unwrap() = eref[&e]; eref.remove(&e); } else { + if side.contains_key(&e) { if side[&e] == side[&old_ref[&e]] { - *side.get_mut(&e).unwrap() = Sign::Plus; + *side.get_mut(&e).unwrap() = Sign::Plus; + } else { + println!("side no find plus {} {}", e.0.index(), e.1.index()); + } side_final = 1; } else { - *side.get_mut(&e).unwrap() = Sign::Minus; + if side.contains_key(&e) { + *side.get_mut(&e).unwrap() = Sign::Minus; + } else { + println!("side no find minus {} {}", e.0.index(), e.1.index()); + } side_final = -1; } } @@ -389,10 +451,9 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { pos } -fn triangulate_embedding( - planar_emb: &mut PlanarEmbedding, -) -> Vec { +fn triangulate_embedding(planar_emb: &mut PlanarEmbedding) -> Vec { let component_sets = connected_components(&planar_emb.embedding); + println!("CONN COMP {:?}", component_sets); for i in 0..(component_sets.len() - 1) { let v1 = component_sets[i].iter().next().unwrap(); @@ -401,7 +462,9 @@ fn triangulate_embedding( } let mut outer_face = vec![]; let mut face_list = vec![]; - let mut edges_counted:HashSet = HashSet::new(); + let mut edges_counted: HashSet<(NodeIndex, NodeIndex)> = HashSet::new(); + println!(" in triangulate component sets{:?}", component_sets); + return outer_face; for v in planar_emb.embedding.node_indices() { for w in planar_emb.neighbors_cw_order(v) { @@ -415,7 +478,11 @@ fn triangulate_embedding( } } } + return outer_face; + + println!("FINAL Face list {:?}", face_list); for face in face_list { + println!("face {:?} outer_face {:?}", face, outer_face); if face != outer_face { triangulate_face(planar_emb, face[0], face[1]); } @@ -424,17 +491,67 @@ fn triangulate_embedding( outer_face } -fn triangulate_face(planar_emb: &PlanarEmbedding, v1: NodeIndex, v2: NodeIndex) { - () +fn triangulate_face(planar_emb: &mut PlanarEmbedding, mut v1: NodeIndex, mut v2: NodeIndex) { + let (_, mut v3) = planar_emb.next_face_half_edge(v1, v2); + let (_, mut v4) = planar_emb.next_face_half_edge(v2, v3); + if v1 == v2 || v1 == v3 { + return; + } + let mut count = 0; + while v1 != v4 && count < 20 { + if planar_emb.embedding.contains_edge(v1, v3) { + (v1, v2, v3) = (v2, v3, v4); + } else { + planar_emb.add_half_edge_cw(v1, v3, Some(v2)); + planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); + (v1, v2, v3) = (v1, v3, v4); + } + (_, v4) = planar_emb.next_face_half_edge(v2, v3); + count += 1; + } } fn make_bi_connected( - planar_emb: &PlanarEmbedding, + planar_emb: &mut PlanarEmbedding, start_node: NodeIndex, - end_node: NodeIndex, - edges_counted: &mut HashSet, + out_node: NodeIndex, + edges_counted: &mut HashSet<(NodeIndex, NodeIndex)>, ) -> Vec { - vec![NodeIndex::new(0), NodeIndex::new(1)] + if edges_counted.contains(&(start_node, out_node)) || start_node == out_node { + return vec![]; + } + edges_counted.insert((start_node, out_node)); + //println!("edges counted {:?} {:?} {:?}", start_node, out_node, edges_counted); + let mut v1 = start_node.clone(); + let mut v2 = out_node.clone(); + let mut face_list: Vec = vec![start_node]; + let (_, mut v3) = planar_emb.next_face_half_edge(v1, v2); + + let mut count = 0; + while (v2 != start_node || v3 != out_node) && v2 != v3 && count < 20 { + // && count < 300 { + //println!("face_list in while {:?} {:?} {:?}", v2, v3, face_list); + + if face_list.contains(&v2) { + //println!("face_list contains v2 {:?} {:?} {:?}", v2, v3, edges_counted); + planar_emb.add_half_edge_cw(v1, v3, Some(v2)); + planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); + edges_counted.insert((v2, v3)); + edges_counted.insert((v3, v1)); + v2 = v1.clone(); + } else { + //println!("face_list not contain v2 {:?} {:?} {:?}", v2, v3, edges_counted); + face_list.push(v2); + } + v1 = v2.clone(); + //println!("2 edges counted {:?} {:?} {:?}", v2, v3, edges_counted); + (v2, v3) = planar_emb.next_face_half_edge(v2, v3); + //println!("3 edges counted {:?} {:?} {:?}", v2, v3, edges_counted); + + edges_counted.insert((v1, v2)); + count += 1; + } + face_list } fn canonical_ordering( diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 93215e26f..21457be57 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -12,6 +12,7 @@ mod bipartite; mod circular; +mod embedding; mod planar; mod random; mod shell; diff --git a/src/layout/planar.rs b/src/layout/planar.rs index 9b6bc43c4..737f50fd6 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -2,12 +2,11 @@ use petgraph::prelude::*; use super::spring::{recenter, rescale, Point}; use crate::iterators::Pos2DMapping; +use crate::layout::embedding::{create_embedding, embedding_to_pos, PlanarEmbedding}; use crate::Graph; use crate::StablePyGraph; use retworkx_core::dictmap::*; -use retworkx_core::planar::{ - create_embedding, embedding_to_pos, is_planar, LRState, PlanarEmbedding, -}; +use retworkx_core::planar::{is_planar, LRState}; pub fn planar_layout( graph: &StablePyGraph, @@ -22,7 +21,7 @@ pub fn planar_layout( } let mut lr_state = LRState::new(graph); - let its_planar = is_planar(graph, &mut lr_state); + let its_planar = is_planar(graph, Some(&mut lr_state)); if !its_planar { return Pos2DMapping { @@ -32,8 +31,9 @@ pub fn planar_layout( let mut planar_emb = PlanarEmbedding::new(); planar_emb.embedding = Graph::with_capacity(node_num, 0); - create_embedding(&mut planar_emb, &lr_state); + create_embedding(&mut planar_emb, &mut lr_state); + // DEBUG // for node in planar_emb.embedding.node_indices() { // println!("emb node {:?}", planar_emb.embedding[node]); // println!("emb edges {:?}", planar_emb.embedding.edges(node)); From e0e900e0eeb93c5712f8280eedda79c4962c2333 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Tue, 21 Jun 2022 16:15:49 -0700 Subject: [PATCH 15/37] Finish create_embedding and start on canonical ordering --- retworkx-core/src/planar/lr_planar.rs | 2 +- src/layout/embedding.rs | 363 ++++++++++++++------------ 2 files changed, 204 insertions(+), 161 deletions(-) diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index 54705d3b7..aea2956c9 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -194,7 +194,7 @@ where } } -#[derive(PartialEq)] +#[derive(Clone, Copy, PartialEq)] pub enum Sign { Plus, Minus, diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index f8ef24750..05eafe239 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -9,8 +9,8 @@ use rayon::prelude::*; use std::fmt::Debug; use std::hash::Hash; -use retworkx_core::connectivity::connected_components; use crate::StablePyGraph; +use retworkx_core::connectivity::connected_components; use retworkx_core::planar::lr_planar::{LRState, Sign}; pub type Point = [f64; 2]; @@ -90,16 +90,17 @@ impl PlanarEmbedding { let mut node = self.get_edge_weight(v, start_node, true); if let Some(mut current_node) = node { - //println!("current_node 1 {:?}", current_node); - - while start_node != current_node { + println!("current_node 1 {:?}", current_node); + let mut count = 0; + while start_node != current_node && count < 20 { + count += 1; nbrs.push(current_node); node = self.get_edge_weight(v, current_node, true); current_node = match node { Some(node) => node, None => break, }; - //println!("current_node 2 {:?}", current_node); + println!("current_node 2 {:?}", current_node); } } println!("nbrs 1 in nbrs_cw_order {:?}", nbrs); @@ -182,7 +183,7 @@ impl PlanarEmbedding { // if new_node.is_none() { println!("NEW NODE NONE in next_face {:?} {:?} {:?}", new_node, v, w); - return (w, v); + panic!("HELP!"); //return (w, v); } (w, new_node.unwrap()) } @@ -205,15 +206,16 @@ impl PlanarEmbedding { } fn get_edge_weight(&self, v: NodeIndex, w: NodeIndex, cw: bool) -> Option { - println!("GET EDGE v w {:?} {:?}", v, w); + //println!("GET EDGE v w {:?} {:?}", v, w); let found_edge = self.embedding.find_edge(v, w); // FIX THIS // // if found_edge.is_none() { + println!("GET EDGE find edge is none {:?}", found_edge); return None; } - println!("GET EDGE find edge{:?}", found_edge); + //println!("GET EDGE find edge{:?}", found_edge); let found_weight = self.embedding.edge_weight(found_edge.unwrap()); //.unwrap(); //println!("after found weight {:?}", found_weight); if found_weight.is_none() { @@ -236,71 +238,69 @@ impl PlanarEmbedding { } } -fn id_to_index(graph: G, node_id: G::NodeId) -> NodeIndex { - NodeIndex::new(graph.to_index(node_id)) -} - -fn index_to_id(graph: G, node_index: NodeIndex) -> G::NodeId { - graph.from_index(node_index.index()) -} - pub fn create_embedding( planar_emb: &mut PlanarEmbedding, lr_state: &mut LRState<&StablePyGraph>, -) -// where -// ::NodeId: Hash + Eq, -// ::NodeId: Debug, -// // These are needed for par_sort -// G: std::marker::Sync, -// ::NodeId: Sync, -{ +) { + // ********** DEBUG for node in lr_state.dir_graph.node_indices() { for edge in lr_state.dir_graph.edges(node) { println!("Edge {:?}, {:?}", edge.source(), edge.target()); } } + // ********** DEBUG END let mut ordered_adjs: Vec> = Vec::new(); + let mut nesting_depth: HashMap<(NodeIndex, NodeIndex), i32> = + HashMap::with_capacity(lr_state.nesting_depth.len()); + + // Create the adjacency list for each node for v in lr_state.dir_graph.node_indices() { ordered_adjs.push(lr_state.dir_graph.edges(v).map(|e| e.target()).collect()); + // Add empty FirstNbr to the embedding let first_nbr = FirstNbr::::default(); planar_emb.embedding.add_node(first_nbr); - let mut nesting_depth: HashMap<(NodeIndex, NodeIndex), i32> = - HashMap::with_capacity(lr_state.nesting_depth.len()); + // Change the sign for nesting_depth for e in lr_state.dir_graph.edges(v) { let edge: (NodeIndex, NodeIndex) = (e.source(), e.target()); - //let edge_base = (index_to_id(&lr_state.graph, edge.source()), index_to_id(&lr_state.graph, edge.target())); let signed_depth: i32 = lr_state.nesting_depth[&edge] as i32; - nesting_depth.insert(edge, - sign(edge, &mut lr_state.eref, &mut lr_state.side) * signed_depth); - //lr_state.nesting_depth[&edge]; + let signed_side = if lr_state.side.contains_key(&edge) + && sign(edge, &mut lr_state.eref, &mut lr_state.side) == Sign::Minus + { + -1 + } else { + 1 + }; + nesting_depth.insert(edge, signed_depth * signed_side); } } + // ********** DEBUG for x in &ordered_adjs { println!("ordered {:?}", x); } - for x in &lr_state.nesting_depth { + // ********** DEBUG END + + // ********** DEBUG + for x in &nesting_depth { println!("nesting {:?}", x); } + // ********** DEBUG END - //lr_state.nesting_depth.iter().enumerate().map(|(e, val)| (e, val * sign(e))); - + // Sort the adjacency list using nesting depth as sort order for (v, adjs) in ordered_adjs.iter_mut().enumerate() { - adjs.par_sort_by_key(|x| { - lr_state.nesting_depth[&( - index_to_id(&lr_state.graph, NodeIndex::new(v)), - index_to_id(&lr_state.graph, *x), - )] - }); + adjs.par_sort_by_key(|x| nesting_depth[&(NodeIndex::new(v), *x)]); } + + // ********** DEBUG for x in &ordered_adjs { println!("ordered 2222 {:?}", x); } + // ********** DEBUG END + // Add the initial half edge cw to the embedding using the ordered adjacency list for v in lr_state.dir_graph.node_indices() { let mut prev_node: Option = None; for w in &ordered_adjs[v.index()] { @@ -308,124 +308,83 @@ pub fn create_embedding( prev_node = Some(*w) } } + + // ********** DEBUG for v in planar_emb.embedding.node_indices() { println!("11111 embedding node {:?} {:?}", v, planar_emb.embedding[v]); } + println!("lr eparent {:?}", lr_state.eparent); + // ********** DEBUG END + // Start the DFS traversal for the embedding let mut left_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); let mut right_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); let mut idx: Vec = vec![0; ordered_adjs.len()]; - for v_id in lr_state.roots.iter() { - let v = id_to_index(&lr_state.graph, *v_id); - // println!( - // "second v {:?} v index {:?} ord {:?} idx {:?}", - // v, - // v.index(), - // ordered_adjs[v.index()], - // idx - // ); - - let mut dfs_stack: Vec = vec![v]; + for v in lr_state.roots.iter() { + // Create the stack with an initial entry of v + let mut dfs_stack: Vec = vec![*v]; - // println!("lr eparent {:?}", lr_state.eparent); while dfs_stack.len() > 0 { let v = dfs_stack.pop().unwrap(); let idx2 = idx[v.index()]; - for (w_pos, w) in ordered_adjs[v.index()][idx2..].iter().enumerate() { - let w_id = index_to_id(&lr_state.graph, *w); - // println!( - // "third v {:?} vindex {:?} w {:?} w_id {:?} w_pos {:?} idx {:?} ", - // v, - // v.index(), - // *w, - // w_id, - // w_pos, - // idx - // ); + + // Iterate over the ordered_adjs starting at the saved index until the end + for w in ordered_adjs[v.index()][idx2..].iter() { idx[v.index()] += 1; - let ei = (v, w); - let ei_id = ( - index_to_id(&lr_state.graph, v), - index_to_id(&lr_state.graph, *w), - ); - if lr_state.eparent.contains_key(&w_id) { - let parent_id = lr_state.eparent[&w_id]; - let (v1, v2) = ( - id_to_index(&lr_state.graph, parent_id.0), - id_to_index(&lr_state.graph, parent_id.1), - ); - - if ei == (v1, &v2) { - // println!("in ei {:?}", ei); - planar_emb.add_half_edge_first(*w, v); - left_ref.entry(v).or_insert(*w); - right_ref.entry(v).or_insert(*w); - dfs_stack.push(v); - dfs_stack.push(*w); - break; + let ei = (v, *w); + if lr_state.eparent.contains_key(&w) && ei == lr_state.eparent[&w] { + println!("in ei {:?}", ei); + planar_emb.add_half_edge_first(*w, v); + left_ref.entry(v).or_insert(*w); + right_ref.entry(v).or_insert(*w); + dfs_stack.push(v); + dfs_stack.push(*w); + break; + } else { + if !lr_state.side.contains_key(&ei) || lr_state.side[&ei] == Sign::Plus { + planar_emb.add_half_edge_cw(*w, v, Some(right_ref[w])); } else { - if lr_state.side[&ei_id] == Sign::Plus { - planar_emb.add_half_edge_cw(*w, v, Some(right_ref[w])); - } else { - planar_emb.add_half_edge_ccw(*w, v, Some(left_ref[w])); - left_ref.entry(*w).or_insert(v); - } - // println!("in else {:?}", ei); + planar_emb.add_half_edge_ccw(*w, v, Some(left_ref[w])); + left_ref.entry(*w).or_insert(v); } + println!("in else {:?}", ei); } } } } + // ********** DEBUG for v in planar_emb.embedding.node_indices() { - println!("22222 embedding node {:?} {:?}", v, planar_emb.embedding[v]); + for w in &ordered_adjs[v.index()] { + println!( + "22222 embedding node {:?} {:?} {:?} {:?}", + v, + planar_emb.embedding[v], + planar_emb.get_edge_weight(v, *w, true), + planar_emb.get_edge_weight(v, *w, false) + ); + } } + // ********** DEBUG END + pub fn sign( edge: (NodeIndex, NodeIndex), eref: &mut HashMap<(NodeIndex, NodeIndex), (NodeIndex, NodeIndex)>, side: &mut HashMap<(NodeIndex, NodeIndex), Sign>, - ) -> i32 { - let mut dfs_stack: Vec<(NodeIndex, NodeIndex)> = vec![edge]; - let mut old_ref: HashMap<(NodeIndex, NodeIndex), (NodeIndex, NodeIndex)> = - HashMap::with_capacity(eref.len()); - - // """Resolve the relative side of an edge to the absolute side.""" - - let mut e: (NodeIndex, NodeIndex) = edge; - let mut side_final: i32 = 1; - // for e in side { - // println!("side {} {}", e.0.0.index(), e.0.1.index()); - // } - //return side_final; - while dfs_stack.len() > 0 { - e = dfs_stack.pop().unwrap(); + ) -> Sign { + // Resolve the relative side of an edge to the absolute side. - if eref.contains_key(&e) { - dfs_stack.push(e); - dfs_stack.push(eref[&e]); - *old_ref.get_mut(&e).unwrap() = eref[&e]; - eref.remove(&e); + if eref.contains_key(&edge) { + if side[&edge].clone() == sign(eref[&edge].clone(), eref, side) { + *side.get_mut(&edge).unwrap() = Sign::Plus; } else { - if side.contains_key(&e) { - if side[&e] == side[&old_ref[&e]] { - *side.get_mut(&e).unwrap() = Sign::Plus; - } else { - println!("side no find plus {} {}", e.0.index(), e.1.index()); - } - side_final = 1; - } else { - if side.contains_key(&e) { - *side.get_mut(&e).unwrap() = Sign::Minus; - } else { - println!("side no find minus {} {}", e.0.index(), e.1.index()); - } - side_final = -1; - } + *side.get_mut(&edge).unwrap() = Sign::Minus; } + eref.remove(&edge); } - side_final + side[&edge] } } @@ -439,7 +398,7 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { .map(|n| default_pos[n.index()]) .collect(); } - let outer_face = triangulate_embedding(planar_emb); + let outer_face = triangulate_embedding(planar_emb, true); let right_t_child = HashMap::::new(); let left_t_child = HashMap::::new(); @@ -451,7 +410,10 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { pos } -fn triangulate_embedding(planar_emb: &mut PlanarEmbedding) -> Vec { +fn triangulate_embedding( + planar_emb: &mut PlanarEmbedding, + fully_triangulate: bool, +) -> Vec { let component_sets = connected_components(&planar_emb.embedding); println!("CONN COMP {:?}", component_sets); @@ -459,58 +421,42 @@ fn triangulate_embedding(planar_emb: &mut PlanarEmbedding) -> Vec { let v1 = component_sets[i].iter().next().unwrap(); let v2 = component_sets[i + 1].iter().next().unwrap(); planar_emb.connect_components(*v1, *v2); + println!("v1 v2 {:?} {:?}", *v1, *v2); } let mut outer_face = vec![]; let mut face_list = vec![]; let mut edges_counted: HashSet<(NodeIndex, NodeIndex)> = HashSet::new(); println!(" in triangulate component sets{:?}", component_sets); - return outer_face; for v in planar_emb.embedding.node_indices() { for w in planar_emb.neighbors_cw_order(v) { let new_face = make_bi_connected(planar_emb, v, w, &mut edges_counted); - let new_len = new_face.len(); - if new_len > 0 { + if new_face.len() > 0 { face_list.push(new_face.clone()); - if new_len > outer_face.len() { + if new_face.len() > outer_face.len() { outer_face = new_face; } } } } - return outer_face; println!("FINAL Face list {:?}", face_list); for face in face_list { println!("face {:?} outer_face {:?}", face, outer_face); - if face != outer_face { + if face != outer_face || fully_triangulate { triangulate_face(planar_emb, face[0], face[1]); } } + if fully_triangulate { + let v1 = outer_face[0]; + let v2 = outer_face[1]; + let v3 = planar_emb.get_edge_weight(v2, v1, false); + outer_face = vec![v1, v2, v3.unwrap()]; + } println!("outer_face {:?}", outer_face); outer_face } -fn triangulate_face(planar_emb: &mut PlanarEmbedding, mut v1: NodeIndex, mut v2: NodeIndex) { - let (_, mut v3) = planar_emb.next_face_half_edge(v1, v2); - let (_, mut v4) = planar_emb.next_face_half_edge(v2, v3); - if v1 == v2 || v1 == v3 { - return; - } - let mut count = 0; - while v1 != v4 && count < 20 { - if planar_emb.embedding.contains_edge(v1, v3) { - (v1, v2, v3) = (v2, v3, v4); - } else { - planar_emb.add_half_edge_cw(v1, v3, Some(v2)); - planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); - (v1, v2, v3) = (v1, v3, v4); - } - (_, v4) = planar_emb.next_face_half_edge(v2, v3); - count += 1; - } -} - fn make_bi_connected( planar_emb: &mut PlanarEmbedding, start_node: NodeIndex, @@ -518,6 +464,10 @@ fn make_bi_connected( edges_counted: &mut HashSet<(NodeIndex, NodeIndex)>, ) -> Vec { if edges_counted.contains(&(start_node, out_node)) || start_node == out_node { + println!( + "biconnect already in start out {:?} {:?}", + start_node, out_node + ); return vec![]; } edges_counted.insert((start_node, out_node)); @@ -528,25 +478,34 @@ fn make_bi_connected( let (_, mut v3) = planar_emb.next_face_half_edge(v1, v2); let mut count = 0; - while (v2 != start_node || v3 != out_node) && v2 != v3 && count < 20 { + while (v2 != start_node || v3 != out_node) && count < 20 { // && count < 300 { - //println!("face_list in while {:?} {:?} {:?}", v2, v3, face_list); + if v1 == v2 { + println!("BICONNECT V1==V2 should raise"); + } + println!("face_list in while {:?} {:?} {:?}", v2, v3, face_list); if face_list.contains(&v2) { - //println!("face_list contains v2 {:?} {:?} {:?}", v2, v3, edges_counted); + println!( + "face_list contains v2 {:?} {:?} {:?}", + v2, v3, edges_counted + ); planar_emb.add_half_edge_cw(v1, v3, Some(v2)); planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); edges_counted.insert((v2, v3)); edges_counted.insert((v3, v1)); v2 = v1.clone(); } else { - //println!("face_list not contain v2 {:?} {:?} {:?}", v2, v3, edges_counted); + println!( + "face_list not contain v2 {:?} {:?} {:?}", + v2, v3, edges_counted + ); face_list.push(v2); } v1 = v2.clone(); - //println!("2 edges counted {:?} {:?} {:?}", v2, v3, edges_counted); + println!("2 edges counted {:?} {:?} {:?}", v2, v3, edges_counted); (v2, v3) = planar_emb.next_face_half_edge(v2, v3); - //println!("3 edges counted {:?} {:?} {:?}", v2, v3, edges_counted); + println!("3 edges counted {:?} {:?} {:?}", v2, v3, edges_counted); edges_counted.insert((v1, v2)); count += 1; @@ -554,10 +513,94 @@ fn make_bi_connected( face_list } +fn triangulate_face(planar_emb: &mut PlanarEmbedding, mut v1: NodeIndex, mut v2: NodeIndex) { + let (_, mut v3) = planar_emb.next_face_half_edge(v1, v2); + let (_, mut v4) = planar_emb.next_face_half_edge(v2, v3); + if v1 == v2 || v1 == v3 { + return; + } + let mut count = 0; + while v1 != v4 && count < 20 { + if planar_emb.embedding.contains_edge(v1, v3) { + (v1, v2, v3) = (v2, v3, v4); + } else { + planar_emb.add_half_edge_cw(v1, v3, Some(v2)); + planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); + (v1, v2, v3) = (v1, v3, v4); + } + (_, v4) = planar_emb.next_face_half_edge(v2, v3); + count += 1; + } +} + fn canonical_ordering( planar_emb: &mut PlanarEmbedding, outer_face: Vec, ) -> Vec<(NodeIndex, Vec)> { + let v1 = outer_face[0]; + let v2 = outer_face[1]; + let mut chords: HashMap = + HashMap::with_capacity(planar_emb.embedding.node_count()); + let mut marked_nodes: HashSet = + HashSet::with_capacity(planar_emb.embedding.node_count()); + let mut ready_to_pick: HashSet = HashSet::with_capacity(outer_face.len()); + + let mut outer_face_cw_nbr: HashMap = + HashMap::with_capacity(outer_face.len()); + let mut outer_face_ccw_nbr: HashMap = + HashMap::with_capacity(outer_face.len()); + let mut prev_nbr = v2.clone(); + + for v in outer_face.iter().rev() { + outer_face_ccw_nbr.entry(prev_nbr).or_insert(*v); + prev_nbr = *v; + } + outer_face_ccw_nbr.entry(prev_nbr).or_insert(v1); + + for v in outer_face.iter().rev() { + outer_face_cw_nbr.entry(prev_nbr).or_insert(*v); + prev_nbr = *v; + } + + fn is_outer_face_nbr( + x: NodeIndex, + y: NodeIndex, + outer_face_cw_nbr: &HashMap, + outer_face_ccw_nbr: &HashMap, + ) -> bool { + if !outer_face_ccw_nbr.contains_key(&x) { + return outer_face_cw_nbr[&x] == y; + } + if !outer_face_ccw_nbr.contains_key(&x) { + return outer_face_cw_nbr[&x] == y; + } + outer_face_cw_nbr[&x] == y || outer_face_ccw_nbr[&x] == y + } + + fn is_on_outer_face( + x: NodeIndex, + v1: NodeIndex, + marked_nodes: &HashSet, + outer_face_ccw_nbr: &HashMap, + ) -> bool { + !marked_nodes.contains(&x) && (outer_face_ccw_nbr.contains_key(&x) || x == v1) + } + + for v in outer_face { + for nbr in planar_emb.neighbors_cw_order(v) { + if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) + && is_outer_face_nbr(v, nbr, &outer_face_cw_nbr, &outer_face_ccw_nbr) + { + if chords.contains_key(&v) { + let chords_plus = chords[&v].clone(); + chords.entry(v).or_insert(chords_plus + 1); + } else { + chords.entry(v).or_insert(1); + } + ready_to_pick.remove(&v); + } + } + } vec![( NodeIndex::new(0), vec![NodeIndex::new(1), NodeIndex::new(2)], From cc32bccdc9517aa2dfdccdb8071466c8c8d0ccf3 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Thu, 23 Jun 2022 15:23:41 -0700 Subject: [PATCH 16/37] Achieved first plot --- src/layout/embedding.rs | 225 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 206 insertions(+), 19 deletions(-) diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index 05eafe239..a37555c51 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -1,13 +1,11 @@ use hashbrown::hash_map::HashMap; use hashbrown::HashSet; -use petgraph::graph::Edge; use petgraph::graph::Graph; use petgraph::prelude::*; -use petgraph::visit::{GraphBase, NodeIndexable}; +use petgraph::visit::NodeIndexable; use petgraph::Directed; use rayon::prelude::*; use std::fmt::Debug; -use std::hash::Hash; use crate::StablePyGraph; use retworkx_core::connectivity::connected_components; @@ -390,6 +388,9 @@ pub fn create_embedding( pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { let mut pos: Vec = Vec::with_capacity(planar_emb.embedding.node_count()); + for p in 0..planar_emb.embedding.node_count() { + pos.push([0.0, 0.0]); + } if planar_emb.embedding.node_count() < 4 { let default_pos = [[0.0, 0.0], [2.0, 0.0], [1.0, 1.0]].to_vec(); return planar_emb @@ -398,15 +399,106 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { .map(|n| default_pos[n.index()]) .collect(); } - let outer_face = triangulate_embedding(planar_emb, true); + let outer_face = triangulate_embedding(planar_emb, false); - let right_t_child = HashMap::::new(); - let left_t_child = HashMap::::new(); - let delta_x = HashMap::::new(); - let y_coord = HashMap::::new(); + let mut right_t_child: HashMap, Option> = HashMap::new(); + let mut left_t_child = HashMap::, Option>::new(); + let mut delta_x = HashMap::, usize>::new(); + let mut y_coord = HashMap::, usize>::new(); let node_list = canonical_ordering(planar_emb, outer_face); + let v1 = node_list[0].0; + let v2 = node_list[1].0; + let v3 = node_list[2].0; + + println!("NODE LIST v1 {:?} {:?}", v1, node_list); + + delta_x.entry(v1).or_insert(0); + y_coord.entry(v1).or_insert(0); + right_t_child.entry(v1).or_insert(v3); + left_t_child.entry(v1).or_insert(None); + + delta_x.entry(v2).or_insert(1); + y_coord.entry(v2).or_insert(0); + right_t_child.entry(v2).or_insert(None); + left_t_child.entry(v2).or_insert(None); + + delta_x.entry(v3).or_insert(1); + y_coord.entry(v3).or_insert(1); + right_t_child.entry(v3).or_insert(v2); + left_t_child.entry(v3).or_insert(None); + + for k in 3..node_list.len() { + let vk = node_list[k].0; + if vk.is_none() { + continue + } + let contour_nbrs = &node_list[k].1; + + let wp = contour_nbrs[0]; + let wp1 = contour_nbrs[1]; + let wq = contour_nbrs[contour_nbrs.len() - 1]; + let wq1 = contour_nbrs[contour_nbrs.len() - 2]; + + let adds_mult_tri = contour_nbrs.len() > 2; + + let delta_wp1_plus = delta_x[&wp1] + 1; + delta_x.entry(wp1).or_insert(delta_wp1_plus); + let delta_wq_plus = delta_x[&wq] + 1; + delta_x.entry(wq).or_insert(delta_wq_plus); + + let delta_x_wp_wq = contour_nbrs.iter().map(|x| delta_x[x]).sum::(); + + println!("delta_x_wp_wq {:?}", delta_x_wp_wq); + + let y_wp = y_coord[&wp].clone(); + let y_wq = y_coord[&wp].clone(); + delta_x.entry(vk).or_insert(delta_x_wp_wq - y_wp + y_wq); //y_coord[&wp] + y_coord[&wq]); + y_coord.entry(vk).or_insert(delta_x_wp_wq + y_wp + y_wq); //y_coord.cloned()[&wp] + y_coord.cloned()[&wq]); + let d_vk = delta_x[&vk].clone(); + delta_x.entry(wq).or_insert(delta_x_wp_wq - d_vk); + + if adds_mult_tri { + let delta_wp1_minus = delta_x[&wp1] - delta_x[&vk]; + delta_x.entry(wp1).or_insert(delta_wp1_minus); + } + + right_t_child.entry(wp).or_insert(vk); + right_t_child.entry(vk).or_insert(wq); + if adds_mult_tri { + left_t_child.entry(vk).or_insert(wp1); + right_t_child.entry(wq1).or_insert(None); + } else { + left_t_child.entry(vk).or_insert(None); + } + } + + pub fn set_position( + parent: Option, + tree: &HashMap::, Option>, + remaining_nodes: &mut Vec>, + delta_x: &HashMap::, usize>, + y_coord: &HashMap::, usize>, + pos: &mut Vec + ) { + let child = tree[&parent]; + let parent_node_x = pos[parent.unwrap().index()][0]; + + if child.is_some() { + let child_x = parent_node_x + (delta_x[&child] as f64); + pos[child.unwrap().index()] = [child_x, (y_coord[&child] as f64)]; + remaining_nodes.push(child); + } + } + + pos[v1.unwrap().index()] = [0 as f64, y_coord[&v1] as f64]; + let mut remaining_nodes = vec![v1]; + while remaining_nodes.len() > 0 { + let parent_node = remaining_nodes.pop().unwrap(); + set_position(parent_node, &left_t_child, &mut remaining_nodes, &delta_x, &y_coord, &mut pos); + set_position(parent_node, &right_t_child, &mut remaining_nodes, &delta_x, &y_coord, &mut pos); + } pos } @@ -536,14 +628,16 @@ fn triangulate_face(planar_emb: &mut PlanarEmbedding, mut v1: NodeIndex, mut v2: fn canonical_ordering( planar_emb: &mut PlanarEmbedding, outer_face: Vec, -) -> Vec<(NodeIndex, Vec)> { +) -> Vec<(Option, Vec>)> { let v1 = outer_face[0]; let v2 = outer_face[1]; - let mut chords: HashMap = - HashMap::with_capacity(planar_emb.embedding.node_count()); - let mut marked_nodes: HashSet = - HashSet::with_capacity(planar_emb.embedding.node_count()); - let mut ready_to_pick: HashSet = HashSet::with_capacity(outer_face.len()); + let mut chords: HashMap = HashMap::new(); + let mut marked_nodes: HashSet = HashSet::new(); + let mut ready_to_pick: HashSet = HashSet::new(); + + for node in outer_face.iter() { + ready_to_pick.insert(*node); + } let mut outer_face_cw_nbr: HashMap = HashMap::with_capacity(outer_face.len()); @@ -589,7 +683,7 @@ fn canonical_ordering( for v in outer_face { for nbr in planar_emb.neighbors_cw_order(v) { if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) - && is_outer_face_nbr(v, nbr, &outer_face_cw_nbr, &outer_face_ccw_nbr) + && !is_outer_face_nbr(v, nbr, &outer_face_cw_nbr, &outer_face_ccw_nbr) { if chords.contains_key(&v) { let chords_plus = chords[&v].clone(); @@ -600,9 +694,102 @@ fn canonical_ordering( ready_to_pick.remove(&v); } } + println!("READY 1st {:?} {:?}", v, ready_to_pick); + println!("Chords 1st {:?}", chords); + } + + let mut canon_order: Vec<(Option, Vec>)> = + vec![(None, vec![]); planar_emb.embedding.node_count()]; + + canon_order[0] = (Some(v1), vec![]); + canon_order[1] = (Some(v2), vec![]); + println!("READY before {:?}", ready_to_pick); + println!("Marked before {:?}", marked_nodes); + + ready_to_pick.remove(&v1); + ready_to_pick.remove(&v2); + + println!("READY after {:?}", ready_to_pick); + for k in (1..planar_emb.embedding.node_count()).rev() { + let v_try = ready_to_pick.iter().next(); + if v_try.is_none() { + continue; + } + let v = v_try.unwrap().clone(); + ready_to_pick.remove(&v); + marked_nodes.insert(v); + + let mut wp: Option = None; + let mut wq: Option = None; + + for nbr in planar_emb.neighbors_cw_order(v) { + if marked_nodes.contains(&nbr) { + continue; + } + if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) { + if nbr == v1 { + wp = Some(v1); + } else if nbr == v2 { + wq = Some(v2); + } else { + if outer_face_cw_nbr[&nbr] == v1 { + wp = Some(nbr); + } else { + wq = Some(nbr); + } + } + } + if !wp.is_none() && !wq.is_none() { + break; + } + } + + let mut wp_wq = vec![wp]; + let mut nbr = wp.unwrap(); + while Some(nbr) != wp { + let next_nbr = planar_emb.get_edge_weight(v, nbr, false).unwrap(); + wp_wq.push(Some(next_nbr)); + outer_face_cw_nbr.entry(nbr).or_insert(next_nbr); + outer_face_ccw_nbr.entry(next_nbr).or_insert(nbr); + nbr = next_nbr; + } + if wp_wq.len() == 2 { + let wp_un = wp.unwrap(); + let chords_wp = chords[&wp_un].clone() - 1; + chords.entry(wp_un).or_insert(chords_wp); + if chords[&wp_un] == 0 { + ready_to_pick.insert(wp_un); + } + let wq_un = wq.unwrap(); + let chords_wq = chords[&wq_un].clone() - 1; + chords.entry(wq_un).or_insert(chords_wq); + if chords[&wq_un] == 0 { + ready_to_pick.insert(wq_un); + } + } else { + let mut new_face_nodes: HashSet = HashSet::new(); + if wp_wq.len() > 1 { + for w in &wp_wq[1..(wp_wq.len() - 1)] { + let w_un = w.unwrap(); + new_face_nodes.insert(w_un); + for nbr in planar_emb.neighbors_cw_order(w_un) { + if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) + && !is_outer_face_nbr(w_un, nbr, &outer_face_cw_nbr, &outer_face_ccw_nbr) + { + let chords_w = chords[&w_un].clone() + 1; + chords.entry(w_un).or_insert(chords_w); + ready_to_pick.remove(&w_un); + if !new_face_nodes.contains(&nbr) { + let chords_nbr = chords[&nbr].clone() + 1; + chords.entry(nbr).or_insert(chords_nbr); + ready_to_pick.remove(&nbr); + } + } + } + } + } + } + canon_order[k] = (Some(v), wp_wq); } - vec![( - NodeIndex::new(0), - vec![NodeIndex::new(1), NodeIndex::new(2)], - )] + canon_order } From df23d908ebcad4dd075c2522b7e7bf1d041d7d4e Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 15 Jul 2022 11:15:56 -0700 Subject: [PATCH 17/37] Working on embedding to pos --- retworkx-core/src/planar/lr_planar.rs | 6 +- src/layout/embedding.rs | 399 ++++++++++++++++++-------- src/layout/planar.rs | 11 + 3 files changed, 289 insertions(+), 127 deletions(-) diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index aea2956c9..41bc53514 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -230,7 +230,7 @@ where /// next back edge in traversal with lowest return point. lowpt_edge: HashMap, Edge>, /// proxy for nesting order ≺ given by twice lowpt (plus 1 if chordal). - pub nesting_depth: HashMap, usize>, + pub nesting_depth: HashMap, i64>, /// stack for conflict pairs. stack: Vec>>, /// marks the top conflict pair when an edge was pushed in the stack. @@ -338,9 +338,9 @@ where if self.lowpt_2[&ei] < self.height[&v] { // if it's chordal, add one. - self.nesting_depth.insert(ei, 2 * low + 1); + self.nesting_depth.insert(ei, (2 * low) as i64 + 1); } else { - self.nesting_depth.insert(ei, 2 * low); + self.nesting_depth.insert(ei, (2 * low) as i64); } // update lowpoints of parent edge. diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index a37555c51..af329e19a 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -39,7 +39,7 @@ impl CwCcw { #[derive(Debug)] pub struct FirstNbr { - first_nbr: Option, + pub first_nbr: Option, } impl Default for FirstNbr { @@ -74,10 +74,11 @@ impl PlanarEmbedding { } } - fn neighbors_cw_order(&mut self, v: NodeIndex) -> Vec { + pub fn neighbors_cw_order(&mut self, v: NodeIndex) -> Vec { let mut nbrs: Vec = vec![]; let first_nbr = self.embedding[v].first_nbr; - println!("first_nbr in nbrs_cw_order {:?}", first_nbr); + println!("first_nbr in nbrs_cw_order v {:?} f nbr {:?}", v, first_nbr); + if first_nbr.is_none() { // v has no neighbors return nbrs; @@ -87,18 +88,21 @@ impl PlanarEmbedding { nbrs.push(start_node); let mut node = self.get_edge_weight(v, start_node, true); + println!("node {:?} nbrs {:?}", node, nbrs); + if let Some(mut current_node) = node { - println!("current_node 1 {:?}", current_node); + println!("current_node 1 start {:?} current {:?}", start_node, current_node); let mut count = 0; while start_node != current_node && count < 20 { count += 1; + println!("begin while - count {:?}", count); nbrs.push(current_node); node = self.get_edge_weight(v, current_node, true); current_node = match node { Some(node) => node, None => break, }; - println!("current_node 2 {:?}", current_node); + println!("current_node 2 {:?} {:?}", current_node, nbrs); } } println!("nbrs 1 in nbrs_cw_order {:?}", nbrs); @@ -112,11 +116,16 @@ impl PlanarEmbedding { end_node: NodeIndex, ref_nbr: Option, ) { + println!("IN HALF CW"); let cw_weight = CwCcw::::default(); self.embedding.add_edge(start_node, end_node, cw_weight); if ref_nbr.is_none() { + println!("ref_nbr None {:?} {:?} {:?}", start_node, end_node, ref_nbr); + // The start node has no neighbors + println!("first update {:?} {:?}", start_node, end_node); self.update_edge_weight(start_node, end_node, end_node, true); + println!("second update"); self.update_edge_weight(start_node, end_node, end_node, false); self.embedding[start_node].first_nbr = Some(end_node); return; @@ -128,7 +137,11 @@ impl PlanarEmbedding { println!("NO REF NBR in ADD CW {:?} {:?}", start_node, ref_nbr_node); } let cw_ref = self.get_edge_weight(start_node, ref_nbr_node, true); + println!("HALF CW before Some(cw_ref_node) start, end, ref, cw_ref {:?} {:?} {:?} {:?}", + start_node, end_node, ref_nbr, cw_ref); + if let Some(cw_ref_node) = cw_ref { + println!("HALF CW Some(cw_ref_node) {:?} {:?}", cw_ref, cw_ref_node); // Alter half-edge data structures self.update_edge_weight(start_node, ref_nbr_node, end_node, true); self.update_edge_weight(start_node, end_node, cw_ref_node, true); @@ -143,6 +156,7 @@ impl PlanarEmbedding { end_node: NodeIndex, ref_nbr: Option, ) { + println!("IN HALF CCW"); if ref_nbr.is_none() { // Start node has no neighbors let cw_weight = CwCcw::::default(); @@ -169,13 +183,14 @@ impl PlanarEmbedding { } else { None }; + println!("in half first: start_node, end_node, ref_node {:?} {:?} {:?}", start_node, end_node, ref_node); self.add_half_edge_ccw(start_node, end_node, ref_node); } fn next_face_half_edge(&self, v: NodeIndex, w: NodeIndex) -> (NodeIndex, NodeIndex) { //println!("next v {:?} w {:?}", v, w); let new_node = self.get_edge_weight(w, v, false); - //println!("new node {:?}", new_node); + println!("new node {:?}", new_node); // FIX THIS // // @@ -204,23 +219,17 @@ impl PlanarEmbedding { } fn get_edge_weight(&self, v: NodeIndex, w: NodeIndex, cw: bool) -> Option { - //println!("GET EDGE v w {:?} {:?}", v, w); let found_edge = self.embedding.find_edge(v, w); - // FIX THIS - // - // if found_edge.is_none() { println!("GET EDGE find edge is none {:?}", found_edge); return None; } - //println!("GET EDGE find edge{:?}", found_edge); + println!("GET EDGE find edge{:?}", found_edge); let found_weight = self.embedding.edge_weight(found_edge.unwrap()); //.unwrap(); //println!("after found weight {:?}", found_weight); if found_weight.is_none() { println!("GET EDGE Weight is none {:?}", found_weight); return None; - // } else { - // let found_weight = found_weight.unwrap(); } if cw { @@ -250,8 +259,8 @@ pub fn create_embedding( let mut ordered_adjs: Vec> = Vec::new(); - let mut nesting_depth: HashMap<(NodeIndex, NodeIndex), i32> = - HashMap::with_capacity(lr_state.nesting_depth.len()); + //let mut nesting_depth: HashMap<(NodeIndex, NodeIndex), i64> = + // HashMap::with_capacity(lr_state.nesting_depth.len()); // Create the adjacency list for each node for v in lr_state.dir_graph.node_indices() { @@ -260,11 +269,18 @@ pub fn create_embedding( // Add empty FirstNbr to the embedding let first_nbr = FirstNbr::::default(); planar_emb.embedding.add_node(first_nbr); + } + // Sort the adjacency list using nesting depth as sort order + for (v, adjs) in ordered_adjs.iter_mut().enumerate() { + adjs.par_sort_by_key(|x| lr_state.nesting_depth[&(NodeIndex::new(v), *x)]); + } + + for v in lr_state.dir_graph.node_indices() { // Change the sign for nesting_depth for e in lr_state.dir_graph.edges(v) { let edge: (NodeIndex, NodeIndex) = (e.source(), e.target()); - let signed_depth: i32 = lr_state.nesting_depth[&edge] as i32; + let signed_depth: i64 = lr_state.nesting_depth[&edge] as i64; let signed_side = if lr_state.side.contains_key(&edge) && sign(edge, &mut lr_state.eref, &mut lr_state.side) == Sign::Minus { @@ -272,7 +288,7 @@ pub fn create_embedding( } else { 1 }; - nesting_depth.insert(edge, signed_depth * signed_side); + lr_state.nesting_depth.insert(edge, signed_depth * signed_side); } } // ********** DEBUG @@ -282,14 +298,14 @@ pub fn create_embedding( // ********** DEBUG END // ********** DEBUG - for x in &nesting_depth { + for x in &lr_state.nesting_depth { println!("nesting {:?}", x); } // ********** DEBUG END // Sort the adjacency list using nesting depth as sort order for (v, adjs) in ordered_adjs.iter_mut().enumerate() { - adjs.par_sort_by_key(|x| nesting_depth[&(NodeIndex::new(v), *x)]); + adjs.par_sort_by_key(|x| lr_state.nesting_depth[&(NodeIndex::new(v), *x)]); } // ********** DEBUG @@ -307,13 +323,28 @@ pub fn create_embedding( } } + // ********** DEBUG // ********** DEBUG for v in planar_emb.embedding.node_indices() { - println!("11111 embedding node {:?} {:?}", v, planar_emb.embedding[v]); + for w in &ordered_adjs[v.index()] { + println!( + "22222 embedding node v {:?} first_nbr {:?} cw(v,w) {:?} ccw(v,w) {:?} cw(w,v) {:?} ccw(w,v) {:?}", + v, + planar_emb.embedding[v], + planar_emb.get_edge_weight(v, *w, true), + planar_emb.get_edge_weight(v, *w, false), + planar_emb.get_edge_weight(*w, v, true), + planar_emb.get_edge_weight(*w, v, false), + ); + } } + // ********** DEBUG END + println!("lr eparent {:?}", lr_state.eparent); // ********** DEBUG END + println!("adjs len {:?} emb count {:?}", ordered_adjs.len(), planar_emb.embedding.node_count()); + // Start the DFS traversal for the embedding let mut left_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); let mut right_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); @@ -324,28 +355,38 @@ pub fn create_embedding( let mut dfs_stack: Vec = vec![*v]; while dfs_stack.len() > 0 { + println!("top of while {:?}", dfs_stack); let v = dfs_stack.pop().unwrap(); let idx2 = idx[v.index()]; + println!("top of while 2 v {:?} idx2 {:?} stack {:?}", v, idx2, dfs_stack); // Iterate over the ordered_adjs starting at the saved index until the end for w in ordered_adjs[v.index()][idx2..].iter() { idx[v.index()] += 1; + println!("In dfs v idx[v] w {:?} {:?} {:?}", v, idx[v.index()], *w); let ei = (v, *w); + println!("lr parent {:?}", lr_state.eparent.contains_key(&w)); + if lr_state.eparent.contains_key(&w) { + println!("parent w {:?} {:?}", lr_state.eparent[&w], ei); + } if lr_state.eparent.contains_key(&w) && ei == lr_state.eparent[&w] { println!("in ei {:?}", ei); planar_emb.add_half_edge_first(*w, v); - left_ref.entry(v).or_insert(*w); - right_ref.entry(v).or_insert(*w); + left_ref.insert(v, *w); + right_ref.insert(v, *w); dfs_stack.push(v); dfs_stack.push(*w); + println!("before break dfs_stack {:?}", dfs_stack); break; } else { if !lr_state.side.contains_key(&ei) || lr_state.side[&ei] == Sign::Plus { + println!("plus cw {:?}", ei); planar_emb.add_half_edge_cw(*w, v, Some(right_ref[w])); } else { + println!("minus cw {:?}", ei); planar_emb.add_half_edge_ccw(*w, v, Some(left_ref[w])); - left_ref.entry(*w).or_insert(v); + left_ref.insert(*w, v); } println!("in else {:?}", ei); } @@ -357,17 +398,20 @@ pub fn create_embedding( for v in planar_emb.embedding.node_indices() { for w in &ordered_adjs[v.index()] { println!( - "22222 embedding node {:?} {:?} {:?} {:?}", + "33333 embedding node v {:?} w {:?} first_nbr {:?} cw(v,w) {:?} ccw(v,w) {:?} cw(w,v) {:?} ccw(w,v) {:?}", v, + w, planar_emb.embedding[v], planar_emb.get_edge_weight(v, *w, true), - planar_emb.get_edge_weight(v, *w, false) + planar_emb.get_edge_weight(v, *w, false), + planar_emb.get_edge_weight(*w, v, true), + planar_emb.get_edge_weight(*w, v, false), ); } } // ********** DEBUG END - pub fn sign( + fn sign( edge: (NodeIndex, NodeIndex), eref: &mut HashMap<(NodeIndex, NodeIndex), (NodeIndex, NodeIndex)>, side: &mut HashMap<(NodeIndex, NodeIndex), Sign>, @@ -400,39 +444,63 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { .collect(); } let outer_face = triangulate_embedding(planar_emb, false); + let mut outer2 = outer_face.clone(); + + println!("\n\nAFTER TRI\n"); + for v in outer_face.clone() { + println!("\nNode {:?}", v); + println!("First_nbr {:?}", planar_emb.embedding[v].first_nbr); + + for nbr in planar_emb.neighbors_cw_order(v) { + println!("Nbr {:?}", nbr); + } + } + println!("DONE\n"); - let mut right_t_child: HashMap, Option> = HashMap::new(); + let mut right_t_child = HashMap::, Option>::new(); let mut left_t_child = HashMap::, Option>::new(); - let mut delta_x = HashMap::, usize>::new(); - let mut y_coord = HashMap::, usize>::new(); + let mut delta_x = HashMap::, i64>::new(); + let mut y_coord = HashMap::, i64>::new(); let node_list = canonical_ordering(planar_emb, outer_face); + println!("\n\nAFTER CANON\n"); + for v in outer2 { + println!("\nNode {:?}", v); + println!("First_nbr {:?}", planar_emb.embedding[v].first_nbr); + + for nbr in planar_emb.neighbors_cw_order(v) { + println!("Nbr {:?}", nbr); + } + } + println!("DONE\n"); + let v1 = node_list[0].0; let v2 = node_list[1].0; let v3 = node_list[2].0; - println!("NODE LIST v1 {:?} {:?}", v1, node_list); + println!("NODE LIST v1 {:?} v2 {:?} v3 {:?} node_list {:?}", v1, v2, v3, node_list); - delta_x.entry(v1).or_insert(0); - y_coord.entry(v1).or_insert(0); - right_t_child.entry(v1).or_insert(v3); - left_t_child.entry(v1).or_insert(None); + delta_x.insert(v1, 0); + y_coord.insert(v1, 0); + right_t_child.insert(v1, v3); + left_t_child.insert(v1, None); - delta_x.entry(v2).or_insert(1); - y_coord.entry(v2).or_insert(0); - right_t_child.entry(v2).or_insert(None); - left_t_child.entry(v2).or_insert(None); + delta_x.insert(v2, 1); + y_coord.insert(v2, 0); + right_t_child.insert(v2, None); + left_t_child.insert(v2, None); - delta_x.entry(v3).or_insert(1); - y_coord.entry(v3).or_insert(1); - right_t_child.entry(v3).or_insert(v2); - left_t_child.entry(v3).or_insert(None); + delta_x.insert(v3, 1); + y_coord.insert(v3, 1); + right_t_child.insert(v3, v2); + left_t_child.insert(v3, None); + println!("delta {:?} y {:?} right {:?} left {:?}", delta_x, y_coord, right_t_child, left_t_child); for k in 3..node_list.len() { let vk = node_list[k].0; if vk.is_none() { - continue + continue; } let contour_nbrs = &node_list[k].1; @@ -441,63 +509,99 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { let wq = contour_nbrs[contour_nbrs.len() - 1]; let wq1 = contour_nbrs[contour_nbrs.len() - 2]; + println!("NODE List in canon k {:?} wp {:?} wp1 {:?} wq {:?} wq1 {:?}", k, wp, wp1, wq, wq1); let adds_mult_tri = contour_nbrs.len() > 2; - let delta_wp1_plus = delta_x[&wp1] + 1; - delta_x.entry(wp1).or_insert(delta_wp1_plus); - let delta_wq_plus = delta_x[&wq] + 1; - delta_x.entry(wq).or_insert(delta_wq_plus); + let mut delta_wp1_plus = 0; + if delta_x.contains_key(&wp1) { + delta_wp1_plus = delta_x[&wp1] + 1; + } + delta_x.insert(wp1, delta_wp1_plus); + println!("delta_x wp1 {:?} d of wp1 {:?} wp1_plus {:?}", &wp1, delta_x[&wp1], delta_wp1_plus); + + let mut delta_wq_plus = 0; + if delta_x.contains_key(&wq) { + delta_wq_plus = delta_x[&wq] + 1; + } + delta_x.insert(wq, delta_wq_plus); + + println!("delta_x wq {:?} d of wq {:?} wq_plus {:?}", &wq, delta_x[&wq], delta_wq_plus); + println!("adds, {:?} delta_x {:?} contour_nbrs {:?}", adds_mult_tri, delta_x, contour_nbrs); - let delta_x_wp_wq = contour_nbrs.iter().map(|x| delta_x[x]).sum::(); + let delta_x_wp_wq = contour_nbrs[1..].iter().map(|x| delta_x[x]).sum::(); - println!("delta_x_wp_wq {:?}", delta_x_wp_wq); + println!("delta_x {:?} delta_x_wp_wq {:?}", delta_x, delta_x_wp_wq); let y_wp = y_coord[&wp].clone(); - let y_wq = y_coord[&wp].clone(); - delta_x.entry(vk).or_insert(delta_x_wp_wq - y_wp + y_wq); //y_coord[&wp] + y_coord[&wq]); - y_coord.entry(vk).or_insert(delta_x_wp_wq + y_wp + y_wq); //y_coord.cloned()[&wp] + y_coord.cloned()[&wq]); + let y_wq = y_coord[&wq].clone(); + delta_x.insert(vk, (delta_x_wp_wq - y_wp + y_wq) % 2); //y_coord[&wp] + y_coord[&wq]); + y_coord.insert(vk, (delta_x_wp_wq + y_wp + y_wq) % 2); //y_coord.cloned()[&wp] + y_coord.cloned()[&wq]); let d_vk = delta_x[&vk].clone(); - delta_x.entry(wq).or_insert(delta_x_wp_wq - d_vk); + delta_x.insert(wq, delta_x_wp_wq - d_vk); + + println!("delta_x {:?} y_coord {:?}", delta_x, y_coord); if adds_mult_tri { let delta_wp1_minus = delta_x[&wp1] - delta_x[&vk]; - delta_x.entry(wp1).or_insert(delta_wp1_minus); + delta_x.insert(wp1, delta_wp1_minus); } - right_t_child.entry(wp).or_insert(vk); - right_t_child.entry(vk).or_insert(wq); + right_t_child.insert(wp, vk); + right_t_child.insert(vk, wq); if adds_mult_tri { - left_t_child.entry(vk).or_insert(wp1); - right_t_child.entry(wq1).or_insert(None); + left_t_child.insert(vk, wp1); + right_t_child.insert(wq1, None); } else { - left_t_child.entry(vk).or_insert(None); + left_t_child.insert(vk, None); } + println!("right {:?} left {:?}", right_t_child, left_t_child); } - pub fn set_position( + fn set_position( parent: Option, - tree: &HashMap::, Option>, + tree: &HashMap, Option>, remaining_nodes: &mut Vec>, - delta_x: &HashMap::, usize>, - y_coord: &HashMap::, usize>, - pos: &mut Vec + delta_x: &HashMap, i64>, + y_coord: &HashMap, i64>, + pos: &mut Vec, ) { + println!("IN SET {:?}", remaining_nodes); let child = tree[&parent]; let parent_node_x = pos[parent.unwrap().index()][0]; if child.is_some() { let child_x = parent_node_x + (delta_x[&child] as f64); - pos[child.unwrap().index()] = [child_x, (y_coord[&child] as f64)]; + pos.insert(child.unwrap().index(), [child_x, (y_coord[&child] as f64)]); remaining_nodes.push(child); } + println!("IN SET END{:?}", remaining_nodes); } - pos[v1.unwrap().index()] = [0 as f64, y_coord[&v1] as f64]; + pos.insert(v1.unwrap().index(), [0.0, y_coord[&v1] as f64]); let mut remaining_nodes = vec![v1]; + println!("\nREMAINING {:?}", remaining_nodes); while remaining_nodes.len() > 0 { let parent_node = remaining_nodes.pop().unwrap(); - set_position(parent_node, &left_t_child, &mut remaining_nodes, &delta_x, &y_coord, &mut pos); - set_position(parent_node, &right_t_child, &mut remaining_nodes, &delta_x, &y_coord, &mut pos); + println!("parent_node {:?} left_t_child {:?} remaining_nodes {:?} delta_x {:?} y_coord {:?} pos {:?} ", + parent_node, left_t_child, remaining_nodes, delta_x, y_coord, pos); + set_position( + parent_node, + &left_t_child, + &mut remaining_nodes, + &delta_x, + &y_coord, + &mut pos, + ); + println!("parent_node {:?} right_t_child {:?} remaining_nodes {:?} delta_x {:?} y_coord {:?} pos {:?} ", + parent_node, right_t_child, remaining_nodes, delta_x, y_coord, pos); + set_position( + parent_node, + &right_t_child, + &mut remaining_nodes, + &delta_x, + &y_coord, + &mut pos, + ); } pos } @@ -555,7 +659,7 @@ fn make_bi_connected( out_node: NodeIndex, edges_counted: &mut HashSet<(NodeIndex, NodeIndex)>, ) -> Vec { - if edges_counted.contains(&(start_node, out_node)) || start_node == out_node { + if edges_counted.contains(&(start_node, out_node)) {//|| start_node == out_node { println!( "biconnect already in start out {:?} {:?}", start_node, out_node @@ -612,13 +716,15 @@ fn triangulate_face(planar_emb: &mut PlanarEmbedding, mut v1: NodeIndex, mut v2: return; } let mut count = 0; - while v1 != v4 && count < 20 { + while v1 != v4 {//} && count < 20 { if planar_emb.embedding.contains_edge(v1, v3) { (v1, v2, v3) = (v2, v3, v4); + println!("\nContains EDGE {:?} {:?} {:?} {:?}", v1, v2, v3, v4); } else { - planar_emb.add_half_edge_cw(v1, v3, Some(v2)); - planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); + //planar_emb.add_half_edge_cw(v1, v3, Some(v2)); + //planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); (v1, v2, v3) = (v1, v3, v4); + println!("\nDont Contains EDGE count {:?} {:?} {:?} {:?} {:?}", count, v1, v2, v3, v4); } (_, v4) = planar_emb.next_face_half_edge(v2, v3); count += 1; @@ -643,18 +749,21 @@ fn canonical_ordering( HashMap::with_capacity(outer_face.len()); let mut outer_face_ccw_nbr: HashMap = HashMap::with_capacity(outer_face.len()); - let mut prev_nbr = v2.clone(); - for v in outer_face.iter().rev() { - outer_face_ccw_nbr.entry(prev_nbr).or_insert(*v); + let mut prev_nbr = v2.clone(); + for v in outer_face[2..outer_face.len()].iter() { + outer_face_ccw_nbr.insert(prev_nbr, *v); prev_nbr = *v; } - outer_face_ccw_nbr.entry(prev_nbr).or_insert(v1); + outer_face_ccw_nbr.insert(prev_nbr, v1); + println!("outer_face_ccw_nbr {:?}", outer_face_ccw_nbr); - for v in outer_face.iter().rev() { - outer_face_cw_nbr.entry(prev_nbr).or_insert(*v); + prev_nbr = v1.clone(); + for v in outer_face[1..outer_face.len()].iter().rev() { + outer_face_cw_nbr.insert(prev_nbr, *v); prev_nbr = *v; } + println!("outer_face_cw_nbr {:?}", outer_face_cw_nbr); fn is_outer_face_nbr( x: NodeIndex, @@ -663,10 +772,12 @@ fn canonical_ordering( outer_face_ccw_nbr: &HashMap, ) -> bool { if !outer_face_ccw_nbr.contains_key(&x) { + println!("1 is outer x {:?} y {:?} outer_ccw {:?} outer cw {:?}", x, y, outer_face_ccw_nbr, outer_face_cw_nbr); return outer_face_cw_nbr[&x] == y; } - if !outer_face_ccw_nbr.contains_key(&x) { - return outer_face_cw_nbr[&x] == y; + if !outer_face_cw_nbr.contains_key(&x) { + println!("2 is outer x {:?} y {:?} outer_ccw {:?} outer cw {:?}", x, y, outer_face_ccw_nbr, outer_face_cw_nbr); + return outer_face_ccw_nbr[&x] == y; } outer_face_cw_nbr[&x] == y || outer_face_ccw_nbr[&x] == y } @@ -681,17 +792,25 @@ fn canonical_ordering( } for v in outer_face { + let mut x = vec![]; + for nbr in planar_emb.neighbors_cw_order(v) { + x.push(nbr); + } + println!("LIST NEIGBORS CW ORDER {:?}", x); + println!("v in outer_face {:?}", v); for nbr in planar_emb.neighbors_cw_order(v) { + println!("nbr in nbr cw order {:?}", nbr); + if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) && !is_outer_face_nbr(v, nbr, &outer_face_cw_nbr, &outer_face_ccw_nbr) { + let mut chords_plus = 0; if chords.contains_key(&v) { - let chords_plus = chords[&v].clone(); - chords.entry(v).or_insert(chords_plus + 1); - } else { - chords.entry(v).or_insert(1); + chords_plus = chords[&v].clone(); } + chords.insert(v, chords_plus + 1); ready_to_pick.remove(&v); + println!("in chords {:?} ready {:?}", chords, ready_to_pick); } } println!("READY 1st {:?} {:?}", v, ready_to_pick); @@ -716,6 +835,7 @@ fn canonical_ordering( continue; } let v = v_try.unwrap().clone(); + println!("in canon v {:?} k {:?}", v, k); ready_to_pick.remove(&v); marked_nodes.insert(v); @@ -723,10 +843,15 @@ fn canonical_ordering( let mut wq: Option = None; for nbr in planar_emb.neighbors_cw_order(v) { + println!("nbr {:?}", nbr); if marked_nodes.contains(&nbr) { continue; } + println!("check outer face {:?}", nbr); + if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) { + println!("is outer {:?}", nbr); + if nbr == v1 { wp = Some(v1); } else if nbr == v2 { @@ -739,57 +864,83 @@ fn canonical_ordering( } } } + println!("wp {:?} wq {:?}", wp, wq); + if !wp.is_none() && !wq.is_none() { break; } } - let mut wp_wq = vec![wp]; - let mut nbr = wp.unwrap(); - while Some(nbr) != wp { - let next_nbr = planar_emb.get_edge_weight(v, nbr, false).unwrap(); - wp_wq.push(Some(next_nbr)); - outer_face_cw_nbr.entry(nbr).or_insert(next_nbr); - outer_face_ccw_nbr.entry(next_nbr).or_insert(nbr); - nbr = next_nbr; - } - if wp_wq.len() == 2 { - let wp_un = wp.unwrap(); - let chords_wp = chords[&wp_un].clone() - 1; - chords.entry(wp_un).or_insert(chords_wp); - if chords[&wp_un] == 0 { - ready_to_pick.insert(wp_un); + let mut wp_wq = vec![]; + if wp.is_some() { + wp_wq = vec![wp]; + let mut nbr = wp.unwrap(); + while Some(nbr) != wq { + let next_nbr = planar_emb.get_edge_weight(v, nbr, false).unwrap(); + println!("nbr {:?} next_nbr {:?} wp {:?} wq {:?} ", nbr, next_nbr, wp, wq); + wp_wq.push(Some(next_nbr)); + outer_face_cw_nbr.insert(nbr, next_nbr); + outer_face_ccw_nbr.insert(next_nbr, nbr); + nbr = next_nbr; + println!("of_cw {:?} of_ccw {:?} next_nbr {:?} wp_wq {:?} ", outer_face_cw_nbr, outer_face_ccw_nbr, next_nbr, wp_wq); } - let wq_un = wq.unwrap(); - let chords_wq = chords[&wq_un].clone() - 1; - chords.entry(wq_un).or_insert(chords_wq); - if chords[&wq_un] == 0 { - ready_to_pick.insert(wq_un); - } - } else { - let mut new_face_nodes: HashSet = HashSet::new(); - if wp_wq.len() > 1 { - for w in &wp_wq[1..(wp_wq.len() - 1)] { - let w_un = w.unwrap(); - new_face_nodes.insert(w_un); - for nbr in planar_emb.neighbors_cw_order(w_un) { - if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) - && !is_outer_face_nbr(w_un, nbr, &outer_face_cw_nbr, &outer_face_ccw_nbr) - { - let chords_w = chords[&w_un].clone() + 1; - chords.entry(w_un).or_insert(chords_w); - ready_to_pick.remove(&w_un); - if !new_face_nodes.contains(&nbr) { - let chords_nbr = chords[&nbr].clone() + 1; - chords.entry(nbr).or_insert(chords_nbr); - ready_to_pick.remove(&nbr); + println!("after while wp_wq{:?}", wp_wq); + if wp_wq.len() == 2 { + let wp_un = wp.unwrap(); + if chords.contains_key(&wp_un) { + let chords_wp = chords[&wp_un].clone() - 1; + chords.insert(wp_un, chords_wp); + if chords[&wp_un] == 0 { + ready_to_pick.insert(wp_un); + } + let wq_un = wq.unwrap(); + let chords_wq = chords[&wq_un].clone() - 1; + chords.insert(wq_un, chords_wq); + if chords[&wq_un] == 0 { + ready_to_pick.insert(wq_un); + } + } + } else { + let mut new_face_nodes: HashSet = HashSet::new(); + if wp_wq.len() > 1 { + for w in &wp_wq[1..(wp_wq.len() - 1)] { + let w_un = w.unwrap(); + new_face_nodes.insert(w_un); + for nbr in planar_emb.neighbors_cw_order(w_un) { + if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) + && !is_outer_face_nbr( + w_un, + nbr, + &outer_face_cw_nbr, + &outer_face_ccw_nbr, + ) + { + if chords.contains_key(&w_un) { + let chords_w = chords[&w_un].clone() + 1; + chords.insert(w_un, chords_w); + ready_to_pick.remove(&w_un); + if !new_face_nodes.contains(&nbr) { + let chords_nbr = chords[&nbr].clone() + 1; + chords.insert(nbr, chords_nbr); + ready_to_pick.remove(&nbr); + } + } } } } } } } + let x = wp_wq.clone(); canon_order[k] = (Some(v), wp_wq); - } - canon_order + println!("\ncanon order k {:?} v {:?} wp_wq {:?}", k, v, x); + } + let mut order: Vec<(Option, Vec>)> = vec![]; + // vec![(None, vec![]); planar_emb.embedding.node_count()]; + order.push((Some(NodeIndex::new(0)), vec![])); + order.push((Some(NodeIndex::new(1)), vec![])); + order.push((Some(NodeIndex::new(2)), vec![Some(NodeIndex::new(0)), Some(NodeIndex::new(1))])); + order.push((Some(NodeIndex::new(3)), vec![Some(NodeIndex::new(0)), Some(NodeIndex::new(2))])); + order.push((Some(NodeIndex::new(4)), vec![Some(NodeIndex::new(3)), Some(NodeIndex::new(2))])); + order } diff --git a/src/layout/planar.rs b/src/layout/planar.rs index 737f50fd6..d40aca3dc 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -33,6 +33,17 @@ pub fn planar_layout( create_embedding(&mut planar_emb, &mut lr_state); + for v in graph.node_indices() { + println!("\nNode {:?}", v); + println!("First_nbr {:?}", planar_emb.embedding[v].first_nbr); + + for nbr in planar_emb.neighbors_cw_order(v) { + println!("Nbr {:?}", nbr); + } + } + println!("DONE\n"); + + // DEBUG // for node in planar_emb.embedding.node_indices() { // println!("emb node {:?}", planar_emb.embedding[node]); From 37379b1a7287032c75743fc713e3c82943590f2e Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sun, 17 Jul 2022 16:48:57 -0700 Subject: [PATCH 18/37] Fix triangulate and almost canonical --- retworkx-core/src/planar/lr_planar.rs | 6 +- src/layout/embedding.rs | 151 ++++++++++---------------- src/layout/planar.rs | 25 ++--- 3 files changed, 71 insertions(+), 111 deletions(-) diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index 41bc53514..05d8ecce5 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -230,7 +230,7 @@ where /// next back edge in traversal with lowest return point. lowpt_edge: HashMap, Edge>, /// proxy for nesting order ≺ given by twice lowpt (plus 1 if chordal). - pub nesting_depth: HashMap, i64>, + pub nesting_depth: HashMap, isize>, /// stack for conflict pairs. stack: Vec>>, /// marks the top conflict pair when an edge was pushed in the stack. @@ -338,9 +338,9 @@ where if self.lowpt_2[&ei] < self.height[&v] { // if it's chordal, add one. - self.nesting_depth.insert(ei, (2 * low) as i64 + 1); + self.nesting_depth.insert(ei, (2 * low) as isize + 1); } else { - self.nesting_depth.insert(ei, (2 * low) as i64); + self.nesting_depth.insert(ei, (2 * low) as isize); } // update lowpoints of parent edge. diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index af329e19a..7d2050f6f 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -259,7 +259,7 @@ pub fn create_embedding( let mut ordered_adjs: Vec> = Vec::new(); - //let mut nesting_depth: HashMap<(NodeIndex, NodeIndex), i64> = + //let mut nesting_depth: HashMap<(NodeIndex, NodeIndex), isize> = // HashMap::with_capacity(lr_state.nesting_depth.len()); // Create the adjacency list for each node @@ -280,7 +280,7 @@ pub fn create_embedding( // Change the sign for nesting_depth for e in lr_state.dir_graph.edges(v) { let edge: (NodeIndex, NodeIndex) = (e.source(), e.target()); - let signed_depth: i64 = lr_state.nesting_depth[&edge] as i64; + let signed_depth: isize = lr_state.nesting_depth[&edge] as isize; let signed_side = if lr_state.side.contains_key(&edge) && sign(edge, &mut lr_state.eref, &mut lr_state.side) == Sign::Minus { @@ -430,11 +430,11 @@ pub fn create_embedding( } } +/// Once the embedding has been created, triangulate the embedding, +/// create a canonical ordering, and convert the embedding to position +/// coordinates. pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { - let mut pos: Vec = Vec::with_capacity(planar_emb.embedding.node_count()); - for p in 0..planar_emb.embedding.node_count() { - pos.push([0.0, 0.0]); - } + let mut pos: Vec = vec![[0.0, 0.0]; planar_emb.embedding.node_count()]; if planar_emb.embedding.node_count() < 4 { let default_pos = [[0.0, 0.0], [2.0, 0.0], [1.0, 1.0]].to_vec(); return planar_emb @@ -444,43 +444,19 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { .collect(); } let outer_face = triangulate_embedding(planar_emb, false); - let mut outer2 = outer_face.clone(); - - println!("\n\nAFTER TRI\n"); - for v in outer_face.clone() { - println!("\nNode {:?}", v); - println!("First_nbr {:?}", planar_emb.embedding[v].first_nbr); - for nbr in planar_emb.neighbors_cw_order(v) { - println!("Nbr {:?}", nbr); - } - } - println!("DONE\n"); + let node_list = canonical_ordering(planar_emb, outer_face); let mut right_t_child = HashMap::, Option>::new(); let mut left_t_child = HashMap::, Option>::new(); - let mut delta_x = HashMap::, i64>::new(); - let mut y_coord = HashMap::, i64>::new(); - - let node_list = canonical_ordering(planar_emb, outer_face); - - println!("\n\nAFTER CANON\n"); - for v in outer2 { - println!("\nNode {:?}", v); - println!("First_nbr {:?}", planar_emb.embedding[v].first_nbr); - - for nbr in planar_emb.neighbors_cw_order(v) { - println!("Nbr {:?}", nbr); - } - } - println!("DONE\n"); + let mut delta_x = HashMap::, isize>::new(); + let mut y_coord = HashMap::, isize>::new(); + // Set the coordinates for the first 3 nodes. let v1 = node_list[0].0; let v2 = node_list[1].0; let v3 = node_list[2].0; - println!("NODE LIST v1 {:?} v2 {:?} v3 {:?} node_list {:?}", v1, v2, v3, node_list); - delta_x.insert(v1, 0); y_coord.insert(v1, 0); right_t_child.insert(v1, v3); @@ -496,12 +472,10 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { right_t_child.insert(v3, v2); left_t_child.insert(v3, None); - println!("delta {:?} y {:?} right {:?} left {:?}", delta_x, y_coord, right_t_child, left_t_child); + // Set coordinates for the remaining nodes, adjusting + // positions along the way as needed. for k in 3..node_list.len() { let vk = node_list[k].0; - if vk.is_none() { - continue; - } let contour_nbrs = &node_list[k].1; let wp = contour_nbrs[0]; @@ -509,43 +483,34 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { let wq = contour_nbrs[contour_nbrs.len() - 1]; let wq1 = contour_nbrs[contour_nbrs.len() - 2]; - println!("NODE List in canon k {:?} wp {:?} wp1 {:?} wq {:?} wq1 {:?}", k, wp, wp1, wq, wq1); let adds_mult_tri = contour_nbrs.len() > 2; - let mut delta_wp1_plus = 0; + let mut delta_wp1_plus = 1; if delta_x.contains_key(&wp1) { delta_wp1_plus = delta_x[&wp1] + 1; } delta_x.insert(wp1, delta_wp1_plus); - println!("delta_x wp1 {:?} d of wp1 {:?} wp1_plus {:?}", &wp1, delta_x[&wp1], delta_wp1_plus); - let mut delta_wq_plus = 0; + let mut delta_wq_plus = 1; if delta_x.contains_key(&wq) { delta_wq_plus = delta_x[&wq] + 1; } delta_x.insert(wq, delta_wq_plus); - println!("delta_x wq {:?} d of wq {:?} wq_plus {:?}", &wq, delta_x[&wq], delta_wq_plus); - println!("adds, {:?} delta_x {:?} contour_nbrs {:?}", adds_mult_tri, delta_x, contour_nbrs); - - let delta_x_wp_wq = contour_nbrs[1..].iter().map(|x| delta_x[x]).sum::(); - - println!("delta_x {:?} delta_x_wp_wq {:?}", delta_x, delta_x_wp_wq); + let delta_x_wp_wq = contour_nbrs[1..].iter().map(|x| delta_x[x]).sum::(); let y_wp = y_coord[&wp].clone(); let y_wq = y_coord[&wq].clone(); - delta_x.insert(vk, (delta_x_wp_wq - y_wp + y_wq) % 2); //y_coord[&wp] + y_coord[&wq]); - y_coord.insert(vk, (delta_x_wp_wq + y_wp + y_wq) % 2); //y_coord.cloned()[&wp] + y_coord.cloned()[&wq]); - let d_vk = delta_x[&vk].clone(); - delta_x.insert(wq, delta_x_wp_wq - d_vk); + delta_x.insert(vk, (delta_x_wp_wq - y_wp + y_wq) / 2 as isize); //y_coord[&wp] + y_coord[&wq]); + y_coord.insert(vk, (delta_x_wp_wq + y_wp + y_wq) / 2 as isize); //y_coord.cloned()[&wp] + y_coord.cloned()[&wq]); - println!("delta_x {:?} y_coord {:?}", delta_x, y_coord); + //let d_vk = delta_x[&vk].clone(); + delta_x.insert(wq, delta_x_wp_wq - delta_x[&vk]);//d_vk); if adds_mult_tri { - let delta_wp1_minus = delta_x[&wp1] - delta_x[&vk]; - delta_x.insert(wp1, delta_wp1_minus); + //let delta_wp1_minus = delta_x[&wp1] - delta_x[&vk]; + delta_x.insert(wp1, delta_x[&wp1] - delta_x[&vk]);//delta_wp1_minus); } - right_t_child.insert(wp, vk); right_t_child.insert(vk, wq); if adds_mult_tri { @@ -554,36 +519,33 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { } else { left_t_child.insert(vk, None); } - println!("right {:?} left {:?}", right_t_child, left_t_child); } + // Set the position of the next tree child. fn set_position( parent: Option, tree: &HashMap, Option>, remaining_nodes: &mut Vec>, - delta_x: &HashMap, i64>, - y_coord: &HashMap, i64>, + delta_x: &HashMap, isize>, + y_coord: &HashMap, isize>, pos: &mut Vec, ) { - println!("IN SET {:?}", remaining_nodes); let child = tree[&parent]; let parent_node_x = pos[parent.unwrap().index()][0]; if child.is_some() { let child_x = parent_node_x + (delta_x[&child] as f64); - pos.insert(child.unwrap().index(), [child_x, (y_coord[&child] as f64)]); + pos[child.unwrap().index()] = [child_x, (y_coord[&child] as f64)]; remaining_nodes.push(child); } - println!("IN SET END{:?}", remaining_nodes); } - pos.insert(v1.unwrap().index(), [0.0, y_coord[&v1] as f64]); + pos[v1.unwrap().index()] = [0.0, y_coord[&v1] as f64]; let mut remaining_nodes = vec![v1]; - println!("\nREMAINING {:?}", remaining_nodes); + + // Set the positions of all the nodes. while remaining_nodes.len() > 0 { let parent_node = remaining_nodes.pop().unwrap(); - println!("parent_node {:?} left_t_child {:?} remaining_nodes {:?} delta_x {:?} y_coord {:?} pos {:?} ", - parent_node, left_t_child, remaining_nodes, delta_x, y_coord, pos); set_position( parent_node, &left_t_child, @@ -592,8 +554,6 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { &y_coord, &mut pos, ); - println!("parent_node {:?} right_t_child {:?} remaining_nodes {:?} delta_x {:?} y_coord {:?} pos {:?} ", - parent_node, right_t_child, remaining_nodes, delta_x, y_coord, pos); set_position( parent_node, &right_t_child, @@ -721,12 +681,15 @@ fn triangulate_face(planar_emb: &mut PlanarEmbedding, mut v1: NodeIndex, mut v2: (v1, v2, v3) = (v2, v3, v4); println!("\nContains EDGE {:?} {:?} {:?} {:?}", v1, v2, v3, v4); } else { - //planar_emb.add_half_edge_cw(v1, v3, Some(v2)); - //planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); + planar_emb.add_half_edge_cw(v1, v3, Some(v2)); + planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); + println!("get cw {:?}", planar_emb.get_edge_weight(v1, v3, true)); + println!("get ccw {:?}", planar_emb.get_edge_weight(v3, v1, false)); (v1, v2, v3) = (v1, v3, v4); println!("\nDont Contains EDGE count {:?} {:?} {:?} {:?} {:?}", count, v1, v2, v3, v4); } (_, v4) = planar_emb.next_face_half_edge(v2, v3); + println!("after v4 v2 {:?} v3 {:?} v4 {:?}", v2, v3, v4); count += 1; } } @@ -829,8 +792,10 @@ fn canonical_ordering( ready_to_pick.remove(&v2); println!("READY after {:?}", ready_to_pick); - for k in (1..planar_emb.embedding.node_count()).rev() { + //println!("\n RANGE {:?}", (1..(planar_emb.embedding.node_count()-1)).rev()); + for k in (1..(planar_emb.embedding.node_count())).rev() { let v_try = ready_to_pick.iter().next(); + println!("\nv_try {:?}", v_try); if v_try.is_none() { continue; } @@ -838,35 +803,38 @@ fn canonical_ordering( println!("in canon v {:?} k {:?}", v, k); ready_to_pick.remove(&v); marked_nodes.insert(v); + println!("ready_to_pick {:?} marked {:?}", ready_to_pick, marked_nodes); let mut wp: Option = None; let mut wq: Option = None; - for nbr in planar_emb.neighbors_cw_order(v) { - println!("nbr {:?}", nbr); - if marked_nodes.contains(&nbr) { + println!("\n nbrs_cw_order of v {:?} {:?}", v, planar_emb.neighbors_cw_order(v)); + for nbr in planar_emb.neighbors_cw_order(v).iter() { + println!("nbr {:?}", *nbr); + if marked_nodes.contains(nbr) { continue; } - println!("check outer face {:?}", nbr); + println!("check outer face {:?}", *nbr); - if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) { - println!("is outer {:?}", nbr); + if is_on_outer_face(*nbr, v1, &marked_nodes, &outer_face_ccw_nbr) { + println!("is outer {:?} {:?} {:?}", *nbr, v1, v2);//, outer_face_cw_nbr[nbr]); - if nbr == v1 { + if *nbr == v1 { wp = Some(v1); - } else if nbr == v2 { + } else if *nbr == v2 { wq = Some(v2); } else { - if outer_face_cw_nbr[&nbr] == v1 { - wp = Some(nbr); + if outer_face_cw_nbr[nbr] == v { + wp = Some(*nbr); } else { - wq = Some(nbr); + wq = Some(*nbr); } } } println!("wp {:?} wq {:?}", wp, wq); - if !wp.is_none() && !wq.is_none() { + if (wp.is_some() && wq.is_some()) { + println!("\nBREAKING {:?} {:?}", wp, wq); break; } } @@ -935,12 +903,13 @@ fn canonical_ordering( canon_order[k] = (Some(v), wp_wq); println!("\ncanon order k {:?} v {:?} wp_wq {:?}", k, v, x); } - let mut order: Vec<(Option, Vec>)> = vec![]; - // vec![(None, vec![]); planar_emb.embedding.node_count()]; - order.push((Some(NodeIndex::new(0)), vec![])); - order.push((Some(NodeIndex::new(1)), vec![])); - order.push((Some(NodeIndex::new(2)), vec![Some(NodeIndex::new(0)), Some(NodeIndex::new(1))])); - order.push((Some(NodeIndex::new(3)), vec![Some(NodeIndex::new(0)), Some(NodeIndex::new(2))])); - order.push((Some(NodeIndex::new(4)), vec![Some(NodeIndex::new(3)), Some(NodeIndex::new(2))])); - order + // let mut order: Vec<(Option, Vec>)> = vec![]; + // // vec![(None, vec![]); planar_emb.embedding.node_count()]; + // order.push((Some(NodeIndex::new(0)), vec![])); + // order.push((Some(NodeIndex::new(1)), vec![])); + // order.push((Some(NodeIndex::new(2)), vec![Some(NodeIndex::new(0)), Some(NodeIndex::new(1))])); + // order.push((Some(NodeIndex::new(3)), vec![Some(NodeIndex::new(0)), Some(NodeIndex::new(2))])); + // order.push((Some(NodeIndex::new(4)), vec![Some(NodeIndex::new(3)), Some(NodeIndex::new(2))])); + println!("\nCANON ORDER {:?}", canon_order); + canon_order } diff --git a/src/layout/planar.rs b/src/layout/planar.rs index d40aca3dc..61a1c3268 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -8,6 +8,8 @@ use crate::StablePyGraph; use retworkx_core::dictmap::*; use retworkx_core::planar::{is_planar, LRState}; +/// If a graph is planar, create a set of position coordinates for a planar +/// layout that can be passed to a drawer. pub fn planar_layout( graph: &StablePyGraph, scale: Option, @@ -20,36 +22,25 @@ pub fn planar_layout( }; } + // First determine if the graph is planar. let mut lr_state = LRState::new(graph); let its_planar = is_planar(graph, Some(&mut lr_state)); + // If not planar, return an empty pos_map if !its_planar { return Pos2DMapping { pos_map: DictMap::new(), }; + + // If planar, create the position coordinates. } else { let mut planar_emb = PlanarEmbedding::new(); planar_emb.embedding = Graph::with_capacity(node_num, 0); + // First create the graph embedding create_embedding(&mut planar_emb, &mut lr_state); - for v in graph.node_indices() { - println!("\nNode {:?}", v); - println!("First_nbr {:?}", planar_emb.embedding[v].first_nbr); - - for nbr in planar_emb.neighbors_cw_order(v) { - println!("Nbr {:?}", nbr); - } - } - println!("DONE\n"); - - - // DEBUG - // for node in planar_emb.embedding.node_indices() { - // println!("emb node {:?}", planar_emb.embedding[node]); - // println!("emb edges {:?}", planar_emb.embedding.edges(node)); - // } - + // Then convert the embedding to position coordinates. let mut pos = embedding_to_pos(&mut planar_emb); if let Some(scale) = scale { From 72ad6bbfba96806af420772a6a9b9869d2a19ed7 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Mon, 18 Jul 2022 16:54:36 -0700 Subject: [PATCH 19/37] All working for smaller graphs --- retworkx-core/src/planar/lr_planar.rs | 22 ++++++++++++---------- src/layout/embedding.rs | 11 ++++++++--- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index 05d8ecce5..c71f355de 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -291,11 +291,12 @@ where } } DfsEvent::TreeEdge(v, w, _) => { - self.dir_graph.add_edge( - NodeIndex::new(self.graph.to_index(v)), - NodeIndex::new(self.graph.to_index(w)), - (), - ); + let v_dir = NodeIndex::new(self.graph.to_index(v)); + let w_dir = NodeIndex::new(self.graph.to_index(w)); + println!("TREE v_dir {:?} w_dir {:?}", v_dir, w_dir); + if !self.dir_graph.contains_edge(v_dir, w_dir) { + self.dir_graph.add_edge(v_dir, w_dir, ()); + } let ei = (v, w); let v_height = self.height[&v]; let w_height = v_height + 1; @@ -309,11 +310,12 @@ where DfsEvent::BackEdge(v, w, _) => { // do *not* consider ``(v, w)`` as a back edge if ``(w, v)`` is a tree edge. if Some(&(w, v)) != self.eparent.get(&v) { - self.dir_graph.add_edge( - NodeIndex::new(self.graph.to_index(v)), - NodeIndex::new(self.graph.to_index(w)), - (), - ); + let v_dir = NodeIndex::new(self.graph.to_index(v)); + let w_dir = NodeIndex::new(self.graph.to_index(w)); + println!("BACK v_dir {:?} w_dir {:?}", v_dir, w_dir); + if !self.dir_graph.contains_edge(v_dir, w_dir) { + self.dir_graph.add_edge(v_dir, w_dir, ()); + } let ei = (v, w); self.lowpt.insert(ei, self.height[&w]); self.lowpt_2.insert(ei, self.height[&v]); diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index 7d2050f6f..3ec1cd1b7 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -276,6 +276,12 @@ pub fn create_embedding( adjs.par_sort_by_key(|x| lr_state.nesting_depth[&(NodeIndex::new(v), *x)]); } + // ********** DEBUG + for x in &ordered_adjs { + println!("ordered {:?}", x); + } + // ********** DEBUG END + for v in lr_state.dir_graph.node_indices() { // Change the sign for nesting_depth for e in lr_state.dir_graph.edges(v) { @@ -792,8 +798,7 @@ fn canonical_ordering( ready_to_pick.remove(&v2); println!("READY after {:?}", ready_to_pick); - //println!("\n RANGE {:?}", (1..(planar_emb.embedding.node_count()-1)).rev()); - for k in (1..(planar_emb.embedding.node_count())).rev() { + for k in (2..(planar_emb.embedding.node_count())).rev() { let v_try = ready_to_pick.iter().next(); println!("\nv_try {:?}", v_try); if v_try.is_none() { @@ -833,7 +838,7 @@ fn canonical_ordering( } println!("wp {:?} wq {:?}", wp, wq); - if (wp.is_some() && wq.is_some()) { + if wp.is_some() && wq.is_some() { println!("\nBREAKING {:?} {:?}", wp, wq); break; } From 101d51a7ea5d58b02c7d9098788074b49ad958f8 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Tue, 19 Jul 2022 15:24:59 -0700 Subject: [PATCH 20/37] Cleanup debug prints and add some docs --- retworkx-core/src/planar/lr_planar.rs | 7 - src/layout/embedding.rs | 222 +++----------------------- 2 files changed, 21 insertions(+), 208 deletions(-) diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index c71f355de..5a11562e7 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -726,12 +726,5 @@ where return false; } } - - // for node in lr_state.dir_graph.node_indices() { - // for edge in lr_state.dir_graph.edges(node) { - // println!("Edge {:?}, {:?}", edge.source(), edge.target()); - // } - // } - true } diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index 3ec1cd1b7..2b62e12c4 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -28,6 +28,7 @@ impl Default for CwCcw { } } +#[allow(dead_code)] impl CwCcw { fn new(cw: T, ccw: T) -> Self { CwCcw { @@ -48,6 +49,7 @@ impl Default for FirstNbr { } } +#[allow(dead_code)] impl FirstNbr { fn new(first_nbr: T) -> Self { FirstNbr { @@ -56,6 +58,8 @@ impl FirstNbr { } } +/// The basic embedding to build the structure that will lead to +/// the position coordinates to display. pub struct PlanarEmbedding { pub embedding: Graph, CwCcw, Directed>, } @@ -77,36 +81,26 @@ impl PlanarEmbedding { pub fn neighbors_cw_order(&mut self, v: NodeIndex) -> Vec { let mut nbrs: Vec = vec![]; let first_nbr = self.embedding[v].first_nbr; - println!("first_nbr in nbrs_cw_order v {:?} f nbr {:?}", v, first_nbr); if first_nbr.is_none() { // v has no neighbors return nbrs; } let start_node = first_nbr.unwrap(); - println!("start_node in nbrs_cw_order {:?}", start_node); nbrs.push(start_node); let mut node = self.get_edge_weight(v, start_node, true); - println!("node {:?} nbrs {:?}", node, nbrs); if let Some(mut current_node) = node { - println!("current_node 1 start {:?} current {:?}", start_node, current_node); - let mut count = 0; - while start_node != current_node && count < 20 { - count += 1; - println!("begin while - count {:?}", count); + while start_node != current_node { nbrs.push(current_node); node = self.get_edge_weight(v, current_node, true); current_node = match node { Some(node) => node, None => break, }; - println!("current_node 2 {:?} {:?}", current_node, nbrs); } } - println!("nbrs 1 in nbrs_cw_order {:?}", nbrs); - nbrs } @@ -116,32 +110,23 @@ impl PlanarEmbedding { end_node: NodeIndex, ref_nbr: Option, ) { - println!("IN HALF CW"); let cw_weight = CwCcw::::default(); self.embedding.add_edge(start_node, end_node, cw_weight); if ref_nbr.is_none() { - println!("ref_nbr None {:?} {:?} {:?}", start_node, end_node, ref_nbr); - // The start node has no neighbors - println!("first update {:?} {:?}", start_node, end_node); self.update_edge_weight(start_node, end_node, end_node, true); - println!("second update"); self.update_edge_weight(start_node, end_node, end_node, false); self.embedding[start_node].first_nbr = Some(end_node); return; } // if ref_nbr not in self[start_node] error let ref_nbr_node = ref_nbr.unwrap(); - // DEBUG + // DEBUG - RAISE? if self.embedding.find_edge(start_node, ref_nbr_node).is_none() { println!("NO REF NBR in ADD CW {:?} {:?}", start_node, ref_nbr_node); } let cw_ref = self.get_edge_weight(start_node, ref_nbr_node, true); - println!("HALF CW before Some(cw_ref_node) start, end, ref, cw_ref {:?} {:?} {:?} {:?}", - start_node, end_node, ref_nbr, cw_ref); - if let Some(cw_ref_node) = cw_ref { - println!("HALF CW Some(cw_ref_node) {:?} {:?}", cw_ref, cw_ref_node); // Alter half-edge data structures self.update_edge_weight(start_node, ref_nbr_node, end_node, true); self.update_edge_weight(start_node, end_node, cw_ref_node, true); @@ -156,7 +141,6 @@ impl PlanarEmbedding { end_node: NodeIndex, ref_nbr: Option, ) { - println!("IN HALF CCW"); if ref_nbr.is_none() { // Start node has no neighbors let cw_weight = CwCcw::::default(); @@ -176,6 +160,7 @@ impl PlanarEmbedding { } fn add_half_edge_first(&mut self, start_node: NodeIndex, end_node: NodeIndex) { + // Add half edge that's first_nbr or None let ref_node: Option = if self.embedding.node_bound() >= start_node.index() && self.embedding[start_node].first_nbr.is_some() { @@ -183,16 +168,13 @@ impl PlanarEmbedding { } else { None }; - println!("in half first: start_node, end_node, ref_node {:?} {:?} {:?}", start_node, end_node, ref_node); self.add_half_edge_ccw(start_node, end_node, ref_node); } fn next_face_half_edge(&self, v: NodeIndex, w: NodeIndex) -> (NodeIndex, NodeIndex) { - //println!("next v {:?} w {:?}", v, w); let new_node = self.get_edge_weight(w, v, false); - println!("new node {:?}", new_node); // FIX THIS - // + // RAISE? // if new_node.is_none() { println!("NEW NODE NONE in next_face {:?} {:?} {:?}", new_node, v, w); @@ -210,7 +192,6 @@ impl PlanarEmbedding { if found_weight.is_none() { return; } - if cw { found_weight.unwrap().cw = Some(new_node); } else { @@ -221,17 +202,16 @@ impl PlanarEmbedding { fn get_edge_weight(&self, v: NodeIndex, w: NodeIndex, cw: bool) -> Option { let found_edge = self.embedding.find_edge(v, w); if found_edge.is_none() { + // RAISE? println!("GET EDGE find edge is none {:?}", found_edge); return None; } - println!("GET EDGE find edge{:?}", found_edge); - let found_weight = self.embedding.edge_weight(found_edge.unwrap()); //.unwrap(); - //println!("after found weight {:?}", found_weight); + let found_weight = self.embedding.edge_weight(found_edge.unwrap()); if found_weight.is_none() { + // RAISE? println!("GET EDGE Weight is none {:?}", found_weight); return None; } - if cw { found_weight.unwrap().cw } else { @@ -240,32 +220,23 @@ impl PlanarEmbedding { } fn connect_components(&mut self, v: NodeIndex, w: NodeIndex) { + // If multiple connected_components, connect them self.add_half_edge_first(v, w); self.add_half_edge_first(w, v); } } +/// Use the LRState data from is_planar to build an +/// embedding. pub fn create_embedding( planar_emb: &mut PlanarEmbedding, lr_state: &mut LRState<&StablePyGraph>, ) { - // ********** DEBUG - for node in lr_state.dir_graph.node_indices() { - for edge in lr_state.dir_graph.edges(node) { - println!("Edge {:?}, {:?}", edge.source(), edge.target()); - } - } - // ********** DEBUG END - let mut ordered_adjs: Vec> = Vec::new(); - //let mut nesting_depth: HashMap<(NodeIndex, NodeIndex), isize> = - // HashMap::with_capacity(lr_state.nesting_depth.len()); - // Create the adjacency list for each node for v in lr_state.dir_graph.node_indices() { ordered_adjs.push(lr_state.dir_graph.edges(v).map(|e| e.target()).collect()); - // Add empty FirstNbr to the embedding let first_nbr = FirstNbr::::default(); planar_emb.embedding.add_node(first_nbr); @@ -276,12 +247,6 @@ pub fn create_embedding( adjs.par_sort_by_key(|x| lr_state.nesting_depth[&(NodeIndex::new(v), *x)]); } - // ********** DEBUG - for x in &ordered_adjs { - println!("ordered {:?}", x); - } - // ********** DEBUG END - for v in lr_state.dir_graph.node_indices() { // Change the sign for nesting_depth for e in lr_state.dir_graph.edges(v) { @@ -297,29 +262,12 @@ pub fn create_embedding( lr_state.nesting_depth.insert(edge, signed_depth * signed_side); } } - // ********** DEBUG - for x in &ordered_adjs { - println!("ordered {:?}", x); - } - // ********** DEBUG END - - // ********** DEBUG - for x in &lr_state.nesting_depth { - println!("nesting {:?}", x); - } - // ********** DEBUG END // Sort the adjacency list using nesting depth as sort order for (v, adjs) in ordered_adjs.iter_mut().enumerate() { adjs.par_sort_by_key(|x| lr_state.nesting_depth[&(NodeIndex::new(v), *x)]); } - // ********** DEBUG - for x in &ordered_adjs { - println!("ordered 2222 {:?}", x); - } - // ********** DEBUG END - // Add the initial half edge cw to the embedding using the ordered adjacency list for v in lr_state.dir_graph.node_indices() { let mut prev_node: Option = None; @@ -329,28 +277,6 @@ pub fn create_embedding( } } - // ********** DEBUG - // ********** DEBUG - for v in planar_emb.embedding.node_indices() { - for w in &ordered_adjs[v.index()] { - println!( - "22222 embedding node v {:?} first_nbr {:?} cw(v,w) {:?} ccw(v,w) {:?} cw(w,v) {:?} ccw(w,v) {:?}", - v, - planar_emb.embedding[v], - planar_emb.get_edge_weight(v, *w, true), - planar_emb.get_edge_weight(v, *w, false), - planar_emb.get_edge_weight(*w, v, true), - planar_emb.get_edge_weight(*w, v, false), - ); - } - } - // ********** DEBUG END - - println!("lr eparent {:?}", lr_state.eparent); - // ********** DEBUG END - - println!("adjs len {:?} emb count {:?}", ordered_adjs.len(), planar_emb.embedding.node_count()); - // Start the DFS traversal for the embedding let mut left_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); let mut right_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); @@ -361,62 +287,32 @@ pub fn create_embedding( let mut dfs_stack: Vec = vec![*v]; while dfs_stack.len() > 0 { - println!("top of while {:?}", dfs_stack); let v = dfs_stack.pop().unwrap(); let idx2 = idx[v.index()]; - println!("top of while 2 v {:?} idx2 {:?} stack {:?}", v, idx2, dfs_stack); // Iterate over the ordered_adjs starting at the saved index until the end for w in ordered_adjs[v.index()][idx2..].iter() { idx[v.index()] += 1; - - println!("In dfs v idx[v] w {:?} {:?} {:?}", v, idx[v.index()], *w); let ei = (v, *w); - println!("lr parent {:?}", lr_state.eparent.contains_key(&w)); - if lr_state.eparent.contains_key(&w) { - println!("parent w {:?} {:?}", lr_state.eparent[&w], ei); - } if lr_state.eparent.contains_key(&w) && ei == lr_state.eparent[&w] { - println!("in ei {:?}", ei); planar_emb.add_half_edge_first(*w, v); left_ref.insert(v, *w); right_ref.insert(v, *w); dfs_stack.push(v); dfs_stack.push(*w); - println!("before break dfs_stack {:?}", dfs_stack); break; } else { if !lr_state.side.contains_key(&ei) || lr_state.side[&ei] == Sign::Plus { - println!("plus cw {:?}", ei); planar_emb.add_half_edge_cw(*w, v, Some(right_ref[w])); } else { - println!("minus cw {:?}", ei); planar_emb.add_half_edge_ccw(*w, v, Some(left_ref[w])); left_ref.insert(*w, v); } - println!("in else {:?}", ei); } } } } - // ********** DEBUG - for v in planar_emb.embedding.node_indices() { - for w in &ordered_adjs[v.index()] { - println!( - "33333 embedding node v {:?} w {:?} first_nbr {:?} cw(v,w) {:?} ccw(v,w) {:?} cw(w,v) {:?} ccw(w,v) {:?}", - v, - w, - planar_emb.embedding[v], - planar_emb.get_edge_weight(v, *w, true), - planar_emb.get_edge_weight(v, *w, false), - planar_emb.get_edge_weight(*w, v, true), - planar_emb.get_edge_weight(*w, v, false), - ); - } - } - // ********** DEBUG END - fn sign( edge: (NodeIndex, NodeIndex), eref: &mut HashMap<(NodeIndex, NodeIndex), (NodeIndex, NodeIndex)>, @@ -577,18 +473,14 @@ fn triangulate_embedding( fully_triangulate: bool, ) -> Vec { let component_sets = connected_components(&planar_emb.embedding); - println!("CONN COMP {:?}", component_sets); - for i in 0..(component_sets.len() - 1) { let v1 = component_sets[i].iter().next().unwrap(); let v2 = component_sets[i + 1].iter().next().unwrap(); planar_emb.connect_components(*v1, *v2); - println!("v1 v2 {:?} {:?}", *v1, *v2); } let mut outer_face = vec![]; let mut face_list = vec![]; let mut edges_counted: HashSet<(NodeIndex, NodeIndex)> = HashSet::new(); - println!(" in triangulate component sets{:?}", component_sets); for v in planar_emb.embedding.node_indices() { for w in planar_emb.neighbors_cw_order(v) { @@ -602,9 +494,7 @@ fn triangulate_embedding( } } - println!("FINAL Face list {:?}", face_list); for face in face_list { - println!("face {:?} outer_face {:?}", face, outer_face); if face != outer_face || fully_triangulate { triangulate_face(planar_emb, face[0], face[1]); } @@ -615,7 +505,6 @@ fn triangulate_embedding( let v3 = planar_emb.get_edge_weight(v2, v1, false); outer_face = vec![v1, v2, v3.unwrap()]; } - println!("outer_face {:?}", outer_face); outer_face } @@ -625,52 +514,34 @@ fn make_bi_connected( out_node: NodeIndex, edges_counted: &mut HashSet<(NodeIndex, NodeIndex)>, ) -> Vec { - if edges_counted.contains(&(start_node, out_node)) {//|| start_node == out_node { - println!( - "biconnect already in start out {:?} {:?}", - start_node, out_node - ); + // If edge already counted return + if edges_counted.contains(&(start_node, out_node)) { return vec![]; } edges_counted.insert((start_node, out_node)); - //println!("edges counted {:?} {:?} {:?}", start_node, out_node, edges_counted); let mut v1 = start_node.clone(); let mut v2 = out_node.clone(); let mut face_list: Vec = vec![start_node]; let (_, mut v3) = planar_emb.next_face_half_edge(v1, v2); - let mut count = 0; - while (v2 != start_node || v3 != out_node) && count < 20 { - // && count < 300 { + while v2 != start_node || v3 != out_node { if v1 == v2 { + // RAISE? println!("BICONNECT V1==V2 should raise"); } - println!("face_list in while {:?} {:?} {:?}", v2, v3, face_list); if face_list.contains(&v2) { - println!( - "face_list contains v2 {:?} {:?} {:?}", - v2, v3, edges_counted - ); planar_emb.add_half_edge_cw(v1, v3, Some(v2)); planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); edges_counted.insert((v2, v3)); edges_counted.insert((v3, v1)); v2 = v1.clone(); } else { - println!( - "face_list not contain v2 {:?} {:?} {:?}", - v2, v3, edges_counted - ); face_list.push(v2); } v1 = v2.clone(); - println!("2 edges counted {:?} {:?} {:?}", v2, v3, edges_counted); (v2, v3) = planar_emb.next_face_half_edge(v2, v3); - println!("3 edges counted {:?} {:?} {:?}", v2, v3, edges_counted); - edges_counted.insert((v1, v2)); - count += 1; } face_list } @@ -681,22 +552,15 @@ fn triangulate_face(planar_emb: &mut PlanarEmbedding, mut v1: NodeIndex, mut v2: if v1 == v2 || v1 == v3 { return; } - let mut count = 0; - while v1 != v4 {//} && count < 20 { + while v1 != v4 { if planar_emb.embedding.contains_edge(v1, v3) { (v1, v2, v3) = (v2, v3, v4); - println!("\nContains EDGE {:?} {:?} {:?} {:?}", v1, v2, v3, v4); } else { planar_emb.add_half_edge_cw(v1, v3, Some(v2)); planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); - println!("get cw {:?}", planar_emb.get_edge_weight(v1, v3, true)); - println!("get ccw {:?}", planar_emb.get_edge_weight(v3, v1, false)); (v1, v2, v3) = (v1, v3, v4); - println!("\nDont Contains EDGE count {:?} {:?} {:?} {:?} {:?}", count, v1, v2, v3, v4); } (_, v4) = planar_emb.next_face_half_edge(v2, v3); - println!("after v4 v2 {:?} v3 {:?} v4 {:?}", v2, v3, v4); - count += 1; } } @@ -725,14 +589,12 @@ fn canonical_ordering( prev_nbr = *v; } outer_face_ccw_nbr.insert(prev_nbr, v1); - println!("outer_face_ccw_nbr {:?}", outer_face_ccw_nbr); prev_nbr = v1.clone(); for v in outer_face[1..outer_face.len()].iter().rev() { outer_face_cw_nbr.insert(prev_nbr, *v); prev_nbr = *v; } - println!("outer_face_cw_nbr {:?}", outer_face_cw_nbr); fn is_outer_face_nbr( x: NodeIndex, @@ -741,11 +603,9 @@ fn canonical_ordering( outer_face_ccw_nbr: &HashMap, ) -> bool { if !outer_face_ccw_nbr.contains_key(&x) { - println!("1 is outer x {:?} y {:?} outer_ccw {:?} outer cw {:?}", x, y, outer_face_ccw_nbr, outer_face_cw_nbr); return outer_face_cw_nbr[&x] == y; } if !outer_face_cw_nbr.contains_key(&x) { - println!("2 is outer x {:?} y {:?} outer_ccw {:?} outer cw {:?}", x, y, outer_face_ccw_nbr, outer_face_cw_nbr); return outer_face_ccw_nbr[&x] == y; } outer_face_cw_nbr[&x] == y || outer_face_ccw_nbr[&x] == y @@ -761,15 +621,7 @@ fn canonical_ordering( } for v in outer_face { - let mut x = vec![]; - for nbr in planar_emb.neighbors_cw_order(v) { - x.push(nbr); - } - println!("LIST NEIGBORS CW ORDER {:?}", x); - println!("v in outer_face {:?}", v); for nbr in planar_emb.neighbors_cw_order(v) { - println!("nbr in nbr cw order {:?}", nbr); - if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) && !is_outer_face_nbr(v, nbr, &outer_face_cw_nbr, &outer_face_ccw_nbr) { @@ -779,11 +631,8 @@ fn canonical_ordering( } chords.insert(v, chords_plus + 1); ready_to_pick.remove(&v); - println!("in chords {:?} ready {:?}", chords, ready_to_pick); } } - println!("READY 1st {:?} {:?}", v, ready_to_pick); - println!("Chords 1st {:?}", chords); } let mut canon_order: Vec<(Option, Vec>)> = @@ -791,39 +640,26 @@ fn canonical_ordering( canon_order[0] = (Some(v1), vec![]); canon_order[1] = (Some(v2), vec![]); - println!("READY before {:?}", ready_to_pick); - println!("Marked before {:?}", marked_nodes); - ready_to_pick.remove(&v1); ready_to_pick.remove(&v2); - println!("READY after {:?}", ready_to_pick); for k in (2..(planar_emb.embedding.node_count())).rev() { let v_try = ready_to_pick.iter().next(); - println!("\nv_try {:?}", v_try); if v_try.is_none() { + // RAISE? continue; } let v = v_try.unwrap().clone(); - println!("in canon v {:?} k {:?}", v, k); ready_to_pick.remove(&v); marked_nodes.insert(v); - println!("ready_to_pick {:?} marked {:?}", ready_to_pick, marked_nodes); let mut wp: Option = None; let mut wq: Option = None; - - println!("\n nbrs_cw_order of v {:?} {:?}", v, planar_emb.neighbors_cw_order(v)); for nbr in planar_emb.neighbors_cw_order(v).iter() { - println!("nbr {:?}", *nbr); if marked_nodes.contains(nbr) { continue; } - println!("check outer face {:?}", *nbr); - if is_on_outer_face(*nbr, v1, &marked_nodes, &outer_face_ccw_nbr) { - println!("is outer {:?} {:?} {:?}", *nbr, v1, v2);//, outer_face_cw_nbr[nbr]); - if *nbr == v1 { wp = Some(v1); } else if *nbr == v2 { @@ -836,10 +672,7 @@ fn canonical_ordering( } } } - println!("wp {:?} wq {:?}", wp, wq); - if wp.is_some() && wq.is_some() { - println!("\nBREAKING {:?} {:?}", wp, wq); break; } } @@ -850,14 +683,11 @@ fn canonical_ordering( let mut nbr = wp.unwrap(); while Some(nbr) != wq { let next_nbr = planar_emb.get_edge_weight(v, nbr, false).unwrap(); - println!("nbr {:?} next_nbr {:?} wp {:?} wq {:?} ", nbr, next_nbr, wp, wq); wp_wq.push(Some(next_nbr)); outer_face_cw_nbr.insert(nbr, next_nbr); outer_face_ccw_nbr.insert(next_nbr, nbr); nbr = next_nbr; - println!("of_cw {:?} of_ccw {:?} next_nbr {:?} wp_wq {:?} ", outer_face_cw_nbr, outer_face_ccw_nbr, next_nbr, wp_wq); } - println!("after while wp_wq{:?}", wp_wq); if wp_wq.len() == 2 { let wp_un = wp.unwrap(); if chords.contains_key(&wp_un) { @@ -904,17 +734,7 @@ fn canonical_ordering( } } } - let x = wp_wq.clone(); canon_order[k] = (Some(v), wp_wq); - println!("\ncanon order k {:?} v {:?} wp_wq {:?}", k, v, x); - } - // let mut order: Vec<(Option, Vec>)> = vec![]; - // // vec![(None, vec![]); planar_emb.embedding.node_count()]; - // order.push((Some(NodeIndex::new(0)), vec![])); - // order.push((Some(NodeIndex::new(1)), vec![])); - // order.push((Some(NodeIndex::new(2)), vec![Some(NodeIndex::new(0)), Some(NodeIndex::new(1))])); - // order.push((Some(NodeIndex::new(3)), vec![Some(NodeIndex::new(0)), Some(NodeIndex::new(2))])); - // order.push((Some(NodeIndex::new(4)), vec![Some(NodeIndex::new(3)), Some(NodeIndex::new(2))])); - println!("\nCANON ORDER {:?}", canon_order); + } canon_order } From 8d5227be71e612f4117d38181a81426c068ef783 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sat, 23 Jul 2022 16:42:12 -0700 Subject: [PATCH 21/37] Debugging down to canonical --- retworkx-core/src/planar/lr_planar.rs | 2 +- src/layout/embedding.rs | 146 +++++++++++++++++++------- src/layout/planar.rs | 3 + 3 files changed, 113 insertions(+), 38 deletions(-) diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index 5a11562e7..77b80f57d 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -194,7 +194,7 @@ where } } -#[derive(Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum Sign { Plus, Minus, diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index 2b62e12c4..92d4fffb7 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -89,17 +89,11 @@ impl PlanarEmbedding { let start_node = first_nbr.unwrap(); nbrs.push(start_node); - let mut node = self.get_edge_weight(v, start_node, true); - - if let Some(mut current_node) = node { - while start_node != current_node { - nbrs.push(current_node); - node = self.get_edge_weight(v, current_node, true); - current_node = match node { - Some(node) => node, - None => break, - }; - } + let mut current_node = self.get_edge_weight(v, start_node, true).unwrap(); + + while start_node != current_node { + nbrs.push(current_node); + current_node = self.get_edge_weight(v, current_node, true).unwrap(); } nbrs } @@ -125,14 +119,15 @@ impl PlanarEmbedding { if self.embedding.find_edge(start_node, ref_nbr_node).is_none() { println!("NO REF NBR in ADD CW {:?} {:?}", start_node, ref_nbr_node); } - let cw_ref = self.get_edge_weight(start_node, ref_nbr_node, true); - if let Some(cw_ref_node) = cw_ref { + let cw_ref = self.get_edge_weight(start_node, ref_nbr_node, true).unwrap(); + println!("In add half cw start {:?} end {:?} ref {:?} cw ref {:?}", start_node, end_node, ref_nbr_node, cw_ref); + //if let Some(cw_ref_node) = cw_ref { // Alter half-edge data structures - self.update_edge_weight(start_node, ref_nbr_node, end_node, true); - self.update_edge_weight(start_node, end_node, cw_ref_node, true); - self.update_edge_weight(start_node, cw_ref_node, end_node, false); - self.update_edge_weight(start_node, end_node, ref_nbr_node, false); - } + self.update_edge_weight(start_node, ref_nbr_node, end_node, true); + self.update_edge_weight(start_node, end_node, cw_ref, true); + self.update_edge_weight(start_node, cw_ref, end_node, false); + self.update_edge_weight(start_node, end_node, ref_nbr_node, false); + //} } fn add_half_edge_ccw( @@ -153,6 +148,7 @@ impl PlanarEmbedding { let ccw_ref_node = self.get_edge_weight(start_node, ref_nbr_node, false); self.add_half_edge_cw(start_node, end_node, ccw_ref_node); if ref_nbr == self.embedding[start_node].first_nbr { + println!("add half ccw in ref=first start {:?} end {:?} ref {:?} first_nbr {:?}", start_node, end_node, ref_nbr, self.embedding[start_node].first_nbr); // Update first neighbor self.embedding[start_node].first_nbr = Some(end_node); } @@ -171,26 +167,35 @@ impl PlanarEmbedding { self.add_half_edge_ccw(start_node, end_node, ref_node); } - fn next_face_half_edge(&self, v: NodeIndex, w: NodeIndex) -> (NodeIndex, NodeIndex) { + fn next_face_half_edge(&mut self, v: NodeIndex, w: NodeIndex) -> (NodeIndex, NodeIndex) { let new_node = self.get_edge_weight(w, v, false); + let cw_weight = CwCcw::::default(); + if new_node.is_none() { + println!("\nFOUND EDGE NONE"); + self.embedding.add_edge(w, v, cw_weight); + } // FIX THIS // RAISE? // - if new_node.is_none() { - println!("NEW NODE NONE in next_face {:?} {:?} {:?}", new_node, v, w); - panic!("HELP!"); //return (w, v); - } + // if new_node.is_none() { + // println!("NEW NODE NONE in next_face {:?} {:?} {:?}", new_node, v, w); + // panic!("HELP!"); //return (w, v); + // } (w, new_node.unwrap()) } fn update_edge_weight(&mut self, v: NodeIndex, w: NodeIndex, new_node: NodeIndex, cw: bool) { let found_edge = self.embedding.find_edge(v, w); + let cw_weight = CwCcw::::default(); if found_edge.is_none() { - return; + println!("\nFOUND EDGE NONE"); + self.embedding.add_edge(v, w, cw_weight); } - let found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()); //.unwrap(); + let mut found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()); //.unwrap(); + let mut cw_weight2 = CwCcw::::default(); if found_weight.is_none() { - return; + println!("\nFOUND Weight NONE"); + found_weight = Some(&mut cw_weight2); } if cw { found_weight.unwrap().cw = Some(new_node); @@ -204,12 +209,14 @@ impl PlanarEmbedding { if found_edge.is_none() { // RAISE? println!("GET EDGE find edge is none {:?}", found_edge); + panic!("GET EDGE EDGE {:?} {:?}", v, w); return None; } let found_weight = self.embedding.edge_weight(found_edge.unwrap()); if found_weight.is_none() { // RAISE? println!("GET EDGE Weight is none {:?}", found_weight); + panic!("GET EDGE WT {:?} {:?}", v, w); return None; } if cw { @@ -241,12 +248,15 @@ pub fn create_embedding( let first_nbr = FirstNbr::::default(); planar_emb.embedding.add_node(first_nbr); } + println!("ordered_adjs {:?}", ordered_adjs); // Sort the adjacency list using nesting depth as sort order for (v, adjs) in ordered_adjs.iter_mut().enumerate() { adjs.par_sort_by_key(|x| lr_state.nesting_depth[&(NodeIndex::new(v), *x)]); } + println!("sorted 1 ordered_adjs {:?}", ordered_adjs); + println!("\nnest 1 {:?}", lr_state.nesting_depth); for v in lr_state.dir_graph.node_indices() { // Change the sign for nesting_depth for e in lr_state.dir_graph.edges(v) { @@ -262,16 +272,21 @@ pub fn create_embedding( lr_state.nesting_depth.insert(edge, signed_depth * signed_side); } } + println!("nest 2 {:?}", lr_state.nesting_depth); + println!("\neref {:?}", lr_state.eref); + println!("side {:?}", lr_state.side); // Sort the adjacency list using nesting depth as sort order for (v, adjs) in ordered_adjs.iter_mut().enumerate() { adjs.par_sort_by_key(|x| lr_state.nesting_depth[&(NodeIndex::new(v), *x)]); } + println!("ordered_adjs 2 {:?}", ordered_adjs); // Add the initial half edge cw to the embedding using the ordered adjacency list for v in lr_state.dir_graph.node_indices() { let mut prev_node: Option = None; for w in &ordered_adjs[v.index()] { + println!("add_half_edge cw v {:?} *w {:?} prev {:?}", v, *w, prev_node); planar_emb.add_half_edge_cw(v, *w, prev_node); prev_node = Some(*w) } @@ -320,15 +335,30 @@ pub fn create_embedding( ) -> Sign { // Resolve the relative side of an edge to the absolute side. + let mut temp_side: Sign; + if side.contains_key(&edge) { + temp_side = side[&edge].clone(); + } + else { + temp_side = Sign::Plus; + } + if eref.contains_key(&edge) { - if side[&edge].clone() == sign(eref[&edge].clone(), eref, side) { - *side.get_mut(&edge).unwrap() = Sign::Plus; + if temp_side == sign(eref[&edge].clone(), eref, side) { + //*side.get_mut(&edge).unwrap() = Sign::Plus; + side.insert(edge, Sign::Plus); } else { - *side.get_mut(&edge).unwrap() = Sign::Minus; + //*side.get_mut(&edge).unwrap() = Sign::Minus; + side.insert(edge, Sign::Minus); } eref.remove(&edge); } - side[&edge] + if side.contains_key(&edge) { + side[&edge] + } + else { + Sign::Plus + } } } @@ -349,6 +379,8 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { let node_list = canonical_ordering(planar_emb, outer_face); + println!("Node_list {:?}", node_list); + let mut right_t_child = HashMap::, Option>::new(); let mut left_t_child = HashMap::, Option>::new(); let mut delta_x = HashMap::, isize>::new(); @@ -473,9 +505,11 @@ fn triangulate_embedding( fully_triangulate: bool, ) -> Vec { let component_sets = connected_components(&planar_emb.embedding); + println!("connected {:?}", component_sets); for i in 0..(component_sets.len() - 1) { - let v1 = component_sets[i].iter().next().unwrap(); - let v2 = component_sets[i + 1].iter().next().unwrap(); + let v1 = component_sets[i].iter().min().unwrap(); + let v2 = component_sets[i + 1].iter().min().unwrap(); + println!("v1 {:?} v2 {:?}", *v1, *v2); planar_emb.connect_components(*v1, *v2); } let mut outer_face = vec![]; @@ -484,7 +518,9 @@ fn triangulate_embedding( for v in planar_emb.embedding.node_indices() { for w in planar_emb.neighbors_cw_order(v) { + println!("before by v {:?} w {:?}", v, w); let new_face = make_bi_connected(planar_emb, v, w, &mut edges_counted); + println!("new_face {:?}", new_face); if new_face.len() > 0 { face_list.push(new_face.clone()); if new_face.len() > outer_face.len() { @@ -494,6 +530,8 @@ fn triangulate_embedding( } } + let fp = face_list.clone(); + println!("\nFACELIST 1 {:?}", fp); for face in face_list { if face != outer_face || fully_triangulate { triangulate_face(planar_emb, face[0], face[1]); @@ -505,6 +543,7 @@ fn triangulate_embedding( let v3 = planar_emb.get_edge_weight(v2, v1, false); outer_face = vec![v1, v2, v3.unwrap()]; } + println!("\nFACELIST 2 {:?}", fp); outer_face } @@ -524,13 +563,16 @@ fn make_bi_connected( let mut face_list: Vec = vec![start_node]; let (_, mut v3) = planar_emb.next_face_half_edge(v1, v2); + println!("\n bi before start {:?} out {:?} v1 {:?} v2 {:?} v3 {:?}", start_node, out_node, v1, v2, v3); while v2 != start_node || v3 != out_node { + println!("\n bi while start {:?} out {:?} v1 {:?} v2 {:?} v3 {:?}", start_node, out_node, v1, v2, v3); if v1 == v2 { // RAISE? println!("BICONNECT V1==V2 should raise"); } if face_list.contains(&v2) { + println!("contains v2 {:?}", v2); planar_emb.add_half_edge_cw(v1, v3, Some(v2)); planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); edges_counted.insert((v2, v3)); @@ -577,6 +619,7 @@ fn canonical_ordering( for node in outer_face.iter() { ready_to_pick.insert(*node); } + println!("outer_face {:?}", outer_face); let mut outer_face_cw_nbr: HashMap = HashMap::with_capacity(outer_face.len()); @@ -596,6 +639,7 @@ fn canonical_ordering( prev_nbr = *v; } + println!("outer cw {:?} outer_ccw {:?}", outer_face_cw_nbr, outer_face_ccw_nbr); fn is_outer_face_nbr( x: NodeIndex, y: NodeIndex, @@ -638,13 +682,20 @@ fn canonical_ordering( let mut canon_order: Vec<(Option, Vec>)> = vec![(None, vec![]); planar_emb.embedding.node_count()]; + println!("ready marked chords {:?} {:?} {:?}", ready_to_pick, marked_nodes, chords); canon_order[0] = (Some(v1), vec![]); canon_order[1] = (Some(v2), vec![]); ready_to_pick.remove(&v1); ready_to_pick.remove(&v2); + println!("ready after {:?}", ready_to_pick); + + for v in (2..(planar_emb.embedding.node_count())).rev() { + println!("v in rev node count to to {:?}", v); + } for k in (2..(planar_emb.embedding.node_count())).rev() { let v_try = ready_to_pick.iter().next(); + println!("v_try {:?}", v_try); if v_try.is_none() { // RAISE? continue; @@ -653,8 +704,12 @@ fn canonical_ordering( ready_to_pick.remove(&v); marked_nodes.insert(v); + for n in planar_emb.neighbors_cw_order(v) { + println!("nbrs v {:?} n {:?}", v, n); + } let mut wp: Option = None; let mut wq: Option = None; + println!("2 ready after {:?}", ready_to_pick); for nbr in planar_emb.neighbors_cw_order(v).iter() { if marked_nodes.contains(nbr) { continue; @@ -665,6 +720,7 @@ fn canonical_ordering( } else if *nbr == v2 { wq = Some(v2); } else { + println!("of_cw_nbr {:?}", outer_face_cw_nbr[nbr]); if outer_face_cw_nbr[nbr] == v { wp = Some(*nbr); } else { @@ -676,9 +732,9 @@ fn canonical_ordering( break; } } - + println!("3 ready {:?} wp {:?} wq {:?}", ready_to_pick, wp, wq); let mut wp_wq = vec![]; - if wp.is_some() { + if wp.is_some() && wq.is_some() { wp_wq = vec![wp]; let mut nbr = wp.unwrap(); while Some(nbr) != wq { @@ -688,6 +744,7 @@ fn canonical_ordering( outer_face_ccw_nbr.insert(next_nbr, nbr); nbr = next_nbr; } + println!("4 ready {:?} wp_wq {:?}", ready_to_pick, wp_wq); if wp_wq.len() == 2 { let wp_un = wp.unwrap(); if chords.contains_key(&wp_un) { @@ -696,19 +753,27 @@ fn canonical_ordering( if chords[&wp_un] == 0 { ready_to_pick.insert(wp_un); } - let wq_un = wq.unwrap(); + } + let wq_un = wq.unwrap(); + if chords.contains_key(&wq_un) { let chords_wq = chords[&wq_un].clone() - 1; chords.insert(wq_un, chords_wq); if chords[&wq_un] == 0 { ready_to_pick.insert(wq_un); } } + println!("5 ready {:?} wp_wq {:?}", ready_to_pick, wp_wq); } else { let mut new_face_nodes: HashSet = HashSet::new(); if wp_wq.len() > 1 { + println!("6 ready {:?} wp_wq {:?}", ready_to_pick, wp_wq); for w in &wp_wq[1..(wp_wq.len() - 1)] { let w_un = w.unwrap(); new_face_nodes.insert(w_un); + } + for w in &new_face_nodes { + let w_un = *w; + ready_to_pick.insert(w_un); for nbr in planar_emb.neighbors_cw_order(w_un) { if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) && !is_outer_face_nbr( @@ -723,8 +788,12 @@ fn canonical_ordering( chords.insert(w_un, chords_w); ready_to_pick.remove(&w_un); if !new_face_nodes.contains(&nbr) { - let chords_nbr = chords[&nbr].clone() + 1; - chords.insert(nbr, chords_nbr); + let mut chords_plus = 1; + if chords.contains_key(&nbr) { + chords_plus = chords[&nbr] + 1 + } + //let chords_nbr = chords[&nbr].clone() + 1; + chords.insert(nbr, chords_plus); ready_to_pick.remove(&nbr); } } @@ -732,8 +801,11 @@ fn canonical_ordering( } } } + println!("7 ready {:?} wp_wq {:?}", ready_to_pick, wp_wq); + } } + println!("ready end v k, wp_wq {:?} {:?} {:?} {:?}", ready_to_pick, v, k, wp_wq); canon_order[k] = (Some(v), wp_wq); } canon_order diff --git a/src/layout/planar.rs b/src/layout/planar.rs index 61a1c3268..b6249da9a 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -40,6 +40,9 @@ pub fn planar_layout( // First create the graph embedding create_embedding(&mut planar_emb, &mut lr_state); + for node in planar_emb.embedding.node_indices() { + println!("node {:?} data {:?}", node, planar_emb.embedding[node]); + } // Then convert the embedding to position coordinates. let mut pos = embedding_to_pos(&mut planar_emb); From 374dfab910eb30b2a0ac18dd3d3b7062bf52262e Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sun, 24 Jul 2022 10:31:57 -0700 Subject: [PATCH 22/37] Final chords fix --- src/layout/embedding.rs | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index 92d4fffb7..f59899b39 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -711,6 +711,7 @@ fn canonical_ordering( let mut wq: Option = None; println!("2 ready after {:?}", ready_to_pick); for nbr in planar_emb.neighbors_cw_order(v).iter() { + println!("top of while ready {:?} v {:?} v1 {:?} v2 {:?} wp {:?} wq {:?} chords{:?}", ready_to_pick, v, v1, v2, wp, wq, chords); if marked_nodes.contains(nbr) { continue; } @@ -732,7 +733,7 @@ fn canonical_ordering( break; } } - println!("3 ready {:?} wp {:?} wq {:?}", ready_to_pick, wp, wq); + println!("3 ready {:?} wp {:?} wq {:?} chords {:?}", ready_to_pick, wp, wq, chords); let mut wp_wq = vec![]; if wp.is_some() && wq.is_some() { wp_wq = vec![wp]; @@ -744,7 +745,7 @@ fn canonical_ordering( outer_face_ccw_nbr.insert(next_nbr, nbr); nbr = next_nbr; } - println!("4 ready {:?} wp_wq {:?}", ready_to_pick, wp_wq); + println!("4 ready {:?} wp_wq {:?} chords {:?}", ready_to_pick, wp_wq, chords); if wp_wq.len() == 2 { let wp_un = wp.unwrap(); if chords.contains_key(&wp_un) { @@ -762,18 +763,20 @@ fn canonical_ordering( ready_to_pick.insert(wq_un); } } - println!("5 ready {:?} wp_wq {:?}", ready_to_pick, wp_wq); + println!("5 ready {:?} wp_wq {:?} chords {:?}", ready_to_pick, wp_wq, chords); } else { let mut new_face_nodes: HashSet = HashSet::new(); if wp_wq.len() > 1 { - println!("6 ready {:?} wp_wq {:?}", ready_to_pick, wp_wq); + println!("6 ready {:?} wp_wq {:?} chords {:?}", ready_to_pick, wp_wq, chords); for w in &wp_wq[1..(wp_wq.len() - 1)] { let w_un = w.unwrap(); new_face_nodes.insert(w_un); } + println!("new face {:?}", new_face_nodes); for w in &new_face_nodes { let w_un = *w; ready_to_pick.insert(w_un); + println!("6.5 ready {:?} w_un {:?}", ready_to_pick, w_un); for nbr in planar_emb.neighbors_cw_order(w_un) { if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) && !is_outer_face_nbr( @@ -783,25 +786,27 @@ fn canonical_ordering( &outer_face_ccw_nbr, ) { + println!("is on nbr {:?}", nbr); + let mut chords_w_plus = 1; if chords.contains_key(&w_un) { - let chords_w = chords[&w_un].clone() + 1; - chords.insert(w_un, chords_w); - ready_to_pick.remove(&w_un); - if !new_face_nodes.contains(&nbr) { - let mut chords_plus = 1; - if chords.contains_key(&nbr) { - chords_plus = chords[&nbr] + 1 - } - //let chords_nbr = chords[&nbr].clone() + 1; - chords.insert(nbr, chords_plus); - ready_to_pick.remove(&nbr); + chords_w_plus = chords[&w_un].clone() + 1; + } + chords.insert(w_un, chords_w_plus); + ready_to_pick.remove(&w_un); + if !new_face_nodes.contains(&nbr) { + let mut chords_plus = 1; + if chords.contains_key(&nbr) { + chords_plus = chords[&nbr] + 1 } + //let chords_nbr = chords[&nbr].clone() + 1; + chords.insert(nbr, chords_plus); + ready_to_pick.remove(&nbr); } } } } } - println!("7 ready {:?} wp_wq {:?}", ready_to_pick, wp_wq); + println!("7 ready {:?} wp_wq {:?} chords {:?}", ready_to_pick, wp_wq, chords); } } From 39a5c3e08fb4eebcc33adf6731db7fb38206535c Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sun, 24 Jul 2022 13:44:36 -0700 Subject: [PATCH 23/37] Cleanup (working all but mem issues) --- retworkx-core/src/planar/lr_planar.rs | 2 - src/layout/embedding.rs | 101 ++++---------------------- src/layout/planar.rs | 4 +- 3 files changed, 16 insertions(+), 91 deletions(-) diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index 77b80f57d..3d6006547 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -293,7 +293,6 @@ where DfsEvent::TreeEdge(v, w, _) => { let v_dir = NodeIndex::new(self.graph.to_index(v)); let w_dir = NodeIndex::new(self.graph.to_index(w)); - println!("TREE v_dir {:?} w_dir {:?}", v_dir, w_dir); if !self.dir_graph.contains_edge(v_dir, w_dir) { self.dir_graph.add_edge(v_dir, w_dir, ()); } @@ -312,7 +311,6 @@ where if Some(&(w, v)) != self.eparent.get(&v) { let v_dir = NodeIndex::new(self.graph.to_index(v)); let w_dir = NodeIndex::new(self.graph.to_index(w)); - println!("BACK v_dir {:?} w_dir {:?}", v_dir, w_dir); if !self.dir_graph.contains_edge(v_dir, w_dir) { self.dir_graph.add_edge(v_dir, w_dir, ()); } diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index f59899b39..16aa0d078 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -120,14 +120,11 @@ impl PlanarEmbedding { println!("NO REF NBR in ADD CW {:?} {:?}", start_node, ref_nbr_node); } let cw_ref = self.get_edge_weight(start_node, ref_nbr_node, true).unwrap(); - println!("In add half cw start {:?} end {:?} ref {:?} cw ref {:?}", start_node, end_node, ref_nbr_node, cw_ref); - //if let Some(cw_ref_node) = cw_ref { - // Alter half-edge data structures + // Alter half-edge data structures self.update_edge_weight(start_node, ref_nbr_node, end_node, true); self.update_edge_weight(start_node, end_node, cw_ref, true); self.update_edge_weight(start_node, cw_ref, end_node, false); self.update_edge_weight(start_node, end_node, ref_nbr_node, false); - //} } fn add_half_edge_ccw( @@ -148,7 +145,6 @@ impl PlanarEmbedding { let ccw_ref_node = self.get_edge_weight(start_node, ref_nbr_node, false); self.add_half_edge_cw(start_node, end_node, ccw_ref_node); if ref_nbr == self.embedding[start_node].first_nbr { - println!("add half ccw in ref=first start {:?} end {:?} ref {:?} first_nbr {:?}", start_node, end_node, ref_nbr, self.embedding[start_node].first_nbr); // Update first neighbor self.embedding[start_node].first_nbr = Some(end_node); } @@ -169,18 +165,10 @@ impl PlanarEmbedding { fn next_face_half_edge(&mut self, v: NodeIndex, w: NodeIndex) -> (NodeIndex, NodeIndex) { let new_node = self.get_edge_weight(w, v, false); - let cw_weight = CwCcw::::default(); if new_node.is_none() { - println!("\nFOUND EDGE NONE"); - self.embedding.add_edge(w, v, cw_weight); - } - // FIX THIS - // RAISE? - // - // if new_node.is_none() { - // println!("NEW NODE NONE in next_face {:?} {:?} {:?}", new_node, v, w); - // panic!("HELP!"); //return (w, v); - // } + // RAISE? + return (w, v); + } (w, new_node.unwrap()) } @@ -188,13 +176,13 @@ impl PlanarEmbedding { let found_edge = self.embedding.find_edge(v, w); let cw_weight = CwCcw::::default(); if found_edge.is_none() { - println!("\nFOUND EDGE NONE"); + // RAISE? self.embedding.add_edge(v, w, cw_weight); } let mut found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()); //.unwrap(); let mut cw_weight2 = CwCcw::::default(); if found_weight.is_none() { - println!("\nFOUND Weight NONE"); + // RAISE? found_weight = Some(&mut cw_weight2); } if cw { @@ -208,15 +196,11 @@ impl PlanarEmbedding { let found_edge = self.embedding.find_edge(v, w); if found_edge.is_none() { // RAISE? - println!("GET EDGE find edge is none {:?}", found_edge); - panic!("GET EDGE EDGE {:?} {:?}", v, w); return None; } let found_weight = self.embedding.edge_weight(found_edge.unwrap()); if found_weight.is_none() { // RAISE? - println!("GET EDGE Weight is none {:?}", found_weight); - panic!("GET EDGE WT {:?} {:?}", v, w); return None; } if cw { @@ -248,15 +232,10 @@ pub fn create_embedding( let first_nbr = FirstNbr::::default(); planar_emb.embedding.add_node(first_nbr); } - println!("ordered_adjs {:?}", ordered_adjs); - // Sort the adjacency list using nesting depth as sort order for (v, adjs) in ordered_adjs.iter_mut().enumerate() { adjs.par_sort_by_key(|x| lr_state.nesting_depth[&(NodeIndex::new(v), *x)]); } - println!("sorted 1 ordered_adjs {:?}", ordered_adjs); - - println!("\nnest 1 {:?}", lr_state.nesting_depth); for v in lr_state.dir_graph.node_indices() { // Change the sign for nesting_depth for e in lr_state.dir_graph.edges(v) { @@ -272,26 +251,18 @@ pub fn create_embedding( lr_state.nesting_depth.insert(edge, signed_depth * signed_side); } } - println!("nest 2 {:?}", lr_state.nesting_depth); - - println!("\neref {:?}", lr_state.eref); - println!("side {:?}", lr_state.side); // Sort the adjacency list using nesting depth as sort order for (v, adjs) in ordered_adjs.iter_mut().enumerate() { adjs.par_sort_by_key(|x| lr_state.nesting_depth[&(NodeIndex::new(v), *x)]); } - println!("ordered_adjs 2 {:?}", ordered_adjs); - // Add the initial half edge cw to the embedding using the ordered adjacency list for v in lr_state.dir_graph.node_indices() { let mut prev_node: Option = None; for w in &ordered_adjs[v.index()] { - println!("add_half_edge cw v {:?} *w {:?} prev {:?}", v, *w, prev_node); planar_emb.add_half_edge_cw(v, *w, prev_node); prev_node = Some(*w) } } - // Start the DFS traversal for the embedding let mut left_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); let mut right_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); @@ -335,20 +306,18 @@ pub fn create_embedding( ) -> Sign { // Resolve the relative side of an edge to the absolute side. - let mut temp_side: Sign; + // Create a temp of Plus in case edge not in side. + let temp_side: Sign; if side.contains_key(&edge) { temp_side = side[&edge].clone(); } else { temp_side = Sign::Plus; } - if eref.contains_key(&edge) { if temp_side == sign(eref[&edge].clone(), eref, side) { - //*side.get_mut(&edge).unwrap() = Sign::Plus; side.insert(edge, Sign::Plus); } else { - //*side.get_mut(&edge).unwrap() = Sign::Minus; side.insert(edge, Sign::Minus); } eref.remove(&edge); @@ -379,8 +348,6 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { let node_list = canonical_ordering(planar_emb, outer_face); - println!("Node_list {:?}", node_list); - let mut right_t_child = HashMap::, Option>::new(); let mut left_t_child = HashMap::, Option>::new(); let mut delta_x = HashMap::, isize>::new(); @@ -505,11 +472,9 @@ fn triangulate_embedding( fully_triangulate: bool, ) -> Vec { let component_sets = connected_components(&planar_emb.embedding); - println!("connected {:?}", component_sets); for i in 0..(component_sets.len() - 1) { let v1 = component_sets[i].iter().min().unwrap(); let v2 = component_sets[i + 1].iter().min().unwrap(); - println!("v1 {:?} v2 {:?}", *v1, *v2); planar_emb.connect_components(*v1, *v2); } let mut outer_face = vec![]; @@ -518,9 +483,7 @@ fn triangulate_embedding( for v in planar_emb.embedding.node_indices() { for w in planar_emb.neighbors_cw_order(v) { - println!("before by v {:?} w {:?}", v, w); - let new_face = make_bi_connected(planar_emb, v, w, &mut edges_counted); - println!("new_face {:?}", new_face); + let new_face = make_bi_connected(planar_emb, &v, &w, &mut edges_counted); if new_face.len() > 0 { face_list.push(new_face.clone()); if new_face.len() > outer_face.len() { @@ -529,9 +492,6 @@ fn triangulate_embedding( } } } - - let fp = face_list.clone(); - println!("\nFACELIST 1 {:?}", fp); for face in face_list { if face != outer_face || fully_triangulate { triangulate_face(planar_emb, face[0], face[1]); @@ -543,36 +503,32 @@ fn triangulate_embedding( let v3 = planar_emb.get_edge_weight(v2, v1, false); outer_face = vec![v1, v2, v3.unwrap()]; } - println!("\nFACELIST 2 {:?}", fp); outer_face } fn make_bi_connected( planar_emb: &mut PlanarEmbedding, - start_node: NodeIndex, - out_node: NodeIndex, + start_node: &NodeIndex, + out_node: &NodeIndex, edges_counted: &mut HashSet<(NodeIndex, NodeIndex)>, ) -> Vec { // If edge already counted return - if edges_counted.contains(&(start_node, out_node)) { + if edges_counted.contains(&(*start_node, *out_node)) { return vec![]; } - edges_counted.insert((start_node, out_node)); + edges_counted.insert((*start_node, *out_node)); let mut v1 = start_node.clone(); let mut v2 = out_node.clone(); - let mut face_list: Vec = vec![start_node]; + let mut face_list: Vec = vec![*start_node]; let (_, mut v3) = planar_emb.next_face_half_edge(v1, v2); - println!("\n bi before start {:?} out {:?} v1 {:?} v2 {:?} v3 {:?}", start_node, out_node, v1, v2, v3); - while v2 != start_node || v3 != out_node { - println!("\n bi while start {:?} out {:?} v1 {:?} v2 {:?} v3 {:?}", start_node, out_node, v1, v2, v3); + while v2 != *start_node || v3 != *out_node { if v1 == v2 { // RAISE? println!("BICONNECT V1==V2 should raise"); } if face_list.contains(&v2) { - println!("contains v2 {:?}", v2); planar_emb.add_half_edge_cw(v1, v3, Some(v2)); planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); edges_counted.insert((v2, v3)); @@ -619,8 +575,6 @@ fn canonical_ordering( for node in outer_face.iter() { ready_to_pick.insert(*node); } - println!("outer_face {:?}", outer_face); - let mut outer_face_cw_nbr: HashMap = HashMap::with_capacity(outer_face.len()); let mut outer_face_ccw_nbr: HashMap = @@ -639,7 +593,6 @@ fn canonical_ordering( prev_nbr = *v; } - println!("outer cw {:?} outer_ccw {:?}", outer_face_cw_nbr, outer_face_ccw_nbr); fn is_outer_face_nbr( x: NodeIndex, y: NodeIndex, @@ -682,20 +635,13 @@ fn canonical_ordering( let mut canon_order: Vec<(Option, Vec>)> = vec![(None, vec![]); planar_emb.embedding.node_count()]; - println!("ready marked chords {:?} {:?} {:?}", ready_to_pick, marked_nodes, chords); canon_order[0] = (Some(v1), vec![]); canon_order[1] = (Some(v2), vec![]); ready_to_pick.remove(&v1); ready_to_pick.remove(&v2); - println!("ready after {:?}", ready_to_pick); - - for v in (2..(planar_emb.embedding.node_count())).rev() { - println!("v in rev node count to to {:?}", v); - } for k in (2..(planar_emb.embedding.node_count())).rev() { let v_try = ready_to_pick.iter().next(); - println!("v_try {:?}", v_try); if v_try.is_none() { // RAISE? continue; @@ -704,14 +650,9 @@ fn canonical_ordering( ready_to_pick.remove(&v); marked_nodes.insert(v); - for n in planar_emb.neighbors_cw_order(v) { - println!("nbrs v {:?} n {:?}", v, n); - } let mut wp: Option = None; let mut wq: Option = None; - println!("2 ready after {:?}", ready_to_pick); for nbr in planar_emb.neighbors_cw_order(v).iter() { - println!("top of while ready {:?} v {:?} v1 {:?} v2 {:?} wp {:?} wq {:?} chords{:?}", ready_to_pick, v, v1, v2, wp, wq, chords); if marked_nodes.contains(nbr) { continue; } @@ -721,7 +662,6 @@ fn canonical_ordering( } else if *nbr == v2 { wq = Some(v2); } else { - println!("of_cw_nbr {:?}", outer_face_cw_nbr[nbr]); if outer_face_cw_nbr[nbr] == v { wp = Some(*nbr); } else { @@ -733,7 +673,6 @@ fn canonical_ordering( break; } } - println!("3 ready {:?} wp {:?} wq {:?} chords {:?}", ready_to_pick, wp, wq, chords); let mut wp_wq = vec![]; if wp.is_some() && wq.is_some() { wp_wq = vec![wp]; @@ -745,7 +684,6 @@ fn canonical_ordering( outer_face_ccw_nbr.insert(next_nbr, nbr); nbr = next_nbr; } - println!("4 ready {:?} wp_wq {:?} chords {:?}", ready_to_pick, wp_wq, chords); if wp_wq.len() == 2 { let wp_un = wp.unwrap(); if chords.contains_key(&wp_un) { @@ -763,20 +701,16 @@ fn canonical_ordering( ready_to_pick.insert(wq_un); } } - println!("5 ready {:?} wp_wq {:?} chords {:?}", ready_to_pick, wp_wq, chords); } else { let mut new_face_nodes: HashSet = HashSet::new(); if wp_wq.len() > 1 { - println!("6 ready {:?} wp_wq {:?} chords {:?}", ready_to_pick, wp_wq, chords); for w in &wp_wq[1..(wp_wq.len() - 1)] { let w_un = w.unwrap(); new_face_nodes.insert(w_un); } - println!("new face {:?}", new_face_nodes); for w in &new_face_nodes { let w_un = *w; ready_to_pick.insert(w_un); - println!("6.5 ready {:?} w_un {:?}", ready_to_pick, w_un); for nbr in planar_emb.neighbors_cw_order(w_un) { if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) && !is_outer_face_nbr( @@ -786,7 +720,6 @@ fn canonical_ordering( &outer_face_ccw_nbr, ) { - println!("is on nbr {:?}", nbr); let mut chords_w_plus = 1; if chords.contains_key(&w_un) { chords_w_plus = chords[&w_un].clone() + 1; @@ -798,7 +731,6 @@ fn canonical_ordering( if chords.contains_key(&nbr) { chords_plus = chords[&nbr] + 1 } - //let chords_nbr = chords[&nbr].clone() + 1; chords.insert(nbr, chords_plus); ready_to_pick.remove(&nbr); } @@ -806,11 +738,8 @@ fn canonical_ordering( } } } - println!("7 ready {:?} wp_wq {:?} chords {:?}", ready_to_pick, wp_wq, chords); - } } - println!("ready end v k, wp_wq {:?} {:?} {:?} {:?}", ready_to_pick, v, k, wp_wq); canon_order[k] = (Some(v), wp_wq); } canon_order diff --git a/src/layout/planar.rs b/src/layout/planar.rs index b6249da9a..25263e3f9 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -28,6 +28,7 @@ pub fn planar_layout( // If not planar, return an empty pos_map if !its_planar { + // RAISE? return Pos2DMapping { pos_map: DictMap::new(), }; @@ -40,9 +41,6 @@ pub fn planar_layout( // First create the graph embedding create_embedding(&mut planar_emb, &mut lr_state); - for node in planar_emb.embedding.node_indices() { - println!("node {:?} data {:?}", node, planar_emb.embedding[node]); - } // Then convert the embedding to position coordinates. let mut pos = embedding_to_pos(&mut planar_emb); From 736243e151fe1013a2eee7c46f05df28fcbb149e Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Tue, 26 Jul 2022 14:02:54 -0700 Subject: [PATCH 24/37] Fix sign bug and IndexSet for ready_to_pick --- src/layout/embedding.rs | 56 ++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index 16aa0d078..6870c0c90 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -1,5 +1,6 @@ use hashbrown::hash_map::HashMap; use hashbrown::HashSet; +use indexmap::set::IndexSet; use petgraph::graph::Graph; use petgraph::prelude::*; use petgraph::visit::NodeIndexable; @@ -115,11 +116,13 @@ impl PlanarEmbedding { } // if ref_nbr not in self[start_node] error let ref_nbr_node = ref_nbr.unwrap(); - // DEBUG - RAISE? + // RAISE? if self.embedding.find_edge(start_node, ref_nbr_node).is_none() { println!("NO REF NBR in ADD CW {:?} {:?}", start_node, ref_nbr_node); } - let cw_ref = self.get_edge_weight(start_node, ref_nbr_node, true).unwrap(); + let cw_ref = self + .get_edge_weight(start_node, ref_nbr_node, true) + .unwrap(); // Alter half-edge data structures self.update_edge_weight(start_node, ref_nbr_node, end_node, true); self.update_edge_weight(start_node, end_node, cw_ref, true); @@ -179,7 +182,7 @@ impl PlanarEmbedding { // RAISE? self.embedding.add_edge(v, w, cw_weight); } - let mut found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()); //.unwrap(); + let mut found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()); let mut cw_weight2 = CwCcw::::default(); if found_weight.is_none() { // RAISE? @@ -241,17 +244,17 @@ pub fn create_embedding( for e in lr_state.dir_graph.edges(v) { let edge: (NodeIndex, NodeIndex) = (e.source(), e.target()); let signed_depth: isize = lr_state.nesting_depth[&edge] as isize; - let signed_side = if lr_state.side.contains_key(&edge) - && sign(edge, &mut lr_state.eref, &mut lr_state.side) == Sign::Minus - { + let signed_side = if sign(edge, &mut lr_state.eref, &mut lr_state.side) == Sign::Minus { -1 } else { 1 }; - lr_state.nesting_depth.insert(edge, signed_depth * signed_side); + lr_state + .nesting_depth + .insert(edge, signed_depth * signed_side); } } - // Sort the adjacency list using nesting depth as sort order + // Sort the adjacency list using revised nesting depth as sort order for (v, adjs) in ordered_adjs.iter_mut().enumerate() { adjs.par_sort_by_key(|x| lr_state.nesting_depth[&(NodeIndex::new(v), *x)]); } @@ -310,8 +313,7 @@ pub fn create_embedding( let temp_side: Sign; if side.contains_key(&edge) { temp_side = side[&edge].clone(); - } - else { + } else { temp_side = Sign::Plus; } if eref.contains_key(&edge) { @@ -324,8 +326,7 @@ pub fn create_embedding( } if side.contains_key(&edge) { side[&edge] - } - else { + } else { Sign::Plus } } @@ -402,15 +403,12 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { let y_wp = y_coord[&wp].clone(); let y_wq = y_coord[&wq].clone(); - delta_x.insert(vk, (delta_x_wp_wq - y_wp + y_wq) / 2 as isize); //y_coord[&wp] + y_coord[&wq]); - y_coord.insert(vk, (delta_x_wp_wq + y_wp + y_wq) / 2 as isize); //y_coord.cloned()[&wp] + y_coord.cloned()[&wq]); - - //let d_vk = delta_x[&vk].clone(); - delta_x.insert(wq, delta_x_wp_wq - delta_x[&vk]);//d_vk); + delta_x.insert(vk, (delta_x_wp_wq - y_wp + y_wq) / 2 as isize); + y_coord.insert(vk, (delta_x_wp_wq + y_wp + y_wq) / 2 as isize); + delta_x.insert(wq, delta_x_wp_wq - delta_x[&vk]); if adds_mult_tri { - //let delta_wp1_minus = delta_x[&wp1] - delta_x[&vk]; - delta_x.insert(wp1, delta_x[&wp1] - delta_x[&vk]);//delta_wp1_minus); + delta_x.insert(wp1, delta_x[&wp1] - delta_x[&vk]); } right_t_child.insert(wp, vk); right_t_child.insert(vk, wq); @@ -570,7 +568,7 @@ fn canonical_ordering( let v2 = outer_face[1]; let mut chords: HashMap = HashMap::new(); let mut marked_nodes: HashSet = HashSet::new(); - let mut ready_to_pick: HashSet = HashSet::new(); + let mut ready_to_pick: IndexSet = IndexSet::new(); for node in outer_face.iter() { ready_to_pick.insert(*node); @@ -627,7 +625,7 @@ fn canonical_ordering( chords_plus = chords[&v].clone(); } chords.insert(v, chords_plus + 1); - ready_to_pick.remove(&v); + ready_to_pick.shift_remove(&v); } } } @@ -637,17 +635,11 @@ fn canonical_ordering( canon_order[0] = (Some(v1), vec![]); canon_order[1] = (Some(v2), vec![]); - ready_to_pick.remove(&v1); - ready_to_pick.remove(&v2); + ready_to_pick.shift_remove(&v1); + ready_to_pick.shift_remove(&v2); for k in (2..(planar_emb.embedding.node_count())).rev() { - let v_try = ready_to_pick.iter().next(); - if v_try.is_none() { - // RAISE? - continue; - } - let v = v_try.unwrap().clone(); - ready_to_pick.remove(&v); + let v = ready_to_pick.pop().unwrap(); marked_nodes.insert(v); let mut wp: Option = None; @@ -725,14 +717,14 @@ fn canonical_ordering( chords_w_plus = chords[&w_un].clone() + 1; } chords.insert(w_un, chords_w_plus); - ready_to_pick.remove(&w_un); + ready_to_pick.shift_remove(&w_un); if !new_face_nodes.contains(&nbr) { let mut chords_plus = 1; if chords.contains_key(&nbr) { chords_plus = chords[&nbr] + 1 } chords.insert(nbr, chords_plus); - ready_to_pick.remove(&nbr); + ready_to_pick.shift_remove(&nbr); } } } From 328506a46a94cf3b7ee05eb846bc1d6f95cf79b7 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 27 Jul 2022 09:43:18 -0700 Subject: [PATCH 25/37] Fix clippy lint --- src/layout/embedding.rs | 208 +++++++++++++++++++--------------------- src/layout/planar.rs | 4 +- 2 files changed, 99 insertions(+), 113 deletions(-) diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index 6870c0c90..6dde0b85b 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -136,21 +136,20 @@ impl PlanarEmbedding { end_node: NodeIndex, ref_nbr: Option, ) { - if ref_nbr.is_none() { - // Start node has no neighbors - let cw_weight = CwCcw::::default(); - self.embedding.add_edge(start_node, end_node, cw_weight); - self.update_edge_weight(start_node, end_node, end_node, true); - self.update_edge_weight(start_node, end_node, end_node, false); - self.embedding[start_node].first_nbr = Some(end_node); - } else { - let ref_nbr_node = ref_nbr.unwrap(); + if let Some(ref_nbr_node) = ref_nbr { let ccw_ref_node = self.get_edge_weight(start_node, ref_nbr_node, false); self.add_half_edge_cw(start_node, end_node, ccw_ref_node); if ref_nbr == self.embedding[start_node].first_nbr { // Update first neighbor self.embedding[start_node].first_nbr = Some(end_node); } + } else { + // Start node has no neighbors + let cw_weight = CwCcw::::default(); + self.embedding.add_edge(start_node, end_node, cw_weight); + self.update_edge_weight(start_node, end_node, end_node, true); + self.update_edge_weight(start_node, end_node, end_node, false); + self.embedding[start_node].first_nbr = Some(end_node); } } @@ -196,20 +195,12 @@ impl PlanarEmbedding { } fn get_edge_weight(&self, v: NodeIndex, w: NodeIndex, cw: bool) -> Option { - let found_edge = self.embedding.find_edge(v, w); - if found_edge.is_none() { - // RAISE? - return None; - } - let found_weight = self.embedding.edge_weight(found_edge.unwrap()); - if found_weight.is_none() { - // RAISE? - return None; - } + let found_edge = self.embedding.find_edge(v, w)?; + let found_weight = self.embedding.edge_weight(found_edge)?; if cw { - found_weight.unwrap().cw + found_weight.cw } else { - found_weight.unwrap().ccw + found_weight.ccw } } @@ -275,7 +266,7 @@ pub fn create_embedding( // Create the stack with an initial entry of v let mut dfs_stack: Vec = vec![*v]; - while dfs_stack.len() > 0 { + while !dfs_stack.is_empty() { let v = dfs_stack.pop().unwrap(); let idx2 = idx[v.index()]; @@ -283,20 +274,18 @@ pub fn create_embedding( for w in ordered_adjs[v.index()][idx2..].iter() { idx[v.index()] += 1; let ei = (v, *w); - if lr_state.eparent.contains_key(&w) && ei == lr_state.eparent[&w] { + if lr_state.eparent.contains_key(w) && ei == lr_state.eparent[w] { planar_emb.add_half_edge_first(*w, v); left_ref.insert(v, *w); right_ref.insert(v, *w); dfs_stack.push(v); dfs_stack.push(*w); break; + } else if !lr_state.side.contains_key(&ei) || lr_state.side[&ei] == Sign::Plus { + planar_emb.add_half_edge_cw(*w, v, Some(right_ref[w])); } else { - if !lr_state.side.contains_key(&ei) || lr_state.side[&ei] == Sign::Plus { - planar_emb.add_half_edge_cw(*w, v, Some(right_ref[w])); - } else { - planar_emb.add_half_edge_ccw(*w, v, Some(left_ref[w])); - left_ref.insert(*w, v); - } + planar_emb.add_half_edge_ccw(*w, v, Some(left_ref[w])); + left_ref.insert(*w, v); } } } @@ -310,14 +299,13 @@ pub fn create_embedding( // Resolve the relative side of an edge to the absolute side. // Create a temp of Plus in case edge not in side. - let temp_side: Sign; - if side.contains_key(&edge) { - temp_side = side[&edge].clone(); + let temp_side: Sign = if side.contains_key(&edge) { + side[&edge] } else { - temp_side = Sign::Plus; - } + Sign::Plus + }; if eref.contains_key(&edge) { - if temp_side == sign(eref[&edge].clone(), eref, side) { + if temp_side == sign(eref[&edge], eref, side) { side.insert(edge, Sign::Plus); } else { side.insert(edge, Sign::Minus); @@ -401,10 +389,10 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { let delta_x_wp_wq = contour_nbrs[1..].iter().map(|x| delta_x[x]).sum::(); - let y_wp = y_coord[&wp].clone(); - let y_wq = y_coord[&wq].clone(); - delta_x.insert(vk, (delta_x_wp_wq - y_wp + y_wq) / 2 as isize); - y_coord.insert(vk, (delta_x_wp_wq + y_wp + y_wq) / 2 as isize); + let y_wp = y_coord[&wp]; + let y_wq = y_coord[&wq]; + delta_x.insert(vk, (delta_x_wp_wq - y_wp + y_wq) / 2_isize); + y_coord.insert(vk, (delta_x_wp_wq + y_wp + y_wq) / 2_isize); delta_x.insert(wq, delta_x_wp_wq - delta_x[&vk]); if adds_mult_tri { @@ -427,14 +415,14 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { remaining_nodes: &mut Vec>, delta_x: &HashMap, isize>, y_coord: &HashMap, isize>, - pos: &mut Vec, + pos: &mut [Point], ) { let child = tree[&parent]; let parent_node_x = pos[parent.unwrap().index()][0]; - if child.is_some() { + if let Some(child_un) = child { let child_x = parent_node_x + (delta_x[&child] as f64); - pos[child.unwrap().index()] = [child_x, (y_coord[&child] as f64)]; + pos[child_un.index()] = [child_x, (y_coord[&child] as f64)]; remaining_nodes.push(child); } } @@ -443,7 +431,7 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { let mut remaining_nodes = vec![v1]; // Set the positions of all the nodes. - while remaining_nodes.len() > 0 { + while !remaining_nodes.is_empty() { let parent_node = remaining_nodes.pop().unwrap(); set_position( parent_node, @@ -482,7 +470,7 @@ fn triangulate_embedding( for v in planar_emb.embedding.node_indices() { for w in planar_emb.neighbors_cw_order(v) { let new_face = make_bi_connected(planar_emb, &v, &w, &mut edges_counted); - if new_face.len() > 0 { + if !new_face.is_empty() { face_list.push(new_face.clone()); if new_face.len() > outer_face.len() { outer_face = new_face; @@ -515,8 +503,8 @@ fn make_bi_connected( return vec![]; } edges_counted.insert((*start_node, *out_node)); - let mut v1 = start_node.clone(); - let mut v2 = out_node.clone(); + let mut v1 = *start_node; + let mut v2 = *out_node; let mut face_list: Vec = vec![*start_node]; let (_, mut v3) = planar_emb.next_face_half_edge(v1, v2); @@ -531,11 +519,11 @@ fn make_bi_connected( planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); edges_counted.insert((v2, v3)); edges_counted.insert((v3, v1)); - v2 = v1.clone(); + v2 = v1; } else { face_list.push(v2); } - v1 = v2.clone(); + v1 = v2; (v2, v3) = planar_emb.next_face_half_edge(v2, v3); edges_counted.insert((v1, v2)); } @@ -578,14 +566,14 @@ fn canonical_ordering( let mut outer_face_ccw_nbr: HashMap = HashMap::with_capacity(outer_face.len()); - let mut prev_nbr = v2.clone(); + let mut prev_nbr = v2; for v in outer_face[2..outer_face.len()].iter() { outer_face_ccw_nbr.insert(prev_nbr, *v); prev_nbr = *v; } outer_face_ccw_nbr.insert(prev_nbr, v1); - prev_nbr = v1.clone(); + prev_nbr = v1; for v in outer_face[1..outer_face.len()].iter().rev() { outer_face_cw_nbr.insert(prev_nbr, *v); prev_nbr = *v; @@ -622,7 +610,7 @@ fn canonical_ordering( { let mut chords_plus = 0; if chords.contains_key(&v) { - chords_plus = chords[&v].clone(); + chords_plus = chords[&v]; } chords.insert(v, chords_plus + 1); ready_to_pick.shift_remove(&v); @@ -653,12 +641,10 @@ fn canonical_ordering( wp = Some(v1); } else if *nbr == v2 { wq = Some(v2); + } else if outer_face_cw_nbr[nbr] == v { + wp = Some(*nbr); } else { - if outer_face_cw_nbr[nbr] == v { - wp = Some(*nbr); - } else { - wq = Some(*nbr); - } + wq = Some(*nbr); } } if wp.is_some() && wq.is_some() { @@ -666,65 +652,65 @@ fn canonical_ordering( } } let mut wp_wq = vec![]; - if wp.is_some() && wq.is_some() { - wp_wq = vec![wp]; - let mut nbr = wp.unwrap(); - while Some(nbr) != wq { - let next_nbr = planar_emb.get_edge_weight(v, nbr, false).unwrap(); - wp_wq.push(Some(next_nbr)); - outer_face_cw_nbr.insert(nbr, next_nbr); - outer_face_ccw_nbr.insert(next_nbr, nbr); - nbr = next_nbr; - } - if wp_wq.len() == 2 { - let wp_un = wp.unwrap(); - if chords.contains_key(&wp_un) { - let chords_wp = chords[&wp_un].clone() - 1; - chords.insert(wp_un, chords_wp); - if chords[&wp_un] == 0 { - ready_to_pick.insert(wp_un); - } + if let Some(wp_un) = wp { + if let Some(wq_un) = wq { + wp_wq = vec![wp]; + let mut nbr = wp.unwrap(); + while Some(nbr) != wq { + let next_nbr = planar_emb.get_edge_weight(v, nbr, false).unwrap(); + wp_wq.push(Some(next_nbr)); + outer_face_cw_nbr.insert(nbr, next_nbr); + outer_face_ccw_nbr.insert(next_nbr, nbr); + nbr = next_nbr; } - let wq_un = wq.unwrap(); - if chords.contains_key(&wq_un) { - let chords_wq = chords[&wq_un].clone() - 1; - chords.insert(wq_un, chords_wq); - if chords[&wq_un] == 0 { - ready_to_pick.insert(wq_un); + if wp_wq.len() == 2 { + if chords.contains_key(&wp_un) { + let chords_wp = chords[&wp_un] - 1; + chords.insert(wp_un, chords_wp); + if chords[&wp_un] == 0 { + ready_to_pick.insert(wp_un); + } } - } - } else { - let mut new_face_nodes: HashSet = HashSet::new(); - if wp_wq.len() > 1 { - for w in &wp_wq[1..(wp_wq.len() - 1)] { - let w_un = w.unwrap(); - new_face_nodes.insert(w_un); + if chords.contains_key(&wq_un) { + let chords_wq = chords[&wq_un] - 1; + chords.insert(wq_un, chords_wq); + if chords[&wq_un] == 0 { + ready_to_pick.insert(wq_un); + } } - for w in &new_face_nodes { - let w_un = *w; - ready_to_pick.insert(w_un); - for nbr in planar_emb.neighbors_cw_order(w_un) { - if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) - && !is_outer_face_nbr( - w_un, - nbr, - &outer_face_cw_nbr, - &outer_face_ccw_nbr, - ) - { - let mut chords_w_plus = 1; - if chords.contains_key(&w_un) { - chords_w_plus = chords[&w_un].clone() + 1; - } - chords.insert(w_un, chords_w_plus); - ready_to_pick.shift_remove(&w_un); - if !new_face_nodes.contains(&nbr) { - let mut chords_plus = 1; - if chords.contains_key(&nbr) { - chords_plus = chords[&nbr] + 1 + } else { + let mut new_face_nodes: HashSet = HashSet::new(); + if wp_wq.len() > 1 { + for w in &wp_wq[1..(wp_wq.len() - 1)] { + let w_un = w.unwrap(); + new_face_nodes.insert(w_un); + } + for w in &new_face_nodes { + let w_un = *w; + ready_to_pick.insert(w_un); + for nbr in planar_emb.neighbors_cw_order(w_un) { + if is_on_outer_face(nbr, v1, &marked_nodes, &outer_face_ccw_nbr) + && !is_outer_face_nbr( + w_un, + nbr, + &outer_face_cw_nbr, + &outer_face_ccw_nbr, + ) + { + let mut chords_w_plus = 1; + if chords.contains_key(&w_un) { + chords_w_plus = chords[&w_un] + 1; + } + chords.insert(w_un, chords_w_plus); + ready_to_pick.shift_remove(&w_un); + if !new_face_nodes.contains(&nbr) { + let mut chords_plus = 1; + if chords.contains_key(&nbr) { + chords_plus = chords[&nbr] + 1 + } + chords.insert(nbr, chords_plus); + ready_to_pick.shift_remove(&nbr); } - chords.insert(nbr, chords_plus); - ready_to_pick.shift_remove(&nbr); } } } diff --git a/src/layout/planar.rs b/src/layout/planar.rs index 25263e3f9..8b139608a 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -29,9 +29,9 @@ pub fn planar_layout( // If not planar, return an empty pos_map if !its_planar { // RAISE? - return Pos2DMapping { + Pos2DMapping { pos_map: DictMap::new(), - }; + } // If planar, create the position coordinates. } else { From 31c29275db6eb0a0820e0a84b663c7f26efd26e6 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 27 Jul 2022 10:00:47 -0700 Subject: [PATCH 26/37] More lint --- src/layout/embedding.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index 6dde0b85b..375224ced 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -364,9 +364,9 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { // Set coordinates for the remaining nodes, adjusting // positions along the way as needed. - for k in 3..node_list.len() { - let vk = node_list[k].0; - let contour_nbrs = &node_list[k].1; + for ordering in node_list.iter().skip(3) { + let vk = ordering.0; + let contour_nbrs = &ordering.1; let wp = contour_nbrs[0]; let wp1 = contour_nbrs[1]; From 2b95ef8280bdc8585c5861bd0f261d4df795f5be Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 27 Jul 2022 10:36:16 -0700 Subject: [PATCH 27/37] Fix retworkx-core tests --- retworkx-core/src/planar/lr_planar.rs | 5 ++-- retworkx-core/tests/test_planar.rs | 41 ++++++++++++++++++--------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index 3d6006547..8122774a4 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -683,7 +683,7 @@ where /// # Example: /// ```rust /// use retworkx_core::petgraph::graph::UnGraph; -/// use retworkx_core::planar::is_planar; +/// use retworkx_core::planar::{is_planar, LRState}; /// /// let grid = UnGraph::<(), ()>::from_edges(&[ /// // row edges @@ -691,7 +691,8 @@ where /// // col edges /// (0, 3), (3, 6), (1, 4), (4, 7), (2, 5), (5, 8), /// ]); -/// assert!(is_planar(&grid)) +/// let mut lr_state = LRState::new(&grid); +/// assert!(is_planar(&grid, Some(&mut lr_state))) /// ``` pub fn is_planar(graph: G, state: Option<&mut LRState>) -> bool where diff --git a/retworkx-core/tests/test_planar.rs b/retworkx-core/tests/test_planar.rs index a2fe3d130..3f7cb3d76 100644 --- a/retworkx-core/tests/test_planar.rs +++ b/retworkx-core/tests/test_planar.rs @@ -13,7 +13,7 @@ //! Test module for planar graphs. use retworkx_core::petgraph::graph::UnGraph; -use retworkx_core::planar::is_planar; +use retworkx_core::planar::{is_planar, LRState}; #[test] fn test_simple_planar_graph() { @@ -30,7 +30,8 @@ fn test_simple_planar_graph() { (4, 5), (5, 7), ]); - let res = is_planar(&graph); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); assert!(res) } @@ -52,7 +53,8 @@ fn test_planar_grid_3_3_graph() { (2, 5), (5, 8), ]); - let res = is_planar(&graph); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); assert!(res) } @@ -73,7 +75,8 @@ fn test_planar_with_self_loop() { (3, 5), (4, 5), ]); - let res = is_planar(&graph); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); assert!(res) } @@ -109,21 +112,24 @@ fn test_goldner_harary_planar_graph() { (9, 10), (10, 11), ]); - let res = is_planar(&graph); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); assert!(res) } #[test] fn test_multiple_components_planar_graph() { let graph = UnGraph::<(), ()>::from_edges(&[(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)]); - let res = is_planar(&graph); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); assert!(res) } #[test] fn test_planar_multi_graph() { let graph = UnGraph::<(), ()>::from_edges(&[(0, 1), (0, 1), (0, 1), (1, 2), (2, 0)]); - let res = is_planar(&graph); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); assert!(res) } @@ -140,7 +146,8 @@ fn test_k3_3_non_planar() { (2, 4), (2, 5), ]); - let res = is_planar(&graph); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); assert_eq!(res, false) } @@ -158,7 +165,8 @@ fn test_k5_non_planar() { (2, 4), (3, 4), ]); - let res = is_planar(&graph); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); assert_eq!(res, false) } @@ -179,7 +187,8 @@ fn test_multiple_components_non_planar() { (7, 8), (8, 6), ]); - let res = is_planar(&graph); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); assert_eq!(res, false) } @@ -198,7 +207,8 @@ fn test_non_planar() { (4, 6), (4, 7), ]); - let res = is_planar(&graph); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); assert_eq!(res, false) } @@ -216,7 +226,8 @@ fn test_planar_graph1() { (0, 10), (1, 7), ]); - let res = is_planar(&graph); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); assert!(res) } @@ -241,7 +252,8 @@ fn test_non_planar_graph2() { (3, 6), (2, 8), ]); - let res = is_planar(&graph); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); assert_eq!(res, false) } @@ -263,6 +275,7 @@ fn test_non_planar_graph3() { (5, 11), (5, 13), ]); - let res = is_planar(&graph); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); assert_eq!(res, false) } From e9f230a9c442410030b4dbe7734548de01e352d6 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 27 Jul 2022 12:26:13 -0700 Subject: [PATCH 28/37] MSRV tuple issue --- src/layout/embedding.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index 375224ced..ebdce8ad8 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -524,7 +524,9 @@ fn make_bi_connected( face_list.push(v2); } v1 = v2; - (v2, v3) = planar_emb.next_face_half_edge(v2, v3); + let edge = planar_emb.next_face_half_edge(v2, v3); + v2 = edge.0; + v3 = edge.1; edges_counted.insert((v1, v2)); } face_list @@ -538,13 +540,19 @@ fn triangulate_face(planar_emb: &mut PlanarEmbedding, mut v1: NodeIndex, mut v2: } while v1 != v4 { if planar_emb.embedding.contains_edge(v1, v3) { - (v1, v2, v3) = (v2, v3, v4); + // (v1, v2, v3) = (v2, v3, v4); + v1 = v2; + v2 = v3; + v3 = v4; } else { planar_emb.add_half_edge_cw(v1, v3, Some(v2)); planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); - (v1, v2, v3) = (v1, v3, v4); + // (v1, v2, v3) = (v1, v3, v4); + v2 = v3; + v3 = v4; } - (_, v4) = planar_emb.next_face_half_edge(v2, v3); + let edge = planar_emb.next_face_half_edge(v2, v3); + v4 = edge.1; } } From 6f29475289f64014f56693752c65ec92db92c55b Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 27 Jul 2022 14:25:45 -0700 Subject: [PATCH 29/37] Remove digraph from planar docs --- src/layout/embedding.rs | 2 -- src/layout/mod.rs | 20 -------------------- src/lib.rs | 1 - 3 files changed, 23 deletions(-) diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index ebdce8ad8..2cf295677 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -540,14 +540,12 @@ fn triangulate_face(planar_emb: &mut PlanarEmbedding, mut v1: NodeIndex, mut v2: } while v1 != v4 { if planar_emb.embedding.contains_edge(v1, v3) { - // (v1, v2, v3) = (v2, v3, v4); v1 = v2; v2 = v3; v3 = v4; } else { planar_emb.add_half_edge_cw(v1, v3, Some(v2)); planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); - // (v1, v2, v3) = (v1, v3, v4); v2 = v3; v3 = v4; } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 21457be57..1bd74615c 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -215,26 +215,6 @@ pub fn graph_planar_layout( planar::planar_layout(&graph.graph, scale, center) } -/// Generate a planar layout -/// -/// :param PyDiGraph graph: The graph to generate the layout for -/// :param float|None scale: Scale factor for positions.If scale is ``None``, -/// no re-scaling is performed. (``default=1.0``) -/// :param tuple center: An optional center position. This is a 2 tuple of two -/// ``float`` values for the center position -/// -/// :returns: The planar layout of the graph. -/// :rtype: Pos2DMapping -// #[pyfunction] -// #[pyo3(text_signature = "(graph, / scale=1.0, center=None)")] -// pub fn digraph_planar_layout( -// graph: &digraph::PyDiGraph, -// scale: Option, -// center: Option<[f64; 2]>, -// ) -> Pos2DMapping { -// planar::planar_layout(&graph.graph, scale, center) -// } - /// Generate a random layout /// /// :param PyGraph graph: The graph to generate the layout for diff --git a/src/lib.rs b/src/lib.rs index fe34cac7a..6020deda2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -434,7 +434,6 @@ fn retworkx(py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(graph_complement))?; m.add_wrapped(wrap_pyfunction!(digraph_complement))?; m.add_wrapped(wrap_pyfunction!(graph_planar_layout))?; - //m.add_wrapped(wrap_pyfunction!(digraph_planar_layout))?; m.add_wrapped(wrap_pyfunction!(graph_random_layout))?; m.add_wrapped(wrap_pyfunction!(digraph_random_layout))?; m.add_wrapped(wrap_pyfunction!(graph_bipartite_layout))?; From 7d0b01e4d716d5632842c522b50b7b4fa6277f27 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sat, 13 Aug 2022 14:31:03 -0700 Subject: [PATCH 30/37] Docs, cleanup, and work on stable graphs, change to rustworkx --- retworkx-core/src/planar/lr_planar.rs | 43 +++++++++--- src/layout/embedding.rs | 95 ++++++++++++++------------- src/layout/mod.rs | 15 +++-- src/layout/planar.rs | 18 ++++- tests/graph/test_planar_layout.py | 52 +++++++++++++++ 5 files changed, 163 insertions(+), 60 deletions(-) create mode 100644 tests/graph/test_planar_layout.py diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index 8122774a4..6a532ed33 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -16,7 +16,8 @@ use std::vec::IntoIter; use hashbrown::{hash_map::Entry, HashMap}; use petgraph::{ - graph::{Graph, NodeIndex}, + graph::NodeIndex, + stable_graph::StableGraph, visit::{ EdgeCount, EdgeRef, GraphBase, GraphProp, IntoEdges, IntoNodeIdentifiers, NodeCount, NodeIndexable, Visitable, @@ -240,7 +241,7 @@ where /// side of edge, or modifier for side of reference edge. pub side: HashMap, Sign>, /// directed graph used to build the embedding - pub dir_graph: Graph<(), (), Directed>, + pub dir_graph: StableGraph<(), (), Directed>, } impl LRState @@ -258,7 +259,7 @@ where let num_nodes = graph.node_count(); let num_edges = graph.edge_count(); - let mut lr_state = LRState { + let lr_state = LRState { graph, roots: Vec::new(), height: HashMap::with_capacity(num_nodes), @@ -274,14 +275,34 @@ where .edge_references() .map(|e| ((e.source(), e.target()), Sign::Plus)) .collect(), - dir_graph: Graph::with_capacity(num_nodes, 0), + dir_graph: StableGraph::with_capacity(num_nodes, 0), }; - for _ in graph.node_identifiers() { - lr_state.dir_graph.add_node(()); - } lr_state } + // Create the directed graph for the embedding in stable format + // to match the original graph. + fn build_dir_graph(&mut self) where ::NodeId: Ord { + let mut tmp_nodes: Vec = Vec::new(); + let mut count: usize = 0; + for _ in 0..self.graph.node_bound() { + self.dir_graph.add_node(()); + } + for gnode in self.graph.node_identifiers() { + let gidx = self.graph.to_index(gnode); + if gidx != count { + for idx in count..gidx { + tmp_nodes.push(idx); + } + count = gidx + } + count += 1 + } + for tmp_node in tmp_nodes { + self.dir_graph.remove_node(NodeIndex::new(tmp_node)); + } + } + fn lr_orientation_visitor(&mut self, event: DfsEvent) { match event { DfsEvent::Discover(v, _) => { @@ -703,15 +724,19 @@ where + NodeIndexable + IntoNodeIdentifiers + Visitable, - G::NodeId: Hash + Eq, + G::NodeId: Hash + Eq + Ord, { + // If None passed for state, create new LRState let mut lr_state = LRState::new(graph); let lr_state = match state { Some(state) => state, None => &mut lr_state, }; - // // Dfs orientation phase + // Build directed graph for the embedding + lr_state.build_dir_graph(); + + // Dfs orientation phase depth_first_search(graph, graph.node_identifiers(), |event| { lr_state.lr_orientation_visitor(event) }); diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index 2cf295677..355d4f91a 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -1,11 +1,21 @@ -use hashbrown::hash_map::HashMap; -use hashbrown::HashSet; -use indexmap::set::IndexSet; -use petgraph::graph::Graph; +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +use hashbrown::{HashMap, HashSet}; +use indexmap::{IndexSet, IndexMap}; use petgraph::prelude::*; use petgraph::visit::NodeIndexable; use petgraph::Directed; -use rayon::prelude::*; +use rayon::prelude::*; // For par_sort use std::fmt::Debug; use crate::StablePyGraph; @@ -62,20 +72,20 @@ impl FirstNbr { /// The basic embedding to build the structure that will lead to /// the position coordinates to display. pub struct PlanarEmbedding { - pub embedding: Graph, CwCcw, Directed>, + pub embedding: StableGraph, CwCcw, Directed>, } impl Default for PlanarEmbedding { fn default() -> Self { PlanarEmbedding { - embedding: Graph::, CwCcw, Directed>::new(), + embedding: StableGraph::, CwCcw, Directed>::new(), } } } impl PlanarEmbedding { pub fn new() -> Self { PlanarEmbedding { - embedding: Graph::, CwCcw, Directed>::new(), + embedding: StableGraph::, CwCcw, Directed>::new(), } } @@ -107,27 +117,28 @@ impl PlanarEmbedding { ) { let cw_weight = CwCcw::::default(); self.embedding.add_edge(start_node, end_node, cw_weight); - if ref_nbr.is_none() { + + if let Some(ref_nbr_node) = ref_nbr { + if self.embedding.find_edge(start_node, ref_nbr_node).is_none() { + // RAISE? + println!("Cannot add edge to {:?}. Reference neighbor {:?} does not exist", start_node, ref_nbr_node); + panic!(); + } + let cw_ref = self + .get_edge_weight(start_node, ref_nbr_node, true) + .unwrap(); + // Alter half-edge data structures + self.update_edge_weight(start_node, ref_nbr_node, end_node, true); + self.update_edge_weight(start_node, end_node, cw_ref, true); + self.update_edge_weight(start_node, cw_ref, end_node, false); + self.update_edge_weight(start_node, end_node, ref_nbr_node, false); + } + else { // The start node has no neighbors self.update_edge_weight(start_node, end_node, end_node, true); self.update_edge_weight(start_node, end_node, end_node, false); self.embedding[start_node].first_nbr = Some(end_node); - return; - } - // if ref_nbr not in self[start_node] error - let ref_nbr_node = ref_nbr.unwrap(); - // RAISE? - if self.embedding.find_edge(start_node, ref_nbr_node).is_none() { - println!("NO REF NBR in ADD CW {:?} {:?}", start_node, ref_nbr_node); } - let cw_ref = self - .get_edge_weight(start_node, ref_nbr_node, true) - .unwrap(); - // Alter half-edge data structures - self.update_edge_weight(start_node, ref_nbr_node, end_node, true); - self.update_edge_weight(start_node, end_node, cw_ref, true); - self.update_edge_weight(start_node, cw_ref, end_node, false); - self.update_edge_weight(start_node, end_node, ref_nbr_node, false); } fn add_half_edge_ccw( @@ -147,6 +158,7 @@ impl PlanarEmbedding { // Start node has no neighbors let cw_weight = CwCcw::::default(); self.embedding.add_edge(start_node, end_node, cw_weight); + self.update_edge_weight(start_node, end_node, end_node, true); self.update_edge_weight(start_node, end_node, end_node, false); self.embedding[start_node].first_nbr = Some(end_node); @@ -155,7 +167,7 @@ impl PlanarEmbedding { fn add_half_edge_first(&mut self, start_node: NodeIndex, end_node: NodeIndex) { // Add half edge that's first_nbr or None - let ref_node: Option = if self.embedding.node_bound() >= start_node.index() + let ref_node: Option = if self.embedding.node_count() >= start_node.index() && self.embedding[start_node].first_nbr.is_some() { self.embedding[start_node].first_nbr @@ -211,25 +223,20 @@ impl PlanarEmbedding { } } -/// Use the LRState data from is_planar to build an -/// embedding. +/// Use the LRState data from is_planar to build an embedding. pub fn create_embedding( planar_emb: &mut PlanarEmbedding, lr_state: &mut LRState<&StablePyGraph>, ) { - let mut ordered_adjs: Vec> = Vec::new(); + let mut ordered_adjs: IndexMap> = IndexMap::with_capacity(lr_state.graph.node_count()); // Create the adjacency list for each node for v in lr_state.dir_graph.node_indices() { - ordered_adjs.push(lr_state.dir_graph.edges(v).map(|e| e.target()).collect()); + ordered_adjs.insert(v, lr_state.dir_graph.edges(v).map(|e| e.target()).collect()); // Add empty FirstNbr to the embedding let first_nbr = FirstNbr::::default(); planar_emb.embedding.add_node(first_nbr); } - // Sort the adjacency list using nesting depth as sort order - for (v, adjs) in ordered_adjs.iter_mut().enumerate() { - adjs.par_sort_by_key(|x| lr_state.nesting_depth[&(NodeIndex::new(v), *x)]); - } for v in lr_state.dir_graph.node_indices() { // Change the sign for nesting_depth for e in lr_state.dir_graph.edges(v) { @@ -246,13 +253,13 @@ pub fn create_embedding( } } // Sort the adjacency list using revised nesting depth as sort order - for (v, adjs) in ordered_adjs.iter_mut().enumerate() { - adjs.par_sort_by_key(|x| lr_state.nesting_depth[&(NodeIndex::new(v), *x)]); + for (v, adjs) in ordered_adjs.iter_mut() { + adjs.par_sort_by_key(|x| lr_state.nesting_depth[&(*v, *x)]); } // Add the initial half edge cw to the embedding using the ordered adjacency list for v in lr_state.dir_graph.node_indices() { let mut prev_node: Option = None; - for w in &ordered_adjs[v.index()] { + for w in ordered_adjs.get(&v).unwrap().iter() { planar_emb.add_half_edge_cw(v, *w, prev_node); prev_node = Some(*w) } @@ -260,7 +267,7 @@ pub fn create_embedding( // Start the DFS traversal for the embedding let mut left_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); let mut right_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); - let mut idx: Vec = vec![0; ordered_adjs.len()]; + let mut idx: Vec = vec![0; lr_state.graph.node_bound()];//ordered_adjs.len()]; for v in lr_state.roots.iter() { // Create the stack with an initial entry of v @@ -271,7 +278,7 @@ pub fn create_embedding( let idx2 = idx[v.index()]; // Iterate over the ordered_adjs starting at the saved index until the end - for w in ordered_adjs[v.index()][idx2..].iter() { + for w in ordered_adjs.get(&v).unwrap()[idx2..].iter() { idx[v.index()] += 1; let ei = (v, *w); if lr_state.eparent.contains_key(w) && ei == lr_state.eparent[w] { @@ -562,11 +569,10 @@ fn canonical_ordering( let v2 = outer_face[1]; let mut chords: HashMap = HashMap::new(); let mut marked_nodes: HashSet = HashSet::new(); - let mut ready_to_pick: IndexSet = IndexSet::new(); - for node in outer_face.iter() { - ready_to_pick.insert(*node); - } + let mut ready_to_pick = outer_face.iter().cloned().collect::>(); + ready_to_pick.par_sort(); + let mut outer_face_cw_nbr: HashMap = HashMap::with_capacity(outer_face.len()); let mut outer_face_ccw_nbr: HashMap = @@ -633,7 +639,8 @@ fn canonical_ordering( ready_to_pick.shift_remove(&v2); for k in (2..(planar_emb.embedding.node_count())).rev() { - let v = ready_to_pick.pop().unwrap(); + let v = ready_to_pick[0]; + ready_to_pick.shift_remove(&v); marked_nodes.insert(v); let mut wp: Option = None; @@ -685,7 +692,7 @@ fn canonical_ordering( } } } else { - let mut new_face_nodes: HashSet = HashSet::new(); + let mut new_face_nodes: IndexSet = IndexSet::new(); if wp_wq.len() > 1 { for w in &wp_wq[1..(wp_wq.len() - 1)] { let w_un = w.unwrap(); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 1bd74615c..a26b54048 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -197,13 +197,20 @@ pub fn digraph_spring_layout( /// Generate a planar layout /// -/// :param PyGraph graph: The graph to generate the layout for -/// :param float|None scale: Scale factor for positions.If scale is ``None``, +/// The algorithm first uses Ulrik Brandes: The Left-Right Planarity Test 2009, +/// http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.217.9208 +/// to determine if the graph is planar. If so, then a planar embedding is created +/// and the drawing is created using M. Chrobak and T.H. Payne: A Linear-time Algorithm +/// for Drawing a Planar Graph on a Grid 1989, +/// http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.51.6677. +/// +/// :param PyGraph graph: The graph to be used +/// :param float|None scale: Scale factor for positions. If scale is ``None``, /// no re-scaling is performed. (``default=1.0``) -/// :param tuple center: An optional center position. This is a 2 tuple of two +/// :param tuple|None center: An optional center position. This is a 2 tuple of two /// ``float`` values for the center position /// -/// :returns: The planar layout of the graph. +/// :returns: A dictionary of positions keyed by node id. /// :rtype: Pos2DMapping #[pyfunction] #[pyo3(text_signature = "(graph, / scale=1.0, center=None)")] diff --git a/src/layout/planar.rs b/src/layout/planar.rs index 8b139608a..480b3d19c 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -1,9 +1,21 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + use petgraph::prelude::*; +use petgraph::visit::NodeIndexable; use super::spring::{recenter, rescale, Point}; use crate::iterators::Pos2DMapping; use crate::layout::embedding::{create_embedding, embedding_to_pos, PlanarEmbedding}; -use crate::Graph; use crate::StablePyGraph; use retworkx_core::dictmap::*; use retworkx_core::planar::{is_planar, LRState}; @@ -15,7 +27,7 @@ pub fn planar_layout( scale: Option, center: Option, ) -> Pos2DMapping { - let node_num = graph.node_count(); + let node_num = graph.node_bound(); if node_num == 0 { return Pos2DMapping { pos_map: DictMap::new(), @@ -36,7 +48,7 @@ pub fn planar_layout( // If planar, create the position coordinates. } else { let mut planar_emb = PlanarEmbedding::new(); - planar_emb.embedding = Graph::with_capacity(node_num, 0); + planar_emb.embedding = StableGraph::with_capacity(node_num, 0); // First create the graph embedding create_embedding(&mut planar_emb, &mut lr_state); diff --git a/tests/graph/test_planar_layout.py b/tests/graph/test_planar_layout.py new file mode 100644 index 000000000..1562e8cbe --- /dev/null +++ b/tests/graph/test_planar_layout.py @@ -0,0 +1,52 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest + +import retworkx + + +class TestPlanarLayout(unittest.TestCase): + def setUp(self): + self.graph = retworkx.PyGraph() + node_a = self.graph.add_node(1) + node_b = self.graph.add_node(2) + self.graph.add_edge(node_a, node_b, 1) + node_c = self.graph.add_node(3) + self.graph.add_edge(node_a, node_c, 2) + + def test_empty_graph(self): + graph = retworkx.PyGraph() + res = retworkx.planar_layout(graph) + self.assertEqual({}, res) + + def test_simple_graph(self): + res = retworkx.planar_layout(self.graph) + self.assertEqual(len(res), 3) + self.assertEqual(len(res[0]), 2) + self.assertIsInstance(res[0][0], float) + + def test_simple_graph_center(self): + res = retworkx.planar_layout(self.graph, center=[0.5, 0.5]) + self.assertEqual(len(res), 3) + self.assertEqual(len(res[0]), 2) + self.assertIsInstance(res[0][0], float) + + def test_graph_with_removed_nodes(self): + graph = retworkx.PyGraph() + nodes = graph.add_nodes_from([0, 1, 2]) + graph.remove_node(nodes[1]) + res = retworkx.planar_layout(graph) + self.assertEqual(len(res), 2) + self.assertTrue(nodes[0] in res) + self.assertTrue(nodes[2] in res) + self.assertFalse(nodes[1] in res) From 3fa89ca572cc12ebf6454e1fb1e02f39780ec983 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sat, 13 Aug 2022 15:01:00 -0700 Subject: [PATCH 31/37] Changing name --- retworkx-core/src/lib.rs | 19 +- retworkx-core/src/planar/lr_planar.rs | 4 +- retworkx/__init__.py | 522 ++++++++------------------ 3 files changed, 163 insertions(+), 382 deletions(-) diff --git a/retworkx-core/src/lib.rs b/retworkx-core/src/lib.rs index 18b956aa6..f15f4c79c 100644 --- a/retworkx-core/src/lib.rs +++ b/retworkx-core/src/lib.rs @@ -10,10 +10,10 @@ // License for the specific language governing permissions and limitations // under the License. -//! # retworkx-core +//! # rustworkx-core //! -//! retworkx-core is a graph algorithm crate built on top of [`petgraph`]. It offers -//! a set of functions that are used in the larger retworkx project but +//! rustworkx-core is a graph algorithm crate built on top of [`petgraph`]. It offers +//! a set of functions that are used in the larger rustworkx project but //! implemented in a generic manner for use by downstream rust projects. //! //! ## Usage @@ -22,14 +22,14 @@ //! //! ```toml //! [dependencies] -//! retworkx-core = "0.11" +//! rustworkx-core = "0.11" //! ``` //! //! Then in your code, it may be used something like this: //! //! ```rust -//! use retworkx_core::petgraph; -//! use retworkx_core::centrality::betweenness_centrality; +//! use rustworkx_core::petgraph; +//! use rustworkx_core::centrality::betweenness_centrality; //! //! let g = petgraph::graph::UnGraph::::from_edges(&[ //! (1, 2), (2, 3), (3, 4), (1, 4) @@ -54,10 +54,10 @@ //! //! ## Release Notes //! -//! The release notes for retworkx-core are included as part of the retworkx +//! The release notes for rustworkx-core are included as part of the rustworkx //! documentation which is hosted at: //! -//! +//! use std::convert::Infallible; @@ -73,7 +73,6 @@ pub mod centrality; pub mod connectivity; /// Module for maximum weight matching algorithms. pub mod max_weight_matching; -pub mod planar; pub mod shortest_path; pub mod traversal; // These modules define additional data structures @@ -82,5 +81,5 @@ pub mod distancemap; mod min_scored; // re-export petgraph so there is a consistent version available to users and -// then only need to require retworkx-core in their dependencies +// then only need to require rustworkx-core in their dependencies pub use petgraph; diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs index 6a532ed33..8681ab574 100644 --- a/retworkx-core/src/planar/lr_planar.rs +++ b/retworkx-core/src/planar/lr_planar.rs @@ -294,9 +294,9 @@ where for idx in count..gidx { tmp_nodes.push(idx); } - count = gidx + count = gidx; } - count += 1 + count += 1; } for tmp_node in tmp_nodes { self.dir_graph.remove_node(NodeIndex::new(tmp_node)); diff --git a/retworkx/__init__.py b/retworkx/__init__.py index 1dc21ec84..c7f640be4 100644 --- a/retworkx/__init__.py +++ b/retworkx/__init__.py @@ -10,87 +10,68 @@ import sys import functools -from .retworkx import * +from .rustworkx import * # flake8: noqa -import retworkx.visit +import rustworkx.visit -sys.modules["retworkx.generators"] = generators +sys.modules["rustworkx.generators"] = generators class PyDAG(PyDiGraph): """A class for creating direct acyclic graphs. - PyDAG is just an alias of the PyDiGraph class and behaves identically to - the :class:`~retworkx.PyDiGraph` class and can be used interchangably + the :class:`~rustworkx.PyDiGraph` class and can be used interchangably with ``PyDiGraph``. It currently exists solely as a backwards - compatibility alias for users of retworkx from prior to the + compatibility alias for users of rustworkx from prior to the 0.4.0 release when there was no PyDiGraph class. - The PyDAG class is used to create a directed graph. It can be a multigraph (have multiple edges between nodes). Each node and edge (although rarely used for edges) is indexed by an integer id. These ids are stable for the lifetime of the graph object and on node or edge deletions you can have holes in the list of indices for the graph. Node indices will be reused on additions after removal. For example: - .. jupyter-execute:: - - import retworkx - - graph = retworkx.PyDAG() + import rustworkx + graph = rustworkx.PyDAG() graph.add_nodes_from(list(range(5))) graph.add_nodes_from(list(range(2))) graph.remove_node(2) print("After deletion:", graph.node_indices()) res_manual = graph.add_parent(6, None, None) print("After adding a new node:", graph.node_indices()) - Additionally, each node and edge contains an arbitrary Python object as a weight/data payload. - You can use the index for access to the data payload as in the following example: - .. jupyter-execute:: - - import retworkx - - graph = retworkx.PyDAG() + import rustworkx + graph = rustworkx.PyDAG() data_payload = "An arbitrary Python object" node_index = graph.add_node(data_payload) print("Node Index: %s" % node_index) print(graph[node_index]) - The PyDAG class implements the Python mapping protocol for nodes so in addition to access you can also update the data payload with: - .. jupyter-execute:: - - import retworkx - - graph = retworkx.PyDAG() + import rustworkx + graph = rustworkx.PyDAG() data_payload = "An arbitrary Python object" node_index = graph.add_node(data_payload) graph[node_index] = "New Payload" print("Node Index: %s" % node_index) print(graph[node_index]) - The PyDAG class has an option for real time cycle checking which can be used to ensure any edges added to the graph does not introduce a cycle. By default the real time cycle checking feature is disabled for performance, however you can enable it by setting the ``check_cycle`` attribute to True. For example:: - - import retworkx - dag = retworkx.PyDAG() + import rustworkx + dag = rustworkx.PyDAG() dag.check_cycle = True - or at object creation:: - - import retworkx - dag = retworkx.PyDAG(check_cycle=True) - + import rustworkx + dag = rustworkx.PyDAG(check_cycle=True) With check_cycle set to true any calls to :meth:`PyDAG.add_edge` will ensure that no cycles are added, ensuring that the PyDAG class truly represents a directed acyclic graph. Do note that this cycle checking on @@ -101,24 +82,19 @@ class PyDAG(PyDiGraph): penalty that grows as the graph does. If you're adding a node and edge at the same time, leveraging :meth:`PyDAG.add_child` or :meth:`PyDAG.add_parent` will avoid this overhead. - By default a ``PyDAG`` is a multigraph (meaning there can be parallel edges between nodes) however this can be disabled by setting the ``multigraph`` kwarg to ``False`` when calling the ``PyDAG`` constructor. For example:: - - import retworkx - dag = retworkx.PyDAG(multigraph=False) - + import rustworkx + dag = rustworkx.PyDAG(multigraph=False) This can only be set at ``PyDiGraph`` initialization and not adjusted after - creation. When :attr:`~retworkx.PyDiGraph.multigraph` is set to ``False`` + creation. When :attr:`~rustworkx.PyDiGraph.multigraph` is set to ``False`` if a method call is made that would add a parallel edge it will instead update the existing edge's weight/data payload. - The maximum number of nodes and edges allowed on a ``PyGraph`` object is :math:`2^{32} - 1` (4,294,967,294) each. Attempting to add more nodes or edges than this will result in an exception being raised. - :param bool check_cycle: When this is set to ``True`` the created ``PyDAG`` has runtime cycle detection enabled. :param bool multgraph: When this is set to ``False`` the created @@ -133,18 +109,15 @@ class PyDAG(PyDiGraph): @functools.singledispatch def distance_matrix(graph, parallel_threshold=300, as_undirected=False, null_value=0.0): """Get the distance matrix for a graph - - This differs from functions like :func:`~retworkx.floyd_warshall_numpy` in + This differs from functions like :func:`~rustworkx.floyd_warshall_numpy` in that the edge weight/data payload is not used and each edge is treated as a distance of 1. - This function is also multithreaded and will run in parallel if the number of nodes in the graph is above the value of ``parallel_threshold`` (it defaults to 300). If the function will be running in parallel the env var ``RAYON_NUM_THREADS`` can be used to adjust how many threads will be used. - :param graph: The graph to get the distance matrix for, can be either a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. :param int parallel_threshold: The number of nodes to calculate the the distance matrix in parallel at. It defaults to 300, but this can be tuned @@ -155,7 +128,6 @@ def distance_matrix(graph, parallel_threshold=300, as_undirected=False, null_val value. This is the default value in the output matrix and it is used to indicate the absence of an edge between 2 nodes. By default this is ``0.0``. - :returns: The distance matrix :rtype: numpy.ndarray """ @@ -182,27 +154,21 @@ def _graph_distance_matrix(graph, parallel_threshold=300, null_value=0.0): @functools.singledispatch def unweighted_average_shortest_path_length(graph, parallel_threshold=300, disconnected=False): r"""Return the average shortest path length with unweighted edges. - The average shortest path length is calculated as - .. math:: - a =\sum_{s,t \in V, s \ne t} \frac{d(s, t)}{n(n-1)} - where :math:`V` is the set of nodes in ``graph``, :math:`d(s, t)` is the shortest path length from :math:`s` to :math:`t`, and :math:`n` is the number of nodes in ``graph``. If ``disconnected`` is set to ``True``, the average will be taken only between connected nodes. - This function is also multithreaded and will run in parallel if the number of nodes in the graph is above the value of ``parallel_threshold`` (it defaults to 300). If the function will be running in parallel the env var ``RAYON_NUM_THREADS`` can be used to adjust how many threads will be used. By default it will use all available CPUs if the environment variable is not specified. - :param graph: The graph to compute the average shortest path length for, - can be either a :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. + can be either a :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. :param int parallel_threshold: The number of nodes to calculate the the distance matrix in parallel at. It defaults to 300, but this can be tuned to any number of nodes. @@ -212,10 +178,8 @@ def unweighted_average_shortest_path_length(graph, parallel_threshold=300, disco :param bool disconnected: If set to ``True`` only connected vertex pairs will be included in the calculation. If ``False``, infinity is returned for disconnected graphs. Default: ``False``. - :returns: The average shortest path length. If no vertex pairs can be included in the calculation this will return NaN. - :rtype: float """ raise TypeError("Invalid Input Type %s for graph" % type(graph)) @@ -243,23 +207,17 @@ def _graph_unweighted_shortest_path_length(graph, parallel_threshold=300, discon @functools.singledispatch def adjacency_matrix(graph, weight_fn=None, default_weight=1.0, null_value=0.0): """Return the adjacency matrix for a graph object - In the case where there are multiple edges between nodes the value in the output matrix will be the sum of the edges' weights. - :param graph: The graph used to generate the adjacency matrix from. Can - either be a :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + either be a :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param callable weight_fn: A callable object (function, lambda, etc) which will be passed the edge object and expected to return a ``float``. This - tells retworkx/rust how to extract a numerical weight as a ``float`` + tells rustworkx/rust how to extract a numerical weight as a ``float`` for edge object. Some simple examples are:: - adjacency_matrix(graph, weight_fn: lambda x: 1) - to return a weight of 1 for all edges. Also:: - adjacency_matrix(graph, weight_fn: lambda x: float(x)) - to cast the edge object as a float as the weight. If this is not specified a default value (either ``default_weight`` or 1) will be used for all edges. @@ -269,7 +227,6 @@ def adjacency_matrix(graph, weight_fn=None, default_weight=1.0, null_value=0.0): value. This is the default value in the output matrix and it is used to indicate the absence of an edge between 2 nodes. By default this is ``0.0``. - :return: The adjacency matrix for the input dag as a numpy array :rtype: numpy.ndarray """ @@ -299,11 +256,9 @@ def _graph_adjacency_matrix(graph, weight_fn=None, default_weight=1.0, null_valu @functools.singledispatch def all_simple_paths(graph, from_, to, min_depth=None, cutoff=None): """Return all simple paths between 2 nodes in a PyGraph object - A simple path is a path with no repeated nodes. - :param graph: The graph to find the path in. Can either be a - class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param int from_: The node index to find the paths from :param int to: The node index to find the paths to :param int min_depth: The minimum depth of the path to include in the @@ -312,7 +267,6 @@ def all_simple_paths(graph, from_, to, min_depth=None, cutoff=None): :param int cutoff: The maximum depth of path to include in the output list of paths. By default includes all paths regardless of depth, setting to 0 will behave like default. - :returns: A list of lists where each inner list is a path of node indices :rtype: list """ @@ -337,30 +291,23 @@ def floyd_warshall( parallel_threshold=300, ): """Find all-pairs shortest path lengths using Floyd's algorithm - Floyd's algorithm is used for finding shortest paths in dense graphs or graphs with negative weights (where Dijkstra's algorithm fails). - This function is multithreaded and will launch a pool with threads equal to the number of CPUs by default if the number of nodes in the graph is above the value of ``parallel_threshold`` (it defaults to 300). You can tune the number of threads with the ``RAYON_NUM_THREADS`` environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would limit the thread pool to 4 threads if parallelization was enabled. - :param graph: The graph to run Floyd's algorithm on. Can - either be a :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + either be a :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param callable weight_fn: A callable object (function, lambda, etc) which will be passed the edge object and expected to return a ``float``. This - tells retworkx/rust how to extract a numerical weight as a ``float`` + tells rustworkx/rust how to extract a numerical weight as a ``float`` for edge object. Some simple examples are:: - floyd_warshall(graph, weight_fn= lambda x: 1) - to return a weight of 1 for all edges. Also:: - floyd_warshall(graph, weight_fn=float) - to cast the edge object as a float as the weight. If this is not specified a default value (either ``default_weight`` or 1) will be used for all edges. @@ -369,17 +316,14 @@ def floyd_warshall( :param int parallel_threshold: The number of nodes to execute the algorithm in parallel at. It defaults to 300, but this can be tuned - :return: A read-only dictionary of path lengths. The keys are the source node indices and the values are a dict of the target node and the length of the shortest path to that node. For example:: - { 0: {0: 0.0, 1: 2.0, 2: 2.0}, 1: {1: 0.0, 2: 1.0}, 2: {0: 1.0, 2: 0.0}, } - :rtype: AllPairsPathLengthMapping """ raise TypeError("Invalid Input Type %s for graph" % type(graph)) @@ -423,30 +367,23 @@ def floyd_warshall_numpy( parallel_threshold=300, ): """Find all-pairs shortest path lengths using Floyd's algorithm - Floyd's algorithm is used for finding shortest paths in dense graphs or graphs with negative weights (where Dijkstra's algorithm fails). - This function is multithreaded and will launch a pool with threads equal to the number of CPUs by default if the number of nodes in the graph is above the value of ``parallel_threshold`` (it defaults to 300). You can tune the number of threads with the ``RAYON_NUM_THREADS`` environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would limit the thread pool to 4 threads if parallelization was enabled. - :param graph: The graph to run Floyd's algorithm on. Can - either be a :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + either be a :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param callable weight_fn: A callable object (function, lambda, etc) which will be passed the edge object and expected to return a ``float``. This - tells retworkx/rust how to extract a numerical weight as a ``float`` + tells rustworkx/rust how to extract a numerical weight as a ``float`` for edge object. Some simple examples are:: - floyd_warshall_numpy(graph, weight_fn: lambda x: 1) - to return a weight of 1 for all edges. Also:: - floyd_warshall_numpy(graph, weight_fn: lambda x: float(x)) - to cast the edge object as a float as the weight. If this is not specified a default value (either ``default_weight`` or 1) will be used for all edges. @@ -455,7 +392,6 @@ def floyd_warshall_numpy( :param int parallel_threshold: The number of nodes to execute the algorithm in parallel at. It defaults to 300, but this can be tuned - :returns: A matrix of shortest path distances between nodes. If there is no path between two nodes then the corresponding matrix entry will be ``np.inf``. @@ -489,9 +425,8 @@ def _graph_floyd_warshall_numpy(graph, weight_fn=None, default_weight=1.0, paral @functools.singledispatch def astar_shortest_path(graph, node, goal_fn, edge_cost_fn, estimate_cost_fn): """Compute the A* shortest path for a graph - :param graph: The input graph to use. Can - either be a :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + either be a :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param int node: The node index to compute the path from :param goal_fn: A python callable that will take in 1 parameter, a node's data object and will return a boolean which will be True if it is the @@ -505,7 +440,6 @@ def astar_shortest_path(graph, node, goal_fn, edge_cost_fn, estimate_cost_fn): the algorithm to find the actual shortest path, it should be admissible, meaning that it should never overestimate the actual cost to get to the nearest goal node. - :returns: The computed shortest path between node and finish as a list of node indices. :rtype: NodeIndices @@ -533,12 +467,10 @@ def dijkstra_shortest_paths( as_undirected=False, ): """Find the shortest path from a node - This function will generate the shortest path from a source node using Dijkstra's algorithm. - :param graph: The input graph to use. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param int source: The node index to find paths from :param int target: An optional target to find a path to :param weight_fn: An optional weight function for an edge. It will accept @@ -548,8 +480,7 @@ def dijkstra_shortest_paths( float value will be used for the weight/cost of each edge. :param bool as_undirected: If set to true the graph will be treated as undirected for finding the shortest path. This only works with a - :class:`~retworkx.PyDiGraph` input for ``graph`` - + :class:`~rustworkx.PyDiGraph` input for ``graph`` :return: Dictionary of paths. The keys are destination node indices and the dict values are lists of node indices making the path. :rtype: dict @@ -590,31 +521,26 @@ def _graph_dijkstra_shortest_path(graph, source, target=None, weight_fn=None, de @functools.singledispatch def all_pairs_dijkstra_shortest_paths(graph, edge_cost_fn): """For each node in the graph, finds the shortest paths to all others. - This function will generate the shortest path from all nodes in the graph using Dijkstra's algorithm. This function is multithreaded and will run launch a thread pool with threads equal to the number of CPUs by default. You can tune the number of threads with the ``RAYON_NUM_THREADS`` environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would limit the thread pool to 4 threads. - :param graph: The input graph to use. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param edge_cost_fn: A callable object that acts as a weight function for an edge. It will accept a single positional argument, the edge's weight object and will return a float which will be used to represent the weight/cost of the edge - :return: A read-only dictionary of paths. The keys are source node indices and the values are a dict of target node indices and a list of node indices making the path. For example:: - { 0: {1: [0, 1], 2: [0, 1, 2]}, 1: {2: [1, 2]}, 2: {0: [2, 0]}, } - :rtype: AllPairsPathMapping """ raise TypeError("Invalid Input Type %s for graph" % type(graph)) @@ -633,21 +559,18 @@ def _graph_all_pairs_dijkstra_shortest_path(graph, edge_cost_fn): @functools.singledispatch def all_pairs_all_simple_paths(graph, min_depth=None, cutoff=None): """Return all the simple paths between all pairs of nodes in the graph - This function is multithreaded and will launch a thread pool with threads equal to the number of CPUs by default. You can tune the number of threads with the ``RAYON_NUM_THREADS`` environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would limit the thread pool to 4 threads. - - :param graph: The graph to find all simple paths in. This can be a :class:`~retworkx.PyGraph` - or a :class:`~retworkx.PyDiGraph` + :param graph: The graph to find all simple paths in. This can be a :class:`~rustworkx.PyGraph` + or a :class:`~rustworkx.PyDiGraph` :param int min_depth: The minimum depth of the path to include in the output list of paths. By default all paths are included regardless of depth, setting to 0 will behave like the default. :param int cutoff: The maximum depth of path to include in the output list of paths. By default includes all paths regardless of depth, setting to 0 will behave like default. - :returns: A mapping of source node indices to a mapping of target node indices to a list of paths between the source and target nodes. :rtype: AllPairsMultiplePathMapping @@ -668,31 +591,26 @@ def _graph_all_pairs_all_simple_paths(graph, min_depth=None, cutoff=None): @functools.singledispatch def all_pairs_dijkstra_path_lengths(graph, edge_cost_fn): """For each node in the graph, calculates the lengths of the shortest paths to all others. - This function will generate the shortest path lengths from all nodes in the graph using Dijkstra's algorithm. This function is multithreaded and will launch a thread pool with threads equal to the number of CPUs by default. You can tune the number of threads with the ``RAYON_NUM_THREADS`` environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would limit the thread pool to 4 threads. - :param graph: The input graph to use. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param edge_cost_fn: A callable object that acts as a weight function for an edge. It will accept a single positional argument, the edge's weight object and will return a float which will be used to represent the weight/cost of the edge - :return: A read-only dictionary of path lengths. The keys are the source node indices and the values are a dict of the target node and the length of the shortest path to that node. For example:: - { 0: {1: 2.0, 2: 2.0}, 1: {2: 1.0}, 2: {0: 1.0}, } - :rtype: AllPairsPathLengthMapping """ raise TypeError("Invalid Input Type %s for graph" % type(graph)) @@ -712,9 +630,8 @@ def _graph_all_pairs_dijkstra_path_lengths(graph, edge_cost_fn): def dijkstra_shortest_path_lengths(graph, node, edge_cost_fn, goal=None): """Compute the lengths of the shortest paths for a graph object using Dijkstra's algorithm. - :param graph: The input graph to use. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param int node: The node index to use as the source for finding the shortest paths from :param edge_cost_fn: A python callable that will take in 1 parameter, an @@ -724,7 +641,6 @@ def dijkstra_shortest_path_lengths(graph, node, edge_cost_fn, goal=None): When specified the traversal will stop when the goal is reached and the output dictionary will only have a single entry with the length of the shortest path to the goal node. - :returns: A dictionary of the shortest paths from the provided node where the key is the node index of the end of the path and the value is the cost/sum of the weights of path @@ -746,21 +662,17 @@ def _graph_dijkstra_shortest_path_lengths(graph, node, edge_cost_fn, goal=None): @functools.singledispatch def k_shortest_path_lengths(graph, start, k, edge_cost, goal=None): """Compute the length of the kth shortest path - Computes the lengths of the kth shortest path from ``start`` to every reachable node. - Computes in :math:`O(k * (|E| + |V|*log(|V|)))` time (average). - :param graph: The graph to find the shortest paths in. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param int start: The node index to find the shortest paths from :param int k: The kth shortest path to find the lengths of :param edge_cost: A python callable that will receive an edge payload and return a float for the cost of that eedge :param int goal: An optional goal node index, if specified the output dictionary - :returns: A dict of lengths where the key is the destination node index and the value is the length of the path. :rtype: dict @@ -781,12 +693,9 @@ def _graph_k_shortest_path_lengths(graph, start, k, edge_cost, goal=None): @functools.singledispatch def dfs_edges(graph, source=None): """Get an edge list of the tree edges from a depth-first traversal - The pseudo-code for the DFS algorithm is listed below. The output contains the tree edges found by the procedure. - :: - DFS(G, v) let S be a stack label v as discovered @@ -801,20 +710,16 @@ def dfs_edges(graph, source=None): else POP(S) end while - .. note:: - If the input is an undirected graph with a single connected component, the output of this function is a spanning tree. - :param graph: The graph to get the DFS edge list from. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param int source: An optional node index to use as the starting node for the depth-first search. The edge list will only return edges in the components reachable from this index. If this is not specified then a source will be chosen arbitrarly and repeated until all components of the graph are searched. - :returns: A list of edges as a tuple of the form ``(source, target)`` in depth-first order :rtype: EdgeList @@ -842,26 +747,21 @@ def is_isomorphic( call_limit=None, ): """Determine if 2 graphs are isomorphic - This checks if 2 graphs are isomorphic both structurally and also comparing the node and edge data using the provided matcher functions. The matcher functions take in 2 data objects and will compare them. A simple example that checks if they're just equal would be:: - - graph_a = retworkx.PyGraph() - graph_b = retworkx.PyGraph() - retworkx.is_isomorphic(graph_a, graph_b, + graph_a = rustworkx.PyGraph() + graph_b = rustworkx.PyGraph() + rustworkx.is_isomorphic(graph_a, graph_b, lambda x, y: x == y) - .. note:: - For better performance on large graphs, consider setting `id_order=False`. - :param first: The first graph to compare. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. :param second: The second graph to compare. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. It should be the same type as the first graph. :param callable node_matcher: A python callable object that takes 2 positional one for each node data object. If the return of this @@ -877,11 +777,9 @@ def is_isomorphic( :param int call_limit: An optional bound on the number of states that VF2 algorithm visits while searching for a solution. If it exceeds this limit, the algorithm will stop and return ``False``. - :returns: ``True`` if the 2 graphs are isomorphic, ``False`` if they are not. :rtype: bool - .. [VF2] VF2++ An Improved Subgraph Isomorphism Algorithm by Alpár Jüttner and Péter Madarasi """ @@ -915,26 +813,21 @@ def _graph_is_isomorphic( @functools.singledispatch def is_isomorphic_node_match(first, second, matcher, id_order=True): """Determine if 2 graphs are isomorphic - This checks if 2 graphs are isomorphic both structurally and also comparing the node data using the provided matcher function. The matcher function takes in 2 node data objects and will compare them. A simple example that checks if they're just equal would be:: - - graph_a = retworkx.PyDAG() - graph_b = retworkx.PyDAG() - retworkx.is_isomorphic_node_match(graph_a, graph_b, + graph_a = rustworkx.PyDAG() + graph_b = rustworkx.PyDAG() + rustworkx.is_isomorphic_node_match(graph_a, graph_b, lambda x, y: x == y) - .. note:: - For better performance on large graphs, consider setting `id_order=False`. - :param first: The first graph to compare. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. :param second: The second graph to compare. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. It should be the same type as the first graph. :param callable matcher: A python callable object that takes 2 positional one for each node data object. If the return of this @@ -943,7 +836,6 @@ def is_isomorphic_node_match(first, second, matcher, id_order=True): :param bool id_order: If set to ``False`` this function will use a heuristic matching order based on [VF2]_ paper. Otherwise it will default to matching the nodes in order specified by their ids. - :returns: ``True`` if the 2 graphs are isomorphic ``False`` if they are not. :rtype: bool @@ -972,7 +864,6 @@ def is_subgraph_isomorphic( call_limit=None, ): """Determine if 2 graphs are subgraph isomorphic - This checks if 2 graphs are subgraph isomorphic both structurally and also comparing the node and edge data using the provided matcher functions. The matcher functions take in 2 data objects and will compare them. @@ -981,17 +872,14 @@ def is_subgraph_isomorphic( set to `False`, we check for a non induced subgraph, meaning the second graph can have fewer edges than the subgraph of the first. By default it's `True`. A simple example that checks if they're just equal would be:: - - graph_a = retworkx.PyGraph() - graph_b = retworkx.PyGraph() - retworkx.is_subgraph_isomorphic(graph_a, graph_b, + graph_a = rustworkx.PyGraph() + graph_b = rustworkx.PyGraph() + rustworkx.is_subgraph_isomorphic(graph_a, graph_b, lambda x, y: x == y) - - :param first: The first graph to compare. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. :param second: The second graph to compare. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. It should be the same type as the first graph. :param callable node_matcher: A python callable object that takes 2 positional one for each node data object. If the return of this @@ -1010,7 +898,6 @@ def is_subgraph_isomorphic( :param int call_limit: An optional bound on the number of states that VF2 algorithm visits while searching for a solution. If it exceeds this limit, the algorithm will stop and return ``False``. - :returns: ``True`` if there is a subgraph of `first` isomorphic to `second` , ``False`` if there is not. :rtype: bool @@ -1051,22 +938,17 @@ def _graph_is_subgraph_isomorphic( @functools.singledispatch def transitivity(graph): """Compute the transitivity of a graph. - This function is multithreaded and will run launch a thread pool with threads equal to the number of CPUs by default. You can tune the number of threads with the ``RAYON_NUM_THREADS`` environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would limit the thread pool to 4 threads. - .. note:: - The function implicitly assumes that there are no parallel edges or self loops. It may produce incorrect/unexpected results if the input graph has self loops or parallel edges. - :param graph: The graph to be used. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. - + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. :returns: Transitivity of the graph. :rtype: float raise TypeError("Invalid Input Type %s for graph" % type(graph)) @@ -1087,18 +969,13 @@ def _graph_transitivity(graph): @functools.singledispatch def core_number(graph): """Return the core number for each node in the graph. - A k-core is a maximal subgraph that contains nodes of degree k or more. - .. note:: - The function implicitly assumes that there are no parallel edges or self loops. It may produce incorrect/unexpected results if the input graph has self loops or parallel edges. - :param graph: The graph to get core numbers. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` - + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :returns: A dictionary keyed by node index to the core number :rtype: dict raise TypeError("Invalid Input Type %s for graph" % type(graph)) @@ -1119,13 +996,10 @@ def _graph_core_number(graph): @functools.singledispatch def complement(graph): """Compute the complement of a graph. - :param graph: The graph to be used, can be either a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. - + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. :returns: The complement of the graph. - :rtype: :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` - + :rtype: :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` .. note:: Parallel edges and self-loops are never created, even if the ``multigraph`` is set to ``True`` @@ -1143,41 +1017,13 @@ def _graph_complement(graph): return graph_complement(graph) -@functools.singledispatch -def planar_layout(graph, scale=1.0, center=None): - """Generate a planar layout - - :param PyGraph|PyDiGraph graph: The graph to generate the layout for - :param float|None scale: Scale factor for positions.If scale is ``None``, - no re-scaling is performed. (``default=1.0``) - :param tuple center: An optional center position. This is a 2 tuple of two - ``float`` values for the center position - - :returns: The planar layout of the graph. - :rtype: Pos2DMapping - """ - raise TypeError("Invalid Input Type %s for graph" % type(graph)) - - -@planar_layout.register(PyDiGraph) -def _digraph_planar_layout(graph, scale=1.0, center=None): - return digraph_planar_layout(graph, scale=1.0, center=center) - - -@planar_layout.register(PyGraph) -def _graph_planar_layout(graph, scale=1.0, center=None): - return graph_planar_layout(graph, scale=1.0, center=center) - - @functools.singledispatch def random_layout(graph, center=None, seed=None): """Generate a random layout - :param PyGraph graph: The graph to generate the layout for :param tuple center: An optional center position. This is a 2 tuple of two ``float`` values for the center position :param int seed: An optional seed to set for the random number generator. - :returns: The random layout of the graph. :rtype: Pos2DMapping """ @@ -1212,14 +1058,12 @@ def spring_layout( ): """ Position nodes using Fruchterman-Reingold force-directed algorithm. - The algorithm simulates a force-directed representation of the network treating edges as springs holding nodes close, while treating nodes as repelling objects, sometimes called an anti-gravity force. Simulation continues until the positions are close to an equilibrium. - :param graph: Graph to be used. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. :param dict pos: Initial node positions as a dictionary with node ids as keys and values as a coordinate list. If ``None``, then use random initial positions. @@ -1252,7 +1096,6 @@ def spring_layout( :param list center: Coordinate pair around which to center the layout. Not used unless fixed is ``None``. (``default=None``) :param int seed: An optional seed to use for the random number generator - :returns: A dictionary of positions keyed by node id. :rtype: dict """ @@ -1326,27 +1169,23 @@ def _graph_spring_layout( def networkx_converter(graph, keep_attributes: bool = False): - """Convert a networkx graph object into a retworkx graph object. - + """Convert a networkx graph object into a rustworkx graph object. .. note:: - - networkx is **not** a dependency of retworkx and this function + networkx is **not** a dependency of rustworkx and this function is provided as a convenience method for users of both networkx and - retworkx. This function will not work unless you install networkx + rustworkx. This function will not work unless you install networkx independently. - :param networkx.Graph graph: The networkx graph to convert. :param bool keep_attributes: If ``True``, add networkx node attributes to - the data payload in the nodes of the output retworkx graph. When set to - ``True``, the node data payloads in the output retworkx graph object + the data payload in the nodes of the output rustworkx graph. When set to + ``True``, the node data payloads in the output rustworkx graph object will be dictionaries with the node attributes from the input networkx graph where the ``"__networkx_node__"`` key contains the node from the input networkx graph. - - :returns: A retworkx graph, either a :class:`~retworkx.PyDiGraph` or a - :class:`~retworkx.PyGraph` based on whether the input graph is directed + :returns: A rustworkx graph, either a :class:`~rustworkx.PyDiGraph` or a + :class:`~rustworkx.PyGraph` based on whether the input graph is directed or not. - :rtype: :class:`~retworkx.PyDiGraph` or :class:`~retworkx.PyGraph` + :rtype: :class:`~rustworkx.PyDiGraph` or :class:`~rustworkx.PyGraph` """ if graph.is_directed(): new_graph = PyDiGraph(multigraph=graph.is_multigraph()) @@ -1377,9 +1216,8 @@ def bipartite_layout( aspect_ratio=4 / 3, ): """Generate a bipartite layout of the graph - :param graph: The graph to generate the layout for. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param set first_nodes: The set of node indices on the left (or top if horitontal is true) :param bool horizontal: An optional bool specifying the orientation of the @@ -1389,7 +1227,6 @@ def bipartite_layout( ``float`` values for the center position :param float aspect_ratio: An optional number for the ratio of the width to the height of the layout. - :returns: The bipartite layout of the graph. :rtype: Pos2DMapping """ @@ -1437,13 +1274,11 @@ def _graph_bipartite_layout( @functools.singledispatch def circular_layout(graph, scale=1, center=None): """Generate a circular layout of the graph - :param graph: The graph to generate the layout for. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param float scale: An optional scaling factor to scale positions :param tuple center: An optional center position. This is a 2 tuple of two ``float`` values for the center position - :returns: The circular layout of the graph. :rtype: Pos2DMapping """ @@ -1464,9 +1299,8 @@ def _graph_circular_layout(graph, scale=1, center=None): def shell_layout(graph, nlist=None, rotate=None, scale=1, center=None): """ Generate a shell layout of the graph - :param graph: The graph to generate the layout for. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param list nlist: The list of lists of indices which represents each shell :param float rotate: Angle (in radians) by which to rotate the starting position of each shell relative to the starting position of the @@ -1474,7 +1308,6 @@ def shell_layout(graph, nlist=None, rotate=None, scale=1, center=None): :param float scale: An optional scaling factor to scale positions :param tuple center: An optional center position. This is a 2 tuple of two ``float`` values for the center position - :returns: The shell layout of the graph. :rtype: Pos2DMapping """ @@ -1495,9 +1328,8 @@ def _graph_shell_layout(graph, nlist=None, rotate=None, scale=1, center=None): def spiral_layout(graph, scale=1, center=None, resolution=0.35, equidistant=False): """ Generate a spiral layout of the graph - :param graph: The graph to generate the layout for. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param float scale: An optional scaling factor to scale positions :param tuple center: An optional center position. This is a 2 tuple of two ``float`` values for the center position @@ -1505,7 +1337,6 @@ def spiral_layout(graph, scale=1, center=None, resolution=0.35, equidistant=Fals Lower values result in more compressed spiral layouts. :param bool equidistant: If true, nodes will be plotted equidistant from each other. - :returns: The spiral layout of the graph. :rtype: Pos2DMapping """ @@ -1537,10 +1368,8 @@ def _graph_spiral_layout(graph, scale=1, center=None, resolution=0.35, equidista @functools.singledispatch def num_shortest_paths_unweighted(graph, source): """Get the number of unweighted shortest paths from a source node - :param PyDiGraph graph: The graph to find the number of shortest paths on :param int source: The source node to find the shortest paths from - :returns: A mapping of target node indices to the number of shortest paths from ``source`` to that node. If there is no path from ``source`` to a node in the graph that node will not be preset in the output mapping. @@ -1562,30 +1391,22 @@ def _graph_num_shortest_paths_unweighted(graph, source): @functools.singledispatch def betweenness_centrality(graph, normalized=True, endpoints=False, parallel_threshold=50): r"""Returns the betweenness centrality of each node in the graph. - Betweenness centrality of a node :math:`v` is the sum of the fraction of all-pairs shortest paths that pass through :math`v` - .. math:: - c_B(v) =\sum_{s,t \in V} \frac{\sigma(s, t|v)}{\sigma(s, t)} - where :math:`V` is the set of nodes, :math:`\sigma(s, t)` is the number of shortest :math`(s, t)` paths, and :math:`\sigma(s, t|v)` is the number of those paths passing through some node :math:`v` other than :math:`s, t`. If :math:`s = t`, :math:`\sigma(s, t) = 1`, and if :math:`v \in {s, t}`, :math:`\sigma(s, t|v) = 0` - The algorithm used in this function is based on: - Ulrik Brandes, A Faster Algorithm for Betweenness Centrality. Journal of Mathematical Sociology 25(2):163-177, 2001. - This function is multithreaded and will run in parallel if the number of nodes in the graph is above the value of ``parallel_threshold`` (it defaults to 50). If the function will be running in parallel the env var ``RAYON_NUM_THREADS`` can be used to adjust how many threads will be used. - :param PyDiGraph graph: The input graph :param bool normalized: Whether to normalize the betweenness scores by the number of distinct paths between all pairs of nodes. @@ -1595,7 +1416,6 @@ def betweenness_centrality(graph, normalized=True, endpoints=False, parallel_thr the betweenness centrality in parallel at if the number of nodes in the graph is less than this value it will run in a single thread. The default value is 50 - :returns: A dictionary mapping each node index to its betweenness centrality. :rtype: dict """ @@ -1625,13 +1445,10 @@ def _graph_betweenness_centrality(graph, normalized=True, endpoints=False, paral @functools.singledispatch def eigenvector_centrality(graph, weight_fn=None, default_weight=1.0, max_iter=100, tol=1e-6): """Compute the eigenvector centrality of a graph. - For details on the eigenvector centrality refer to: - Phillip Bonacich. “Power and Centrality: A Family of Measures.” American Journal of Sociology 92(5):1170–1182, 1986 - This function uses a power iteration method to compute the eigenvector and convergence is not guaranteed. The function will stop when `max_iter` iterations is reached or when the computed vector between two iterations @@ -1639,12 +1456,10 @@ def eigenvector_centrality(graph, weight_fn=None, default_weight=1.0, max_iter=1 The implementation of this algorithm is based on the NetworkX `eigenvector_centrality() `__ function. - In the case of multigraphs the weights of any parallel edges will be summed when computing the eigenvector centrality. - :param graph: Graph to be used. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. :param weight_fn: An optional input callable that will be passed the edge's payload object and is expected to return a `float` weight for that edge. If this is not specified ``default_weight`` will be used as the weight @@ -1655,7 +1470,6 @@ def eigenvector_centrality(graph, weight_fn=None, default_weight=1.0, max_iter=1 not specified a default value of 100 is used. :param float tol: The error tolerance used when checking for convergence in the power method. If this is not specified default value of 1e-6 is used. - :returns: a read-only dict-like object whose keys are the node indices and values are the centrality score for that node. :rtype: CentralityMapping @@ -1693,22 +1507,19 @@ def vf2_mapping( ): """ Return an iterator over all vf2 mappings between two graphs. - This funcion will run the vf2 algorithm used from - :func:`~retworkx.is_isomorphic` and :func:`~retworkx.is_subgraph_isomorphic` + :func:`~rustworkx.is_isomorphic` and :func:`~rustworkx.is_subgraph_isomorphic` but instead of returning a boolean it will return an iterator over all possible mapping of node ids found from ``first`` to ``second``. If the graphs are not isomorphic then the iterator will be empty. A simple example that retrieves one mapping would be:: - - graph_a = retworkx.generators.path_graph(3) - graph_b = retworkx.generators.path_graph(2) - vf2 = retworkx.vf2_mapping(graph_a, graph_b, subgraph=True) + graph_a = rustworkx.generators.path_graph(3) + graph_b = rustworkx.generators.path_graph(2) + vf2 = rustworkx.vf2_mapping(graph_a, graph_b, subgraph=True) try: mapping = next(vf2) except StopIteration: pass - :param first: The first graph to find the mapping for :param second: The second graph to find the mapping for :param node_matcher: An optional python callable object that takes 2 @@ -1730,7 +1541,6 @@ def vf2_mapping( :param int call_limit: An optional bound on the number of states that VF2 algorithm visits while searching for a solution. If it exceeds this limit, the algorithm will stop. Default: ``None``. - :returns: An iterator over dicitonaries of node indices from ``first`` to node indices in ``second`` representing the mapping found. :rtype: Iterable[NodeMap] @@ -1792,35 +1602,29 @@ def union( merge_edges=False, ): """Return a new graph by forming a union from two input graph objects - The algorithm in this function operates in three phases: - 1. Add all the nodes from ``second`` into ``first``. operates in :math:`\\mathcal{O}(n_2)`, with :math:`n_2` being number of nodes in ``second``. 2. Merge nodes from ``second`` over ``first`` given that: - - The ``merge_nodes`` is ``True``. operates in :math:`\\mathcal{O}(n_1 n_2)`, with :math:`n_1` being the number of nodes in ``first`` and :math:`n_2` the number of nodes in ``second`` - The respective node in ``second`` and ``first`` share the same weight/data payload. - 3. Adds all the edges from ``second`` to ``first``. If the ``merge_edges`` parameter is ``True`` and the respective edge in ``second`` and ``first`` share the same weight/data payload they will be merged together. - :param first: The first graph object :param second: The second graph object :param bool merge_nodes: If set to ``True`` nodes will be merged between ``second`` and ``first`` if the weights are equal. Default: ``False``. :param bool merge_edges: If set to ``True`` edges will be merged between ``second`` and ``first`` if the weights are equal. Default: ``False``. - :returns: A new graph object that is the union of ``second`` and ``first``. It's worth noting the weight/data payload objects are passed by reference from ``first`` and ``second`` to this new object. - :rtype: :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :rtype: :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` """ raise TypeError("Invalid Input Type %s for graph" % type(first)) @@ -1852,10 +1656,8 @@ def tensor_product( ): """Return a new graph by forming the tensor product from two input graph objects - :param first: The first graph object :param second: The second graph object - :returns: A new graph object that is the tensor product of ``second`` and ``first``. It's worth noting the weight/data payload objects are passed by reference from ``first`` and ``second`` to this new object. @@ -1863,14 +1665,12 @@ def tensor_product( are a tuple where the first element is a node of the first graph and the second element is a node of the second graph, and the values are the map of those elements to node indices in the product graph. For example:: - { (0, 0): 0, (0, 1): 1, } - - :rtype: Tuple[:class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`, - :class:`~retworkx.ProductNodeMap`] + :rtype: Tuple[:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`, + :class:`~rustworkx.ProductNodeMap`] """ raise TypeError("Invalid Input Type %s for graph" % type(first)) @@ -1898,10 +1698,8 @@ def cartesian_product( ): """Return a new graph by forming the cartesian product from two input graph objects - :param first: The first graph object :param second: The second graph object - :returns: A new graph object that is the union of ``second`` and ``first``. It's worth noting the weight/data payload objects are passed by reference from ``first`` and ``second`` to this new object. @@ -1909,14 +1707,12 @@ def cartesian_product( are a tuple where the first element is a node of the first graph and the second element is a node of the second graph, and the values are the map of those elements to node indices in the product graph. For example:: - { (0, 0): 0, (0, 1): 1, } - - :rtype: Tuple[:class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`, - :class:`~retworkx.ProductNodeMap`] + :rtype: Tuple[:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`, + :class:`~rustworkx.ProductNodeMap`] """ raise TypeError("Invalid Input Type %s for graph" % type(first)) @@ -1940,13 +1736,10 @@ def _graph_cartesian_product( @functools.singledispatch def bfs_search(graph, source, visitor): """Breadth-first traversal of a directed/undirected graph. - The pseudo-code for the BFS algorithm is listed below, with the annotated event points, for which the given visitor object will be called with the appropriate method. - :: - BFS(G, s) for each vertex u in V color[u] := WHITE @@ -1966,47 +1759,35 @@ def bfs_search(graph, source, visitor): end for color[u] := BLACK finish vertex u end while - If an exception is raised inside the callback function, the graph traversal will be stopped immediately. You can exploit this to exit early by raising a - :class:`~retworkx.visit.StopSearch` exception, in which case the search function + :class:`~rustworkx.visit.StopSearch` exception, in which case the search function will return but without raising back the exception. You can also prune part of - the search tree by raising :class:`~retworkx.visit.PruneSearch`. - + the search tree by raising :class:`~rustworkx.visit.PruneSearch`. In the following example we keep track of the tree edges: - .. jupyter-execute:: - - import retworkx - from retworkx.visit import BFSVisitor - - + import rustworkx + from rustworkx.visit import BFSVisitor class TreeEdgesRecorder(BFSVisitor): - def __init__(self): self.edges = [] - def tree_edge(self, edge): self.edges.append(edge) - - graph = retworkx.PyDiGraph() + graph = rustworkx.PyDiGraph() graph.extend_from_edge_list([(1, 3), (0, 1), (2, 1), (0, 2)]) vis = TreeEdgesRecorder() - retworkx.bfs_search(graph, [0], vis) + rustworkx.bfs_search(graph, [0], vis) print('Tree edges:', vis.edges) - .. note:: - Graph can **not** be mutated while traversing. - - :param graph: The graph to be used. This can be a :class:`~retworkx.PyGraph` - or a :class:`~retworkx.PyDiGraph` + :param graph: The graph to be used. This can be a :class:`~rustworkx.PyGraph` + or a :class:`~rustworkx.PyDiGraph` :param List[int] source: An optional list of node indices to use as the starting nodes for the breadth-first search. If this is not specified then a source will be chosen arbitrarly and repeated until all components of the graph are searched. :param visitor: A visitor object that is invoked at the event points inside the - algorithm. This should be a subclass of :class:`~retworkx.visit.BFSVisitor`. + algorithm. This should be a subclass of :class:`~rustworkx.visit.BFSVisitor`. """ raise TypeError("Invalid Input Type %s for graph" % type(graph)) @@ -2024,20 +1805,16 @@ def _graph_bfs_search(graph, source, visitor): @functools.singledispatch def dfs_search(graph, source, visitor): """Depth-first traversal of a directed/undirected graph. - The pseudo-code for the DFS algorithm is listed below, with the annotated event points, for which the given visitor object will be called with the appropriate method. - :: - DFS(G) for each vertex u in V color[u] := WHITE initialize vertex u end for time := 0 call DFS-VISIT(G, source) start vertex s - DFS-VISIT(G, u) color[u] := GRAY discover vertex u for each v in Adj[u] examine edge (u,v) @@ -2049,44 +1826,33 @@ def dfs_search(graph, source, visitor): ... end for color[u] := BLACK finish vertex u - If an exception is raised inside the callback function, the graph traversal will be stopped immediately. You can exploit this to exit early by raising a - :class:`~retworkx.visit.StopSearch` exception. You can also prune part of the - search tree by raising :class:`~retworkx.visit.PruneSearch`. - + :class:`~rustworkx.visit.StopSearch` exception. You can also prune part of the + search tree by raising :class:`~rustworkx.visit.PruneSearch`. In the following example we keep track of the tree edges: - .. jupyter-execute:: - - import retworkx - from retworkx.visit import DFSVisitor - + import rustworkx + from rustworkx.visit import DFSVisitor class TreeEdgesRecorder(DFSVisitor): - def __init__(self): self.edges = [] - def tree_edge(self, edge): self.edges.append(edge) - - graph = retworkx.PyGraph() + graph = rustworkx.PyGraph() graph.extend_from_edge_list([(1, 3), (0, 1), (2, 1), (0, 2)]) vis = TreeEdgesRecorder() - retworkx.dfs_search(graph, [0], vis) + rustworkx.dfs_search(graph, [0], vis) print('Tree edges:', vis.edges) - .. note:: - Graph can *not* be mutated while traversing. - :param PyGraph graph: The graph to be used. :param List[int] source: An optional list of node indices to use as the starting nodes for the depth-first search. If this is not specified then a source will be chosen arbitrarly and repeated until all components of the graph are searched. :param visitor: A visitor object that is invoked at the event points inside the - algorithm. This should be a subclass of :class:`~retworkx.visit.DFSVisitor`. + algorithm. This should be a subclass of :class:`~rustworkx.visit.DFSVisitor`. """ raise TypeError("Invalid Input Type %s for graph" % type(graph)) @@ -2104,13 +1870,10 @@ def _graph_dfs_search(graph, source, visitor): @functools.singledispatch def dijkstra_search(graph, source, weight_fn, visitor): """Dijkstra traversal of a graph. - The pseudo-code for the Dijkstra algorithm is listed below, with the annotated event points, for which the given visitor object will be called with the appropriate method. - :: - DIJKSTRA(G, source, weight) for each vertex u in V d[u] := infinity @@ -2131,19 +1894,15 @@ def dijkstra_search(graph, source, weight_fn, visitor): INSERT(Q, v) end for finish vertex u end while - If an exception is raised inside the callback function, the graph traversal will be stopped immediately. You can exploit this to exit early by raising a - :class:`~retworkx.visit.StopSearch` exception, in which case the search function + :class:`~rustworkx.visit.StopSearch` exception, in which case the search function will return but without raising back the exception. You can also prune part of the - search tree by raising :class:`~retworkx.visit.PruneSearch`. - + search tree by raising :class:`~rustworkx.visit.PruneSearch`. .. note:: - Graph can **not** be mutated while traversing. - - :param graph: The graph to be used. This can be a :class:`~retworkx.PyGraph` - or a :class:`~retworkx.PyDiGraph`. + :param graph: The graph to be used. This can be a :class:`~rustworkx.PyGraph` + or a :class:`~rustworkx.PyDiGraph`. :param List[int] source: An optional list of node indices to use as the starting nodes for the dijkstra search. If this is not specified then a source will be chosen arbitrarly and repeated until all components of the @@ -2153,7 +1912,7 @@ def dijkstra_search(graph, source, weight_fn, visitor): will be used to represent the weight/cost of the edge. If not specified, a default value of cost ``1.0`` will be used for each edge. :param visitor: A visitor object that is invoked at the event points inside the - algorithm. This should be a subclass of :class:`~retworkx.visit.DijkstraVisitor`. + algorithm. This should be a subclass of :class:`~rustworkx.visit.DijkstraVisitor`. """ raise TypeError("Invalid Input Type %s for graph" % type(graph)) @@ -2178,12 +1937,10 @@ def bellman_ford_shortest_paths( as_undirected=False, ): """Find the shortest path from a node - This function will generate the shortest path from a source node using the Bellman-Ford algorithm wit the SPFA heuristic. - :param graph: The input graph to use. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param int source: The node index to find paths from :param int target: An optional target to find a path to :param weight_fn: An optional weight function for an edge. It will accept @@ -2193,13 +1950,11 @@ def bellman_ford_shortest_paths( float value will be used for the weight/cost of each edge. :param bool as_undirected: If set to true the graph will be treated as undirected for finding the shortest path. This only works with a - :class:`~retworkx.PyDiGraph` input for ``graph`` - + :class:`~rustworkx.PyDiGraph` input for ``graph`` :return: A read-only dictionary of paths. The keys are destination node indices and the dict values are lists of node indices making the path. :rtype: PathMapping - - :raises: :class:`~retworkx.NegativeCycle`: when there is a negative cycle and the shortest + :raises: :class:`~rustworkx.NegativeCycle`: when there is a negative cycle and the shortest path is not defined """ raise TypeError("Invalid Input Type %s for graph" % type(graph)) @@ -2241,9 +1996,8 @@ def _graph_bellman_ford_shortest_path( def bellman_ford_shortest_path_lengths(graph, node, edge_cost_fn, goal=None): """Compute the lengths of the shortest paths for a graph object using the Bellman-Ford algorithm with the SPFA heuristic. - :param graph: The input graph to use. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param int node: The node index to use as the source for finding the shortest paths from :param edge_cost_fn: A python callable that will take in 1 parameter, an @@ -2252,13 +2006,11 @@ def bellman_ford_shortest_path_lengths(graph, node, edge_cost_fn, goal=None): :param int goal: An optional node index to use as the end of the path. When specified the output dictionary will only have a single entry with the length of the shortest path to the goal node. - :returns: A read-only dictionary of the shortest paths from the provided node where the key is the node index of the end of the path and the value is the cost/sum of the weights of path :rtype: PathLengthMapping - - :raises: :class:`~retworkx.NegativeCycle`: when there is a negative cycle and the shortest + :raises: :class:`~rustworkx.NegativeCycle`: when there is a negative cycle and the shortest path is not defined """ raise TypeError("Invalid Input Type %s for graph" % type(graph)) @@ -2277,34 +2029,28 @@ def _graph_bellman_ford_shortest_path_lengths(graph, node, edge_cost_fn, goal=No @functools.singledispatch def all_pairs_bellman_ford_path_lengths(graph, edge_cost_fn): """For each node in the graph, calculates the lengths of the shortest paths to all others. - This function will generate the shortest path lengths from all nodes in the graph using the Bellman-Ford algorithm. This function is multithreaded and will launch a thread pool with threads equal to the number of CPUs by default. You can tune the number of threads with the ``RAYON_NUM_THREADS`` environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would limit the thread pool to 4 threads. - :param graph: The input graph to use. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param edge_cost_fn: A callable object that acts as a weight function for an edge. It will accept a single positional argument, the edge's weight object and will return a float which will be used to represent the weight/cost of the edge - :return: A read-only dictionary of path lengths. The keys are the source node indices and the values are a dict of the target node and the length of the shortest path to that node. For example:: - { 0: {1: 2.0, 2: 2.0}, 1: {2: 1.0}, 2: {0: 1.0}, } - :rtype: AllPairsPathLengthMapping - - :raises: :class:`~retworkx.NegativeCycle`: when there is a negative cycle and the shortest + :raises: :class:`~rustworkx.NegativeCycle`: when there is a negative cycle and the shortest path is not defined """ raise TypeError("Invalid Input Type %s for graph" % type(graph)) @@ -2323,34 +2069,28 @@ def _graph_all_pairs_bellman_ford_path_lengths(graph, edge_cost_fn): @functools.singledispatch def all_pairs_bellman_ford_shortest_paths(graph, edge_cost_fn): """For each node in the graph, finds the shortest paths to all others. - This function will generate the shortest path from all nodes in the graph using the Bellman-Ford algorithm. This function is multithreaded and will run launch a thread pool with threads equal to the number of CPUs by default. You can tune the number of threads with the ``RAYON_NUM_THREADS`` environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would limit the thread pool to 4 threads. - :param graph: The input graph to use. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param edge_cost_fn: A callable object that acts as a weight function for an edge. It will accept a single positional argument, the edge's weight object and will return a float which will be used to represent the weight/cost of the edge - :return: A read-only dictionary of paths. The keys are source node indices and the values are a dict of target node indices and a list of node indices making the path. For example:: - { 0: {1: [0, 1], 2: [0, 1, 2]}, 1: {2: [1, 2]}, 2: {0: [2, 0]}, } - :rtype: AllPairsPathMapping - - :raises: :class:`~retworkx.NegativeCycle`: when there is a negative cycle and the shortest + :raises: :class:`~rustworkx.NegativeCycle`: when there is a negative cycle and the shortest path is not defined """ raise TypeError("Invalid Input Type %s for graph" % type(graph)) @@ -2364,3 +2104,45 @@ def _digraph_all_pairs_bellman_ford_shortest_path(graph, edge_cost_fn): @all_pairs_bellman_ford_shortest_paths.register(PyGraph) def _graph_all_pairs_bellman_ford_shortest_path(graph, edge_cost_fn): return graph_all_pairs_bellman_ford_shortest_paths(graph, edge_cost_fn) + + +@functools.singledispatch +def node_link_json(graph, path=None, graph_attrs=None, node_attrs=None, edge_attrs=None): + """Generate a JSON object representing a graph in a node-link format + :param graph: The graph to generate the JSON for. Can either be a + :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. + :param str path: An optional path to write the JSON output to. If specified + the function will not return anything and instead will write the JSON + to the file specified. + :param graph_attrs: An optional callable that will be passed the + :attr:`~.PyGraph.attrs` attribute of the graph and is expected to + return a dictionary of string keys to string values representing the + graph attributes. This dictionary will be included as attributes in + the output JSON. If anything other than a dictionary with string keys + and string values is returned an exception will be raised. + :param node_attrs: An optional callable that will be passed the node data + payload for each node in the graph and is expected to return a + dictionary of string keys to string values representing the data payload. + This dictionary will be used as the ``data`` field for each node. + :param edge_attrs: An optional callable that will be passed the edge data + payload for each node in the graph and is expected to return a + dictionary of string keys to string values representing the data payload. + This dictionary will be used as the ``data`` field for each edge. + :returns: Either the JSON string for the payload or ``None`` if ``path`` is specified + :rtype: str + """ + raise TypeError("Invalid Input Type %s for graph" % type(graph)) + + +@node_link_json.register(PyDiGraph) +def _digraph_node_link_json(graph, path=None, graph_attrs=None, node_attrs=None, edge_attrs=None): + return digraph_node_link_json( + graph, path=path, graph_attrs=graph_attrs, node_attrs=node_attrs, edge_attrs=edge_attrs + ) + + +@node_link_json.register(PyGraph) +def _graph_node_link_json(graph, path=None, graph_attrs=None, node_attrs=None, edge_attrs=None): + return graph_node_link_json( + graph, path=path, graph_attrs=graph_attrs, node_attrs=node_attrs, edge_attrs=edge_attrs + ) From b1069a4b8d27e7d331b87b0b3165bd61218edb59 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sat, 13 Aug 2022 15:35:51 -0700 Subject: [PATCH 32/37] Finish name change --- rustworkx-core/src/lib.rs | 1 + rustworkx-core/src/planar/lr_planar.rs | 757 +++++++++++++++++++++++++ rustworkx-core/src/planar/mod.rs | 17 + src/layout/embedding.rs | 21 +- src/layout/planar.rs | 4 +- 5 files changed, 789 insertions(+), 11 deletions(-) create mode 100644 rustworkx-core/src/planar/lr_planar.rs create mode 100644 rustworkx-core/src/planar/mod.rs diff --git a/rustworkx-core/src/lib.rs b/rustworkx-core/src/lib.rs index f15f4c79c..40d839545 100644 --- a/rustworkx-core/src/lib.rs +++ b/rustworkx-core/src/lib.rs @@ -73,6 +73,7 @@ pub mod centrality; pub mod connectivity; /// Module for maximum weight matching algorithms. pub mod max_weight_matching; +pub mod planar; pub mod shortest_path; pub mod traversal; // These modules define additional data structures diff --git a/rustworkx-core/src/planar/lr_planar.rs b/rustworkx-core/src/planar/lr_planar.rs new file mode 100644 index 000000000..bd2e80e09 --- /dev/null +++ b/rustworkx-core/src/planar/lr_planar.rs @@ -0,0 +1,757 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +use std::cmp::Ordering; +use std::hash::Hash; +use std::vec::IntoIter; + +use hashbrown::{hash_map::Entry, HashMap}; +use petgraph::{ + graph::NodeIndex, + stable_graph::StableGraph, + visit::{ + EdgeCount, EdgeRef, GraphBase, GraphProp, IntoEdges, IntoNodeIdentifiers, NodeCount, + NodeIndexable, Visitable, + }, + Directed, Undirected, +}; + +use crate::traversal::{depth_first_search, DfsEvent}; + +type Edge = (::NodeId, ::NodeId); + +fn insert_or_min(xs: &mut HashMap, key: K, val: V) +where + K: Hash + Eq, + V: Ord + Copy, +{ + xs.entry(key) + .and_modify(|e| { + if val < *e { + *e = val; + } + }) + .or_insert(val); +} + +fn edges_filtered_and_sorted_by( + graph: G, + a: G::NodeId, + filter: P, + compare: F, +) -> IntoIter> +where + G: IntoEdges, + P: Fn(&Edge) -> bool, + F: Fn(&Edge) -> K, + K: Ord, +{ + let mut edges = graph + .edges(a) + .filter_map(|edge| { + let e = (edge.source(), edge.target()); + if filter(&e) { + Some(e) + } else { + None + } + }) + .collect::>(); + edges.sort_by_key(compare); + // Remove parallel edges since they do *not* affect whether a graph is planar. + edges.dedup(); + edges.into_iter() +} + +fn is_target(edge: Option<&Edge>, v: G::NodeId) -> Option<&Edge> { + edge.filter(|e| e.1 == v) +} + +#[derive(Clone, Copy, PartialEq, PartialOrd)] +struct Interval { + inner: Option<(T, T)>, +} + +impl Default for Interval { + fn default() -> Self { + Interval { inner: None } + } +} + +impl Interval { + fn new(low: T, high: T) -> Self { + Interval { + inner: Some((low, high)), + } + } + + fn is_empty(&self) -> bool { + self.inner.is_none() + } + + fn unwrap(self) -> (T, T) { + self.inner.unwrap() + } + + fn low(&self) -> Option<&T> { + match self.inner { + Some((ref low, _)) => Some(low), + None => None, + } + } + + fn high(&self) -> Option<&T> { + match self.inner { + Some((_, ref high)) => Some(high), + None => None, + } + } + + fn as_ref(&mut self) -> Option<&(T, T)> { + self.inner.as_ref() + } + + fn as_mut(&mut self) -> Option<&mut (T, T)> { + self.inner.as_mut() + } + + fn as_mut_low(&mut self) -> Option<&mut T> { + match self.inner { + Some((ref mut low, _)) => Some(low), + None => None, + } + } +} + +impl Interval<(T, T)> +where + T: Copy + Hash + Eq, +{ + /// Returns ``true`` if the interval conflicts with ``edge``. + fn conflict(&self, lr_state: &LRState, edge: Edge) -> bool + where + G: GraphBase, + { + match self.inner { + Some((_, ref h)) => lr_state.lowpt.get(h) > lr_state.lowpt.get(&edge), + _ => false, + } + } +} + +#[derive(Clone, Copy, PartialEq, PartialOrd)] +struct ConflictPair { + left: Interval, + right: Interval, +} + +impl Default for ConflictPair { + fn default() -> Self { + ConflictPair { + left: Interval::default(), + right: Interval::default(), + } + } +} + +impl ConflictPair { + fn new(left: Interval, right: Interval) -> Self { + ConflictPair { left, right } + } + + fn swap(&mut self) { + std::mem::swap(&mut self.left, &mut self.right) + } + + fn is_empty(&self) -> bool { + self.left.is_empty() && self.right.is_empty() + } +} + +impl ConflictPair<(T, T)> +where + T: Copy + Hash + Eq, +{ + /// Returns the lowest low point of a conflict pair. + fn lowest(&self, lr_state: &LRState) -> usize + where + G: GraphBase, + { + match (self.left.low(), self.right.low()) { + (Some(l_low), Some(r_low)) => lr_state.lowpt[l_low].min(lr_state.lowpt[r_low]), + (Some(l_low), None) => lr_state.lowpt[l_low], + (None, Some(r_low)) => lr_state.lowpt[r_low], + (None, None) => std::usize::MAX, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Sign { + Plus, + Minus, +} + +/// Similar to ``DfsEvent`` plus an extra event ``FinishEdge`` +/// that indicates that we have finished processing an edge. +enum LRTestDfsEvent { + Finish(N), + TreeEdge(N, N), + BackEdge(N, N), + FinishEdge(N, N), +} + +// An error: graph is *not* planar. +struct NonPlanar {} + +pub struct LRState +where + G::NodeId: Hash + Eq, +{ + pub graph: G, + /// roots of the DFS forest. + pub roots: Vec, + /// distnace from root. + height: HashMap, + /// parent edge. + pub eparent: HashMap>, + /// height of lowest return point. + lowpt: HashMap, usize>, + /// height of next-to-lowest return point. Only used to check if an edge is chordal. + lowpt_2: HashMap, usize>, + /// next back edge in traversal with lowest return point. + lowpt_edge: HashMap, Edge>, + /// proxy for nesting order ≺ given by twice lowpt (plus 1 if chordal). + pub nesting_depth: HashMap, isize>, + /// stack for conflict pairs. + stack: Vec>>, + /// marks the top conflict pair when an edge was pushed in the stack. + stack_emarker: HashMap, ConflictPair>>, + /// edge relative to which side is defined. + pub eref: HashMap, Edge>, + /// side of edge, or modifier for side of reference edge. + pub side: HashMap, Sign>, + /// directed graph used to build the embedding + pub dir_graph: StableGraph<(), (), Directed>, +} + +impl LRState +where + G: GraphBase + + NodeCount + + EdgeCount + + IntoNodeIdentifiers + + NodeIndexable + + IntoEdges + + Visitable, + G::NodeId: Hash + Eq, +{ + pub fn new(graph: G) -> Self { + let num_nodes = graph.node_count(); + let num_edges = graph.edge_count(); + + let lr_state = LRState { + graph, + roots: Vec::new(), + height: HashMap::with_capacity(num_nodes), + eparent: HashMap::with_capacity(num_edges), + lowpt: HashMap::with_capacity(num_edges), + lowpt_2: HashMap::with_capacity(num_edges), + lowpt_edge: HashMap::with_capacity(num_edges), + nesting_depth: HashMap::with_capacity(num_edges), + stack: Vec::new(), + stack_emarker: HashMap::with_capacity(num_edges), + eref: HashMap::with_capacity(num_edges), + side: graph + .edge_references() + .map(|e| ((e.source(), e.target()), Sign::Plus)) + .collect(), + dir_graph: StableGraph::with_capacity(num_nodes, 0), + }; + lr_state + } + + // Create the directed graph for the embedding in stable format + // to match the original graph. + fn build_dir_graph(&mut self) + where + ::NodeId: Ord, + { + let mut tmp_nodes: Vec = Vec::new(); + let mut count: usize = 0; + for _ in 0..self.graph.node_bound() { + self.dir_graph.add_node(()); + } + for gnode in self.graph.node_identifiers() { + let gidx = self.graph.to_index(gnode); + if gidx != count { + for idx in count..gidx { + tmp_nodes.push(idx); + } + count = gidx; + } + count += 1; + } + for tmp_node in tmp_nodes { + self.dir_graph.remove_node(NodeIndex::new(tmp_node)); + } + } + + fn lr_orientation_visitor(&mut self, event: DfsEvent) { + match event { + DfsEvent::Discover(v, _) => { + if let Entry::Vacant(entry) = self.height.entry(v) { + entry.insert(0); + self.roots.push(v); + } + } + DfsEvent::TreeEdge(v, w, _) => { + let v_dir = NodeIndex::new(self.graph.to_index(v)); + let w_dir = NodeIndex::new(self.graph.to_index(w)); + if !self.dir_graph.contains_edge(v_dir, w_dir) { + self.dir_graph.add_edge(v_dir, w_dir, ()); + } + let ei = (v, w); + let v_height = self.height[&v]; + let w_height = v_height + 1; + + self.eparent.insert(w, ei); + self.height.insert(w, w_height); + // now initialize low points. + self.lowpt.insert(ei, v_height); + self.lowpt_2.insert(ei, w_height); + } + DfsEvent::BackEdge(v, w, _) => { + // do *not* consider ``(v, w)`` as a back edge if ``(w, v)`` is a tree edge. + if Some(&(w, v)) != self.eparent.get(&v) { + let v_dir = NodeIndex::new(self.graph.to_index(v)); + let w_dir = NodeIndex::new(self.graph.to_index(w)); + if !self.dir_graph.contains_edge(v_dir, w_dir) { + self.dir_graph.add_edge(v_dir, w_dir, ()); + } + let ei = (v, w); + self.lowpt.insert(ei, self.height[&w]); + self.lowpt_2.insert(ei, self.height[&v]); + } + } + DfsEvent::Finish(v, _) => { + for edge in self.graph.edges(v) { + let w = edge.target(); + let ei = (v, w); + + // determine nesting depth. + let low = match self.lowpt.get(&ei) { + Some(val) => *val, + None => + // if ``lowpt`` does *not* contain edge ``(v, w)``, it means + // that it's *not* a tree or a back edge so we skip it since + // it's oriented in the reverse direction. + { + continue + } + }; + + if self.lowpt_2[&ei] < self.height[&v] { + // if it's chordal, add one. + self.nesting_depth.insert(ei, (2 * low) as isize + 1); + } else { + self.nesting_depth.insert(ei, (2 * low) as isize); + } + + // update lowpoints of parent edge. + if let Some(e_par) = self.eparent.get(&v) { + match self.lowpt[&ei].cmp(&self.lowpt[e_par]) { + Ordering::Less => { + self.lowpt_2 + .insert(*e_par, self.lowpt[e_par].min(self.lowpt_2[&ei])); + self.lowpt.insert(*e_par, self.lowpt[&ei]); + } + Ordering::Greater => { + insert_or_min(&mut self.lowpt_2, *e_par, self.lowpt[&ei]); + } + _ => { + let val = self.lowpt_2[&ei]; + insert_or_min(&mut self.lowpt_2, *e_par, val); + } + } + } + } + } + _ => {} + } + } + + fn lr_testing_visitor(&mut self, event: LRTestDfsEvent) -> Result<(), NonPlanar> { + match event { + LRTestDfsEvent::TreeEdge(v, w) => { + let ei = (v, w); + if let Some(&last) = self.stack.last() { + self.stack_emarker.insert(ei, last); + } + } + LRTestDfsEvent::BackEdge(v, w) => { + let ei = (v, w); + if let Some(&last) = self.stack.last() { + self.stack_emarker.insert(ei, last); + } + self.lowpt_edge.insert(ei, ei); + let c_pair = ConflictPair::new(Interval::default(), Interval::new(ei, ei)); + self.stack.push(c_pair); + } + LRTestDfsEvent::FinishEdge(v, w) => { + let ei = (v, w); + if self.lowpt[&ei] < self.height[&v] { + // ei has return edge + let e_par = self.eparent[&v]; + let val = self.lowpt_edge[&ei]; + + match self.lowpt_edge.entry(e_par) { + Entry::Occupied(_) => { + self.add_constraints(ei, e_par)?; + } + Entry::Vacant(o) => { + o.insert(val); + } + } + } + } + LRTestDfsEvent::Finish(v) => { + if let Some(&e) = self.eparent.get(&v) { + let u = e.0; + self.remove_back_edges(u); + + // side of ``e = (u, v)` is side of a highest return edge + if self.lowpt[&e] < self.height[&u] { + if let Some(top) = self.stack.last() { + let e_high = match (top.left.high(), top.right.high()) { + (Some(hl), Some(hr)) => { + if self.lowpt[hl] > self.lowpt[hr] { + hl + } else { + hr + } + } + (Some(hl), None) => hl, + (None, Some(hr)) => hr, + _ => { + // Otherwise ``top`` would be empty, but we don't push + // empty conflict pairs in stack. + unreachable!() + } + }; + self.eref.insert(e, *e_high); + } + } + } + } + } + + Ok(()) + } + + fn until_top_of_stack_hits_emarker(&mut self, ei: Edge) -> Option>> { + if let Some(&c_pair) = self.stack.last() { + if self.stack_emarker[&ei] != c_pair { + return self.stack.pop(); + } + } + + None + } + + fn until_top_of_stack_is_conflicting(&mut self, ei: Edge) -> Option>> { + if let Some(c_pair) = self.stack.last() { + if c_pair.left.conflict(self, ei) || c_pair.right.conflict(self, ei) { + return self.stack.pop(); + } + } + + None + } + + /// Unify intervals ``pi``, ``qi``. + /// + /// Interval ``qi`` must be non - empty and contain edges + /// with smaller lowpt than interval ``pi``. + fn union_intervals(&mut self, pi: &mut Interval>, qi: Interval>) { + match pi.as_mut_low() { + Some(p_low) => { + let (q_low, q_high) = qi.unwrap(); + self.eref.insert(*p_low, q_high); + *p_low = q_low; + } + None => { + *pi = qi; + } + } + } + + /// Adding constraints associated with edge ``ei``. + fn add_constraints(&mut self, ei: Edge, e: Edge) -> Result<(), NonPlanar> { + let mut c_pair = ConflictPair::>::default(); + + // merge return edges of ei into ``c_pair.right``. + while let Some(mut q_pair) = self.until_top_of_stack_hits_emarker(ei) { + if !q_pair.left.is_empty() { + q_pair.swap(); + + if !q_pair.left.is_empty() { + return Err(NonPlanar {}); + } + } + + // We call unwrap since ``q_pair`` was in stack and + // ``q_pair.right``, ``q_pair.left`` can't be both empty + // since we don't push empty conflict pairs in stack. + let qr_low = q_pair.right.low().unwrap(); + if self.lowpt[qr_low] > self.lowpt[&e] { + // merge intervals + self.union_intervals(&mut c_pair.right, q_pair.right); + } else { + // make consinsent + self.eref.insert(*qr_low, self.lowpt_edge[&e]); + } + } + + // merge conflicting return edges of e1, . . . , ei−1 into ``c_pair.left``. + while let Some(mut q_pair) = self.until_top_of_stack_is_conflicting(ei) { + if q_pair.right.conflict(self, ei) { + q_pair.swap(); + + if q_pair.right.conflict(self, ei) { + return Err(NonPlanar {}); + } + } + + // merge interval below lowpt(ei) into ``c_pair.right``. + if let Some((qr_low, qr_high)) = q_pair.right.as_ref() { + if let Some(pr_low) = c_pair.right.as_mut_low() { + self.eref.insert(*pr_low, *qr_high); + *pr_low = *qr_low; + } + }; + self.union_intervals(&mut c_pair.left, q_pair.left); + } + + if !c_pair.is_empty() { + self.stack.push(c_pair); + } + + Ok(()) + } + + fn until_lowest_top_of_stack_has_height( + &mut self, + v: G::NodeId, + ) -> Option>> { + if let Some(c_pair) = self.stack.last() { + if c_pair.lowest(self) == self.height[&v] { + return self.stack.pop(); + } + } + + None + } + + fn follow_eref_until_is_target(&self, edge: Edge, v: G::NodeId) -> Option> { + let mut res = Some(&edge); + while let Some(b) = is_target::(res, v) { + res = self.eref.get(b); + } + + res.copied() + } + + /// Trim back edges ending at parent v. + fn remove_back_edges(&mut self, v: G::NodeId) { + // drop entire conflict pairs. + while let Some(c_pair) = self.until_lowest_top_of_stack_has_height(v) { + if let Some(pl_low) = c_pair.left.low() { + self.side.insert(*pl_low, Sign::Minus); + } + } + + // one more conflict pair to consider. + if let Some(mut c_pair) = self.stack.pop() { + // trim left interval. + if let Some((pl_low, pl_high)) = c_pair.left.as_mut() { + match self.follow_eref_until_is_target(*pl_high, v) { + Some(val) => { + *pl_high = val; + } + None => { + // just emptied. + // We call unwrap since right interval cannot be empty for otherwise + // the entire conflict pair had been removed. + let pr_low = c_pair.right.low().unwrap(); + self.eref.insert(*pl_low, *pr_low); + self.side.insert(*pl_low, Sign::Minus); + c_pair.left = Interval::default(); + } + } + } + + // trim right interval + if let Some((pr_low, ref mut pr_high)) = c_pair.right.as_mut() { + match self.follow_eref_until_is_target(*pr_high, v) { + Some(val) => { + *pr_high = val; + } + None => { + // just emptied. + // We call unwrap since left interval cannot be empty for otherwise + // the entire conflict pair had been removed. + let pl_low = c_pair.left.low().unwrap(); + self.eref.insert(*pr_low, *pl_low); + self.side.insert(*pr_low, Sign::Minus); + c_pair.right = Interval::default(); + } + }; + } + + if !c_pair.is_empty() { + self.stack.push(c_pair); + } + } + } +} + +/// Visits the DFS - oriented tree that we have pre-computed +/// and stored in ``lr_state``. We traverse the edges of +/// a node in nesting depth order. Events are emitted at points +/// of interest and should be handled by ``visitor``. +fn lr_visit_ordered_dfs_tree( + lr_state: &mut LRState, + v: G::NodeId, + mut visitor: F, +) -> Result<(), E> +where + G: GraphBase + IntoEdges, + G::NodeId: Hash + Eq, + F: FnMut(&mut LRState, LRTestDfsEvent) -> Result<(), E>, +{ + let mut stack: Vec<(G::NodeId, IntoIter>)> = vec![( + v, + edges_filtered_and_sorted_by( + lr_state.graph, + v, + // if ``lowpt`` does *not* contain edge ``e = (v, w)``, it means + // that it's *not* a tree or a back edge so we skip it since + // it's oriented in the reverse direction. + |e| lr_state.lowpt.contains_key(e), + // we sort edges based on nesting depth order. + |e| lr_state.nesting_depth[e], + ), + )]; + + while let Some(elem) = stack.last_mut() { + let v = elem.0; + let adjacent_edges = &mut elem.1; + let mut next = None; + + for (v, w) in adjacent_edges { + if Some(&(v, w)) == lr_state.eparent.get(&w) { + // tree edge + visitor(lr_state, LRTestDfsEvent::TreeEdge(v, w))?; + next = Some(w); + break; + } else { + // back edge + visitor(lr_state, LRTestDfsEvent::BackEdge(v, w))?; + visitor(lr_state, LRTestDfsEvent::FinishEdge(v, w))?; + } + } + + match next { + Some(w) => stack.push(( + w, + edges_filtered_and_sorted_by( + lr_state.graph, + w, + |e| lr_state.lowpt.contains_key(e), + |e| lr_state.nesting_depth[e], + ), + )), + None => { + stack.pop(); + visitor(lr_state, LRTestDfsEvent::Finish(v))?; + if let Some(&(u, v)) = lr_state.eparent.get(&v) { + visitor(lr_state, LRTestDfsEvent::FinishEdge(u, v))?; + } + } + } + } + + Ok(()) +} + +/// Check if an undirected graph is planar. +/// +/// A graph is planar iff it can be drawn in a plane without any edge +/// intersections. +/// +/// The planarity check algorithm is based on the +/// Left-Right Planarity Test: +/// +/// [`Ulrik Brandes: The Left-Right Planarity Test (2009)`](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.217.9208) +/// +/// # Example: +/// ```rust +/// use retworkx_core::petgraph::graph::UnGraph; +/// use retworkx_core::planar::{is_planar, LRState}; +/// +/// let grid = UnGraph::<(), ()>::from_edges(&[ +/// // row edges +/// (0, 1), (1, 2), (3, 4), (4, 5), (6, 7), (7, 8), +/// // col edges +/// (0, 3), (3, 6), (1, 4), (4, 7), (2, 5), (5, 8), +/// ]); +/// let mut lr_state = LRState::new(&grid); +/// assert!(is_planar(&grid, Some(&mut lr_state))) +/// ``` +pub fn is_planar(graph: G, state: Option<&mut LRState>) -> bool +where + G: GraphProp + + NodeCount + + EdgeCount + + IntoEdges + + NodeIndexable + + IntoNodeIdentifiers + + Visitable, + G::NodeId: Hash + Eq + Ord, +{ + // If None passed for state, create new LRState + let mut lr_state = LRState::new(graph); + let lr_state = match state { + Some(state) => state, + None => &mut lr_state, + }; + + // Build directed graph for the embedding + lr_state.build_dir_graph(); + + // Dfs orientation phase + depth_first_search(graph, graph.node_identifiers(), |event| { + lr_state.lr_orientation_visitor(event) + }); + + // Left - Right partition. + for v in lr_state.roots.clone() { + let res = lr_visit_ordered_dfs_tree(lr_state, v, |lr_state, event| { + lr_state.lr_testing_visitor(event) + }); + if res.is_err() { + return false; + } + } + true +} diff --git a/rustworkx-core/src/planar/mod.rs b/rustworkx-core/src/planar/mod.rs new file mode 100644 index 000000000..d6fe1f4e6 --- /dev/null +++ b/rustworkx-core/src/planar/mod.rs @@ -0,0 +1,17 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +//! Module for planar graphs. + +pub mod lr_planar; + +pub use lr_planar::{is_planar, LRState}; diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index 355d4f91a..da00df2a2 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -11,7 +11,7 @@ // under the License. use hashbrown::{HashMap, HashSet}; -use indexmap::{IndexSet, IndexMap}; +use indexmap::{IndexMap, IndexSet}; use petgraph::prelude::*; use petgraph::visit::NodeIndexable; use petgraph::Directed; @@ -19,8 +19,8 @@ use rayon::prelude::*; // For par_sort use std::fmt::Debug; use crate::StablePyGraph; -use retworkx_core::connectivity::connected_components; -use retworkx_core::planar::lr_planar::{LRState, Sign}; +use rustworkx_core::connectivity::connected_components; +use rustworkx_core::planar::lr_planar::{LRState, Sign}; pub type Point = [f64; 2]; @@ -121,7 +121,10 @@ impl PlanarEmbedding { if let Some(ref_nbr_node) = ref_nbr { if self.embedding.find_edge(start_node, ref_nbr_node).is_none() { // RAISE? - println!("Cannot add edge to {:?}. Reference neighbor {:?} does not exist", start_node, ref_nbr_node); + println!( + "Cannot add edge to {:?}. Reference neighbor {:?} does not exist", + start_node, ref_nbr_node + ); panic!(); } let cw_ref = self @@ -132,8 +135,7 @@ impl PlanarEmbedding { self.update_edge_weight(start_node, end_node, cw_ref, true); self.update_edge_weight(start_node, cw_ref, end_node, false); self.update_edge_weight(start_node, end_node, ref_nbr_node, false); - } - else { + } else { // The start node has no neighbors self.update_edge_weight(start_node, end_node, end_node, true); self.update_edge_weight(start_node, end_node, end_node, false); @@ -167,7 +169,7 @@ impl PlanarEmbedding { fn add_half_edge_first(&mut self, start_node: NodeIndex, end_node: NodeIndex) { // Add half edge that's first_nbr or None - let ref_node: Option = if self.embedding.node_count() >= start_node.index() + let ref_node: Option = if self.embedding.node_bound() >= start_node.index() && self.embedding[start_node].first_nbr.is_some() { self.embedding[start_node].first_nbr @@ -228,7 +230,8 @@ pub fn create_embedding( planar_emb: &mut PlanarEmbedding, lr_state: &mut LRState<&StablePyGraph>, ) { - let mut ordered_adjs: IndexMap> = IndexMap::with_capacity(lr_state.graph.node_count()); + let mut ordered_adjs: IndexMap> = + IndexMap::with_capacity(lr_state.graph.node_count()); // Create the adjacency list for each node for v in lr_state.dir_graph.node_indices() { @@ -267,7 +270,7 @@ pub fn create_embedding( // Start the DFS traversal for the embedding let mut left_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); let mut right_ref: HashMap = HashMap::with_capacity(ordered_adjs.len()); - let mut idx: Vec = vec![0; lr_state.graph.node_bound()];//ordered_adjs.len()]; + let mut idx: Vec = vec![0; lr_state.graph.node_bound()]; for v in lr_state.roots.iter() { // Create the stack with an initial entry of v diff --git a/src/layout/planar.rs b/src/layout/planar.rs index 480b3d19c..f480224ed 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -17,8 +17,8 @@ use super::spring::{recenter, rescale, Point}; use crate::iterators::Pos2DMapping; use crate::layout::embedding::{create_embedding, embedding_to_pos, PlanarEmbedding}; use crate::StablePyGraph; -use retworkx_core::dictmap::*; -use retworkx_core::planar::{is_planar, LRState}; +use rustworkx_core::dictmap::*; +use rustworkx_core::planar::{is_planar, LRState}; /// If a graph is planar, create a set of position coordinates for a planar /// layout that can be passed to a drawer. From d5b96be6c2f148922aed6354bb9cb3c36dbdba85 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Mon, 15 Aug 2022 08:09:59 -0700 Subject: [PATCH 33/37] Fixing stable graph --- rustworkx/__init__.py | 27 ++++++++++++++++++++++++++- src/layout/embedding.rs | 8 +++++--- src/layout/planar.rs | 3 ++- src/layout/spring.rs | 1 + 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/rustworkx/__init__.py b/rustworkx/__init__.py index a31c0cf2a..71d80eea3 100644 --- a/rustworkx/__init__.py +++ b/rustworkx/__init__.py @@ -1168,6 +1168,31 @@ def _graph_random_layout(graph, center=None, seed=None): return graph_random_layout(graph, center=center, seed=seed) +@functools.singledispatch +def planar_layout(graph, center=None, seed=None): + """Generate a new planar layout + + :param PyGraph graph: The graph to generate the layout for + :param tuple center: An optional center position. This is a 2 tuple of two + ``float`` values for the center position + :param int seed: An optional seed to set for the random number generator. + + :returns: The planar layout of the graph. + :rtype: Pos2DMapping + """ + raise TypeError("Invalid Input Type %s for graph" % type(graph)) + + +@planar_layout.register(PyDiGraph) +def _digraph_planar_layout(graph, center=None, seed=None): + return digraph_planar_layout(graph, center=center, seed=seed) + + +@planar_layout.register(PyGraph) +def _graph_planar_layout(graph, center=None, seed=None): + return graph_planar_layout(graph, center=center, seed=seed) + + @functools.singledispatch def spring_layout( graph, @@ -2345,7 +2370,7 @@ def node_link_json(graph, path=None, graph_attrs=None, node_attrs=None, edge_att """Generate a JSON object representing a graph in a node-link format :param graph: The graph to generate the JSON for. Can either be a - :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. :param str path: An optional path to write the JSON output to. If specified the function will not return anything and instead will write the JSON to the file specified. diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index da00df2a2..3ebcf16fd 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -24,7 +24,7 @@ use rustworkx_core::planar::lr_planar::{LRState, Sign}; pub type Point = [f64; 2]; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CwCcw { cw: Option, ccw: Option, @@ -49,7 +49,7 @@ impl CwCcw { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FirstNbr { pub first_nbr: Option, } @@ -71,6 +71,7 @@ impl FirstNbr { /// The basic embedding to build the structure that will lead to /// the position coordinates to display. +#[derive(Clone)] pub struct PlanarEmbedding { pub embedding: StableGraph, CwCcw, Directed>, } @@ -477,7 +478,8 @@ fn triangulate_embedding( let mut face_list = vec![]; let mut edges_counted: HashSet<(NodeIndex, NodeIndex)> = HashSet::new(); - for v in planar_emb.embedding.node_indices() { + let x = planar_emb.clone();//embedding.node_indices().clone(); + for v in x.embedding.node_indices() {//planar_emb.embedding.node_indices() { for w in planar_emb.neighbors_cw_order(v) { let new_face = make_bi_connected(planar_emb, &v, &w, &mut edges_counted); if !new_face.is_empty() { diff --git a/src/layout/planar.rs b/src/layout/planar.rs index f480224ed..e01e4fe14 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -56,8 +56,9 @@ pub fn planar_layout( // Then convert the embedding to position coordinates. let mut pos = embedding_to_pos(&mut planar_emb); + //let x = pos.len(); if let Some(scale) = scale { - rescale(&mut pos, scale, (0..node_num).collect()); + rescale(&mut pos, scale, graph.node_indices().map(|n| n.index()).collect()); } if let Some(center) = center { recenter(&mut pos, center); diff --git a/src/layout/spring.rs b/src/layout/spring.rs index 0a03db7f0..99bd58fd3 100644 --- a/src/layout/spring.rs +++ b/src/layout/spring.rs @@ -176,6 +176,7 @@ pub fn rescale(pos: &mut [Point], scale: Nt, indices: Vec) { } // find mean in each dimension let mut mu: Point = [0.0, 0.0]; + println!(" pos {:?} indices {:?}", pos, indices); for &n in &indices { mu[0] += pos[n][0]; mu[1] += pos[n][1]; From 913fc21a3cac05ab1823f5d5721afb7ef3393cb7 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 17 Aug 2022 09:17:25 -0700 Subject: [PATCH 34/37] Stable graph tweaks and more rustworkx test transitions --- rustworkx-core/src/planar/lr_planar.rs | 9 +- rustworkx-core/tests/test_planar.rs | 281 ++++++++++++++++++ rustworkx/__init__.py | 12 +- src/layout/embedding.rs | 47 ++- src/layout/planar.rs | 16 +- src/layout/spring.rs | 1 - tests/graph/test_planar_layout.py | 26 +- .../graph/test_planar_layout.py | 62 ++++ 8 files changed, 414 insertions(+), 40 deletions(-) create mode 100644 rustworkx-core/tests/test_planar.rs create mode 100644 tests/rustworkx_tests/graph/test_planar_layout.py diff --git a/rustworkx-core/src/planar/lr_planar.rs b/rustworkx-core/src/planar/lr_planar.rs index bd2e80e09..36a7d8cc4 100644 --- a/rustworkx-core/src/planar/lr_planar.rs +++ b/rustworkx-core/src/planar/lr_planar.rs @@ -259,7 +259,7 @@ where let num_nodes = graph.node_count(); let num_edges = graph.edge_count(); - let lr_state = LRState { + LRState { graph, roots: Vec::new(), height: HashMap::with_capacity(num_nodes), @@ -276,8 +276,7 @@ where .map(|e| ((e.source(), e.target()), Sign::Plus)) .collect(), dir_graph: StableGraph::with_capacity(num_nodes, 0), - }; - lr_state + } } // Create the directed graph for the embedding in stable format @@ -706,8 +705,8 @@ where /// /// # Example: /// ```rust -/// use retworkx_core::petgraph::graph::UnGraph; -/// use retworkx_core::planar::{is_planar, LRState}; +/// use rustworkx_core::petgraph::graph::UnGraph; +/// use rustworkx_core::planar::{is_planar, LRState}; /// /// let grid = UnGraph::<(), ()>::from_edges(&[ /// // row edges diff --git a/rustworkx-core/tests/test_planar.rs b/rustworkx-core/tests/test_planar.rs new file mode 100644 index 000000000..fd1cd672e --- /dev/null +++ b/rustworkx-core/tests/test_planar.rs @@ -0,0 +1,281 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +//! Test module for planar graphs. + +use rustworkx_core::petgraph::graph::UnGraph; +use rustworkx_core::planar::{is_planar, LRState}; + +#[test] +fn test_simple_planar_graph() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (1, 2), + (2, 3), + (3, 4), + (4, 6), + (6, 7), + (7, 1), + (1, 5), + (5, 2), + (2, 4), + (4, 5), + (5, 7), + ]); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); + assert!(res) +} + +#[test] +fn test_planar_grid_3_3_graph() { + let graph = UnGraph::<(), ()>::from_edges(&[ + // row edges + (0, 1), + (1, 2), + (3, 4), + (4, 5), + (6, 7), + (7, 8), + // col edges + (0, 3), + (3, 6), + (1, 4), + (4, 7), + (2, 5), + (5, 8), + ]); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); + assert!(res) +} + +#[test] +fn test_planar_with_self_loop() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (1, 1), + (2, 2), + (3, 3), + (4, 4), + (5, 5), + (1, 2), + (1, 3), + (1, 5), + (2, 5), + (2, 4), + (3, 4), + (3, 5), + (4, 5), + ]); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); + assert!(res) +} + +#[test] +fn test_goldner_harary_planar_graph() { + // test goldner-harary graph (a maximal planar graph) + let graph = UnGraph::<(), ()>::from_edges(&[ + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (1, 7), + (1, 8), + (1, 10), + (1, 11), + (2, 3), + (2, 4), + (2, 6), + (2, 7), + (2, 9), + (2, 10), + (2, 11), + (3, 4), + (4, 5), + (4, 6), + (4, 7), + (5, 7), + (6, 7), + (7, 8), + (7, 9), + (7, 10), + (8, 10), + (9, 10), + (10, 11), + ]); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); + assert!(res) +} + +#[test] +fn test_multiple_components_planar_graph() { + let graph = UnGraph::<(), ()>::from_edges(&[(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)]); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); + assert!(res) +} + +#[test] +fn test_planar_multi_graph() { + let graph = UnGraph::<(), ()>::from_edges(&[(0, 1), (0, 1), (0, 1), (1, 2), (2, 0)]); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); + assert!(res) +} + +#[test] +fn test_k3_3_non_planar() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (0, 3), + (0, 4), + (0, 5), + (1, 3), + (1, 4), + (1, 5), + (2, 3), + (2, 4), + (2, 5), + ]); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); + assert_eq!(res, false) +} + +#[test] +fn test_k5_non_planar() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 2), + (1, 3), + (1, 4), + (2, 3), + (2, 4), + (3, 4), + ]); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); + assert_eq!(res, false) +} + +#[test] +fn test_multiple_components_non_planar() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 2), + (1, 3), + (1, 4), + (2, 3), + (2, 4), + (3, 4), + (6, 7), + (7, 8), + (8, 6), + ]); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); + assert_eq!(res, false) +} + +#[test] +fn test_non_planar() { + // tests a graph that has no subgraph directly isomorphic to K5 or K3_3. + let graph = UnGraph::<(), ()>::from_edges(&[ + (1, 5), + (1, 6), + (1, 7), + (2, 6), + (2, 3), + (3, 5), + (3, 7), + (4, 5), + (4, 6), + (4, 7), + ]); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); + assert_eq!(res, false) +} + +#[test] +fn test_planar_graph1() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (3, 10), + (2, 13), + (1, 13), + (7, 11), + (0, 8), + (8, 13), + (0, 2), + (0, 7), + (0, 10), + (1, 7), + ]); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); + assert!(res) +} + +#[test] +fn test_non_planar_graph2() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (1, 2), + (4, 13), + (0, 13), + (4, 5), + (7, 10), + (1, 7), + (0, 3), + (2, 6), + (5, 6), + (7, 13), + (4, 8), + (0, 8), + (0, 9), + (2, 13), + (6, 7), + (3, 6), + (2, 8), + ]); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); + assert_eq!(res, false) +} + +#[test] +fn test_non_planar_graph3() { + let graph = UnGraph::<(), ()>::from_edges(&[ + (0, 7), + (3, 11), + (3, 4), + (8, 9), + (4, 11), + (1, 7), + (1, 13), + (1, 11), + (3, 5), + (5, 7), + (1, 3), + (0, 4), + (5, 11), + (5, 13), + ]); + let mut lr_state = LRState::new(&graph); + let res = is_planar(&graph, Some(&mut lr_state)); + assert_eq!(res, false) +} diff --git a/rustworkx/__init__.py b/rustworkx/__init__.py index 71d80eea3..8e0392bc0 100644 --- a/rustworkx/__init__.py +++ b/rustworkx/__init__.py @@ -1169,8 +1169,8 @@ def _graph_random_layout(graph, center=None, seed=None): @functools.singledispatch -def planar_layout(graph, center=None, seed=None): - """Generate a new planar layout +def planar_layout(graph, scale=None, center=None): + """Generate a planar layout :param PyGraph graph: The graph to generate the layout for :param tuple center: An optional center position. This is a 2 tuple of two @@ -1184,13 +1184,13 @@ def planar_layout(graph, center=None, seed=None): @planar_layout.register(PyDiGraph) -def _digraph_planar_layout(graph, center=None, seed=None): - return digraph_planar_layout(graph, center=center, seed=seed) +def _digraph_planar_layout(graph, scale=None, center=None): + return digraph_planar_layout(graph, scale=scale, center=center) @planar_layout.register(PyGraph) -def _graph_planar_layout(graph, center=None, seed=None): - return graph_planar_layout(graph, center=center, seed=seed) +def _graph_planar_layout(graph, scale=None, center=None): + return graph_planar_layout(graph, scale=scale, center=center) @functools.singledispatch diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index 3ebcf16fd..ba06916a1 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -194,6 +194,7 @@ impl PlanarEmbedding { let cw_weight = CwCcw::::default(); if found_edge.is_none() { // RAISE? + println!("update v {:?} w {:?}", v, w); self.embedding.add_edge(v, w, cw_weight); } let mut found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()); @@ -234,12 +235,11 @@ pub fn create_embedding( let mut ordered_adjs: IndexMap> = IndexMap::with_capacity(lr_state.graph.node_count()); + add_nodes_to_embedding(planar_emb, &lr_state.dir_graph); + // Create the adjacency list for each node for v in lr_state.dir_graph.node_indices() { ordered_adjs.insert(v, lr_state.dir_graph.edges(v).map(|e| e.target()).collect()); - // Add empty FirstNbr to the embedding - let first_nbr = FirstNbr::::default(); - planar_emb.embedding.add_node(first_nbr); } for v in lr_state.dir_graph.node_indices() { // Change the sign for nesting_depth @@ -302,6 +302,31 @@ pub fn create_embedding( } } + fn add_nodes_to_embedding( + planar_emb: &mut PlanarEmbedding, + dir_graph: &StableGraph<(), (), Directed>, + ) { + let mut tmp_nodes: Vec = Vec::new(); + let mut count: usize = 0; + for _ in 0..dir_graph.node_bound() { + let first_nbr = FirstNbr::::default(); + planar_emb.embedding.add_node(first_nbr); + } + for gnode in dir_graph.node_indices() { + let gidx = gnode.index(); + if gidx != count { + for idx in count..gidx { + tmp_nodes.push(idx); + } + count = gidx; + } + count += 1; + } + for tmp_node in tmp_nodes { + planar_emb.embedding.remove_node(NodeIndex::new(tmp_node)); + } + } + fn sign( edge: (NodeIndex, NodeIndex), eref: &mut HashMap<(NodeIndex, NodeIndex), (NodeIndex, NodeIndex)>, @@ -335,14 +360,9 @@ pub fn create_embedding( /// create a canonical ordering, and convert the embedding to position /// coordinates. pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { - let mut pos: Vec = vec![[0.0, 0.0]; planar_emb.embedding.node_count()]; - if planar_emb.embedding.node_count() < 4 { - let default_pos = [[0.0, 0.0], [2.0, 0.0], [1.0, 1.0]].to_vec(); - return planar_emb - .embedding - .node_indices() - .map(|n| default_pos[n.index()]) - .collect(); + let mut pos: Vec = vec![[0.0, 0.0]; planar_emb.embedding.node_bound()]; + if planar_emb.embedding.node_bound() < 4 { + return [[0.0, 0.0], [2.0, 0.0], [1.0, 1.0]].to_vec(); } let outer_face = triangulate_embedding(planar_emb, false); @@ -437,7 +457,6 @@ pub fn embedding_to_pos(planar_emb: &mut PlanarEmbedding) -> Vec { remaining_nodes.push(child); } } - pos[v1.unwrap().index()] = [0.0, y_coord[&v1] as f64]; let mut remaining_nodes = vec![v1]; @@ -478,8 +497,8 @@ fn triangulate_embedding( let mut face_list = vec![]; let mut edges_counted: HashSet<(NodeIndex, NodeIndex)> = HashSet::new(); - let x = planar_emb.clone();//embedding.node_indices().clone(); - for v in x.embedding.node_indices() {//planar_emb.embedding.node_indices() { + let indices: Vec = planar_emb.embedding.node_indices().collect(); + for v in indices { for w in planar_emb.neighbors_cw_order(v) { let new_face = make_bi_connected(planar_emb, &v, &w, &mut edges_counted); if !new_face.is_empty() { diff --git a/src/layout/planar.rs b/src/layout/planar.rs index e01e4fe14..e0d4e8793 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -56,19 +56,23 @@ pub fn planar_layout( // Then convert the embedding to position coordinates. let mut pos = embedding_to_pos(&mut planar_emb); - //let x = pos.len(); if let Some(scale) = scale { - rescale(&mut pos, scale, graph.node_indices().map(|n| n.index()).collect()); + rescale( + &mut pos, + scale, + graph.node_indices().map(|n| n.index()).collect(), + ); } if let Some(center) = center { recenter(&mut pos, center); } Pos2DMapping { - pos_map: planar_emb - .embedding + pos_map: graph .node_indices() - .map(|n| n.index()) - .zip(pos) + .map(|n| { + let n = n.index(); + (n, pos[n]) + }) .collect(), } } diff --git a/src/layout/spring.rs b/src/layout/spring.rs index 99bd58fd3..0a03db7f0 100644 --- a/src/layout/spring.rs +++ b/src/layout/spring.rs @@ -176,7 +176,6 @@ pub fn rescale(pos: &mut [Point], scale: Nt, indices: Vec) { } // find mean in each dimension let mut mu: Point = [0.0, 0.0]; - println!(" pos {:?} indices {:?}", pos, indices); for &n in &indices { mu[0] += pos[n][0]; mu[1] += pos[n][1]; diff --git a/tests/graph/test_planar_layout.py b/tests/graph/test_planar_layout.py index 1562e8cbe..1360fedae 100644 --- a/tests/graph/test_planar_layout.py +++ b/tests/graph/test_planar_layout.py @@ -12,12 +12,12 @@ import unittest -import retworkx +import rustworkx class TestPlanarLayout(unittest.TestCase): def setUp(self): - self.graph = retworkx.PyGraph() + self.graph = rustworkx.PyGraph() node_a = self.graph.add_node(1) node_b = self.graph.add_node(2) self.graph.add_edge(node_a, node_b, 1) @@ -25,28 +25,38 @@ def setUp(self): self.graph.add_edge(node_a, node_c, 2) def test_empty_graph(self): - graph = retworkx.PyGraph() - res = retworkx.planar_layout(graph) + graph = rustworkx.PyGraph() + res = rustworkx.planar_layout(graph) self.assertEqual({}, res) def test_simple_graph(self): - res = retworkx.planar_layout(self.graph) + res = rustworkx.planar_layout(self.graph) self.assertEqual(len(res), 3) self.assertEqual(len(res[0]), 2) self.assertIsInstance(res[0][0], float) def test_simple_graph_center(self): - res = retworkx.planar_layout(self.graph, center=[0.5, 0.5]) + res = rustworkx.planar_layout(self.graph, center=[0.5, 0.5]) self.assertEqual(len(res), 3) self.assertEqual(len(res[0]), 2) self.assertIsInstance(res[0][0], float) def test_graph_with_removed_nodes(self): - graph = retworkx.PyGraph() + graph = rustworkx.PyGraph() nodes = graph.add_nodes_from([0, 1, 2]) graph.remove_node(nodes[1]) - res = retworkx.planar_layout(graph) + res = rustworkx.planar_layout(graph) self.assertEqual(len(res), 2) self.assertTrue(nodes[0] in res) self.assertTrue(nodes[2] in res) self.assertFalse(nodes[1] in res) + + def test_graph_with_more_removed_nodes(self): + graph = rustworkx.PyGraph() + nodes = graph.add_nodes_from([0, 1, 2, 3, 4, 5]) + graph.remove_node(nodes[3]) + res = rustworkx.planar_layout(graph) + self.assertEqual(len(res), 5) + self.assertTrue(nodes[0] in res) + self.assertTrue(nodes[4] in res) + self.assertFalse(nodes[3] in res) diff --git a/tests/rustworkx_tests/graph/test_planar_layout.py b/tests/rustworkx_tests/graph/test_planar_layout.py new file mode 100644 index 000000000..1360fedae --- /dev/null +++ b/tests/rustworkx_tests/graph/test_planar_layout.py @@ -0,0 +1,62 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest + +import rustworkx + + +class TestPlanarLayout(unittest.TestCase): + def setUp(self): + self.graph = rustworkx.PyGraph() + node_a = self.graph.add_node(1) + node_b = self.graph.add_node(2) + self.graph.add_edge(node_a, node_b, 1) + node_c = self.graph.add_node(3) + self.graph.add_edge(node_a, node_c, 2) + + def test_empty_graph(self): + graph = rustworkx.PyGraph() + res = rustworkx.planar_layout(graph) + self.assertEqual({}, res) + + def test_simple_graph(self): + res = rustworkx.planar_layout(self.graph) + self.assertEqual(len(res), 3) + self.assertEqual(len(res[0]), 2) + self.assertIsInstance(res[0][0], float) + + def test_simple_graph_center(self): + res = rustworkx.planar_layout(self.graph, center=[0.5, 0.5]) + self.assertEqual(len(res), 3) + self.assertEqual(len(res[0]), 2) + self.assertIsInstance(res[0][0], float) + + def test_graph_with_removed_nodes(self): + graph = rustworkx.PyGraph() + nodes = graph.add_nodes_from([0, 1, 2]) + graph.remove_node(nodes[1]) + res = rustworkx.planar_layout(graph) + self.assertEqual(len(res), 2) + self.assertTrue(nodes[0] in res) + self.assertTrue(nodes[2] in res) + self.assertFalse(nodes[1] in res) + + def test_graph_with_more_removed_nodes(self): + graph = rustworkx.PyGraph() + nodes = graph.add_nodes_from([0, 1, 2, 3, 4, 5]) + graph.remove_node(nodes[3]) + res = rustworkx.planar_layout(graph) + self.assertEqual(len(res), 5) + self.assertTrue(nodes[0] in res) + self.assertTrue(nodes[4] in res) + self.assertFalse(nodes[3] in res) From 6cb9491b2d030f9863db8d79445c802578daf9c3 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 17 Aug 2022 09:24:43 -0700 Subject: [PATCH 35/37] Lint --- rustworkx-core/src/planar/lr_planar.rs | 2 +- src/layout/embedding.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/rustworkx-core/src/planar/lr_planar.rs b/rustworkx-core/src/planar/lr_planar.rs index 36a7d8cc4..2457fa6f8 100644 --- a/rustworkx-core/src/planar/lr_planar.rs +++ b/rustworkx-core/src/planar/lr_planar.rs @@ -195,7 +195,7 @@ where } } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Sign { Plus, Minus, diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index ba06916a1..3f715f110 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -24,7 +24,7 @@ use rustworkx_core::planar::lr_planar::{LRState, Sign}; pub type Point = [f64; 2]; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct CwCcw { cw: Option, ccw: Option, @@ -49,7 +49,7 @@ impl CwCcw { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct FirstNbr { pub first_nbr: Option, } @@ -71,7 +71,6 @@ impl FirstNbr { /// The basic embedding to build the structure that will lead to /// the position coordinates to display. -#[derive(Clone)] pub struct PlanarEmbedding { pub embedding: StableGraph, CwCcw, Directed>, } From 90b2fac25268e034186954e322925fbfc6d01036 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Mon, 22 Aug 2022 08:27:58 -0700 Subject: [PATCH 36/37] Final error checking and cleanup --- retworkx-core/src/planar/lr_planar.rs | 754 -------------------------- retworkx-core/src/planar/mod.rs | 17 - retworkx-core/tests/test_planar.rs | 281 ---------- src/layout/embedding.rs | 30 +- src/layout/mod.rs | 2 +- src/layout/planar.rs | 22 +- src/lib.rs | 3 + 7 files changed, 14 insertions(+), 1095 deletions(-) delete mode 100644 retworkx-core/src/planar/lr_planar.rs delete mode 100644 retworkx-core/src/planar/mod.rs delete mode 100644 retworkx-core/tests/test_planar.rs diff --git a/retworkx-core/src/planar/lr_planar.rs b/retworkx-core/src/planar/lr_planar.rs deleted file mode 100644 index 8681ab574..000000000 --- a/retworkx-core/src/planar/lr_planar.rs +++ /dev/null @@ -1,754 +0,0 @@ -// Licensed under the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -use std::cmp::Ordering; -use std::hash::Hash; -use std::vec::IntoIter; - -use hashbrown::{hash_map::Entry, HashMap}; -use petgraph::{ - graph::NodeIndex, - stable_graph::StableGraph, - visit::{ - EdgeCount, EdgeRef, GraphBase, GraphProp, IntoEdges, IntoNodeIdentifiers, NodeCount, - NodeIndexable, Visitable, - }, - Directed, Undirected, -}; - -use crate::traversal::{depth_first_search, DfsEvent}; - -type Edge = (::NodeId, ::NodeId); - -fn insert_or_min(xs: &mut HashMap, key: K, val: V) -where - K: Hash + Eq, - V: Ord + Copy, -{ - xs.entry(key) - .and_modify(|e| { - if val < *e { - *e = val; - } - }) - .or_insert(val); -} - -fn edges_filtered_and_sorted_by( - graph: G, - a: G::NodeId, - filter: P, - compare: F, -) -> IntoIter> -where - G: IntoEdges, - P: Fn(&Edge) -> bool, - F: Fn(&Edge) -> K, - K: Ord, -{ - let mut edges = graph - .edges(a) - .filter_map(|edge| { - let e = (edge.source(), edge.target()); - if filter(&e) { - Some(e) - } else { - None - } - }) - .collect::>(); - edges.sort_by_key(compare); - // Remove parallel edges since they do *not* affect whether a graph is planar. - edges.dedup(); - edges.into_iter() -} - -fn is_target(edge: Option<&Edge>, v: G::NodeId) -> Option<&Edge> { - edge.filter(|e| e.1 == v) -} - -#[derive(Clone, Copy, PartialEq, PartialOrd)] -struct Interval { - inner: Option<(T, T)>, -} - -impl Default for Interval { - fn default() -> Self { - Interval { inner: None } - } -} - -impl Interval { - fn new(low: T, high: T) -> Self { - Interval { - inner: Some((low, high)), - } - } - - fn is_empty(&self) -> bool { - self.inner.is_none() - } - - fn unwrap(self) -> (T, T) { - self.inner.unwrap() - } - - fn low(&self) -> Option<&T> { - match self.inner { - Some((ref low, _)) => Some(low), - None => None, - } - } - - fn high(&self) -> Option<&T> { - match self.inner { - Some((_, ref high)) => Some(high), - None => None, - } - } - - fn as_ref(&mut self) -> Option<&(T, T)> { - self.inner.as_ref() - } - - fn as_mut(&mut self) -> Option<&mut (T, T)> { - self.inner.as_mut() - } - - fn as_mut_low(&mut self) -> Option<&mut T> { - match self.inner { - Some((ref mut low, _)) => Some(low), - None => None, - } - } -} - -impl Interval<(T, T)> -where - T: Copy + Hash + Eq, -{ - /// Returns ``true`` if the interval conflicts with ``edge``. - fn conflict(&self, lr_state: &LRState, edge: Edge) -> bool - where - G: GraphBase, - { - match self.inner { - Some((_, ref h)) => lr_state.lowpt.get(h) > lr_state.lowpt.get(&edge), - _ => false, - } - } -} - -#[derive(Clone, Copy, PartialEq, PartialOrd)] -struct ConflictPair { - left: Interval, - right: Interval, -} - -impl Default for ConflictPair { - fn default() -> Self { - ConflictPair { - left: Interval::default(), - right: Interval::default(), - } - } -} - -impl ConflictPair { - fn new(left: Interval, right: Interval) -> Self { - ConflictPair { left, right } - } - - fn swap(&mut self) { - std::mem::swap(&mut self.left, &mut self.right) - } - - fn is_empty(&self) -> bool { - self.left.is_empty() && self.right.is_empty() - } -} - -impl ConflictPair<(T, T)> -where - T: Copy + Hash + Eq, -{ - /// Returns the lowest low point of a conflict pair. - fn lowest(&self, lr_state: &LRState) -> usize - where - G: GraphBase, - { - match (self.left.low(), self.right.low()) { - (Some(l_low), Some(r_low)) => lr_state.lowpt[l_low].min(lr_state.lowpt[r_low]), - (Some(l_low), None) => lr_state.lowpt[l_low], - (None, Some(r_low)) => lr_state.lowpt[r_low], - (None, None) => std::usize::MAX, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Sign { - Plus, - Minus, -} - -/// Similar to ``DfsEvent`` plus an extra event ``FinishEdge`` -/// that indicates that we have finished processing an edge. -enum LRTestDfsEvent { - Finish(N), - TreeEdge(N, N), - BackEdge(N, N), - FinishEdge(N, N), -} - -// An error: graph is *not* planar. -struct NonPlanar {} - -pub struct LRState -where - G::NodeId: Hash + Eq, -{ - pub graph: G, - /// roots of the DFS forest. - pub roots: Vec, - /// distnace from root. - height: HashMap, - /// parent edge. - pub eparent: HashMap>, - /// height of lowest return point. - lowpt: HashMap, usize>, - /// height of next-to-lowest return point. Only used to check if an edge is chordal. - lowpt_2: HashMap, usize>, - /// next back edge in traversal with lowest return point. - lowpt_edge: HashMap, Edge>, - /// proxy for nesting order ≺ given by twice lowpt (plus 1 if chordal). - pub nesting_depth: HashMap, isize>, - /// stack for conflict pairs. - stack: Vec>>, - /// marks the top conflict pair when an edge was pushed in the stack. - stack_emarker: HashMap, ConflictPair>>, - /// edge relative to which side is defined. - pub eref: HashMap, Edge>, - /// side of edge, or modifier for side of reference edge. - pub side: HashMap, Sign>, - /// directed graph used to build the embedding - pub dir_graph: StableGraph<(), (), Directed>, -} - -impl LRState -where - G: GraphBase - + NodeCount - + EdgeCount - + IntoNodeIdentifiers - + NodeIndexable - + IntoEdges - + Visitable, - G::NodeId: Hash + Eq, -{ - pub fn new(graph: G) -> Self { - let num_nodes = graph.node_count(); - let num_edges = graph.edge_count(); - - let lr_state = LRState { - graph, - roots: Vec::new(), - height: HashMap::with_capacity(num_nodes), - eparent: HashMap::with_capacity(num_edges), - lowpt: HashMap::with_capacity(num_edges), - lowpt_2: HashMap::with_capacity(num_edges), - lowpt_edge: HashMap::with_capacity(num_edges), - nesting_depth: HashMap::with_capacity(num_edges), - stack: Vec::new(), - stack_emarker: HashMap::with_capacity(num_edges), - eref: HashMap::with_capacity(num_edges), - side: graph - .edge_references() - .map(|e| ((e.source(), e.target()), Sign::Plus)) - .collect(), - dir_graph: StableGraph::with_capacity(num_nodes, 0), - }; - lr_state - } - - // Create the directed graph for the embedding in stable format - // to match the original graph. - fn build_dir_graph(&mut self) where ::NodeId: Ord { - let mut tmp_nodes: Vec = Vec::new(); - let mut count: usize = 0; - for _ in 0..self.graph.node_bound() { - self.dir_graph.add_node(()); - } - for gnode in self.graph.node_identifiers() { - let gidx = self.graph.to_index(gnode); - if gidx != count { - for idx in count..gidx { - tmp_nodes.push(idx); - } - count = gidx; - } - count += 1; - } - for tmp_node in tmp_nodes { - self.dir_graph.remove_node(NodeIndex::new(tmp_node)); - } - } - - fn lr_orientation_visitor(&mut self, event: DfsEvent) { - match event { - DfsEvent::Discover(v, _) => { - if let Entry::Vacant(entry) = self.height.entry(v) { - entry.insert(0); - self.roots.push(v); - } - } - DfsEvent::TreeEdge(v, w, _) => { - let v_dir = NodeIndex::new(self.graph.to_index(v)); - let w_dir = NodeIndex::new(self.graph.to_index(w)); - if !self.dir_graph.contains_edge(v_dir, w_dir) { - self.dir_graph.add_edge(v_dir, w_dir, ()); - } - let ei = (v, w); - let v_height = self.height[&v]; - let w_height = v_height + 1; - - self.eparent.insert(w, ei); - self.height.insert(w, w_height); - // now initialize low points. - self.lowpt.insert(ei, v_height); - self.lowpt_2.insert(ei, w_height); - } - DfsEvent::BackEdge(v, w, _) => { - // do *not* consider ``(v, w)`` as a back edge if ``(w, v)`` is a tree edge. - if Some(&(w, v)) != self.eparent.get(&v) { - let v_dir = NodeIndex::new(self.graph.to_index(v)); - let w_dir = NodeIndex::new(self.graph.to_index(w)); - if !self.dir_graph.contains_edge(v_dir, w_dir) { - self.dir_graph.add_edge(v_dir, w_dir, ()); - } - let ei = (v, w); - self.lowpt.insert(ei, self.height[&w]); - self.lowpt_2.insert(ei, self.height[&v]); - } - } - DfsEvent::Finish(v, _) => { - for edge in self.graph.edges(v) { - let w = edge.target(); - let ei = (v, w); - - // determine nesting depth. - let low = match self.lowpt.get(&ei) { - Some(val) => *val, - None => - // if ``lowpt`` does *not* contain edge ``(v, w)``, it means - // that it's *not* a tree or a back edge so we skip it since - // it's oriented in the reverse direction. - { - continue - } - }; - - if self.lowpt_2[&ei] < self.height[&v] { - // if it's chordal, add one. - self.nesting_depth.insert(ei, (2 * low) as isize + 1); - } else { - self.nesting_depth.insert(ei, (2 * low) as isize); - } - - // update lowpoints of parent edge. - if let Some(e_par) = self.eparent.get(&v) { - match self.lowpt[&ei].cmp(&self.lowpt[e_par]) { - Ordering::Less => { - self.lowpt_2 - .insert(*e_par, self.lowpt[e_par].min(self.lowpt_2[&ei])); - self.lowpt.insert(*e_par, self.lowpt[&ei]); - } - Ordering::Greater => { - insert_or_min(&mut self.lowpt_2, *e_par, self.lowpt[&ei]); - } - _ => { - let val = self.lowpt_2[&ei]; - insert_or_min(&mut self.lowpt_2, *e_par, val); - } - } - } - } - } - _ => {} - } - } - - fn lr_testing_visitor(&mut self, event: LRTestDfsEvent) -> Result<(), NonPlanar> { - match event { - LRTestDfsEvent::TreeEdge(v, w) => { - let ei = (v, w); - if let Some(&last) = self.stack.last() { - self.stack_emarker.insert(ei, last); - } - } - LRTestDfsEvent::BackEdge(v, w) => { - let ei = (v, w); - if let Some(&last) = self.stack.last() { - self.stack_emarker.insert(ei, last); - } - self.lowpt_edge.insert(ei, ei); - let c_pair = ConflictPair::new(Interval::default(), Interval::new(ei, ei)); - self.stack.push(c_pair); - } - LRTestDfsEvent::FinishEdge(v, w) => { - let ei = (v, w); - if self.lowpt[&ei] < self.height[&v] { - // ei has return edge - let e_par = self.eparent[&v]; - let val = self.lowpt_edge[&ei]; - - match self.lowpt_edge.entry(e_par) { - Entry::Occupied(_) => { - self.add_constraints(ei, e_par)?; - } - Entry::Vacant(o) => { - o.insert(val); - } - } - } - } - LRTestDfsEvent::Finish(v) => { - if let Some(&e) = self.eparent.get(&v) { - let u = e.0; - self.remove_back_edges(u); - - // side of ``e = (u, v)` is side of a highest return edge - if self.lowpt[&e] < self.height[&u] { - if let Some(top) = self.stack.last() { - let e_high = match (top.left.high(), top.right.high()) { - (Some(hl), Some(hr)) => { - if self.lowpt[hl] > self.lowpt[hr] { - hl - } else { - hr - } - } - (Some(hl), None) => hl, - (None, Some(hr)) => hr, - _ => { - // Otherwise ``top`` would be empty, but we don't push - // empty conflict pairs in stack. - unreachable!() - } - }; - self.eref.insert(e, *e_high); - } - } - } - } - } - - Ok(()) - } - - fn until_top_of_stack_hits_emarker(&mut self, ei: Edge) -> Option>> { - if let Some(&c_pair) = self.stack.last() { - if self.stack_emarker[&ei] != c_pair { - return self.stack.pop(); - } - } - - None - } - - fn until_top_of_stack_is_conflicting(&mut self, ei: Edge) -> Option>> { - if let Some(c_pair) = self.stack.last() { - if c_pair.left.conflict(self, ei) || c_pair.right.conflict(self, ei) { - return self.stack.pop(); - } - } - - None - } - - /// Unify intervals ``pi``, ``qi``. - /// - /// Interval ``qi`` must be non - empty and contain edges - /// with smaller lowpt than interval ``pi``. - fn union_intervals(&mut self, pi: &mut Interval>, qi: Interval>) { - match pi.as_mut_low() { - Some(p_low) => { - let (q_low, q_high) = qi.unwrap(); - self.eref.insert(*p_low, q_high); - *p_low = q_low; - } - None => { - *pi = qi; - } - } - } - - /// Adding constraints associated with edge ``ei``. - fn add_constraints(&mut self, ei: Edge, e: Edge) -> Result<(), NonPlanar> { - let mut c_pair = ConflictPair::>::default(); - - // merge return edges of ei into ``c_pair.right``. - while let Some(mut q_pair) = self.until_top_of_stack_hits_emarker(ei) { - if !q_pair.left.is_empty() { - q_pair.swap(); - - if !q_pair.left.is_empty() { - return Err(NonPlanar {}); - } - } - - // We call unwrap since ``q_pair`` was in stack and - // ``q_pair.right``, ``q_pair.left`` can't be both empty - // since we don't push empty conflict pairs in stack. - let qr_low = q_pair.right.low().unwrap(); - if self.lowpt[qr_low] > self.lowpt[&e] { - // merge intervals - self.union_intervals(&mut c_pair.right, q_pair.right); - } else { - // make consinsent - self.eref.insert(*qr_low, self.lowpt_edge[&e]); - } - } - - // merge conflicting return edges of e1, . . . , ei−1 into ``c_pair.left``. - while let Some(mut q_pair) = self.until_top_of_stack_is_conflicting(ei) { - if q_pair.right.conflict(self, ei) { - q_pair.swap(); - - if q_pair.right.conflict(self, ei) { - return Err(NonPlanar {}); - } - } - - // merge interval below lowpt(ei) into ``c_pair.right``. - if let Some((qr_low, qr_high)) = q_pair.right.as_ref() { - if let Some(pr_low) = c_pair.right.as_mut_low() { - self.eref.insert(*pr_low, *qr_high); - *pr_low = *qr_low; - } - }; - self.union_intervals(&mut c_pair.left, q_pair.left); - } - - if !c_pair.is_empty() { - self.stack.push(c_pair); - } - - Ok(()) - } - - fn until_lowest_top_of_stack_has_height( - &mut self, - v: G::NodeId, - ) -> Option>> { - if let Some(c_pair) = self.stack.last() { - if c_pair.lowest(self) == self.height[&v] { - return self.stack.pop(); - } - } - - None - } - - fn follow_eref_until_is_target(&self, edge: Edge, v: G::NodeId) -> Option> { - let mut res = Some(&edge); - while let Some(b) = is_target::(res, v) { - res = self.eref.get(b); - } - - res.copied() - } - - /// Trim back edges ending at parent v. - fn remove_back_edges(&mut self, v: G::NodeId) { - // drop entire conflict pairs. - while let Some(c_pair) = self.until_lowest_top_of_stack_has_height(v) { - if let Some(pl_low) = c_pair.left.low() { - self.side.insert(*pl_low, Sign::Minus); - } - } - - // one more conflict pair to consider. - if let Some(mut c_pair) = self.stack.pop() { - // trim left interval. - if let Some((pl_low, pl_high)) = c_pair.left.as_mut() { - match self.follow_eref_until_is_target(*pl_high, v) { - Some(val) => { - *pl_high = val; - } - None => { - // just emptied. - // We call unwrap since right interval cannot be empty for otherwise - // the entire conflict pair had been removed. - let pr_low = c_pair.right.low().unwrap(); - self.eref.insert(*pl_low, *pr_low); - self.side.insert(*pl_low, Sign::Minus); - c_pair.left = Interval::default(); - } - } - } - - // trim right interval - if let Some((pr_low, ref mut pr_high)) = c_pair.right.as_mut() { - match self.follow_eref_until_is_target(*pr_high, v) { - Some(val) => { - *pr_high = val; - } - None => { - // just emptied. - // We call unwrap since left interval cannot be empty for otherwise - // the entire conflict pair had been removed. - let pl_low = c_pair.left.low().unwrap(); - self.eref.insert(*pr_low, *pl_low); - self.side.insert(*pr_low, Sign::Minus); - c_pair.right = Interval::default(); - } - }; - } - - if !c_pair.is_empty() { - self.stack.push(c_pair); - } - } - } -} - -/// Visits the DFS - oriented tree that we have pre-computed -/// and stored in ``lr_state``. We traverse the edges of -/// a node in nesting depth order. Events are emitted at points -/// of interest and should be handled by ``visitor``. -fn lr_visit_ordered_dfs_tree( - lr_state: &mut LRState, - v: G::NodeId, - mut visitor: F, -) -> Result<(), E> -where - G: GraphBase + IntoEdges, - G::NodeId: Hash + Eq, - F: FnMut(&mut LRState, LRTestDfsEvent) -> Result<(), E>, -{ - let mut stack: Vec<(G::NodeId, IntoIter>)> = vec![( - v, - edges_filtered_and_sorted_by( - lr_state.graph, - v, - // if ``lowpt`` does *not* contain edge ``e = (v, w)``, it means - // that it's *not* a tree or a back edge so we skip it since - // it's oriented in the reverse direction. - |e| lr_state.lowpt.contains_key(e), - // we sort edges based on nesting depth order. - |e| lr_state.nesting_depth[e], - ), - )]; - - while let Some(elem) = stack.last_mut() { - let v = elem.0; - let adjacent_edges = &mut elem.1; - let mut next = None; - - for (v, w) in adjacent_edges { - if Some(&(v, w)) == lr_state.eparent.get(&w) { - // tree edge - visitor(lr_state, LRTestDfsEvent::TreeEdge(v, w))?; - next = Some(w); - break; - } else { - // back edge - visitor(lr_state, LRTestDfsEvent::BackEdge(v, w))?; - visitor(lr_state, LRTestDfsEvent::FinishEdge(v, w))?; - } - } - - match next { - Some(w) => stack.push(( - w, - edges_filtered_and_sorted_by( - lr_state.graph, - w, - |e| lr_state.lowpt.contains_key(e), - |e| lr_state.nesting_depth[e], - ), - )), - None => { - stack.pop(); - visitor(lr_state, LRTestDfsEvent::Finish(v))?; - if let Some(&(u, v)) = lr_state.eparent.get(&v) { - visitor(lr_state, LRTestDfsEvent::FinishEdge(u, v))?; - } - } - } - } - - Ok(()) -} - -/// Check if an undirected graph is planar. -/// -/// A graph is planar iff it can be drawn in a plane without any edge -/// intersections. -/// -/// The planarity check algorithm is based on the -/// Left-Right Planarity Test: -/// -/// [`Ulrik Brandes: The Left-Right Planarity Test (2009)`](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.217.9208) -/// -/// # Example: -/// ```rust -/// use retworkx_core::petgraph::graph::UnGraph; -/// use retworkx_core::planar::{is_planar, LRState}; -/// -/// let grid = UnGraph::<(), ()>::from_edges(&[ -/// // row edges -/// (0, 1), (1, 2), (3, 4), (4, 5), (6, 7), (7, 8), -/// // col edges -/// (0, 3), (3, 6), (1, 4), (4, 7), (2, 5), (5, 8), -/// ]); -/// let mut lr_state = LRState::new(&grid); -/// assert!(is_planar(&grid, Some(&mut lr_state))) -/// ``` -pub fn is_planar(graph: G, state: Option<&mut LRState>) -> bool -where - G: GraphProp - + NodeCount - + EdgeCount - + IntoEdges - + NodeIndexable - + IntoNodeIdentifiers - + Visitable, - G::NodeId: Hash + Eq + Ord, -{ - // If None passed for state, create new LRState - let mut lr_state = LRState::new(graph); - let lr_state = match state { - Some(state) => state, - None => &mut lr_state, - }; - - // Build directed graph for the embedding - lr_state.build_dir_graph(); - - // Dfs orientation phase - depth_first_search(graph, graph.node_identifiers(), |event| { - lr_state.lr_orientation_visitor(event) - }); - - // Left - Right partition. - for v in lr_state.roots.clone() { - let res = lr_visit_ordered_dfs_tree(lr_state, v, |lr_state, event| { - lr_state.lr_testing_visitor(event) - }); - if res.is_err() { - return false; - } - } - true -} diff --git a/retworkx-core/src/planar/mod.rs b/retworkx-core/src/planar/mod.rs deleted file mode 100644 index d6fe1f4e6..000000000 --- a/retworkx-core/src/planar/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed under the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -//! Module for planar graphs. - -pub mod lr_planar; - -pub use lr_planar::{is_planar, LRState}; diff --git a/retworkx-core/tests/test_planar.rs b/retworkx-core/tests/test_planar.rs deleted file mode 100644 index 3f7cb3d76..000000000 --- a/retworkx-core/tests/test_planar.rs +++ /dev/null @@ -1,281 +0,0 @@ -// Licensed under the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -//! Test module for planar graphs. - -use retworkx_core::petgraph::graph::UnGraph; -use retworkx_core::planar::{is_planar, LRState}; - -#[test] -fn test_simple_planar_graph() { - let graph = UnGraph::<(), ()>::from_edges(&[ - (1, 2), - (2, 3), - (3, 4), - (4, 6), - (6, 7), - (7, 1), - (1, 5), - (5, 2), - (2, 4), - (4, 5), - (5, 7), - ]); - let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); - assert!(res) -} - -#[test] -fn test_planar_grid_3_3_graph() { - let graph = UnGraph::<(), ()>::from_edges(&[ - // row edges - (0, 1), - (1, 2), - (3, 4), - (4, 5), - (6, 7), - (7, 8), - // col edges - (0, 3), - (3, 6), - (1, 4), - (4, 7), - (2, 5), - (5, 8), - ]); - let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); - assert!(res) -} - -#[test] -fn test_planar_with_self_loop() { - let graph = UnGraph::<(), ()>::from_edges(&[ - (1, 1), - (2, 2), - (3, 3), - (4, 4), - (5, 5), - (1, 2), - (1, 3), - (1, 5), - (2, 5), - (2, 4), - (3, 4), - (3, 5), - (4, 5), - ]); - let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); - assert!(res) -} - -#[test] -fn test_goldner_harary_planar_graph() { - // test goldner-harary graph (a maximal planar graph) - let graph = UnGraph::<(), ()>::from_edges(&[ - (1, 2), - (1, 3), - (1, 4), - (1, 5), - (1, 7), - (1, 8), - (1, 10), - (1, 11), - (2, 3), - (2, 4), - (2, 6), - (2, 7), - (2, 9), - (2, 10), - (2, 11), - (3, 4), - (4, 5), - (4, 6), - (4, 7), - (5, 7), - (6, 7), - (7, 8), - (7, 9), - (7, 10), - (8, 10), - (9, 10), - (10, 11), - ]); - let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); - assert!(res) -} - -#[test] -fn test_multiple_components_planar_graph() { - let graph = UnGraph::<(), ()>::from_edges(&[(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)]); - let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); - assert!(res) -} - -#[test] -fn test_planar_multi_graph() { - let graph = UnGraph::<(), ()>::from_edges(&[(0, 1), (0, 1), (0, 1), (1, 2), (2, 0)]); - let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); - assert!(res) -} - -#[test] -fn test_k3_3_non_planar() { - let graph = UnGraph::<(), ()>::from_edges(&[ - (0, 3), - (0, 4), - (0, 5), - (1, 3), - (1, 4), - (1, 5), - (2, 3), - (2, 4), - (2, 5), - ]); - let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); - assert_eq!(res, false) -} - -#[test] -fn test_k5_non_planar() { - let graph = UnGraph::<(), ()>::from_edges(&[ - (0, 1), - (0, 2), - (0, 3), - (0, 4), - (1, 2), - (1, 3), - (1, 4), - (2, 3), - (2, 4), - (3, 4), - ]); - let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); - assert_eq!(res, false) -} - -#[test] -fn test_multiple_components_non_planar() { - let graph = UnGraph::<(), ()>::from_edges(&[ - (0, 1), - (0, 2), - (0, 3), - (0, 4), - (1, 2), - (1, 3), - (1, 4), - (2, 3), - (2, 4), - (3, 4), - (6, 7), - (7, 8), - (8, 6), - ]); - let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); - assert_eq!(res, false) -} - -#[test] -fn test_non_planar() { - // tests a graph that has no subgraph directly isomorphic to K5 or K3_3. - let graph = UnGraph::<(), ()>::from_edges(&[ - (1, 5), - (1, 6), - (1, 7), - (2, 6), - (2, 3), - (3, 5), - (3, 7), - (4, 5), - (4, 6), - (4, 7), - ]); - let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); - assert_eq!(res, false) -} - -#[test] -fn test_planar_graph1() { - let graph = UnGraph::<(), ()>::from_edges(&[ - (3, 10), - (2, 13), - (1, 13), - (7, 11), - (0, 8), - (8, 13), - (0, 2), - (0, 7), - (0, 10), - (1, 7), - ]); - let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); - assert!(res) -} - -#[test] -fn test_non_planar_graph2() { - let graph = UnGraph::<(), ()>::from_edges(&[ - (1, 2), - (4, 13), - (0, 13), - (4, 5), - (7, 10), - (1, 7), - (0, 3), - (2, 6), - (5, 6), - (7, 13), - (4, 8), - (0, 8), - (0, 9), - (2, 13), - (6, 7), - (3, 6), - (2, 8), - ]); - let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); - assert_eq!(res, false) -} - -#[test] -fn test_non_planar_graph3() { - let graph = UnGraph::<(), ()>::from_edges(&[ - (0, 7), - (3, 11), - (3, 4), - (8, 9), - (4, 11), - (1, 7), - (1, 13), - (1, 11), - (3, 5), - (5, 7), - (1, 3), - (0, 4), - (5, 11), - (5, 13), - ]); - let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); - assert_eq!(res, false) -} diff --git a/src/layout/embedding.rs b/src/layout/embedding.rs index 3f715f110..826ccf027 100644 --- a/src/layout/embedding.rs +++ b/src/layout/embedding.rs @@ -119,14 +119,6 @@ impl PlanarEmbedding { self.embedding.add_edge(start_node, end_node, cw_weight); if let Some(ref_nbr_node) = ref_nbr { - if self.embedding.find_edge(start_node, ref_nbr_node).is_none() { - // RAISE? - println!( - "Cannot add edge to {:?}. Reference neighbor {:?} does not exist", - start_node, ref_nbr_node - ); - panic!(); - } let cw_ref = self .get_edge_weight(start_node, ref_nbr_node, true) .unwrap(); @@ -181,27 +173,12 @@ impl PlanarEmbedding { fn next_face_half_edge(&mut self, v: NodeIndex, w: NodeIndex) -> (NodeIndex, NodeIndex) { let new_node = self.get_edge_weight(w, v, false); - if new_node.is_none() { - // RAISE? - return (w, v); - } (w, new_node.unwrap()) } fn update_edge_weight(&mut self, v: NodeIndex, w: NodeIndex, new_node: NodeIndex, cw: bool) { let found_edge = self.embedding.find_edge(v, w); - let cw_weight = CwCcw::::default(); - if found_edge.is_none() { - // RAISE? - println!("update v {:?} w {:?}", v, w); - self.embedding.add_edge(v, w, cw_weight); - } - let mut found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()); - let mut cw_weight2 = CwCcw::::default(); - if found_weight.is_none() { - // RAISE? - found_weight = Some(&mut cw_weight2); - } + let found_weight = self.embedding.edge_weight_mut(found_edge.unwrap()); if cw { found_weight.unwrap().cw = Some(new_node); } else { @@ -539,11 +516,6 @@ fn make_bi_connected( let (_, mut v3) = planar_emb.next_face_half_edge(v1, v2); while v2 != *start_node || v3 != *out_node { - if v1 == v2 { - // RAISE? - println!("BICONNECT V1==V2 should raise"); - } - if face_list.contains(&v2) { planar_emb.add_half_edge_cw(v1, v3, Some(v2)); planar_emb.add_half_edge_ccw(v3, v1, Some(v2)); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index a26b54048..878b7b029 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -218,7 +218,7 @@ pub fn graph_planar_layout( graph: &graph::PyGraph, scale: Option, center: Option<[f64; 2]>, -) -> Pos2DMapping { +) -> PyResult { planar::planar_layout(&graph.graph, scale, center) } diff --git a/src/layout/planar.rs b/src/layout/planar.rs index e0d4e8793..bac91ba3d 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -12,7 +12,9 @@ use petgraph::prelude::*; use petgraph::visit::NodeIndexable; +use pyo3::prelude::*; +use super::super::GraphNotPlanar; use super::spring::{recenter, rescale, Point}; use crate::iterators::Pos2DMapping; use crate::layout::embedding::{create_embedding, embedding_to_pos, PlanarEmbedding}; @@ -26,24 +28,18 @@ pub fn planar_layout( graph: &StablePyGraph, scale: Option, center: Option, -) -> Pos2DMapping { +) -> PyResult { let node_num = graph.node_bound(); if node_num == 0 { - return Pos2DMapping { + return Ok(Pos2DMapping { pos_map: DictMap::new(), - }; + }); } // First determine if the graph is planar. let mut lr_state = LRState::new(graph); - let its_planar = is_planar(graph, Some(&mut lr_state)); - - // If not planar, return an empty pos_map - if !its_planar { - // RAISE? - Pos2DMapping { - pos_map: DictMap::new(), - } + if !is_planar(graph, Some(&mut lr_state)) { + Err(GraphNotPlanar::new_err("The input graph is not planar.")) // If planar, create the position coordinates. } else { @@ -66,7 +62,7 @@ pub fn planar_layout( if let Some(center) = center { recenter(&mut pos, center); } - Pos2DMapping { + Ok(Pos2DMapping { pos_map: graph .node_indices() .map(|n| { @@ -74,6 +70,6 @@ pub fn planar_layout( (n, pos[n]) }) .collect(), - } + }) } } diff --git a/src/lib.rs b/src/lib.rs index f2420b013..a0cb1943e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -322,6 +322,8 @@ create_exception!(rustworkx, JSONSerializationError, PyException); create_exception!(rustworkx, NegativeCycle, PyException); // Failed to Converge on a solution create_exception!(rustworkx, FailedToConverge, PyException); +// Graph is not planar +create_exception!(rustworkx, GraphNotPlanar, PyException); #[pymodule] fn rustworkx(py: Python<'_>, m: &PyModule) -> PyResult<()> { @@ -339,6 +341,7 @@ fn rustworkx(py: Python<'_>, m: &PyModule) -> PyResult<()> { py.get_type::(), )?; m.add("FailedToConverge", py.get_type::())?; + m.add("GraphNotPlanar", py.get_type::())?; m.add_wrapped(wrap_pyfunction!(bfs_successors))?; m.add_wrapped(wrap_pyfunction!(graph_bfs_search))?; m.add_wrapped(wrap_pyfunction!(digraph_bfs_search))?; From 3a080e74e45aae4ccd9abdc66d1b01eca96aeae3 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Tue, 4 Oct 2022 13:28:38 -0700 Subject: [PATCH 37/37] Merge lr_planar changes and update tests --- rustworkx-core/src/planar/lr_planar.rs | 43 ++++++++++++++++++++++++-- rustworkx-core/src/planar/mod.rs | 2 +- rustworkx-core/tests/test_planar.rs | 28 ++++++++--------- src/layout/planar.rs | 4 +-- 4 files changed, 57 insertions(+), 20 deletions(-) diff --git a/rustworkx-core/src/planar/lr_planar.rs b/rustworkx-core/src/planar/lr_planar.rs index cdd1b546a..a76c0afc8 100644 --- a/rustworkx-core/src/planar/lr_planar.rs +++ b/rustworkx-core/src/planar/lr_planar.rs @@ -704,7 +704,7 @@ where /// # Example: /// ```rust /// use rustworkx_core::petgraph::graph::UnGraph; -/// use rustworkx_core::planar::{is_planar, LRState}; +/// use rustworkx_core::planar::{is_planar_for_layout, LRState}; /// /// let grid = UnGraph::<(), ()>::from_edges(&[ /// // row edges @@ -713,9 +713,9 @@ where /// (0, 3), (3, 6), (1, 4), (4, 7), (2, 5), (5, 8), /// ]); /// let mut lr_state = LRState::new(&grid); -/// assert!(is_planar(&grid, Some(&mut lr_state))) +/// assert!(is_planar_for_layout(&grid, Some(&mut lr_state))) /// ``` -pub fn is_planar(graph: G, state: Option<&mut LRState>) -> bool +pub fn is_planar_for_layout(graph: G, state: Option<&mut LRState>) -> bool where G: GraphProp + NodeCount @@ -753,3 +753,40 @@ where true } + +/// Check if an undirected graph is planar. +/// +/// A graph is planar iff it can be drawn in a plane without any edge +/// intersections. +/// +/// The planarity check algorithm is based on the +/// Left-Right Planarity Test: +/// +/// [`Ulrik Brandes: The Left-Right Planarity Test (2009)`](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.217.9208) +/// +/// # Example: +/// ```rust +/// use rustworkx_core::petgraph::graph::UnGraph; +/// use rustworkx_core::planar::is_planar; +/// +/// let grid = UnGraph::<(), ()>::from_edges(&[ +/// // row edges +/// (0, 1), (1, 2), (3, 4), (4, 5), (6, 7), (7, 8), +/// // col edges +/// (0, 3), (3, 6), (1, 4), (4, 7), (2, 5), (5, 8), +/// ]); +/// assert!(is_planar(&grid)) +/// ``` +pub fn is_planar(graph: G) -> bool +where + G: GraphProp + + NodeCount + + EdgeCount + + IntoEdges + + IntoNodeIdentifiers + + NodeIndexable + + Visitable, + G::NodeId: Hash + Eq + Ord, +{ + is_planar_for_layout(graph, None) +} diff --git a/rustworkx-core/src/planar/mod.rs b/rustworkx-core/src/planar/mod.rs index d6fe1f4e6..a7e5ca020 100644 --- a/rustworkx-core/src/planar/mod.rs +++ b/rustworkx-core/src/planar/mod.rs @@ -14,4 +14,4 @@ pub mod lr_planar; -pub use lr_planar::{is_planar, LRState}; +pub use lr_planar::{is_planar, is_planar_for_layout, LRState}; diff --git a/rustworkx-core/tests/test_planar.rs b/rustworkx-core/tests/test_planar.rs index fd1cd672e..8a4fa97b7 100644 --- a/rustworkx-core/tests/test_planar.rs +++ b/rustworkx-core/tests/test_planar.rs @@ -13,7 +13,7 @@ //! Test module for planar graphs. use rustworkx_core::petgraph::graph::UnGraph; -use rustworkx_core::planar::{is_planar, LRState}; +use rustworkx_core::planar::{is_planar_for_layout, LRState}; #[test] fn test_simple_planar_graph() { @@ -31,7 +31,7 @@ fn test_simple_planar_graph() { (5, 7), ]); let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); + let res = is_planar_for_layout(&graph, Some(&mut lr_state)); assert!(res) } @@ -54,7 +54,7 @@ fn test_planar_grid_3_3_graph() { (5, 8), ]); let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); + let res = is_planar_for_layout(&graph, Some(&mut lr_state)); assert!(res) } @@ -76,7 +76,7 @@ fn test_planar_with_self_loop() { (4, 5), ]); let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); + let res = is_planar_for_layout(&graph, Some(&mut lr_state)); assert!(res) } @@ -113,7 +113,7 @@ fn test_goldner_harary_planar_graph() { (10, 11), ]); let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); + let res = is_planar_for_layout(&graph, Some(&mut lr_state)); assert!(res) } @@ -121,7 +121,7 @@ fn test_goldner_harary_planar_graph() { fn test_multiple_components_planar_graph() { let graph = UnGraph::<(), ()>::from_edges(&[(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)]); let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); + let res = is_planar_for_layout(&graph, Some(&mut lr_state)); assert!(res) } @@ -129,7 +129,7 @@ fn test_multiple_components_planar_graph() { fn test_planar_multi_graph() { let graph = UnGraph::<(), ()>::from_edges(&[(0, 1), (0, 1), (0, 1), (1, 2), (2, 0)]); let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); + let res = is_planar_for_layout(&graph, Some(&mut lr_state)); assert!(res) } @@ -147,7 +147,7 @@ fn test_k3_3_non_planar() { (2, 5), ]); let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); + let res = is_planar_for_layout(&graph, Some(&mut lr_state)); assert_eq!(res, false) } @@ -166,7 +166,7 @@ fn test_k5_non_planar() { (3, 4), ]); let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); + let res = is_planar_for_layout(&graph, Some(&mut lr_state)); assert_eq!(res, false) } @@ -188,7 +188,7 @@ fn test_multiple_components_non_planar() { (8, 6), ]); let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); + let res = is_planar_for_layout(&graph, Some(&mut lr_state)); assert_eq!(res, false) } @@ -208,7 +208,7 @@ fn test_non_planar() { (4, 7), ]); let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); + let res = is_planar_for_layout(&graph, Some(&mut lr_state)); assert_eq!(res, false) } @@ -227,7 +227,7 @@ fn test_planar_graph1() { (1, 7), ]); let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); + let res = is_planar_for_layout(&graph, Some(&mut lr_state)); assert!(res) } @@ -253,7 +253,7 @@ fn test_non_planar_graph2() { (2, 8), ]); let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); + let res = is_planar_for_layout(&graph, Some(&mut lr_state)); assert_eq!(res, false) } @@ -276,6 +276,6 @@ fn test_non_planar_graph3() { (5, 13), ]); let mut lr_state = LRState::new(&graph); - let res = is_planar(&graph, Some(&mut lr_state)); + let res = is_planar_for_layout(&graph, Some(&mut lr_state)); assert_eq!(res, false) } diff --git a/src/layout/planar.rs b/src/layout/planar.rs index bac91ba3d..457136e04 100644 --- a/src/layout/planar.rs +++ b/src/layout/planar.rs @@ -20,7 +20,7 @@ use crate::iterators::Pos2DMapping; use crate::layout::embedding::{create_embedding, embedding_to_pos, PlanarEmbedding}; use crate::StablePyGraph; use rustworkx_core::dictmap::*; -use rustworkx_core::planar::{is_planar, LRState}; +use rustworkx_core::planar::{is_planar_for_layout, LRState}; /// If a graph is planar, create a set of position coordinates for a planar /// layout that can be passed to a drawer. @@ -38,7 +38,7 @@ pub fn planar_layout( // First determine if the graph is planar. let mut lr_state = LRState::new(graph); - if !is_planar(graph, Some(&mut lr_state)) { + if !is_planar_for_layout(graph, Some(&mut lr_state)) { Err(GraphNotPlanar::new_err("The input graph is not planar.")) // If planar, create the position coordinates.