Source code for kosmos.circuit_runner.qiskit_runner

import numpy as np
from qiskit import QuantumCircuit, generate_preset_pass_manager, transpile
from qiskit.providers import BackendV2 as Backend
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import RuntimeJobV2 as RuntimeJob
from qiskit_ibm_runtime.sampler import SamplerV2 as Sampler

from kosmos.circuit_runner.circuit_runner import CircuitRunner
from kosmos.circuit_runner.ibm_runtime_utils import qiskit_remote_backend
from kosmos.circuit_runner.qiskit_result import job_expectation_values
from kosmos.circuit_runner.typing import QuantumCircuitFramework
from kosmos.ml.models.vqc.circuit.qiskit_circuit.gradient_method import (
    GradientMethod,
    ParameterShiftRule,
)
from kosmos.utils.rng import RNG


[docs] class QiskitRunner(CircuitRunner): """General Qiskit circuit runner. Attributes: backend (Backend): The Qiskit backend to use for circuit execution. num_shots (int): Number of shots. """ def __init__( self, backend: Backend, num_shots: int = 1024, gradient_method: GradientMethod | None = None, ) -> None: """Initialize the Qiskit runner. Args: backend (Backend): The Qiskit backend to use for circuit execution. num_shots (int): Number of shots. Defaults to 1024. gradient_method (GradientMethod | None): The gradient method to use when running VQCs. Uses ParameterShiftRule() if None. Defaults to None. """ self.backend = backend self.num_shots = num_shots self._gradient_method = gradient_method or ParameterShiftRule() @property def framework(self) -> QuantumCircuitFramework: """The framework used by the circuit runner.""" return "qiskit"
[docs] def run_sampler(self, circuits: list[QuantumCircuit]) -> RuntimeJob: """Run circuits using Sampler primitive. Args: circuits (list[QuantumCircuit]): The circuits to run. Returns: RuntimeJob: The submitted job. """ pm = generate_preset_pass_manager(backend=self.backend, optimization_level=3) isa_circuits = pm.run(circuits) sampler = Sampler(self.backend) return sampler.run(isa_circuits, shots=self.num_shots)
[docs] def expectation_values(self, circuits: list[QuantumCircuit]) -> list[np.ndarray]: """Compute Z-basis expectation values for the given circuits. Args: circuits (list[QuantumCircuit]): The circuits to run. Returns: list[np.ndarray]: A list of arrays, where each array contains the expectation values for one circuit in the job. """ job = self.run_sampler(circuits) return job_expectation_values(job)
[docs] def get_gradient_method( self, parameterized_circuit: "QiskitParameterizedCircuit", # noqa: F821 ) -> GradientMethod: """Get the gradient method instance. Args: parameterized_circuit (QiskitParameterizedCircuit): The Qiskit parameterized circuit. Returns: GradientMethod: The gradient method instance. """ self._gradient_method.set_parameterized_circuit(parameterized_circuit) return self._gradient_method
[docs] def density_matrix( self, circuit: QuantumCircuit, *, compute_expectation_values: bool = True ) -> tuple[np.ndarray, list[np.ndarray]]: """Execute circuit and return final density matrix. Args: circuit (QuantumCircuit): Quantum circuit we want to execute. compute_expectation_values (bool): Whether to compute expectation values. Defaults to True. Returns: tuple[np.ndarray, list[np.ndarray]]: Density matrix and the measurements. """ dm_circuit = circuit.copy() dm_circuit.data = [instr for instr in dm_circuit.data if instr.operation.name != "measure"] dm_circuit.save_density_matrix(label="final_state") transpiled_dm_circuit = transpile(dm_circuit, self.backend) job = self.backend.run(transpiled_dm_circuit, shots=self.num_shots) result = job.result() density_matrix = np.asarray(result.data(0)["final_state"]) expectation_values = [] if compute_expectation_values: num_qubits = circuit.num_qubits exp_vals = np.zeros(num_qubits, dtype=np.float32) dim = 2**num_qubits for basis_state in range(dim): rho_ii = density_matrix[basis_state, basis_state].real for qubit_idx in range(num_qubits): bit_value = (basis_state >> qubit_idx) & 1 exp_vals[qubit_idx] += (1.0 - 2.0 * bit_value) * rho_ii expectation_values = [exp_vals] return density_matrix, expectation_values
[docs] class AerSimulatorRunner(QiskitRunner): """Qiskit circuit runner using the AerSimulator using density matrix simulation.""" def __init__( self, num_shots: int = 1024, gradient_method: GradientMethod | None = None ) -> None: """Initialize the AerSimulator runner. Args: num_shots (int): Number of shots. Defaults to 1024. gradient_method (GradientMethod | None): The gradient method to use when running VQCs. Uses ParameterShiftRule() if None. Defaults to None. """ noise_model = None aer_simulator = AerSimulator( method="density_matrix", noise_model=noise_model, seed_simulator=RNG.get_seed() ) super().__init__(aer_simulator, num_shots, gradient_method)
[docs] class IBMRuntimeRunner(QiskitRunner): """Qiskit circuit runner using an IBM Runtime backend.""" def __init__( self, qiskit_runtime_service: QiskitRuntimeService, backend_name: str | None = None, min_num_qubits: int | None = None, num_shots: int = 1024, gradient_method: GradientMethod | None = None, ) -> None: """Initialize the IBM Runtime runner. Args: qiskit_runtime_service (QiskitRuntimeService): The Qiskit Runtime service instance. backend_name (str | None): The name of the backend. Returns the least busy backend if None. Defaults to None. min_num_qubits (int | None): The minimum number of qubits. Defaults to None. num_shots (int): Number of shots. Defaults to 1024. gradient_method (GradientMethod | None): The gradient method to use when running VQCs. Uses ParameterShiftRule() if None. Defaults to None. """ backend = qiskit_remote_backend(qiskit_runtime_service, backend_name, min_num_qubits) super().__init__(backend, num_shots, gradient_method)