diff --git a/doc/gates.md b/doc/gates.md
index 74718d29..d31f36d6 100644
--- a/doc/gates.md
+++ b/doc/gates.md
@@ -3043,7 +3043,7 @@ Decomposition (into H, S, CX, M, R):
-### The 'SPP' Instruction
+### The 'SPP' Gate
The generalized S gate. Phases the -1 eigenspace of Pauli product observables by i.
@@ -3109,7 +3109,7 @@ Decomposition (into H, S, CX, M, R):
-### The 'SPP_DAG' Instruction
+### The 'SPP_DAG' Gate
The generalized S_DAG gate. Phases the -1 eigenspace of Pauli product observables by -i.
diff --git a/src/stim/circuit/circuit_pybind_test.py b/src/stim/circuit/circuit_pybind_test.py
index 89a92e8b..d6a8fec0 100644
--- a/src/stim/circuit/circuit_pybind_test.py
+++ b/src/stim/circuit/circuit_pybind_test.py
@@ -864,7 +864,6 @@ def test_likeliest_error_sat_problem():
DETECTOR rec[-1] rec[-2]
""")
sat_str = c.likeliest_error_sat_problem(quantization=100)
- print(sat_str)
assert sat_str == 'p wcnf 2 4 401\n18 -1 0\n100 -2 0\n401 -1 0\n401 2 0\n'
@@ -1902,3 +1901,14 @@ def test_pop():
def test_circuit_create_with_odd_cx():
with pytest.raises(ValueError, match="0, 1, 2"):
stim.Circuit("CX 0 1 2")
+
+
+def test_to_tableau():
+ assert stim.Circuit().to_tableau() == stim.Tableau(0)
+ assert stim.Circuit("QUBIT_COORDS 0").to_tableau() == stim.Tableau(1)
+ assert stim.Circuit("I 0").to_tableau() == stim.Tableau(1)
+ assert stim.Circuit("H 0").to_tableau() == stim.Tableau.from_named_gate("H")
+ assert stim.Circuit("CX 0 1").to_tableau() == stim.Tableau.from_named_gate("CX")
+ assert stim.Circuit("SPP Z0").to_tableau() == stim.Tableau.from_named_gate("S")
+ assert stim.Circuit("SPP X0").to_tableau() == stim.Tableau.from_named_gate("SQRT_X")
+ assert stim.Circuit("SPP_DAG Y0*Y1").to_tableau() == stim.Tableau.from_named_gate("SQRT_YY_DAG")
diff --git a/src/stim/cmd/command_help.cc b/src/stim/cmd/command_help.cc
index 9eb214de..d0a600c6 100644
--- a/src/stim/cmd/command_help.cc
+++ b/src/stim/cmd/command_help.cc
@@ -290,7 +290,7 @@ void print_stabilizer_generators(Acc &out, const Gate &gate) {
}
void print_bloch_vector(Acc &out, const Gate &gate) {
- if (!(gate.flags & GATE_IS_UNITARY) || (gate.flags & GATE_TARGETS_PAIRS)) {
+ if (!(gate.flags & GATE_IS_UNITARY) || !(gate.flags & GATE_IS_SINGLE_QUBIT_GATE)) {
return;
}
@@ -343,7 +343,7 @@ void print_bloch_vector(Acc &out, const Gate &gate) {
}
void print_unitary_matrix(Acc &out, const Gate &gate) {
- if (!(gate.flags & GATE_IS_UNITARY)) {
+ if (!gate.has_known_unitary_matrix()) {
return;
}
auto matrix = gate.unitary();
diff --git a/src/stim/gates/gate_data_pauli_product.cc b/src/stim/gates/gate_data_pauli_product.cc
index 30505bf2..22c392f4 100644
--- a/src/stim/gates/gate_data_pauli_product.cc
+++ b/src/stim/gates/gate_data_pauli_product.cc
@@ -99,7 +99,7 @@ S 1
.id = GateType::SPP,
.best_candidate_inverse_id = GateType::SPP_DAG,
.arg_count = 0,
- .flags = (GateFlags)(GATE_TARGETS_PAULI_STRING | GATE_TARGETS_COMBINERS),
+ .flags = (GateFlags)(GATE_TARGETS_PAULI_STRING | GATE_TARGETS_COMBINERS | GATE_IS_UNITARY),
.category = "P_Generalized Pauli Product Gates",
.help = R"MARKDOWN(
The generalized S gate. Phases the -1 eigenspace of Pauli product observables by i.
@@ -174,7 +174,7 @@ CX 2 1
.id = GateType::SPP_DAG,
.best_candidate_inverse_id = GateType::SPP,
.arg_count = 0,
- .flags = (GateFlags)(GATE_TARGETS_PAULI_STRING | GATE_TARGETS_COMBINERS),
+ .flags = (GateFlags)(GATE_TARGETS_PAULI_STRING | GATE_TARGETS_COMBINERS | GATE_IS_UNITARY),
.category = "P_Generalized Pauli Product Gates",
.help = R"MARKDOWN(
The generalized S_DAG gate. Phases the -1 eigenspace of Pauli product observables by -i.
diff --git a/src/stim/gates/gates.cc b/src/stim/gates/gates.cc
index 2ee52a2b..a63c2992 100644
--- a/src/stim/gates/gates.cc
+++ b/src/stim/gates/gates.cc
@@ -267,6 +267,10 @@ std::array Gate::to_axis_angle() const {
return {rx, ry, rz, acosf(rs) * 2};
}
+bool Gate::has_known_unitary_matrix() const {
+ return (flags & GateFlags::GATE_IS_UNITARY) && (flags & (GateFlags::GATE_IS_SINGLE_QUBIT_GATE | GateFlags::GATE_TARGETS_PAIRS));
+}
+
std::vector>> Gate::unitary() const {
if (unitary_data.size() != 2 && unitary_data.size() != 4) {
throw std::out_of_range(std::string(name) + " doesn't have 1q or 2q unitary data.");
diff --git a/src/stim/gates/gates.h b/src/stim/gates/gates.h
index 32fba97b..ad145b75 100644
--- a/src/stim/gates/gates.h
+++ b/src/stim/gates/gates.h
@@ -258,7 +258,7 @@ struct Gate {
template
std::vector> flows() const {
- if (flags & GateFlags::GATE_IS_UNITARY) {
+ if (has_known_unitary_matrix()) {
auto t = tableau();
if (flags & GateFlags::GATE_TARGETS_PAIRS) {
return {
@@ -285,6 +285,12 @@ struct Gate {
bool is_symmetric() const;
GateType hadamard_conjugated(bool ignoring_sign) const;
+ /// Determines if the gate has a specified unitary matrix.
+ ///
+ /// Some unitary gates, such as SPP, don't have a specified matrix because the
+ /// matrix depends crucially on the targets.
+ bool has_known_unitary_matrix() const;
+
/// Converts a single qubit unitary gate into an euler-angles rotation.
///
/// Returns:
diff --git a/src/stim/gates/gates.pybind.cc b/src/stim/gates/gates.pybind.cc
index df54e498..af2b0c36 100644
--- a/src/stim/gates/gates.pybind.cc
+++ b/src/stim/gates/gates.pybind.cc
@@ -50,7 +50,7 @@ pybind11::object gate_tableau(const Gate &self) {
return pybind11::none();
}
pybind11::object gate_unitary_matrix(const Gate &self) {
- if (self.flags & GATE_IS_UNITARY) {
+ if (self.has_known_unitary_matrix()) {
auto r = self.unitary();
auto n = r.size();
std::complex *buffer = new std::complex[n * n];
diff --git a/src/stim/gates/gates.test.cc b/src/stim/gates/gates.test.cc
index 1fe5c6b3..5d76180a 100644
--- a/src/stim/gates/gates.test.cc
+++ b/src/stim/gates/gates.test.cc
@@ -144,7 +144,7 @@ TEST_EACH_WORD_SIZE_W(gate_data, decompositions_are_correct, {
TEST_EACH_WORD_SIZE_W(gate_data, unitary_inverses_are_correct, {
for (const auto &g : GATE_DATA.items) {
- if (g.flags & GATE_IS_UNITARY) {
+ if (g.has_known_unitary_matrix()) {
auto g_t_inv = g.tableau().inverse(false);
auto g_inv_t = GATE_DATA[g.best_candidate_inverse_id].tableau();
EXPECT_EQ(g_t_inv, g_inv_t) << g.name;
diff --git a/src/stim/gates/gates_test.py b/src/stim/gates/gates_test.py
index aae2b00c..400f5597 100644
--- a/src/stim/gates/gates_test.py
+++ b/src/stim/gates/gates_test.py
@@ -59,14 +59,17 @@ def test_gate_data_repr():
def test_gate_data_inverse():
for v in stim.gate_data().values():
assert v.is_unitary == (v.inverse is not None)
- if v.is_unitary:
- assert np.allclose(v.unitary_matrix.conj().T, v.inverse.unitary_matrix, atol=1e-6)
+ matrix = v.unitary_matrix
+ if matrix is not None:
+ assert v.is_unitary
+ assert np.allclose(matrix.conj().T, v.inverse.unitary_matrix, atol=1e-6)
assert v.inverse == v.generalized_inverse
assert stim.gate_data('H').inverse == stim.gate_data('H')
assert stim.gate_data('S').inverse == stim.gate_data('S_DAG')
assert stim.gate_data('M').inverse is None
assert stim.gate_data('CXSWAP').inverse == stim.gate_data('SWAPCX')
+ assert stim.gate_data('SPP').inverse == stim.gate_data('SPP_DAG')
assert stim.gate_data('S').generalized_inverse == stim.gate_data('S_DAG')
assert stim.gate_data('M').generalized_inverse == stim.gate_data('M')
diff --git a/src/stim/py/stim_pybind_test.py b/src/stim/py/stim_pybind_test.py
index 26ba4c46..368dca93 100644
--- a/src/stim/py/stim_pybind_test.py
+++ b/src/stim/py/stim_pybind_test.py
@@ -136,6 +136,13 @@ def test_main_write_to_file():
assert "Generated repetition_code" in f.read()
+def test_main_help(capsys):
+ assert stim.main(command_line_args=["help"]) == 0
+ captured = capsys.readouterr()
+ assert captured.err == ""
+ assert 'Available stim commands' in captured.out
+
+
def test_main_redirects_stdout(capsys):
assert stim.main(command_line_args=[
"gen",
diff --git a/src/stim/simulators/error_analyzer.test.cc b/src/stim/simulators/error_analyzer.test.cc
index c8d7d49c..9b8ee699 100644
--- a/src/stim/simulators/error_analyzer.test.cc
+++ b/src/stim/simulators/error_analyzer.test.cc
@@ -307,7 +307,7 @@ TEST_EACH_WORD_SIZE_W(ErrorAnalyzer, unitary_gates_match_frame_simulator, {
data.push_back(GateTarget::qubit(k));
}
for (const auto &gate : GATE_DATA.items) {
- if (gate.flags & GATE_IS_UNITARY) {
+ if (gate.has_known_unitary_matrix()) {
e.undo_gate({gate.id, {}, data});
f.do_gate({gate.inverse().id, {}, data});
for (size_t q = 0; q < 16; q++) {
diff --git a/src/stim/simulators/frame_simulator.test.cc b/src/stim/simulators/frame_simulator.test.cc
index df071bdc..c01df1b3 100644
--- a/src/stim/simulators/frame_simulator.test.cc
+++ b/src/stim/simulators/frame_simulator.test.cc
@@ -97,7 +97,7 @@ bool is_bulk_frame_operation_consistent_with_tableau(const Gate &gate) {
TEST_EACH_WORD_SIZE_W(FrameSimulator, bulk_operations_consistent_with_tableau_data, {
for (const auto &gate : GATE_DATA.items) {
- if (gate.flags & GATE_IS_UNITARY) {
+ if (gate.has_known_unitary_matrix()) {
EXPECT_TRUE(is_bulk_frame_operation_consistent_with_tableau(gate)) << gate.name;
}
}
diff --git a/src/stim/simulators/graph_simulator.test.cc b/src/stim/simulators/graph_simulator.test.cc
index 0f57cae1..d5b79ba0 100644
--- a/src/stim/simulators/graph_simulator.test.cc
+++ b/src/stim/simulators/graph_simulator.test.cc
@@ -257,7 +257,7 @@ TEST(graph_simulator, all_unitary_gates_work) {
SpanRef t1 = t2;
t1.ptr_end--;
for (const auto &gate : GATE_DATA.items) {
- if (!(gate.flags & GATE_IS_UNITARY)) {
+ if (!gate.has_known_unitary_matrix()) {
continue;
}
Circuit circuit;
diff --git a/src/stim/simulators/sparse_rev_frame_tracker.test.cc b/src/stim/simulators/sparse_rev_frame_tracker.test.cc
index ac26753f..0101a8ce 100644
--- a/src/stim/simulators/sparse_rev_frame_tracker.test.cc
+++ b/src/stim/simulators/sparse_rev_frame_tracker.test.cc
@@ -144,7 +144,7 @@ static std::vector qubit_targets(const std::vector &target
TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, fuzz_all_unitary_gates_vs_tableau, {
auto rng = INDEPENDENT_TEST_RNG();
for (const auto &gate : GATE_DATA.items) {
- if (gate.flags & GATE_IS_UNITARY) {
+ if (gate.has_known_unitary_matrix()) {
size_t n = (gate.flags & GATE_TARGETS_PAIRS) ? 2 : 1;
SparseUnsignedRevFrameTracker tracker_gate(n + 3, 0, 0);
for (size_t q = 0; q < n; q++) {
diff --git a/src/stim/simulators/tableau_simulator.test.cc b/src/stim/simulators/tableau_simulator.test.cc
index 521ab832..2f256df6 100644
--- a/src/stim/simulators/tableau_simulator.test.cc
+++ b/src/stim/simulators/tableau_simulator.test.cc
@@ -311,7 +311,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, unitary_gates_consistent_with_tableau_da
auto t = Tableau::random(10, rng);
TableauSimulator sim(INDEPENDENT_TEST_RNG(), 10);
for (const auto &gate : GATE_DATA.items) {
- if (!(gate.flags & GATE_IS_UNITARY)) {
+ if (!gate.has_known_unitary_matrix()) {
continue;
}
sim.inv_state = t;
diff --git a/src/stim/stabilizers/pauli_string_ref.test.cc b/src/stim/stabilizers/pauli_string_ref.test.cc
index d7565244..a51ad40e 100644
--- a/src/stim/stabilizers/pauli_string_ref.test.cc
+++ b/src/stim/stabilizers/pauli_string_ref.test.cc
@@ -71,7 +71,7 @@ TEST_EACH_WORD_SIZE_W(pauli_string, do_instruction_agrees_with_tableau_sim, {
sim.inv_state = Tableau::random(sim.inv_state.num_qubits, sim.rng);
for (const auto &gate : GATE_DATA.items) {
- if (gate.flags & GATE_IS_UNITARY) {
+ if (gate.has_known_unitary_matrix()) {
check_pauli_string_do_instruction_agrees_with_tableau_sim(gate, sim);
}
}
diff --git a/src/stim/stabilizers/tableau.test.cc b/src/stim/stabilizers/tableau.test.cc
index e6baee63..f76206f4 100644
--- a/src/stim/stabilizers/tableau.test.cc
+++ b/src/stim/stabilizers/tableau.test.cc
@@ -184,7 +184,7 @@ TEST_EACH_WORD_SIZE_W(tableau, str, {
TEST_EACH_WORD_SIZE_W(tableau, gate_tableau_data_vs_unitary_data, {
for (const auto &gate : GATE_DATA.items) {
- if (gate.flags & GATE_IS_UNITARY) {
+ if (gate.has_known_unitary_matrix()) {
EXPECT_TRUE(tableau_agrees_with_unitary(gate.tableau(), gate.unitary())) << gate.name;
}
}
@@ -192,7 +192,7 @@ TEST_EACH_WORD_SIZE_W(tableau, gate_tableau_data_vs_unitary_data, {
TEST_EACH_WORD_SIZE_W(tableau, inverse_data, {
for (const auto &gate : GATE_DATA.items) {
- if (gate.flags & GATE_IS_UNITARY) {
+ if (gate.has_known_unitary_matrix()) {
auto &inv_gate = gate.inverse();
auto tab = gate.tableau();
auto inv_tab = inv_gate.tableau();
@@ -1163,7 +1163,7 @@ TEST_EACH_WORD_SIZE_W(tableau, unitary_big_endian, {
TEST_EACH_WORD_SIZE_W(tableau, unitary_vs_gate_data, {
for (const auto &gate : GATE_DATA.items) {
- if (gate.flags & GATE_IS_UNITARY) {
+ if (gate.has_known_unitary_matrix()) {
std::vector> flat_expected;
for (const auto &row : gate.unitary()) {
flat_expected.insert(flat_expected.end(), row.begin(), row.end());
diff --git a/src/stim/util_top/stabilizers_vs_amplitudes.test.cc b/src/stim/util_top/stabilizers_vs_amplitudes.test.cc
index c207d324..9bebf6f4 100644
--- a/src/stim/util_top/stabilizers_vs_amplitudes.test.cc
+++ b/src/stim/util_top/stabilizers_vs_amplitudes.test.cc
@@ -10,7 +10,7 @@ using namespace stim;
TEST_EACH_WORD_SIZE_W(conversions, unitary_to_tableau_vs_gate_data, {
for (const auto &gate : GATE_DATA.items) {
- if (gate.flags & GATE_IS_UNITARY) {
+ if (gate.has_known_unitary_matrix()) {
EXPECT_EQ(unitary_to_tableau(gate.unitary(), true), gate.tableau()) << gate.name;
}
}
@@ -20,7 +20,7 @@ TEST_EACH_WORD_SIZE_W(conversions, tableau_to_unitary_vs_gate_data, {
VectorSimulator v1(2);
VectorSimulator v2(2);
for (const auto &gate : GATE_DATA.items) {
- if (gate.flags & GATE_IS_UNITARY) {
+ if (gate.has_known_unitary_matrix()) {
auto actual = tableau_to_unitary(gate.tableau(), true);
auto expected = gate.unitary();
v1.state.clear();