1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
//! Views into non-hierarchical parts of `PortView`s.

use std::collections::BTreeSet;

use crate::{
    algorithms::{ConvexChecker, TopoConvexChecker},
    Direction, LinkView, NodeIndex, PortIndex, PortView,
};

use super::filter::FilteredGraph;

type NodeCallback = fn(NodeIndex, &SubgraphContext) -> bool;
type PortCallback = fn(PortIndex, &SubgraphContext) -> bool;

/// View into a subgraph of a PortView.
///
/// The subgraph is given by boundary edges that define where one "enters" the
/// subgraph (incoming boundary edge) and one "leaves" it (outgoing boundary
/// edges).
///
/// A subgraph is well-defined if the incoming/outgoing boundary edges partition
/// the edges between the children of the root node into three sets:
///  - boundary edges: either incoming boundary or outgoing boundary edges,
///  - interior edges: edges such that all the successor edges are either
///    outgoing boundary edges or interior edges AND all the predecessor edges
///    are either incoming boundary edges or interior edges,
///  - exterior edges: edges such that all the successor edges are either
///    incoming boundary edges or exterior edges AND all the predecessor edges
///    are either outgoing boundary edges or exterior edges.
///
/// Then the subgraph is made of the interior edges and contains all nodes that
/// are
///  - adjacent to an interior edge, or
///  - are the target of an incoming boundary edge AND the source of an outgoing boundary edge.
///
/// An intuitive way of looking at this definition is to imagine that the
/// boundary edges form a wall around the subgraph, and the subgraph is given
/// by all nodes and edges that can be reached from within without crossing the
/// wall. The directedness of edges (incoming/outgoing) defines which side of
/// the wall is inside, and which is outside.
///
/// If both incoming and outgoing boundary edges are empty, the subgraph is
/// taken to be the entire graph.
///
/// If an invalid subgraph is defined, then behaviour is undefined.
///
/// At initialisation, this performs a one-off expensive computation (linear in
/// the size of the subgraph) to determine the nodes that are in the subgraph.
pub type Subgraph<G> = FilteredGraph<G, NodeCallback, PortCallback, SubgraphContext>;

/// Internal context used in the [`Subgraph`] adaptor.
#[derive(Debug, Clone)]
pub struct SubgraphContext {
    nodes: BTreeSet<NodeIndex>,
    ports: BTreeSet<PortIndex>,
    inputs: Vec<PortIndex>,
    outputs: Vec<PortIndex>,
}

impl<G: LinkView> Subgraph<G>
where
    G: Clone,
{
    /// Create a new subgraph view of `graph`.
    ///
    /// ### Arguments
    ///
    /// - `graph`: the graph to take a subgraph from,
    /// - `boundary`: the boundary ports. Incoming ports are incoming boundary edges,
    /// and outgoing ports are outgoing boundary edges.
    ///
    /// This initialisation is linear in the size of the subgraph.
    pub fn new_subgraph(graph: G, boundary: impl IntoIterator<Item = PortIndex>) -> Self {
        let mut inputs = Vec::new();
        let mut outputs = Vec::new();

        let boundary = boundary.into_iter().map(|p| {
            match graph.port_direction(p).unwrap() {
                Direction::Incoming => inputs.push(p),
                Direction::Outgoing => outputs.push(p),
            };
            p
        });

        let (nodes, ports) = traverse_subgraph(graph.clone(), boundary);
        let context = SubgraphContext {
            nodes: nodes.into_iter().collect(),
            ports: ports.into_iter().collect(),
            inputs,
            outputs,
        };
        Self::new(
            graph,
            |n, ctx| ctx.nodes.contains(&n),
            |p, ctx| ctx.ports.contains(&p),
            context,
        )
    }

    /// Whether the subgraph is convex.
    pub fn is_convex(&self) -> bool {
        let checker = TopoConvexChecker::new(self.graph());
        self.is_convex_with_checker(&checker)
    }

    /// Whether the subgraph is convex, using a pre-existing checker.
    pub fn is_convex_with_checker(&self, checker: &impl ConvexChecker) -> bool {
        checker.is_convex(
            self.nodes_iter(),
            self.context().inputs.iter().copied(),
            self.context().outputs.iter().copied(),
        )
    }
}

/// Traverse the subgraph defined by the boundary edges.
///
/// Start just inside the boundaries and follow each edge that is not itself
/// a boundary.
fn traverse_subgraph<G: LinkView>(
    graph: G,
    boundary: impl IntoIterator<Item = PortIndex>,
) -> (BTreeSet<NodeIndex>, BTreeSet<PortIndex>) {
    // Nodes within subgraph
    let mut nodes = BTreeSet::new();
    // Ports within subgraph
    let mut ports = BTreeSet::new();
    // For every visited edge, we mark both ports as visited
    let mut visited = BTreeSet::new();

    // The set of nodes to traverse
    let mut nodes_to_process: BTreeSet<_> = boundary
        .into_iter()
        .map(|p| {
            let this_node = graph.port_node(p).unwrap();
            if let Some(other_port) = graph.port_link(p) {
                visited.insert(other_port.into());
            }
            visited.insert(p);
            this_node
        })
        .collect();

    if nodes_to_process.is_empty() {
        // Edge case: no boundary edges, so the subgraph is the entire graph
        nodes_to_process = graph.nodes_iter().collect();
    }

    while let Some(node) = nodes_to_process.pop_first() {
        nodes.insert(node);
        // Traverse every unvisited edge in `node`
        for p in graph.all_ports(node) {
            if visited.insert(p) {
                // Visit it
                ports.insert(p);
                if let Some(other_port) = graph.port_link(p) {
                    visited.insert(other_port.into());
                    ports.insert(other_port.into());
                    // Possibly a new node!
                    let other_node = graph.port_node(other_port).unwrap();
                    nodes_to_process.insert(other_node);
                }
            }
        }
    }

    (nodes, ports)
}

#[cfg(test)]
mod tests {
    use itertools::Itertools;

    use crate::{LinkMut, PortGraph, PortMut, PortView};

    use super::*;

    /// Create the following graph
    ///
    ///  ┌─────┐┌─┐
    ///  │0    ││3│
    ///  └┬───┬┘└┬┘
    ///   │   │  │
    ///  ┌▽─┐┌▽─┐│
    ///  │1 ││2 ││
    ///  └┬┬┘└┬┬┘│
    ///   ││ ┌┘│ │
    ///   │└─│┐│┌┘
    ///   │┌─┘│││
    ///  ┌▽▽┐┌▽▽▽┐
    ///  │5 ││4  │
    ///  └──┘└───┘
    fn graph() -> PortGraph {
        let mut graph = PortGraph::new();
        let n0 = graph.add_node(0, 2);
        let n1 = graph.add_node(1, 2);
        let n2 = graph.add_node(1, 2);
        let n3 = graph.add_node(0, 1);
        let n4 = graph.add_node(3, 0);
        let n5 = graph.add_node(2, 0);
        graph.link_nodes(n0, 0, n1, 0).unwrap();
        graph.link_nodes(n0, 1, n2, 0).unwrap();
        graph.link_nodes(n3, 0, n4, 0).unwrap();
        graph.link_nodes(n1, 0, n4, 1).unwrap();
        graph.link_nodes(n2, 1, n4, 2).unwrap();
        graph.link_nodes(n1, 1, n5, 0).unwrap();
        graph.link_nodes(n2, 0, n5, 1).unwrap();
        graph
    }

    #[test]
    fn test_traverse_subgraph_single_node() {
        let graph = graph();
        let (_, n1, _, _, _, _) = (0..6).map(NodeIndex::new).collect_tuple().unwrap();

        // Define the incoming and outgoing boundary edges
        let boundary = graph.all_ports(n1);

        // Traverse the subgraph
        let (nodes, ports) = traverse_subgraph(&graph, boundary);

        // Check that the correct nodes and ports were found
        assert_eq!(nodes, [n1].into_iter().collect());
        assert!(ports.is_empty());
    }

    #[test]
    fn test_traverse_subgraph_all_but_one_edge() {
        let graph = graph();
        let (_, n1, _, _, n4, _) = (0..6).map(NodeIndex::new).collect_tuple().unwrap();

        // Define the incoming and outgoing boundary edges
        let boundary = [graph.output(n1, 0).unwrap(), graph.input(n4, 1).unwrap()];

        // Traverse the subgraph
        let (nodes, ports) = traverse_subgraph(&graph, boundary);

        // Check that the correct nodes and ports were found
        assert_eq!(nodes, graph.nodes_iter().collect());
        assert_eq!(ports.len(), graph.port_count() - 2);
    }

