Skip to content

Commit

Permalink
Include all gates in simulator targets (#147)
Browse files Browse the repository at this point in the history
* Simulator targets now include all gates, regardless of qubit count
* Major refactor to make code more readable, with functions being extracted as appropriate
* Adds the device qubit count to the target.
  • Loading branch information
speller26 committed Feb 9, 2024
1 parent c70791a commit bec3079
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 191 deletions.
322 changes: 132 additions & 190 deletions qiskit_braket_provider/providers/adapter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Util function for provider."""

from collections.abc import Callable, Iterable
from math import pi
from typing import Optional, Union
import warnings

Expand All @@ -12,30 +14,20 @@
)
import braket.circuits.gates as braket_gates

from braket.device_schema import (
DeviceActionType,
GateModelQpuParadigmProperties,
JaqcdDeviceActionProperties,
OpenQASMDeviceActionProperties,
)
from braket.device_schema import DeviceActionType, OpenQASMDeviceActionProperties
from braket.device_schema.ionq import IonqDeviceCapabilities
from braket.device_schema.oqc import OqcDeviceCapabilities
from braket.device_schema.rigetti import RigettiDeviceCapabilities
from braket.device_schema.simulators import (
GateModelSimulatorDeviceCapabilities,
GateModelSimulatorParadigmProperties,
)
from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities
from braket.devices import LocalSimulator
from braket.ir.openqasm.modifiers import Control

from numpy import pi

from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Instruction as QiskitInstruction
from qiskit.circuit import ControlledGate, Measure, Parameter
import qiskit.circuit.library as qiskit_gates

from qiskit.transpiler import InstructionProperties, Target
from qiskit.transpiler import Target
from qiskit_ionq import ionq_gates
from qiskit_braket_provider.exception import QiskitBraketException

Expand Down Expand Up @@ -232,210 +224,160 @@ def _get_controlled_gateset(max_qubits: Optional[int] = None) -> set[str]:


def local_simulator_to_target(simulator: LocalSimulator) -> Target:
"""Converts properties of LocalSimulator into Qiskit Target object.
"""Converts properties of a Braket LocalSimulator into a Qiskit Target object.
Args:
simulator: AWS LocalSimulator
simulator (LocalSimulator): Amazon Braket LocalSimulator
Returns:
target for Qiskit backend
Target: Target for Qiskit backend
"""
target = Target()

instructions = [
inst for inst in _GATE_NAME_TO_QISKIT_GATE.values() if inst is not None
]
properties = simulator.properties
paradigm: GateModelSimulatorParadigmProperties = properties.paradigm

# add measurement instruction
target.add_instruction(Measure(), {(i,): None for i in range(paradigm.qubitCount)})

for instruction in instructions:
instruction_props: Optional[
dict[Union[tuple[int], tuple[int, int]], Optional[InstructionProperties]]
] = {}

if instruction.num_qubits == 1:
for i in range(paradigm.qubitCount):
instruction_props[(i,)] = None
target.add_instruction(instruction, instruction_props)
elif instruction.num_qubits == 2:
for src in range(paradigm.qubitCount):
for dst in range(paradigm.qubitCount):
if src != dst:
instruction_props[(src, dst)] = None
instruction_props[(dst, src)] = None
target.add_instruction(instruction, instruction_props)

return target
return _simulator_target(
Target(
description=f"Target for Amazon Braket local simulator: {simulator.name}"
),
simulator.properties,
)


def aws_device_to_target(device: AwsDevice) -> Target:
"""Converts properties of Braket device into Qiskit Target object.
"""Converts properties of Braket AwsDevice into a Qiskit Target object.
Args:
device: AWS Braket device
device (AwsDevice): Amazon Braket AwsDevice
Returns:
target for Qiskit backend
Target: Target for Qiskit backend
"""
# building target
target = Target(description=f"Target for AWS Device: {device.name}")

target = Target(description=f"Target for Amazon Braket device: {device.name}")
properties = device.properties
# gate model devices
if isinstance(

if isinstance(properties, GateModelSimulatorDeviceCapabilities):
return _simulator_target(target, properties)
elif isinstance(
properties,
(IonqDeviceCapabilities, RigettiDeviceCapabilities, OqcDeviceCapabilities),
):
action_properties: OpenQASMDeviceActionProperties = (
properties.action.get(DeviceActionType.OPENQASM)
if properties.action.get(DeviceActionType.OPENQASM)
else properties.action.get(DeviceActionType.JAQCD)
)
paradigm: GateModelQpuParadigmProperties = properties.paradigm
connectivity = paradigm.connectivity
instructions: list[QiskitInstruction] = []

for operation in action_properties.supportedOperations:
instruction = _GATE_NAME_TO_QISKIT_GATE.get(operation.lower(), None)
if instruction is not None:
# TODO: remove when target will be supporting > 2 qubit gates # pylint:disable=fixme
if instruction.num_qubits <= 2:
instructions.append(instruction)

# add measurement instructions
target.add_instruction(
Measure(), {(i,): None for i in range(paradigm.qubitCount)}
)
return _qpu_target(target, properties)

for instruction in instructions:
instruction_props: Optional[
dict[
Union[tuple[int], tuple[int, int]], Optional[InstructionProperties]
]
] = {}
# adding 1 qubit instructions
if instruction.num_qubits == 1:
for i in range(paradigm.qubitCount):
instruction_props[(i,)] = None
# adding 2 qubit instructions
elif instruction.num_qubits == 2:
# building coupling map for fully connected device
if connectivity.fullyConnected:
for src in range(paradigm.qubitCount):
for dst in range(paradigm.qubitCount):
if src != dst:
instruction_props[(src, dst)] = None
instruction_props[(dst, src)] = None
# building coupling map for device with connectivity graph
else:
if isinstance(properties, RigettiDeviceCapabilities):

def convert_continuous_qubit_indices(
connectivity_graph: dict,
) -> dict:
"""Aspen qubit indices are discontinuous (label between x0 and x7, x being
the number of the octagon) while the Qiskit transpiler creates and/or
handles coupling maps with continuous indices. This function converts the
discontinous connectivity graph from Aspen to a continuous one.
Args:
connectivity_graph (dict): connectivity graph from Aspen. For example
4 qubit system, the connectivity graph will be:
{"0": ["1", "2", "7"], "1": ["0","2","7"], "2": ["0","1","7"],
"7": ["0","1","2"]}
Returns:
dict: Connectivity graph with continuous indices. For example for an
input connectivity graph with discontinuous indices (qubit 0, 1, 2 and
then qubit 7) as shown here:
{"0": ["1", "2", "7"], "1": ["0","2","7"], "2": ["0","1","7"],
"7": ["0","1","2"]}
the qubit index 7 will be mapped to qubit index 3 for the qiskit
transpilation step. Thereby the resultant continous qubit indices
output will be:
{"0": ["1", "2", "3"], "1": ["0","2","3"], "2": ["0","1","3"],
"3": ["0","1","2"]}
"""
# Creates list of existing qubit indices which are discontinuous.
indices = [int(key) for key in connectivity_graph.keys()]
indices.sort()
# Creates a list of continuous indices for number of qubits.
map_list = list(range(len(indices)))
# Creates a dictionary to remap the discountinous indices to continuous.
mapper = dict(zip(indices, map_list))
# Performs the remapping from the discontinous to the continuous indices.
continous_connectivity_graph = {
mapper[int(k)]: [mapper[int(v)] for v in val]
for k, val in connectivity_graph.items()
}
return continous_connectivity_graph

connectivity.connectivityGraph = (
convert_continuous_qubit_indices(
connectivity.connectivityGraph
)
)

for src, connections in connectivity.connectivityGraph.items():
for dst in connections:
instruction_props[(int(src), int(dst))] = None
# for more than 2 qubits
else:
instruction_props = None
raise QiskitBraketException(
f"Cannot convert to target. "
f"{properties.__class__} device capabilities are not supported yet."
)

target.add_instruction(instruction, instruction_props)

# gate model simulators
elif isinstance(properties, GateModelSimulatorDeviceCapabilities):
simulator_action_properties: JaqcdDeviceActionProperties = (
properties.action.get(DeviceActionType.JAQCD)
)
simulator_paradigm: GateModelSimulatorParadigmProperties = properties.paradigm
instructions = []

for operation in simulator_action_properties.supportedOperations:
instruction = _GATE_NAME_TO_QISKIT_GATE.get(operation.lower(), None)
if instruction is not None:
# TODO: remove when target will be supporting > 2 qubit gates # pylint:disable=fixme
if instruction.num_qubits <= 2:
instructions.append(instruction)

# add measurement instructions
target.add_instruction(
Measure(), {(i,): None for i in range(simulator_paradigm.qubitCount)}
)
def _simulator_target(target: Target, properties: GateModelSimulatorDeviceCapabilities):
target.num_qubits = properties.paradigm.qubitCount
action = (
properties.action.get(DeviceActionType.OPENQASM)
if properties.action.get(DeviceActionType.OPENQASM)
else properties.action.get(DeviceActionType.JAQCD)
)
for operation in action.supportedOperations:
instruction = _GATE_NAME_TO_QISKIT_GATE.get(operation.lower(), None)
if instruction:
target.add_instruction(instruction)
target.add_instruction(Measure())
return target


def _qpu_target(
target: Target,
properties: Union[
IonqDeviceCapabilities, RigettiDeviceCapabilities, OqcDeviceCapabilities
],
):
action_properties = (
properties.action.get(DeviceActionType.OPENQASM)
if properties.action.get(DeviceActionType.OPENQASM)
else properties.action.get(DeviceActionType.JAQCD)
)
qubit_count = properties.paradigm.qubitCount
target.num_qubits = qubit_count
connectivity = properties.paradigm.connectivity

for operation in action_properties.supportedOperations:
instruction = _GATE_NAME_TO_QISKIT_GATE.get(operation.lower(), None)

for instruction in instructions:
simulator_instruction_props: Optional[
dict[
Union[tuple[int], tuple[int, int]],
Optional[InstructionProperties],
]
] = {}
# adding 1 qubit instructions
# TODO: Add 3+ qubit gates once Target supports them # pylint:disable=fixme
if instruction and instruction.num_qubits <= 2:
if instruction.num_qubits == 1:
for i in range(simulator_paradigm.qubitCount):
simulator_instruction_props[(i,)] = None
# adding 2 qubit instructions
target.add_instruction(
instruction, {(i,): None for i in range(qubit_count)}
)
elif instruction.num_qubits == 2:
# building coupling map for fully connected device
for src in range(simulator_paradigm.qubitCount):
for dst in range(simulator_paradigm.qubitCount):
if src != dst:
simulator_instruction_props[(src, dst)] = None
simulator_instruction_props[(dst, src)] = None
target.add_instruction(instruction, simulator_instruction_props)
target.add_instruction(
instruction,
_2q_instruction_properties(qubit_count, connectivity, properties),
)

target.add_instruction(Measure(), {(i,): None for i in range(qubit_count)})
return target


def _2q_instruction_properties(qubit_count, connectivity, properties):
instruction_props = {}

# building coupling map for fully connected device
if connectivity.fullyConnected:
for src in range(qubit_count):
for dst in range(qubit_count):
if src != dst:
instruction_props[(src, dst)] = None
instruction_props[(dst, src)] = None

# building coupling map for device with connectivity graph
else:
raise QiskitBraketException(
f"Cannot convert to target. "
f"{properties.__class__} device capabilities are not supported yet."
)
if isinstance(properties, RigettiDeviceCapabilities):
connectivity.connectivityGraph = _convert_aspen_qubit_indices(
connectivity.connectivityGraph
)

return target
for src, connections in connectivity.connectivityGraph.items():
for dst in connections:
instruction_props[(int(src), int(dst))] = None

return instruction_props


def _convert_aspen_qubit_indices(connectivity_graph: dict) -> dict:
"""Aspen qubit indices are discontinuous (label between x0 and x7, x being
the number of the octagon) while the Qiskit transpiler creates and/or
handles coupling maps with continuous indices. This function converts the
discontinuous connectivity graph from Aspen to a continuous one.
Args:
connectivity_graph (dict): connectivity graph from Aspen. For example
4 qubit system, the connectivity graph will be:
{"0": ["1", "2", "7"], "1": ["0","2","7"], "2": ["0","1","7"],
"7": ["0","1","2"]}
Returns:
dict: Connectivity graph with continuous indices. For example for an
input connectivity graph with discontinuous indices (qubit 0, 1, 2 and
then qubit 7) as shown here:
{"0": ["1", "2", "7"], "1": ["0","2","7"], "2": ["0","1","7"],
"7": ["0","1","2"]}
the qubit index 7 will be mapped to qubit index 3 for the qiskit
transpilation step. Thereby the resultant continous qubit indices
output will be:
{"0": ["1", "2", "3"], "1": ["0","2","3"], "2": ["0","1","3"],
"3": ["0","1","2"]}
"""
# Creates list of existing qubit indices which are discontinuous.
indices = [int(key) for key in connectivity_graph.keys()]
indices.sort()
# Creates a list of continuous indices for number of qubits.
map_list = list(range(len(indices)))
# Creates a dictionary to remap the discontinuous indices to continuous.
mapper = dict(zip(indices, map_list))
# Performs the remapping from the discontinuous to the continuous indices.
continous_connectivity_graph = {
mapper[int(k)]: [mapper[int(v)] for v in val]
for k, val in connectivity_graph.items()
}
return continous_connectivity_graph


def to_braket(
Expand Down
1 change: 1 addition & 0 deletions qiskit_braket_provider/providers/braket_job.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""AWS Braket job."""

from datetime import datetime
from typing import List, Optional, Union
from warnings import warn
Expand Down
1 change: 1 addition & 0 deletions qiskit_braket_provider/version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
"""Qiskit-Braket provider version."""

__version__ = "0.0.5"
1 change: 1 addition & 0 deletions tests/providers/test_adapter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Tests for Qiskit to Braket adapter."""

from unittest import TestCase
from unittest.mock import Mock, patch

Expand Down
Loading

0 comments on commit bec3079

Please sign in to comment.