From d71e9c3b6507dce4ef0c8499227f34eb9c494af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ramos?= <1joaoramos1@gmail.com> Date: Fri, 24 Apr 2026 16:01:15 +0200 Subject: [PATCH 1/3] Implements pickling of user graph --- .../driver/user_graph.pybind.cc | 162 ++++++++++++------ 1 file changed, 114 insertions(+), 48 deletions(-) diff --git a/src/pymatching/sparse_blossom/driver/user_graph.pybind.cc b/src/pymatching/sparse_blossom/driver/user_graph.pybind.cc index 7b227e5cc..190698d9f 100644 --- a/src/pymatching/sparse_blossom/driver/user_graph.pybind.cc +++ b/src/pymatching/sparse_blossom/driver/user_graph.pybind.cc @@ -14,6 +14,10 @@ #include "pymatching/sparse_blossom/driver/user_graph.pybind.h" +#include +#include +#include + #include "pybind11/pybind11.h" #include "pymatching/sparse_blossom/driver/mwpm_decoding.h" #include "pymatching/sparse_blossom/driver/user_graph.h" @@ -21,7 +25,7 @@ using namespace py::literals; -pm_pybind::CompressedSparseColumnCheckMatrix::CompressedSparseColumnCheckMatrix(const py::object &matrix) { +pm_pybind::CompressedSparseColumnCheckMatrix::CompressedSparseColumnCheckMatrix(const py::object& matrix) { py::object csc_matrix = py::module_::import("scipy.sparse").attr("csc_matrix"); if (!py::isinstance(matrix, csc_matrix)) throw std::invalid_argument("Check matrix must be a `scipy.sparse.csc_matrix`."); @@ -64,12 +68,12 @@ pm_pybind::CompressedSparseColumnCheckMatrix::CompressedSparseColumnCheckMatrix( } } -py::class_ pm_pybind::pybind_user_graph(py::module &m) { +py::class_ pm_pybind::pybind_user_graph(py::module& m) { auto g = py::class_(m, "MatchingGraph"); return g; } -pm::MERGE_STRATEGY merge_strategy_from_string(const std::string &merge_strategy) { +pm::MERGE_STRATEGY merge_strategy_from_string(const std::string& merge_strategy) { static std::unordered_map const table = { {"disallow", pm::DISALLOW}, {"independent", pm::INDEPENDENT}, @@ -84,19 +88,19 @@ pm::MERGE_STRATEGY merge_strategy_from_string(const std::string &merge_strategy) } } -void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_ &g) { +void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_& g) { g.def(py::init<>()); g.def(py::init(), "num_nodes"_a); g.def(py::init(), "num_nodes"_a, "num_fault_ids"_a); g.def( "add_edge", - [](pm::UserGraph &self, + [](pm::UserGraph& self, int64_t node1, int64_t node2, - const std::set &observables, + const std::set& observables, double weight, double error_probability, - const std::string &merge_strategy) { + const std::string& merge_strategy) { // Using signed integer (int64_t) instead of size_t for the python API, since it can be useful to // return -1 as the virtual boundary when inspecting the graph. if (node1 < 0 || node2 < 0) @@ -122,12 +126,12 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_ &observables, + const std::set& observables, double weight, double error_probability, - const std::string &merge_strategy) { + const std::string& merge_strategy) { // Using signed integer (int64_t) instead of size_t for the python API, since it can be useful to // return -1 as the virtual boundary when inspecting the graph. if (node < 0) @@ -158,18 +162,18 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_(self.get_num_observables(), 0); auto syndrome_vec = new std::vector(self.get_num_nodes(), 0); self.add_noise(error_vec->data(), syndrome_vec->data()); - auto syndrome_capsule = py::capsule(syndrome_vec, [](void *syndrome) { - delete reinterpret_cast *>(syndrome); + auto syndrome_capsule = py::capsule(syndrome_vec, [](void* syndrome) { + delete reinterpret_cast*>(syndrome); }); py::array_t syndrome_arr = py::array_t(syndrome_vec->size(), syndrome_vec->data(), syndrome_capsule); - auto err_capsule = py::capsule(error_vec, [](void *error) { - delete reinterpret_cast *>(error); + auto err_capsule = py::capsule(error_vec, [](void* error) { + delete reinterpret_cast*>(error); }); py::array_t error_arr = py::array_t(error_vec->size(), error_vec->data(), err_capsule); @@ -178,17 +182,17 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_ &detection_events, bool enable_correlations) { + [](pm::UserGraph& self, const py::array_t& detection_events, bool enable_correlations) { std::vector detection_events_vec( detection_events.data(), detection_events.data() + detection_events.size()); - auto &mwpm = enable_correlations ? self.get_mwpm_with_search_graph() : self.get_mwpm(); + auto& mwpm = enable_correlations ? self.get_mwpm_with_search_graph() : self.get_mwpm(); auto obs_crossed = new std::vector(self.get_num_observables(), 0); pm::total_weight_int weight = 0; pm::decode_detection_events(mwpm, detection_events_vec, obs_crossed->data(), weight, enable_correlations); double rescaled_weight = (double)weight / mwpm.flooder.graph.normalising_constant; - auto err_capsule = py::capsule(obs_crossed, [](void *x) { - delete reinterpret_cast *>(x); + auto err_capsule = py::capsule(obs_crossed, [](void* x) { + delete reinterpret_cast*>(x); }); py::array_t obs_crossed_arr = py::array_t(obs_crossed->size(), obs_crossed->data(), err_capsule); @@ -199,8 +203,8 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_ &detection_events, bool enable_correlations) { - auto &mwpm = self.get_mwpm_with_search_graph(); + [](pm::UserGraph& self, const py::array_t& detection_events, bool enable_correlations) { + auto& mwpm = self.get_mwpm_with_search_graph(); std::vector detection_events_vec( detection_events.data(), detection_events.data() + detection_events.size()); auto edges = new std::vector(); @@ -219,19 +223,19 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_ &detection_events) { - auto &mwpm = self.get_mwpm(); + [](pm::UserGraph& self, const py::array_t& detection_events) { + auto& mwpm = self.get_mwpm(); std::vector detection_events_vec( detection_events.data(), detection_events.data() + detection_events.size()); pm::decode_detection_events_to_match_edges(mwpm, detection_events_vec); py::ssize_t num_edges = (py::ssize_t)(mwpm.flooder.match_edges.size()); py::array_t match_edges = py::array_t(num_edges * 2); py::buffer_info buff = match_edges.request(); - int64_t *ptr = (int64_t *)buff.ptr; + int64_t* ptr = (int64_t*)buff.ptr; // Convert match edges to a vector of int64_t for (size_t i = 0; i < mwpm.flooder.match_edges.size(); i++) { - auto &e = mwpm.flooder.match_edges[i]; + auto& e = mwpm.flooder.match_edges[i]; ptr[2 * i] = e.loc_from - &mwpm.flooder.graph.nodes[0]; if (e.loc_to) { ptr[2 * i + 1] = e.loc_to - &mwpm.flooder.graph.nodes[0]; @@ -246,8 +250,8 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_ &shots, + [](pm::UserGraph& self, + const py::array_t& shots, bool bit_packed_shots, bool bit_packed_predictions, bool enable_correlations) { @@ -279,13 +283,13 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_ predictions = py::array_t(shots.shape(0) * num_observable_bytes); predictions[py::make_tuple(py::ellipsis())] = 0; // Initialise to 0 py::buffer_info buff = predictions.request(); - uint8_t *predictions_ptr = (uint8_t *)buff.ptr; + uint8_t* predictions_ptr = (uint8_t*)buff.ptr; // Reserve weights array py::array_t weights = py::array_t(shots.shape(0)); auto ws = weights.mutable_unchecked<1>(); - auto &mwpm = enable_correlations ? self.get_mwpm_with_search_graph() : self.get_mwpm(); + auto& mwpm = enable_correlations ? self.get_mwpm_with_search_graph() : self.get_mwpm(); std::vector detection_events; // Vector used to extract predicted observables when decoding if bit_packed_predictions is true @@ -340,14 +344,14 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_ &detection_events) { - auto &mwpm = self.get_mwpm(); + [](pm::UserGraph& self, const py::array_t& detection_events) { + auto& mwpm = self.get_mwpm(); std::vector detection_events_vec( detection_events.data(), detection_events.data() + detection_events.size()); pm::decode_detection_events_to_match_edges(mwpm, detection_events_vec); py::dict match_dict; // Convert match edges to a vector of int64_t - for (auto &e : mwpm.flooder.match_edges) { + for (auto& e : mwpm.flooder.match_edges) { int64_t from_idx = e.loc_from - &mwpm.flooder.graph.nodes[0]; if (e.loc_to) { int64_t to_idx = e.loc_to - &mwpm.flooder.graph.nodes[0]; @@ -361,10 +365,10 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_ 1) { p = -1.0; @@ -387,7 +391,7 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_= self.nodes.size()) throw std::invalid_argument("node1 (" + std::to_string(node1) + ") not in graph"); size_t idx = self.nodes[node1].index_of_neighbor(node2); @@ -407,7 +411,7 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_= self.nodes.size()) throw std::invalid_argument("node (" + std::to_string(node) + ") not in graph"); size_t idx = self.nodes[node].index_of_neighbor(SIZE_MAX); @@ -425,7 +429,7 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_ &weights, - const py::array_t &error_probabilities, - const std::string &merge_strategy, + [](const py::object& check_matrix, + const py::array_t& weights, + const py::array_t& error_probabilities, + const std::string& merge_strategy, bool use_virtual_boundary_node, size_t num_repetitions, - const py::array_t &timelike_weights, - const py::array_t &measurement_error_probabilities, - py::object &faults_matrix) { + const py::array_t& timelike_weights, + const py::array_t& measurement_error_probabilities, + py::object& faults_matrix) { auto H = CompressedSparseColumnCheckMatrix(check_matrix); if (faults_matrix.is(py::none())) { @@ -612,4 +616,66 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_>(); + g.loaded_from_dem_without_correlations = t[2].cast(); + + for (const auto& edge_obj : t[0].cast()) { + auto et = edge_obj.cast(); + pm::UserEdge edge; + edge.node1 = et[0].cast(); + edge.node2 = et[1].cast(); + edge.observable_indices = et[2].cast>(); + edge.weight = et[3].cast(); + edge.error_probability = et[4].cast(); + + for (const auto& iw_obj : et[5].cast()) { + auto iw_tuple = iw_obj.cast(); + pm::ImpliedWeightUnconverted iw; + iw.node1 = iw_tuple[0].cast(); + iw.node2 = iw_tuple[1].cast(); + iw.implied_weight = iw_tuple[2].cast(); + edge.implied_weights_for_other_edges.push_back(iw); + } + + size_t max_node = std::max(edge.node1, edge.node2); + while (g.nodes.size() <= max_node) + g.nodes.emplace_back(); + + g.edges.push_back(edge); + auto edge_it = std::prev(g.edges.end()); + + g.nodes[edge.node1].neighbors.push_back({edge_it, 0}); + g.nodes[edge.node2].neighbors.push_back({edge_it, 1}); + } + + for (size_t i : g.boundary_nodes) + if (i < g.nodes.size()) + g.nodes[i].is_boundary = true; + + return g; + })); } From ed574c27c89a57a8bebae911991b650acbed1599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ramos?= <1joaoramos1@gmail.com> Date: Sun, 26 Apr 2026 14:40:53 +0200 Subject: [PATCH 2/3] Fixes observable and mwpm initialization, and inverted edge neighbor pos assignment --- .clang-format | 15 ++- .../driver/user_graph.pybind.cc | 127 +++++++++--------- 2 files changed, 72 insertions(+), 70 deletions(-) diff --git a/.clang-format b/.clang-format index 92eae3968..923c3a234 100644 --- a/.clang-format +++ b/.clang-format @@ -11,12 +11,13 @@ AllowShortLoopsOnASingleLine: false AllowShortLambdasOnASingleLine: None BinPackArguments: false BinPackParameters: false - +PointerAlignment: Right +ReferenceAlignment: Right IncludeCategories: - - Regex: '^<.*>$' - Priority: 1 - - Regex: '^"gtest/.*"$' - Priority: 2 - - Regex: '^".*"$' - Priority: 3 + - Regex: "^<.*>$" + Priority: 1 + - Regex: '^"gtest/.*"$' + Priority: 2 + - Regex: '^".*"$' + Priority: 3 ... diff --git a/src/pymatching/sparse_blossom/driver/user_graph.pybind.cc b/src/pymatching/sparse_blossom/driver/user_graph.pybind.cc index 190698d9f..b9e6681a1 100644 --- a/src/pymatching/sparse_blossom/driver/user_graph.pybind.cc +++ b/src/pymatching/sparse_blossom/driver/user_graph.pybind.cc @@ -14,10 +14,6 @@ #include "pymatching/sparse_blossom/driver/user_graph.pybind.h" -#include -#include -#include - #include "pybind11/pybind11.h" #include "pymatching/sparse_blossom/driver/mwpm_decoding.h" #include "pymatching/sparse_blossom/driver/user_graph.h" @@ -25,7 +21,7 @@ using namespace py::literals; -pm_pybind::CompressedSparseColumnCheckMatrix::CompressedSparseColumnCheckMatrix(const py::object& matrix) { +pm_pybind::CompressedSparseColumnCheckMatrix::CompressedSparseColumnCheckMatrix(const py::object &matrix) { py::object csc_matrix = py::module_::import("scipy.sparse").attr("csc_matrix"); if (!py::isinstance(matrix, csc_matrix)) throw std::invalid_argument("Check matrix must be a `scipy.sparse.csc_matrix`."); @@ -68,12 +64,12 @@ pm_pybind::CompressedSparseColumnCheckMatrix::CompressedSparseColumnCheckMatrix( } } -py::class_ pm_pybind::pybind_user_graph(py::module& m) { +py::class_ pm_pybind::pybind_user_graph(py::module &m) { auto g = py::class_(m, "MatchingGraph"); return g; } -pm::MERGE_STRATEGY merge_strategy_from_string(const std::string& merge_strategy) { +pm::MERGE_STRATEGY merge_strategy_from_string(const std::string &merge_strategy) { static std::unordered_map const table = { {"disallow", pm::DISALLOW}, {"independent", pm::INDEPENDENT}, @@ -88,19 +84,19 @@ pm::MERGE_STRATEGY merge_strategy_from_string(const std::string& merge_strategy) } } -void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_& g) { +void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_ &g) { g.def(py::init<>()); g.def(py::init(), "num_nodes"_a); g.def(py::init(), "num_nodes"_a, "num_fault_ids"_a); g.def( "add_edge", - [](pm::UserGraph& self, + [](pm::UserGraph &self, int64_t node1, int64_t node2, - const std::set& observables, + const std::set &observables, double weight, double error_probability, - const std::string& merge_strategy) { + const std::string &merge_strategy) { // Using signed integer (int64_t) instead of size_t for the python API, since it can be useful to // return -1 as the virtual boundary when inspecting the graph. if (node1 < 0 || node2 < 0) @@ -126,12 +122,12 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_& observables, + const std::set &observables, double weight, double error_probability, - const std::string& merge_strategy) { + const std::string &merge_strategy) { // Using signed integer (int64_t) instead of size_t for the python API, since it can be useful to // return -1 as the virtual boundary when inspecting the graph. if (node < 0) @@ -162,18 +158,18 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_(self.get_num_observables(), 0); auto syndrome_vec = new std::vector(self.get_num_nodes(), 0); self.add_noise(error_vec->data(), syndrome_vec->data()); - auto syndrome_capsule = py::capsule(syndrome_vec, [](void* syndrome) { - delete reinterpret_cast*>(syndrome); + auto syndrome_capsule = py::capsule(syndrome_vec, [](void *syndrome) { + delete reinterpret_cast *>(syndrome); }); py::array_t syndrome_arr = py::array_t(syndrome_vec->size(), syndrome_vec->data(), syndrome_capsule); - auto err_capsule = py::capsule(error_vec, [](void* error) { - delete reinterpret_cast*>(error); + auto err_capsule = py::capsule(error_vec, [](void *error) { + delete reinterpret_cast *>(error); }); py::array_t error_arr = py::array_t(error_vec->size(), error_vec->data(), err_capsule); @@ -182,17 +178,17 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_& detection_events, bool enable_correlations) { + [](pm::UserGraph &self, const py::array_t &detection_events, bool enable_correlations) { std::vector detection_events_vec( detection_events.data(), detection_events.data() + detection_events.size()); - auto& mwpm = enable_correlations ? self.get_mwpm_with_search_graph() : self.get_mwpm(); + auto &mwpm = enable_correlations ? self.get_mwpm_with_search_graph() : self.get_mwpm(); auto obs_crossed = new std::vector(self.get_num_observables(), 0); pm::total_weight_int weight = 0; pm::decode_detection_events(mwpm, detection_events_vec, obs_crossed->data(), weight, enable_correlations); double rescaled_weight = (double)weight / mwpm.flooder.graph.normalising_constant; - auto err_capsule = py::capsule(obs_crossed, [](void* x) { - delete reinterpret_cast*>(x); + auto err_capsule = py::capsule(obs_crossed, [](void *x) { + delete reinterpret_cast *>(x); }); py::array_t obs_crossed_arr = py::array_t(obs_crossed->size(), obs_crossed->data(), err_capsule); @@ -203,8 +199,8 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_& detection_events, bool enable_correlations) { - auto& mwpm = self.get_mwpm_with_search_graph(); + [](pm::UserGraph &self, const py::array_t &detection_events, bool enable_correlations) { + auto &mwpm = self.get_mwpm_with_search_graph(); std::vector detection_events_vec( detection_events.data(), detection_events.data() + detection_events.size()); auto edges = new std::vector(); @@ -223,19 +219,19 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_& detection_events) { - auto& mwpm = self.get_mwpm(); + [](pm::UserGraph &self, const py::array_t &detection_events) { + auto &mwpm = self.get_mwpm(); std::vector detection_events_vec( detection_events.data(), detection_events.data() + detection_events.size()); pm::decode_detection_events_to_match_edges(mwpm, detection_events_vec); py::ssize_t num_edges = (py::ssize_t)(mwpm.flooder.match_edges.size()); py::array_t match_edges = py::array_t(num_edges * 2); py::buffer_info buff = match_edges.request(); - int64_t* ptr = (int64_t*)buff.ptr; + int64_t *ptr = (int64_t *)buff.ptr; // Convert match edges to a vector of int64_t for (size_t i = 0; i < mwpm.flooder.match_edges.size(); i++) { - auto& e = mwpm.flooder.match_edges[i]; + auto &e = mwpm.flooder.match_edges[i]; ptr[2 * i] = e.loc_from - &mwpm.flooder.graph.nodes[0]; if (e.loc_to) { ptr[2 * i + 1] = e.loc_to - &mwpm.flooder.graph.nodes[0]; @@ -250,8 +246,8 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_& shots, + [](pm::UserGraph &self, + const py::array_t &shots, bool bit_packed_shots, bool bit_packed_predictions, bool enable_correlations) { @@ -283,13 +279,13 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_ predictions = py::array_t(shots.shape(0) * num_observable_bytes); predictions[py::make_tuple(py::ellipsis())] = 0; // Initialise to 0 py::buffer_info buff = predictions.request(); - uint8_t* predictions_ptr = (uint8_t*)buff.ptr; + uint8_t *predictions_ptr = (uint8_t *)buff.ptr; // Reserve weights array py::array_t weights = py::array_t(shots.shape(0)); auto ws = weights.mutable_unchecked<1>(); - auto& mwpm = enable_correlations ? self.get_mwpm_with_search_graph() : self.get_mwpm(); + auto &mwpm = enable_correlations ? self.get_mwpm_with_search_graph() : self.get_mwpm(); std::vector detection_events; // Vector used to extract predicted observables when decoding if bit_packed_predictions is true @@ -344,14 +340,14 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_& detection_events) { - auto& mwpm = self.get_mwpm(); + [](pm::UserGraph &self, const py::array_t &detection_events) { + auto &mwpm = self.get_mwpm(); std::vector detection_events_vec( detection_events.data(), detection_events.data() + detection_events.size()); pm::decode_detection_events_to_match_edges(mwpm, detection_events_vec); py::dict match_dict; // Convert match edges to a vector of int64_t - for (auto& e : mwpm.flooder.match_edges) { + for (auto &e : mwpm.flooder.match_edges) { int64_t from_idx = e.loc_from - &mwpm.flooder.graph.nodes[0]; if (e.loc_to) { int64_t to_idx = e.loc_to - &mwpm.flooder.graph.nodes[0]; @@ -365,10 +361,10 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_ 1) { p = -1.0; @@ -391,7 +387,7 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_= self.nodes.size()) throw std::invalid_argument("node1 (" + std::to_string(node1) + ") not in graph"); size_t idx = self.nodes[node1].index_of_neighbor(node2); @@ -411,7 +407,7 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_= self.nodes.size()) throw std::invalid_argument("node (" + std::to_string(node) + ") not in graph"); size_t idx = self.nodes[node].index_of_neighbor(SIZE_MAX); @@ -429,7 +425,7 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_& weights, - const py::array_t& error_probabilities, - const std::string& merge_strategy, + [](const py::object &check_matrix, + const py::array_t &weights, + const py::array_t &error_probabilities, + const std::string &merge_strategy, bool use_virtual_boundary_node, size_t num_repetitions, - const py::array_t& timelike_weights, - const py::array_t& measurement_error_probabilities, - py::object& faults_matrix) { + const py::array_t &timelike_weights, + const py::array_t &measurement_error_probabilities, + py::object &faults_matrix) { auto H = CompressedSparseColumnCheckMatrix(check_matrix); if (faults_matrix.is(py::none())) { @@ -619,11 +615,11 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_>(); - g.loaded_from_dem_without_correlations = t[2].cast(); + pm::UserGraph g(t[0].cast(), t[1].cast()); + g.boundary_nodes = t[3].cast>(); + g.loaded_from_dem_without_correlations = t[4].cast(); - for (const auto& edge_obj : t[0].cast()) { + for (const auto &edge_obj : t[2].cast()) { auto et = edge_obj.cast(); pm::UserEdge edge; edge.node1 = et[0].cast(); @@ -652,7 +653,7 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_(); edge.error_probability = et[4].cast(); - for (const auto& iw_obj : et[5].cast()) { + for (const auto &iw_obj : et[5].cast()) { auto iw_tuple = iw_obj.cast(); pm::ImpliedWeightUnconverted iw; iw.node1 = iw_tuple[0].cast(); @@ -668,8 +669,8 @@ void pm_pybind::pybind_user_graph_methods(py::module& m, py::class_ Date: Sun, 26 Apr 2026 15:14:11 +0200 Subject: [PATCH 3/3] Simplifies serialisation --- .../sparse_blossom/driver/user_graph.pybind.cc | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/pymatching/sparse_blossom/driver/user_graph.pybind.cc b/src/pymatching/sparse_blossom/driver/user_graph.pybind.cc index b9e6681a1..0fb989329 100644 --- a/src/pymatching/sparse_blossom/driver/user_graph.pybind.cc +++ b/src/pymatching/sparse_blossom/driver/user_graph.pybind.cc @@ -655,17 +655,10 @@ void pm_pybind::pybind_user_graph_methods(py::module &m, py::class_()) { auto iw_tuple = iw_obj.cast(); - pm::ImpliedWeightUnconverted iw; - iw.node1 = iw_tuple[0].cast(); - iw.node2 = iw_tuple[1].cast(); - iw.implied_weight = iw_tuple[2].cast(); - edge.implied_weights_for_other_edges.push_back(iw); + edge.implied_weights_for_other_edges.emplace_back( + iw_tuple[0].cast(), iw_tuple[1].cast(), iw_tuple[2].cast()); } - size_t max_node = std::max(edge.node1, edge.node2); - while (g.nodes.size() <= max_node) - g.nodes.emplace_back(); - g.edges.push_back(edge); auto edge_it = std::prev(g.edges.end());