diff --git a/src/stim/dem/detector_error_model.cc b/src/stim/dem/detector_error_model.cc index 6f663dbf..c2282faa 100644 --- a/src/stim/dem/detector_error_model.cc +++ b/src/stim/dem/detector_error_model.cc @@ -16,6 +16,7 @@ #include "stim/dem/detector_error_model.h" +#include #include #include #include @@ -793,3 +794,86 @@ DetectorErrorModel DetectorErrorModel::without_tags() const { } return result; } + +bool is_structural(DemInstructionType type) { + return type == DemInstructionType::DEM_SHIFT_DETECTORS || type == DemInstructionType::DEM_REPEAT_BLOCK; +} + +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; + }; + + 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; + } + + 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 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 op + index_in_a++; + index_in_b++; + continue; + } + } +} + +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 0f5d53d5..68bbe6ed 100644 --- a/src/stim/dem/detector_error_model.h +++ b/src/stim/dem/detector_error_model.h @@ -135,6 +135,14 @@ 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, + /// 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. + /// + /// Note: worst-case time is O(n log n) due to sorting segments. + 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 429642c9..e53c05a5 100644 --- a/src/stim/dem/detector_error_model.test.cc +++ b/src/stim/dem/detector_error_model.test.cc @@ -894,3 +894,232 @@ 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")); } + +/// 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[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[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)); +}