Skip to content
This repository has been archived by the owner on Jun 17, 2024. It is now read-only.

Commit

Permalink
fix: replaced backend adapters by backend classes
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Wagner committed Apr 15, 2024
1 parent a676016 commit 634d483
Show file tree
Hide file tree
Showing 15 changed files with 298 additions and 296 deletions.
94 changes: 62 additions & 32 deletions planqk/qiskit/backend.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import datetime
from abc import ABC
from abc import ABC, abstractmethod
from copy import copy
from typing import Optional

from qiskit.circuit import Instruction as QiskitInstruction, Delay, Parameter
from qiskit.circuit import Measure
from qiskit.providers import BackendV2, Provider
from qiskit.providers.models import QasmBackendConfiguration, GateConfig
from qiskit.transpiler import Target

from .client.backend_dtos import ConfigurationDto, TYPE, BackendDto, ConnectivityDto, PROVIDER
from .client.backend_dtos import ConfigurationDto, TYPE, BackendDto, PROVIDER
from .client.job_dtos import JobDto
from .job import PlanqkJob
from .options import OptionsV2
from .providers.adapter import ProviderAdapterFactory


class PlanqkBackend(BackendV2, ABC):
Expand Down Expand Up @@ -54,32 +55,61 @@ def __init__( # pylint: disable=too-many-arguments
**fields,
)
self._backend_info = backend_info
self._is_simulator = self.backend_info.type == TYPE.SIMULATOR
self._target = self._planqk_backend_to_target()
self._configuration = self._planqk_backend_dto_to_configuration()
self._instance = None

@property
def backend_info(self):
return self._backend_info

@property
def is_simulator(self):
return self._is_simulator

@abstractmethod
def to_gate(self, name: str):
pass

@abstractmethod
def get_single_qubit_gate_properties(self):
pass

@abstractmethod
def get_multi_qubit_gate_properties(self):
pass

non_gate_instr_mapping = {
"delay": Delay(Parameter("t")),
"measure": Measure(),
}

def to_non_gate_instruction(self, name: str) -> Optional[QiskitInstruction]:
instr = self.non_gate_instr_mapping.get(name, None)
if instr is not None:
instr.has_single_gate_props = True
return instr
return None

def _planqk_backend_to_target(self) -> Target:
"""Converts properties of a PlanQK actual into Qiskit Target object.
Returns:
target for Qiskit actual
"""
# building target
configuration: ConfigurationDto = self._backend_info.configuration

configuration: ConfigurationDto = self.backend_info.configuration
qubit_count: int = configuration.qubit_count
target = Target(description=f"Target for PlanQK actual {self.name}", num_qubits=qubit_count)

is_simulator = self._backend_info.type == TYPE.SIMULATOR
qubits = configuration.qubits
connectivity: ConnectivityDto = self._backend_info.configuration.connectivity

adapter = ProviderAdapterFactory.get_adapter(self._backend_info.provider)

single_qubit_props = adapter.single_qubit_gate_props(qubits, is_simulator)
multi_qubit_props = adapter.multi_qubit_gate_props(qubits, connectivity, is_simulator)
single_qubit_props = self.get_single_qubit_gate_properties()
multi_qubit_props = self.get_multi_qubit_gate_properties()
gates_names = {gate.name.lower() for gate in configuration.gates}

for gate in gates_names:
gate = adapter.to_gate(gate, is_simulator)
gate = self.to_gate(gate)

if gate is None:
continue
Expand All @@ -96,7 +126,7 @@ def _planqk_backend_to_target(self) -> Target:

non_gate_instructions = set(configuration.instructions).difference(gates_names).difference({'measure'})
for non_gate_instruction_name in non_gate_instructions:
instruction = adapter.to_non_gate_instruction(non_gate_instruction_name, is_simulator)
instruction = self.to_non_gate_instruction(non_gate_instruction_name)
if instruction is not None:
if instruction.has_single_gate_props:
target.add_instruction(instruction, single_qubit_props)
Expand All @@ -107,30 +137,30 @@ def _planqk_backend_to_target(self) -> Target:

