pyquil.experiment package

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 of ExperimentSetting 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 and reset instance variables. The shots instance variable is the number of shots to take per ExperimentSetting. The reset 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. see run_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 corresponding to the state preparation and measurement specifications encoded in the provided ExperimentSetting, taking into account the full set of qubits that are 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 to be used in a program that is trying to perform readout symmetrization via 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 another Experiment object that can be used to calibrate the various multi-qubit observables involved in this Experiment. This is achieved 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 would always give +1 (-1), but when symmetric readout error is present the effect is to scale the resultant expectations by some constant factor. Determining this scale factor is what we call readout calibration, and then the readout error in subsequent measurements can then be mitigated by simply 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 containing the main body program along with some additions to support the various state preparation, measurement, and symmetrization specifications of this Experiment.

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 an RX gate (also parameterized by a declared value) before each MEASURE operation. In addition, a RESET operation is prepended to the Program 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 every ExperimentSetting in this Experiment.

get_meas_qubits() List[int][source]

Return the sorted list of qubits that are involved in the all the out_operators of the settings for this Experiment object.

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]
reverse() None[source]
serializable() Dict[str, Any][source]
setting_strings() Generator[str, None, None][source]
settings_string(abbrev_after: int | None = None) str[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
serializable() Dict[str, Any][source]
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 an Experiment.

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]

The opposite of str(expt)

in_state: TensorProductState
out_operator: PauliTerm
serializable() str[source]
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 a TypeError).

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]
states_as_set() FrozenSet[_OneQState][source]
pyquil.experiment.bitstrings_to_expectations(bitstrings: ndarray, joint_expectations: List[List[int]] | None = None) ndarray[source]

Given an array of bitstrings (each of which is represented as an array of bits), 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 the ExperimentSetting associated with the result 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]

Merges the list of experiments into a single experiment that runs the sum of the individual experiment programs and contains all of the combined experiment 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:

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 | ndarray, var_a: float | ndarray, b: float | ndarray, var_b: float | ndarray) float | ndarray[source]

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:
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]

Convenience method to read pyquil.experiment objects from a JSON file.

See to_json().

pyquil.experiment.to_json(fn: str, obj: Any) str[source]

Convenience method to save pyquil.experiment objects as a JSON file.

See read_json().

pyquil.experiment.zeros_state(qubits: Iterable[int]) TensorProductState[source]