Source code for pennylane_ionq.device

# Copyright 2019-2021 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This module contains the device class for constructing IonQ devices for PennyLane.
"""

# pylint: disable=too-many-arguments

import inspect
import logging
import warnings
from time import sleep

import numpy as np

from pennylane import pauli_decompose, SparseHamiltonian
from pennylane.devices import QubitDevice
from pennylane.ops.op_math import Exp, Sum, SProd
from pennylane.ops import Identity, PauliX, PauliY, PauliZ
from pennylane.ops.op_math.prod import Prod

from pennylane.ops.op_math.linear_combination import LinearCombination

from .api_client import Job, JobExecutionError
from .exceptions import (
    CircuitIndexNotSetException,
    ComplexEvolutionCoefficientsNotSupported,
    NotSupportedEvolutionInstance,
    OperatorNotSupportedInEvolutionGateGenerator,
)
from ._version import __version__

logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())

_qis_operation_map = {
    # native PennyLane operations also native to IonQ
    "PauliX": "x",
    "PauliY": "y",
    "PauliZ": "z",
    "Hadamard": "h",
    "CNOT": "cnot",
    "Evolution": "pauliexp",
    "SWAP": "swap",
    "RX": "rx",
    "RY": "ry",
    "RZ": "rz",
    "S": "s",
    "S.inv": "si",
    "T": "t",
    "T.inv": "ti",
    "SX": "v",
    "SX.inv": "vi",
    # Ising gates defined in this plugin for IonQ hardware
    "XX": "xx",
    "YY": "yy",
    "ZZ": "zz",
    # PennyLane native Ising gates (equivalent to plugin gates above)
    "IsingXX": "xx",
    "IsingYY": "yy",
    "IsingZZ": "zz",
}

_native_operation_map = {
    "GPI": "gpi",
    "GPI2": "gpi2",
    "MS": "ms",
}

_GATESET_OPS = {
    "native": _native_operation_map,
    "qis": _qis_operation_map,
}

PAULI_MAP = {"PauliX": "X", "PauliY": "Y", "PauliZ": "Z", "Identity": "I"}

NO_ANALYTIC_MSG = "The ionq device does not support analytic expectation values."


class IonQDevice(QubitDevice):
    r"""IonQ device for PennyLane.

    Args:
        wires (int or Iterable[Number, str]]): Number of wires to initialize the device with,
            or iterable that contains unique labels for the subsystems as numbers (i.e., ``[-1, 0, 2]``)
            or strings (``['ancilla', 'q1', 'q2']``).

            .. note::

                Custom wire labels (e.g., strings or non-consecutive integers) are used for user convenience only.
                They have no effect on the transpilation process or the final qubit layout on the hardware.

    Kwargs:
        target (str): the target device, either ``"simulator"`` or ``"qpu"``. Defaults to ``simulator``.
        gateset (str): the target gateset, either ``"qis"`` or ``"native"``. Defaults to ``qis``.
        shots (int, list[int], None): Number of circuit evaluations/random samples used to estimate
            expectation values of observables. Defaults to None.
            If a list of integers is passed, the circuit evaluations are batched over the list of shots.
        job_name (str | None): Optional job name. Defaults to None.
        api_key (str): The IonQ API key. If not provided, the environment
            variable ``IONQ_API_KEY`` is used.
        compilation (dict | None): Settings for compilation when creating a job. Defaults to None.
            Example: ``{"opt": 0, "precision": "1E-3"}``. See
            `IonQ API Job Creation <https://docs.ionq.com/api-reference/v0.4/jobs/create-job>`_ for details.
        error_mitigation (dict | None): settings for error mitigation when creating a job. Defaults to None.
            Not available on all backends. Set by default on some hardware systems. See
            `IonQ API Job Creation <https://docs.ionq.com/api-reference/v0.4/jobs/create-job>`_  and
            `IonQ Debiasing and Sharpening <https://ionq.com/resources/debiasing-and-sharpening>`_ for details.
            Valid keys include: ``debiasing`` (bool).
        sharpen (bool): whether to use sharpening when accessing the results of an executed job. Defaults to None
            (no value passed at job retrieval). Will generally return more accurate results if your expected output
            distribution has peaks. See `IonQ Debiasing and Sharpening
            <https://ionq.com/resources/debiasing-and-sharpening>`_ for details.
        noise (dict | None): {"model": str, "seed": int or None}. Defaults to None.
        dry_run (bool): If True, the job will be submitted by the API client but not processed remotely.
            Useful for obtaining cost estimates. Defaults to False.
        metadata (dict | None): optional metadata to attach to the job. Defaults to None.
    """

    # pylint: disable=too-many-instance-attributes
    name = "IonQ PennyLane plugin"
    short_name = "ionq"
    pennylane_requires = ">=0.44.0"
    version = __version__
    author = "Xanadu Inc."

    _capabilities = {
        "model": "qubit",
        "tensor_observables": True,
        "inverse_operations": True,
    }

    # Note: unlike QubitDevice, IonQ does not support QubitUnitary,
    # and therefore does not support the Hermitian observable.
    observables = {"PauliX", "PauliY", "PauliZ", "Hadamard", "Identity", "Prod"}

    # pylint: disable=too-many-arguments
    def __init__(
        self,
        wires,
        *,
        target="simulator",
        gateset="qis",
        shots=None,
        job_name=None,
        api_key=None,
        compilation=None,
        error_mitigation=None,
        sharpen=None,
        dry_run=False,
        noise=None,
        metadata=None,
    ):

        super().__init__(wires=wires, shots=shots)
        self._current_circuit_index = None
        self.job_name = job_name
        self.target = target
        self.api_key = api_key
        self.gateset = gateset
        self.compilation = compilation
        self.error_mitigation = error_mitigation
        self.sharpen = sharpen
        self.dry_run = dry_run
        self.noise = noise
        self.metadata = metadata
        self._operation_map = _GATESET_OPS[gateset]
        self.histograms = []
        self._samples = None
        self.reset()

    def batch_transform(self, circuit):
        """Apply a batch transform for preprocessing a circuit prior to execution."""

        if not circuit.shots:
            raise ValueError(NO_ANALYTIC_MSG)
        return super().batch_transform(circuit)

    def reset(self, circuits_array_length=1):
        """Reset the device"""
        self._current_circuit_index = None
        self._samples = None
        self.histograms = []
        self.input = {
            "qubits": self.num_wires,
            "gateset": self.gateset,
        }
        if circuits_array_length > 1:
            self.input["circuits"] = [{"circuit": []} for _ in range(circuits_array_length)]
        else:
            self.input["circuit"] = []
        self.job = {
            "type": ("ionq.multi-circuit.v1" if circuits_array_length > 1 else "ionq.circuit.v1"),
            "input": self.input,
            "backend": self.target,
        }
        if self.shots is not None:
            self.job["shots"] = self.shots
        if self.job_name is not None:
            self.job["name"] = self.job_name
        if self.dry_run:
            self.job["dry_run"] = self.dry_run
        if self.noise is not None:
            self.job["noise"] = self.noise
        if self.metadata is not None:
            self.job["metadata"] = self.metadata
        if self.compilation is not None:
            self.job["settings"] = {"compilation": self.compilation}
        if self.error_mitigation is not None:
            if "settings" not in self.job:
                self.job["settings"] = {}
            self.job["settings"]["error_mitigation"] = self.error_mitigation
        if self.job["backend"] == "qpu":
            self.job["backend"] = "qpu.aria-1"
            warnings.warn(
                "The ionq_qpu backend is deprecated. Defaulting to ionq_qpu.aria-1.",
                UserWarning,
                stacklevel=2,
            )

    def set_current_circuit_index(self, circuit_index):
        """Sets the index of the current circuit for which operations are applied upon.
        In case of multiple circuits being submitted via batch_execute method
        self._current_circuit_index tracks the index of the current circuit.
        """
        self._current_circuit_index = circuit_index

    def batch_execute(self, circuits):
        """Execute a batch of quantum circuits on the device.

        The circuits are represented by tapes, and they are executed one-by-one using the
        device's ``execute`` method. The results are collected in a list.

        Args:
            circuits (list[~.tape.QuantumTape]): circuits to execute on the device

        Returns:
            list[array[float]]: list of measured value(s)
        """
        if logger.isEnabledFor(logging.DEBUG):
            logger.debug(  # pragma: no cover
                """Entry with args=(circuits=%s) called by=%s""",
                circuits,
                "::L".join(
                    str(i) for i in inspect.getouterframes(inspect.currentframe(), 2)[1][1:3]
                ),
            )

        self.reset(circuits_array_length=len(circuits))

        # Use tape-level shots if device shots are not set
        tape_shots = circuits[0].shots.total_shots if circuits[0].shots else None
        if self.shots is None and tape_shots is not None:
            self.job["shots"] = tape_shots  # pylint: disable=access-member-before-definition

        for circuit_index, circuit in enumerate(circuits):
            self.check_validity(circuit.operations, circuit.observables)
            self.batch_apply(
                circuit.operations,
                rotations=self._get_diagonalizing_gates(circuit),
                circuit_index=circuit_index,
            )
        self._submit_job()

        if self.dry_run:
            return [[] for _ in circuits]

        original_shots = self.shots  # pylint: disable=access-member-before-definition
        original_shot_vector = self._shot_vector  # pylint: disable=access-member-before-definition

        results = []
        for circuit_index, circuit in enumerate(circuits):
            self.set_current_circuit_index(circuit_index)
            if self.shots is None and circuit.shots:
                self._shot_vector = circuit.shots.shot_vector
                self.shots = circuit.shots.total_shots
            self._samples = self.generate_samples()

            # compute the required statistics
            if self._shot_vector is not None:
                result = self.shot_vec_statistics(circuit)
            else:
                result = self.statistics(circuit)
                single_measurement = len(circuit.measurements) == 1

                result = result[0] if single_measurement else tuple(result)

            self.set_current_circuit_index(None)
            self._samples = None
            results.append(result)

        self.shots = original_shots
        self._shot_vector = original_shot_vector

        # increment counter for number of executions of qubit device
        self._num_executions += 1

        if self.tracker.active:
            for circuit in circuits:
                tape_resources = circuit.specs["resources"]

                self.tracker.update(
                    executions=1,
                    shots=self._shots,
                    results=results,
                    resources=tape_resources,
                )

            self.tracker.update(batches=1, batch_len=len(circuits))
            self.tracker.record()

        return results

    def batch_apply(self, operations, circuit_index, **kwargs):
        "Apply circuit operations when submitting for execution a batch of circuits."

        rotations = kwargs.pop("rotations", [])

        if len(operations) == 0 and len(rotations) == 0:
            warnings.warn("Circuit is empty. Empty circuits return failures. Submitting anyway.")

        for operation in operations:
            self._apply_operation(operation, circuit_index)

        # diagonalize observables
        for operation in rotations:
            self._apply_operation(operation, circuit_index)

    @property
    def operations(self):
        """Get the supported set of operations.

        Returns:
            set[str]: the set of PennyLane operation names the device supports
        """
        return set(self._operation_map.keys())

    def apply(self, operations, **kwargs):
        """Implementation of QubitDevice abstract method apply."""

        self.reset()
        rotations = kwargs.pop("rotations", [])

        if len(operations) == 0 and len(rotations) == 0:
            warnings.warn("Circuit is empty. Empty circuits return failures. Submitting anyway.")

        for operation in operations:
            self._apply_operation(operation)

        # diagonalize observables
        for operation in rotations:
            self._apply_operation(operation)

        self._submit_job()

    def _apply_operation(self, operation, circuit_index=0):
        """Applies operations to the internal device state.

        Args:
            operation (.Operation): operation to apply on the device
            circuit_index: index of the circuit to apply operation to
        """
        wires = self.map_wires(operation.wires).tolist()
        if operation.name == "Evolution":
            self._apply_evolution_operation(operation, circuit_index, wires)
        else:
            self._apply_simple_operation(operation, circuit_index, wires)

    def _append_gate(self, gate, circuit_index):
        """Appends a gate dict to the appropriate circuit in the input payload."""
        if "circuits" in self.input:
            self.input["circuits"][circuit_index]["circuit"].append(gate)
        else:
            self.input["circuit"].append(gate)

    def _apply_evolution_operation(self, operation, circuit_index, wires):
        """Applies Evolution operations to the internal device state.
        The number of steps argument for Evolution gate will be ignored even if provided because
        IonQ implements hardware-efficient approximate compilation schemes for pauliexp gates.
        """
        warnings.warn(
            "The 'num_steps' argument for the Evolution gate will be ignored. The API maps this "
            "gate to IonQ's 'pauliexp' gate, for which IonQ implements its own hardware-efficient "
            "approximate compilation schemes.",
            UserWarning,
        )
        name = operation.name
        terms, coefficients = self._decompose_evolution(operation, wires)
        terms, coefficients = self._remove_trivial_terms(terms, coefficients)
        if len(terms) > 0:
            gate = {"gate": self._operation_map[name]}
            gate["targets"] = wires
            gate["terms"] = terms
            # 1. Float conversion to prevent numpy types (np.float64) in the JSON payload.
            # 2. IonQ API expects positive time values for their `pauliexp` gate.
            gate["time"] = abs(float(operation.param))
            # 3. Add missing sign convention to coefficients by multiplying by -1.
            gate["coefficients"] = [
                np.sign(operation.param) * (-1) * float(v) for v in coefficients
            ]
            self._append_gate(gate, circuit_index)

    def _apply_simple_operation(self, operation, circuit_index, wires):
        """Applies regular operations (gates) to the internal device state."""
        name = operation.name
        params = operation.parameters
        gate = {"gate": self._operation_map[name]}
        if len(wires) == 2:
            if name in {
                "SWAP",
                "XX",
                "YY",
                "ZZ",
                "IsingXX",
                "IsingYY",
                "IsingZZ",
                "MS",
            }:
                # these gates takes two targets
                gate["targets"] = wires
            else:
                gate["control"] = wires[0]
                gate["target"] = wires[1]
        else:
            gate["target"] = wires[0]

        if self.gateset == "native":
            if len(params) > 1:
                gate["phases"] = [float(v) for v in params[:2]]
                if len(params) > 2:
                    gate["angle"] = float(params[2])
            else:
                gate["phase"] = float(params[0])
        elif params:
            gate["rotation"] = float(params[0])
        self._append_gate(gate, circuit_index)

    def _remove_trivial_terms(self, terms, coefficients):
        """Removes all-identity (II..I) terms from the list of terms."""
        filtered = [(t, c) for t, c in zip(terms, coefficients) if "X" in t or "Y" in t or "Z" in t]
        if not filtered:
            return [], []
        terms, coefficients = zip(*filtered)
        return list(terms), list(coefficients)

    def _decompose_evolution(self, operation, wires: list[int]) -> tuple[list[str], list[float]]:
        """Decompose an Evolution gate's generator into IonQ Pauli terms and coefficients.

        Returns:
            tuple: (terms, coefficients) where terms is a list of Pauli strings
                and coefficients is a list of floats.
        """
        ops = None
        coefficients = None
        generator = operation.generator()

        if isinstance(generator, LinearCombination):
            ops = []
            coefficients = []
            coeffs = generator.coeffs.tolist()
            gen_ops = generator.ops
            for coeff, gen_op in zip(coeffs, gen_ops):
                scaled = coeff * gen_op
                if isinstance(gen_op, (Sum, Prod, PauliX, PauliY, PauliZ, Identity)):
                    c, o = scaled.terms()
                    coefficients.extend(c)
                    ops.extend(o)
                else:
                    op_wires = scaled.wires.tolist()
                    decomp = pauli_decompose(scaled.matrix(), wire_order=op_wires, pauli=False)
                    coefficients.extend(decomp.coeffs.tolist())
                    ops.extend(decomp.ops)
        elif isinstance(generator, SparseHamiltonian):
            decomp = pauli_decompose(generator.H.toarray(), wire_order=wires, pauli=False)
            ops = decomp.ops
            coefficients = decomp.coeffs.tolist()
        elif isinstance(generator, SProd):
            if isinstance(generator.base, (PauliX, PauliY, PauliZ, Identity)):
                ops = [generator.base]
                coefficients = [generator.scalar]
            elif isinstance(generator.base, (Sum, Prod)):
                base_coeffs, base_ops = generator.base.terms()
                coefficients = [generator.scalar * float(c) for c in base_coeffs]
                ops = base_ops
            elif isinstance(generator.base, Exp):
                decomp = pauli_decompose(generator.matrix(), wire_order=wires, pauli=False)
                ops = decomp.ops
                coefficients = decomp.coeffs.tolist()

        if ops is None:
            raise NotSupportedEvolutionInstance()

        if any(isinstance(c, complex) for c in coefficients):
            raise ComplexEvolutionCoefficientsNotSupported()

        terms = self._operations_to_ionq_pauli_names(ops, wires)
        return terms, coefficients

    def _operations_to_ionq_pauli_names(self, ops, wires) -> list[str]:
        """Converts a list of operations to a list of IonQ compatible Pauli matrix names."""

        def map_operand_to_term(operand):
            try:
                return PAULI_MAP[operand.name]
            except KeyError as exc:
                supported = ", ".join(PAULI_MAP.keys())
                raise KeyError(
                    f"Operand {operand.name} is not supported for Evolution gate. "
                    f"Supported operands: {supported}."
                ) from exc

        def join_terms(terms, wires):
            """Pennylane uses big-endian ordering, IonQ uses little-endian ordering."""
            big_endian_term = "".join(terms.get(wire, "I") for wire in wires)
            little_endian_term = big_endian_term[::-1]
            return little_endian_term

        ionq_terms = []
        for op in ops:
            terms = {}
            if isinstance(op, Prod):
                for operand in op.operands:
                    term_name = map_operand_to_term(operand)
                    term_wire = operand.wires[0]
                    terms[term_wire] = term_name
                ionq_terms.append(join_terms(terms, wires))
            elif isinstance(op, (PauliX, PauliY, PauliZ)):
                term_name = map_operand_to_term(op)
                term_wire = op.wires[0]
                terms[term_wire] = term_name
                ionq_terms.append(join_terms(terms, wires))
            elif isinstance(op, Identity):
                ionq_terms.append(join_terms(terms, wires))
            else:
                raise OperatorNotSupportedInEvolutionGateGenerator(
                    f"Unsupported operator in generator of Evolution gate: {op}"
                )
        return ionq_terms

    def _submit_job(self):

        job = Job(api_key=self.api_key)

        # send job for execution
        job.manager.create(**self.job)

        if self.dry_run:
            return

        # retrieve results
        while not job.is_complete:
            sleep(0.01)
            job.reload()
            if job.is_failed:
                raise JobExecutionError("Job failed")

        params = {} if self.sharpen is None else {"sharpen": self.sharpen}

        job.manager.get(resource_id=job.id.value, params=params)

        # The returned job histogram is of the form
        # dict[str, float], and maps the computational basis
        # state (as a base-10 integer string) to the probability
        # as a floating point value between 0 and 1.
        # e.g., {"0": 0.413, "9": 0.111, "17": 0.476}
        some_inner_value = next(iter(job.data.value.values()))
        if isinstance(some_inner_value, dict):
            self.histograms = list(job.data.value.values())
        else:
            self.histograms = [job.data.value]

    @property
    def prob(self):
        """None or array[float]: Array of computational basis state probabilities. If
        no job has been submitted, returns ``None``.
        """
        if self._current_circuit_index is None and len(self.histograms) > 1:
            raise CircuitIndexNotSetException()

        if self._current_circuit_index is not None:
            histogram = self.histograms[self._current_circuit_index]
        else:
            try:
                histogram = self.histograms[0]
            except IndexError:
                return None

        # The IonQ API returns basis states using little-endian ordering.
        # Here, we rearrange the states to match the big-endian ordering
        # expected by PennyLane.
        basis_states = (int(bin(int(k))[2:].rjust(self.num_wires, "0")[::-1], 2) for k in histogram)
        idx = np.fromiter(basis_states, dtype=int)

        # convert the sparse probs into a probability array
        prob_array = np.zeros([2**self.num_wires])

        # histogram values don't always perfectly sum to exactly one
        histogram_values = histogram.values()
        norm = sum(histogram_values)
        prob_array[idx] = np.fromiter(histogram_values, float) / norm

        return prob_array

    def probability(self, wires=None, shot_range=None, bin_size=None):
        wires = wires or self.wires

        if shot_range is None and bin_size is None:
            return self.marginal_prob(self.prob, wires)

        return self.estimate_probability(wires=wires, shot_range=shot_range, bin_size=bin_size)


[docs] class SimulatorDevice(IonQDevice): r"""Simulator device for IonQ. Args: wires (int or Iterable[Number, str]]): Number of wires to initialize the device with, or iterable that contains unique labels for the subsystems as numbers (i.e., ``[-1, 0, 2]``) or strings (``['ancilla', 'q1', 'q2']``). .. note:: Custom wire labels (e.g., strings or non-consecutive integers) are used for user convenience only. They have no effect on the transpilation process or the final qubit layout on the hardware. gateset (str): the target gateset, either ``"qis"`` or ``"native"``. Defaults to ``qis``. shots (int, list[int], None): Number of circuit evaluations/random samples used to estimate expectation values of observables. If ``None``, the device calculates probability, expectation values, and variances analytically. If an integer, it specifies the number of samples to estimate these quantities. If a list of integers is passed, the circuit evaluations are batched over the list of shots. Defaults to None. api_key (str): The IonQ API key. If not provided, the environment variable ``IONQ_API_KEY`` is used. noise (dict | None): {"model": str, "seed": int or None}. Defaults to None. metadata (dict | None): optional metadata to attach to the job. Defaults to None. """ name = "IonQ Simulator PennyLane plugin" short_name = "ionq.simulator" def __init__( self, wires, *, gateset="qis", shots=None, job_name=None, compilation=None, api_key=None, dry_run=False, noise=None, metadata=None, ): super().__init__( wires=wires, target="simulator", gateset=gateset, shots=shots, job_name=job_name, api_key=api_key, compilation=compilation, dry_run=dry_run, noise=noise, metadata=metadata, )
[docs] def generate_samples(self): """Generates samples by random sampling with the probabilities returned by the simulator.""" number_of_states = 2**self.num_wires samples = self.sample_basis_states(number_of_states, self.prob) return QubitDevice.states_to_binary(samples, self.num_wires)
[docs] class QPUDevice(IonQDevice): r"""QPU device for IonQ. Args: wires (int or Iterable[Number, str]]): Number of wires to initialize the device with, or iterable that contains unique labels for the subsystems as numbers (i.e., ``[-1, 0, 2]``) or strings (``['ancilla', 'q1', 'q2']``). .. note:: Custom wire labels (e.g., strings or non-consecutive integers) are used for user convenience only. They have no effect on the transpilation process or the final qubit layout on the hardware. gateset (str): the target gateset, either ``"qis"`` or ``"native"``. Defaults to ``qis``. backend (str): Optional specifier for an IonQ backend. Can be ``"aria-1"``, ``"aria-2"``, etc. Default to ``aria-1``. shots (int, list[int], None): Number of circuit evaluations/random samples used to estimate expectation values of observables. Defaults to None. If a list of integers is passed, the circuit evaluations are batched over the list of shots. api_key (str): The IonQ API key. If not provided, the environment variable ``IONQ_API_KEY`` is used. error_mitigation (dict | None): settings for error mitigation when creating a job. Defaults to None. Not available on all backends. Set by default on some hardware systems. See `IonQ API Job Creation <https://docs.ionq.com/api-reference/v0.4/jobs/create-job>`_ and `IonQ Debiasing and Sharpening <https://ionq.com/resources/debiasing-and-sharpening>`_ for details. Valid keys include: ``debiasing`` (bool). sharpen (bool): whether to use sharpening when accessing the results of an executed job. Defaults to None (no value passed at job retrieval). Will generally return more accurate results if your expected output distribution has peaks. See `IonQ Debiasing and Sharpening <https://ionq.com/resources/debiasing-and-sharpening>`_ for details. dry_run (bool): whether to run the job in dry run mode. Defaults to False. metadata (dict | None): optional metadata to attach to the job. Defaults to None. """ name = "IonQ QPU PennyLane plugin" short_name = "ionq.qpu" # pylint: disable=too-many-arguments def __init__( self, wires, *, gateset="qis", shots=None, job_name=None, backend="aria-1", compilation=None, error_mitigation=None, sharpen=None, api_key=None, dry_run=False, metadata=None, ): target = "qpu" self.backend = backend if self.backend is not None: target += "." + self.backend super().__init__( wires=wires, target=target, gateset=gateset, shots=shots, job_name=job_name, api_key=api_key, compilation=compilation, error_mitigation=error_mitigation, sharpen=sharpen, dry_run=dry_run, metadata=metadata, )
[docs] def generate_samples(self): """Generates samples from the qpu. Note that the order of the samples returned here is not indicative of the order in which the experiments were done, but is instead controlled by a random shuffle (and hence set by numpy random seed). """ number_of_states = 2**self.num_wires counts = np.rint( self.prob * self.shots, out=np.zeros(number_of_states, dtype=int), casting="unsafe", ) samples = np.repeat(np.arange(number_of_states), counts) np.random.shuffle(samples) return QubitDevice.states_to_binary(samples, self.num_wires)