Skip to content

Commit

Permalink
change: Update documentation, add check to GateConnectivityCriterion …
Browse files Browse the repository at this point in the history
…to ensure user-provided undirected graphs have symmetric forwards/backwards edges
  • Loading branch information
ltnln committed Jul 18, 2024
1 parent 3c9fd67 commit 0601c63
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 57 deletions.
15 changes: 8 additions & 7 deletions src/braket/aws/aws_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -870,9 +870,9 @@ def _parse_calibration_json(
@property
def emulator(self) -> Emulator:
"""
A device emulator mimics the restrictions and noise of an AWS QPU by validating and
A device emulator mimics the restrictions and noise of the AWS QPU by validating and
compiling programs before running them on a simulated backend. An emulator can be used
as a soft check that a program can run on an AwsDevice.
as a soft check that a program can run the target AwsDevice.
Examples:
>>> device = AwsDevice(Devices.IQM.Garnet)
Expand All @@ -883,7 +883,8 @@ def emulator(self) -> Emulator:
>>> print(result.result().measurement_counts)
Returns:
Emulator: An emulator for this device, if this is not a simulator device.
Emulator: An emulator for this device, if this is not a simulator device. Raises an
exception if an emulator is requested for al simulator device.
"""
if self._arn in [simulator_enum.value for simulator_enum in Devices.Amazon]:
raise ValueError(
Expand All @@ -896,7 +897,7 @@ def emulator(self) -> Emulator:
def _setup_emulator(self) -> Emulator:
"""
Sets up an Emulator object whose properties mimic that of this AwsDevice, if the device is a
real QPU (not simulated).
real QPU (not a simulator).
Returns:
Emulator: An emulator with a noise model, compilation passes, and validation passes
Expand All @@ -921,8 +922,8 @@ def validate(
) -> None:
"""
Runs all non-modifying emulator passes on the input program and raises an
error if any device-specific criterion are not met by the program. If the
program meets all criterion, returns.
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
Expand Down Expand Up @@ -960,7 +961,7 @@ def emulate(
inputs: Optional[dict[str, float]] = None,
) -> QuantumTask:
"""Emulate a quantum task specification on this quantum device emulator.
A quantum task can be a circuit or an annealing problem. Emulation
A quantum task can be a circuit. Emulation
involves running all emulator passes on the input program before running
the program on the emulator's backend.
Expand Down
23 changes: 11 additions & 12 deletions src/braket/aws/aws_emulator_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@ def create_qubit_count_criterion(properties: DeviceCapabilities) -> QubitCountCr
QHP-specific schema.
Returns:
QubitCountCriterion: An eulator pass that checks that the number of qubits used in a program
does not exceed that of the max qubit count on the device.
QubitCountCriterion: 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)


def create_gate_criterion(properties: DeviceCapabilities) -> GateCriterion:
supported_gates = properties.action[DeviceActionType.OPENQASM].supportedOperations
"""
Create a GateCriterion pass which defines what supported and native gates are allowed in a
program based on the provided device properties.
Expand All @@ -48,13 +47,7 @@ def create_gate_criterion(properties: DeviceCapabilities) -> GateCriterion:
verbatim circuits only use native gates.
"""

if isinstance(properties, IqmDeviceCapabilities):
try:
supported_gates.remove("start_verbatim_box")
supported_gates.remove("end_verbatim_box")
except ValueError:
pass

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

return GateCriterion(supported_gates=supported_gates, native_gates=native_gates)
Expand All @@ -65,7 +58,7 @@ def create_connectivity_criterion(
properties: DeviceCapabilities, connectivity_graph: DiGraph
) -> ConnectivityCriterion:
"""
Creates a ConnectivityCriterion pass which validates that multi-qubit gates are applied to
Creates a ConnectivityCriterion pass which validates that two-qubit gates are applied to
connected qubits based on this device's connectivity graph.
Args:
Expand Down Expand Up @@ -118,6 +111,8 @@ def _(
for u, v in gate_connectivity_graph.edges:
edge_key = "-".join([str(qubit) for qubit in (u, v)])
edge_property = edge_properties.get(edge_key)

# Check that the QHP provided calibration data for this edge.
if not edge_property:
gate_connectivity_graph[u][v]["supported_gates"] = set()
continue
Expand All @@ -126,6 +121,8 @@ def _(
)
gate_connectivity_graph[u][v]["supported_gates"] = set(edge_supported_gates)

# Add the reversed edge to ensure gates can be applied
# in both directions for a given qubit pair.
for u, v in gate_connectivity_graph.edges:
if (v, u) not in gate_connectivity_graph.edges or gate_connectivity_graph[v][u].get(
"supported_gates"
Expand Down Expand Up @@ -163,7 +160,9 @@ def get_qpu_gate_translation(
Args:
properties (DeviceCapabilities): Device capabilities object based on a
device-specific schema.
gate_name (Union[str, Iterable[str]]): The name(s) of the gate(s)
gate_name (Union[str, Iterable[str]]): The name(s) of the gate(s). If gate_name is a list
of string gate names, this function attempts to retrieve translations of all the gate
names.
Returns:
Union[str, list[str]]: The translated gate name(s)
Expand Down
2 changes: 1 addition & 1 deletion src/braket/emulators/emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def run_program_passes(
"""
Passes the input program through all EmulatorPass objects contained in this
emulator and applies the emulator's noise model, if it exists, before
retruning the compiled program.
returning the compiled program.
Args:
task_specification (ProgramType): The input program to validate and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,6 @@


class ConnectivityCriterion(EmulatorCriterion):
"""
args:
connectivity_graph (Union[Dict[int, List[int]], DiGraph]): Either a sparse matrix or DiGraph
representation of the device connectivity. Can be None if fully_connected is true.
fully_connected (bool): If true, the all qubits in the device are connected.
num_qubits (int): The number of qubits in the device; if fully_connected is True,
this is used to create a complete graph with num_qubits nodes; ignored if
connectivity_graph is provided and fully_connected if False.
qubit_labels (Iterable[int]): A set of qubit labels; if fully_connected is True,
the qubits_labels are used as nodes of a fully connected topology; ignored if
connectivity_graph is provided and fully_connected if False.
"""

def __init__(
self,
connectivity_graph: Union[Dict[int, Iterable[int]], DiGraph] = None,
Expand All @@ -37,6 +20,27 @@ def __init__(
qubit_labels: Union[Iterable[int], QubitSet] = None,
directed: bool = True,
):
"""
args:
connectivity_graph (Union[Dict[int, List[int]], DiGraph]): Either a sparse matrix or
DiGraph representation of the device connectivity. Can be None if fully_connected is
true.
fully_connected (bool): If true, the all qubits in the device are connected.
num_qubits (int): The number of qubits in the device; if fully_connected is True,
this is used to create a complete graph with num_qubits nodes; ignored if
connectivity_graph is provided and fully_connected if False.
qubit_labels (Iterable[int]): A set of qubit labels; if fully_connected is True,
the qubits_labels are used as nodes of a fully connected topology; ignored if
connectivity_graph is provided and fully_connected if False.
directed (bool): Denotes if the connectivity graph is directed or undirected. If
the connectivity graph is undirected, this constructor attempts to fill in any
missing back edges.
"""

if not (connectivity_graph or fully_connected):
raise ValueError(
"Either the connectivity_graph must be provided or fully_connected must be True."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,32 @@

from abc import abstractmethod

from braket.circuits import Circuit
from braket.emulators.emulator_passes.emulator_pass import EmulatorPass, ProgramType


class EmulatorCriterion(EmulatorPass):
@abstractmethod
def validate(self, circuit: Circuit) -> None:
def validate(self, program: ProgramType) -> None:
"""
An emulator criterion is used to perform some non-modifying validation
pass on an input program. Implementations of validate should return
nothing if the input program passes validation and raise an error otherwise.
Args:
circuit (Circuit): circuit to be evaluated against this criteria.
program (ProgramType): The program to be evaluated against this criteria.
"""
raise NotImplementedError

def run(self, program: ProgramType) -> ProgramType:
"""
Validate the input program and return the program, unmodified.
Args:
program (ProgramType): The program to validate.
Returns:
ProgramType: The unmodified progam passed in as input.
"""
self.validate(program)
return program

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ def __init__(
self._gate_connectivity_graph.add_edge(
*back_edge, supported_gates=supported_gates
)
else:
# check that the supported gate sets are identical
if (
self._gate_connectivity_graph[u][v]["supported_gates"]
!= self._gate_connectivity_graph[v][u]["supported_gates"]
):
raise ValueError(

Check warning on line 36 in src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py

View check run for this annotation

Codecov / codecov/patch

src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py#L36

Added line #L36 was not covered by tests
f"Connectivity Graph marked as undirected\
but edges ({u}, {v}) and ({v}, {u}) have different supported\
gate sets."
)

elif isinstance(gate_connectivity_graph, dict):
self._gate_connectivity_graph = DiGraph()
Expand Down Expand Up @@ -79,11 +90,6 @@ def validate(self, circuit: Circuit) -> None:
f"Qubit {target_qubit} does not exist in the device topology."
)
idx += 1

if idx == len(circuit.instructions) or not isinstance(
circuit.instructions[idx].operator, EndVerbatimBox
):
raise ValueError(f"No end verbatim box found at index {idx} in the circuit.")
idx += 1

def validate_instruction_connectivity(
Expand Down
12 changes: 8 additions & 4 deletions src/braket/emulators/emulator_passes/criteria/gate_criterion.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,23 @@ class GateCriterion(EmulatorCriterion):
def __init__(self, supported_gates: Iterator[str] = [], native_gates: Iterator[str] = []):
"""
args:
native_gates (Iterator[str]): A list of gates supported inside of verbatim mode by
the emulator.
supported_gates (Iterator[str]): A list of gates supported outside of verbatim mode
by the emulator. A gate is a Braket gate name.
by the emulator. A gate is a Braket gate name.
native_gates (Iterator[str]): A list of gates supported inside of verbatim mode by
the emulator.
"""
if len(supported_gates) == 0 and len(native_gates) == 0:
raise ValueError("Supported gate set or native gate set must be provided.")

try:
self._supported_gates = set(BRAKET_GATES[gate.lower()] for gate in supported_gates)
except KeyError as e:
raise ValueError(f"Input {str(e)} in supported_gates is not a valid Braket gate name.")

try:
self._native_gates = set(BRAKET_GATES[gate.lower()] for gate in native_gates)
except KeyError as e:
raise ValueError(f"Input {str(e)} is not a valid Braket gate name.")
raise ValueError(f"Input {str(e)} in native_gates is not a valid Braket gate name.")

def validate(self, circuit: Circuit) -> None:
"""
Expand Down
2 changes: 1 addition & 1 deletion test/unit_tests/braket/aws/test_aws_emulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ def basic_device_capabilities():
"braket.ir.openqasm.program": {
"actionType": "braket.ir.openqasm.program",
"version": ["1"],
"supportedOperations": ["H", "CNot", "Ry", "XX", "YY", "start_verbatim_box"],
"supportedOperations": ["H", "CNot", "Ry", "XX", "YY"],
}
},
"paradigm": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
import pytest
from networkx.utils import graphs_equal

from braket.circuits import Circuit, Gate, Instruction
from braket.circuits.compiler_directives import StartVerbatimBox
from braket.circuits import Circuit, Gate
from braket.circuits.noises import BitFlip
from braket.emulators.emulator_passes.criteria import GateConnectivityCriterion

Expand Down Expand Up @@ -185,7 +184,7 @@ def test_undirected_graph_construction_from_dict():
[(0, 1, {"supported_gates": ["CNot", "CZ"]}), (1, 0, {"supported_gates": ["CNot", "CZ"]})],
[
(0, 1, {"supported_gates": ["CNot", "CZ"]}),
(1, 2, {"supported_gates": ["CNot"]}),
(1, 2, {"supported_gates": ["CNot", "CZ", "XX"]}),
(2, 3, {"supported_gates": ["CZ"]}),
(2, 1, {"supported_gates": ["CNot", "CZ", "XX"]}),
],
Expand Down Expand Up @@ -258,7 +257,6 @@ def create_undirected_graph_with_exisiting_back_edges(representation):
Circuit().add_verbatim_box(Circuit().h(4)),
Circuit().add_verbatim_box(Circuit().swap(1, 2).xx(0, 3, np.pi / 2).iswap(0, 1)),
Circuit().add_verbatim_box(Circuit().cnot(0, 3)),
Circuit().add_instruction(Instruction(StartVerbatimBox())),
],
)
def test_invalid_circuits(basic_4_node_graph, circuit):
Expand Down
18 changes: 15 additions & 3 deletions test/unit_tests/braket/emulation/test_gate_criterion.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,21 @@ def test_non_verbatim_circuit_only_native_gates():
criterion.validate(circuit)


@pytest.mark.parametrize("supported_gates,native_gates", [([], []), (["CX"], [])])
def test_invalid_instantiation(supported_gates, native_gates):
with pytest.raises(ValueError):
@pytest.mark.parametrize(
"supported_gates,native_gates,error_message",
[
([], [], "Supported gate set or native gate set must be provided."),
(["CX"], [], "Input 'cx' in supported_gates is not a valid Braket gate name."),
([], ["CX"], "Input 'cx' in native_gates is not a valid Braket gate name."),
(
["Toffoli"],
["CX"],
"Input 'toffoli' in supported_gates is not a valid Braket gate name.",
),
],
)
def test_invalid_instantiation(supported_gates, native_gates, error_message):
with pytest.raises(ValueError, match=error_message):
GateCriterion(supported_gates, native_gates)


Expand Down

0 comments on commit 0601c63

Please sign in to comment.