diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 85259a244..6cf06b5c3 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -294,6 +294,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.Tableau.to_circuit`](#stim.Tableau.to_circuit) - [`stim.Tableau.to_numpy`](#stim.Tableau.to_numpy) - [`stim.Tableau.to_pauli_string`](#stim.Tableau.to_pauli_string) + - [`stim.Tableau.to_stabilizers`](#stim.Tableau.to_stabilizers) - [`stim.Tableau.to_state_vector`](#stim.Tableau.to_state_vector) - [`stim.Tableau.to_unitary_matrix`](#stim.Tableau.to_unitary_matrix) - [`stim.Tableau.x_output`](#stim.Tableau.x_output) @@ -10349,6 +10350,62 @@ def to_pauli_string( """ ``` + +```python +# stim.Tableau.to_stabilizers + +# (in class stim.Tableau) +def to_stabilizers( + self, + *, + canonicalize: bool = False, +) -> List[stim.PauliString]: + """Returns the stabilizer generators of the tableau, optionally canonicalized. + + The stabilizer generators of the tableau are its Z outputs. Canonicalizing + standardizes the generators, so that states that are equal will produce the + same generators. For example, [ZI, IZ], [ZI, ZZ], amd [ZZ, ZI] describe equal + states and all canonicalize to [ZI, IZ]. + + The canonical form is computed as follows: + + 1. Get a list of stabilizers using `tableau.z_output(k)` for each k. + 2. Perform Gaussian elimination. pivoting on standard generators. + 2a) Pivot on g=X0 first, then Z0, X1, Z1, X2, Z2, etc. + 2b) Find a stabilizer that uses the generator g. If there are none, + go to the next g. + 2c) Multiply that stabilizer into all other stabilizers that use the + generator g. + 2d) Swap that stabilizer with the stabilizer at position `r` then + increment `r`. `r` starts at 0. + + Args: + canonicalize: Defaults to False. When False, the tableau's Z outputs + are returned unchanged. When True, the Z outputs are rewritten + into a standard form. Two stabilizer states have the same standard + form if and only if they describe equivalent quantum states. + + Returns: + A List[stim.PauliString] of the tableau's stabilizer generators. + + Examples: + >>> import stim + >>> t = stim.Tableau.from_named_gate("CNOT") + + >>> raw_stabilizers = t.to_stabilizers() + >>> for e in raw_stabilizers: + ... print(repr(e)) + stim.PauliString("+Z_") + stim.PauliString("+ZZ") + + >>> canonical_stabilizers = t.to_stabilizers(canonicalize=True) + >>> for e in canonical_stabilizers: + ... print(repr(e)) + stim.PauliString("+Z_") + stim.PauliString("+_Z") + """ +``` + ```python # stim.Tableau.to_state_vector diff --git a/doc/stim.pyi b/doc/stim.pyi index aa99273d7..c6c90d962 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -8070,6 +8070,55 @@ class Tableau: >>> print(t.to_pauli_string()) +ZY_X """ + def to_stabilizers( + self, + *, + canonicalize: bool = False, + ) -> List[stim.PauliString]: + """Returns the stabilizer generators of the tableau, optionally canonicalized. + + The stabilizer generators of the tableau are its Z outputs. Canonicalizing + standardizes the generators, so that states that are equal will produce the + same generators. For example, [ZI, IZ], [ZI, ZZ], amd [ZZ, ZI] describe equal + states and all canonicalize to [ZI, IZ]. + + The canonical form is computed as follows: + + 1. Get a list of stabilizers using `tableau.z_output(k)` for each k. + 2. Perform Gaussian elimination. pivoting on standard generators. + 2a) Pivot on g=X0 first, then Z0, X1, Z1, X2, Z2, etc. + 2b) Find a stabilizer that uses the generator g. If there are none, + go to the next g. + 2c) Multiply that stabilizer into all other stabilizers that use the + generator g. + 2d) Swap that stabilizer with the stabilizer at position `r` then + increment `r`. `r` starts at 0. + + Args: + canonicalize: Defaults to False. When False, the tableau's Z outputs + are returned unchanged. When True, the Z outputs are rewritten + into a standard form. Two stabilizer states have the same standard + form if and only if they describe equivalent quantum states. + + Returns: + A List[stim.PauliString] of the tableau's stabilizer generators. + + Examples: + >>> import stim + >>> t = stim.Tableau.from_named_gate("CNOT") + + >>> raw_stabilizers = t.to_stabilizers() + >>> for e in raw_stabilizers: + ... print(repr(e)) + stim.PauliString("+Z_") + stim.PauliString("+ZZ") + + >>> canonical_stabilizers = t.to_stabilizers(canonicalize=True) + >>> for e in canonical_stabilizers: + ... print(repr(e)) + stim.PauliString("+Z_") + stim.PauliString("+_Z") + """ def to_state_vector( self, *, diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index aa99273d7..c6c90d962 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -8070,6 +8070,55 @@ class Tableau: >>> print(t.to_pauli_string()) +ZY_X """ + def to_stabilizers( + self, + *, + canonicalize: bool = False, + ) -> List[stim.PauliString]: + """Returns the stabilizer generators of the tableau, optionally canonicalized. + + The stabilizer generators of the tableau are its Z outputs. Canonicalizing + standardizes the generators, so that states that are equal will produce the + same generators. For example, [ZI, IZ], [ZI, ZZ], amd [ZZ, ZI] describe equal + states and all canonicalize to [ZI, IZ]. + + The canonical form is computed as follows: + + 1. Get a list of stabilizers using `tableau.z_output(k)` for each k. + 2. Perform Gaussian elimination. pivoting on standard generators. + 2a) Pivot on g=X0 first, then Z0, X1, Z1, X2, Z2, etc. + 2b) Find a stabilizer that uses the generator g. If there are none, + go to the next g. + 2c) Multiply that stabilizer into all other stabilizers that use the + generator g. + 2d) Swap that stabilizer with the stabilizer at position `r` then + increment `r`. `r` starts at 0. + + Args: + canonicalize: Defaults to False. When False, the tableau's Z outputs + are returned unchanged. When True, the Z outputs are rewritten + into a standard form. Two stabilizer states have the same standard + form if and only if they describe equivalent quantum states. + + Returns: + A List[stim.PauliString] of the tableau's stabilizer generators. + + Examples: + >>> import stim + >>> t = stim.Tableau.from_named_gate("CNOT") + + >>> raw_stabilizers = t.to_stabilizers() + >>> for e in raw_stabilizers: + ... print(repr(e)) + stim.PauliString("+Z_") + stim.PauliString("+ZZ") + + >>> canonical_stabilizers = t.to_stabilizers(canonicalize=True) + >>> for e in canonical_stabilizers: + ... print(repr(e)) + stim.PauliString("+Z_") + stim.PauliString("+_Z") + """ def to_state_vector( self, *, diff --git a/src/stim/circuit/export_qasm.cc b/src/stim/circuit/export_qasm.cc index 1381e436c..a5a9ec8da 100644 --- a/src/stim/circuit/export_qasm.cc +++ b/src/stim/circuit/export_qasm.cc @@ -45,7 +45,6 @@ struct QasmExporter { reference_sample(stats.num_measurements), measurement_offset(0), detector_offset(0) { - // Init used_gates. collect_used_gates(circuit); @@ -71,11 +70,7 @@ struct QasmExporter { } void output_decomposed_operation( - bool invert_measurement_result, - GateType g, - const char *q0_name, - const char *q1_name, - const char *m_name) { + bool invert_measurement_result, GateType g, const char *q0_name, const char *q1_name, const char *m_name) { auto q2n = [&](GateTarget t) { return t.qubit_value() == 0 ? q0_name : q1_name; }; @@ -159,10 +154,7 @@ struct QasmExporter { buf_m.str(""); buf_q1 << "q[" << t.qubit_value() << "]"; buf_m << "rec[" << measurement_offset << "]"; - output_measurement( - t.is_inverted_result_target(), - buf_q1.str().c_str(), - buf_m.str().c_str()); + output_measurement(t.is_inverted_result_target(), buf_q1.str().c_str(), buf_m.str().c_str()); measurement_offset++; } for (size_t k = 0; k < cnot.targets.size(); k += 2) { @@ -180,10 +172,7 @@ struct QasmExporter { }); } - void output_decomposable_instruction( - const CircuitInstruction &instruction, - bool decompose_inline) { - + void output_decomposable_instruction(const CircuitInstruction &instruction, bool decompose_inline) { auto f = GATE_DATA[instruction.gate_type].flags; auto step = (f & GATE_TARGETS_PAIRS) ? 2 : 1; for (size_t k = 0; k < instruction.targets.size(); k += step) { @@ -234,8 +223,8 @@ struct QasmExporter { auto t1 = instruction.targets[k]; auto t2 = instruction.targets[k + 1]; if (t1.is_qubit_target() && t2.is_qubit_target()) { - out << qasm_names[(int)instruction.gate_type] << " q[" << t1.qubit_value() << "], q[" << t2.qubit_value() - << "];\n"; + out << qasm_names[(int)instruction.gate_type] << " q[" << t1.qubit_value() << "], q[" + << t2.qubit_value() << "];\n"; } else if (t1.is_qubit_target() || t2.is_qubit_target()) { GateTarget control; GateTarget target; diff --git a/src/stim/diagram/timeline/timeline_ascii_drawer.test.cc b/src/stim/diagram/timeline/timeline_ascii_drawer.test.cc index 98a8ec4d2..54c5b56bc 100644 --- a/src/stim/diagram/timeline/timeline_ascii_drawer.test.cc +++ b/src/stim/diagram/timeline/timeline_ascii_drawer.test.cc @@ -513,11 +513,6 @@ TEST(circuit_diagram_timeline_text, repetition_code_transposed) { TEST(circuit_diagram_timeline_text, test_circuit_all_ops) { auto circuit = generate_test_circuit_with_all_operations(); - std::cerr << "\n" + DiagramTimelineAsciiDrawer::make_diagram(circuit).str() + "\n" << "===============\n"; - std::cerr << "\n" + DiagramTimelineAsciiDrawer::make_diagram(circuit).str() + "\n" << "===============\n"; - std::cerr << "\n" + DiagramTimelineAsciiDrawer::make_diagram(circuit).str() + "\n" << "===============\n"; - std::cerr << "\n" + DiagramTimelineAsciiDrawer::make_diagram(circuit).str() + "\n" << "===============\n"; - std::cerr << "\n" + DiagramTimelineAsciiDrawer::make_diagram(circuit).str() + "\n" << "===============\n"; ASSERT_EQ("\n" + DiagramTimelineAsciiDrawer::make_diagram(circuit).str() + "\n", R"DIAGRAM( /-------------------\ /--------------\ /---------------------\ /-----------------------------------------------------------------------------------------------------------------------------------------------------\ /------------------------------------\ /REP 3 /---\ \ /--------------------------------------------------------------------------------------------------------------------------\ q0: -QUBIT_COORDS(1,2,3)-I-C_XYZ-SQRT_X-----ZSWAP-----SQRT_XX-----X------------DEPOLARIZE1(0.02)---------------X_ERROR(0.01)------------------------------------------------------------------------------------------------MPP[X]:rec[2]-MPP[Z]:rec[3]-MRX:rec[4]-MXX:rec[11]-|------H-@---|---MR:rec[15]-X_ERROR(0.1)-MR(0.01):rec[16]-DETECTOR(2,4,6):D0=rec[16]-OBSERVABLE_INCLUDE:L0*=rec[16]-MPAD:rec[17]-MPAD:rec[19]-MRX:rec[20]----X^rec[24]-- diff --git a/src/stim/simulators/tableau_simulator.inl b/src/stim/simulators/tableau_simulator.inl index 112fde44a..de872532b 100644 --- a/src/stim/simulators/tableau_simulator.inl +++ b/src/stim/simulators/tableau_simulator.inl @@ -1445,35 +1445,7 @@ std::pair> TableauSimulator::measure_kickback_x(GateTarg template std::vector> TableauSimulator::canonical_stabilizers() const { - Tableau t = inv_state.inverse(); - size_t n = t.num_qubits; - std::vector> stabilizers; - for (size_t k = 0; k < n; k++) { - stabilizers.push_back(t.zs[k]); - } - - size_t min_pivot = 0; - for (size_t q = 0; q < n; q++) { - for (size_t b = 0; b < 2; b++) { - size_t pivot = min_pivot; - while (pivot < n && !(b ? stabilizers[pivot].zs : stabilizers[pivot].xs)[q]) { - pivot++; - } - if (pivot == n) { - continue; - } - for (size_t s = 0; s < n; s++) { - if (s != pivot && (b ? stabilizers[s].zs : stabilizers[s].xs)[q]) { - stabilizers[s].ref() *= stabilizers[pivot]; - } - } - if (min_pivot != pivot) { - std::swap(stabilizers[min_pivot], stabilizers[pivot]); - } - min_pivot += 1; - } - } - return stabilizers; + return inv_state.inverse().stabilizers(true); } template diff --git a/src/stim/stabilizers/tableau.h b/src/stim/stabilizers/tableau.h index 0c728598e..30aa684c7 100644 --- a/src/stim/stabilizers/tableau.h +++ b/src/stim/stabilizers/tableau.h @@ -227,6 +227,8 @@ struct Tableau { PauliString inverse_y_output(size_t input_index, bool skip_sign = false) const; /// Faster version of tableau.inverse().zs[input_index]. PauliString inverse_z_output(size_t input_index, bool skip_sign = false) const; + + std::vector> stabilizers(bool canonical) const; }; template diff --git a/src/stim/stabilizers/tableau.inl b/src/stim/stabilizers/tableau.inl index 3c7a750a1..88d861599 100644 --- a/src/stim/stabilizers/tableau.inl +++ b/src/stim/stabilizers/tableau.inl @@ -749,4 +749,38 @@ PauliString Tableau::y_output(size_t input_index) const { return result; } +template +std::vector> Tableau::stabilizers(bool canonical) const { + std::vector> stabilizers; + for (size_t k = 0; k < num_qubits; k++) { + stabilizers.push_back(zs[k]); + } + + if (canonical) { + size_t min_pivot = 0; + for (size_t q = 0; q < num_qubits; q++) { + for (size_t b = 0; b < 2; b++) { + size_t pivot = min_pivot; + while (pivot < num_qubits && !(b ? stabilizers[pivot].zs : stabilizers[pivot].xs)[q]) { + pivot++; + } + if (pivot == num_qubits) { + continue; + } + for (size_t s = 0; s < num_qubits; s++) { + if (s != pivot && (b ? stabilizers[s].zs : stabilizers[s].xs)[q]) { + stabilizers[s].ref() *= stabilizers[pivot]; + } + } + if (min_pivot != pivot) { + std::swap(stabilizers[min_pivot], stabilizers[pivot]); + } + min_pivot += 1; + } + } + } + + return stabilizers; +} + } // namespace stim diff --git a/src/stim/stabilizers/tableau.pybind.cc b/src/stim/stabilizers/tableau.pybind.cc index ce5eb5d8f..7c946203e 100644 --- a/src/stim/stabilizers/tableau.pybind.cc +++ b/src/stim/stabilizers/tableau.pybind.cc @@ -2193,4 +2193,64 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_ &self, bool canonical) { + auto stabilizers = self.stabilizers(canonical); + std::vector result; + result.reserve(stabilizers.size()); + for (auto &s : stabilizers) { + result.emplace_back(std::move(s), false); + } + return result; + }, + pybind11::kw_only(), + pybind11::arg("canonicalize") = false, + clean_doc_string(R"DOC( + Returns the stabilizer generators of the tableau, optionally canonicalized. + + The stabilizer generators of the tableau are its Z outputs. Canonicalizing + standardizes the generators, so that states that are equal will produce the + same generators. For example, [ZI, IZ], [ZI, ZZ], amd [ZZ, ZI] describe equal + states and all canonicalize to [ZI, IZ]. + + The canonical form is computed as follows: + + 1. Get a list of stabilizers using `tableau.z_output(k)` for each k. + 2. Perform Gaussian elimination. pivoting on standard generators. + 2a) Pivot on g=X0 first, then Z0, X1, Z1, X2, Z2, etc. + 2b) Find a stabilizer that uses the generator g. If there are none, + go to the next g. + 2c) Multiply that stabilizer into all other stabilizers that use the + generator g. + 2d) Swap that stabilizer with the stabilizer at position `r` then + increment `r`. `r` starts at 0. + + Args: + canonicalize: Defaults to False. When False, the tableau's Z outputs + are returned unchanged. When True, the Z outputs are rewritten + into a standard form. Two stabilizer states have the same standard + form if and only if they describe equivalent quantum states. + + Returns: + A List[stim.PauliString] of the tableau's stabilizer generators. + + Examples: + >>> import stim + >>> t = stim.Tableau.from_named_gate("CNOT") + + >>> raw_stabilizers = t.to_stabilizers() + >>> for e in raw_stabilizers: + ... print(repr(e)) + stim.PauliString("+Z_") + stim.PauliString("+ZZ") + + >>> canonical_stabilizers = t.to_stabilizers(canonicalize=True) + >>> for e in canonical_stabilizers: + ... print(repr(e)) + stim.PauliString("+Z_") + stim.PauliString("+_Z") + )DOC") + .data()); } diff --git a/src/stim/stabilizers/tableau_pybind_test.py b/src/stim/stabilizers/tableau_pybind_test.py index 95a1e6925..83060b64c 100644 --- a/src/stim/stabilizers/tableau_pybind_test.py +++ b/src/stim/stabilizers/tableau_pybind_test.py @@ -923,3 +923,24 @@ def test_signs(): _ = t.y_sign(2) with pytest.raises(ValueError, match="target"): _ = t.z_sign(2) + + +def test_to_stabilizers(): + t = stim.Tableau.from_stabilizers([ + stim.PauliString("XXXX"), + stim.PauliString("YYYY"), + stim.PauliString("YYZZ"), + stim.PauliString("XXZZ"), + ]) + assert t.to_stabilizers() == [ + stim.PauliString("XXXX"), + stim.PauliString("YYYY"), + stim.PauliString("YYZZ"), + stim.PauliString("XXZZ"), + ] + assert t.to_stabilizers(canonicalize=True) == [ + stim.PauliString("-XX__"), + stim.PauliString("-ZZ__"), + stim.PauliString("-__XX"), + stim.PauliString("-__ZZ"), + ]