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

Commit

Permalink
feat!: Migrated to Qiskit 1.0
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The SDK offers now access to the PlanQK backends through Qiskit 1.0
  • Loading branch information
Sebastian Wagner committed Apr 3, 2024
2 parents a1c4dd0 + 62360a7 commit 73bbcb1
Show file tree
Hide file tree
Showing 13 changed files with 123 additions and 76 deletions.
16 changes: 9 additions & 7 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ channels:
dependencies:
- python=3.9
- pip>=21.1.3
- pydantic=1.9.0
- pydantic==2.5.0
- pip:
## dev dependencies
- qiskit==0.44.1
- qiskit-ibm-runtime==0.17.0
- qiskit-ibm-provider==0.7.2
- qiskit==1.0.2
- qiskit-ibm-runtime==0.21.2
- qiskit-ibm-provider==0.10.0
- setuptools
- pytest
- pytest-cov
Expand All @@ -21,10 +21,12 @@ dependencies:
## prod dependencies
- requests
# aws braket
- amazon-braket-default-simulator==1.21.0
- amazon-braket-schemas==1.21.0
# we need the latest dev version as this fixes the issue with the Rigetti's qubit ids
- git+https://github.com/qiskit-community/qiskit-braket-provider.git@main
# azure quantum
- azure-quantum[qiskit]==0.28.*
- qiskit-ionq==0.4.4
- azure-quantum[qiskit]==2.0.0
- qiskit-ionq==0.5.0
# dwave
- dwave-ocean-sdk==6.4.1
- dwave-ocean-sdk==6.9.0
35 changes: 21 additions & 14 deletions planqk/qiskit/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
from abc import ABC
from copy import copy

from qiskit.circuit import 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 planqk.qiskit.providers.job_input_converter import convert_to_backend_input, convert_to_backend_params
from .client.backend_dtos import ConfigurationDto, TYPE, BackendDto, ConnectivityDto, PROVIDER, HARDWARE_PROVIDER
from .client.job_dtos import JobDto
from .job import PlanqkJob
Expand Down Expand Up @@ -58,6 +56,7 @@ def __init__( # pylint: disable=too-many-arguments
self._backend_info = backend_info
self._target = self._planqk_backend_to_target()
self._configuration = self._planqk_backend_dto_to_configuration()
self._instance = None

def _planqk_backend_to_target(self) -> Target:
"""Converts properties of a PlanQK actual into Qiskit Target object.
Expand All @@ -78,26 +77,31 @@ def _planqk_backend_to_target(self) -> Target:

single_qubit_props = adapter.single_qubit_gate_props(qubits, is_simulator)
multi_qubit_props = adapter.multi_qubit_gate_props(qubits, connectivity, is_simulator)
for gate in configuration.gates:
name = gate.name
gate = adapter.op_to_instruction(name)
gates_names = {gate.name.lower() for gate in configuration.gates}
for gate in gates_names:
gate = adapter.to_gate(gate, is_simulator)

if gate is None:
continue

if gate.num_qubits == 1:
target.add_instruction(gate, single_qubit_props)
elif gate.num_qubits == 2:
target.add_instruction(gate, multi_qubit_props)
target.add_instruction(instruction=gate, properties=single_qubit_props)
elif gate.num_qubits > 1:
target.add_instruction(instruction=gate, properties=multi_qubit_props)
elif gate.num_qubits == 0 and single_qubit_props == {None: None}:
# For gates without qubit number qargs can not be determined
target.add_instruction(gate, {None: None})
target.add_instruction(instruction=gate, properties={None: None})

target.add_instruction(Measure(), single_qubit_props)

if "delay" in configuration.instructions and "delay" not in target:
# gate_props = {None: None} if is_simulator else single_qubit_gate_props()
target.add_instruction(Delay(Parameter("t")), single_qubit_props)
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)
if instruction is not None:
if instruction.has_single_gate_props:
target.add_instruction(instruction, single_qubit_props)
else:
target.add_instruction(instruction=instruction, name=non_gate_instruction_name)

return target

Expand Down Expand Up @@ -130,7 +134,8 @@ def _planqk_backend_dto_to_configuration(self) -> QasmBackendConfiguration:

def _get_gate_config_from_target(self, name) -> GateConfig:
operations = [operation for operation in self._target.operations
if operation.name.casefold() == name.casefold()]
if isinstance(operation.name, str) # Filters out the IBM conditional instructions having no name
and operation.name.casefold() == name.casefold()]
if len(operations) == 1:
operation = operations[0]
return GateConfig(
Expand Down Expand Up @@ -168,6 +173,8 @@ def run(self, circuit, **kwargs) -> PlanqkJob:
Returns:
PlanqkJob: The job instance for the circuit that was run.
"""
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)):
Expand All @@ -187,7 +194,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, options)
backend_input = convert_to_backend_input(supported_input_formats, circuit, self, options)
input_params = convert_to_backend_params(self._backend_info.provider, circuit, options)

