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

Commit

Permalink
feat: add support for accessing IBM backends through IBMProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Wagner committed Apr 10, 2024
1 parent 42040b4 commit d5189d3
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 55 deletions.
29 changes: 11 additions & 18 deletions planqk/qiskit/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from qiskit.providers.models import QasmBackendConfiguration, GateConfig
from qiskit.transpiler import Target

from .client.backend_dtos import ConfigurationDto, TYPE, BackendDto, ConnectivityDto, PROVIDER, HARDWARE_PROVIDER
from .client.backend_dtos import ConfigurationDto, TYPE, BackendDto, ConnectivityDto, PROVIDER
from .client.job_dtos import JobDto
from .job import PlanqkJob
from .options import OptionsV2
Expand Down Expand Up @@ -45,14 +45,14 @@ def __init__( # pylint: disable=too-many-arguments
**fields: other arguments
"""

super().__init__(
provider=provider,
name=name,
description=description,
online_date=online_date,
backend_version=backend_version,
**fields,
)
BackendV2.__init__(self,
provider=provider,
name=name,
description=description,
online_date=online_date,
backend_version=backend_version,
**fields,
)
self._backend_info = backend_info
self._target = self._planqk_backend_to_target()
self._configuration = self._planqk_backend_dto_to_configuration()
Expand Down Expand Up @@ -130,6 +130,7 @@ def _planqk_backend_dto_to_configuration(self) -> QasmBackendConfiguration:
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 Down Expand Up @@ -175,8 +176,6 @@ def run(self, circuit, **kwargs) -> PlanqkJob:
"""
from planqk.qiskit.providers.job_input_converter import convert_to_backend_input, convert_to_backend_params

self._validate_provider_for_backend()

if isinstance(circuit, (list, tuple)):
if len(circuit) > 1:
raise ValueError("Multi-experiment jobs are not supported")
Expand All @@ -194,6 +193,7 @@ def run(self, circuit, **kwargs) -> PlanqkJob:
options[field] = kwargs[field]

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)

Expand All @@ -206,13 +206,6 @@ def run(self, circuit, **kwargs) -> PlanqkJob:

return PlanqkJob(backend=self, job_details=job_request)

def _validate_provider_for_backend(self):
from planqk.qiskit.runtime_provider import PlanqkQiskitRuntimeService
if (self._backend_info.hardware_provider == HARDWARE_PROVIDER.IBM
and not isinstance(self.provider, PlanqkQiskitRuntimeService)):
raise ValueError(f"Jobs for IBM backends must not be created with {self.provider.__class__.__name__}. "
f"Use {PlanqkQiskitRuntimeService.__name__} instead.")

