Skip to content
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
125 changes: 118 additions & 7 deletions pygsti/circuits/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# in compliance with the License. You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 or in the LICENSE file in the root pyGSTi directory.
#***************************************************************************************************

from __future__ import annotations
import collections as _collections
import itertools as _itertools
import warnings as _warnings
Expand All @@ -22,6 +22,10 @@
from pygsti.tools import slicetools as _slct
from pygsti.tools.legacytools import deprecate as _deprecate_fn

from typing import Union, Optional, TYPE_CHECKING
if TYPE_CHECKING:
import stim

#Externally, we'd like to do thinks like:
# c = Circuit( LabelList )
# c.append_line("Q0")
Expand Down Expand Up @@ -3762,7 +3766,9 @@ def _write_q_circuit_tex(self, filename): # TODO
f.close()


def convert_to_stim_tableau_layers(self, gate_name_conversions=None, num_qubits=None):
def convert_to_stim_tableau_layers(self, gate_name_conversions: Optional[dict[str, stim.Tableau]] = None,
num_qubits: Optional[int] = None,
qubit_label_conversions: Optional[dict[Union[str, int], int]] = None) -> list[stim.Tableau]:
"""
Converts this circuit to a list of stim tableau layers

Expand All @@ -3773,6 +3779,41 @@ def convert_to_stim_tableau_layers(self, gate_name_conversions=None, num_qubits=
If None a standard set of gate names is used from
`pygsti.tools.internalgates`

num_qubits : int, optional (default None)
Number of qubits which should be included in the each Tableau.
If None this value will be attempted to be inferred from the
Circuit's line_labels.

qubit_label_conversions : dict, optional (default None)
A map from the circuit's qubit labels into integers in the range 0 to N-1,
where N is the number of qubits, where the integer indices indicate
which qubit in the stim Tableau to map a given circuit operation into.
If not specified an attempt will be made to infer this mapping based on the
circuit's qubit labels and an exception will be made if this inference is not possible.

Note: in the following line_labels = self.line_labels.

Inference is supported for line labels with the following format.

- Qubit labels that are either integers, or string representations of integers, prefixed by
the character 'Q' or 'q', that are monotonically increasing.

If num_qubits is not specified, then these qubit labels will be mapped into the range [0,N-1] based
on their index.

If num_qubits is specified then there are different subcases:
If num_qubits == len(line_labels) then these qubit labels will be mapped into the range [0,N-1] based
on their index.
If num_qubit > len(line_labels) we have two possibilities:
1. If max(line_labels) <= num_qubits then the line_labels are mapped directly into their value
in the range [0, N-1].
2. If max(line_labels) > num_qubits then the line_labels are mapped into [0,N-1] using their index.
Note if num_qubits<len(line_labels) then an exception is raised.

If the default conversion behavior doesn't suit your needs, or doesn't support your label format then
a manual dictionary should be specified.


Returns
-------
A layer by layer list of stim tableaus
Expand All @@ -3783,12 +3824,46 @@ def convert_to_stim_tableau_layers(self, gate_name_conversions=None, num_qubits=
raise ImportError("Stim is required for this operation, and it does not appear to be installed.")
if gate_name_conversions is None:
gate_name_conversions = _itgs.standard_gatenames_stim_conversions()
line_labels = self._line_labels

if num_qubits is None:
line_labels = self._line_labels
assert line_labels != ('*',), "Cannot convert circuits with placeholder line label to stim Tableau unless number of qubits is specified."
num_qubits=len(line_labels)


if qubit_label_conversions is None: #attempt to infer this.
assert len(line_labels)<=num_qubits, 'More line labels specified than qubits, cannot infer qubit label conversion mapping.'

#case 1: qubit labels are monotonically increasing integers and span a range of length num_qubits.
if all([isinstance(lbl, int) for lbl in line_labels]) and all([(line_labels[i+1]-line_labels[i])>0 for i in range(len(line_labels)-1)]):
if len(line_labels) == num_qubits or max(line_labels)>num_qubits:
qubit_label_conversions = {lbl:i for i, lbl in enumerate(line_labels)}
elif max(line_labels)<=num_qubits:
qubit_label_conversions = {lbl:lbl for lbl in line_labels}

#Case 2: qubit labels are strings of the form 'Qi' or 'qi' where the i's are string representations of integers otherwise matching the constraints of
#case 1.
elif all([isinstance(lbl,str) for lbl in line_labels]):
if all([lbl[0]=='Q' or lbl[0]=='q' for lbl in line_labels]) and all([lbl[1:].isnumeric() for lbl in line_labels]):
int_line_labels = [int(lbl[1:]) for lbl in line_labels]
if all([(int_line_labels[i+1] - int_line_labels[i])>0 for i in range(len(line_labels)-1)]):
if len(int_line_labels) == num_qubits or max(int_line_labels)>num_qubits:
qubit_label_conversions = {lbl:i for i, lbl in enumerate(line_labels)}
elif max(int_line_labels)<=num_qubits:
qubit_label_conversions = {str(lbl):lbl for lbl in int_line_labels}
else:
raise ValueError(f'Unsupported line_label type {type(line_labels[0])}, only str or int supported.')

if qubit_label_conversions is None: #then no automatic conversion was inferred.
msg = 'Unable to infer a mapping from line labels of this circuit into integers in the range [0,N-1] '\
+'as required for conversion to a stim.Tableau object. Please see the docstring for this method for the '\
+'supported automatic inferencing. Please manually specify a qubit_label_conversions dictionary.'
raise RuntimeError(msg)
else: #validate qubit_label_conversion dictionary has the keys we need.
msg = 'qubit_label_conversions does not contain keys for all of the line_labels in this circuit.'
assert all([lbl in qubit_label_conversions for lbl in line_labels]), msg
msg1 = 'All qubit_label_conversions values should be ints in the range [0, num_qubits-1]'
assert all([isinstance(val,int) and (0 <= val <=num_qubits-1) for val in qubit_label_conversions.values()]), msg1

stim_layers=[]

if self._static:
Expand All @@ -3800,11 +3875,13 @@ def convert_to_stim_tableau_layers(self, gate_name_conversions=None, num_qubits=
stim_layer = empty_tableau.copy()
for sub_lbl in layer:
temp = gate_name_conversions[sub_lbl.name]
stim_layer.append(temp, sub_lbl.qubits)
stim_layer.append(temp, [qubit_label_conversions[qubit_lbl] for qubit_lbl in sub_lbl.qubits])
stim_layers.append(stim_layer)
return stim_layers

def convert_to_stim_tableau(self, gate_name_conversions=None):
def convert_to_stim_tableau(self, gate_name_conversions: Optional[dict[str, stim.Tableau]] = None,
num_qubits: Optional[int] = None,
qubit_label_conversions: Optional[dict[Union[str, int], int]] = None) -> stim.Tableau:
"""
Converts this circuit to a stim tableau

