Skip to content

Commit 9f61318

Browse files
authored
fix: Link and neighbour iterators counting self-loops twice (#132)
The `all_links` iterator for both `Portgraph` and `Multiportgraph`, and `all_neighbours` for the latter yielded self-loops twice. This in turn caused `insert_graph` to try inserting repeated links, as reported in #130. This PR fixes the error and adds tests for that. Fixes #130. drive-by: Add missing checks in the pre-commit hook.
1 parent 64c0ae5 commit 9f61318

File tree

6 files changed

+261
-52
lines changed

6 files changed

+261
-52
lines changed

.github/pre-commit

+16-3
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,35 @@ if [[ "${IGNORE_RUSTHOOKS:=0}" -ne 0 ]]; then
99
exit 0
1010
fi
1111

12-
if ! cargo fmt -- --check
12+
if ! cargo fmt --all -- --check
1313
then
1414
echo "There are some code style issues."
1515
echo "Run cargo fmt first."
1616
exit 1
1717
fi
1818

19+
if ! cargo check --all --all-features --workspace
20+
then
21+
echo "There are some compilation warnings."
22+
exit 1
23+
fi
24+
25+
if ! cargo test --all-features --workspace
26+
then
27+
echo "There are some test issues."
28+
exit 1
29+
fi
30+
1931
if ! cargo clippy --all-targets --all-features --workspace -- -D warnings
2032
then
2133
echo "There are some clippy issues."
2234
exit 1
2335
fi
2436

25-
if ! cargo test --all-features
37+
RUSTDOCFLAGS="-Dwarnings"
38+
if ! cargo doc --no-deps --all-features --workspace
2639
then
27-
echo "There are some test issues."
40+
echo "There are some clippy issues."
2841
exit 1
2942
fi
3043

justfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ test:
88

99
# Auto-fix all clippy warnings
1010
fix:
11-
cargo clippy --all-targets --all-features --workspace --fix --allow-staged
11+
cargo clippy --all-targets --all-features --workspace --fix --allow-staged --allow-dirty
1212

1313
# Run the pre-commit checks
1414
check:

src/multiportgraph.rs

+101-4
Original file line numberDiff line numberDiff line change
@@ -344,22 +344,23 @@ impl LinkView for MultiPortGraph {
344344

345345
#[inline]
346346
fn links(&self, node: NodeIndex, direction: Direction) -> Self::NodeLinks<'_> {
347-
NodeLinks::new(self, self.ports(node, direction))
347+
NodeLinks::new(self, self.ports(node, direction), 0..0)
348348
}
349349

350350
#[inline]
351351
fn all_links(&self, node: NodeIndex) -> Self::NodeLinks<'_> {
352-
NodeLinks::new(self, self.all_ports(node))
352+
let output_ports = self.graph.node_outgoing_ports(node);
353+
NodeLinks::new(self, self.all_ports(node), output_ports)
353354
}
354355

355356
#[inline]
356357
fn neighbours(&self, node: NodeIndex, direction: Direction) -> Self::Neighbours<'_> {
357-
Neighbours::new(self, self.subports(node, direction))
358+
Neighbours::new(self, self.subports(node, direction), node, false)
358359
}
359360

360361
#[inline]
361362
fn all_neighbours(&self, node: NodeIndex) -> Self::Neighbours<'_> {
362-
Neighbours::new(self, self.all_subports(node))
363+
Neighbours::new(self, self.all_subports(node), node, true)
363364
}
364365

