Source code for mitiq.interface.mitiq_qiskit.qiskit_utils

# Copyright (C) 2021 Unitary Fund
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

"""Qiskit utility functions."""
from typing import Tuple
from functools import partial
import numpy as np
import numpy.typing as npt
import qiskit
from qiskit import QuantumCircuit
from typing import Optional

# Noise simulation packages
from qiskit.providers.aer.noise import NoiseModel
from qiskit.providers.aer.noise.errors.standard_errors import (
    depolarizing_error,
)
from qiskit.providers import Backend

from mitiq import MeasurementResult, Observable, Executor


[docs]def initialized_depolarizing_noise(noise_level: float) -> NoiseModel: """Initializes a depolarizing noise Qiskit NoiseModel. Args: noise_level: The noise strength as a float, e.g., 0.01 is 0.1%. Returns: A Qiskit depolarizing NoiseModel. """ # initialize a qiskit noise model noise_model = NoiseModel() # we assume the same depolarizing error for each # gate of the standard IBM basis noise_model.add_all_qubit_quantum_error( depolarizing_error(noise_level, 1), ["u1", "u2", "u3"] ) noise_model.add_all_qubit_quantum_error( depolarizing_error(noise_level, 2), ["cx"] ) return noise_model
[docs]def execute(circuit: QuantumCircuit, obs: npt.NDArray[np.complex64]) -> float: """Simulates a noiseless evolution and returns the expectation value of some observable. Args: circuit: The input Qiskit circuit. obs: The observable to measure as a NumPy array. Returns: The expectation value of obs as a float. """ return execute_with_noise(circuit, obs, noise_model=None)
[docs]def execute_with_shots( circuit: QuantumCircuit, obs: npt.NDArray[np.complex64], shots: int ) -> float: """Simulates the evolution of the circuit and returns the expectation value of the observable. Args: circuit: The input Qiskit circuit. obs: The observable to measure as a NumPy array. shots: The number of measurements. Returns: The expectation value of obs as a float. """ return execute_with_shots_and_noise( circuit, obs, noise_model=None, shots=shots, )
[docs]def execute_with_noise( circuit: QuantumCircuit, obs: npt.NDArray[np.complex64], noise_model: NoiseModel, ) -> float: """Simulates the evolution of the noisy circuit and returns the exact expectation value of the observable. Args: circuit: The input Qiskit circuit. obs: The observable to measure as a NumPy array. noise_model: The input Qiskit noise model. Returns: The expectation value of obs as a float. """ # Avoid mutating circuit circ = circuit.copy() circ.save_density_matrix() if noise_model is None: basis_gates = None else: basis_gates = noise_model.basis_gates + ["save_density_matrix"] # execution of the experiment job = qiskit.execute( circ, backend=qiskit.Aer.get_backend("aer_simulator_density_matrix"), noise_model=noise_model, basis_gates=basis_gates, # we want all gates to be actually applied, # so we skip any circuit optimization optimization_level=0, shots=1, ) rho = job.result().data()["density_matrix"] expectation = np.real(np.trace(rho @ obs)) return expectation
[docs]def execute_with_shots_and_noise( circuit: QuantumCircuit, obs: npt.NDArray[np.complex64], noise_model: NoiseModel, shots: int, seed: Optional[int] = None, ) -> float: """Simulates the evolution of the noisy circuit and returns the statistical estimate of the expectation value of the observable. Args: circuit: The input Qiskit circuit. obs: The observable to measure as a NumPy array. noise: The input Qiskit noise model. shots: The number of measurements. seed: Optional seed for qiskit simulator. Returns: The expectation value of obs as a float. """ # Avoid mutating circuit circ = circuit.copy() # we need to modify the circuit to measure obs in its eigenbasis # we do this by appending a unitary operation # obtains a U s.t. obs = U diag(eigvals) U^dag eigvals, U = np.linalg.eigh(obs) circ.unitary(np.linalg.inv(U), qubits=range(circ.num_qubits)) circ.measure_all() if noise_model is None: basis_gates = None else: basis_gates = noise_model.basis_gates # execution of the experiment job = qiskit.execute( circ, backend=qiskit.Aer.get_backend("aer_simulator"), backend_options={"method": "density_matrix"}, noise_model=noise_model, # we want all gates to be actually applied, # so we skip any circuit optimization basis_gates=basis_gates, optimization_level=0, shots=shots, seed_simulator=seed, ) counts = job.result().get_counts() expectation = 0 for bitstring, count in counts.items(): expectation += ( eigvals[int(bitstring[0 : circ.num_qubits], 2)] * count / shots ) return expectation
[docs]def sample_bitstrings( circuit: QuantumCircuit, backend: Optional[Backend] = None, noise_model: Optional[NoiseModel] = None, # type: ignore shots: int = 10000, measure_all: bool = False, qubit_indices: Optional[Tuple[int]] = None, ) -> MeasurementResult: """Returns measurement bitstrings obtained from executing the input circuit on a Qiskit backend (passed as an argument). Note that the input circuit must contain measurement gates (unless ``measure_all`` is ``True``). Args: circuit: The input Qiskit circuit. backend: A real or fake Qiskit backend. The input circuit should be transpiled into a compatible gate set. It may be necessary to set ``optimization_level=0`` when transpiling. noise_model: A valid Qiskit ``NoiseModel`` object. This option is used if and only if ``backend`` is ``None``. In this case a default density matrix simulator is used with ``optimization_level=0``. shots: The number of measurements. measure_all: If True, measurement gates are applied to all qubits. qubit_indices: Optional qubit indices associated to bitstrings. Returns: The measured bitstrings casted as a Mitiq :class:`.MeasurementResult` object. """ if measure_all: circuit = circuit.measure_all(inplace=False) if backend: job = backend.run(circuit, shots=shots) elif noise_model: job = qiskit.execute( circuit, backend=qiskit.Aer.get_backend("aer_simulator"), backend_options={"method": "density_matrix"}, noise_model=noise_model, # we want all gates to be actually applied, # so we skip any circuit optimization basis_gates=noise_model.basis_gates, optimization_level=0, shots=shots, ) else: raise ValueError( "Either a backend or a noise model must be given as input." ) counts = job.result().get_counts(circuit) bitstrings = [] for key, val in counts.items(): bitstring = [int(c) for c in key] for _ in range(val): bitstrings.append(bitstring) return MeasurementResult( result=bitstrings, qubit_indices=qubit_indices, )
[docs]def compute_expectation_value_on_noisy_backend( circuit: QuantumCircuit, obs: Observable, backend: Optional[Backend] = None, noise_model: Optional[NoiseModel] = None, # type: ignore shots: int = 10000, measure_all: bool = False, qubit_indices: Optional[Tuple[int]] = None, ) -> complex: """Returns the noisy expectation value of the input Mitiq observable obtained from executing the input circuit on a Qiskit backend. Args: circuit: The input Qiskit circuit. obs: The Mitiq observable to compute the expectation value of. backend: A real or fake Qiskit backend. The input circuit should be transpiled into a compatible gate set. noise_model: A valid Qiskit ``NoiseModel`` object. This option is used if and only if ``backend`` is ``None``. In this case a default density matrix simulator is used with ``optimization_level=0``. shots: The number of measurements. measure_all: If True, measurement gates are applied to all qubits. qubit_indices: Optional qubit indices associated to bitstrings. Returns: The noisy expectation value. """ execute = partial( sample_bitstrings, backend=backend, noise_model=noise_model, shots=shots, measure_all=measure_all, qubit_indices=qubit_indices, ) executor = Executor(execute) return executor.evaluate(circuit, obs)[0]