Source code for mpqp.execution.devices

"""An :class:`AvailableDevice` is a device on which one can run or submit a 
circuit. While it is an abstract class, all its concrete implementations are
enums with a few methods, required by :class:`AvailableDevice`.

Each supported provider has its available devices listed as these enums:

- :class:`IBMDevice`,
- :class:`ATOSDevice`,
- :class:`AWSDevice`,
- :class:`GOOGLEDevice`.
- :class:`AZUREDevice`.

Not all combinations of :class:`AvailableDevice` and 
:class:`~mpqp.execution.job.JobType` are possible. Here is the list of
compatible jobs types and devices.

For more information about handling Remote devices, please refer to the `Remote devices handling <execution-extras.html>`_ section.

.. csv-table:: Job/Device Compatibility Matrix
   :file: ../../docs/resources/job-device_compat.csv
   :widths: 7, 25, 6, 7, 10, 10, 15
   :header-rows: 1
"""

from abc import abstractmethod
from enum import Enum, auto

from mpqp.execution.connection.env_manager import get_env_variable


[docs]class AvailableDevice(Enum): """Class used to define a generic device (quantum computer or simulator)."""
[docs] @abstractmethod def is_remote(self) -> bool: """Indicates whether a device is remote or not. Returns: ``True`` if this device is remote. """ pass
[docs] @abstractmethod def is_gate_based(self) -> bool: """Indicates whether a device is gate-based or not. Returns: ``True`` if this device is a gate-based simulator/QPU.""" pass
[docs] @abstractmethod def is_simulator(self) -> bool: """Indicates whether a device is a simulator or not. Returns: ``True`` if this device is a simulator.""" pass
[docs] @abstractmethod def is_noisy_simulator(self) -> bool: """Indicates whether a device can simulate noise or not. Returns: ``True`` if this device can simulate noise. """ pass
[docs] def has_reduced_gate_set(self) -> bool: """Indicates whether a simulator does not handle all the native gates. Returns: ``True`` if this device only handles a restricted set of gates.""" return False
[docs] @abstractmethod def supports_samples(self) -> bool: pass
[docs] @abstractmethod def supports_state_vector(self) -> bool: pass
[docs] @abstractmethod def supports_observable(self) -> bool: pass
[docs] @abstractmethod def supports_observable_ideal(self) -> bool: pass
[docs]class IBMDevice(AvailableDevice): """Enum regrouping all available devices provided by IBM Quantum. Warning: Since previous versions, many devices have been disabled by IBM. This may affect your code. We are currently investigating this issue to check if a workaround is possible for some of them (like replacing a simulator by an equivalent one for instance). """ AER_SIMULATOR = "automatic" AER_SIMULATOR_STATEVECTOR = "statevector" AER_SIMULATOR_DENSITY_MATRIX = "density_matrix" AER_SIMULATOR_STABILIZER = "stabilizer" AER_SIMULATOR_EXTENDED_STABILIZER = "extended_stabilizer" AER_SIMULATOR_MATRIX_PRODUCT_STATE = "matrix_product_state" IBM_SHERBROOKE = "ibm_sherbrooke" IBM_BRISBANE = "ibm_brisbane" IBM_KYIV = "ibm_kyiv" IBM_FEZ = "ibm_fez" IBM_RENSSELAER = "ibm_rensselaer" IBM_BRUSSELS = "ibm_brussels" IBM_KAWASAKI = "ibm_kawasaki" IBM_QUEBEC = "ibm_quebec" IBM_TORINO = "ibm_torino" IBM_NAZCA = "ibm_nazca" IBM_STRASBOURG = "ibm_strasbourg" # RETIRED - IBM_OSAKA = "ibm_osaka" # RETIRED - IBM_KYOTO = "ibm_kyoto" # RETIRED - IBM_CUSCO = "ibm_cusco" # RETIRED - IBM_ITHACA = "ibm_ithaca" IBM_CLEVELAND = "ibm_cleveland" # RETIRED - IBM_CAIRO = "ibm_cairo" # RETIRED - IBM_HANOI = "ibm_hanoi" # RETIRED - IBM_ALGIERS = "ibm_algiers" # RETIRED - IBM_KOLKATA = "ibm_kolkata" # RETIRED - IBM_MUMBAI = "ibm_mumbai" IBM_PEEKSKILL = "ibm_peekskill" IBM_LEAST_BUSY = "ibm_least_busy" def is_remote(self) -> bool: return self.name.startswith("IBM") def is_gate_based(self) -> bool: return True def has_reduced_gate_set(self) -> bool: return self in { IBMDevice.AER_SIMULATOR_STABILIZER, IBMDevice.AER_SIMULATOR_EXTENDED_STABILIZER, } def is_simulator(self) -> bool: return self in { IBMDevice.AER_SIMULATOR, IBMDevice.AER_SIMULATOR_STATEVECTOR, IBMDevice.AER_SIMULATOR_DENSITY_MATRIX, IBMDevice.AER_SIMULATOR_STABILIZER, IBMDevice.AER_SIMULATOR_EXTENDED_STABILIZER, IBMDevice.AER_SIMULATOR_MATRIX_PRODUCT_STATE, } def is_noisy_simulator(self) -> bool: return self.is_simulator() def supports_samples(self) -> bool: return True def supports_state_vector(self) -> bool: return self in { IBMDevice.AER_SIMULATOR, IBMDevice.AER_SIMULATOR_STATEVECTOR, IBMDevice.AER_SIMULATOR_MATRIX_PRODUCT_STATE, } def supports_observable(self) -> bool: return self not in { IBMDevice.AER_SIMULATOR_EXTENDED_STABILIZER, } def supports_observable_ideal(self) -> bool: return self in { IBMDevice.AER_SIMULATOR, IBMDevice.AER_SIMULATOR_STATEVECTOR, IBMDevice.AER_SIMULATOR_DENSITY_MATRIX, IBMDevice.AER_SIMULATOR_STABILIZER, # IBMDevice.AER_SIMULATOR_EXTENDED_STABILIZER, IBMDevice.AER_SIMULATOR_MATRIX_PRODUCT_STATE, }
[docs]class ATOSDevice(AvailableDevice): """Enum regrouping all available devices provided by ATOS.""" MYQLM_PYLINALG = auto() MYQLM_CLINALG = auto() QLM_LINALG = auto() QLM_MPS = auto() QLM_MPO = auto() QLM_NOISYQPROC = auto() def is_remote(self): return self.name.startswith("QLM") def is_gate_based(self) -> bool: return True def is_simulator(self) -> bool: return True def is_noisy_simulator(self) -> bool: return self in {ATOSDevice.QLM_NOISYQPROC, ATOSDevice.QLM_MPO}
[docs] @staticmethod def from_str_remote(name: str): """Returns the first remote ATOSDevice matching the given name. Args: name: A substring of the desired device's name. Raises: ValueError: If no device corresponding to the given name could be found. Examples: >>> ATOSDevice.from_str_remote('NoisyQProc') <ATOSDevice.QLM_NOISYQPROC: 6> >>> ATOSDevice.from_str_remote('linalg') <ATOSDevice.QLM_LINALG: 3> >>> ATOSDevice.from_str_remote('Mps') <ATOSDevice.QLM_MPS: 4> """ u_name = name.upper() for elem in ATOSDevice: if u_name in elem.name and elem.is_remote(): return elem raise ValueError(f"No device found for name `{name}`.")
def supports_samples(self) -> bool: return True def supports_state_vector(self) -> bool: return self in { ATOSDevice.MYQLM_PYLINALG, ATOSDevice.MYQLM_CLINALG, ATOSDevice.QLM_LINALG, ATOSDevice.QLM_MPS, } def supports_observable(self) -> bool: return self in { ATOSDevice.MYQLM_PYLINALG, ATOSDevice.MYQLM_CLINALG, ATOSDevice.QLM_LINALG, } def supports_observable_ideal(self) -> bool: return True
[docs]class AWSDevice(AvailableDevice): """Enum regrouping all available devices provided by AWS.""" BRAKET_LOCAL_SIMULATOR = "LocalSimulator" BRAKET_SV1_SIMULATOR = "quantum-simulator/amazon/sv1" BRAKET_DM1_SIMULATOR = "quantum-simulator/amazon/dm1" BRAKET_TN1_SIMULATOR = "quantum-simulator/amazon/tn1" IONQ_ARIA_1 = "qpu/ionq/Aria-1" IONQ_ARIA_2 = "qpu/ionq/Aria-2" IONQ_FORTE_1 = "qpu/ionq/Forte-1" QUERA_AQUILA = "qpu/quera/Aquila" RIGETTI_ANKAA_2 = "qpu/rigetti/Ankaa-2" IQM_GARNET = "qpu/iqm/Garnet" def is_remote(self): return self != AWSDevice.BRAKET_LOCAL_SIMULATOR def is_gate_based(self) -> bool: return True def is_simulator(self) -> bool: return "SIMULATOR" in self.name def is_noisy_simulator(self) -> bool: return self in [ AWSDevice.BRAKET_LOCAL_SIMULATOR, AWSDevice.BRAKET_DM1_SIMULATOR, ]
[docs] def get_arn(self) -> str: """Retrieve the AWSDevice arn from this AWSDevice element. Returns: The arn of the device. Examples: >>> AWSDevice.IONQ_ARIA_1.get_arn() 'arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1' >>> AWSDevice.BRAKET_SV1_SIMULATOR.get_arn() 'arn:aws:braket:::device/quantum-simulator/amazon/sv1' >>> AWSDevice.RIGETTI_ANKAA_2.get_arn() 'arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2' """ region = self.get_region() if self.is_simulator(): region = "" return "arn:aws:braket:" + region + "::device/" + self.value
[docs] def get_region(self) -> str: """Retrieve the AWS region from this AWSDevice element. Returns: The region of the device. Examples: >>> AWSDevice.IONQ_ARIA_1.get_region() 'us-east-1' >>> AWSDevice.BRAKET_SV1_SIMULATOR.get_region() == get_env_variable("AWS_DEFAULT_REGION") True >>> AWSDevice.RIGETTI_ANKAA_2.get_region() 'us-west-1' """ if not self.is_remote(): raise ValueError("No arn for a local simulator") elif self == AWSDevice.RIGETTI_ANKAA_2: return "us-west-1" elif self == AWSDevice.IQM_GARNET: return "eu-north-1" elif self in [ AWSDevice.IONQ_ARIA_1, AWSDevice.IONQ_ARIA_2, AWSDevice.IONQ_FORTE_1, AWSDevice.QUERA_AQUILA, ]: return "us-east-1" else: return get_env_variable("AWS_DEFAULT_REGION")
[docs] @staticmethod def from_arn(arn: str): """Returns the right AWSDevice from the arn given in parameter. Args: arn: The AWS arn identifying the AWSDevice. Examples: >>> AWSDevice.from_arn('arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1') <AWSDevice.IONQ_ARIA_1: 'qpu/ionq/Aria-1'> >>> AWSDevice.from_arn('arn:aws:braket:::device/quantum-simulator/amazon/sv1') <AWSDevice.BRAKET_SV1_SIMULATOR: 'quantum-simulator/amazon/sv1'> """ for elem in AWSDevice: if elem.value in arn: return elem raise ValueError(f"No device found for ARN `{arn}`.")
def supports_samples(self) -> bool: return True def supports_state_vector(self) -> bool: return self in { AWSDevice.BRAKET_LOCAL_SIMULATOR, AWSDevice.BRAKET_SV1_SIMULATOR, } def supports_observable(self) -> bool: return True def supports_observable_ideal(self) -> bool: return self in { AWSDevice.BRAKET_LOCAL_SIMULATOR, AWSDevice.BRAKET_TN1_SIMULATOR, }
[docs]class GOOGLEDevice(AvailableDevice): """Enum regrouping all available devices provided by Google.""" CIRQ_LOCAL_SIMULATOR = "LocalSimulator" PROCESSOR_RAINBOW = "rainbow" PROCESSOR_WEBER = "weber" IONQ_SIMULATOR = "simulator" IONQ_QPU = "qpu" def is_remote(self): if self.name.startswith("IONQ"): return True return False
[docs] def is_ionq(self): """``True`` if the device is from ``IonQ``.""" return self.name.startswith("IONQ")
def is_gate_based(self) -> bool: return True def is_simulator(self) -> bool: return "SIMULATOR" in self.name
[docs] def is_processor(self) -> bool: """ Check if the device is a processor. Returns: True if the device is a processor, False otherwise. """ return self.name.startswith("PROCESSOR")
def has_reduced_gate_set(self) -> bool: return self in { GOOGLEDevice.PROCESSOR_RAINBOW, GOOGLEDevice.PROCESSOR_WEBER, GOOGLEDevice.IONQ_SIMULATOR, GOOGLEDevice.IONQ_QPU, } def supports_samples(self) -> bool: return True def supports_state_vector(self) -> bool: return self in { GOOGLEDevice.CIRQ_LOCAL_SIMULATOR, } def supports_observable(self) -> bool: return self in { GOOGLEDevice.PROCESSOR_RAINBOW, GOOGLEDevice.PROCESSOR_WEBER, GOOGLEDevice.CIRQ_LOCAL_SIMULATOR, } def supports_observable_ideal(self) -> bool: return self in { GOOGLEDevice.PROCESSOR_RAINBOW, GOOGLEDevice.PROCESSOR_WEBER, GOOGLEDevice.CIRQ_LOCAL_SIMULATOR, }
[docs]class AZUREDevice(AvailableDevice): """Enum regrouping all available devices provided by Azure.""" IONQ_SIMULATOR = "ionq.simulator" IONQ_QPU = "ionq.qpu" IONQ_QPU_ARIA_1 = "ionq.qpu.aria-1" IONQ_QPU_ARIA_2 = "ionq.qpu.aria-2" QUANTINUUM_SIM_H1_1 = "quantinuum.qpu.h1-1" QUANTINUUM_SIM_H1_1SC = "quantinuum.sim.h1-1sc" QUANTINUUM_SIM_H1_1E = "quantinuum.sim.h1-1e" RIGETTI_SIM_QVM = "rigetti.sim.qvm" RIGETTI_SIM_QPU_ANKAA_2 = "rigetti.qpu.ankaa-2" MICROSOFT_ESTIMATOR = "microsoft.estimator" def is_remote(self): return True def is_gate_based(self) -> bool: return True def is_simulator(self) -> bool: return self == AZUREDevice.IONQ_SIMULATOR def is_noisy_simulator(self) -> bool: raise NotImplementedError( 'Noisy simulations are not yet implemented for Azure.' )
[docs] def is_ionq(self): return self.name.startswith("IONQ")
def supports_samples(self) -> bool: return not self == AZUREDevice.MICROSOFT_ESTIMATOR def supports_state_vector(self) -> bool: return False def supports_observable(self) -> bool: return False def supports_observable_ideal(self) -> bool: return False