Skip to content

Commit 616bbcd

Browse files
authored
Fix undefined behavior in cudaq::qec::to_parity_matrix (#87)
Using `const_cast` to remove the const-ness of a parameter results in undefined behavior if the incoming parameter was truly a `const` value. Given that the `to_parity_matrix` function was called by `const` class member functions with member variables as parameters, this was indeed unsafe behavior. While I have not seen any direct badness result from this, I think it's best to pre-emptively clean this up. --------- Signed-off-by: Ben Howe <[email protected]>
1 parent 9bc4cbb commit 616bbcd

File tree

4 files changed

+69
-38
lines changed

4 files changed

+69
-38
lines changed

libs/qec/lib/codes/repetition.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ repetition::repetition(const heterogeneous_map &options) : code() {
4444
Lz = Lz * cudaq::spin::i(get_num_data_qubits() - 1);
4545

4646
m_pauli_observables.push_back(Lz);
47+
48+
// Sort now to avoid repeated sorts later.
49+
sortStabilizerOps(m_stabilizers);
50+
sortStabilizerOps(m_pauli_observables);
4751
}
4852

4953
/// @brief Register the repetition code type

libs/qec/lib/codes/steane.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ steane::steane(const heterogeneous_map &options) : code() {
3030
m_stabilizers = fromPauliWords(
3131
{"XXXXIII", "IXXIXXI", "IIXXIXX", "ZZZZIII", "IZZIZZI", "IIZZIZZ"});
3232
m_pauli_observables = fromPauliWords({"IIIIXXX", "IIIIZZZ"});
33+
34+
// Sort now to avoid repeated sorts later.
35+
sortStabilizerOps(m_stabilizers);
36+
sortStabilizerOps(m_pauli_observables);
3337
}
3438

3539
/// @brief Register the Steane code type

libs/qec/lib/codes/surface_code.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,10 @@ surface_code::surface_code(const heterogeneous_map &options) : code() {
358358
m_stabilizers = grid.get_spin_op_stabilizers();
359359
m_pauli_observables = grid.get_spin_op_observables();
360360

361+
// Sort now to avoid repeated sorts later.
362+
sortStabilizerOps(m_stabilizers);
363+
sortStabilizerOps(m_pauli_observables);
364+
361365
operation_encodings.insert(std::make_pair(operation::x, x));
362366
operation_encodings.insert(std::make_pair(operation::z, z));
363367
operation_encodings.insert(std::make_pair(operation::cx, cx));

libs/qec/lib/stabilizer_utils.cpp

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
/****************************************************************-*- C++ -*-****
2-
* Copyright (c) 2024 NVIDIA Corporation & Affiliates. *
1+
/*******************************************************************************
2+
* Copyright (c) 2024 - 2025 NVIDIA Corporation & Affiliates. *
33
* All rights reserved. *
44
* *
55
* This source code and the accompanying materials are made available under *
@@ -8,29 +8,36 @@
88
#include "cudaq/qec/stabilizer_utils.h"
99

1010
namespace cudaq::qec {
11+
// Sort the stabilizers by the occurrence of 'Z' first, then by 'X'.
12+
// An earlier occurrence is considered "less".
13+
// Example: a = "ZZX", b = "XXZ" -> a < b
14+
static bool spinOpComparator(const cudaq::spin_op &a, const cudaq::spin_op &b) {
15+
auto astr = a.to_string(false);
16+
auto bstr = b.to_string(false);
17+
auto zIdxA = astr.find_first_of("Z");
18+
auto zIdxB = bstr.find_first_of("Z");
19+
if (zIdxA == std::string::npos) {
20+
if (zIdxB != std::string::npos)
21+
return false;
22+
23+
// No Z in either, must both contain a X
24+
auto xIdxA = astr.find_first_of("X");
25+
return xIdxA < bstr.find_first_of("X");
26+
}
27+
28+
// First contains a Z
29+
if (zIdxB == std::string::npos)
30+
return true;
31+
32+
return zIdxA < zIdxB;
33+
}
34+
35+
static bool isStabilizerSorted(const std::vector<cudaq::spin_op> &ops) {
36+
return std::is_sorted(ops.begin(), ops.end(), spinOpComparator);
37+
}
38+
1139
void sortStabilizerOps(std::vector<cudaq::spin_op> &ops) {
12-
// Sort the stabilizers, z first, then x
13-
std::sort(ops.begin(), ops.end(),
14-
[](const cudaq::spin_op &a, const cudaq::spin_op &b) {
15-
auto astr = a.to_string(false);
16-
auto bstr = b.to_string(false);
17-
auto zIdxA = astr.find_first_of("Z");
18-
auto zIdxB = bstr.find_first_of("Z");
19-
if (zIdxA == std::string::npos) {
20-
if (zIdxB != std::string::npos)
21-
return false;
22-
23-
// No Z in either, must both contain a X
24-
auto xIdxA = astr.find_first_of("X");
25-
return xIdxA < bstr.find_first_of("X");
26-
}
27-
28-
// First contains a Z
29-
if (zIdxB == std::string::npos)
30-
return true;
31-
32-
return zIdxA < zIdxB;
33-
});
40+
std::sort(ops.begin(), ops.end(), spinOpComparator);
3441
}
3542

3643
// Need to push into the form
@@ -42,14 +49,25 @@ to_parity_matrix(const std::vector<cudaq::spin_op> &stabilizers,
4249
if (stabilizers.empty())
4350
return cudaqx::tensor<uint8_t>();
4451

45-
sortStabilizerOps(const_cast<std::vector<cudaq::spin_op> &>(stabilizers));
52+
// Stabilizers must be sorted prior to use, but they are "const" into this
53+
// function. First check to see if they are already sorted, and if so, proceed
54+
// without making any copies. Otherwise make a copy, sort the copy, and make
55+
// the downstream code use the sorted copy.
56+
const std::vector<cudaq::spin_op> *p_stabilizers = &stabilizers;
57+
std::vector<cudaq::spin_op> stabilizers_copy; // only use if necessary
58+
if (!isStabilizerSorted(stabilizers)) {
59+
// Make a copy and sort them
60+
stabilizers_copy = stabilizers;
61+
sortStabilizerOps(stabilizers_copy);
62+
p_stabilizers = &stabilizers_copy;
63+
}
4664

4765
if (type == stabilizer_type::XZ) {
48-
auto numQubits = stabilizers[0].num_qubits();
49-
cudaqx::tensor<uint8_t> t({stabilizers.size(), 2 * numQubits});
66+
auto numQubits = (*p_stabilizers)[0].num_qubits();
67+
cudaqx::tensor<uint8_t> t({p_stabilizers->size(), 2 * numQubits});
5068
// Start by counting the number of Z spin_ops
5169
std::size_t numZRows = 0;
52-
for (auto &op : stabilizers)
70+
for (auto &op : *p_stabilizers)
5371
if (op.to_string(false).find("Z") != std::string::npos)
5472
numZRows++;
5573
else
@@ -58,16 +76,16 @@ to_parity_matrix(const std::vector<cudaq::spin_op> &stabilizers,
5876
// Need to shift Z bits left
5977
for (std::size_t row = 0; row < numZRows; row++) {
6078
for (std::size_t i = numQubits; i < 2 * numQubits; i++) {
61-
if (stabilizers[row].get_raw_data().first[0][i])
79+
if ((*p_stabilizers)[row].get_raw_data().first[0][i])
6280
t.at({row, i - numQubits}) = 1;
6381
}
6482
}
6583

66-
auto numXRows = stabilizers.size() - numZRows;
84+
auto numXRows = p_stabilizers->size() - numZRows;
6785

6886
for (std::size_t row = 0; row < numXRows; row++) {
6987
for (std::size_t i = 0; i < numQubits; i++) {
70-
if (stabilizers[numZRows + row].get_raw_data().first[0][i])
88+
if ((*p_stabilizers)[numZRows + row].get_raw_data().first[0][i])
7189
t.at({numZRows + row, i + numQubits}) = 1;
7290
}
7391
}
@@ -76,10 +94,10 @@ to_parity_matrix(const std::vector<cudaq::spin_op> &stabilizers,
7694
}
7795

7896
if (type == stabilizer_type::Z) {
79-
auto numQubits = stabilizers[0].num_qubits();
97+
auto numQubits = (*p_stabilizers)[0].num_qubits();
8098
// Start by counting the number of Z spin_ops
8199
std::size_t numZRows = 0;
82-
for (auto &op : stabilizers)
100+
for (auto &op : *p_stabilizers)
83101
if (op.to_string(false).find("Z") != std::string::npos)
84102
numZRows++;
85103
else
@@ -91,32 +109,32 @@ to_parity_matrix(const std::vector<cudaq::spin_op> &stabilizers,
91109
cudaqx::tensor<uint8_t> ret({numZRows, numQubits});
92110
for (std::size_t row = 0; row < numZRows; row++) {
93111
for (std::size_t i = numQubits; i < 2 * numQubits; i++) {
94-
if (stabilizers[row].get_raw_data().first[0][i])
112+
if ((*p_stabilizers)[row].get_raw_data().first[0][i])
95113
ret.at({row, i - numQubits}) = 1;
96114
}
97115
}
98116

99117
return ret;
100118
}
101119

102-
auto numQubits = stabilizers[0].num_qubits();
120+
auto numQubits = (*p_stabilizers)[0].num_qubits();
103121
// Start by counting the number of Z spin_ops
104122
std::size_t numZRows = 0;
105-
for (auto &op : stabilizers)
123+
for (auto &op : *p_stabilizers)
106124
if (op.to_string(false).find("Z") != std::string::npos)
107125
numZRows++;
108126
else
109127
break;
110128

111-
auto numXRows = stabilizers.size() - numZRows;
129+
auto numXRows = p_stabilizers->size() - numZRows;
112130

113131
if (numXRows == 0)
114132
return cudaqx::tensor<uint8_t>();
115133

116134
cudaqx::tensor<uint8_t> ret({numXRows, numQubits});
117135
for (std::size_t row = 0; row < numXRows; row++) {
118136
for (std::size_t i = 0; i < numQubits; i++) {
119-
if (stabilizers[numZRows + row].get_raw_data().first[0][i])
137+
if ((*p_stabilizers)[numZRows + row].get_raw_data().first[0][i])
120138
ret.at({row, i}) = 1;
121139
}
122140
}
@@ -130,6 +148,7 @@ cudaqx::tensor<uint8_t> to_parity_matrix(const std::vector<std::string> &words,
130148
std::vector<cudaq::spin_op> ops;
131149
for (auto &os : words)
132150
ops.emplace_back(cudaq::spin_op::from_word(os));
151+
sortStabilizerOps(ops);
133152
return to_parity_matrix(ops, type);
134153
}
135154
} // namespace cudaq::qec

0 commit comments

Comments
 (0)