Expand All @@ -3814,12 +3891,46 @@ def convert_to_stim_tableau(self, gate_name_conversions=None):
A map from pygsti gatenames to standard stim tableaus.
If None a standard set of gate names is used from
`pygsti.tools.internalgates`

num_qubits : int, optional (default None)
Number of qubits which should be included in the overall Tableau.
If None this value will be attempted to be inferred from the
Circuit's line_labels.

qubit_label_conversions : dict, optional (default None)
A map from the circuit's qubit labels into integers in the range 0 to N-1,
where N is the number of qubits, where the integer indices indicate
which qubit in the stim Tableau to map a given circuit operation into.
If not specified an attempt will be made to infer this mapping based on the
circuit's qubit labels and an exception will be made if this inference is not possible.

Note: in the following line_labels = self.line_labels.

Inference is supported for line labels with the following format.

- Qubit labels that are either integers, or string representations of integers, prefixed by
the character 'Q' or 'q', that are monotonically increasing.

If num_qubits is not specified, then these qubit labels will be mapped into the range [0,N-1] based
on their index.

If num_qubits is specified then there are different subcases:
If num_qubits == len(line_labels) then these qubit labels will be mapped into the range [0,N-1] based
on their index.
If num_qubit > len(line_labels) we have two possibilities:
1. If max(line_labels) <= num_qubits then the line_labels are mapped directly into their value
in the range [0, N-1].
2. If max(line_labels) > num_qubits then the line_labels are mapped into [0,N-1] using their index.
Note if num_qubits<len(line_labels) then an exception is raised.

If the default conversion behavior doesn't suit your needs, or doesn't support your label format then
a manual dictionary should be specified.

Returns
-------
A single stim.Tableau representing the entire circuit.
"""
layers=self.convert_to_stim_tableau_layers(gate_name_conversions)
layers=self.convert_to_stim_tableau_layers(gate_name_conversions, num_qubits, qubit_label_conversions)
if layers:
tableau=layers[0]
for layer in layers[1:]:
Expand Down
Loading
Loading