def _planqk_backend_dto_to_configuration(self) -> QasmBackendConfiguration:
basis_gates = [self._get_gate_config_from_target(basis_gate.name)
for basis_gate in self._backend_info.configuration.gates if basis_gate.native_gate
for basis_gate in self.backend_info.configuration.gates if basis_gate.native_gate
and self._get_gate_config_from_target(basis_gate.name) is not None]
gates = [self._get_gate_config_from_target(gate.name)
for gate in self._backend_info.configuration.gates if not gate.native_gate
for gate in self.backend_info.configuration.gates if not gate.native_gate
and self._get_gate_config_from_target(gate.name) is not None]

return QasmBackendConfiguration(
backend_name=self.name,
backend_version=self.backend_version,
n_qubits=self._backend_info.configuration.qubit_count,
n_qubits=self.backend_info.configuration.qubit_count,
basis_gates=basis_gates,
gates=gates,
local=False,
simulator=self._backend_info.type == TYPE.SIMULATOR,
simulator=self.backend_info.type == TYPE.SIMULATOR,
conditional=False,
open_pulse=False,
memory=self._backend_info.configuration.memory_result_supported,
max_shots=self._backend_info.configuration.shots_range.max,
memory=self.backend_info.configuration.memory_result_supported,
max_shots=self.backend_info.configuration.shots_range.max,
coupling_map=self.coupling_map,
supported_instructions=self._target.instructions,
max_experiments=self._backend_info.configuration.shots_range.max, # Only one circuit is supported per job
description=self._backend_info.documentation.description,
min_shots=self._backend_info.configuration.shots_range.min,
online_date=self._backend_info.updated_at # TODO replace with online date
max_experiments=self.backend_info.configuration.shots_range.max, # Only one circuit is supported per job
description=self.backend_info.documentation.description,
min_shots=self.backend_info.configuration.shots_range.min,
online_date=self.backend_info.updated_at # TODO replace with online date
)

def _get_gate_config_from_target(self, name) -> GateConfig:
Expand All @@ -155,11 +185,11 @@ def max_circuits(self):

@property
def min_shots(self):
return self._backend_info.configuration.shots_range.min
return self.backend_info.configuration.shots_range.min

@property
def max_shots(self):
return self._backend_info.configuration.shots_range.max
return self.backend_info.configuration.shots_range.max

@classmethod
def _default_options(cls):
Expand All @@ -183,7 +213,7 @@ def run(self, circuit, **kwargs) -> PlanqkJob:

# PennyLane-Qiskit Plugin identifies the result based on the circuit name which must be "circ0"
circuit.name = "circ0"
shots = kwargs.get('shots', self._backend_info.configuration.shots_range.min)
shots = kwargs.get('shots', self.backend_info.configuration.shots_range.min)

# add kwargs, if defined as options, to a copy of the options
options = copy(self.options)
Expand All @@ -192,13 +222,13 @@ def run(self, circuit, **kwargs) -> PlanqkJob:
if field in options.data:
options[field] = kwargs[field]

supported_input_formats = self._backend_info.configuration.supported_input_formats
supported_input_formats = self.backend_info.configuration.supported_input_formats

backend_input = convert_to_backend_input(supported_input_formats, circuit, self, options)
input_params = convert_to_backend_params(self._backend_info.provider, circuit, options)
input_params = convert_to_backend_params(self.backend_info.provider, circuit, options)