365366
#[inline]
@@ -811,6 +812,7 @@ pub mod test {
811812
let mut g = MultiPortGraph::new();
812813
let node0 = g.add_node(1, 2);
813814
let node1 = g.add_node(2, 1);
815+
let node0_input0 = g.input(node0, 0).unwrap();
814816
let (node0_output0, node0_output1) = g.outputs(node0).collect_tuple().unwrap();
815817
let (node1_input0, node1_input1) = g.inputs(node1).collect_tuple().unwrap();
816818

@@ -866,5 +868,100 @@ pub mod test {
866868
);
867869
assert_eq!(g.all_neighbours(node0).collect_vec(), [node1, node1, node1]);
868870
assert_eq!(g.port_links(node0_output0).collect_vec(), links[0..2]);
871+
872+
// Self-link
873+
// The `all_links` / `all_neighbours` iterators should only return these once.
874+
g.link_nodes(node0, 0, node0, 0).unwrap();
875+
assert_eq!(
876+
g.subport_outputs(node0).collect_vec(),
877+
[
878+
SubportIndex::new_multi(node0_output0, 0),
879+
SubportIndex::new_multi(node0_output0, 1),
880+
SubportIndex::new_multi(node0_output0, 2),
881+
SubportIndex::new_unique(node0_output1),
882+
]
883+
);
884+
assert_eq!(
885+
g.subport_inputs(node0).collect_vec(),
886+
[SubportIndex::new_unique(node0_input0)]
887+
);
888+
889+
let links = [
890+
(
891+
SubportIndex::new_multi(node0_output0, 0),
892+
SubportIndex::new_unique(node1_input0),
893+
),
894+
(
895+
SubportIndex::new_multi(node0_output0, 1),
896+
SubportIndex::new_multi(node1_input1, 0),
897+
),
898+
(
899+
SubportIndex::new_multi(node0_output0, 2),
900+
SubportIndex::new_unique(node0_input0),
901+
),
902+
(
903+
SubportIndex::new_unique(node0_output1),
904+
SubportIndex::new_multi(node1_input1, 1),
905+
),
906+
];
907+
assert_eq!(
908+
g.input_links(node0).collect_vec(),
909+
[(
910+
SubportIndex::new_unique(node0_input0),
911+
SubportIndex::new_multi(node0_output0, 2),
912+
)]
913+
);
914+
assert_eq!(g.output_links(node0).collect_vec(), links);
915+
assert_eq!(g.all_links(node0).collect_vec(), links);
916+
assert_eq!(g.input_neighbours(node0).collect_vec(), [node0]);
917+
assert_eq!(
918+
g.output_neighbours(node0).collect_vec(),
919+
[node1, node1, node0, node1]
920+
);
921+
assert_eq!(
922+
g.all_neighbours(node0).collect_vec(),
923+
[node1, node1, node0, node1]
924+
);
925+
assert_eq!(g.port_links(node0_output0).collect_vec(), links[0..3]);
926+
}
927+
928+
#[test]
929+
fn insert_graph() -> Result<(), Box<dyn std::error::Error>> {
930+
let mut g = crate::MultiPortGraph::new();
931+
// Add dummy nodes to produce different node ids than in the other graph.
932+
g.add_node(0, 0);
933+
g.add_node(0, 0);
934+
let node0g = g.add_node(1, 1);
935+
let node1g = g.add_node(1, 1);
936+
g.link_nodes(node0g, 0, node1g, 0)?;
937+
938+
let mut h = PortGraph::new();
939+
let node0h = h.add_node(2, 2);
940+
let node1h = h.add_node(1, 1);
941+
h.link_nodes(node0h, 0, node1h, 0)?;
942+
h.link_nodes(node0h, 1, node0h, 0)?;
943+
h.link_nodes(node1h, 0, node0h, 1)?;
944+
945+
let map = g.insert_graph(&h)?;
946+
assert_eq!(map.len(), 2);
947+
948+
assert_eq!(g.node_count(), 6);
949+
assert_eq!(g.link_count(), 4);
950+
assert!(g.contains_node(map[&node0h]));
951+
assert!(g.contains_node(map[&node1h]));
952+
assert_eq!(
953+
g.input_neighbours(map[&node0h]).collect_vec(),
954+
vec![map[&node0h], map[&node1h]]
955+
);
956+
assert_eq!(
957+
g.output_neighbours(map[&node0h]).collect_vec(),
958+
vec![map[&node1h], map[&node0h]]
959+
);
960+
assert_eq!(
961+
g.all_neighbours(map[&node0h]).collect_vec(),
962+
vec![map[&node1h], map[&node1h], map[&node0h]]
963+
);
964+
965+
Ok(())
869966
}
870967
}

src/multiportgraph/iter.rs

+70-27
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::ops::Range;
66

77
use super::{MultiPortGraph, SubportIndex};
88
use crate::portgraph::{self, NodePorts};
9-
use crate::{LinkView, NodeIndex, PortIndex, PortOffset, PortView};
9+
use crate::{Direction, LinkView, NodeIndex, PortIndex, PortOffset, PortView};
1010

1111
/// Iterator over the nodes of a graph.
1212
#[derive(Clone)]
@@ -127,14 +127,27 @@ pub struct Neighbours<'a> {
127127
multigraph: &'a MultiPortGraph,
128128
subports: NodeSubports<'a>,
129129
current_copy_node: Option<NodeIndex>,
130+
/// The node for which the neighbours are being iterated.
131+
node: NodeIndex,
132+
/// Whether to ignore self-loops in the input -> output direction.
133+
/// This is used to avoid counting self-loops twice when iterating both
134+
/// input and output neighbours.
135+
ignore_dupped_self_loops: bool,
130136
}
131137

