Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stim.Tableau.to_stabilizers #663

Merged
merged 1 commit into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading