Skip to content

Commit

Permalink
change: Use 'validator' in place of 'criterion', fix usage of generic…
Browse files Browse the repository at this point in the history
… program type in EmulatorPasses
  • Loading branch information
ltnln committed Jul 29, 2024
1 parent 70881c6 commit b698648
Show file tree
Hide file tree
Showing 22 changed files with 295 additions and 305 deletions.
30 changes: 15 additions & 15 deletions src/braket/aws/aws_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@
from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation
from braket.annealing.problem import Problem
from braket.aws.aws_emulator_helpers import (
connectivity_criterion,
gate_connectivity_criterion,
gate_criterion,
qubit_count_criterion,
connectivity_validator,
gate_connectivity_validator,
gate_validator,
qubit_count_validator,
)
from braket.aws.aws_noise_models import device_noise_model
from braket.aws.aws_quantum_task import AwsQuantumTask
Expand All @@ -48,8 +48,8 @@
from braket.device_schema.pulse.pulse_device_action_properties_v1 import PulseDeviceActionProperties
from braket.devices import Devices
from braket.devices.device import Device
from braket.emulators import Emulator
from braket.emulators.emulator_passes import ProgramType
from braket.emulation import Emulator
from braket.emulation.emulator_passes import ProgramType
from braket.ir.blackbird import Program as BlackbirdProgram
from braket.ir.openqasm import Program as OpenQasmProgram
from braket.parametric.free_parameter import FreeParameter
Expand Down Expand Up @@ -908,30 +908,30 @@ def _setup_emulator(self) -> Emulator:
noise_model=emulator_noise_model, backend="braket_dm", name=self._name
)

self._emulator.add_pass(qubit_count_criterion(self.properties))
self._emulator.add_pass(gate_criterion(self.properties))
self._emulator.add_pass(connectivity_criterion(self.properties, self.topology_graph))
self._emulator.add_pass(gate_connectivity_criterion(self.properties, self.topology_graph))
self._emulator.add_pass(qubit_count_validator(self.properties))
self._emulator.add_pass(gate_validator(self.properties))
self._emulator.add_pass(connectivity_validator(self.properties, self.topology_graph))
self._emulator.add_pass(gate_connectivity_validator(self.properties, self.topology_graph))
return self._emulator

def validate(
self,
task_specification: Circuit,
task_specification: ProgramType,
) -> None:
"""
Runs all non-modifying emulator passes on the input program and raises an
error if any device-specific criteria are not met by the program. If the
program meets all criteria, returns.
Args:
task_specification (Circuit): The quantum program to emulate against
task_specification (ProgramType): The quantum program to emulate against
this AwsDevice device properties.
"""
self.emulator.validate(task_specification)
return