132138
impl<'a> Neighbours<'a> {
133-
pub(super) fn new(multigraph: &'a MultiPortGraph, subports: NodeSubports<'a>) -> Self {
139+
pub(super) fn new(
140+
multigraph: &'a MultiPortGraph,
141+
subports: NodeSubports<'a>,
142+
node: NodeIndex,
143+
ignore_dupped_self_loops: bool,
144+
) -> Self {
134145
Self {
135146
multigraph,
136147
subports,
137148
current_copy_node: None,
149+
node,
150+
ignore_dupped_self_loops,
138151
}
139152
}
140153
}
@@ -143,26 +156,42 @@ impl<'a> Iterator for Neighbours<'a> {
143156
type Item = NodeIndex;
144157

145158
fn next(&mut self) -> Option<Self::Item> {
146-
let link = self.subports.find_map(|subport| {
147-
let port_index = subport.port();
148-
if !self.multigraph.is_multiport(port_index) {
149-
self.multigraph.graph.port_link(port_index)
150-
} else {
151-
// There is a copy node
152-
if subport.offset() == 0 {
153-
self.current_copy_node = self.multigraph.get_copy_node(port_index);
159+
loop {
160+
let link = self.subports.find_map(|subport| {
161+
let port_index = subport.port();
162+
if !self.multigraph.is_multiport(port_index) {
163+
self.multigraph.graph.port_link(port_index)
164+
} else {
165+
// There is a copy node
166+
if subport.offset() == 0 {
167+
self.current_copy_node = self.multigraph.get_copy_node(port_index);
168+
}
169+
let copy_node = self
170+
.current_copy_node
171+
.expect("Copy node not connected to a multiport.");
172+
let dir = self.multigraph.graph.port_direction(port_index).unwrap();
173+
let offset = PortOffset::new(dir, subport.offset());
174+
let subport_index =
175+
self.multigraph.graph.port_index(copy_node, offset).unwrap();
176+
self.multigraph.graph.port_link(subport_index)
154177
}
155-
let copy_node = self
156-
.current_copy_node
157-
.expect("Copy node not connected to a multiport.");
158-
let dir = self.multigraph.graph.port_direction(port_index).unwrap();
159-
let offset = PortOffset::new(dir, subport.offset());
160-
let subport_index = self.multigraph.graph.port_index(copy_node, offset).unwrap();
161-
self.multigraph.graph.port_link(subport_index)
178+
})?;
179+
let link_subport = self.multigraph.get_subport_from_index(link).unwrap();
180+
let node = self
181+
.multigraph
182+
.graph
183+
.port_node(link_subport.port())
184+
.unwrap();
185+
// Ignore self-loops in the input -> output direction.
186+
if self.ignore_dupped_self_loops
187+
&& node == self.node
188+
&& self.multigraph.port_direction(link_subport.port()).unwrap()
189+
== Direction::Outgoing
190+
{
191+
continue;
162192
}
163-
})?;
164-
let link_subport = self.multigraph.get_subport_from_index(link).unwrap();
165-
self.multigraph.graph.port_node(link_subport.port())
193+
return Some(node);
194+
}
166195
}
167196
}
168197

@@ -178,14 +207,22 @@ pub struct NodeLinks<'a> {
178207
multigraph: &'a MultiPortGraph,
179208
ports: NodePorts,
180209
current_links: Option<PortLinks<'a>>,
210+
/// Ignore links to the given target ports.
211+
/// This is used to avoid counting self-loops twice.
212+
ignore_target_ports: Range<usize>,
181213
}
182214

183215
impl<'a> NodeLinks<'a> {
184-
pub(super) fn new(multigraph: &'a MultiPortGraph, ports: NodePorts) -> Self {
216+
pub(super) fn new(
217+
multigraph: &'a MultiPortGraph,
218+
ports: NodePorts,
219+
ignore_target_ports: Range<usize>,
220+
) -> Self {
185221
Self {
186222
multigraph,
187223
ports,
188224
current_links: None,
225+
ignore_target_ports,
189226
}
190227
}
191228
}
@@ -196,14 +233,20 @@ impl<'a> Iterator for NodeLinks<'a> {
196233

197234
fn next(&mut self) -> Option<Self::Item> {
198235
loop {
199-
if let Some(links) = &mut self.current_links {
200-
if let Some(link) = links.next() {
201-
return Some(link);
202-
}
236+
let Some(links) = &mut self.current_links else {
237+
let port = self.ports.next()?;
238+
self.current_links = Some(PortLinks::new(self.multigraph, port));
239+
continue;
240+
};
241+
let Some((from, to)) = links.next() else {
203242
self.current_links = None;
243+
continue;
244+
};
245+
// Ignore self-loops in the input -> output direction.
246+
if self.ignore_target_ports.contains(&to.port().index()) {
247+
continue;
204248
}
205-
let port = self.ports.next()?;
206-
self.current_links = Some(PortLinks::new(self.multigraph, port));
249+
return Some((from, to));
207250
}
208251
}
209252
}

0 commit comments

Comments
 (0)