From 1cd7c9f8642006e4f32ed381434647d435255f19 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 24 Nov 2023 23:53:23 -0800 Subject: [PATCH] Add `stim.Tableau.to_circuit("graph_state")` (#669) - Add `stim::GraphSimulator` with support for unitary operations Fixes https://github.com/quantumlib/Stim/issues/647 --- doc/python_api_reference_vDev.md | 72 ++- doc/stim.pyi | 72 ++- file_lists/source_files_no_main | 1 + file_lists/test_files | 1 + glue/python/src/stim/__init__.pyi | 72 ++- src/stim.h | 1 + src/stim/simulators/graph_simulator.cc | 514 ++++++++++++++++++++ src/stim/simulators/graph_simulator.h | 78 +++ src/stim/simulators/graph_simulator.test.cc | 320 ++++++++++++ src/stim/stabilizers/conversions.h | 22 +- src/stim/stabilizers/conversions.inl | 18 +- src/stim/stabilizers/tableau.pybind.cc | 74 ++- src/stim/stabilizers/tableau_pybind_test.py | 9 + 13 files changed, 1166 insertions(+), 88 deletions(-) create mode 100644 src/stim/simulators/graph_simulator.cc create mode 100644 src/stim/simulators/graph_simulator.h create mode 100644 src/stim/simulators/graph_simulator.test.cc diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 1791d4f5c..8d31a8fac 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -10092,7 +10092,6 @@ def then( # (in class stim.Tableau) def to_circuit( self, - *, method: str = 'elimination', ) -> stim.Circuit: """Synthesizes a circuit that implements the tableau's Clifford operation. @@ -10107,6 +10106,20 @@ def to_circuit( Circuit qubit count: n Circuit operation count: O(n^2) Circuit depth: O(n^2) + "graph_state": Prepares the tableau's state using a graph state circuit. + Gate set: RX, CZ, H, S, X, Y, Z + Circuit qubit count: n + Circuit operation count: O(n^2) + + The circuit will be made up of three layers: + 1. An RX layer initializing all qubits. + 2. A CZ layer coupling the qubits. + (Each CZ is an edge in the graph state.) + 3. A single qubit rotation layer. + + Note: "graph_state" treats the tableau as a state instead of as a + Clifford operation. It will preserve the set of stabilizers, but + not the exact choice of generators. Returns: The synthesized circuit. @@ -10115,35 +10128,52 @@ def to_circuit( >>> import stim >>> tableau = stim.Tableau.from_conjugated_generators( ... xs=[ - ... stim.PauliString("-_YZ"), - ... stim.PauliString("-YY_"), - ... stim.PauliString("-XZX"), + ... stim.PauliString("+YZ__"), + ... stim.PauliString("-Y_XY"), + ... stim.PauliString("+___Y"), + ... stim.PauliString("+YZX_"), ... ], ... zs=[ - ... stim.PauliString("+Y_Y"), - ... stim.PauliString("-_XY"), - ... stim.PauliString("-Y__"), + ... stim.PauliString("+XZYY"), + ... stim.PauliString("-XYX_"), + ... stim.PauliString("-ZXXZ"), + ... stim.PauliString("+XXZ_"), ... ], ... ) - >>> tableau.to_circuit(method="elimination") + + >>> tableau.to_circuit() stim.Circuit(''' - CX 2 0 0 2 2 0 S 0 - H 0 - S 0 - H 1 - CX 0 1 0 2 - H 1 2 - CX 1 0 2 0 2 1 1 2 2 1 + H 0 1 3 + CX 0 1 0 2 0 3 + S 1 3 + H 1 3 + CX 1 0 3 0 3 1 1 3 3 1 H 1 - S 1 2 - H 2 - CX 2 1 - S 2 - H 0 1 2 + S 1 + CX 1 3 + H 2 3 + CX 2 1 3 1 3 2 2 3 3 2 + H 3 + CX 2 3 + S 3 + H 3 0 1 2 S 0 0 1 1 2 2 H 0 1 2 - S 1 1 2 2 + S 3 3 + ''') + + >>> tableau.to_circuit("graph_state") + stim.Circuit(''' + RX 0 1 2 3 + TICK + CZ 0 3 1 2 1 3 + TICK + X 0 1 + Z 2 + S 2 3 + H 3 + S 3 ''') """ ``` diff --git a/doc/stim.pyi b/doc/stim.pyi index adcdecd69..b10cf74e6 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -7828,7 +7828,6 @@ class Tableau: """ def to_circuit( self, - *, method: str = 'elimination', ) -> stim.Circuit: """Synthesizes a circuit that implements the tableau's Clifford operation. @@ -7843,6 +7842,20 @@ class Tableau: Circuit qubit count: n Circuit operation count: O(n^2) Circuit depth: O(n^2) + "graph_state": Prepares the tableau's state using a graph state circuit. + Gate set: RX, CZ, H, S, X, Y, Z + Circuit qubit count: n + Circuit operation count: O(n^2) + + The circuit will be made up of three layers: + 1. An RX layer initializing all qubits. + 2. A CZ layer coupling the qubits. + (Each CZ is an edge in the graph state.) + 3. A single qubit rotation layer. + + Note: "graph_state" treats the tableau as a state instead of as a + Clifford operation. It will preserve the set of stabilizers, but + not the exact choice of generators. Returns: The synthesized circuit. @@ -7851,35 +7864,52 @@ class Tableau: >>> import stim >>> tableau = stim.Tableau.from_conjugated_generators( ... xs=[ - ... stim.PauliString("-_YZ"), - ... stim.PauliString("-YY_"), - ... stim.PauliString("-XZX"), + ... stim.PauliString("+YZ__"), + ... stim.PauliString("-Y_XY"), + ... stim.PauliString("+___Y"), + ... stim.PauliString("+YZX_"), ... ], ... zs=[ - ... stim.PauliString("+Y_Y"), - ... stim.PauliString("-_XY"), - ... stim.PauliString("-Y__"), + ... stim.PauliString("+XZYY"), + ... stim.PauliString("-XYX_"), + ... stim.PauliString("-ZXXZ"), + ... stim.PauliString("+XXZ_"), ... ], ... ) - >>> tableau.to_circuit(method="elimination") + + >>> tableau.to_circuit() stim.Circuit(''' - CX 2 0 0 2 2 0 - S 0 - H 0 S 0 + H 0 1 3 + CX 0 1 0 2 0 3 + S 1 3 + H 1 3 + CX 1 0 3 0 3 1 1 3 3 1 H 1 - CX 0 1 0 2 - H 1 2 - CX 1 0 2 0 2 1 1 2 2 1 - H 1 - S 1 2 - H 2 - CX 2 1 - S 2 - H 0 1 2 + S 1 + CX 1 3 + H 2 3 + CX 2 1 3 1 3 2 2 3 3 2 + H 3 + CX 2 3 + S 3 + H 3 0 1 2 S 0 0 1 1 2 2 H 0 1 2 - S 1 1 2 2 + S 3 3 + ''') + + >>> tableau.to_circuit("graph_state") + stim.Circuit(''' + RX 0 1 2 3 + TICK + CZ 0 3 1 2 1 3 + TICK + X 0 1 + Z 2 + S 2 3 + H 3 + S 3 ''') """ def to_numpy( diff --git a/file_lists/source_files_no_main b/file_lists/source_files_no_main index a0b00cf76..ffaf8a6ed 100644 --- a/file_lists/source_files_no_main +++ b/file_lists/source_files_no_main @@ -80,6 +80,7 @@ src/stim/search/hyper/search_state.cc src/stim/simulators/error_analyzer.cc src/stim/simulators/error_matcher.cc src/stim/simulators/force_streaming.cc +src/stim/simulators/graph_simulator.cc src/stim/simulators/matched_error.cc src/stim/simulators/sparse_rev_frame_tracker.cc src/stim/simulators/transform_without_feedback.cc diff --git a/file_lists/test_files b/file_lists/test_files index 42fc44604..44306ccfc 100644 --- a/file_lists/test_files +++ b/file_lists/test_files @@ -63,6 +63,7 @@ src/stim/simulators/error_analyzer.test.cc src/stim/simulators/error_matcher.test.cc src/stim/simulators/frame_simulator.test.cc src/stim/simulators/frame_simulator_util.test.cc +src/stim/simulators/graph_simulator.test.cc src/stim/simulators/matched_error.test.cc src/stim/simulators/measurements_to_detection_events.test.cc src/stim/simulators/sparse_rev_frame_tracker.test.cc diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index adcdecd69..b10cf74e6 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -7828,7 +7828,6 @@ class Tableau: """ def to_circuit( self, - *, method: str = 'elimination', ) -> stim.Circuit: """Synthesizes a circuit that implements the tableau's Clifford operation. @@ -7843,6 +7842,20 @@ class Tableau: Circuit qubit count: n Circuit operation count: O(n^2) Circuit depth: O(n^2) + "graph_state": Prepares the tableau's state using a graph state circuit. + Gate set: RX, CZ, H, S, X, Y, Z + Circuit qubit count: n + Circuit operation count: O(n^2) + + The circuit will be made up of three layers: + 1. An RX layer initializing all qubits. + 2. A CZ layer coupling the qubits. + (Each CZ is an edge in the graph state.) + 3. A single qubit rotation layer. + + Note: "graph_state" treats the tableau as a state instead of as a + Clifford operation. It will preserve the set of stabilizers, but + not the exact choice of generators. Returns: The synthesized circuit. @@ -7851,35 +7864,52 @@ class Tableau: >>> import stim >>> tableau = stim.Tableau.from_conjugated_generators( ... xs=[ - ... stim.PauliString("-_YZ"), - ... stim.PauliString("-YY_"), - ... stim.PauliString("-XZX"), + ... stim.PauliString("+YZ__"), + ... stim.PauliString("-Y_XY"), + ... stim.PauliString("+___Y"), + ... stim.PauliString("+YZX_"), ... ], ... zs=[ - ... stim.PauliString("+Y_Y"), - ... stim.PauliString("-_XY"), - ... stim.PauliString("-Y__"), + ... stim.PauliString("+XZYY"), + ... stim.PauliString("-XYX_"), + ... stim.PauliString("-ZXXZ"), + ... stim.PauliString("+XXZ_"), ... ], ... ) - >>> tableau.to_circuit(method="elimination") + + >>> tableau.to_circuit() stim.Circuit(''' - CX 2 0 0 2 2 0 - S 0 - H 0 S 0 + H 0 1 3 + CX 0 1 0 2 0 3 + S 1 3 + H 1 3 + CX 1 0 3 0 3 1 1 3 3 1 H 1 - CX 0 1 0 2 - H 1 2 - CX 1 0 2 0 2 1 1 2 2 1 - H 1 - S 1 2 - H 2 - CX 2 1 - S 2 - H 0 1 2 + S 1 + CX 1 3 + H 2 3 + CX 2 1 3 1 3 2 2 3 3 2 + H 3 + CX 2 3 + S 3 + H 3 0 1 2 S 0 0 1 1 2 2 H 0 1 2 - S 1 1 2 2 + S 3 3 + ''') + + >>> tableau.to_circuit("graph_state") + stim.Circuit(''' + RX 0 1 2 3 + TICK + CZ 0 3 1 2 1 3 + TICK + X 0 1 + Z 2 + S 2 3 + H 3 + S 3 ''') """ def to_numpy( diff --git a/src/stim.h b/src/stim.h index b64047f3e..98fcb5ac5 100644 --- a/src/stim.h +++ b/src/stim.h @@ -91,6 +91,7 @@ #include "stim/simulators/force_streaming.h" #include "stim/simulators/frame_simulator.h" #include "stim/simulators/frame_simulator_util.h" +#include "stim/simulators/graph_simulator.h" #include "stim/simulators/matched_error.h" #include "stim/simulators/measurements_to_detection_events.h" #include "stim/simulators/sparse_rev_frame_tracker.h" diff --git a/src/stim/simulators/graph_simulator.cc b/src/stim/simulators/graph_simulator.cc new file mode 100644 index 000000000..1b5d2a250 --- /dev/null +++ b/src/stim/simulators/graph_simulator.cc @@ -0,0 +1,514 @@ +#include "stim/simulators/graph_simulator.h" + +using namespace stim; + +GraphSimulator::GraphSimulator(size_t num_qubits) + : num_qubits(num_qubits), adj(num_qubits, num_qubits), paulis(num_qubits), x2outs(num_qubits), z2outs(num_qubits) { + for (size_t k = 0; k < num_qubits; k++) { + x2outs.zs[k] = 1; + z2outs.xs[k] = 1; + } +} + +void GraphSimulator::do_1q_gate(GateType gate, size_t qubit) { + GateTarget t = GateTarget::qubit(qubit); + x2outs.ref().after_inplace(CircuitInstruction{gate, {}, &t}, false); + z2outs.ref().after_inplace(CircuitInstruction{gate, {}, &t}, false); + paulis.xs[qubit] ^= z2outs.sign; + paulis.zs[qubit] ^= x2outs.sign; + x2outs.sign = 0; + z2outs.sign = 0; +} + +std::tuple GraphSimulator::after2inside_basis_transform(size_t qubit, bool x, bool z) { + bool xx = x2outs.xs[qubit]; + bool xz = x2outs.zs[qubit]; + bool zx = z2outs.xs[qubit]; + bool zz = z2outs.zs[qubit]; + bool out_x = (x & zz) ^ (z & zx); + bool out_z = (x & xz) ^ (z & xx); + bool sign = false; + sign ^= paulis.xs[qubit] & out_z; + sign ^= paulis.zs[qubit] & out_x; + sign ^= out_x == out_z && !(xx ^ zz) && !(xx ^ xz ^ zx); + return {out_x, out_z, sign}; +} + +void GraphSimulator::inside_do_cz(size_t a, size_t b) { + adj[a][b] ^= 1; + adj[b][a] ^= 1; +} + +void GraphSimulator::inside_do_cx(size_t c, size_t t) { + adj[c] ^= adj[t]; + for (size_t k = 0; k < num_qubits; k++) { + adj[k][c] = adj[c][k]; + } + paulis.zs[c] ^= adj[c][c]; + adj[c][c] = 0; +} + +void GraphSimulator::inside_do_sqrt_z(size_t q) { + bool x2x = x2outs.xs[q]; + bool x2z = x2outs.zs[q]; + bool z2x = z2outs.xs[q]; + bool z2z = z2outs.zs[q]; + paulis.zs[q] ^= paulis.xs[q]; + paulis.zs[q] ^= !(x2x ^ z2z) && !(x2x ^ x2z ^ z2x); + x2outs.xs[q] ^= z2x; + x2outs.zs[q] ^= z2z; +} + +void GraphSimulator::inside_do_sqrt_x_dag(size_t q) { + bool x2x = x2outs.xs[q]; + bool x2z = x2outs.zs[q]; + bool z2x = z2outs.xs[q]; + bool z2z = z2outs.zs[q]; + paulis.xs[q] ^= paulis.zs[q]; + paulis.xs[q] ^= !(x2x ^ z2z) && !(x2x ^ x2z ^ z2x); + z2outs.xs[q] ^= x2x; + z2outs.zs[q] ^= x2z; +} + +void GraphSimulator::inside_do_cy(size_t c, size_t t) { + inside_do_cz(c, t); + inside_do_cx(c, t); + inside_do_sqrt_z(c); +} + +void GraphSimulator::verify_invariants() const { + // No self-adjacency. + for (size_t q = 0; q < num_qubits; q++) { + assert(!adj[q][q]); + } + // Undirected adjacency. + for (size_t q1 = 0; q1 < num_qubits; q1++) { + for (size_t q2 = q1 + 1; q2 < num_qubits; q2++) { + assert(adj[q1][q2] == adj[q2][q1]); + } + } + // Single qubits gates are clifford. + for (size_t q = 0; q < num_qubits; q++) { + assert(x2outs.xs[q] || x2outs.zs[q]); + assert(z2outs.xs[q] || z2outs.zs[q]); + assert((x2outs.xs[q] != z2outs.xs[q]) || (x2outs.zs[q] != z2outs.zs[q])); + } +} + +void GraphSimulator::do_complementation(size_t q) { + buffer.clear(); + for (size_t neighbor = 0; neighbor < num_qubits; neighbor++) { + if (adj[q][neighbor]) { + buffer.push_back(neighbor); + inside_do_sqrt_z(neighbor); + } + } + for (size_t k1 = 0; k1 < buffer.size(); k1++) { + for (size_t k2 = k1 + 1; k2 < buffer.size(); k2++) { + inside_do_cz(buffer[k1], buffer[k2]); + } + } + inside_do_sqrt_x_dag(q); +} + +void GraphSimulator::inside_do_ycx(size_t q1, size_t q2) { + if (adj[q1][q2]) { + // Y:X -> SQRT_X(Y):SQRT_Z(X) = Z:Y + do_complementation(q1); + inside_do_cy(q1, q2); + paulis.zs[q1] ^= 1; + } else { + // Y:X -> SQRT_X(Y):Y = Z:X + do_complementation(q1); + inside_do_cx(q1, q2); + } +} + +void GraphSimulator::inside_do_ycy(size_t q1, size_t q2) { + if (adj[q1][q2]) { + // Y:Y -> SQRT_X(Y):SQRT_Z(Y) = Z:X + do_complementation(q1); + inside_do_cx(q1, q2); + } else { + // Y:Y -> SQRT_X(Y):Y = Z:Y + do_complementation(q1); + inside_do_cy(q1, q2); + } +} + +void GraphSimulator::inside_do_xcx(size_t q1, size_t q2) { + if (adj[q1][q2]) { + // X:X -> S(X):SQRT_X_DAG(X) = (-Y):X + do_complementation(q2); + // (-Y):X -> SQRT_X_DAG(-Y):S(X) = (-Z):(-Y) + do_complementation(q1); + inside_do_cy(q1, q2); + paulis.zs[q1] ^= 1; + paulis.xs[q2] ^= 1; + paulis.zs[q2] ^= 1; + } else { + // Need an S gate. + // Get it by finding a neighbor to do local complementation on. + for (size_t q3 = 0; q3 < num_qubits; q3++) { + if (adj[q1][q3]) { + do_complementation(q3); + if (adj[q2][q3]) { + // X:X -> S(X):S(X) = (-Y):(-Y) + paulis.xs[q1] ^= 1; + paulis.zs[q1] ^= 1; + paulis.xs[q2] ^= 1; + paulis.zs[q2] ^= 1; + inside_do_ycy(q1, q2); + } else { + // X:X -> S(X):X = (-Y):X + paulis.xs[q2] ^= 1; + inside_do_ycx(q1, q2); + } + return; + } + } + + // q1 has no CZ gates applied to it. + // Therefore, inside the single qubit gates, q1 is in the |+> state. + // Therefore the XCX gate's control isn't satisfied, and it does nothing. + // <-- look at all the doing-nothing right here. --> + } +} + +void GraphSimulator::inside_do_pauli_interaction(bool x1, bool z1, bool x2, bool z2, size_t q1, size_t q2) { + int p1 = x1 + z1 * 2 - 1; + int p2 = x2 + z2 * 2 - 1; + switch (p1 + p2 * 3) { + case 0: // X:X + inside_do_xcx(q1, q2); + break; + case 1: // Z:X + inside_do_cx(q1, q2); + break; + case 2: // Y:X + inside_do_ycx(q1, q2); + break; + case 3: // X:Z + inside_do_cx(q2, q1); + break; + case 4: // Z:Z + inside_do_cz(q1, q2); + break; + case 5: // Y:Z + inside_do_cy(q2, q1); + break; + case 6: // X:Y + inside_do_ycx(q2, q1); + break; + case 7: // Z:Y + inside_do_cy(q1, q2); + break; + case 8: // Y:Y + inside_do_ycy(q1, q2); + break; + default: + throw std::invalid_argument("Unknown pauli interaction."); + } +} + +std::ostream &stim::operator<<(std::ostream &out, const GraphSimulator &sim) { + out << "stim::GraphSimulator{\n"; + out << " .num_qubits=" << sim.num_qubits << ",\n"; + out << " .paulis=" << sim.paulis << ",\n"; + out << " .x2outs=" << sim.x2outs << ",\n"; + out << " .z2outs=" << sim.z2outs << ",\n"; + out << " .adj=stim::simd_bit_table<64>::from_text(R\"TAB(\n" << sim.adj.str(sim.num_qubits) << "\n)TAB\"),\n"; + out << "}"; + return out; +} + +std::string GraphSimulator::str() const { + std::stringstream ss; + ss << *this; + return ss.str(); +} + +GraphSimulator GraphSimulator::random_state(size_t n, std::mt19937_64 &rng) { + GraphSimulator sim(n); + sim.adj = simd_bit_table<64>::random(n, n, rng); + for (size_t q1 = 0; q1 < n; q1++) { + sim.adj[q1][q1] = 0; + for (size_t q2 = q1 + 1; q2 < n; q2++) { + sim.adj[q1][q2] = sim.adj[q2][q1]; + } + } + sim.paulis = PauliString<64>::random(n, rng); + sim.paulis.sign = 0; + std::array gate_xz_data{ + 0b1001, // I + 0b0110, // H + 0b1011, // S + 0b1101, // SQRT_X_DAG + 0b0111, // C_XYZ + 0b1110, // C_ZYX + }; + for (size_t q = 0; q < n; q++) { + auto r = gate_xz_data[rng() % 6]; + sim.x2outs.xs[q] = r & 1; + sim.x2outs.zs[q] = r & 2; + sim.z2outs.xs[q] = r & 4; + sim.z2outs.zs[q] = r & 8; + } + return sim; +} + +void GraphSimulator::do_pauli_interaction(bool x1, bool z1, bool x2, bool z2, size_t qubit1, size_t qubit2) { + // Propagate the interaction across the single qubit gate layer. + auto [x1_in, z1_in, sign1] = after2inside_basis_transform(qubit1, x1, z1); + auto [x2_in, z2_in, sign2] = after2inside_basis_transform(qubit2, x2, z2); + + // Kickback minus signs into Pauli gates on the other side. + if (sign1) { + paulis.xs[qubit2] ^= x2_in; + paulis.zs[qubit2] ^= z2_in; + } + if (sign2) { + paulis.xs[qubit1] ^= x1_in; + paulis.zs[qubit1] ^= z1_in; + } + + // Do the positive-sign interaction between the single-qubit layer and the CZ layer. + inside_do_pauli_interaction(x1_in, z1_in, x2_in, z2_in, qubit1, qubit2); +} + +void GraphSimulator::do_gate_by_decomposition(const CircuitInstruction &inst) { + const Gate &d = GATE_DATA[inst.gate_type]; + bool is_all_qubits = true; + for (auto t : inst.targets) { + is_all_qubits &= t.is_qubit_target(); + } + if (!is_all_qubits || d.h_s_cx_m_r_decomposition == nullptr || !(d.flags & GATE_TARGETS_PAIRS)) { + throw std::invalid_argument("Not supported: " + inst.str()); + } + + Circuit circuit(d.h_s_cx_m_r_decomposition); + for (size_t k = 0; k < inst.targets.size(); k += 2) { + auto a = inst.targets[k]; + auto b = inst.targets[k + 1]; + for (const auto &inst2 : circuit.operations) { + assert( + inst2.gate_type == GateType::CX || inst2.gate_type == GateType::H || inst2.gate_type == GateType::S || + inst2.gate_type == GateType::R || inst2.gate_type == GateType::M); + if (inst2.gate_type == GateType::CX) { + for (size_t k2 = 0; k2 < inst2.targets.size(); k2 += 2) { + auto a2 = inst2.targets[k2]; + auto b2 = inst2.targets[k2 + 1]; + assert(a2.is_qubit_target()); + assert(b2.is_qubit_target()); + assert(a2.qubit_value() == 0 || a2.qubit_value() == 1); + assert(b2.qubit_value() == 0 || b2.qubit_value() == 1); + auto a3 = a2.qubit_value() == 0 ? a : b; + auto b3 = b2.qubit_value() == 0 ? a : b; + do_pauli_interaction(false, true, true, false, a3.qubit_value(), b3.qubit_value()); + } + } else { + for (GateTarget a2 : inst2.targets) { + assert(a2.is_qubit_target()); + assert(a2.qubit_value() == 0 || a2.qubit_value() == 1); + auto a3 = a2.qubit_value() == 0 ? a : b; + do_1q_gate(inst2.gate_type, a3.qubit_value()); + } + } + } + } +} + +void GraphSimulator::do_2q_unitary_instruction(const CircuitInstruction &inst) { + uint8_t p1; + uint8_t p2; + constexpr uint8_t X = 0b01; + constexpr uint8_t Y = 0b11; + constexpr uint8_t Z = 0b10; + switch (inst.gate_type) { + case GateType::XCX: + p1 = X; + p2 = X; + break; + case GateType::XCY: + p1 = X; + p2 = Y; + break; + case GateType::XCZ: + p1 = X; + p2 = Z; + break; + case GateType::YCX: + p1 = Y; + p2 = X; + break; + case GateType::YCY: + p1 = Y; + p2 = Y; + break; + case GateType::YCZ: + p1 = Y; + p2 = Z; + break; + case GateType::CX: + p1 = Z; + p2 = X; + break; + case GateType::CY: + p1 = Z; + p2 = Y; + break; + case GateType::CZ: + p1 = Z; + p2 = Z; + break; + default: + do_gate_by_decomposition(inst); + return; + } + bool x1 = p1 & 1; + bool z1 = p1 & 2; + bool x2 = p2 & 1; + bool z2 = p2 & 2; + + for (size_t k = 0; k < inst.targets.size(); k += 2) { + auto t1 = inst.targets[k]; + auto t2 = inst.targets[k + 1]; + if (!t1.is_qubit_target() || !t2.is_qubit_target()) { + throw std::invalid_argument("Unsupported operation: " + inst.str()); + } + do_pauli_interaction(x1, z1, x2, z2, t1.qubit_value(), t2.qubit_value()); + } +} + +void GraphSimulator::output_pauli_layer(Circuit &out, bool to_hs_xyz) const { + std::array, 4> groups; + + for (size_t q = 0; q < paulis.num_qubits; q++) { + bool x = paulis.xs[q]; + bool z = paulis.zs[q]; + if (to_hs_xyz) { + bool xx = x2outs.xs[q]; + bool xz = x2outs.zs[q]; + bool zx = z2outs.xs[q]; + bool zz = z2outs.zs[q]; + z ^= xx == 1 && xz == 1 && zx == 1 && zz == 0; + } + groups[x + 2 * z].push_back(GateTarget::qubit(q)); + } + + auto f = [&](GateType g, int k) { + if (!groups[k].empty()) { + out.safe_append(g, groups[k], {}); + } + }; + f(GateType::X, 0b01); + f(GateType::Y, 0b11); + f(GateType::Z, 0b10); +} + +void GraphSimulator::output_clifford_layer(Circuit &out, bool to_hs_xyz) const { + output_pauli_layer(out, to_hs_xyz); + + std::array, 16> groups; + for (size_t q = 0; q < x2outs.num_qubits; q++) { + bool xx = x2outs.xs[q]; + bool xz = x2outs.zs[q]; + bool zx = z2outs.xs[q]; + bool zz = z2outs.zs[q]; + groups[xx + 2 * xz + 4 * zx + 8 * zz].push_back(GateTarget::qubit(q)); + } + + std::array, 3> shs; + auto f = [&](GateType g, int k, std::array use_shs) { + if (to_hs_xyz) { + for (size_t j = 0; j < 3; j++) { + if (use_shs[j]) { + shs[j].insert(shs[j].end(), groups[k].begin(), groups[k].end()); + } + } + } else { + if (!groups[k].empty()) { + out.safe_append(g, groups[k], {}); + } + } + }; + f(GateType::C_XYZ, 0b0111, {1, 1, 0}); + f(GateType::C_ZYX, 0b1110, {0, 1, 1}); + f(GateType::H, 0b0110, {0, 1, 0}); + f(GateType::S, 0b1011, {1, 0, 0}); + f(GateType::SQRT_X_DAG, 0b1101, {1, 1, 1}); + for (size_t k = 0; k < 3; k++) { + if (!shs[k].empty()) { + std::sort(shs[k].begin(), shs[k].end()); + out.safe_append(k == 1 ? GateType::H : GateType::S, shs[k], {}); + } + } +} + +Circuit GraphSimulator::to_circuit(bool to_hs_xyz) const { + std::vector targets; + targets.reserve(2 * num_qubits); + Circuit out; + + for (size_t q = 0; q < num_qubits; q++) { + targets.push_back(GateTarget::qubit(q)); + } + if (!targets.empty()) { + out.safe_append(GateType::RX, targets, {}); + } + out.safe_append(GateType::TICK, {}, {}); + + bool has_cz = false; + for (size_t q = 0; q < num_qubits; q++) { + targets.clear(); + for (size_t q2 = q + 1; q2 < num_qubits; q2++) { + if (adj[q][q2]) { + targets.push_back(GateTarget::qubit(q)); + targets.push_back(GateTarget::qubit(q2)); + } + } + if (!targets.empty()) { + out.safe_append(GateType::CZ, targets, {}); + } + has_cz |= !targets.empty(); + } + if (has_cz) { + out.safe_append(GateType::TICK, {}, {}); + } + + output_clifford_layer(out, to_hs_xyz); + + return out; +} + +void GraphSimulator::do_instruction(const CircuitInstruction &instruction) { + auto f = GATE_DATA[instruction.gate_type].flags; + + if (f & GATE_IS_UNITARY) { + if (f & GATE_IS_SINGLE_QUBIT_GATE) { + for (auto t : instruction.targets) { + do_1q_gate(instruction.gate_type, t.qubit_value()); + } + return; + } + if (f & GATE_TARGETS_PAIRS) { + do_2q_unitary_instruction(instruction); + return; + } + } + + switch (instruction.gate_type) { + case GateType::TICK: + case GateType::QUBIT_COORDS: + case GateType::SHIFT_COORDS: + return; // No effect. + default: + throw std::invalid_argument("Unsupported operation: " + instruction.str()); + } +} + +void GraphSimulator::do_circuit(const Circuit &circuit) { + circuit.for_each_operation([&](const CircuitInstruction &inst) { + do_instruction(inst); + }); +} diff --git a/src/stim/simulators/graph_simulator.h b/src/stim/simulators/graph_simulator.h new file mode 100644 index 000000000..d90afec60 --- /dev/null +++ b/src/stim/simulators/graph_simulator.h @@ -0,0 +1,78 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _STIM_SIMULATORS_GRAPH_SIMULATOR_H +#define _STIM_SIMULATORS_GRAPH_SIMULATOR_H + +#include "stim/circuit/circuit.h" +#include "stim/mem/simd_bit_table.h" +#include "stim/mem/simd_bits.h" +#include "stim/stabilizers/pauli_string.h" + +namespace stim { + +struct GraphSimulator { + // RX applied to each qubit. + size_t num_qubits; + // Then CZs applied according to this adjacency matrix. + simd_bit_table<64> adj; + // Then Paulis adding sign data. + PauliString<64> paulis; + // Then an unsigned Clifford mapping. + PauliString<64> x2outs; + PauliString<64> z2outs; + // Used as temporary workspace. + std::vector buffer; + + explicit GraphSimulator(size_t num_qubits); + static GraphSimulator random_state(size_t n, std::mt19937_64 &rng); + + Circuit to_circuit(bool to_hs_xyz = false) const; + + void do_circuit(const Circuit &circuit); + void do_instruction(const CircuitInstruction &instruction); + void do_complementation(size_t c); + void verify_invariants() const; + void inside_do_sqrt_z(size_t q); + void inside_do_sqrt_x_dag(size_t q); + std::tuple after2inside_basis_transform(size_t qubit, bool x, bool z); + + std::string str() const; + + private: + void output_pauli_layer(Circuit &out, bool to_hs_xyz) const; + void output_clifford_layer(Circuit &out, bool to_hs_xyz) const; + + void do_1q_gate(GateType gate, size_t qubit); + void do_2q_unitary_instruction(const CircuitInstruction &inst); + void do_pauli_interaction(bool x1, bool z1, bool x2, bool z2, size_t qubit1, size_t qubit2); + void do_gate_by_decomposition(const CircuitInstruction &inst); + + // These operations apply to the state inside of the single qubit gates. + void inside_do_cz(size_t a, size_t b); + void inside_do_cx(size_t c, size_t t); + void inside_do_cy(size_t c, size_t t); + void inside_do_ycx(size_t q1, size_t q2); + void inside_do_ycy(size_t q1, size_t q2); + void inside_do_xcx(size_t q1, size_t q2); + void inside_do_pauli_interaction(bool x1, bool z1, bool x2, bool z2, size_t q1, size_t q2); +}; + +std::ostream &operator<<(std::ostream &out, const GraphSimulator &sim); + +} // namespace stim + +#endif diff --git a/src/stim/simulators/graph_simulator.test.cc b/src/stim/simulators/graph_simulator.test.cc new file mode 100644 index 000000000..ef72b16fe --- /dev/null +++ b/src/stim/simulators/graph_simulator.test.cc @@ -0,0 +1,320 @@ +#include "stim/simulators/graph_simulator.h" + +#include "gtest/gtest.h" + +#include "stim/diagram/timeline/timeline_ascii_drawer.h" +#include "stim/simulators/tableau_simulator.h" +#include "stim/test_util.test.h" + +using namespace stim; + +void expect_graph_circuit_is_equivalent(const Circuit &circuit) { + auto n = circuit.count_qubits(); + GraphSimulator sim(n); + sim.do_circuit(circuit); + sim.verify_invariants(); + Circuit converted = sim.to_circuit(); + TableauSimulator<64> sim1(std::mt19937_64{}, n); + TableauSimulator<64> sim2(std::mt19937_64{}, n); + sim1.expand_do_circuit(circuit); + sim2.expand_do_circuit(converted); + auto s1 = sim1.canonical_stabilizers(); + auto s2 = sim2.canonical_stabilizers(); + if (s1 != s2) { + EXPECT_EQ(s1, s2) << "\nACTUAL:\n" + << stim_draw_internal::DiagramTimelineAsciiDrawer::make_diagram(circuit) << "\nCONVERTED:\n" + << stim_draw_internal::DiagramTimelineAsciiDrawer::make_diagram(converted); + } +} + +void expect_graph_sim_effect_matches_tableau_sim(const GraphSimulator &state, const Circuit &effect) { + GraphSimulator state_after_effect = state; + auto n = state_after_effect.num_qubits; + state_after_effect.do_circuit(effect); + state_after_effect.verify_invariants(); + + TableauSimulator<64> tableau_sim1(std::mt19937_64{}, n); + TableauSimulator<64> tableau_sim2(std::mt19937_64{}, n); + tableau_sim1.expand_do_circuit(state.to_circuit()); + tableau_sim1.expand_do_circuit(effect); + tableau_sim2.expand_do_circuit(state_after_effect.to_circuit()); + auto s1 = tableau_sim1.canonical_stabilizers(); + auto s2 = tableau_sim2.canonical_stabilizers(); + if (s1 != s2) { + EXPECT_EQ(s1, s2) << "EFFECT:\nstim::Circuit(R\"CIRCUIT(\n" + << effect << "\n)CIRCUIT\")" + << "\n" + << "STATE:\n" + << state << "\n"; + } +} + +TEST(graph_simulator, converts_circuits) { + expect_graph_circuit_is_equivalent(Circuit(R"CIRCUIT( + H 2 3 5 + S 0 1 2 + C_XYZ 1 2 3 + S_DAG 1 + H_YZ 5 4 + SQRT_Y 2 4 + X 1 + )CIRCUIT")); + + expect_graph_circuit_is_equivalent(Circuit(R"CIRCUIT( + H 0 1 + CZ 0 1 + )CIRCUIT")); + + expect_graph_circuit_is_equivalent(Circuit(R"CIRCUIT( + H 0 1 2 3 + CZ 0 1 0 2 0 3 + )CIRCUIT")); + + expect_graph_circuit_is_equivalent(Circuit(R"CIRCUIT( + H 0 1 2 3 + CZ 0 1 + CX 2 1 + )CIRCUIT")); +} + +TEST(graph_simulator, after2inside_basis_transform) { + GraphSimulator sim(6); + // VI+SH- + sim.x2outs = PauliString<64>("XXYYZZ"); + sim.z2outs = PauliString<64>("YZXZXY"); + + ASSERT_EQ(sim.after2inside_basis_transform(0u, 1, 0), (std::make_tuple(1, 0, false))); + ASSERT_EQ(sim.after2inside_basis_transform(0u, 1, 1), (std::make_tuple(0, 1, false))); + ASSERT_EQ(sim.after2inside_basis_transform(0u, 0, 1), (std::make_tuple(1, 1, true))); + + ASSERT_EQ(sim.after2inside_basis_transform(1u, 1, 0), (std::make_tuple(1, 0, false))); + ASSERT_EQ(sim.after2inside_basis_transform(1u, 0, 1), (std::make_tuple(0, 1, false))); + ASSERT_EQ(sim.after2inside_basis_transform(1u, 1, 1), (std::make_tuple(1, 1, false))); + + ASSERT_EQ(sim.after2inside_basis_transform(2u, 1, 1), (std::make_tuple(1, 0, false))); + ASSERT_EQ(sim.after2inside_basis_transform(2u, 1, 0), (std::make_tuple(0, 1, false))); + ASSERT_EQ(sim.after2inside_basis_transform(2u, 0, 1), (std::make_tuple(1, 1, false))); + + ASSERT_EQ(sim.after2inside_basis_transform(3u, 1, 1), (std::make_tuple(1, 0, false))); + ASSERT_EQ(sim.after2inside_basis_transform(3u, 0, 1), (std::make_tuple(0, 1, false))); + ASSERT_EQ(sim.after2inside_basis_transform(3u, 1, 0), (std::make_tuple(1, 1, true))); + + ASSERT_EQ(sim.after2inside_basis_transform(4u, 0, 1), (std::make_tuple(1, 0, false))); + ASSERT_EQ(sim.after2inside_basis_transform(4u, 1, 0), (std::make_tuple(0, 1, false))); + ASSERT_EQ(sim.after2inside_basis_transform(4u, 1, 1), (std::make_tuple(1, 1, true))); + + ASSERT_EQ(sim.after2inside_basis_transform(5u, 0, 1), (std::make_tuple(1, 0, false))); + ASSERT_EQ(sim.after2inside_basis_transform(5u, 1, 1), (std::make_tuple(0, 1, false))); + ASSERT_EQ(sim.after2inside_basis_transform(5u, 1, 0), (std::make_tuple(1, 1, false))); +} + +TEST(graph_simulator, to_hs_xyz) { + GraphSimulator sim(10); + sim.do_circuit(Circuit(R"CIRCUIT( + H 0 1 2 3 4 5 6 7 8 9 + I 0 + H 1 + S 2 + SQRT_X_DAG 3 + C_XYZ 4 + C_ZYX 5 + X 6 + Y 7 + Z 8 + H 9 + Z 9 + )CIRCUIT")); + ASSERT_EQ(sim.to_circuit(true), Circuit(R"CIRCUIT( + RX 0 1 2 3 4 5 6 7 8 9 + TICK + X 6 9 + Y 7 + Z 4 8 + S 2 3 4 + H 1 3 4 5 9 + S 3 5 + )CIRCUIT")); +} + +TEST(graph_simulator, inside_do_sqrt_z) { + GraphSimulator sim(10); + sim.do_circuit(Circuit(R"CIRCUIT( + H 0 1 2 3 4 5 6 7 8 9 + I 0 + H 1 + S 2 + SQRT_X_DAG 3 + C_XYZ 4 + C_ZYX 5 + X 6 + Y 7 + Z 8 + H 9 + Z 9 + )CIRCUIT")); + ASSERT_EQ(sim.to_circuit(), Circuit(R"CIRCUIT( + RX 0 1 2 3 4 5 6 7 8 9 + TICK + X 6 9 + Y 7 + Z 8 + C_XYZ 4 + C_ZYX 5 + H 1 9 + S 2 + SQRT_X_DAG 3 + )CIRCUIT")); + + for (size_t q = 0; q < sim.num_qubits; q++) { + sim.inside_do_sqrt_z(q); + } + sim.verify_invariants(); + ASSERT_EQ(sim.to_circuit(), Circuit(R"CIRCUIT( + RX 0 1 2 3 4 5 6 7 8 9 + TICK + X 7 9 + Y 6 + Z 1 2 3 8 + C_XYZ 1 9 + C_ZYX 3 + H 4 + S 0 6 7 8 + SQRT_X_DAG 5 + )CIRCUIT")); +} + +TEST(graph_simulator, inside_do_sqrt_x_dag) { + GraphSimulator sim(10); + sim.do_circuit(Circuit(R"CIRCUIT( + H 0 1 2 3 4 5 6 7 8 9 + I 0 + H 1 + S 2 + SQRT_X_DAG 3 + C_XYZ 4 + C_ZYX 5 + X 6 + Y 7 + Z 8 + H 9 + Z 9 + )CIRCUIT")); + ASSERT_EQ(sim.to_circuit(), Circuit(R"CIRCUIT( + RX 0 1 2 3 4 5 6 7 8 9 + TICK + X 6 9 + Y 7 + Z 8 + C_XYZ 4 + C_ZYX 5 + H 1 9 + S 2 + SQRT_X_DAG 3 + )CIRCUIT")); + + for (size_t q = 0; q < sim.num_qubits; q++) { + sim.inside_do_sqrt_x_dag(q); + } + sim.verify_invariants(); + ASSERT_EQ(sim.to_circuit(), Circuit(R"CIRCUIT( + RX 0 1 2 3 4 5 6 7 8 9 + TICK + X 1 2 3 6 + Y 8 + Z 7 + C_XYZ 2 + C_ZYX 1 9 + H 5 + S 4 + SQRT_X_DAG 0 6 7 8 + )CIRCUIT")); +} + +TEST(graph_simulator, do_complementation) { + for (size_t k = 0; k < 50; k++) { + auto rng = INDEPENDENT_TEST_RNG(); + GraphSimulator sim = GraphSimulator::random_state(8, rng); + GraphSimulator sim2 = sim; + sim2.do_complementation(3); + sim.verify_invariants(); + sim2.verify_invariants(); + + TableauSimulator<64> tableau_sim1(std::mt19937_64{}, 8); + TableauSimulator<64> tableau_sim2(std::mt19937_64{}, 8); + tableau_sim1.expand_do_circuit(sim.to_circuit()); + tableau_sim2.expand_do_circuit(sim2.to_circuit()); + auto s1 = tableau_sim1.canonical_stabilizers(); + auto s2 = tableau_sim2.canonical_stabilizers(); + if (s1 != s2) { + ASSERT_EQ(s1, s2) << sim; + } + } +} + +TEST(graph_simulator, all_unitary_gates_work) { + auto rng = INDEPENDENT_TEST_RNG(); + std::vector targets{GateTarget::qubit(2), GateTarget::qubit(5)}; + SpanRef t2 = targets; + SpanRef t1 = t2; + t1.ptr_end--; + for (const auto &gate : GATE_DATA.items) { + if (!(gate.flags & GATE_IS_UNITARY)) { + continue; + } + Circuit circuit; + circuit.safe_append(gate.id, (gate.flags & GATE_TARGETS_PAIRS) ? t2 : t1, {}); + for (size_t k = 0; k < 20; k++) { + expect_graph_sim_effect_matches_tableau_sim(GraphSimulator::random_state(8, rng), circuit); + } + } +} + +TEST(graph_simulator, to_circuit_single_qubit_gates) { + GraphSimulator sim(6); + ASSERT_EQ(sim.to_circuit(), Circuit(R"CIRCUIT( + RX 0 1 2 3 4 5 + TICK + H 0 1 2 3 4 5 + )CIRCUIT")); + + sim.do_circuit(Circuit(R"CIRCUIT( + H 0 1 2 3 4 5 + )CIRCUIT")); + ASSERT_EQ(sim.to_circuit(), Circuit(R"CIRCUIT( + RX 0 1 2 3 4 5 + TICK + )CIRCUIT")); + + sim.do_circuit(Circuit(R"CIRCUIT( + H 0 + S 1 + C_XYZ 2 3 3 + SQRT_X_DAG 4 + )CIRCUIT")); + ASSERT_EQ(sim.to_circuit(), Circuit(R"CIRCUIT( + RX 0 1 2 3 4 5 + TICK + C_XYZ 2 + C_ZYX 3 + H 0 + S 1 + SQRT_X_DAG 4 + )CIRCUIT")); + + sim.do_circuit(Circuit(R"CIRCUIT( + X 0 + S 1 + Y 2 + Z 3 + )CIRCUIT")); + ASSERT_EQ(sim.to_circuit(), Circuit(R"CIRCUIT( + RX 0 1 2 3 4 5 + TICK + X 2 3 + Z 0 1 + C_XYZ 2 + C_ZYX 3 + H 0 + SQRT_X_DAG 4 + )CIRCUIT")); +} diff --git a/src/stim/stabilizers/conversions.h b/src/stim/stabilizers/conversions.h index bbfe9f5d5..4991e2077 100644 --- a/src/stim/stabilizers/conversions.h +++ b/src/stim/stabilizers/conversions.h @@ -105,17 +105,35 @@ std::vector> circuit_to_output_state_vector(const Circuit &c /// /// Args: /// tableau: The tableau to synthesize into a circuit. -/// method: The method to use when synthesizing the circuit. Available values are: -/// "elimination": Uses Gaussian elimination to cancel off-diagonal terms one by one. +/// method: The method to use when synthesizing the circuit. Available values: +/// "elimination": Cancels off-diagonal terms using Gaussian elimination. /// Gate set: H, S, CX /// Circuit qubit count: n /// Circuit operation count: O(n^2) /// Circuit depth: O(n^2) +/// "graph_state": Prepares the tableau's state using a graph state circuit. +/// Gate set: RX, CZ, H, S, X, Y, Z +/// Circuit qubit count: n +/// Circuit operation count: O(n^2) +/// +/// The circuit will be made up of three layers: +/// 1. An RX layer initializing all qubits. +/// 2. A CZ layer coupling the qubits. +/// an edge in the graph state.) +/// 3. A single qubit rotation layer. +/// +/// Note: "graph_state" treats the tableau as a state instead of as a +/// Clifford operation. It will preserve the set of stabilizers, but +/// not the exact choice of generators. /// /// Returns: /// The synthesized circuit. template Circuit tableau_to_circuit(const Tableau &tableau, const std::string &method); +template +Circuit tableau_to_circuit_graph_method(const Tableau &tableau); +template +Circuit tableau_to_circuit_elimination_method(const Tableau &tableau); /// Converts a unitary matrix into a stabilizer tableau. /// diff --git a/src/stim/stabilizers/conversions.inl b/src/stim/stabilizers/conversions.inl index 611ed8ad4..0787e2174 100644 --- a/src/stim/stabilizers/conversions.inl +++ b/src/stim/stabilizers/conversions.inl @@ -2,6 +2,7 @@ #include "stim/simulators/tableau_simulator.h" #include "stim/simulators/vector_simulator.h" #include "stim/stabilizers/conversions.h" +#include "stim/simulators/graph_simulator.h" namespace stim { @@ -211,13 +212,28 @@ std::vector> circuit_to_output_state_vector(const Circuit &c template Circuit tableau_to_circuit(const Tableau &tableau, const std::string &method) { - if (method != "elimination") { + if (method == "elimination") { + return tableau_to_circuit_elimination_method(tableau); + } else if (method == "graph_state") { + return tableau_to_circuit_graph_method(tableau); + } else { std::stringstream ss; ss << "Unknown method: '" << method << "'. Known methods:\n"; ss << " - 'elimination'"; + ss << " - 'graph'"; throw std::invalid_argument(ss.str()); } +} +template +Circuit tableau_to_circuit_graph_method(const Tableau &tableau) { + GraphSimulator sim(tableau.num_qubits); + sim.do_circuit(tableau_to_circuit_elimination_method(tableau)); + return sim.to_circuit(true); +} + +template +Circuit tableau_to_circuit_elimination_method(const Tableau &tableau) { Tableau remaining = tableau.inverse(); Circuit recorded_circuit; auto apply = [&](GateType gate_type, uint32_t target) { diff --git a/src/stim/stabilizers/tableau.pybind.cc b/src/stim/stabilizers/tableau.pybind.cc index b251566a6..30a3ef6d5 100644 --- a/src/stim/stabilizers/tableau.pybind.cc +++ b/src/stim/stabilizers/tableau.pybind.cc @@ -668,10 +668,9 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_ &self, const std::string &method) { return tableau_to_circuit(self, method); }, - pybind11::kw_only(), pybind11::arg("method") = "elimination", clean_doc_string(R"DOC( - @signature def to_circuit(self, *, method: str = 'elimination') -> stim.Circuit: + @signature def to_circuit(self, method: str = 'elimination') -> stim.Circuit: Synthesizes a circuit that implements the tableau's Clifford operation. The circuits returned by this method are not guaranteed to be stable @@ -684,6 +683,20 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_>> import stim >>> tableau = stim.Tableau.from_conjugated_generators( ... xs=[ - ... stim.PauliString("-_YZ"), - ... stim.PauliString("-YY_"), - ... stim.PauliString("-XZX"), + ... stim.PauliString("+YZ__"), + ... stim.PauliString("-Y_XY"), + ... stim.PauliString("+___Y"), + ... stim.PauliString("+YZX_"), ... ], ... zs=[ - ... stim.PauliString("+Y_Y"), - ... stim.PauliString("-_XY"), - ... stim.PauliString("-Y__"), + ... stim.PauliString("+XZYY"), + ... stim.PauliString("-XYX_"), + ... stim.PauliString("-ZXXZ"), + ... stim.PauliString("+XXZ_"), ... ], ... ) - >>> tableau.to_circuit(method="elimination") + + >>> tableau.to_circuit() stim.Circuit(''' - CX 2 0 0 2 2 0 S 0 - H 0 - S 0 - H 1 - CX 0 1 0 2 - H 1 2 - CX 1 0 2 0 2 1 1 2 2 1 + H 0 1 3 + CX 0 1 0 2 0 3 + S 1 3 + H 1 3 + CX 1 0 3 0 3 1 1 3 3 1 H 1 - S 1 2 - H 2 - CX 2 1 - S 2 - H 0 1 2 + S 1 + CX 1 3 + H 2 3 + CX 2 1 3 1 3 2 2 3 3 2 + H 3 + CX 2 3 + S 3 + H 3 0 1 2 S 0 0 1 1 2 2 H 0 1 2 - S 1 1 2 2 + S 3 3 + ''') + + >>> tableau.to_circuit("graph_state") + stim.Circuit(''' + RX 0 1 2 3 + TICK + CZ 0 3 1 2 1 3 + TICK + X 0 1 + Z 2 + S 2 3 + H 3 + S 3 ''') )DOC") .data()); diff --git a/src/stim/stabilizers/tableau_pybind_test.py b/src/stim/stabilizers/tableau_pybind_test.py index 83060b64c..49279d7ac 100644 --- a/src/stim/stabilizers/tableau_pybind_test.py +++ b/src/stim/stabilizers/tableau_pybind_test.py @@ -944,3 +944,12 @@ def test_to_stabilizers(): stim.PauliString("-__XX"), stim.PauliString("-__ZZ"), ] + + +def test_to_circuit_graph_state_preserves_stabilizers(): + t = stim.Tableau.random(10) + c = t.to_circuit("graph_state") + c = stim.Circuit(str(c).replace('RX', 'H')) + original = t.to_stabilizers(canonicalize=True) + reconstructed = c.to_tableau().to_stabilizers(canonicalize=True) + assert original == reconstructed