Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/stim/dem/detector_error_model.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include "stim/dem/detector_error_model.h"

#include <algorithm>
#include <cmath>
#include <iomanip>
#include <limits>
Expand Down Expand Up @@ -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<DemInstruction> 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;
}
Comment on lines +859 to +862
Copy link
Copy Markdown
Contributor Author

@abbrazi abbrazi May 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point, you might ask why I don't simply check op_a != op_b before handling the dedicated shift/repeat cases. For repeats, that would result in unexpected failures, i.e., where the the DEMs have the same repeat blocks but they are stored in different orders in the backing blocks arrays. That seems like an implementation detail that should not result in this member function reporting inequality.

// 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);
}
8 changes: 8 additions & 0 deletions src/stim/dem/detector_error_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
229 changes: 229 additions & 0 deletions src/stim/dem/detector_error_model.test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Loading