job_request = JobDto(backend_id=self._backend_info.id,
provider=self._backend_info.provider.name,
job_request = JobDto(backend_id=self.backend_info.id,
provider=self.backend_info.provider.name,
input_format=backend_input[0],
input=backend_input[1],
shots=shots,
Expand Down Expand Up @@ -228,4 +258,4 @@ def configuration(self) -> QasmBackendConfiguration:
@property
def backend_provider(self) -> PROVIDER:
"""Return the provider offering the quantum backend resource."""
return self._backend_info.provider
return self.backend_info.provider
10 changes: 7 additions & 3 deletions planqk/qiskit/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ def get_backend(self, name=None, provider: PROVIDER = None, **kwargs):
# add additional parameters to the backend init params
backend_init_params.update(**kwargs)

return self._get_backend_object(backend_dto, backend_init_params)

def _get_backend_object(self, backend_dto, backend_init_params):
if backend_dto.provider == PROVIDER.AWS:
from planqk.qiskit.providers.aws.aws_backend import PlanqkAwsBackend
return PlanqkAwsBackend(**backend_init_params)
Expand All @@ -103,10 +106,11 @@ def get_backend(self, name=None, provider: PROVIDER = None, **kwargs):
from planqk.qiskit.providers.qryd.qryd_backend import PlanqkQrydBackend
return PlanqkQrydBackend(**backend_init_params)
elif backend_dto.provider in {PROVIDER.IBM, PROVIDER.IBM_CLOUD}:
from planqk.qiskit.providers.ibm.ibm_backend import PlanqkIbmBackend
return PlanqkIbmBackend(**backend_init_params)
from planqk.qiskit.providers.ibm.ibm_provider_backend import PlanqkIbmProviderBackend
return PlanqkIbmProviderBackend(**backend_init_params)
else:
return PlanqkBackend(**backend_init_params)
return QiskitBackendNotFoundError(
f"Backends of provider '{backend_dto.provider}' are not supported.")

def retrieve_job(self, backend: PlanqkBackend, job_id: str):
"""
Expand Down
50 changes: 0 additions & 50 deletions planqk/qiskit/providers/adapter.py

This file was deleted.

29 changes: 29 additions & 0 deletions planqk/qiskit/providers/aws/aws_backend.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
from typing import Optional

from qiskit.circuit import Gate
from qiskit_braket_provider.providers.adapter import _GATE_NAME_TO_QISKIT_GATE

from planqk.qiskit import PlanqkBackend
from planqk.qiskit.options import OptionsV2

Expand All @@ -10,3 +15,27 @@ def __init__(self, **kwargs):
@classmethod
def _default_options(cls):
return OptionsV2()

def to_gate(self, name: str) -> Optional[Gate]:
name = name.lower()
gate = _GATE_NAME_TO_QISKIT_GATE.get(name, None)
# Braket quantum backends only support 1 and 2 qubit gates
return gate if (gate and gate.num_qubits < 3) or self.is_simulator else None

def get_single_qubit_gate_properties(self) -> dict:
if self.is_simulator:
return {None: None}
return {(int(qubit.id),): None for qubit in self.backend_info.configuration.qubits}

def get_multi_qubit_gate_properties(self) -> dict:
qubits = self.backend_info.configuration.qubits
connectivity = self.backend_info.configuration.connectivity
if self.is_simulator:
return {None: None}
if connectivity.fully_connected:
return {(int(qubit1.id), int(qubit2.id)): None for qubit1 in qubits for qubit2 in qubits
if qubit1.id != qubit2.id}
else:
return {(int(qubit), int(connected_qubit)): None
for qubit, connections in connectivity.graph.items()
for connected_qubit in connections}
33 changes: 0 additions & 33 deletions planqk/qiskit/providers/aws_adapter.py

This file was deleted.

21 changes: 21 additions & 0 deletions planqk/qiskit/providers/azure/azure_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typing import Optional

from qiskit.circuit import Gate

from planqk.qiskit import PlanqkBackend


class PlanqkAzureBackend(PlanqkBackend):

def __init__(self, **kwargs):
super().__init__(**kwargs)

def to_gate(self, name: str) -> Optional[Gate]:
name = name.lower()
return Gate(name, 0, [])

def get_single_qubit_gate_properties(self) -> dict:
return {None: None}

def get_multi_qubit_gate_properties(self) -> dict:
return {None: None}
7 changes: 3 additions & 4 deletions planqk/qiskit/providers/azure/ionq_backend.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
from planqk.qiskit import PlanqkBackend
from planqk.qiskit.options import OptionsV2
from planqk.qiskit.providers.azure.azure_backend import PlanqkAzureBackend


class PlanqkAzureIonqBackend(PlanqkBackend):
class PlanqkAzureIonqBackend(PlanqkAzureBackend):

def __init__(self, **kwargs):
super().__init__(**kwargs)

@classmethod
def _default_options(cls):
return OptionsV2(
gateset="qis",
)
gateset="qis", )
17 changes: 0 additions & 17 deletions planqk/qiskit/providers/azure_adapter.py

This file was deleted.

Loading

0 comments on commit 634d483

Please sign in to comment.