def run_emulator_passes(
def run_passes(
self, task_specification: ProgramType, apply_noise_model: bool = True
) -> ProgramType:
"""
Expand All @@ -954,7 +954,7 @@ def run_emulator_passes(

def emulate(
self,
task_specification: Circuit,
task_specification: ProgramType,
shots: Optional[int] = None,
inputs: Optional[dict[str, float]] = None,
) -> QuantumTask:
Expand All @@ -964,7 +964,7 @@ def emulate(
the program on the emulator's backend.
Args:
task_specification (Circuit): Specification of a quantum task
task_specification (ProgramType): Specification of a quantum task
to run on device.
shots (Optional[int]): The number of times to run the quantum task on the device.
Expand Down
76 changes: 38 additions & 38 deletions src/braket/aws/aws_emulator_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,56 @@
from braket.device_schema.ionq import IonqDeviceCapabilities
from braket.device_schema.iqm import IqmDeviceCapabilities
from braket.device_schema.rigetti import RigettiDeviceCapabilities
from braket.emulators.emulator_passes import (
ConnectivityCriterion,
GateConnectivityCriterion,
GateCriterion,
QubitCountCriterion,
from braket.emulation.emulator_passes import (
ConnectivityValidator,
GateConnectivityValidator,
GateValidator,
QubitCountValidator,
)


def qubit_count_criterion(properties: DeviceCapabilities) -> QubitCountCriterion:
def qubit_count_validator(properties: DeviceCapabilities) -> QubitCountValidator:
"""
Create a QubitCountCriterion pass which checks that the number of qubits used in a program does
Create a QubitCountValidator pass which checks that the number of qubits used in a program does
not exceed the number of qubits allowed by a QPU, as defined in the device properties.
Args:
properties (DeviceCapabilities): QPU Device Capabilities object with a
QHP-specific schema.
Returns:
QubitCountCriterion: An emulator pass that checks that the number of qubits used in a
QubitCountValidator: An emulator pass that checks that the number of qubits used in a
program does not exceed that of the max qubit count on the device.
"""
qubit_count = properties.paradigm.qubitCount
return QubitCountCriterion(qubit_count)
return QubitCountValidator(qubit_count)


def gate_criterion(properties: DeviceCapabilities) -> GateCriterion:
def gate_validator(properties: DeviceCapabilities) -> GateValidator:
"""
Create a GateCriterion pass which defines what supported and native gates are allowed in a
Create a GateValidator pass which defines what supported and native gates are allowed in a
program based on the provided device properties.
Args:
properties (DeviceCapabilities): QPU Device Capabilities object with a
QHP-specific schema.
Returns:
GateCriterion: An emulator pass that checks that a circuit only uses supported gates and
GateValidator: An emulator pass that checks that a circuit only uses supported gates and
verbatim circuits only use native gates.
"""

supported_gates = properties.action[DeviceActionType.OPENQASM].supportedOperations
native_gates = properties.paradigm.nativeGateSet

return GateCriterion(supported_gates=supported_gates, native_gates=native_gates)
return GateValidator(supported_gates=supported_gates, native_gates=native_gates)


def connectivity_criterion(
def connectivity_validator(
properties: DeviceCapabilities, connectivity_graph: DiGraph
) -> ConnectivityCriterion:
) -> ConnectivityValidator:
"""
Creates a ConnectivityCriterion pass which validates that two-qubit gates are applied to
Creates a ConnectivityValidator pass which validates that two-qubit gates are applied to
connected qubits based on this device's connectivity graph.
Args:
Expand All @@ -67,53 +67,53 @@ def connectivity_criterion(
connectivity_graph (DiGraph): Connectivity graph for this device.
Returns:
ConnectivityCriterion: An emulator pass that checks that a circuit only applies two-qubit
ConnectivityValidator: An emulator pass that checks that a circuit only applies two-qubit
gates to connected qubits on the device.
"""

return _connectivity_criterion(properties, connectivity_graph)
return _connectivity_validator(properties, connectivity_graph)


@singledispatch
def _connectivity_criterion(
def _connectivity_validator(
properties: DeviceCapabilities, connectivity_graph: DiGraph
) -> ConnectivityCriterion:
) -> ConnectivityValidator:

connectivity_criterion = ConnectivityCriterion(connectivity_graph)
return connectivity_criterion
connectivity_validator = ConnectivityValidator(connectivity_graph)
return connectivity_validator


@_connectivity_criterion.register(IqmDeviceCapabilities)
def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> ConnectivityCriterion:
@_connectivity_validator.register(IqmDeviceCapabilities)
def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> ConnectivityValidator:
"""
IQM qubit connectivity is undirected but the directed graph that represents qubit connectivity
does not include back-edges. Thus, we must explicitly introduce back edges before creating
the ConnectivityCriterion for an IQM device.
the ConnectivityValidator for an IQM device.
"""
connectivity_graph = connectivity_graph.copy()
for edge in connectivity_graph.edges:
connectivity_graph.add_edge(edge[1], edge[0])
return ConnectivityCriterion(connectivity_graph)
return ConnectivityValidator(connectivity_graph)


def gate_connectivity_criterion(
def gate_connectivity_validator(
properties: DeviceCapabilities, connectivity_graph: DiGraph
) -> GateConnectivityCriterion:
return _gate_connectivity_criterion(properties, connectivity_graph)
) -> GateConnectivityValidator:
return _gate_connectivity_validator(properties, connectivity_graph)


@singledispatch
def _gate_connectivity_criterion(
def _gate_connectivity_validator(
properties: DeviceCapabilities, connectivity_graph: DiGraph
) -> GateConnectivityCriterion:
) -> GateConnectivityValidator:
raise NotImplementedError


@_gate_connectivity_criterion.register(IqmDeviceCapabilities)
@_gate_connectivity_criterion.register(RigettiDeviceCapabilities)
@_gate_connectivity_validator.register(IqmDeviceCapabilities)
@_gate_connectivity_validator.register(RigettiDeviceCapabilities)
def _(
properties: RigettiDeviceCapabilities, connectivity_graph: DiGraph
) -> GateConnectivityCriterion:
) -> GateConnectivityValidator:
"""
Both IQM and Rigetti have undirected connectivity graphs; Rigetti device capabilities
provide back edges, but the calibration data only provides edges in one direction.
Expand Down Expand Up @@ -145,11 +145,11 @@ def _(
v, u, supported_gates=set(gate_connectivity_graph[u][v]["supported_gates"])
)

return GateConnectivityCriterion(gate_connectivity_graph)
return GateConnectivityValidator(gate_connectivity_graph)


@_gate_connectivity_criterion.register(IonqDeviceCapabilities)
def _(properties: IonqDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion:
@_gate_connectivity_validator.register(IonqDeviceCapabilities)
def _(properties: IonqDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityValidator:
"""
Qubits in IonQ's trapped ion devices are all fully connected with identical
gate-pair capabilities. IonQ does not expliclty provide a set of edges for
Expand All @@ -162,7 +162,7 @@ def _(properties: IonqDeviceCapabilities, connectivity_graph: DiGraph) -> GateCo
for edge in gate_connectivity_graph.edges:
gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] = set(native_gates)

return GateConnectivityCriterion(gate_connectivity_graph)
return GateConnectivityValidator(gate_connectivity_graph)


def _get_qpu_gate_translations(
Expand Down
1 change: 1 addition & 0 deletions src/braket/emulation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from braket.emulation.emulator import Emulator # noqa: F40
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
from __future__ import annotations

from abc import ABC
from typing import Iterable, Union

from braket.emulators.emulator_passes import EmulatorPass, ProgramType
from braket.emulators.emulator_passes.criteria import EmulatorCriterion
from braket.emulation.emulator_passes import EmulationPass, ProgramType
from braket.emulation.emulator_passes.criteria import ValidationPass


class EmulatorInterface(ABC):
def __init__(self, emulator_passes: Iterable[EmulatorPass] = None):
class BaseEmulator:
def __init__(self, emulator_passes: Iterable[EmulationPass] = None):
self._emulator_passes = emulator_passes if emulator_passes is not None else []

def run_passes(self, task_specification: ProgramType) -> ProgramType:
"""
This method passes the input program through the EmulatorPasses contained
This method passes the input program through the EmulationPasses contained
within this emulator. An emulator pass may simply validate a program or may
modify or entirely transform the program (to an equivalent quantum program).
Expand All @@ -30,40 +29,42 @@ def run_passes(self, task_specification: ProgramType) -> ProgramType:

def validate(self, task_specification: ProgramType) -> None:
"""
This method passes the input program through EmulatorPasses that perform
This method passes the input program through EmulationPasses that perform
only validation, without modifying the input program.
Args:
task_specification (ProgramType): The program to validate with this
emulator's validation passes.
"""
for emulator_pass in self._emulator_passes:
if isinstance(emulator_pass, EmulatorCriterion):
if isinstance(emulator_pass, ValidationPass):
emulator_pass(task_specification)

def add_pass(
self, emulator_pass: Union[Iterable[EmulatorPass], EmulatorPass]
) -> EmulatorInterface:
self, emulator_pass: Union[Iterable[EmulationPass], EmulationPass]
) -> BaseEmulator:
"""
Append a new EmulatorPass or a list of EmulatorPass objects.
Append a new EmulationPass or a list of EmulationPass objects.
Args:
emulator_pass (Union[Iterable[EmulatorPass], EmulatorPass]): Either a
single EmulatorPass object or a list of EmulatorPass objects that
emulator_pass (Union[Iterable[EmulationPass], EmulationPass]): Either a
single EmulationPass object or a list of EmulationPass objects that
will be used in validation and program compilation passes by this
emulator.
Returns:
EmulatorInterface: Returns an updated self.
BaseEmulator: Returns an updated self.
Raises:
TypeError: If the input is not an iterable or an EmulatorPass.
TypeError: If the input is not an iterable or an EmulationPass.
"""
if isinstance(emulator_pass, Iterable):
self._emulator_passes.extend(emulator_pass)
elif isinstance(emulator_pass, EmulatorPass):
elif isinstance(emulator_pass, EmulationPass):
self._emulator_passes.append(emulator_pass)
else:
raise TypeError("emulator_pass must be an EmulatorPass or an iterable of EmulatorPass")
raise TypeError(
"emulator_pass must be an EmulationPass or an iterable of EmulationPass"
)
return self
Loading

0 comments on commit b698648

Please sign in to comment.