Skip to content

Commit

Permalink
Add stim.Tableau.to_stabilizers
Browse files Browse the repository at this point in the history
Fixes #637
  • Loading branch information
Strilanc committed Nov 20, 2023
1 parent 3b995f1 commit 592807a
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 50 deletions.
57 changes: 57 additions & 0 deletions doc/python_api_reference_vDev.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -10349,6 +10350,62 @@ def to_pauli_string(
"""
```

<a name="stim.Tableau.to_stabilizers"></a>
```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")
"""
```

<a name="stim.Tableau.to_state_vector"></a>
```python
# stim.Tableau.to_state_vector
Expand Down
49 changes: 49 additions & 0 deletions doc/stim.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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,
*,
Expand Down
49 changes: 49 additions & 0 deletions glue/python/src/stim/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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,
*,
Expand Down
21 changes: 5 additions & 16 deletions src/stim/circuit/export_qasm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ struct QasmExporter {
reference_sample(stats.num_measurements),
measurement_offset(0),
detector_offset(0) {

// Init used_gates.
collect_used_gates(circuit);

Expand All @@ -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;
};
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 0 additions & 5 deletions src/stim/diagram/timeline/timeline_ascii_drawer.test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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]--
Expand Down
30 changes: 1 addition & 29 deletions src/stim/simulators/tableau_simulator.inl
Original file line number Diff line number Diff line change
Expand Up @@ -1445,35 +1445,7 @@ std::pair<bool, PauliString<W>> TableauSimulator<W>::measure_kickback_x(GateTarg

template <size_t W>
std::vector<PauliString<W>> TableauSimulator<W>::canonical_stabilizers() const {
Tableau<W> t = inv_state.inverse();
size_t n = t.num_qubits;
std::vector<PauliString<W>> 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 <size_t W>
Expand Down
2 changes: 2 additions & 0 deletions src/stim/stabilizers/tableau.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ struct Tableau {
PauliString<W> inverse_y_output(size_t input_index, bool skip_sign = false) const;
/// Faster version of tableau.inverse().zs[input_index].
PauliString<W> inverse_z_output(size_t input_index, bool skip_sign = false) const;

std::vector<PauliString<W>> stabilizers(bool canonical) const;
};

template <size_t W>
Expand Down
34 changes: 34 additions & 0 deletions src/stim/stabilizers/tableau.inl
Original file line number Diff line number Diff line change
Expand Up @@ -749,4 +749,38 @@ PauliString<W> Tableau<W>::y_output(size_t input_index) const {
return result;
}

template <size_t W>
std::vector<PauliString<W>> Tableau<W>::stabilizers(bool canonical) const {
std::vector<PauliString<W>> 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
Loading

0 comments on commit 592807a

Please sign in to comment.