From 69ff13fe267c20a8941ed37a609523b36ec91e58 Mon Sep 17 00:00:00 2001 From: Abbas Bracken Ziad Date: Thu, 26 Feb 2026 18:41:23 +0000 Subject: [PATCH 1/5] Add support for comparing DEMs up to instruction ordering --- src/stim/dem/detector_error_model.cc | 29 +++++++++++++++++++++++ src/stim/dem/detector_error_model.h | 7 ++++++ src/stim/dem/detector_error_model.test.cc | 18 ++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/src/stim/dem/detector_error_model.cc b/src/stim/dem/detector_error_model.cc index 6f663dbf6..2e3167c7f 100644 --- a/src/stim/dem/detector_error_model.cc +++ b/src/stim/dem/detector_error_model.cc @@ -16,9 +16,11 @@ #include "stim/dem/detector_error_model.h" +#include #include #include #include +#include #include "stim/util_bot/str_util.h" @@ -793,3 +795,30 @@ DetectorErrorModel DetectorErrorModel::without_tags() const { } return result; } + +bool DetectorErrorModel::equal_up_to_instruction_ordering(const DetectorErrorModel &other) const { + if (instructions.size() != other.instructions.size()) { + return false; + } + + auto get_sorted_indices = [](const std::vector &ins) { + std::vector indices(ins.size()); + std::iota(indices.begin(), indices.end(), 0); + std::sort(indices.begin(), indices.end(), [&ins](size_t i, size_t j) { + return ins[i] < ins[j]; + }); + return indices; + }; + + // sort the indices to avoid copying instructions + auto sorted_lhs_indices = get_sorted_indices(instructions); + auto sorted_rhs_indices = get_sorted_indices(other.instructions); + + for (size_t i = 0; i < sorted_lhs_indices.size(); i++) { + if (!(instructions[sorted_lhs_indices[i]] == other.instructions[sorted_rhs_indices[i]])) { + return false; + } + } + + return true; +} diff --git a/src/stim/dem/detector_error_model.h b/src/stim/dem/detector_error_model.h index 0f5d53d55..2f4edc806 100644 --- a/src/stim/dem/detector_error_model.h +++ b/src/stim/dem/detector_error_model.h @@ -135,6 +135,13 @@ struct DetectorErrorModel { /// Returns an equivalent detector error model with no repeat blocks or detector_shift instructions. DetectorErrorModel flattened() const; + + /// Returns true if the detector error model is equal to the other detector error model up to instruction ordering. + /// + /// For example, error(0.01) D0 error(0.002) D1 L0 is considered the same as error(0.002) D1 L0 error(0.01) D0. + /// + /// Note: requires O(n log n) time due to sorting. Prefer `==` when you know the instructions are in the same order. + bool equal_up_to_instruction_ordering(const DetectorErrorModel &other) const; }; void print_detector_error_model(std::ostream &out, const DetectorErrorModel &v, size_t indent); diff --git a/src/stim/dem/detector_error_model.test.cc b/src/stim/dem/detector_error_model.test.cc index 429642c90..484ad1e9d 100644 --- a/src/stim/dem/detector_error_model.test.cc +++ b/src/stim/dem/detector_error_model.test.cc @@ -894,3 +894,21 @@ TEST(detector_error_model, parse_windows_newlines) { DetectorErrorModel("error(0.125) D0\r\ndetector(5) D10\r\n"), DetectorErrorModel("error(0.125) D0\r\ndetector(5) D10\r\n")); } + +TEST(detector_error_model, equal_up_to_instruction_ordering) { + DetectorErrorModel lhs(R"MODEL( + error(0.01) D0 + error(0.002) D1 L0 + detector(5, 10) D0 + detector(5, 15) D1 + logical_observable L0 + )MODEL"); + DetectorErrorModel rhs(R"MODEL( + error(0.002) D1 L0 + error(0.01) D0 + detector(5, 15) D1 + detector(5, 10) D0 + logical_observable L0 + )MODEL"); + EXPECT_TRUE(lhs.equal_up_to_instruction_ordering(rhs)); +} From 854289c28dbf6a631292f69b53a8df8109f5cfde Mon Sep 17 00:00:00 2001 From: Abbas Bracken Ziad Date: Wed, 6 May 2026 23:26:20 +0100 Subject: [PATCH 2/5] Add support for structural ops efficiently --- src/stim/dem/detector_error_model.cc | 92 ++++++++++++++++++----- src/stim/dem/detector_error_model.h | 2 +- src/stim/dem/detector_error_model.test.cc | 4 +- 3 files changed, 77 insertions(+), 21 deletions(-) diff --git a/src/stim/dem/detector_error_model.cc b/src/stim/dem/detector_error_model.cc index 2e3167c7f..0a2074ebc 100644 --- a/src/stim/dem/detector_error_model.cc +++ b/src/stim/dem/detector_error_model.cc @@ -16,7 +16,7 @@ #include "stim/dem/detector_error_model.h" -#include +#include #include #include #include @@ -796,29 +796,85 @@ DetectorErrorModel DetectorErrorModel::without_tags() const { return result; } -bool DetectorErrorModel::equal_up_to_instruction_ordering(const DetectorErrorModel &other) const { - if (instructions.size() != other.instructions.size()) { - return false; - } +bool is_structural(DemInstructionType type) { + return type == DemInstructionType::DEM_SHIFT_DETECTORS || type == DemInstructionType::DEM_REPEAT_BLOCK; +} - auto get_sorted_indices = [](const std::vector &ins) { - std::vector indices(ins.size()); - std::iota(indices.begin(), indices.end(), 0); - std::sort(indices.begin(), indices.end(), [&ins](size_t i, size_t j) { - return ins[i] < ins[j]; - }); - return indices; +bool is_equal_up_to_instruction_ordering_helper(const DetectorErrorModel &a, const DetectorErrorModel &b) { + size_t index_in_a = 0; + size_t index_in_b = 0; + + auto get_next_segment = [](const DetectorErrorModel &dem, size_t &i) { + std::vector segment; + while (i < dem.instructions.size() && !is_structural(dem.instructions[i].type)) { + segment.push_back(dem.instructions[i]); + i++; + } + std::sort(segment.begin(), segment.end()); + return segment; }; - // sort the indices to avoid copying instructions - auto sorted_lhs_indices = get_sorted_indices(instructions); - auto sorted_rhs_indices = get_sorted_indices(other.instructions); + while (true) { + // compare next segments + auto segment_a = get_next_segment(a, index_in_a); + auto segment_b = get_next_segment(b, index_in_b); + if (segment_a != segment_b) { + return false; + } + + // check for terminating condition + bool traversed_a = index_in_a == a.instructions.size(); + bool traversed_b = index_in_b == b.instructions.size(); + if (traversed_a || traversed_b) { + return traversed_a && traversed_b; + } + + // compare next structural ops + const auto &structural_op_a = a.instructions[index_in_a]; + const auto &structural_op_b = b.instructions[index_in_b]; + + if (structural_op_a.type != structural_op_b.type) { + return false; + } - for (size_t i = 0; i < sorted_lhs_indices.size(); i++) { - if (!(instructions[sorted_lhs_indices[i]] == other.instructions[sorted_rhs_indices[i]])) { + if (structural_op_a.tag != structural_op_b.tag) { return false; } + + if (structural_op_a.type == DemInstructionType::DEM_SHIFT_DETECTORS) { + // check coordinate shift + if (structural_op_a.arg_data != structural_op_b.arg_data) { + return false; + } + // check index shift + if (structural_op_a.target_data[0].data != structural_op_b.target_data[0].data) { + return false; + } + // advance to next payload op + index_in_a++; + index_in_b++; + continue; + } + + if (structural_op_a.type == DemInstructionType::DEM_REPEAT_BLOCK) { + // check reps + if (structural_op_a.repeat_block_rep_count() != structural_op_b.repeat_block_rep_count()) { + return false; + } + // check equality of repeat body + const auto &body_a = structural_op_a.repeat_block_body(a); + const auto &body_b = structural_op_b.repeat_block_body(b); + if (!is_equal_up_to_instruction_ordering_helper(body_a, body_b)) { + return false; + } + // advance to next payload op + index_in_a++; + index_in_b++; + continue; + } } +} - return true; +bool DetectorErrorModel::is_equal_up_to_instruction_ordering(const DetectorErrorModel &other) const { + return is_equal_up_to_instruction_ordering_helper(*this, other); } diff --git a/src/stim/dem/detector_error_model.h b/src/stim/dem/detector_error_model.h index 2f4edc806..ffd68ffed 100644 --- a/src/stim/dem/detector_error_model.h +++ b/src/stim/dem/detector_error_model.h @@ -141,7 +141,7 @@ struct DetectorErrorModel { /// For example, error(0.01) D0 error(0.002) D1 L0 is considered the same as error(0.002) D1 L0 error(0.01) D0. /// /// Note: requires O(n log n) time due to sorting. Prefer `==` when you know the instructions are in the same order. - bool equal_up_to_instruction_ordering(const DetectorErrorModel &other) const; + bool is_equal_up_to_instruction_ordering(const DetectorErrorModel &other) const; }; void print_detector_error_model(std::ostream &out, const DetectorErrorModel &v, size_t indent); diff --git a/src/stim/dem/detector_error_model.test.cc b/src/stim/dem/detector_error_model.test.cc index 484ad1e9d..5a0f46881 100644 --- a/src/stim/dem/detector_error_model.test.cc +++ b/src/stim/dem/detector_error_model.test.cc @@ -895,7 +895,7 @@ TEST(detector_error_model, parse_windows_newlines) { DetectorErrorModel("error(0.125) D0\r\ndetector(5) D10\r\n")); } -TEST(detector_error_model, equal_up_to_instruction_ordering) { +TEST(detector_error_model, is_equal_up_to_instruction_ordering_flattened) { DetectorErrorModel lhs(R"MODEL( error(0.01) D0 error(0.002) D1 L0 @@ -910,5 +910,5 @@ TEST(detector_error_model, equal_up_to_instruction_ordering) { detector(5, 10) D0 logical_observable L0 )MODEL"); - EXPECT_TRUE(lhs.equal_up_to_instruction_ordering(rhs)); + EXPECT_TRUE(lhs.is_equal_up_to_instruction_ordering(rhs)); } From 10fb06241b6d9a43e9ae621b9ebcbc28c0a01b19 Mon Sep 17 00:00:00 2001 From: Abbas Bracken Ziad Date: Thu, 7 May 2026 11:54:00 +0100 Subject: [PATCH 3/5] Add a bunch of test cases --- src/stim/dem/detector_error_model.cc | 4 +- src/stim/dem/detector_error_model.test.cc | 215 +++++++++++++++++++++- 2 files changed, 215 insertions(+), 4 deletions(-) diff --git a/src/stim/dem/detector_error_model.cc b/src/stim/dem/detector_error_model.cc index 0a2074ebc..1c87576ea 100644 --- a/src/stim/dem/detector_error_model.cc +++ b/src/stim/dem/detector_error_model.cc @@ -850,7 +850,7 @@ bool is_equal_up_to_instruction_ordering_helper(const DetectorErrorModel &a, con if (structural_op_a.target_data[0].data != structural_op_b.target_data[0].data) { return false; } - // advance to next payload op + // advance to next op index_in_a++; index_in_b++; continue; @@ -867,7 +867,7 @@ bool is_equal_up_to_instruction_ordering_helper(const DetectorErrorModel &a, con if (!is_equal_up_to_instruction_ordering_helper(body_a, body_b)) { return false; } - // advance to next payload op + // advance to next op index_in_a++; index_in_b++; continue; diff --git a/src/stim/dem/detector_error_model.test.cc b/src/stim/dem/detector_error_model.test.cc index 5a0f46881..e53c05a55 100644 --- a/src/stim/dem/detector_error_model.test.cc +++ b/src/stim/dem/detector_error_model.test.cc @@ -895,9 +895,10 @@ TEST(detector_error_model, parse_windows_newlines) { DetectorErrorModel("error(0.125) D0\r\ndetector(5) D10\r\n")); } +/// Without structural barriers, a segment compares as an order-insensitive instruction sequence. TEST(detector_error_model, is_equal_up_to_instruction_ordering_flattened) { DetectorErrorModel lhs(R"MODEL( - error(0.01) D0 + error[dead](0.01) D0 error(0.002) D1 L0 detector(5, 10) D0 detector(5, 15) D1 @@ -905,10 +906,220 @@ TEST(detector_error_model, is_equal_up_to_instruction_ordering_flattened) { )MODEL"); DetectorErrorModel rhs(R"MODEL( error(0.002) D1 L0 - error(0.01) D0 + error[dead](0.01) D0 + detector(5, 15) D1 + detector(5, 10) D0 + logical_observable L0 + )MODEL"); + EXPECT_TRUE(lhs.is_equal_up_to_instruction_ordering(rhs)); +} + +/// Segment comparison includes tags, so a mismatched tag causes inequality. +TEST(detector_error_model, is_equal_up_to_instruction_ordering_flattened_tag_mismatch) { + DetectorErrorModel lhs(R"MODEL( + error[dead](0.01) D0 + error(0.002) D1 L0 + detector(5, 10) D0 + detector(5, 15) D1 + logical_observable L0 + )MODEL"); + DetectorErrorModel rhs(R"MODEL( + error(0.002) D1 L0 + error[beef](0.01) D0 detector(5, 15) D1 detector(5, 10) D0 logical_observable L0 )MODEL"); + EXPECT_FALSE(lhs.is_equal_up_to_instruction_ordering(rhs)); +} + +/// An extra `shift_detectors` barrier (even shifting by 0) makes models not equal. +TEST(detector_error_model, is_equal_up_to_instruction_ordering_shift_missing) { + DetectorErrorModel lhs(R"MODEL( + error(0.1) D0 + shift_detectors 0 + error(0.2) D1 + )MODEL"); + DetectorErrorModel rhs(R"MODEL( + error(0.1) D0 + error(0.2) D1 + )MODEL"); + EXPECT_FALSE(lhs.is_equal_up_to_instruction_ordering(rhs)); +} + +/// `shift_detectors(...)` barriers must match exactly (including the coordinate shift vector). +TEST(detector_error_model, is_equal_up_to_instruction_ordering_shift_mismatch) { + DetectorErrorModel lhs(R"MODEL( + detector(5, 10) D0 + detector(5, 15) D1 + shift_detectors(0, 1) 1 + detector(5, 10) D0 + detector(5, 15) D1 + )MODEL"); + DetectorErrorModel rhs(R"MODEL( + detector(5, 10) D0 + detector(5, 15) D1 + shift_detectors(1, 1) 1 + detector(5, 15) D1 + detector(5, 10) D0 + )MODEL"); + EXPECT_FALSE(lhs.is_equal_up_to_instruction_ordering(rhs)); +} + +/// `shift_detectors` barriers must match exactly (including tags). +TEST(detector_error_model, is_equal_up_to_instruction_ordering_shift_tag_mismatch) { + DetectorErrorModel lhs(R"MODEL( + detector(5, 10) D0 + detector(5, 15) D1 + shift_detectors[dead](0, 1) 1 + detector(5, 10) D0 + detector(5, 15) D1 + )MODEL"); + DetectorErrorModel rhs(R"MODEL( + detector(5, 10) D0 + detector(5, 15) D1 + shift_detectors[beef](0, 1) 1 + detector(5, 15) D1 + detector(5, 10) D0 + )MODEL"); + EXPECT_FALSE(lhs.is_equal_up_to_instruction_ordering(rhs)); +} + +/// Segment order-insensitivity is allowed on either side of a matching `shift_detectors` barrier. +TEST(detector_error_model, is_equal_up_to_instruction_ordering_shift_once) { + DetectorErrorModel lhs(R"MODEL( + detector(5, 10) D0 + detector(5, 15) D1 + shift_detectors[dead](0, 1) 1 + detector(5, 10) D0 + detector(5, 15) D1 + )MODEL"); + DetectorErrorModel rhs(R"MODEL( + detector(5, 10) D0 + detector(5, 15) D1 + shift_detectors[dead](0, 1) 1 + detector(5, 15) D1 + detector(5, 10) D0 + )MODEL"); + EXPECT_TRUE(lhs.is_equal_up_to_instruction_ordering(rhs)); +} + +/// Multiple `shift_detectors` barriers create multiple independently order-insensitive segments. +TEST(detector_error_model, is_equal_up_to_instruction_ordering_shift_twice) { + DetectorErrorModel lhs(R"MODEL( + detector(5, 10) D0 + detector(5, 15) D1 + shift_detectors[dead](0, 1) 1 + detector(5, 10) D0 + detector(5, 15) D1 + shift_detectors 1 + error(0.01) D0 + error(0.002) D1 L0 + )MODEL"); + DetectorErrorModel rhs(R"MODEL( + detector(5, 10) D0 + detector(5, 15) D1 + shift_detectors[dead](0, 1) 1 + detector(5, 15) D1 + detector(5, 10) D0 + shift_detectors 1 + error(0.002) D1 L0 + error(0.01) D0 + )MODEL"); + EXPECT_TRUE(lhs.is_equal_up_to_instruction_ordering(rhs)); +} + +/// `repeat` blocks are structural barriers and must match on repetition count. +TEST(detector_error_model, is_equal_up_to_instruction_ordering_repeat_reps_mismatch) { + DetectorErrorModel lhs(R"MODEL( + repeat 3 { + error(0.002) D1 L0 + error(0.01) D0 + shift_detectors 1 + } + )MODEL"); + DetectorErrorModel rhs(R"MODEL( + repeat 2 { + error(0.002) D1 L0 + error(0.01) D0 + shift_detectors 1 + } + )MODEL"); + EXPECT_FALSE(lhs.is_equal_up_to_instruction_ordering(rhs)); +} + +/// Identical `repeat` blocks compare equal without unrolling. +TEST(detector_error_model, is_equal_up_to_instruction_ordering_repeat) { + DetectorErrorModel lhs(R"MODEL( + repeat 3 { + error(0.002) D1 L0 + error(0.01) D0 + shift_detectors 1 + } + )MODEL"); + DetectorErrorModel rhs(R"MODEL( + repeat 3 { + error(0.002) D1 L0 + error(0.01) D0 + shift_detectors 1 + } + )MODEL"); + EXPECT_TRUE(lhs.is_equal_up_to_instruction_ordering(rhs)); +} + +/// A `repeat` block is not equal to an unrolled equivalent (barrier alignment is required). +TEST(detector_error_model, is_equal_up_to_instruction_ordering_repeat_versus_inline) { + DetectorErrorModel lhs(R"MODEL( + repeat 2 { + error(0.1) D0 + shift_detectors 1 + } + )MODEL"); + DetectorErrorModel rhs(R"MODEL( + error(0.1) D0 + shift_detectors 1 + error(0.1) D0 + shift_detectors 1 + )MODEL"); + EXPECT_FALSE(lhs.is_equal_up_to_instruction_ordering(rhs)); +} + +/// Empty segments between consecutive structural barriers are handled correctly. +TEST(detector_error_model, is_equal_up_to_instruction_ordering_consecutive_barriers_ok) { + DetectorErrorModel lhs(R"MODEL( + shift_detectors 1 + shift_detectors 2 + error(0.1) D0 + )MODEL"); + DetectorErrorModel rhs(R"MODEL( + shift_detectors 1 + shift_detectors 2 + error(0.1) D0 + )MODEL"); + EXPECT_TRUE(lhs.is_equal_up_to_instruction_ordering(rhs)); +} + +/// Nested `repeat` blocks compare recursively, allowing order-insensitive segments inside the inner body. +TEST(detector_error_model, is_equal_up_to_instruction_ordering_nested_repeats) { + DetectorErrorModel lhs(R"MODEL( + repeat 2 { + repeat 3 { + error(0.1) D0 + error(0.2) D1 + shift_detectors 1 + } + shift_detectors 5 + } + )MODEL"); + DetectorErrorModel rhs(R"MODEL( + repeat 2 { + repeat 3 { + error(0.2) D1 + error(0.1) D0 + shift_detectors 1 + } + shift_detectors 5 + } + )MODEL"); EXPECT_TRUE(lhs.is_equal_up_to_instruction_ordering(rhs)); } From 16728d46cdd324db9348845206a10a493fefcf8a Mon Sep 17 00:00:00 2001 From: Abbas Bracken Ziad Date: Thu, 7 May 2026 12:09:00 +0100 Subject: [PATCH 4/5] Update docs --- src/stim/dem/detector_error_model.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/stim/dem/detector_error_model.h b/src/stim/dem/detector_error_model.h index ffd68ffed..68bbe6ed6 100644 --- a/src/stim/dem/detector_error_model.h +++ b/src/stim/dem/detector_error_model.h @@ -136,11 +136,12 @@ struct DetectorErrorModel { /// Returns an equivalent detector error model with no repeat blocks or detector_shift instructions. DetectorErrorModel flattened() const; - /// Returns true if the detector error model is equal to the other detector error model up to instruction ordering. + /// Returns true if the detector error model is equal to the other detector error model up to instruction ordering, + /// within segments between structural instructions. Structural instructions (`shift_detectors` and `repeat`) must + /// match exactly and in the same order, and `repeat` bodies are compared recursively. Within each segment, + /// `error`/`detector`/`logical_observable` instructions are compared order-insensitively. /// - /// For example, error(0.01) D0 error(0.002) D1 L0 is considered the same as error(0.002) D1 L0 error(0.01) D0. - /// - /// Note: requires O(n log n) time due to sorting. Prefer `==` when you know the instructions are in the same order. + /// Note: worst-case time is O(n log n) due to sorting segments. bool is_equal_up_to_instruction_ordering(const DetectorErrorModel &other) const; }; From 4a0ca5d423fb66b47459c393d5e84c97a79c6d24 Mon Sep 17 00:00:00 2001 From: Abbas Bracken Ziad Date: Thu, 7 May 2026 12:12:24 +0100 Subject: [PATCH 5/5] Remove numeric include --- src/stim/dem/detector_error_model.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stim/dem/detector_error_model.cc b/src/stim/dem/detector_error_model.cc index 1c87576ea..c2282faa6 100644 --- a/src/stim/dem/detector_error_model.cc +++ b/src/stim/dem/detector_error_model.cc @@ -20,7 +20,6 @@ #include #include #include -#include #include "stim/util_bot/str_util.h"