    #[test]
    fn test_traverse_subgraph_basic() {
        let graph = graph();
        let (_, n1, n2, _, _, n5) = (0..6).map(NodeIndex::new).collect_tuple().unwrap();

        // Define the incoming and outgoing boundary edges
        let incoming = [graph.inputs(n1), graph.inputs(n2)].into_iter().flatten();
        let outgoing = [graph.output(n1, 0).unwrap(), graph.output(n2, 1).unwrap()];

        // Traverse the subgraph
        let (nodes, ports) = traverse_subgraph(&graph, incoming.chain(outgoing));

        // Check that the correct nodes and ports were found
        assert_eq!(nodes, [n1, n2, n5].into_iter().collect());
        assert_eq!(ports.len(), 4)
    }

    #[test]
    fn test_traverse_subgraph_almost_complete() {
        let graph = graph();
        let (n0, n1, n2, n3, n4, n5) = (0..6).map(NodeIndex::new).collect_tuple().unwrap();

        // Define the incoming and outgoing boundary edges
        let incoming = [
            graph.input(n1, 0).unwrap(),
            graph.input(n4, 2).unwrap(),
            graph.input(n5, 1).unwrap(),
        ];
        let outgoing = [
            graph.output(n0, 0).unwrap(),
            graph.output(n2, 1).unwrap(),
            graph.output(n2, 0).unwrap(),
        ];
        let boundary = incoming.into_iter().chain(outgoing);

        // Traverse the subgraph
        let (nodes, ports) = traverse_subgraph(&graph, boundary);

        // Check that the correct nodes and ports were found
        assert_eq!(nodes, [n0, n1, n2, n3, n4, n5].into_iter().collect());
        assert_eq!(
            ports,
            [
                vec![graph.output(n0, 1).unwrap()],
                graph.outputs(n1).collect(),
                graph.inputs(n2).collect(),
                graph.all_ports(n3).collect(),
                vec![graph.input(n4, 0).unwrap(), graph.input(n4, 1).unwrap()],
                vec![graph.input(n5, 0).unwrap()],
            ]
            .into_iter()
            .flatten()
            .collect()
        );
    }

    #[test]
    fn test_traverse_subgraph_complete() {
        let graph = graph();

        // Traverse the subgraph
        let (nodes, ports) = traverse_subgraph(&graph, []);

        assert_eq!(nodes, graph.nodes_iter().collect());
        assert_eq!(ports, graph.ports_iter().collect());
    }

    #[test]
    fn test_is_convex() {
        let graph = graph();
        let (n0, n1, n2, _, n4, n5) = (0..6).map(NodeIndex::new).collect_tuple().unwrap();

        let boundary = graph.all_ports(n1);
        let subg = Subgraph::new_subgraph(&graph, boundary);
        assert!(subg.is_convex());

        let boundary = [graph.output(n1, 0).unwrap(), graph.input(n4, 1).unwrap()];
        let subg = Subgraph::new_subgraph(&graph, boundary);
        assert!(!subg.is_convex());

        // Define the incoming and outgoing boundary edges
        let incoming = [
            graph.input(n1, 0).unwrap(),
            graph.input(n4, 2).unwrap(),
            graph.input(n5, 1).unwrap(),
        ];
        let outgoing = [
            graph.output(n0, 0).unwrap(),
            graph.output(n2, 1).unwrap(),
            graph.output(n2, 0).unwrap(),
        ];
        let boundary = incoming.into_iter().chain(outgoing);
        let subg = Subgraph::new_subgraph(&graph, boundary);
        assert!(!subg.is_convex());
    }

    #[test]
    fn test_is_convex_line() {
        let mut graph = PortGraph::new();
        let n0 = graph.add_node(0, 1);
        let n1 = graph.add_node(1, 1);
        let n2 = graph.add_node(1, 0);
        graph.link_nodes(n0, 0, n1, 0).unwrap();
        graph.link_nodes(n1, 0, n2, 0).unwrap();

        let boundary = [graph.output(n0, 0).unwrap(), graph.input(n2, 0).unwrap()];
        let subg = Subgraph::new_subgraph(&graph, boundary);
        assert_eq!(subg.nodes_iter().collect_vec(), [n0, n2]);
        assert!(!subg.is_convex());
    }
}