job_request = JobDto(backend_id=self._backend_info.id,
Expand Down
5 changes: 2 additions & 3 deletions planqk/qiskit/job.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
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
15 changes: 14 additions & 1 deletion planqk/qiskit/providers/adapter.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
from typing import Optional, List

from qiskit.circuit import Gate, Delay, Parameter, Measure
from qiskit.circuit import Instruction as QiskitInstruction

from planqk.qiskit.client.backend_dtos import PROVIDER, QubitDto, ConnectivityDto


class ProviderAdapter:
def op_to_instruction(self, operation: str) -> Optional[QiskitInstruction]:
non_gate_instr_mapping = {
"delay": Delay(Parameter("t")),
"measure": Measure(),
}

def to_gate(self, name: str, is_simulator: bool = False) -> Optional[Gate]:
pass

def to_non_gate_instruction(self, name: str, is_simulator: bool = False) -> 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 single_qubit_gate_props(self, qubits: List[QubitDto], is_simulator: bool = False):
pass

Expand Down
14 changes: 11 additions & 3 deletions planqk/qiskit/providers/aws/aws_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ def __init__(self, **kwargs):

@classmethod
def _default_options(cls):
return OptionsV2(
verbatim=False
)
return OptionsV2()


class PlanqkAwsSv1Backend(PlanqkAwsBackend):

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

@classmethod
def _default_options(cls):
return OptionsV2()
18 changes: 11 additions & 7 deletions planqk/qiskit/providers/aws_adapter.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
from typing import Optional, List

from qiskit.circuit import Instruction as QiskitInstruction, Gate
from qiskit.circuit import Gate
from qiskit_braket_provider.providers.adapter import _GATE_NAME_TO_QISKIT_GATE

import planqk.qiskit.providers.adapter as adapter
from planqk.qiskit.client.backend_dtos import QubitDto, ConnectivityDto
from planqk.qiskit.providers.aws_converters import qiskit_gate_name_to_braket_gate_mapping


class AwsAdapter(adapter.ProviderAdapter):
"""Adapter for AWS Braket backend."""

def op_to_instruction(self, operation: str) -> Optional[QiskitInstruction]:
operation = operation.lower()
gate = qiskit_gate_name_to_braket_gate_mapping.get(operation, None) or Gate(operation, 0, [])
# Braket only supports 1 and 2 qubit gates
return gate if gate.num_qubits < 3 else None
def to_gate(self, name: str, is_simulator: bool = False) -> 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 is_simulator else None

def single_qubit_gate_props(self, qubits: List[QubitDto], is_simulator: bool = False):
if is_simulator:
return {None: None}
return {(int(qubit.id),): None for qubit in qubits}

def multi_qubit_gate_props(self, qubits: List[QubitDto], connectivity: ConnectivityDto, is_simulator: bool = False):
if 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}
Expand Down
9 changes: 4 additions & 5 deletions planqk/qiskit/providers/azure_adapter.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
from typing import Optional, List

from qiskit.circuit import Instruction as QiskitInstruction, Gate

from planqk.qiskit.client.backend_dtos import QubitDto, ConnectivityDto
from planqk.qiskit.providers.adapter import ProviderAdapter
from qiskit.circuit import Gate


class AzureAdapter(ProviderAdapter):
def op_to_instruction(self, operation: str) -> Optional[QiskitInstruction]:
operation = operation.lower()
return Gate(operation, 0, [])
def to_gate(self, name: str, is_simulator: bool = False) -> Optional[Gate]:
name = name.lower()
return Gate(name, 0, [])

def single_qubit_gate_props(self, qubits: List[QubitDto], is_simulator: bool = False):
return {None: None}
Expand Down
25 changes: 21 additions & 4 deletions planqk/qiskit/providers/ibm/ibm_adapter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional, List

from qiskit.circuit import Instruction as QiskitInstruction, Gate
from qiskit.circuit import Gate, IfElseOp, WhileLoopOp, ForLoopOp, SwitchCaseOp, Instruction
from qiskit.circuit import Parameter, Reset
from qiskit.circuit.library import IGate, SXGate, XGate, CXGate, RZGate, ECRGate, CZGate

