pyquil.experiment package¶
Classes and functions to facilitate running experiments on a quantum computer.
- class pyquil.experiment.CalibrationMethod(value)[source]¶
Bases:
IntEnum
An enumeration.
- MINUS_EIGENSTATE = -1¶
- NONE = 0¶
- PLUS_EIGENSTATE = 1¶
- class pyquil.experiment.Experiment(settings: list[ExperimentSetting] | list[list[ExperimentSetting]], program: Program, *, symmetrization: int = SymmetrizationLevel.EXHAUSTIVE, calibration: int = CalibrationMethod.PLUS_EIGENSTATE)[source]¶
Bases:
object
A tomography-like experiment.
Many near-term quantum algorithms involve:
some limited state preparation
enacting a quantum process (like in tomography) or preparing a variational ansatz state (like in VQE)
measuring observables of the state.
Where we typically use a large number of (state_prep, measure) pairs but keep the ansatz program consistent. This class stores the ansatz program as a
Program
and maintains a list ofExperimentSetting
objects which each represent a (state_prep, measure) pair.Settings diagonalized by a shared tensor product basis (TPB) can (optionally) be estimated simultaneously. Therefore, this class is backed by a list of list of ExperimentSettings. Settings sharing an inner list will be estimated simultaneously. If you don’t want this, provide a list of length-1-lists. As a convenience, if you pass a 1D list to the constructor will expand it to a list of length-1-lists.
This class will not group settings for you. Please see
group_experiments()
for a function that will automatically process a Experiment to group Experiments sharing a TPB.- Variables:
settings – The collection of ExperimentSetting objects that define this experiment.
program – The main program body of this experiment. Also determines the
shots
andreset
instance variables. Theshots
instance variable is the number of shots to take per ExperimentSetting. Thereset
instance variable is whether to actively reset qubits instead of waiting several times the coherence length for qubits to decay to|0>
naturally. Setting this to True is much faster but there is a ~1% error per qubit in the reset operation. Thermal noise from “traditional” reset is not routinely characterized but is of the same order.symmetrization –
the level of readout symmetrization to perform for the estimation and optional calibration of each observable. The following integer levels, encapsulated in the
SymmetrizationLevel
integer enum, are currently supported:-1 – exhaustive symmetrization uses every possible combination of flips
0 – no symmetrization
1 – symmetrization using an orthogonal array (OA) with strength 1
2 – symmetrization using an orthogonal array (OA) with strength 2
3 – symmetrization using an orthogonal array (OA) with strength 3
Note that (default) exhaustive symmetrization requires a number of QPU calls exponential in the number of qubits in the union of the support of the observables in any group of settings in
tomo_experiment
; the number of shots may need to be increased to accommodate this. seerun_symmetrized_readout()
in api._quantum_computer for more information.
- append(expts: ExperimentSetting | list[ExperimentSetting]) None [source]¶
- build_setting_memory_map(setting: ExperimentSetting) dict[str, list[float]] [source]¶
Build the memory map for state prep and measurement specs in the ExperimentSetting, considering all qubits.
The memory map is built corresponding to the state preparation and measurement specifications encoded in the provided ExperimentSetting, taking into account the full set of qubits present in the Experiment object.
- Returns:
Memory map for state prep and measurement.
- build_symmetrization_memory_maps(qubits: Sequence[int], label: str = 'symmetrization') list[dict[str, list[float]]] [source]¶
Build a list of memory maps for readout symmetrization in a program using parametric compilation.
For example, if we have the following program:
RX(symmetrization[0]) 0 RX(symmetrization[1]) 1 MEASURE 0 ro[0] MEASURE 1 ro[1]
We can perform exhaustive readout symmetrization on our two qubits by providing the four following memory maps, and then appropriately flipping the resultant bitstrings:
{‘symmetrization’: [0.0, 0.0]} -> XOR results with [0,0] {‘symmetrization’: [0.0, pi]} -> XOR results with [0,1] {‘symmetrization’: [pi, 0.0]} -> XOR results with [1,0] {‘symmetrization’: [pi, pi]} -> XOR results with [1,1]
- Parameters:
qubits – List of qubits to symmetrize readout for.
label – Name of the declared memory region. Defaults to “symmetrization”.
- Returns:
List of memory maps that performs the desired level of symmetrization.
- count(expt: list[ExperimentSetting]) int [source]¶
- extend(expts: list[list[ExperimentSetting]]) None [source]¶
- generate_calibration_experiment() Experiment [source]¶
Generate an Experiment for calibrating multi-qubit observables to mitigate readout errors.
Generate another Experiment object to calibrate the various multi-qubit observables involved in this Experiment. This is done by preparing the plus-one (minus-one) eigenstate of each out_operator and measuring the resulting expectation value of the same out_operator. Ideally, this should always yield +1 (-1), but the presence of symmetric readout error scales the results by a constant factor. Determining this scale factor is known as readout calibration, allowing for error mitigation in subsequent measurements by dividing by the scale factor.
- Returns:
A new
Experiment
that can calibrate the readout error of all the observables involved in this experiment.
- generate_experiment_program() Program [source]¶
Generate a parameterized program with the main body and additions for state prep, measurement, and symmetrization.
State preparation and measurement are achieved via ZXZXZ-decomposed single-qubit gates, where the angles of each
RZ
rotation are declared parameters that can be assigned at runtime. Symmetrization is achieved by putting anRX
gate (also parameterized by a declared value) before eachMEASURE
operation. In addition, aRESET
operation is prepended to theProgram
if the experiment has active qubit reset enabled. Finally, each qubit specified in the settings is measured, and the number of shots is added.- Returns:
Parameterized
Program
that is capable of collecting statistics for everyExperimentSetting
in thisExperiment
.
- get_meas_qubits() list[int] [source]¶
Return the sorted list of qubits involved in all out_operators of this Experiment object’s settings.
- get_meas_registers(qubits: Sequence[int] | None = None) list[int] [source]¶
Return the sorted list of memory registers corresponding to the list of qubits provided.
If no qubits are provided, just returns the list of numbers from 0 to n-1 where n is the number of qubits resulting from the
get_meas_qubits
method.
- index(expt: list[ExperimentSetting], start: int = 0, stop: int = 0) int [source]¶
- insert(index: int, expt: list[ExperimentSetting]) None [source]¶
- pop(index: int = 0) list[ExperimentSetting] [source]¶
- remove(expt: list[ExperimentSetting]) None [source]¶
- sort(key: Callable[[list[ExperimentSetting]], Any] | None = None, reverse: bool = False) None [source]¶
- class pyquil.experiment.ExperimentResult(setting: ExperimentSetting, expectation: float | complex, total_counts: int, std_err: float | complex | None = None, raw_expectation: float | complex | None = None, raw_std_err: float | complex | None = None, calibration_expectation: float | complex | None = None, calibration_std_err: float | complex | None = None, calibration_counts: int | None = None, additional_results: list[ExperimentResult] | None = None)[source]¶
Bases:
object
An expectation and standard deviation for the measurement of one experiment setting in a tomographic experiment.
In the case of readout error calibration, we also include expectation, standard deviation and count for the calibration results, as well as the expectation and standard deviation for the corrected results.
- additional_results: list[ExperimentResult] | None = None¶
- calibration_counts: int | None = None¶
- calibration_expectation: float | complex | None = None¶
- calibration_std_err: float | complex | None = None¶
- expectation: float | complex¶
- raw_expectation: float | complex | None = None¶
- raw_std_err: float | None = None¶
- setting: ExperimentSetting¶
- std_err: float | complex | None = None¶
- total_counts: int¶
- class pyquil.experiment.ExperimentSetting(in_state: TensorProductState, out_operator: PauliTerm, additional_expectations: list[list[int]] | None = None)[source]¶
Bases:
object
Input and output settings for a tomography-like experiment.
Many near-term quantum algorithms take the following form:
Start in a pauli state
Prepare some ansatz
Measure it w.r.t. pauli operators
Where we typically use a large number of (start, measure) pairs but keep the ansatz preparation program consistent. This class represents the (start, measure) pairs. Typically a large number of these
ExperimentSetting
objects will be created and grouped into anExperiment
.- Variables:
additional_expectations – A list of lists, where each inner list specifies a qubit subset to calculate the joint expectation value for. This attribute allows users to extract simultaneously measurable expectation values from a single experiment setting.
- additional_expectations: list[list[int]] | None = None¶
- classmethod from_str(s: str) ExperimentSetting [source]¶
Opposite of str(expt).
- in_state: TensorProductState¶
- class pyquil.experiment.OperatorEncoder(*, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False, indent=None, separators=None, default=None)[source]¶
Bases:
JSONEncoder
Constructor for JSONEncoder, with sensible defaults.
If skipkeys is false, then it is a TypeError to attempt encoding of keys that are not str, int, float or None. If skipkeys is True, such items are simply skipped.
If ensure_ascii is true, the output is guaranteed to be str objects with all incoming non-ASCII characters escaped. If ensure_ascii is false, the output can contain non-ASCII characters.
If check_circular is true, then lists, dicts, and custom encoded objects will be checked for circular references during encoding to prevent an infinite recursion (which would cause an RecursionError). Otherwise, no such check takes place.
If allow_nan is true, then NaN, Infinity, and -Infinity will be encoded as such. This behavior is not JSON specification compliant, but is consistent with most JavaScript based encoders and decoders. Otherwise, it will be a ValueError to encode such floats.
If sort_keys is true, then the output of dictionaries will be sorted by key; this is useful for regression tests to ensure that JSON serializations can be compared on a day-to-day basis.
If indent is a non-negative integer, then JSON array elements and object members will be pretty-printed with that indent level. An indent level of 0 will only insert newlines. None is the most compact representation.
If specified, separators should be an (item_separator, key_separator) tuple. The default is (’, ‘, ‘: ‘) if indent is
None
and (‘,’, ‘: ‘) otherwise. To get the most compact JSON representation, you should specify (‘,’, ‘:’) to eliminate whitespace.If specified, default is a function that gets called for objects that can’t otherwise be serialized. It should return a JSON encodable version of the object or raise a
TypeError
.- default(o: Any) Any [source]¶
Implement this method in a subclass such that it returns a serializable object for
o
, or calls the base implementation (to raise aTypeError
).For example, to support arbitrary iterators, you could implement default like this:
def default(self, o): try: iterable = iter(o) except TypeError: pass else: return list(iterable) # Let the base class default method raise the TypeError return JSONEncoder.default(self, o)
- pyquil.experiment.SIC0(q: int) TensorProductState [source]¶
- pyquil.experiment.SIC1(q: int) TensorProductState [source]¶
- pyquil.experiment.SIC2(q: int) TensorProductState [source]¶
- pyquil.experiment.SIC3(q: int) TensorProductState [source]¶
- class pyquil.experiment.SymmetrizationLevel(value)[source]¶
Bases:
IntEnum
An enumeration.
- EXHAUSTIVE = -1¶
- NONE = 0¶
- OA_STRENGTH_1 = 1¶
- OA_STRENGTH_2 = 2¶
- OA_STRENGTH_3 = 3¶
- class pyquil.experiment.TensorProductState(states: Iterable[_OneQState] | None = None)[source]¶
Bases:
object
A description of a multi-qubit quantum state that is a tensor product of many _OneQStates states.
- classmethod from_str(s: str) TensorProductState [source]¶
- states: list[_OneQState]¶
- pyquil.experiment.bitstrings_to_expectations(bitstrings: ndarray, joint_expectations: list[list[int]] | None = None) ndarray [source]¶
Given an array of bitstrings, map them to expectation values and return the desired joint expectation values.
If no joint expectations are desired, then just the 1 -> -1, 0 -> 1 mapping is performed.
- Parameters:
bitstrings – Array of bitstrings to map.
joint_expectations – Joint expectation values to calculate. Each entry is a list which contains the qubits to use in calculating the joint expectation value. Entries of length one just calculate single-qubit expectation values. Defaults to None, which is equivalent to the list of single-qubit expectations [[0], [1], …, [n-1]] for bitstrings of length n.
- Returns:
An array of expectation values, of the same length as the array of bitstrings. The “width” could be different than the length of an individual bitstring (n) depending on the value of the
joint_expectations
parameter.
- pyquil.experiment.correct_experiment_result(result: ExperimentResult, calibration: ExperimentResult) ExperimentResult [source]¶
Given a raw, unmitigated result and its associated readout calibration, produce the result absent readout error.
- Parameters:
result – An
ExperimentResult
object with unmitigated readout error.calibration – An
ExperimentResult
object resulting from running readout calibration on theExperimentSetting
associated with theresult
parameter.
- Returns:
An
ExperimentResult
object corrected for symmetric readout error.
- pyquil.experiment.get_results_by_qubit_groups(results: Iterable[ExperimentResult], qubit_groups: Sequence[Sequence[int]]) dict[tuple[int, ...], list[ExperimentResult]] [source]¶
Organizes ExperimentResults by the group of qubits on which the observable of the result acts.
Each experiment result will be associated with a qubit group key if the observable of the result.setting acts on a subset of the qubits in the group. If the result does not act on a subset of qubits of any given group then the result is ignored.
Note that for groups of qubits which are not pairwise disjoint, one result may be associated to multiple groups.
- Parameters:
qubit_groups – groups of qubits for which you want the pertinent results.
results – ExperimentResults from running an Experiment
- Returns:
a dictionary whose keys are individual groups of qubits (as sorted tuples). The corresponding value is the list of experiment results whose observables measure some subset of that qubit group. The result order is maintained within each group.
- pyquil.experiment.group_settings(experiments: Experiment, method: str = 'greedy') Experiment [source]¶
Group experiments that are diagonal in a shared tensor product basis (TPB) to minimize number of QPU runs.
Background
Given some PauliTerm operator, the ‘natural’ tensor product basis to diagonalize this term is the one which diagonalizes each Pauli operator in the product term-by-term.
For example, X(1) * Z(0) would be diagonal in the ‘natural’ tensor product basis
{(|0> +/- |1>)/Sqrt[2]} * {|0>, |1>}
, whereas Z(1) * X(0) would be diagonal in the ‘natural’ tpb{|0>, |1>} * {(|0> +/- |1>)/Sqrt[2]}
. The two operators commute but are not diagonal in each others ‘natural’ tpb (in fact, they are anti-diagonal in each others ‘natural’ tpb). This function tests whether two operators given as PauliTerms are both diagonal in each others ‘natural’ tpb.Note that for the given example of X(1) * Z(0) and Z(1) * X(0), we can construct the following basis which simultaneously diagonalizes both operators:
-- |0>' = |0> (|+>) + |1> (|->) -- |1>' = |0> (|+>) - |1> (|->) -- |2>' = |0> (|->) + |1> (|+>) -- |3>' = |0> (-|->) + |1> (|+>)
In this basis, X Z looks like diag(1, -1, 1, -1), and Z X looks like diag(1, 1, -1, -1). Notice however that this basis cannot be constructed with single-qubit operations, as each of the basis vectors are entangled states.
Methods
The “greedy” method will keep a running set of ‘buckets’ into which grouped ExperimentSettings will be placed. Each new ExperimentSetting considered is assigned to the first applicable bucket and a new bucket is created if there are no applicable buckets.
The “clique-removal” method maps the term grouping problem onto Max Clique graph problem. This method constructs a NetworkX graph where an edge exists between two settings that share an nTPB and then uses networkx’s algorithm for clique removal. This method can give you marginally better groupings in certain circumstances, but constructing the graph is pretty slow so “greedy” is the default.
- Parameters:
experiments – a tomography experiment
method – method used for grouping; the allowed methods are one of [‘greedy’, ‘clique-removal’]
- Returns:
a tomography experiment with all the same settings, just grouped according to shared TPBs.
- pyquil.experiment.merge_disjoint_experiments(experiments: list[Experiment], group_merged_settings: bool = True) Experiment [source]¶
Merge the experiments into one that runs all individual programs and includes all combined settings.
A group of Experiments whose programs operate on disjoint sets of qubits can be ‘parallelized’ so that the total number of runs can be reduced after grouping the settings. Settings which act on disjoint sets of qubits can be automatically estimated from the same run on the quantum computer.
If any experiment programs act on a shared qubit they cannot be thoughtlessly composed since the order of operations on the shared qubit may have a significant impact on the program behaviour; therefore we do not recommend using this method if this is the case.
Even when the individual experiments act on disjoint sets of qubits you must be careful not to associate ‘parallel’ with ‘simultaneous’ execution. Physically the gates specified in a pyquil Program occur as soon as resources are available; meanwhile, measurement happens only after all gates. There is no specification of the exact timing of gates beyond their causal relationships. Therefore, while grouping experiments into parallel operation can be quite beneficial for time savings, do not depend on any simultaneous execution of gates on different qubits, and be wary of the fact that measurement happens only after all gates have finished.
Note that to get the time saving benefits the settings must be grouped on the merged experiment–by default this is done before returning the experiment.
- Parameters:
experiments – a group of experiments to combine into a single experiment
group_merged_settings – By default group the settings of the merged experiment.
- Returns:
a single experiment that runs the summed program and all settings.
- pyquil.experiment.merge_memory_map_lists(mml1: list[dict[str, list[float]]], mml2: list[dict[str, list[float]]]) list[dict[str, list[float]]] [source]¶
Given two lists of memory maps, produce the “cartesian product” of the memory maps.
For example:
merge_memory_map_lists([{a: 1}, {a: 2}], [{b: 3, c: 4}, {b: 5, c: 6}])
-> [{a: 1, b: 3, c: 4}, {a: 1, b: 5, c: 6}, {a: 2, b: 3, c: 4}, {a: 2, b: 5, c: 6}]
- Parameters:
mml1 – The first memory map list.
mml2 – The second memory map list.
- Returns:
A list of the merged memory maps.
- pyquil.experiment.minusX(q: int) TensorProductState [source]¶
- pyquil.experiment.minusY(q: int) TensorProductState [source]¶
- pyquil.experiment.minusZ(q: int) TensorProductState [source]¶
- pyquil.experiment.plusX(q: int) TensorProductState [source]¶
- pyquil.experiment.plusY(q: int) TensorProductState [source]¶
- pyquil.experiment.plusZ(q: int) TensorProductState [source]¶
- pyquil.experiment.ratio_variance(a: float | complex | number | ndarray, var_a: float | complex | number | ndarray, b: float | complex | number | ndarray, var_b: float | complex | number | ndarray) float | complex | number | ndarray [source]¶
Compute the variance on the ratio Y = A/B.
Given random variables ‘A’ and ‘B’, compute the variance on the ratio Y = A/B. Denote the mean of the random variables as a = E[A] and b = E[B] while the variances are var_a = Var[A] and var_b = Var[B] and the covariance as Cov[A,B]. The following expression approximates the variance of Y
Var[Y] approx (a/b) ^2 * ( var_a /a^2 + var_b / b^2 - 2 * Cov[A,B]/(a*b) )
We assume the covariance of A and B is negligible, resting on the assumption that A and B are independently measured. The expression above rests on the assumption that B is non-zero, an assumption which we expect to hold true in most cases, but makes no such assumptions about A. If we allow E[A] = 0, then calculating the expression above via numpy would complain about dividing by zero. Instead, we can re-write the above expression as
Var[Y] approx var_a /b^2 + (a^2 * var_b) / b^4
where we have dropped the covariance term as noted above.
- See the following for more details:
https://doi.org/10.1002/(SICI)1097-0320(20000401)39:4<300::AID-CYTO8>3.0.CO;2-O
- Parameters:
a – Mean of ‘A’, to be used as the numerator in a ratio.
var_a – Variance in ‘A’
b – Mean of ‘B’, to be used as the numerator in a ratio.
var_b – Variance in ‘B’
- pyquil.experiment.read_json(fn: str) Any [source]¶
Read pyquil.experiment objects from a JSON file.
See
to_json()
.
- pyquil.experiment.to_json(fn: str, obj: Any) str [source]¶
Save pyquil.experiment objects as a JSON file.
See
read_json()
.
- pyquil.experiment.zeros_state(qubits: Iterable[int]) TensorProductState [source]¶