Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add planar layout to rustworkx #645

Open
wants to merge 47 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d45dc17
First setup of planar layout for retworkx
enavarro51 Mar 28, 2022
c0f7cd9
Add PlanarEmbedding structure
enavarro51 Apr 20, 2022
fab350a
Add embedding functions
enavarro51 Apr 23, 2022
e770d14
Add update_edge_weight
enavarro51 Apr 29, 2022
975d342
Merge branch 'main' into planar_layout
enavarro51 May 3, 2022
fe6b0db
More PlanarEmbedding functions
enavarro51 May 4, 2022
64895a2
Cleanup PlanarEmbedding
enavarro51 May 7, 2022
a648320
Load lr_planar
enavarro51 May 13, 2022
a9fedab
Finalize structure and start lr_state changes
enavarro51 May 20, 2022
c0ac5e9
Implementing dfs_embedding
enavarro51 May 23, 2022
e7fcbcf
Merge branch 'main' into planar_layout
enavarro51 May 23, 2022
969db13
Debugging embedding
enavarro51 May 29, 2022
b0def5a
Finish phase 1 creating embedding
enavarro51 May 29, 2022
41c46cb
Work on sign and cleanup. Ready to start embedding_to_pos
enavarro51 May 31, 2022
4ee70c6
Do triangulate code
enavarro51 Jun 3, 2022
1240f76
Move embedding to retworkx
enavarro51 Jun 18, 2022
e0e900e
Finish create_embedding and start on canonical ordering
enavarro51 Jun 21, 2022
eeb6e18
Merge branch 'main' into planar_layout
enavarro51 Jun 21, 2022
cc32bcc
Achieved first plot
enavarro51 Jun 23, 2022
df23d90
Working on embedding to pos
enavarro51 Jul 15, 2022
37379b1
Fix triangulate and almost canonical
enavarro51 Jul 17, 2022
72ad6bb
All working for smaller graphs
enavarro51 Jul 18, 2022
101d51a
Cleanup debug prints and add some docs
enavarro51 Jul 19, 2022
8d5227b
Debugging down to canonical
enavarro51 Jul 23, 2022
374dfab
Final chords fix
enavarro51 Jul 24, 2022
39a5c3e
Cleanup (working all but mem issues)
enavarro51 Jul 24, 2022
c2c93da
Merge branch 'main' into planar_layout
enavarro51 Jul 24, 2022
736243e
Fix sign bug and IndexSet for ready_to_pick
enavarro51 Jul 26, 2022
328506a
Fix clippy lint
enavarro51 Jul 27, 2022
31c2927
More lint
enavarro51 Jul 27, 2022
2b95ef8
Fix retworkx-core tests
enavarro51 Jul 27, 2022
e9f230a
MSRV tuple issue
enavarro51 Jul 27, 2022
6f29475
Remove digraph from planar docs
enavarro51 Jul 27, 2022
7d0b01e
Docs, cleanup, and work on stable graphs, change to rustworkx
enavarro51 Aug 13, 2022
3fa89ca
Changing name
enavarro51 Aug 13, 2022
b536f5d
Merge main
enavarro51 Aug 13, 2022
b1069a4
Finish name change
enavarro51 Aug 13, 2022
d5b96be
Fixing stable graph
enavarro51 Aug 15, 2022
913fc21
Stable graph tweaks and more rustworkx test transitions
enavarro51 Aug 17, 2022
6cb9491
Lint
enavarro51 Aug 17, 2022
90b2fac
Final error checking and cleanup
enavarro51 Aug 22, 2022
de44601
Merge branch 'main' into planar_layout
enavarro51 Aug 22, 2022
812adad
Merge branch 'main' into planar_layout
enavarro51 Oct 3, 2022
3a080e7
Merge lr_planar changes and update tests
enavarro51 Oct 4, 2022
d14ad6e
Merge branch 'main' into planar_layout
enavarro51 Oct 4, 2022
9e3bb7c
Merge branch 'main' into planar_layout
IvanIsCoding Jan 13, 2024
b1fcaa8
Merge branch 'main' into planar_layout
enavarro51 Jun 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 118 additions & 23 deletions rustworkx-core/src/planar/lr_planar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ 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,
Visitable,
NodeIndexable, Visitable,
},
Undirected,
Directed, Undirected,
};

use crate::traversal::{depth_first_search, DfsEvent};
Expand Down Expand Up @@ -191,7 +193,8 @@ where
}
}

enum Sign {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Sign {
Plus,
Minus,
}
Expand All @@ -208,41 +211,49 @@ enum LRTestDfsEvent<N> {
// An error: graph is *not* planar.
struct NonPlanar {}

struct LRState<G: GraphBase>
pub struct LRState<G: GraphBase>
where
G::NodeId: Hash + Eq,
{
graph: G,
pub graph: G,
/// roots of the DFS forest.
roots: Vec<G::NodeId>,
pub roots: Vec<G::NodeId>,
/// distnace from root.
height: HashMap<G::NodeId, usize>,
/// parent edge.
eparent: HashMap<G::NodeId, Edge<G>>,
pub eparent: HashMap<G::NodeId, Edge<G>>,
/// height of lowest return point.
lowpt: HashMap<Edge<G>, usize>,
/// height of next-to-lowest return point. Only used to check if an edge is chordal.
lowpt_2: HashMap<Edge<G>, usize>,
/// next back edge in traversal with lowest return point.
lowpt_edge: HashMap<Edge<G>, Edge<G>>,
/// proxy for nesting order ≺ given by twice lowpt (plus 1 if chordal).
nesting_depth: HashMap<Edge<G>, usize>,
pub nesting_depth: HashMap<Edge<G>, isize>,
/// stack for conflict pairs.
stack: Vec<ConflictPair<Edge<G>>>,
/// marks the top conflict pair when an edge was pushed in the stack.
stack_emarker: HashMap<Edge<G>, ConflictPair<Edge<G>>>,
/// edge relative to which side is defined.
eref: HashMap<Edge<G>, Edge<G>>,
pub eref: HashMap<Edge<G>, Edge<G>>,
/// side of edge, or modifier for side of reference edge.
side: HashMap<Edge<G>, Sign>,
pub side: HashMap<Edge<G>, Sign>,
/// directed graph used to build the embedding
pub dir_graph: StableGraph<(), (), Directed>,
}

impl<G> LRState<G>
where
G: GraphBase + NodeCount + EdgeCount + IntoEdges + Visitable,
G: GraphBase
+ NodeCount
+ EdgeCount
+ IntoNodeIdentifiers
+ NodeIndexable
+ IntoEdges
+ 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();

Expand All @@ -262,6 +273,33 @@ where
.edge_references()
.map(|e| ((e.source(), e.target()), Sign::Plus))
.collect(),
dir_graph: StableGraph::with_capacity(num_nodes, 0),
}
}

// Create the directed graph for the embedding in stable format
// to match the original graph.
fn build_dir_graph(&mut self)
where
<G as GraphBase>::NodeId: Ord,
{
let mut tmp_nodes: Vec<usize> = 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));
}
}

Expand All @@ -274,6 +312,11 @@ 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));
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;
Expand All @@ -287,6 +330,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) {
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]);
Expand All @@ -311,9 +359,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 isize + 1);
} else {
self.nesting_depth.insert(ei, 2 * low);
self.nesting_depth.insert(ei, (2 * low) as isize);
}

// update lowpoints of parent edge.
Expand Down Expand Up @@ -656,37 +704,47 @@ where
/// # Example:
/// ```rust
/// use rustworkx_core::petgraph::graph::UnGraph;
/// use rustworkx_core::planar::is_planar;
/// use rustworkx_core::planar::{is_planar_for_layout, 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),
/// ]);
/// assert!(is_planar(&grid))
/// let mut lr_state = LRState::new(&grid);
/// assert!(is_planar_for_layout(&grid, Some(&mut lr_state)))
/// ```
pub fn is_planar<G>(graph: G) -> bool
pub fn is_planar_for_layout<G>(graph: G, state: Option<&mut LRState<G>>) -> bool
where
G: GraphProp<EdgeType = Undirected>
+ NodeCount
+ EdgeCount
+ IntoEdges
+ NodeIndexable
+ IntoNodeIdentifiers
+ Visitable,
G::NodeId: Hash + Eq,
G::NodeId: Hash + Eq + Ord,
{
let mut state = LRState::new(graph);
// 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| {
state.lr_orientation_visitor(event)
lr_state.lr_orientation_visitor(event)
});

// 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;
Expand All @@ -695,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<G>(graph: G) -> bool
where
G: GraphProp<EdgeType = Undirected>
+ NodeCount
+ EdgeCount
+ IntoEdges
+ IntoNodeIdentifiers
+ NodeIndexable
+ Visitable,
G::NodeId: Hash + Eq + Ord,
{
is_planar_for_layout(graph, None)
}
4 changes: 2 additions & 2 deletions rustworkx-core/src/planar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@

//! Module for planar graphs.

mod lr_planar;
pub mod lr_planar;

pub use lr_planar::is_planar;
pub use lr_planar::{is_planar, is_planar_for_layout, LRState};
Loading