Expand All @@ -18,11 +18,28 @@
"cz": CZGate(),
}

qiskit_control_flow_mapping = {
"if_else": IfElseOp,
"while_loop": WhileLoopOp,
"for_loop": ForLoopOp,
"switch_case": SwitchCaseOp,
}


class IbmAdapter(ProviderAdapter):
def op_to_instruction(self, operation: str) -> Optional[QiskitInstruction]:
operation = operation.lower()
return ibm_name_mapping.get(operation, None) or Gate(operation, 0, [])

def to_gate(self, name: str, is_simulator: bool = False) -> Optional[Gate]:
name = name.lower()

return ibm_name_mapping.get(name, None) or Gate(name, 0, [])

def to_non_gate_instruction(self, name: str, is_simulator: bool = False) -> Optional[Instruction]:
if name in qiskit_control_flow_mapping:
instr = qiskit_control_flow_mapping[name]
instr.has_single_gate_props = False
return instr

return super().to_non_gate_instruction(name, is_simulator)

def single_qubit_gate_props(self, qubits: List[QubitDto], is_simulator: bool = False):
return {None: None} if is_simulator else {(int(qubit.id),): None for qubit in qubits}
Expand Down
3 changes: 1 addition & 2 deletions planqk/qiskit/providers/ibm/ibm_backend.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from qiskit.qobj.utils import MeasLevel, MeasReturnType

from planqk.qiskit import PlanqkBackend
from planqk.qiskit.options import OptionsV2
from qiskit.qobj.utils import MeasLevel, MeasReturnType


class PlanqkIbmBackend(PlanqkBackend):
Expand Down
28 changes: 15 additions & 13 deletions planqk/qiskit/providers/job_input_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,32 @@

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.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


def _convert_to_open_qasm_3(circuit: QuantumCircuit, options: Options):
def _convert_to_open_qasm_3(circuit: QuantumCircuit, backend: PlanqkBackend, options: Options):
shots = options.get("shots", 1)
inputs = options.get("inputs", {})
verbatim = options.get("verbatim", False)

braket_circuit = to_braket(circuit, verbatim=verbatim)
basis_gates = backend.operation_names if not verbatim else None
braket_circuit = to_braket(circuit, basis_gates, verbatim=verbatim)

validate_circuit_and_shots(braket_circuit, shots)

return transform_to_qasm_3_program(braket_circuit, False, inputs)


def _convert_to_ionq(circuit: QuantumCircuit, options: Options):
def _convert_to_ionq(circuit: QuantumCircuit, backend: PlanqkBackend, options: Options):
gateset = options.get("gateset", "qis")
ionq_circ, _, _ = qiskit_circ_to_ionq_circ(circuit, gateset=gateset)
return {
Expand All @@ -36,14 +38,14 @@ def _convert_to_ionq(circuit: QuantumCircuit, options: Options):
}


def _convert_to_qiskit_primitive(circuit: QuantumCircuit, options: Options):
def _convert_to_qiskit_primitive(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)


def _convert_to_qoqo_circuit(circuit: QuantumCircuit, options: Options):
def _convert_to_qoqo_circuit(circuit: QuantumCircuit, backend: PlanqkBackend, options: Options):
return convert_to_wire_format(circuit=circuit, options=options)


Expand All @@ -52,7 +54,7 @@ def _create_qoqo_input_params(circuit: QuantumCircuit, options: Options):


def _create_aws_input_params(circuit: QuantumCircuit, options: Options):
return {'disable_qubit_rewiring': False, 'qubit_count': circuit.num_qubits}
return {'disable_qubit_rewiring': False}


def _create_empty_input_params(circuit: Circuit, options: Options):
Expand All @@ -76,12 +78,12 @@ class UnsupportedFormatException(Exception):
pass


def convert_to_backend_input(supported_input_formats: List[INPUT_FORMAT], circuit, options=None) \
def convert_to_backend_input(supported_input_formats: List[INPUT_FORMAT], circuit, backend=None, options=None) \
-> Tuple[INPUT_FORMAT, dict]:
for input_format in supported_input_formats:
convert_circuit = input_format_converter_factory.get(input_format)
if convert_circuit:
return input_format, convert_circuit(circuit=circuit, options=options)
return input_format, convert_circuit(circuit=circuit, backend=backend, options=options)
raise UnsupportedFormatException("Could not convert input to any of the supported inputs formats of the actual")


Expand Down
Loading

0 comments on commit 73bbcb1

Please sign in to comment.