def retrieve_job(self, job_id: str) -> PlanqkJob:
"""Return a single job.
Expand Down
9 changes: 5 additions & 4 deletions planqk/qiskit/client/job_dtos.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@


class INPUT_FORMAT(str, Enum):
BRAKET_OPEN_QASM_V3 = "BRAKET_OPEN_QASM_V3"
OPEN_QASM_V3 = "OPEN_QASM_V3"
IONQ_CIRCUIT_V1 = "IONQ_CIRCUIT_V1"
QISKIT_PRIMITIVE = "QISKIT_PRIMITIVE"
QISKIT = "QISKIT"
QOQO = "QOQO"


Expand Down Expand Up @@ -49,10 +50,10 @@ def __post_init__(self):

class RuntimeJobParamsDto(BaseModel):
program_id: str
image: Optional[str]
image: Optional[str] = None
hgp: Optional[str]
log_level: Optional[str]
session_id: Optional[str]
log_level: Optional[str] = None
session_id: Optional[str] = None
max_execution_time: Optional[int] = None
start_session: Optional[bool] = False
session_time: Optional[int] = None
5 changes: 3 additions & 2 deletions planqk/qiskit/job.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from typing import Optional

from planqk.qiskit.client.client import _PlanqkClient
from planqk.qiskit.client.job_dtos import JobDto
from qiskit.providers import JobV1, JobStatus, Backend
from qiskit.qobj import QobjExperimentHeader
from qiskit.result import Result
from qiskit.result.models import ExperimentResult, ExperimentResultData

from planqk.qiskit.client.client import _PlanqkClient
from planqk.qiskit.client.job_dtos import JobDto

JobStatusMap = {
"CREATED": JobStatus.INITIALIZING,
"PENDING": JobStatus.QUEUED,
Expand Down
6 changes: 3 additions & 3 deletions planqk/qiskit/providers/aws_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
from braket.ir.openqasm import Program as OpenQASMProgram


def transform_to_qasm_3_program(braket_circuit: Circuit,
disable_qubit_rewiring: bool,
inputs: Dict[str, float]) -> str:
def transform_braket_to_qasm_3_program(braket_circuit: Circuit,
disable_qubit_rewiring: bool,
inputs: Dict[str, float]) -> str:
"""Transforms a Braket input to a QASM 3 program."""

qubit_reference_type = QubitReferenceType.VIRTUAL
Expand Down
73 changes: 70 additions & 3 deletions planqk/qiskit/providers/ibm/ibm_backend.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
from planqk.qiskit import PlanqkBackend
from planqk.qiskit.options import OptionsV2
import json
from typing import Dict, Optional, List

from qiskit import QuantumCircuit
from qiskit.providers import Options
from qiskit.providers.models import BackendStatus
from qiskit.qobj.utils import MeasLevel, MeasReturnType
from qiskit_ibm_provider import IBMBackend
from qiskit_ibm_provider.job import IBMCircuitJob
from qiskit_ibm_provider.utils import RuntimeEncoder

from planqk.qiskit import PlanqkBackend, PlanqkJob
from planqk.qiskit.client.backend_dtos import STATUS
from planqk.qiskit.client.client import _PlanqkClient
from planqk.qiskit.client.job_dtos import JobDto, INPUT_FORMAT, RuntimeJobParamsDto
from planqk.qiskit.options import OptionsV2
from planqk.qiskit.planqk_runtime_job import PlanqkRuntimeJob


def _encode_circuit_base64(circuit: QuantumCircuit, backend: PlanqkBackend, options: Options):
# Transforms circuit to base64 encoded byte stream
input_json_str = json.dumps(circuit, cls=RuntimeEncoder)
# Transform back to json but with the base64 encoded byte stream
return json.loads(input_json_str)


class PlanqkIbmBackend(PlanqkBackend):

def __init__(self, **kwargs):
super().__init__(**kwargs)
PlanqkBackend.__init__(self, **kwargs)
self.ibm_backend = IBMBackend(configuration=self.configuration(), provider=None, api_client=None)
self.ibm_backend._runtime_run = self._submit_job
self.ibm_backend.status = self.status

def _default_options(self):
return OptionsV2(
Expand All @@ -24,3 +48,46 @@ def _default_options(self):
noise_model=None,
seed_simulator=None,
)

def run(self, circuit, **kwargs) -> PlanqkRuntimeJob:
return IBMBackend.run(self.ibm_backend, circuit, **kwargs)

def status(self):
operational = self._backend_info.status == STATUS.ONLINE
status_msg = "active" if operational else self._backend_info.status.name.lower()
pending_jobs = 0 # TODO set pending jobs

return BackendStatus.from_dict({'backend_name': self.name,
'backend_version': self.backend_version,
'operational': operational,
'status_msg': status_msg,
'pending_jobs': pending_jobs})

def _submit_job(
self,
program_id: str,
inputs: Dict,
backend_name: str,
job_tags: Optional[List[str]] = None,
image: Optional[str] = None,
) -> IBMCircuitJob:
encoded_input = _encode_circuit_base64(circuit=inputs, backend=self, options=None)
hgp_name = 'ibm-q/open/main'

runtime_job_params = RuntimeJobParamsDto(
program_id=program_id,
hgp=hgp_name
)

job_request = JobDto(backend_id=self._backend_info.id,
provider=self._backend_info.provider.name,
input_format=INPUT_FORMAT.QISKIT,
input=encoded_input,
shots=inputs.get('shots'),
input_params=runtime_job_params.dict())

return PlanqkRuntimeJob(backend=self, job_details=job_request)

def retrieve_job(self, job_id: str) -> PlanqkJob:
job_details = _PlanqkClient.get_job(job_id)
return PlanqkRuntimeJob(backend=self, job_id=job_id, job_details=job_details)
19 changes: 10 additions & 9 deletions planqk/qiskit/providers/job_input_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@

from braket.circuits import Circuit
from braket.circuits.circuit_helpers import validate_circuit_and_shots
from planqk.qiskit.backend import PlanqkBackend
from planqk.qiskit.client.backend_dtos import PROVIDER
from planqk.qiskit.client.job_dtos import INPUT_FORMAT
from planqk.qiskit.providers.aws_converters import transform_to_qasm_3_program
from planqk.qiskit.providers.qryd.qryd_converters import convert_to_wire_format, create_qoqu_input_params
from qiskit import QuantumCircuit
from qiskit.providers import Options
from qiskit_braket_provider.providers.adapter import to_braket
from qiskit_ibm_runtime import RuntimeEncoder
from qiskit_ionq.helpers import qiskit_circ_to_ionq_circ

from planqk.qiskit.backend import PlanqkBackend
from planqk.qiskit.client.backend_dtos import PROVIDER
from planqk.qiskit.client.job_dtos import INPUT_FORMAT
from planqk.qiskit.providers.aws_converters import transform_braket_to_qasm_3_program
from planqk.qiskit.providers.qryd.qryd_converters import convert_to_wire_format, create_qoqu_input_params


def _convert_to_open_qasm_3(circuit: QuantumCircuit, backend: PlanqkBackend, options: Options):
def _convert_to_braket_qasm_3(circuit: QuantumCircuit, backend: PlanqkBackend, options: Options):
shots = options.get("shots", 1)
inputs = options.get("inputs", {})
verbatim = options.get("verbatim", False)
Expand All @@ -25,7 +26,7 @@ def _convert_to_open_qasm_3(circuit: QuantumCircuit, backend: PlanqkBackend, opt

validate_circuit_and_shots(braket_circuit, shots)

return transform_to_qasm_3_program(braket_circuit, False, inputs)
return transform_braket_to_qasm_3_program(braket_circuit, False, inputs)


def _convert_to_ionq(circuit: QuantumCircuit, backend: PlanqkBackend, options: Options):
Expand Down Expand Up @@ -62,9 +63,9 @@ def _create_empty_input_params(circuit: Circuit, options: Options):


input_format_converter_factory = {
INPUT_FORMAT.OPEN_QASM_V3: _convert_to_open_qasm_3,
INPUT_FORMAT.BRAKET_OPEN_QASM_V3: _convert_to_braket_qasm_3,
INPUT_FORMAT.IONQ_CIRCUIT_V1: _convert_to_ionq,
INPUT_FORMAT.QISKIT_PRIMITIVE: _convert_to_qiskit_primitive,
INPUT_FORMAT.QISKIT: _convert_to_qiskit_primitive,
INPUT_FORMAT.QOQO: _convert_to_qoqo_circuit
}

Expand Down
15 changes: 3 additions & 12 deletions planqk/qiskit/runtime_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,8 @@ def run(self,

qrt_options.validate(channel=self.channel)

hgp_name = 'ibm-q/open/main' # TODO determine dynamically
# if self._channel == "ibm_quantum":
# # Find the right hgp
# hgp = self._get_hgp(instance=qrt_options.instance, backend_name=qrt_options.backend)
# hgp_name = hgp.name
# backend = self.backend(name=qrt_options.backend, instance=hgp_name)
# status = backend.status()
# if status.operational is True and status.status_msg != "active":
# logger.warning(
# f"The backend {backend.name} currently has a status of {status.status_msg}."
# )
hgp_name = 'ibm-q/open/main'

runtime_job_params = RuntimeJobParamsDto(
program_id=program_id,
image=qrt_options.image,
Expand All @@ -94,7 +85,7 @@ def run(self,
session_time=qrt_options.session_time,
)

input_data = convert_to_backend_input([INPUT_FORMAT.QISKIT_PRIMITIVE], inputs)
input_data = convert_to_backend_input([INPUT_FORMAT.QISKIT], inputs)

backend_id = options.get('backend')
backend = self.backend(backend_id)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setup(
name='planqk-quantum',
version="2.0.0",
version="2.1.0rc1",
author='Anaqor AG',
author_email='info@anaqor.io',
url='https://github.com/planqk/planqk-quantum',
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/planqk/client_mocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"fully_connected": True,
"graph": {},
},
"supported_input_formats": ["OPEN_QASM_V3"],
"supported_input_formats": ["BRAKET_OPEN_QASM_V3"],
"shots_range": {
"min": 1,
"max": 10
Expand Down Expand Up @@ -72,7 +72,7 @@
"fully_connected": False,
"graph": {},
},
"supported_input_formats": ["OPEN_QASM_V3"],
"supported_input_formats": ["BRAKET_OPEN_QASM_V3"],
"shots_range": {
"min": 2,
"max": 20
Expand All @@ -99,7 +99,7 @@
"shots": 10,
"id": "123",
"input": {"param1": "value1", "param2": "value2"},
"input_format": "OPEN_QASM_V3",
"input_format": "BRAKET_OPEN_QASM_V3",
"input_params": {"param1": "value1", "param2": "value2"},
"begin_execution_time": "2023-07-11T10:00:00",
"cancellation_time": "2023-07-11T11:00:00",
Expand Down

0 comments on commit d5189d3

Please sign in to comment.