• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / rustworkx / 18563991601

16 Oct 2025 02:04PM UTC coverage: 94.108% (-0.05%) from 94.156%
18563991601

Pull #1517

github

web-flow
Merge 7d1e5168e into 8d2742d6b
Pull Request #1517: Bugfix/json preserves node gaps

57 of 71 new or added lines in 4 files covered. (80.28%)

2 existing lines in 2 files now uncovered.

18177 of 19315 relevant lines covered (94.11%)

965694.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.74
/src/json/node_link_data.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

13
use std::collections::BTreeMap;
14
use std::fs::File;
15

16
use hashbrown::HashMap;
17

18
use serde::{Deserialize, Serialize};
19

20
use pyo3::prelude::*;
21
use pyo3::IntoPyObjectExt;
22
use pyo3::Python;
23

24
use petgraph::visit::EdgeRef;
25
use petgraph::visit::IntoEdgeReferences;
26
use petgraph::EdgeType;
27

28
use crate::JSONSerializationError;
29
use crate::NodeIndex;
30
use crate::StablePyGraph;
31

32
#[derive(Serialize)]
33
pub struct Graph {
34
    pub directed: bool,
35
    pub multigraph: bool,
36
    pub attrs: Option<BTreeMap<String, String>>,
37
    pub nodes: Vec<Node>,
38
    pub links: Vec<Link>,
39
}
40

41
#[derive(Deserialize)]
42
pub struct GraphInput {
43
    pub directed: bool,
44
    pub multigraph: bool,
45
    pub attrs: Option<BTreeMap<String, String>>,
46
    pub nodes: Vec<NodeInput>,
47
    pub links: Vec<LinkInput>,
48
}
49

50
#[derive(Serialize)]
51
pub struct Node {
52
    id: usize,
53
    data: Option<BTreeMap<String, String>>,
54
}
55

56
#[derive(Deserialize)]
57
pub struct NodeInput {
58
    id: Option<usize>,
59
    data: Option<BTreeMap<String, String>>,
60
}
61

62
#[derive(Deserialize)]
63
pub struct LinkInput {
64
    source: usize,
65
    target: usize,
66
    #[allow(dead_code)]
67
    id: Option<usize>,
68
    data: Option<BTreeMap<String, String>>,
69
}
70

71
#[derive(Serialize)]
72
pub struct Link {
73
    source: usize,
74
    target: usize,
75
    id: usize,
76
    data: Option<BTreeMap<String, String>>,
77
}
78

79
#[allow(clippy::too_many_arguments)]
80
pub fn parse_node_link_data<Ty: EdgeType>(
26✔
81
    py: &Python,
26✔
82
    graph: GraphInput,
26✔
83
    out_graph: &mut StablePyGraph<Ty>,
26✔
84
    node_attrs: Option<PyObject>,
26✔
85
    edge_attrs: Option<PyObject>,
26✔
86
) -> PyResult<bool> {
26✔
87
    let mut id_mapping: HashMap<usize, NodeIndex> = HashMap::with_capacity(graph.nodes.len());
26✔
88

89
    // Check if nodes have explicit IDs that need preservation
90
    let preserve_ids = graph.nodes.iter().any(|n| n.id.is_some());
26✔
91

92
    let node_removed = if preserve_ids {
26✔
93
        // Find the maximum node ID to determine how many placeholder nodes we need
94
        let max_id = graph.nodes.iter().filter_map(|n| n.id).max().unwrap_or(0);
26✔
95

96
        // Create placeholder nodes up to max_id
97
        let mut tmp_nodes: Vec<NodeIndex> = Vec::new();
26✔
98
        for _ in 0..=max_id {
3,856✔
99
            let idx = out_graph.add_node(py.None());
3,856✔
100
            tmp_nodes.push(idx);
3,856✔
101
        }
3,856✔
102

103
        // Replace placeholder nodes with actual data and track which to keep
104
        for node in graph.nodes {
3,866✔
105
            let payload = match node.data {
3,840✔
106
                Some(data) => match node_attrs {
3,544✔
NEW
107
                    Some(ref callback) => callback.call1(*py, (data,))?,
×
108
                    None => data.into_py_any(*py)?,
3,544✔
109
                },
110
                None => py.None(),
296✔
111
            };
112
            let node_id = node.id.unwrap_or(0);
3,840✔
113
            let idx = NodeIndex::new(node_id);
3,840✔
114

115
            // Replace the placeholder with actual data
116
            if let Some(weight) = out_graph.node_weight_mut(idx) {
3,840✔
117
                *weight = payload;
3,840✔
118
            }
3,840✔
119

120
            id_mapping.insert(node_id, idx);
3,840✔
121
            // Mark this index as used (remove from tmp_nodes)
122
            tmp_nodes.retain(|&n| n != idx);
1,576,576✔
123
        }
124

125
        // Track if we're removing any nodes (indicates gaps in indices)
126
        let has_gaps = !tmp_nodes.is_empty();
26✔
127

128
        // Remove remaining placeholder nodes
129
        for tmp_node in tmp_nodes {
42✔
130
            out_graph.remove_node(tmp_node);
16✔
131
        }
16✔
132

133
        has_gaps
26✔
134
    } else {
135
        // No explicit IDs, just add nodes sequentially (legacy behavior)
NEW
136
        for node in graph.nodes {
×
NEW
137
            let payload = match node.data {
×
NEW
138
                Some(data) => match node_attrs {
×
NEW
139
                    Some(ref callback) => callback.call1(*py, (data,))?,
×
NEW
140
                    None => data.into_py_any(*py)?,
×
141
                },
NEW
142
                None => py.None(),
×
143
            };
NEW
144
            let id = out_graph.add_node(payload);
×
NEW
145
            id_mapping.insert(id.index(), id);
×
146
        }
NEW
147
        false
×
148
    };
149

150
    for edge in graph.links {
4,484✔
151
        let data = match edge.data {
4,458✔
152
            Some(data) => match edge_attrs {
4,184✔
153
                Some(ref callback) => callback.call1(*py, (data,))?,
×
154
                None => data.into_py_any(*py)?,
4,184✔
155
            },
156
            None => py.None(),
274✔
157
        };
158
        out_graph.add_edge(id_mapping[&edge.source], id_mapping[&edge.target], data);
4,458✔
159
    }
160
    Ok(node_removed)
26✔
161
}
26✔
162

