rustworkx_core/planar/
lr_planar.rs

1// Licensed under the Apache License, Version 2.0 (the "License"); you may
2// not use this file except in compliance with the License. You may obtain
3// a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10// License for the specific language governing permissions and limitations
11// under the License.
12
13use std::cmp::Ordering;
14use std::hash::Hash;
15use std::vec::IntoIter;
16
17use hashbrown::{hash_map::Entry, HashMap};
18use petgraph::{
19    visit::{
20        EdgeCount, EdgeRef, GraphBase, GraphProp, IntoEdges, IntoNodeIdentifiers, NodeCount,
21        Visitable,
22    },
23    Undirected,
24};
25
26use crate::traversal::{depth_first_search, DfsEvent};
27
28type Edge<G> = (<G as GraphBase>::NodeId, <G as GraphBase>::NodeId);
29
30fn modify_if_min<K, V>(xs: &mut HashMap<K, V>, key: K, val: V)
31where
32    K: Hash + Eq,
33    V: Ord + Copy,
34{
35    xs.entry(key).and_modify(|e| {
36        if val < *e {
37            *e = val;
38        }
39    });
40}
41
42fn edges_filtered_and_sorted_by<G, P, F, K>(
43    graph: G,
44    a: G::NodeId,
45    filter: P,
46    compare: F,
47) -> IntoIter<Edge<G>>
48where
49    G: IntoEdges,
50    P: Fn(&Edge<G>) -> bool,
51    F: Fn(&Edge<G>) -> K,
52    K: Ord,
53{
54    let mut edges = graph
55        .edges(a)
56        .filter_map(|edge| {
57            let e = (edge.source(), edge.target());
58            filter(&e).then_some(e)
59        })
60        .collect::<Vec<_>>();
61    edges.sort_by_key(compare);
62    // Remove parallel edges since they do *not* affect whether a graph is planar.
63    edges.dedup();
64    edges.into_iter()
65}
66
67fn is_target<G: GraphBase>(edge: Option<&Edge<G>>, v: G::NodeId) -> Option<&Edge<G>> {
68    edge.filter(|e| e.1 == v)
69}
70
71#[derive(Clone, Copy, PartialEq, PartialOrd)]
72struct Interval<T> {
73    inner: Option<(T, T)>,
74}
75
76impl<T> Default for Interval<T> {
77    fn default() -> Self {
78        Interval { inner: None }
79    }
80}
81
82impl<T> Interval<T> {
83    fn new(low: T, high: T) -> Self {
84        Interval {
85            inner: Some((low, high)),
86        }
87    }
88
89    fn is_empty(&self) -> bool {
90        self.inner.is_none()
91    }
92
93    fn unwrap(self) -> (T, T) {
94        self.inner.unwrap()
95    }
96
97    fn low(&self) -> Option<&T> {
98        match self.inner {
99            Some((ref low, _)) => Some(low),
100            None => None,
101        }
102    }
103
104    fn high(&self) -> Option<&T> {
105        match self.inner {
106            Some((_, ref high)) => Some(high),
107            None => None,
108        }
109    }
110
111    fn as_ref(&mut self) -> Option<&(T, T)> {
112        self.inner.as_ref()
113    }
114
115    fn as_mut(&mut self) -> Option<&mut (T, T)> {
116        self.inner.as_mut()
117    }
118
119    fn as_mut_low(&mut self) -> Option<&mut T> {
120        match self.inner {
121            Some((ref mut low, _)) => Some(low),
122            None => None,
123        }
124    }
125}
126
127impl<T> Interval<(T, T)>
128where
129    T: Copy + Hash + Eq,
130{
131    /// Returns ``true`` if the interval conflicts with ``edge``.
132    fn conflict<G>(&self, lr_state: &LRState<G>, edge: Edge<G>) -> bool
133    where
134        G: GraphBase<NodeId = T>,
135    {
136        match self.inner {
137            Some((_, ref h)) => lr_state.lowpt.get(h) > lr_state.lowpt.get(&edge),
138            _ => false,
139        }
140    }
141}
142
143#[derive(Clone, Copy, PartialEq, PartialOrd)]
144struct ConflictPair<T> {
145    left: Interval<T>,
146    right: Interval<T>,
147}
148
149impl<T> Default for ConflictPair<T> {
150    fn default() -> Self {
151        ConflictPair {
152            left: Interval::default(),
153            right: Interval::default(),
154        }
155    }
156}
157
158impl<T> ConflictPair<T> {
159    fn new(left: Interval<T>, right: Interval<T>) -> Self {
160        ConflictPair { left, right }
161    }
162
163    fn swap(&mut self) {
164        std::mem::swap(&mut self.left, &mut self.right)
165    }
166
167    fn is_empty(&self) -> bool {
168        self.left.is_empty() && self.right.is_empty()
169    }
170}
171
172impl<T> ConflictPair<(T, T)>
173where
174    T: Copy + Hash + Eq,
175{
176    /// Returns the lowest low point of a conflict pair.
177    fn lowest<G>(&self, lr_state: &LRState<G>) -> usize
178    where
179        G: GraphBase<NodeId = T>,
180    {
181        match (self.left.low(), self.right.low()) {
182            (Some(l_low), Some(r_low)) => lr_state.lowpt[l_low].min(lr_state.lowpt[r_low]),
183            (Some(l_low), None) => lr_state.lowpt[l_low],
184            (None, Some(r_low)) => lr_state.lowpt[r_low],
185            (None, None) => usize::MAX,
186        }
187    }
188}
189
190enum Sign {
191    Plus,
192    Minus,
193}
194
195/// Similar to ``DfsEvent`` plus an extra event ``FinishEdge``
196/// that indicates that we have finished processing an edge.
197enum LRTestDfsEvent<N> {
198    Finish(N),
199    TreeEdge(N, N),
200    BackEdge(N, N),
201    FinishEdge(N, N),
202}
203
204// An error: graph is *not* planar.
205struct NonPlanar {}
206
207struct LRState<G: GraphBase>
208where
209    G::NodeId: Hash + Eq,
210{
211    graph: G,
212    /// roots of the DFS forest.
213    roots: Vec<G::NodeId>,
214    /// distance from root.
215    height: HashMap<G::NodeId, usize>,
216    /// parent edge.
217    eparent: HashMap<G::NodeId, Edge<G>>,
218    /// height of lowest return point.
219    lowpt: HashMap<Edge<G>, usize>,
220    /// height of next-to-lowest return point. Only used to check if an edge is chordal.
221    lowpt_2: HashMap<Edge<G>, usize>,
222    /// next back edge in traversal with lowest return point.
223    lowpt_edge: HashMap<Edge<G>, Edge<G>>,
224    /// proxy for nesting order ≺ given by twice lowpt (plus 1 if chordal).
225    nesting_depth: HashMap<Edge<G>, usize>,
226    /// stack for conflict pairs.
227    stack: Vec<ConflictPair<Edge<G>>>,
228    /// marks the top conflict pair when an edge was pushed in the stack.
229    stack_emarker: HashMap<Edge<G>, ConflictPair<Edge<G>>>,
230    /// edge relative to which side is defined.
231    eref: HashMap<Edge<G>, Edge<G>>,
232    /// side of edge, or modifier for side of reference edge.
233    side: HashMap<Edge<G>, Sign>,
234}
235
236impl<G> LRState<G>
237where
238    G: GraphBase + NodeCount + EdgeCount + IntoEdges + Visitable,
239    G::NodeId: Hash + Eq,
240{
241    fn new(graph: G) -> Self {
242        let num_nodes = graph.node_count();
243        let num_edges = graph.edge_count();
244
245        LRState {
246            graph,
247            roots: Vec::new(),
248            height: HashMap::with_capacity(num_nodes),
249            eparent: HashMap::with_capacity(num_edges),
250            lowpt: HashMap::with_capacity(num_edges),
251            lowpt_2: HashMap::with_capacity(num_edges),
252            lowpt_edge: HashMap::with_capacity(num_edges),
253            nesting_depth: HashMap::with_capacity(num_edges),
254            stack: Vec::new(),
255            stack_emarker: HashMap::with_capacity(num_edges),
256            eref: HashMap::with_capacity(num_edges),
257            side: graph
258                .edge_references()
259                .map(|e| ((e.source(), e.target()), Sign::Plus))
260                .collect(),
261        }
262    }
263
264    fn lr_orientation_visitor(&mut self, event: DfsEvent<G::NodeId, &G::EdgeWeight>) {
265        match event {
266            DfsEvent::Discover(v, _) => {
267                if let Entry::Vacant(entry) = self.height.entry(v) {
268                    entry.insert(0);
269                    self.roots.push(v);
270                }
271            }
272            DfsEvent::TreeEdge(v, w, _) => {
273                let ei = (v, w);
274                let v_height = self.height[&v];
275                let w_height = v_height + 1;
276
277                self.eparent.insert(w, ei);
278                self.height.insert(w, w_height);
279                // now initialize low points.
280                self.lowpt.insert(ei, v_height);
281                self.lowpt_2.insert(ei, w_height);
282            }
283            DfsEvent::BackEdge(v, w, _) => {
284                // do *not* consider ``(v, w)`` as a back edge if ``(w, v)`` is a tree edge.
285                if Some(&(w, v)) != self.eparent.get(&v) {
286                    let ei = (v, w);
287                    self.lowpt.insert(ei, self.height[&w]);
288                    self.lowpt_2.insert(ei, self.height[&v]);
289                }
290            }
291            DfsEvent::Finish(v, _) => {
292                for edge in self.graph.edges(v) {
293                    let w = edge.target();
294                    let ei = (v, w);
295
296                    // determine nesting depth.
297                    let low = match self.lowpt.get(&ei) {
298                        Some(val) => *val,
299                        None =>
300                        // if ``lowpt`` does *not* contain edge ``(v, w)``, it means
301                        // that it's *not* a tree or a back edge so we skip it since
302                        // it's oriented in the reverse direction.
303                        {
304                            continue
305                        }
306                    };
307
308                    if self.lowpt_2[&ei] < self.height[&v] {
309                        // if it's chordal, add one.
310                        self.nesting_depth.insert(ei, 2 * low + 1);
311                    } else {
312                        self.nesting_depth.insert(ei, 2 * low);
313                    }
314
315                    // update lowpoints of parent edge.
316                    if let Some(e_par) = self.eparent.get(&v) {
317                        match self.lowpt[&ei].cmp(&self.lowpt[e_par]) {
318                            Ordering::Less => {
319                                self.lowpt_2
320                                    .insert(*e_par, self.lowpt[e_par].min(self.lowpt_2[&ei]));
321                                self.lowpt.insert(*e_par, self.lowpt[&ei]);
322                            }
323                            Ordering::Greater => {
324                                modify_if_min(&mut self.lowpt_2, *e_par, self.lowpt[&ei]);
325                            }
326                            _ => {
327                                let val = self.lowpt_2[&ei];
328                                modify_if_min(&mut self.lowpt_2, *e_par, val);
329                            }
330                        }
331                    }
332                }
333            }
334            _ => (),
335        }
336    }
337
338    fn lr_testing_visitor(&mut self, event: LRTestDfsEvent<G::NodeId>) -> Result<(), NonPlanar> {
339        match event {
340            LRTestDfsEvent::TreeEdge(v, w) => {
341                let ei = (v, w);
342                if let Some(&last) = self.stack.last() {
343                    self.stack_emarker.insert(ei, last);
344                }
345            }
346            LRTestDfsEvent::BackEdge(v, w) => {
347                let ei = (v, w);
348                if let Some(&last) = self.stack.last() {
349                    self.stack_emarker.insert(ei, last);
350                }
351                self.lowpt_edge.insert(ei, ei);
352                let c_pair = ConflictPair::new(Interval::default(), Interval::new(ei, ei));
353                self.stack.push(c_pair);
354            }
355            LRTestDfsEvent::FinishEdge(v, w) => {
356                let ei = (v, w);
357                if self.lowpt[&ei] < self.height[&v] {
358                    // ei has return edge
359                    let e_par = self.eparent[&v];
360                    let val = self.lowpt_edge[&ei];
361
362                    match self.lowpt_edge.entry(e_par) {
363                        Entry::Occupied(_) => {
364                            self.add_constraints(ei, e_par)?;
365                        }
366                        Entry::Vacant(o) => {
367                            o.insert(val);
368                        }
369                    }
370                }
371            }
372            LRTestDfsEvent::Finish(v) => {
373                if let Some(&e) = self.eparent.get(&v) {
374                    let u = e.0;
375                    self.remove_back_edges(u);
376
377                    // side of ``e = (u, v)` is side of a highest return edge
378                    if self.lowpt[&e] < self.height[&u] {
379                        if let Some(top) = self.stack.last() {
380                            let e_high = match (top.left.high(), top.right.high()) {
381                                (Some(hl), Some(hr)) => {
382                                    if self.lowpt[hl] > self.lowpt[hr] {
383                                        hl
384                                    } else {
385                                        hr
386                                    }
387                                }
388                                (Some(hl), None) => hl,
389                                (None, Some(hr)) => hr,
390                                _ => {
391                                    // Otherwise ``top`` would be empty, but we don't push
392                                    // empty conflict pairs in stack.
393                                    unreachable!()
394                                }
395                            };
396                            self.eref.insert(e, *e_high);
397                        }
398                    }
399                }
400            }
401        }
402
403        Ok(())
404    }
405
406    fn until_top_of_stack_hits_emarker(&mut self, ei: Edge<G>) -> Option<ConflictPair<Edge<G>>> {
407        if let Some(&c_pair) = self.stack.last() {
408            if self.stack_emarker[&ei] != c_pair {
409                return self.stack.pop();
410            }
411        }
412
413        None
414    }
415
416    fn until_top_of_stack_is_conflicting(&mut self, ei: Edge<G>) -> Option<ConflictPair<Edge<G>>> {
417        if let Some(c_pair) = self.stack.last() {
418            if c_pair.left.conflict(self, ei) || c_pair.right.conflict(self, ei) {
419                return self.stack.pop();
420            }
421        }
422
423        None
424    }
425
426    /// Unify intervals ``pi``, ``qi``.
427    ///
428    /// Interval ``qi`` must be non - empty and contain edges
429    /// with smaller lowpt than interval ``pi``.
430    fn union_intervals(&mut self, pi: &mut Interval<Edge<G>>, qi: Interval<Edge<G>>) {
431        match pi.as_mut_low() {
432            Some(p_low) => {
433                let (q_low, q_high) = qi.unwrap();
434                self.eref.insert(*p_low, q_high);
435                *p_low = q_low;
436            }
437            None => {
438                *pi = qi;
439            }
440        }
441    }
442
443    /// Adding constraints associated with edge ``ei``.
444    fn add_constraints(&mut self, ei: Edge<G>, e: Edge<G>) -> Result<(), NonPlanar> {
445        let mut c_pair = ConflictPair::<Edge<G>>::default();
446
447        // merge return edges of ei into ``c_pair.right``.
448        while let Some(mut q_pair) = self.until_top_of_stack_hits_emarker(ei) {
449            if !q_pair.left.is_empty() {
450                q_pair.swap();
451
452                if !q_pair.left.is_empty() {
453                    return Err(NonPlanar {});
454                }
455            }
456
457            // We call unwrap since ``q_pair`` was in stack and
458            // ``q_pair.right``, ``q_pair.left`` can't be both empty
459            // since we don't push empty conflict pairs in stack.
460            let qr_low = q_pair.right.low().unwrap();
461            if self.lowpt[qr_low] > self.lowpt[&e] {
462                // merge intervals
463                self.union_intervals(&mut c_pair.right, q_pair.right);
464            } else {
465                // make consinsent
466                self.eref.insert(*qr_low, self.lowpt_edge[&e]);
467            }
468        }
469
470        // merge conflicting return edges of e1, . . . , ei−1 into ``c_pair.left``.
471        while let Some(mut q_pair) = self.until_top_of_stack_is_conflicting(ei) {
472            if q_pair.right.conflict(self, ei) {
473                q_pair.swap();
474
475                if q_pair.right.conflict(self, ei) {
476                    return Err(NonPlanar {});
477                }
478            }
479
480            // merge interval below lowpt(ei) into ``c_pair.right``.
481            if let Some((qr_low, qr_high)) = q_pair.right.as_ref() {
482                if let Some(pr_low) = c_pair.right.as_mut_low() {
483                    self.eref.insert(*pr_low, *qr_high);
484                    *pr_low = *qr_low;
485                }
486            };
487            self.union_intervals(&mut c_pair.left, q_pair.left);
488        }
489
490        if !c_pair.is_empty() {
491            self.stack.push(c_pair);
492        }
493
494        Ok(())
495    }
496
497    fn until_lowest_top_of_stack_has_height(
498        &mut self,
499        v: G::NodeId,
500    ) -> Option<ConflictPair<Edge<G>>> {
501        if let Some(c_pair) = self.stack.last() {
502            if c_pair.lowest(self) == self.height[&v] {
503                return self.stack.pop();
504            }
505        }
506
507        None
508    }
509
510    fn follow_eref_until_is_target(&self, edge: Edge<G>, v: G::NodeId) -> Option<Edge<G>> {
511        let mut res = Some(&edge);
512        while let Some(b) = is_target::<G>(res, v) {
513            res = self.eref.get(b);
514        }
515
516        res.copied()
517    }
518
519    /// Trim back edges ending at parent v.
520    fn remove_back_edges(&mut self, v: G::NodeId) {
521        // drop entire conflict pairs.
522        while let Some(c_pair) = self.until_lowest_top_of_stack_has_height(v) {
523            if let Some(pl_low) = c_pair.left.low() {
524                self.side.insert(*pl_low, Sign::Minus);
525            }
526        }
527
528        // one more conflict pair to consider.
529        if let Some(mut c_pair) = self.stack.pop() {
530            // trim left interval.
531            if let Some((pl_low, pl_high)) = c_pair.left.as_mut() {
532                match self.follow_eref_until_is_target(*pl_high, v) {
533                    Some(val) => {
534                        *pl_high = val;
535                    }
536                    None => {
537                        // just emptied.
538                        // We call unwrap since right interval cannot be empty for otherwise
539                        // the entire conflict pair had been removed.
540                        let pr_low = c_pair.right.low().unwrap();
541                        self.eref.insert(*pl_low, *pr_low);
542                        self.side.insert(*pl_low, Sign::Minus);
543                        c_pair.left = Interval::default();
544                    }
545                }
546            }
547
548            // trim right interval
549            if let Some((pr_low, ref mut pr_high)) = c_pair.right.as_mut() {
550                match self.follow_eref_until_is_target(*pr_high, v) {
551                    Some(val) => {
552                        *pr_high = val;
553                    }
554                    None => {
555                        // just emptied.
556                        // We call unwrap since left interval cannot be empty for otherwise
557                        // the entire conflict pair had been removed.
558                        let pl_low = c_pair.left.low().unwrap();
559                        self.eref.insert(*pr_low, *pl_low);
560                        self.side.insert(*pr_low, Sign::Minus);
561                        c_pair.right = Interval::default();
562                    }
563                };
564            }
565
566            if !c_pair.is_empty() {
567                self.stack.push(c_pair);
568            }
569        }
570    }
571}
572
573/// Visits the DFS - oriented tree that we have pre-computed
574/// and stored in ``lr_state``. We traverse the edges of
575/// a node in nesting depth order. Events are emitted at points
576/// of interest and should be handled by ``visitor``.
577fn lr_visit_ordered_dfs_tree<G, F, E>(
578    lr_state: &mut LRState<G>,
579    v: G::NodeId,
580    mut visitor: F,
581) -> Result<(), E>
582where
583    G: GraphBase + IntoEdges,
584    G::NodeId: Hash + Eq,
585    F: FnMut(&mut LRState<G>, LRTestDfsEvent<G::NodeId>) -> Result<(), E>,
586{
587    let mut stack: Vec<(G::NodeId, IntoIter<Edge<G>>)> = vec![(
588        v,
589        edges_filtered_and_sorted_by(
590            lr_state.graph,
591            v,
592            // if ``lowpt`` does *not* contain edge ``e = (v, w)``, it means
593            // that it's *not* a tree or a back edge so we skip it since
594            // it's oriented in the reverse direction.
595            |e| lr_state.lowpt.contains_key(e),
596            // we sort edges based on nesting depth order.
597            |e| lr_state.nesting_depth[e],
598        ),
599    )];
600
601    while let Some(elem) = stack.last_mut() {
602        let v = elem.0;
603        let adjacent_edges = &mut elem.1;
604        let mut next = None;
605
606        for (v, w) in adjacent_edges {
607            if Some(&(v, w)) == lr_state.eparent.get(&w) {
608                // tree edge
609                visitor(lr_state, LRTestDfsEvent::TreeEdge(v, w))?;
610                next = Some(w);
611                break;
612            } else {
613                // back edge
614                visitor(lr_state, LRTestDfsEvent::BackEdge(v, w))?;
615                visitor(lr_state, LRTestDfsEvent::FinishEdge(v, w))?;
616            }
617        }
618
619        match next {
620            Some(w) => stack.push((
621                w,
622                edges_filtered_and_sorted_by(
623                    lr_state.graph,
624                    w,
625                    |e| lr_state.lowpt.contains_key(e),
626                    |e| lr_state.nesting_depth[e],
627                ),
628            )),
629            None => {
630                stack.pop();
631                visitor(lr_state, LRTestDfsEvent::Finish(v))?;
632                if let Some(&(u, v)) = lr_state.eparent.get(&v) {
633                    visitor(lr_state, LRTestDfsEvent::FinishEdge(u, v))?;
634                }
635            }
636        }
637    }
638
639    Ok(())
640}
641
642/// Check if an undirected graph is planar.
643///
644/// A graph is planar iff it can be drawn in a plane without any edge
645/// intersections.
646///
647/// The planarity check algorithm  is based on the
648/// Left-Right Planarity Test:
649///
650/// [`Ulrik Brandes:  The Left-Right Planarity Test (2009)`](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.217.9208)
651///
652/// # Example:
653/// ```rust
654/// use rustworkx_core::petgraph::graph::UnGraph;
655/// use rustworkx_core::planar::is_planar;
656///
657/// let grid = UnGraph::<(), ()>::from_edges(&[
658///    // row edges
659///    (0, 1), (1, 2), (3, 4), (4, 5), (6, 7), (7, 8),
660///    // col edges
661///    (0, 3), (3, 6), (1, 4), (4, 7), (2, 5), (5, 8),
662/// ]);
663/// assert!(is_planar(&grid))
664/// ```
665pub fn is_planar<G>(graph: G) -> bool
666where
667    G: GraphProp<EdgeType = Undirected>
668        + NodeCount
669        + EdgeCount
670        + IntoEdges
671        + IntoNodeIdentifiers
672        + Visitable,
673    G::NodeId: Hash + Eq,
674{
675    let mut state = LRState::new(graph);
676
677    // Dfs orientation phase
678    depth_first_search(graph, graph.node_identifiers(), |event| {
679        state.lr_orientation_visitor(event)
680    });
681
682    // Left - Right partition.
683    for v in state.roots.clone() {
684        let res = lr_visit_ordered_dfs_tree(&mut state, v, |state, event| {
685            state.lr_testing_visitor(event)
686        });
687        if res.is_err() {
688            return false;
689        }
690    }
691
692    true
693}