163
#[allow(clippy::too_many_arguments)]
164
pub fn node_link_data<Ty: EdgeType>(
58✔
165
    py: Python,
58✔
166
    graph: &StablePyGraph<Ty>,
58✔
167
    multigraph: bool,
58✔
168
    attrs: &PyObject,
58✔
169
    path: Option<String>,
58✔
170
    graph_attrs: Option<PyObject>,
58✔
171
    node_attrs: Option<PyObject>,
58✔
172
    edge_attrs: Option<PyObject>,
58✔
173
) -> PyResult<Option<String>> {
58✔
174
    let attr_callable = |attrs: &PyObject, obj: &PyObject| -> PyResult<BTreeMap<String, String>> {
7,788✔
175
        let res = attrs.call1(py, (obj,))?;
7,788✔
176
        res.extract(py)
7,788✔
177
    };
7,788✔
178
    let mut nodes: Vec<Node> = Vec::with_capacity(graph.node_count());
58✔
179
    for n in graph.node_indices() {
3,868✔
180
        let data = match node_attrs {
3,868✔
181
            Some(ref callback) => Some(attr_callable(callback, &graph[n])?),
3,568✔
182
            None => None,
300✔
183
        };
184
        nodes.push(Node {
3,868✔
185
            id: n.index(),
3,868✔
186
            data,
3,868✔
187
        });
3,868✔
188
    }
189
    let mut links: Vec<Link> = Vec::with_capacity(graph.edge_count());
58✔
190
    for e in graph.edge_references() {
4,474✔
191
        let data = match edge_attrs {
4,474✔
192
            Some(ref callback) => Some(attr_callable(callback, e.weight())?),
4,200✔
193
            None => None,
274✔
194
        };
195
        links.push(Link {
4,474✔
196
            source: e.source().index(),
4,474✔
197
            target: e.target().index(),
4,474✔
198
            id: e.id().index(),
4,474✔
199
            data,
4,474✔
200
        });
4,474✔
201
    }
202

203
    let graph_attrs = match graph_attrs {
58✔
204
        Some(ref callback) => Some(attr_callable(callback, attrs)?),
20✔
205
        None => None,
38✔
206
    };
207

208
    let output_struct = Graph {
54✔
209
        directed: graph.is_directed(),
54✔
210
        multigraph,
54✔
211
        attrs: graph_attrs,
54✔
212
        nodes,
54✔
213
        links,
54✔
214
    };
54✔
215
    match path {
54✔
216
        None => match serde_json::to_string(&output_struct) {
38✔
217
            Ok(v) => Ok(Some(v)),
38✔
218
            Err(e) => Err(JSONSerializationError::new_err(format!("JSON Error: {e}"))),
×
219
        },
220
        Some(filename) => {
16✔
221
            let file = File::create(filename)?;
16✔
222
            match serde_json::to_writer(file, &output_struct) {
12✔
223
                Ok(_) => Ok(None),
12✔
224
                Err(e) => Err(JSONSerializationError::new_err(format!("JSON Error: {e}"))),
×
225
            }
226
        }
227
    }
228
}
58✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc