From ea85675fcf3cdf0c516adef64f3f72307dae0ff5 Mon Sep 17 00:00:00 2001 From: Nagji Date: Sun, 2 Jun 2024 23:35:16 -0700 Subject: [PATCH 01/91] feat: Introduce emulator criterion --- .../emulators/criteria/emulator_criterion.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/braket/emulators/criteria/emulator_criterion.py diff --git a/src/braket/emulators/criteria/emulator_criterion.py b/src/braket/emulators/criteria/emulator_criterion.py new file mode 100644 index 000000000..5250d882d --- /dev/null +++ b/src/braket/emulators/criteria/emulator_criterion.py @@ -0,0 +1,23 @@ +from __future__ import annotations +from abc import ABC, abstractmethod +from typing import Dict +from braket.circuits import Circuit #TODO: abstract to general task_specification type? Possible w.r.t. noise models? + + +class EmulatorCriterion(ABC): + + @abstractmethod + def validate(self, circuit: Circuit) -> None: + """ + Args: + circuit (Circuit): circuit to be evaluated against this criteria. + + Returns: + returns nothing if the circuit is valid; otherwise, the appropriate error is raised. + """ + raise NotImplementedError + + + @abstractmethod + def __eq__(self, other: EmulatorCriterion) -> bool: + raise NotImplementedError \ No newline at end of file From 28ec84ef5c9efb44b7e716885d7b5634f4f09cfe Mon Sep 17 00:00:00 2001 From: Nagji Date: Sun, 2 Jun 2024 23:35:44 -0700 Subject: [PATCH 02/91] feat: Introduce NativeGateCriterion + Unit Tests --- .../criteria/native_gate_criterion.py | 48 ++++++++++ .../emulation/test_native_gate_criterion.py | 91 +++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 src/braket/emulators/criteria/native_gate_criterion.py create mode 100644 test/unit_tests/braket/emulation/test_native_gate_criterion.py diff --git a/src/braket/emulators/criteria/native_gate_criterion.py b/src/braket/emulators/criteria/native_gate_criterion.py new file mode 100644 index 000000000..fcc39967a --- /dev/null +++ b/src/braket/emulators/criteria/native_gate_criterion.py @@ -0,0 +1,48 @@ +from collections.abc import Iterator +from braket.circuits.gate import Gate +from braket.circuits.compiler_directives import StartVerbatimBox, EndVerbatimBox +from braket.emulators.criteria import EmulatorCriterion +from braket.circuits import Circuit +from braket.circuits.translations import BRAKET_GATES + +class NativeGateCriterion(EmulatorCriterion): + + def __init__(self, native_gates: Iterator[str]): + """ + args: + native_gates (Iterator[str]): A list of native gates supported by the emulator. + Validating native gates is relevant when considering whether or not a verbatim box in a program + is possible on a given device. + """ + if len(native_gates) == 0: + raise ValueError("At least one native gate must be provided.") + try: + self._native_gates = set(BRAKET_GATES[gate] for gate in native_gates) + except KeyError as e: + raise ValueError(f"Input {str(e)} is not a valid Braket gate name.") + + + def validate(self, circuit: Circuit) -> None: + for idx in range(len(circuit.instructions)): + instruction = circuit.instructions[idx] + if isinstance(instruction.operator, StartVerbatimBox): + idx += 1 + while idx < len(circuit.instructions) and not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): + instruction = circuit.instructions[idx] + if isinstance(instruction.operator, Gate): + gate = instruction.operator + if type(gate) in self._native_gates: + idx += 1 + continue + raise ValueError(f"Gate {gate.name} is not a native gate supported by this emulator.") + idx += 1 + + if not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): + raise ValueError(f"No end verbatim box found at index {idx} in the circuit.") + idx += 1 + + + def __eq__(self, other: EmulatorCriterion) -> bool: + return isinstance(other, NativeGateCriterion) and \ + self._native_gates == other._native_gates + diff --git a/test/unit_tests/braket/emulation/test_native_gate_criterion.py b/test/unit_tests/braket/emulation/test_native_gate_criterion.py new file mode 100644 index 000000000..564400814 --- /dev/null +++ b/test/unit_tests/braket/emulation/test_native_gate_criterion.py @@ -0,0 +1,91 @@ +import pytest + +from braket.emulators.criteria.native_gate_criterion import NativeGateCriterion +from braket.circuits import Circuit, Gate, gates + + +@pytest.fixture +def h_cnot_gates(): + return ["h", "cnot"] + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().x(0).h(1).swap(0, 1), + Circuit().add_verbatim_box( + Circuit().h(0).cnot(0, 1).h(2).cnot(0, 3).h(range(3)) + ), + Circuit().cswap(0, 1, 2).z(3).add_verbatim_box( + Circuit().h(0).cnot(0, 1).h(2).cnot(0, 3) + ).cnot(2, 4).rx(5, 0).add_verbatim_box( + Circuit().h(0).cnot(0, 1).h(5).cnot(4, 6) + ) + ] +) +def test_valid_circuits(h_cnot_gates, circuit): + """ + NativeGateCriterion should not raise any errors when validating these circuits given + a native gate set of [H, CNOT] + """ + NativeGateCriterion(h_cnot_gates).validate(circuit) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit().add_verbatim_box( + Circuit().z(0) + ), + Circuit().phaseshift(0, 0).h(0).add_verbatim_box( + Circuit().h(0).cnot(1, 2).rx(3, 0) + ).add_verbatim_box( + Circuit().h(0).cnot(0, 1).h(0).cnot(1, 3) + ), + Circuit().h(0).cnot(0, 1).h(2).add_verbatim_box( + Circuit().h(2).cnot(2, 3).h(4) + ).add_verbatim_box( + Circuit().cy(2, 3) + ) + ] +) +def test_invalid_circuits(h_cnot_gates, circuit): + """ + NativeGateCriterion should raise an error when validating these circuits given + a native gate set of [H, CNOT] + """ + with pytest.raises(ValueError): + NativeGateCriterion(h_cnot_gates).validate(circuit) + + +def test_empty_native_gates(): + with pytest.raises(ValueError): + NativeGateCriterion([]) + + +@pytest.mark.parametrize( + "gate_set_1, gate_set_2", + [ + (["h"], ["h"]), + (["cnot", "h"], ["h", "cnot"]), + (["phaseshift", "cnot", "rx", "ry"], ["ry", "rx", "cnot", "phaseshift"]) + ], +) +def test_equality(gate_set_1, gate_set_2): + assert NativeGateCriterion(gate_set_1) == NativeGateCriterion(gate_set_2) + + +@pytest.mark.parametrize( + "gate_set_1, gate_set_2", + [ + (["h"], ["x"]), + (["cnot"], ["h", "cnot"]), + (["cnot", "h"], ["h"]), + (["phaseshift", "cnot", "ms", "ry"], ["ry", "rx", "cnot", "ms"]) + ], +) +def test_inequality(gate_set_1, gate_set_2): + assert NativeGateCriterion(gate_set_1) != NativeGateCriterion(gate_set_2) + + From f41c52b9f4786aac374c1d85767ca8c4891fc58d Mon Sep 17 00:00:00 2001 From: Nagji Date: Sun, 2 Jun 2024 23:36:12 -0700 Subject: [PATCH 03/91] feat: Introduce SupportedGateCriterion + Unit Tests --- .../criteria/supported_gate_criterion.py | 35 ++++++++ .../test_supported_gate_criterion.py | 89 +++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 src/braket/emulators/criteria/supported_gate_criterion.py create mode 100644 test/unit_tests/braket/emulation/test_supported_gate_criterion.py diff --git a/src/braket/emulators/criteria/supported_gate_criterion.py b/src/braket/emulators/criteria/supported_gate_criterion.py new file mode 100644 index 000000000..0367da6a9 --- /dev/null +++ b/src/braket/emulators/criteria/supported_gate_criterion.py @@ -0,0 +1,35 @@ +from collections.abc import Iterator +from braket.circuits.gate import Gate +from braket.emulators.criteria import EmulatorCriterion +from braket.circuits import Circuit +from braket.circuits.translations import BRAKET_GATES + + +class SupportedGateCriterion(EmulatorCriterion): + def __init__(self, supported_gates: Iterator[str]): + """ + args: + supported_gates (Iterator[str]): A list of gates supported by the emulator. A gate can + be a QASM symbol, a Braket gate name, or a Braket gate instance. + """ + if len(supported_gates) == 0: + raise ValueError("At least one supported gate must be provided.") + + try: + self._supported_gates = set(BRAKET_GATES[gate] for gate in supported_gates) + except KeyError as e: + raise ValueError(f"Input {str(e)} is not a valid Braket gate name.") + + def validate(self, circuit: Circuit) -> None: + for instruction in circuit.instructions: + if isinstance(instruction.operator, Gate): + gate = instruction.operator + if type(gate) in self._supported_gates: + continue + else: + raise ValueError(f"Gate {gate.name} is not supported by this emulator.") + + def __eq__(self, other: EmulatorCriterion) -> bool: + return isinstance(other, SupportedGateCriterion) and \ + self._supported_gates == other._supported_gates + diff --git a/test/unit_tests/braket/emulation/test_supported_gate_criterion.py b/test/unit_tests/braket/emulation/test_supported_gate_criterion.py new file mode 100644 index 000000000..2519c8ccf --- /dev/null +++ b/test/unit_tests/braket/emulation/test_supported_gate_criterion.py @@ -0,0 +1,89 @@ +import pytest + +from braket.emulators.criteria import SupportedGateCriterion +from braket.circuits import Circuit +import numpy as np + +@pytest.fixture +def h_cnot_gates(): + return ["h", "cnot"] + + +@pytest.fixture +def aspen3_supported_gates(): + return ['cz', 'xy', 'ccnot', 'cnot', 'cphaseshift', 'cphaseshift00', 'cphaseshift01', \ + 'cphaseshift10', 'cswap', 'h', 'i', 'iswap', 'phaseshift', 'pswap', 'rx', 'ry', 'rz',\ + 's', 'si', 'swap', 't', 'ti', 'x', 'y', 'z'] + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().h(range(5)).cnot(0, 1), + Circuit().swap(0, 1).h(2).add_verbatim_box( + Circuit().h(0).cnot(0, 1) + ).z(3), + Circuit().phaseshift(0, np.pi/4).rx(1, np.pi/4).iswap(0, 1).si(2), + Circuit().add_verbatim_box( + Circuit() + ), + Circuit().cphaseshift01(0, 1, np.pi/4) + ] +) +def test_valid_circuits(aspen3_supported_gates, circuit): + """ + SupportedGateCriterion should not raise any errors when validating these circuits. + """ + SupportedGateCriterion(aspen3_supported_gates).validate(circuit) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit().z(0), + Circuit().h(0).cnot(0, 1).z(2), + Circuit().add_verbatim_box( + Circuit().z(2) + ), + Circuit().cphaseshift01(0, 1, np.pi/4).h(0).cnot(0, 1), + Circuit().h(0).add_verbatim_box( + Circuit().cnot(1, 2).h(range(5)).h(3) + ).rx(range(4), np.pi/4) + ] +) +def test_invalid_circuits(h_cnot_gates, circuit): + """ + SupportedGateCriterion should raise errors when validating these circuits. + """ + with pytest.raises(ValueError): + SupportedGateCriterion(h_cnot_gates).validate(circuit) + + +def test_empty_supported_gates(): + with pytest.raises(ValueError): + SupportedGateCriterion([]) + + +@pytest.mark.parametrize( + "gate_set_1, gate_set_2", + [ + (["h"], ["h"]), + (["cnot", "h"], ["h", "cnot"]), + (["phaseshift", "cnot", "rx", "ry"], ["ry", "rx", "cnot", "phaseshift"]) + ], +) +def test_equality(gate_set_1, gate_set_2): + assert SupportedGateCriterion(gate_set_1) == SupportedGateCriterion(gate_set_2) + + +@pytest.mark.parametrize( + "gate_set_1, gate_set_2", + [ + (["h"], ["x"]), + (["cnot"], ["h", "cnot"]), + (["cnot", "h"], ["h"]), + (["phaseshift", "cnot", "ms", "ry"], ["ry", "rx", "cnot", "ms"]) + ], +) +def test_inequality(gate_set_1, gate_set_2): + assert SupportedGateCriterion(gate_set_1) != SupportedGateCriterion(gate_set_2) \ No newline at end of file From fee7888a2a22e0bf2b93b425d778912e05266af7 Mon Sep 17 00:00:00 2001 From: Nagji Date: Sun, 2 Jun 2024 23:36:31 -0700 Subject: [PATCH 04/91] feat: Introduce Connectivity Criterion + Unit Tests --- .../criteria/connectivity_criterion.py | 101 ++++++++++ .../emulation/test_connectivity_criterion.py | 173 ++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 src/braket/emulators/criteria/connectivity_criterion.py create mode 100644 test/unit_tests/braket/emulation/test_connectivity_criterion.py diff --git a/src/braket/emulators/criteria/connectivity_criterion.py b/src/braket/emulators/criteria/connectivity_criterion.py new file mode 100644 index 000000000..f2ece2ba2 --- /dev/null +++ b/src/braket/emulators/criteria/connectivity_criterion.py @@ -0,0 +1,101 @@ +from braket.emulators.criteria import EmulatorCriterion +from networkx import DiGraph, complete_graph, from_dict_of_lists +from networkx.utils import graphs_equal +from typing import Union, Dict +from collections.abc import Iterable +from braket.circuits import Circuit +from braket.circuits.instruction import Instruction +from braket.registers.qubit_set import QubitSet +from braket.circuits.compiler_directives import StartVerbatimBox, EndVerbatimBox +from braket.circuits.gate import Gate + + + + +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, + fully_connected = False, + num_qubits: int = None, + qubit_labels: Union[Iterable[int], QubitSet] = None): + if not (connectivity_graph or fully_connected): + raise ValueError("Either the connectivity_graph must be provided or fully_connected must be True.") + + if fully_connected: + if not (num_qubits or qubit_labels) or (num_qubits and qubit_labels): + raise ValueError("Either num_qubits or qubit_labels (NOT both) must be provided if fully_connected is True.") + self._connectivity_graph = complete_graph(num_qubits if num_qubits else qubit_labels, create_using=DiGraph()) + elif not isinstance(connectivity_graph, DiGraph): + try: + self._connectivity_graph = from_dict_of_lists(connectivity_graph, create_using=DiGraph()) + except Exception as e: + raise ValueError(f"connectivity_graph must be a valid DiGraph or a dictionary mapping integers (nodes) to a list of integers (adjancency lists): {e}") + else: + self._connectivity_graph = connectivity_graph + + + def validate(self, circuit: Circuit) -> None: + """ + Verifies that any verbatim box in a circuit is runnable with respect to the + device connectivity definied by this criteria. + """ + for idx in range(len(circuit.instructions)): + instruction = circuit.instructions[idx] + if isinstance(instruction.operator, StartVerbatimBox): + idx += 1 + while idx < len(circuit.instructions) and not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): + instruction = circuit.instructions[idx] + if isinstance(instruction.operator, Gate): + if instruction.operator.qubit_count == 2: #Assuming only maximum 2-qubit native gates are supported + self.validate_instruction_connectivity(instruction.control, instruction.target) + else: + #just check that the target qubit exists in the connectivity graph + target_qubit = instruction.target[0] + if not target_qubit in self._connectivity_graph: + raise ValueError(f"Qubit {target_qubit} does not exist in the device topology.") + idx += 1 + + if 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(self, control_qubits: QubitSet, target_qubits: QubitSet): + #Create edges between each of the target qubits + gate_connectivity_graph = DiGraph() + + #Create an edge from each control bit to each target qubit + if len(control_qubits) == 1 and len(target_qubits) == 1: + add_edge(control_qubits[0], target_qubits[0]) + elif len(target_qubits) == 2: + gate_connectivity_graph.add_edges_from([ + (target_qubits[0], target_qubits[1]), + (target_qubits[1], target_qubits[0]) + ]) + else: + raise ValueError("Unrecognized qubit targetting setup for a 2 qubit gate.") + #Check that each edge exists in this criterion's connectivity graph + for e in gate_connectivity_graph.edges: + if not self._connectivity_graph.has_edge(*e): + raise ValueError(f"{e[1]} is not connected to qubit {e[0]} in this device.") + + + + + def __eq__(self, other: EmulatorCriterion) -> bool: + return isinstance(other, ConnectivityCriterion) \ + and graphs_equal(self._connectivity_graph, other._connectivity_graph) diff --git a/test/unit_tests/braket/emulation/test_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_connectivity_criterion.py new file mode 100644 index 000000000..2ecc2fdb4 --- /dev/null +++ b/test/unit_tests/braket/emulation/test_connectivity_criterion.py @@ -0,0 +1,173 @@ +import pytest + +from braket.emulators.criteria import ConnectivityCriterion +from braket.circuits import Circuit +import networkx as nx +import numpy as np + + +@pytest.fixture +def basic_2_node_complete_graph(): + return nx.complete_graph(2, create_using=nx.DiGraph()) + +@pytest.fixture +def basic_noncontig_qubits_2_node_complete_graph(): + return nx.complete_graph([1, 10], create_using=nx.DiGraph()) + +@pytest.fixture +def five_node_digraph(): + edge_set = { + 0: [1, 3], + 1: [0, 2, 10], + 2: [1, 3, 11], + 10: [1, 11], + 11: [2, 10] + } + return nx.from_dict_of_lists(edge_set, create_using=nx.DiGraph()) + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().add_verbatim_box( + Circuit() + ), + Circuit().i(range(3)).cnot(3, 4), + Circuit().add_verbatim_box( + Circuit().h(0).h(1).cnot(0, 1).cnot(1, 0) + ), + Circuit().i(range(3)).add_verbatim_box( + Circuit().swap(0, 1).phaseshift(1, np.pi/4).cphaseshift01(1, 0, np.pi/4) + ) + ] +) +def test_basic_contiguous_circuits(basic_2_node_complete_graph, circuit): + """ + ConnectivityGateCriterion should not raise any errors when validating these circuits. + """ + ConnectivityCriterion(basic_2_node_complete_graph).validate(circuit) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().add_verbatim_box( + Circuit() + ), + Circuit().i(range(3)).cnot(3, 4).x(111), + Circuit().add_verbatim_box( + Circuit().h(1).h(10).cnot(1, 10).cnot(10, 1) + ), + Circuit().i(range(3)).add_verbatim_box( + Circuit().swap(1, 10).phaseshift(10, np.pi/4).cphaseshift01(10, 1, np.pi/4) + ) + ] +) +def test_valid_discontiguous_circuits(basic_noncontig_qubits_2_node_complete_graph, circuit): + """ + ConnectivityGateCriterion should not raise any errors when validating these circuits. + """ + ConnectivityCriterion(basic_noncontig_qubits_2_node_complete_graph).validate(circuit) + + +def test_complete_graph_instantation_with_num_qubits(): + """ + Tests that, if fully_connected is True and num_qubits are passed into the + ConnectivityCriterion constructor, a fully connected graph is created. + """ + num_qubits = 5 + criterion = ConnectivityCriterion(num_qubits=num_qubits, fully_connected=True) + vb = Circuit() + for i in range(num_qubits): + for j in range(num_qubits): + if i != j: + vb.cnot(i, j) + else: + vb.i(i) + circuit = Circuit().add_verbatim_box(vb) + criterion.validate(circuit) + assert nx.utils.graphs_equal(criterion._connectivity_graph, nx.complete_graph(num_qubits, create_using=nx.DiGraph())) + +def test_complete_graph_instantation_with_qubit_labels(): + """ + Tests that, if fully_connected is True and num_qubits are passed into the + ConnectivityCriterion constructor, a fully connected graph is created. + """ + qubit_labels = [0, 1, 10, 11, 110, 111, 112, 113] + criterion = ConnectivityCriterion(qubit_labels=qubit_labels, fully_connected=True) + vb = Circuit() + for i in qubit_labels: + for j in qubit_labels: + if i != j: + vb.cnot(i, j) + else: + vb.i(i) + circuit = Circuit().add_verbatim_box(vb) + criterion.validate(circuit) + assert nx.utils.graphs_equal(criterion._connectivity_graph, nx.complete_graph(qubit_labels, create_using=nx.DiGraph())) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit().add_verbatim_box( + Circuit().cnot(0, 2) + ), + Circuit().add_verbatim_box( + Circuit().swap(1, 3) + ), + Circuit().add_verbatim_box( + Circuit().cnot(1, 10).cphaseshift01(2, 11, np.pi/4) + ).x(4).add_verbatim_box( + Circuit().swap(2, 10) + ) + ] +) +def test_invalid_2_qubit_gates(five_node_digraph, circuit): + with pytest.raises(ValueError): + ConnectivityCriterion(five_node_digraph).validate(circuit) + +@pytest.mark.parametrize( + "circuit", + [ + Circuit().x(4).add_verbatim_box( + Circuit().x(4) + ), + Circuit().x(110).add_verbatim_box( + Circuit().phaseshift(4, np.pi/4) + ), + Circuit().add_verbatim_box( + Circuit().cnot(1, 10).cphaseshift01(2, 11, np.pi/4) + ).x(4).add_verbatim_box( + Circuit().h(111) + ) + ] +) +def test_invalid_1_qubit_gates(five_node_digraph, circuit): + with pytest.raises(ValueError): + ConnectivityCriterion(five_node_digraph).validate(circuit) + +def test_equality_graph_created_with_dict(five_node_digraph): + graph = { + 0: [1, 3], + 1: [0, 2, 10], + 2: [1, 3, 11], + 10: [1, 11], + 11: [2, 10] + } + criteria_from_digraph = ConnectivityCriterion(five_node_digraph) + criteria_from_dict = ConnectivityCriterion(graph) + assert criteria_from_dict == criteria_from_digraph + +@pytest.mark.parametrize( + "connectivity_graph, fully_connected, num_qubits, qubit_labels", + [ + (None, True, None, None), + (nx.DiGraph(), True, None, None), + (None, True, 5, [0, 1]) + ] +) +def test_invalid_constructors(connectivity_graph, fully_connected, num_qubits, qubit_labels): + with pytest.raises(ValueError): + ConnectivityCriterion(connectivity_graph, fully_connected, num_qubits, qubit_labels) From 6f216ab4b220a412cde601c9566ac592b380e6fd Mon Sep 17 00:00:00 2001 From: Nagji Date: Sun, 2 Jun 2024 23:37:13 -0700 Subject: [PATCH 05/91] feat: Add __init__.py to emulators/criteria module --- src/braket/emulators/criteria/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/braket/emulators/criteria/__init__.py diff --git a/src/braket/emulators/criteria/__init__.py b/src/braket/emulators/criteria/__init__.py new file mode 100644 index 000000000..6e6dc5f22 --- /dev/null +++ b/src/braket/emulators/criteria/__init__.py @@ -0,0 +1,5 @@ +from braket.emulators.criteria.emulator_criterion import EmulatorCriterion +from braket.emulators.criteria.native_gate_criterion import NativeGateCriterion +from braket.emulators.criteria.supported_gate_criterion import SupportedGateCriterion +from braket.emulators.criteria.connectivity_criterion import ConnectivityCriterion + From 4e909e589058a7f4ae5dedae2f2f262c654afa81 Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 5 Jun 2024 12:04:41 -0700 Subject: [PATCH 06/91] feat: Add OpenQASM3.0-to-Pytket translations --- .../pytket_translator/composed_gates.py | 51 +++++++++++++++++++ .../pytket_translator/translations.py | 51 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 src/braket/emulators/pytket_translator/composed_gates.py create mode 100644 src/braket/emulators/pytket_translator/translations.py diff --git a/src/braket/emulators/pytket_translator/composed_gates.py b/src/braket/emulators/pytket_translator/composed_gates.py new file mode 100644 index 000000000..5a96239e1 --- /dev/null +++ b/src/braket/emulators/pytket_translator/composed_gates.py @@ -0,0 +1,51 @@ +from typing import List + +from pytket.circuit import Circuit, OpType + + +class ComposedGates: + @staticmethod + def add_pswap(circ: Circuit, arguments, qubits: List[int]): + assert len(arguments) == 1 + assert len(qubits) == 2 + circ.add_gate(OpType.ZZPhase, arguments, [qubits[0], qubits[1]]) + circ.add_gate(OpType.SWAP, [qubits[0], qubits[1]]) + circ.add_phase(arguments[0] / 2) + + @staticmethod + def add_cphaseshift00(circ: Circuit, arguments, qubits: List[int]): + assert len(arguments) == 1 + assert len(qubits) == 2 + circ.add_gate(OpType.X, [qubits[1]]) + circ.add_gate(OpType.CX, [qubits[1], qubits[0]]) + circ.add_gate(OpType.CU1, arguments, [qubits[1], qubits[0]]) + circ.add_gate(OpType.CX, [qubits[1], qubits[0]]) + circ.add_gate(OpType.X, [qubits[1]]) + + @staticmethod + def add_cphaseshift01(circ: Circuit, arguments, qubits: List[int]): + assert len(arguments) == 1 + assert len(qubits) == 2 + circ.add_gate(OpType.CX, [qubits[1], qubits[0]]) + circ.add_gate(OpType.CU1, arguments, [qubits[1], qubits[0]]) + circ.add_gate(OpType.CX, [qubits[1], qubits[0]]) + + @staticmethod + def add_cphaseshift10(circ: Circuit, arguments, qubits: List[int]): + assert len(arguments) == 1 + assert len(qubits) == 2 + circ.add_gate(OpType.CX, [qubits[0], qubits[1]]) + circ.add_gate(OpType.CU1, arguments, [qubits[0], qubits[1]]) + circ.add_gate(OpType.CX, [qubits[0], qubits[1]]) + + @staticmethod + def add_cphaseshift(circ: Circuit, arguments, qubits: List[int]): + assert len(arguments) == 1 + assert len(qubits) == 2 + circ.add_gate(OpType.CU1, arguments, [qubits[0], qubits[1]]) + + @staticmethod + def add_prx(circ: Circuit, arguments, qubits: List[int]): + assert len(arguments) == 2 + assert len(qubits) == 1 + circ.add_gate(OpType.PhasedX, arguments, qubits) \ No newline at end of file diff --git a/src/braket/emulators/pytket_translator/translations.py b/src/braket/emulators/pytket_translator/translations.py new file mode 100644 index 000000000..2d8621daf --- /dev/null +++ b/src/braket/emulators/pytket_translator/translations.py @@ -0,0 +1,51 @@ +from braket.emulators.pytket_translator.composed_gates import ComposedGates + +""" + OpenQASM-3.0 to Pytket Name Translations +""" +PYTKET_GATES = { + "gphase": "Phase", + "i": "noop", + "h": "H", + "x": "X", + "y": "Y", + "z": "Z", + "cv": "CV", + "cnot": "CX", + "cy": "CY", + "cz": "CZ", + "ecr": "ECR", + "s": "S", + "si": "Sdg", + "t": "T", + "ti": "Ti", + "v": "V", + "vi": "Vi", + "phaseshift": "U1", + "rx": "Rx", + "ry": "Ry", + "rz": "Rz", + "U": "U3", + "swap": "SWAP", + "iswap": "ISWAPMax", + "xy": "ISWAP", + "xx": "XXPhase", + "yy": "YYPhase", + "zz": "ZZPhase", + "ccnot": "CCX", + "cswap": "CSWAP", + "unitary": "U3", + "gpi": "GPI", + "gpi2": "GPI2", + "ms": "AAMS" +} + + +COMPOSED_GATES = { + "cphaseshift": ComposedGates.add_cphaseshift, + "cphaseshift00": ComposedGates.add_cphaseshift00, + "cphaseshift01": ComposedGates.add_cphaseshift01, + "cphaseshift10": ComposedGates.add_cphaseshift10, + "pswap": ComposedGates.add_pswap, + "prx": ComposedGates.add_prx, +} \ No newline at end of file From e3e459343f18b5b547ed2f3cf267412acc480593 Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 5 Jun 2024 12:05:28 -0700 Subject: [PATCH 07/91] feat: Introduce PytketProgramContext to generate PytketCircuits from OpenQASM3.0 sources --- .../emulators/pytket_translator/__init__.py | 2 + .../pytket_program_context.py | 109 ++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 src/braket/emulators/pytket_translator/__init__.py create mode 100644 src/braket/emulators/pytket_translator/pytket_program_context.py diff --git a/src/braket/emulators/pytket_translator/__init__.py b/src/braket/emulators/pytket_translator/__init__.py new file mode 100644 index 000000000..5da7c1eb8 --- /dev/null +++ b/src/braket/emulators/pytket_translator/__init__.py @@ -0,0 +1,2 @@ +from braket.emulators.pytket_translator.translations import PYTKET_GATES, COMPOSED_GATES +from braket.emulators.pytket_translator.pytket_program_context import PytketProgramContext \ No newline at end of file diff --git a/src/braket/emulators/pytket_translator/pytket_program_context.py b/src/braket/emulators/pytket_translator/pytket_program_context.py new file mode 100644 index 000000000..cdbcb0291 --- /dev/null +++ b/src/braket/emulators/pytket_translator/pytket_program_context.py @@ -0,0 +1,109 @@ +from braket.default_simulator.openqasm.program_context import AbstractProgramContext +from pytket.circuit import ( + Circuit, + OpType, + Unitary1qBox, + Unitary2qBox, + Unitary3qBox +) +from pytket.unit_id import Qubit, Bit +from typing import Optional, Union, Any +from sympy import Expr +from braket.emulators.pytket_translator.translations import ( + PYTKET_GATES, + COMPOSED_GATES +) +import numpy as np + + +class PytketProgramContext(AbstractProgramContext): + def __init__(self, circuit: Optional[Circuit] = None): + """Inits a `PytketProgramContext`. + + Args: + circuit (Optional[Circuit]): A partially-built Pytket circuit to continue building with this + context. Default: None. + """ + super().__init__() + self._circuit = circuit or Circuit() + self._qubits_set = set() + + @property + def circuit(self) -> Circuit: + """Returns the Pytket circuit being built in this context.""" + return self._circuit + + def is_builtin_gate(self, name: str) -> bool: + user_defined_gate = self.is_user_defined_gate(name) + result = (name in PYTKET_GATES or COMPOSED_GATES) and not user_defined_gate + return result + + def add_gate_instruction( + self, gate_name: str, target: tuple[int, ...], *params, ctrl_modifiers: list[int], power: int + ): + self._check_and_update_qubits(target) + gate_name = self._get_tket_gate_name(gate_name) + if hasattr(OpType, gate_name): + op = getattr(OpType, gate_name) + if len(*params) > 0: + self._circuit.add_gate(op, *params, target) + else: + self._circuit.add_gate(op, target) + + elif gate_name in COMPOSED_GATES: + COMPOSED_GATES[gate_name](self._circuit, *params, target) + + + def _get_tket_gate_name(self, gate_name: str) -> str: + gate_name = gate_name.lower() + return PYTKET_GATES.get(gate_name, gate_name) + + def _check_and_update_qubits(self, target: tuple[int, ...]): + for qubit in target: + if qubit not in self._qubits_set: + new_qubit = Qubit(index=qubit) + self._qubits_set.add(qubit) + self._circuit.add_qubit(new_qubit) + + def add_phase_instruction(self, target, phase_value): + self.add_gate_instruction("gphase", target, phase_value) + + def add_measure(self, target: tuple[int]): + if len(target) == 0: + return + self._check_and_update_qubits(target) + for index, qubit in enumerate(target): + self._circuit.add_bit(Bit(qubit)) + self._circuit.Measure(qubit, qubit) + + def add_custom_unitary( + self, + unitary: np.ndarray, + target: tuple[int, ...], + ): + num_targets = len(target) + if not (1 <= num_targets <= 3): + raise ValueError("At most 3 qubit gates are supported for custom unitary operations.") + + operator_qubit_count = int(np.log2(unitary.shape[0])) + if operator_qubit_count != num_targets: + raise ValueError( + f"Operator qubit count {operator_qubit_count} must be equal to size of target qubit set {target}" + ) + + self._check_and_update_qubits(target) + if num_targets == 1: + unitary_box = Unitary1qBox(unitary) + self._circuit.add_unitary1qbox(unitary_box, *target) + elif num_targets == 2: + unitary_box = Unitary2qBox(unitary) + self._circuit.add_unitary2qbox(unitary_box, *target) + elif num_targets == 3: + unitary_box = Unitary3qBox(unitary) + self._circuit.add_unitary3qbox(unitary_box, *target) + + + def handle_parameter_value(self, value: Union[float, Expr]) -> Any: + if isinstance(value, Expr): + return value.evalf() + return value \ No newline at end of file From eda10b7ae4c611396e0a78e3f631168fac9c9dfe Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 6 Jun 2024 11:17:01 -0700 Subject: [PATCH 08/91] feat: Begin emulator pass implementation --- .../emulators/emulator_passes/__init__.py | 2 + .../emulator_passes/emulator_pass.py | 28 +++++++++++ .../emulator_passes/lexi_routing_pass.py | 49 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 src/braket/emulators/emulator_passes/__init__.py create mode 100644 src/braket/emulators/emulator_passes/emulator_pass.py create mode 100644 src/braket/emulators/emulator_passes/lexi_routing_pass.py diff --git a/src/braket/emulators/emulator_passes/__init__.py b/src/braket/emulators/emulator_passes/__init__.py new file mode 100644 index 000000000..251afc877 --- /dev/null +++ b/src/braket/emulators/emulator_passes/__init__.py @@ -0,0 +1,2 @@ +from braket.emulators.emulator_passes.emulator_pass import EmulatorPass +from braket.emulators.emulator_passes.lexi_routing_pass import LexiRoutingPass \ No newline at end of file diff --git a/src/braket/emulators/emulator_passes/emulator_pass.py b/src/braket/emulators/emulator_passes/emulator_pass.py new file mode 100644 index 000000000..7e6a96ca5 --- /dev/null +++ b/src/braket/emulators/emulator_passes/emulator_pass.py @@ -0,0 +1,28 @@ +from __future__ import annotations +from abc import ABC, abstractmethod +from braket.circuits import Circuit +from braket.ir.openqasm import Program as OpenQasmProgram +from typing import Union, TypeVar + +ProgramType = TypeVar("ProgramType") + +class EmulatorPass(ABC): + """ + Base class for all emulator compiler passes. + """ + @abstractmethod + def run_pass[ProgramType](self, task_specification: ProgramType) -> ProgramType: + """ + + Args: + task_specification ProgramType: The program to run the pass on. + + Returns: + ProgramType: A partial compilation of the input program. + """ + raise NotImplementedError + + + def __call__(self, task_specification: ProgramType) -> ProgramType: + """Allow the pass to be called like a function.""" + return self.run_pass(task_specification) \ No newline at end of file diff --git a/src/braket/emulators/emulator_passes/lexi_routing_pass.py b/src/braket/emulators/emulator_passes/lexi_routing_pass.py new file mode 100644 index 000000000..c9a49fb58 --- /dev/null +++ b/src/braket/emulators/emulator_passes/lexi_routing_pass.py @@ -0,0 +1,49 @@ +from braket.emulators.emulator_passes import EmulatorPass +from braket.circuits import Circuit +from braket.ir.openqasm import Program as OpenQasmProgram +from braket.emulators.pytket_translator import PytketProgramContext +from braket.circuits.serialization import IRType +from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager +from pytket.architecture import Architecture +from typing import Union, Dict +from collections.abc import Iterable +from networkx import DiGraph +from braket.default_simulator.openqasm.interpreter import Interpreter +from functools import singledispatchmethod + +class LexiRoutingPass(EmulatorPass): + def __init__(self, hardware_topology:Union[Dict[int, Iterable[int]], DiGraph]): + super().__init__() + self._mapping_manager = MappingManager(self._get_architecture(hardware_topology)) + self._lexi_label = LexiLabellingMethod() + self._lexi_route = LexiRouteRoutingMethod() + + + def _get_architecture(self, hardware_topology: Union[Dict[int, Iterable[int]], DiGraph]): + if isinstance(hardware_topology, dict): + edge_list = [(q1, q2) for q1, edges in hardware_topology.items() for q2 in edges] + elif isinstance(hardware_topology, DiGraph): + edge_list = list(hardware_topology.edges) + + return Architecture(edge_list) + + + + @singledispatchmethod + def run_pass(self, task_specification): + raise NotImplementedError(f"LexiRoutingPass does not support task specification type: {type(task_specification)}") + + + @run_pass.register + def _(self, task_specification: Circuit) -> Circuit: + open_qasm_program = task_specification.to_ir(ir_type=IRType.OPENQASM) + mapped_open_qasm_program = self.run_pass(open_qasm_program) + resulting_circuit = Circuit.from_ir(mapped_open_qasm_program) + return resulting_circuit + + @run_pass.register + def _(self, task_specification: OpenQasmProgram) -> OpenQasmProgram: + pytket_circuit = Interpreter(PytketProgramContext()).build_circuit(task_specification.source) + self._mapping_manager.route_circuit(pytket_circuit, [self._lexi_label, self._lexi_route]) + #TODO convert back to openqasm + return pytket_circuit \ No newline at end of file From 096aae7a7622c3cd3ff818641195a54b709f7977 Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 6 Jun 2024 12:05:46 -0700 Subject: [PATCH 09/91] feat: Change gate translations to use pytket OpType objects instead of attribute strings; introduce PyTket-to-QASM translations --- .../emulators/pytket_translator/__init__.py | 6 +- .../pytket_program_context.py | 16 ++-- .../pytket_translator/translations.py | 85 +++++++++++-------- 3 files changed, 60 insertions(+), 47 deletions(-) diff --git a/src/braket/emulators/pytket_translator/__init__.py b/src/braket/emulators/pytket_translator/__init__.py index 5da7c1eb8..bcd35c2b3 100644 --- a/src/braket/emulators/pytket_translator/__init__.py +++ b/src/braket/emulators/pytket_translator/__init__.py @@ -1,2 +1,6 @@ -from braket.emulators.pytket_translator.translations import PYTKET_GATES, COMPOSED_GATES +from braket.emulators.pytket_translator.translations import ( + PYTKET_TO_QASM, + COMPOSED_GATES, + QASM_TO_PYTKET +) from braket.emulators.pytket_translator.pytket_program_context import PytketProgramContext \ No newline at end of file diff --git a/src/braket/emulators/pytket_translator/pytket_program_context.py b/src/braket/emulators/pytket_translator/pytket_program_context.py index cdbcb0291..974d21b4c 100644 --- a/src/braket/emulators/pytket_translator/pytket_program_context.py +++ b/src/braket/emulators/pytket_translator/pytket_program_context.py @@ -10,7 +10,7 @@ from typing import Optional, Union, Any from sympy import Expr from braket.emulators.pytket_translator.translations import ( - PYTKET_GATES, + QASM_TO_PYTKET, COMPOSED_GATES ) import numpy as np @@ -35,29 +35,25 @@ def circuit(self) -> Circuit: def is_builtin_gate(self, name: str) -> bool: user_defined_gate = self.is_user_defined_gate(name) - result = (name in PYTKET_GATES or COMPOSED_GATES) and not user_defined_gate + result = (name in QASM_TO_PYTKET or COMPOSED_GATES) and not user_defined_gate return result def add_gate_instruction( self, gate_name: str, target: tuple[int, ...], *params, ctrl_modifiers: list[int], power: int ): self._check_and_update_qubits(target) - gate_name = self._get_tket_gate_name(gate_name) - if hasattr(OpType, gate_name): - op = getattr(OpType, gate_name) + if gate_name in QASM_TO_PYTKET: + op = QASM_TO_PYTKET[gate_name] if len(*params) > 0: self._circuit.add_gate(op, *params, target) else: self._circuit.add_gate(op, target) - elif gate_name in COMPOSED_GATES: COMPOSED_GATES[gate_name](self._circuit, *params, target) + else: + raise ValueError(f"Gate {gate_name} is not supported in pytket translations.") - def _get_tket_gate_name(self, gate_name: str) -> str: - gate_name = gate_name.lower() - return PYTKET_GATES.get(gate_name, gate_name) - def _check_and_update_qubits(self, target: tuple[int, ...]): for qubit in target: if qubit not in self._qubits_set: diff --git a/src/braket/emulators/pytket_translator/translations.py b/src/braket/emulators/pytket_translator/translations.py index 2d8621daf..88847b438 100644 --- a/src/braket/emulators/pytket_translator/translations.py +++ b/src/braket/emulators/pytket_translator/translations.py @@ -1,43 +1,45 @@ from braket.emulators.pytket_translator.composed_gates import ComposedGates +from pytket.circuit import OpType + """ OpenQASM-3.0 to Pytket Name Translations """ -PYTKET_GATES = { - "gphase": "Phase", - "i": "noop", - "h": "H", - "x": "X", - "y": "Y", - "z": "Z", - "cv": "CV", - "cnot": "CX", - "cy": "CY", - "cz": "CZ", - "ecr": "ECR", - "s": "S", - "si": "Sdg", - "t": "T", - "ti": "Ti", - "v": "V", - "vi": "Vi", - "phaseshift": "U1", - "rx": "Rx", - "ry": "Ry", - "rz": "Rz", - "U": "U3", - "swap": "SWAP", - "iswap": "ISWAPMax", - "xy": "ISWAP", - "xx": "XXPhase", - "yy": "YYPhase", - "zz": "ZZPhase", - "ccnot": "CCX", - "cswap": "CSWAP", - "unitary": "U3", - "gpi": "GPI", - "gpi2": "GPI2", - "ms": "AAMS" +QASM_TO_PYTKET = { + "gphase": OpType.Phase, + "i": OpType.noop, + "h": OpType.H, + "x": OpType.X, + "y": OpType.Y, + "z": OpType.Z, + "cv": OpType.CV, + "cnot": OpType.CX, + "cy": OpType.CY, + "cz": OpType.CZ, + "ecr": OpType.ECR, + "s": OpType.S, + "si": OpType.Sdg, + "t": OpType.T, + "ti": OpType.Tdg, + "v": OpType.V, + "vi": OpType.Vdg, + "phaseshift": OpType.U1, + "rx": OpType.Rx, + "ry": OpType.Ry, + "rz": OpType.Rz, + "U": OpType.U3, + "swap": OpType.SWAP, + "iswap": OpType.ISWAPMax, + "xy": OpType.ISWAP, + "xx": OpType.XXPhase, + "yy": OpType.YYPhase, + "zz": OpType.ZZPhase, + "ccnot": OpType.CCX, + "cswap": OpType.CSWAP, + "unitary": OpType.U3, + "gpi": OpType.GPI, + "gpi2": OpType.GPI2, + "ms": OpType.AAMS } @@ -48,4 +50,15 @@ "cphaseshift10": ComposedGates.add_cphaseshift10, "pswap": ComposedGates.add_pswap, "prx": ComposedGates.add_prx, -} \ No newline at end of file +} + +""" + Pytket to OpenQASM-3.0 Name Translations +""" +PYTKET_TO_QASM = {optype: qasm_name for qasm_name, optype in QASM_TO_PYTKET.items()} + +# For gates which have multiple valid OpenQASM names, like "cx" and "CX", we overwrite +# the values to make sure we use the preferred name. +PYTKET_TO_QASM[OpType.CX] = "cx" # prefer over "CX" +PYTKET_TO_QASM[OpType.U3] = "u3" # prefer over "U" +PYTKET_TO_QASM[OpType.Rz] = "rz" # prefer over "Rz" \ No newline at end of file From a801ad416c2a79756a298f34370a3bda480eb055 Mon Sep 17 00:00:00 2001 From: Nagji Date: Fri, 7 Jun 2024 13:28:54 -0700 Subject: [PATCH 10/91] feat: Introduce Pytket-to-Openqasm Translators --- .../pytket_translator/qasm3_gen/__init__.py | 2 + .../qasm3_gen/qasm_context.py | 34 ++++++ .../qasm3_gen/qasm_writer.py | 114 ++++++++++++++++++ .../qasm3_gen/tket_to_qasm3.py | 107 ++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 src/braket/emulators/pytket_translator/qasm3_gen/__init__.py create mode 100644 src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py create mode 100644 src/braket/emulators/pytket_translator/qasm3_gen/qasm_writer.py create mode 100644 src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/__init__.py b/src/braket/emulators/pytket_translator/qasm3_gen/__init__.py new file mode 100644 index 000000000..9932c457e --- /dev/null +++ b/src/braket/emulators/pytket_translator/qasm3_gen/__init__.py @@ -0,0 +1,2 @@ +from braket.emulators.pytket_translator.qasm3_gen.tket_to_qasm3 import tket_to_qasm3 +from braket.emulators.pytket_translator.qasm3_gen.qasm_context import QasmContext \ No newline at end of file diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py b/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py new file mode 100644 index 000000000..4ced105ea --- /dev/null +++ b/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py @@ -0,0 +1,34 @@ +from collections import namedtuple +from typing import Dict, List, Union +from sympy import Expr, Symbol, pi +from braket.emulators.pytket_translator.translations import MEASUREMENT_REGISTER_NAME + +"""A single measurement: from qubit index to the bit expression.""" +Measurement = namedtuple("Measurement", ("qubit", "bit")) + +"""A gate operation, with a given name, classical args, and qubits""" +Gate = namedtuple("Gate", ("name", "args", "qubits")) + + +class QasmContext: + + def __init__(self, input_parameters: Dict[str, str]): + self.input_parameters = input_parameters + self.num_bits: int = 0 + self.gates: List[Gate] = [] + self.measurements: List[Measurement] = [] + + + def set_num_bits(self, num_bits: int) -> None: + self.num_bits = num_bits + + def add_gate(self, name: str, args: List[Union[Expr, Symbol]], qubits: List[int]): + print("Adding gate: ", name, args, qubits) + self.gates.append(Gate(name, args, qubits)) + + + def add_measurement(self, qubit: int, cbit: int): + print("Adding measurement: ", qubit, cbit) + self.measurements.append(Measurement(qubit, f"{MEASUREMENT_REGISTER_NAME}[{cbit}]")) + + \ No newline at end of file diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/qasm_writer.py b/src/braket/emulators/pytket_translator/qasm3_gen/qasm_writer.py new file mode 100644 index 000000000..68d73afd1 --- /dev/null +++ b/src/braket/emulators/pytket_translator/qasm3_gen/qasm_writer.py @@ -0,0 +1,114 @@ +from abc import ABC, abstractmethod +import io +from braket.emulators.pytket_translator.qasm3_gen.qasm_context import ( + QasmContext, + Gate, + Measurement +) +from braket.emulators.pytket_translator.translations import MEASUREMENT_REGISTER_NAME + +class QasmWriter(ABC): + """Abstract class for OpenQASM program writing. It handles basic program writing flow, + but methods can be overwritten by subclasses to modify output behavior. + """ + + def __init__(self, context: QasmContext): + """Initialize a new QasmWriter from a context containing program information. + + The writer does not modify the context. + """ + self.context = context + + def get_program(self) -> str: + """Return the OpenQASM3 program program string from the constructor argument + ProgramContext. + """ + stream = io.StringIO() + stream.write(self.get_program_header()) + stream.write(self.get_input_declarations()) + stream.write(self.get_classical_bit_declarations()) + # stream.write(self.get_verbatim_pragma()) + # stream.write(self.get_boxed_program_body()) + stream.write(self.get_body()) + stream.write(self.get_measurements()) + return stream.getvalue() + + def get_program_header(self) -> str: + return "OPENQASM 3.0;\n" + + @abstractmethod + def get_input_declarations(self) -> str: + """Return input declaration statements. + + For example: + "input float theta;\ninput float alpha;\n" + """ + + def get_classical_bit_declarations(self) -> str: + return ( + f"bit[{self.context.num_bits}] {MEASUREMENT_REGISTER_NAME};\n" + if self.context.num_bits + else "" + ) + + + def get_verbatim_pragma(self) -> str: + return f"#pragma braket verbatim\n" + + def get_boxed_program_body(self) -> str: + return f"box {{\n{self.get_body()}}}\n" + + def get_body(self) -> str: + stream = io.StringIO() + for gate in self.context.gates: + stream.write(self.get_gate(gate)) + return stream.getvalue() + + def get_gate(self, gate: Gate) -> str: + return f"{gate.name}{self.get_gate_args(gate)} {self.get_gate_qubits(gate)};\n" + + @abstractmethod + def get_gate_args(self, gate: Gate) -> str: + """Return gate arguments. + + For example: + "0.5,pi*theta" + """ + + def get_gate_qubits(self, gate: Gate) -> str: + return ",".join(self.format_qubit(q) for q in gate.qubits) + + def get_measurements(self) -> str: + return "\n".join( + f"{meas.bit} = measure {self.format_qubit(meas.qubit)};" + for meas in self.context.measurements + ) + + def format_qubit(self, qubit: int) -> str: + return f"${qubit}" + + +class BasicQasmWriter(QasmWriter): + """This writer returns human-readable, basic OpenQASM. + + For example: + + OPENQASM 3.0; + input angle theta; + bit[1] c; + #pragma braket verbatim + box { + rx(pi*theta) $0; + } + c[0] = measure $0; + + """ + + def get_input_declarations(self) -> str: + stream = io.StringIO() + for param_name, param_type in self.context.input_parameters.items(): + stream.write(f"input {param_type} {param_name};\n") + return stream.getvalue() + + def get_gate_args(self, gate: Gate) -> str: + return f"({','.join(str(arg) for arg in gate.args)})" if gate.args else "" \ No newline at end of file diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py b/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py new file mode 100644 index 000000000..bfe07f7a1 --- /dev/null +++ b/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py @@ -0,0 +1,107 @@ +from braket.emulators.pytket_translator import PYTKET_TO_QASM +from pytket.circuit import Circuit, OpType, Node +from sympy import Expr, pi, Symbol +from typing import Dict, Union, List, Set, Optional +from dataclasses import dataclass +from functools import singledispatchmethod +from braket.emulators.pytket_translator.qasm3_gen.qasm_context import QasmContext + + + +@dataclass +class Qasm3: + program: str + + +def tket_to_qasm3( + circuit: Circuit, + input_parameters: Dict[str, str]=None, + gate_overrides: Dict[OpType, str]=None +) -> Qasm3: + ticket_visitor = TketCircuitVisitor(QasmContext(input_parameters), gate_overrides) + ticket_visitor.walk_circuit(circuit) + return ticket_visitor.context + +class TketCircuitVisitor: + def __init__(self, context, gate_overrides): + self.context = context + self.gate_overrides = gate_overrides + self._measured_nodes: Set[Node] = set() + + + def walk_circuit(self, circuit: Circuit): + self.context.set_num_bits(len(circuit.bits)) + for command in circuit: + self._visit_command(command) + + def _visit_command(self, command: Node): + op = command.op + self._validate_args_not_measured(command.args) + optype = op.type + if optype == OpType.CircBox: + self._visit_box(command, optype) + elif optype == OpType.Measure: + self._visit_measure(command, optype) + else: + self._visit_gate(command, optype) + + def _validate_args_not_measured(self, args): + for arg in args: + if arg in self._measured_nodes: + raise ValueError( + "Circuit QASM cannot be generated as circuit contains midcircuit " + f"measurements on qubit: {arg}" + ) + + + def _visit_box(self, command: Node, optype): + circ = command.op.get_circuit() + for command in circ: + self._visit_command(command) + + def _visit_measure(self, command: Node, optype): + qubit_node = command.args[0] + qubit = qubit_node.index[0] + cbit = command.args[1].index + self.context.add_measurement(qubit, cbit) + self._measured_nodes.add(qubit_node) + + # @_visit_op.register + # def _(self, command: Node, optype: OpType.CustomGate): + # gate_name = command.op.gate.name + # if gate_name not in SUPPORTED_CUSTOM_GATES: + # raise ValueError(f"Encountered unsupported custom gate {gate_name}") + # self._visit_gate(gate_name, command.op.params, command.args) + + + def _visit_gate(self, command: Node, optype): + """ + Check to see if this operation is a gate known by OpenQASM3.0; if it is, retrieve the appropriate translation + and add the operation to the context. + """ + gate_name: str + if optype in self.gate_overrides: + gate_name = self.gate_overrides[optype] + elif optype in PYTKET_TO_QASM: + gate_name = PYTKET_TO_QASM[optype] + else: + raise ValueError(f"Operation {optype} cannot be translated to OpenQASM3.0.") + + qubits = command.args + params = command.op.params + print("args: ", params) + print("qubits: ", qubits) + + params = self._gate_angles_in_radians(params) + qubits = [q.index[0] for q in qubits] + self.context.add_gate(gate_name, params, qubits) + + + + + def _gate_angles_in_radians(self, params): + return [self._tau_to_radians(param) for param in params] + + def _tau_to_radians(self, arg: Union[float, Expr, Symbol]): + return pi * arg + \ No newline at end of file From 36eff020d35413d3af66953e426abfc0736a6867 Mon Sep 17 00:00:00 2001 From: Nagji Date: Fri, 7 Jun 2024 13:29:37 -0700 Subject: [PATCH 11/91] fix: Covert from radians to Tket half-turns in PytketContext --- .../emulators/pytket_translator/pytket_program_context.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/braket/emulators/pytket_translator/pytket_program_context.py b/src/braket/emulators/pytket_translator/pytket_program_context.py index 974d21b4c..ecf221aea 100644 --- a/src/braket/emulators/pytket_translator/pytket_program_context.py +++ b/src/braket/emulators/pytket_translator/pytket_program_context.py @@ -14,6 +14,7 @@ COMPOSED_GATES ) import numpy as np +from sympy import Symbol, pi class PytketProgramContext(AbstractProgramContext): @@ -39,12 +40,14 @@ def is_builtin_gate(self, name: str) -> bool: return result def add_gate_instruction( - self, gate_name: str, target: tuple[int, ...], *params, ctrl_modifiers: list[int], power: int + self, gate_name: str, target: tuple[int, ...], params, ctrl_modifiers: list[int], power: int ): self._check_and_update_qubits(target) + # Convert from Braket's radians to TKET's half-turns + params = [param / pi for param in params] if gate_name in QASM_TO_PYTKET: op = QASM_TO_PYTKET[gate_name] - if len(*params) > 0: + if len(params) > 0: self._circuit.add_gate(op, *params, target) else: self._circuit.add_gate(op, target) From a328916e4e70c8068f47b1cb9766706960e12334 Mon Sep 17 00:00:00 2001 From: Nagji Date: Fri, 7 Jun 2024 13:30:04 -0700 Subject: [PATCH 12/91] feat: add MEASUREMENT_REGISTER_NAME to translation constants --- src/braket/emulators/pytket_translator/translations.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/braket/emulators/pytket_translator/translations.py b/src/braket/emulators/pytket_translator/translations.py index 88847b438..d6b486a15 100644 --- a/src/braket/emulators/pytket_translator/translations.py +++ b/src/braket/emulators/pytket_translator/translations.py @@ -1,6 +1,8 @@ from braket.emulators.pytket_translator.composed_gates import ComposedGates from pytket.circuit import OpType +"""The measurement register identifier.""" +MEASUREMENT_REGISTER_NAME = "c" """ OpenQASM-3.0 to Pytket Name Translations From 94b4226f07067a907d2c85cb77f54df50f46e8cd Mon Sep 17 00:00:00 2001 From: Nagji Date: Fri, 7 Jun 2024 13:57:29 -0700 Subject: [PATCH 13/91] feat: Introduce EmulatorPass abstraction and have EmulatorCriterion inherit from EmulatorPass --- .../criteria/__init__.py | 0 .../criteria/connectivity_criterion.py | 0 .../criteria/emulator_criterion.py | 11 +++++++--- .../criteria/native_gate_criterion.py | 0 .../criteria/supported_gate_criterion.py | 0 .../emulator_passes/emulator_pass.py | 20 +++++++++++++++++++ 6 files changed, 28 insertions(+), 3 deletions(-) rename src/braket/emulators/{ => emulator_passes}/criteria/__init__.py (100%) rename src/braket/emulators/{ => emulator_passes}/criteria/connectivity_criterion.py (100%) rename src/braket/emulators/{ => emulator_passes}/criteria/emulator_criterion.py (69%) rename src/braket/emulators/{ => emulator_passes}/criteria/native_gate_criterion.py (100%) rename src/braket/emulators/{ => emulator_passes}/criteria/supported_gate_criterion.py (100%) create mode 100644 src/braket/emulators/emulator_passes/emulator_pass.py diff --git a/src/braket/emulators/criteria/__init__.py b/src/braket/emulators/emulator_passes/criteria/__init__.py similarity index 100% rename from src/braket/emulators/criteria/__init__.py rename to src/braket/emulators/emulator_passes/criteria/__init__.py diff --git a/src/braket/emulators/criteria/connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py similarity index 100% rename from src/braket/emulators/criteria/connectivity_criterion.py rename to src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py diff --git a/src/braket/emulators/criteria/emulator_criterion.py b/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py similarity index 69% rename from src/braket/emulators/criteria/emulator_criterion.py rename to src/braket/emulators/emulator_passes/criteria/emulator_criterion.py index 5250d882d..dba6c4052 100644 --- a/src/braket/emulators/criteria/emulator_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py @@ -1,10 +1,11 @@ from __future__ import annotations -from abc import ABC, abstractmethod +from abc import abstractmethod from typing import Dict from braket.circuits import Circuit #TODO: abstract to general task_specification type? Possible w.r.t. noise models? +from braket.emulators.emulator_passes import EmulatorPass, ProgramType -class EmulatorCriterion(ABC): +class EmulatorCriterion(EmulatorPass): @abstractmethod def validate(self, circuit: Circuit) -> None: @@ -17,7 +18,11 @@ def validate(self, circuit: Circuit) -> None: """ raise NotImplementedError - + + def run[ProgramType](self, program: ProgramType) -> ProgramType: + self.validate(program) + return program + @abstractmethod def __eq__(self, other: EmulatorCriterion) -> bool: raise NotImplementedError \ No newline at end of file diff --git a/src/braket/emulators/criteria/native_gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py similarity index 100% rename from src/braket/emulators/criteria/native_gate_criterion.py rename to src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py diff --git a/src/braket/emulators/criteria/supported_gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/supported_gate_criterion.py similarity index 100% rename from src/braket/emulators/criteria/supported_gate_criterion.py rename to src/braket/emulators/emulator_passes/criteria/supported_gate_criterion.py diff --git a/src/braket/emulators/emulator_passes/emulator_pass.py b/src/braket/emulators/emulator_passes/emulator_pass.py new file mode 100644 index 000000000..5464e4871 --- /dev/null +++ b/src/braket/emulators/emulator_passes/emulator_pass.py @@ -0,0 +1,20 @@ +from abc import ABC, abstractmethod +from typing import Any, TypeVar + + +ProgramType = TypeVar("ProgramType") + +class EmulatorPass(ABC): + @abstractmethod + def run[ProgramType](self, program: ProgramType) -> ProgramType: + """Runs the emulator pass on the provided program. + Args: + program (ProgramType): The program to run the emulator pass on. + Returns: + ProgramType: The program after the emulator pass has been applied. + """ + raise NotImplementedError + + + def __call__[ProgramType](self, program: ProgramType) -> ProgramType: + return self.run(program) From 1d37b0a174eadcf6bbd3697ec9e4954307423ed6 Mon Sep 17 00:00:00 2001 From: Nagji Date: Fri, 7 Jun 2024 14:00:53 -0700 Subject: [PATCH 14/91] fix: Update __init__.pys for emulator_pass and criteria directories --- src/braket/emulators/emulator_passes/__init__.py | 6 ++++++ src/braket/emulators/emulator_passes/criteria/__init__.py | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 src/braket/emulators/emulator_passes/__init__.py diff --git a/src/braket/emulators/emulator_passes/__init__.py b/src/braket/emulators/emulator_passes/__init__.py new file mode 100644 index 000000000..ab4ff3729 --- /dev/null +++ b/src/braket/emulators/emulator_passes/__init__.py @@ -0,0 +1,6 @@ +from braket.emulators.emulator_passes.emulator_pass import EmulatorPass +from braket.emulators.emulator_passes.criteria import ( + ConnectivityCriterion, + SupportedGateCriterion, + NativeGateCriterion, +) \ No newline at end of file diff --git a/src/braket/emulators/emulator_passes/criteria/__init__.py b/src/braket/emulators/emulator_passes/criteria/__init__.py index 6e6dc5f22..94f8733ed 100644 --- a/src/braket/emulators/emulator_passes/criteria/__init__.py +++ b/src/braket/emulators/emulator_passes/criteria/__init__.py @@ -1,5 +1,5 @@ -from braket.emulators.criteria.emulator_criterion import EmulatorCriterion -from braket.emulators.criteria.native_gate_criterion import NativeGateCriterion -from braket.emulators.criteria.supported_gate_criterion import SupportedGateCriterion -from braket.emulators.criteria.connectivity_criterion import ConnectivityCriterion +from braket.emulators.emulator_passes.criteria import EmulatorCriterion +from braket.emulators.emulator_passes.criteria.native_gate_criterion import NativeGateCriterion +from braket.emulators.emulator_passes.criteria.supported_gate_criterion import SupportedGateCriterion +from braket.emulators.emulator_passes.criteria.connectivity_criterion import ConnectivityCriterion From 52e04654c31176fff651bac02e1e376d36565c0e Mon Sep 17 00:00:00 2001 From: Nagji Date: Fri, 7 Jun 2024 14:47:55 -0700 Subject: [PATCH 15/91] fix: Correct import paths in emulator_passes source and tests --- src/braket/emulators/emulator_passes/__init__.py | 2 +- src/braket/emulators/emulator_passes/criteria/__init__.py | 2 +- .../emulator_passes/criteria/connectivity_criterion.py | 2 +- .../emulator_passes/criteria/native_gate_criterion.py | 2 +- .../emulator_passes/criteria/supported_gate_criterion.py | 2 +- .../unit_tests/braket/emulation/test_connectivity_criterion.py | 2 +- test/unit_tests/braket/emulation/test_native_gate_criterion.py | 2 +- .../braket/emulation/test_supported_gate_criterion.py | 3 +-- 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/braket/emulators/emulator_passes/__init__.py b/src/braket/emulators/emulator_passes/__init__.py index ab4ff3729..896b28fa8 100644 --- a/src/braket/emulators/emulator_passes/__init__.py +++ b/src/braket/emulators/emulator_passes/__init__.py @@ -1,4 +1,4 @@ -from braket.emulators.emulator_passes.emulator_pass import EmulatorPass +from braket.emulators.emulator_passes.emulator_pass import EmulatorPass, ProgramType from braket.emulators.emulator_passes.criteria import ( ConnectivityCriterion, SupportedGateCriterion, diff --git a/src/braket/emulators/emulator_passes/criteria/__init__.py b/src/braket/emulators/emulator_passes/criteria/__init__.py index 94f8733ed..824dd570f 100644 --- a/src/braket/emulators/emulator_passes/criteria/__init__.py +++ b/src/braket/emulators/emulator_passes/criteria/__init__.py @@ -1,4 +1,4 @@ -from braket.emulators.emulator_passes.criteria import EmulatorCriterion +from braket.emulators.emulator_passes.criteria.emulator_criterion import EmulatorCriterion from braket.emulators.emulator_passes.criteria.native_gate_criterion import NativeGateCriterion from braket.emulators.emulator_passes.criteria.supported_gate_criterion import SupportedGateCriterion from braket.emulators.emulator_passes.criteria.connectivity_criterion import ConnectivityCriterion diff --git a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py index f2ece2ba2..9bf23106e 100644 --- a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py @@ -1,4 +1,4 @@ -from braket.emulators.criteria import EmulatorCriterion +from braket.emulators.emulator_passes.criteria import EmulatorCriterion from networkx import DiGraph, complete_graph, from_dict_of_lists from networkx.utils import graphs_equal from typing import Union, Dict diff --git a/src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py index fcc39967a..e3278495c 100644 --- a/src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py @@ -1,7 +1,7 @@ from collections.abc import Iterator from braket.circuits.gate import Gate from braket.circuits.compiler_directives import StartVerbatimBox, EndVerbatimBox -from braket.emulators.criteria import EmulatorCriterion +from braket.emulators.emulator_passes.criteria import EmulatorCriterion from braket.circuits import Circuit from braket.circuits.translations import BRAKET_GATES diff --git a/src/braket/emulators/emulator_passes/criteria/supported_gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/supported_gate_criterion.py index 0367da6a9..8c042f95c 100644 --- a/src/braket/emulators/emulator_passes/criteria/supported_gate_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/supported_gate_criterion.py @@ -1,6 +1,6 @@ from collections.abc import Iterator from braket.circuits.gate import Gate -from braket.emulators.criteria import EmulatorCriterion +from braket.emulators.emulator_passes.criteria import EmulatorCriterion from braket.circuits import Circuit from braket.circuits.translations import BRAKET_GATES diff --git a/test/unit_tests/braket/emulation/test_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_connectivity_criterion.py index 2ecc2fdb4..7d98667b1 100644 --- a/test/unit_tests/braket/emulation/test_connectivity_criterion.py +++ b/test/unit_tests/braket/emulation/test_connectivity_criterion.py @@ -1,6 +1,6 @@ import pytest -from braket.emulators.criteria import ConnectivityCriterion +from braket.emulators.emulator_passes.criteria import ConnectivityCriterion from braket.circuits import Circuit import networkx as nx import numpy as np diff --git a/test/unit_tests/braket/emulation/test_native_gate_criterion.py b/test/unit_tests/braket/emulation/test_native_gate_criterion.py index 564400814..bb0043c57 100644 --- a/test/unit_tests/braket/emulation/test_native_gate_criterion.py +++ b/test/unit_tests/braket/emulation/test_native_gate_criterion.py @@ -1,6 +1,6 @@ import pytest -from braket.emulators.criteria.native_gate_criterion import NativeGateCriterion +from braket.emulators.emulator_passes.criteria import NativeGateCriterion from braket.circuits import Circuit, Gate, gates diff --git a/test/unit_tests/braket/emulation/test_supported_gate_criterion.py b/test/unit_tests/braket/emulation/test_supported_gate_criterion.py index 2519c8ccf..48a26484a 100644 --- a/test/unit_tests/braket/emulation/test_supported_gate_criterion.py +++ b/test/unit_tests/braket/emulation/test_supported_gate_criterion.py @@ -1,6 +1,5 @@ import pytest - -from braket.emulators.criteria import SupportedGateCriterion +from braket.emulators.emulator_passes.criteria import SupportedGateCriterion from braket.circuits import Circuit import numpy as np From 467c27c754071a74cc6f5ac95a62dcc414d58e5b Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 10 Jun 2024 11:44:55 -0700 Subject: [PATCH 16/91] feat: Introduce Gate Connectivity Criterion --- .../emulator_passes/criteria/__init__.py | 2 +- .../criteria/gate_connectivity_criterion.py | 76 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py diff --git a/src/braket/emulators/emulator_passes/criteria/__init__.py b/src/braket/emulators/emulator_passes/criteria/__init__.py index 824dd570f..370cdc9e8 100644 --- a/src/braket/emulators/emulator_passes/criteria/__init__.py +++ b/src/braket/emulators/emulator_passes/criteria/__init__.py @@ -2,4 +2,4 @@ from braket.emulators.emulator_passes.criteria.native_gate_criterion import NativeGateCriterion from braket.emulators.emulator_passes.criteria.supported_gate_criterion import SupportedGateCriterion from braket.emulators.emulator_passes.criteria.connectivity_criterion import ConnectivityCriterion - +from braket.emulators.emulator_passes.criteria.gate_connectivity_criterion import GateConnectivityCriterion diff --git a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py new file mode 100644 index 000000000..a7a06dfc9 --- /dev/null +++ b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py @@ -0,0 +1,76 @@ +from dataclasses import dataclass +from braket.circuits.circuit import Circuit +from braket.emulators.emulator_passes.criteria import EmulatorCriterion +from typing import Dict, Tuple, Union, Any, Set, Iterable +from braket.circuits.compiler_directives import StartVerbatimBox, EndVerbatimBox +from braket.circuits.gate import Gate +from braket.registers.qubit_set import QubitSet +from networkx import DiGraph + + +class GateConnectivityCriterion(EmulatorCriterion): + def __init__(self, + gate_connectivity_graph: Union[ + Dict[Tuple[Any, Any], Iterable[str]], + DiGraph + ]): + super().__init__() + if isinstance(gate_connectivity_graph, DiGraph): + self._gate_connectivity_graph = gate_connectivity_graph + elif isinstance(gate_connectivity_graph, dict): + self._gate_connectivity_graph = DiGraph() + for edge, supported_gates in gate_connectivity_graph.items(): + self._gate_connectivity_graph.add_edge(*edge, supported_gates=supported_gates) + else: + raise TypeError("gate_connectivity_graph must either be a dictionary of edges mapped to supported gates lists, or a DiGraph with supported \ + gates provided as edge attributs. ") + + def validate(self, circuit: Circuit) -> None: + """ + Verifies that any multiqubit gates used within a verbatim box are supported by the devices gate + connectivity defined by this criteria. + """ + for idx in range(len(circuit.instructions)): + instruction = circuit.instructions[idx] + if isinstance(instruction.operator, StartVerbatimBox): + idx += 1 + while idx < len(circuit.instructions) and not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): + instruction = circuit.instructions[idx] + if isinstance(instruction.operator, Gate): + if instruction.operator.qubit_count == 2: #Assuming only maximum 2-qubit native gates are supported + self.validate_instruction_connectivity(instruction.operator.name, instruction.control, instruction.target) + else: + #just check that the target qubit exists in the connectivity graph + target_qubit = instruction.target[0] + if not target_qubit in self._gate_connectivity_graph: + raise ValueError(f"Qubit {target_qubit} does not exist in the device topology.") + idx += 1 + + if 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(self, gate_name: str, control_qubits: QubitSet, target_qubits: QubitSet): + #Create edges between each of the target qubits + if len(control_qubits) == 1 and len(target_qubits) == 1: + e = (control_qubits[0], target_qubits[0]) + elif len(target_qubits) == 2: + e = (target_qubits[0], target_qubits[1]) + else: + raise ValueError("Unrecognized qubit targetting setup for a 2 qubit gate.") + + #Check that each edge exists in this criterion's connectivity graph + if not self._gate_connectivity_graph.has_edge(*e): + raise ValueError(f"{e[0]} is not connected to {e[1]} on this device.") + supported_gates = self._gate_connectivity_graph[e[0]][e[1]]["supported_gates"] + print(gate_name, supported_gates) + if gate_name not in supported_gates: + raise ValueError(f"Qubit pair ({e[0]}, {e[1]}) does not support gate {gate_name} on this device.") + + + def __eq__(self, other: EmulatorCriterion) -> bool: + return isinstance(other, GateConnectivityCriterion) \ + and all([self._gate_connectivity_graph[edge] == other._gate_connectivity_graph.get(edge) for edge in self._gate_connectivity_graph.keys()]) \ + and len(self._gate_connectivity_graph) == len(other._gate_connectivity_graph) \ No newline at end of file From 5dca87ddc89967558679ac31c84869bb6debaae5 Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 10 Jun 2024 11:45:32 -0700 Subject: [PATCH 17/91] fix: Correct qubit order in error print statement --- .../emulator_passes/criteria/connectivity_criterion.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py index 9bf23106e..76d15ed25 100644 --- a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py @@ -77,10 +77,10 @@ def validate(self, circuit: Circuit) -> None: def validate_instruction_connectivity(self, control_qubits: QubitSet, target_qubits: QubitSet): #Create edges between each of the target qubits gate_connectivity_graph = DiGraph() - + print(control_qubits, target_qubits) #Create an edge from each control bit to each target qubit if len(control_qubits) == 1 and len(target_qubits) == 1: - add_edge(control_qubits[0], target_qubits[0]) + gate_connectivity_graph.add_edge(control_qubits[0], target_qubits[0]) elif len(target_qubits) == 2: gate_connectivity_graph.add_edges_from([ (target_qubits[0], target_qubits[1]), @@ -91,7 +91,7 @@ def validate_instruction_connectivity(self, control_qubits: QubitSet, target_qub #Check that each edge exists in this criterion's connectivity graph for e in gate_connectivity_graph.edges: if not self._connectivity_graph.has_edge(*e): - raise ValueError(f"{e[1]} is not connected to qubit {e[0]} in this device.") + raise ValueError(f"{e[0]} is not connected to qubit {e[1]} in this device.") From 4c926584cc9369854b4c0bb5d01258445868d73a Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 10 Jun 2024 16:00:32 -0700 Subject: [PATCH 18/91] feat: Add GateConnectivityCriterion tests --- .../test_gate_connectivity_criterion.py | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py diff --git a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py new file mode 100644 index 000000000..037d6b7a5 --- /dev/null +++ b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py @@ -0,0 +1,198 @@ +import pytest + +from braket.emulators.emulator_passes.criteria import GateConnectivityCriterion +from braket.circuits import Circuit +import networkx as nx +from networkx.utils import graphs_equal +import numpy as np + + +@pytest.fixture +def basic_4_node_graph(): + G = nx.DiGraph() + G.add_edges_from( + [ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 2, {"supported_gates": ["Swap", "CNot"]}), + (0, 3, {"supported_gates": ["XX", "XY"]}) + ] + ) + return G + + +@pytest.fixture +def basic_discontiguous_4_node_graph(): + G = nx.DiGraph() + G.add_edges_from( + [ + (0, 5, {"supported_gates": ["CNot", "CZ"]}), + (2, 4, {"supported_gates": ["Swap", "CNot"]}), + (3, 0, {"supported_gates": ["XX", "XY"]}) + ] + ) + return G + +@pytest.fixture +def basic_undirected_4_node_graph_as_dict(): + return { + (0, 1): ["CNot", "Swap", "CX", "XX"], + (1, 2): ["CNot", "CZ", "ISwap", "XY"], + (0, 3): ["PSwap", "CNot", "XY"] + } + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().add_verbatim_box( + Circuit().cnot(0, 1).h(range(4)) + ), + Circuit().add_verbatim_box( + Circuit().cnot(0, 1).cz(0, 1).swap(1, 2).xx(0, 3, np.pi/2) + ).add_verbatim_box( + Circuit().xy(0, 3, np.pi/2).cnot(1, 2).cz(0, 1) + ), + Circuit().i(range(10)).cnot(0, 2).yy(8, 9, np.pi/2).h(7).add_verbatim_box( + Circuit().cnot(0, 1).cz(0, 1) + ).cnot(0, 2).swap(4, 6) + ] +) +def test_valid_basic_contiguous_circuits(basic_4_node_graph, circuit): + """ + ConnectivityGateCriterion should not raise any errors when validating these circuits. + """ + gate_connectivity_criterion = GateConnectivityCriterion(basic_4_node_graph) + gate_connectivity_criterion.validate(circuit) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().add_verbatim_box( + Circuit().cnot(0, 5) + ), + Circuit().add_verbatim_box( + Circuit().cnot(2, 4).cz(0, 5).swap(2, 4).xx(3, 0, np.pi/2) + ).add_verbatim_box( + Circuit().xy(3, 0, np.pi/2).cnot(2, 4).cz(0, 5) + ), + Circuit().i(range(10)).cnot(0, 2).yy(8, 9, np.pi/2).h(7).add_verbatim_box( + Circuit().cnot(0, 5).swap(2, 4) + ).cnot(0, 2).swap(4, 6) + ] +) +def test_valid_basic_discontiguous_circuits(basic_discontiguous_4_node_graph, circuit): + """ + ConnectivityGateCriterion should not raise any errors when validating these circuits. + """ + gate_connectivity_criterion = GateConnectivityCriterion(basic_discontiguous_4_node_graph) + gate_connectivity_criterion.validate(circuit) + + + +def test_directed_graph_construction_from_dict(): + """ + ConnectivityGateCriterion should correctly construct a graph from a dictionary + representation of the connectivity. + """ + dict_representation = { + (0, 1): ["CNot", "CZ"], + (1, 2): ["Swap", "CNot", "YY"], + (0, 2): ["XX", "XY", "CNot", "CZ"], + (2, 5): ["XX", "XY", "CNot", "CZ"] + } + digraph_representation = nx.DiGraph() + digraph_representation.add_edges_from([ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 2, {"supported_gates": ["Swap", "CNot", "YY"]}), + (0, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + (2, 5, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}) + ]) + gcc = GateConnectivityCriterion(dict_representation, directed=True) + assert graphs_equal(gcc._gate_connectivity_graph, digraph_representation) + + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().add_verbatim_box( + Circuit().cnot(0, 1).cnot(1, 0) + ), + Circuit().swap(0, 1).add_verbatim_box( + Circuit().iswap(2, 1).pswap(3, 0, np.pi/2).pswap(0, 3, np.pi/2) + ), + Circuit().cnot(2, 3).h(5).pswap(0, 6, np.pi/2).add_verbatim_box( + Circuit().xy(2, 1, np.pi/2).cnot(0, 1).cnot(1, 0) + ).add_verbatim_box( + Circuit().cnot(0, 3).cnot(3, 0).xy(0, 3, np.pi/2) + ) + ] +) +def test_undirected_criteria_with_valid_circuits(basic_undirected_4_node_graph_as_dict, circuit): + """ + ConnectivityGateCriterion should not raise any errors when validating these circuits. + """ + gate_connectivity_criterion = GateConnectivityCriterion(basic_undirected_4_node_graph_as_dict, directed=False) + gate_connectivity_criterion.validate(circuit) + + + +def test_undirected_graph_construction_from_dict(): + """ + ConnectivityGateCriterion should correctly construct an undirected graph from a dictionary + representation of the connectivity. + """ + dict_representation = { + (0, 1): ["CNot", "CZ"], + (1, 2): ["Swap", "CNot", "YY"], + (0, 2): ["XX", "XY", "CNot", "CZ"], + (2, 5): ["XX", "XY", "CNot", "CZ"] + } + digraph_representation = nx.DiGraph() + digraph_representation.add_edges_from([ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 2, {"supported_gates": ["Swap", "CNot", "YY"]}), + (0, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + (2, 5, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + (1, 0, {"supported_gates": ["CNot", "CZ"]}), + (2, 1, {"supported_gates": ["Swap", "CNot", "YY"]}), + (2, 0, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + (5, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + ]) + gcc = GateConnectivityCriterion(dict_representation) + assert graphs_equal(gcc._gate_connectivity_graph, digraph_representation) + + + + # G.add_edges_from( + # [ + # (0, 1, {"supported_gates": ["CNot", "CZ"]}), + # (1, 2, {"supported_gates": ["Swap", "CNot"]}), + # (0, 3, {"supported_gates": ["XX", "XY"]}) + # ] + # ) +@pytest.mark.parametrize( + "circuit", + [ + Circuit().add_verbatim_box( + Circuit().cnot(1, 0) + ), + 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) + ) + ] +) +def test_invalid_circuits(basic_4_node_graph, circuit): + with pytest.raises(ValueError): + gate_connectivity_criterion = GateConnectivityCriterion(basic_4_node_graph) + gate_connectivity_criterion.validate(circuit) \ No newline at end of file From 88c95302a6f32379ff6af0d784619f27a5fd5d6f Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 12 Jun 2024 16:54:32 -0700 Subject: [PATCH 19/91] feat: Basic AWS Noise Model --- .../emulators/device_noise_models/__init__.py | 1 + .../gate_device_noise_mode.py | 226 ++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 src/braket/emulators/device_noise_models/__init__.py create mode 100644 src/braket/emulators/device_noise_models/gate_device_noise_mode.py diff --git a/src/braket/emulators/device_noise_models/__init__.py b/src/braket/emulators/device_noise_models/__init__.py new file mode 100644 index 000000000..cb8e4619b --- /dev/null +++ b/src/braket/emulators/device_noise_models/__init__.py @@ -0,0 +1 @@ +from braket.emulators.device_noise_models.gate_device_noise_mode import GateDeviceNoiseModel \ No newline at end of file diff --git a/src/braket/emulators/device_noise_models/gate_device_noise_mode.py b/src/braket/emulators/device_noise_models/gate_device_noise_mode.py new file mode 100644 index 000000000..93ebf9a9a --- /dev/null +++ b/src/braket/emulators/device_noise_models/gate_device_noise_mode.py @@ -0,0 +1,226 @@ +from abc import ABC, abstractmethod +from braket.circuits.noise_model import GateCriteria, NoiseModel, ObservableCriteria +from typing import List +from dataclasses import dataclass +from typing import Dict, Tuple, List, Set, Optional +from braket.circuits import Gate +from braket.circuits.noise_model import NoiseModel +from braket.devices import Devices +from braket.aws import AwsDevice +from braket.device_schema.standardized_gate_model_qpu_device_properties_v1 import ( + StandardizedGateModelQpuDeviceProperties, OneQubitProperties, GateFidelity2Q, Fidelity1Q +) +import logging +from braket.circuits.noises import AmplitudeDamping, BitFlip, Depolarizing, PhaseDamping, TwoQubitDepolarizing + +import numpy as np + +@dataclass +class GateFidelity: + gate_name: str + fidelity:float + + +""" + The following gate duration values are not available through Braket device calibration data and must + be hardcoded. +""" +QPU_GATE_DURATIONS = { + Devices.Rigetti.AspenM3: { + "single_qubit_gate_duration": 40e-9, + "two_qubit_gate_duration": 240e-9 + }, + Devices.IQM.Garnet: { + "single_qubit_gate_duration": 32e-9, + "two_qubit_gate_duration": 60e-9 + } +} + +GATE_NAME_TRANSLATIONS = { + "CPHASE": "CPhaseShift", + "GPI": "GPi" +} + + +@dataclass +class GateDeviceCalibrationData: + single_qubit_gate_duration: float + two_qubit_gate_duration: float + qubit_labels: Set[int] + single_qubit_specs: Dict[int, Dict[str, float]] + two_qubit_edge_specs: Dict[Tuple[int, int], List[GateFidelity]] + + def _validate_single_qubit_specs(self): + for qubit in self.single_qubit_specs.keys(): + if qubit not in self.qubit_labels: + raise ValueError(f"Invalid qubit label {qubit}") + + def _validate_two_qubit_specs(self): + for edge in self.two_qubit_edge_specs.keys(): + if edge[0] not in self.qubit_labels or edge[1] not in self.qubit_labels: + raise ValueError(f"Invalid qubit pair {edge}") + + def __post_init__(self): + self._validate_single_qubit_specs() + self._validate_two_qubit_specs() + + +class GateDeviceNoiseModel(NoiseModel): + def __init__(self, arn: str, aws_device: AwsDevice = None): + super().__init__() + self._arn = arn + device_properties = (aws_device or AwsDevice(self._arn)).properties + self._gate_calibration_data = self._setup_device_calibration_data(device_properties) + self._setup_basic_noise_model_strategy() + + def _setup_device_calibration_data(self, device_properties) -> GateDeviceCalibrationData: + if hasattr(device_properties, "standardized") and isinstance( + device_properties.standardized, StandardizedGateModelQpuDeviceProperties + ): + return self._setup_standardized_calibration_data(device_properties) + elif self._arn in Devices.IonQ: + return self._setup_ionq_device_calibration_data(device_properties) + + + def _setup_standardized_calibration_data(self, device_properties) -> GateDeviceCalibrationData: + gate_durations = QPU_GATE_DURATIONS.get(self._arn, None) + if not gate_durations: + raise ValueError(f"Gate durations are not available for device {self._arn}") + single_qubit_gate_duration = gate_durations["single_qubit_gate_duration"] + two_qubit_gate_duration = gate_durations["two_qubit_gate_duration"] + + standardized_properties = device_properties.standardized + one_qubit_properties = standardized_properties.oneQubitProperties + qubit_labels = set(int(qubit) for qubit in one_qubit_properties.keys()) + single_qubit_specs = {int(qubit): self._create_qubit_specs(one_qubit_properties[qubit]) for qubit in one_qubit_properties.keys()} + + + two_qubit_properties = standardized_properties.twoQubitProperties + two_qubit_edge_specs = { + tuple(int(qubit) for qubit in edge.split("-")[0:2]): self._create_edge_specs(gate_fidelities.twoQubitGateFidelity) + for edge, gate_fidelities in two_qubit_properties.items()} + return GateDeviceCalibrationData( + single_qubit_gate_duration, + two_qubit_gate_duration, + qubit_labels, + single_qubit_specs, + two_qubit_edge_specs + ) + + def _create_qubit_specs(self, qubit_properties: OneQubitProperties) -> Dict[str, int]: + T1 = qubit_properties.T1.value + T2 = qubit_properties.T2.value + qubit_fidelities = qubit_properties.oneQubitFidelity + one_qubit_fidelities = { + qubit_fidelity.fidelityType.name : qubit_fidelity.fidelity for qubit_fidelity in qubit_fidelities + } + one_qubit_fidelities["T1"] = T1 + one_qubit_fidelities["T2"] = T2 + return one_qubit_fidelities + + def _create_edge_specs(self, edge_properties: List[GateFidelity2Q]) -> List[GateFidelity]: + return [ + GateFidelity(GATE_NAME_TRANSLATIONS.get(gate_fidelity.gateName, gate_fidelity.gateName), gate_fidelity.fidelity) + for gate_fidelity in edge_properties + ] + + + def _setup_ionq_device_calibration_data(self, device_properties): + """ + IonQ's Trapped Ion Devices do not have per-qubit calibration data and instead + provide averaged qubit and two-qubit gate fidelities across the device. All qubits + are connected in a trapped ion device. + + We instead copy the averaged fidelities to each qubit and qubit edge pair. + """ + calibration_data = device_properties.provider + fidelity_data = calibration_data.fidelity + timing_data = calibration_data.timing + qubit_count = device_properties.paradigm.qubitCount + native_gates = device_properties.paradigm.nativeGateSet + single_qubit_gate_duration = timing_data["1Q"] + two_qubit_gate_duration = timing_data["2Q"] + average_active_reset_fidelity = timing_data["reset"] + average_T1 = timing_data["T1"] + average_T2 = timing_data["T2"] + single_qubit_rb_fidelity = fidelity_data["1Q"]["mean"] + two_qubit_rb_fidelity = fidelity_data["2Q"]["mean"] + average_readout_fidelity = fidelity_data["spam"]["mean"] + + native_gate_fidelities = [ + GateFidelity(GATE_NAME_TRANSLATIONS.get(native_gate, native_gate), two_qubit_rb_fidelity) for native_gate in native_gates + ] + + single_qubit_specs = {} + two_qubit_edge_specs = {} + for ii in range(qubit_count): + qubit_spec = { + "RANDOMIZED_BENCHMARKING": single_qubit_rb_fidelity, + "SIMULTANEOUS_RANDOMIZED_BENCHMARKING": None, + "READOUT": average_readout_fidelity, + "T1": average_T1, + "T2": average_T2, + "ACTIVE_RESET": average_active_reset_fidelity + } + single_qubit_specs[ii] = qubit_spec + + for jj in range(ii + 1, qubit_count): + two_qubit_edge_specs[(ii, jj)] = native_gate_fidelities + + return GateDeviceCalibrationData( + single_qubit_gate_duration, + two_qubit_gate_duration, + set(range(qubit_count)), + single_qubit_specs, + two_qubit_edge_specs + ) + + + def _setup_basic_noise_model_strategy(self): + """ + Apply a basic noise model strategy consisting of: + - T1 Dampening + - T2 Phase Dampening + - 1 Qubit RB Depolarizing Noise + - 1 Qubit Readout Error + - 2 Qubit Gate Depolarizing Noise + """ + gate_duration_1Q = self._gate_calibration_data.single_qubit_gate_duration + gate_duration_2Q = self._gate_calibration_data.two_qubit_gate_duration + for qubit, data in self._gate_calibration_data.single_qubit_specs.items(): + #T1 dampening + T1 = data["T1"] + damping_prob = 1 - np.exp(-(gate_duration_1Q/T1)) + self.add_noise(AmplitudeDamping(damping_prob), GateCriteria(qubits=qubit)) + + #T2 Phase Dampening + T2 = data["T2"] + dephasing_prob = 0.5 * (1 - np.exp(-(gate_duration_1Q/T2))) + self.add_noise(PhaseDamping(dephasing_prob), GateCriteria(qubits=qubit)) + + #1 Qubit RB Depolarizing Noise + if "SIMULTANEOUS_RANDOMIZED_BENCHMARKING" in data: + benchmark_fidelity = data["SIMULTANEOUS_RANDOMIZED_BENCHMARKING"] + else: + benchmark_fidelity = data.get(["RANDOMIZED_BENCHMARKING"]) + if benchmark_fidelity: + depolarizing_rate = 1 - benchmark_fidelity + self.add_noise(Depolarizing(depolarizing_rate), GateCriteria(qubits=qubit)) + + #1 Qubit Readout Error + readout_error_rate = 1 - data["READOUT"] + self.add_noise(BitFlip(readout_error_rate), ObservableCriteria(qubits=qubit)) + + for edge, data in self._gate_calibration_data.two_qubit_edge_specs.items(): + for gate_fidelity in data: + rate = 1 - gate_fidelity.fidelity + gate_name = gate_fidelity.gate_name + if hasattr(Gate, gate_name): + gate = getattr(Gate, gate_name) + else: + # raise ValueError(f"Cannot find gate {gate_name}.") + logging.warning(f"Cannot find gate {gate_name}.") + continue + self.add_noise(TwoQubitDepolarizing(rate), GateCriteria(gate, [(edge[0], edge[1]), (edge[1], edge[0])])) + + \ No newline at end of file From 1ff8cfadb71d14eea42880653762ce1027a3a1ac Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 13 Jun 2024 17:07:21 -0700 Subject: [PATCH 20/91] fix: Discard IonQ Single Qubit Gate Fidelities and use Gate classes instead of gate name strings in GateFidelity data class. --- .../gate_device_noise_mode.py | 54 ++++++++++++------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/braket/emulators/device_noise_models/gate_device_noise_mode.py b/src/braket/emulators/device_noise_models/gate_device_noise_mode.py index 93ebf9a9a..e48802300 100644 --- a/src/braket/emulators/device_noise_models/gate_device_noise_mode.py +++ b/src/braket/emulators/device_noise_models/gate_device_noise_mode.py @@ -15,11 +15,6 @@ import numpy as np -@dataclass -class GateFidelity: - gate_name: str - fidelity:float - """ The following gate duration values are not available through Braket device calibration data and must @@ -38,8 +33,15 @@ class GateFidelity: GATE_NAME_TRANSLATIONS = { "CPHASE": "CPhaseShift", - "GPI": "GPi" + "GPI": "GPi", + "GPI2": "GPi2" } + + +@dataclass +class GateFidelity: + gate: Gate + fidelity:float @dataclass @@ -119,10 +121,15 @@ def _create_qubit_specs(self, qubit_properties: OneQubitProperties) -> Dict[str, return one_qubit_fidelities def _create_edge_specs(self, edge_properties: List[GateFidelity2Q]) -> List[GateFidelity]: - return [ - GateFidelity(GATE_NAME_TRANSLATIONS.get(gate_fidelity.gateName, gate_fidelity.gateName), gate_fidelity.fidelity) - for gate_fidelity in edge_properties - ] + edge_specs = [] + for edge_property in edge_properties: + gate_name = GATE_NAME_TRANSLATIONS.get(edge_property.gateName, edge_property.gateName) + if hasattr(Gate, gate_name): + gate = getattr(Gate, gate_name) + edge_specs.append(GateFidelity(gate, edge_property.fidelity)) + else: + logging.warning(f"Unsupported gate {gate_name}") + return edge_specs def _setup_ionq_device_calibration_data(self, device_properties): @@ -147,9 +154,22 @@ def _setup_ionq_device_calibration_data(self, device_properties): two_qubit_rb_fidelity = fidelity_data["2Q"]["mean"] average_readout_fidelity = fidelity_data["spam"]["mean"] - native_gate_fidelities = [ - GateFidelity(GATE_NAME_TRANSLATIONS.get(native_gate, native_gate), two_qubit_rb_fidelity) for native_gate in native_gates - ] + native_gate_fidelities = [] + for native_gate in native_gates: + gate_name = GATE_NAME_TRANSLATIONS.get(native_gate, native_gate) + if hasattr(Gate, gate_name): + gate = getattr(Gate, gate_name) + if gate.fixed_qubit_count() != 2: + """ + The noise model does not consider any single-qubit gate specific fidelities and instead + applies depolarizing noise associated with the individual qubits themselves (RB/sRB fidelities). + This is a choice of this particular model to simplify the implementation as not all QHPs provide + single-qubit gate fidelities. + """ + continue + native_gate_fidelities.append(GateFidelity(gate, two_qubit_rb_fidelity)) + else: + logging.warning(f"Unsupported gate {native_gate}") single_qubit_specs = {} two_qubit_edge_specs = {} @@ -214,13 +234,7 @@ def _setup_basic_noise_model_strategy(self): for edge, data in self._gate_calibration_data.two_qubit_edge_specs.items(): for gate_fidelity in data: rate = 1 - gate_fidelity.fidelity - gate_name = gate_fidelity.gate_name - if hasattr(Gate, gate_name): - gate = getattr(Gate, gate_name) - else: - # raise ValueError(f"Cannot find gate {gate_name}.") - logging.warning(f"Cannot find gate {gate_name}.") - continue + gate = gate_fidelity.gate self.add_noise(TwoQubitDepolarizing(rate), GateCriteria(gate, [(edge[0], edge[1]), (edge[1], edge[0])])) \ No newline at end of file From edef55d29a1ff6831efc8187079decc1abe673ea Mon Sep 17 00:00:00 2001 From: Nagji Date: Fri, 14 Jun 2024 12:04:00 -0700 Subject: [PATCH 21/91] fix: Include EmulatorCriterion in __init__.py for emulator_passes module --- src/braket/emulators/emulator_passes/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/braket/emulators/emulator_passes/__init__.py b/src/braket/emulators/emulator_passes/__init__.py index 896b28fa8..bac3d7e00 100644 --- a/src/braket/emulators/emulator_passes/__init__.py +++ b/src/braket/emulators/emulator_passes/__init__.py @@ -1,5 +1,6 @@ from braket.emulators.emulator_passes.emulator_pass import EmulatorPass, ProgramType from braket.emulators.emulator_passes.criteria import ( + EmulatorCriterion, ConnectivityCriterion, SupportedGateCriterion, NativeGateCriterion, From 14d09e8560d54334085af00b1d169d581217cd45 Mon Sep 17 00:00:00 2001 From: Nagji Date: Fri, 14 Jun 2024 12:04:47 -0700 Subject: [PATCH 22/91] feat: Introduce Emulator Interface and AwsEmulator --- src/braket/emulators/aws_emulator.py | 71 ++++++++++++++++++++++ src/braket/emulators/emulater_interface.py | 45 ++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 src/braket/emulators/aws_emulator.py create mode 100644 src/braket/emulators/emulater_interface.py diff --git a/src/braket/emulators/aws_emulator.py b/src/braket/emulators/aws_emulator.py new file mode 100644 index 000000000..1bd91e9de --- /dev/null +++ b/src/braket/emulators/aws_emulator.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from braket.emulators.emulater_interface import EmulatorInterface +from braket.circuits.noise_model import NoiseModel +from braket.aws import AwsDevice, AwsSession +from braket.emulators.emulator_passes import ( + EmulatorCriterion, + NativeGateCriterion, + SupportedGateCriterion, + ConnectivityCriterion +) +from braket.emulators.device_noise_models import GateDeviceNoiseModel +from braket.device_schema import DeviceActionType +from braket.device_schema import ( + DeviceCapabilities, + DeviceConnectivity, + DeviceActionType +) + + +from typing import Optional + + +class AwsEmulator(AwsDevice, EmulatorInterface): + """ + An emulator whose structure and constraints and defined by an AWS Braket Device. An AWS Emulator is created by passing a valid device ARN. + Device metadata is used to instantiate the emulator criteria. + """ + + def __init__(self, + arn: str, + backend: str = "default", + aws_session: Optional[AwsSession] = None, + noise_model: Optional[NoiseModel] = None + ): + EmulatorInterface.__init__(self) + AwsDevice.__init__(self, arn, aws_session, noise_model) + self._backend = backend + self._initialize_gate_set_criteria() + self._initialize_connectivity_criteria() + self._initialize_emulator_noise_model() + + + + def _initialize_gate_set_criteria(self): + """ + Initializes the emulator to support only the gate set and native gate set supported by the target device. + args: + aws_device (AwsDevice): The Braket AwsDevice from which to copy the supported and native gate sets from. + """ + + #create native gate criterion to validate gates inside of a verbatim box + native_gates = self.properties.paradigm.nativeGateSet + native_gate_criterion = NativeGateCriterion(native_gates) + self.add_pass(native_gate_criterion) + + #create supported gate criterion to validate gates outside of a verbatim box + supported_gates = self.properties.action[DeviceActionType.OPENQASM].supportedOperations + supported_gates_criterion = SupportedGateCriterion(supported_gates) + self.add_pass(supported_gates_criterion) + + + def _initialize_connectivity_criteria(self): + self.add_pass(ConnectivityCriterion(self.topology_graph)) + + def _initialize_emulator_noise_model(self): + emulator_noise_model = GateDeviceNoiseModel(self._arn, self.properties) + self._emulator_noise_model = emulator_noise_model + + + \ No newline at end of file diff --git a/src/braket/emulators/emulater_interface.py b/src/braket/emulators/emulater_interface.py new file mode 100644 index 000000000..1a2fc80fb --- /dev/null +++ b/src/braket/emulators/emulater_interface.py @@ -0,0 +1,45 @@ +from abc import ABC, abstractmethod +from braket.emulators.emulator_passes import EmulatorPass, ProgramType +from braket.emulators.emulator_passes.criteria import EmulatorCriterion +from collections.abc import Iterator +from typing import Iterable, Union + + +class EmulatorInterface(ABC): + def __init__(self, emulator_passes: Iterable[EmulatorPass] = None): + self._emulator_passes = emulator_passes if emulator_passes is not None else [] + + def run_program_passes[ProgramType](self, task_specification: ProgramType) -> ProgramType: + """ + This method passes the input program through the EmulatorPasses 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). + Args: + task_specification (ProgramType): The program to run the emulator passes on. + + Returns: + (ProgramType): A "compiled" program of the same type as the input. + + """ + for emulator_pass in self._emulator_passes: + task_specification = emulator_pass(task_specification) + return task_specification + + + def run_validation_passes[ProgramType](self, task_specification: ProgramType) -> None: + """ + This method passes the input program through EmulatorPasses that perform only validation, without modifying the input program. + """ + for emulator_pass in self._emulator_passes: + if isinstance(emulator_pass, EmulatorCriterion): + emulator_pass(task_specification) + + + def add_pass(self, emulator_pass: Union[Iterable[EmulatorPass], EmulatorPass]) -> None: + if isinstance(emulator_pass, Iterator): + self._emulator_passes.extend(emulator_pass) + elif isinstance(emulator_pass, EmulatorPass): + self._emulator_passes.append(emulator_pass) + else: + print(isinstance(emulator_pass, EmulatorPass)) + print(type(emulator_pass)) + raise TypeError("emulator_pass must be an EmulatorPass or an iterable of EmulatorPass") \ No newline at end of file From c229adcbe657381f32d63eab0491c67125404a72 Mon Sep 17 00:00:00 2001 From: Nagji Date: Fri, 14 Jun 2024 12:08:24 -0700 Subject: [PATCH 23/91] fix: Fix gate device noise model file name typo --- src/braket/emulators/device_noise_models/__init__.py | 2 +- ...gate_device_noise_mode.py => gate_device_noise_model.py} | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) rename src/braket/emulators/device_noise_models/{gate_device_noise_mode.py => gate_device_noise_model.py} (98%) diff --git a/src/braket/emulators/device_noise_models/__init__.py b/src/braket/emulators/device_noise_models/__init__.py index cb8e4619b..89f824650 100644 --- a/src/braket/emulators/device_noise_models/__init__.py +++ b/src/braket/emulators/device_noise_models/__init__.py @@ -1 +1 @@ -from braket.emulators.device_noise_models.gate_device_noise_mode import GateDeviceNoiseModel \ No newline at end of file +from braket.emulators.device_noise_models.gate_device_noise_model import GateDeviceNoiseModel \ No newline at end of file diff --git a/src/braket/emulators/device_noise_models/gate_device_noise_mode.py b/src/braket/emulators/device_noise_models/gate_device_noise_model.py similarity index 98% rename from src/braket/emulators/device_noise_models/gate_device_noise_mode.py rename to src/braket/emulators/device_noise_models/gate_device_noise_model.py index e48802300..48a5b77bd 100644 --- a/src/braket/emulators/device_noise_models/gate_device_noise_mode.py +++ b/src/braket/emulators/device_noise_models/gate_device_noise_model.py @@ -7,6 +7,8 @@ from braket.circuits.noise_model import NoiseModel from braket.devices import Devices from braket.aws import AwsDevice + +from braket.device_schema import DeviceCapabilities from braket.device_schema.standardized_gate_model_qpu_device_properties_v1 import ( StandardizedGateModelQpuDeviceProperties, OneQubitProperties, GateFidelity2Q, Fidelity1Q ) @@ -68,10 +70,10 @@ def __post_init__(self): class GateDeviceNoiseModel(NoiseModel): - def __init__(self, arn: str, aws_device: AwsDevice = None): + def __init__(self, arn: str, device_properties: DeviceCapabilities): super().__init__() self._arn = arn - device_properties = (aws_device or AwsDevice(self._arn)).properties + device_properties = device_properties or AwsDevice(self._arn).properties self._gate_calibration_data = self._setup_device_calibration_data(device_properties) self._setup_basic_noise_model_strategy() From 74fecd0ee6f0f17d3eedd03878da30d1dabbaece Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 17 Jun 2024 23:37:21 -0700 Subject: [PATCH 24/91] fix: Remove stray print statement --- .../emulators/emulator_passes/criteria/connectivity_criterion.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py index 76d15ed25..599e5c94c 100644 --- a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py @@ -77,7 +77,6 @@ def validate(self, circuit: Circuit) -> None: def validate_instruction_connectivity(self, control_qubits: QubitSet, target_qubits: QubitSet): #Create edges between each of the target qubits gate_connectivity_graph = DiGraph() - print(control_qubits, target_qubits) #Create an edge from each control bit to each target qubit if len(control_qubits) == 1 and len(target_qubits) == 1: gate_connectivity_graph.add_edge(control_qubits[0], target_qubits[0]) From 28e79bc3a04540ddf66aef73afaeeb71b867b584 Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 17 Jun 2024 23:38:48 -0700 Subject: [PATCH 25/91] feat: Introduce emulators and instantiate emulators within AWS devices using QPU device properties --- src/braket/aws/aws_device.py | 29 +++++ src/braket/aws/aws_emulator_helpers.py | 174 +++++++++++++++++++++++++ src/braket/emulators/__init__.py | 1 + src/braket/emulators/emulator.py | 80 ++++++++++++ 4 files changed, 284 insertions(+) create mode 100644 src/braket/aws/aws_emulator_helpers.py create mode 100644 src/braket/emulators/__init__.py create mode 100644 src/braket/emulators/emulator.py diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 041098f5a..ff1fe1c48 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -49,6 +49,14 @@ from braket.schema_common import BraketSchemaBase +from braket.emulators import Emulator +from braket.aws.aws_emulator_helpers import ( + create_supported_gate_criterion, + create_native_gate_criterion, + create_connectivity_criterion, + create_gate_connectivity_criterion +) + class AwsDeviceType(str, Enum): """Possible AWS device types""" @@ -855,3 +863,24 @@ def _parse_calibration_json( parsed_calibration_data[gate_qubit_key] = gate_qubit_pulse return parsed_calibration_data + + @property + def emulator(self) -> Emulator: + if not hasattr(self, "_emulator"): + self._emulator = self._setup_emulator() + return 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). + """ + emulator_noise_model = None + self._emulator = Emulator(noise_model=emulator_noise_model) + + self._emulator.add_pass(create_supported_gate_criterion(self.properties)) + self._emulator.add_pass(create_native_gate_criterion(self.properties)) + self._emulator.add_pass(create_connectivity_criterion(self.properties, self.topology_graph)) + self._emulator.add_pass(create_gate_connectivity_criterion(self.properties, self.topology_graph)) + return self._emulator \ No newline at end of file diff --git a/src/braket/aws/aws_emulator_helpers.py b/src/braket/aws/aws_emulator_helpers.py new file mode 100644 index 000000000..011469f01 --- /dev/null +++ b/src/braket/aws/aws_emulator_helpers.py @@ -0,0 +1,174 @@ +from braket.emulators import Emulator +from braket.emulators.emulator_passes import ( + SupportedGateCriterion, + NativeGateCriterion, + ConnectivityCriterion, + GateConnectivityCriterion +) +from braket.device_schema import ( + DeviceActionType, + StandardizedGateModelQpuDeviceProperties, + DeviceCapabilities +) +from braket.device_schema.ionq import IonqDeviceCapabilities +from braket.device_schema.rigetti import RigettiDeviceCapabilities +from braket.device_schema.iqm import IqmDeviceCapabilities + +from functools import singledispatch +from typing import Union +from collections.abc import Iterable +from networkx import DiGraph + + + +def create_supported_gate_criterion(properties: DeviceCapabilities) -> SupportedGateCriterion: + supported_gates = properties.action[DeviceActionType.OPENQASM].supportedOperations + """TODO: Issue in IQM Garnet Supported Operations: Includes "startVerbatimBox" and "endVerbatimBox" instructions in supported operations, + which are braket specific pragmas. Filter out explicitly until they are removed from device properties.""" + + if isinstance(properties, IqmDeviceCapabilities): + try: + supported_gates.remove("start_verbatim_box") + supported_gates.remove("end_verbatim_box") + except ValueError: + pass + + supported_gates_criterion = SupportedGateCriterion(supported_gates) + return supported_gates_criterion + + +def create_native_gate_criterion(properties: DeviceCapabilities) -> NativeGateCriterion: + native_gates = properties.paradigm.nativeGateSet + native_gate_criterion = NativeGateCriterion(native_gates) + return native_gate_criterion + + +@singledispatch +def create_connectivity_criterion(properties: DeviceCapabilities, connectivity_graph: DiGraph) -> ConnectivityCriterion: + connectivity_criterion = ConnectivityCriterion(connectivity_graph) + return connectivity_criterion + +@create_connectivity_criterion.register(IqmDeviceCapabilities) +def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> ConnectivityCriterion: + """ + 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. + """ + connectivity_graph = connectivity_graph.copy() + for edge in connectivity_graph.edges: + connectivity_graph.add_edge(edge[1], edge[0]) + return ConnectivityCriterion(connectivity_graph) + +@singledispatch +def create_gate_connectivity_criterion(properties, connectivity_graph: DiGraph) -> GateConnectivityCriterion: + raise NotImplementedError + +@create_gate_connectivity_criterion.register(RigettiDeviceCapabilities) +def _(properties: RigettiDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: + print("Creating Rigetti Gate Connecitivity") + """ + Rigetti provides device capabilities using a standardized properties schema for gate devices. + + Rigetti provides both forwards and backwards edges for their undirected gate connectivity graph, so + no new needs to be introduced when creating a GateConnectivityCriterion object for a Rigetti QPU. + """ + gate_connectivity_graph = connectivity_graph.copy() + edge_properties = properties.standardized.twoQubitProperties + for edge in gate_connectivity_graph.edges: + edge_key = "-".join([str(qubit) for qubit in edge]) + edge_property = edge_properties.get(edge_key, list()) + if not edge_property: + continue + edge_supported_gates = get_qpu_gate_translation(properties, + [property.gateName for property in edge_property.twoQubitGateFidelity]) + gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] = edge_supported_gates + + return GateConnectivityCriterion(gate_connectivity_graph) + + +@create_gate_connectivity_criterion.register(IqmDeviceCapabilities) +def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: + print("Creating IQM Gate Connecitivity") + """ + IQM provides device capabilities using a standardized properties schema for gate devices. + + IQM provides only forward edges for their *undirected* gate connectivity graph, so back-edges must + be introduced when creating the GateConnectivityCriterion object for an IQM QPU. + """ + gate_connectivity_graph = connectivity_graph.copy() + for edge in gate_connectivity_graph.edges: + gate_connectivity_graph.add_edge(edge[1], edge[0]) + + edge_properties = properties.standardized.twoQubitProperties + for edge_property in edge_properties.keys(): + edge = [int(qubit) for qubit in edge_property.split("-")] + edge_supported_gates = get_qpu_gate_translation(properties, + [property.gateName for property in edge_properties[edge_property].twoQubitGateFidelity]) + gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] = edge_supported_gates + gate_connectivity_graph[edge[1]][edge[0]]["supported_gates"] = edge_supported_gates + + return GateConnectivityCriterion(gate_connectivity_graph) + + + +@create_gate_connectivity_criterion.register(IonqDeviceCapabilities) +def _(properties: IonqDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: + print("Creating IonQ Gate Connecitivity") + """ + Qubits in IonQ's trapped ion devices are all fully connected with identical gate-pair capabilities. + Thus, IonQ does not expliclty provide a set of edges for gate connectivity between qubit pairs in + their trapped ion QPUs. We extrapolate gate connectivity across all possible qubit edge pairs. + """ + gate_connectivity_graph = connectivity_graph.copy() + native_gates = get_qpu_gate_translation(properties, properties.paradigm.nativeGateSet) + for edge in gate_connectivity_graph.edges: + gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] = native_gates + + return GateConnectivityCriterion(gate_connectivity_graph) + +def get_qpu_gate_translation(properties: DeviceCapabilities, gate_name: Union[str, Iterable[str]]) -> Union[str, list[str]]: + """Returns the translated gate name(s) for a given QPU ARN and gate name(s). + + Args: + arn (str): The ARN of the QPU + gate_name (Union[str, Iterable[str]]): The name(s) of the gate(s) + + Returns: + Union[str, list[str]]: The translated gate name(s) + """ + if isinstance(gate_name, Iterable): + return [_get_qpu_gate_translation(properties, name) for name in gate_name] + return _get_qpu_gate_translation(properties, gate_name) + + +@singledispatch +def _get_qpu_gate_translation(properties, gate_name: str) -> str: + """Returns the translated gate name for a given QPU ARN and gate name. + + Args: + properties (str): QHP device properties type + gate_name (str): The name of the gate + + Returns: + str: The translated gate name + """ + return gate_name + + +#TODO: put translations in global dict with explicit QHP names as keys? + +@_get_qpu_gate_translation.register(RigettiDeviceCapabilities) +def _(properties: RigettiDeviceCapabilities, gate_name: str) -> str: + translations = { + "CPHASE": "CPhaseShift" + } + return translations.get(gate_name, gate_name) + +@_get_qpu_gate_translation.register(IonqDeviceCapabilities) +def _(properties: IonqDeviceCapabilities, gate_name: str) -> str: + translations = { + "GPI": "GPi", + "GPI2": "GPi2" + } + return translations.get(gate_name, gate_name) \ No newline at end of file diff --git a/src/braket/emulators/__init__.py b/src/braket/emulators/__init__.py new file mode 100644 index 000000000..4b9c350c8 --- /dev/null +++ b/src/braket/emulators/__init__.py @@ -0,0 +1 @@ +from braket.emulators.emulator import Emulator diff --git a/src/braket/emulators/emulator.py b/src/braket/emulators/emulator.py new file mode 100644 index 000000000..e62947028 --- /dev/null +++ b/src/braket/emulators/emulator.py @@ -0,0 +1,80 @@ +from __future__ import annotations +from typing import Iterable, Any, Optional, Union, Optional +from abc import abstractmethod +from braket.simulator import BraketSimulator +from braket.tasks import QuantumTask +from braket.devices.local_simulator import LocalSimulator +from braket.emulators.emulater_interface import EmulatorInterface +from braket.emulators.emulator_passes import EmulatorPass, ProgramType +from braket.devices import Device +from braket.circuits import Circuit +from braket.circuits.noise_model import NoiseModel +from braket.ir.openqasm import Program as OpenQasmProgram +from braket.tasks.quantum_task_batch import QuantumTaskBatch +import logging + +class Emulator(Device, EmulatorInterface): + """An emulator is a simulation device that more closely resembles the capabilities and constraints of a real + device or of a specific device model.""" + + def __init__(self, backend: Union[str, Device]="default", + noise_model: Optional[NoiseModel] = None, + emulator_passes: Iterable[EmulatorPass] = None, **kwargs): + + EmulatorInterface.__init__(self, emulator_passes) + self._noise_model = noise_model + if noise_model and backend == "default": + logging.warning("Setting LocalSimulator backend to use 'braket_dm' because a NoiseModel was provided.") + backend = "braket_dm" + + self._backend = LocalSimulator(backend=backend, noise_model=noise_model) + + + def run(self, + task_specification: Union[ + Circuit, + OpenQasmProgram, + ], + shots: int = 0, + inputs: Optional[dict[str, float]] = None, + dry_run=False, + *args: Any, + **kwargs: Any + ) -> QuantumTask: + """ + This method validates the input program against the emulator's passes and applies any provided noise model before + running the circuit. + """ + task_specification = self.run_program_passes(task_specification) + return self._backend.run(task_specification, shots, inputs, *args, **kwargs) + + + def run_batch( # noqa: C901 + self, + task_specifications: Union[ + Union[ + Circuit, OpenQasmProgram + ], + list[ + Union[ + Circuit, + OpenQasmProgram, + ] + ], + ], + shots: Optional[int] = 0, + max_parallel: Optional[int] = None, + inputs: Optional[Union[dict[str, float], list[dict[str, float]]]] = None, + *args, + **kwargs, + ) -> QuantumTaskBatch: + raise NotImplementedError("Emulator.run_batch() is not implemented yet.") + + @property + def noise_model(self): + return self._noise_model + + @noise_model.setter + def noise_model(self, noise_model: NoiseModel): + self._noise_model = noise_model + self._backend = LocalSimulator(backend="braket_dm", noise_model=noise_model) \ No newline at end of file From dc4a9cf879ca4f908b60381020b736d06747bf9f Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 18 Jun 2024 11:36:54 -0700 Subject: [PATCH 26/91] feat: Introduce Qubit Count Criteria --- src/braket/aws/aws_device.py | 2 ++ .../emulators/emulator_passes/__init__.py | 2 ++ .../emulator_passes/criteria/__init__.py | 1 + .../criteria/qubit_count_criterion.py | 17 +++++++++++++++++ 4 files changed, 22 insertions(+) create mode 100644 src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index ff1fe1c48..49425b8f4 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -51,6 +51,7 @@ from braket.emulators import Emulator from braket.aws.aws_emulator_helpers import ( + create_qubit_count_criterion, create_supported_gate_criterion, create_native_gate_criterion, create_connectivity_criterion, @@ -879,6 +880,7 @@ def _setup_emulator(self) -> Emulator: emulator_noise_model = None self._emulator = Emulator(noise_model=emulator_noise_model) + self._emulator.add_pass(create_supported_gate_criterion(self.properties)) self._emulator.add_pass(create_supported_gate_criterion(self.properties)) self._emulator.add_pass(create_native_gate_criterion(self.properties)) self._emulator.add_pass(create_connectivity_criterion(self.properties, self.topology_graph)) diff --git a/src/braket/emulators/emulator_passes/__init__.py b/src/braket/emulators/emulator_passes/__init__.py index bac3d7e00..41809f8ac 100644 --- a/src/braket/emulators/emulator_passes/__init__.py +++ b/src/braket/emulators/emulator_passes/__init__.py @@ -4,4 +4,6 @@ ConnectivityCriterion, SupportedGateCriterion, NativeGateCriterion, + GateConnectivityCriterion, + QubitCountCriterion ) \ No newline at end of file diff --git a/src/braket/emulators/emulator_passes/criteria/__init__.py b/src/braket/emulators/emulator_passes/criteria/__init__.py index 370cdc9e8..c69966f38 100644 --- a/src/braket/emulators/emulator_passes/criteria/__init__.py +++ b/src/braket/emulators/emulator_passes/criteria/__init__.py @@ -3,3 +3,4 @@ from braket.emulators.emulator_passes.criteria.supported_gate_criterion import SupportedGateCriterion from braket.emulators.emulator_passes.criteria.connectivity_criterion import ConnectivityCriterion from braket.emulators.emulator_passes.criteria.gate_connectivity_criterion import GateConnectivityCriterion +from braket.emulators.emulator_passes.criteria.qubit_count_criterion import QubitCountCriterion diff --git a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py new file mode 100644 index 000000000..f29949e3e --- /dev/null +++ b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py @@ -0,0 +1,17 @@ +from braket.emulators.emulator_passes import EmulatorCriterion, ProgramType +from braket.circuits import Circuit + + +class QubitCountCriterion(EmulatorCriterion): + """ + A simple criterion class that checks that an input program does not use more qubits + than available on a device, as set during this criterion's instantiation. + """ + def __init__(self, qubit_count: int): + self._qubit_count = qubit_count + + + def validate(self, circuit: Circuit) -> Circuit: + if circuit.qubit_count > self._qubit_count: + raise ValueError(f"Circuit must use at most {self._qubit_count} qubits, \ + but uses {circuit.qubit_count} qubits.") From 56e730e6c752bb6b4154b07edffe2cdf3b3657f8 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 18 Jun 2024 13:55:24 -0700 Subject: [PATCH 27/91] feat: Move NoiseModel generation to a set of helper functions and call inside AwsDevice --- src/braket/aws/aws_device.py | 8 +- src/braket/aws/aws_noise_models.py | 245 ++++++++++++++++++ .../gate_device_noise_model.py | 242 ----------------- 3 files changed, 251 insertions(+), 244 deletions(-) create mode 100644 src/braket/aws/aws_noise_models.py delete mode 100644 src/braket/emulators/device_noise_models/gate_device_noise_model.py diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 49425b8f4..ffc3f8c03 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -40,6 +40,7 @@ # TODO: Remove device_action module once this is added to init in the schemas repo from braket.device_schema.pulse.pulse_device_action_properties_v1 import PulseDeviceActionProperties from braket.devices.device import Device +from braket.devices import Devices from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram from braket.parametric.free_parameter import FreeParameter @@ -57,6 +58,7 @@ create_connectivity_criterion, create_gate_connectivity_criterion ) +from braket.aws.aws_noise_models import create_device_noise_model class AwsDeviceType(str, Enum): """Possible AWS device types""" @@ -867,6 +869,8 @@ def _parse_calibration_json( @property def emulator(self) -> Emulator: + if self._arn in Devices.Amazon: + raise ValueError("Creating an emulator from a Braket managed simulator is not supported.") if not hasattr(self, "_emulator"): self._emulator = self._setup_emulator() return self._emulator @@ -877,8 +881,8 @@ 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). """ - emulator_noise_model = None - self._emulator = Emulator(noise_model=emulator_noise_model) + emulator_noise_model = create_device_noise_model(self.properties, self._arn) + self._emulator = Emulator(noise_model=emulator_noise_model, backend="braket_dm") self._emulator.add_pass(create_supported_gate_criterion(self.properties)) self._emulator.add_pass(create_supported_gate_criterion(self.properties)) diff --git a/src/braket/aws/aws_noise_models.py b/src/braket/aws/aws_noise_models.py new file mode 100644 index 000000000..42190f020 --- /dev/null +++ b/src/braket/aws/aws_noise_models.py @@ -0,0 +1,245 @@ +from braket.circuits.noise_model import NoiseModel, GateCriteria, ObservableCriteria +from braket.circuits.noises import ( + AmplitudeDamping, + BitFlip, + Depolarizing, + PhaseDamping, + TwoQubitDepolarizing +) +from typing import Dict, Tuple, List, Set, Union +from braket.circuits import Gate +from braket.devices import Devices +from braket.aws.aws_emulator_helpers import get_qpu_gate_translation + +from braket.device_schema import ( + DeviceActionType, + StandardizedGateModelQpuDeviceProperties, + DeviceCapabilities +) +from braket.device_schema.ionq import IonqDeviceCapabilities +from braket.device_schema.rigetti import RigettiDeviceCapabilities +from braket.device_schema.iqm import IqmDeviceCapabilities +from braket.device_schema.standardized_gate_model_qpu_device_properties_v1 import ( + StandardizedGateModelQpuDeviceProperties, OneQubitProperties, GateFidelity2Q, Fidelity1Q +) + +import numpy as np +from dataclasses import dataclass +from functools import singledispatch +import logging + +""" + The following gate duration values are not available through Braket device calibration data and must + be hardcoded. +""" +QPU_GATE_DURATIONS = { + Devices.Rigetti.AspenM3: { + "single_qubit_gate_duration": 40e-9, + "two_qubit_gate_duration": 240e-9 + }, + Devices.IQM.Garnet: { + "single_qubit_gate_duration": 32e-9, + "two_qubit_gate_duration": 60e-9 + } +} + +@dataclass +class GateFidelity: + gate: Gate + fidelity:float + +@dataclass +class GateDeviceCalibrationData: + single_qubit_gate_duration: float + two_qubit_gate_duration: float + qubit_labels: Set[int] + single_qubit_specs: Dict[int, Dict[str, float]] + two_qubit_edge_specs: Dict[Tuple[int, int], List[GateFidelity]] + + def _validate_single_qubit_specs(self): + for qubit in self.single_qubit_specs.keys(): + if qubit not in self.qubit_labels: + raise ValueError(f"Invalid qubit label {qubit}") + + def _validate_two_qubit_specs(self): + for edge in self.two_qubit_edge_specs.keys(): + if edge[0] not in self.qubit_labels or edge[1] not in self.qubit_labels: + raise ValueError(f"Invalid qubit pair {edge}") + + def __post_init__(self): + self._validate_single_qubit_specs() + self._validate_two_qubit_specs() + + +def create_device_noise_model(properties: DeviceCapabilities, arn: str) -> NoiseModel: + device_calibration_data = _setup_calibration_specs(properties, arn) + noise_model = _setup_basic_noise_model_strategy(device_calibration_data) + return noise_model + +@singledispatch +def _setup_calibration_specs(properties, arn: str) -> NoiseModel: + raise NotImplementedError(f"A noise model cannot be created from device capabilities with \ + type {type(properties)}.") + + + +#Rigetti and IonQ provide calibration data in a standardized data structure that can be parsed generally. +@_setup_calibration_specs.register(RigettiDeviceCapabilities) +@_setup_calibration_specs.register(IqmDeviceCapabilities) +def _(properties: Union[RigettiDeviceCapabilities, IqmDeviceCapabilities], arn: str) -> NoiseModel: + gate_durations = QPU_GATE_DURATIONS.get(arn, None) + if not gate_durations: + raise ValueError(f"Gate durations are not available for device {arn}") + single_qubit_gate_duration = gate_durations["single_qubit_gate_duration"] + two_qubit_gate_duration = gate_durations["two_qubit_gate_duration"] + + standardized_properties = properties.standardized + one_qubit_properties = standardized_properties.oneQubitProperties + qubit_labels = set(int(qubit) for qubit in one_qubit_properties.keys()) + single_qubit_specs = {int(qubit): _create_qubit_specs(one_qubit_properties[qubit]) for qubit in one_qubit_properties.keys()} + + + two_qubit_properties = standardized_properties.twoQubitProperties + two_qubit_edge_specs = { + tuple(int(qubit) for qubit in edge.split("-")[0:2]): _create_edge_specs(properties, gate_fidelities.twoQubitGateFidelity) + for edge, gate_fidelities in two_qubit_properties.items()} + + return GateDeviceCalibrationData( + single_qubit_gate_duration, + two_qubit_gate_duration, + qubit_labels, + single_qubit_specs, + two_qubit_edge_specs + ) + + +@_setup_calibration_specs.register(IonqDeviceCapabilities) +def _(properties: IonqDeviceCapabilities, arn: str) -> NoiseModel: + """ + IonQ's Trapped Ion Devices do not have per-qubit calibration data and instead + provide averaged qubit and two-qubit gate fidelities across the device. All qubits + are connected in a trapped ion device. + + We instead copy the averaged fidelities to each qubit and qubit edge pair. + """ + calibration_data = properties.provider + fidelity_data = calibration_data.fidelity + timing_data = calibration_data.timing + qubit_count = properties.paradigm.qubitCount + native_gates = get_qpu_gate_translation(properties, properties.paradigm.nativeGateSet) + + single_qubit_gate_duration = timing_data["1Q"] + two_qubit_gate_duration = timing_data["2Q"] + average_active_reset_fidelity = timing_data["reset"] + average_T1 = timing_data["T1"] + average_T2 = timing_data["T2"] + single_qubit_rb_fidelity = fidelity_data["1Q"]["mean"] + two_qubit_rb_fidelity = fidelity_data["2Q"]["mean"] + average_readout_fidelity = fidelity_data["spam"]["mean"] + + native_gate_fidelities = [] + for native_gate in native_gates: + gate_name = native_gate + if hasattr(Gate, gate_name): + gate = getattr(Gate, gate_name) + if gate.fixed_qubit_count() != 2: + """ + The noise model applies depolarizing noise associated with the individual qubits themselves (RB/sRB fidelities). + This is a choice of this particular model to generalize the implementation as not all QHPs provide + single-qubit gate fidelities. + """ + continue + native_gate_fidelities.append(GateFidelity(gate, two_qubit_rb_fidelity)) + else: + logging.warning(f"Unsupported gate {native_gate}") + + single_qubit_specs = {} + two_qubit_edge_specs = {} + for ii in range(qubit_count): + qubit_spec = { + "RANDOMIZED_BENCHMARKING": single_qubit_rb_fidelity, + "SIMULTANEOUS_RANDOMIZED_BENCHMARKING": None, + "READOUT": average_readout_fidelity, + "T1": average_T1, + "T2": average_T2, + "ACTIVE_RESET": average_active_reset_fidelity + } + single_qubit_specs[ii] = qubit_spec + + for jj in range(ii + 1, qubit_count): + two_qubit_edge_specs[(ii, jj)] = native_gate_fidelities + + return GateDeviceCalibrationData( + single_qubit_gate_duration, + two_qubit_gate_duration, + set(range(qubit_count)), + single_qubit_specs, + two_qubit_edge_specs + ) + +def _create_qubit_specs(qubit_properties: OneQubitProperties) -> Dict[str, int]: + T1 = qubit_properties.T1.value + T2 = qubit_properties.T2.value + qubit_fidelities = qubit_properties.oneQubitFidelity + one_qubit_fidelities = { + qubit_fidelity.fidelityType.name : qubit_fidelity.fidelity for qubit_fidelity in qubit_fidelities + } + one_qubit_fidelities["T1"] = T1 + one_qubit_fidelities["T2"] = T2 + return one_qubit_fidelities + +def _create_edge_specs(properties: DeviceCapabilities, edge_properties: List[GateFidelity2Q]) -> List[GateFidelity]: + edge_specs = [] + for edge_property in edge_properties: + gate_name = get_qpu_gate_translation(properties, edge_property.gateName) + if hasattr(Gate, gate_name): + gate = getattr(Gate, gate_name) + edge_specs.append(GateFidelity(gate, edge_property.fidelity)) + else: + logging.warning(f"Unsupported gate {gate_name}") + return edge_specs + + +def _setup_basic_noise_model_strategy(gate_calibration_data: GateDeviceCalibrationData) -> NoiseModel: + """ + Apply a basic noise model strategy consisting of: + - T1 Dampening + - T2 Phase Dampening + - 1 Qubit RB Depolarizing Noise + - 1 Qubit Readout Error + - 2 Qubit Gate Depolarizing Noise + """ + noise_model = NoiseModel() + gate_duration_1Q = gate_calibration_data.single_qubit_gate_duration + gate_duration_2Q = gate_calibration_data.two_qubit_gate_duration + for qubit, data in gate_calibration_data.single_qubit_specs.items(): + #T1 dampening + T1 = data["T1"] + damping_prob = 1 - np.exp(-(gate_duration_1Q/T1)) + noise_model.add_noise(AmplitudeDamping(damping_prob), GateCriteria(qubits=qubit)) + + #T2 Phase Dampening + T2 = data["T2"] + dephasing_prob = 0.5 * (1 - np.exp(-(gate_duration_1Q/T2))) + noise_model.add_noise(PhaseDamping(dephasing_prob), GateCriteria(qubits=qubit)) + + #1 Qubit RB Depolarizing Noise + if "SIMULTANEOUS_RANDOMIZED_BENCHMARKING" in data: + benchmark_fidelity = data["SIMULTANEOUS_RANDOMIZED_BENCHMARKING"] + else: + benchmark_fidelity = data.get(["RANDOMIZED_BENCHMARKING"]) + if benchmark_fidelity: + depolarizing_rate = 1 - benchmark_fidelity + noise_model.add_noise(Depolarizing(depolarizing_rate), GateCriteria(qubits=qubit)) + + #1 Qubit Readout Error + readout_error_rate = 1 - data["READOUT"] + noise_model.add_noise(BitFlip(readout_error_rate), ObservableCriteria(qubits=qubit)) + + for edge, data in gate_calibration_data.two_qubit_edge_specs.items(): + for gate_fidelity in data: + rate = 1 - gate_fidelity.fidelity + gate = gate_fidelity.gate + noise_model.add_noise(TwoQubitDepolarizing(rate), GateCriteria(gate, [(edge[0], edge[1]), (edge[1], edge[0])])) + + return noise_model \ No newline at end of file diff --git a/src/braket/emulators/device_noise_models/gate_device_noise_model.py b/src/braket/emulators/device_noise_models/gate_device_noise_model.py deleted file mode 100644 index 48a5b77bd..000000000 --- a/src/braket/emulators/device_noise_models/gate_device_noise_model.py +++ /dev/null @@ -1,242 +0,0 @@ -from abc import ABC, abstractmethod -from braket.circuits.noise_model import GateCriteria, NoiseModel, ObservableCriteria -from typing import List -from dataclasses import dataclass -from typing import Dict, Tuple, List, Set, Optional -from braket.circuits import Gate -from braket.circuits.noise_model import NoiseModel -from braket.devices import Devices -from braket.aws import AwsDevice - -from braket.device_schema import DeviceCapabilities -from braket.device_schema.standardized_gate_model_qpu_device_properties_v1 import ( - StandardizedGateModelQpuDeviceProperties, OneQubitProperties, GateFidelity2Q, Fidelity1Q -) -import logging -from braket.circuits.noises import AmplitudeDamping, BitFlip, Depolarizing, PhaseDamping, TwoQubitDepolarizing - -import numpy as np - - -""" - The following gate duration values are not available through Braket device calibration data and must - be hardcoded. -""" -QPU_GATE_DURATIONS = { - Devices.Rigetti.AspenM3: { - "single_qubit_gate_duration": 40e-9, - "two_qubit_gate_duration": 240e-9 - }, - Devices.IQM.Garnet: { - "single_qubit_gate_duration": 32e-9, - "two_qubit_gate_duration": 60e-9 - } -} - -GATE_NAME_TRANSLATIONS = { - "CPHASE": "CPhaseShift", - "GPI": "GPi", - "GPI2": "GPi2" -} - - -@dataclass -class GateFidelity: - gate: Gate - fidelity:float - - -@dataclass -class GateDeviceCalibrationData: - single_qubit_gate_duration: float - two_qubit_gate_duration: float - qubit_labels: Set[int] - single_qubit_specs: Dict[int, Dict[str, float]] - two_qubit_edge_specs: Dict[Tuple[int, int], List[GateFidelity]] - - def _validate_single_qubit_specs(self): - for qubit in self.single_qubit_specs.keys(): - if qubit not in self.qubit_labels: - raise ValueError(f"Invalid qubit label {qubit}") - - def _validate_two_qubit_specs(self): - for edge in self.two_qubit_edge_specs.keys(): - if edge[0] not in self.qubit_labels or edge[1] not in self.qubit_labels: - raise ValueError(f"Invalid qubit pair {edge}") - - def __post_init__(self): - self._validate_single_qubit_specs() - self._validate_two_qubit_specs() - - -class GateDeviceNoiseModel(NoiseModel): - def __init__(self, arn: str, device_properties: DeviceCapabilities): - super().__init__() - self._arn = arn - device_properties = device_properties or AwsDevice(self._arn).properties - self._gate_calibration_data = self._setup_device_calibration_data(device_properties) - self._setup_basic_noise_model_strategy() - - def _setup_device_calibration_data(self, device_properties) -> GateDeviceCalibrationData: - if hasattr(device_properties, "standardized") and isinstance( - device_properties.standardized, StandardizedGateModelQpuDeviceProperties - ): - return self._setup_standardized_calibration_data(device_properties) - elif self._arn in Devices.IonQ: - return self._setup_ionq_device_calibration_data(device_properties) - - - def _setup_standardized_calibration_data(self, device_properties) -> GateDeviceCalibrationData: - gate_durations = QPU_GATE_DURATIONS.get(self._arn, None) - if not gate_durations: - raise ValueError(f"Gate durations are not available for device {self._arn}") - single_qubit_gate_duration = gate_durations["single_qubit_gate_duration"] - two_qubit_gate_duration = gate_durations["two_qubit_gate_duration"] - - standardized_properties = device_properties.standardized - one_qubit_properties = standardized_properties.oneQubitProperties - qubit_labels = set(int(qubit) for qubit in one_qubit_properties.keys()) - single_qubit_specs = {int(qubit): self._create_qubit_specs(one_qubit_properties[qubit]) for qubit in one_qubit_properties.keys()} - - - two_qubit_properties = standardized_properties.twoQubitProperties - two_qubit_edge_specs = { - tuple(int(qubit) for qubit in edge.split("-")[0:2]): self._create_edge_specs(gate_fidelities.twoQubitGateFidelity) - for edge, gate_fidelities in two_qubit_properties.items()} - return GateDeviceCalibrationData( - single_qubit_gate_duration, - two_qubit_gate_duration, - qubit_labels, - single_qubit_specs, - two_qubit_edge_specs - ) - - def _create_qubit_specs(self, qubit_properties: OneQubitProperties) -> Dict[str, int]: - T1 = qubit_properties.T1.value - T2 = qubit_properties.T2.value - qubit_fidelities = qubit_properties.oneQubitFidelity - one_qubit_fidelities = { - qubit_fidelity.fidelityType.name : qubit_fidelity.fidelity for qubit_fidelity in qubit_fidelities - } - one_qubit_fidelities["T1"] = T1 - one_qubit_fidelities["T2"] = T2 - return one_qubit_fidelities - - def _create_edge_specs(self, edge_properties: List[GateFidelity2Q]) -> List[GateFidelity]: - edge_specs = [] - for edge_property in edge_properties: - gate_name = GATE_NAME_TRANSLATIONS.get(edge_property.gateName, edge_property.gateName) - if hasattr(Gate, gate_name): - gate = getattr(Gate, gate_name) - edge_specs.append(GateFidelity(gate, edge_property.fidelity)) - else: - logging.warning(f"Unsupported gate {gate_name}") - return edge_specs - - - def _setup_ionq_device_calibration_data(self, device_properties): - """ - IonQ's Trapped Ion Devices do not have per-qubit calibration data and instead - provide averaged qubit and two-qubit gate fidelities across the device. All qubits - are connected in a trapped ion device. - - We instead copy the averaged fidelities to each qubit and qubit edge pair. - """ - calibration_data = device_properties.provider - fidelity_data = calibration_data.fidelity - timing_data = calibration_data.timing - qubit_count = device_properties.paradigm.qubitCount - native_gates = device_properties.paradigm.nativeGateSet - single_qubit_gate_duration = timing_data["1Q"] - two_qubit_gate_duration = timing_data["2Q"] - average_active_reset_fidelity = timing_data["reset"] - average_T1 = timing_data["T1"] - average_T2 = timing_data["T2"] - single_qubit_rb_fidelity = fidelity_data["1Q"]["mean"] - two_qubit_rb_fidelity = fidelity_data["2Q"]["mean"] - average_readout_fidelity = fidelity_data["spam"]["mean"] - - native_gate_fidelities = [] - for native_gate in native_gates: - gate_name = GATE_NAME_TRANSLATIONS.get(native_gate, native_gate) - if hasattr(Gate, gate_name): - gate = getattr(Gate, gate_name) - if gate.fixed_qubit_count() != 2: - """ - The noise model does not consider any single-qubit gate specific fidelities and instead - applies depolarizing noise associated with the individual qubits themselves (RB/sRB fidelities). - This is a choice of this particular model to simplify the implementation as not all QHPs provide - single-qubit gate fidelities. - """ - continue - native_gate_fidelities.append(GateFidelity(gate, two_qubit_rb_fidelity)) - else: - logging.warning(f"Unsupported gate {native_gate}") - - single_qubit_specs = {} - two_qubit_edge_specs = {} - for ii in range(qubit_count): - qubit_spec = { - "RANDOMIZED_BENCHMARKING": single_qubit_rb_fidelity, - "SIMULTANEOUS_RANDOMIZED_BENCHMARKING": None, - "READOUT": average_readout_fidelity, - "T1": average_T1, - "T2": average_T2, - "ACTIVE_RESET": average_active_reset_fidelity - } - single_qubit_specs[ii] = qubit_spec - - for jj in range(ii + 1, qubit_count): - two_qubit_edge_specs[(ii, jj)] = native_gate_fidelities - - return GateDeviceCalibrationData( - single_qubit_gate_duration, - two_qubit_gate_duration, - set(range(qubit_count)), - single_qubit_specs, - two_qubit_edge_specs - ) - - - def _setup_basic_noise_model_strategy(self): - """ - Apply a basic noise model strategy consisting of: - - T1 Dampening - - T2 Phase Dampening - - 1 Qubit RB Depolarizing Noise - - 1 Qubit Readout Error - - 2 Qubit Gate Depolarizing Noise - """ - gate_duration_1Q = self._gate_calibration_data.single_qubit_gate_duration - gate_duration_2Q = self._gate_calibration_data.two_qubit_gate_duration - for qubit, data in self._gate_calibration_data.single_qubit_specs.items(): - #T1 dampening - T1 = data["T1"] - damping_prob = 1 - np.exp(-(gate_duration_1Q/T1)) - self.add_noise(AmplitudeDamping(damping_prob), GateCriteria(qubits=qubit)) - - #T2 Phase Dampening - T2 = data["T2"] - dephasing_prob = 0.5 * (1 - np.exp(-(gate_duration_1Q/T2))) - self.add_noise(PhaseDamping(dephasing_prob), GateCriteria(qubits=qubit)) - - #1 Qubit RB Depolarizing Noise - if "SIMULTANEOUS_RANDOMIZED_BENCHMARKING" in data: - benchmark_fidelity = data["SIMULTANEOUS_RANDOMIZED_BENCHMARKING"] - else: - benchmark_fidelity = data.get(["RANDOMIZED_BENCHMARKING"]) - if benchmark_fidelity: - depolarizing_rate = 1 - benchmark_fidelity - self.add_noise(Depolarizing(depolarizing_rate), GateCriteria(qubits=qubit)) - - #1 Qubit Readout Error - readout_error_rate = 1 - data["READOUT"] - self.add_noise(BitFlip(readout_error_rate), ObservableCriteria(qubits=qubit)) - - for edge, data in self._gate_calibration_data.two_qubit_edge_specs.items(): - for gate_fidelity in data: - rate = 1 - gate_fidelity.fidelity - gate = gate_fidelity.gate - self.add_noise(TwoQubitDepolarizing(rate), GateCriteria(gate, [(edge[0], edge[1]), (edge[1], edge[0])])) - - \ No newline at end of file From cf0af85bf568a23a2275291ae1f1f6671e754607 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 18 Jun 2024 14:04:14 -0700 Subject: [PATCH 28/91] fix: Clean Up Branch --- src/braket/aws/aws_emulator_helpers.py | 15 ++-- src/braket/emulators/aws_emulator.py | 71 ------------------- .../emulators/device_noise_models/__init__.py | 1 - src/braket/emulators/emulater_interface.py | 2 - src/braket/emulators/emulator.py | 2 +- .../criteria/native_gate_criterion.py | 2 +- .../criteria/qubit_count_criterion.py | 2 +- 7 files changed, 12 insertions(+), 83 deletions(-) delete mode 100644 src/braket/emulators/aws_emulator.py delete mode 100644 src/braket/emulators/device_noise_models/__init__.py diff --git a/src/braket/aws/aws_emulator_helpers.py b/src/braket/aws/aws_emulator_helpers.py index 011469f01..76a99b296 100644 --- a/src/braket/aws/aws_emulator_helpers.py +++ b/src/braket/aws/aws_emulator_helpers.py @@ -3,7 +3,8 @@ SupportedGateCriterion, NativeGateCriterion, ConnectivityCriterion, - GateConnectivityCriterion + GateConnectivityCriterion, + QubitCountCriterion ) from braket.device_schema import ( DeviceActionType, @@ -36,6 +37,10 @@ def create_supported_gate_criterion(properties: DeviceCapabilities) -> Supported supported_gates_criterion = SupportedGateCriterion(supported_gates) return supported_gates_criterion + +def create_qubit_count_criterion(properties: DeviceCapabilities) -> QubitCountCriterion: + qubit_count = properties.paradigm.qubitCount + return QubitCountCriterion(qubit_count) def create_native_gate_criterion(properties: DeviceCapabilities) -> NativeGateCriterion: native_gates = properties.paradigm.nativeGateSet @@ -66,7 +71,6 @@ def create_gate_connectivity_criterion(properties, connectivity_graph: DiGraph) @create_gate_connectivity_criterion.register(RigettiDeviceCapabilities) def _(properties: RigettiDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: - print("Creating Rigetti Gate Connecitivity") """ Rigetti provides device capabilities using a standardized properties schema for gate devices. @@ -89,7 +93,6 @@ def _(properties: RigettiDeviceCapabilities, connectivity_graph: DiGraph) -> Gat @create_gate_connectivity_criterion.register(IqmDeviceCapabilities) def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: - print("Creating IQM Gate Connecitivity") """ IQM provides device capabilities using a standardized properties schema for gate devices. @@ -114,7 +117,6 @@ def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> GateCon @create_gate_connectivity_criterion.register(IonqDeviceCapabilities) def _(properties: IonqDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: - print("Creating IonQ Gate Connecitivity") """ Qubits in IonQ's trapped ion devices are all fully connected with identical gate-pair capabilities. Thus, IonQ does not expliclty provide a set of edges for gate connectivity between qubit pairs in @@ -137,9 +139,10 @@ def get_qpu_gate_translation(properties: DeviceCapabilities, gate_name: Union[st Returns: Union[str, list[str]]: The translated gate name(s) """ - if isinstance(gate_name, Iterable): + if isinstance(gate_name, str): + return _get_qpu_gate_translation(properties, gate_name) + else: return [_get_qpu_gate_translation(properties, name) for name in gate_name] - return _get_qpu_gate_translation(properties, gate_name) @singledispatch diff --git a/src/braket/emulators/aws_emulator.py b/src/braket/emulators/aws_emulator.py deleted file mode 100644 index 1bd91e9de..000000000 --- a/src/braket/emulators/aws_emulator.py +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -from braket.emulators.emulater_interface import EmulatorInterface -from braket.circuits.noise_model import NoiseModel -from braket.aws import AwsDevice, AwsSession -from braket.emulators.emulator_passes import ( - EmulatorCriterion, - NativeGateCriterion, - SupportedGateCriterion, - ConnectivityCriterion -) -from braket.emulators.device_noise_models import GateDeviceNoiseModel -from braket.device_schema import DeviceActionType -from braket.device_schema import ( - DeviceCapabilities, - DeviceConnectivity, - DeviceActionType -) - - -from typing import Optional - - -class AwsEmulator(AwsDevice, EmulatorInterface): - """ - An emulator whose structure and constraints and defined by an AWS Braket Device. An AWS Emulator is created by passing a valid device ARN. - Device metadata is used to instantiate the emulator criteria. - """ - - def __init__(self, - arn: str, - backend: str = "default", - aws_session: Optional[AwsSession] = None, - noise_model: Optional[NoiseModel] = None - ): - EmulatorInterface.__init__(self) - AwsDevice.__init__(self, arn, aws_session, noise_model) - self._backend = backend - self._initialize_gate_set_criteria() - self._initialize_connectivity_criteria() - self._initialize_emulator_noise_model() - - - - def _initialize_gate_set_criteria(self): - """ - Initializes the emulator to support only the gate set and native gate set supported by the target device. - args: - aws_device (AwsDevice): The Braket AwsDevice from which to copy the supported and native gate sets from. - """ - - #create native gate criterion to validate gates inside of a verbatim box - native_gates = self.properties.paradigm.nativeGateSet - native_gate_criterion = NativeGateCriterion(native_gates) - self.add_pass(native_gate_criterion) - - #create supported gate criterion to validate gates outside of a verbatim box - supported_gates = self.properties.action[DeviceActionType.OPENQASM].supportedOperations - supported_gates_criterion = SupportedGateCriterion(supported_gates) - self.add_pass(supported_gates_criterion) - - - def _initialize_connectivity_criteria(self): - self.add_pass(ConnectivityCriterion(self.topology_graph)) - - def _initialize_emulator_noise_model(self): - emulator_noise_model = GateDeviceNoiseModel(self._arn, self.properties) - self._emulator_noise_model = emulator_noise_model - - - \ No newline at end of file diff --git a/src/braket/emulators/device_noise_models/__init__.py b/src/braket/emulators/device_noise_models/__init__.py deleted file mode 100644 index 89f824650..000000000 --- a/src/braket/emulators/device_noise_models/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from braket.emulators.device_noise_models.gate_device_noise_model import GateDeviceNoiseModel \ No newline at end of file diff --git a/src/braket/emulators/emulater_interface.py b/src/braket/emulators/emulater_interface.py index 1a2fc80fb..c9a44587c 100644 --- a/src/braket/emulators/emulater_interface.py +++ b/src/braket/emulators/emulater_interface.py @@ -40,6 +40,4 @@ def add_pass(self, emulator_pass: Union[Iterable[EmulatorPass], EmulatorPass]) - elif isinstance(emulator_pass, EmulatorPass): self._emulator_passes.append(emulator_pass) else: - print(isinstance(emulator_pass, EmulatorPass)) - print(type(emulator_pass)) raise TypeError("emulator_pass must be an EmulatorPass or an iterable of EmulatorPass") \ No newline at end of file diff --git a/src/braket/emulators/emulator.py b/src/braket/emulators/emulator.py index e62947028..96d4c3330 100644 --- a/src/braket/emulators/emulator.py +++ b/src/braket/emulators/emulator.py @@ -24,7 +24,7 @@ def __init__(self, backend: Union[str, Device]="default", EmulatorInterface.__init__(self, emulator_passes) self._noise_model = noise_model if noise_model and backend == "default": - logging.warning("Setting LocalSimulator backend to use 'braket_dm' because a NoiseModel was provided.") + logging.info("Setting LocalSimulator backend to use 'braket_dm' because a NoiseModel was provided.") backend = "braket_dm" self._backend = LocalSimulator(backend=backend, noise_model=noise_model) diff --git a/src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py index e3278495c..a0ec4cbc6 100644 --- a/src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py @@ -17,7 +17,7 @@ def __init__(self, native_gates: Iterator[str]): if len(native_gates) == 0: raise ValueError("At least one native gate must be provided.") try: - self._native_gates = set(BRAKET_GATES[gate] for gate in native_gates) + 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.") diff --git a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py index f29949e3e..367ccb5da 100644 --- a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py @@ -1,4 +1,4 @@ -from braket.emulators.emulator_passes import EmulatorCriterion, ProgramType +from braket.emulators.emulator_passes.criteria import EmulatorCriterion from braket.circuits import Circuit From b14cf127f89510223bfddff0b0801361d86155e5 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 18 Jun 2024 21:49:43 -0700 Subject: [PATCH 29/91] feat: Complete tket_to_qasm3 translations --- .../pytket_translator/composed_gates.py | 12 -------- .../pytket_program_context.py | 4 +-- .../qasm3_gen/qasm_context.py | 6 ++-- .../qasm3_gen/tket_to_qasm3.py | 29 +++++++++++-------- .../pytket_translator/translations.py | 14 +++------ 5 files changed, 26 insertions(+), 39 deletions(-) diff --git a/src/braket/emulators/pytket_translator/composed_gates.py b/src/braket/emulators/pytket_translator/composed_gates.py index 5a96239e1..7aa93e440 100644 --- a/src/braket/emulators/pytket_translator/composed_gates.py +++ b/src/braket/emulators/pytket_translator/composed_gates.py @@ -37,15 +37,3 @@ def add_cphaseshift10(circ: Circuit, arguments, qubits: List[int]): circ.add_gate(OpType.CX, [qubits[0], qubits[1]]) circ.add_gate(OpType.CU1, arguments, [qubits[0], qubits[1]]) circ.add_gate(OpType.CX, [qubits[0], qubits[1]]) - - @staticmethod - def add_cphaseshift(circ: Circuit, arguments, qubits: List[int]): - assert len(arguments) == 1 - assert len(qubits) == 2 - circ.add_gate(OpType.CU1, arguments, [qubits[0], qubits[1]]) - - @staticmethod - def add_prx(circ: Circuit, arguments, qubits: List[int]): - assert len(arguments) == 2 - assert len(qubits) == 1 - circ.add_gate(OpType.PhasedX, arguments, qubits) \ No newline at end of file diff --git a/src/braket/emulators/pytket_translator/pytket_program_context.py b/src/braket/emulators/pytket_translator/pytket_program_context.py index ecf221aea..7da36b71b 100644 --- a/src/braket/emulators/pytket_translator/pytket_program_context.py +++ b/src/braket/emulators/pytket_translator/pytket_program_context.py @@ -48,11 +48,11 @@ def add_gate_instruction( if gate_name in QASM_TO_PYTKET: op = QASM_TO_PYTKET[gate_name] if len(params) > 0: - self._circuit.add_gate(op, *params, target) + self._circuit.add_gate(op, params, target) else: self._circuit.add_gate(op, target) elif gate_name in COMPOSED_GATES: - COMPOSED_GATES[gate_name](self._circuit, *params, target) + COMPOSED_GATES[gate_name](self._circuit, params, target) else: raise ValueError(f"Gate {gate_name} is not supported in pytket translations.") diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py b/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py index 4ced105ea..628678981 100644 --- a/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py +++ b/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py @@ -12,7 +12,7 @@ class QasmContext: - def __init__(self, input_parameters: Dict[str, str]): + def __init__(self, input_parameters: Dict[str, str]=dict()): self.input_parameters = input_parameters self.num_bits: int = 0 self.gates: List[Gate] = [] @@ -23,12 +23,12 @@ def set_num_bits(self, num_bits: int) -> None: self.num_bits = num_bits def add_gate(self, name: str, args: List[Union[Expr, Symbol]], qubits: List[int]): - print("Adding gate: ", name, args, qubits) self.gates.append(Gate(name, args, qubits)) def add_measurement(self, qubit: int, cbit: int): - print("Adding measurement: ", qubit, cbit) self.measurements.append(Measurement(qubit, f"{MEASUREMENT_REGISTER_NAME}[{cbit}]")) + def add_parameter(self, param_name: str, param_type: str): + self.input_parameters[param_name] = param_type \ No newline at end of file diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py b/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py index bfe07f7a1..5d52a1de4 100644 --- a/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py +++ b/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py @@ -1,5 +1,5 @@ from braket.emulators.pytket_translator import PYTKET_TO_QASM -from pytket.circuit import Circuit, OpType, Node +from pytket.circuit import Circuit, OpType, Command, Node from sympy import Expr, pi, Symbol from typing import Dict, Union, List, Set, Optional from dataclasses import dataclass @@ -15,15 +15,15 @@ class Qasm3: def tket_to_qasm3( circuit: Circuit, - input_parameters: Dict[str, str]=None, - gate_overrides: Dict[OpType, str]=None + input_parameters: Dict[str, str]=dict(), + gate_overrides: Dict[OpType, str]=dict() ) -> Qasm3: ticket_visitor = TketCircuitVisitor(QasmContext(input_parameters), gate_overrides) ticket_visitor.walk_circuit(circuit) return ticket_visitor.context class TketCircuitVisitor: - def __init__(self, context, gate_overrides): + def __init__(self, context, gate_overrides: Dict[OpType, str]=dict()): self.context = context self.gate_overrides = gate_overrides self._measured_nodes: Set[Node] = set() @@ -34,7 +34,7 @@ def walk_circuit(self, circuit: Circuit): for command in circuit: self._visit_command(command) - def _visit_command(self, command: Node): + def _visit_command(self, command: Command): op = command.op self._validate_args_not_measured(command.args) optype = op.type @@ -54,15 +54,15 @@ def _validate_args_not_measured(self, args): ) - def _visit_box(self, command: Node, optype): + def _visit_box(self, command: Command, optype): circ = command.op.get_circuit() for command in circ: self._visit_command(command) - def _visit_measure(self, command: Node, optype): + def _visit_measure(self, command: Command, optype): qubit_node = command.args[0] qubit = qubit_node.index[0] - cbit = command.args[1].index + cbit = command.args[1].index[0] self.context.add_measurement(qubit, cbit) self._measured_nodes.add(qubit_node) @@ -74,7 +74,7 @@ def _visit_measure(self, command: Node, optype): # self._visit_gate(gate_name, command.op.params, command.args) - def _visit_gate(self, command: Node, optype): + def _visit_gate(self, command: Command, optype): """ Check to see if this operation is a gate known by OpenQASM3.0; if it is, retrieve the appropriate translation and add the operation to the context. @@ -89,9 +89,14 @@ def _visit_gate(self, command: Node, optype): qubits = command.args params = command.op.params - print("args: ", params) - print("qubits: ", qubits) - + + + #Look for any free parameters and add them to the context for initialization + for param in params: + if isinstance(param, Expr): + for symbol in param.free_symbols: + if symbol != pi: + self.context.add_parameter(str(symbol), "float") params = self._gate_angles_in_radians(params) qubits = [q.index[0] for q in qubits] self.context.add_gate(gate_name, params, qubits) diff --git a/src/braket/emulators/pytket_translator/translations.py b/src/braket/emulators/pytket_translator/translations.py index d6b486a15..b394b3931 100644 --- a/src/braket/emulators/pytket_translator/translations.py +++ b/src/braket/emulators/pytket_translator/translations.py @@ -41,26 +41,20 @@ "unitary": OpType.U3, "gpi": OpType.GPI, "gpi2": OpType.GPI2, - "ms": OpType.AAMS + "ms": OpType.AAMS, + "cphaseshift": OpType.CU1, + "prx": OpType.PhasedX } COMPOSED_GATES = { - "cphaseshift": ComposedGates.add_cphaseshift, "cphaseshift00": ComposedGates.add_cphaseshift00, "cphaseshift01": ComposedGates.add_cphaseshift01, "cphaseshift10": ComposedGates.add_cphaseshift10, - "pswap": ComposedGates.add_pswap, - "prx": ComposedGates.add_prx, + "pswap": ComposedGates.add_pswap } """ Pytket to OpenQASM-3.0 Name Translations """ PYTKET_TO_QASM = {optype: qasm_name for qasm_name, optype in QASM_TO_PYTKET.items()} - -# For gates which have multiple valid OpenQASM names, like "cx" and "CX", we overwrite -# the values to make sure we use the preferred name. -PYTKET_TO_QASM[OpType.CX] = "cx" # prefer over "CX" -PYTKET_TO_QASM[OpType.U3] = "u3" # prefer over "U" -PYTKET_TO_QASM[OpType.Rz] = "rz" # prefer over "Rz" \ No newline at end of file From 0ecd0fad958e17f74ae661adcc4e41c209f5dbd3 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 18 Jun 2024 22:46:20 -0700 Subject: [PATCH 30/91] fix: Add tket_to_qasm3 to pytket_translator module __init__.py --- src/braket/emulators/pytket_translator/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/braket/emulators/pytket_translator/__init__.py b/src/braket/emulators/pytket_translator/__init__.py index bcd35c2b3..a1c9cbcdb 100644 --- a/src/braket/emulators/pytket_translator/__init__.py +++ b/src/braket/emulators/pytket_translator/__init__.py @@ -3,4 +3,5 @@ COMPOSED_GATES, QASM_TO_PYTKET ) -from braket.emulators.pytket_translator.pytket_program_context import PytketProgramContext \ No newline at end of file +from braket.emulators.pytket_translator.pytket_program_context import PytketProgramContext +from braket.emulators.pytket_translator.qasm3_gen import tket_to_qasm3 \ No newline at end of file From bc19df56e2ed70a91abe6552c29473288210c02a Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 18 Jun 2024 22:46:43 -0700 Subject: [PATCH 31/91] feat: Introduce LexiRoutingPass --- .../emulator_passes/lexi_routing_pass.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/braket/emulators/emulator_passes/lexi_routing_pass.py diff --git a/src/braket/emulators/emulator_passes/lexi_routing_pass.py b/src/braket/emulators/emulator_passes/lexi_routing_pass.py new file mode 100644 index 000000000..09f109ba0 --- /dev/null +++ b/src/braket/emulators/emulator_passes/lexi_routing_pass.py @@ -0,0 +1,49 @@ +from braket.emulators.emulator_passes import EmulatorPass +from braket.circuits import Circuit +from braket.ir.openqasm import Program as OpenQasmProgram +from braket.emulators.pytket_translator import PytketProgramContext, tket_to_qasm3 +from braket.circuits.serialization import IRType +from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager +from pytket.architecture import Architecture +from typing import Union, Dict +from collections.abc import Iterable +from networkx import DiGraph +from braket.default_simulator.openqasm.interpreter import Interpreter +from functools import singledispatchmethod + +class LexiRoutingPass(EmulatorPass): + def __init__(self, hardware_topology: Union[Dict[int, Iterable[int]], DiGraph]): + super().__init__() + self._mapping_manager = MappingManager(self._get_architecture(hardware_topology)) + self._lexi_label = LexiLabellingMethod() + self._lexi_route = LexiRouteRoutingMethod() + + + def _get_architecture(self, hardware_topology: Union[Dict[int, Iterable[int]], DiGraph]): + if isinstance(hardware_topology, dict): + edge_list = [(q1, q2) for q1, edges in hardware_topology.items() for q2 in edges] + elif isinstance(hardware_topology, DiGraph): + edge_list = list(hardware_topology.edges) + + return Architecture(edge_list) + + + + @singledispatchmethod + def run(self, task_specification): + raise NotImplementedError(f"LexiRoutingPass does not support task specification type: {type(task_specification)}") + + + @run.register + def _(self, task_specification: Circuit) -> Circuit: + open_qasm_program = task_specification.to_ir(ir_type=IRType.OPENQASM) + mapped_open_qasm_program = self.run(open_qasm_program) + resulting_circuit = Circuit.from_ir(mapped_open_qasm_program) + return resulting_circuit + + @run.register + def _(self, task_specification: OpenQasmProgram) -> OpenQasmProgram: + pytket_circuit = Interpreter(PytketProgramContext()).build_circuit(task_specification.source) + self._mapping_manager.route_circuit(pytket_circuit, [self._lexi_label, self._lexi_route]) + open_qasm_program_string = tket_to_qasm3(pytket_circuit) + return OpenQasmProgram(source=open_qasm_program_string) \ No newline at end of file From 7c1ac20dd2e5853cec39099a827ed9fca2ca35fb Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 19 Jun 2024 10:21:45 -0700 Subject: [PATCH 32/91] feat: Split out mapping into a run() dispatch with a pytket circuit as the leading argument --- .../emulators/emulator_passes/lexi_routing_pass.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/braket/emulators/emulator_passes/lexi_routing_pass.py b/src/braket/emulators/emulator_passes/lexi_routing_pass.py index 09f109ba0..d8837fe4e 100644 --- a/src/braket/emulators/emulator_passes/lexi_routing_pass.py +++ b/src/braket/emulators/emulator_passes/lexi_routing_pass.py @@ -5,6 +5,7 @@ from braket.circuits.serialization import IRType from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager from pytket.architecture import Architecture +from pytket import Circuit as PytketCircuit from typing import Union, Dict from collections.abc import Iterable from networkx import DiGraph @@ -44,6 +45,11 @@ def _(self, task_specification: Circuit) -> Circuit: @run.register def _(self, task_specification: OpenQasmProgram) -> OpenQasmProgram: pytket_circuit = Interpreter(PytketProgramContext()).build_circuit(task_specification.source) - self._mapping_manager.route_circuit(pytket_circuit, [self._lexi_label, self._lexi_route]) + pytket_circuit = self.run(pytket_circuit) open_qasm_program_string = tket_to_qasm3(pytket_circuit) - return OpenQasmProgram(source=open_qasm_program_string) \ No newline at end of file + return OpenQasmProgram(source=open_qasm_program_string) + + @run.register + def _(self, task_specification: PytketCircuit) -> PytketCircuit: + self._mapping_manager.route_circuit(task_specification, [self._lexi_label, self._lexi_route]) + return task_specification From ad202d73ca910625ade59c5facfe4e68133a0235 Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 19 Jun 2024 10:26:57 -0700 Subject: [PATCH 33/91] fix: Add LexiRoutingPass to emulator_passes module __init__.py --- src/braket/emulators/emulator_passes/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/braket/emulators/emulator_passes/__init__.py b/src/braket/emulators/emulator_passes/__init__.py index 41809f8ac..efff82e15 100644 --- a/src/braket/emulators/emulator_passes/__init__.py +++ b/src/braket/emulators/emulator_passes/__init__.py @@ -6,4 +6,5 @@ NativeGateCriterion, GateConnectivityCriterion, QubitCountCriterion -) \ No newline at end of file +) +from braket.emulators.emulator_passes.lexi_routing_pass import LexiRoutingPass \ No newline at end of file From 50c53cd44aab7c8598e3d34117cc77d580a26335 Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 19 Jun 2024 10:41:14 -0700 Subject: [PATCH 34/91] fix: Implement __eq__ in qubit_count_criterion and fix error print statement formatting. --- .../emulator_passes/criteria/qubit_count_criterion.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py index 367ccb5da..3a843fdad 100644 --- a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py @@ -13,5 +13,8 @@ def __init__(self, qubit_count: int): def validate(self, circuit: Circuit) -> Circuit: if circuit.qubit_count > self._qubit_count: - raise ValueError(f"Circuit must use at most {self._qubit_count} qubits, \ - but uses {circuit.qubit_count} qubits.") + raise ValueError(f"Circuit must use at most {self._qubit_count} qubits, but uses {circuit.qubit_count} qubits.") + + def __eq__(self, other: EmulatorCriterion) -> bool: + return isinstance(other, QubitCountCriterion) and \ + self._qubit_count == other._qubit_count \ No newline at end of file From 10a1dee0a218d677c9fb4bb6d3db2a40fc1e7268 Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 19 Jun 2024 10:45:06 -0700 Subject: [PATCH 35/91] feat: AwsDevices add mapping/routing pass to their emulator based on connectivity graph --- src/braket/aws/aws_device.py | 5 ++++- src/braket/aws/aws_emulator_helpers.py | 28 ++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index ffc3f8c03..69d45591d 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -56,7 +56,8 @@ create_supported_gate_criterion, create_native_gate_criterion, create_connectivity_criterion, - create_gate_connectivity_criterion + create_gate_connectivity_criterion, + create_lexi_mapping_routing_pass ) from braket.aws.aws_noise_models import create_device_noise_model @@ -884,9 +885,11 @@ def _setup_emulator(self) -> Emulator: emulator_noise_model = create_device_noise_model(self.properties, self._arn) self._emulator = Emulator(noise_model=emulator_noise_model, backend="braket_dm") + self.emulator.add_pass(create_qubit_count_criterion(self.properties)) self._emulator.add_pass(create_supported_gate_criterion(self.properties)) self._emulator.add_pass(create_supported_gate_criterion(self.properties)) self._emulator.add_pass(create_native_gate_criterion(self.properties)) self._emulator.add_pass(create_connectivity_criterion(self.properties, self.topology_graph)) self._emulator.add_pass(create_gate_connectivity_criterion(self.properties, self.topology_graph)) + self._emulator.add_pass(create_lexi_mapping_routing_pass(self.properties, self.topology_graph)) return self._emulator \ No newline at end of file diff --git a/src/braket/aws/aws_emulator_helpers.py b/src/braket/aws/aws_emulator_helpers.py index 76a99b296..cabb76de1 100644 --- a/src/braket/aws/aws_emulator_helpers.py +++ b/src/braket/aws/aws_emulator_helpers.py @@ -4,7 +4,8 @@ NativeGateCriterion, ConnectivityCriterion, GateConnectivityCriterion, - QubitCountCriterion + QubitCountCriterion, + LexiRoutingPass ) from braket.device_schema import ( DeviceActionType, @@ -174,4 +175,27 @@ def _(properties: IonqDeviceCapabilities, gate_name: str) -> str: "GPI": "GPi", "GPI2": "GPi2" } - return translations.get(gate_name, gate_name) \ No newline at end of file + return translations.get(gate_name, gate_name) + + +@singledispatch +def create_lexi_mapping_routing_pass(properties: DeviceCapabilities, connectivity_graph: DiGraph) -> LexiRoutingPass: + raise NotImplementedError + + +@create_lexi_mapping_routing_pass.register(RigettiDeviceCapabilities) +@create_lexi_mapping_routing_pass.register(IonqDeviceCapabilities) +def _(properties: Union[RigettiDeviceCapabilities, IonqDeviceCapabilities], connectivity_graph: DiGraph) -> LexiRoutingPass: + return LexiRoutingPass(connectivity_graph) + +@create_lexi_mapping_routing_pass.register(IqmDeviceCapabilities) +def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> LexiRoutingPass: + """ + IQM provides only forward edges for their *undirected* gate connectivity graph, so back-edges must + be introduced when creating the GateConnectivityCriterion object for an IQM QPU. + """ + + connectivity_graph = connectivity_graph.copy() + for edge in connectivity_graph.edges: + connectivity_graph.add_edge(edge[1], edge[0]) + return LexiRoutingPass(connectivity_graph) \ No newline at end of file From 49809bad9801c70c1b4d46d8d7aeb94ddba7586c Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 19 Jun 2024 15:46:07 -0700 Subject: [PATCH 36/91] feat: Add validation and compile methods to AwsDevice --- src/braket/aws/aws_device.py | 70 +++++++++++++++++++++++++++--- src/braket/aws/aws_noise_models.py | 1 + 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 69d45591d..42ff95662 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -59,6 +59,7 @@ create_gate_connectivity_criterion, create_lexi_mapping_routing_pass ) +from braket.tasks import QuantumTask from braket.aws.aws_noise_models import create_device_noise_model class AwsDeviceType(str, Enum): @@ -877,19 +878,78 @@ def emulator(self) -> Emulator: return self._emulator - def _setup_emulator(self) -> Emulator: + def _setup_emulator(self, emulator_noise_model: NoiseModel = None) -> Emulator: """ Sets up an Emulator object whose properties mimic that of this AwsDevice, if the device is a real QPU (not simulated). """ - emulator_noise_model = create_device_noise_model(self.properties, self._arn) + if not emulator_noise_model: + emulator_noise_model = create_device_noise_model(self.properties, self._arn) self._emulator = Emulator(noise_model=emulator_noise_model, backend="braket_dm") - self.emulator.add_pass(create_qubit_count_criterion(self.properties)) - self._emulator.add_pass(create_supported_gate_criterion(self.properties)) + self._emulator.add_pass(create_qubit_count_criterion(self.properties)) self._emulator.add_pass(create_supported_gate_criterion(self.properties)) self._emulator.add_pass(create_native_gate_criterion(self.properties)) self._emulator.add_pass(create_connectivity_criterion(self.properties, self.topology_graph)) self._emulator.add_pass(create_gate_connectivity_criterion(self.properties, self.topology_graph)) self._emulator.add_pass(create_lexi_mapping_routing_pass(self.properties, self.topology_graph)) - return self._emulator \ No newline at end of file + return self._emulator + + + def validate(self, task_specification: Union[ + Circuit, + Problem, + OpenQasmProgram, + BlackbirdProgram, + PulseSequence, + AnalogHamiltonianSimulation, + ]): + """ + 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 the input program without modification. + """ + self.emulator.run_validation_passes(task_specification) + return task_specification + + + def run_emulator_passes(self, task_specification: Union[ + Circuit, + Problem, + OpenQasmProgram, + BlackbirdProgram, + PulseSequence, + AnalogHamiltonianSimulation, + ]): + """ + Runs all emulator passes and returns the modified program, which should be the same + type as the input program. + """ + if isinstance(task_specification, Circuit): + task_specification = task_specification.copy() + + return self.emulator.run_program_passes(task_specification) + + + def emulate( + self, + task_specification: Union[ + Circuit, + Problem, + OpenQasmProgram, + BlackbirdProgram, + PulseSequence, + AnalogHamiltonianSimulation, + ], + shots: Optional[int] = None, + inputs: Optional[dict[str, float]] = None, + gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, + ) -> QuantumTask: + + if isinstance(task_specification, Circuit): + task_specification = task_specification.copy() + + return self.emulator.run( + task_specification, shots=shots, inputs=inputs + ) + \ No newline at end of file diff --git a/src/braket/aws/aws_noise_models.py b/src/braket/aws/aws_noise_models.py index 42190f020..d6dae474f 100644 --- a/src/braket/aws/aws_noise_models.py +++ b/src/braket/aws/aws_noise_models.py @@ -151,6 +151,7 @@ def _(properties: IonqDeviceCapabilities, arn: str) -> NoiseModel: continue native_gate_fidelities.append(GateFidelity(gate, two_qubit_rb_fidelity)) else: + continue logging.warning(f"Unsupported gate {native_gate}") single_qubit_specs = {} From a3f845904b6049a8091e372ab17cc42c11b82601 Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 19 Jun 2024 15:46:50 -0700 Subject: [PATCH 37/91] fix: Update connectivity criterion to validate connectivity of ALL 2-qubit instructions if a verbatim box exists at all in the circuit --- .../criteria/connectivity_criterion.py | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py index 599e5c94c..c3a47365f 100644 --- a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py @@ -54,25 +54,19 @@ def validate(self, circuit: Circuit) -> None: Verifies that any verbatim box in a circuit is runnable with respect to the device connectivity definied by this criteria. """ + #If any of the instructions are in verbatim mode, all qubit references must point to hardware qubits. Otherwise, this circuit need not be validated. + if not any([isinstance(instruction.operator, StartVerbatimBox) for instruction in circuit.instructions]): + return for idx in range(len(circuit.instructions)): instruction = circuit.instructions[idx] - if isinstance(instruction.operator, StartVerbatimBox): - idx += 1 - while idx < len(circuit.instructions) and not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): - instruction = circuit.instructions[idx] - if isinstance(instruction.operator, Gate): - if instruction.operator.qubit_count == 2: #Assuming only maximum 2-qubit native gates are supported - self.validate_instruction_connectivity(instruction.control, instruction.target) - else: - #just check that the target qubit exists in the connectivity graph - target_qubit = instruction.target[0] - if not target_qubit in self._connectivity_graph: - raise ValueError(f"Qubit {target_qubit} does not exist in the device topology.") - idx += 1 - - if not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): - raise ValueError(f"No end verbatim box found at index {idx} in the circuit.") - idx += 1 + if isinstance(instruction.operator, Gate): + if instruction.operator.qubit_count == 2: #Assuming only maximum 2-qubit native gates are supported + self.validate_instruction_connectivity(instruction.control, instruction.target) + else: + #just check that the target qubit exists in the connectivity graph + target_qubit = instruction.target[0] + if not target_qubit in self._connectivity_graph: + raise ValueError(f"Qubit {target_qubit} does not exist in the device topology.") def validate_instruction_connectivity(self, control_qubits: QubitSet, target_qubits: QubitSet): #Create edges between each of the target qubits From 4c8039b9a2b9ce5848ca41b8b4770b9902f0d2d1 Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 19 Jun 2024 15:47:06 -0700 Subject: [PATCH 38/91] fix: Remove stray print statement --- .../emulator_passes/criteria/gate_connectivity_criterion.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py index a7a06dfc9..6908efe20 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py @@ -65,7 +65,6 @@ def validate_instruction_connectivity(self, gate_name: str, control_qubits: Qubi if not self._gate_connectivity_graph.has_edge(*e): raise ValueError(f"{e[0]} is not connected to {e[1]} on this device.") supported_gates = self._gate_connectivity_graph[e[0]][e[1]]["supported_gates"] - print(gate_name, supported_gates) if gate_name not in supported_gates: raise ValueError(f"Qubit pair ({e[0]}, {e[1]}) does not support gate {gate_name} on this device.") From d4bd18c888e4211c65868d7ff54017608796a340 Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 20 Jun 2024 14:11:21 -0700 Subject: [PATCH 39/91] fix: Add classical indices to add_measure instruction in Tket Context --- .../emulators/pytket_translator/pytket_program_context.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/braket/emulators/pytket_translator/pytket_program_context.py b/src/braket/emulators/pytket_translator/pytket_program_context.py index 7da36b71b..a039b4b02 100644 --- a/src/braket/emulators/pytket_translator/pytket_program_context.py +++ b/src/braket/emulators/pytket_translator/pytket_program_context.py @@ -8,6 +8,7 @@ ) from pytket.unit_id import Qubit, Bit from typing import Optional, Union, Any +from collections.abc import Iterable from sympy import Expr from braket.emulators.pytket_translator.translations import ( QASM_TO_PYTKET, @@ -67,13 +68,14 @@ def _check_and_update_qubits(self, target: tuple[int, ...]): def add_phase_instruction(self, target, phase_value): self.add_gate_instruction("gphase", target, phase_value) - def add_measure(self, target: tuple[int]): + def add_measure(self, target: tuple[int], classical_targets: Iterable[int] = None): if len(target) == 0: return self._check_and_update_qubits(target) for index, qubit in enumerate(target): - self._circuit.add_bit(Bit(qubit)) - self._circuit.Measure(qubit, qubit) + target_bit = classical_targets[index] if classical_targets is not None else qubit + self._circuit.add_bit(Bit(target_bit)) + self._circuit.Measure(qubit, target_bit) def add_custom_unitary( self, From d9c9ede87933d56a63134feda1566eb5b100e0b2 Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 20 Jun 2024 14:12:14 -0700 Subject: [PATCH 40/91] fix: Add classical targets option to add_measure instruction in BraketProgramContext --- src/braket/circuits/braket_program_context.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 4371637d3..29f76bdd3 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. from typing import Optional, Union +from collections.abc import Iterable import numpy as np from sympy import Expr, Number @@ -161,13 +162,14 @@ def handle_parameter_value( return FreeParameterExpression(evaluated_value) return value - def add_measure(self, target: tuple[int]) -> None: + def add_measure(self, target: tuple[int], classical_targets: Iterable[int] = None) -> None: """Add a measure instruction to the circuit Args: target (tuple[int]): the target qubits to be measured. """ - for index, qubit in enumerate(target): + for iter, qubit in enumerate(target): + index = classical_targets[iter] if classical_targets else iter instruction = Instruction(Measure(index=index), qubit) self._circuit.add_instruction(instruction) if self._circuit._measure_targets: From e2c4b558420e52b25641d5937f559895145e196b Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 24 Jun 2024 13:44:38 -0700 Subject: [PATCH 41/91] fix: Update add_instruction to update circuit _measurement_targets at the instruction level instead of only if add_measure is called. --- src/braket/circuits/braket_program_context.py | 4 ---- src/braket/circuits/circuit.py | 14 +++++++---- .../braket/circuits/test_circuit.py | 23 +++++++++++++++++++ 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 29f76bdd3..f3c75cd4f 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -172,7 +172,3 @@ def add_measure(self, target: tuple[int], classical_targets: Iterable[int] = Non index = classical_targets[iter] if classical_targets else iter instruction = Instruction(Measure(index=index), qubit) self._circuit.add_instruction(instruction) - if self._circuit._measure_targets: - self._circuit._measure_targets.append(qubit) - else: - self._circuit._measure_targets = [qubit] diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index f15e03647..ff846c237 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -503,7 +503,14 @@ def add_instruction( # Check if there is a measure instruction on the circuit self._check_if_qubit_measured(instruction, target, target_mapping) - + + #Update measure targets if instruction is a measurement + if isinstance(instruction.operator, Measure): + if self._measure_targets: + self._measure_targets.append(target or instruction.target[0]) + else: + self._measure_targets = [target or instruction.target[0]] + if not target_mapping and not target: # Nothing has been supplied, add instruction instructions_to_add = [instruction] @@ -710,10 +717,7 @@ def _add_measure(self, target_qubits: QubitSetInput) -> None: target=target, ) ) - if self._measure_targets: - self._measure_targets.append(target) - else: - self._measure_targets = [target] + def measure(self, target_qubits: QubitSetInput) -> Circuit: """ diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 71eecd1f1..755fc77fa 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -890,6 +890,29 @@ def test_from_ir_round_trip_transformation(): assert Circuit.from_ir(ir) == Circuit.from_ir(circuit.to_ir("OPENQASM")) assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") +def test_from_ir_round_trip_transformation_with_targeted_measurements(): + circuit = Circuit().h(0).cnot(0, 1).\ + add_instruction(Instruction(Measure(index=2), 1)).\ + add_instruction(Instruction(Measure(index=1), 2)).\ + add_instruction(Instruction(Measure(index=0), 0)) + ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[3] b;", + "qubit[3] q;", + "h q[0];", + "cnot q[0], q[1];", + "b[2] = measure q[1];", + "b[1] = measure q[2];", + "b[0] = measure q[0];" + ] + ), + inputs={}, + ) + + assert Circuit.from_ir(ir) == Circuit.from_ir(circuit.to_ir("OPENQASM")) + assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") def test_add_with_instruction_with_default(cnot_instr): circ = Circuit().add(cnot_instr) From f1de87a71b71832ac29edab3980de4532dcee710 Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 24 Jun 2024 16:43:45 -0700 Subject: [PATCH 42/91] feat: Merge SupportedGateCriterion and NativeGateCriterion classes into GateCriterion class --- src/braket/aws/aws_device.py | 12 +- src/braket/aws/aws_emulator_helpers.py | 28 ++-- src/braket/aws/aws_noise_models.py | 3 +- src/braket/emulators/emulator.py | 8 +- .../emulators/emulator_passes/__init__.py | 5 +- .../emulator_passes/criteria/__init__.py | 3 +- .../criteria/gate_criterion.py | 53 +++++++ .../braket/emulation/test_gate_criterion.py | 137 ++++++++++++++++++ .../emulation/test_native_gate_criterion.py | 91 ------------ .../test_supported_gate_criterion.py | 88 ----------- 10 files changed, 219 insertions(+), 209 deletions(-) create mode 100644 src/braket/emulators/emulator_passes/criteria/gate_criterion.py create mode 100644 test/unit_tests/braket/emulation/test_gate_criterion.py delete mode 100644 test/unit_tests/braket/emulation/test_native_gate_criterion.py delete mode 100644 test/unit_tests/braket/emulation/test_supported_gate_criterion.py diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 42ff95662..822c02174 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -53,8 +53,7 @@ from braket.emulators import Emulator from braket.aws.aws_emulator_helpers import ( create_qubit_count_criterion, - create_supported_gate_criterion, - create_native_gate_criterion, + create_gate_criterion, create_connectivity_criterion, create_gate_connectivity_criterion, create_lexi_mapping_routing_pass @@ -888,8 +887,9 @@ def _setup_emulator(self, emulator_noise_model: NoiseModel = None) -> Emulator: self._emulator = Emulator(noise_model=emulator_noise_model, backend="braket_dm") self._emulator.add_pass(create_qubit_count_criterion(self.properties)) - self._emulator.add_pass(create_supported_gate_criterion(self.properties)) - self._emulator.add_pass(create_native_gate_criterion(self.properties)) + self._emulator.add_pass(create_gate_criterion(self.properties)) + # self._emulator.add_pass(create_supported_gate_criterion(self.properties)) + # self._emulator.add_pass(create_native_gate_criterion(self.properties)) self._emulator.add_pass(create_connectivity_criterion(self.properties, self.topology_graph)) self._emulator.add_pass(create_gate_connectivity_criterion(self.properties, self.topology_graph)) self._emulator.add_pass(create_lexi_mapping_routing_pass(self.properties, self.topology_graph)) @@ -920,7 +920,7 @@ def run_emulator_passes(self, task_specification: Union[ BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation, - ]): + ], apply_noise_model=True): """ Runs all emulator passes and returns the modified program, which should be the same type as the input program. @@ -928,7 +928,7 @@ def run_emulator_passes(self, task_specification: Union[ if isinstance(task_specification, Circuit): task_specification = task_specification.copy() - return self.emulator.run_program_passes(task_specification) + return self.emulator.run_program_passes(task_specification, apply_noise_model) def emulate( diff --git a/src/braket/aws/aws_emulator_helpers.py b/src/braket/aws/aws_emulator_helpers.py index cabb76de1..12a3417ee 100644 --- a/src/braket/aws/aws_emulator_helpers.py +++ b/src/braket/aws/aws_emulator_helpers.py @@ -1,11 +1,10 @@ from braket.emulators import Emulator -from braket.emulators.emulator_passes import ( - SupportedGateCriterion, - NativeGateCriterion, +from braket.emulators.emulator_passes import ( ConnectivityCriterion, GateConnectivityCriterion, QubitCountCriterion, - LexiRoutingPass + LexiRoutingPass, + GateCriterion ) from braket.device_schema import ( DeviceActionType, @@ -22,8 +21,13 @@ from networkx import DiGraph + + +def create_qubit_count_criterion(properties: DeviceCapabilities) -> QubitCountCriterion: + qubit_count = properties.paradigm.qubitCount + return QubitCountCriterion(qubit_count) -def create_supported_gate_criterion(properties: DeviceCapabilities) -> SupportedGateCriterion: +def create_gate_criterion(properties: DeviceCapabilities) -> GateCriterion: supported_gates = properties.action[DeviceActionType.OPENQASM].supportedOperations """TODO: Issue in IQM Garnet Supported Operations: Includes "startVerbatimBox" and "endVerbatimBox" instructions in supported operations, which are braket specific pragmas. Filter out explicitly until they are removed from device properties.""" @@ -35,19 +39,9 @@ def create_supported_gate_criterion(properties: DeviceCapabilities) -> Supported except ValueError: pass - supported_gates_criterion = SupportedGateCriterion(supported_gates) - return supported_gates_criterion - - -def create_qubit_count_criterion(properties: DeviceCapabilities) -> QubitCountCriterion: - qubit_count = properties.paradigm.qubitCount - return QubitCountCriterion(qubit_count) - -def create_native_gate_criterion(properties: DeviceCapabilities) -> NativeGateCriterion: native_gates = properties.paradigm.nativeGateSet - native_gate_criterion = NativeGateCriterion(native_gates) - return native_gate_criterion - + + return GateCriterion(supported_gates=supported_gates, native_gates=native_gates) @singledispatch def create_connectivity_criterion(properties: DeviceCapabilities, connectivity_graph: DiGraph) -> ConnectivityCriterion: diff --git a/src/braket/aws/aws_noise_models.py b/src/braket/aws/aws_noise_models.py index d6dae474f..87467fdf2 100644 --- a/src/braket/aws/aws_noise_models.py +++ b/src/braket/aws/aws_noise_models.py @@ -197,7 +197,8 @@ def _create_edge_specs(properties: DeviceCapabilities, edge_properties: List[Gat gate = getattr(Gate, gate_name) edge_specs.append(GateFidelity(gate, edge_property.fidelity)) else: - logging.warning(f"Unsupported gate {gate_name}") + continue + # logging.warning(f"Unsupported gate {gate_name}") return edge_specs diff --git a/src/braket/emulators/emulator.py b/src/braket/emulators/emulator.py index 96d4c3330..8e22e6403 100644 --- a/src/braket/emulators/emulator.py +++ b/src/braket/emulators/emulator.py @@ -77,4 +77,10 @@ def noise_model(self): @noise_model.setter def noise_model(self, noise_model: NoiseModel): self._noise_model = noise_model - self._backend = LocalSimulator(backend="braket_dm", noise_model=noise_model) \ No newline at end of file + self._backend = LocalSimulator(backend="braket_dm", noise_model=noise_model) + + def run_program_passes(self, task_specification: ProgramType, apply_noise_model=True) -> ProgramType: + program = super().run_program_passes(task_specification) + if apply_noise_model: + return self._noise_model.apply(program) + return program \ No newline at end of file diff --git a/src/braket/emulators/emulator_passes/__init__.py b/src/braket/emulators/emulator_passes/__init__.py index efff82e15..e2d2e61f0 100644 --- a/src/braket/emulators/emulator_passes/__init__.py +++ b/src/braket/emulators/emulator_passes/__init__.py @@ -2,9 +2,8 @@ from braket.emulators.emulator_passes.criteria import ( EmulatorCriterion, ConnectivityCriterion, - SupportedGateCriterion, - NativeGateCriterion, GateConnectivityCriterion, - QubitCountCriterion + QubitCountCriterion, + GateCriterion ) from braket.emulators.emulator_passes.lexi_routing_pass import LexiRoutingPass \ No newline at end of file diff --git a/src/braket/emulators/emulator_passes/criteria/__init__.py b/src/braket/emulators/emulator_passes/criteria/__init__.py index c69966f38..a3af842ae 100644 --- a/src/braket/emulators/emulator_passes/criteria/__init__.py +++ b/src/braket/emulators/emulator_passes/criteria/__init__.py @@ -1,6 +1,5 @@ from braket.emulators.emulator_passes.criteria.emulator_criterion import EmulatorCriterion -from braket.emulators.emulator_passes.criteria.native_gate_criterion import NativeGateCriterion -from braket.emulators.emulator_passes.criteria.supported_gate_criterion import SupportedGateCriterion from braket.emulators.emulator_passes.criteria.connectivity_criterion import ConnectivityCriterion from braket.emulators.emulator_passes.criteria.gate_connectivity_criterion import GateConnectivityCriterion from braket.emulators.emulator_passes.criteria.qubit_count_criterion import QubitCountCriterion +from braket.emulators.emulator_passes.criteria.gate_criterion import GateCriterion \ No newline at end of file diff --git a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py new file mode 100644 index 000000000..210cfa5ea --- /dev/null +++ b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py @@ -0,0 +1,53 @@ +from collections.abc import Iterator +from braket.circuits.gate import Gate +from braket.emulators.emulator_passes.criteria import EmulatorCriterion +from braket.circuits.compiler_directives import StartVerbatimBox, EndVerbatimBox +from braket.circuits import Circuit +from braket.circuits.translations import BRAKET_GATES + + +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. + """ + 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) + 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.") + + def validate(self, circuit: Circuit) -> None: + idx = 0 + while idx < len(circuit.instructions): + instruction = circuit.instructions[idx] + if isinstance(instruction.operator, StartVerbatimBox): + idx += 1 + while idx < len(circuit.instructions) and not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): + instruction = circuit.instructions[idx] + if isinstance(instruction.operator, Gate): + gate = instruction.operator + if not type(gate) in self._native_gates: + raise ValueError(f"Gate {gate.name} is not a native gate supported by this emulator.") + idx += 1 + if not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): + raise ValueError(f"No end verbatim box found at index {idx} in the circuit.") + elif isinstance(instruction.operator, Gate): + gate = instruction.operator + if not type(gate) in self._supported_gates: + raise ValueError(f"Gate {gate.name} is not supported by this emulator.") + idx += 1 + + + def __eq__(self, other: EmulatorCriterion) -> bool: + return isinstance(other, GateCriterion) and \ + self._supported_gates == other._supported_gates and \ + self._native_gates == other._native_gates + diff --git a/test/unit_tests/braket/emulation/test_gate_criterion.py b/test/unit_tests/braket/emulation/test_gate_criterion.py new file mode 100644 index 000000000..7f45a42d9 --- /dev/null +++ b/test/unit_tests/braket/emulation/test_gate_criterion.py @@ -0,0 +1,137 @@ +import pytest +from braket.emulators.emulator_passes.criteria import GateCriterion +from braket.circuits import Circuit +import numpy as np + +@pytest.fixture +def basic_gate_set(): + return (["h", "cnot"], ['cz', 'prx']) + + +@pytest.fixture +def mock_qpu_gates(): + supported_gates = ['ccnot', 'cnot', 'cphaseshift', 'cswap', 'swap', 'iswap', 'pswap', 'ecr', 'cy', 'cz', 'xy', \ + 'zz', 'h', 'i', 'phaseshift', 'rx', 'ry', 'v', 'vi', 'x', 'y', 'z'] + + native_gates = ['cz', 'prx'] + return (supported_gates, native_gates) + + + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().h(range(4)).cnot(0, 5).pswap(0, 1, np.pi/4).xy(0, 1, 0.5).cphaseshift(0, 1, np.pi/4), + Circuit().swap(0, 1).rx(0, 0.5).v(1).h(2).add_verbatim_box( + Circuit().cz(0, 1).cz(0, 6).prx(0, np.pi/4, np.pi/5) + ).z(3), + Circuit().add_verbatim_box( + Circuit().cz(0, 2).prx(0, 0.5, 0.5) + ).add_verbatim_box( + Circuit().cz(0, 4).cz(3, 6) + ), + Circuit().add_verbatim_box( + Circuit() + ) + ] +) +def test_valid_circuits(mock_qpu_gates, circuit): + """ + GateCriterion should not raise any errors when validating these circuits. + """ + GateCriterion(mock_qpu_gates[0], mock_qpu_gates[1]).validate(circuit) + +def test_only_supported_gates(): + supported_gates = ["h", "cnot", "rx", "xx", "y"] + criterion = GateCriterion(supported_gates=supported_gates) + circuit = Circuit().h(0).cnot(0, 1).rx(4, np.pi/4).xx(2, 3, np.pi/4).y(7) + criterion.validate(circuit) + + +def test_verbatim_circuit_only_supported_gates(): + supported_gates = ["h", "cnot", "rx", "xx", "y"] + criterion = GateCriterion(supported_gates=supported_gates) + circuit = Circuit().add_verbatim_box( + Circuit().h(0) + ) + + with pytest.raises(ValueError): + criterion.validate(circuit) + + +def test_only_native_gates(): + native_gates = ["h", "cnot", "rx", "xx", "y"] + criterion = GateCriterion(native_gates=native_gates) + vb = Circuit().h(0).cnot(0, 1).rx(4, np.pi/4).xx(2, 3, np.pi/4).y(7) + circuit = Circuit().add_verbatim_box(vb) + criterion.validate(circuit) + +def test_non_verbatim_circuit_only_native_gates(): + native_gates = ["h", "cnot", "rx", "xx", "y"] + criterion = GateCriterion(native_gates=native_gates) + vb = Circuit().h(0).cnot(0, 1).rx(4, np.pi/4).xx(2, 3, np.pi/4).y(7) + circuit = Circuit().add_verbatim_box(vb) + circuit.i(0) + with pytest.raises(ValueError): + criterion.validate(circuit) + +def test_empty_instantiation(): + with pytest.raises(ValueError): + GateCriterion() + + + + # return (["h", "cnot"], ['cz', 'prx']) + +@pytest.mark.parametrize( + "circuit", + [ + Circuit().z(0), + Circuit().h(0).cnot(0, 1).cz(0, 1), + Circuit().add_verbatim_box( + Circuit().h(2) + ), + Circuit().cphaseshift01(0, 1, np.pi/4).h(0).cnot(0, 1), + Circuit().h(0).add_verbatim_box( + Circuit().cz(1, 2).prx(range(5), np.pi/4, np.pi/2).cz(2, 6) + ).prx(range(4), np.pi/4, np.pi/6) + ] +) +def test_invalid_circuits(basic_gate_set, circuit): + """ + GateCriterion should raise errors when validating these circuits. + """ + with pytest.raises(ValueError): + GateCriterion(basic_gate_set[0], basic_gate_set[1]).validate(circuit) + + +# def test_empty_supported_gates(): +# with pytest.raises(ValueError): +# GateCriterion([]) + + +# @pytest.mark.parametrize( +# "gate_set_1, gate_set_2", +# [ +# (["h"], ["h"]), +# (["cnot", "h"], ["h", "cnot"]), +# (["phaseshift", "cnot", "rx", "ry"], ["ry", "rx", "cnot", "phaseshift"]) +# ], +# ) +# def test_equality(gate_set_1, gate_set_2): +# assert GateCriterion(gate_set_1) == GateCriterion(gate_set_2) + + +# @pytest.mark.parametrize( +# "gate_set_1, gate_set_2", +# [ +# (["h"], ["x"]), +# (["cnot"], ["h", "cnot"]), +# (["cnot", "h"], ["h"]), +# (["phaseshift", "cnot", "ms", "ry"], ["ry", "rx", "cnot", "ms"]) +# ], +# ) +# def test_inequality(gate_set_1, gate_set_2): +# assert GateCriterion(gate_set_1) != GateCriterion(gate_set_2) \ No newline at end of file diff --git a/test/unit_tests/braket/emulation/test_native_gate_criterion.py b/test/unit_tests/braket/emulation/test_native_gate_criterion.py deleted file mode 100644 index bb0043c57..000000000 --- a/test/unit_tests/braket/emulation/test_native_gate_criterion.py +++ /dev/null @@ -1,91 +0,0 @@ -import pytest - -from braket.emulators.emulator_passes.criteria import NativeGateCriterion -from braket.circuits import Circuit, Gate, gates - - -@pytest.fixture -def h_cnot_gates(): - return ["h", "cnot"] - - -@pytest.mark.parametrize( - "circuit", - [ - Circuit(), - Circuit().x(0).h(1).swap(0, 1), - Circuit().add_verbatim_box( - Circuit().h(0).cnot(0, 1).h(2).cnot(0, 3).h(range(3)) - ), - Circuit().cswap(0, 1, 2).z(3).add_verbatim_box( - Circuit().h(0).cnot(0, 1).h(2).cnot(0, 3) - ).cnot(2, 4).rx(5, 0).add_verbatim_box( - Circuit().h(0).cnot(0, 1).h(5).cnot(4, 6) - ) - ] -) -def test_valid_circuits(h_cnot_gates, circuit): - """ - NativeGateCriterion should not raise any errors when validating these circuits given - a native gate set of [H, CNOT] - """ - NativeGateCriterion(h_cnot_gates).validate(circuit) - - -@pytest.mark.parametrize( - "circuit", - [ - Circuit().add_verbatim_box( - Circuit().z(0) - ), - Circuit().phaseshift(0, 0).h(0).add_verbatim_box( - Circuit().h(0).cnot(1, 2).rx(3, 0) - ).add_verbatim_box( - Circuit().h(0).cnot(0, 1).h(0).cnot(1, 3) - ), - Circuit().h(0).cnot(0, 1).h(2).add_verbatim_box( - Circuit().h(2).cnot(2, 3).h(4) - ).add_verbatim_box( - Circuit().cy(2, 3) - ) - ] -) -def test_invalid_circuits(h_cnot_gates, circuit): - """ - NativeGateCriterion should raise an error when validating these circuits given - a native gate set of [H, CNOT] - """ - with pytest.raises(ValueError): - NativeGateCriterion(h_cnot_gates).validate(circuit) - - -def test_empty_native_gates(): - with pytest.raises(ValueError): - NativeGateCriterion([]) - - -@pytest.mark.parametrize( - "gate_set_1, gate_set_2", - [ - (["h"], ["h"]), - (["cnot", "h"], ["h", "cnot"]), - (["phaseshift", "cnot", "rx", "ry"], ["ry", "rx", "cnot", "phaseshift"]) - ], -) -def test_equality(gate_set_1, gate_set_2): - assert NativeGateCriterion(gate_set_1) == NativeGateCriterion(gate_set_2) - - -@pytest.mark.parametrize( - "gate_set_1, gate_set_2", - [ - (["h"], ["x"]), - (["cnot"], ["h", "cnot"]), - (["cnot", "h"], ["h"]), - (["phaseshift", "cnot", "ms", "ry"], ["ry", "rx", "cnot", "ms"]) - ], -) -def test_inequality(gate_set_1, gate_set_2): - assert NativeGateCriterion(gate_set_1) != NativeGateCriterion(gate_set_2) - - diff --git a/test/unit_tests/braket/emulation/test_supported_gate_criterion.py b/test/unit_tests/braket/emulation/test_supported_gate_criterion.py deleted file mode 100644 index 48a26484a..000000000 --- a/test/unit_tests/braket/emulation/test_supported_gate_criterion.py +++ /dev/null @@ -1,88 +0,0 @@ -import pytest -from braket.emulators.emulator_passes.criteria import SupportedGateCriterion -from braket.circuits import Circuit -import numpy as np - -@pytest.fixture -def h_cnot_gates(): - return ["h", "cnot"] - - -@pytest.fixture -def aspen3_supported_gates(): - return ['cz', 'xy', 'ccnot', 'cnot', 'cphaseshift', 'cphaseshift00', 'cphaseshift01', \ - 'cphaseshift10', 'cswap', 'h', 'i', 'iswap', 'phaseshift', 'pswap', 'rx', 'ry', 'rz',\ - 's', 'si', 'swap', 't', 'ti', 'x', 'y', 'z'] - -@pytest.mark.parametrize( - "circuit", - [ - Circuit(), - Circuit().h(range(5)).cnot(0, 1), - Circuit().swap(0, 1).h(2).add_verbatim_box( - Circuit().h(0).cnot(0, 1) - ).z(3), - Circuit().phaseshift(0, np.pi/4).rx(1, np.pi/4).iswap(0, 1).si(2), - Circuit().add_verbatim_box( - Circuit() - ), - Circuit().cphaseshift01(0, 1, np.pi/4) - ] -) -def test_valid_circuits(aspen3_supported_gates, circuit): - """ - SupportedGateCriterion should not raise any errors when validating these circuits. - """ - SupportedGateCriterion(aspen3_supported_gates).validate(circuit) - - -@pytest.mark.parametrize( - "circuit", - [ - Circuit().z(0), - Circuit().h(0).cnot(0, 1).z(2), - Circuit().add_verbatim_box( - Circuit().z(2) - ), - Circuit().cphaseshift01(0, 1, np.pi/4).h(0).cnot(0, 1), - Circuit().h(0).add_verbatim_box( - Circuit().cnot(1, 2).h(range(5)).h(3) - ).rx(range(4), np.pi/4) - ] -) -def test_invalid_circuits(h_cnot_gates, circuit): - """ - SupportedGateCriterion should raise errors when validating these circuits. - """ - with pytest.raises(ValueError): - SupportedGateCriterion(h_cnot_gates).validate(circuit) - - -def test_empty_supported_gates(): - with pytest.raises(ValueError): - SupportedGateCriterion([]) - - -@pytest.mark.parametrize( - "gate_set_1, gate_set_2", - [ - (["h"], ["h"]), - (["cnot", "h"], ["h", "cnot"]), - (["phaseshift", "cnot", "rx", "ry"], ["ry", "rx", "cnot", "phaseshift"]) - ], -) -def test_equality(gate_set_1, gate_set_2): - assert SupportedGateCriterion(gate_set_1) == SupportedGateCriterion(gate_set_2) - - -@pytest.mark.parametrize( - "gate_set_1, gate_set_2", - [ - (["h"], ["x"]), - (["cnot"], ["h", "cnot"]), - (["cnot", "h"], ["h"]), - (["phaseshift", "cnot", "ms", "ry"], ["ry", "rx", "cnot", "ms"]) - ], -) -def test_inequality(gate_set_1, gate_set_2): - assert SupportedGateCriterion(gate_set_1) != SupportedGateCriterion(gate_set_2) \ No newline at end of file From 8c1b3ec5e59b3f2975b61e29e897700ea5512826 Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 24 Jun 2024 23:51:00 -0700 Subject: [PATCH 43/91] fix: Allow GateConnectivityCriterion to be instantiated with a graph marked as undirected --- .../criteria/gate_connectivity_criterion.py | 16 +++++- .../test_gate_connectivity_criterion.py | 56 ++++++++++++++----- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py index 6908efe20..3c8dd7e2b 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py @@ -13,14 +13,26 @@ def __init__(self, gate_connectivity_graph: Union[ Dict[Tuple[Any, Any], Iterable[str]], DiGraph - ]): + ], + directed=True): super().__init__() if isinstance(gate_connectivity_graph, DiGraph): self._gate_connectivity_graph = gate_connectivity_graph + if not directed: + for edge in self._gate_connectivity_graph.edges: + back_edge = (edge[1], edge[0]) + if back_edge not in self._gate_connectivity_graph.edges: + supported_gates = self._gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] + self._gate_connectivity_graph.add_edge(*back_edge, supported_gates=supported_gates) + elif isinstance(gate_connectivity_graph, dict): self._gate_connectivity_graph = DiGraph() for edge, supported_gates in gate_connectivity_graph.items(): - self._gate_connectivity_graph.add_edge(*edge, supported_gates=supported_gates) + self._gate_connectivity_graph.add_edge(edge[0], edge[1], supported_gates=supported_gates) + if not directed: + back_edge = (edge[1], edge[0]) + if back_edge not in gate_connectivity_graph: + self._gate_connectivity_graph.add_edge(edge[1], edge[0], supported_gates=supported_gates) else: raise TypeError("gate_connectivity_graph must either be a dictionary of edges mapped to supported gates lists, or a DiGraph with supported \ gates provided as edge attributs. ") diff --git a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py index 037d6b7a5..ca731bf5d 100644 --- a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py +++ b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py @@ -33,7 +33,7 @@ def basic_discontiguous_4_node_graph(): return G @pytest.fixture -def basic_undirected_4_node_graph_as_dict(): +def basic_4_node_graph_as_dict(): return { (0, 1): ["CNot", "Swap", "CX", "XX"], (1, 2): ["CNot", "CZ", "ISwap", "XY"], @@ -83,6 +83,8 @@ def test_valid_basic_contiguous_circuits(basic_4_node_graph, circuit): ).cnot(0, 2).swap(4, 6) ] ) + + def test_valid_basic_discontiguous_circuits(basic_discontiguous_4_node_graph, circuit): """ ConnectivityGateCriterion should not raise any errors when validating these circuits. @@ -110,7 +112,7 @@ def test_directed_graph_construction_from_dict(): (0, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), (2, 5, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}) ]) - gcc = GateConnectivityCriterion(dict_representation, directed=True) + gcc = GateConnectivityCriterion(dict_representation) assert graphs_equal(gcc._gate_connectivity_graph, digraph_representation) @@ -120,7 +122,7 @@ def test_directed_graph_construction_from_dict(): [ Circuit(), Circuit().add_verbatim_box( - Circuit().cnot(0, 1).cnot(1, 0) + Circuit().cnot(0, 1) ), Circuit().swap(0, 1).add_verbatim_box( Circuit().iswap(2, 1).pswap(3, 0, np.pi/2).pswap(0, 3, np.pi/2) @@ -132,11 +134,11 @@ def test_directed_graph_construction_from_dict(): ) ] ) -def test_undirected_criteria_with_valid_circuits(basic_undirected_4_node_graph_as_dict, circuit): +def test_undirected_criteria_from_dict_with_valid_circuits(basic_4_node_graph_as_dict, circuit): """ ConnectivityGateCriterion should not raise any errors when validating these circuits. """ - gate_connectivity_criterion = GateConnectivityCriterion(basic_undirected_4_node_graph_as_dict, directed=False) + gate_connectivity_criterion = GateConnectivityCriterion(basic_4_node_graph_as_dict, directed=False) gate_connectivity_criterion.validate(circuit) @@ -163,18 +165,46 @@ def test_undirected_graph_construction_from_dict(): (2, 0, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), (5, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), ]) - gcc = GateConnectivityCriterion(dict_representation) + gcc = GateConnectivityCriterion(dict_representation, directed=False) assert graphs_equal(gcc._gate_connectivity_graph, digraph_representation) +@pytest.mark.parametrize( + "representation", + [ + { + (0, 1): ["CNot", "CZ"], + (1, 0): ["CZ, XX"], + (2, 0): ["CNot, YY"] + }, + nx.from_dict_of_dicts( + { + 0: {1: {"supported_gates": ["CNot", "CZ"]}}, + 1: {0: {"supported_gates": ["CZ", "XX"]}}, + 2: {2: {"supported_gates": ["CNot", "YY"]}} + } + ) + ] +) +def create_undirected_graph_with_exisiting_back_edges(representation): + """ + Check that creating an undirected graph with a graph that + contains forwards and backwards edges with different constraints + is created properly. + """ + + gcc = GateConnectivityCriterion(representation, directed=False) + expected_digraph_representation = nx.DiGraph() + expected_digraph_representation.add_edges_from([ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 0, {"supported_gates": ["CZ", "XX"]}), + (2, 0, {"supported_gates": ["CNot", "YY"]}), + (0, 2, {"supported_gates": ["CNot", "YY"]}) + ]) + + assert graphs_equal(gcc._gate_connectivity_graph, expected_digraph_representation) + - # G.add_edges_from( - # [ - # (0, 1, {"supported_gates": ["CNot", "CZ"]}), - # (1, 2, {"supported_gates": ["Swap", "CNot"]}), - # (0, 3, {"supported_gates": ["XX", "XY"]}) - # ] - # ) @pytest.mark.parametrize( "circuit", [ From d38432b945fe9fdfad75646e301cf8a0aaf33096 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 25 Jun 2024 00:10:06 -0700 Subject: [PATCH 44/91] feat: Allow ConnectivityGraphs to be instantiated with a graph marked as undirected --- .../criteria/connectivity_criterion.py | 6 +++- .../emulation/test_connectivity_criterion.py | 28 ++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py index c3a47365f..0f44b7551 100644 --- a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py @@ -32,7 +32,8 @@ class ConnectivityCriterion(EmulatorCriterion): def __init__(self, connectivity_graph: Union[Dict[int, Iterable[int]], DiGraph] = None, fully_connected = False, num_qubits: int = None, - qubit_labels: Union[Iterable[int], QubitSet] = None): + qubit_labels: Union[Iterable[int], QubitSet] = None, + directed: bool = False): if not (connectivity_graph or fully_connected): raise ValueError("Either the connectivity_graph must be provided or fully_connected must be True.") @@ -48,6 +49,9 @@ def __init__(self, connectivity_graph: Union[Dict[int, Iterable[int]], DiGraph] else: self._connectivity_graph = connectivity_graph + if not directed: + for edge in self._connectivity_graph.edges: + self._connectivity_graph.add_edge(edge[1], edge[0]) def validate(self, circuit: Circuit) -> None: """ diff --git a/test/unit_tests/braket/emulation/test_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_connectivity_criterion.py index 7d98667b1..5a62dbddf 100644 --- a/test/unit_tests/braket/emulation/test_connectivity_criterion.py +++ b/test/unit_tests/braket/emulation/test_connectivity_criterion.py @@ -4,7 +4,7 @@ from braket.circuits import Circuit import networkx as nx import numpy as np - +from networkx.utils import graphs_equal @pytest.fixture def basic_2_node_complete_graph(): @@ -32,11 +32,11 @@ def five_node_digraph(): Circuit().add_verbatim_box( Circuit() ), - Circuit().i(range(3)).cnot(3, 4), + Circuit().i(range(2)).cnot(3, 4), Circuit().add_verbatim_box( Circuit().h(0).h(1).cnot(0, 1).cnot(1, 0) ), - Circuit().i(range(3)).add_verbatim_box( + Circuit().h(range(2)).add_verbatim_box( Circuit().swap(0, 1).phaseshift(1, np.pi/4).cphaseshift01(1, 0, np.pi/4) ) ] @@ -59,11 +59,12 @@ def test_basic_contiguous_circuits(basic_2_node_complete_graph, circuit): Circuit().add_verbatim_box( Circuit().h(1).h(10).cnot(1, 10).cnot(10, 1) ), - Circuit().i(range(3)).add_verbatim_box( + Circuit().add_verbatim_box( Circuit().swap(1, 10).phaseshift(10, np.pi/4).cphaseshift01(10, 1, np.pi/4) ) ] ) + def test_valid_discontiguous_circuits(basic_noncontig_qubits_2_node_complete_graph, circuit): """ ConnectivityGateCriterion should not raise any errors when validating these circuits. @@ -171,3 +172,22 @@ def test_equality_graph_created_with_dict(five_node_digraph): def test_invalid_constructors(connectivity_graph, fully_connected, num_qubits, qubit_labels): with pytest.raises(ValueError): ConnectivityCriterion(connectivity_graph, fully_connected, num_qubits, qubit_labels) + + +@pytest.mark.parametrize( + "representation", + [ + { + 1: [0, 2, 3], + 2: [3, 4], + 3: [6] + }, + nx.from_edgelist([(1, 0), (1, 2), (1, 3), (2, 3), (2, 4), (3, 6)], create_using=nx.DiGraph) + ] +) +def test_undirected_graph_construction(representation): + expected_digraph = nx.from_edgelist( + [(1, 0), (0, 1), (1, 2), (2, 1), (1, 3), (3, 1), (2, 3), (3, 2), (2, 4), (4, 2), (3, 6), (6, 3)], create_using=nx.DiGraph + ) + cc = ConnectivityCriterion(representation, directed=False) + assert graphs_equal(cc._connectivity_graph, expected_digraph) \ No newline at end of file From a4906d98f447f3825287df1cba88a1ed35092341 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 25 Jun 2024 10:43:06 -0700 Subject: [PATCH 45/91] feat: Add QubitCountCriterion tests and check if qubit_count is negative during instantiation --- .../criteria/qubit_count_criterion.py | 2 + .../emulation/test_qubit_count_criterion.py | 47 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 test/unit_tests/braket/emulation/test_qubit_count_criterion.py diff --git a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py index 3a843fdad..bc37bc170 100644 --- a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py @@ -8,6 +8,8 @@ class QubitCountCriterion(EmulatorCriterion): than available on a device, as set during this criterion's instantiation. """ def __init__(self, qubit_count: int): + if qubit_count <= 0: + raise ValueError(f"qubit_count ({qubit_count}) must be a positive integer.") self._qubit_count = qubit_count diff --git a/test/unit_tests/braket/emulation/test_qubit_count_criterion.py b/test/unit_tests/braket/emulation/test_qubit_count_criterion.py new file mode 100644 index 000000000..d4e68930c --- /dev/null +++ b/test/unit_tests/braket/emulation/test_qubit_count_criterion.py @@ -0,0 +1,47 @@ +from braket.emulators.emulator_passes import QubitCountCriterion +from braket.circuits import Circuit +import numpy as np +import pytest + +@pytest.mark.parametrize( + "qubit_count,circuit", + [ + (1, Circuit()), + (10, Circuit().add_verbatim_box(Circuit())), + (1, Circuit().z(0)), + (1, Circuit().z(3).x(3)), + (2, Circuit().cnot(0, 1).swap(1, 0)), + (2, Circuit().z(0).add_verbatim_box( + Circuit().cnot(0, 4) + ).yy(0, 4, np.pi/4)), + (50, Circuit().i(range(50)).measure(range(50))) + ] +) +def test_valid_circuits(qubit_count, circuit): + """ + QubitCountCriterion should not raise any errors when validating these circuits. + """ + QubitCountCriterion(qubit_count=qubit_count).validate(circuit) + + +@pytest.mark.parametrize( + "qubit_count", + [0, -1] +) +def test_invalid_instantiation(qubit_count): + with pytest.raises(ValueError): + QubitCountCriterion(qubit_count) + + +@pytest.mark.parametrize( + "qubit_count,circuit", + [ + (1, Circuit().cnot(0, 1)), + (2, Circuit().cnot(0, 1).x(2)), + (50, Circuit().i(range(50)).measure(range(50)).measure(50)) + ] +) +def test_invalid_circuits(qubit_count, circuit): + with pytest.raises(ValueError, match=f"Circuit must use at most {qubit_count} qubits, but uses {circuit.qubit_count} qubits."): + QubitCountCriterion(qubit_count).validate(circuit) + \ No newline at end of file From 39bbfaa2b5ef4d9de595553680a4a5c0cd1f8df7 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 25 Jun 2024 10:59:17 -0700 Subject: [PATCH 46/91] change: remove SpportedGateCriterion and NativeGateCriterion after replacing with GateCriterion --- .../criteria/native_gate_criterion.py | 48 ------------------- .../criteria/supported_gate_criterion.py | 35 -------------- 2 files changed, 83 deletions(-) delete mode 100644 src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py delete mode 100644 src/braket/emulators/emulator_passes/criteria/supported_gate_criterion.py diff --git a/src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py deleted file mode 100644 index a0ec4cbc6..000000000 --- a/src/braket/emulators/emulator_passes/criteria/native_gate_criterion.py +++ /dev/null @@ -1,48 +0,0 @@ -from collections.abc import Iterator -from braket.circuits.gate import Gate -from braket.circuits.compiler_directives import StartVerbatimBox, EndVerbatimBox -from braket.emulators.emulator_passes.criteria import EmulatorCriterion -from braket.circuits import Circuit -from braket.circuits.translations import BRAKET_GATES - -class NativeGateCriterion(EmulatorCriterion): - - def __init__(self, native_gates: Iterator[str]): - """ - args: - native_gates (Iterator[str]): A list of native gates supported by the emulator. - Validating native gates is relevant when considering whether or not a verbatim box in a program - is possible on a given device. - """ - if len(native_gates) == 0: - raise ValueError("At least one native gate must be provided.") - 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.") - - - def validate(self, circuit: Circuit) -> None: - for idx in range(len(circuit.instructions)): - instruction = circuit.instructions[idx] - if isinstance(instruction.operator, StartVerbatimBox): - idx += 1 - while idx < len(circuit.instructions) and not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): - instruction = circuit.instructions[idx] - if isinstance(instruction.operator, Gate): - gate = instruction.operator - if type(gate) in self._native_gates: - idx += 1 - continue - raise ValueError(f"Gate {gate.name} is not a native gate supported by this emulator.") - idx += 1 - - if not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): - raise ValueError(f"No end verbatim box found at index {idx} in the circuit.") - idx += 1 - - - def __eq__(self, other: EmulatorCriterion) -> bool: - return isinstance(other, NativeGateCriterion) and \ - self._native_gates == other._native_gates - diff --git a/src/braket/emulators/emulator_passes/criteria/supported_gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/supported_gate_criterion.py deleted file mode 100644 index 8c042f95c..000000000 --- a/src/braket/emulators/emulator_passes/criteria/supported_gate_criterion.py +++ /dev/null @@ -1,35 +0,0 @@ -from collections.abc import Iterator -from braket.circuits.gate import Gate -from braket.emulators.emulator_passes.criteria import EmulatorCriterion -from braket.circuits import Circuit -from braket.circuits.translations import BRAKET_GATES - - -class SupportedGateCriterion(EmulatorCriterion): - def __init__(self, supported_gates: Iterator[str]): - """ - args: - supported_gates (Iterator[str]): A list of gates supported by the emulator. A gate can - be a QASM symbol, a Braket gate name, or a Braket gate instance. - """ - if len(supported_gates) == 0: - raise ValueError("At least one supported gate must be provided.") - - try: - self._supported_gates = set(BRAKET_GATES[gate] for gate in supported_gates) - except KeyError as e: - raise ValueError(f"Input {str(e)} is not a valid Braket gate name.") - - def validate(self, circuit: Circuit) -> None: - for instruction in circuit.instructions: - if isinstance(instruction.operator, Gate): - gate = instruction.operator - if type(gate) in self._supported_gates: - continue - else: - raise ValueError(f"Gate {gate.name} is not supported by this emulator.") - - def __eq__(self, other: EmulatorCriterion) -> bool: - return isinstance(other, SupportedGateCriterion) and \ - self._supported_gates == other._supported_gates - From dcc061e61e39b43a39e9ad56c8ac7399a48b3616 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 25 Jun 2024 12:50:09 -0700 Subject: [PATCH 47/91] feat: Add tests for tket_to_qasm3 translation, remove extraneous comments/lines in tket_to_qasm3.py --- .../qasm3_gen/tket_to_qasm3.py | 15 +-- .../pytket_translator/test_tket_to_qasm3.py | 110 ++++++++++++++++++ 2 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 test/unit_tests/braket/emulation/pytket_translator/test_tket_to_qasm3.py diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py b/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py index bb18967f5..038611b62 100644 --- a/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py +++ b/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py @@ -52,8 +52,7 @@ def _validate_args_not_measured(self, args): raise ValueError( "Circuit QASM cannot be generated as circuit contains midcircuit " f"measurements on qubit: {arg}" - ) - + ) def _visit_box(self, command: Command, optype): circ = command.op.get_circuit() @@ -67,13 +66,6 @@ def _visit_measure(self, command: Command, optype): self.context.add_measurement(qubit, cbit) self._measured_nodes.add(qubit_node) - # @_visit_op.register - # def _(self, command: Node, optype: OpType.CustomGate): - # gate_name = command.op.gate.name - # if gate_name not in SUPPORTED_CUSTOM_GATES: - # raise ValueError(f"Encountered unsupported custom gate {gate_name}") - # self._visit_gate(gate_name, command.op.params, command.args) - def _visit_gate(self, command: Command, optype): """ @@ -101,10 +93,7 @@ def _visit_gate(self, command: Command, optype): params = self._gate_angles_in_radians(params) qubits = [q.index[0] for q in qubits] self.context.add_gate(gate_name, params, qubits) - - - - + def _gate_angles_in_radians(self, params): return [self._tau_to_radians(param) for param in params] diff --git a/test/unit_tests/braket/emulation/pytket_translator/test_tket_to_qasm3.py b/test/unit_tests/braket/emulation/pytket_translator/test_tket_to_qasm3.py new file mode 100644 index 000000000..12e3b7fcc --- /dev/null +++ b/test/unit_tests/braket/emulation/pytket_translator/test_tket_to_qasm3.py @@ -0,0 +1,110 @@ +import pytest +from pytket.circuit import Circuit, Bit, OpType +import pytket.circuit +from braket.emulators.pytket_translator import tket_to_qasm3 +from sympy import Symbol +import re + +@pytest.fixture +def simple_bell_circuit(): + circ = Circuit(3) + for i in range(3): + circ.add_bit(Bit("c", i)) + circ.Rx(0.5, 0) + circ.H(0) + circ.CX(1, 2) + circ.CX(0, 1) + circ.Measure(0, 0) + circ.Measure(1, 1) + circ.Measure(2, 2) + return circ + +@pytest.fixture +def simple_parametric_circuit(): + circ = Circuit(1) + circ.add_bit(Bit("c", 0)) + circ.Rx(Symbol("theta"), 0) + circ.Measure(0, 0) + return circ + +def test_simple_bell(simple_bell_circuit): + expected = """ +OPENQASM 3.0; +bit[3] c; +rx(0.5*pi) $0; +cnot $1,$2; +h $0; +cnot $0,$1; +c[2] = measure $2; +c[0] = measure $0; +c[1] = measure $1; +""".strip() + qasm_result = tket_to_qasm3(simple_bell_circuit).strip() + assert qasm_result == expected + + +def test_parametric_circuit(simple_parametric_circuit): + expected = """ +OPENQASM 3.0; +input float theta; +bit[1] c; +rx(pi*theta) $0; +c[0] = measure $0; +""".strip() + qasm_result = tket_to_qasm3(simple_parametric_circuit).strip() + assert expected == qasm_result + +def test_empty_circuit(): + circ = Circuit(0) + expected = """OPENQASM 3.0; +""" + qasm_result = tket_to_qasm3(circ) + assert expected == qasm_result + +def test_multiple_parameter_circuits(): + theta = Symbol("theta") + phi = Symbol("phi") + circ = Circuit(1) + circ.add_bit(Bit("c", 0)) + circ.Rx(theta, 0) + circ.Rx(theta + phi, 0) + expected=""" +OPENQASM 3.0; +input float theta; +input float phi; +bit[1] c; +rx(pi*theta) $0; +rx(pi*(phi + theta)) $0; +""".strip() + qasm_result = tket_to_qasm3(circ).strip() + assert expected == qasm_result + + +def test_invalid_operation_on_measured_targets(): + circ = Circuit(1) + circ.add_bit(Bit("c", 0)) + circ.Measure(0, 0) + circ.X(0) + + error_message = re.escape("Circuit QASM cannot be generated as circuit contains midcircuit measurements on qubit: q[0]") + with pytest.raises(ValueError, match=error_message): + tket_to_qasm3(circ) + +def test_translation_with_gate_overrides(simple_bell_circuit): + gate_override = { + OpType.CX: "CNOT", + OpType.H: "HADAMARD" + } + expected = """ +OPENQASM 3.0; +bit[3] c; +rx(0.5*pi) $0; +CNOT $1,$2; +HADAMARD $0; +CNOT $0,$1; +c[2] = measure $2; +c[0] = measure $0; +c[1] = measure $1; +""".strip() + qasm_result = tket_to_qasm3(simple_bell_circuit, gate_overrides=gate_override).strip() + assert qasm_result == expected \ No newline at end of file From 4b207eeab175a270d6c5d29fa42fc020d432ffea Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 25 Jun 2024 15:27:15 -0700 Subject: [PATCH 48/91] change: Remove references to NativeGateCriterion and SupportedGateCriterion --- src/braket/aws/aws_device.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 822c02174..37ddcabc2 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -888,8 +888,6 @@ def _setup_emulator(self, emulator_noise_model: NoiseModel = None) -> Emulator: self._emulator.add_pass(create_qubit_count_criterion(self.properties)) self._emulator.add_pass(create_gate_criterion(self.properties)) - # self._emulator.add_pass(create_supported_gate_criterion(self.properties)) - # self._emulator.add_pass(create_native_gate_criterion(self.properties)) self._emulator.add_pass(create_connectivity_criterion(self.properties, self.topology_graph)) self._emulator.add_pass(create_gate_connectivity_criterion(self.properties, self.topology_graph)) self._emulator.add_pass(create_lexi_mapping_routing_pass(self.properties, self.topology_graph)) From 726879956241371850c73ed798f07efcf0291df5 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 25 Jun 2024 15:44:29 -0700 Subject: [PATCH 49/91] fix: Add set apply_noise_model flag to false in Emulator.run to prevent noise_model from being applied to a circuit twice. --- src/braket/emulators/emulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/emulators/emulator.py b/src/braket/emulators/emulator.py index 8e22e6403..1c2d6af2f 100644 --- a/src/braket/emulators/emulator.py +++ b/src/braket/emulators/emulator.py @@ -45,7 +45,7 @@ def run(self, This method validates the input program against the emulator's passes and applies any provided noise model before running the circuit. """ - task_specification = self.run_program_passes(task_specification) + task_specification = self.run_program_passes(task_specification, apply_noise_model=False) #Don't apply noise model as the local simulator will automatically apply it. return self._backend.run(task_specification, shots, inputs, *args, **kwargs) From a55edb24e7c515f3256e83dec4054d8e92898988 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 25 Jun 2024 17:04:31 -0700 Subject: [PATCH 50/91] change: Target default simulator branch with classical register indices implementation --- setup.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 43d2c52ad..3845fd479 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.21.3", - "amazon-braket-default-simulator>=1.21.4", + "amazon-braket-default-simulator @ git+https://github.com/Altanali/amazon-braket-default-simulator-python.git@main", "oqpy~=0.3.5", "backoff", "boltons", diff --git a/tox.ini b/tox.ini index 95c862106..39ef86dd5 100644 --- a/tox.ini +++ b/tox.ini @@ -130,4 +130,4 @@ commands = deps = # If you need to test on a certain branch, add @ after .git git+https://github.com/amazon-braket/amazon-braket-schemas-python.git - git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git + git+https://github.com/Altanali/amazon-braket-default-simulator-python.git@main From e39941d30ade90d9719c8ea86654e029e345f517 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 25 Jun 2024 17:32:45 -0700 Subject: [PATCH 51/91] fix: Run linter and fix formatting/complexity --- setup.py | 3 ++- src/braket/circuits/braket_program_context.py | 9 +++++++-- src/braket/circuits/circuit.py | 13 +++++-------- .../unit_tests/braket/circuits/test_circuit.py | 18 ++++++++++++------ 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/setup.py b/setup.py index 3845fd479..ca5b0f943 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,8 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.21.3", - "amazon-braket-default-simulator @ git+https://github.com/Altanali/amazon-braket-default-simulator-python.git@main", + "amazon-braket-default-simulator @ \ + git+https://github.com/Altanali/amazon-braket-default-simulator-python.git@main", "oqpy~=0.3.5", "backoff", "boltons", diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index f3c75cd4f..37ddaf012 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -11,8 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import Optional, Union from collections.abc import Iterable +from typing import Optional, Union import numpy as np from sympy import Expr, Number @@ -162,11 +162,16 @@ def handle_parameter_value( return FreeParameterExpression(evaluated_value) return value - def add_measure(self, target: tuple[int], classical_targets: Iterable[int] = None) -> None: + def add_measure( + self, target: tuple[int], classical_targets: Iterable[int] | None = None + ) -> None: """Add a measure instruction to the circuit Args: target (tuple[int]): the target qubits to be measured. + + classical_targets (Iterable[int] | None): the classical registers + to use in the qubit measurement. """ for iter, qubit in enumerate(target): index = classical_targets[iter] if classical_targets else iter diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index ff846c237..9ce7df7f8 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -503,14 +503,12 @@ def add_instruction( # Check if there is a measure instruction on the circuit self._check_if_qubit_measured(instruction, target, target_mapping) - - #Update measure targets if instruction is a measurement + + # Update measure targets if instruction is a measurement if isinstance(instruction.operator, Measure): - if self._measure_targets: - self._measure_targets.append(target or instruction.target[0]) - else: - self._measure_targets = [target or instruction.target[0]] - + measure_target = target or instruction.target[0] + self._measure_targets = (self._measure_targets or []) + [measure_target] + if not target_mapping and not target: # Nothing has been supplied, add instruction instructions_to_add = [instruction] @@ -718,7 +716,6 @@ def _add_measure(self, target_qubits: QubitSetInput) -> None: ) ) - def measure(self, target_qubits: QubitSetInput) -> Circuit: """ Add a `measure` operator to `self` ensuring only the target qubits are measured. diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 755fc77fa..a126768ee 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -890,11 +890,16 @@ def test_from_ir_round_trip_transformation(): assert Circuit.from_ir(ir) == Circuit.from_ir(circuit.to_ir("OPENQASM")) assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") + def test_from_ir_round_trip_transformation_with_targeted_measurements(): - circuit = Circuit().h(0).cnot(0, 1).\ - add_instruction(Instruction(Measure(index=2), 1)).\ - add_instruction(Instruction(Measure(index=1), 2)).\ - add_instruction(Instruction(Measure(index=0), 0)) + circuit = ( + Circuit() + .h(0) + .cnot(0, 1) + .add_instruction(Instruction(Measure(index=2), 1)) + .add_instruction(Instruction(Measure(index=1), 2)) + .add_instruction(Instruction(Measure(index=0), 0)) + ) ir = OpenQasmProgram( source="\n".join( [ @@ -904,8 +909,8 @@ def test_from_ir_round_trip_transformation_with_targeted_measurements(): "h q[0];", "cnot q[0], q[1];", "b[2] = measure q[1];", - "b[1] = measure q[2];", - "b[0] = measure q[0];" + "b[1] = measure q[2];", + "b[0] = measure q[0];", ] ), inputs={}, @@ -914,6 +919,7 @@ def test_from_ir_round_trip_transformation_with_targeted_measurements(): assert Circuit.from_ir(ir) == Circuit.from_ir(circuit.to_ir("OPENQASM")) assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") + def test_add_with_instruction_with_default(cnot_instr): circ = Circuit().add(cnot_instr) assert circ == Circuit().add_instruction(cnot_instr) From ac496771556804f16436a417a8be82d147742f8c Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 26 Jun 2024 11:59:27 -0700 Subject: [PATCH 52/91] feat: Run Linter --- src/braket/aws/aws_device.py | 89 ++++--- src/braket/aws/aws_emulator_helpers.py | 153 ++++++----- src/braket/aws/aws_noise_models.py | 201 +++++++------- src/braket/emulators/emulater_interface.py | 25 +- src/braket/emulators/emulator.py | 76 +++--- .../emulators/emulator_passes/__init__.py | 12 +- .../emulator_passes/criteria/__init__.py | 8 +- .../criteria/connectivity_criterion.py | 126 +++++---- .../criteria/emulator_criterion.py | 23 +- .../criteria/gate_connectivity_criterion.py | 109 +++++--- .../criteria/gate_criterion.py | 41 +-- .../criteria/qubit_count_criterion.py | 15 +- .../emulator_passes/emulator_pass.py | 5 +- .../emulator_passes/lexi_routing_pass.py | 47 ++-- .../emulators/pytket_translator/__init__.py | 8 +- .../pytket_translator/composed_gates.py | 56 +++- .../pytket_program_context.py | 53 ++-- .../pytket_translator/qasm3_gen/__init__.py | 2 +- .../qasm3_gen/qasm_context.py | 19 +- .../qasm3_gen/qasm_writer.py | 49 ++-- .../qasm3_gen/tket_to_qasm3.py | 150 ++++++++--- .../pytket_translator/translations.py | 19 +- .../pytket_translator/test_tket_to_qasm3.py | 39 +-- .../emulation/test_connectivity_criterion.py | 171 ++++++------ .../test_gate_connectivity_criterion.py | 251 +++++++++--------- .../braket/emulation/test_gate_criterion.py | 126 +++++---- .../emulation/test_qubit_count_criterion.py | 56 ++-- 27 files changed, 1088 insertions(+), 841 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 37ddcabc2..c8c2b362d 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -27,6 +27,14 @@ from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem +from braket.aws.aws_emulator_helpers import ( + create_connectivity_criterion, + create_gate_connectivity_criterion, + create_gate_criterion, + create_lexi_mapping_routing_pass, + create_qubit_count_criterion, +) +from braket.aws.aws_noise_models import create_device_noise_model from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_quantum_task_batch import AwsQuantumTaskBatch from braket.aws.aws_session import AwsSession @@ -39,8 +47,9 @@ # TODO: Remove device_action module once this is added to init in the schemas repo from braket.device_schema.pulse.pulse_device_action_properties_v1 import PulseDeviceActionProperties -from braket.devices.device import Device from braket.devices import Devices +from braket.devices.device import Device +from braket.emulators import Emulator from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram from braket.parametric.free_parameter import FreeParameter @@ -48,18 +57,8 @@ from braket.pulse import ArbitraryWaveform, Frame, Port, PulseSequence from braket.pulse.waveforms import _parse_waveform_from_calibration_schema from braket.schema_common import BraketSchemaBase - - -from braket.emulators import Emulator -from braket.aws.aws_emulator_helpers import ( - create_qubit_count_criterion, - create_gate_criterion, - create_connectivity_criterion, - create_gate_connectivity_criterion, - create_lexi_mapping_routing_pass -) from braket.tasks import QuantumTask -from braket.aws.aws_noise_models import create_device_noise_model + class AwsDeviceType(str, Enum): """Possible AWS device types""" @@ -871,64 +870,73 @@ def _parse_calibration_json( @property def emulator(self) -> Emulator: if self._arn in Devices.Amazon: - raise ValueError("Creating an emulator from a Braket managed simulator is not supported.") + raise ValueError( + "Creating an emulator from a Braket managed simulator is not supported." + ) if not hasattr(self, "_emulator"): self._emulator = self._setup_emulator() return self._emulator - - - def _setup_emulator(self, emulator_noise_model: NoiseModel = None) -> Emulator: - """ - Sets up an Emulator object whose properties mimic that of this AwsDevice, if the device is a - real QPU (not simulated). + + def _setup_emulator(self, emulator_noise_model: NoiseModel = None) -> Emulator: + """ + Sets up an Emulator object whose properties mimic that of this AwsDevice, if the device is a + real QPU (not simulated). """ if not emulator_noise_model: emulator_noise_model = create_device_noise_model(self.properties, self._arn) self._emulator = Emulator(noise_model=emulator_noise_model, backend="braket_dm") - + self._emulator.add_pass(create_qubit_count_criterion(self.properties)) self._emulator.add_pass(create_gate_criterion(self.properties)) self._emulator.add_pass(create_connectivity_criterion(self.properties, self.topology_graph)) - self._emulator.add_pass(create_gate_connectivity_criterion(self.properties, self.topology_graph)) - self._emulator.add_pass(create_lexi_mapping_routing_pass(self.properties, self.topology_graph)) + self._emulator.add_pass( + create_gate_connectivity_criterion(self.properties, self.topology_graph) + ) + self._emulator.add_pass( + create_lexi_mapping_routing_pass(self.properties, self.topology_graph) + ) return self._emulator - - - def validate(self, task_specification: Union[ + + def validate( + self, + task_specification: Union[ Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation, - ]): + ], + ): """ - 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 the input program without modification. + 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 the input program without modification. """ self.emulator.run_validation_passes(task_specification) return task_specification - - - def run_emulator_passes(self, task_specification: Union[ + + def run_emulator_passes( + self, + task_specification: Union[ Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation, - ], apply_noise_model=True): + ], + apply_noise_model=True, + ): """ Runs all emulator passes and returns the modified program, which should be the same type as the input program. """ if isinstance(task_specification, Circuit): task_specification = task_specification.copy() - + return self.emulator.run_program_passes(task_specification, apply_noise_model) - - + def emulate( self, task_specification: Union[ @@ -943,11 +951,8 @@ def emulate( inputs: Optional[dict[str, float]] = None, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, ) -> QuantumTask: - + if isinstance(task_specification, Circuit): task_specification = task_specification.copy() - - return self.emulator.run( - task_specification, shots=shots, inputs=inputs - ) - \ No newline at end of file + + return self.emulator.run(task_specification, shots=shots, inputs=inputs) diff --git a/src/braket/aws/aws_emulator_helpers.py b/src/braket/aws/aws_emulator_helpers.py index 12a3417ee..74947943b 100644 --- a/src/braket/aws/aws_emulator_helpers.py +++ b/src/braket/aws/aws_emulator_helpers.py @@ -1,95 +1,107 @@ -from braket.emulators import Emulator -from braket.emulators.emulator_passes import ( - ConnectivityCriterion, - GateConnectivityCriterion, - QubitCountCriterion, - LexiRoutingPass, - GateCriterion -) +from collections.abc import Iterable +from functools import singledispatch +from typing import Union + +from networkx import DiGraph + from braket.device_schema import ( DeviceActionType, + DeviceCapabilities, StandardizedGateModelQpuDeviceProperties, - DeviceCapabilities ) from braket.device_schema.ionq import IonqDeviceCapabilities -from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.device_schema.iqm import IqmDeviceCapabilities - -from functools import singledispatch -from typing import Union -from collections.abc import Iterable -from networkx import DiGraph +from braket.device_schema.rigetti import RigettiDeviceCapabilities +from braket.emulators import Emulator +from braket.emulators.emulator_passes import ( + ConnectivityCriterion, + GateConnectivityCriterion, + GateCriterion, + LexiRoutingPass, + QubitCountCriterion, +) - - def create_qubit_count_criterion(properties: DeviceCapabilities) -> QubitCountCriterion: qubit_count = properties.paradigm.qubitCount return QubitCountCriterion(qubit_count) + def create_gate_criterion(properties: DeviceCapabilities) -> GateCriterion: supported_gates = properties.action[DeviceActionType.OPENQASM].supportedOperations """TODO: Issue in IQM Garnet Supported Operations: Includes "startVerbatimBox" and "endVerbatimBox" instructions in supported operations, - which are braket specific pragmas. Filter out explicitly until they are removed from device properties.""" - + which are braket specific pragmas. Filter out explicitly until they are removed from device properties.""" + if isinstance(properties, IqmDeviceCapabilities): - try: + try: supported_gates.remove("start_verbatim_box") supported_gates.remove("end_verbatim_box") except ValueError: pass - + native_gates = properties.paradigm.nativeGateSet - + return GateCriterion(supported_gates=supported_gates, native_gates=native_gates) + @singledispatch -def create_connectivity_criterion(properties: DeviceCapabilities, connectivity_graph: DiGraph) -> ConnectivityCriterion: +def create_connectivity_criterion( + properties: DeviceCapabilities, connectivity_graph: DiGraph +) -> ConnectivityCriterion: connectivity_criterion = ConnectivityCriterion(connectivity_graph) return connectivity_criterion + @create_connectivity_criterion.register(IqmDeviceCapabilities) def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> ConnectivityCriterion: """ - IQM qubit connectivity is undirected but the directed graph that represents qubit connectivity + 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 ConnectivityCriterion 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) + @singledispatch -def create_gate_connectivity_criterion(properties, connectivity_graph: DiGraph) -> GateConnectivityCriterion: +def create_gate_connectivity_criterion( + properties, connectivity_graph: DiGraph +) -> GateConnectivityCriterion: raise NotImplementedError + @create_gate_connectivity_criterion.register(RigettiDeviceCapabilities) -def _(properties: RigettiDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: +def _( + properties: RigettiDeviceCapabilities, connectivity_graph: DiGraph +) -> GateConnectivityCriterion: """ - Rigetti provides device capabilities using a standardized properties schema for gate devices. - - Rigetti provides both forwards and backwards edges for their undirected gate connectivity graph, so - no new needs to be introduced when creating a GateConnectivityCriterion object for a Rigetti QPU. + Rigetti provides device capabilities using a standardized properties schema for gate devices. + + Rigetti provides both forwards and backwards edges for their undirected gate + connectivity graph, so no new needs to be introduced when creating a + GateConnectivityCriterion object for a Rigetti QPU. """ gate_connectivity_graph = connectivity_graph.copy() edge_properties = properties.standardized.twoQubitProperties - for edge in gate_connectivity_graph.edges: + for edge in gate_connectivity_graph.edges: edge_key = "-".join([str(qubit) for qubit in edge]) - edge_property = edge_properties.get(edge_key, list()) + edge_property = edge_properties.get(edge_key, list()) if not edge_property: continue - edge_supported_gates = get_qpu_gate_translation(properties, - [property.gateName for property in edge_property.twoQubitGateFidelity]) + edge_supported_gates = get_qpu_gate_translation( + properties, [property.gateName for property in edge_property.twoQubitGateFidelity] + ) gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] = edge_supported_gates - + return GateConnectivityCriterion(gate_connectivity_graph) - - + + @create_gate_connectivity_criterion.register(IqmDeviceCapabilities) -def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: +def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: """ - IQM provides device capabilities using a standardized properties schema for gate devices. + IQM provides device capabilities using a standardized properties schema for gate devices. IQM provides only forward edges for their *undirected* gate connectivity graph, so back-edges must be introduced when creating the GateConnectivityCriterion object for an IQM QPU. @@ -97,34 +109,38 @@ def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> GateCon gate_connectivity_graph = connectivity_graph.copy() for edge in gate_connectivity_graph.edges: gate_connectivity_graph.add_edge(edge[1], edge[0]) - + edge_properties = properties.standardized.twoQubitProperties for edge_property in edge_properties.keys(): edge = [int(qubit) for qubit in edge_property.split("-")] - edge_supported_gates = get_qpu_gate_translation(properties, - [property.gateName for property in edge_properties[edge_property].twoQubitGateFidelity]) + edge_supported_gates = get_qpu_gate_translation( + properties, + [property.gateName for property in edge_properties[edge_property].twoQubitGateFidelity], + ) gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] = edge_supported_gates gate_connectivity_graph[edge[1]][edge[0]]["supported_gates"] = edge_supported_gates return GateConnectivityCriterion(gate_connectivity_graph) - - + @create_gate_connectivity_criterion.register(IonqDeviceCapabilities) -def _(properties: IonqDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: +def _(properties: IonqDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: """ - Qubits in IonQ's trapped ion devices are all fully connected with identical gate-pair capabilities. - Thus, IonQ does not expliclty provide a set of edges for gate connectivity between qubit pairs in - their trapped ion QPUs. We extrapolate gate connectivity across all possible qubit edge pairs. + Qubits in IonQ's trapped ion devices are all fully connected with identical gate-pair capabilities. + Thus, IonQ does not expliclty provide a set of edges for gate connectivity between qubit pairs in + their trapped ion QPUs. We extrapolate gate connectivity across all possible qubit edge pairs. """ gate_connectivity_graph = connectivity_graph.copy() native_gates = get_qpu_gate_translation(properties, properties.paradigm.nativeGateSet) - for edge in gate_connectivity_graph.edges: + for edge in gate_connectivity_graph.edges: gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] = native_gates - + return GateConnectivityCriterion(gate_connectivity_graph) -def get_qpu_gate_translation(properties: DeviceCapabilities, gate_name: Union[str, Iterable[str]]) -> Union[str, list[str]]: + +def get_qpu_gate_translation( + properties: DeviceCapabilities, gate_name: Union[str, Iterable[str]] +) -> Union[str, list[str]]: """Returns the translated gate name(s) for a given QPU ARN and gate name(s). Args: @@ -154,42 +170,45 @@ def _get_qpu_gate_translation(properties, gate_name: str) -> str: return gate_name -#TODO: put translations in global dict with explicit QHP names as keys? +# TODO: put translations in global dict with explicit QHP names as keys? + @_get_qpu_gate_translation.register(RigettiDeviceCapabilities) -def _(properties: RigettiDeviceCapabilities, gate_name: str) -> str: - translations = { - "CPHASE": "CPhaseShift" - } - return translations.get(gate_name, gate_name) +def _(properties: RigettiDeviceCapabilities, gate_name: str) -> str: + translations = {"CPHASE": "CPhaseShift"} + return translations.get(gate_name, gate_name) + @_get_qpu_gate_translation.register(IonqDeviceCapabilities) -def _(properties: IonqDeviceCapabilities, gate_name: str) -> str: - translations = { - "GPI": "GPi", - "GPI2": "GPi2" - } +def _(properties: IonqDeviceCapabilities, gate_name: str) -> str: + translations = {"GPI": "GPi", "GPI2": "GPi2"} return translations.get(gate_name, gate_name) @singledispatch -def create_lexi_mapping_routing_pass(properties: DeviceCapabilities, connectivity_graph: DiGraph) -> LexiRoutingPass: +def create_lexi_mapping_routing_pass( + properties: DeviceCapabilities, connectivity_graph: DiGraph +) -> LexiRoutingPass: raise NotImplementedError @create_lexi_mapping_routing_pass.register(RigettiDeviceCapabilities) @create_lexi_mapping_routing_pass.register(IonqDeviceCapabilities) -def _(properties: Union[RigettiDeviceCapabilities, IonqDeviceCapabilities], connectivity_graph: DiGraph) -> LexiRoutingPass: +def _( + properties: Union[RigettiDeviceCapabilities, IonqDeviceCapabilities], + connectivity_graph: DiGraph, +) -> LexiRoutingPass: return LexiRoutingPass(connectivity_graph) + @create_lexi_mapping_routing_pass.register(IqmDeviceCapabilities) def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> LexiRoutingPass: """ IQM provides only forward edges for their *undirected* gate connectivity graph, so back-edges must be introduced when creating the GateConnectivityCriterion object for an IQM QPU. """ - + connectivity_graph = connectivity_graph.copy() for edge in connectivity_graph.edges: connectivity_graph.add_edge(edge[1], edge[0]) - return LexiRoutingPass(connectivity_graph) \ No newline at end of file + return LexiRoutingPass(connectivity_graph) diff --git a/src/braket/aws/aws_noise_models.py b/src/braket/aws/aws_noise_models.py index 87467fdf2..4a3c45413 100644 --- a/src/braket/aws/aws_noise_models.py +++ b/src/braket/aws/aws_noise_models.py @@ -1,32 +1,35 @@ -from braket.circuits.noise_model import NoiseModel, GateCriteria, ObservableCriteria +import logging +from dataclasses import dataclass +from functools import singledispatch +from typing import Dict, List, Set, Tuple, Union + +import numpy as np + +from braket.aws.aws_emulator_helpers import get_qpu_gate_translation +from braket.circuits import Gate +from braket.circuits.noise_model import GateCriteria, NoiseModel, ObservableCriteria from braket.circuits.noises import ( AmplitudeDamping, BitFlip, Depolarizing, PhaseDamping, - TwoQubitDepolarizing + TwoQubitDepolarizing, ) -from typing import Dict, Tuple, List, Set, Union -from braket.circuits import Gate -from braket.devices import Devices -from braket.aws.aws_emulator_helpers import get_qpu_gate_translation - from braket.device_schema import ( DeviceActionType, + DeviceCapabilities, StandardizedGateModelQpuDeviceProperties, - DeviceCapabilities ) from braket.device_schema.ionq import IonqDeviceCapabilities -from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.device_schema.iqm import IqmDeviceCapabilities +from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.device_schema.standardized_gate_model_qpu_device_properties_v1 import ( - StandardizedGateModelQpuDeviceProperties, OneQubitProperties, GateFidelity2Q, Fidelity1Q + Fidelity1Q, + GateFidelity2Q, + OneQubitProperties, + StandardizedGateModelQpuDeviceProperties, ) - -import numpy as np -from dataclasses import dataclass -from functools import singledispatch -import logging +from braket.devices import Devices """ The following gate duration values are not available through Braket device calibration data and must @@ -35,19 +38,18 @@ QPU_GATE_DURATIONS = { Devices.Rigetti.AspenM3: { "single_qubit_gate_duration": 40e-9, - "two_qubit_gate_duration": 240e-9 - }, - Devices.IQM.Garnet: { - "single_qubit_gate_duration": 32e-9, - "two_qubit_gate_duration": 60e-9 - } + "two_qubit_gate_duration": 240e-9, + }, + Devices.IQM.Garnet: {"single_qubit_gate_duration": 32e-9, "two_qubit_gate_duration": 60e-9}, } + @dataclass class GateFidelity: gate: Gate - fidelity:float - + fidelity: float + + @dataclass class GateDeviceCalibrationData: single_qubit_gate_duration: float @@ -55,35 +57,37 @@ class GateDeviceCalibrationData: qubit_labels: Set[int] single_qubit_specs: Dict[int, Dict[str, float]] two_qubit_edge_specs: Dict[Tuple[int, int], List[GateFidelity]] - + def _validate_single_qubit_specs(self): for qubit in self.single_qubit_specs.keys(): if qubit not in self.qubit_labels: raise ValueError(f"Invalid qubit label {qubit}") - + def _validate_two_qubit_specs(self): for edge in self.two_qubit_edge_specs.keys(): if edge[0] not in self.qubit_labels or edge[1] not in self.qubit_labels: raise ValueError(f"Invalid qubit pair {edge}") - + def __post_init__(self): self._validate_single_qubit_specs() self._validate_two_qubit_specs() - - + + def create_device_noise_model(properties: DeviceCapabilities, arn: str) -> NoiseModel: device_calibration_data = _setup_calibration_specs(properties, arn) noise_model = _setup_basic_noise_model_strategy(device_calibration_data) return noise_model + @singledispatch def _setup_calibration_specs(properties, arn: str) -> NoiseModel: - raise NotImplementedError(f"A noise model cannot be created from device capabilities with \ - type {type(properties)}.") - + raise NotImplementedError( + f"A noise model cannot be created from device capabilities with \ + type {type(properties)}." + ) -#Rigetti and IonQ provide calibration data in a standardized data structure that can be parsed generally. +# Rigetti and IonQ provide calibration data in a standardized data structure that can be parsed generally. @_setup_calibration_specs.register(RigettiDeviceCapabilities) @_setup_calibration_specs.register(IqmDeviceCapabilities) def _(properties: Union[RigettiDeviceCapabilities, IqmDeviceCapabilities], arn: str) -> NoiseModel: @@ -92,42 +96,47 @@ def _(properties: Union[RigettiDeviceCapabilities, IqmDeviceCapabilities], arn: raise ValueError(f"Gate durations are not available for device {arn}") single_qubit_gate_duration = gate_durations["single_qubit_gate_duration"] two_qubit_gate_duration = gate_durations["two_qubit_gate_duration"] - + standardized_properties = properties.standardized one_qubit_properties = standardized_properties.oneQubitProperties qubit_labels = set(int(qubit) for qubit in one_qubit_properties.keys()) - single_qubit_specs = {int(qubit): _create_qubit_specs(one_qubit_properties[qubit]) for qubit in one_qubit_properties.keys()} - - + single_qubit_specs = { + int(qubit): _create_qubit_specs(one_qubit_properties[qubit]) + for qubit in one_qubit_properties.keys() + } + two_qubit_properties = standardized_properties.twoQubitProperties two_qubit_edge_specs = { - tuple(int(qubit) for qubit in edge.split("-")[0:2]): _create_edge_specs(properties, gate_fidelities.twoQubitGateFidelity) - for edge, gate_fidelities in two_qubit_properties.items()} - + tuple(int(qubit) for qubit in edge.split("-")[0:2]): _create_edge_specs( + properties, gate_fidelities.twoQubitGateFidelity + ) + for edge, gate_fidelities in two_qubit_properties.items() + } + return GateDeviceCalibrationData( single_qubit_gate_duration, two_qubit_gate_duration, qubit_labels, single_qubit_specs, - two_qubit_edge_specs + two_qubit_edge_specs, ) - - + + @_setup_calibration_specs.register(IonqDeviceCapabilities) def _(properties: IonqDeviceCapabilities, arn: str) -> NoiseModel: """ - IonQ's Trapped Ion Devices do not have per-qubit calibration data and instead - provide averaged qubit and two-qubit gate fidelities across the device. All qubits - are connected in a trapped ion device. - - We instead copy the averaged fidelities to each qubit and qubit edge pair. + IonQ's Trapped Ion Devices do not have per-qubit calibration data and instead + provide averaged qubit and two-qubit gate fidelities across the device. All qubits + are connected in a trapped ion device. + + We instead copy the averaged fidelities to each qubit and qubit edge pair. """ calibration_data = properties.provider fidelity_data = calibration_data.fidelity timing_data = calibration_data.timing qubit_count = properties.paradigm.qubitCount native_gates = get_qpu_gate_translation(properties, properties.paradigm.nativeGateSet) - + single_qubit_gate_duration = timing_data["1Q"] two_qubit_gate_duration = timing_data["2Q"] average_active_reset_fidelity = timing_data["reset"] @@ -144,88 +153,95 @@ def _(properties: IonqDeviceCapabilities, arn: str) -> NoiseModel: gate = getattr(Gate, gate_name) if gate.fixed_qubit_count() != 2: """ - The noise model applies depolarizing noise associated with the individual qubits themselves (RB/sRB fidelities). + The noise model applies depolarizing noise associated with the individual qubits themselves (RB/sRB fidelities). This is a choice of this particular model to generalize the implementation as not all QHPs provide - single-qubit gate fidelities. + single-qubit gate fidelities. """ continue native_gate_fidelities.append(GateFidelity(gate, two_qubit_rb_fidelity)) else: continue logging.warning(f"Unsupported gate {native_gate}") - + single_qubit_specs = {} two_qubit_edge_specs = {} for ii in range(qubit_count): qubit_spec = { - "RANDOMIZED_BENCHMARKING": single_qubit_rb_fidelity, + "RANDOMIZED_BENCHMARKING": single_qubit_rb_fidelity, "SIMULTANEOUS_RANDOMIZED_BENCHMARKING": None, "READOUT": average_readout_fidelity, "T1": average_T1, "T2": average_T2, - "ACTIVE_RESET": average_active_reset_fidelity + "ACTIVE_RESET": average_active_reset_fidelity, } single_qubit_specs[ii] = qubit_spec - + for jj in range(ii + 1, qubit_count): two_qubit_edge_specs[(ii, jj)] = native_gate_fidelities - + return GateDeviceCalibrationData( single_qubit_gate_duration, two_qubit_gate_duration, set(range(qubit_count)), single_qubit_specs, - two_qubit_edge_specs + two_qubit_edge_specs, ) - + + def _create_qubit_specs(qubit_properties: OneQubitProperties) -> Dict[str, int]: T1 = qubit_properties.T1.value T2 = qubit_properties.T2.value qubit_fidelities = qubit_properties.oneQubitFidelity one_qubit_fidelities = { - qubit_fidelity.fidelityType.name : qubit_fidelity.fidelity for qubit_fidelity in qubit_fidelities + qubit_fidelity.fidelityType.name: qubit_fidelity.fidelity + for qubit_fidelity in qubit_fidelities } one_qubit_fidelities["T1"] = T1 one_qubit_fidelities["T2"] = T2 return one_qubit_fidelities -def _create_edge_specs(properties: DeviceCapabilities, edge_properties: List[GateFidelity2Q]) -> List[GateFidelity]: - edge_specs = [] - for edge_property in edge_properties: - gate_name = get_qpu_gate_translation(properties, edge_property.gateName) - if hasattr(Gate, gate_name): - gate = getattr(Gate, gate_name) - edge_specs.append(GateFidelity(gate, edge_property.fidelity)) - else: - continue - # logging.warning(f"Unsupported gate {gate_name}") - return edge_specs - - -def _setup_basic_noise_model_strategy(gate_calibration_data: GateDeviceCalibrationData) -> NoiseModel: + +def _create_edge_specs( + properties: DeviceCapabilities, edge_properties: List[GateFidelity2Q] +) -> List[GateFidelity]: + edge_specs = [] + for edge_property in edge_properties: + gate_name = get_qpu_gate_translation(properties, edge_property.gateName) + if hasattr(Gate, gate_name): + gate = getattr(Gate, gate_name) + edge_specs.append(GateFidelity(gate, edge_property.fidelity)) + else: + continue + # logging.warning(f"Unsupported gate {gate_name}") + return edge_specs + + +def _setup_basic_noise_model_strategy( + gate_calibration_data: GateDeviceCalibrationData, +) -> NoiseModel: """ - Apply a basic noise model strategy consisting of: - - T1 Dampening - - T2 Phase Dampening - - 1 Qubit RB Depolarizing Noise - - 1 Qubit Readout Error - - 2 Qubit Gate Depolarizing Noise + Apply a basic noise model strategy consisting of: + - T1 Dampening + - T2 Phase Dampening + - 1 Qubit RB Depolarizing Noise + - 1 Qubit Readout Error + - 2 Qubit Gate Depolarizing Noise """ noise_model = NoiseModel() gate_duration_1Q = gate_calibration_data.single_qubit_gate_duration gate_duration_2Q = gate_calibration_data.two_qubit_gate_duration for qubit, data in gate_calibration_data.single_qubit_specs.items(): - #T1 dampening + # T1 dampening T1 = data["T1"] - damping_prob = 1 - np.exp(-(gate_duration_1Q/T1)) + damping_prob = 1 - np.exp(-(gate_duration_1Q / T1)) noise_model.add_noise(AmplitudeDamping(damping_prob), GateCriteria(qubits=qubit)) - - #T2 Phase Dampening + + # T2 Phase Dampening T2 = data["T2"] - dephasing_prob = 0.5 * (1 - np.exp(-(gate_duration_1Q/T2))) + dephasing_prob = 0.5 * (1 - np.exp(-(gate_duration_1Q / T2))) noise_model.add_noise(PhaseDamping(dephasing_prob), GateCriteria(qubits=qubit)) - - #1 Qubit RB Depolarizing Noise + + # 1 Qubit RB Depolarizing Noise if "SIMULTANEOUS_RANDOMIZED_BENCHMARKING" in data: benchmark_fidelity = data["SIMULTANEOUS_RANDOMIZED_BENCHMARKING"] else: @@ -233,15 +249,18 @@ def _setup_basic_noise_model_strategy(gate_calibration_data: GateDeviceCalibrati if benchmark_fidelity: depolarizing_rate = 1 - benchmark_fidelity noise_model.add_noise(Depolarizing(depolarizing_rate), GateCriteria(qubits=qubit)) - - #1 Qubit Readout Error + + # 1 Qubit Readout Error readout_error_rate = 1 - data["READOUT"] noise_model.add_noise(BitFlip(readout_error_rate), ObservableCriteria(qubits=qubit)) - + for edge, data in gate_calibration_data.two_qubit_edge_specs.items(): for gate_fidelity in data: rate = 1 - gate_fidelity.fidelity gate = gate_fidelity.gate - noise_model.add_noise(TwoQubitDepolarizing(rate), GateCriteria(gate, [(edge[0], edge[1]), (edge[1], edge[0])])) - - return noise_model \ No newline at end of file + noise_model.add_noise( + TwoQubitDepolarizing(rate), + GateCriteria(gate, [(edge[0], edge[1]), (edge[1], edge[0])]), + ) + + return noise_model diff --git a/src/braket/emulators/emulater_interface.py b/src/braket/emulators/emulater_interface.py index c9a44587c..59d789f1b 100644 --- a/src/braket/emulators/emulater_interface.py +++ b/src/braket/emulators/emulater_interface.py @@ -1,9 +1,10 @@ from abc import ABC, abstractmethod -from braket.emulators.emulator_passes import EmulatorPass, ProgramType -from braket.emulators.emulator_passes.criteria import EmulatorCriterion from collections.abc import Iterator from typing import Iterable, Union +from braket.emulators.emulator_passes import EmulatorPass, ProgramType +from braket.emulators.emulator_passes.criteria import EmulatorCriterion + class EmulatorInterface(ABC): def __init__(self, emulator_passes: Iterable[EmulatorPass] = None): @@ -12,32 +13,30 @@ def __init__(self, emulator_passes: Iterable[EmulatorPass] = None): def run_program_passes[ProgramType](self, task_specification: ProgramType) -> ProgramType: """ This method passes the input program through the EmulatorPasses 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). + or may modify or entirely transform the program (to an equivalent quantum program). Args: - task_specification (ProgramType): The program to run the emulator passes on. + task_specification (ProgramType): The program to run the emulator passes on. - Returns: - (ProgramType): A "compiled" program of the same type as the input. + Returns: + (ProgramType): A "compiled" program of the same type as the input. """ for emulator_pass in self._emulator_passes: task_specification = emulator_pass(task_specification) return task_specification - - - def run_validation_passes[ProgramType](self, task_specification: ProgramType) -> None: + + def run_validation_passes[ProgramType](self, task_specification: ProgramType) -> None: """ - This method passes the input program through EmulatorPasses that perform only validation, without modifying the input program. + This method passes the input program through EmulatorPasses that perform only validation, without modifying the input program. """ for emulator_pass in self._emulator_passes: if isinstance(emulator_pass, EmulatorCriterion): emulator_pass(task_specification) - - + def add_pass(self, emulator_pass: Union[Iterable[EmulatorPass], EmulatorPass]) -> None: if isinstance(emulator_pass, Iterator): self._emulator_passes.extend(emulator_pass) elif isinstance(emulator_pass, EmulatorPass): self._emulator_passes.append(emulator_pass) else: - raise TypeError("emulator_pass must be an EmulatorPass or an iterable of EmulatorPass") \ No newline at end of file + raise TypeError("emulator_pass must be an EmulatorPass or an iterable of EmulatorPass") diff --git a/src/braket/emulators/emulator.py b/src/braket/emulators/emulator.py index 1c2d6af2f..aa4d290e1 100644 --- a/src/braket/emulators/emulator.py +++ b/src/braket/emulators/emulator.py @@ -1,60 +1,68 @@ from __future__ import annotations -from typing import Iterable, Any, Optional, Union, Optional + +import logging from abc import abstractmethod -from braket.simulator import BraketSimulator -from braket.tasks import QuantumTask +from typing import Any, Iterable, Optional, Union + +from braket.circuits import Circuit +from braket.circuits.noise_model import NoiseModel +from braket.devices import Device from braket.devices.local_simulator import LocalSimulator from braket.emulators.emulater_interface import EmulatorInterface from braket.emulators.emulator_passes import EmulatorPass, ProgramType -from braket.devices import Device -from braket.circuits import Circuit -from braket.circuits.noise_model import NoiseModel from braket.ir.openqasm import Program as OpenQasmProgram +from braket.simulator import BraketSimulator +from braket.tasks import QuantumTask from braket.tasks.quantum_task_batch import QuantumTaskBatch -import logging + class Emulator(Device, EmulatorInterface): """An emulator is a simulation device that more closely resembles the capabilities and constraints of a real device or of a specific device model.""" - def __init__(self, backend: Union[str, Device]="default", - noise_model: Optional[NoiseModel] = None, - emulator_passes: Iterable[EmulatorPass] = None, **kwargs): - + def __init__( + self, + backend: Union[str, Device] = "default", + noise_model: Optional[NoiseModel] = None, + emulator_passes: Iterable[EmulatorPass] = None, + **kwargs, + ): + EmulatorInterface.__init__(self, emulator_passes) self._noise_model = noise_model - if noise_model and backend == "default": - logging.info("Setting LocalSimulator backend to use 'braket_dm' because a NoiseModel was provided.") + if noise_model and backend == "default": + logging.info( + "Setting LocalSimulator backend to use 'braket_dm' because a NoiseModel was provided." + ) backend = "braket_dm" - - self._backend = LocalSimulator(backend=backend, noise_model=noise_model) + self._backend = LocalSimulator(backend=backend, noise_model=noise_model) - def run(self, - task_specification: Union[ + def run( + self, + task_specification: Union[ Circuit, OpenQasmProgram, - ], - shots: int = 0, - inputs: Optional[dict[str, float]] = None, - dry_run=False, - *args: Any, - **kwargs: Any + ], + shots: int = 0, + inputs: Optional[dict[str, float]] = None, + dry_run=False, + *args: Any, + **kwargs: Any, ) -> QuantumTask: """ This method validates the input program against the emulator's passes and applies any provided noise model before running the circuit. """ - task_specification = self.run_program_passes(task_specification, apply_noise_model=False) #Don't apply noise model as the local simulator will automatically apply it. + task_specification = self.run_program_passes( + task_specification, apply_noise_model=False + ) # Don't apply noise model as the local simulator will automatically apply it. return self._backend.run(task_specification, shots, inputs, *args, **kwargs) - def run_batch( # noqa: C901 self, task_specifications: Union[ - Union[ - Circuit, OpenQasmProgram - ], + Union[Circuit, OpenQasmProgram], list[ Union[ Circuit, @@ -69,18 +77,20 @@ def run_batch( # noqa: C901 **kwargs, ) -> QuantumTaskBatch: raise NotImplementedError("Emulator.run_batch() is not implemented yet.") - + @property def noise_model(self): return self._noise_model - + @noise_model.setter def noise_model(self, noise_model: NoiseModel): self._noise_model = noise_model self._backend = LocalSimulator(backend="braket_dm", noise_model=noise_model) - - def run_program_passes(self, task_specification: ProgramType, apply_noise_model=True) -> ProgramType: + + def run_program_passes( + self, task_specification: ProgramType, apply_noise_model=True + ) -> ProgramType: program = super().run_program_passes(task_specification) if apply_noise_model: return self._noise_model.apply(program) - return program \ No newline at end of file + return program diff --git a/src/braket/emulators/emulator_passes/__init__.py b/src/braket/emulators/emulator_passes/__init__.py index e2d2e61f0..c95d67bbd 100644 --- a/src/braket/emulators/emulator_passes/__init__.py +++ b/src/braket/emulators/emulator_passes/__init__.py @@ -1,9 +1,9 @@ -from braket.emulators.emulator_passes.emulator_pass import EmulatorPass, ProgramType from braket.emulators.emulator_passes.criteria import ( - EmulatorCriterion, ConnectivityCriterion, - GateConnectivityCriterion, - QubitCountCriterion, - GateCriterion + EmulatorCriterion, + GateConnectivityCriterion, + GateCriterion, + QubitCountCriterion, ) -from braket.emulators.emulator_passes.lexi_routing_pass import LexiRoutingPass \ No newline at end of file +from braket.emulators.emulator_passes.emulator_pass import EmulatorPass, ProgramType +from braket.emulators.emulator_passes.lexi_routing_pass import LexiRoutingPass diff --git a/src/braket/emulators/emulator_passes/criteria/__init__.py b/src/braket/emulators/emulator_passes/criteria/__init__.py index a3af842ae..c90db322a 100644 --- a/src/braket/emulators/emulator_passes/criteria/__init__.py +++ b/src/braket/emulators/emulator_passes/criteria/__init__.py @@ -1,5 +1,7 @@ -from braket.emulators.emulator_passes.criteria.emulator_criterion import EmulatorCriterion from braket.emulators.emulator_passes.criteria.connectivity_criterion import ConnectivityCriterion -from braket.emulators.emulator_passes.criteria.gate_connectivity_criterion import GateConnectivityCriterion +from braket.emulators.emulator_passes.criteria.emulator_criterion import EmulatorCriterion +from braket.emulators.emulator_passes.criteria.gate_connectivity_criterion import ( + GateConnectivityCriterion, +) +from braket.emulators.emulator_passes.criteria.gate_criterion import GateCriterion from braket.emulators.emulator_passes.criteria.qubit_count_criterion import QubitCountCriterion -from braket.emulators.emulator_passes.criteria.gate_criterion import GateCriterion \ No newline at end of file diff --git a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py index 0f44b7551..d50f18d17 100644 --- a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py @@ -1,98 +1,118 @@ -from braket.emulators.emulator_passes.criteria import EmulatorCriterion +from collections.abc import Iterable +from typing import Dict, Union + from networkx import DiGraph, complete_graph, from_dict_of_lists from networkx.utils import graphs_equal -from typing import Union, Dict -from collections.abc import Iterable + from braket.circuits import Circuit -from braket.circuits.instruction import Instruction -from braket.registers.qubit_set import QubitSet -from braket.circuits.compiler_directives import StartVerbatimBox, EndVerbatimBox +from braket.circuits.compiler_directives import StartVerbatimBox from braket.circuits.gate import Gate +from braket.emulators.emulator_passes.criteria.emulator_criterion import EmulatorCriterion +from braket.registers.qubit_set import QubitSet - - -class ConnectivityCriterion(EmulatorCriterion): +class ConnectivityCriterion(EmulatorCriterion): """ - args: - connectivity_graph (Union[Dict[int, List[int]], DiGraph]): Either a sparse matrix or DiGraph + 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 + 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 + 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, - fully_connected = False, - num_qubits: int = None, - qubit_labels: Union[Iterable[int], QubitSet] = None, - directed: bool = False): - if not (connectivity_graph or fully_connected): - raise ValueError("Either the connectivity_graph must be provided or fully_connected must be True.") + + def __init__( + self, + connectivity_graph: Union[Dict[int, Iterable[int]], DiGraph] = None, + fully_connected=False, + num_qubits: int = None, + qubit_labels: Union[Iterable[int], QubitSet] = None, + directed: bool = False, + ): + if not (connectivity_graph or fully_connected): + raise ValueError( + "Either the connectivity_graph must be provided or fully_connected must be True." + ) if fully_connected: if not (num_qubits or qubit_labels) or (num_qubits and qubit_labels): - raise ValueError("Either num_qubits or qubit_labels (NOT both) must be provided if fully_connected is True.") - self._connectivity_graph = complete_graph(num_qubits if num_qubits else qubit_labels, create_using=DiGraph()) + raise ValueError( + "Either num_qubits or qubit_labels (NOT both) must be provided if fully_connected is True." + ) + self._connectivity_graph = complete_graph( + num_qubits if num_qubits else qubit_labels, create_using=DiGraph() + ) elif not isinstance(connectivity_graph, DiGraph): try: - self._connectivity_graph = from_dict_of_lists(connectivity_graph, create_using=DiGraph()) - except Exception as e: - raise ValueError(f"connectivity_graph must be a valid DiGraph or a dictionary mapping integers (nodes) to a list of integers (adjancency lists): {e}") - else: - self._connectivity_graph = connectivity_graph + self._connectivity_graph = from_dict_of_lists( + connectivity_graph, create_using=DiGraph() + ) + except Exception as e: + raise ValueError( + f"connectivity_graph must be a valid DiGraph or a dictionary mapping integers (nodes) to a list of integers (adjancency lists): {e}" + ) + else: + self._connectivity_graph = connectivity_graph if not directed: for edge in self._connectivity_graph.edges: self._connectivity_graph.add_edge(edge[1], edge[0]) - def validate(self, circuit: Circuit) -> None: + def validate(self, circuit: Circuit) -> None: """ - Verifies that any verbatim box in a circuit is runnable with respect to the - device connectivity definied by this criteria. + Verifies that any verbatim box in a circuit is runnable with respect to the + device connectivity definied by this criteria. """ - #If any of the instructions are in verbatim mode, all qubit references must point to hardware qubits. Otherwise, this circuit need not be validated. - if not any([isinstance(instruction.operator, StartVerbatimBox) for instruction in circuit.instructions]): - return - for idx in range(len(circuit.instructions)): + # If any of the instructions are in verbatim mode, all qubit references + # must point to hardware qubits. Otherwise, this circuit need not be validated. + if not any( + [ + isinstance(instruction.operator, StartVerbatimBox) + for instruction in circuit.instructions + ] + ): + return + for idx in range(len(circuit.instructions)): instruction = circuit.instructions[idx] if isinstance(instruction.operator, Gate): - if instruction.operator.qubit_count == 2: #Assuming only maximum 2-qubit native gates are supported + if ( + instruction.operator.qubit_count == 2 + ): # Assuming only maximum 2-qubit native gates are supported self.validate_instruction_connectivity(instruction.control, instruction.target) - else: - #just check that the target qubit exists in the connectivity graph + else: + # just check that the target qubit exists in the connectivity graph target_qubit = instruction.target[0] if not target_qubit in self._connectivity_graph: - raise ValueError(f"Qubit {target_qubit} does not exist in the device topology.") + raise ValueError( + f"Qubit {target_qubit} does not exist in the device topology." + ) - def validate_instruction_connectivity(self, control_qubits: QubitSet, target_qubits: QubitSet): - #Create edges between each of the target qubits + def validate_instruction_connectivity(self, control_qubits: QubitSet, target_qubits: QubitSet): + # Create edges between each of the target qubits gate_connectivity_graph = DiGraph() - #Create an edge from each control bit to each target qubit + # Create an edge from each control bit to each target qubit if len(control_qubits) == 1 and len(target_qubits) == 1: gate_connectivity_graph.add_edge(control_qubits[0], target_qubits[0]) elif len(target_qubits) == 2: - gate_connectivity_graph.add_edges_from([ - (target_qubits[0], target_qubits[1]), - (target_qubits[1], target_qubits[0]) - ]) - else: + gate_connectivity_graph.add_edges_from( + [(target_qubits[0], target_qubits[1]), (target_qubits[1], target_qubits[0])] + ) + else: raise ValueError("Unrecognized qubit targetting setup for a 2 qubit gate.") - #Check that each edge exists in this criterion's connectivity graph + # Check that each edge exists in this criterion's connectivity graph for e in gate_connectivity_graph.edges: if not self._connectivity_graph.has_edge(*e): raise ValueError(f"{e[0]} is not connected to qubit {e[1]} in this device.") - - - - def __eq__(self, other: EmulatorCriterion) -> bool: - return isinstance(other, ConnectivityCriterion) \ - and graphs_equal(self._connectivity_graph, other._connectivity_graph) + def __eq__(self, other: EmulatorCriterion) -> bool: + return isinstance(other, ConnectivityCriterion) and graphs_equal( + self._connectivity_graph, other._connectivity_graph + ) diff --git a/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py b/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py index dba6c4052..acb45caa7 100644 --- a/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py @@ -1,28 +1,31 @@ from __future__ import annotations + from abc import abstractmethod from typing import Dict -from braket.circuits import Circuit #TODO: abstract to general task_specification type? Possible w.r.t. noise models? -from braket.emulators.emulator_passes import EmulatorPass, ProgramType + +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, circuit: Circuit) -> None: """ - Args: + Args: circuit (Circuit): circuit to be evaluated against this criteria. - Returns: + Returns: returns nothing if the circuit is valid; otherwise, the appropriate error is raised. """ raise NotImplementedError - - + def run[ProgramType](self, program: ProgramType) -> ProgramType: self.validate(program) return program - + @abstractmethod def __eq__(self, other: EmulatorCriterion) -> bool: - raise NotImplementedError \ No newline at end of file + raise NotImplementedError diff --git a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py index 3c8dd7e2b..774253eb6 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py @@ -1,20 +1,21 @@ from dataclasses import dataclass +from typing import Any, Dict, Iterable, Set, Tuple, Union + +from networkx import DiGraph + from braket.circuits.circuit import Circuit -from braket.emulators.emulator_passes.criteria import EmulatorCriterion -from typing import Dict, Tuple, Union, Any, Set, Iterable -from braket.circuits.compiler_directives import StartVerbatimBox, EndVerbatimBox +from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox from braket.circuits.gate import Gate +from braket.emulators.emulator_passes.criteria.emulator_criterion import EmulatorCriterion from braket.registers.qubit_set import QubitSet -from networkx import DiGraph class GateConnectivityCriterion(EmulatorCriterion): - def __init__(self, - gate_connectivity_graph: Union[ - Dict[Tuple[Any, Any], Iterable[str]], - DiGraph - ], - directed=True): + def __init__( + self, + gate_connectivity_graph: Union[Dict[Tuple[Any, Any], Iterable[str]], DiGraph], + directed=True, + ): super().__init__() if isinstance(gate_connectivity_graph, DiGraph): self._gate_connectivity_graph = gate_connectivity_graph @@ -22,66 +23,92 @@ def __init__(self, for edge in self._gate_connectivity_graph.edges: back_edge = (edge[1], edge[0]) if back_edge not in self._gate_connectivity_graph.edges: - supported_gates = self._gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] - self._gate_connectivity_graph.add_edge(*back_edge, supported_gates=supported_gates) - + supported_gates = self._gate_connectivity_graph[edge[0]][edge[1]][ + "supported_gates" + ] + self._gate_connectivity_graph.add_edge( + *back_edge, supported_gates=supported_gates + ) + elif isinstance(gate_connectivity_graph, dict): self._gate_connectivity_graph = DiGraph() for edge, supported_gates in gate_connectivity_graph.items(): - self._gate_connectivity_graph.add_edge(edge[0], edge[1], supported_gates=supported_gates) + self._gate_connectivity_graph.add_edge( + edge[0], edge[1], supported_gates=supported_gates + ) if not directed: back_edge = (edge[1], edge[0]) if back_edge not in gate_connectivity_graph: - self._gate_connectivity_graph.add_edge(edge[1], edge[0], supported_gates=supported_gates) + self._gate_connectivity_graph.add_edge( + edge[1], edge[0], supported_gates=supported_gates + ) else: - raise TypeError("gate_connectivity_graph must either be a dictionary of edges mapped to supported gates lists, or a DiGraph with supported \ - gates provided as edge attributs. ") + raise TypeError( + "gate_connectivity_graph must either be a dictionary of edges mapped to supported gates lists, or a DiGraph with supported \ + gates provided as edge attributs. " + ) def validate(self, circuit: Circuit) -> None: """ - Verifies that any multiqubit gates used within a verbatim box are supported by the devices gate - connectivity defined by this criteria. + Verifies that any multiqubit gates used within a verbatim box are supported by the devices gate + connectivity defined by this criteria. """ - for idx in range(len(circuit.instructions)): + for idx in range(len(circuit.instructions)): instruction = circuit.instructions[idx] if isinstance(instruction.operator, StartVerbatimBox): idx += 1 - while idx < len(circuit.instructions) and not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): + while idx < len(circuit.instructions) and not isinstance( + circuit.instructions[idx].operator, EndVerbatimBox + ): instruction = circuit.instructions[idx] if isinstance(instruction.operator, Gate): - if instruction.operator.qubit_count == 2: #Assuming only maximum 2-qubit native gates are supported - self.validate_instruction_connectivity(instruction.operator.name, instruction.control, instruction.target) - else: - #just check that the target qubit exists in the connectivity graph + if ( + instruction.operator.qubit_count == 2 + ): # Assuming only maximum 2-qubit native gates are supported + self.validate_instruction_connectivity( + instruction.operator.name, instruction.control, instruction.target + ) + else: + # just check that the target qubit exists in the connectivity graph target_qubit = instruction.target[0] if not target_qubit in self._gate_connectivity_graph: - raise ValueError(f"Qubit {target_qubit} does not exist in the device topology.") + raise ValueError( + f"Qubit {target_qubit} does not exist in the device topology." + ) idx += 1 if 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(self, gate_name: str, control_qubits: QubitSet, target_qubits: QubitSet): - #Create edges between each of the target qubits - if len(control_qubits) == 1 and len(target_qubits) == 1: + + def validate_instruction_connectivity( + self, gate_name: str, control_qubits: QubitSet, target_qubits: QubitSet + ): + # Create edges between each of the target qubits + if len(control_qubits) == 1 and len(target_qubits) == 1: e = (control_qubits[0], target_qubits[0]) elif len(target_qubits) == 2: e = (target_qubits[0], target_qubits[1]) - else: + else: raise ValueError("Unrecognized qubit targetting setup for a 2 qubit gate.") - - #Check that each edge exists in this criterion's connectivity graph + + # Check that each edge exists in this criterion's connectivity graph if not self._gate_connectivity_graph.has_edge(*e): raise ValueError(f"{e[0]} is not connected to {e[1]} on this device.") supported_gates = self._gate_connectivity_graph[e[0]][e[1]]["supported_gates"] if gate_name not in supported_gates: - raise ValueError(f"Qubit pair ({e[0]}, {e[1]}) does not support gate {gate_name} on this device.") - - + raise ValueError( + f"Qubit pair ({e[0]}, {e[1]}) does not support gate {gate_name} on this device." + ) + def __eq__(self, other: EmulatorCriterion) -> bool: - return isinstance(other, GateConnectivityCriterion) \ - and all([self._gate_connectivity_graph[edge] == other._gate_connectivity_graph.get(edge) for edge in self._gate_connectivity_graph.keys()]) \ - and len(self._gate_connectivity_graph) == len(other._gate_connectivity_graph) \ No newline at end of file + return ( + isinstance(other, GateConnectivityCriterion) + and all( + [ + self._gate_connectivity_graph[edge] == other._gate_connectivity_graph.get(edge) + for edge in self._gate_connectivity_graph.keys() + ] + ) + and len(self._gate_connectivity_graph) == len(other._gate_connectivity_graph) + ) diff --git a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py index 210cfa5ea..6d88f2bfb 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py @@ -1,42 +1,47 @@ -from collections.abc import Iterator -from braket.circuits.gate import Gate -from braket.emulators.emulator_passes.criteria import EmulatorCriterion -from braket.circuits.compiler_directives import StartVerbatimBox, EndVerbatimBox +from collections.abc import Iterator + from braket.circuits import Circuit +from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox +from braket.circuits.gate import Gate from braket.circuits.translations import BRAKET_GATES +from braket.emulators.emulator_passes.criteria.emulator_criterion import EmulatorCriterion class GateCriterion(EmulatorCriterion): def __init__(self, supported_gates: Iterator[str] = [], native_gates: Iterator[str] = []): """ - args: + args: native_gates (Iterator[str]): A list of gates supported inside of verbatim mode by - the emulator. + 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. """ 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) - self._native_gates = set(BRAKET_GATES[gate.lower()] for gate in native_gates) + 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.") - def validate(self, circuit: Circuit) -> None: + def validate(self, circuit: Circuit) -> None: idx = 0 while idx < len(circuit.instructions): instruction = circuit.instructions[idx] if isinstance(instruction.operator, StartVerbatimBox): idx += 1 - while idx < len(circuit.instructions) and not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): + while idx < len(circuit.instructions) and not isinstance( + circuit.instructions[idx].operator, EndVerbatimBox + ): instruction = circuit.instructions[idx] if isinstance(instruction.operator, Gate): gate = instruction.operator if not type(gate) in self._native_gates: - raise ValueError(f"Gate {gate.name} is not a native gate supported by this emulator.") - idx += 1 + raise ValueError( + f"Gate {gate.name} is not a native gate supported by this emulator." + ) + idx += 1 if not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): raise ValueError(f"No end verbatim box found at index {idx} in the circuit.") elif isinstance(instruction.operator, Gate): @@ -44,10 +49,10 @@ def validate(self, circuit: Circuit) -> None: if not type(gate) in self._supported_gates: raise ValueError(f"Gate {gate.name} is not supported by this emulator.") idx += 1 - def __eq__(self, other: EmulatorCriterion) -> bool: - return isinstance(other, GateCriterion) and \ - self._supported_gates == other._supported_gates and \ - self._native_gates == other._native_gates - + return ( + isinstance(other, GateCriterion) + and self._supported_gates == other._supported_gates + and self._native_gates == other._native_gates + ) diff --git a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py index bc37bc170..c69033a2d 100644 --- a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py @@ -1,5 +1,5 @@ -from braket.emulators.emulator_passes.criteria import EmulatorCriterion -from braket.circuits import Circuit +from braket.circuits import Circuit +from braket.emulators.emulator_passes.criteria.emulator_criterion import EmulatorCriterion class QubitCountCriterion(EmulatorCriterion): @@ -7,16 +7,17 @@ class QubitCountCriterion(EmulatorCriterion): A simple criterion class that checks that an input program does not use more qubits than available on a device, as set during this criterion's instantiation. """ + def __init__(self, qubit_count: int): if qubit_count <= 0: raise ValueError(f"qubit_count ({qubit_count}) must be a positive integer.") self._qubit_count = qubit_count - - + def validate(self, circuit: Circuit) -> Circuit: if circuit.qubit_count > self._qubit_count: - raise ValueError(f"Circuit must use at most {self._qubit_count} qubits, but uses {circuit.qubit_count} qubits.") + raise ValueError( + f"Circuit must use at most {self._qubit_count} qubits, but uses {circuit.qubit_count} qubits." + ) def __eq__(self, other: EmulatorCriterion) -> bool: - return isinstance(other, QubitCountCriterion) and \ - self._qubit_count == other._qubit_count \ No newline at end of file + return isinstance(other, QubitCountCriterion) and self._qubit_count == other._qubit_count diff --git a/src/braket/emulators/emulator_passes/emulator_pass.py b/src/braket/emulators/emulator_passes/emulator_pass.py index 5464e4871..b86d0025d 100644 --- a/src/braket/emulators/emulator_passes/emulator_pass.py +++ b/src/braket/emulators/emulator_passes/emulator_pass.py @@ -1,9 +1,9 @@ from abc import ABC, abstractmethod from typing import Any, TypeVar - ProgramType = TypeVar("ProgramType") + class EmulatorPass(ABC): @abstractmethod def run[ProgramType](self, program: ProgramType) -> ProgramType: @@ -14,7 +14,6 @@ def run[ProgramType](self, program: ProgramType) -> ProgramType: ProgramType: The program after the emulator pass has been applied. """ raise NotImplementedError - - + def __call__[ProgramType](self, program: ProgramType) -> ProgramType: return self.run(program) diff --git a/src/braket/emulators/emulator_passes/lexi_routing_pass.py b/src/braket/emulators/emulator_passes/lexi_routing_pass.py index d8837fe4e..54e888679 100644 --- a/src/braket/emulators/emulator_passes/lexi_routing_pass.py +++ b/src/braket/emulators/emulator_passes/lexi_routing_pass.py @@ -1,16 +1,19 @@ -from braket.emulators.emulator_passes import EmulatorPass -from braket.circuits import Circuit -from braket.ir.openqasm import Program as OpenQasmProgram -from braket.emulators.pytket_translator import PytketProgramContext, tket_to_qasm3 -from braket.circuits.serialization import IRType -from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager -from pytket.architecture import Architecture -from pytket import Circuit as PytketCircuit -from typing import Union, Dict from collections.abc import Iterable +from functools import singledispatchmethod +from typing import Dict, Union + from networkx import DiGraph +from pytket import Circuit as PytketCircuit +from pytket.architecture import Architecture +from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager + +from braket.circuits import Circuit +from braket.circuits.serialization import IRType from braket.default_simulator.openqasm.interpreter import Interpreter -from functools import singledispatchmethod +from braket.emulators.emulator_passes import EmulatorPass +from braket.emulators.pytket_translator import PytketProgramContext, tket_to_qasm3 +from braket.ir.openqasm import Program as OpenQasmProgram + class LexiRoutingPass(EmulatorPass): def __init__(self, hardware_topology: Union[Dict[int, Iterable[int]], DiGraph]): @@ -18,38 +21,40 @@ def __init__(self, hardware_topology: Union[Dict[int, Iterable[int]], DiGraph]): self._mapping_manager = MappingManager(self._get_architecture(hardware_topology)) self._lexi_label = LexiLabellingMethod() self._lexi_route = LexiRouteRoutingMethod() - def _get_architecture(self, hardware_topology: Union[Dict[int, Iterable[int]], DiGraph]): if isinstance(hardware_topology, dict): edge_list = [(q1, q2) for q1, edges in hardware_topology.items() for q2 in edges] elif isinstance(hardware_topology, DiGraph): edge_list = list(hardware_topology.edges) - - return Architecture(edge_list) - + return Architecture(edge_list) @singledispatchmethod def run(self, task_specification): - raise NotImplementedError(f"LexiRoutingPass does not support task specification type: {type(task_specification)}") - - + raise NotImplementedError( + f"LexiRoutingPass does not support task specification type: {type(task_specification)}" + ) + @run.register def _(self, task_specification: Circuit) -> Circuit: open_qasm_program = task_specification.to_ir(ir_type=IRType.OPENQASM) mapped_open_qasm_program = self.run(open_qasm_program) resulting_circuit = Circuit.from_ir(mapped_open_qasm_program) return resulting_circuit - + @run.register def _(self, task_specification: OpenQasmProgram) -> OpenQasmProgram: - pytket_circuit = Interpreter(PytketProgramContext()).build_circuit(task_specification.source) + pytket_circuit = Interpreter(PytketProgramContext()).build_circuit( + task_specification.source + ) pytket_circuit = self.run(pytket_circuit) open_qasm_program_string = tket_to_qasm3(pytket_circuit) return OpenQasmProgram(source=open_qasm_program_string) - + @run.register def _(self, task_specification: PytketCircuit) -> PytketCircuit: - self._mapping_manager.route_circuit(task_specification, [self._lexi_label, self._lexi_route]) + self._mapping_manager.route_circuit( + task_specification, [self._lexi_label, self._lexi_route] + ) return task_specification diff --git a/src/braket/emulators/pytket_translator/__init__.py b/src/braket/emulators/pytket_translator/__init__.py index a1c9cbcdb..58798ef68 100644 --- a/src/braket/emulators/pytket_translator/__init__.py +++ b/src/braket/emulators/pytket_translator/__init__.py @@ -1,7 +1,7 @@ +from braket.emulators.pytket_translator.pytket_program_context import PytketProgramContext +from braket.emulators.pytket_translator.qasm3_gen import tket_to_qasm3 from braket.emulators.pytket_translator.translations import ( - PYTKET_TO_QASM, COMPOSED_GATES, - QASM_TO_PYTKET + PYTKET_TO_QASM, + QASM_TO_PYTKET, ) -from braket.emulators.pytket_translator.pytket_program_context import PytketProgramContext -from braket.emulators.pytket_translator.qasm3_gen import tket_to_qasm3 \ No newline at end of file diff --git a/src/braket/emulators/pytket_translator/composed_gates.py b/src/braket/emulators/pytket_translator/composed_gates.py index 7aa93e440..fe898c098 100644 --- a/src/braket/emulators/pytket_translator/composed_gates.py +++ b/src/braket/emulators/pytket_translator/composed_gates.py @@ -1,11 +1,24 @@ -from typing import List +from collections.abc import Iterable +from typing import List, Union from pytket.circuit import Circuit, OpType +from sympy import Expr, Symbol class ComposedGates: @staticmethod - def add_pswap(circ: Circuit, arguments, qubits: List[int]): + def add_pswap( + circ: Circuit, arguments: Iterable[Union[float, Expr, Symbol]], qubits: List[int] + ) -> None: + """ + Applies a pswap operation to the target qubits in the provided circuit using + gate operations available in Pytket. + Args: + circ (Circuit): The pytket circuit to add the gate to. + arguments (Iterable[Union[float, Expr, Symbol]]): Contains the + pswap angle argument. + qubits (List[int]): The indices of the target qubits in the pswap operation. + """ assert len(arguments) == 1 assert len(qubits) == 2 circ.add_gate(OpType.ZZPhase, arguments, [qubits[0], qubits[1]]) @@ -13,7 +26,18 @@ def add_pswap(circ: Circuit, arguments, qubits: List[int]): circ.add_phase(arguments[0] / 2) @staticmethod - def add_cphaseshift00(circ: Circuit, arguments, qubits: List[int]): + def add_cphaseshift00( + circ: Circuit, arguments: Iterable[Union[float, Expr, Symbol]], qubits: List[int] + ) -> None: + """ + Applies a cphaseshift00 operation to the target qubits in the provided circuit using + gate operations available in Pytket. + Args: + circ (Circuit): The pytket circuit to add the gate to. + arguments (Iterable[Union[float, Expr, Symbol]]): Contains the + cphaseshift00 angle argument. + qubits (List[int]): The indices of the target qubits in the cphaseshift00 operation. + """ assert len(arguments) == 1 assert len(qubits) == 2 circ.add_gate(OpType.X, [qubits[1]]) @@ -23,7 +47,18 @@ def add_cphaseshift00(circ: Circuit, arguments, qubits: List[int]): circ.add_gate(OpType.X, [qubits[1]]) @staticmethod - def add_cphaseshift01(circ: Circuit, arguments, qubits: List[int]): + def add_cphaseshift01( + circ: Circuit, arguments: Iterable[Union[float, Expr, Symbol]], qubits: List[int] + ) -> None: + """ + Applies a cphaseshift01 operation to the target qubits in the provided circuit using + gate operations available in Pytket. + Args: + circ (Circuit): The pytket circuit to add the gate to. + arguments (Iterable[Union[float, Expr, Symbol]]): Contains the + cphaseshift01 angle argument. + qubits (List[int]): The indices of the target qubits in the cphaseshift01 operation. + """ assert len(arguments) == 1 assert len(qubits) == 2 circ.add_gate(OpType.CX, [qubits[1], qubits[0]]) @@ -31,7 +66,18 @@ def add_cphaseshift01(circ: Circuit, arguments, qubits: List[int]): circ.add_gate(OpType.CX, [qubits[1], qubits[0]]) @staticmethod - def add_cphaseshift10(circ: Circuit, arguments, qubits: List[int]): + def add_cphaseshift10( + circ: Circuit, arguments: Iterable[Union[float, Expr, Symbol]], qubits: List[int] + ) -> None: + """ + Applies a cphaseshift10 operation to the target qubits in the provided circuit using + gate operations available in Pytket. + Args: + circ (Circuit): The pytket circuit to add the gate to. + arguments (Iterable[Union[float, Expr, Symbol]]): Contains the + cphaseshift10 angle argument. + qubits (List[int]): The indices of the target qubits in the cphaseshift10 operation. + """ assert len(arguments) == 1 assert len(qubits) == 2 circ.add_gate(OpType.CX, [qubits[0], qubits[1]]) diff --git a/src/braket/emulators/pytket_translator/pytket_program_context.py b/src/braket/emulators/pytket_translator/pytket_program_context.py index a039b4b02..3bd054493 100644 --- a/src/braket/emulators/pytket_translator/pytket_program_context.py +++ b/src/braket/emulators/pytket_translator/pytket_program_context.py @@ -1,21 +1,13 @@ -from braket.default_simulator.openqasm.program_context import AbstractProgramContext -from pytket.circuit import ( - Circuit, - OpType, - Unitary1qBox, - Unitary2qBox, - Unitary3qBox -) -from pytket.unit_id import Qubit, Bit -from typing import Optional, Union, Any from collections.abc import Iterable -from sympy import Expr -from braket.emulators.pytket_translator.translations import ( - QASM_TO_PYTKET, - COMPOSED_GATES -) +from typing import Any, Optional, Union + import numpy as np -from sympy import Symbol, pi +from pytket.circuit import Circuit, OpType, Unitary1qBox, Unitary2qBox, Unitary3qBox +from pytket.unit_id import Bit, Qubit +from sympy import Expr, Symbol, pi + +from braket.default_simulator.openqasm.program_context import AbstractProgramContext +from braket.emulators.pytket_translator.translations import COMPOSED_GATES, QASM_TO_PYTKET class PytketProgramContext(AbstractProgramContext): @@ -32,17 +24,22 @@ def __init__(self, circuit: Optional[Circuit] = None): @property def circuit(self) -> Circuit: - """Returns the Pytket circuit being built in this context.""" - return self._circuit + """Returns the Pytket circuit being built in this context.""" + return self._circuit - def is_builtin_gate(self, name: str) -> bool: + def is_builtin_gate(self, name: str) -> bool: user_defined_gate = self.is_user_defined_gate(name) result = (name in QASM_TO_PYTKET or COMPOSED_GATES) and not user_defined_gate return result def add_gate_instruction( - self, gate_name: str, target: tuple[int, ...], params, ctrl_modifiers: list[int], power: int - ): + self, + gate_name: str, + target: tuple[int, ...], + params: Iterable[Union[float, Expr, Symbol]], + ctrl_modifiers: list[int], + power: int, + ): self._check_and_update_qubits(target) # Convert from Braket's radians to TKET's half-turns params = [param / pi for param in params] @@ -56,10 +53,9 @@ def add_gate_instruction( COMPOSED_GATES[gate_name](self._circuit, params, target) else: raise ValueError(f"Gate {gate_name} is not supported in pytket translations.") - - + def _check_and_update_qubits(self, target: tuple[int, ...]): - for qubit in target: + for qubit in target: if qubit not in self._qubits_set: new_qubit = Qubit(index=qubit) self._qubits_set.add(qubit) @@ -69,7 +65,7 @@ def add_phase_instruction(self, target, phase_value): self.add_gate_instruction("gphase", target, phase_value) def add_measure(self, target: tuple[int], classical_targets: Iterable[int] = None): - if len(target) == 0: + if len(target) == 0: return self._check_and_update_qubits(target) for index, qubit in enumerate(target): @@ -81,7 +77,7 @@ def add_custom_unitary( self, unitary: np.ndarray, target: tuple[int, ...], - ): + ): num_targets = len(target) if not (1 <= num_targets <= 3): raise ValueError("At most 3 qubit gates are supported for custom unitary operations.") @@ -93,7 +89,7 @@ def add_custom_unitary( ) self._check_and_update_qubits(target) - if num_targets == 1: + if num_targets == 1: unitary_box = Unitary1qBox(unitary) self._circuit.add_unitary1qbox(unitary_box, *target) elif num_targets == 2: @@ -103,8 +99,7 @@ def add_custom_unitary( unitary_box = Unitary3qBox(unitary) self._circuit.add_unitary3qbox(unitary_box, *target) - def handle_parameter_value(self, value: Union[float, Expr]) -> Any: if isinstance(value, Expr): return value.evalf() - return value \ No newline at end of file + return value diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/__init__.py b/src/braket/emulators/pytket_translator/qasm3_gen/__init__.py index 9932c457e..25fc7f2c6 100644 --- a/src/braket/emulators/pytket_translator/qasm3_gen/__init__.py +++ b/src/braket/emulators/pytket_translator/qasm3_gen/__init__.py @@ -1,2 +1,2 @@ +from braket.emulators.pytket_translator.qasm3_gen.qasm_context import QasmContext from braket.emulators.pytket_translator.qasm3_gen.tket_to_qasm3 import tket_to_qasm3 -from braket.emulators.pytket_translator.qasm3_gen.qasm_context import QasmContext \ No newline at end of file diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py b/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py index 628678981..4f0a8e7c7 100644 --- a/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py +++ b/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py @@ -1,6 +1,8 @@ from collections import namedtuple from typing import Dict, List, Union + from sympy import Expr, Symbol, pi + from braket.emulators.pytket_translator.translations import MEASUREMENT_REGISTER_NAME """A single measurement: from qubit index to the bit expression.""" @@ -11,24 +13,21 @@ class QasmContext: - - def __init__(self, input_parameters: Dict[str, str]=dict()): + + def __init__(self, input_parameters: Dict[str, str] = dict()): self.input_parameters = input_parameters self.num_bits: int = 0 self.gates: List[Gate] = [] self.measurements: List[Measurement] = [] - - + def set_num_bits(self, num_bits: int) -> None: self.num_bits = num_bits - - def add_gate(self, name: str, args: List[Union[Expr, Symbol]], qubits: List[int]): + + def add_gate(self, name: str, args: List[Union[Expr, Symbol]], qubits: List[int]): self.gates.append(Gate(name, args, qubits)) - - + def add_measurement(self, qubit: int, cbit: int): self.measurements.append(Measurement(qubit, f"{MEASUREMENT_REGISTER_NAME}[{cbit}]")) - + def add_parameter(self, param_name: str, param_type: str): self.input_parameters[param_name] = param_type - \ No newline at end of file diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/qasm_writer.py b/src/braket/emulators/pytket_translator/qasm3_gen/qasm_writer.py index 68d73afd1..39cb7acce 100644 --- a/src/braket/emulators/pytket_translator/qasm3_gen/qasm_writer.py +++ b/src/braket/emulators/pytket_translator/qasm3_gen/qasm_writer.py @@ -1,11 +1,9 @@ -from abc import ABC, abstractmethod import io -from braket.emulators.pytket_translator.qasm3_gen.qasm_context import ( - QasmContext, - Gate, - Measurement -) -from braket.emulators.pytket_translator.translations import MEASUREMENT_REGISTER_NAME +from abc import ABC, abstractmethod + +from braket.emulators.pytket_translator.qasm3_gen.qasm_context import Gate, QasmContext +from braket.emulators.pytket_translator.translations import MEASUREMENT_REGISTER_NAME + class QasmWriter(ABC): """Abstract class for OpenQASM program writing. It handles basic program writing flow, @@ -13,21 +11,25 @@ class QasmWriter(ABC): """ def __init__(self, context: QasmContext): - """Initialize a new QasmWriter from a context containing program information. - + """ + Initialize a new QasmWriter from a context containing program information. The writer does not modify the context. """ self.context = context def get_program(self) -> str: - """Return the OpenQASM3 program program string from the constructor argument + """ + Return the OpenQASM 3.0 program program string from the constructor argument ProgramContext. + + Returns: + str: A complete OpenQASM 3.0 program string. """ stream = io.StringIO() stream.write(self.get_program_header()) stream.write(self.get_input_declarations()) stream.write(self.get_classical_bit_declarations()) - # stream.write(self.get_verbatim_pragma()) + # stream.write(self.get_verbatim_pragma()) # stream.write(self.get_boxed_program_body()) stream.write(self.get_body()) stream.write(self.get_measurements()) @@ -50,15 +52,21 @@ def get_classical_bit_declarations(self) -> str: if self.context.num_bits else "" ) - - + def get_verbatim_pragma(self) -> str: - return f"#pragma braket verbatim\n" + return "#pragma braket verbatim\n" def get_boxed_program_body(self) -> str: return f"box {{\n{self.get_body()}}}\n" def get_body(self) -> str: + """ + Creates an OpenQASM 3.0 program body from the program operations and + returns a string containing the program body. + + Returns: + str: the OpenQASM 3.0 program body. + """ stream = io.StringIO() for gate in self.context.gates: stream.write(self.get_gate(gate)) @@ -86,8 +94,8 @@ def get_measurements(self) -> str: def format_qubit(self, qubit: int) -> str: return f"${qubit}" - - + + class BasicQasmWriter(QasmWriter): """This writer returns human-readable, basic OpenQASM. @@ -105,10 +113,17 @@ class BasicQasmWriter(QasmWriter): """ def get_input_declarations(self) -> str: + """ + Creates parameter declarations for program inputs (e.g. theta, phi). + Parameters are assumed to all be of type float. + + Returns: + str: OpenQASM 3.0 lines with paramater variable declarations. + """ stream = io.StringIO() for param_name, param_type in self.context.input_parameters.items(): stream.write(f"input {param_type} {param_name};\n") return stream.getvalue() def get_gate_args(self, gate: Gate) -> str: - return f"({','.join(str(arg) for arg in gate.args)})" if gate.args else "" \ No newline at end of file + return f"({','.join(str(arg) for arg in gate.args)})" if gate.args else "" diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py b/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py index 038611b62..a53aca9ef 100644 --- a/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py +++ b/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py @@ -1,90 +1,134 @@ -from braket.emulators.pytket_translator import PYTKET_TO_QASM -from pytket.circuit import Circuit, OpType, Command, Node -from sympy import Expr, pi, Symbol -from typing import Dict, Union, List, Set, Optional -from dataclasses import dataclass -from functools import singledispatchmethod +from typing import Dict, List, Set, Union + +from pytket.circuit import Circuit, Command, Node, OpType, UnitID +from sympy import Expr, Symbol, pi + +from braket.emulators.pytket_translator.translations import PYTKET_TO_QASM from braket.emulators.pytket_translator.qasm3_gen.qasm_context import QasmContext from braket.emulators.pytket_translator.qasm3_gen.qasm_writer import BasicQasmWriter -@dataclass -class Qasm3: - program: str - - def tket_to_qasm3( - circuit: Circuit, - input_parameters: Dict[str, str]=dict(), - gate_overrides: Dict[OpType, str]=dict() -) -> str: + circuit: Circuit, + input_parameters: Dict[str, str] = dict(), + gate_overrides: Dict[OpType, str] = dict(), +) -> str: + """ + Converts a Pytket circuit to an OpenQASM 3.0 string. + + Args: + circuit (Circuit): The Pytket circuit to translate. + input_parameters (Dict[str, str]): Program parameters to declare at the beginning + of the OpenQASM 3.0 program. + gate_overrides (Dict[OpType, str]): Gate names overrides that take precedence + over the default OpenQASM 3.0 gate names. + + Returns: + str: An OpenQASM 3.0 program string. + """ ticket_visitor = TketCircuitVisitor(QasmContext(input_parameters), gate_overrides) ticket_visitor.walk_circuit(circuit) writer = BasicQasmWriter(ticket_visitor.context) return writer.get_program() -class TketCircuitVisitor: - def __init__(self, context, gate_overrides: Dict[OpType, str]=dict()): + +class TketCircuitVisitor: + def __init__(self, context, gate_overrides: Dict[OpType, str] = dict()): self.context = context self.gate_overrides = gate_overrides self._measured_nodes: Set[Node] = set() - - - def walk_circuit(self, circuit: Circuit): + + def walk_circuit(self, circuit: Circuit) -> None: + """ + Visits all the commands in the circuit and processes them by their + operation type. + + Args: + circuit (Circuit): The Pytket circuit to perform the walk on. + """ self.context.set_num_bits(len(circuit.bits)) for command in circuit: self._visit_command(command) - def _visit_command(self, command: Command): + def _visit_command(self, command: Command) -> None: + """ + Calls the appropriate visit function based on the operation type of the given + command. + + Args: + command (Command): The circuit instruction to process. + """ op = command.op self._validate_args_not_measured(command.args) optype = op.type if optype == OpType.CircBox: - self._visit_box(command, optype) + self._visit_box(command) elif optype == OpType.Measure: - self._visit_measure(command, optype) + self._visit_measure(command) else: self._visit_gate(command, optype) - - def _validate_args_not_measured(self, args): + + def _validate_args_not_measured(self, args: List[UnitID]) -> None: + """ + Checks that all qubits used in the instruction have not already been measured. + + Args: + args (List[UnitID]): List of targets to validate. + """ for arg in args: if arg in self._measured_nodes: raise ValueError( "Circuit QASM cannot be generated as circuit contains midcircuit " f"measurements on qubit: {arg}" - ) - - def _visit_box(self, command: Command, optype): + ) + + def _visit_box(self, command: Command) -> None: + """ + Visits all the instructions inside a Pytket boxed circuit. + + Args: + command (Command): Contains the instructions of the boxed circuit. + """ circ = command.op.get_circuit() for command in circ: self._visit_command(command) - - def _visit_measure(self, command: Command, optype): + + def _visit_measure(self, command: Command) -> None: + """ + Adds a measure instruction to the QASM context being built. + + Args: + command (Command): Specifies the classical and qubit indices targeted + in this measurement instruction. + """ qubit_node = command.args[0] qubit = qubit_node.index[0] cbit = command.args[1].index[0] self.context.add_measurement(qubit, cbit) self._measured_nodes.add(qubit_node) - - - def _visit_gate(self, command: Command, optype): + + def _visit_gate(self, command: Command, optype: OpType) -> None: """ - Check to see if this operation is a gate known by OpenQASM3.0; if it is, retrieve the appropriate translation - and add the operation to the context. + Check to see if this operation is a gate known by OpenQASM3.0; if it is, + retrieve the appropriate translation and add the operation to the context. + + Args: + command (Command): The Pytket instruction containing the gate targets. + optype (OpType): Specifies the gate operation being used in this + instruction. """ - gate_name: str + gate_name: str if optype in self.gate_overrides: gate_name = self.gate_overrides[optype] elif optype in PYTKET_TO_QASM: gate_name = PYTKET_TO_QASM[optype] else: raise ValueError(f"Operation {optype} cannot be translated to OpenQASM3.0.") - + qubits = command.args params = command.op.params - - - #Look for any free parameters and add them to the context for initialization + + # Look for any free parameters and add them to the context for initialization for param in params: if isinstance(param, Expr): for symbol in param.free_symbols: @@ -94,9 +138,27 @@ def _visit_gate(self, command: Command, optype): qubits = [q.index[0] for q in qubits] self.context.add_gate(gate_name, params, qubits) - def _gate_angles_in_radians(self, params): + def _gate_angles_in_radians(self, params: List[Union[Expr, float]]) -> List[Expr]: + """ + Converts a list of expressions or values in half-angle units to radians. + + Args: + params (List[Union[Expr, float]]): List of angle arguments to convert + to radians. + + Returns: + List[Expr]: A list of gate angle arguments with radian units. + """ return [self._tau_to_radians(param) for param in params] - - def _tau_to_radians(self, arg: Union[float, Expr, Symbol]): + + def _tau_to_radians(self, arg: Union[float, Expr, Symbol]) -> Expr: + """ + Converts expressions from half-angle units to radians. + + Args: + arg (Union[float, Expr, Symbol]): expression to convert. + + Returns: + Expr: Returns an expression with radian units. + """ return pi * arg - \ No newline at end of file diff --git a/src/braket/emulators/pytket_translator/translations.py b/src/braket/emulators/pytket_translator/translations.py index b394b3931..d8c472d8e 100644 --- a/src/braket/emulators/pytket_translator/translations.py +++ b/src/braket/emulators/pytket_translator/translations.py @@ -1,5 +1,6 @@ +from pytket.circuit import OpType + from braket.emulators.pytket_translator.composed_gates import ComposedGates -from pytket.circuit import OpType """The measurement register identifier.""" MEASUREMENT_REGISTER_NAME = "c" @@ -39,19 +40,19 @@ "ccnot": OpType.CCX, "cswap": OpType.CSWAP, "unitary": OpType.U3, - "gpi": OpType.GPI, - "gpi2": OpType.GPI2, - "ms": OpType.AAMS, + "gpi": OpType.GPI, + "gpi2": OpType.GPI2, + "ms": OpType.AAMS, "cphaseshift": OpType.CU1, - "prx": OpType.PhasedX + "prx": OpType.PhasedX, } COMPOSED_GATES = { - "cphaseshift00": ComposedGates.add_cphaseshift00, - "cphaseshift01": ComposedGates.add_cphaseshift01, - "cphaseshift10": ComposedGates.add_cphaseshift10, - "pswap": ComposedGates.add_pswap + "cphaseshift00": ComposedGates.add_cphaseshift00, + "cphaseshift01": ComposedGates.add_cphaseshift01, + "cphaseshift10": ComposedGates.add_cphaseshift10, + "pswap": ComposedGates.add_pswap, } """ diff --git a/test/unit_tests/braket/emulation/pytket_translator/test_tket_to_qasm3.py b/test/unit_tests/braket/emulation/pytket_translator/test_tket_to_qasm3.py index 12e3b7fcc..8781924e1 100644 --- a/test/unit_tests/braket/emulation/pytket_translator/test_tket_to_qasm3.py +++ b/test/unit_tests/braket/emulation/pytket_translator/test_tket_to_qasm3.py @@ -1,9 +1,11 @@ +import re + import pytest -from pytket.circuit import Circuit, Bit, OpType -import pytket.circuit -from braket.emulators.pytket_translator import tket_to_qasm3 +from pytket.circuit import Bit, Circuit, OpType from sympy import Symbol -import re + +from braket.emulators.pytket_translator import tket_to_qasm3 + @pytest.fixture def simple_bell_circuit(): @@ -19,6 +21,7 @@ def simple_bell_circuit(): circ.Measure(2, 2) return circ + @pytest.fixture def simple_parametric_circuit(): circ = Circuit(1) @@ -27,6 +30,7 @@ def simple_parametric_circuit(): circ.Measure(0, 0) return circ + def test_simple_bell(simple_bell_circuit): expected = """ OPENQASM 3.0; @@ -41,7 +45,7 @@ def test_simple_bell(simple_bell_circuit): """.strip() qasm_result = tket_to_qasm3(simple_bell_circuit).strip() assert qasm_result == expected - + def test_parametric_circuit(simple_parametric_circuit): expected = """ @@ -53,14 +57,16 @@ def test_parametric_circuit(simple_parametric_circuit): """.strip() qasm_result = tket_to_qasm3(simple_parametric_circuit).strip() assert expected == qasm_result - + + def test_empty_circuit(): circ = Circuit(0) expected = """OPENQASM 3.0; """ qasm_result = tket_to_qasm3(circ) assert expected == qasm_result - + + def test_multiple_parameter_circuits(): theta = Symbol("theta") phi = Symbol("phi") @@ -68,7 +74,7 @@ def test_multiple_parameter_circuits(): circ.add_bit(Bit("c", 0)) circ.Rx(theta, 0) circ.Rx(theta + phi, 0) - expected=""" + expected = """ OPENQASM 3.0; input float theta; input float phi; @@ -85,16 +91,17 @@ def test_invalid_operation_on_measured_targets(): circ.add_bit(Bit("c", 0)) circ.Measure(0, 0) circ.X(0) - - error_message = re.escape("Circuit QASM cannot be generated as circuit contains midcircuit measurements on qubit: q[0]") + + error_message = re.escape( + "Circuit QASM cannot be generated as circuit contains midcircuit\ + measurements on qubit: q[0]" + ) with pytest.raises(ValueError, match=error_message): tket_to_qasm3(circ) - + + def test_translation_with_gate_overrides(simple_bell_circuit): - gate_override = { - OpType.CX: "CNOT", - OpType.H: "HADAMARD" - } + gate_override = {OpType.CX: "CNOT", OpType.H: "HADAMARD"} expected = """ OPENQASM 3.0; bit[3] c; @@ -107,4 +114,4 @@ def test_translation_with_gate_overrides(simple_bell_circuit): c[1] = measure $1; """.strip() qasm_result = tket_to_qasm3(simple_bell_circuit, gate_overrides=gate_override).strip() - assert qasm_result == expected \ No newline at end of file + assert qasm_result == expected diff --git a/test/unit_tests/braket/emulation/test_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_connectivity_criterion.py index 5a62dbddf..7b018ef03 100644 --- a/test/unit_tests/braket/emulation/test_connectivity_criterion.py +++ b/test/unit_tests/braket/emulation/test_connectivity_criterion.py @@ -1,81 +1,72 @@ -import pytest - -from braket.emulators.emulator_passes.criteria import ConnectivityCriterion -from braket.circuits import Circuit import networkx as nx import numpy as np +import pytest from networkx.utils import graphs_equal +from braket.circuits import Circuit +from braket.emulators.emulator_passes.criteria import ConnectivityCriterion + + @pytest.fixture def basic_2_node_complete_graph(): return nx.complete_graph(2, create_using=nx.DiGraph()) + @pytest.fixture def basic_noncontig_qubits_2_node_complete_graph(): return nx.complete_graph([1, 10], create_using=nx.DiGraph()) + @pytest.fixture def five_node_digraph(): - edge_set = { - 0: [1, 3], - 1: [0, 2, 10], - 2: [1, 3, 11], - 10: [1, 11], - 11: [2, 10] - } + edge_set = {0: [1, 3], 1: [0, 2, 10], 2: [1, 3, 11], 10: [1, 11], 11: [2, 10]} return nx.from_dict_of_lists(edge_set, create_using=nx.DiGraph()) + @pytest.mark.parametrize( - "circuit", + "circuit", [ - Circuit(), - Circuit().add_verbatim_box( - Circuit() + Circuit(), + Circuit().add_verbatim_box(Circuit()), + Circuit().i(range(2)).cnot(3, 4), + Circuit().add_verbatim_box(Circuit().h(0).h(1).cnot(0, 1).cnot(1, 0)), + Circuit() + .h(range(2)) + .add_verbatim_box( + Circuit().swap(0, 1).phaseshift(1, np.pi / 4).cphaseshift01(1, 0, np.pi / 4) ), - Circuit().i(range(2)).cnot(3, 4), - Circuit().add_verbatim_box( - Circuit().h(0).h(1).cnot(0, 1).cnot(1, 0) - ), - Circuit().h(range(2)).add_verbatim_box( - Circuit().swap(0, 1).phaseshift(1, np.pi/4).cphaseshift01(1, 0, np.pi/4) - ) - ] + ], ) def test_basic_contiguous_circuits(basic_2_node_complete_graph, circuit): """ - ConnectivityGateCriterion should not raise any errors when validating these circuits. + ConnectivityGateCriterion should not raise any errors when validating these circuits. """ ConnectivityCriterion(basic_2_node_complete_graph).validate(circuit) @pytest.mark.parametrize( - "circuit", + "circuit", [ - Circuit(), + Circuit(), + Circuit().add_verbatim_box(Circuit()), + Circuit().i(range(3)).cnot(3, 4).x(111), + Circuit().add_verbatim_box(Circuit().h(1).h(10).cnot(1, 10).cnot(10, 1)), Circuit().add_verbatim_box( - Circuit() + Circuit().swap(1, 10).phaseshift(10, np.pi / 4).cphaseshift01(10, 1, np.pi / 4) ), - Circuit().i(range(3)).cnot(3, 4).x(111), - Circuit().add_verbatim_box( - Circuit().h(1).h(10).cnot(1, 10).cnot(10, 1) - ), - Circuit().add_verbatim_box( - Circuit().swap(1, 10).phaseshift(10, np.pi/4).cphaseshift01(10, 1, np.pi/4) - ) - ] + ], ) - def test_valid_discontiguous_circuits(basic_noncontig_qubits_2_node_complete_graph, circuit): """ - ConnectivityGateCriterion should not raise any errors when validating these circuits. + ConnectivityGateCriterion should not raise any errors when validating these circuits. """ ConnectivityCriterion(basic_noncontig_qubits_2_node_complete_graph).validate(circuit) def test_complete_graph_instantation_with_num_qubits(): """ - Tests that, if fully_connected is True and num_qubits are passed into the - ConnectivityCriterion constructor, a fully connected graph is created. + Tests that, if fully_connected is True and num_qubits are passed into the + ConnectivityCriterion constructor, a fully connected graph is created. """ num_qubits = 5 criterion = ConnectivityCriterion(num_qubits=num_qubits, fully_connected=True) @@ -88,12 +79,15 @@ def test_complete_graph_instantation_with_num_qubits(): vb.i(i) circuit = Circuit().add_verbatim_box(vb) criterion.validate(circuit) - assert nx.utils.graphs_equal(criterion._connectivity_graph, nx.complete_graph(num_qubits, create_using=nx.DiGraph())) + assert nx.utils.graphs_equal( + criterion._connectivity_graph, nx.complete_graph(num_qubits, create_using=nx.DiGraph()) + ) + def test_complete_graph_instantation_with_qubit_labels(): """ - Tests that, if fully_connected is True and num_qubits are passed into the - ConnectivityCriterion constructor, a fully connected graph is created. + Tests that, if fully_connected is True and num_qubits are passed into the + ConnectivityCriterion constructor, a fully connected graph is created. """ qubit_labels = [0, 1, 10, 11, 110, 111, 112, 113] criterion = ConnectivityCriterion(qubit_labels=qubit_labels, fully_connected=True) @@ -106,68 +100,53 @@ def test_complete_graph_instantation_with_qubit_labels(): vb.i(i) circuit = Circuit().add_verbatim_box(vb) criterion.validate(circuit) - assert nx.utils.graphs_equal(criterion._connectivity_graph, nx.complete_graph(qubit_labels, create_using=nx.DiGraph())) + assert nx.utils.graphs_equal( + criterion._connectivity_graph, nx.complete_graph(qubit_labels, create_using=nx.DiGraph()) + ) @pytest.mark.parametrize( "circuit", [ - Circuit().add_verbatim_box( - Circuit().cnot(0, 2) - ), - Circuit().add_verbatim_box( - Circuit().swap(1, 3) - ), - Circuit().add_verbatim_box( - Circuit().cnot(1, 10).cphaseshift01(2, 11, np.pi/4) - ).x(4).add_verbatim_box( - Circuit().swap(2, 10) - ) - ] + Circuit().add_verbatim_box(Circuit().cnot(0, 2)), + Circuit().add_verbatim_box(Circuit().swap(1, 3)), + Circuit() + .add_verbatim_box(Circuit().cnot(1, 10).cphaseshift01(2, 11, np.pi / 4)) + .x(4) + .add_verbatim_box(Circuit().swap(2, 10)), + ], ) def test_invalid_2_qubit_gates(five_node_digraph, circuit): with pytest.raises(ValueError): ConnectivityCriterion(five_node_digraph).validate(circuit) + @pytest.mark.parametrize( "circuit", [ - Circuit().x(4).add_verbatim_box( - Circuit().x(4) - ), - Circuit().x(110).add_verbatim_box( - Circuit().phaseshift(4, np.pi/4) - ), - Circuit().add_verbatim_box( - Circuit().cnot(1, 10).cphaseshift01(2, 11, np.pi/4) - ).x(4).add_verbatim_box( - Circuit().h(111) - ) - ] + Circuit().x(4).add_verbatim_box(Circuit().x(4)), + Circuit().x(110).add_verbatim_box(Circuit().phaseshift(4, np.pi / 4)), + Circuit() + .add_verbatim_box(Circuit().cnot(1, 10).cphaseshift01(2, 11, np.pi / 4)) + .x(4) + .add_verbatim_box(Circuit().h(111)), + ], ) def test_invalid_1_qubit_gates(five_node_digraph, circuit): with pytest.raises(ValueError): ConnectivityCriterion(five_node_digraph).validate(circuit) + def test_equality_graph_created_with_dict(five_node_digraph): - graph = { - 0: [1, 3], - 1: [0, 2, 10], - 2: [1, 3, 11], - 10: [1, 11], - 11: [2, 10] - } + graph = {0: [1, 3], 1: [0, 2, 10], 2: [1, 3, 11], 10: [1, 11], 11: [2, 10]} criteria_from_digraph = ConnectivityCriterion(five_node_digraph) criteria_from_dict = ConnectivityCriterion(graph) assert criteria_from_dict == criteria_from_digraph + @pytest.mark.parametrize( - "connectivity_graph, fully_connected, num_qubits, qubit_labels", - [ - (None, True, None, None), - (nx.DiGraph(), True, None, None), - (None, True, 5, [0, 1]) - ] + "connectivity_graph, fully_connected, num_qubits, qubit_labels", + [(None, True, None, None), (nx.DiGraph(), True, None, None), (None, True, 5, [0, 1])], ) def test_invalid_constructors(connectivity_graph, fully_connected, num_qubits, qubit_labels): with pytest.raises(ValueError): @@ -175,19 +154,29 @@ def test_invalid_constructors(connectivity_graph, fully_connected, num_qubits, q @pytest.mark.parametrize( - "representation", + "representation", [ - { - 1: [0, 2, 3], - 2: [3, 4], - 3: [6] - }, - nx.from_edgelist([(1, 0), (1, 2), (1, 3), (2, 3), (2, 4), (3, 6)], create_using=nx.DiGraph) - ] + {1: [0, 2, 3], 2: [3, 4], 3: [6]}, + nx.from_edgelist([(1, 0), (1, 2), (1, 3), (2, 3), (2, 4), (3, 6)], create_using=nx.DiGraph), + ], ) -def test_undirected_graph_construction(representation): +def test_undirected_graph_construction(representation): expected_digraph = nx.from_edgelist( - [(1, 0), (0, 1), (1, 2), (2, 1), (1, 3), (3, 1), (2, 3), (3, 2), (2, 4), (4, 2), (3, 6), (6, 3)], create_using=nx.DiGraph + [ + (1, 0), + (0, 1), + (1, 2), + (2, 1), + (1, 3), + (3, 1), + (2, 3), + (3, 2), + (2, 4), + (4, 2), + (3, 6), + (6, 3), + ], + create_using=nx.DiGraph, ) cc = ConnectivityCriterion(representation, directed=False) - assert graphs_equal(cc._connectivity_graph, expected_digraph) \ No newline at end of file + assert graphs_equal(cc._connectivity_graph, expected_digraph) diff --git a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py index ca731bf5d..a6fea6578 100644 --- a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py +++ b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py @@ -1,10 +1,10 @@ +import networkx as nx +import numpy as np import pytest +from networkx.utils import graphs_equal -from braket.emulators.emulator_passes.criteria import GateConnectivityCriterion from braket.circuits import Circuit -import networkx as nx -from networkx.utils import graphs_equal -import numpy as np +from braket.emulators.emulator_passes.criteria import GateConnectivityCriterion @pytest.fixture @@ -12,9 +12,9 @@ def basic_4_node_graph(): G = nx.DiGraph() G.add_edges_from( [ - (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (0, 1, {"supported_gates": ["CNot", "CZ"]}), (1, 2, {"supported_gates": ["Swap", "CNot"]}), - (0, 3, {"supported_gates": ["XX", "XY"]}) + (0, 3, {"supported_gates": ["XX", "XY"]}), ] ) return G @@ -25,204 +25,197 @@ def basic_discontiguous_4_node_graph(): G = nx.DiGraph() G.add_edges_from( [ - (0, 5, {"supported_gates": ["CNot", "CZ"]}), + (0, 5, {"supported_gates": ["CNot", "CZ"]}), (2, 4, {"supported_gates": ["Swap", "CNot"]}), - (3, 0, {"supported_gates": ["XX", "XY"]}) + (3, 0, {"supported_gates": ["XX", "XY"]}), ] ) return G - + + @pytest.fixture def basic_4_node_graph_as_dict(): return { - (0, 1): ["CNot", "Swap", "CX", "XX"], - (1, 2): ["CNot", "CZ", "ISwap", "XY"], - (0, 3): ["PSwap", "CNot", "XY"] + (0, 1): ["CNot", "Swap", "CX", "XX"], + (1, 2): ["CNot", "CZ", "ISwap", "XY"], + (0, 3): ["PSwap", "CNot", "XY"], } - - + + @pytest.mark.parametrize( - "circuit", + "circuit", [ - Circuit(), - Circuit().add_verbatim_box( - Circuit().cnot(0, 1).h(range(4)) - ), - Circuit().add_verbatim_box( - Circuit().cnot(0, 1).cz(0, 1).swap(1, 2).xx(0, 3, np.pi/2) - ).add_verbatim_box( - Circuit().xy(0, 3, np.pi/2).cnot(1, 2).cz(0, 1) - ), - Circuit().i(range(10)).cnot(0, 2).yy(8, 9, np.pi/2).h(7).add_verbatim_box( - Circuit().cnot(0, 1).cz(0, 1) - ).cnot(0, 2).swap(4, 6) - ] + Circuit(), + Circuit().add_verbatim_box(Circuit().cnot(0, 1).h(range(4))), + Circuit() + .add_verbatim_box(Circuit().cnot(0, 1).cz(0, 1).swap(1, 2).xx(0, 3, np.pi / 2)) + .add_verbatim_box(Circuit().xy(0, 3, np.pi / 2).cnot(1, 2).cz(0, 1)), + Circuit() + .i(range(10)) + .cnot(0, 2) + .yy(8, 9, np.pi / 2) + .h(7) + .add_verbatim_box(Circuit().cnot(0, 1).cz(0, 1)) + .cnot(0, 2) + .swap(4, 6), + ], ) def test_valid_basic_contiguous_circuits(basic_4_node_graph, circuit): """ - ConnectivityGateCriterion should not raise any errors when validating these circuits. + ConnectivityGateCriterion should not raise any errors when validating these circuits. """ gate_connectivity_criterion = GateConnectivityCriterion(basic_4_node_graph) gate_connectivity_criterion.validate(circuit) - - + + @pytest.mark.parametrize( - "circuit", + "circuit", [ - Circuit(), - Circuit().add_verbatim_box( - Circuit().cnot(0, 5) - ), - Circuit().add_verbatim_box( - Circuit().cnot(2, 4).cz(0, 5).swap(2, 4).xx(3, 0, np.pi/2) - ).add_verbatim_box( - Circuit().xy(3, 0, np.pi/2).cnot(2, 4).cz(0, 5) - ), - Circuit().i(range(10)).cnot(0, 2).yy(8, 9, np.pi/2).h(7).add_verbatim_box( - Circuit().cnot(0, 5).swap(2, 4) - ).cnot(0, 2).swap(4, 6) - ] + Circuit(), + Circuit().add_verbatim_box(Circuit().cnot(0, 5)), + Circuit() + .add_verbatim_box(Circuit().cnot(2, 4).cz(0, 5).swap(2, 4).xx(3, 0, np.pi / 2)) + .add_verbatim_box(Circuit().xy(3, 0, np.pi / 2).cnot(2, 4).cz(0, 5)), + Circuit() + .i(range(10)) + .cnot(0, 2) + .yy(8, 9, np.pi / 2) + .h(7) + .add_verbatim_box(Circuit().cnot(0, 5).swap(2, 4)) + .cnot(0, 2) + .swap(4, 6), + ], ) - - def test_valid_basic_discontiguous_circuits(basic_discontiguous_4_node_graph, circuit): """ - ConnectivityGateCriterion should not raise any errors when validating these circuits. + ConnectivityGateCriterion should not raise any errors when validating these circuits. """ gate_connectivity_criterion = GateConnectivityCriterion(basic_discontiguous_4_node_graph) gate_connectivity_criterion.validate(circuit) - - - + + def test_directed_graph_construction_from_dict(): """ - ConnectivityGateCriterion should correctly construct a graph from a dictionary - representation of the connectivity. + ConnectivityGateCriterion should correctly construct a graph from a dictionary + representation of the connectivity. """ dict_representation = { (0, 1): ["CNot", "CZ"], (1, 2): ["Swap", "CNot", "YY"], (0, 2): ["XX", "XY", "CNot", "CZ"], - (2, 5): ["XX", "XY", "CNot", "CZ"] + (2, 5): ["XX", "XY", "CNot", "CZ"], } digraph_representation = nx.DiGraph() - digraph_representation.add_edges_from([ - (0, 1, {"supported_gates": ["CNot", "CZ"]}), - (1, 2, {"supported_gates": ["Swap", "CNot", "YY"]}), - (0, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), - (2, 5, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}) - ]) + digraph_representation.add_edges_from( + [ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 2, {"supported_gates": ["Swap", "CNot", "YY"]}), + (0, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + (2, 5, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + ] + ) gcc = GateConnectivityCriterion(dict_representation) assert graphs_equal(gcc._gate_connectivity_graph, digraph_representation) - - - + + @pytest.mark.parametrize( - "circuit", + "circuit", [ Circuit(), - Circuit().add_verbatim_box( - Circuit().cnot(0, 1) - ), - Circuit().swap(0, 1).add_verbatim_box( - Circuit().iswap(2, 1).pswap(3, 0, np.pi/2).pswap(0, 3, np.pi/2) - ), - Circuit().cnot(2, 3).h(5).pswap(0, 6, np.pi/2).add_verbatim_box( - Circuit().xy(2, 1, np.pi/2).cnot(0, 1).cnot(1, 0) - ).add_verbatim_box( - Circuit().cnot(0, 3).cnot(3, 0).xy(0, 3, np.pi/2) - ) - ] -) + Circuit().add_verbatim_box(Circuit().cnot(0, 1)), + Circuit() + .swap(0, 1) + .add_verbatim_box(Circuit().iswap(2, 1).pswap(3, 0, np.pi / 2).pswap(0, 3, np.pi / 2)), + Circuit() + .cnot(2, 3) + .h(5) + .pswap(0, 6, np.pi / 2) + .add_verbatim_box(Circuit().xy(2, 1, np.pi / 2).cnot(0, 1).cnot(1, 0)) + .add_verbatim_box(Circuit().cnot(0, 3).cnot(3, 0).xy(0, 3, np.pi / 2)), + ], +) def test_undirected_criteria_from_dict_with_valid_circuits(basic_4_node_graph_as_dict, circuit): """ - ConnectivityGateCriterion should not raise any errors when validating these circuits. + ConnectivityGateCriterion should not raise any errors when validating these circuits. """ - gate_connectivity_criterion = GateConnectivityCriterion(basic_4_node_graph_as_dict, directed=False) + gate_connectivity_criterion = GateConnectivityCriterion( + basic_4_node_graph_as_dict, directed=False + ) gate_connectivity_criterion.validate(circuit) - def test_undirected_graph_construction_from_dict(): """ - ConnectivityGateCriterion should correctly construct an undirected graph from a dictionary - representation of the connectivity. + ConnectivityGateCriterion should correctly construct an undirected graph from a dictionary + representation of the connectivity. """ dict_representation = { (0, 1): ["CNot", "CZ"], (1, 2): ["Swap", "CNot", "YY"], (0, 2): ["XX", "XY", "CNot", "CZ"], - (2, 5): ["XX", "XY", "CNot", "CZ"] + (2, 5): ["XX", "XY", "CNot", "CZ"], } digraph_representation = nx.DiGraph() - digraph_representation.add_edges_from([ - (0, 1, {"supported_gates": ["CNot", "CZ"]}), - (1, 2, {"supported_gates": ["Swap", "CNot", "YY"]}), - (0, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), - (2, 5, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), - (1, 0, {"supported_gates": ["CNot", "CZ"]}), - (2, 1, {"supported_gates": ["Swap", "CNot", "YY"]}), - (2, 0, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), - (5, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), - ]) + digraph_representation.add_edges_from( + [ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 2, {"supported_gates": ["Swap", "CNot", "YY"]}), + (0, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + (2, 5, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + (1, 0, {"supported_gates": ["CNot", "CZ"]}), + (2, 1, {"supported_gates": ["Swap", "CNot", "YY"]}), + (2, 0, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + (5, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + ] + ) gcc = GateConnectivityCriterion(dict_representation, directed=False) assert graphs_equal(gcc._gate_connectivity_graph, digraph_representation) - + @pytest.mark.parametrize( - "representation", + "representation", [ - { - (0, 1): ["CNot", "CZ"], - (1, 0): ["CZ, XX"], - (2, 0): ["CNot, YY"] - }, + {(0, 1): ["CNot", "CZ"], (1, 0): ["CZ, XX"], (2, 0): ["CNot, YY"]}, nx.from_dict_of_dicts( { 0: {1: {"supported_gates": ["CNot", "CZ"]}}, - 1: {0: {"supported_gates": ["CZ", "XX"]}}, - 2: {2: {"supported_gates": ["CNot", "YY"]}} + 1: {0: {"supported_gates": ["CZ", "XX"]}}, + 2: {2: {"supported_gates": ["CNot", "YY"]}}, } - ) - ] + ), + ], ) def create_undirected_graph_with_exisiting_back_edges(representation): """ - Check that creating an undirected graph with a graph that + Check that creating an undirected graph with a graph that contains forwards and backwards edges with different constraints is created properly. """ - + gcc = GateConnectivityCriterion(representation, directed=False) expected_digraph_representation = nx.DiGraph() - expected_digraph_representation.add_edges_from([ - (0, 1, {"supported_gates": ["CNot", "CZ"]}), - (1, 0, {"supported_gates": ["CZ", "XX"]}), - (2, 0, {"supported_gates": ["CNot", "YY"]}), - (0, 2, {"supported_gates": ["CNot", "YY"]}) - ]) - + expected_digraph_representation.add_edges_from( + [ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 0, {"supported_gates": ["CZ", "XX"]}), + (2, 0, {"supported_gates": ["CNot", "YY"]}), + (0, 2, {"supported_gates": ["CNot", "YY"]}), + ] + ) + assert graphs_equal(gcc._gate_connectivity_graph, expected_digraph_representation) @pytest.mark.parametrize( - "circuit", + "circuit", [ - Circuit().add_verbatim_box( - Circuit().cnot(1, 0) - ), - 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_verbatim_box(Circuit().cnot(1, 0)), + 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)), + ], ) -def test_invalid_circuits(basic_4_node_graph, circuit): +def test_invalid_circuits(basic_4_node_graph, circuit): with pytest.raises(ValueError): gate_connectivity_criterion = GateConnectivityCriterion(basic_4_node_graph) - gate_connectivity_criterion.validate(circuit) \ No newline at end of file + gate_connectivity_criterion.validate(circuit) diff --git a/test/unit_tests/braket/emulation/test_gate_criterion.py b/test/unit_tests/braket/emulation/test_gate_criterion.py index 7f45a42d9..a306be384 100644 --- a/test/unit_tests/braket/emulation/test_gate_criterion.py +++ b/test/unit_tests/braket/emulation/test_gate_criterion.py @@ -1,62 +1,88 @@ +import numpy as np import pytest -from braket.emulators.emulator_passes.criteria import GateCriterion + from braket.circuits import Circuit -import numpy as np +from braket.emulators.emulator_passes.criteria import GateCriterion + -@pytest.fixture +@pytest.fixture def basic_gate_set(): - return (["h", "cnot"], ['cz', 'prx']) + return (["h", "cnot"], ["cz", "prx"]) @pytest.fixture def mock_qpu_gates(): - supported_gates = ['ccnot', 'cnot', 'cphaseshift', 'cswap', 'swap', 'iswap', 'pswap', 'ecr', 'cy', 'cz', 'xy', \ - 'zz', 'h', 'i', 'phaseshift', 'rx', 'ry', 'v', 'vi', 'x', 'y', 'z'] + supported_gates = [ + "ccnot", + "cnot", + "cphaseshift", + "cswap", + "swap", + "iswap", + "pswap", + "ecr", + "cy", + "cz", + "xy", + "zz", + "h", + "i", + "phaseshift", + "rx", + "ry", + "v", + "vi", + "x", + "y", + "z", + ] - native_gates = ['cz', 'prx'] + native_gates = ["cz", "prx"] return (supported_gates, native_gates) - - @pytest.mark.parametrize( - "circuit", + "circuit", [ Circuit(), - Circuit().h(range(4)).cnot(0, 5).pswap(0, 1, np.pi/4).xy(0, 1, 0.5).cphaseshift(0, 1, np.pi/4), - Circuit().swap(0, 1).rx(0, 0.5).v(1).h(2).add_verbatim_box( - Circuit().cz(0, 1).cz(0, 6).prx(0, np.pi/4, np.pi/5) - ).z(3), - Circuit().add_verbatim_box( - Circuit().cz(0, 2).prx(0, 0.5, 0.5) - ).add_verbatim_box( - Circuit().cz(0, 4).cz(3, 6) - ), - Circuit().add_verbatim_box( - Circuit() - ) - ] + Circuit() + .h(range(4)) + .cnot(0, 5) + .pswap(0, 1, np.pi / 4) + .xy(0, 1, 0.5) + .cphaseshift(0, 1, np.pi / 4), + Circuit() + .swap(0, 1) + .rx(0, 0.5) + .v(1) + .h(2) + .add_verbatim_box(Circuit().cz(0, 1).cz(0, 6).prx(0, np.pi / 4, np.pi / 5)) + .z(3), + Circuit() + .add_verbatim_box(Circuit().cz(0, 2).prx(0, 0.5, 0.5)) + .add_verbatim_box(Circuit().cz(0, 4).cz(3, 6)), + Circuit().add_verbatim_box(Circuit()), + ], ) def test_valid_circuits(mock_qpu_gates, circuit): """ - GateCriterion should not raise any errors when validating these circuits. + GateCriterion should not raise any errors when validating these circuits. """ GateCriterion(mock_qpu_gates[0], mock_qpu_gates[1]).validate(circuit) + def test_only_supported_gates(): supported_gates = ["h", "cnot", "rx", "xx", "y"] criterion = GateCriterion(supported_gates=supported_gates) - circuit = Circuit().h(0).cnot(0, 1).rx(4, np.pi/4).xx(2, 3, np.pi/4).y(7) + circuit = Circuit().h(0).cnot(0, 1).rx(4, np.pi / 4).xx(2, 3, np.pi / 4).y(7) criterion.validate(circuit) - - + + def test_verbatim_circuit_only_supported_gates(): supported_gates = ["h", "cnot", "rx", "xx", "y"] criterion = GateCriterion(supported_gates=supported_gates) - circuit = Circuit().add_verbatim_box( - Circuit().h(0) - ) - + circuit = Circuit().add_verbatim_box(Circuit().h(0)) + with pytest.raises(ValueError): criterion.validate(circuit) @@ -64,44 +90,44 @@ def test_verbatim_circuit_only_supported_gates(): def test_only_native_gates(): native_gates = ["h", "cnot", "rx", "xx", "y"] criterion = GateCriterion(native_gates=native_gates) - vb = Circuit().h(0).cnot(0, 1).rx(4, np.pi/4).xx(2, 3, np.pi/4).y(7) + vb = Circuit().h(0).cnot(0, 1).rx(4, np.pi / 4).xx(2, 3, np.pi / 4).y(7) circuit = Circuit().add_verbatim_box(vb) criterion.validate(circuit) - + + def test_non_verbatim_circuit_only_native_gates(): native_gates = ["h", "cnot", "rx", "xx", "y"] criterion = GateCriterion(native_gates=native_gates) - vb = Circuit().h(0).cnot(0, 1).rx(4, np.pi/4).xx(2, 3, np.pi/4).y(7) + vb = Circuit().h(0).cnot(0, 1).rx(4, np.pi / 4).xx(2, 3, np.pi / 4).y(7) circuit = Circuit().add_verbatim_box(vb) circuit.i(0) with pytest.raises(ValueError): criterion.validate(circuit) - + + def test_empty_instantiation(): with pytest.raises(ValueError): GateCriterion() - - - - # return (["h", "cnot"], ['cz', 'prx']) + + # return (["h", "cnot"], ['cz', 'prx']) + @pytest.mark.parametrize( "circuit", [ Circuit().z(0), - Circuit().h(0).cnot(0, 1).cz(0, 1), - Circuit().add_verbatim_box( - Circuit().h(2) - ), - Circuit().cphaseshift01(0, 1, np.pi/4).h(0).cnot(0, 1), - Circuit().h(0).add_verbatim_box( - Circuit().cz(1, 2).prx(range(5), np.pi/4, np.pi/2).cz(2, 6) - ).prx(range(4), np.pi/4, np.pi/6) - ] + Circuit().h(0).cnot(0, 1).cz(0, 1), + Circuit().add_verbatim_box(Circuit().h(2)), + Circuit().cphaseshift01(0, 1, np.pi / 4).h(0).cnot(0, 1), + Circuit() + .h(0) + .add_verbatim_box(Circuit().cz(1, 2).prx(range(5), np.pi / 4, np.pi / 2).cz(2, 6)) + .prx(range(4), np.pi / 4, np.pi / 6), + ], ) def test_invalid_circuits(basic_gate_set, circuit): """ - GateCriterion should raise errors when validating these circuits. + GateCriterion should raise errors when validating these circuits. """ with pytest.raises(ValueError): GateCriterion(basic_gate_set[0], basic_gate_set[1]).validate(circuit) @@ -134,4 +160,4 @@ def test_invalid_circuits(basic_gate_set, circuit): # ], # ) # def test_inequality(gate_set_1, gate_set_2): -# assert GateCriterion(gate_set_1) != GateCriterion(gate_set_2) \ No newline at end of file +# assert GateCriterion(gate_set_1) != GateCriterion(gate_set_2) diff --git a/test/unit_tests/braket/emulation/test_qubit_count_criterion.py b/test/unit_tests/braket/emulation/test_qubit_count_criterion.py index d4e68930c..071dbbe0e 100644 --- a/test/unit_tests/braket/emulation/test_qubit_count_criterion.py +++ b/test/unit_tests/braket/emulation/test_qubit_count_criterion.py @@ -1,47 +1,47 @@ -from braket.emulators.emulator_passes import QubitCountCriterion -from braket.circuits import Circuit import numpy as np import pytest +from braket.circuits import Circuit +from braket.emulators.emulator_passes import QubitCountCriterion + + @pytest.mark.parametrize( - "qubit_count,circuit", + "qubit_count,circuit", [ - (1, Circuit()), + (1, Circuit()), (10, Circuit().add_verbatim_box(Circuit())), (1, Circuit().z(0)), - (1, Circuit().z(3).x(3)), - (2, Circuit().cnot(0, 1).swap(1, 0)), - (2, Circuit().z(0).add_verbatim_box( - Circuit().cnot(0, 4) - ).yy(0, 4, np.pi/4)), - (50, Circuit().i(range(50)).measure(range(50))) - ] + (1, Circuit().z(3).x(3)), + (2, Circuit().cnot(0, 1).swap(1, 0)), + (2, Circuit().z(0).add_verbatim_box(Circuit().cnot(0, 4)).yy(0, 4, np.pi / 4)), + (50, Circuit().i(range(50)).measure(range(50))), + ], ) def test_valid_circuits(qubit_count, circuit): """ - QubitCountCriterion should not raise any errors when validating these circuits. + QubitCountCriterion should not raise any errors when validating these circuits. """ QubitCountCriterion(qubit_count=qubit_count).validate(circuit) - - -@pytest.mark.parametrize( - "qubit_count", - [0, -1] -) + + +@pytest.mark.parametrize("qubit_count", [0, -1]) def test_invalid_instantiation(qubit_count): with pytest.raises(ValueError): QubitCountCriterion(qubit_count) - - + + @pytest.mark.parametrize( - "qubit_count,circuit", + "qubit_count,circuit", [ - (1, Circuit().cnot(0, 1)), - (2, Circuit().cnot(0, 1).x(2)), - (50, Circuit().i(range(50)).measure(range(50)).measure(50)) - ] -) + (1, Circuit().cnot(0, 1)), + (2, Circuit().cnot(0, 1).x(2)), + (50, Circuit().i(range(50)).measure(range(50)).measure(50)), + ], +) def test_invalid_circuits(qubit_count, circuit): - with pytest.raises(ValueError, match=f"Circuit must use at most {qubit_count} qubits, but uses {circuit.qubit_count} qubits."): + with pytest.raises( + ValueError, + match=f"Circuit must use at most {qubit_count} qubits, \ + but uses {circuit.qubit_count} qubits.", + ): QubitCountCriterion(qubit_count).validate(circuit) - \ No newline at end of file From 7b3ebf3ffab8e442e0c87d60c003d46cc664b227 Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 26 Jun 2024 12:02:05 -0700 Subject: [PATCH 53/91] fix: Revert default simulator versions and dependent round_trip tests --- setup.py | 3 +- .../braket/circuits/test_circuit.py | 29 ------------------- tox.ini | 2 +- 3 files changed, 2 insertions(+), 32 deletions(-) diff --git a/setup.py b/setup.py index ca5b0f943..43d2c52ad 100644 --- a/setup.py +++ b/setup.py @@ -28,8 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.21.3", - "amazon-braket-default-simulator @ \ - git+https://github.com/Altanali/amazon-braket-default-simulator-python.git@main", + "amazon-braket-default-simulator>=1.21.4", "oqpy~=0.3.5", "backoff", "boltons", diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index a126768ee..71eecd1f1 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -891,35 +891,6 @@ def test_from_ir_round_trip_transformation(): assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") -def test_from_ir_round_trip_transformation_with_targeted_measurements(): - circuit = ( - Circuit() - .h(0) - .cnot(0, 1) - .add_instruction(Instruction(Measure(index=2), 1)) - .add_instruction(Instruction(Measure(index=1), 2)) - .add_instruction(Instruction(Measure(index=0), 0)) - ) - ir = OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[3] b;", - "qubit[3] q;", - "h q[0];", - "cnot q[0], q[1];", - "b[2] = measure q[1];", - "b[1] = measure q[2];", - "b[0] = measure q[0];", - ] - ), - inputs={}, - ) - - assert Circuit.from_ir(ir) == Circuit.from_ir(circuit.to_ir("OPENQASM")) - assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") - - def test_add_with_instruction_with_default(cnot_instr): circ = Circuit().add(cnot_instr) assert circ == Circuit().add_instruction(cnot_instr) diff --git a/tox.ini b/tox.ini index 39ef86dd5..95c862106 100644 --- a/tox.ini +++ b/tox.ini @@ -130,4 +130,4 @@ commands = deps = # If you need to test on a certain branch, add @ after .git git+https://github.com/amazon-braket/amazon-braket-schemas-python.git - git+https://github.com/Altanali/amazon-braket-default-simulator-python.git@main + git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git From b5828ad64ada29f8547eb3f0015a93cfeeb98c47 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 26 Jun 2024 12:23:43 -0700 Subject: [PATCH 54/91] Update src/braket/circuits/braket_program_context.py --- src/braket/circuits/braket_program_context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 37ddaf012..499d55543 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -163,14 +163,14 @@ def handle_parameter_value( return value def add_measure( - self, target: tuple[int], classical_targets: Iterable[int] | None = None + self, target: tuple[int], classical_targets: Union[Iterable[int], None] = None ) -> None: """Add a measure instruction to the circuit Args: target (tuple[int]): the target qubits to be measured. - classical_targets (Iterable[int] | None): the classical registers + classical_targets (Union[Iterable[int], None]): the classical registers to use in the qubit measurement. """ for iter, qubit in enumerate(target): From 6b0ad924b87696930fcef663f1a5c9eb26ecb706 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 26 Jun 2024 12:26:32 -0700 Subject: [PATCH 55/91] Update src/braket/circuits/braket_program_context.py --- src/braket/circuits/braket_program_context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 499d55543..7b751f89c 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -163,14 +163,14 @@ def handle_parameter_value( return value def add_measure( - self, target: tuple[int], classical_targets: Union[Iterable[int], None] = None + self, target: tuple[int], classical_targets: Optional[Iterable[int]] = None ) -> None: """Add a measure instruction to the circuit Args: target (tuple[int]): the target qubits to be measured. - classical_targets (Union[Iterable[int], None]): the classical registers + classical_targets (Optional[Iterable[int]]): the classical registers to use in the qubit measurement. """ for iter, qubit in enumerate(target): From ece64a42a0333cea307d8048cb61d162e1202ba2 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 26 Jun 2024 12:26:55 -0700 Subject: [PATCH 56/91] Update src/braket/circuits/braket_program_context.py --- src/braket/circuits/braket_program_context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 7b751f89c..558692510 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -169,7 +169,6 @@ def add_measure( Args: target (tuple[int]): the target qubits to be measured. - classical_targets (Optional[Iterable[int]]): the classical registers to use in the qubit measurement. """ From ae3a0e3d39a151ff1dab7d787352c702f5ec4d49 Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 26 Jun 2024 16:25:04 -0700 Subject: [PATCH 57/91] fix: Add round_trip test for classical target tracking during measurement --- setup.py | 2 +- .../braket/circuits/test_circuit.py | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 43d2c52ad..54987c283 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.21.3", - "amazon-braket-default-simulator>=1.21.4", + "amazon-braket-default-simulator>=1.25.0", "oqpy~=0.3.5", "backoff", "boltons", diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 71eecd1f1..b6bf39562 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -3610,3 +3610,31 @@ def test_circuit_with_global_phase(): "b[0] = measure $0;", ] ) + +def test_from_ir_round_trip_transformation_with_targeted_measurements(): + circuit = ( + Circuit() + .h(0) + .cnot(0, 1) + .add_instruction(Instruction(Measure(index=2), 1)) + .add_instruction(Instruction(Measure(index=1), 2)) + .add_instruction(Instruction(Measure(index=0), 0)) + ) + ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[3] b;", + "qubit[3] q;", + "h q[0];", + "cnot q[0], q[1];", + "b[2] = measure q[1];", + "b[1] = measure q[2];", + "b[0] = measure q[0];", + ] + ), + inputs={}, + ) + + assert Circuit.from_ir(ir) == Circuit.from_ir(circuit.to_ir("OPENQASM")) + assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") \ No newline at end of file From 8756960c66682e874accd72b3509d573cb91b8a2 Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 26 Jun 2024 16:36:02 -0700 Subject: [PATCH 58/91] change: Run Linter --- test/unit_tests/braket/circuits/test_circuit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index b6bf39562..897828b27 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -3611,6 +3611,7 @@ def test_circuit_with_global_phase(): ] ) + def test_from_ir_round_trip_transformation_with_targeted_measurements(): circuit = ( Circuit() @@ -3637,4 +3638,4 @@ def test_from_ir_round_trip_transformation_with_targeted_measurements(): ) assert Circuit.from_ir(ir) == Circuit.from_ir(circuit.to_ir("OPENQASM")) - assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") \ No newline at end of file + assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") From c6e60f1e195be452e6eda9cb7d52bc37fa0ea0ac Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 27 Jun 2024 15:41:45 -0700 Subject: [PATCH 59/91] fix: Remove measurements from Pytket circuit before performing mapping --- .../emulator_passes/lexi_routing_pass.py | 20 ++++++++++++--- .../emulators/pytket_translator/__init__.py | 5 +++- .../pytket_program_context.py | 25 +++++++++++++------ 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/braket/emulators/emulator_passes/lexi_routing_pass.py b/src/braket/emulators/emulator_passes/lexi_routing_pass.py index 54e888679..8e620c215 100644 --- a/src/braket/emulators/emulator_passes/lexi_routing_pass.py +++ b/src/braket/emulators/emulator_passes/lexi_routing_pass.py @@ -11,8 +11,9 @@ from braket.circuits.serialization import IRType from braket.default_simulator.openqasm.interpreter import Interpreter from braket.emulators.emulator_passes import EmulatorPass -from braket.emulators.pytket_translator import PytketProgramContext, tket_to_qasm3 +from braket.emulators.pytket_translator import PytketProgramContext, PytketProgramTranslation, tket_to_qasm3 from braket.ir.openqasm import Program as OpenQasmProgram +from braket.circuits.compiler_directives import StartVerbatimBox class LexiRoutingPass(EmulatorPass): @@ -38,22 +39,33 @@ def run(self, task_specification): @run.register def _(self, task_specification: Circuit) -> Circuit: + has_verbatim = any([isinstance(instruction.operator, StartVerbatimBox) \ + for instruction in task_specification.instructions]) + + #If the circuit has a verbatim box, don't perform circuit mapping. + if has_verbatim: + return task_specification + open_qasm_program = task_specification.to_ir(ir_type=IRType.OPENQASM) mapped_open_qasm_program = self.run(open_qasm_program) resulting_circuit = Circuit.from_ir(mapped_open_qasm_program) return resulting_circuit @run.register - def _(self, task_specification: OpenQasmProgram) -> OpenQasmProgram: - pytket_circuit = Interpreter(PytketProgramContext()).build_circuit( + def _(self, task_specification: OpenQasmProgram, is_verbatim=False) -> OpenQasmProgram: + pytket_translation: PytketProgramTranslation = Interpreter(PytketProgramContext()).build_circuit( task_specification.source ) + if pytket_translation.is_verbatim: + return task_specification + + pytket_circuit = pytket_translation.circuit pytket_circuit = self.run(pytket_circuit) open_qasm_program_string = tket_to_qasm3(pytket_circuit) return OpenQasmProgram(source=open_qasm_program_string) @run.register - def _(self, task_specification: PytketCircuit) -> PytketCircuit: + def _(self, task_specification: PytketCircuit, is_verbatim=False) -> PytketCircuit: self._mapping_manager.route_circuit( task_specification, [self._lexi_label, self._lexi_route] ) diff --git a/src/braket/emulators/pytket_translator/__init__.py b/src/braket/emulators/pytket_translator/__init__.py index 58798ef68..43265d215 100644 --- a/src/braket/emulators/pytket_translator/__init__.py +++ b/src/braket/emulators/pytket_translator/__init__.py @@ -1,4 +1,7 @@ -from braket.emulators.pytket_translator.pytket_program_context import PytketProgramContext +from braket.emulators.pytket_translator.pytket_program_context import ( + PytketProgramContext, + PytketProgramTranslation +) from braket.emulators.pytket_translator.qasm3_gen import tket_to_qasm3 from braket.emulators.pytket_translator.translations import ( COMPOSED_GATES, diff --git a/src/braket/emulators/pytket_translator/pytket_program_context.py b/src/braket/emulators/pytket_translator/pytket_program_context.py index 3bd054493..142a3fd54 100644 --- a/src/braket/emulators/pytket_translator/pytket_program_context.py +++ b/src/braket/emulators/pytket_translator/pytket_program_context.py @@ -1,5 +1,6 @@ +from dataclasses import dataclass, field from collections.abc import Iterable -from typing import Any, Optional, Union +from typing import Any, Optional, Union, Dict import numpy as np from pytket.circuit import Circuit, OpType, Unitary1qBox, Unitary2qBox, Unitary3qBox @@ -9,6 +10,11 @@ from braket.default_simulator.openqasm.program_context import AbstractProgramContext from braket.emulators.pytket_translator.translations import COMPOSED_GATES, QASM_TO_PYTKET +@dataclass +class PytketProgramTranslation: + circuit: Circuit = field(default_factory=Circuit) + is_verbatim: bool = field(default=False) + measurements: Dict[Union[int, Qubit], Union[int, Bit]] = field(default_factory=dict) class PytketProgramContext(AbstractProgramContext): def __init__(self, circuit: Optional[Circuit] = None): @@ -19,13 +25,18 @@ def __init__(self, circuit: Optional[Circuit] = None): context. Default: None. """ super().__init__() - self._circuit = circuit or Circuit() + self._program_translation = PytketProgramTranslation() self._qubits_set = set() @property - def circuit(self) -> Circuit: - """Returns the Pytket circuit being built in this context.""" - return self._circuit + def _circuit(self) -> Circuit: + """Returns the Pytket Circuit being built in this context.""" + return self._program_translation.circuit + + @property + def circuit(self) -> PytketProgramTranslation: + """Returns the PytketProgramTranslation being built in this context.""" + return self._program_translation def is_builtin_gate(self, name: str) -> bool: user_defined_gate = self.is_user_defined_gate(name) @@ -70,8 +81,8 @@ def add_measure(self, target: tuple[int], classical_targets: Iterable[int] = Non self._check_and_update_qubits(target) for index, qubit in enumerate(target): target_bit = classical_targets[index] if classical_targets is not None else qubit - self._circuit.add_bit(Bit(target_bit)) - self._circuit.Measure(qubit, target_bit) + pytket_bit = Bit(target_bit) + self._program_translation.measurements[qubit] = pytket_bit def add_custom_unitary( self, From 5bec8d4545643e39dffa3bb1a22a558ef788f1c6 Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 27 Jun 2024 15:58:59 -0700 Subject: [PATCH 60/91] feat: Add decompose bridge pass after Tket Routing --- src/braket/emulators/emulator_passes/lexi_routing_pass.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/braket/emulators/emulator_passes/lexi_routing_pass.py b/src/braket/emulators/emulator_passes/lexi_routing_pass.py index 8e620c215..f50ad3781 100644 --- a/src/braket/emulators/emulator_passes/lexi_routing_pass.py +++ b/src/braket/emulators/emulator_passes/lexi_routing_pass.py @@ -6,6 +6,7 @@ from pytket import Circuit as PytketCircuit from pytket.architecture import Architecture from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager +from pytket.transform import Transform from braket.circuits import Circuit from braket.circuits.serialization import IRType @@ -69,4 +70,5 @@ def _(self, task_specification: PytketCircuit, is_verbatim=False) -> PytketCircu self._mapping_manager.route_circuit( task_specification, [self._lexi_label, self._lexi_route] ) + Transform.DecomposeBRIDGE().apply(task_specification) return task_specification From 40bf1799d90e2ad4ac4f67780a0429558115f71f Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 27 Jun 2024 16:29:25 -0700 Subject: [PATCH 61/91] feat: Add emulator names to error messages and use AwsDevice names when creating emulators --- src/braket/aws/aws_device.py | 2 +- src/braket/emulators/emulator.py | 29 +++++++++++++++---- .../criteria/gate_criterion.py | 4 +-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index c8c2b362d..52c1ac8ee 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -884,7 +884,7 @@ def _setup_emulator(self, emulator_noise_model: NoiseModel = None) -> Emulator: """ if not emulator_noise_model: emulator_noise_model = create_device_noise_model(self.properties, self._arn) - self._emulator = Emulator(noise_model=emulator_noise_model, backend="braket_dm") + self._emulator = Emulator(noise_model=emulator_noise_model, backend="braket_dm", name=self._name) self._emulator.add_pass(create_qubit_count_criterion(self.properties)) self._emulator.add_pass(create_gate_criterion(self.properties)) diff --git a/src/braket/emulators/emulator.py b/src/braket/emulators/emulator.py index aa4d290e1..720dac0f7 100644 --- a/src/braket/emulators/emulator.py +++ b/src/braket/emulators/emulator.py @@ -27,7 +27,11 @@ def __init__( emulator_passes: Iterable[EmulatorPass] = None, **kwargs, ): - + Device.__init__( + self, + name=kwargs.get("name", "DeviceEmulator"), + status="AVAILABLE" + ) EmulatorInterface.__init__(self, emulator_passes) self._noise_model = noise_model if noise_model and backend == "default": @@ -56,7 +60,8 @@ def run( """ task_specification = self.run_program_passes( task_specification, apply_noise_model=False - ) # Don't apply noise model as the local simulator will automatically apply it. + ) + # Don't apply noise model as the local simulator will automatically apply it. return self._backend.run(task_specification, shots, inputs, *args, **kwargs) def run_batch( # noqa: C901 @@ -90,7 +95,19 @@ def noise_model(self, noise_model: NoiseModel): def run_program_passes( self, task_specification: ProgramType, apply_noise_model=True ) -> ProgramType: - program = super().run_program_passes(task_specification) - if apply_noise_model: - return self._noise_model.apply(program) - return program + try: + program = super().run_program_passes(task_specification) + if apply_noise_model: + return self._noise_model.apply(program) + return program + except Exception as e: + self._raise_exception(e) + + def run_validation_passes(self, task_specification: ProgramType) -> None: + try: + super().run_validation_passes(task_specification) + except Exception as e: + self._raise_exception(e) + + def _raise_exception(self, exception: Exception): + raise type(exception)(str(exception) + f" ({self._name})") \ No newline at end of file diff --git a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py index 6d88f2bfb..8183dcbbe 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py @@ -39,7 +39,7 @@ def validate(self, circuit: Circuit) -> None: gate = instruction.operator if not type(gate) in self._native_gates: raise ValueError( - f"Gate {gate.name} is not a native gate supported by this emulator." + f"Gate {gate.name} is not a native gate supported by this device." ) idx += 1 if not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): @@ -47,7 +47,7 @@ def validate(self, circuit: Circuit) -> None: elif isinstance(instruction.operator, Gate): gate = instruction.operator if not type(gate) in self._supported_gates: - raise ValueError(f"Gate {gate.name} is not supported by this emulator.") + raise ValueError(f"Gate {gate.name} is not supported by this device.") idx += 1 def __eq__(self, other: EmulatorCriterion) -> bool: From 3e1b47c8a507a73341c99cedc9d7666d0875540c Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 27 Jun 2024 22:03:16 -0700 Subject: [PATCH 62/91] fix: Fix mistake with applying RB calibration data if sRB data is unavailable --- src/braket/aws/aws_noise_models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/braket/aws/aws_noise_models.py b/src/braket/aws/aws_noise_models.py index 4a3c45413..53d87c83d 100644 --- a/src/braket/aws/aws_noise_models.py +++ b/src/braket/aws/aws_noise_models.py @@ -242,14 +242,14 @@ def _setup_basic_noise_model_strategy( noise_model.add_noise(PhaseDamping(dephasing_prob), GateCriteria(qubits=qubit)) # 1 Qubit RB Depolarizing Noise - if "SIMULTANEOUS_RANDOMIZED_BENCHMARKING" in data: + if data.get("SIMULTANEOUS_RANDOMIZED_BENCHMARKING"): benchmark_fidelity = data["SIMULTANEOUS_RANDOMIZED_BENCHMARKING"] else: - benchmark_fidelity = data.get(["RANDOMIZED_BENCHMARKING"]) + benchmark_fidelity = data.get("RANDOMIZED_BENCHMARKING") if benchmark_fidelity: depolarizing_rate = 1 - benchmark_fidelity noise_model.add_noise(Depolarizing(depolarizing_rate), GateCriteria(qubits=qubit)) - + # 1 Qubit Readout Error readout_error_rate = 1 - data["READOUT"] noise_model.add_noise(BitFlip(readout_error_rate), ObservableCriteria(qubits=qubit)) From 3f5094ead90d8cb371c170b4e3c94092ac9317cd Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 1 Jul 2024 11:41:45 -0700 Subject: [PATCH 63/91] fix: Fix add_pass to correctly check if iterable supplied as argument --- src/braket/emulators/emulater_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/emulators/emulater_interface.py b/src/braket/emulators/emulater_interface.py index 59d789f1b..ad5f85731 100644 --- a/src/braket/emulators/emulater_interface.py +++ b/src/braket/emulators/emulater_interface.py @@ -34,7 +34,7 @@ def run_validation_passes[ProgramType](self, task_specification: ProgramType) -> emulator_pass(task_specification) def add_pass(self, emulator_pass: Union[Iterable[EmulatorPass], EmulatorPass]) -> None: - if isinstance(emulator_pass, Iterator): + if isinstance(emulator_pass, Iterable): self._emulator_passes.extend(emulator_pass) elif isinstance(emulator_pass, EmulatorPass): self._emulator_passes.append(emulator_pass) From 66703d7202dfb0bd2e47b8a9940b92c428c16ff9 Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 1 Jul 2024 11:42:15 -0700 Subject: [PATCH 64/91] fix: Return nothing in AwsDevice validate if program is valid. --- src/braket/aws/aws_device.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 52c1ac8ee..c4137c1b9 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -911,11 +911,12 @@ def validate( """ 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 the input program without modification. + program meets all criterion, returns. """ self.emulator.run_validation_passes(task_specification) - return task_specification - + return + + def run_emulator_passes( self, task_specification: Union[ From ac070e2560c83c79e1b3b02a4b1d011e0e129b44 Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 4 Jul 2024 13:45:55 -0700 Subject: [PATCH 65/91] change: Remove tket passes and files, fix emulator_interface.py filename typo --- ...ter_interface.py => emulator_interface.py} | 0 .../emulator_passes/lexi_routing_pass.py | 74 -------- .../emulators/pytket_translator/__init__.py | 10 -- .../pytket_translator/composed_gates.py | 85 --------- .../pytket_program_context.py | 116 ------------- .../pytket_translator/qasm3_gen/__init__.py | 2 - .../qasm3_gen/qasm_context.py | 33 ---- .../qasm3_gen/qasm_writer.py | 129 -------------- .../qasm3_gen/tket_to_qasm3.py | 164 ------------------ .../pytket_translator/translations.py | 61 ------- 10 files changed, 674 deletions(-) rename src/braket/emulators/{emulater_interface.py => emulator_interface.py} (100%) delete mode 100644 src/braket/emulators/emulator_passes/lexi_routing_pass.py delete mode 100644 src/braket/emulators/pytket_translator/__init__.py delete mode 100644 src/braket/emulators/pytket_translator/composed_gates.py delete mode 100644 src/braket/emulators/pytket_translator/pytket_program_context.py delete mode 100644 src/braket/emulators/pytket_translator/qasm3_gen/__init__.py delete mode 100644 src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py delete mode 100644 src/braket/emulators/pytket_translator/qasm3_gen/qasm_writer.py delete mode 100644 src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py delete mode 100644 src/braket/emulators/pytket_translator/translations.py diff --git a/src/braket/emulators/emulater_interface.py b/src/braket/emulators/emulator_interface.py similarity index 100% rename from src/braket/emulators/emulater_interface.py rename to src/braket/emulators/emulator_interface.py diff --git a/src/braket/emulators/emulator_passes/lexi_routing_pass.py b/src/braket/emulators/emulator_passes/lexi_routing_pass.py deleted file mode 100644 index f50ad3781..000000000 --- a/src/braket/emulators/emulator_passes/lexi_routing_pass.py +++ /dev/null @@ -1,74 +0,0 @@ -from collections.abc import Iterable -from functools import singledispatchmethod -from typing import Dict, Union - -from networkx import DiGraph -from pytket import Circuit as PytketCircuit -from pytket.architecture import Architecture -from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager -from pytket.transform import Transform - -from braket.circuits import Circuit -from braket.circuits.serialization import IRType -from braket.default_simulator.openqasm.interpreter import Interpreter -from braket.emulators.emulator_passes import EmulatorPass -from braket.emulators.pytket_translator import PytketProgramContext, PytketProgramTranslation, tket_to_qasm3 -from braket.ir.openqasm import Program as OpenQasmProgram -from braket.circuits.compiler_directives import StartVerbatimBox - - -class LexiRoutingPass(EmulatorPass): - def __init__(self, hardware_topology: Union[Dict[int, Iterable[int]], DiGraph]): - super().__init__() - self._mapping_manager = MappingManager(self._get_architecture(hardware_topology)) - self._lexi_label = LexiLabellingMethod() - self._lexi_route = LexiRouteRoutingMethod() - - def _get_architecture(self, hardware_topology: Union[Dict[int, Iterable[int]], DiGraph]): - if isinstance(hardware_topology, dict): - edge_list = [(q1, q2) for q1, edges in hardware_topology.items() for q2 in edges] - elif isinstance(hardware_topology, DiGraph): - edge_list = list(hardware_topology.edges) - - return Architecture(edge_list) - - @singledispatchmethod - def run(self, task_specification): - raise NotImplementedError( - f"LexiRoutingPass does not support task specification type: {type(task_specification)}" - ) - - @run.register - def _(self, task_specification: Circuit) -> Circuit: - has_verbatim = any([isinstance(instruction.operator, StartVerbatimBox) \ - for instruction in task_specification.instructions]) - - #If the circuit has a verbatim box, don't perform circuit mapping. - if has_verbatim: - return task_specification - - open_qasm_program = task_specification.to_ir(ir_type=IRType.OPENQASM) - mapped_open_qasm_program = self.run(open_qasm_program) - resulting_circuit = Circuit.from_ir(mapped_open_qasm_program) - return resulting_circuit - - @run.register - def _(self, task_specification: OpenQasmProgram, is_verbatim=False) -> OpenQasmProgram: - pytket_translation: PytketProgramTranslation = Interpreter(PytketProgramContext()).build_circuit( - task_specification.source - ) - if pytket_translation.is_verbatim: - return task_specification - - pytket_circuit = pytket_translation.circuit - pytket_circuit = self.run(pytket_circuit) - open_qasm_program_string = tket_to_qasm3(pytket_circuit) - return OpenQasmProgram(source=open_qasm_program_string) - - @run.register - def _(self, task_specification: PytketCircuit, is_verbatim=False) -> PytketCircuit: - self._mapping_manager.route_circuit( - task_specification, [self._lexi_label, self._lexi_route] - ) - Transform.DecomposeBRIDGE().apply(task_specification) - return task_specification diff --git a/src/braket/emulators/pytket_translator/__init__.py b/src/braket/emulators/pytket_translator/__init__.py deleted file mode 100644 index 43265d215..000000000 --- a/src/braket/emulators/pytket_translator/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from braket.emulators.pytket_translator.pytket_program_context import ( - PytketProgramContext, - PytketProgramTranslation -) -from braket.emulators.pytket_translator.qasm3_gen import tket_to_qasm3 -from braket.emulators.pytket_translator.translations import ( - COMPOSED_GATES, - PYTKET_TO_QASM, - QASM_TO_PYTKET, -) diff --git a/src/braket/emulators/pytket_translator/composed_gates.py b/src/braket/emulators/pytket_translator/composed_gates.py deleted file mode 100644 index fe898c098..000000000 --- a/src/braket/emulators/pytket_translator/composed_gates.py +++ /dev/null @@ -1,85 +0,0 @@ -from collections.abc import Iterable -from typing import List, Union - -from pytket.circuit import Circuit, OpType -from sympy import Expr, Symbol - - -class ComposedGates: - @staticmethod - def add_pswap( - circ: Circuit, arguments: Iterable[Union[float, Expr, Symbol]], qubits: List[int] - ) -> None: - """ - Applies a pswap operation to the target qubits in the provided circuit using - gate operations available in Pytket. - Args: - circ (Circuit): The pytket circuit to add the gate to. - arguments (Iterable[Union[float, Expr, Symbol]]): Contains the - pswap angle argument. - qubits (List[int]): The indices of the target qubits in the pswap operation. - """ - assert len(arguments) == 1 - assert len(qubits) == 2 - circ.add_gate(OpType.ZZPhase, arguments, [qubits[0], qubits[1]]) - circ.add_gate(OpType.SWAP, [qubits[0], qubits[1]]) - circ.add_phase(arguments[0] / 2) - - @staticmethod - def add_cphaseshift00( - circ: Circuit, arguments: Iterable[Union[float, Expr, Symbol]], qubits: List[int] - ) -> None: - """ - Applies a cphaseshift00 operation to the target qubits in the provided circuit using - gate operations available in Pytket. - Args: - circ (Circuit): The pytket circuit to add the gate to. - arguments (Iterable[Union[float, Expr, Symbol]]): Contains the - cphaseshift00 angle argument. - qubits (List[int]): The indices of the target qubits in the cphaseshift00 operation. - """ - assert len(arguments) == 1 - assert len(qubits) == 2 - circ.add_gate(OpType.X, [qubits[1]]) - circ.add_gate(OpType.CX, [qubits[1], qubits[0]]) - circ.add_gate(OpType.CU1, arguments, [qubits[1], qubits[0]]) - circ.add_gate(OpType.CX, [qubits[1], qubits[0]]) - circ.add_gate(OpType.X, [qubits[1]]) - - @staticmethod - def add_cphaseshift01( - circ: Circuit, arguments: Iterable[Union[float, Expr, Symbol]], qubits: List[int] - ) -> None: - """ - Applies a cphaseshift01 operation to the target qubits in the provided circuit using - gate operations available in Pytket. - Args: - circ (Circuit): The pytket circuit to add the gate to. - arguments (Iterable[Union[float, Expr, Symbol]]): Contains the - cphaseshift01 angle argument. - qubits (List[int]): The indices of the target qubits in the cphaseshift01 operation. - """ - assert len(arguments) == 1 - assert len(qubits) == 2 - circ.add_gate(OpType.CX, [qubits[1], qubits[0]]) - circ.add_gate(OpType.CU1, arguments, [qubits[1], qubits[0]]) - circ.add_gate(OpType.CX, [qubits[1], qubits[0]]) - - @staticmethod - def add_cphaseshift10( - circ: Circuit, arguments: Iterable[Union[float, Expr, Symbol]], qubits: List[int] - ) -> None: - """ - Applies a cphaseshift10 operation to the target qubits in the provided circuit using - gate operations available in Pytket. - Args: - circ (Circuit): The pytket circuit to add the gate to. - arguments (Iterable[Union[float, Expr, Symbol]]): Contains the - cphaseshift10 angle argument. - qubits (List[int]): The indices of the target qubits in the cphaseshift10 operation. - """ - assert len(arguments) == 1 - assert len(qubits) == 2 - circ.add_gate(OpType.CX, [qubits[0], qubits[1]]) - circ.add_gate(OpType.CU1, arguments, [qubits[0], qubits[1]]) - circ.add_gate(OpType.CX, [qubits[0], qubits[1]]) diff --git a/src/braket/emulators/pytket_translator/pytket_program_context.py b/src/braket/emulators/pytket_translator/pytket_program_context.py deleted file mode 100644 index 142a3fd54..000000000 --- a/src/braket/emulators/pytket_translator/pytket_program_context.py +++ /dev/null @@ -1,116 +0,0 @@ -from dataclasses import dataclass, field -from collections.abc import Iterable -from typing import Any, Optional, Union, Dict - -import numpy as np -from pytket.circuit import Circuit, OpType, Unitary1qBox, Unitary2qBox, Unitary3qBox -from pytket.unit_id import Bit, Qubit -from sympy import Expr, Symbol, pi - -from braket.default_simulator.openqasm.program_context import AbstractProgramContext -from braket.emulators.pytket_translator.translations import COMPOSED_GATES, QASM_TO_PYTKET - -@dataclass -class PytketProgramTranslation: - circuit: Circuit = field(default_factory=Circuit) - is_verbatim: bool = field(default=False) - measurements: Dict[Union[int, Qubit], Union[int, Bit]] = field(default_factory=dict) - -class PytketProgramContext(AbstractProgramContext): - def __init__(self, circuit: Optional[Circuit] = None): - """Inits a `PytketProgramContext`. - - Args: - circuit (Optional[Circuit]): A partially-built Pytket circuit to continue building with this - context. Default: None. - """ - super().__init__() - self._program_translation = PytketProgramTranslation() - self._qubits_set = set() - - @property - def _circuit(self) -> Circuit: - """Returns the Pytket Circuit being built in this context.""" - return self._program_translation.circuit - - @property - def circuit(self) -> PytketProgramTranslation: - """Returns the PytketProgramTranslation being built in this context.""" - return self._program_translation - - def is_builtin_gate(self, name: str) -> bool: - user_defined_gate = self.is_user_defined_gate(name) - result = (name in QASM_TO_PYTKET or COMPOSED_GATES) and not user_defined_gate - return result - - def add_gate_instruction( - self, - gate_name: str, - target: tuple[int, ...], - params: Iterable[Union[float, Expr, Symbol]], - ctrl_modifiers: list[int], - power: int, - ): - self._check_and_update_qubits(target) - # Convert from Braket's radians to TKET's half-turns - params = [param / pi for param in params] - if gate_name in QASM_TO_PYTKET: - op = QASM_TO_PYTKET[gate_name] - if len(params) > 0: - self._circuit.add_gate(op, params, target) - else: - self._circuit.add_gate(op, target) - elif gate_name in COMPOSED_GATES: - COMPOSED_GATES[gate_name](self._circuit, params, target) - else: - raise ValueError(f"Gate {gate_name} is not supported in pytket translations.") - - def _check_and_update_qubits(self, target: tuple[int, ...]): - for qubit in target: - if qubit not in self._qubits_set: - new_qubit = Qubit(index=qubit) - self._qubits_set.add(qubit) - self._circuit.add_qubit(new_qubit) - - def add_phase_instruction(self, target, phase_value): - self.add_gate_instruction("gphase", target, phase_value) - - def add_measure(self, target: tuple[int], classical_targets: Iterable[int] = None): - if len(target) == 0: - return - self._check_and_update_qubits(target) - for index, qubit in enumerate(target): - target_bit = classical_targets[index] if classical_targets is not None else qubit - pytket_bit = Bit(target_bit) - self._program_translation.measurements[qubit] = pytket_bit - - def add_custom_unitary( - self, - unitary: np.ndarray, - target: tuple[int, ...], - ): - num_targets = len(target) - if not (1 <= num_targets <= 3): - raise ValueError("At most 3 qubit gates are supported for custom unitary operations.") - - operator_qubit_count = int(np.log2(unitary.shape[0])) - if operator_qubit_count != num_targets: - raise ValueError( - f"Operator qubit count {operator_qubit_count} must be equal to size of target qubit set {target}" - ) - - self._check_and_update_qubits(target) - if num_targets == 1: - unitary_box = Unitary1qBox(unitary) - self._circuit.add_unitary1qbox(unitary_box, *target) - elif num_targets == 2: - unitary_box = Unitary2qBox(unitary) - self._circuit.add_unitary2qbox(unitary_box, *target) - elif num_targets == 3: - unitary_box = Unitary3qBox(unitary) - self._circuit.add_unitary3qbox(unitary_box, *target) - - def handle_parameter_value(self, value: Union[float, Expr]) -> Any: - if isinstance(value, Expr): - return value.evalf() - return value diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/__init__.py b/src/braket/emulators/pytket_translator/qasm3_gen/__init__.py deleted file mode 100644 index 25fc7f2c6..000000000 --- a/src/braket/emulators/pytket_translator/qasm3_gen/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from braket.emulators.pytket_translator.qasm3_gen.qasm_context import QasmContext -from braket.emulators.pytket_translator.qasm3_gen.tket_to_qasm3 import tket_to_qasm3 diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py b/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py deleted file mode 100644 index 4f0a8e7c7..000000000 --- a/src/braket/emulators/pytket_translator/qasm3_gen/qasm_context.py +++ /dev/null @@ -1,33 +0,0 @@ -from collections import namedtuple -from typing import Dict, List, Union - -from sympy import Expr, Symbol, pi - -from braket.emulators.pytket_translator.translations import MEASUREMENT_REGISTER_NAME - -"""A single measurement: from qubit index to the bit expression.""" -Measurement = namedtuple("Measurement", ("qubit", "bit")) - -"""A gate operation, with a given name, classical args, and qubits""" -Gate = namedtuple("Gate", ("name", "args", "qubits")) - - -class QasmContext: - - def __init__(self, input_parameters: Dict[str, str] = dict()): - self.input_parameters = input_parameters - self.num_bits: int = 0 - self.gates: List[Gate] = [] - self.measurements: List[Measurement] = [] - - def set_num_bits(self, num_bits: int) -> None: - self.num_bits = num_bits - - def add_gate(self, name: str, args: List[Union[Expr, Symbol]], qubits: List[int]): - self.gates.append(Gate(name, args, qubits)) - - def add_measurement(self, qubit: int, cbit: int): - self.measurements.append(Measurement(qubit, f"{MEASUREMENT_REGISTER_NAME}[{cbit}]")) - - def add_parameter(self, param_name: str, param_type: str): - self.input_parameters[param_name] = param_type diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/qasm_writer.py b/src/braket/emulators/pytket_translator/qasm3_gen/qasm_writer.py deleted file mode 100644 index 39cb7acce..000000000 --- a/src/braket/emulators/pytket_translator/qasm3_gen/qasm_writer.py +++ /dev/null @@ -1,129 +0,0 @@ -import io -from abc import ABC, abstractmethod - -from braket.emulators.pytket_translator.qasm3_gen.qasm_context import Gate, QasmContext -from braket.emulators.pytket_translator.translations import MEASUREMENT_REGISTER_NAME - - -class QasmWriter(ABC): - """Abstract class for OpenQASM program writing. It handles basic program writing flow, - but methods can be overwritten by subclasses to modify output behavior. - """ - - def __init__(self, context: QasmContext): - """ - Initialize a new QasmWriter from a context containing program information. - The writer does not modify the context. - """ - self.context = context - - def get_program(self) -> str: - """ - Return the OpenQASM 3.0 program program string from the constructor argument - ProgramContext. - - Returns: - str: A complete OpenQASM 3.0 program string. - """ - stream = io.StringIO() - stream.write(self.get_program_header()) - stream.write(self.get_input_declarations()) - stream.write(self.get_classical_bit_declarations()) - # stream.write(self.get_verbatim_pragma()) - # stream.write(self.get_boxed_program_body()) - stream.write(self.get_body()) - stream.write(self.get_measurements()) - return stream.getvalue() - - def get_program_header(self) -> str: - return "OPENQASM 3.0;\n" - - @abstractmethod - def get_input_declarations(self) -> str: - """Return input declaration statements. - - For example: - "input float theta;\ninput float alpha;\n" - """ - - def get_classical_bit_declarations(self) -> str: - return ( - f"bit[{self.context.num_bits}] {MEASUREMENT_REGISTER_NAME};\n" - if self.context.num_bits - else "" - ) - - def get_verbatim_pragma(self) -> str: - return "#pragma braket verbatim\n" - - def get_boxed_program_body(self) -> str: - return f"box {{\n{self.get_body()}}}\n" - - def get_body(self) -> str: - """ - Creates an OpenQASM 3.0 program body from the program operations and - returns a string containing the program body. - - Returns: - str: the OpenQASM 3.0 program body. - """ - stream = io.StringIO() - for gate in self.context.gates: - stream.write(self.get_gate(gate)) - return stream.getvalue() - - def get_gate(self, gate: Gate) -> str: - return f"{gate.name}{self.get_gate_args(gate)} {self.get_gate_qubits(gate)};\n" - - @abstractmethod - def get_gate_args(self, gate: Gate) -> str: - """Return gate arguments. - - For example: - "0.5,pi*theta" - """ - - def get_gate_qubits(self, gate: Gate) -> str: - return ",".join(self.format_qubit(q) for q in gate.qubits) - - def get_measurements(self) -> str: - return "\n".join( - f"{meas.bit} = measure {self.format_qubit(meas.qubit)};" - for meas in self.context.measurements - ) - - def format_qubit(self, qubit: int) -> str: - return f"${qubit}" - - -class BasicQasmWriter(QasmWriter): - """This writer returns human-readable, basic OpenQASM. - - For example: - - OPENQASM 3.0; - input angle theta; - bit[1] c; - #pragma braket verbatim - box { - rx(pi*theta) $0; - } - c[0] = measure $0; - - """ - - def get_input_declarations(self) -> str: - """ - Creates parameter declarations for program inputs (e.g. theta, phi). - Parameters are assumed to all be of type float. - - Returns: - str: OpenQASM 3.0 lines with paramater variable declarations. - """ - stream = io.StringIO() - for param_name, param_type in self.context.input_parameters.items(): - stream.write(f"input {param_type} {param_name};\n") - return stream.getvalue() - - def get_gate_args(self, gate: Gate) -> str: - return f"({','.join(str(arg) for arg in gate.args)})" if gate.args else "" diff --git a/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py b/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py deleted file mode 100644 index a53aca9ef..000000000 --- a/src/braket/emulators/pytket_translator/qasm3_gen/tket_to_qasm3.py +++ /dev/null @@ -1,164 +0,0 @@ -from typing import Dict, List, Set, Union - -from pytket.circuit import Circuit, Command, Node, OpType, UnitID -from sympy import Expr, Symbol, pi - -from braket.emulators.pytket_translator.translations import PYTKET_TO_QASM -from braket.emulators.pytket_translator.qasm3_gen.qasm_context import QasmContext -from braket.emulators.pytket_translator.qasm3_gen.qasm_writer import BasicQasmWriter - - -def tket_to_qasm3( - circuit: Circuit, - input_parameters: Dict[str, str] = dict(), - gate_overrides: Dict[OpType, str] = dict(), -) -> str: - """ - Converts a Pytket circuit to an OpenQASM 3.0 string. - - Args: - circuit (Circuit): The Pytket circuit to translate. - input_parameters (Dict[str, str]): Program parameters to declare at the beginning - of the OpenQASM 3.0 program. - gate_overrides (Dict[OpType, str]): Gate names overrides that take precedence - over the default OpenQASM 3.0 gate names. - - Returns: - str: An OpenQASM 3.0 program string. - """ - ticket_visitor = TketCircuitVisitor(QasmContext(input_parameters), gate_overrides) - ticket_visitor.walk_circuit(circuit) - writer = BasicQasmWriter(ticket_visitor.context) - return writer.get_program() - - -class TketCircuitVisitor: - def __init__(self, context, gate_overrides: Dict[OpType, str] = dict()): - self.context = context - self.gate_overrides = gate_overrides - self._measured_nodes: Set[Node] = set() - - def walk_circuit(self, circuit: Circuit) -> None: - """ - Visits all the commands in the circuit and processes them by their - operation type. - - Args: - circuit (Circuit): The Pytket circuit to perform the walk on. - """ - self.context.set_num_bits(len(circuit.bits)) - for command in circuit: - self._visit_command(command) - - def _visit_command(self, command: Command) -> None: - """ - Calls the appropriate visit function based on the operation type of the given - command. - - Args: - command (Command): The circuit instruction to process. - """ - op = command.op - self._validate_args_not_measured(command.args) - optype = op.type - if optype == OpType.CircBox: - self._visit_box(command) - elif optype == OpType.Measure: - self._visit_measure(command) - else: - self._visit_gate(command, optype) - - def _validate_args_not_measured(self, args: List[UnitID]) -> None: - """ - Checks that all qubits used in the instruction have not already been measured. - - Args: - args (List[UnitID]): List of targets to validate. - """ - for arg in args: - if arg in self._measured_nodes: - raise ValueError( - "Circuit QASM cannot be generated as circuit contains midcircuit " - f"measurements on qubit: {arg}" - ) - - def _visit_box(self, command: Command) -> None: - """ - Visits all the instructions inside a Pytket boxed circuit. - - Args: - command (Command): Contains the instructions of the boxed circuit. - """ - circ = command.op.get_circuit() - for command in circ: - self._visit_command(command) - - def _visit_measure(self, command: Command) -> None: - """ - Adds a measure instruction to the QASM context being built. - - Args: - command (Command): Specifies the classical and qubit indices targeted - in this measurement instruction. - """ - qubit_node = command.args[0] - qubit = qubit_node.index[0] - cbit = command.args[1].index[0] - self.context.add_measurement(qubit, cbit) - self._measured_nodes.add(qubit_node) - - def _visit_gate(self, command: Command, optype: OpType) -> None: - """ - Check to see if this operation is a gate known by OpenQASM3.0; if it is, - retrieve the appropriate translation and add the operation to the context. - - Args: - command (Command): The Pytket instruction containing the gate targets. - optype (OpType): Specifies the gate operation being used in this - instruction. - """ - gate_name: str - if optype in self.gate_overrides: - gate_name = self.gate_overrides[optype] - elif optype in PYTKET_TO_QASM: - gate_name = PYTKET_TO_QASM[optype] - else: - raise ValueError(f"Operation {optype} cannot be translated to OpenQASM3.0.") - - qubits = command.args - params = command.op.params - - # Look for any free parameters and add them to the context for initialization - for param in params: - if isinstance(param, Expr): - for symbol in param.free_symbols: - if symbol != pi: - self.context.add_parameter(str(symbol), "float") - params = self._gate_angles_in_radians(params) - qubits = [q.index[0] for q in qubits] - self.context.add_gate(gate_name, params, qubits) - - def _gate_angles_in_radians(self, params: List[Union[Expr, float]]) -> List[Expr]: - """ - Converts a list of expressions or values in half-angle units to radians. - - Args: - params (List[Union[Expr, float]]): List of angle arguments to convert - to radians. - - Returns: - List[Expr]: A list of gate angle arguments with radian units. - """ - return [self._tau_to_radians(param) for param in params] - - def _tau_to_radians(self, arg: Union[float, Expr, Symbol]) -> Expr: - """ - Converts expressions from half-angle units to radians. - - Args: - arg (Union[float, Expr, Symbol]): expression to convert. - - Returns: - Expr: Returns an expression with radian units. - """ - return pi * arg diff --git a/src/braket/emulators/pytket_translator/translations.py b/src/braket/emulators/pytket_translator/translations.py deleted file mode 100644 index d8c472d8e..000000000 --- a/src/braket/emulators/pytket_translator/translations.py +++ /dev/null @@ -1,61 +0,0 @@ -from pytket.circuit import OpType - -from braket.emulators.pytket_translator.composed_gates import ComposedGates - -"""The measurement register identifier.""" -MEASUREMENT_REGISTER_NAME = "c" - -""" - OpenQASM-3.0 to Pytket Name Translations -""" -QASM_TO_PYTKET = { - "gphase": OpType.Phase, - "i": OpType.noop, - "h": OpType.H, - "x": OpType.X, - "y": OpType.Y, - "z": OpType.Z, - "cv": OpType.CV, - "cnot": OpType.CX, - "cy": OpType.CY, - "cz": OpType.CZ, - "ecr": OpType.ECR, - "s": OpType.S, - "si": OpType.Sdg, - "t": OpType.T, - "ti": OpType.Tdg, - "v": OpType.V, - "vi": OpType.Vdg, - "phaseshift": OpType.U1, - "rx": OpType.Rx, - "ry": OpType.Ry, - "rz": OpType.Rz, - "U": OpType.U3, - "swap": OpType.SWAP, - "iswap": OpType.ISWAPMax, - "xy": OpType.ISWAP, - "xx": OpType.XXPhase, - "yy": OpType.YYPhase, - "zz": OpType.ZZPhase, - "ccnot": OpType.CCX, - "cswap": OpType.CSWAP, - "unitary": OpType.U3, - "gpi": OpType.GPI, - "gpi2": OpType.GPI2, - "ms": OpType.AAMS, - "cphaseshift": OpType.CU1, - "prx": OpType.PhasedX, -} - - -COMPOSED_GATES = { - "cphaseshift00": ComposedGates.add_cphaseshift00, - "cphaseshift01": ComposedGates.add_cphaseshift01, - "cphaseshift10": ComposedGates.add_cphaseshift10, - "pswap": ComposedGates.add_pswap, -} - -""" - Pytket to OpenQASM-3.0 Name Translations -""" -PYTKET_TO_QASM = {optype: qasm_name for qasm_name, optype in QASM_TO_PYTKET.items()} From c37bac46f852ccfa0e8c711933eb522b7ede7fc7 Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 4 Jul 2024 14:28:40 -0700 Subject: [PATCH 66/91] fix: Update EmulatorInterface import in Emulator module --- src/braket/emulators/emulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/emulators/emulator.py b/src/braket/emulators/emulator.py index 720dac0f7..43332c834 100644 --- a/src/braket/emulators/emulator.py +++ b/src/braket/emulators/emulator.py @@ -8,7 +8,7 @@ from braket.circuits.noise_model import NoiseModel from braket.devices import Device from braket.devices.local_simulator import LocalSimulator -from braket.emulators.emulater_interface import EmulatorInterface +from braket.emulators.emulator_interface import EmulatorInterface from braket.emulators.emulator_passes import EmulatorPass, ProgramType from braket.ir.openqasm import Program as OpenQasmProgram from braket.simulator import BraketSimulator From 545c8ce20da7fffe126108c96f83d8490987f85e Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 4 Jul 2024 14:29:48 -0700 Subject: [PATCH 67/91] fix: Remove references to LexiRouting pass throughout repo. --- src/braket/aws/aws_device.py | 4 --- src/braket/aws/aws_emulator_helpers.py | 32 +------------------ .../emulators/emulator_passes/__init__.py | 3 +- 3 files changed, 2 insertions(+), 37 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index c4137c1b9..a6513212a 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -31,7 +31,6 @@ create_connectivity_criterion, create_gate_connectivity_criterion, create_gate_criterion, - create_lexi_mapping_routing_pass, create_qubit_count_criterion, ) from braket.aws.aws_noise_models import create_device_noise_model @@ -892,9 +891,6 @@ def _setup_emulator(self, emulator_noise_model: NoiseModel = None) -> Emulator: self._emulator.add_pass( create_gate_connectivity_criterion(self.properties, self.topology_graph) ) - self._emulator.add_pass( - create_lexi_mapping_routing_pass(self.properties, self.topology_graph) - ) return self._emulator def validate( diff --git a/src/braket/aws/aws_emulator_helpers.py b/src/braket/aws/aws_emulator_helpers.py index 74947943b..604ea4295 100644 --- a/src/braket/aws/aws_emulator_helpers.py +++ b/src/braket/aws/aws_emulator_helpers.py @@ -17,7 +17,6 @@ ConnectivityCriterion, GateConnectivityCriterion, GateCriterion, - LexiRoutingPass, QubitCountCriterion, ) @@ -182,33 +181,4 @@ def _(properties: RigettiDeviceCapabilities, gate_name: str) -> str: @_get_qpu_gate_translation.register(IonqDeviceCapabilities) def _(properties: IonqDeviceCapabilities, gate_name: str) -> str: translations = {"GPI": "GPi", "GPI2": "GPi2"} - return translations.get(gate_name, gate_name) - - -@singledispatch -def create_lexi_mapping_routing_pass( - properties: DeviceCapabilities, connectivity_graph: DiGraph -) -> LexiRoutingPass: - raise NotImplementedError - - -@create_lexi_mapping_routing_pass.register(RigettiDeviceCapabilities) -@create_lexi_mapping_routing_pass.register(IonqDeviceCapabilities) -def _( - properties: Union[RigettiDeviceCapabilities, IonqDeviceCapabilities], - connectivity_graph: DiGraph, -) -> LexiRoutingPass: - return LexiRoutingPass(connectivity_graph) - - -@create_lexi_mapping_routing_pass.register(IqmDeviceCapabilities) -def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> LexiRoutingPass: - """ - IQM provides only forward edges for their *undirected* gate connectivity graph, so back-edges must - be introduced when creating the GateConnectivityCriterion object for an IQM QPU. - """ - - connectivity_graph = connectivity_graph.copy() - for edge in connectivity_graph.edges: - connectivity_graph.add_edge(edge[1], edge[0]) - return LexiRoutingPass(connectivity_graph) + return translations.get(gate_name, gate_name) \ No newline at end of file diff --git a/src/braket/emulators/emulator_passes/__init__.py b/src/braket/emulators/emulator_passes/__init__.py index c95d67bbd..2f917091f 100644 --- a/src/braket/emulators/emulator_passes/__init__.py +++ b/src/braket/emulators/emulator_passes/__init__.py @@ -5,5 +5,4 @@ GateCriterion, QubitCountCriterion, ) -from braket.emulators.emulator_passes.emulator_pass import EmulatorPass, ProgramType -from braket.emulators.emulator_passes.lexi_routing_pass import LexiRoutingPass +from braket.emulators.emulator_passes.emulator_pass import EmulatorPass, ProgramType \ No newline at end of file From afbb96ca26f65f1867146d901ca77854d5492591 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 9 Jul 2024 15:31:51 -0700 Subject: [PATCH 68/91] feat!: Run linter, add comments/documentation, add unit tests for emulator passes --- src/braket/aws/aws_device.py | 87 +++++++-- src/braket/aws/aws_emulator_helpers.py | 76 +++++--- src/braket/aws/aws_noise_models.py | 53 ++++-- src/braket/emulators/__init__.py | 2 +- src/braket/emulators/emulator.py | 121 +++++++++---- src/braket/emulators/emulator_interface.py | 37 +++- .../emulators/emulator_passes/__init__.py | 4 +- .../emulator_passes/criteria/__init__.py | 16 +- .../criteria/connectivity_criterion.py | 41 ++++- .../criteria/emulator_criterion.py | 15 +- .../criteria/gate_connectivity_criterion.py | 43 +++-- .../criteria/gate_criterion.py | 11 +- .../criteria/qubit_count_criterion.py | 13 +- .../emulator_passes/emulator_pass.py | 6 +- .../pytket_translator/test_tket_to_qasm3.py | 117 ------------ .../emulation/test_connectivity_criterion.py | 57 ++++-- .../braket/emulation/test_emulator.py | 170 ++++++++++++++++++ .../test_gate_connectivity_criterion.py | 81 ++++++++- .../braket/emulation/test_gate_criterion.py | 74 ++++---- .../emulation/test_qubit_count_criterion.py | 13 +- 20 files changed, 726 insertions(+), 311 deletions(-) delete mode 100644 test/unit_tests/braket/emulation/pytket_translator/test_tket_to_qasm3.py create mode 100644 test/unit_tests/braket/emulation/test_emulator.py diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index a6513212a..595683764 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -868,6 +868,22 @@ def _parse_calibration_json( @property def emulator(self) -> Emulator: + """ + A device emulator mimics the restrictions and noise of an 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. + + Examples: + >>> device = AwsDevice(Devices.IQM.Garnet) + >>> circuit = Circuit().cnot(0, 1).h(2).cz(2, 3) + >>> device.validate(circuit) + >>> # validates, compiles and runs on the local simulator. + >>> result = device.emulator(circuit, shots=100) + >>> print(result.result().measurement_counts) + + Returns: + Emulator: An emulator for this device, if this is not a simulator device. + """ if self._arn in Devices.Amazon: raise ValueError( "Creating an emulator from a Braket managed simulator is not supported." @@ -876,14 +892,24 @@ def emulator(self) -> Emulator: self._emulator = self._setup_emulator() return self._emulator - def _setup_emulator(self, emulator_noise_model: NoiseModel = None) -> Emulator: + def _setup_emulator(self, emulator_noise_model: NoiseModel | None = None) -> Emulator: """ Sets up an Emulator object whose properties mimic that of this AwsDevice, if the device is a real QPU (not simulated). + + Args: + emulator_noise_model (NoiseModel | None): Use the provided noise model in the emulator + instead of creating one. + + Returns: + Emulator: An emulator with a noise model, compilation passes, and validation passes + based on this device's properites. """ if not emulator_noise_model: emulator_noise_model = create_device_noise_model(self.properties, self._arn) - self._emulator = Emulator(noise_model=emulator_noise_model, backend="braket_dm", name=self._name) + self._emulator = Emulator( + noise_model=emulator_noise_model, backend="braket_dm", name=self._name + ) self._emulator.add_pass(create_qubit_count_criterion(self.properties)) self._emulator.add_pass(create_gate_criterion(self.properties)) @@ -903,31 +929,39 @@ def validate( PulseSequence, AnalogHamiltonianSimulation, ], - ): + ) -> 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. + + Args: + + task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, + PulseSequence, AnalogHamiltonianSimulation]): The quantum program to emulate against + this AwsDevice device properties. + """ self.emulator.run_validation_passes(task_specification) return - - - def run_emulator_passes( - self, - task_specification: Union[ - Circuit, - Problem, - OpenQasmProgram, - BlackbirdProgram, - PulseSequence, - AnalogHamiltonianSimulation, - ], - apply_noise_model=True, - ): + + def run_emulator_passes[ + ProgramType + ](self, task_specification: ProgramType, apply_noise_model: bool = True) -> ProgramType: """ Runs all emulator passes and returns the modified program, which should be the same type as the input program. + + Args: + task_specification (ProgramType): The quantum program to emulate against + this AwsDevice device properties. + + apply_noise_model (bool): If true, apply a device specific noise model to the program + before returning. + + Returns: + ProgramType: A validated and compiled program that may be augmented with noise + operations to mimic noise on this device. """ if isinstance(task_specification, Circuit): task_specification = task_specification.copy() @@ -946,9 +980,26 @@ def emulate( ], shots: Optional[int] = None, inputs: Optional[dict[str, float]] = None, - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, ) -> QuantumTask: + """Emulate a quantum task specification on this quantum device emulator. + A quantum task can be a circuit or an annealing problem. Emulation + involves running all emulator passes on the input program before running + the program on the emulator's backend. + + Args: + task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, + PulseSequence, AnalogHamiltonianSimulation]): Specification of a quantum task + to run on device. + shots (Optional[int]): The number of times to run the quantum task on the device. + Default is `None`. + + inputs (Optional[dict[str, float]]): Inputs to be passed along with the + IR. If IR is an OpenQASM Program, the inputs will be updated with this value. + Not all devices and IR formats support inputs. Default: {}. + Returns: + QuantumTask: The QuantumTask tracking task execution on this device emulator. + """ if isinstance(task_specification, Circuit): task_specification = task_specification.copy() diff --git a/src/braket/aws/aws_emulator_helpers.py b/src/braket/aws/aws_emulator_helpers.py index 604ea4295..4f6019c51 100644 --- a/src/braket/aws/aws_emulator_helpers.py +++ b/src/braket/aws/aws_emulator_helpers.py @@ -4,15 +4,10 @@ from networkx import DiGraph -from braket.device_schema import ( - DeviceActionType, - DeviceCapabilities, - StandardizedGateModelQpuDeviceProperties, -) +from braket.device_schema import DeviceActionType, DeviceCapabilities from braket.device_schema.ionq import IonqDeviceCapabilities from braket.device_schema.iqm import IqmDeviceCapabilities from braket.device_schema.rigetti import RigettiDeviceCapabilities -from braket.emulators import Emulator from braket.emulators.emulator_passes import ( ConnectivityCriterion, GateConnectivityCriterion, @@ -22,14 +17,36 @@ def create_qubit_count_criterion(properties: DeviceCapabilities) -> QubitCountCriterion: + """ + Create a QubitCountCriterion 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 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. + """ qubit_count = properties.paradigm.qubitCount return QubitCountCriterion(qubit_count) def create_gate_criterion(properties: DeviceCapabilities) -> GateCriterion: supported_gates = properties.action[DeviceActionType.OPENQASM].supportedOperations - """TODO: Issue in IQM Garnet Supported Operations: Includes "startVerbatimBox" and "endVerbatimBox" instructions in supported operations, - which are braket specific pragmas. Filter out explicitly until they are removed from device properties.""" + """ + Create a GateCriterion 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 + verbatim circuits only use native gates. + """ if isinstance(properties, IqmDeviceCapabilities): try: @@ -47,6 +64,20 @@ def create_gate_criterion(properties: DeviceCapabilities) -> GateCriterion: def create_connectivity_criterion( properties: DeviceCapabilities, connectivity_graph: DiGraph ) -> ConnectivityCriterion: + """ + Creates a ConnectivityCriterion pass which validates that multi-qubit gates are applied to + connected qubits based on this device's connectivity graph. + + Args: + properties (DeviceCapabilities): QPU Device Capabilities object with a + QHP-specific schema. + + connectivity_graph (DiGraph): Connectivity graph for this device. + + Returns: + ConnectivityCriterion: An emulator pass that checks that a circuit only applies two-qubit + gates to connected qubits on the device. + """ connectivity_criterion = ConnectivityCriterion(connectivity_graph) return connectivity_criterion @@ -66,7 +97,7 @@ def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> Connect @singledispatch def create_gate_connectivity_criterion( - properties, connectivity_graph: DiGraph + properties: DeviceCapabilities, connectivity_graph: DiGraph ) -> GateConnectivityCriterion: raise NotImplementedError @@ -79,7 +110,7 @@ def _( Rigetti provides device capabilities using a standardized properties schema for gate devices. Rigetti provides both forwards and backwards edges for their undirected gate - connectivity graph, so no new needs to be introduced when creating a + connectivity graph, so no new needs to be introduced when creating a GateConnectivityCriterion object for a Rigetti QPU. """ gate_connectivity_graph = connectivity_graph.copy() @@ -102,8 +133,9 @@ def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> GateCon """ IQM provides device capabilities using a standardized properties schema for gate devices. - IQM provides only forward edges for their *undirected* gate connectivity graph, so back-edges must - be introduced when creating the GateConnectivityCriterion object for an IQM QPU. + IQM provides only forward edges for their *undirected* gate + connectivity graph, so back-edges must be introduced when creating the + GateConnectivityCriterion object for an IQM QPU. """ gate_connectivity_graph = connectivity_graph.copy() for edge in gate_connectivity_graph.edges: @@ -125,9 +157,10 @@ def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> GateCon @create_gate_connectivity_criterion.register(IonqDeviceCapabilities) def _(properties: IonqDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: """ - Qubits in IonQ's trapped ion devices are all fully connected with identical gate-pair capabilities. - Thus, IonQ does not expliclty provide a set of edges for gate connectivity between qubit pairs in - their trapped ion QPUs. We extrapolate gate connectivity across all possible qubit edge pairs. + 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 + gate connectivity between qubit pairs in their trapped ion QPUs. + We extrapolate gate connectivity across all possible qubit edge pairs. """ gate_connectivity_graph = connectivity_graph.copy() native_gates = get_qpu_gate_translation(properties, properties.paradigm.nativeGateSet) @@ -140,10 +173,12 @@ def _(properties: IonqDeviceCapabilities, connectivity_graph: DiGraph) -> GateCo def get_qpu_gate_translation( properties: DeviceCapabilities, gate_name: Union[str, Iterable[str]] ) -> Union[str, list[str]]: - """Returns the translated gate name(s) for a given QPU ARN and gate name(s). + """Returns the translated gate name(s) for a given QPU device capabilities schema type + and gate name(s). Args: - arn (str): The ARN of the QPU + properties (DeviceCapabilities): Device capabilities object based on a + device-specific schema. gate_name (Union[str, Iterable[str]]): The name(s) of the gate(s) Returns: @@ -156,11 +191,12 @@ def get_qpu_gate_translation( @singledispatch -def _get_qpu_gate_translation(properties, gate_name: str) -> str: +def _get_qpu_gate_translation(properties: DeviceCapabilities, gate_name: str) -> str: """Returns the translated gate name for a given QPU ARN and gate name. Args: - properties (str): QHP device properties type + properties (DeviceCapabilities): QPU Device Capabilities object with a + QHP-specific schema. gate_name (str): The name of the gate Returns: @@ -181,4 +217,4 @@ def _(properties: RigettiDeviceCapabilities, gate_name: str) -> str: @_get_qpu_gate_translation.register(IonqDeviceCapabilities) def _(properties: IonqDeviceCapabilities, gate_name: str) -> str: translations = {"GPI": "GPi", "GPI2": "GPi2"} - return translations.get(gate_name, gate_name) \ No newline at end of file + return translations.get(gate_name, gate_name) diff --git a/src/braket/aws/aws_noise_models.py b/src/braket/aws/aws_noise_models.py index 53d87c83d..a9591c83e 100644 --- a/src/braket/aws/aws_noise_models.py +++ b/src/braket/aws/aws_noise_models.py @@ -15,16 +15,11 @@ PhaseDamping, TwoQubitDepolarizing, ) -from braket.device_schema import ( - DeviceActionType, - DeviceCapabilities, - StandardizedGateModelQpuDeviceProperties, -) +from braket.device_schema import DeviceCapabilities from braket.device_schema.ionq import IonqDeviceCapabilities from braket.device_schema.iqm import IqmDeviceCapabilities from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.device_schema.standardized_gate_model_qpu_device_properties_v1 import ( - Fidelity1Q, GateFidelity2Q, OneQubitProperties, StandardizedGateModelQpuDeviceProperties, @@ -32,8 +27,8 @@ from braket.devices import Devices """ - The following gate duration values are not available through Braket device calibration data and must - be hardcoded. + The following gate duration values are not available through Braket device + calibration data and must be hardcoded. """ QPU_GATE_DURATIONS = { Devices.Rigetti.AspenM3: { @@ -58,12 +53,20 @@ class GateDeviceCalibrationData: single_qubit_specs: Dict[int, Dict[str, float]] two_qubit_edge_specs: Dict[Tuple[int, int], List[GateFidelity]] - def _validate_single_qubit_specs(self): + def _validate_single_qubit_specs(self) -> None: + """ + Checks single qubit specs and the input qubit labels are compatible with + one another. + """ for qubit in self.single_qubit_specs.keys(): if qubit not in self.qubit_labels: raise ValueError(f"Invalid qubit label {qubit}") - def _validate_two_qubit_specs(self): + def _validate_two_qubit_specs(self) -> None: + """ + Checks that the qubit edge specs and the input qubit labels are compatible + with one another. + """ for edge in self.two_qubit_edge_specs.keys(): if edge[0] not in self.qubit_labels or edge[1] not in self.qubit_labels: raise ValueError(f"Invalid qubit pair {edge}") @@ -74,20 +77,34 @@ def __post_init__(self): def create_device_noise_model(properties: DeviceCapabilities, arn: str) -> NoiseModel: + """ + Create a device-specific noise model using the calibration data provided + in the device properties object for a QPU. + + Args: + properties (DeviceCapabilities): Data structure containing + device properties and calibration data to be used when creating the + device noise model for an emulator. + + arn (str): Amazon Braket ARN for the QPU. + + Returns: + NoiseModel: Returns a NoiseModel object created using the device's + calibration data created from the device properties. + """ device_calibration_data = _setup_calibration_specs(properties, arn) noise_model = _setup_basic_noise_model_strategy(device_calibration_data) return noise_model @singledispatch -def _setup_calibration_specs(properties, arn: str) -> NoiseModel: +def _setup_calibration_specs(properties: DeviceCapabilities, arn: str) -> NoiseModel: raise NotImplementedError( f"A noise model cannot be created from device capabilities with \ type {type(properties)}." ) -# Rigetti and IonQ provide calibration data in a standardized data structure that can be parsed generally. @_setup_calibration_specs.register(RigettiDeviceCapabilities) @_setup_calibration_specs.register(IqmDeviceCapabilities) def _(properties: Union[RigettiDeviceCapabilities, IqmDeviceCapabilities], arn: str) -> NoiseModel: @@ -97,7 +114,7 @@ def _(properties: Union[RigettiDeviceCapabilities, IqmDeviceCapabilities], arn: single_qubit_gate_duration = gate_durations["single_qubit_gate_duration"] two_qubit_gate_duration = gate_durations["two_qubit_gate_duration"] - standardized_properties = properties.standardized + standardized_properties: StandardizedGateModelQpuDeviceProperties = properties.standardized one_qubit_properties = standardized_properties.oneQubitProperties qubit_labels = set(int(qubit) for qubit in one_qubit_properties.keys()) single_qubit_specs = { @@ -153,9 +170,10 @@ def _(properties: IonqDeviceCapabilities, arn: str) -> NoiseModel: gate = getattr(Gate, gate_name) if gate.fixed_qubit_count() != 2: """ - The noise model applies depolarizing noise associated with the individual qubits themselves (RB/sRB fidelities). - This is a choice of this particular model to generalize the implementation as not all QHPs provide - single-qubit gate fidelities. + The noise model applies depolarizing noise associated with the + individual qubits themselves (RB/sRB fidelities). This is a choice + of this particular model to generalize the implementation as not + all QHPs provide single-qubit gate fidelities. """ continue native_gate_fidelities.append(GateFidelity(gate, two_qubit_rb_fidelity)) @@ -229,7 +247,6 @@ def _setup_basic_noise_model_strategy( """ noise_model = NoiseModel() gate_duration_1Q = gate_calibration_data.single_qubit_gate_duration - gate_duration_2Q = gate_calibration_data.two_qubit_gate_duration for qubit, data in gate_calibration_data.single_qubit_specs.items(): # T1 dampening T1 = data["T1"] @@ -249,7 +266,7 @@ def _setup_basic_noise_model_strategy( if benchmark_fidelity: depolarizing_rate = 1 - benchmark_fidelity noise_model.add_noise(Depolarizing(depolarizing_rate), GateCriteria(qubits=qubit)) - + # 1 Qubit Readout Error readout_error_rate = 1 - data["READOUT"] noise_model.add_noise(BitFlip(readout_error_rate), ObservableCriteria(qubits=qubit)) diff --git a/src/braket/emulators/__init__.py b/src/braket/emulators/__init__.py index 4b9c350c8..60a33ea70 100644 --- a/src/braket/emulators/__init__.py +++ b/src/braket/emulators/__init__.py @@ -1 +1 @@ -from braket.emulators.emulator import Emulator +from braket.emulators.emulator import Emulator # noqa: F40 diff --git a/src/braket/emulators/emulator.py b/src/braket/emulators/emulator.py index 43332c834..cedb43a3e 100644 --- a/src/braket/emulators/emulator.py +++ b/src/braket/emulators/emulator.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -from abc import abstractmethod from typing import Any, Iterable, Optional, Union from braket.circuits import Circuit @@ -11,56 +10,71 @@ from braket.emulators.emulator_interface import EmulatorInterface from braket.emulators.emulator_passes import EmulatorPass, ProgramType from braket.ir.openqasm import Program as OpenQasmProgram -from braket.simulator import BraketSimulator from braket.tasks import QuantumTask from braket.tasks.quantum_task_batch import QuantumTaskBatch class Emulator(Device, EmulatorInterface): - """An emulator is a simulation device that more closely resembles the capabilities and constraints of a real - device or of a specific device model.""" + + DEFAULT_SIMULATOR_BACKEND = "default" + DEFAULT_NOISY_BACKEND = "braket_dm" + """An emulator is a simulation device that more closely resembles + the capabilities and constraints of a real device or of a specific device model.""" def __init__( self, - backend: Union[str, Device] = "default", + backend: str = "default", noise_model: Optional[NoiseModel] = None, emulator_passes: Iterable[EmulatorPass] = None, **kwargs, ): - Device.__init__( - self, - name=kwargs.get("name", "DeviceEmulator"), - status="AVAILABLE" - ) + Device.__init__(self, name=kwargs.get("name", "DeviceEmulator"), status="AVAILABLE") EmulatorInterface.__init__(self, emulator_passes) self._noise_model = noise_model + + backend_name = self._get_local_simulator_backend(backend, noise_model) + self._backend = LocalSimulator(backend=backend_name, noise_model=noise_model) + + def _get_local_simulator_backend(self, backend: str, noise_model: Optional[NoiseModel] = None): if noise_model and backend == "default": logging.info( - "Setting LocalSimulator backend to use 'braket_dm' because a NoiseModel was provided." + "Setting LocalSimulator backend to use 'braket_dm' \ + because a NoiseModel was provided." ) - backend = "braket_dm" - - self._backend = LocalSimulator(backend=backend, noise_model=noise_model) - + return Emulator.DEFAULT_NOISY_BACKEND + return Emulator.DEFAULT_SIMULATOR_BACKEND + def run( self, task_specification: Union[ Circuit, OpenQasmProgram, ], - shots: int = 0, + shots: Optional[int] = 0, inputs: Optional[dict[str, float]] = None, - dry_run=False, *args: Any, **kwargs: Any, ) -> QuantumTask: + """Emulate a quantum task specification on this quantum device emulator. + A quantum task can be a circuit or an annealing problem. Emulation + involves running all emulator passes on the input program before running + the program on the emulator's backend. + + Args: + task_specification (Union[Circuit, OpenQasmProgram]): Specification of a quantum task + to run on device. + shots (Optional[int]): The number of times to run the quantum task on the device. + Default is `None`. + inputs (Optional[dict[str, float]]): Inputs to be passed along with the + IR. If IR is an OpenQASM Program, the inputs will be updated with this value. + Not all devices and IR formats support inputs. Default: {}. + *args (Any): Arbitrary arguments. + **kwargs (Any): Arbitrary keyword arguments. + + Returns: + QuantumTask: The QuantumTask tracking task execution on this device emulator. """ - This method validates the input program against the emulator's passes and applies any provided noise model before - running the circuit. - """ - task_specification = self.run_program_passes( - task_specification, apply_noise_model=False - ) + task_specification = self.run_program_passes(task_specification, apply_noise_model=False) # Don't apply noise model as the local simulator will automatically apply it. return self._backend.run(task_specification, shots, inputs, *args, **kwargs) @@ -84,30 +98,75 @@ def run_batch( # noqa: C901 raise NotImplementedError("Emulator.run_batch() is not implemented yet.") @property - def noise_model(self): + def noise_model(self) -> NoiseModel: + """ + An emulator may be defined with a quantum noise model which mimics the noise + on a physical device. A quantum noise model can be defined using the + NoiseModel class. The noise model is applied to Braket Circuits before + running them on the emulator backend. + + Returns: + NoiseModel: This emulator's noise model. + """ return self._noise_model @noise_model.setter - def noise_model(self, noise_model: NoiseModel): + def noise_model(self, noise_model: NoiseModel) -> None: + """ + Setter method for the Emulator noise_model property. Re-instantiates + the backend with the new NoiseModel object. + + Args: + noise_model (NoiseModel): The new noise model. + """ self._noise_model = noise_model self._backend = LocalSimulator(backend="braket_dm", noise_model=noise_model) def run_program_passes( - self, task_specification: ProgramType, apply_noise_model=True + self, task_specification: ProgramType, apply_noise_model: bool = True ) -> ProgramType: + """ + 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. + + Args: + task_specification (ProgramType): The input program to validate and + compile based on this emulator's EmulatorPasses + apply_noise_model (bool): If true, apply this emulator's noise model + to the compiled program before returning the final program. + + Returns: + ProgramType: A compiled program with a noise model applied, if one + exists for this emulator and apply_noise_model is true. + """ try: program = super().run_program_passes(task_specification) - if apply_noise_model: + if apply_noise_model and self.noise_model: return self._noise_model.apply(program) return program except Exception as e: self._raise_exception(e) - + def run_validation_passes(self, task_specification: ProgramType) -> None: + """ + Runs only EmulatorPasses that are EmulatorCriterion, i.e. all non-modifying + validation passes on the input program. + + Args: + task_specification (ProgramType): The input program to validate. + """ try: super().run_validation_passes(task_specification) except Exception as e: self._raise_exception(e) - - def _raise_exception(self, exception: Exception): - raise type(exception)(str(exception) + f" ({self._name})") \ No newline at end of file + + def _raise_exception(self, exception: Exception) -> None: + """ + Wrapper for exceptions, appends the emulator's name to the exception + note. + + Args: + exception (Exception): The exception to modify and raise. + """ + raise type(exception)(str(exception) + f" ({self._name})") diff --git a/src/braket/emulators/emulator_interface.py b/src/braket/emulators/emulator_interface.py index ad5f85731..2f0296835 100644 --- a/src/braket/emulators/emulator_interface.py +++ b/src/braket/emulators/emulator_interface.py @@ -1,8 +1,9 @@ -from abc import ABC, abstractmethod -from collections.abc import Iterator +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 import EmulatorPass from braket.emulators.emulator_passes.criteria import EmulatorCriterion @@ -12,13 +13,14 @@ def __init__(self, emulator_passes: Iterable[EmulatorPass] = None): def run_program_passes[ProgramType](self, task_specification: ProgramType) -> ProgramType: """ - This method passes the input program through the EmulatorPasses 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). + This method passes the input program through the EmulatorPasses 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). Args: task_specification (ProgramType): The program to run the emulator passes on. Returns: - (ProgramType): A "compiled" program of the same type as the input. + ProgramType: A "compiled" program of the same type as the input. """ for emulator_pass in self._emulator_passes: @@ -27,16 +29,35 @@ def run_program_passes[ProgramType](self, task_specification: ProgramType) -> Pr def run_validation_passes[ProgramType](self, task_specification: ProgramType) -> None: """ - This method passes the input program through EmulatorPasses that perform only validation, without modifying the input program. + This method passes the input program through EmulatorPasses 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): emulator_pass(task_specification) - def add_pass(self, emulator_pass: Union[Iterable[EmulatorPass], EmulatorPass]) -> None: + def add_pass(self, emulator_pass: Union[Iterable[EmulatorPass], EmulatorPass]) -> EmulatorInterface: + """ + Append a new EmulatorPass or a list of EmulatorPass objects. + + Args: + emulator_pass (Union[Iterable[EmulatorPass], EmulatorPass]): Either a + single EmulatorPass object or a list of EmulatorPass objects that + will be used in validation and program compilation passes by this + emulator. + + Returns: + EmulatorInterface: Returns an updated self. + + """ if isinstance(emulator_pass, Iterable): self._emulator_passes.extend(emulator_pass) elif isinstance(emulator_pass, EmulatorPass): self._emulator_passes.append(emulator_pass) else: raise TypeError("emulator_pass must be an EmulatorPass or an iterable of EmulatorPass") + return self \ No newline at end of file diff --git a/src/braket/emulators/emulator_passes/__init__.py b/src/braket/emulators/emulator_passes/__init__.py index 2f917091f..0de674ab4 100644 --- a/src/braket/emulators/emulator_passes/__init__.py +++ b/src/braket/emulators/emulator_passes/__init__.py @@ -1,8 +1,8 @@ -from braket.emulators.emulator_passes.criteria import ( +from braket.emulators.emulator_passes.criteria import ( # noqa: F401 ConnectivityCriterion, EmulatorCriterion, GateConnectivityCriterion, GateCriterion, QubitCountCriterion, ) -from braket.emulators.emulator_passes.emulator_pass import EmulatorPass, ProgramType \ No newline at end of file +from braket.emulators.emulator_passes.emulator_pass import EmulatorPass, ProgramType # noqa: F401 diff --git a/src/braket/emulators/emulator_passes/criteria/__init__.py b/src/braket/emulators/emulator_passes/criteria/__init__.py index c90db322a..081defd8c 100644 --- a/src/braket/emulators/emulator_passes/criteria/__init__.py +++ b/src/braket/emulators/emulator_passes/criteria/__init__.py @@ -1,7 +1,13 @@ -from braket.emulators.emulator_passes.criteria.connectivity_criterion import ConnectivityCriterion -from braket.emulators.emulator_passes.criteria.emulator_criterion import EmulatorCriterion -from braket.emulators.emulator_passes.criteria.gate_connectivity_criterion import ( +from braket.emulators.emulator_passes.criteria.connectivity_criterion import ( # noqa: F401 + ConnectivityCriterion, +) +from braket.emulators.emulator_passes.criteria.emulator_criterion import ( # noqa: F401 + EmulatorCriterion, +) +from braket.emulators.emulator_passes.criteria.gate_connectivity_criterion import ( # noqa: F401 GateConnectivityCriterion, ) -from braket.emulators.emulator_passes.criteria.gate_criterion import GateCriterion -from braket.emulators.emulator_passes.criteria.qubit_count_criterion import QubitCountCriterion +from braket.emulators.emulator_passes.criteria.gate_criterion import GateCriterion # noqa: F401 +from braket.emulators.emulator_passes.criteria.qubit_count_criterion import ( # noqa: F401 + QubitCountCriterion, +) diff --git a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py index d50f18d17..d61468987 100644 --- a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py @@ -35,7 +35,7 @@ def __init__( fully_connected=False, num_qubits: int = None, qubit_labels: Union[Iterable[int], QubitSet] = None, - directed: bool = False, + directed: bool = True, ): if not (connectivity_graph or fully_connected): raise ValueError( @@ -43,9 +43,10 @@ def __init__( ) if fully_connected: - if not (num_qubits or qubit_labels) or (num_qubits and qubit_labels): + if not((num_qubits is None) ^ (qubit_labels is None)): raise ValueError( - "Either num_qubits or qubit_labels (NOT both) must be provided if fully_connected is True." + "Either num_qubits or qubit_labels (NOT both) must be \ + provided if fully_connected is True." ) self._connectivity_graph = complete_graph( num_qubits if num_qubits else qubit_labels, create_using=DiGraph() @@ -57,7 +58,8 @@ def __init__( ) except Exception as e: raise ValueError( - f"connectivity_graph must be a valid DiGraph or a dictionary mapping integers (nodes) to a list of integers (adjancency lists): {e}" + f"connectivity_graph must be a valid DiGraph or a dictionary\ + mapping integers (nodes) to a list of integers (adjancency lists): {e}" ) else: self._connectivity_graph = connectivity_graph @@ -65,13 +67,21 @@ def __init__( if not directed: for edge in self._connectivity_graph.edges: self._connectivity_graph.add_edge(edge[1], edge[0]) + else: + print("here") def validate(self, circuit: Circuit) -> None: """ Verifies that any verbatim box in a circuit is runnable with respect to the - device connectivity definied by this criteria. + device connectivity definied by this criterion. If any sub-circuit of the + input circuit is verbatim, we validate the connectivity of all gate operations + in the circuit. + + Args: + circuit (Circuit): The Braket circuit whose gate operations to + validate. """ - # If any of the instructions are in verbatim mode, all qubit references + # If any of the instructions are in verbatim mode, all qubit references # must point to hardware qubits. Otherwise, this circuit need not be validated. if not any( [ @@ -90,18 +100,31 @@ def validate(self, circuit: Circuit) -> None: else: # just check that the target qubit exists in the connectivity graph target_qubit = instruction.target[0] - if not target_qubit in self._connectivity_graph: + if target_qubit not in self._connectivity_graph: raise ValueError( f"Qubit {target_qubit} does not exist in the device topology." ) - def validate_instruction_connectivity(self, control_qubits: QubitSet, target_qubits: QubitSet): + def validate_instruction_connectivity( + self, control_qubits: QubitSet, target_qubits: QubitSet + ) -> None: + """ + Checks if a two-qubit instruction is valid based on this criterion's connectivity + graph. + + Args: + control_qubits (QubitSet): The control qubits used in this multi-qubit + operation. + target_qubits (QubitSet): The target qubits of this operation. For many gates, + both the control and target are stored in "target_qubits", so we may + see target_qubits have length 2. + """ # Create edges between each of the target qubits gate_connectivity_graph = DiGraph() # Create an edge from each control bit to each target qubit if len(control_qubits) == 1 and len(target_qubits) == 1: gate_connectivity_graph.add_edge(control_qubits[0], target_qubits[0]) - elif len(target_qubits) == 2: + elif len(control_qubits) == 0 and len(target_qubits) == 2: gate_connectivity_graph.add_edges_from( [(target_qubits[0], target_qubits[1]), (target_qubits[1], target_qubits[0])] ) diff --git a/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py b/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py index acb45caa7..23cad5ff0 100644 --- a/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py @@ -1,28 +1,25 @@ from __future__ import annotations from abc import abstractmethod -from typing import Dict -from braket.circuits import ( - Circuit, -) +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: """ + 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. - - Returns: - returns nothing if the circuit is valid; otherwise, the appropriate error is raised. """ raise NotImplementedError - def run[ProgramType](self, program: ProgramType) -> ProgramType: + def run(self, program: ProgramType) -> ProgramType: self.validate(program) return program diff --git a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py index 774253eb6..b2770c882 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py @@ -1,7 +1,7 @@ -from dataclasses import dataclass -from typing import Any, Dict, Iterable, Set, Tuple, Union +from typing import Any, Dict, Iterable, Tuple, Union from networkx import DiGraph +from networkx.utils import graphs_equal from braket.circuits.circuit import Circuit from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox @@ -44,14 +44,19 @@ def __init__( ) else: raise TypeError( - "gate_connectivity_graph must either be a dictionary of edges mapped to supported gates lists, or a DiGraph with supported \ - gates provided as edge attributs. " + "Gate_connectivity_graph must either be a dictionary of edges mapped to \ +supported gates lists, or a DiGraph with supported gates \ +provided as edge attributes." ) def validate(self, circuit: Circuit) -> None: """ - Verifies that any multiqubit gates used within a verbatim box are supported by the devices gate - connectivity defined by this criteria. + Verifies that any multiqubit gates used within a verbatim box are supported + by the devices gate connectivity defined by this criteria. + + Args: + circuit (Circuit): The circuit whose gate instructions need to be validated + against this criterion's gate connectivity graph. """ for idx in range(len(circuit.instructions)): instruction = circuit.instructions[idx] @@ -71,23 +76,33 @@ def validate(self, circuit: Circuit) -> None: else: # just check that the target qubit exists in the connectivity graph target_qubit = instruction.target[0] - if not target_qubit in self._gate_connectivity_graph: + if target_qubit not in self._gate_connectivity_graph: raise ValueError( f"Qubit {target_qubit} does not exist in the device topology." ) idx += 1 - if not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): + 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( self, gate_name: str, control_qubits: QubitSet, target_qubits: QubitSet - ): + ) -> None: + """ + Checks if a specific is able to be applied to the control and target qubits based + on this criterion's gate connectivity graph. + + Args: + gate_name (str): The name of the gate being applied. + control_qubits (QubitSet): The set of control qubits used by this gate operation. + target_qubits (QubitSet): The set of target qubits used by this gate operation. + """ # Create edges between each of the target qubits if len(control_qubits) == 1 and len(target_qubits) == 1: e = (control_qubits[0], target_qubits[0]) - elif len(target_qubits) == 2: + elif len(control_qubits) == 0 and len(target_qubits) == 2: e = (target_qubits[0], target_qubits[1]) else: raise ValueError("Unrecognized qubit targetting setup for a 2 qubit gate.") @@ -104,11 +119,5 @@ def validate_instruction_connectivity( def __eq__(self, other: EmulatorCriterion) -> bool: return ( isinstance(other, GateConnectivityCriterion) - and all( - [ - self._gate_connectivity_graph[edge] == other._gate_connectivity_graph.get(edge) - for edge in self._gate_connectivity_graph.keys() - ] - ) - and len(self._gate_connectivity_graph) == len(other._gate_connectivity_graph) + and graphs_equal(self._gate_connectivity_graph, other._gate_connectivity_graph) ) diff --git a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py index 8183dcbbe..ff1434a23 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py @@ -26,6 +26,14 @@ def __init__(self, supported_gates: Iterator[str] = [], native_gates: Iterator[s raise ValueError(f"Input {str(e)} is not a valid Braket gate name.") def validate(self, circuit: Circuit) -> None: + """ + Checks that all non-verbatim gates used in the circuit are in this criterion's + supported gate set and that all verbatim gates used in the circuit are in this + criterion's native gate set. + + Args: + circuit (Circuit): The Braket circuit whose gates to validate. + """ idx = 0 while idx < len(circuit.instructions): instruction = circuit.instructions[idx] @@ -42,7 +50,8 @@ def validate(self, circuit: Circuit) -> None: f"Gate {gate.name} is not a native gate supported by this device." ) idx += 1 - if not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): + 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.") elif isinstance(instruction.operator, Gate): gate = instruction.operator diff --git a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py index c69033a2d..797f961f1 100644 --- a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py @@ -13,10 +13,19 @@ def __init__(self, qubit_count: int): raise ValueError(f"qubit_count ({qubit_count}) must be a positive integer.") self._qubit_count = qubit_count - def validate(self, circuit: Circuit) -> Circuit: + def validate(self, circuit: Circuit) -> None: + """ + Checks that the number of qubits used in this circuit does not exceed this + criterion's qubit_count max. + + Args: + circuit (Circuit): The Braket circuit whose qubit count to validate. + + """ if circuit.qubit_count > self._qubit_count: raise ValueError( - f"Circuit must use at most {self._qubit_count} qubits, but uses {circuit.qubit_count} qubits." + f"Circuit must use at most {self._qubit_count} qubits, \ +but uses {circuit.qubit_count} qubits." ) def __eq__(self, other: EmulatorCriterion) -> bool: diff --git a/src/braket/emulators/emulator_passes/emulator_pass.py b/src/braket/emulators/emulator_passes/emulator_pass.py index b86d0025d..2c2dae8cf 100644 --- a/src/braket/emulators/emulator_passes/emulator_pass.py +++ b/src/braket/emulators/emulator_passes/emulator_pass.py @@ -1,12 +1,12 @@ from abc import ABC, abstractmethod -from typing import Any, TypeVar +from typing import TypeVar ProgramType = TypeVar("ProgramType") class EmulatorPass(ABC): @abstractmethod - def run[ProgramType](self, program: ProgramType) -> ProgramType: + def run(self, program: ProgramType) -> ProgramType: """Runs the emulator pass on the provided program. Args: program (ProgramType): The program to run the emulator pass on. @@ -15,5 +15,5 @@ def run[ProgramType](self, program: ProgramType) -> ProgramType: """ raise NotImplementedError - def __call__[ProgramType](self, program: ProgramType) -> ProgramType: + def __call__(self, program: ProgramType) -> ProgramType: return self.run(program) diff --git a/test/unit_tests/braket/emulation/pytket_translator/test_tket_to_qasm3.py b/test/unit_tests/braket/emulation/pytket_translator/test_tket_to_qasm3.py deleted file mode 100644 index 8781924e1..000000000 --- a/test/unit_tests/braket/emulation/pytket_translator/test_tket_to_qasm3.py +++ /dev/null @@ -1,117 +0,0 @@ -import re - -import pytest -from pytket.circuit import Bit, Circuit, OpType -from sympy import Symbol - -from braket.emulators.pytket_translator import tket_to_qasm3 - - -@pytest.fixture -def simple_bell_circuit(): - circ = Circuit(3) - for i in range(3): - circ.add_bit(Bit("c", i)) - circ.Rx(0.5, 0) - circ.H(0) - circ.CX(1, 2) - circ.CX(0, 1) - circ.Measure(0, 0) - circ.Measure(1, 1) - circ.Measure(2, 2) - return circ - - -@pytest.fixture -def simple_parametric_circuit(): - circ = Circuit(1) - circ.add_bit(Bit("c", 0)) - circ.Rx(Symbol("theta"), 0) - circ.Measure(0, 0) - return circ - - -def test_simple_bell(simple_bell_circuit): - expected = """ -OPENQASM 3.0; -bit[3] c; -rx(0.5*pi) $0; -cnot $1,$2; -h $0; -cnot $0,$1; -c[2] = measure $2; -c[0] = measure $0; -c[1] = measure $1; -""".strip() - qasm_result = tket_to_qasm3(simple_bell_circuit).strip() - assert qasm_result == expected - - -def test_parametric_circuit(simple_parametric_circuit): - expected = """ -OPENQASM 3.0; -input float theta; -bit[1] c; -rx(pi*theta) $0; -c[0] = measure $0; -""".strip() - qasm_result = tket_to_qasm3(simple_parametric_circuit).strip() - assert expected == qasm_result - - -def test_empty_circuit(): - circ = Circuit(0) - expected = """OPENQASM 3.0; -""" - qasm_result = tket_to_qasm3(circ) - assert expected == qasm_result - - -def test_multiple_parameter_circuits(): - theta = Symbol("theta") - phi = Symbol("phi") - circ = Circuit(1) - circ.add_bit(Bit("c", 0)) - circ.Rx(theta, 0) - circ.Rx(theta + phi, 0) - expected = """ -OPENQASM 3.0; -input float theta; -input float phi; -bit[1] c; -rx(pi*theta) $0; -rx(pi*(phi + theta)) $0; -""".strip() - qasm_result = tket_to_qasm3(circ).strip() - assert expected == qasm_result - - -def test_invalid_operation_on_measured_targets(): - circ = Circuit(1) - circ.add_bit(Bit("c", 0)) - circ.Measure(0, 0) - circ.X(0) - - error_message = re.escape( - "Circuit QASM cannot be generated as circuit contains midcircuit\ - measurements on qubit: q[0]" - ) - with pytest.raises(ValueError, match=error_message): - tket_to_qasm3(circ) - - -def test_translation_with_gate_overrides(simple_bell_circuit): - gate_override = {OpType.CX: "CNOT", OpType.H: "HADAMARD"} - expected = """ -OPENQASM 3.0; -bit[3] c; -rx(0.5*pi) $0; -CNOT $1,$2; -HADAMARD $0; -CNOT $0,$1; -c[2] = measure $2; -c[0] = measure $0; -c[1] = measure $1; -""".strip() - qasm_result = tket_to_qasm3(simple_bell_circuit, gate_overrides=gate_override).strip() - assert qasm_result == expected diff --git a/test/unit_tests/braket/emulation/test_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_connectivity_criterion.py index 7b018ef03..13fc576a9 100644 --- a/test/unit_tests/braket/emulation/test_connectivity_criterion.py +++ b/test/unit_tests/braket/emulation/test_connectivity_criterion.py @@ -18,7 +18,7 @@ def basic_noncontig_qubits_2_node_complete_graph(): @pytest.fixture -def five_node_digraph(): +def six_node_digraph(): edge_set = {0: [1, 3], 1: [0, 2, 10], 2: [1, 3, 11], 10: [1, 11], 11: [2, 10]} return nx.from_dict_of_lists(edge_set, create_using=nx.DiGraph()) @@ -116,9 +116,9 @@ def test_complete_graph_instantation_with_qubit_labels(): .add_verbatim_box(Circuit().swap(2, 10)), ], ) -def test_invalid_2_qubit_gates(five_node_digraph, circuit): +def test_invalid_2_qubit_gates(six_node_digraph, circuit): with pytest.raises(ValueError): - ConnectivityCriterion(five_node_digraph).validate(circuit) + ConnectivityCriterion(six_node_digraph).validate(circuit) @pytest.mark.parametrize( @@ -132,25 +132,31 @@ def test_invalid_2_qubit_gates(five_node_digraph, circuit): .add_verbatim_box(Circuit().h(111)), ], ) -def test_invalid_1_qubit_gates(five_node_digraph, circuit): +def test_invalid_1_qubit_gates(six_node_digraph, circuit): with pytest.raises(ValueError): - ConnectivityCriterion(five_node_digraph).validate(circuit) + ConnectivityCriterion(six_node_digraph).validate(circuit) -def test_equality_graph_created_with_dict(five_node_digraph): +def test_equality_graph_created_with_dict(six_node_digraph): graph = {0: [1, 3], 1: [0, 2, 10], 2: [1, 3, 11], 10: [1, 11], 11: [2, 10]} - criteria_from_digraph = ConnectivityCriterion(five_node_digraph) + criteria_from_digraph = ConnectivityCriterion(six_node_digraph) criteria_from_dict = ConnectivityCriterion(graph) assert criteria_from_dict == criteria_from_digraph @pytest.mark.parametrize( - "connectivity_graph, fully_connected, num_qubits, qubit_labels", - [(None, True, None, None), (nx.DiGraph(), True, None, None), (None, True, 5, [0, 1])], + "connectivity_graph, fully_connected, num_qubits, qubit_labels, directed", + [ + (None, True, None, None, False), + (nx.DiGraph(), True, None, None, False), + (None, True, 5, [0, 1], False), + (None, False, None, None, False), + (nx.from_edgelist([(0, 1)], create_using=nx.Graph()), False, None, None, False) + ], ) -def test_invalid_constructors(connectivity_graph, fully_connected, num_qubits, qubit_labels): +def test_invalid_constructors(connectivity_graph, fully_connected, num_qubits, qubit_labels, directed): with pytest.raises(ValueError): - ConnectivityCriterion(connectivity_graph, fully_connected, num_qubits, qubit_labels) + ConnectivityCriterion(connectivity_graph, fully_connected, num_qubits, qubit_labels, directed) @pytest.mark.parametrize( @@ -180,3 +186,32 @@ def test_undirected_graph_construction(representation): ) cc = ConnectivityCriterion(representation, directed=False) assert graphs_equal(cc._connectivity_graph, expected_digraph) + + + + + + +# @pytest.fixture +# def six_node_digraph(): +# edge_set = {0: [1, 3], 1: [0, 2, 10], 2: [1, 3, 11], 10: [1, 11], 11: [2, 10]} +# return nx.from_dict_of_lists(edge_set, create_using=nx.DiGraph()) + + +@pytest.mark.parametrize( + "controls,targets,is_valid", + [ + ([0], [1], True), + ([], [0, 1], True), + ([3], [0], True), + ([0, 2], [], False), + ([0], [1, 2], False), + ] +) +def test_validate_instruction_method(controls, targets, is_valid, six_node_digraph): + gcc = ConnectivityCriterion(six_node_digraph, directed=False) + if is_valid: + gcc.validate_instruction_connectivity(controls, targets) + else: + with pytest.raises(ValueError): + gcc.validate_instruction_connectivity(controls, targets) \ No newline at end of file diff --git a/test/unit_tests/braket/emulation/test_emulator.py b/test/unit_tests/braket/emulation/test_emulator.py new file mode 100644 index 000000000..34600d7cb --- /dev/null +++ b/test/unit_tests/braket/emulation/test_emulator.py @@ -0,0 +1,170 @@ +import re +import pytest +from braket.emulators import Emulator +from braket.circuits import Circuit, Gate +from braket.circuits.noise_model import ( + NoiseModel, + GateCriteria +) + +from braket.circuits.noises import BitFlip +from braket.devices import local_simulator +from braket.default_simulator import StateVectorSimulator, DensityMatrixSimulator +from braket.emulators.emulator_passes import ( + EmulatorPass, + QubitCountCriterion, + GateCriterion, + ProgramType +) +from unittest.mock import Mock +import numpy as np + + +class AlwaysFailPass(EmulatorPass): + def run(self, program: ProgramType): + raise ValueError("This pass always raises an error.") + +mock_circuit_entry = Mock() +mock_circuit_dm_entry = Mock() +mock_circuit_entry.load.return_value = StateVectorSimulator +mock_circuit_dm_entry.load.return_value = DensityMatrixSimulator +local_simulator._simulator_devices.update({ + "default": mock_circuit_entry, + "braket_dm": mock_circuit_dm_entry +}) + +@pytest.fixture +def basic_emulator(): + qubit_count_criterion = QubitCountCriterion(4) + return Emulator(emulator_passes=[qubit_count_criterion]) + + +def test_empty_emulator_validation(): + emulator = Emulator() + circuit = Circuit().h(0).cnot(0, 1) + emulator.run_validation_passes(circuit) + + +def test_basic_emulator(basic_emulator): + """ + Should not error out when passed a valid circuit. + """ + circuit = Circuit().cnot(0, 1) + circuit = basic_emulator.run_program_passes(circuit) + assert circuit == circuit + + +def test_basic_invalidate(basic_emulator): + """ + Emulator should raise an error thrown by the QubitCountCriterion. + """ + circuit = Circuit().x(range(6)) + match_string = re.escape(f"Circuit must use at most 4 qubits, \ +but uses {circuit.qubit_count} qubits. (DeviceEmulator)") + with pytest.raises(ValueError, match=match_string): + basic_emulator.run_program_passes(circuit) + + +def test_add_pass_single(): + emulator = Emulator() + qubit_count_criterion = QubitCountCriterion(4) + emulator.add_pass(qubit_count_criterion) + + assert emulator._emulator_passes == [qubit_count_criterion] + +def test_bad_add_pass(): + emulator = Emulator() + with pytest.raises(TypeError): + emulator.add_pass(None) + +def test_add_pass_multiple(): + native_gate_criterion = GateCriterion(native_gates=["CZ", "PRx"]) + emulator = Emulator(emulator_passes=[native_gate_criterion]) + qubit_count_criterion = QubitCountCriterion(4) + gate_criterion = GateCriterion(supported_gates=["H", "CNot"]) + + emulator.add_pass([qubit_count_criterion, gate_criterion]) + assert emulator._emulator_passes == \ + [native_gate_criterion, qubit_count_criterion, gate_criterion] + +def test_use_correct_backend_if_noise_model(): + noise_model = NoiseModel() + emulator = Emulator(noise_model=noise_model) + assert emulator._backend.name == "DensityMatrixSimulator" + +def test_update_noise_model(): + emulator = Emulator() + assert emulator._backend.name == "StateVectorSimulator" + noise_model = NoiseModel() + noise_model.add_noise( + BitFlip(0.1), GateCriteria(Gate.H()) + ) + + emulator.noise_model = noise_model + assert emulator._backend.name == "DensityMatrixSimulator" + assert emulator._backend._noise_model == noise_model + assert emulator.noise_model == noise_model + + +def test_validation_only_pass(): + qubit_count_criterion = QubitCountCriterion(4) + bad_pass = AlwaysFailPass() + emulator = Emulator(emulator_passes=[bad_pass, qubit_count_criterion]) + + circuit = Circuit().h(range(5)) + match_string = re.escape(f"Circuit must use at most 4 qubits, \ +but uses {circuit.qubit_count} qubits. (DeviceEmulator)") + with pytest.raises(ValueError, match=match_string): + emulator.run_validation_passes(circuit) + + +def test_apply_noise_model(): + noise_model = NoiseModel() + noise_model.add_noise(BitFlip(0.1), GateCriteria(Gate.H)) + emulator = Emulator(noise_model=noise_model) + + circuit = Circuit().h(0) + circuit = emulator.run_program_passes(circuit) + + noisy_circuit = Circuit().h(0).apply_gate_noise(BitFlip(0.1), Gate.H) + assert circuit == noisy_circuit + + circuit = Circuit().h(0) + circuit = emulator.run_program_passes(circuit, apply_noise_model=False) + + target_circ = Circuit().h(0) + assert circuit == target_circ + +def test_noiseless_run(): + qubit_count_criterion = QubitCountCriterion(4) + gate_criterion = GateCriterion(supported_gates=["H"]) + emulator = Emulator(emulator_passes=[qubit_count_criterion, gate_criterion]) + circuit = Circuit().h(0).state_vector() + + + result = emulator.run(circuit).result() + state_vector = result.result_types[0].value + target_state_vector = np.array([1/np.sqrt(2) + 0j, 1/np.sqrt(2) + 0j]) + assert all(np.isclose(state_vector, target_state_vector)) + + +def test_noisy_run(): + noise_model = NoiseModel() + noise_model.add_noise(BitFlip(0.1), GateCriteria(Gate.H)) + + qubit_count_criterion = QubitCountCriterion(4) + gate_criterion = GateCriterion(supported_gates=["H"]) + emulator = Emulator(emulator_passes=[qubit_count_criterion, gate_criterion],\ + noise_model=noise_model) + + circuit = Circuit().h(0) + open_qasm_source = """OPENQASM 3.0; +bit[1] b; +qubit[1] q; +h q[0]; +#pragma braket noise bit_flip(0.1) q[0] +b[0] = measure q[0];""".strip() + + result = emulator.run(circuit, shots=1).result() + emulation_source = result.additional_metadata.action.source.strip() + assert emulation_source == open_qasm_source \ No newline at end of file diff --git a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py index a6fea6578..716620674 100644 --- a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py +++ b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py @@ -3,9 +3,12 @@ import pytest from networkx.utils import graphs_equal -from braket.circuits import Circuit +from braket.circuits import Circuit, Gate from braket.emulators.emulator_passes.criteria import GateConnectivityCriterion - +from braket.circuits.noises import BitFlip +from braket.circuits.noise_model import GateCriteria +from braket.circuits.compiler_directives import StartVerbatimBox +from braket.circuits import Instruction @pytest.fixture def basic_4_node_graph(): @@ -58,6 +61,9 @@ def basic_4_node_graph_as_dict(): .add_verbatim_box(Circuit().cnot(0, 1).cz(0, 1)) .cnot(0, 2) .swap(4, 6), + Circuit().add_verbatim_box( + Circuit().h(0).apply_gate_noise(BitFlip(0.1), target_gates=Gate.H) + ) ], ) def test_valid_basic_contiguous_circuits(basic_4_node_graph, circuit): @@ -151,6 +157,7 @@ def test_undirected_graph_construction_from_dict(): """ dict_representation = { (0, 1): ["CNot", "CZ"], + (1, 0): ["CZ", "XX"], (1, 2): ["Swap", "CNot", "YY"], (0, 2): ["XX", "XY", "CNot", "CZ"], (2, 5): ["XX", "XY", "CNot", "CZ"], @@ -162,7 +169,7 @@ def test_undirected_graph_construction_from_dict(): (1, 2, {"supported_gates": ["Swap", "CNot", "YY"]}), (0, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), (2, 5, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), - (1, 0, {"supported_gates": ["CNot", "CZ"]}), + (1, 0, {"supported_gates": ["CZ", "XX"]}), (2, 1, {"supported_gates": ["Swap", "CNot", "YY"]}), (2, 0, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), (5, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), @@ -171,6 +178,49 @@ def test_undirected_graph_construction_from_dict(): gcc = GateConnectivityCriterion(dict_representation, directed=False) assert graphs_equal(gcc._gate_connectivity_graph, digraph_representation) +@pytest.mark.parametrize( + "edges", + [ + [ + (0, 1, {"supported_gates": ["CNot, CZ"]}) + ], + [ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 0, {"supported_gates": ["CNot", "CZ"]}) + ], + [ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 2, {"supported_gates": ["CNot"]}), + (2, 3, {"supported_gates": ["CZ"]}), + (2, 1, {"supported_gates": ["CNot", "CZ", "XX"]}) + ], + [ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 2, {"supported_gates": ["CNot", "CZ"]}), + (4, 2, {"supported_gates": ["CNot", "CZ"]}), + (3, 2, {"supported_gates": ["CNot", "CZ"]}) + ] + ] +) +def test_undirected_graph_from_digraph(edges): + """ + Check that undirected topologies created from a digraph correctly add all possible + back edges to the criterion's connectivity graph. + """ + directed_graph = nx.DiGraph() + directed_graph.add_edges_from(edges) + undirected_graph = directed_graph.copy() + + for edge in edges: + if (edge[1], edge[0]) not in undirected_graph.edges: + undirected_graph.add_edges_from([(edge[1], edge[0], edge[2])]) + + gcc = GateConnectivityCriterion(directed_graph, directed=False) + assert graphs_equal(gcc._gate_connectivity_graph, undirected_graph) + + gcc_other = GateConnectivityCriterion(undirected_graph) + assert gcc_other == gcc + @pytest.mark.parametrize( "representation", @@ -213,9 +263,34 @@ 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): with pytest.raises(ValueError): gate_connectivity_criterion = GateConnectivityCriterion(basic_4_node_graph) gate_connectivity_criterion.validate(circuit) + +def test_invalid_connectivity_graph(): + bad_graph = nx.complete_graph(5, create_using=nx.Graph()) + with pytest.raises(TypeError): + GateConnectivityCriterion(bad_graph) + +@pytest.mark.parametrize( + "gate_name,controls,targets,is_valid", + [ + ("CZ", [0], [1], True), + ("CNot", [], [0, 1], True), + ("XY", [3], [0], True), + ("CZ", [0, 2], [], False), + ("Swap", [0], [1, 2], False), + ("ZZ", [3], [0], False) + ] +) +def test_validate_instruction_method(gate_name, controls, targets, is_valid, basic_4_node_graph): + gcc = GateConnectivityCriterion(basic_4_node_graph, directed=False) + if is_valid: + gcc.validate_instruction_connectivity(gate_name, controls, targets) + else: + with pytest.raises(ValueError): + gcc.validate_instruction_connectivity(gate_name, controls, targets) diff --git a/test/unit_tests/braket/emulation/test_gate_criterion.py b/test/unit_tests/braket/emulation/test_gate_criterion.py index a306be384..d552acc41 100644 --- a/test/unit_tests/braket/emulation/test_gate_criterion.py +++ b/test/unit_tests/braket/emulation/test_gate_criterion.py @@ -1,8 +1,10 @@ import numpy as np import pytest -from braket.circuits import Circuit +from braket.circuits import Circuit, Gate, Instruction from braket.emulators.emulator_passes.criteria import GateCriterion +from braket.circuits.noises import BitFlip +from braket.circuits.compiler_directives import StartVerbatimBox @pytest.fixture @@ -61,7 +63,10 @@ def mock_qpu_gates(): Circuit() .add_verbatim_box(Circuit().cz(0, 2).prx(0, 0.5, 0.5)) .add_verbatim_box(Circuit().cz(0, 4).cz(3, 6)), - Circuit().add_verbatim_box(Circuit()), + Circuit().h(0).add_verbatim_box(Circuit()), + Circuit().add_verbatim_box( + Circuit().prx(0, np.pi/4, np.pi/4).apply_gate_noise(BitFlip(0.1), Gate.PRx) + ).v(1) ], ) def test_valid_circuits(mock_qpu_gates, circuit): @@ -105,11 +110,16 @@ def test_non_verbatim_circuit_only_native_gates(): criterion.validate(circuit) -def test_empty_instantiation(): +@pytest.mark.parametrize( + "supported_gates,native_gates", + [ + ([],[]), + (["CX"], []) + ] +) +def test_invalid_instantiation(supported_gates, native_gates): with pytest.raises(ValueError): - GateCriterion() - - # return (["h", "cnot"], ['cz', 'prx']) + GateCriterion(supported_gates, native_gates) @pytest.mark.parametrize( @@ -123,6 +133,7 @@ def test_empty_instantiation(): .h(0) .add_verbatim_box(Circuit().cz(1, 2).prx(range(5), np.pi / 4, np.pi / 2).cz(2, 6)) .prx(range(4), np.pi / 4, np.pi / 6), + Circuit().add_instruction(Instruction(StartVerbatimBox())) ], ) def test_invalid_circuits(basic_gate_set, circuit): @@ -133,31 +144,26 @@ def test_invalid_circuits(basic_gate_set, circuit): GateCriterion(basic_gate_set[0], basic_gate_set[1]).validate(circuit) -# def test_empty_supported_gates(): -# with pytest.raises(ValueError): -# GateCriterion([]) - - -# @pytest.mark.parametrize( -# "gate_set_1, gate_set_2", -# [ -# (["h"], ["h"]), -# (["cnot", "h"], ["h", "cnot"]), -# (["phaseshift", "cnot", "rx", "ry"], ["ry", "rx", "cnot", "phaseshift"]) -# ], -# ) -# def test_equality(gate_set_1, gate_set_2): -# assert GateCriterion(gate_set_1) == GateCriterion(gate_set_2) - - -# @pytest.mark.parametrize( -# "gate_set_1, gate_set_2", -# [ -# (["h"], ["x"]), -# (["cnot"], ["h", "cnot"]), -# (["cnot", "h"], ["h"]), -# (["phaseshift", "cnot", "ms", "ry"], ["ry", "rx", "cnot", "ms"]) -# ], -# ) -# def test_inequality(gate_set_1, gate_set_2): -# assert GateCriterion(gate_set_1) != GateCriterion(gate_set_2) +@pytest.mark.parametrize( + "gate_set_1, gate_set_2", + [ + (["h"], ["h"]), + (["cnot", "h"], ["h", "cnot"]), + (["phaseshift", "cnot", "rx", "ry"], ["ry", "rx", "cnot", "phaseshift"]) + ], +) +def test_equality(gate_set_1, gate_set_2): + assert GateCriterion(gate_set_1) == GateCriterion(gate_set_2) + + +@pytest.mark.parametrize( + "gate_set_1, gate_set_2", + [ + (["h"], ["x"]), + (["cnot"], ["h", "cnot"]), + (["cnot", "h"], ["h"]), + (["phaseshift", "cnot", "ms", "ry"], ["ry", "rx", "cnot", "ms"]) + ], +) +def test_inequality(gate_set_1, gate_set_2): + assert GateCriterion(gate_set_1) != GateCriterion(gate_set_2) diff --git a/test/unit_tests/braket/emulation/test_qubit_count_criterion.py b/test/unit_tests/braket/emulation/test_qubit_count_criterion.py index 071dbbe0e..51eedaaca 100644 --- a/test/unit_tests/braket/emulation/test_qubit_count_criterion.py +++ b/test/unit_tests/braket/emulation/test_qubit_count_criterion.py @@ -21,7 +21,7 @@ def test_valid_circuits(qubit_count, circuit): """ QubitCountCriterion should not raise any errors when validating these circuits. """ - QubitCountCriterion(qubit_count=qubit_count).validate(circuit) + QubitCountCriterion(qubit_count=qubit_count).__call__(circuit) @pytest.mark.parametrize("qubit_count", [0, -1]) @@ -42,6 +42,15 @@ def test_invalid_circuits(qubit_count, circuit): with pytest.raises( ValueError, match=f"Circuit must use at most {qubit_count} qubits, \ - but uses {circuit.qubit_count} qubits.", +but uses {circuit.qubit_count} qubits.", ): QubitCountCriterion(qubit_count).validate(circuit) + + +def test_equality(): + qcc_1 = QubitCountCriterion(1) + qcc_2 = QubitCountCriterion(2) + + + assert qcc_1 != qcc_2 + assert qcc_1 == QubitCountCriterion(1) \ No newline at end of file From 934b0ae92ac266b3191b9306a0db61c8710554dd Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 10 Jul 2024 16:13:58 -0700 Subject: [PATCH 69/91] test!: Add unit tests and run linters --- src/braket/aws/aws_device.py | 3 +- src/braket/aws/aws_noise_models.py | 3 - src/braket/emulators/emulator.py | 34 +++-- src/braket/emulators/emulator_interface.py | 14 +- .../criteria/connectivity_criterion.py | 4 +- .../criteria/gate_connectivity_criterion.py | 10 +- .../criteria/gate_criterion.py | 5 +- .../emulation/test_connectivity_criterion.py | 26 ++-- .../braket/emulation/test_emulator.py | 121 ++++++++++-------- .../test_gate_connectivity_criterion.py | 53 ++++---- .../braket/emulation/test_gate_criterion.py | 26 ++-- .../emulation/test_qubit_count_criterion.py | 5 +- 12 files changed, 159 insertions(+), 145 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 595683764..a37674924 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -936,7 +936,6 @@ def validate( program meets all criterion, returns. Args: - task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation]): The quantum program to emulate against this AwsDevice device properties. @@ -1003,4 +1002,4 @@ def emulate( if isinstance(task_specification, Circuit): task_specification = task_specification.copy() - return self.emulator.run(task_specification, shots=shots, inputs=inputs) + return self.emulator.run(task_specification, shots, inputs) diff --git a/src/braket/aws/aws_noise_models.py b/src/braket/aws/aws_noise_models.py index a9591c83e..8562d827e 100644 --- a/src/braket/aws/aws_noise_models.py +++ b/src/braket/aws/aws_noise_models.py @@ -228,9 +228,6 @@ def _create_edge_specs( if hasattr(Gate, gate_name): gate = getattr(Gate, gate_name) edge_specs.append(GateFidelity(gate, edge_property.fidelity)) - else: - continue - # logging.warning(f"Unsupported gate {gate_name}") return edge_specs diff --git a/src/braket/emulators/emulator.py b/src/braket/emulators/emulator.py index cedb43a3e..7352260e8 100644 --- a/src/braket/emulators/emulator.py +++ b/src/braket/emulators/emulator.py @@ -15,12 +15,13 @@ class Emulator(Device, EmulatorInterface): - + DEFAULT_SIMULATOR_BACKEND = "default" DEFAULT_NOISY_BACKEND = "braket_dm" """An emulator is a simulation device that more closely resembles the capabilities and constraints of a real device or of a specific device model.""" + def __init__( self, backend: str = "default", @@ -31,19 +32,34 @@ def __init__( Device.__init__(self, name=kwargs.get("name", "DeviceEmulator"), status="AVAILABLE") EmulatorInterface.__init__(self, emulator_passes) self._noise_model = noise_model - + backend_name = self._get_local_simulator_backend(backend, noise_model) self._backend = LocalSimulator(backend=backend_name, noise_model=noise_model) - def _get_local_simulator_backend(self, backend: str, noise_model: Optional[NoiseModel] = None): - if noise_model and backend == "default": - logging.info( - "Setting LocalSimulator backend to use 'braket_dm' \ - because a NoiseModel was provided." - ) + def _get_local_simulator_backend( + self, backend: str, noise_model: Optional[NoiseModel] = None + ) -> str: + """ + Returns the name of the backend to use with the local simulator. + + Args: + backend (str): The name of the backend requested by the customer, or default if none + were provided. + noise_model (Optional[NoiseModel]): A noise model to use with the emulator, if at all. + If a noise model is provided, the density matrix simulator is used. + + Returns: + str: The name of the backend to pass into the LocalSimulator constructor. + """ + if noise_model: + if backend == "default": + logging.info( + "Setting LocalSimulator backend to use 'braket_dm' \ + because a NoiseModel was provided." + ) return Emulator.DEFAULT_NOISY_BACKEND return Emulator.DEFAULT_SIMULATOR_BACKEND - + def run( self, task_specification: Union[ diff --git a/src/braket/emulators/emulator_interface.py b/src/braket/emulators/emulator_interface.py index 2f0296835..7a079c3d4 100644 --- a/src/braket/emulators/emulator_interface.py +++ b/src/braket/emulators/emulator_interface.py @@ -40,7 +40,9 @@ def run_validation_passes[ProgramType](self, task_specification: ProgramType) -> if isinstance(emulator_pass, EmulatorCriterion): emulator_pass(task_specification) - def add_pass(self, emulator_pass: Union[Iterable[EmulatorPass], EmulatorPass]) -> EmulatorInterface: + def add_pass( + self, emulator_pass: Union[Iterable[EmulatorPass], EmulatorPass] + ) -> EmulatorInterface: """ Append a new EmulatorPass or a list of EmulatorPass objects. @@ -49,10 +51,10 @@ def add_pass(self, emulator_pass: Union[Iterable[EmulatorPass], EmulatorPass]) - single EmulatorPass object or a list of EmulatorPass objects that will be used in validation and program compilation passes by this emulator. - - Returns: - EmulatorInterface: Returns an updated self. - + + Returns: + EmulatorInterface: Returns an updated self. + """ if isinstance(emulator_pass, Iterable): self._emulator_passes.extend(emulator_pass) @@ -60,4 +62,4 @@ def add_pass(self, emulator_pass: Union[Iterable[EmulatorPass], EmulatorPass]) - self._emulator_passes.append(emulator_pass) else: raise TypeError("emulator_pass must be an EmulatorPass or an iterable of EmulatorPass") - return self \ No newline at end of file + return self diff --git a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py index d61468987..7385587f2 100644 --- a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py @@ -43,7 +43,7 @@ def __init__( ) if fully_connected: - if not((num_qubits is None) ^ (qubit_labels is None)): + if not ((num_qubits is None) ^ (qubit_labels is None)): raise ValueError( "Either num_qubits or qubit_labels (NOT both) must be \ provided if fully_connected is True." @@ -67,8 +67,6 @@ def __init__( if not directed: for edge in self._connectivity_graph.edges: self._connectivity_graph.add_edge(edge[1], edge[0]) - else: - print("here") def validate(self, circuit: Circuit) -> None: """ diff --git a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py index b2770c882..75b593091 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py @@ -82,8 +82,9 @@ def validate(self, circuit: Circuit) -> None: ) idx += 1 - if idx == len(circuit.instructions) or \ - not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): + 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 @@ -117,7 +118,6 @@ def validate_instruction_connectivity( ) def __eq__(self, other: EmulatorCriterion) -> bool: - return ( - isinstance(other, GateConnectivityCriterion) - and graphs_equal(self._gate_connectivity_graph, other._gate_connectivity_graph) + return isinstance(other, GateConnectivityCriterion) and graphs_equal( + self._gate_connectivity_graph, other._gate_connectivity_graph ) diff --git a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py index ff1434a23..54eb46f7a 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py @@ -50,8 +50,9 @@ def validate(self, circuit: Circuit) -> None: f"Gate {gate.name} is not a native gate supported by this device." ) idx += 1 - if idx == len(circuit.instructions) or \ - not isinstance(circuit.instructions[idx].operator, EndVerbatimBox): + 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.") elif isinstance(instruction.operator, Gate): gate = instruction.operator diff --git a/test/unit_tests/braket/emulation/test_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_connectivity_criterion.py index 13fc576a9..87afefb5c 100644 --- a/test/unit_tests/braket/emulation/test_connectivity_criterion.py +++ b/test/unit_tests/braket/emulation/test_connectivity_criterion.py @@ -147,16 +147,20 @@ def test_equality_graph_created_with_dict(six_node_digraph): @pytest.mark.parametrize( "connectivity_graph, fully_connected, num_qubits, qubit_labels, directed", [ - (None, True, None, None, False), + (None, True, None, None, False), (nx.DiGraph(), True, None, None, False), (None, True, 5, [0, 1], False), - (None, False, None, None, False), - (nx.from_edgelist([(0, 1)], create_using=nx.Graph()), False, None, None, False) + (None, False, None, None, False), + (nx.from_edgelist([(0, 1)], create_using=nx.Graph()), False, None, None, False), ], ) -def test_invalid_constructors(connectivity_graph, fully_connected, num_qubits, qubit_labels, directed): +def test_invalid_constructors( + connectivity_graph, fully_connected, num_qubits, qubit_labels, directed +): with pytest.raises(ValueError): - ConnectivityCriterion(connectivity_graph, fully_connected, num_qubits, qubit_labels, directed) + ConnectivityCriterion( + connectivity_graph, fully_connected, num_qubits, qubit_labels, directed + ) @pytest.mark.parametrize( @@ -186,11 +190,7 @@ def test_undirected_graph_construction(representation): ) cc = ConnectivityCriterion(representation, directed=False) assert graphs_equal(cc._connectivity_graph, expected_digraph) - - - - - + # @pytest.fixture # def six_node_digraph(): @@ -201,12 +201,12 @@ def test_undirected_graph_construction(representation): @pytest.mark.parametrize( "controls,targets,is_valid", [ - ([0], [1], True), + ([0], [1], True), ([], [0, 1], True), ([3], [0], True), ([0, 2], [], False), ([0], [1, 2], False), - ] + ], ) def test_validate_instruction_method(controls, targets, is_valid, six_node_digraph): gcc = ConnectivityCriterion(six_node_digraph, directed=False) @@ -214,4 +214,4 @@ def test_validate_instruction_method(controls, targets, is_valid, six_node_digra gcc.validate_instruction_connectivity(controls, targets) else: with pytest.raises(ValueError): - gcc.validate_instruction_connectivity(controls, targets) \ No newline at end of file + gcc.validate_instruction_connectivity(controls, targets) diff --git a/test/unit_tests/braket/emulation/test_emulator.py b/test/unit_tests/braket/emulation/test_emulator.py index 34600d7cb..c21c0b21b 100644 --- a/test/unit_tests/braket/emulation/test_emulator.py +++ b/test/unit_tests/braket/emulation/test_emulator.py @@ -1,37 +1,36 @@ import re +from unittest.mock import Mock + +import numpy as np import pytest -from braket.emulators import Emulator -from braket.circuits import Circuit, Gate -from braket.circuits.noise_model import ( - NoiseModel, - GateCriteria -) +from braket.circuits import Circuit, Gate +from braket.circuits.noise_model import GateCriteria, NoiseModel from braket.circuits.noises import BitFlip +from braket.default_simulator import DensityMatrixSimulator, StateVectorSimulator from braket.devices import local_simulator -from braket.default_simulator import StateVectorSimulator, DensityMatrixSimulator +from braket.emulators import Emulator from braket.emulators.emulator_passes import ( EmulatorPass, - QubitCountCriterion, - GateCriterion, - ProgramType + GateCriterion, + ProgramType, + QubitCountCriterion, ) -from unittest.mock import Mock -import numpy as np class AlwaysFailPass(EmulatorPass): - def run(self, program: ProgramType): + def run(self, program: ProgramType): raise ValueError("This pass always raises an error.") + mock_circuit_entry = Mock() mock_circuit_dm_entry = Mock() mock_circuit_entry.load.return_value = StateVectorSimulator mock_circuit_dm_entry.load.return_value = DensityMatrixSimulator -local_simulator._simulator_devices.update({ - "default": mock_circuit_entry, - "braket_dm": mock_circuit_dm_entry -}) +local_simulator._simulator_devices.update( + {"default": mock_circuit_entry, "braket_dm": mock_circuit_dm_entry} +) + @pytest.fixture def basic_emulator(): @@ -43,24 +42,26 @@ def test_empty_emulator_validation(): emulator = Emulator() circuit = Circuit().h(0).cnot(0, 1) emulator.run_validation_passes(circuit) - - + + def test_basic_emulator(basic_emulator): """ - Should not error out when passed a valid circuit. + Should not error out when passed a valid circuit. """ circuit = Circuit().cnot(0, 1) circuit = basic_emulator.run_program_passes(circuit) assert circuit == circuit - + def test_basic_invalidate(basic_emulator): """ Emulator should raise an error thrown by the QubitCountCriterion. """ circuit = Circuit().x(range(6)) - match_string = re.escape(f"Circuit must use at most 4 qubits, \ -but uses {circuit.qubit_count} qubits. (DeviceEmulator)") + match_string = re.escape( + f"Circuit must use at most 4 qubits, \ +but uses {circuit.qubit_count} qubits. (DeviceEmulator)" + ) with pytest.raises(ValueError, match=match_string): basic_emulator.run_program_passes(circuit) @@ -71,92 +72,100 @@ def test_add_pass_single(): emulator.add_pass(qubit_count_criterion) assert emulator._emulator_passes == [qubit_count_criterion] - + + def test_bad_add_pass(): emulator = Emulator() with pytest.raises(TypeError): emulator.add_pass(None) - + + def test_add_pass_multiple(): native_gate_criterion = GateCriterion(native_gates=["CZ", "PRx"]) emulator = Emulator(emulator_passes=[native_gate_criterion]) qubit_count_criterion = QubitCountCriterion(4) gate_criterion = GateCriterion(supported_gates=["H", "CNot"]) - + emulator.add_pass([qubit_count_criterion, gate_criterion]) - assert emulator._emulator_passes == \ - [native_gate_criterion, qubit_count_criterion, gate_criterion] + assert emulator._emulator_passes == [ + native_gate_criterion, + qubit_count_criterion, + gate_criterion, + ] + def test_use_correct_backend_if_noise_model(): noise_model = NoiseModel() emulator = Emulator(noise_model=noise_model) assert emulator._backend.name == "DensityMatrixSimulator" - + + def test_update_noise_model(): emulator = Emulator() assert emulator._backend.name == "StateVectorSimulator" noise_model = NoiseModel() - noise_model.add_noise( - BitFlip(0.1), GateCriteria(Gate.H()) - ) - + noise_model.add_noise(BitFlip(0.1), GateCriteria(Gate.H())) + emulator.noise_model = noise_model assert emulator._backend.name == "DensityMatrixSimulator" assert emulator._backend._noise_model == noise_model assert emulator.noise_model == noise_model - - + + def test_validation_only_pass(): qubit_count_criterion = QubitCountCriterion(4) bad_pass = AlwaysFailPass() emulator = Emulator(emulator_passes=[bad_pass, qubit_count_criterion]) - + circuit = Circuit().h(range(5)) - match_string = re.escape(f"Circuit must use at most 4 qubits, \ -but uses {circuit.qubit_count} qubits. (DeviceEmulator)") + match_string = re.escape( + f"Circuit must use at most 4 qubits, \ +but uses {circuit.qubit_count} qubits. (DeviceEmulator)" + ) with pytest.raises(ValueError, match=match_string): emulator.run_validation_passes(circuit) - - + + def test_apply_noise_model(): noise_model = NoiseModel() noise_model.add_noise(BitFlip(0.1), GateCriteria(Gate.H)) emulator = Emulator(noise_model=noise_model) - + circuit = Circuit().h(0) circuit = emulator.run_program_passes(circuit) - + noisy_circuit = Circuit().h(0).apply_gate_noise(BitFlip(0.1), Gate.H) assert circuit == noisy_circuit - + circuit = Circuit().h(0) circuit = emulator.run_program_passes(circuit, apply_noise_model=False) - + target_circ = Circuit().h(0) assert circuit == target_circ - + + def test_noiseless_run(): qubit_count_criterion = QubitCountCriterion(4) gate_criterion = GateCriterion(supported_gates=["H"]) emulator = Emulator(emulator_passes=[qubit_count_criterion, gate_criterion]) circuit = Circuit().h(0).state_vector() - - + result = emulator.run(circuit).result() state_vector = result.result_types[0].value - target_state_vector = np.array([1/np.sqrt(2) + 0j, 1/np.sqrt(2) + 0j]) + target_state_vector = np.array([1 / np.sqrt(2) + 0j, 1 / np.sqrt(2) + 0j]) assert all(np.isclose(state_vector, target_state_vector)) - + def test_noisy_run(): noise_model = NoiseModel() noise_model.add_noise(BitFlip(0.1), GateCriteria(Gate.H)) - - qubit_count_criterion = QubitCountCriterion(4) + + qubit_count_criterion = QubitCountCriterion(4) gate_criterion = GateCriterion(supported_gates=["H"]) - emulator = Emulator(emulator_passes=[qubit_count_criterion, gate_criterion],\ - noise_model=noise_model) - + emulator = Emulator( + emulator_passes=[qubit_count_criterion, gate_criterion], noise_model=noise_model + ) + circuit = Circuit().h(0) open_qasm_source = """OPENQASM 3.0; bit[1] b; @@ -164,7 +173,7 @@ def test_noisy_run(): h q[0]; #pragma braket noise bit_flip(0.1) q[0] b[0] = measure q[0];""".strip() - + result = emulator.run(circuit, shots=1).result() emulation_source = result.additional_metadata.action.source.strip() - assert emulation_source == open_qasm_source \ No newline at end of file + assert emulation_source == open_qasm_source diff --git a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py index 716620674..db509e5a1 100644 --- a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py +++ b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py @@ -3,12 +3,11 @@ import pytest from networkx.utils import graphs_equal -from braket.circuits import Circuit, Gate -from braket.emulators.emulator_passes.criteria import GateConnectivityCriterion -from braket.circuits.noises import BitFlip -from braket.circuits.noise_model import GateCriteria +from braket.circuits import Circuit, Gate, Instruction from braket.circuits.compiler_directives import StartVerbatimBox -from braket.circuits import Instruction +from braket.circuits.noises import BitFlip +from braket.emulators.emulator_passes.criteria import GateConnectivityCriterion + @pytest.fixture def basic_4_node_graph(): @@ -63,7 +62,7 @@ def basic_4_node_graph_as_dict(): .swap(4, 6), Circuit().add_verbatim_box( Circuit().h(0).apply_gate_noise(BitFlip(0.1), target_gates=Gate.H) - ) + ), ], ) def test_valid_basic_contiguous_circuits(basic_4_node_graph, circuit): @@ -178,49 +177,45 @@ def test_undirected_graph_construction_from_dict(): gcc = GateConnectivityCriterion(dict_representation, directed=False) assert graphs_equal(gcc._gate_connectivity_graph, digraph_representation) + @pytest.mark.parametrize( - "edges", + "edges", [ - [ - (0, 1, {"supported_gates": ["CNot, CZ"]}) - ], - [ - (0, 1, {"supported_gates": ["CNot", "CZ"]}), - (1, 0, {"supported_gates": ["CNot", "CZ"]}) - ], + [(0, 1, {"supported_gates": ["CNot, CZ"]})], + [(0, 1, {"supported_gates": ["CNot", "CZ"]}), (1, 0, {"supported_gates": ["CNot", "CZ"]})], [ (0, 1, {"supported_gates": ["CNot", "CZ"]}), (1, 2, {"supported_gates": ["CNot"]}), (2, 3, {"supported_gates": ["CZ"]}), - (2, 1, {"supported_gates": ["CNot", "CZ", "XX"]}) + (2, 1, {"supported_gates": ["CNot", "CZ", "XX"]}), ], [ (0, 1, {"supported_gates": ["CNot", "CZ"]}), (1, 2, {"supported_gates": ["CNot", "CZ"]}), (4, 2, {"supported_gates": ["CNot", "CZ"]}), - (3, 2, {"supported_gates": ["CNot", "CZ"]}) - ] - ] + (3, 2, {"supported_gates": ["CNot", "CZ"]}), + ], + ], ) def test_undirected_graph_from_digraph(edges): """ Check that undirected topologies created from a digraph correctly add all possible - back edges to the criterion's connectivity graph. + back edges to the criterion's connectivity graph. """ directed_graph = nx.DiGraph() directed_graph.add_edges_from(edges) undirected_graph = directed_graph.copy() - + for edge in edges: if (edge[1], edge[0]) not in undirected_graph.edges: undirected_graph.add_edges_from([(edge[1], edge[0], edge[2])]) - + gcc = GateConnectivityCriterion(directed_graph, directed=False) assert graphs_equal(gcc._gate_connectivity_graph, undirected_graph) - + gcc_other = GateConnectivityCriterion(undirected_graph) assert gcc_other == gcc - + @pytest.mark.parametrize( "representation", @@ -263,7 +258,7 @@ 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())) + Circuit().add_instruction(Instruction(StartVerbatimBox())), ], ) def test_invalid_circuits(basic_4_node_graph, circuit): @@ -271,21 +266,23 @@ def test_invalid_circuits(basic_4_node_graph, circuit): gate_connectivity_criterion = GateConnectivityCriterion(basic_4_node_graph) gate_connectivity_criterion.validate(circuit) + def test_invalid_connectivity_graph(): bad_graph = nx.complete_graph(5, create_using=nx.Graph()) with pytest.raises(TypeError): GateConnectivityCriterion(bad_graph) - + + @pytest.mark.parametrize( "gate_name,controls,targets,is_valid", [ - ("CZ", [0], [1], True), + ("CZ", [0], [1], True), ("CNot", [], [0, 1], True), ("XY", [3], [0], True), ("CZ", [0, 2], [], False), ("Swap", [0], [1, 2], False), - ("ZZ", [3], [0], False) - ] + ("ZZ", [3], [0], False), + ], ) def test_validate_instruction_method(gate_name, controls, targets, is_valid, basic_4_node_graph): gcc = GateConnectivityCriterion(basic_4_node_graph, directed=False) diff --git a/test/unit_tests/braket/emulation/test_gate_criterion.py b/test/unit_tests/braket/emulation/test_gate_criterion.py index d552acc41..bf8ce5329 100644 --- a/test/unit_tests/braket/emulation/test_gate_criterion.py +++ b/test/unit_tests/braket/emulation/test_gate_criterion.py @@ -2,9 +2,9 @@ import pytest from braket.circuits import Circuit, Gate, Instruction -from braket.emulators.emulator_passes.criteria import GateCriterion -from braket.circuits.noises import BitFlip from braket.circuits.compiler_directives import StartVerbatimBox +from braket.circuits.noises import BitFlip +from braket.emulators.emulator_passes.criteria import GateCriterion @pytest.fixture @@ -64,9 +64,11 @@ def mock_qpu_gates(): .add_verbatim_box(Circuit().cz(0, 2).prx(0, 0.5, 0.5)) .add_verbatim_box(Circuit().cz(0, 4).cz(3, 6)), Circuit().h(0).add_verbatim_box(Circuit()), - Circuit().add_verbatim_box( - Circuit().prx(0, np.pi/4, np.pi/4).apply_gate_noise(BitFlip(0.1), Gate.PRx) - ).v(1) + Circuit() + .add_verbatim_box( + Circuit().prx(0, np.pi / 4, np.pi / 4).apply_gate_noise(BitFlip(0.1), Gate.PRx) + ) + .v(1), ], ) def test_valid_circuits(mock_qpu_gates, circuit): @@ -110,13 +112,7 @@ def test_non_verbatim_circuit_only_native_gates(): criterion.validate(circuit) -@pytest.mark.parametrize( - "supported_gates,native_gates", - [ - ([],[]), - (["CX"], []) - ] -) +@pytest.mark.parametrize("supported_gates,native_gates", [([], []), (["CX"], [])]) def test_invalid_instantiation(supported_gates, native_gates): with pytest.raises(ValueError): GateCriterion(supported_gates, native_gates) @@ -133,7 +129,7 @@ def test_invalid_instantiation(supported_gates, native_gates): .h(0) .add_verbatim_box(Circuit().cz(1, 2).prx(range(5), np.pi / 4, np.pi / 2).cz(2, 6)) .prx(range(4), np.pi / 4, np.pi / 6), - Circuit().add_instruction(Instruction(StartVerbatimBox())) + Circuit().add_instruction(Instruction(StartVerbatimBox())), ], ) def test_invalid_circuits(basic_gate_set, circuit): @@ -149,7 +145,7 @@ def test_invalid_circuits(basic_gate_set, circuit): [ (["h"], ["h"]), (["cnot", "h"], ["h", "cnot"]), - (["phaseshift", "cnot", "rx", "ry"], ["ry", "rx", "cnot", "phaseshift"]) + (["phaseshift", "cnot", "rx", "ry"], ["ry", "rx", "cnot", "phaseshift"]), ], ) def test_equality(gate_set_1, gate_set_2): @@ -162,7 +158,7 @@ def test_equality(gate_set_1, gate_set_2): (["h"], ["x"]), (["cnot"], ["h", "cnot"]), (["cnot", "h"], ["h"]), - (["phaseshift", "cnot", "ms", "ry"], ["ry", "rx", "cnot", "ms"]) + (["phaseshift", "cnot", "ms", "ry"], ["ry", "rx", "cnot", "ms"]), ], ) def test_inequality(gate_set_1, gate_set_2): diff --git a/test/unit_tests/braket/emulation/test_qubit_count_criterion.py b/test/unit_tests/braket/emulation/test_qubit_count_criterion.py index 51eedaaca..c487e2eab 100644 --- a/test/unit_tests/braket/emulation/test_qubit_count_criterion.py +++ b/test/unit_tests/braket/emulation/test_qubit_count_criterion.py @@ -50,7 +50,6 @@ def test_invalid_circuits(qubit_count, circuit): def test_equality(): qcc_1 = QubitCountCriterion(1) qcc_2 = QubitCountCriterion(2) - - + assert qcc_1 != qcc_2 - assert qcc_1 == QubitCountCriterion(1) \ No newline at end of file + assert qcc_1 == QubitCountCriterion(1) From b787222369bea82d13374b59f1e74efbda2ade6d Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 10 Jul 2024 16:20:21 -0700 Subject: [PATCH 70/91] test: Add AwsNoise model tests --- .../braket/aws/test_aws_noise_models.py | 401 ++++++++++++++++++ 1 file changed, 401 insertions(+) create mode 100644 test/unit_tests/braket/aws/test_aws_noise_models.py diff --git a/test/unit_tests/braket/aws/test_aws_noise_models.py b/test/unit_tests/braket/aws/test_aws_noise_models.py new file mode 100644 index 000000000..db3e0e373 --- /dev/null +++ b/test/unit_tests/braket/aws/test_aws_noise_models.py @@ -0,0 +1,401 @@ +import json +from unittest.mock import Mock + +import numpy as np +import pytest +from common_test_utils import RIGETTI_REGION + +from braket.aws import AwsDevice +from braket.aws.aws_noise_models import ( + QPU_GATE_DURATIONS, + GateDeviceCalibrationData, + GateFidelity, + _setup_calibration_specs, + create_device_noise_model, +) +from braket.circuits import Gate +from braket.circuits.noise_model import GateCriteria, NoiseModel, ObservableCriteria +from braket.circuits.noises import ( + AmplitudeDamping, + BitFlip, + Depolarizing, + PhaseDamping, + TwoQubitDepolarizing, +) +from braket.device_schema.error_mitigation.debias import Debias +from braket.device_schema.ionq import IonqDeviceCapabilities, IonqDeviceParameters +from braket.device_schema.rigetti import RigettiDeviceCapabilities +from braket.devices import Devices + +MOCK_STANDARDIZED_CALIBRATION_JSON = { + "braketSchemaHeader": { + "name": "braket.device_schema.standardized_gate_model_qpu_device_properties", + "version": "1", + }, + "oneQubitProperties": { + "0": { + "T1": {"value": 0.5, "standardError": None, "unit": "S"}, + "T2": {"value": 0.2, "standardError": None, "unit": "S"}, + "oneQubitFidelity": [ + { + "fidelityType": {"name": "RANDOMIZED_BENCHMARKING", "description": None}, + "fidelity": 0.99, + "standardError": 1e-2, + }, + { + "fidelityType": { + "name": "SIMULTANEOUS_RANDOMIZED_BENCHMARKING", + "description": None, + }, + "fidelity": 0.9934, + "standardError": 0.0065, + }, + { + "fidelityType": {"name": "READOUT", "description": None}, + "fidelity": 0.958, + "standardError": None, + }, + ], + }, + "1": { + "T1": {"value": 0.97, "standardError": None, "unit": "S"}, + "T2": {"value": 0.234, "standardError": None, "unit": "S"}, + "oneQubitFidelity": [ + { + "fidelityType": {"name": "RANDOMIZED_BENCHMARKING", "description": None}, + "fidelity": 0.9983, + "standardError": 4e-5, + }, + { + "fidelityType": { + "name": "SIMULTANEOUS_RANDOMIZED_BENCHMARKING", + "description": None, + }, + "fidelity": 0.879, + "standardError": 0.00058, + }, + { + "fidelityType": {"name": "READOUT", "description": None}, + "fidelity": 0.989, + "standardError": None, + }, + ], + }, + "2": { + "T1": {"value": 0.8, "standardError": None, "unit": "S"}, + "T2": {"value": 0.4, "standardError": None, "unit": "S"}, + "oneQubitFidelity": [ + { + "fidelityType": {"name": "READOUT", "description": None}, + "fidelity": 0.958, + "standardError": None, + } + ], + }, + }, + "twoQubitProperties": { + "0-1": { + "twoQubitGateFidelity": [ + { + "direction": None, + "gateName": "CZ", + "fidelity": 0.9358, + "standardError": 0.01437, + "fidelityType": {"name": "INTERLEAVED_RANDOMIZED_BENCHMARKING"}, + }, + { + "direction": None, + "gateName": "Two_Qubit_Clifford", + "fidelity": 0.9, + "standardError": 0.0237, + "fidelityType": {"name": "INTERLEAVED_RANDOMIZED_BENCHMARKING"}, + }, + { + "direction": None, + "gateName": "CPHASE", + "fidelity": 0.9, + "standardError": 0.01437, + "fidelityType": {"name": "INTERLEAVED_RANDOMIZED_BENCHMARKING"}, + }, + ] + } + }, +} + + +MOCK_STANDARDIZED_QPU_CAPABILITIES_JSON_1 = { + "braketSchemaHeader": { + "name": "braket.device_schema.rigetti.rigetti_device_capabilities", + "version": "1", + }, + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": ["H"], + } + }, + "paradigm": { + "qubitCount": 3, + "nativeGateSet": ["cz", "cphaseshift"], + "connectivity": { + "fullyConnected": False, + "connectivityGraph": {"0": ["1", "2"], "1": ["0"], "2": ["0"]}, + }, + }, + "standardized": MOCK_STANDARDIZED_CALIBRATION_JSON, + "deviceParameters": {}, +} + +MOCK_STANDARDIZED_GATE_MODEL_RIGETTI_CAPABILITIES_1 = RigettiDeviceCapabilities.parse_obj( + MOCK_STANDARDIZED_QPU_CAPABILITIES_JSON_1 +) + + +MOCK_IONQ_GATE_MODEL_CAPABILITIES_JSON_1 = { + "braketSchemaHeader": { + "name": "braket.device_schema.ionq.ionq_device_capabilities", + "version": "1", + }, + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": ["x", "y"], + } + }, + "paradigm": { + "qubitCount": 2, + "nativeGateSet": ["CZ", "CPhaseShift", "GPI", "Toffoli"], + "connectivity": {"fullyConnected": True, "connectivityGraph": {}}, + }, + "provider": { + "braketSchemaHeader": { + "name": "braket.device_schema.ionq.ionq_provider_properties", + "version": "1", + }, + "errorMitigation": {Debias: {"minimumShots": 2500}}, + "fidelity": {"1Q": {"mean": 0.98}, "2Q": {"mean": 0.9625}, "spam": {"mean": 0.9}}, + "timing": { + "1Q": 0.000135, + "2Q": 0.0006, + "T1": 10.0, + "T2": 1.0, + "readout": 0.0003, + "reset": 2e-05, + }, + }, + "deviceParameters": json.loads(IonqDeviceParameters.schema_json()), +} + +MOCK_IONQ_DEVICE_CAPABILITIES = IonqDeviceCapabilities.parse_obj( + MOCK_IONQ_GATE_MODEL_CAPABILITIES_JSON_1 +) + + +@pytest.fixture +def rigetti_target_noise_model(): + gate_duration_1Q = QPU_GATE_DURATIONS[Devices.Rigetti.AspenM3]["single_qubit_gate_duration"] + target_noise_model = ( + NoiseModel() + .add_noise(AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / 0.5))), GateCriteria(qubits=0)) + .add_noise( + PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / 0.2)))), GateCriteria(qubits=0) + ) + .add_noise(Depolarizing(1 - 0.9934), GateCriteria(qubits=0)) + .add_noise(BitFlip(1 - 0.958), ObservableCriteria(qubits=0)) + .add_noise(AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / 0.97))), GateCriteria(qubits=1)) + .add_noise( + PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / 0.234)))), GateCriteria(qubits=1) + ) + .add_noise(Depolarizing(1 - 0.879), GateCriteria(qubits=1)) + .add_noise(BitFlip(1 - 0.989), ObservableCriteria(qubits=1)) + .add_noise(AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / 0.8))), GateCriteria(qubits=2)) + .add_noise( + PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / 0.4)))), GateCriteria(qubits=2) + ) + .add_noise(BitFlip(1 - 0.958), ObservableCriteria(qubits=2)) + .add_noise(TwoQubitDepolarizing(1 - 0.9358), GateCriteria(Gate.CZ, [(1, 0), (0, 1)])) + .add_noise(TwoQubitDepolarizing(1 - 0.9), GateCriteria(Gate.CPhaseShift, [(1, 0), (0, 1)])) + ) + + return target_noise_model + + +@pytest.fixture +def ionq_target_noise_model(): + T1 = 10.0 + T2 = 1.0 + readout = 0.9 + gate_duration_1Q = 0.000135 + single_rb = 0.98 + two_qubit_rb = 0.9625 + target_noise_model = NoiseModel() + qubit_count = MOCK_IONQ_DEVICE_CAPABILITIES.paradigm.qubitCount + for i in range(qubit_count): + target_noise_model = ( + target_noise_model.add_noise( + AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / T1))), GateCriteria(qubits=i) + ) + .add_noise( + PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / T2)))), GateCriteria(qubits=i) + ) + .add_noise(Depolarizing(1 - single_rb), GateCriteria(qubits=i)) + .add_noise(BitFlip(1 - readout), ObservableCriteria(qubits=i)) + ) + + for i in range(qubit_count): + for j in range(i, qubit_count): + if i != j: + target_noise_model = target_noise_model.add_noise( + TwoQubitDepolarizing(1 - two_qubit_rb), GateCriteria(Gate.CZ, [(i, j), (j, i)]) + ).add_noise( + TwoQubitDepolarizing(1 - two_qubit_rb), + GateCriteria(Gate.CPhaseShift, [(i, j), (j, i)]), + ) + return target_noise_model + + +def test_standardized_noise_model(rigetti_target_noise_model): + noise_model = create_device_noise_model( + MOCK_STANDARDIZED_GATE_MODEL_RIGETTI_CAPABILITIES_1, Devices.Rigetti.AspenM3 + ) + + assert noise_model.instructions == rigetti_target_noise_model.instructions + + +@pytest.mark.parametrize( + "single_qubit_gate_duration,two_qubit_gate_duration,qubit_labels,\ + single_qubit_specs,two_qubit_edge_specs", + [ + (0.5, 0.2, {0, 1}, {2: {"h": 0.5}}, {(0, 1): GateFidelity(Gate.H, 0.5)}), + (0.5, 0.2, {0, 1}, {0: {"h": 0.5}}, {(0, 2): GateFidelity(Gate.H, 0.5)}), + ], +) +def test_invalid_gate_calibration_data( + single_qubit_gate_duration, + two_qubit_gate_duration, + qubit_labels, + single_qubit_specs, + two_qubit_edge_specs, +): + with pytest.raises(ValueError): + GateDeviceCalibrationData( + single_qubit_gate_duration, + two_qubit_gate_duration, + qubit_labels, + single_qubit_specs, + two_qubit_edge_specs, + ) + + +def test_missing_gate_durations(): + with pytest.raises(ValueError): + _setup_calibration_specs(MOCK_STANDARDIZED_GATE_MODEL_RIGETTI_CAPABILITIES_1, "bad_arn") + + +def test_ionq_noise_model(ionq_target_noise_model): + noise_model = create_device_noise_model(MOCK_IONQ_DEVICE_CAPABILITIES, Devices.IonQ.Aria1) + assert noise_model.instructions == ionq_target_noise_model.instructions + + +MOCK_DEFAULT_S3_DESTINATION_FOLDER = ( + "amazon-braket-us-test-1-00000000", + "tasks", +) + +MOCK_GATE_MODEL_QPU_1 = { + "deviceName": "Aspen-M3", + "deviceType": "QPU", + "providerName": "Rigetti", + "deviceStatus": "OFFLINE", + "deviceCapabilities": MOCK_STANDARDIZED_GATE_MODEL_RIGETTI_CAPABILITIES_1.json(), + "deviceQueueInfo": [ + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "19", "queuePriority": "Normal"}, + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "3", "queuePriority": "Priority"}, + {"queue": "JOBS_QUEUE", "queueSize": "0 (3 prioritized job(s) running)"}, + ], +} + +MOCK_GATE_MODEL_QPU_2 = { + "deviceName": "Aria-1", + "deviceType": "QPU", + "providerName": "IonQ", + "deviceStatus": "OFFLINE", + "deviceCapabilities": MOCK_IONQ_DEVICE_CAPABILITIES.json(), + "deviceQueueInfo": [ + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "19", "queuePriority": "Normal"}, + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "3", "queuePriority": "Priority"}, + {"queue": "JOBS_QUEUE", "queueSize": "0 (3 prioritized job(s) running)"}, + ], +} + + +@pytest.fixture +def aws_session(): + _boto_session = Mock() + _boto_session.region_name = RIGETTI_REGION + _boto_session.profile_name = "test-profile" + + creds = Mock() + creds.method = "other" + _boto_session.get_credentials.return_value = creds + + _aws_session = Mock() + _aws_session.boto_session = _boto_session + _aws_session._default_bucket = MOCK_DEFAULT_S3_DESTINATION_FOLDER[0] + _aws_session.default_bucket.return_value = _aws_session._default_bucket + _aws_session._custom_default_bucket = False + _aws_session.account_id = "00000000" + _aws_session.region = RIGETTI_REGION + return _aws_session + + +@pytest.fixture( + params=[ + "arn:aws:braket:us-west-1::device/quantum-simulator/amazon/sim", + "arn:aws:braket:::device/quantum-simulator/amazon/sim", + ] +) +def arn(request): + return request.param + + +@pytest.fixture +def rigetti_device(aws_session): + def _device(arn): + aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + aws_session.search_devices.return_value = [MOCK_GATE_MODEL_QPU_1] + return AwsDevice(arn, aws_session) + + return _device + + +@pytest.fixture +def ionq_device(aws_session): + def _device(arn): + aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_2 + aws_session.search_devices.return_value = [MOCK_GATE_MODEL_QPU_2] + return AwsDevice(arn, aws_session) + + return _device From 6cd4ffc6b4afa817d45d5ffe2dc53d3787ca87af Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 15 Jul 2024 12:55:58 -0700 Subject: [PATCH 71/91] test: Reach 100% coverage, run linters --- src/braket/aws/aws_device.py | 29 +- src/braket/aws/aws_emulator_helpers.py | 57 +- .../criteria/gate_connectivity_criterion.py | 8 +- test/unit_tests/braket/aws/test_aws_device.py | 11 + .../braket/aws/test_aws_emulation.py | 712 ++++++++++++++++++ .../braket/aws/test_aws_noise_models.py | 401 ---------- .../braket/devices/test_local_simulator.py | 34 +- .../braket/emulation/test_emulator.py | 14 +- .../braket/emulation/test_gate_criterion.py | 3 +- 9 files changed, 786 insertions(+), 483 deletions(-) create mode 100644 test/unit_tests/braket/aws/test_aws_emulation.py delete mode 100644 test/unit_tests/braket/aws/test_aws_noise_models.py diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index a37674924..b6bb0f15b 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -892,21 +892,16 @@ def emulator(self) -> Emulator: self._emulator = self._setup_emulator() return self._emulator - def _setup_emulator(self, emulator_noise_model: NoiseModel | None = None) -> 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). - Args: - emulator_noise_model (NoiseModel | None): Use the provided noise model in the emulator - instead of creating one. - Returns: Emulator: An emulator with a noise model, compilation passes, and validation passes based on this device's properites. """ - if not emulator_noise_model: - emulator_noise_model = create_device_noise_model(self.properties, self._arn) + emulator_noise_model = create_device_noise_model(self.properties, self._arn) self._emulator = Emulator( noise_model=emulator_noise_model, backend="braket_dm", name=self._name ) @@ -962,21 +957,12 @@ def run_emulator_passes[ ProgramType: A validated and compiled program that may be augmented with noise operations to mimic noise on this device. """ - if isinstance(task_specification, Circuit): - task_specification = task_specification.copy() - + task_specification = task_specification.copy() return self.emulator.run_program_passes(task_specification, apply_noise_model) def emulate( self, - task_specification: Union[ - Circuit, - Problem, - OpenQasmProgram, - BlackbirdProgram, - PulseSequence, - AnalogHamiltonianSimulation, - ], + task_specification: Circuit, shots: Optional[int] = None, inputs: Optional[dict[str, float]] = None, ) -> QuantumTask: @@ -986,8 +972,7 @@ def emulate( the program on the emulator's backend. Args: - task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, - PulseSequence, AnalogHamiltonianSimulation]): Specification of a quantum task + task_specification (Circuit): Specification of a quantum task to run on device. shots (Optional[int]): The number of times to run the quantum task on the device. @@ -999,7 +984,5 @@ def emulate( Returns: QuantumTask: The QuantumTask tracking task execution on this device emulator. """ - if isinstance(task_specification, Circuit): - task_specification = task_specification.copy() - + task_specification = task_specification.copy() return self.emulator.run(task_specification, shots, inputs) diff --git a/src/braket/aws/aws_emulator_helpers.py b/src/braket/aws/aws_emulator_helpers.py index 4f6019c51..000509730 100644 --- a/src/braket/aws/aws_emulator_helpers.py +++ b/src/braket/aws/aws_emulator_helpers.py @@ -102,54 +102,37 @@ def create_gate_connectivity_criterion( raise NotImplementedError +@create_gate_connectivity_criterion.register(IqmDeviceCapabilities) @create_gate_connectivity_criterion.register(RigettiDeviceCapabilities) def _( properties: RigettiDeviceCapabilities, connectivity_graph: DiGraph ) -> GateConnectivityCriterion: """ - Rigetti provides device capabilities using a standardized properties schema for gate devices. - - Rigetti provides both forwards and backwards edges for their undirected gate - connectivity graph, so no new needs to be introduced when creating a - GateConnectivityCriterion object for a Rigetti QPU. + Both IQM and Rigetti have undirected connectivity graphs; Rigetti device capabilities + provide back edges, but the calibration data only provides edges in one direction. + Additionally, IQM does not provide back edges in its connectivity_graph (nor is this + resolved manually by AwsDevice at the moment). """ gate_connectivity_graph = connectivity_graph.copy() edge_properties = properties.standardized.twoQubitProperties - for edge in gate_connectivity_graph.edges: - edge_key = "-".join([str(qubit) for qubit in edge]) - edge_property = edge_properties.get(edge_key, list()) + 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) if not edge_property: + gate_connectivity_graph[u][v]["supported_gates"] = set() continue edge_supported_gates = get_qpu_gate_translation( properties, [property.gateName for property in edge_property.twoQubitGateFidelity] ) - gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] = edge_supported_gates - - return GateConnectivityCriterion(gate_connectivity_graph) + gate_connectivity_graph[u][v]["supported_gates"] = set(edge_supported_gates) - -@create_gate_connectivity_criterion.register(IqmDeviceCapabilities) -def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: - """ - IQM provides device capabilities using a standardized properties schema for gate devices. - - IQM provides only forward edges for their *undirected* gate - connectivity graph, so back-edges must be introduced when creating the - GateConnectivityCriterion object for an IQM QPU. - """ - gate_connectivity_graph = connectivity_graph.copy() - for edge in gate_connectivity_graph.edges: - gate_connectivity_graph.add_edge(edge[1], edge[0]) - - edge_properties = properties.standardized.twoQubitProperties - for edge_property in edge_properties.keys(): - edge = [int(qubit) for qubit in edge_property.split("-")] - edge_supported_gates = get_qpu_gate_translation( - properties, - [property.gateName for property in edge_properties[edge_property].twoQubitGateFidelity], - ) - gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] = edge_supported_gates - gate_connectivity_graph[edge[1]][edge[0]]["supported_gates"] = edge_supported_gates + 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" + ) in [None, set()]: + gate_connectivity_graph.add_edge( + v, u, supported_gates=set(gate_connectivity_graph[u][v]["supported_gates"]) + ) return GateConnectivityCriterion(gate_connectivity_graph) @@ -164,8 +147,9 @@ def _(properties: IonqDeviceCapabilities, connectivity_graph: DiGraph) -> GateCo """ gate_connectivity_graph = connectivity_graph.copy() native_gates = get_qpu_gate_translation(properties, properties.paradigm.nativeGateSet) + for edge in gate_connectivity_graph.edges: - gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] = native_gates + gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] = set(native_gates) return GateConnectivityCriterion(gate_connectivity_graph) @@ -205,9 +189,6 @@ def _get_qpu_gate_translation(properties: DeviceCapabilities, gate_name: str) -> return gate_name -# TODO: put translations in global dict with explicit QHP names as keys? - - @_get_qpu_gate_translation.register(RigettiDeviceCapabilities) def _(properties: RigettiDeviceCapabilities, gate_name: str) -> str: translations = {"CPHASE": "CPhaseShift"} diff --git a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py index 75b593091..449af1431 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py @@ -20,12 +20,10 @@ def __init__( if isinstance(gate_connectivity_graph, DiGraph): self._gate_connectivity_graph = gate_connectivity_graph if not directed: - for edge in self._gate_connectivity_graph.edges: - back_edge = (edge[1], edge[0]) + for u, v in self._gate_connectivity_graph.edges: + back_edge = (v, u) if back_edge not in self._gate_connectivity_graph.edges: - supported_gates = self._gate_connectivity_graph[edge[0]][edge[1]][ - "supported_gates" - ] + supported_gates = self._gate_connectivity_graph[u][v]["supported_gates"] self._gate_connectivity_graph.add_edge( *back_edge, supported_gates=supported_gates ) diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index a778dfb5f..ba604cf9c 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -567,6 +567,17 @@ def test_device_simulator_no_aws_session(aws_session_init, aws_session): aws_session.get_device.assert_called_with(arn) +@patch("braket.aws.aws_device.AwsSession") +def test_attempt_get_emulator_with_simulators(aws_session_init, aws_session): + arn = SV1_ARN + aws_session_init.return_value = aws_session + aws_session.get_device.return_value = MOCK_GATE_MODEL_SIMULATOR + device = AwsDevice(arn) + error_message = "Creating an emulator from a Braket managed simulator is not supported." + with pytest.raises(ValueError, match=error_message): + emulator = device.emulator + + @patch("braket.aws.aws_device.AwsSession.copy_session") @patch("braket.aws.aws_device.AwsSession") @pytest.mark.parametrize( diff --git a/test/unit_tests/braket/aws/test_aws_emulation.py b/test/unit_tests/braket/aws/test_aws_emulation.py new file mode 100644 index 000000000..534dc1ea8 --- /dev/null +++ b/test/unit_tests/braket/aws/test_aws_emulation.py @@ -0,0 +1,712 @@ +import json +from unittest.mock import Mock, patch + +import networkx as nx +import numpy as np +import pytest +from common_test_utils import RIGETTI_ARN, RIGETTI_REGION + +from braket.aws import AwsDevice +from braket.aws.aws_emulator_helpers import get_qpu_gate_translation +from braket.aws.aws_noise_models import ( + GateDeviceCalibrationData, + GateFidelity, + _setup_calibration_specs, + create_device_noise_model, +) +from braket.circuits import Circuit, Gate +from braket.circuits.noise_model import GateCriteria, NoiseModel, ObservableCriteria +from braket.circuits.noises import ( + AmplitudeDamping, + BitFlip, + Depolarizing, + PhaseDamping, + TwoQubitDepolarizing, +) +from braket.device_schema import DeviceCapabilities +from braket.device_schema.error_mitigation.debias import Debias +from braket.device_schema.ionq import IonqDeviceCapabilities, IonqDeviceParameters +from braket.device_schema.iqm import IqmDeviceCapabilities +from braket.device_schema.rigetti import RigettiDeviceCapabilities +from braket.devices import Devices +from braket.devices.local_simulator import LocalSimulator +from braket.emulators import Emulator +from braket.emulators.emulator_passes import ( + ConnectivityCriterion, + GateConnectivityCriterion, + GateCriterion, + QubitCountCriterion, +) + +REGION = "us-west-1" + +IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/Forte1" +IQM_ARN = "arn:aws:braket:::device/qpu/iqm/Garent" + + +MOCK_QPU_GATE_DURATIONS = { + RIGETTI_ARN: { + "single_qubit_gate_duration": 40e-9, + "two_qubit_gate_duration": 240e-9, + }, + IQM_ARN: {"single_qubit_gate_duration": 32e-9, "two_qubit_gate_duration": 60e-9}, +} + + +@pytest.fixture +def basic_device_capabilities(): + return DeviceCapabilities.parse_obj( + { + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + }, + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + }, + }, + "deviceParameters": {}, + } + ) + + +MOCK_STANDARDIZED_CALIBRATION_JSON = { + "braketSchemaHeader": { + "name": "braket.device_schema.standardized_gate_model_qpu_device_properties", + "version": "1", + }, + "oneQubitProperties": { + "0": { + "T1": {"value": 0.5, "standardError": None, "unit": "S"}, + "T2": {"value": 0.2, "standardError": None, "unit": "S"}, + "oneQubitFidelity": [ + { + "fidelityType": {"name": "RANDOMIZED_BENCHMARKING", "description": None}, + "fidelity": 0.99, + "standardError": 1e-2, + }, + { + "fidelityType": { + "name": "SIMULTANEOUS_RANDOMIZED_BENCHMARKING", + "description": None, + }, + "fidelity": 0.9934, + "standardError": 0.0065, + }, + { + "fidelityType": {"name": "READOUT", "description": None}, + "fidelity": 0.958, + "standardError": None, + }, + ], + }, + "1": { + "T1": {"value": 0.97, "standardError": None, "unit": "S"}, + "T2": {"value": 0.234, "standardError": None, "unit": "S"}, + "oneQubitFidelity": [ + { + "fidelityType": {"name": "RANDOMIZED_BENCHMARKING", "description": None}, + "fidelity": 0.9983, + "standardError": 4e-5, + }, + { + "fidelityType": { + "name": "SIMULTANEOUS_RANDOMIZED_BENCHMARKING", + "description": None, + }, + "fidelity": 0.879, + "standardError": 0.00058, + }, + { + "fidelityType": {"name": "READOUT", "description": None}, + "fidelity": 0.989, + "standardError": None, + }, + ], + }, + "2": { + "T1": {"value": 0.8, "standardError": None, "unit": "S"}, + "T2": {"value": 0.4, "standardError": None, "unit": "S"}, + "oneQubitFidelity": [ + { + "fidelityType": {"name": "READOUT", "description": None}, + "fidelity": 0.958, + "standardError": None, + } + ], + }, + }, + "twoQubitProperties": { + "0-1": { + "twoQubitGateFidelity": [ + { + "direction": None, + "gateName": "CZ", + "fidelity": 0.9358, + "standardError": 0.01437, + "fidelityType": {"name": "INTERLEAVED_RANDOMIZED_BENCHMARKING"}, + }, + { + "direction": None, + "gateName": "Two_Qubit_Clifford", + "fidelity": 0.9, + "standardError": 0.0237, + "fidelityType": {"name": "INTERLEAVED_RANDOMIZED_BENCHMARKING"}, + }, + { + "direction": None, + "gateName": "CPHASE", + "fidelity": 0.9, + "standardError": 0.01437, + "fidelityType": {"name": "INTERLEAVED_RANDOMIZED_BENCHMARKING"}, + }, + ] + } + }, +} + +MOCK_STANDARDIZED_CALIBRATION_JSON_2 = MOCK_STANDARDIZED_CALIBRATION_JSON.copy() +MOCK_STANDARDIZED_CALIBRATION_JSON["twoQubitProperties"]["0-1"]["twoQubitGateFidelity"][2][ + "gateName" +] = "CPhaseShift" + +MOCK_RIGETTI_QPU_CAPABILITIES_1 = { + "braketSchemaHeader": { + "name": "braket.device_schema.rigetti.rigetti_device_capabilities", + "version": "1", + }, + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": ["H", "X", "CNot", "CZ", "Rx", "Ry", "YY"], + } + }, + "paradigm": { + "qubitCount": 3, + "nativeGateSet": ["cz", "prx", "cphaseshift"], + "connectivity": { + "fullyConnected": False, + "connectivityGraph": {"0": ["1", "2"], "1": ["0"], "2": ["0"]}, + }, + }, + "standardized": MOCK_STANDARDIZED_CALIBRATION_JSON, + "deviceParameters": {}, +} + +MOCK_IQM_QPU_CAPABILITIES_1 = { + "braketSchemaHeader": { + "name": "braket.device_schema.iqm.iqm_device_capabilities", + "version": "1", + }, + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": ["H", "CNot", "Ry", "XX", "YY", "start_verbatim_box"], + } + }, + "paradigm": { + "qubitCount": 4, + "nativeGateSet": ["cz", "prx", "cphaseshift"], + "connectivity": { + "fullyConnected": False, + "connectivityGraph": {"0": ["1", "2"], "2": ["3"]}, + }, + }, + "standardized": MOCK_STANDARDIZED_CALIBRATION_JSON, + "deviceParameters": {}, +} + + +@pytest.fixture +def rigetti_device_capabilities(): + return RigettiDeviceCapabilities.parse_obj(MOCK_RIGETTI_QPU_CAPABILITIES_1) + + +@pytest.fixture +def iqm_device_capabilities(): + return IqmDeviceCapabilities.parse_obj(MOCK_IQM_QPU_CAPABILITIES_1) + + +MOCK_IONQ_GATE_MODEL_CAPABILITIES_JSON_1 = { + "braketSchemaHeader": { + "name": "braket.device_schema.ionq.ionq_device_capabilities", + "version": "1", + }, + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": ["x", "y"], + } + }, + "paradigm": { + "qubitCount": 2, + "nativeGateSet": ["CZ", "CPhaseShift", "GPI"], + "connectivity": {"fullyConnected": True, "connectivityGraph": {}}, + }, + "provider": { + "braketSchemaHeader": { + "name": "braket.device_schema.ionq.ionq_provider_properties", + "version": "1", + }, + "errorMitigation": {Debias: {"minimumShots": 2500}}, + "fidelity": {"1Q": {"mean": 0.98}, "2Q": {"mean": 0.9625}, "spam": {"mean": 0.9}}, + "timing": { + "1Q": 0.000135, + "2Q": 0.0006, + "T1": 10.0, + "T2": 1.0, + "readout": 0.0003, + "reset": 2e-05, + }, + }, + "deviceParameters": json.loads(IonqDeviceParameters.schema_json()), +} + + +@pytest.fixture +def ionq_device_capabilities(): + return IonqDeviceCapabilities.parse_obj(MOCK_IONQ_GATE_MODEL_CAPABILITIES_JSON_1) + + +@pytest.fixture +def rigetti_target_noise_model(): + gate_duration_1Q = MOCK_QPU_GATE_DURATIONS[RIGETTI_ARN]["single_qubit_gate_duration"] + target_noise_model = ( + NoiseModel() + .add_noise(AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / 0.5))), GateCriteria(qubits=0)) + .add_noise( + PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / 0.2)))), GateCriteria(qubits=0) + ) + .add_noise(Depolarizing(1 - 0.9934), GateCriteria(qubits=0)) + .add_noise(BitFlip(1 - 0.958), ObservableCriteria(qubits=0)) + .add_noise(AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / 0.97))), GateCriteria(qubits=1)) + .add_noise( + PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / 0.234)))), GateCriteria(qubits=1) + ) + .add_noise(Depolarizing(1 - 0.879), GateCriteria(qubits=1)) + .add_noise(BitFlip(1 - 0.989), ObservableCriteria(qubits=1)) + .add_noise(AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / 0.8))), GateCriteria(qubits=2)) + .add_noise( + PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / 0.4)))), GateCriteria(qubits=2) + ) + .add_noise(BitFlip(1 - 0.958), ObservableCriteria(qubits=2)) + .add_noise(TwoQubitDepolarizing(1 - 0.9358), GateCriteria(Gate.CZ, [(1, 0), (0, 1)])) + .add_noise(TwoQubitDepolarizing(1 - 0.9), GateCriteria(Gate.CPhaseShift, [(1, 0), (0, 1)])) + ) + + return target_noise_model + + +@pytest.fixture +def iqm_target_noise_model(): + gate_duration_1Q = MOCK_QPU_GATE_DURATIONS[IQM_ARN]["single_qubit_gate_duration"] + target_noise_model = ( + NoiseModel() + .add_noise(AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / 0.5))), GateCriteria(qubits=0)) + .add_noise( + PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / 0.2)))), GateCriteria(qubits=0) + ) + .add_noise(Depolarizing(1 - 0.9934), GateCriteria(qubits=0)) + .add_noise(BitFlip(1 - 0.958), ObservableCriteria(qubits=0)) + .add_noise(AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / 0.97))), GateCriteria(qubits=1)) + .add_noise( + PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / 0.234)))), GateCriteria(qubits=1) + ) + .add_noise(Depolarizing(1 - 0.879), GateCriteria(qubits=1)) + .add_noise(BitFlip(1 - 0.989), ObservableCriteria(qubits=1)) + .add_noise(AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / 0.8))), GateCriteria(qubits=2)) + .add_noise( + PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / 0.4)))), GateCriteria(qubits=2) + ) + .add_noise(BitFlip(1 - 0.958), ObservableCriteria(qubits=2)) + .add_noise(TwoQubitDepolarizing(1 - 0.9358), GateCriteria(Gate.CZ, [(1, 0), (0, 1)])) + .add_noise(TwoQubitDepolarizing(1 - 0.9), GateCriteria(Gate.CPhaseShift, [(1, 0), (0, 1)])) + ) + + return target_noise_model + + +@pytest.fixture +def ionq_target_noise_model(ionq_device_capabilities): + T1 = 10.0 + T2 = 1.0 + readout = 0.9 + gate_duration_1Q = 0.000135 + single_rb = 0.98 + two_qubit_rb = 0.9625 + target_noise_model = NoiseModel() + qubit_count = ionq_device_capabilities.paradigm.qubitCount + for i in range(qubit_count): + target_noise_model = ( + target_noise_model.add_noise( + AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / T1))), GateCriteria(qubits=i) + ) + .add_noise( + PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / T2)))), GateCriteria(qubits=i) + ) + .add_noise(Depolarizing(1 - single_rb), GateCriteria(qubits=i)) + .add_noise(BitFlip(1 - readout), ObservableCriteria(qubits=i)) + ) + + for i in range(qubit_count): + for j in range(i, qubit_count): + if i != j: + target_noise_model = target_noise_model.add_noise( + TwoQubitDepolarizing(1 - two_qubit_rb), GateCriteria(Gate.CZ, [(i, j), (j, i)]) + ).add_noise( + TwoQubitDepolarizing(1 - two_qubit_rb), + GateCriteria(Gate.CPhaseShift, [(i, j), (j, i)]), + ) + return target_noise_model + + +@patch.dict("braket.aws.aws_noise_models.QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) +def test_standardized_noise_model(rigetti_device_capabilities, rigetti_target_noise_model): + noise_model = create_device_noise_model(rigetti_device_capabilities, RIGETTI_ARN) + + assert noise_model.instructions == rigetti_target_noise_model.instructions + + +@pytest.mark.parametrize( + "single_qubit_gate_duration,two_qubit_gate_duration,qubit_labels,\ + single_qubit_specs,two_qubit_edge_specs", + [ + (0.5, 0.2, {0, 1}, {2: {"h": 0.5}}, {(0, 1): GateFidelity(Gate.H, 0.5)}), + (0.5, 0.2, {0, 1}, {0: {"h": 0.5}}, {(0, 2): GateFidelity(Gate.H, 0.5)}), + ], +) +def test_invalid_gate_calibration_data( + single_qubit_gate_duration, + two_qubit_gate_duration, + qubit_labels, + single_qubit_specs, + two_qubit_edge_specs, +): + with pytest.raises(ValueError): + GateDeviceCalibrationData( + single_qubit_gate_duration, + two_qubit_gate_duration, + qubit_labels, + single_qubit_specs, + two_qubit_edge_specs, + ) + + +def test_missing_gate_durations(rigetti_device_capabilities): + with pytest.raises(ValueError): + _setup_calibration_specs(rigetti_device_capabilities, "bad_arn") + + +def test_ionq_noise_model(ionq_device_capabilities, ionq_target_noise_model): + noise_model = create_device_noise_model(ionq_device_capabilities, Devices.IonQ.Aria1) + assert noise_model.instructions == ionq_target_noise_model.instructions + + +MOCK_DEFAULT_S3_DESTINATION_FOLDER = ( + "amazon-braket-us-test-1-00000000", + "tasks", +) + + +@pytest.fixture +def mock_rigetti_qpu_device(rigetti_device_capabilities): + return { + "deviceName": "Aspen-M3", + "deviceType": "QPU", + "providerName": "Rigetti", + "deviceStatus": "OFFLINE", + "deviceCapabilities": rigetti_device_capabilities.json(), + "deviceQueueInfo": [ + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "19", "queuePriority": "Normal"}, + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "3", "queuePriority": "Priority"}, + {"queue": "JOBS_QUEUE", "queueSize": "0 (3 prioritized job(s) running)"}, + ], + } + + +@pytest.fixture +def mock_iqm_qpu_device(iqm_device_capabilities): + return { + "deviceName": "Harmony", + "deviceType": "QPU", + "providerName": "IQM", + "deviceStatus": "OFFLINE", + "deviceCapabilities": iqm_device_capabilities.json(), + "deviceQueueInfo": [ + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "19", "queuePriority": "Normal"}, + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "3", "queuePriority": "Priority"}, + {"queue": "JOBS_QUEUE", "queueSize": "0 (3 prioritized job(s) running)"}, + ], + } + + +@pytest.fixture +def mock_ionq_qpu_device(ionq_device_capabilities): + return { + "deviceName": "Aspen-M3", + "deviceType": "QPU", + "providerName": "Rigetti", + "deviceStatus": "OFFLINE", + "deviceCapabilities": ionq_device_capabilities.json(), + "deviceQueueInfo": [ + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "19", "queuePriority": "Normal"}, + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "3", "queuePriority": "Priority"}, + {"queue": "JOBS_QUEUE", "queueSize": "0 (3 prioritized job(s) running)"}, + ], + } + + +@pytest.fixture +def aws_session(): + _boto_session = Mock() + _boto_session.region_name = RIGETTI_REGION + _boto_session.profile_name = "test-profile" + + creds = Mock() + creds.method = "other" + _boto_session.get_credentials.return_value = creds + + _aws_session = Mock() + _aws_session.boto_session = _boto_session + _aws_session._default_bucket = MOCK_DEFAULT_S3_DESTINATION_FOLDER[0] + _aws_session.default_bucket.return_value = _aws_session._default_bucket + _aws_session._custom_default_bucket = False + _aws_session.account_id = "00000000" + _aws_session.region = RIGETTI_REGION + return _aws_session + + +@pytest.fixture( + params=[ + "arn:aws:braket:us-west-1::device/quantum-simulator/amazon/sim", + "arn:aws:braket:::device/quantum-simulator/amazon/sim", + ] +) +def arn(request): + return request.param + + +@pytest.fixture +def ionq_device(aws_session, mock_ionq_qpu_device): + def _device(): + aws_session.get_device.return_value = mock_ionq_qpu_device + aws_session.search_devices.return_value = [mock_ionq_qpu_device] + return AwsDevice(IONQ_ARN, aws_session) + + return _device() + + +@pytest.fixture +def iqm_device(aws_session, mock_iqm_qpu_device): + def _device(): + aws_session.get_device.return_value = mock_iqm_qpu_device + aws_session.search_devices.return_value = [mock_iqm_qpu_device] + return AwsDevice(IQM_ARN, aws_session) + + return _device() + + +@pytest.fixture +def rigetti_device(aws_session, mock_rigetti_qpu_device): + def _device(): + aws_session.get_device.return_value = mock_rigetti_qpu_device + aws_session.search_devices.return_value = [mock_rigetti_qpu_device] + return AwsDevice(RIGETTI_ARN, aws_session) + + return _device() + + +def test_ionq_emulator(ionq_device): + emulator = ionq_device.emulator + target_emulator_passes = [ + QubitCountCriterion(ionq_device.properties.paradigm.qubitCount), + GateCriterion( + supported_gates=["x", "Y"], + native_gates=["cz", "gpi", "cphaseshift"], + ), + ConnectivityCriterion(nx.from_edgelist([(0, 1), (1, 0)], create_using=nx.DiGraph())), + GateConnectivityCriterion( + nx.from_dict_of_dicts( + { + 0: {1: {"supported_gates": set(["CZ", "CPhaseShift", "GPi"])}}, + 1: {0: {"supported_gates": set(["CZ", "CPhaseShift", "GPi"])}}, + }, + create_using=nx.DiGraph(), + ) + ), + ] + emulator._emulator_passes == target_emulator_passes + + +@patch.dict("braket.aws.aws_noise_models.QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) +def test_rigetti_emulator(rigetti_device, rigetti_target_noise_model): + emulator = rigetti_device.emulator + assert emulator.noise_model + assert len(emulator.noise_model.instructions) == len(rigetti_target_noise_model.instructions) + assert all( + i1 == i2 + for i1, i2 in zip( + emulator.noise_model.instructions, rigetti_target_noise_model.instructions + ) + ) + + target_emulator_passes = [ + QubitCountCriterion(rigetti_device.properties.paradigm.qubitCount), + GateCriterion( + supported_gates=["H", "X", "CNot", "CZ", "Rx", "Ry", "YY"], + native_gates=["cz", "prx", "cphaseshift"], + ), + ConnectivityCriterion( + nx.from_edgelist([(0, 1), (0, 2), (1, 0), (2, 0)], create_using=nx.DiGraph()) + ), + GateConnectivityCriterion( + nx.from_dict_of_dicts( + { + 0: { + 1: {"supported_gates": set(["CZ", "CPhaseShift", "Two_Qubit_Clifford"])}, + 2: {"supported_gates": set()}, + }, + 1: {0: {"supported_gates": set(["CZ", "CPhaseShift", "Two_Qubit_Clifford"])}}, + 2: {0: {"supported_gates": set()}}, + }, + create_using=nx.DiGraph(), + ) + ), + ] + assert emulator._emulator_passes == target_emulator_passes + + +@patch.dict("braket.aws.aws_noise_models.QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) +def test_iqm_emulator(iqm_device, iqm_target_noise_model): + emulator = iqm_device.emulator + assert emulator.noise_model + assert len(emulator.noise_model.instructions) == len(iqm_target_noise_model.instructions) + assert emulator.noise_model.instructions == iqm_target_noise_model.instructions + target_emulator_passes = [ + QubitCountCriterion(iqm_device.properties.paradigm.qubitCount), + GateCriterion( + supported_gates=["H", "CNot", "Ry", "XX", "YY"], + native_gates=["cz", "prx", "cphaseshift"], + ), + ConnectivityCriterion( + nx.from_edgelist( + [(0, 1), (0, 2), (1, 0), (2, 0), (2, 3), (3, 2)], create_using=nx.DiGraph() + ) + ), + GateConnectivityCriterion( + nx.from_dict_of_dicts( + { + 0: { + 1: {"supported_gates": set(["CZ", "CPhaseShift", "Two_Qubit_Clifford"])}, + 2: {"supported_gates": set()}, + }, + 1: {0: {"supported_gates": set(["CZ", "CPhaseShift", "Two_Qubit_Clifford"])}}, + 2: {0: {"supported_gates": set()}, 3: {"supported_gates": set()}}, + 3: {2: {"supported_gates": set()}}, + }, + create_using=nx.DiGraph(), + ) + ), + ] + + for i in range(4): + assert emulator._emulator_passes[i] == target_emulator_passes[i] + + +@pytest.mark.parametrize( + "device_capabilities,gate_name,expected_result", + [ + ("basic_device_capabilities", "fake_gate", "fake_gate"), + ("rigetti_device_capabilities", "CPHASE", "CPhaseShift"), + ("ionq_device_capabilities", "GPI", "GPi"), + ("ionq_device_capabilities", ["GPI", "GPI2", "fake_gate"], ["GPi", "GPi2", "fake_gate"]), + ], +) +def test_get_gate_translations(device_capabilities, gate_name, expected_result, request): + device_capabilities_obj = request.getfixturevalue(device_capabilities) + assert get_qpu_gate_translation(device_capabilities_obj, gate_name) == expected_result + + +@patch.dict("braket.aws.aws_noise_models.QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) +@pytest.mark.parametrize( + "circuit,is_valid", + [ + (Circuit(), True), + (Circuit().cnot(0, 1).h(2), True), + (Circuit().x(4).yy(4, 8, np.pi), True), + (Circuit().add_verbatim_box(Circuit().cz(0, 1)).h(5), False), + (Circuit().x(range(5)), False), + (Circuit().add_verbatim_box(Circuit().cz(0, 1)).rx(1, np.pi / 4), True), + (Circuit().add_verbatim_box(Circuit().cz(0, 2)), False), + (Circuit().xx(0, 1, np.pi / 4), False), + ], +) +def test_emulator_passes(circuit, is_valid, rigetti_device): + if is_valid: + rigetti_device.validate(circuit) + assert rigetti_device.run_emulator_passes(circuit, apply_noise_model=False) == circuit + else: + with pytest.raises(ValueError): + rigetti_device.validate(circuit) + + +@patch.dict("braket.aws.aws_noise_models.QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) +@patch.object(LocalSimulator, "run") +def test_device_emulate(mock_run, rigetti_device): + circuit = Circuit().h(0).cnot(0, 1) + rigetti_device.emulate(circuit, shots=100) + mock_run.assert_called_once() + + +@patch.dict("braket.aws.aws_noise_models.QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) +@patch.object(AwsDevice, "_setup_emulator", return_value=Emulator()) +def test_get_emulator_multiple(mock_setup, rigetti_device): + emulator = rigetti_device.emulator + assert emulator._emulator_passes == [] + emulator = rigetti_device.emulator + mock_setup.assert_called_once() diff --git a/test/unit_tests/braket/aws/test_aws_noise_models.py b/test/unit_tests/braket/aws/test_aws_noise_models.py deleted file mode 100644 index db3e0e373..000000000 --- a/test/unit_tests/braket/aws/test_aws_noise_models.py +++ /dev/null @@ -1,401 +0,0 @@ -import json -from unittest.mock import Mock - -import numpy as np -import pytest -from common_test_utils import RIGETTI_REGION - -from braket.aws import AwsDevice -from braket.aws.aws_noise_models import ( - QPU_GATE_DURATIONS, - GateDeviceCalibrationData, - GateFidelity, - _setup_calibration_specs, - create_device_noise_model, -) -from braket.circuits import Gate -from braket.circuits.noise_model import GateCriteria, NoiseModel, ObservableCriteria -from braket.circuits.noises import ( - AmplitudeDamping, - BitFlip, - Depolarizing, - PhaseDamping, - TwoQubitDepolarizing, -) -from braket.device_schema.error_mitigation.debias import Debias -from braket.device_schema.ionq import IonqDeviceCapabilities, IonqDeviceParameters -from braket.device_schema.rigetti import RigettiDeviceCapabilities -from braket.devices import Devices - -MOCK_STANDARDIZED_CALIBRATION_JSON = { - "braketSchemaHeader": { - "name": "braket.device_schema.standardized_gate_model_qpu_device_properties", - "version": "1", - }, - "oneQubitProperties": { - "0": { - "T1": {"value": 0.5, "standardError": None, "unit": "S"}, - "T2": {"value": 0.2, "standardError": None, "unit": "S"}, - "oneQubitFidelity": [ - { - "fidelityType": {"name": "RANDOMIZED_BENCHMARKING", "description": None}, - "fidelity": 0.99, - "standardError": 1e-2, - }, - { - "fidelityType": { - "name": "SIMULTANEOUS_RANDOMIZED_BENCHMARKING", - "description": None, - }, - "fidelity": 0.9934, - "standardError": 0.0065, - }, - { - "fidelityType": {"name": "READOUT", "description": None}, - "fidelity": 0.958, - "standardError": None, - }, - ], - }, - "1": { - "T1": {"value": 0.97, "standardError": None, "unit": "S"}, - "T2": {"value": 0.234, "standardError": None, "unit": "S"}, - "oneQubitFidelity": [ - { - "fidelityType": {"name": "RANDOMIZED_BENCHMARKING", "description": None}, - "fidelity": 0.9983, - "standardError": 4e-5, - }, - { - "fidelityType": { - "name": "SIMULTANEOUS_RANDOMIZED_BENCHMARKING", - "description": None, - }, - "fidelity": 0.879, - "standardError": 0.00058, - }, - { - "fidelityType": {"name": "READOUT", "description": None}, - "fidelity": 0.989, - "standardError": None, - }, - ], - }, - "2": { - "T1": {"value": 0.8, "standardError": None, "unit": "S"}, - "T2": {"value": 0.4, "standardError": None, "unit": "S"}, - "oneQubitFidelity": [ - { - "fidelityType": {"name": "READOUT", "description": None}, - "fidelity": 0.958, - "standardError": None, - } - ], - }, - }, - "twoQubitProperties": { - "0-1": { - "twoQubitGateFidelity": [ - { - "direction": None, - "gateName": "CZ", - "fidelity": 0.9358, - "standardError": 0.01437, - "fidelityType": {"name": "INTERLEAVED_RANDOMIZED_BENCHMARKING"}, - }, - { - "direction": None, - "gateName": "Two_Qubit_Clifford", - "fidelity": 0.9, - "standardError": 0.0237, - "fidelityType": {"name": "INTERLEAVED_RANDOMIZED_BENCHMARKING"}, - }, - { - "direction": None, - "gateName": "CPHASE", - "fidelity": 0.9, - "standardError": 0.01437, - "fidelityType": {"name": "INTERLEAVED_RANDOMIZED_BENCHMARKING"}, - }, - ] - } - }, -} - - -MOCK_STANDARDIZED_QPU_CAPABILITIES_JSON_1 = { - "braketSchemaHeader": { - "name": "braket.device_schema.rigetti.rigetti_device_capabilities", - "version": "1", - }, - "service": { - "executionWindows": [ - { - "executionDay": "Everyday", - "windowStartHour": "11:00", - "windowEndHour": "12:00", - } - ], - "shotsRange": [1, 10], - }, - "action": { - "braket.ir.openqasm.program": { - "actionType": "braket.ir.openqasm.program", - "version": ["1"], - "supportedOperations": ["H"], - } - }, - "paradigm": { - "qubitCount": 3, - "nativeGateSet": ["cz", "cphaseshift"], - "connectivity": { - "fullyConnected": False, - "connectivityGraph": {"0": ["1", "2"], "1": ["0"], "2": ["0"]}, - }, - }, - "standardized": MOCK_STANDARDIZED_CALIBRATION_JSON, - "deviceParameters": {}, -} - -MOCK_STANDARDIZED_GATE_MODEL_RIGETTI_CAPABILITIES_1 = RigettiDeviceCapabilities.parse_obj( - MOCK_STANDARDIZED_QPU_CAPABILITIES_JSON_1 -) - - -MOCK_IONQ_GATE_MODEL_CAPABILITIES_JSON_1 = { - "braketSchemaHeader": { - "name": "braket.device_schema.ionq.ionq_device_capabilities", - "version": "1", - }, - "service": { - "executionWindows": [ - { - "executionDay": "Everyday", - "windowStartHour": "11:00", - "windowEndHour": "12:00", - } - ], - "shotsRange": [1, 10], - }, - "action": { - "braket.ir.openqasm.program": { - "actionType": "braket.ir.openqasm.program", - "version": ["1"], - "supportedOperations": ["x", "y"], - } - }, - "paradigm": { - "qubitCount": 2, - "nativeGateSet": ["CZ", "CPhaseShift", "GPI", "Toffoli"], - "connectivity": {"fullyConnected": True, "connectivityGraph": {}}, - }, - "provider": { - "braketSchemaHeader": { - "name": "braket.device_schema.ionq.ionq_provider_properties", - "version": "1", - }, - "errorMitigation": {Debias: {"minimumShots": 2500}}, - "fidelity": {"1Q": {"mean": 0.98}, "2Q": {"mean": 0.9625}, "spam": {"mean": 0.9}}, - "timing": { - "1Q": 0.000135, - "2Q": 0.0006, - "T1": 10.0, - "T2": 1.0, - "readout": 0.0003, - "reset": 2e-05, - }, - }, - "deviceParameters": json.loads(IonqDeviceParameters.schema_json()), -} - -MOCK_IONQ_DEVICE_CAPABILITIES = IonqDeviceCapabilities.parse_obj( - MOCK_IONQ_GATE_MODEL_CAPABILITIES_JSON_1 -) - - -@pytest.fixture -def rigetti_target_noise_model(): - gate_duration_1Q = QPU_GATE_DURATIONS[Devices.Rigetti.AspenM3]["single_qubit_gate_duration"] - target_noise_model = ( - NoiseModel() - .add_noise(AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / 0.5))), GateCriteria(qubits=0)) - .add_noise( - PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / 0.2)))), GateCriteria(qubits=0) - ) - .add_noise(Depolarizing(1 - 0.9934), GateCriteria(qubits=0)) - .add_noise(BitFlip(1 - 0.958), ObservableCriteria(qubits=0)) - .add_noise(AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / 0.97))), GateCriteria(qubits=1)) - .add_noise( - PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / 0.234)))), GateCriteria(qubits=1) - ) - .add_noise(Depolarizing(1 - 0.879), GateCriteria(qubits=1)) - .add_noise(BitFlip(1 - 0.989), ObservableCriteria(qubits=1)) - .add_noise(AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / 0.8))), GateCriteria(qubits=2)) - .add_noise( - PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / 0.4)))), GateCriteria(qubits=2) - ) - .add_noise(BitFlip(1 - 0.958), ObservableCriteria(qubits=2)) - .add_noise(TwoQubitDepolarizing(1 - 0.9358), GateCriteria(Gate.CZ, [(1, 0), (0, 1)])) - .add_noise(TwoQubitDepolarizing(1 - 0.9), GateCriteria(Gate.CPhaseShift, [(1, 0), (0, 1)])) - ) - - return target_noise_model - - -@pytest.fixture -def ionq_target_noise_model(): - T1 = 10.0 - T2 = 1.0 - readout = 0.9 - gate_duration_1Q = 0.000135 - single_rb = 0.98 - two_qubit_rb = 0.9625 - target_noise_model = NoiseModel() - qubit_count = MOCK_IONQ_DEVICE_CAPABILITIES.paradigm.qubitCount - for i in range(qubit_count): - target_noise_model = ( - target_noise_model.add_noise( - AmplitudeDamping(1 - np.exp(-(gate_duration_1Q / T1))), GateCriteria(qubits=i) - ) - .add_noise( - PhaseDamping(0.5 * (1 - np.exp(-(gate_duration_1Q / T2)))), GateCriteria(qubits=i) - ) - .add_noise(Depolarizing(1 - single_rb), GateCriteria(qubits=i)) - .add_noise(BitFlip(1 - readout), ObservableCriteria(qubits=i)) - ) - - for i in range(qubit_count): - for j in range(i, qubit_count): - if i != j: - target_noise_model = target_noise_model.add_noise( - TwoQubitDepolarizing(1 - two_qubit_rb), GateCriteria(Gate.CZ, [(i, j), (j, i)]) - ).add_noise( - TwoQubitDepolarizing(1 - two_qubit_rb), - GateCriteria(Gate.CPhaseShift, [(i, j), (j, i)]), - ) - return target_noise_model - - -def test_standardized_noise_model(rigetti_target_noise_model): - noise_model = create_device_noise_model( - MOCK_STANDARDIZED_GATE_MODEL_RIGETTI_CAPABILITIES_1, Devices.Rigetti.AspenM3 - ) - - assert noise_model.instructions == rigetti_target_noise_model.instructions - - -@pytest.mark.parametrize( - "single_qubit_gate_duration,two_qubit_gate_duration,qubit_labels,\ - single_qubit_specs,two_qubit_edge_specs", - [ - (0.5, 0.2, {0, 1}, {2: {"h": 0.5}}, {(0, 1): GateFidelity(Gate.H, 0.5)}), - (0.5, 0.2, {0, 1}, {0: {"h": 0.5}}, {(0, 2): GateFidelity(Gate.H, 0.5)}), - ], -) -def test_invalid_gate_calibration_data( - single_qubit_gate_duration, - two_qubit_gate_duration, - qubit_labels, - single_qubit_specs, - two_qubit_edge_specs, -): - with pytest.raises(ValueError): - GateDeviceCalibrationData( - single_qubit_gate_duration, - two_qubit_gate_duration, - qubit_labels, - single_qubit_specs, - two_qubit_edge_specs, - ) - - -def test_missing_gate_durations(): - with pytest.raises(ValueError): - _setup_calibration_specs(MOCK_STANDARDIZED_GATE_MODEL_RIGETTI_CAPABILITIES_1, "bad_arn") - - -def test_ionq_noise_model(ionq_target_noise_model): - noise_model = create_device_noise_model(MOCK_IONQ_DEVICE_CAPABILITIES, Devices.IonQ.Aria1) - assert noise_model.instructions == ionq_target_noise_model.instructions - - -MOCK_DEFAULT_S3_DESTINATION_FOLDER = ( - "amazon-braket-us-test-1-00000000", - "tasks", -) - -MOCK_GATE_MODEL_QPU_1 = { - "deviceName": "Aspen-M3", - "deviceType": "QPU", - "providerName": "Rigetti", - "deviceStatus": "OFFLINE", - "deviceCapabilities": MOCK_STANDARDIZED_GATE_MODEL_RIGETTI_CAPABILITIES_1.json(), - "deviceQueueInfo": [ - {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "19", "queuePriority": "Normal"}, - {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "3", "queuePriority": "Priority"}, - {"queue": "JOBS_QUEUE", "queueSize": "0 (3 prioritized job(s) running)"}, - ], -} - -MOCK_GATE_MODEL_QPU_2 = { - "deviceName": "Aria-1", - "deviceType": "QPU", - "providerName": "IonQ", - "deviceStatus": "OFFLINE", - "deviceCapabilities": MOCK_IONQ_DEVICE_CAPABILITIES.json(), - "deviceQueueInfo": [ - {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "19", "queuePriority": "Normal"}, - {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "3", "queuePriority": "Priority"}, - {"queue": "JOBS_QUEUE", "queueSize": "0 (3 prioritized job(s) running)"}, - ], -} - - -@pytest.fixture -def aws_session(): - _boto_session = Mock() - _boto_session.region_name = RIGETTI_REGION - _boto_session.profile_name = "test-profile" - - creds = Mock() - creds.method = "other" - _boto_session.get_credentials.return_value = creds - - _aws_session = Mock() - _aws_session.boto_session = _boto_session - _aws_session._default_bucket = MOCK_DEFAULT_S3_DESTINATION_FOLDER[0] - _aws_session.default_bucket.return_value = _aws_session._default_bucket - _aws_session._custom_default_bucket = False - _aws_session.account_id = "00000000" - _aws_session.region = RIGETTI_REGION - return _aws_session - - -@pytest.fixture( - params=[ - "arn:aws:braket:us-west-1::device/quantum-simulator/amazon/sim", - "arn:aws:braket:::device/quantum-simulator/amazon/sim", - ] -) -def arn(request): - return request.param - - -@pytest.fixture -def rigetti_device(aws_session): - def _device(arn): - aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - aws_session.search_devices.return_value = [MOCK_GATE_MODEL_QPU_1] - return AwsDevice(arn, aws_session) - - return _device - - -@pytest.fixture -def ionq_device(aws_session): - def _device(arn): - aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_2 - aws_session.search_devices.return_value = [MOCK_GATE_MODEL_QPU_2] - return AwsDevice(arn, aws_session) - - return _device diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 216d161c7..e53d6b9f4 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import json +import sys import textwrap import warnings from typing import Any, Optional @@ -386,20 +387,25 @@ def properties(self) -> DeviceCapabilities: return RydbergSimulatorDeviceCapabilities.parse_obj(properties) -mock_circuit_entry = Mock() -mock_program_entry = Mock() -mock_jaqcd_entry = Mock() -mock_circuit_dm_entry = Mock() -mock_circuit_entry.load.return_value = DummyCircuitSimulator -mock_program_entry.load.return_value = DummyProgramSimulator -mock_jaqcd_entry.load.return_value = DummyJaqcdSimulator -mock_circuit_dm_entry.load.return_value = DummyProgramDensityMatrixSimulator -local_simulator._simulator_devices = { - "dummy": mock_circuit_entry, - "dummy_oq3": mock_program_entry, - "dummy_jaqcd": mock_jaqcd_entry, - "dummy_oq3_dm": mock_circuit_dm_entry, -} +@pytest.fixture(autouse=True) +def _simulator_devices(request): + if request.module == sys.modules[__name__]: + mock_circuit_entry = Mock() + mock_program_entry = Mock() + mock_jaqcd_entry = Mock() + mock_circuit_dm_entry = Mock() + mock_circuit_entry.load.return_value = DummyCircuitSimulator + mock_program_entry.load.return_value = DummyProgramSimulator + mock_jaqcd_entry.load.return_value = DummyJaqcdSimulator + mock_circuit_dm_entry.load.return_value = DummyProgramDensityMatrixSimulator + local_simulator._simulator_devices = { + "dummy": mock_circuit_entry, + "dummy_oq3": mock_program_entry, + "dummy_jaqcd": mock_jaqcd_entry, + "dummy_oq3_dm": mock_circuit_dm_entry, + } + return local_simulator._simulator_devices + mock_ahs_program = AnalogHamiltonianSimulation( register=AtomArrangement(), hamiltonian=Hamiltonian() diff --git a/test/unit_tests/braket/emulation/test_emulator.py b/test/unit_tests/braket/emulation/test_emulator.py index c21c0b21b..b0a2852ad 100644 --- a/test/unit_tests/braket/emulation/test_emulator.py +++ b/test/unit_tests/braket/emulation/test_emulator.py @@ -31,6 +31,16 @@ def run(self, program: ProgramType): {"default": mock_circuit_entry, "braket_dm": mock_circuit_dm_entry} ) +# @pytest.fixture(autouse=True) +# def _setup_simulator_devices(): +# mock_circuit_entry = Mock() +# mock_circuit_dm_entry = Mock() +# mock_circuit_entry.load.return_value = StateVectorSimulator +# mock_circuit_dm_entry.load.return_value = DensityMatrixSimulator +# local_simulator._simulator_devices.update( +# {"default": mock_circuit_entry, "braket_dm": mock_circuit_dm_entry} +# ) + @pytest.fixture def basic_emulator(): @@ -163,7 +173,9 @@ def test_noisy_run(): qubit_count_criterion = QubitCountCriterion(4) gate_criterion = GateCriterion(supported_gates=["H"]) emulator = Emulator( - emulator_passes=[qubit_count_criterion, gate_criterion], noise_model=noise_model + backend="braket_dm", + emulator_passes=[qubit_count_criterion, gate_criterion], + noise_model=noise_model, ) circuit = Circuit().h(0) diff --git a/test/unit_tests/braket/emulation/test_gate_criterion.py b/test/unit_tests/braket/emulation/test_gate_criterion.py index bf8ce5329..b7ad2d2ba 100644 --- a/test/unit_tests/braket/emulation/test_gate_criterion.py +++ b/test/unit_tests/braket/emulation/test_gate_criterion.py @@ -68,7 +68,8 @@ def mock_qpu_gates(): .add_verbatim_box( Circuit().prx(0, np.pi / 4, np.pi / 4).apply_gate_noise(BitFlip(0.1), Gate.PRx) ) - .v(1), + .v(1) + .apply_gate_noise(BitFlip(0.1), Gate.V), ], ) def test_valid_circuits(mock_qpu_gates, circuit): From 1e16ba1cbe44431b30d4369435d9fcf9a3904d73 Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 15 Jul 2024 13:01:16 -0700 Subject: [PATCH 72/91] change: Change task_specification type in validate documentation --- src/braket/aws/aws_device.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index b6bb0f15b..7fd458710 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -916,14 +916,7 @@ def _setup_emulator(self) -> Emulator: def validate( self, - task_specification: Union[ - Circuit, - Problem, - OpenQasmProgram, - BlackbirdProgram, - PulseSequence, - AnalogHamiltonianSimulation, - ], + task_specification: Circuit, ) -> None: """ Runs all non-modifying emulator passes on the input program and raises an @@ -931,8 +924,7 @@ def validate( program meets all criterion, returns. Args: - task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, - PulseSequence, AnalogHamiltonianSimulation]): The quantum program to emulate against + task_specification (Circuit): The quantum program to emulate against this AwsDevice device properties. """ From fdfa8c0df61cb1232b71c61a3b643cd4a49245b0 Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 15 Jul 2024 13:50:29 -0700 Subject: [PATCH 73/91] fix: Use explicit ProgramType as Python3.9 does not support type parameter lists --- src/braket/aws/aws_device.py | 7 ++++--- src/braket/emulators/emulator_interface.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 7fd458710..229fc1a6c 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -49,6 +49,7 @@ 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.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram from braket.parametric.free_parameter import FreeParameter @@ -931,9 +932,9 @@ def validate( self.emulator.run_validation_passes(task_specification) return - def run_emulator_passes[ - ProgramType - ](self, task_specification: ProgramType, apply_noise_model: bool = True) -> ProgramType: + def run_emulator_passes( + self, task_specification: ProgramType, apply_noise_model: bool = True + ) -> ProgramType: """ Runs all emulator passes and returns the modified program, which should be the same type as the input program. diff --git a/src/braket/emulators/emulator_interface.py b/src/braket/emulators/emulator_interface.py index 7a079c3d4..47a163285 100644 --- a/src/braket/emulators/emulator_interface.py +++ b/src/braket/emulators/emulator_interface.py @@ -3,7 +3,7 @@ from abc import ABC from typing import Iterable, Union -from braket.emulators.emulator_passes import EmulatorPass +from braket.emulators.emulator_passes import EmulatorPass, ProgramType from braket.emulators.emulator_passes.criteria import EmulatorCriterion @@ -11,7 +11,7 @@ class EmulatorInterface(ABC): def __init__(self, emulator_passes: Iterable[EmulatorPass] = None): self._emulator_passes = emulator_passes if emulator_passes is not None else [] - def run_program_passes[ProgramType](self, task_specification: ProgramType) -> ProgramType: + def run_program_passes(self, task_specification: ProgramType) -> ProgramType: """ This method passes the input program through the EmulatorPasses contained within this emulator. An emulator pass may simply validate a program or may @@ -27,7 +27,7 @@ def run_program_passes[ProgramType](self, task_specification: ProgramType) -> Pr task_specification = emulator_pass(task_specification) return task_specification - def run_validation_passes[ProgramType](self, task_specification: ProgramType) -> None: + def run_validation_passes(self, task_specification: ProgramType) -> None: """ This method passes the input program through EmulatorPasses that perform only validation, without modifying the input program. From 20c59a2d19056185925c7458fb75b63889428d62 Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 15 Jul 2024 14:30:02 -0700 Subject: [PATCH 74/91] test: Add test for IonQ create_noise_model dispatch for native gates not supported by Braket --- src/braket/aws/aws_noise_models.py | 5 ----- test/unit_tests/braket/aws/test_aws_emulation.py | 6 ++++-- test/unit_tests/braket/emulation/test_emulator.py | 10 ---------- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/braket/aws/aws_noise_models.py b/src/braket/aws/aws_noise_models.py index 8562d827e..cd6e246e5 100644 --- a/src/braket/aws/aws_noise_models.py +++ b/src/braket/aws/aws_noise_models.py @@ -1,4 +1,3 @@ -import logging from dataclasses import dataclass from functools import singledispatch from typing import Dict, List, Set, Tuple, Union @@ -177,10 +176,6 @@ def _(properties: IonqDeviceCapabilities, arn: str) -> NoiseModel: """ continue native_gate_fidelities.append(GateFidelity(gate, two_qubit_rb_fidelity)) - else: - continue - logging.warning(f"Unsupported gate {native_gate}") - single_qubit_specs = {} two_qubit_edge_specs = {} for ii in range(qubit_count): diff --git a/test/unit_tests/braket/aws/test_aws_emulation.py b/test/unit_tests/braket/aws/test_aws_emulation.py index 534dc1ea8..e0e18af98 100644 --- a/test/unit_tests/braket/aws/test_aws_emulation.py +++ b/test/unit_tests/braket/aws/test_aws_emulation.py @@ -178,7 +178,7 @@ def basic_device_capabilities(): } MOCK_STANDARDIZED_CALIBRATION_JSON_2 = MOCK_STANDARDIZED_CALIBRATION_JSON.copy() -MOCK_STANDARDIZED_CALIBRATION_JSON["twoQubitProperties"]["0-1"]["twoQubitGateFidelity"][2][ +MOCK_STANDARDIZED_CALIBRATION_JSON_2["twoQubitProperties"]["0-1"]["twoQubitGateFidelity"][2][ "gateName" ] = "CPhaseShift" @@ -212,7 +212,7 @@ def basic_device_capabilities(): "connectivityGraph": {"0": ["1", "2"], "1": ["0"], "2": ["0"]}, }, }, - "standardized": MOCK_STANDARDIZED_CALIBRATION_JSON, + "standardized": MOCK_STANDARDIZED_CALIBRATION_JSON_2, "deviceParameters": {}, } @@ -443,6 +443,8 @@ def test_missing_gate_durations(rigetti_device_capabilities): def test_ionq_noise_model(ionq_device_capabilities, ionq_target_noise_model): + # modify capabilities to include gate not supported by braket but included in IonQ capabilities. + ionq_device_capabilities.paradigm.nativeGateSet.append("Two_Qubit_Clifford") noise_model = create_device_noise_model(ionq_device_capabilities, Devices.IonQ.Aria1) assert noise_model.instructions == ionq_target_noise_model.instructions diff --git a/test/unit_tests/braket/emulation/test_emulator.py b/test/unit_tests/braket/emulation/test_emulator.py index b0a2852ad..ca1a24e71 100644 --- a/test/unit_tests/braket/emulation/test_emulator.py +++ b/test/unit_tests/braket/emulation/test_emulator.py @@ -31,16 +31,6 @@ def run(self, program: ProgramType): {"default": mock_circuit_entry, "braket_dm": mock_circuit_dm_entry} ) -# @pytest.fixture(autouse=True) -# def _setup_simulator_devices(): -# mock_circuit_entry = Mock() -# mock_circuit_dm_entry = Mock() -# mock_circuit_entry.load.return_value = StateVectorSimulator -# mock_circuit_dm_entry.load.return_value = DensityMatrixSimulator -# local_simulator._simulator_devices.update( -# {"default": mock_circuit_entry, "braket_dm": mock_circuit_dm_entry} -# ) - @pytest.fixture def basic_emulator(): From 073fb4e7aa17c9e1bccd5310955c2e4a6eae3c7b Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 15 Jul 2024 15:13:25 -0700 Subject: [PATCH 75/91] fix: Place local_simulator._simulator_device setup in a fixture rather than in global setup --- .../braket/emulation/test_emulator.py | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/test/unit_tests/braket/emulation/test_emulator.py b/test/unit_tests/braket/emulation/test_emulator.py index ca1a24e71..16f2b9aed 100644 --- a/test/unit_tests/braket/emulation/test_emulator.py +++ b/test/unit_tests/braket/emulation/test_emulator.py @@ -23,23 +23,29 @@ def run(self, program: ProgramType): raise ValueError("This pass always raises an error.") -mock_circuit_entry = Mock() -mock_circuit_dm_entry = Mock() -mock_circuit_entry.load.return_value = StateVectorSimulator -mock_circuit_dm_entry.load.return_value = DensityMatrixSimulator -local_simulator._simulator_devices.update( - {"default": mock_circuit_entry, "braket_dm": mock_circuit_dm_entry} -) +@pytest.fixture +def setup_local_simulator_devices(): + mock_circuit_entry = Mock() + mock_circuit_dm_entry = Mock() + mock_circuit_entry.load.return_value = StateVectorSimulator + mock_circuit_dm_entry.load.return_value = DensityMatrixSimulator + _simulator_devices = {"default": mock_circuit_entry, "braket_dm": mock_circuit_dm_entry} + local_simulator._simulator_devices.update(_simulator_devices) + + +@pytest.fixture +def empty_emulator(setup_local_simulator_devices): + return Emulator() @pytest.fixture -def basic_emulator(): +def basic_emulator(empty_emulator): qubit_count_criterion = QubitCountCriterion(4) - return Emulator(emulator_passes=[qubit_count_criterion]) + return empty_emulator.add_pass(emulator_pass=[qubit_count_criterion]) -def test_empty_emulator_validation(): - emulator = Emulator() +def test_empty_emulator_validation(empty_emulator): + emulator = empty_emulator circuit = Circuit().h(0).cnot(0, 1) emulator.run_validation_passes(circuit) @@ -66,21 +72,21 @@ def test_basic_invalidate(basic_emulator): basic_emulator.run_program_passes(circuit) -def test_add_pass_single(): - emulator = Emulator() +def test_add_pass_single(empty_emulator): + emulator = empty_emulator qubit_count_criterion = QubitCountCriterion(4) emulator.add_pass(qubit_count_criterion) assert emulator._emulator_passes == [qubit_count_criterion] -def test_bad_add_pass(): - emulator = Emulator() +def test_bad_add_pass(empty_emulator): + emulator = empty_emulator with pytest.raises(TypeError): emulator.add_pass(None) -def test_add_pass_multiple(): +def test_add_pass_multiple(setup_local_simulator_devices): native_gate_criterion = GateCriterion(native_gates=["CZ", "PRx"]) emulator = Emulator(emulator_passes=[native_gate_criterion]) qubit_count_criterion = QubitCountCriterion(4) @@ -94,14 +100,14 @@ def test_add_pass_multiple(): ] -def test_use_correct_backend_if_noise_model(): +def test_use_correct_backend_if_noise_model(setup_local_simulator_devices): noise_model = NoiseModel() emulator = Emulator(noise_model=noise_model) assert emulator._backend.name == "DensityMatrixSimulator" -def test_update_noise_model(): - emulator = Emulator() +def test_update_noise_model(empty_emulator): + emulator = empty_emulator assert emulator._backend.name == "StateVectorSimulator" noise_model = NoiseModel() noise_model.add_noise(BitFlip(0.1), GateCriteria(Gate.H())) @@ -112,7 +118,7 @@ def test_update_noise_model(): assert emulator.noise_model == noise_model -def test_validation_only_pass(): +def test_validation_only_pass(setup_local_simulator_devices): qubit_count_criterion = QubitCountCriterion(4) bad_pass = AlwaysFailPass() emulator = Emulator(emulator_passes=[bad_pass, qubit_count_criterion]) @@ -126,7 +132,7 @@ def test_validation_only_pass(): emulator.run_validation_passes(circuit) -def test_apply_noise_model(): +def test_apply_noise_model(setup_local_simulator_devices): noise_model = NoiseModel() noise_model.add_noise(BitFlip(0.1), GateCriteria(Gate.H)) emulator = Emulator(noise_model=noise_model) @@ -144,7 +150,7 @@ def test_apply_noise_model(): assert circuit == target_circ -def test_noiseless_run(): +def test_noiseless_run(setup_local_simulator_devices): qubit_count_criterion = QubitCountCriterion(4) gate_criterion = GateCriterion(supported_gates=["H"]) emulator = Emulator(emulator_passes=[qubit_count_criterion, gate_criterion]) @@ -156,7 +162,7 @@ def test_noiseless_run(): assert all(np.isclose(state_vector, target_state_vector)) -def test_noisy_run(): +def test_noisy_run(setup_local_simulator_devices): noise_model = NoiseModel() noise_model.add_noise(BitFlip(0.1), GateCriteria(Gate.H)) From 3c9fd6731e5049362575e125163cb98cfa1ae6b5 Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 15 Jul 2024 15:40:03 -0700 Subject: [PATCH 76/91] fix: Compare arn strings against explicit enum string values --- src/braket/aws/aws_device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 229fc1a6c..e7deee418 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -885,7 +885,7 @@ def emulator(self) -> Emulator: Returns: Emulator: An emulator for this device, if this is not a simulator device. """ - if self._arn in Devices.Amazon: + if self._arn in [simulator_enum.value for simulator_enum in Devices.Amazon]: raise ValueError( "Creating an emulator from a Braket managed simulator is not supported." ) From 0601c63dfc91f268b64a0d42dfeecb3ddeb70e21 Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 18 Jul 2024 09:40:51 -0700 Subject: [PATCH 77/91] change: Update documentation, add check to GateConnectivityCriterion to ensure user-provided undirected graphs have symmetric forwards/backwards edges --- src/braket/aws/aws_device.py | 15 ++++---- src/braket/aws/aws_emulator_helpers.py | 23 ++++++----- src/braket/emulators/emulator.py | 2 +- .../criteria/connectivity_criterion.py | 38 ++++++++++--------- .../criteria/emulator_criterion.py | 14 +++++-- .../criteria/gate_connectivity_criterion.py | 16 +++++--- .../criteria/gate_criterion.py | 12 ++++-- .../braket/aws/test_aws_emulation.py | 2 +- .../test_gate_connectivity_criterion.py | 6 +-- .../braket/emulation/test_gate_criterion.py | 18 +++++++-- 10 files changed, 89 insertions(+), 57 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index e7deee418..dd5706f68 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -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) @@ -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( @@ -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 @@ -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 @@ -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. diff --git a/src/braket/aws/aws_emulator_helpers.py b/src/braket/aws/aws_emulator_helpers.py index 000509730..b703f77e2 100644 --- a/src/braket/aws/aws_emulator_helpers.py +++ b/src/braket/aws/aws_emulator_helpers.py @@ -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. @@ -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) @@ -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: @@ -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 @@ -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" @@ -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) diff --git a/src/braket/emulators/emulator.py b/src/braket/emulators/emulator.py index 7352260e8..b321d1969 100644 --- a/src/braket/emulators/emulator.py +++ b/src/braket/emulators/emulator.py @@ -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 diff --git a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py index 7385587f2..1c47b2616 100644 --- a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py @@ -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, @@ -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." diff --git a/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py b/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py index 23cad5ff0..a55d6c1e5 100644 --- a/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py @@ -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 diff --git a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py index 449af1431..1035a5333 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py @@ -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( + 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() @@ -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( diff --git a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py index 54eb46f7a..a332c5c14 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py @@ -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: """ diff --git a/test/unit_tests/braket/aws/test_aws_emulation.py b/test/unit_tests/braket/aws/test_aws_emulation.py index e0e18af98..4a72dcef8 100644 --- a/test/unit_tests/braket/aws/test_aws_emulation.py +++ b/test/unit_tests/braket/aws/test_aws_emulation.py @@ -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": { diff --git a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py index db509e5a1..c01d10b03 100644 --- a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py +++ b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py @@ -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 @@ -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"]}), ], @@ -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): diff --git a/test/unit_tests/braket/emulation/test_gate_criterion.py b/test/unit_tests/braket/emulation/test_gate_criterion.py index b7ad2d2ba..9f85969d6 100644 --- a/test/unit_tests/braket/emulation/test_gate_criterion.py +++ b/test/unit_tests/braket/emulation/test_gate_criterion.py @@ -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) From c5b9fc37c7b4b02eb7851efe290b866f68b71ac9 Mon Sep 17 00:00:00 2001 From: Nagji Date: Thu, 18 Jul 2024 10:41:34 -0700 Subject: [PATCH 78/91] change: Clean up GateConnectivityCriterion instantiation logic. --- .../criteria/gate_connectivity_criterion.py | 62 +++++++++---------- .../test_gate_connectivity_criterion.py | 25 +++++++- 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py index 1035a5333..77085a50a 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py @@ -17,40 +17,12 @@ def __init__( directed=True, ): super().__init__() - if isinstance(gate_connectivity_graph, DiGraph): - self._gate_connectivity_graph = gate_connectivity_graph - if not directed: - for u, v in self._gate_connectivity_graph.edges: - back_edge = (v, u) - if back_edge not in self._gate_connectivity_graph.edges: - supported_gates = self._gate_connectivity_graph[u][v]["supported_gates"] - 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( - f"Connectivity Graph marked as undirected\ - but edges ({u}, {v}) and ({v}, {u}) have different supported\ - gate sets." - ) - - elif isinstance(gate_connectivity_graph, dict): + if isinstance(gate_connectivity_graph, dict): self._gate_connectivity_graph = DiGraph() - for edge, supported_gates in gate_connectivity_graph.items(): - self._gate_connectivity_graph.add_edge( - edge[0], edge[1], supported_gates=supported_gates - ) - if not directed: - back_edge = (edge[1], edge[0]) - if back_edge not in gate_connectivity_graph: - self._gate_connectivity_graph.add_edge( - edge[1], edge[0], supported_gates=supported_gates - ) + for (u, v), supported_gates in gate_connectivity_graph.items(): + self._gate_connectivity_graph.add_edge(u, v, supported_gates=supported_gates) + elif isinstance(gate_connectivity_graph, DiGraph): + self._gate_connectivity_graph = gate_connectivity_graph else: raise TypeError( "Gate_connectivity_graph must either be a dictionary of edges mapped to \ @@ -58,6 +30,30 @@ def __init__( provided as edge attributes." ) + if not directed: + """ + Add reverse edges and check that any supplied reverse edges have + identical supported gate sets to their corresponding forwards edge. + """ + for u, v in self._gate_connectivity_graph.edges: + back_edge = (v, u) + if back_edge not in self._gate_connectivity_graph.edges: + supported_gates = self._gate_connectivity_graph[u][v]["supported_gates"] + 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( + f"Connectivity Graph marked as undirected\ + but edges ({u}, {v}) and ({v}, {u}) have different supported\ + gate sets." + ) + def validate(self, circuit: Circuit) -> None: """ Verifies that any multiqubit gates used within a verbatim box are supported diff --git a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py index c01d10b03..c0575c3e8 100644 --- a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py +++ b/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py @@ -155,7 +155,7 @@ def test_undirected_graph_construction_from_dict(): """ dict_representation = { (0, 1): ["CNot", "CZ"], - (1, 0): ["CZ", "XX"], + (1, 0): ["CNot", "CZ"], (1, 2): ["Swap", "CNot", "YY"], (0, 2): ["XX", "XY", "CNot", "CZ"], (2, 5): ["XX", "XY", "CNot", "CZ"], @@ -167,7 +167,7 @@ def test_undirected_graph_construction_from_dict(): (1, 2, {"supported_gates": ["Swap", "CNot", "YY"]}), (0, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), (2, 5, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), - (1, 0, {"supported_gates": ["CZ", "XX"]}), + (1, 0, {"supported_gates": ["CNot", "CZ"]}), (2, 1, {"supported_gates": ["Swap", "CNot", "YY"]}), (2, 0, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), (5, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), @@ -289,3 +289,24 @@ def test_validate_instruction_method(gate_name, controls, targets, is_valid, bas else: with pytest.raises(ValueError): gcc.validate_instruction_connectivity(gate_name, controls, targets) + + +@pytest.mark.parametrize( + "graph", + [ + ( + nx.from_dict_of_dicts( + { + 0: {1: {"supported_gates": ["cnot", "cz"]}}, + 1: {0: {"supported_gates": ["cz", "cnot", "xx"]}}, + }, + create_using=nx.DiGraph(), + ) + ), + ({(0, 1): ["cnot", "cz"], (1, 0): ["cz", "cnot", "xx"]}), + ({(0, 1): ["xx", "yy"], (1, 0): ["yy", "xx"]}), + ], +) +def test_invalid_undirected_graph(graph): + with pytest.raises(ValueError): + GateConnectivityCriterion(graph, directed=False) From 34b94ae2920582c9548a048e3818b2bd90cc0857 Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 22 Jul 2024 13:50:28 -0700 Subject: [PATCH 79/91] change: Add Raises documentation and make dispatched functions private in aws_emulator_helpers; general documentation cleanup. --- src/braket/aws/aws_device.py | 26 +++++------ src/braket/aws/aws_emulator_helpers.py | 38 +++++++++++----- src/braket/aws/aws_noise_models.py | 20 ++++++--- src/braket/emulators/emulator.py | 10 ++--- src/braket/emulators/emulator_interface.py | 8 +++- .../criteria/connectivity_criterion.py | 44 +++++++++++++------ .../criteria/emulator_criterion.py | 12 +++++ .../criteria/gate_connectivity_criterion.py | 7 +++ .../criteria/gate_criterion.py | 12 ++++- .../criteria/qubit_count_criterion.py | 3 ++ .../emulator_passes/emulator_pass.py | 8 +++- .../braket/aws/test_aws_emulation.py | 22 +++++----- .../braket/emulation/test_emulator.py | 12 ++--- 13 files changed, 149 insertions(+), 73 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index dd5706f68..530c67186 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -28,12 +28,12 @@ from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem from braket.aws.aws_emulator_helpers import ( - create_connectivity_criterion, - create_gate_connectivity_criterion, - create_gate_criterion, - create_qubit_count_criterion, + connectivity_criterion, + gate_connectivity_criterion, + gate_criterion, + qubit_count_criterion, ) -from braket.aws.aws_noise_models import create_device_noise_model +from braket.aws.aws_noise_models import device_noise_model from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_quantum_task_batch import AwsQuantumTaskBatch from braket.aws.aws_session import AwsSession @@ -903,17 +903,15 @@ def _setup_emulator(self) -> Emulator: Emulator: An emulator with a noise model, compilation passes, and validation passes based on this device's properites. """ - emulator_noise_model = create_device_noise_model(self.properties, self._arn) + emulator_noise_model = device_noise_model(self.properties, self._arn) self._emulator = Emulator( noise_model=emulator_noise_model, backend="braket_dm", name=self._name ) - self._emulator.add_pass(create_qubit_count_criterion(self.properties)) - self._emulator.add_pass(create_gate_criterion(self.properties)) - self._emulator.add_pass(create_connectivity_criterion(self.properties, self.topology_graph)) - self._emulator.add_pass( - create_gate_connectivity_criterion(self.properties, self.topology_graph) - ) + 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)) return self._emulator def validate( @@ -930,7 +928,7 @@ def validate( this AwsDevice device properties. """ - self.emulator.run_validation_passes(task_specification) + self.emulator.validate(task_specification) return def run_emulator_passes( @@ -952,7 +950,7 @@ def run_emulator_passes( operations to mimic noise on this device. """ task_specification = task_specification.copy() - return self.emulator.run_program_passes(task_specification, apply_noise_model) + return self.emulator.run_passes(task_specification, apply_noise_model) def emulate( self, diff --git a/src/braket/aws/aws_emulator_helpers.py b/src/braket/aws/aws_emulator_helpers.py index b703f77e2..719c6bd34 100644 --- a/src/braket/aws/aws_emulator_helpers.py +++ b/src/braket/aws/aws_emulator_helpers.py @@ -16,7 +16,7 @@ ) -def create_qubit_count_criterion(properties: DeviceCapabilities) -> QubitCountCriterion: +def qubit_count_criterion(properties: DeviceCapabilities) -> QubitCountCriterion: """ Create a QubitCountCriterion 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. @@ -33,7 +33,7 @@ def create_qubit_count_criterion(properties: DeviceCapabilities) -> QubitCountCr return QubitCountCriterion(qubit_count) -def create_gate_criterion(properties: DeviceCapabilities) -> GateCriterion: +def gate_criterion(properties: DeviceCapabilities) -> GateCriterion: """ Create a GateCriterion pass which defines what supported and native gates are allowed in a program based on the provided device properties. @@ -53,8 +53,7 @@ def create_gate_criterion(properties: DeviceCapabilities) -> GateCriterion: return GateCriterion(supported_gates=supported_gates, native_gates=native_gates) -@singledispatch -def create_connectivity_criterion( +def connectivity_criterion( properties: DeviceCapabilities, connectivity_graph: DiGraph ) -> ConnectivityCriterion: """ @@ -71,11 +70,20 @@ def create_connectivity_criterion( ConnectivityCriterion: 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) + + +@singledispatch +def _connectivity_criterion( + properties: DeviceCapabilities, connectivity_graph: DiGraph +) -> ConnectivityCriterion: + connectivity_criterion = ConnectivityCriterion(connectivity_graph) return connectivity_criterion -@create_connectivity_criterion.register(IqmDeviceCapabilities) +@_connectivity_criterion.register(IqmDeviceCapabilities) def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> ConnectivityCriterion: """ IQM qubit connectivity is undirected but the directed graph that represents qubit connectivity @@ -88,15 +96,21 @@ def _(properties: IqmDeviceCapabilities, connectivity_graph: DiGraph) -> Connect return ConnectivityCriterion(connectivity_graph) +def gate_connectivity_criterion( + properties: DeviceCapabilities, connectivity_graph: DiGraph +) -> GateConnectivityCriterion: + return _gate_connectivity_criterion(properties, connectivity_graph) + + @singledispatch -def create_gate_connectivity_criterion( +def _gate_connectivity_criterion( properties: DeviceCapabilities, connectivity_graph: DiGraph ) -> GateConnectivityCriterion: raise NotImplementedError -@create_gate_connectivity_criterion.register(IqmDeviceCapabilities) -@create_gate_connectivity_criterion.register(RigettiDeviceCapabilities) +@_gate_connectivity_criterion.register(IqmDeviceCapabilities) +@_gate_connectivity_criterion.register(RigettiDeviceCapabilities) def _( properties: RigettiDeviceCapabilities, connectivity_graph: DiGraph ) -> GateConnectivityCriterion: @@ -116,7 +130,7 @@ def _( if not edge_property: gate_connectivity_graph[u][v]["supported_gates"] = set() continue - edge_supported_gates = get_qpu_gate_translation( + edge_supported_gates = _get_qpu_gate_translations( properties, [property.gateName for property in edge_property.twoQubitGateFidelity] ) gate_connectivity_graph[u][v]["supported_gates"] = set(edge_supported_gates) @@ -134,7 +148,7 @@ def _( return GateConnectivityCriterion(gate_connectivity_graph) -@create_gate_connectivity_criterion.register(IonqDeviceCapabilities) +@_gate_connectivity_criterion.register(IonqDeviceCapabilities) def _(properties: IonqDeviceCapabilities, connectivity_graph: DiGraph) -> GateConnectivityCriterion: """ Qubits in IonQ's trapped ion devices are all fully connected with identical @@ -143,7 +157,7 @@ def _(properties: IonqDeviceCapabilities, connectivity_graph: DiGraph) -> GateCo We extrapolate gate connectivity across all possible qubit edge pairs. """ gate_connectivity_graph = connectivity_graph.copy() - native_gates = get_qpu_gate_translation(properties, properties.paradigm.nativeGateSet) + native_gates = _get_qpu_gate_translations(properties, properties.paradigm.nativeGateSet) for edge in gate_connectivity_graph.edges: gate_connectivity_graph[edge[0]][edge[1]]["supported_gates"] = set(native_gates) @@ -151,7 +165,7 @@ def _(properties: IonqDeviceCapabilities, connectivity_graph: DiGraph) -> GateCo return GateConnectivityCriterion(gate_connectivity_graph) -def get_qpu_gate_translation( +def _get_qpu_gate_translations( properties: DeviceCapabilities, gate_name: Union[str, Iterable[str]] ) -> Union[str, list[str]]: """Returns the translated gate name(s) for a given QPU device capabilities schema type diff --git a/src/braket/aws/aws_noise_models.py b/src/braket/aws/aws_noise_models.py index cd6e246e5..8f795528e 100644 --- a/src/braket/aws/aws_noise_models.py +++ b/src/braket/aws/aws_noise_models.py @@ -4,7 +4,7 @@ import numpy as np -from braket.aws.aws_emulator_helpers import get_qpu_gate_translation +from braket.aws.aws_emulator_helpers import _get_qpu_gate_translations from braket.circuits import Gate from braket.circuits.noise_model import GateCriteria, NoiseModel, ObservableCriteria from braket.circuits.noises import ( @@ -29,7 +29,7 @@ The following gate duration values are not available through Braket device calibration data and must be hardcoded. """ -QPU_GATE_DURATIONS = { +_QPU_GATE_DURATIONS = { Devices.Rigetti.AspenM3: { "single_qubit_gate_duration": 40e-9, "two_qubit_gate_duration": 240e-9, @@ -56,6 +56,10 @@ def _validate_single_qubit_specs(self) -> None: """ Checks single qubit specs and the input qubit labels are compatible with one another. + + Raises: + ValueError: If a qubit in the single-qubit calibration data is not mentioned in the + provided qubit labels. """ for qubit in self.single_qubit_specs.keys(): if qubit not in self.qubit_labels: @@ -65,6 +69,10 @@ def _validate_two_qubit_specs(self) -> None: """ Checks that the qubit edge specs and the input qubit labels are compatible with one another. + + Raises: + ValueError: If a qubit in the two-qubit calibration data is not mentioned in the + provided qubit labels. """ for edge in self.two_qubit_edge_specs.keys(): if edge[0] not in self.qubit_labels or edge[1] not in self.qubit_labels: @@ -75,7 +83,7 @@ def __post_init__(self): self._validate_two_qubit_specs() -def create_device_noise_model(properties: DeviceCapabilities, arn: str) -> NoiseModel: +def device_noise_model(properties: DeviceCapabilities, arn: str) -> NoiseModel: """ Create a device-specific noise model using the calibration data provided in the device properties object for a QPU. @@ -107,7 +115,7 @@ def _setup_calibration_specs(properties: DeviceCapabilities, arn: str) -> NoiseM @_setup_calibration_specs.register(RigettiDeviceCapabilities) @_setup_calibration_specs.register(IqmDeviceCapabilities) def _(properties: Union[RigettiDeviceCapabilities, IqmDeviceCapabilities], arn: str) -> NoiseModel: - gate_durations = QPU_GATE_DURATIONS.get(arn, None) + gate_durations = _QPU_GATE_DURATIONS.get(arn, None) if not gate_durations: raise ValueError(f"Gate durations are not available for device {arn}") single_qubit_gate_duration = gate_durations["single_qubit_gate_duration"] @@ -151,7 +159,7 @@ def _(properties: IonqDeviceCapabilities, arn: str) -> NoiseModel: fidelity_data = calibration_data.fidelity timing_data = calibration_data.timing qubit_count = properties.paradigm.qubitCount - native_gates = get_qpu_gate_translation(properties, properties.paradigm.nativeGateSet) + native_gates = _get_qpu_gate_translations(properties, properties.paradigm.nativeGateSet) single_qubit_gate_duration = timing_data["1Q"] two_qubit_gate_duration = timing_data["2Q"] @@ -219,7 +227,7 @@ def _create_edge_specs( ) -> List[GateFidelity]: edge_specs = [] for edge_property in edge_properties: - gate_name = get_qpu_gate_translation(properties, edge_property.gateName) + gate_name = _get_qpu_gate_translations(properties, edge_property.gateName) if hasattr(Gate, gate_name): gate = getattr(Gate, gate_name) edge_specs.append(GateFidelity(gate, edge_property.fidelity)) diff --git a/src/braket/emulators/emulator.py b/src/braket/emulators/emulator.py index b321d1969..cfea4a55f 100644 --- a/src/braket/emulators/emulator.py +++ b/src/braket/emulators/emulator.py @@ -90,7 +90,7 @@ def run( Returns: QuantumTask: The QuantumTask tracking task execution on this device emulator. """ - task_specification = self.run_program_passes(task_specification, apply_noise_model=False) + task_specification = self.run_passes(task_specification, apply_noise_model=False) # Don't apply noise model as the local simulator will automatically apply it. return self._backend.run(task_specification, shots, inputs, *args, **kwargs) @@ -138,7 +138,7 @@ def noise_model(self, noise_model: NoiseModel) -> None: self._noise_model = noise_model self._backend = LocalSimulator(backend="braket_dm", noise_model=noise_model) - def run_program_passes( + def run_passes( self, task_specification: ProgramType, apply_noise_model: bool = True ) -> ProgramType: """ @@ -157,14 +157,14 @@ def run_program_passes( exists for this emulator and apply_noise_model is true. """ try: - program = super().run_program_passes(task_specification) + program = super().run_passes(task_specification) if apply_noise_model and self.noise_model: return self._noise_model.apply(program) return program except Exception as e: self._raise_exception(e) - def run_validation_passes(self, task_specification: ProgramType) -> None: + def validate(self, task_specification: ProgramType) -> None: """ Runs only EmulatorPasses that are EmulatorCriterion, i.e. all non-modifying validation passes on the input program. @@ -173,7 +173,7 @@ def run_validation_passes(self, task_specification: ProgramType) -> None: task_specification (ProgramType): The input program to validate. """ try: - super().run_validation_passes(task_specification) + super().validate(task_specification) except Exception as e: self._raise_exception(e) diff --git a/src/braket/emulators/emulator_interface.py b/src/braket/emulators/emulator_interface.py index 47a163285..8c5d7cadc 100644 --- a/src/braket/emulators/emulator_interface.py +++ b/src/braket/emulators/emulator_interface.py @@ -11,11 +11,12 @@ class EmulatorInterface(ABC): def __init__(self, emulator_passes: Iterable[EmulatorPass] = None): self._emulator_passes = emulator_passes if emulator_passes is not None else [] - def run_program_passes(self, task_specification: ProgramType) -> ProgramType: + def run_passes(self, task_specification: ProgramType) -> ProgramType: """ This method passes the input program through the EmulatorPasses 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). + Args: task_specification (ProgramType): The program to run the emulator passes on. @@ -27,7 +28,7 @@ def run_program_passes(self, task_specification: ProgramType) -> ProgramType: task_specification = emulator_pass(task_specification) return task_specification - def run_validation_passes(self, task_specification: ProgramType) -> None: + def validate(self, task_specification: ProgramType) -> None: """ This method passes the input program through EmulatorPasses that perform only validation, without modifying the input program. @@ -55,6 +56,9 @@ def add_pass( Returns: EmulatorInterface: Returns an updated self. + Raises: + TypeError: If the input is not an iterable or an EmulatorPass. + """ if isinstance(emulator_pass, Iterable): self._emulator_passes.extend(emulator_pass) diff --git a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py index 1c47b2616..3f3298b41 100644 --- a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py @@ -1,5 +1,5 @@ from collections.abc import Iterable -from typing import Dict, Union +from typing import Dict, Optional, Union from networkx import DiGraph, complete_graph, from_dict_of_lists from networkx.utils import graphs_equal @@ -14,31 +14,40 @@ class ConnectivityCriterion(EmulatorCriterion): def __init__( self, - connectivity_graph: Union[Dict[int, Iterable[int]], DiGraph] = None, + connectivity_graph: Optional[Union[Dict[int, Iterable[int]], DiGraph]] = None, fully_connected=False, - num_qubits: int = None, - qubit_labels: Union[Iterable[int], QubitSet] = None, + num_qubits: Optional[int] = None, + qubit_labels: Optional[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. + A ConnectivityCriterion instance takes in a qubit connectivity graph and validates that + a circuit that uses verbatim circuits makes valid hardware qubit references in single + and two-qubit gate operations. + + Args: + connectivity_graph (Optional[Union[Dict[int, Iterable[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. + num_qubits (Optional[int]): The number of qubits in the device; if fully_connected is + True, 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. + qubit_labels (Optional[Union[Iterable[int], QubitSet]]): 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. + + Raises: + ValueError: If the inputs do not correctly yield a connectivity graph; i.e. + fully_connected is true but neither/both num qubits and qubit labels are defined + or a valid DiGraph or dict representation of a connectivity graph is not provided. """ if not (connectivity_graph or fully_connected): @@ -82,6 +91,9 @@ def validate(self, circuit: Circuit) -> None: Args: circuit (Circuit): The Braket circuit whose gate operations to validate. + + Raises: + ValueError: If a hardware qubit reference does not exist in the connectivity graph. """ # If any of the instructions are in verbatim mode, all qubit references # must point to hardware qubits. Otherwise, this circuit need not be validated. @@ -120,6 +132,10 @@ def validate_instruction_connectivity( target_qubits (QubitSet): The target qubits of this operation. For many gates, both the control and target are stored in "target_qubits", so we may see target_qubits have length 2. + + Raises: + ValueError: If any two-qubit gate operation uses a qubit edge that does not exist + in the qubit connectivity graph. """ # Create edges between each of the target qubits gate_connectivity_graph = DiGraph() diff --git a/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py b/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py index a55d6c1e5..c12b44057 100644 --- a/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py @@ -33,4 +33,16 @@ def run(self, program: ProgramType) -> ProgramType: @abstractmethod def __eq__(self, other: EmulatorCriterion) -> bool: + """ + Checks whether or not two EmulatorCriterion are equivalent, i.e. they sets of programs + they validate and invalidate are identical. + + Args: + other (EmulatorCriterion): The other criterion instance to compare with this instance. + + + Raises: + NotImplementedError: Method not implemented. + """ + raise NotImplementedError diff --git a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py index 77085a50a..9603a6c25 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py @@ -62,6 +62,10 @@ def validate(self, circuit: Circuit) -> None: Args: circuit (Circuit): The circuit whose gate instructions need to be validated against this criterion's gate connectivity graph. + + Raises: + ValueError if any of the gate operations use qubits or qubit edges that don't exist + in the qubit connectivity graph or the gate operation is not supported by the edge. """ for idx in range(len(circuit.instructions)): instruction = circuit.instructions[idx] @@ -99,6 +103,9 @@ def validate_instruction_connectivity( gate_name (str): The name of the gate being applied. control_qubits (QubitSet): The set of control qubits used by this gate operation. target_qubits (QubitSet): The set of target qubits used by this gate operation. + + Raises: + ValueError if the gate operation is not possible on the qubit connectivity graph. """ # Create edges between each of the target qubits if len(control_qubits) == 1 and len(target_qubits) == 1: diff --git a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py index a332c5c14..52d6072a2 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/gate_criterion.py @@ -10,13 +10,17 @@ class GateCriterion(EmulatorCriterion): def __init__(self, supported_gates: Iterator[str] = [], native_gates: Iterator[str] = []): """ - args: + Args: supported_gates (Iterator[str]): A list of gates supported outside of verbatim mode 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. + + Raises: + ValueError: If supported_gates and and native_gates are empty or any of the provided + gate are not supported by the Braket BDK. """ - if len(supported_gates) == 0 and len(native_gates) == 0: + if not len(supported_gates) and not len(native_gates): raise ValueError("Supported gate set or native gate set must be provided.") try: @@ -37,6 +41,10 @@ def validate(self, circuit: Circuit) -> None: Args: circuit (Circuit): The Braket circuit whose gates to validate. + + Raises: + ValueError: If a gate operation or verbatim gate operation is not in this criterion's + supported or native gate set, respectively. """ idx = 0 while idx < len(circuit.instructions): diff --git a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py index 797f961f1..50eae30e3 100644 --- a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py +++ b/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py @@ -21,6 +21,9 @@ def validate(self, circuit: Circuit) -> None: Args: circuit (Circuit): The Braket circuit whose qubit count to validate. + Raises: + ValueError: If the number of qubits used in the circuit exceeds the qubit_count. + """ if circuit.qubit_count > self._qubit_count: raise ValueError( diff --git a/src/braket/emulators/emulator_passes/emulator_pass.py b/src/braket/emulators/emulator_passes/emulator_pass.py index 2c2dae8cf..b61b9e9f4 100644 --- a/src/braket/emulators/emulator_passes/emulator_pass.py +++ b/src/braket/emulators/emulator_passes/emulator_pass.py @@ -7,11 +7,17 @@ class EmulatorPass(ABC): @abstractmethod def run(self, program: ProgramType) -> ProgramType: - """Runs the emulator pass on the provided program. + """ + Runs the emulator pass on the provided program. + Args: program (ProgramType): The program to run the emulator pass on. + Returns: ProgramType: The program after the emulator pass has been applied. + + Raises: + NotImplementedError: Method not implemented. """ raise NotImplementedError diff --git a/test/unit_tests/braket/aws/test_aws_emulation.py b/test/unit_tests/braket/aws/test_aws_emulation.py index 4a72dcef8..cc3167fe9 100644 --- a/test/unit_tests/braket/aws/test_aws_emulation.py +++ b/test/unit_tests/braket/aws/test_aws_emulation.py @@ -7,12 +7,12 @@ from common_test_utils import RIGETTI_ARN, RIGETTI_REGION from braket.aws import AwsDevice -from braket.aws.aws_emulator_helpers import get_qpu_gate_translation +from braket.aws.aws_emulator_helpers import _get_qpu_gate_translations from braket.aws.aws_noise_models import ( GateDeviceCalibrationData, GateFidelity, _setup_calibration_specs, - create_device_noise_model, + device_noise_model, ) from braket.circuits import Circuit, Gate from braket.circuits.noise_model import GateCriteria, NoiseModel, ObservableCriteria @@ -405,9 +405,9 @@ def ionq_target_noise_model(ionq_device_capabilities): return target_noise_model -@patch.dict("braket.aws.aws_noise_models.QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) +@patch.dict("braket.aws.aws_noise_models._QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) def test_standardized_noise_model(rigetti_device_capabilities, rigetti_target_noise_model): - noise_model = create_device_noise_model(rigetti_device_capabilities, RIGETTI_ARN) + noise_model = device_noise_model(rigetti_device_capabilities, RIGETTI_ARN) assert noise_model.instructions == rigetti_target_noise_model.instructions @@ -445,7 +445,7 @@ def test_missing_gate_durations(rigetti_device_capabilities): def test_ionq_noise_model(ionq_device_capabilities, ionq_target_noise_model): # modify capabilities to include gate not supported by braket but included in IonQ capabilities. ionq_device_capabilities.paradigm.nativeGateSet.append("Two_Qubit_Clifford") - noise_model = create_device_noise_model(ionq_device_capabilities, Devices.IonQ.Aria1) + noise_model = device_noise_model(ionq_device_capabilities, Devices.IonQ.Aria1) assert noise_model.instructions == ionq_target_noise_model.instructions @@ -585,7 +585,7 @@ def test_ionq_emulator(ionq_device): emulator._emulator_passes == target_emulator_passes -@patch.dict("braket.aws.aws_noise_models.QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) +@patch.dict("braket.aws.aws_noise_models._QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) def test_rigetti_emulator(rigetti_device, rigetti_target_noise_model): emulator = rigetti_device.emulator assert emulator.noise_model @@ -623,7 +623,7 @@ def test_rigetti_emulator(rigetti_device, rigetti_target_noise_model): assert emulator._emulator_passes == target_emulator_passes -@patch.dict("braket.aws.aws_noise_models.QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) +@patch.dict("braket.aws.aws_noise_models._QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) def test_iqm_emulator(iqm_device, iqm_target_noise_model): emulator = iqm_device.emulator assert emulator.noise_model @@ -671,10 +671,10 @@ def test_iqm_emulator(iqm_device, iqm_target_noise_model): ) def test_get_gate_translations(device_capabilities, gate_name, expected_result, request): device_capabilities_obj = request.getfixturevalue(device_capabilities) - assert get_qpu_gate_translation(device_capabilities_obj, gate_name) == expected_result + assert _get_qpu_gate_translations(device_capabilities_obj, gate_name) == expected_result -@patch.dict("braket.aws.aws_noise_models.QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) +@patch.dict("braket.aws.aws_noise_models._QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) @pytest.mark.parametrize( "circuit,is_valid", [ @@ -697,7 +697,7 @@ def test_emulator_passes(circuit, is_valid, rigetti_device): rigetti_device.validate(circuit) -@patch.dict("braket.aws.aws_noise_models.QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) +@patch.dict("braket.aws.aws_noise_models._QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) @patch.object(LocalSimulator, "run") def test_device_emulate(mock_run, rigetti_device): circuit = Circuit().h(0).cnot(0, 1) @@ -705,7 +705,7 @@ def test_device_emulate(mock_run, rigetti_device): mock_run.assert_called_once() -@patch.dict("braket.aws.aws_noise_models.QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) +@patch.dict("braket.aws.aws_noise_models._QPU_GATE_DURATIONS", MOCK_QPU_GATE_DURATIONS) @patch.object(AwsDevice, "_setup_emulator", return_value=Emulator()) def test_get_emulator_multiple(mock_setup, rigetti_device): emulator = rigetti_device.emulator diff --git a/test/unit_tests/braket/emulation/test_emulator.py b/test/unit_tests/braket/emulation/test_emulator.py index 16f2b9aed..562822f85 100644 --- a/test/unit_tests/braket/emulation/test_emulator.py +++ b/test/unit_tests/braket/emulation/test_emulator.py @@ -47,7 +47,7 @@ def basic_emulator(empty_emulator): def test_empty_emulator_validation(empty_emulator): emulator = empty_emulator circuit = Circuit().h(0).cnot(0, 1) - emulator.run_validation_passes(circuit) + emulator.validate(circuit) def test_basic_emulator(basic_emulator): @@ -55,7 +55,7 @@ def test_basic_emulator(basic_emulator): Should not error out when passed a valid circuit. """ circuit = Circuit().cnot(0, 1) - circuit = basic_emulator.run_program_passes(circuit) + circuit = basic_emulator.run_passes(circuit) assert circuit == circuit @@ -69,7 +69,7 @@ def test_basic_invalidate(basic_emulator): but uses {circuit.qubit_count} qubits. (DeviceEmulator)" ) with pytest.raises(ValueError, match=match_string): - basic_emulator.run_program_passes(circuit) + basic_emulator.run_passes(circuit) def test_add_pass_single(empty_emulator): @@ -129,7 +129,7 @@ def test_validation_only_pass(setup_local_simulator_devices): but uses {circuit.qubit_count} qubits. (DeviceEmulator)" ) with pytest.raises(ValueError, match=match_string): - emulator.run_validation_passes(circuit) + emulator.validate(circuit) def test_apply_noise_model(setup_local_simulator_devices): @@ -138,13 +138,13 @@ def test_apply_noise_model(setup_local_simulator_devices): emulator = Emulator(noise_model=noise_model) circuit = Circuit().h(0) - circuit = emulator.run_program_passes(circuit) + circuit = emulator.run_passes(circuit) noisy_circuit = Circuit().h(0).apply_gate_noise(BitFlip(0.1), Gate.H) assert circuit == noisy_circuit circuit = Circuit().h(0) - circuit = emulator.run_program_passes(circuit, apply_noise_model=False) + circuit = emulator.run_passes(circuit, apply_noise_model=False) target_circ = Circuit().h(0) assert circuit == target_circ From b698648deb4112197e7e49f5ecd2cfc4f2cfc618 Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 29 Jul 2024 16:25:11 -0700 Subject: [PATCH 80/91] change: Use 'validator' in place of 'criterion', fix usage of generic program type in EmulatorPasses --- src/braket/aws/aws_device.py | 30 ++++---- src/braket/aws/aws_emulator_helpers.py | 76 +++++++++---------- src/braket/emulation/__init__.py | 1 + .../base_emulator.py} | 35 ++++----- .../{emulators => emulation}/emulator.py | 24 +++--- .../emulation/emulator_passes/__init__.py | 8 ++ .../emulator_passes/criteria/__init__.py | 11 +++ .../criteria/connectivity_validator.py} | 37 ++++----- .../criteria/gate_connectivity_validator.py} | 32 ++++---- .../criteria/gate_validator.py} | 48 +++++++----- .../criteria/qubit_count_validator.py} | 14 ++-- .../criteria/validation_pass.py} | 22 +----- .../emulator_passes/emulator_pass.py | 4 +- src/braket/emulators/__init__.py | 1 - .../emulators/emulator_passes/__init__.py | 8 -- .../emulator_passes/criteria/__init__.py | 13 ---- .../braket/aws/test_aws_emulation.py | 38 +++++----- ...rion.py => test_connectivity_validator.py} | 44 +++++------ .../braket/emulation/test_emulator.py | 56 +++++++------- ...py => test_gate_connectivity_validator.py} | 50 ++++++------ ...te_criterion.py => test_gate_validator.py} | 32 ++++---- ...erion.py => test_qubit_count_validator.py} | 16 ++-- 22 files changed, 295 insertions(+), 305 deletions(-) create mode 100644 src/braket/emulation/__init__.py rename src/braket/{emulators/emulator_interface.py => emulation/base_emulator.py} (61%) rename src/braket/{emulators => emulation}/emulator.py (90%) create mode 100644 src/braket/emulation/emulator_passes/__init__.py create mode 100644 src/braket/emulation/emulator_passes/criteria/__init__.py rename src/braket/{emulators/emulator_passes/criteria/connectivity_criterion.py => emulation/emulator_passes/criteria/connectivity_validator.py} (85%) rename src/braket/{emulators/emulator_passes/criteria/gate_connectivity_criterion.py => emulation/emulator_passes/criteria/gate_connectivity_validator.py} (84%) rename src/braket/{emulators/emulator_passes/criteria/gate_criterion.py => emulation/emulator_passes/criteria/gate_validator.py} (63%) rename src/braket/{emulators/emulator_passes/criteria/qubit_count_criterion.py => emulation/emulator_passes/criteria/qubit_count_validator.py} (68%) rename src/braket/{emulators/emulator_passes/criteria/emulator_criterion.py => emulation/emulator_passes/criteria/validation_pass.py} (56%) rename src/braket/{emulators => emulation}/emulator_passes/emulator_pass.py (88%) delete mode 100644 src/braket/emulators/__init__.py delete mode 100644 src/braket/emulators/emulator_passes/__init__.py delete mode 100644 src/braket/emulators/emulator_passes/criteria/__init__.py rename test/unit_tests/braket/emulation/{test_connectivity_criterion.py => test_connectivity_validator.py} (80%) rename test/unit_tests/braket/emulation/{test_gate_connectivity_criterion.py => test_gate_connectivity_validator.py} (85%) rename test/unit_tests/braket/emulation/{test_gate_criterion.py => test_gate_validator.py} (82%) rename test/unit_tests/braket/emulation/{test_qubit_count_criterion.py => test_qubit_count_validator.py} (74%) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 530c67186..f9a9463c0 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -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 @@ -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 @@ -908,15 +908,15 @@ 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 @@ -924,14 +924,14 @@ def validate( 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: """ @@ -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: @@ -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. diff --git a/src/braket/aws/aws_emulator_helpers.py b/src/braket/aws/aws_emulator_helpers.py index 719c6bd34..f054dee61 100644 --- a/src/braket/aws/aws_emulator_helpers.py +++ b/src/braket/aws/aws_emulator_helpers.py @@ -8,17 +8,17 @@ 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: @@ -26,16 +26,16 @@ def qubit_count_criterion(properties: DeviceCapabilities) -> QubitCountCriterion 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: @@ -43,21 +43,21 @@ def gate_criterion(properties: DeviceCapabilities) -> GateCriterion: 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: @@ -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. @@ -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 @@ -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( diff --git a/src/braket/emulation/__init__.py b/src/braket/emulation/__init__.py new file mode 100644 index 000000000..e1c29ccd4 --- /dev/null +++ b/src/braket/emulation/__init__.py @@ -0,0 +1 @@ +from braket.emulation.emulator import Emulator # noqa: F40 diff --git a/src/braket/emulators/emulator_interface.py b/src/braket/emulation/base_emulator.py similarity index 61% rename from src/braket/emulators/emulator_interface.py rename to src/braket/emulation/base_emulator.py index 8c5d7cadc..63bcb3c11 100644 --- a/src/braket/emulators/emulator_interface.py +++ b/src/braket/emulation/base_emulator.py @@ -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). @@ -30,7 +29,7 @@ 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: @@ -38,32 +37,34 @@ def validate(self, task_specification: ProgramType) -> None: 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 diff --git a/src/braket/emulators/emulator.py b/src/braket/emulation/emulator.py similarity index 90% rename from src/braket/emulators/emulator.py rename to src/braket/emulation/emulator.py index cfea4a55f..ec20563d4 100644 --- a/src/braket/emulators/emulator.py +++ b/src/braket/emulation/emulator.py @@ -7,17 +7,17 @@ from braket.circuits.noise_model import NoiseModel from braket.devices import Device from braket.devices.local_simulator import LocalSimulator -from braket.emulators.emulator_interface import EmulatorInterface -from braket.emulators.emulator_passes import EmulatorPass, ProgramType +from braket.emulation.base_emulator import BaseEmulator +from braket.emulation.emulator_passes import EmulationPass, ProgramType from braket.ir.openqasm import Program as OpenQasmProgram from braket.tasks import QuantumTask from braket.tasks.quantum_task_batch import QuantumTaskBatch -class Emulator(Device, EmulatorInterface): +class Emulator(Device, BaseEmulator): - DEFAULT_SIMULATOR_BACKEND = "default" - DEFAULT_NOISY_BACKEND = "braket_dm" + _DEFAULT_SIMULATOR_BACKEND = "default" + _DEFAULT_NOISY_BACKEND = "braket_dm" """An emulator is a simulation device that more closely resembles the capabilities and constraints of a real device or of a specific device model.""" @@ -26,11 +26,11 @@ def __init__( self, backend: str = "default", noise_model: Optional[NoiseModel] = None, - emulator_passes: Iterable[EmulatorPass] = None, + emulator_passes: Iterable[EmulationPass] = None, **kwargs, ): Device.__init__(self, name=kwargs.get("name", "DeviceEmulator"), status="AVAILABLE") - EmulatorInterface.__init__(self, emulator_passes) + BaseEmulator.__init__(self, emulator_passes) self._noise_model = noise_model backend_name = self._get_local_simulator_backend(backend, noise_model) @@ -57,8 +57,8 @@ def _get_local_simulator_backend( "Setting LocalSimulator backend to use 'braket_dm' \ because a NoiseModel was provided." ) - return Emulator.DEFAULT_NOISY_BACKEND - return Emulator.DEFAULT_SIMULATOR_BACKEND + return Emulator._DEFAULT_NOISY_BACKEND + return Emulator._DEFAULT_SIMULATOR_BACKEND def run( self, @@ -142,13 +142,13 @@ def run_passes( self, task_specification: ProgramType, apply_noise_model: bool = True ) -> ProgramType: """ - Passes the input program through all EmulatorPass objects contained in this + Passes the input program through all EmulationPass objects contained in this emulator and applies the emulator's noise model, if it exists, before returning the compiled program. Args: task_specification (ProgramType): The input program to validate and - compile based on this emulator's EmulatorPasses + compile based on this emulator's EmulationPasses apply_noise_model (bool): If true, apply this emulator's noise model to the compiled program before returning the final program. @@ -166,7 +166,7 @@ def run_passes( def validate(self, task_specification: ProgramType) -> None: """ - Runs only EmulatorPasses that are EmulatorCriterion, i.e. all non-modifying + Runs only EmulationPasses that are ValidationPass, i.e. all non-modifying validation passes on the input program. Args: diff --git a/src/braket/emulation/emulator_passes/__init__.py b/src/braket/emulation/emulator_passes/__init__.py new file mode 100644 index 000000000..0f328d6fa --- /dev/null +++ b/src/braket/emulation/emulator_passes/__init__.py @@ -0,0 +1,8 @@ +from braket.emulation.emulator_passes.criteria import ( # noqa: F401 + ConnectivityValidator, + GateConnectivityValidator, + GateValidator, + QubitCountValidator, + ValidationPass, +) +from braket.emulation.emulator_passes.emulator_pass import EmulationPass, ProgramType # noqa: F401 diff --git a/src/braket/emulation/emulator_passes/criteria/__init__.py b/src/braket/emulation/emulator_passes/criteria/__init__.py new file mode 100644 index 000000000..c15972293 --- /dev/null +++ b/src/braket/emulation/emulator_passes/criteria/__init__.py @@ -0,0 +1,11 @@ +from braket.emulation.emulator_passes.criteria.connectivity_validator import ( # noqa: F401 + ConnectivityValidator, +) +from braket.emulation.emulator_passes.criteria.gate_connectivity_validator import ( # noqa: F401 + GateConnectivityValidator, +) +from braket.emulation.emulator_passes.criteria.gate_validator import GateValidator # noqa: F401 +from braket.emulation.emulator_passes.criteria.qubit_count_validator import ( # noqa: F401 + QubitCountValidator, +) +from braket.emulation.emulator_passes.criteria.validation_pass import ValidationPass # noqa: F401 diff --git a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py b/src/braket/emulation/emulator_passes/criteria/connectivity_validator.py similarity index 85% rename from src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py rename to src/braket/emulation/emulator_passes/criteria/connectivity_validator.py index 3f3298b41..c3557cb31 100644 --- a/src/braket/emulators/emulator_passes/criteria/connectivity_criterion.py +++ b/src/braket/emulation/emulator_passes/criteria/connectivity_validator.py @@ -7,21 +7,21 @@ from braket.circuits import Circuit from braket.circuits.compiler_directives import StartVerbatimBox from braket.circuits.gate import Gate -from braket.emulators.emulator_passes.criteria.emulator_criterion import EmulatorCriterion +from braket.emulation.emulator_passes.criteria.validation_pass import ValidationPass from braket.registers.qubit_set import QubitSet -class ConnectivityCriterion(EmulatorCriterion): +class ConnectivityValidator(ValidationPass[Circuit]): def __init__( self, connectivity_graph: Optional[Union[Dict[int, Iterable[int]], DiGraph]] = None, - fully_connected=False, + fully_connected: bool = False, num_qubits: Optional[int] = None, qubit_labels: Optional[Union[Iterable[int], QubitSet]] = None, directed: bool = True, ): """ - A ConnectivityCriterion instance takes in a qubit connectivity graph and validates that + A ConnectivityValidator instance takes in a qubit connectivity graph and validates that a circuit that uses verbatim circuits makes valid hardware qubit references in single and two-qubit gate operations. @@ -50,9 +50,10 @@ def __init__( or a valid DiGraph or dict representation of a connectivity graph is not provided. """ - if not (connectivity_graph or fully_connected): + if not ((connectivity_graph is not None) ^ fully_connected): raise ValueError( - "Either the connectivity_graph must be provided or fully_connected must be True." + "Either the connectivity_graph must be provided OR fully_connected must be True\ + (not both)." ) if fully_connected: @@ -81,15 +82,15 @@ def __init__( for edge in self._connectivity_graph.edges: self._connectivity_graph.add_edge(edge[1], edge[0]) - def validate(self, circuit: Circuit) -> None: + def validate(self, program: Circuit) -> None: """ Verifies that any verbatim box in a circuit is runnable with respect to the - device connectivity definied by this criterion. If any sub-circuit of the + device connectivity definied by this validator. If any sub-circuit of the input circuit is verbatim, we validate the connectivity of all gate operations in the circuit. Args: - circuit (Circuit): The Braket circuit whose gate operations to + program (Circuit): The Braket circuit whose gate operations to validate. Raises: @@ -100,17 +101,17 @@ def validate(self, circuit: Circuit) -> None: if not any( [ isinstance(instruction.operator, StartVerbatimBox) - for instruction in circuit.instructions + for instruction in program.instructions ] ): return - for idx in range(len(circuit.instructions)): - instruction = circuit.instructions[idx] + for idx in range(len(program.instructions)): + instruction = program.instructions[idx] if isinstance(instruction.operator, Gate): if ( instruction.operator.qubit_count == 2 ): # Assuming only maximum 2-qubit native gates are supported - self.validate_instruction_connectivity(instruction.control, instruction.target) + self._validate_instruction_connectivity(instruction.control, instruction.target) else: # just check that the target qubit exists in the connectivity graph target_qubit = instruction.target[0] @@ -119,11 +120,11 @@ def validate(self, circuit: Circuit) -> None: f"Qubit {target_qubit} does not exist in the device topology." ) - def validate_instruction_connectivity( + def _validate_instruction_connectivity( self, control_qubits: QubitSet, target_qubits: QubitSet ) -> None: """ - Checks if a two-qubit instruction is valid based on this criterion's connectivity + Checks if a two-qubit instruction is valid based on this validator's connectivity graph. Args: @@ -148,12 +149,12 @@ def validate_instruction_connectivity( ) else: raise ValueError("Unrecognized qubit targetting setup for a 2 qubit gate.") - # Check that each edge exists in this criterion's connectivity graph + # Check that each edge exists in this validator's connectivity graph for e in gate_connectivity_graph.edges: if not self._connectivity_graph.has_edge(*e): raise ValueError(f"{e[0]} is not connected to qubit {e[1]} in this device.") - def __eq__(self, other: EmulatorCriterion) -> bool: - return isinstance(other, ConnectivityCriterion) and graphs_equal( + def __eq__(self, other: ValidationPass) -> bool: + return isinstance(other, ConnectivityValidator) and graphs_equal( self._connectivity_graph, other._connectivity_graph ) diff --git a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py b/src/braket/emulation/emulator_passes/criteria/gate_connectivity_validator.py similarity index 84% rename from src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py rename to src/braket/emulation/emulator_passes/criteria/gate_connectivity_validator.py index 9603a6c25..e78b11e25 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_connectivity_criterion.py +++ b/src/braket/emulation/emulator_passes/criteria/gate_connectivity_validator.py @@ -6,11 +6,11 @@ from braket.circuits.circuit import Circuit from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox from braket.circuits.gate import Gate -from braket.emulators.emulator_passes.criteria.emulator_criterion import EmulatorCriterion +from braket.emulation.emulator_passes.criteria.validation_pass import ValidationPass from braket.registers.qubit_set import QubitSet -class GateConnectivityCriterion(EmulatorCriterion): +class GateConnectivityValidator(ValidationPass[Circuit]): def __init__( self, gate_connectivity_graph: Union[Dict[Tuple[Any, Any], Iterable[str]], DiGraph], @@ -54,32 +54,32 @@ def __init__( gate sets." ) - def validate(self, circuit: Circuit) -> None: + def validate(self, program: Circuit) -> None: """ Verifies that any multiqubit gates used within a verbatim box are supported by the devices gate connectivity defined by this criteria. Args: - circuit (Circuit): The circuit whose gate instructions need to be validated - against this criterion's gate connectivity graph. + program (Circuit): The circuit whose gate instructions need to be validated + against this validator's gate connectivity graph. Raises: ValueError if any of the gate operations use qubits or qubit edges that don't exist in the qubit connectivity graph or the gate operation is not supported by the edge. """ - for idx in range(len(circuit.instructions)): - instruction = circuit.instructions[idx] + for idx in range(len(program.instructions)): + instruction = program.instructions[idx] if isinstance(instruction.operator, StartVerbatimBox): idx += 1 - while idx < len(circuit.instructions) and not isinstance( - circuit.instructions[idx].operator, EndVerbatimBox + while idx < len(program.instructions) and not isinstance( + program.instructions[idx].operator, EndVerbatimBox ): - instruction = circuit.instructions[idx] + instruction = program.instructions[idx] if isinstance(instruction.operator, Gate): if ( instruction.operator.qubit_count == 2 ): # Assuming only maximum 2-qubit native gates are supported - self.validate_instruction_connectivity( + self._validate_instruction_connectivity( instruction.operator.name, instruction.control, instruction.target ) else: @@ -92,12 +92,12 @@ def validate(self, circuit: Circuit) -> None: idx += 1 idx += 1 - def validate_instruction_connectivity( + def _validate_instruction_connectivity( self, gate_name: str, control_qubits: QubitSet, target_qubits: QubitSet ) -> None: """ Checks if a specific is able to be applied to the control and target qubits based - on this criterion's gate connectivity graph. + on this validator's gate connectivity graph. Args: gate_name (str): The name of the gate being applied. @@ -115,7 +115,7 @@ def validate_instruction_connectivity( else: raise ValueError("Unrecognized qubit targetting setup for a 2 qubit gate.") - # Check that each edge exists in this criterion's connectivity graph + # Check that each edge exists in this validator's connectivity graph if not self._gate_connectivity_graph.has_edge(*e): raise ValueError(f"{e[0]} is not connected to {e[1]} on this device.") supported_gates = self._gate_connectivity_graph[e[0]][e[1]]["supported_gates"] @@ -124,7 +124,7 @@ def validate_instruction_connectivity( f"Qubit pair ({e[0]}, {e[1]}) does not support gate {gate_name} on this device." ) - def __eq__(self, other: EmulatorCriterion) -> bool: - return isinstance(other, GateConnectivityCriterion) and graphs_equal( + def __eq__(self, other: ValidationPass) -> bool: + return isinstance(other, GateConnectivityValidator) and graphs_equal( self._gate_connectivity_graph, other._gate_connectivity_graph ) diff --git a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py b/src/braket/emulation/emulator_passes/criteria/gate_validator.py similarity index 63% rename from src/braket/emulators/emulator_passes/criteria/gate_criterion.py rename to src/braket/emulation/emulator_passes/criteria/gate_validator.py index 52d6072a2..f8c2c0f83 100644 --- a/src/braket/emulators/emulator_passes/criteria/gate_criterion.py +++ b/src/braket/emulation/emulator_passes/criteria/gate_validator.py @@ -1,25 +1,31 @@ from collections.abc import Iterator +from typing import Optional from braket.circuits import Circuit from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox from braket.circuits.gate import Gate from braket.circuits.translations import BRAKET_GATES -from braket.emulators.emulator_passes.criteria.emulator_criterion import EmulatorCriterion +from braket.emulation.emulator_passes.criteria.validation_pass import ValidationPass -class GateCriterion(EmulatorCriterion): - def __init__(self, supported_gates: Iterator[str] = [], native_gates: Iterator[str] = []): +class GateValidator(ValidationPass[Circuit]): + def __init__( + self, + supported_gates: Optional[Iterator[str]] = None, + native_gates: Optional[Iterator[str]] = None, + ): """ Args: - supported_gates (Iterator[str]): A list of gates supported outside of verbatim mode - 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. + supported_gates (Optional[Iterator[str]]): A list of gates supported outside of + verbatim modeby the emulator. A gate is a Braket gate name. + native_gates (Optional[Iterator[str]]): A list of gates supported inside of + verbatim mode by the emulator. Raises: ValueError: If supported_gates and and native_gates are empty or any of the provided gate are not supported by the Braket BDK. """ + supported_gates, native_gates = (supported_gates or []), (native_gates or []) if not len(supported_gates) and not len(native_gates): raise ValueError("Supported gate set or native gate set must be provided.") @@ -33,28 +39,28 @@ def __init__(self, supported_gates: Iterator[str] = [], native_gates: Iterator[s except KeyError as e: raise ValueError(f"Input {str(e)} in native_gates is not a valid Braket gate name.") - def validate(self, circuit: Circuit) -> None: + def validate(self, program: Circuit) -> None: """ - Checks that all non-verbatim gates used in the circuit are in this criterion's + Checks that all non-verbatim gates used in the circuit are in this validator's supported gate set and that all verbatim gates used in the circuit are in this - criterion's native gate set. + validator's native gate set. Args: - circuit (Circuit): The Braket circuit whose gates to validate. + program (Circuit): The Braket circuit whose gates to validate. Raises: - ValueError: If a gate operation or verbatim gate operation is not in this criterion's + ValueError: If a gate operation or verbatim gate operation is not in this validator's supported or native gate set, respectively. """ idx = 0 - while idx < len(circuit.instructions): - instruction = circuit.instructions[idx] + while idx < len(program.instructions): + instruction = program.instructions[idx] if isinstance(instruction.operator, StartVerbatimBox): idx += 1 - while idx < len(circuit.instructions) and not isinstance( - circuit.instructions[idx].operator, EndVerbatimBox + while idx < len(program.instructions) and not isinstance( + program.instructions[idx].operator, EndVerbatimBox ): - instruction = circuit.instructions[idx] + instruction = program.instructions[idx] if isinstance(instruction.operator, Gate): gate = instruction.operator if not type(gate) in self._native_gates: @@ -62,8 +68,8 @@ def validate(self, circuit: Circuit) -> None: f"Gate {gate.name} is not a native gate supported by this device." ) idx += 1 - if idx == len(circuit.instructions) or not isinstance( - circuit.instructions[idx].operator, EndVerbatimBox + if idx == len(program.instructions) or not isinstance( + program.instructions[idx].operator, EndVerbatimBox ): raise ValueError(f"No end verbatim box found at index {idx} in the circuit.") elif isinstance(instruction.operator, Gate): @@ -72,9 +78,9 @@ def validate(self, circuit: Circuit) -> None: raise ValueError(f"Gate {gate.name} is not supported by this device.") idx += 1 - def __eq__(self, other: EmulatorCriterion) -> bool: + def __eq__(self, other: ValidationPass) -> bool: return ( - isinstance(other, GateCriterion) + isinstance(other, GateValidator) and self._supported_gates == other._supported_gates and self._native_gates == other._native_gates ) diff --git a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py b/src/braket/emulation/emulator_passes/criteria/qubit_count_validator.py similarity index 68% rename from src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py rename to src/braket/emulation/emulator_passes/criteria/qubit_count_validator.py index 50eae30e3..5e5b26aff 100644 --- a/src/braket/emulators/emulator_passes/criteria/qubit_count_criterion.py +++ b/src/braket/emulation/emulator_passes/criteria/qubit_count_validator.py @@ -1,11 +1,11 @@ from braket.circuits import Circuit -from braket.emulators.emulator_passes.criteria.emulator_criterion import EmulatorCriterion +from braket.emulation.emulator_passes.criteria.validation_pass import ValidationPass -class QubitCountCriterion(EmulatorCriterion): +class QubitCountValidator(ValidationPass[Circuit]): """ - A simple criterion class that checks that an input program does not use more qubits - than available on a device, as set during this criterion's instantiation. + A simple validator class that checks that an input program does not use more qubits + than available on a device, as set during this validator's instantiation. """ def __init__(self, qubit_count: int): @@ -16,7 +16,7 @@ def __init__(self, qubit_count: int): def validate(self, circuit: Circuit) -> None: """ Checks that the number of qubits used in this circuit does not exceed this - criterion's qubit_count max. + validator's qubit_count max. Args: circuit (Circuit): The Braket circuit whose qubit count to validate. @@ -31,5 +31,5 @@ def validate(self, circuit: Circuit) -> None: but uses {circuit.qubit_count} qubits." ) - def __eq__(self, other: EmulatorCriterion) -> bool: - return isinstance(other, QubitCountCriterion) and self._qubit_count == other._qubit_count + def __eq__(self, other: ValidationPass) -> bool: + return isinstance(other, QubitCountValidator) and self._qubit_count == other._qubit_count diff --git a/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py b/src/braket/emulation/emulator_passes/criteria/validation_pass.py similarity index 56% rename from src/braket/emulators/emulator_passes/criteria/emulator_criterion.py rename to src/braket/emulation/emulator_passes/criteria/validation_pass.py index c12b44057..1641a3f79 100644 --- a/src/braket/emulators/emulator_passes/criteria/emulator_criterion.py +++ b/src/braket/emulation/emulator_passes/criteria/validation_pass.py @@ -2,14 +2,14 @@ from abc import abstractmethod -from braket.emulators.emulator_passes.emulator_pass import EmulatorPass, ProgramType +from braket.emulation.emulator_passes.emulator_pass import EmulationPass, ProgramType -class EmulatorCriterion(EmulatorPass): +class ValidationPass(EmulationPass[ProgramType]): @abstractmethod def validate(self, program: ProgramType) -> None: """ - An emulator criterion is used to perform some non-modifying validation + An emulator validator 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. @@ -30,19 +30,3 @@ def run(self, program: ProgramType) -> ProgramType: """ self.validate(program) return program - - @abstractmethod - def __eq__(self, other: EmulatorCriterion) -> bool: - """ - Checks whether or not two EmulatorCriterion are equivalent, i.e. they sets of programs - they validate and invalidate are identical. - - Args: - other (EmulatorCriterion): The other criterion instance to compare with this instance. - - - Raises: - NotImplementedError: Method not implemented. - """ - - raise NotImplementedError diff --git a/src/braket/emulators/emulator_passes/emulator_pass.py b/src/braket/emulation/emulator_passes/emulator_pass.py similarity index 88% rename from src/braket/emulators/emulator_passes/emulator_pass.py rename to src/braket/emulation/emulator_passes/emulator_pass.py index b61b9e9f4..9bbd62b4f 100644 --- a/src/braket/emulators/emulator_passes/emulator_pass.py +++ b/src/braket/emulation/emulator_passes/emulator_pass.py @@ -1,10 +1,10 @@ from abc import ABC, abstractmethod -from typing import TypeVar +from typing import Generic, TypeVar ProgramType = TypeVar("ProgramType") -class EmulatorPass(ABC): +class EmulationPass(ABC, Generic[ProgramType]): @abstractmethod def run(self, program: ProgramType) -> ProgramType: """ diff --git a/src/braket/emulators/__init__.py b/src/braket/emulators/__init__.py deleted file mode 100644 index 60a33ea70..000000000 --- a/src/braket/emulators/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from braket.emulators.emulator import Emulator # noqa: F40 diff --git a/src/braket/emulators/emulator_passes/__init__.py b/src/braket/emulators/emulator_passes/__init__.py deleted file mode 100644 index 0de674ab4..000000000 --- a/src/braket/emulators/emulator_passes/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from braket.emulators.emulator_passes.criteria import ( # noqa: F401 - ConnectivityCriterion, - EmulatorCriterion, - GateConnectivityCriterion, - GateCriterion, - QubitCountCriterion, -) -from braket.emulators.emulator_passes.emulator_pass import EmulatorPass, ProgramType # noqa: F401 diff --git a/src/braket/emulators/emulator_passes/criteria/__init__.py b/src/braket/emulators/emulator_passes/criteria/__init__.py deleted file mode 100644 index 081defd8c..000000000 --- a/src/braket/emulators/emulator_passes/criteria/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from braket.emulators.emulator_passes.criteria.connectivity_criterion import ( # noqa: F401 - ConnectivityCriterion, -) -from braket.emulators.emulator_passes.criteria.emulator_criterion import ( # noqa: F401 - EmulatorCriterion, -) -from braket.emulators.emulator_passes.criteria.gate_connectivity_criterion import ( # noqa: F401 - GateConnectivityCriterion, -) -from braket.emulators.emulator_passes.criteria.gate_criterion import GateCriterion # noqa: F401 -from braket.emulators.emulator_passes.criteria.qubit_count_criterion import ( # noqa: F401 - QubitCountCriterion, -) diff --git a/test/unit_tests/braket/aws/test_aws_emulation.py b/test/unit_tests/braket/aws/test_aws_emulation.py index cc3167fe9..e36068dce 100644 --- a/test/unit_tests/braket/aws/test_aws_emulation.py +++ b/test/unit_tests/braket/aws/test_aws_emulation.py @@ -30,12 +30,12 @@ from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.devices import Devices from braket.devices.local_simulator import LocalSimulator -from braket.emulators import Emulator -from braket.emulators.emulator_passes import ( - ConnectivityCriterion, - GateConnectivityCriterion, - GateCriterion, - QubitCountCriterion, +from braket.emulation import Emulator +from braket.emulation.emulator_passes import ( + ConnectivityValidator, + GateConnectivityValidator, + GateValidator, + QubitCountValidator, ) REGION = "us-west-1" @@ -566,13 +566,13 @@ def _device(): def test_ionq_emulator(ionq_device): emulator = ionq_device.emulator target_emulator_passes = [ - QubitCountCriterion(ionq_device.properties.paradigm.qubitCount), - GateCriterion( + QubitCountValidator(ionq_device.properties.paradigm.qubitCount), + GateValidator( supported_gates=["x", "Y"], native_gates=["cz", "gpi", "cphaseshift"], ), - ConnectivityCriterion(nx.from_edgelist([(0, 1), (1, 0)], create_using=nx.DiGraph())), - GateConnectivityCriterion( + ConnectivityValidator(nx.from_edgelist([(0, 1), (1, 0)], create_using=nx.DiGraph())), + GateConnectivityValidator( nx.from_dict_of_dicts( { 0: {1: {"supported_gates": set(["CZ", "CPhaseShift", "GPi"])}}, @@ -598,15 +598,15 @@ def test_rigetti_emulator(rigetti_device, rigetti_target_noise_model): ) target_emulator_passes = [ - QubitCountCriterion(rigetti_device.properties.paradigm.qubitCount), - GateCriterion( + QubitCountValidator(rigetti_device.properties.paradigm.qubitCount), + GateValidator( supported_gates=["H", "X", "CNot", "CZ", "Rx", "Ry", "YY"], native_gates=["cz", "prx", "cphaseshift"], ), - ConnectivityCriterion( + ConnectivityValidator( nx.from_edgelist([(0, 1), (0, 2), (1, 0), (2, 0)], create_using=nx.DiGraph()) ), - GateConnectivityCriterion( + GateConnectivityValidator( nx.from_dict_of_dicts( { 0: { @@ -630,17 +630,17 @@ def test_iqm_emulator(iqm_device, iqm_target_noise_model): assert len(emulator.noise_model.instructions) == len(iqm_target_noise_model.instructions) assert emulator.noise_model.instructions == iqm_target_noise_model.instructions target_emulator_passes = [ - QubitCountCriterion(iqm_device.properties.paradigm.qubitCount), - GateCriterion( + QubitCountValidator(iqm_device.properties.paradigm.qubitCount), + GateValidator( supported_gates=["H", "CNot", "Ry", "XX", "YY"], native_gates=["cz", "prx", "cphaseshift"], ), - ConnectivityCriterion( + ConnectivityValidator( nx.from_edgelist( [(0, 1), (0, 2), (1, 0), (2, 0), (2, 3), (3, 2)], create_using=nx.DiGraph() ) ), - GateConnectivityCriterion( + GateConnectivityValidator( nx.from_dict_of_dicts( { 0: { @@ -691,7 +691,7 @@ def test_get_gate_translations(device_capabilities, gate_name, expected_result, def test_emulator_passes(circuit, is_valid, rigetti_device): if is_valid: rigetti_device.validate(circuit) - assert rigetti_device.run_emulator_passes(circuit, apply_noise_model=False) == circuit + assert rigetti_device.run_passes(circuit, apply_noise_model=False) == circuit else: with pytest.raises(ValueError): rigetti_device.validate(circuit) diff --git a/test/unit_tests/braket/emulation/test_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_connectivity_validator.py similarity index 80% rename from test/unit_tests/braket/emulation/test_connectivity_criterion.py rename to test/unit_tests/braket/emulation/test_connectivity_validator.py index 87afefb5c..ea4478dd8 100644 --- a/test/unit_tests/braket/emulation/test_connectivity_criterion.py +++ b/test/unit_tests/braket/emulation/test_connectivity_validator.py @@ -4,7 +4,7 @@ from networkx.utils import graphs_equal from braket.circuits import Circuit -from braket.emulators.emulator_passes.criteria import ConnectivityCriterion +from braket.emulation.emulator_passes.criteria import ConnectivityValidator @pytest.fixture @@ -39,9 +39,9 @@ def six_node_digraph(): ) def test_basic_contiguous_circuits(basic_2_node_complete_graph, circuit): """ - ConnectivityGateCriterion should not raise any errors when validating these circuits. + ConnectivityValidator should not raise any errors when validating these circuits. """ - ConnectivityCriterion(basic_2_node_complete_graph).validate(circuit) + ConnectivityValidator(basic_2_node_complete_graph).validate(circuit) @pytest.mark.parametrize( @@ -58,18 +58,18 @@ def test_basic_contiguous_circuits(basic_2_node_complete_graph, circuit): ) def test_valid_discontiguous_circuits(basic_noncontig_qubits_2_node_complete_graph, circuit): """ - ConnectivityGateCriterion should not raise any errors when validating these circuits. + ConnectivityValidator should not raise any errors when validating these circuits. """ - ConnectivityCriterion(basic_noncontig_qubits_2_node_complete_graph).validate(circuit) + ConnectivityValidator(basic_noncontig_qubits_2_node_complete_graph).validate(circuit) def test_complete_graph_instantation_with_num_qubits(): """ Tests that, if fully_connected is True and num_qubits are passed into the - ConnectivityCriterion constructor, a fully connected graph is created. + ConnectivityValidator constructor, a fully connected graph is created. """ num_qubits = 5 - criterion = ConnectivityCriterion(num_qubits=num_qubits, fully_connected=True) + validator = ConnectivityValidator(num_qubits=num_qubits, fully_connected=True) vb = Circuit() for i in range(num_qubits): for j in range(num_qubits): @@ -78,19 +78,19 @@ def test_complete_graph_instantation_with_num_qubits(): else: vb.i(i) circuit = Circuit().add_verbatim_box(vb) - criterion.validate(circuit) + validator.validate(circuit) assert nx.utils.graphs_equal( - criterion._connectivity_graph, nx.complete_graph(num_qubits, create_using=nx.DiGraph()) + validator._connectivity_graph, nx.complete_graph(num_qubits, create_using=nx.DiGraph()) ) def test_complete_graph_instantation_with_qubit_labels(): """ Tests that, if fully_connected is True and num_qubits are passed into the - ConnectivityCriterion constructor, a fully connected graph is created. + ConnectivityValidator constructor, a fully connected graph is created. """ qubit_labels = [0, 1, 10, 11, 110, 111, 112, 113] - criterion = ConnectivityCriterion(qubit_labels=qubit_labels, fully_connected=True) + validator = ConnectivityValidator(qubit_labels=qubit_labels, fully_connected=True) vb = Circuit() for i in qubit_labels: for j in qubit_labels: @@ -99,9 +99,9 @@ def test_complete_graph_instantation_with_qubit_labels(): else: vb.i(i) circuit = Circuit().add_verbatim_box(vb) - criterion.validate(circuit) + validator.validate(circuit) assert nx.utils.graphs_equal( - criterion._connectivity_graph, nx.complete_graph(qubit_labels, create_using=nx.DiGraph()) + validator._connectivity_graph, nx.complete_graph(qubit_labels, create_using=nx.DiGraph()) ) @@ -118,7 +118,7 @@ def test_complete_graph_instantation_with_qubit_labels(): ) def test_invalid_2_qubit_gates(six_node_digraph, circuit): with pytest.raises(ValueError): - ConnectivityCriterion(six_node_digraph).validate(circuit) + ConnectivityValidator(six_node_digraph).validate(circuit) @pytest.mark.parametrize( @@ -134,13 +134,13 @@ def test_invalid_2_qubit_gates(six_node_digraph, circuit): ) def test_invalid_1_qubit_gates(six_node_digraph, circuit): with pytest.raises(ValueError): - ConnectivityCriterion(six_node_digraph).validate(circuit) + ConnectivityValidator(six_node_digraph).validate(circuit) def test_equality_graph_created_with_dict(six_node_digraph): graph = {0: [1, 3], 1: [0, 2, 10], 2: [1, 3, 11], 10: [1, 11], 11: [2, 10]} - criteria_from_digraph = ConnectivityCriterion(six_node_digraph) - criteria_from_dict = ConnectivityCriterion(graph) + criteria_from_digraph = ConnectivityValidator(six_node_digraph) + criteria_from_dict = ConnectivityValidator(graph) assert criteria_from_dict == criteria_from_digraph @@ -158,7 +158,7 @@ def test_invalid_constructors( connectivity_graph, fully_connected, num_qubits, qubit_labels, directed ): with pytest.raises(ValueError): - ConnectivityCriterion( + ConnectivityValidator( connectivity_graph, fully_connected, num_qubits, qubit_labels, directed ) @@ -188,7 +188,7 @@ def test_undirected_graph_construction(representation): ], create_using=nx.DiGraph, ) - cc = ConnectivityCriterion(representation, directed=False) + cc = ConnectivityValidator(representation, directed=False) assert graphs_equal(cc._connectivity_graph, expected_digraph) @@ -209,9 +209,9 @@ def test_undirected_graph_construction(representation): ], ) def test_validate_instruction_method(controls, targets, is_valid, six_node_digraph): - gcc = ConnectivityCriterion(six_node_digraph, directed=False) + gcc = ConnectivityValidator(six_node_digraph, directed=False) if is_valid: - gcc.validate_instruction_connectivity(controls, targets) + gcc._validate_instruction_connectivity(controls, targets) else: with pytest.raises(ValueError): - gcc.validate_instruction_connectivity(controls, targets) + gcc._validate_instruction_connectivity(controls, targets) diff --git a/test/unit_tests/braket/emulation/test_emulator.py b/test/unit_tests/braket/emulation/test_emulator.py index 562822f85..a8045052c 100644 --- a/test/unit_tests/braket/emulation/test_emulator.py +++ b/test/unit_tests/braket/emulation/test_emulator.py @@ -9,16 +9,16 @@ from braket.circuits.noises import BitFlip from braket.default_simulator import DensityMatrixSimulator, StateVectorSimulator from braket.devices import local_simulator -from braket.emulators import Emulator -from braket.emulators.emulator_passes import ( - EmulatorPass, - GateCriterion, +from braket.emulation import Emulator +from braket.emulation.emulator_passes import ( + EmulationPass, + GateValidator, ProgramType, - QubitCountCriterion, + QubitCountValidator, ) -class AlwaysFailPass(EmulatorPass): +class AlwaysFailPass(EmulationPass): def run(self, program: ProgramType): raise ValueError("This pass always raises an error.") @@ -40,8 +40,8 @@ def empty_emulator(setup_local_simulator_devices): @pytest.fixture def basic_emulator(empty_emulator): - qubit_count_criterion = QubitCountCriterion(4) - return empty_emulator.add_pass(emulator_pass=[qubit_count_criterion]) + qubit_count_validator = QubitCountValidator(4) + return empty_emulator.add_pass(emulator_pass=[qubit_count_validator]) def test_empty_emulator_validation(empty_emulator): @@ -61,7 +61,7 @@ def test_basic_emulator(basic_emulator): def test_basic_invalidate(basic_emulator): """ - Emulator should raise an error thrown by the QubitCountCriterion. + Emulator should raise an error thrown by the QubitCountValidator. """ circuit = Circuit().x(range(6)) match_string = re.escape( @@ -74,10 +74,10 @@ def test_basic_invalidate(basic_emulator): def test_add_pass_single(empty_emulator): emulator = empty_emulator - qubit_count_criterion = QubitCountCriterion(4) - emulator.add_pass(qubit_count_criterion) + qubit_count_validator = QubitCountValidator(4) + emulator.add_pass(qubit_count_validator) - assert emulator._emulator_passes == [qubit_count_criterion] + assert emulator._emulator_passes == [qubit_count_validator] def test_bad_add_pass(empty_emulator): @@ -87,16 +87,16 @@ def test_bad_add_pass(empty_emulator): def test_add_pass_multiple(setup_local_simulator_devices): - native_gate_criterion = GateCriterion(native_gates=["CZ", "PRx"]) - emulator = Emulator(emulator_passes=[native_gate_criterion]) - qubit_count_criterion = QubitCountCriterion(4) - gate_criterion = GateCriterion(supported_gates=["H", "CNot"]) + native_gate_validator = GateValidator(native_gates=["CZ", "PRx"]) + emulator = Emulator(emulator_passes=[native_gate_validator]) + qubit_count_validator = QubitCountValidator(4) + gate_validator = GateValidator(supported_gates=["H", "CNot"]) - emulator.add_pass([qubit_count_criterion, gate_criterion]) + emulator.add_pass([qubit_count_validator, gate_validator]) assert emulator._emulator_passes == [ - native_gate_criterion, - qubit_count_criterion, - gate_criterion, + native_gate_validator, + qubit_count_validator, + gate_validator, ] @@ -119,9 +119,9 @@ def test_update_noise_model(empty_emulator): def test_validation_only_pass(setup_local_simulator_devices): - qubit_count_criterion = QubitCountCriterion(4) + qubit_count_validator = QubitCountValidator(4) bad_pass = AlwaysFailPass() - emulator = Emulator(emulator_passes=[bad_pass, qubit_count_criterion]) + emulator = Emulator(emulator_passes=[bad_pass, qubit_count_validator]) circuit = Circuit().h(range(5)) match_string = re.escape( @@ -151,9 +151,9 @@ def test_apply_noise_model(setup_local_simulator_devices): def test_noiseless_run(setup_local_simulator_devices): - qubit_count_criterion = QubitCountCriterion(4) - gate_criterion = GateCriterion(supported_gates=["H"]) - emulator = Emulator(emulator_passes=[qubit_count_criterion, gate_criterion]) + qubit_count_validator = QubitCountValidator(4) + gate_validator = GateValidator(supported_gates=["H"]) + emulator = Emulator(emulator_passes=[qubit_count_validator, gate_validator]) circuit = Circuit().h(0).state_vector() result = emulator.run(circuit).result() @@ -166,11 +166,11 @@ def test_noisy_run(setup_local_simulator_devices): noise_model = NoiseModel() noise_model.add_noise(BitFlip(0.1), GateCriteria(Gate.H)) - qubit_count_criterion = QubitCountCriterion(4) - gate_criterion = GateCriterion(supported_gates=["H"]) + qubit_count_validator = QubitCountValidator(4) + gate_validator = GateValidator(supported_gates=["H"]) emulator = Emulator( backend="braket_dm", - emulator_passes=[qubit_count_criterion, gate_criterion], + emulator_passes=[qubit_count_validator, gate_validator], noise_model=noise_model, ) diff --git a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py b/test/unit_tests/braket/emulation/test_gate_connectivity_validator.py similarity index 85% rename from test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py rename to test/unit_tests/braket/emulation/test_gate_connectivity_validator.py index c0575c3e8..6b81cc110 100644 --- a/test/unit_tests/braket/emulation/test_gate_connectivity_criterion.py +++ b/test/unit_tests/braket/emulation/test_gate_connectivity_validator.py @@ -5,7 +5,7 @@ from braket.circuits import Circuit, Gate from braket.circuits.noises import BitFlip -from braket.emulators.emulator_passes.criteria import GateConnectivityCriterion +from braket.emulation.emulator_passes.criteria import GateConnectivityValidator @pytest.fixture @@ -66,10 +66,10 @@ def basic_4_node_graph_as_dict(): ) def test_valid_basic_contiguous_circuits(basic_4_node_graph, circuit): """ - ConnectivityGateCriterion should not raise any errors when validating these circuits. + GateConnectivityValidator should not raise any errors when validating these circuits. """ - gate_connectivity_criterion = GateConnectivityCriterion(basic_4_node_graph) - gate_connectivity_criterion.validate(circuit) + gate_connectivity_validator = GateConnectivityValidator(basic_4_node_graph) + gate_connectivity_validator.validate(circuit) @pytest.mark.parametrize( @@ -92,15 +92,15 @@ def test_valid_basic_contiguous_circuits(basic_4_node_graph, circuit): ) def test_valid_basic_discontiguous_circuits(basic_discontiguous_4_node_graph, circuit): """ - ConnectivityGateCriterion should not raise any errors when validating these circuits. + GateConnectivityValidator should not raise any errors when validating these circuits. """ - gate_connectivity_criterion = GateConnectivityCriterion(basic_discontiguous_4_node_graph) - gate_connectivity_criterion.validate(circuit) + gate_connectivity_validator = GateConnectivityValidator(basic_discontiguous_4_node_graph) + gate_connectivity_validator.validate(circuit) def test_directed_graph_construction_from_dict(): """ - ConnectivityGateCriterion should correctly construct a graph from a dictionary + GateConnectivityValidator should correctly construct a graph from a dictionary representation of the connectivity. """ dict_representation = { @@ -118,7 +118,7 @@ def test_directed_graph_construction_from_dict(): (2, 5, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), ] ) - gcc = GateConnectivityCriterion(dict_representation) + gcc = GateConnectivityValidator(dict_representation) assert graphs_equal(gcc._gate_connectivity_graph, digraph_representation) @@ -140,17 +140,17 @@ def test_directed_graph_construction_from_dict(): ) def test_undirected_criteria_from_dict_with_valid_circuits(basic_4_node_graph_as_dict, circuit): """ - ConnectivityGateCriterion should not raise any errors when validating these circuits. + GateConnectivityValidator should not raise any errors when validating these circuits. """ - gate_connectivity_criterion = GateConnectivityCriterion( + gate_connectivity_validator = GateConnectivityValidator( basic_4_node_graph_as_dict, directed=False ) - gate_connectivity_criterion.validate(circuit) + gate_connectivity_validator.validate(circuit) def test_undirected_graph_construction_from_dict(): """ - ConnectivityGateCriterion should correctly construct an undirected graph from a dictionary + GateConnectivityValidator should correctly construct an undirected graph from a dictionary representation of the connectivity. """ dict_representation = { @@ -173,7 +173,7 @@ def test_undirected_graph_construction_from_dict(): (5, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), ] ) - gcc = GateConnectivityCriterion(dict_representation, directed=False) + gcc = GateConnectivityValidator(dict_representation, directed=False) assert graphs_equal(gcc._gate_connectivity_graph, digraph_representation) @@ -199,7 +199,7 @@ def test_undirected_graph_construction_from_dict(): def test_undirected_graph_from_digraph(edges): """ Check that undirected topologies created from a digraph correctly add all possible - back edges to the criterion's connectivity graph. + back edges to the validator's connectivity graph. """ directed_graph = nx.DiGraph() directed_graph.add_edges_from(edges) @@ -209,10 +209,10 @@ def test_undirected_graph_from_digraph(edges): if (edge[1], edge[0]) not in undirected_graph.edges: undirected_graph.add_edges_from([(edge[1], edge[0], edge[2])]) - gcc = GateConnectivityCriterion(directed_graph, directed=False) + gcc = GateConnectivityValidator(directed_graph, directed=False) assert graphs_equal(gcc._gate_connectivity_graph, undirected_graph) - gcc_other = GateConnectivityCriterion(undirected_graph) + gcc_other = GateConnectivityValidator(undirected_graph) assert gcc_other == gcc @@ -236,7 +236,7 @@ def create_undirected_graph_with_exisiting_back_edges(representation): is created properly. """ - gcc = GateConnectivityCriterion(representation, directed=False) + gcc = GateConnectivityValidator(representation, directed=False) expected_digraph_representation = nx.DiGraph() expected_digraph_representation.add_edges_from( [ @@ -261,14 +261,14 @@ def create_undirected_graph_with_exisiting_back_edges(representation): ) def test_invalid_circuits(basic_4_node_graph, circuit): with pytest.raises(ValueError): - gate_connectivity_criterion = GateConnectivityCriterion(basic_4_node_graph) - gate_connectivity_criterion.validate(circuit) + gate_connectivity_validator = GateConnectivityValidator(basic_4_node_graph) + gate_connectivity_validator.validate(circuit) def test_invalid_connectivity_graph(): bad_graph = nx.complete_graph(5, create_using=nx.Graph()) with pytest.raises(TypeError): - GateConnectivityCriterion(bad_graph) + GateConnectivityValidator(bad_graph) @pytest.mark.parametrize( @@ -283,12 +283,12 @@ def test_invalid_connectivity_graph(): ], ) def test_validate_instruction_method(gate_name, controls, targets, is_valid, basic_4_node_graph): - gcc = GateConnectivityCriterion(basic_4_node_graph, directed=False) + gcc = GateConnectivityValidator(basic_4_node_graph, directed=False) if is_valid: - gcc.validate_instruction_connectivity(gate_name, controls, targets) + gcc._validate_instruction_connectivity(gate_name, controls, targets) else: with pytest.raises(ValueError): - gcc.validate_instruction_connectivity(gate_name, controls, targets) + gcc._validate_instruction_connectivity(gate_name, controls, targets) @pytest.mark.parametrize( @@ -309,4 +309,4 @@ def test_validate_instruction_method(gate_name, controls, targets, is_valid, bas ) def test_invalid_undirected_graph(graph): with pytest.raises(ValueError): - GateConnectivityCriterion(graph, directed=False) + GateConnectivityValidator(graph, directed=False) diff --git a/test/unit_tests/braket/emulation/test_gate_criterion.py b/test/unit_tests/braket/emulation/test_gate_validator.py similarity index 82% rename from test/unit_tests/braket/emulation/test_gate_criterion.py rename to test/unit_tests/braket/emulation/test_gate_validator.py index 9f85969d6..2003b629f 100644 --- a/test/unit_tests/braket/emulation/test_gate_criterion.py +++ b/test/unit_tests/braket/emulation/test_gate_validator.py @@ -4,7 +4,7 @@ from braket.circuits import Circuit, Gate, Instruction from braket.circuits.compiler_directives import StartVerbatimBox from braket.circuits.noises import BitFlip -from braket.emulators.emulator_passes.criteria import GateCriterion +from braket.emulation.emulator_passes.criteria import GateValidator @pytest.fixture @@ -74,43 +74,43 @@ def mock_qpu_gates(): ) def test_valid_circuits(mock_qpu_gates, circuit): """ - GateCriterion should not raise any errors when validating these circuits. + GateValidator should not raise any errors when validating these circuits. """ - GateCriterion(mock_qpu_gates[0], mock_qpu_gates[1]).validate(circuit) + GateValidator(mock_qpu_gates[0], mock_qpu_gates[1]).validate(circuit) def test_only_supported_gates(): supported_gates = ["h", "cnot", "rx", "xx", "y"] - criterion = GateCriterion(supported_gates=supported_gates) + validator = GateValidator(supported_gates=supported_gates) circuit = Circuit().h(0).cnot(0, 1).rx(4, np.pi / 4).xx(2, 3, np.pi / 4).y(7) - criterion.validate(circuit) + validator.validate(circuit) def test_verbatim_circuit_only_supported_gates(): supported_gates = ["h", "cnot", "rx", "xx", "y"] - criterion = GateCriterion(supported_gates=supported_gates) + validator = GateValidator(supported_gates=supported_gates) circuit = Circuit().add_verbatim_box(Circuit().h(0)) with pytest.raises(ValueError): - criterion.validate(circuit) + validator.validate(circuit) def test_only_native_gates(): native_gates = ["h", "cnot", "rx", "xx", "y"] - criterion = GateCriterion(native_gates=native_gates) + validator = GateValidator(native_gates=native_gates) vb = Circuit().h(0).cnot(0, 1).rx(4, np.pi / 4).xx(2, 3, np.pi / 4).y(7) circuit = Circuit().add_verbatim_box(vb) - criterion.validate(circuit) + validator.validate(circuit) def test_non_verbatim_circuit_only_native_gates(): native_gates = ["h", "cnot", "rx", "xx", "y"] - criterion = GateCriterion(native_gates=native_gates) + validator = GateValidator(native_gates=native_gates) vb = Circuit().h(0).cnot(0, 1).rx(4, np.pi / 4).xx(2, 3, np.pi / 4).y(7) circuit = Circuit().add_verbatim_box(vb) circuit.i(0) with pytest.raises(ValueError): - criterion.validate(circuit) + validator.validate(circuit) @pytest.mark.parametrize( @@ -128,7 +128,7 @@ def test_non_verbatim_circuit_only_native_gates(): ) def test_invalid_instantiation(supported_gates, native_gates, error_message): with pytest.raises(ValueError, match=error_message): - GateCriterion(supported_gates, native_gates) + GateValidator(supported_gates, native_gates) @pytest.mark.parametrize( @@ -147,10 +147,10 @@ def test_invalid_instantiation(supported_gates, native_gates, error_message): ) def test_invalid_circuits(basic_gate_set, circuit): """ - GateCriterion should raise errors when validating these circuits. + GateValidator should raise errors when validating these circuits. """ with pytest.raises(ValueError): - GateCriterion(basic_gate_set[0], basic_gate_set[1]).validate(circuit) + GateValidator(basic_gate_set[0], basic_gate_set[1]).validate(circuit) @pytest.mark.parametrize( @@ -162,7 +162,7 @@ def test_invalid_circuits(basic_gate_set, circuit): ], ) def test_equality(gate_set_1, gate_set_2): - assert GateCriterion(gate_set_1) == GateCriterion(gate_set_2) + assert GateValidator(gate_set_1) == GateValidator(gate_set_2) @pytest.mark.parametrize( @@ -175,4 +175,4 @@ def test_equality(gate_set_1, gate_set_2): ], ) def test_inequality(gate_set_1, gate_set_2): - assert GateCriterion(gate_set_1) != GateCriterion(gate_set_2) + assert GateValidator(gate_set_1) != GateValidator(gate_set_2) diff --git a/test/unit_tests/braket/emulation/test_qubit_count_criterion.py b/test/unit_tests/braket/emulation/test_qubit_count_validator.py similarity index 74% rename from test/unit_tests/braket/emulation/test_qubit_count_criterion.py rename to test/unit_tests/braket/emulation/test_qubit_count_validator.py index c487e2eab..1007d726b 100644 --- a/test/unit_tests/braket/emulation/test_qubit_count_criterion.py +++ b/test/unit_tests/braket/emulation/test_qubit_count_validator.py @@ -2,7 +2,7 @@ import pytest from braket.circuits import Circuit -from braket.emulators.emulator_passes import QubitCountCriterion +from braket.emulation.emulator_passes import QubitCountValidator @pytest.mark.parametrize( @@ -19,15 +19,15 @@ ) def test_valid_circuits(qubit_count, circuit): """ - QubitCountCriterion should not raise any errors when validating these circuits. + QubitCountValidator should not raise any errors when validating these circuits. """ - QubitCountCriterion(qubit_count=qubit_count).__call__(circuit) + QubitCountValidator(qubit_count=qubit_count).__call__(circuit) @pytest.mark.parametrize("qubit_count", [0, -1]) def test_invalid_instantiation(qubit_count): with pytest.raises(ValueError): - QubitCountCriterion(qubit_count) + QubitCountValidator(qubit_count) @pytest.mark.parametrize( @@ -44,12 +44,12 @@ def test_invalid_circuits(qubit_count, circuit): match=f"Circuit must use at most {qubit_count} qubits, \ but uses {circuit.qubit_count} qubits.", ): - QubitCountCriterion(qubit_count).validate(circuit) + QubitCountValidator(qubit_count).validate(circuit) def test_equality(): - qcc_1 = QubitCountCriterion(1) - qcc_2 = QubitCountCriterion(2) + qcc_1 = QubitCountValidator(1) + qcc_2 = QubitCountValidator(2) assert qcc_1 != qcc_2 - assert qcc_1 == QubitCountCriterion(1) + assert qcc_1 == QubitCountValidator(1) From e1cb3074916a925749a4a7c7c9abf2aa52976069 Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 29 Jul 2024 20:08:49 -0700 Subject: [PATCH 81/91] change: Reorganize modules to place all gate-device emulator passes in a designated directory. --- src/braket/aws/aws_device.py | 2 +- ...s_emulator_helpers.py => aws_emulation.py} | 0 src/braket/aws/aws_noise_models.py | 2 +- src/braket/emulation/base_emulator.py | 2 +- .../emulation/emulator_passes/__init__.py | 4 +- .../emulator_passes/criteria/__init__.py | 11 -- .../gate_device_passes/__init__.py | 13 ++ .../connectivity_criterion.py | 160 ++++++++++++++++++ .../connectivity_validator.py | 2 +- .../gate_connectivity_validator.py | 2 +- .../gate_validator.py | 2 +- .../qubit_count_validator.py | 2 +- .../{criteria => }/validation_pass.py | 0 .../braket/aws/test_aws_emulation.py | 2 +- .../emulation/test_connectivity_validator.py | 2 +- .../test_gate_connectivity_validator.py | 2 +- .../braket/emulation/test_gate_validator.py | 2 +- 17 files changed, 186 insertions(+), 24 deletions(-) rename src/braket/aws/{aws_emulator_helpers.py => aws_emulation.py} (100%) delete mode 100644 src/braket/emulation/emulator_passes/criteria/__init__.py create mode 100644 src/braket/emulation/emulator_passes/gate_device_passes/__init__.py create mode 100644 src/braket/emulation/emulator_passes/gate_device_passes/connectivity_criterion.py rename src/braket/emulation/emulator_passes/{criteria => gate_device_passes}/connectivity_validator.py (98%) rename src/braket/emulation/emulator_passes/{criteria => gate_device_passes}/gate_connectivity_validator.py (98%) rename src/braket/emulation/emulator_passes/{criteria => gate_device_passes}/gate_validator.py (97%) rename src/braket/emulation/emulator_passes/{criteria => gate_device_passes}/qubit_count_validator.py (93%) rename src/braket/emulation/emulator_passes/{criteria => }/validation_pass.py (100%) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index f9a9463c0..b8b40ebde 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -27,7 +27,7 @@ from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem -from braket.aws.aws_emulator_helpers import ( +from braket.aws.aws_emulation import ( connectivity_validator, gate_connectivity_validator, gate_validator, diff --git a/src/braket/aws/aws_emulator_helpers.py b/src/braket/aws/aws_emulation.py similarity index 100% rename from src/braket/aws/aws_emulator_helpers.py rename to src/braket/aws/aws_emulation.py diff --git a/src/braket/aws/aws_noise_models.py b/src/braket/aws/aws_noise_models.py index 8f795528e..5254ce381 100644 --- a/src/braket/aws/aws_noise_models.py +++ b/src/braket/aws/aws_noise_models.py @@ -4,7 +4,7 @@ import numpy as np -from braket.aws.aws_emulator_helpers import _get_qpu_gate_translations +from braket.aws.aws_emulation import _get_qpu_gate_translations from braket.circuits import Gate from braket.circuits.noise_model import GateCriteria, NoiseModel, ObservableCriteria from braket.circuits.noises import ( diff --git a/src/braket/emulation/base_emulator.py b/src/braket/emulation/base_emulator.py index 63bcb3c11..5da37aa66 100644 --- a/src/braket/emulation/base_emulator.py +++ b/src/braket/emulation/base_emulator.py @@ -3,7 +3,7 @@ from typing import Iterable, Union from braket.emulation.emulator_passes import EmulationPass, ProgramType -from braket.emulation.emulator_passes.criteria import ValidationPass +from braket.emulation.emulator_passes.gate_device_passes import ValidationPass class BaseEmulator: diff --git a/src/braket/emulation/emulator_passes/__init__.py b/src/braket/emulation/emulator_passes/__init__.py index 0f328d6fa..2dea3be17 100644 --- a/src/braket/emulation/emulator_passes/__init__.py +++ b/src/braket/emulation/emulator_passes/__init__.py @@ -1,8 +1,8 @@ -from braket.emulation.emulator_passes.criteria import ( # noqa: F401 +from braket.emulation.emulator_passes.emulator_pass import EmulationPass, ProgramType # noqa: F401 +from braket.emulation.emulator_passes.gate_device_passes import ( # noqa: F401 ConnectivityValidator, GateConnectivityValidator, GateValidator, QubitCountValidator, ValidationPass, ) -from braket.emulation.emulator_passes.emulator_pass import EmulationPass, ProgramType # noqa: F401 diff --git a/src/braket/emulation/emulator_passes/criteria/__init__.py b/src/braket/emulation/emulator_passes/criteria/__init__.py deleted file mode 100644 index c15972293..000000000 --- a/src/braket/emulation/emulator_passes/criteria/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from braket.emulation.emulator_passes.criteria.connectivity_validator import ( # noqa: F401 - ConnectivityValidator, -) -from braket.emulation.emulator_passes.criteria.gate_connectivity_validator import ( # noqa: F401 - GateConnectivityValidator, -) -from braket.emulation.emulator_passes.criteria.gate_validator import GateValidator # noqa: F401 -from braket.emulation.emulator_passes.criteria.qubit_count_validator import ( # noqa: F401 - QubitCountValidator, -) -from braket.emulation.emulator_passes.criteria.validation_pass import ValidationPass # noqa: F401 diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/__init__.py b/src/braket/emulation/emulator_passes/gate_device_passes/__init__.py new file mode 100644 index 000000000..6fc61d939 --- /dev/null +++ b/src/braket/emulation/emulator_passes/gate_device_passes/__init__.py @@ -0,0 +1,13 @@ +from braket.emulation.emulator_passes.gate_device_passes.connectivity_validator import ( # noqa: F401 E501 + ConnectivityValidator, +) +from braket.emulation.emulator_passes.gate_device_passes.gate_connectivity_validator import ( # noqa: F401 E501 + GateConnectivityValidator, +) +from braket.emulation.emulator_passes.gate_device_passes.gate_validator import ( # noqa: F401 E501 + GateValidator, +) +from braket.emulation.emulator_passes.gate_device_passes.qubit_count_validator import ( # noqa: F401 E501 + QubitCountValidator, +) +from braket.emulation.emulator_passes.validation_pass import ValidationPass # noqa: F401 E501 diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_criterion.py b/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_criterion.py new file mode 100644 index 000000000..3079762c6 --- /dev/null +++ b/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_criterion.py @@ -0,0 +1,160 @@ +from collections.abc import Iterable +from typing import Dict, Optional, Union + +from networkx import DiGraph, complete_graph, from_dict_of_lists +from networkx.utils import graphs_equal + +from braket.circuits import Circuit +from braket.circuits.compiler_directives import StartVerbatimBox +from braket.circuits.gate import Gate +from braket.emulation.emulator_passes.validation_pass import ValidationPass +from braket.registers.qubit_set import QubitSet + + +class ConnectivityValidator(ValidationPass): + def __init__( + self, + connectivity_graph: Optional[Union[Dict[int, Iterable[int]], DiGraph]] = None, + fully_connected: bool = False, + num_qubits: Optional[int] = None, + qubit_labels: Optional[Union[Iterable[int], QubitSet]] = None, + directed: bool = True, + ): + """ + A ConnectivityValidator instance takes in a qubit connectivity graph and validates that + a circuit that uses verbatim circuits makes valid hardware qubit references in single + and two-qubit gate operations. + + Args: + connectivity_graph (Optional[Union[Dict[int, Iterable[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 (Optional[int]): The number of qubits in the device; if fully_connected is + True, create a complete graph with num_qubits nodes; ignored if + connectivity_graph is provided and fully_connected if False. + + qubit_labels (Optional[Union[Iterable[int], QubitSet]]): 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. + + Raises: + ValueError: If the inputs do not correctly yield a connectivity graph; i.e. + fully_connected is true but neither/both num qubits and qubit labels are defined + or a valid DiGraph or dict representation of a connectivity graph is not provided. + """ + + if not ((connectivity_graph is not None) ^ fully_connected): + raise ValueError( + "Either the connectivity_graph must be provided OR fully_connected must be True\ + (not both)." + ) + + if fully_connected: + if not ((num_qubits is None) ^ (qubit_labels is None)): + raise ValueError( + "Either num_qubits or qubit_labels (NOT both) must be \ + provided if fully_connected is True." + ) + self._connectivity_graph = complete_graph( + num_qubits if num_qubits else qubit_labels, create_using=DiGraph() + ) + elif not isinstance(connectivity_graph, DiGraph): + try: + self._connectivity_graph = from_dict_of_lists( + connectivity_graph, create_using=DiGraph() + ) + except Exception as e: + raise ValueError( + f"connectivity_graph must be a valid DiGraph or a dictionary\ + mapping integers (nodes) to a list of integers (adjancency lists): {e}" + ) + else: + self._connectivity_graph = connectivity_graph + + if not directed: + for edge in self._connectivity_graph.edges: + self._connectivity_graph.add_edge(edge[1], edge[0]) + + def validate(self, circuit: Circuit) -> None: + """ + Verifies that any verbatim box in a circuit is runnable with respect to the + device connectivity definied by this criterion. If any sub-circuit of the + input circuit is verbatim, we validate the connectivity of all gate operations + in the circuit. + + Args: + circuit (Circuit): The Braket circuit whose gate operations to + validate. + + Raises: + ValueError: If a hardware qubit reference does not exist in the connectivity graph. + """ + # If any of the instructions are in verbatim mode, all qubit references + # must point to hardware qubits. Otherwise, this circuit need not be validated. + if not any( + [ + isinstance(instruction.operator, StartVerbatimBox) + for instruction in circuit.instructions + ] + ): + return + for idx in range(len(circuit.instructions)): + instruction = circuit.instructions[idx] + if isinstance(instruction.operator, Gate): + if ( + instruction.operator.qubit_count == 2 + ): # Assuming only maximum 2-qubit native gates are supported + self._validate_instruction_connectivity(instruction.control, instruction.target) + else: + # just check that the target qubit exists in the connectivity graph + target_qubit = instruction.target[0] + if target_qubit not in self._connectivity_graph: + raise ValueError( + f"Qubit {target_qubit} does not exist in the device topology." + ) + + def _validate_instruction_connectivity( + self, control_qubits: QubitSet, target_qubits: QubitSet + ) -> None: + """ + Checks if a two-qubit instruction is valid based on this criterion's connectivity + graph. + + Args: + control_qubits (QubitSet): The control qubits used in this multi-qubit + operation. + target_qubits (QubitSet): The target qubits of this operation. For many gates, + both the control and target are stored in "target_qubits", so we may + see target_qubits have length 2. + + Raises: + ValueError: If any two-qubit gate operation uses a qubit edge that does not exist + in the qubit connectivity graph. + """ + # Create edges between each of the target qubits + gate_connectivity_graph = DiGraph() + # Create an edge from each control bit to each target qubit + if len(control_qubits) == 1 and len(target_qubits) == 1: + gate_connectivity_graph.add_edge(control_qubits[0], target_qubits[0]) + elif len(control_qubits) == 0 and len(target_qubits) == 2: + gate_connectivity_graph.add_edges_from( + [(target_qubits[0], target_qubits[1]), (target_qubits[1], target_qubits[0])] + ) + else: + raise ValueError("Unrecognized qubit targetting setup for a 2 qubit gate.") + # Check that each edge exists in this criterion's connectivity graph + for e in gate_connectivity_graph.edges: + if not self._connectivity_graph.has_edge(*e): + raise ValueError(f"{e[0]} is not connected to qubit {e[1]} in this device.") + + def __eq__(self, other: ValidationPass) -> bool: + return isinstance(other, ConnectivityValidator) and graphs_equal( + self._connectivity_graph, other._connectivity_graph + ) diff --git a/src/braket/emulation/emulator_passes/criteria/connectivity_validator.py b/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_validator.py similarity index 98% rename from src/braket/emulation/emulator_passes/criteria/connectivity_validator.py rename to src/braket/emulation/emulator_passes/gate_device_passes/connectivity_validator.py index c3557cb31..5980db2d3 100644 --- a/src/braket/emulation/emulator_passes/criteria/connectivity_validator.py +++ b/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_validator.py @@ -7,7 +7,7 @@ from braket.circuits import Circuit from braket.circuits.compiler_directives import StartVerbatimBox from braket.circuits.gate import Gate -from braket.emulation.emulator_passes.criteria.validation_pass import ValidationPass +from braket.emulation.emulator_passes.validation_pass import ValidationPass from braket.registers.qubit_set import QubitSet diff --git a/src/braket/emulation/emulator_passes/criteria/gate_connectivity_validator.py b/src/braket/emulation/emulator_passes/gate_device_passes/gate_connectivity_validator.py similarity index 98% rename from src/braket/emulation/emulator_passes/criteria/gate_connectivity_validator.py rename to src/braket/emulation/emulator_passes/gate_device_passes/gate_connectivity_validator.py index e78b11e25..e70c0cf9f 100644 --- a/src/braket/emulation/emulator_passes/criteria/gate_connectivity_validator.py +++ b/src/braket/emulation/emulator_passes/gate_device_passes/gate_connectivity_validator.py @@ -6,7 +6,7 @@ from braket.circuits.circuit import Circuit from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox from braket.circuits.gate import Gate -from braket.emulation.emulator_passes.criteria.validation_pass import ValidationPass +from braket.emulation.emulator_passes.validation_pass import ValidationPass from braket.registers.qubit_set import QubitSet diff --git a/src/braket/emulation/emulator_passes/criteria/gate_validator.py b/src/braket/emulation/emulator_passes/gate_device_passes/gate_validator.py similarity index 97% rename from src/braket/emulation/emulator_passes/criteria/gate_validator.py rename to src/braket/emulation/emulator_passes/gate_device_passes/gate_validator.py index f8c2c0f83..4381e4811 100644 --- a/src/braket/emulation/emulator_passes/criteria/gate_validator.py +++ b/src/braket/emulation/emulator_passes/gate_device_passes/gate_validator.py @@ -5,7 +5,7 @@ from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox from braket.circuits.gate import Gate from braket.circuits.translations import BRAKET_GATES -from braket.emulation.emulator_passes.criteria.validation_pass import ValidationPass +from braket.emulation.emulator_passes.validation_pass import ValidationPass class GateValidator(ValidationPass[Circuit]): diff --git a/src/braket/emulation/emulator_passes/criteria/qubit_count_validator.py b/src/braket/emulation/emulator_passes/gate_device_passes/qubit_count_validator.py similarity index 93% rename from src/braket/emulation/emulator_passes/criteria/qubit_count_validator.py rename to src/braket/emulation/emulator_passes/gate_device_passes/qubit_count_validator.py index 5e5b26aff..8fdaf8ecd 100644 --- a/src/braket/emulation/emulator_passes/criteria/qubit_count_validator.py +++ b/src/braket/emulation/emulator_passes/gate_device_passes/qubit_count_validator.py @@ -1,5 +1,5 @@ from braket.circuits import Circuit -from braket.emulation.emulator_passes.criteria.validation_pass import ValidationPass +from braket.emulation.emulator_passes.validation_pass import ValidationPass class QubitCountValidator(ValidationPass[Circuit]): diff --git a/src/braket/emulation/emulator_passes/criteria/validation_pass.py b/src/braket/emulation/emulator_passes/validation_pass.py similarity index 100% rename from src/braket/emulation/emulator_passes/criteria/validation_pass.py rename to src/braket/emulation/emulator_passes/validation_pass.py diff --git a/test/unit_tests/braket/aws/test_aws_emulation.py b/test/unit_tests/braket/aws/test_aws_emulation.py index e36068dce..5f769fb96 100644 --- a/test/unit_tests/braket/aws/test_aws_emulation.py +++ b/test/unit_tests/braket/aws/test_aws_emulation.py @@ -7,7 +7,7 @@ from common_test_utils import RIGETTI_ARN, RIGETTI_REGION from braket.aws import AwsDevice -from braket.aws.aws_emulator_helpers import _get_qpu_gate_translations +from braket.aws.aws_emulation import _get_qpu_gate_translations from braket.aws.aws_noise_models import ( GateDeviceCalibrationData, GateFidelity, diff --git a/test/unit_tests/braket/emulation/test_connectivity_validator.py b/test/unit_tests/braket/emulation/test_connectivity_validator.py index ea4478dd8..58709e490 100644 --- a/test/unit_tests/braket/emulation/test_connectivity_validator.py +++ b/test/unit_tests/braket/emulation/test_connectivity_validator.py @@ -4,7 +4,7 @@ from networkx.utils import graphs_equal from braket.circuits import Circuit -from braket.emulation.emulator_passes.criteria import ConnectivityValidator +from braket.emulation.emulator_passes.gate_device_passes import ConnectivityValidator @pytest.fixture diff --git a/test/unit_tests/braket/emulation/test_gate_connectivity_validator.py b/test/unit_tests/braket/emulation/test_gate_connectivity_validator.py index 6b81cc110..c9852606b 100644 --- a/test/unit_tests/braket/emulation/test_gate_connectivity_validator.py +++ b/test/unit_tests/braket/emulation/test_gate_connectivity_validator.py @@ -5,7 +5,7 @@ from braket.circuits import Circuit, Gate from braket.circuits.noises import BitFlip -from braket.emulation.emulator_passes.criteria import GateConnectivityValidator +from braket.emulation.emulator_passes.gate_device_passes import GateConnectivityValidator @pytest.fixture diff --git a/test/unit_tests/braket/emulation/test_gate_validator.py b/test/unit_tests/braket/emulation/test_gate_validator.py index 2003b629f..51b57980d 100644 --- a/test/unit_tests/braket/emulation/test_gate_validator.py +++ b/test/unit_tests/braket/emulation/test_gate_validator.py @@ -4,7 +4,7 @@ from braket.circuits import Circuit, Gate, Instruction from braket.circuits.compiler_directives import StartVerbatimBox from braket.circuits.noises import BitFlip -from braket.emulation.emulator_passes.criteria import GateValidator +from braket.emulation.emulator_passes.gate_device_passes import GateValidator @pytest.fixture From 3b500a35673f4d5a84c45eb096c37b31d0bee062 Mon Sep 17 00:00:00 2001 From: Nagji Date: Mon, 29 Jul 2024 23:15:45 -0700 Subject: [PATCH 82/91] fix: Correct __init__.pys for emulator_passes module and remove circular dependencies --- src/braket/aws/aws_emulation.py | 2 +- src/braket/emulation/base_emulator.py | 3 +-- src/braket/emulation/emulator_passes/__init__.py | 8 +------- .../emulator_passes/gate_device_passes/__init__.py | 1 - .../gate_device_passes/connectivity_criterion.py | 2 +- .../gate_device_passes/connectivity_validator.py | 2 +- .../gate_device_passes/gate_connectivity_validator.py | 2 +- .../gate_device_passes/gate_validator.py | 2 +- .../gate_device_passes/qubit_count_validator.py | 2 +- test/unit_tests/braket/aws/test_aws_emulation.py | 2 +- test/unit_tests/braket/emulation/test_emulator.py | 10 +++------- .../braket/emulation/test_qubit_count_validator.py | 2 +- 12 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/braket/aws/aws_emulation.py b/src/braket/aws/aws_emulation.py index f054dee61..2b380ee95 100644 --- a/src/braket/aws/aws_emulation.py +++ b/src/braket/aws/aws_emulation.py @@ -8,7 +8,7 @@ from braket.device_schema.ionq import IonqDeviceCapabilities from braket.device_schema.iqm import IqmDeviceCapabilities from braket.device_schema.rigetti import RigettiDeviceCapabilities -from braket.emulation.emulator_passes import ( +from braket.emulation.emulator_passes.gate_device_passes import ( ConnectivityValidator, GateConnectivityValidator, GateValidator, diff --git a/src/braket/emulation/base_emulator.py b/src/braket/emulation/base_emulator.py index 5da37aa66..3c6b0b180 100644 --- a/src/braket/emulation/base_emulator.py +++ b/src/braket/emulation/base_emulator.py @@ -2,8 +2,7 @@ from typing import Iterable, Union -from braket.emulation.emulator_passes import EmulationPass, ProgramType -from braket.emulation.emulator_passes.gate_device_passes import ValidationPass +from braket.emulation.emulator_passes import EmulationPass, ProgramType, ValidationPass class BaseEmulator: diff --git a/src/braket/emulation/emulator_passes/__init__.py b/src/braket/emulation/emulator_passes/__init__.py index 2dea3be17..aac13fe94 100644 --- a/src/braket/emulation/emulator_passes/__init__.py +++ b/src/braket/emulation/emulator_passes/__init__.py @@ -1,8 +1,2 @@ from braket.emulation.emulator_passes.emulator_pass import EmulationPass, ProgramType # noqa: F401 -from braket.emulation.emulator_passes.gate_device_passes import ( # noqa: F401 - ConnectivityValidator, - GateConnectivityValidator, - GateValidator, - QubitCountValidator, - ValidationPass, -) +from braket.emulation.emulator_passes.validation_pass import ValidationPass # noqa: F401 diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/__init__.py b/src/braket/emulation/emulator_passes/gate_device_passes/__init__.py index 6fc61d939..3a5bf20cb 100644 --- a/src/braket/emulation/emulator_passes/gate_device_passes/__init__.py +++ b/src/braket/emulation/emulator_passes/gate_device_passes/__init__.py @@ -10,4 +10,3 @@ from braket.emulation.emulator_passes.gate_device_passes.qubit_count_validator import ( # noqa: F401 E501 QubitCountValidator, ) -from braket.emulation.emulator_passes.validation_pass import ValidationPass # noqa: F401 E501 diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_criterion.py b/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_criterion.py index 3079762c6..8a7691946 100644 --- a/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_criterion.py +++ b/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_criterion.py @@ -7,7 +7,7 @@ from braket.circuits import Circuit from braket.circuits.compiler_directives import StartVerbatimBox from braket.circuits.gate import Gate -from braket.emulation.emulator_passes.validation_pass import ValidationPass +from braket.emulation.emulator_passes import ValidationPass from braket.registers.qubit_set import QubitSet diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_validator.py b/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_validator.py index 5980db2d3..51c9f69d4 100644 --- a/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_validator.py +++ b/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_validator.py @@ -7,7 +7,7 @@ from braket.circuits import Circuit from braket.circuits.compiler_directives import StartVerbatimBox from braket.circuits.gate import Gate -from braket.emulation.emulator_passes.validation_pass import ValidationPass +from braket.emulation.emulator_passes import ValidationPass from braket.registers.qubit_set import QubitSet diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/gate_connectivity_validator.py b/src/braket/emulation/emulator_passes/gate_device_passes/gate_connectivity_validator.py index e70c0cf9f..723707559 100644 --- a/src/braket/emulation/emulator_passes/gate_device_passes/gate_connectivity_validator.py +++ b/src/braket/emulation/emulator_passes/gate_device_passes/gate_connectivity_validator.py @@ -6,7 +6,7 @@ from braket.circuits.circuit import Circuit from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox from braket.circuits.gate import Gate -from braket.emulation.emulator_passes.validation_pass import ValidationPass +from braket.emulation.emulator_passes import ValidationPass from braket.registers.qubit_set import QubitSet diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/gate_validator.py b/src/braket/emulation/emulator_passes/gate_device_passes/gate_validator.py index 4381e4811..f13e492b9 100644 --- a/src/braket/emulation/emulator_passes/gate_device_passes/gate_validator.py +++ b/src/braket/emulation/emulator_passes/gate_device_passes/gate_validator.py @@ -5,7 +5,7 @@ from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox from braket.circuits.gate import Gate from braket.circuits.translations import BRAKET_GATES -from braket.emulation.emulator_passes.validation_pass import ValidationPass +from braket.emulation.emulator_passes import ValidationPass class GateValidator(ValidationPass[Circuit]): diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/qubit_count_validator.py b/src/braket/emulation/emulator_passes/gate_device_passes/qubit_count_validator.py index 8fdaf8ecd..0ca9d3311 100644 --- a/src/braket/emulation/emulator_passes/gate_device_passes/qubit_count_validator.py +++ b/src/braket/emulation/emulator_passes/gate_device_passes/qubit_count_validator.py @@ -1,5 +1,5 @@ from braket.circuits import Circuit -from braket.emulation.emulator_passes.validation_pass import ValidationPass +from braket.emulation.emulator_passes import ValidationPass class QubitCountValidator(ValidationPass[Circuit]): diff --git a/test/unit_tests/braket/aws/test_aws_emulation.py b/test/unit_tests/braket/aws/test_aws_emulation.py index 5f769fb96..1b4465938 100644 --- a/test/unit_tests/braket/aws/test_aws_emulation.py +++ b/test/unit_tests/braket/aws/test_aws_emulation.py @@ -31,7 +31,7 @@ from braket.devices import Devices from braket.devices.local_simulator import LocalSimulator from braket.emulation import Emulator -from braket.emulation.emulator_passes import ( +from braket.emulation.emulator_passes.gate_device_passes import ( ConnectivityValidator, GateConnectivityValidator, GateValidator, diff --git a/test/unit_tests/braket/emulation/test_emulator.py b/test/unit_tests/braket/emulation/test_emulator.py index a8045052c..a105ab804 100644 --- a/test/unit_tests/braket/emulation/test_emulator.py +++ b/test/unit_tests/braket/emulation/test_emulator.py @@ -10,15 +10,11 @@ from braket.default_simulator import DensityMatrixSimulator, StateVectorSimulator from braket.devices import local_simulator from braket.emulation import Emulator -from braket.emulation.emulator_passes import ( - EmulationPass, - GateValidator, - ProgramType, - QubitCountValidator, -) +from braket.emulation.emulator_passes import EmulationPass, ProgramType +from braket.emulation.emulator_passes.gate_device_passes import GateValidator, QubitCountValidator -class AlwaysFailPass(EmulationPass): +class AlwaysFailPass(EmulationPass[ProgramType]): def run(self, program: ProgramType): raise ValueError("This pass always raises an error.") diff --git a/test/unit_tests/braket/emulation/test_qubit_count_validator.py b/test/unit_tests/braket/emulation/test_qubit_count_validator.py index 1007d726b..4e2d536a4 100644 --- a/test/unit_tests/braket/emulation/test_qubit_count_validator.py +++ b/test/unit_tests/braket/emulation/test_qubit_count_validator.py @@ -2,7 +2,7 @@ import pytest from braket.circuits import Circuit -from braket.emulation.emulator_passes import QubitCountValidator +from braket.emulation.emulator_passes.gate_device_passes import QubitCountValidator @pytest.mark.parametrize( From 7d7279e7f25662865cb82b7e85db6e29030e4f16 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 30 Jul 2024 10:53:46 -0700 Subject: [PATCH 83/91] change: Rename emulator_passes directory to emulation_passes --- src/braket/aws/aws_device.py | 2 +- src/braket/aws/aws_emulation.py | 2 +- src/braket/emulation/base_emulator.py | 2 +- src/braket/emulation/emulation_passes/__init__.py | 5 +++++ .../emulation_pass.py} | 0 .../emulation_passes/gate_device_passes/__init__.py | 12 ++++++++++++ .../gate_device_passes/connectivity_criterion.py | 2 +- .../gate_device_passes/connectivity_validator.py | 2 +- .../gate_connectivity_validator.py | 2 +- .../gate_device_passes/gate_validator.py | 2 +- .../gate_device_passes/qubit_count_validator.py | 2 +- .../validation_pass.py | 2 +- src/braket/emulation/emulator.py | 2 +- src/braket/emulation/emulator_passes/__init__.py | 2 -- .../emulator_passes/gate_device_passes/__init__.py | 12 ------------ test/unit_tests/braket/aws/test_aws_emulation.py | 2 +- .../braket/emulation/test_connectivity_validator.py | 2 +- test/unit_tests/braket/emulation/test_emulator.py | 4 ++-- .../emulation/test_gate_connectivity_validator.py | 2 +- .../braket/emulation/test_gate_validator.py | 2 +- .../braket/emulation/test_qubit_count_validator.py | 2 +- 21 files changed, 34 insertions(+), 31 deletions(-) create mode 100644 src/braket/emulation/emulation_passes/__init__.py rename src/braket/emulation/{emulator_passes/emulator_pass.py => emulation_passes/emulation_pass.py} (100%) create mode 100644 src/braket/emulation/emulation_passes/gate_device_passes/__init__.py rename src/braket/emulation/{emulator_passes => emulation_passes}/gate_device_passes/connectivity_criterion.py (99%) rename src/braket/emulation/{emulator_passes => emulation_passes}/gate_device_passes/connectivity_validator.py (99%) rename src/braket/emulation/{emulator_passes => emulation_passes}/gate_device_passes/gate_connectivity_validator.py (99%) rename src/braket/emulation/{emulator_passes => emulation_passes}/gate_device_passes/gate_validator.py (98%) rename src/braket/emulation/{emulator_passes => emulation_passes}/gate_device_passes/qubit_count_validator.py (95%) rename src/braket/emulation/{emulator_passes => emulation_passes}/validation_pass.py (91%) delete mode 100644 src/braket/emulation/emulator_passes/__init__.py delete mode 100644 src/braket/emulation/emulator_passes/gate_device_passes/__init__.py diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index b8b40ebde..8603f648d 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -49,7 +49,7 @@ from braket.devices import Devices from braket.devices.device import Device from braket.emulation import Emulator -from braket.emulation.emulator_passes import ProgramType +from braket.emulation.emulation_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 diff --git a/src/braket/aws/aws_emulation.py b/src/braket/aws/aws_emulation.py index 2b380ee95..75ff9ad58 100644 --- a/src/braket/aws/aws_emulation.py +++ b/src/braket/aws/aws_emulation.py @@ -8,7 +8,7 @@ from braket.device_schema.ionq import IonqDeviceCapabilities from braket.device_schema.iqm import IqmDeviceCapabilities from braket.device_schema.rigetti import RigettiDeviceCapabilities -from braket.emulation.emulator_passes.gate_device_passes import ( +from braket.emulation.emulation_passes.gate_device_passes import ( ConnectivityValidator, GateConnectivityValidator, GateValidator, diff --git a/src/braket/emulation/base_emulator.py b/src/braket/emulation/base_emulator.py index 3c6b0b180..769a4df5a 100644 --- a/src/braket/emulation/base_emulator.py +++ b/src/braket/emulation/base_emulator.py @@ -2,7 +2,7 @@ from typing import Iterable, Union -from braket.emulation.emulator_passes import EmulationPass, ProgramType, ValidationPass +from braket.emulation.emulation_passes import EmulationPass, ProgramType, ValidationPass class BaseEmulator: diff --git a/src/braket/emulation/emulation_passes/__init__.py b/src/braket/emulation/emulation_passes/__init__.py new file mode 100644 index 000000000..a54b81cca --- /dev/null +++ b/src/braket/emulation/emulation_passes/__init__.py @@ -0,0 +1,5 @@ +from braket.emulation.emulation_passes.emulation_pass import ( # noqa: F401 + EmulationPass, + ProgramType, +) +from braket.emulation.emulation_passes.validation_pass import ValidationPass # noqa: F401 diff --git a/src/braket/emulation/emulator_passes/emulator_pass.py b/src/braket/emulation/emulation_passes/emulation_pass.py similarity index 100% rename from src/braket/emulation/emulator_passes/emulator_pass.py rename to src/braket/emulation/emulation_passes/emulation_pass.py diff --git a/src/braket/emulation/emulation_passes/gate_device_passes/__init__.py b/src/braket/emulation/emulation_passes/gate_device_passes/__init__.py new file mode 100644 index 000000000..030791be6 --- /dev/null +++ b/src/braket/emulation/emulation_passes/gate_device_passes/__init__.py @@ -0,0 +1,12 @@ +from braket.emulation.emulation_passes.gate_device_passes.connectivity_validator import ( # noqa: F401 E501 + ConnectivityValidator, +) +from braket.emulation.emulation_passes.gate_device_passes.gate_connectivity_validator import ( # noqa: F401 E501 + GateConnectivityValidator, +) +from braket.emulation.emulation_passes.gate_device_passes.gate_validator import ( # noqa: F401 E501 + GateValidator, +) +from braket.emulation.emulation_passes.gate_device_passes.qubit_count_validator import ( # noqa: F401 E501 + QubitCountValidator, +) diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_criterion.py b/src/braket/emulation/emulation_passes/gate_device_passes/connectivity_criterion.py similarity index 99% rename from src/braket/emulation/emulator_passes/gate_device_passes/connectivity_criterion.py rename to src/braket/emulation/emulation_passes/gate_device_passes/connectivity_criterion.py index 8a7691946..60ea7272d 100644 --- a/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_criterion.py +++ b/src/braket/emulation/emulation_passes/gate_device_passes/connectivity_criterion.py @@ -7,7 +7,7 @@ from braket.circuits import Circuit from braket.circuits.compiler_directives import StartVerbatimBox from braket.circuits.gate import Gate -from braket.emulation.emulator_passes import ValidationPass +from braket.emulation.emulation_passes import ValidationPass from braket.registers.qubit_set import QubitSet diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_validator.py b/src/braket/emulation/emulation_passes/gate_device_passes/connectivity_validator.py similarity index 99% rename from src/braket/emulation/emulator_passes/gate_device_passes/connectivity_validator.py rename to src/braket/emulation/emulation_passes/gate_device_passes/connectivity_validator.py index 51c9f69d4..b858cc6a4 100644 --- a/src/braket/emulation/emulator_passes/gate_device_passes/connectivity_validator.py +++ b/src/braket/emulation/emulation_passes/gate_device_passes/connectivity_validator.py @@ -7,7 +7,7 @@ from braket.circuits import Circuit from braket.circuits.compiler_directives import StartVerbatimBox from braket.circuits.gate import Gate -from braket.emulation.emulator_passes import ValidationPass +from braket.emulation.emulation_passes import ValidationPass from braket.registers.qubit_set import QubitSet diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/gate_connectivity_validator.py b/src/braket/emulation/emulation_passes/gate_device_passes/gate_connectivity_validator.py similarity index 99% rename from src/braket/emulation/emulator_passes/gate_device_passes/gate_connectivity_validator.py rename to src/braket/emulation/emulation_passes/gate_device_passes/gate_connectivity_validator.py index 723707559..b236aa87c 100644 --- a/src/braket/emulation/emulator_passes/gate_device_passes/gate_connectivity_validator.py +++ b/src/braket/emulation/emulation_passes/gate_device_passes/gate_connectivity_validator.py @@ -6,7 +6,7 @@ from braket.circuits.circuit import Circuit from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox from braket.circuits.gate import Gate -from braket.emulation.emulator_passes import ValidationPass +from braket.emulation.emulation_passes import ValidationPass from braket.registers.qubit_set import QubitSet diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/gate_validator.py b/src/braket/emulation/emulation_passes/gate_device_passes/gate_validator.py similarity index 98% rename from src/braket/emulation/emulator_passes/gate_device_passes/gate_validator.py rename to src/braket/emulation/emulation_passes/gate_device_passes/gate_validator.py index f13e492b9..c628879c3 100644 --- a/src/braket/emulation/emulator_passes/gate_device_passes/gate_validator.py +++ b/src/braket/emulation/emulation_passes/gate_device_passes/gate_validator.py @@ -5,7 +5,7 @@ from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox from braket.circuits.gate import Gate from braket.circuits.translations import BRAKET_GATES -from braket.emulation.emulator_passes import ValidationPass +from braket.emulation.emulation_passes import ValidationPass class GateValidator(ValidationPass[Circuit]): diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/qubit_count_validator.py b/src/braket/emulation/emulation_passes/gate_device_passes/qubit_count_validator.py similarity index 95% rename from src/braket/emulation/emulator_passes/gate_device_passes/qubit_count_validator.py rename to src/braket/emulation/emulation_passes/gate_device_passes/qubit_count_validator.py index 0ca9d3311..5ddf3cbbb 100644 --- a/src/braket/emulation/emulator_passes/gate_device_passes/qubit_count_validator.py +++ b/src/braket/emulation/emulation_passes/gate_device_passes/qubit_count_validator.py @@ -1,5 +1,5 @@ from braket.circuits import Circuit -from braket.emulation.emulator_passes import ValidationPass +from braket.emulation.emulation_passes import ValidationPass class QubitCountValidator(ValidationPass[Circuit]): diff --git a/src/braket/emulation/emulator_passes/validation_pass.py b/src/braket/emulation/emulation_passes/validation_pass.py similarity index 91% rename from src/braket/emulation/emulator_passes/validation_pass.py rename to src/braket/emulation/emulation_passes/validation_pass.py index 1641a3f79..ab3fa1ec7 100644 --- a/src/braket/emulation/emulator_passes/validation_pass.py +++ b/src/braket/emulation/emulation_passes/validation_pass.py @@ -2,7 +2,7 @@ from abc import abstractmethod -from braket.emulation.emulator_passes.emulator_pass import EmulationPass, ProgramType +from braket.emulation.emulation_passes.emulation_pass import EmulationPass, ProgramType class ValidationPass(EmulationPass[ProgramType]): diff --git a/src/braket/emulation/emulator.py b/src/braket/emulation/emulator.py index ec20563d4..12b56f36f 100644 --- a/src/braket/emulation/emulator.py +++ b/src/braket/emulation/emulator.py @@ -8,7 +8,7 @@ from braket.devices import Device from braket.devices.local_simulator import LocalSimulator from braket.emulation.base_emulator import BaseEmulator -from braket.emulation.emulator_passes import EmulationPass, ProgramType +from braket.emulation.emulation_passes import EmulationPass, ProgramType from braket.ir.openqasm import Program as OpenQasmProgram from braket.tasks import QuantumTask from braket.tasks.quantum_task_batch import QuantumTaskBatch diff --git a/src/braket/emulation/emulator_passes/__init__.py b/src/braket/emulation/emulator_passes/__init__.py deleted file mode 100644 index aac13fe94..000000000 --- a/src/braket/emulation/emulator_passes/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from braket.emulation.emulator_passes.emulator_pass import EmulationPass, ProgramType # noqa: F401 -from braket.emulation.emulator_passes.validation_pass import ValidationPass # noqa: F401 diff --git a/src/braket/emulation/emulator_passes/gate_device_passes/__init__.py b/src/braket/emulation/emulator_passes/gate_device_passes/__init__.py deleted file mode 100644 index 3a5bf20cb..000000000 --- a/src/braket/emulation/emulator_passes/gate_device_passes/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from braket.emulation.emulator_passes.gate_device_passes.connectivity_validator import ( # noqa: F401 E501 - ConnectivityValidator, -) -from braket.emulation.emulator_passes.gate_device_passes.gate_connectivity_validator import ( # noqa: F401 E501 - GateConnectivityValidator, -) -from braket.emulation.emulator_passes.gate_device_passes.gate_validator import ( # noqa: F401 E501 - GateValidator, -) -from braket.emulation.emulator_passes.gate_device_passes.qubit_count_validator import ( # noqa: F401 E501 - QubitCountValidator, -) diff --git a/test/unit_tests/braket/aws/test_aws_emulation.py b/test/unit_tests/braket/aws/test_aws_emulation.py index 1b4465938..6d0728326 100644 --- a/test/unit_tests/braket/aws/test_aws_emulation.py +++ b/test/unit_tests/braket/aws/test_aws_emulation.py @@ -31,7 +31,7 @@ from braket.devices import Devices from braket.devices.local_simulator import LocalSimulator from braket.emulation import Emulator -from braket.emulation.emulator_passes.gate_device_passes import ( +from braket.emulation.emulation_passes.gate_device_passes import ( ConnectivityValidator, GateConnectivityValidator, GateValidator, diff --git a/test/unit_tests/braket/emulation/test_connectivity_validator.py b/test/unit_tests/braket/emulation/test_connectivity_validator.py index 58709e490..c79ad0106 100644 --- a/test/unit_tests/braket/emulation/test_connectivity_validator.py +++ b/test/unit_tests/braket/emulation/test_connectivity_validator.py @@ -4,7 +4,7 @@ from networkx.utils import graphs_equal from braket.circuits import Circuit -from braket.emulation.emulator_passes.gate_device_passes import ConnectivityValidator +from braket.emulation.emulation_passes.gate_device_passes import ConnectivityValidator @pytest.fixture diff --git a/test/unit_tests/braket/emulation/test_emulator.py b/test/unit_tests/braket/emulation/test_emulator.py index a105ab804..84ac902bb 100644 --- a/test/unit_tests/braket/emulation/test_emulator.py +++ b/test/unit_tests/braket/emulation/test_emulator.py @@ -10,8 +10,8 @@ from braket.default_simulator import DensityMatrixSimulator, StateVectorSimulator from braket.devices import local_simulator from braket.emulation import Emulator -from braket.emulation.emulator_passes import EmulationPass, ProgramType -from braket.emulation.emulator_passes.gate_device_passes import GateValidator, QubitCountValidator +from braket.emulation.emulation_passes import EmulationPass, ProgramType +from braket.emulation.emulation_passes.gate_device_passes import GateValidator, QubitCountValidator class AlwaysFailPass(EmulationPass[ProgramType]): diff --git a/test/unit_tests/braket/emulation/test_gate_connectivity_validator.py b/test/unit_tests/braket/emulation/test_gate_connectivity_validator.py index c9852606b..ec629a4c1 100644 --- a/test/unit_tests/braket/emulation/test_gate_connectivity_validator.py +++ b/test/unit_tests/braket/emulation/test_gate_connectivity_validator.py @@ -5,7 +5,7 @@ from braket.circuits import Circuit, Gate from braket.circuits.noises import BitFlip -from braket.emulation.emulator_passes.gate_device_passes import GateConnectivityValidator +from braket.emulation.emulation_passes.gate_device_passes import GateConnectivityValidator @pytest.fixture diff --git a/test/unit_tests/braket/emulation/test_gate_validator.py b/test/unit_tests/braket/emulation/test_gate_validator.py index 51b57980d..a6de6a509 100644 --- a/test/unit_tests/braket/emulation/test_gate_validator.py +++ b/test/unit_tests/braket/emulation/test_gate_validator.py @@ -4,7 +4,7 @@ from braket.circuits import Circuit, Gate, Instruction from braket.circuits.compiler_directives import StartVerbatimBox from braket.circuits.noises import BitFlip -from braket.emulation.emulator_passes.gate_device_passes import GateValidator +from braket.emulation.emulation_passes.gate_device_passes import GateValidator @pytest.fixture diff --git a/test/unit_tests/braket/emulation/test_qubit_count_validator.py b/test/unit_tests/braket/emulation/test_qubit_count_validator.py index 4e2d536a4..effb21367 100644 --- a/test/unit_tests/braket/emulation/test_qubit_count_validator.py +++ b/test/unit_tests/braket/emulation/test_qubit_count_validator.py @@ -2,7 +2,7 @@ import pytest from braket.circuits import Circuit -from braket.emulation.emulator_passes.gate_device_passes import QubitCountValidator +from braket.emulation.emulation_passes.gate_device_passes import QubitCountValidator @pytest.mark.parametrize( From 8c63da614307565f0feb9455094f2352ed2b32f4 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 30 Jul 2024 11:10:47 -0700 Subject: [PATCH 84/91] change: Remove outdated connectivity_criterion.py file --- .../connectivity_criterion.py | 160 ------------------ 1 file changed, 160 deletions(-) delete mode 100644 src/braket/emulation/emulation_passes/gate_device_passes/connectivity_criterion.py diff --git a/src/braket/emulation/emulation_passes/gate_device_passes/connectivity_criterion.py b/src/braket/emulation/emulation_passes/gate_device_passes/connectivity_criterion.py deleted file mode 100644 index 60ea7272d..000000000 --- a/src/braket/emulation/emulation_passes/gate_device_passes/connectivity_criterion.py +++ /dev/null @@ -1,160 +0,0 @@ -from collections.abc import Iterable -from typing import Dict, Optional, Union - -from networkx import DiGraph, complete_graph, from_dict_of_lists -from networkx.utils import graphs_equal - -from braket.circuits import Circuit -from braket.circuits.compiler_directives import StartVerbatimBox -from braket.circuits.gate import Gate -from braket.emulation.emulation_passes import ValidationPass -from braket.registers.qubit_set import QubitSet - - -class ConnectivityValidator(ValidationPass): - def __init__( - self, - connectivity_graph: Optional[Union[Dict[int, Iterable[int]], DiGraph]] = None, - fully_connected: bool = False, - num_qubits: Optional[int] = None, - qubit_labels: Optional[Union[Iterable[int], QubitSet]] = None, - directed: bool = True, - ): - """ - A ConnectivityValidator instance takes in a qubit connectivity graph and validates that - a circuit that uses verbatim circuits makes valid hardware qubit references in single - and two-qubit gate operations. - - Args: - connectivity_graph (Optional[Union[Dict[int, Iterable[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 (Optional[int]): The number of qubits in the device; if fully_connected is - True, create a complete graph with num_qubits nodes; ignored if - connectivity_graph is provided and fully_connected if False. - - qubit_labels (Optional[Union[Iterable[int], QubitSet]]): 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. - - Raises: - ValueError: If the inputs do not correctly yield a connectivity graph; i.e. - fully_connected is true but neither/both num qubits and qubit labels are defined - or a valid DiGraph or dict representation of a connectivity graph is not provided. - """ - - if not ((connectivity_graph is not None) ^ fully_connected): - raise ValueError( - "Either the connectivity_graph must be provided OR fully_connected must be True\ - (not both)." - ) - - if fully_connected: - if not ((num_qubits is None) ^ (qubit_labels is None)): - raise ValueError( - "Either num_qubits or qubit_labels (NOT both) must be \ - provided if fully_connected is True." - ) - self._connectivity_graph = complete_graph( - num_qubits if num_qubits else qubit_labels, create_using=DiGraph() - ) - elif not isinstance(connectivity_graph, DiGraph): - try: - self._connectivity_graph = from_dict_of_lists( - connectivity_graph, create_using=DiGraph() - ) - except Exception as e: - raise ValueError( - f"connectivity_graph must be a valid DiGraph or a dictionary\ - mapping integers (nodes) to a list of integers (adjancency lists): {e}" - ) - else: - self._connectivity_graph = connectivity_graph - - if not directed: - for edge in self._connectivity_graph.edges: - self._connectivity_graph.add_edge(edge[1], edge[0]) - - def validate(self, circuit: Circuit) -> None: - """ - Verifies that any verbatim box in a circuit is runnable with respect to the - device connectivity definied by this criterion. If any sub-circuit of the - input circuit is verbatim, we validate the connectivity of all gate operations - in the circuit. - - Args: - circuit (Circuit): The Braket circuit whose gate operations to - validate. - - Raises: - ValueError: If a hardware qubit reference does not exist in the connectivity graph. - """ - # If any of the instructions are in verbatim mode, all qubit references - # must point to hardware qubits. Otherwise, this circuit need not be validated. - if not any( - [ - isinstance(instruction.operator, StartVerbatimBox) - for instruction in circuit.instructions - ] - ): - return - for idx in range(len(circuit.instructions)): - instruction = circuit.instructions[idx] - if isinstance(instruction.operator, Gate): - if ( - instruction.operator.qubit_count == 2 - ): # Assuming only maximum 2-qubit native gates are supported - self._validate_instruction_connectivity(instruction.control, instruction.target) - else: - # just check that the target qubit exists in the connectivity graph - target_qubit = instruction.target[0] - if target_qubit not in self._connectivity_graph: - raise ValueError( - f"Qubit {target_qubit} does not exist in the device topology." - ) - - def _validate_instruction_connectivity( - self, control_qubits: QubitSet, target_qubits: QubitSet - ) -> None: - """ - Checks if a two-qubit instruction is valid based on this criterion's connectivity - graph. - - Args: - control_qubits (QubitSet): The control qubits used in this multi-qubit - operation. - target_qubits (QubitSet): The target qubits of this operation. For many gates, - both the control and target are stored in "target_qubits", so we may - see target_qubits have length 2. - - Raises: - ValueError: If any two-qubit gate operation uses a qubit edge that does not exist - in the qubit connectivity graph. - """ - # Create edges between each of the target qubits - gate_connectivity_graph = DiGraph() - # Create an edge from each control bit to each target qubit - if len(control_qubits) == 1 and len(target_qubits) == 1: - gate_connectivity_graph.add_edge(control_qubits[0], target_qubits[0]) - elif len(control_qubits) == 0 and len(target_qubits) == 2: - gate_connectivity_graph.add_edges_from( - [(target_qubits[0], target_qubits[1]), (target_qubits[1], target_qubits[0])] - ) - else: - raise ValueError("Unrecognized qubit targetting setup for a 2 qubit gate.") - # Check that each edge exists in this criterion's connectivity graph - for e in gate_connectivity_graph.edges: - if not self._connectivity_graph.has_edge(*e): - raise ValueError(f"{e[0]} is not connected to qubit {e[1]} in this device.") - - def __eq__(self, other: ValidationPass) -> bool: - return isinstance(other, ConnectivityValidator) and graphs_equal( - self._connectivity_graph, other._connectivity_graph - ) From a79e07d64d7f306671cace3de8b8162d075d8673 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 30 Jul 2024 13:15:24 -0700 Subject: [PATCH 85/91] fix: Use deepcopy before applying emulation passes to a program, fix Emulator._get_local_simulator_backend overwriting user provided backend name. --- src/braket/aws/aws_device.py | 5 +++-- src/braket/emulation/emulator.py | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 8603f648d..ea8e3788d 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -18,6 +18,7 @@ import os import urllib.request import warnings +from copy import deepcopy from datetime import datetime from enum import Enum from typing import Any, ClassVar, Optional, Union @@ -949,7 +950,7 @@ def run_passes( ProgramType: A validated and compiled program that may be augmented with noise operations to mimic noise on this device. """ - task_specification = task_specification.copy() + task_specification = deepcopy(task_specification) return self.emulator.run_passes(task_specification, apply_noise_model) def emulate( @@ -976,5 +977,5 @@ def emulate( Returns: QuantumTask: The QuantumTask tracking task execution on this device emulator. """ - task_specification = task_specification.copy() + task_specification = deepcopy(task_specification) return self.emulator.run(task_specification, shots, inputs) diff --git a/src/braket/emulation/emulator.py b/src/braket/emulation/emulator.py index 12b56f36f..18dfe723f 100644 --- a/src/braket/emulation/emulator.py +++ b/src/braket/emulation/emulator.py @@ -51,14 +51,15 @@ def _get_local_simulator_backend( Returns: str: The name of the backend to pass into the LocalSimulator constructor. """ - if noise_model: - if backend == "default": + if backend == "default": + if noise_model: logging.info( "Setting LocalSimulator backend to use 'braket_dm' \ because a NoiseModel was provided." ) - return Emulator._DEFAULT_NOISY_BACKEND - return Emulator._DEFAULT_SIMULATOR_BACKEND + return Emulator._DEFAULT_NOISY_BACKEND + return Emulator._DEFAULT_SIMULATOR_BACKEND + return backend def run( self, From f30379c4104cea6010d97d04e00dfb24c33e1504 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 30 Jul 2024 14:56:04 -0700 Subject: [PATCH 86/91] fix: Fix Emulator raising a new Exception instance from the original exception raised by an EmulatorPass in order to modify the message with the Emulator name. --- src/braket/emulation/emulator.py | 2 +- test/unit_tests/braket/aws/test_aws_emulation.py | 2 +- test/unit_tests/braket/emulation/test_emulator.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/braket/emulation/emulator.py b/src/braket/emulation/emulator.py index 18dfe723f..51cc8e93b 100644 --- a/src/braket/emulation/emulator.py +++ b/src/braket/emulation/emulator.py @@ -186,4 +186,4 @@ def _raise_exception(self, exception: Exception) -> None: Args: exception (Exception): The exception to modify and raise. """ - raise type(exception)(str(exception) + f" ({self._name})") + raise Exception(str(exception) + f" ({self._name})") from exception diff --git a/test/unit_tests/braket/aws/test_aws_emulation.py b/test/unit_tests/braket/aws/test_aws_emulation.py index 6d0728326..53d5ae53a 100644 --- a/test/unit_tests/braket/aws/test_aws_emulation.py +++ b/test/unit_tests/braket/aws/test_aws_emulation.py @@ -693,7 +693,7 @@ def test_emulator_passes(circuit, is_valid, rigetti_device): rigetti_device.validate(circuit) assert rigetti_device.run_passes(circuit, apply_noise_model=False) == circuit else: - with pytest.raises(ValueError): + with pytest.raises(Exception): rigetti_device.validate(circuit) diff --git a/test/unit_tests/braket/emulation/test_emulator.py b/test/unit_tests/braket/emulation/test_emulator.py index 84ac902bb..8fe68b2c2 100644 --- a/test/unit_tests/braket/emulation/test_emulator.py +++ b/test/unit_tests/braket/emulation/test_emulator.py @@ -64,7 +64,7 @@ def test_basic_invalidate(basic_emulator): f"Circuit must use at most 4 qubits, \ but uses {circuit.qubit_count} qubits. (DeviceEmulator)" ) - with pytest.raises(ValueError, match=match_string): + with pytest.raises(Exception, match=match_string): basic_emulator.run_passes(circuit) @@ -124,7 +124,7 @@ def test_validation_only_pass(setup_local_simulator_devices): f"Circuit must use at most 4 qubits, \ but uses {circuit.qubit_count} qubits. (DeviceEmulator)" ) - with pytest.raises(ValueError, match=match_string): + with pytest.raises(Exception, match=match_string): emulator.validate(circuit) From 9bddfbcce5c9c63e5579701299ba32e9cdfe80c2 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 30 Jul 2024 15:15:13 -0700 Subject: [PATCH 87/91] change: Change error message when trying to create a NoiseModel from unsupported device capabilities --- src/braket/aws/aws_noise_models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/braket/aws/aws_noise_models.py b/src/braket/aws/aws_noise_models.py index 5254ce381..5a4f9223f 100644 --- a/src/braket/aws/aws_noise_models.py +++ b/src/braket/aws/aws_noise_models.py @@ -107,8 +107,7 @@ def device_noise_model(properties: DeviceCapabilities, arn: str) -> NoiseModel: @singledispatch def _setup_calibration_specs(properties: DeviceCapabilities, arn: str) -> NoiseModel: raise NotImplementedError( - f"A noise model cannot be created from device capabilities with \ - type {type(properties)}." + f"A noise model cannot be created from device capabilities with type {type(properties)}." ) From 4626332fc173af651ff5441fe9bbdcb46eb0637b Mon Sep 17 00:00:00 2001 From: Nagji Date: Fri, 2 Aug 2024 12:27:52 -0700 Subject: [PATCH 88/91] feat: Replace 'EmulationPass' with more general BasePass class --- src/braket/aws/aws_device.py | 3 +-- src/braket/emulation/base_emulator.py | 27 +++++++++---------- .../emulation/emulation_passes/__init__.py | 4 --- .../gate_device_passes/gate_validator.py | 16 ++++++----- .../emulation_passes/validation_pass.py | 4 +-- src/braket/emulation/emulator.py | 10 +++---- src/braket/passes/__init__.py | 1 + .../emulation_pass.py => passes/base_pass.py} | 2 +- .../braket/emulation/test_emulator.py | 4 +-- 9 files changed, 33 insertions(+), 38 deletions(-) create mode 100644 src/braket/passes/__init__.py rename src/braket/{emulation/emulation_passes/emulation_pass.py => passes/base_pass.py} (93%) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index ea8e3788d..309e23e7d 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -50,11 +50,11 @@ from braket.devices import Devices from braket.devices.device import Device from braket.emulation import Emulator -from braket.emulation.emulation_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 from braket.parametric.free_parameter_expression import _is_float +from braket.passes import ProgramType from braket.pulse import ArbitraryWaveform, Frame, Port, PulseSequence from braket.pulse.waveforms import _parse_waveform_from_calibration_schema from braket.schema_common import BraketSchemaBase @@ -930,7 +930,6 @@ def validate( """ self.emulator.validate(task_specification) - return def run_passes( self, task_specification: ProgramType, apply_noise_model: bool = True diff --git a/src/braket/emulation/base_emulator.py b/src/braket/emulation/base_emulator.py index 769a4df5a..0674d8663 100644 --- a/src/braket/emulation/base_emulator.py +++ b/src/braket/emulation/base_emulator.py @@ -2,16 +2,17 @@ from typing import Iterable, Union -from braket.emulation.emulation_passes import EmulationPass, ProgramType, ValidationPass +from braket.emulation.emulation_passes import ValidationPass +from braket.passes import BasePass, ProgramType class BaseEmulator: - def __init__(self, emulator_passes: Iterable[EmulationPass] = None): + def __init__(self, emulator_passes: Iterable[BasePass] = 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 EmulationPasses contained + This method passes the input program through the Passes 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). @@ -28,7 +29,7 @@ def run_passes(self, task_specification: ProgramType) -> ProgramType: def validate(self, task_specification: ProgramType) -> None: """ - This method passes the input program through EmulationPasses that perform + This method passes the input program through Passes that perform only validation, without modifying the input program. Args: @@ -39,15 +40,13 @@ def validate(self, task_specification: ProgramType) -> None: if isinstance(emulator_pass, ValidationPass): emulator_pass(task_specification) - def add_pass( - self, emulator_pass: Union[Iterable[EmulationPass], EmulationPass] - ) -> BaseEmulator: + def add_pass(self, emulator_pass: Union[Iterable[BasePass], BasePass]) -> BaseEmulator: """ - Append a new EmulationPass or a list of EmulationPass objects. + Append a new BasePass or a list of BasePass objects. Args: - emulator_pass (Union[Iterable[EmulationPass], EmulationPass]): Either a - single EmulationPass object or a list of EmulationPass objects that + emulator_pass (Union[Iterable[BasePass], BasePass]): Either a + single Pass object or a list of Pass objects that will be used in validation and program compilation passes by this emulator. @@ -55,15 +54,13 @@ def add_pass( BaseEmulator: Returns an updated self. Raises: - TypeError: If the input is not an iterable or an EmulationPass. + TypeError: If the input is not an iterable or an Pass. """ if isinstance(emulator_pass, Iterable): self._emulator_passes.extend(emulator_pass) - elif isinstance(emulator_pass, EmulationPass): + elif isinstance(emulator_pass, BasePass): self._emulator_passes.append(emulator_pass) else: - raise TypeError( - "emulator_pass must be an EmulationPass or an iterable of EmulationPass" - ) + raise TypeError("emulator_pass must be an Pass or an iterable of Pass") return self diff --git a/src/braket/emulation/emulation_passes/__init__.py b/src/braket/emulation/emulation_passes/__init__.py index a54b81cca..691d2da7a 100644 --- a/src/braket/emulation/emulation_passes/__init__.py +++ b/src/braket/emulation/emulation_passes/__init__.py @@ -1,5 +1 @@ -from braket.emulation.emulation_passes.emulation_pass import ( # noqa: F401 - EmulationPass, - ProgramType, -) from braket.emulation.emulation_passes.validation_pass import ValidationPass # noqa: F401 diff --git a/src/braket/emulation/emulation_passes/gate_device_passes/gate_validator.py b/src/braket/emulation/emulation_passes/gate_device_passes/gate_validator.py index c628879c3..3149b3899 100644 --- a/src/braket/emulation/emulation_passes/gate_device_passes/gate_validator.py +++ b/src/braket/emulation/emulation_passes/gate_device_passes/gate_validator.py @@ -1,4 +1,4 @@ -from collections.abc import Iterator +from collections.abc import Iterable from typing import Optional from braket.circuits import Circuit @@ -11,14 +11,14 @@ class GateValidator(ValidationPass[Circuit]): def __init__( self, - supported_gates: Optional[Iterator[str]] = None, - native_gates: Optional[Iterator[str]] = None, + supported_gates: Optional[Iterable[str]] = None, + native_gates: Optional[Iterable[str]] = None, ): """ Args: - supported_gates (Optional[Iterator[str]]): A list of gates supported outside of + supported_gates (Optional[Iterable[str]]): A list of gates supported outside of verbatim modeby the emulator. A gate is a Braket gate name. - native_gates (Optional[Iterator[str]]): A list of gates supported inside of + native_gates (Optional[Iterable[str]]): A list of gates supported inside of verbatim mode by the emulator. Raises: @@ -30,12 +30,14 @@ def __init__( 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) + self._supported_gates = frozenset( + 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) + self._native_gates = frozenset(BRAKET_GATES[gate.lower()] for gate in native_gates) except KeyError as e: raise ValueError(f"Input {str(e)} in native_gates is not a valid Braket gate name.") diff --git a/src/braket/emulation/emulation_passes/validation_pass.py b/src/braket/emulation/emulation_passes/validation_pass.py index ab3fa1ec7..5a5f5b016 100644 --- a/src/braket/emulation/emulation_passes/validation_pass.py +++ b/src/braket/emulation/emulation_passes/validation_pass.py @@ -2,10 +2,10 @@ from abc import abstractmethod -from braket.emulation.emulation_passes.emulation_pass import EmulationPass, ProgramType +from braket.passes.base_pass import BasePass, ProgramType -class ValidationPass(EmulationPass[ProgramType]): +class ValidationPass(BasePass[ProgramType]): @abstractmethod def validate(self, program: ProgramType) -> None: """ diff --git a/src/braket/emulation/emulator.py b/src/braket/emulation/emulator.py index 51cc8e93b..c263d8292 100644 --- a/src/braket/emulation/emulator.py +++ b/src/braket/emulation/emulator.py @@ -8,8 +8,8 @@ from braket.devices import Device from braket.devices.local_simulator import LocalSimulator from braket.emulation.base_emulator import BaseEmulator -from braket.emulation.emulation_passes import EmulationPass, ProgramType from braket.ir.openqasm import Program as OpenQasmProgram +from braket.passes import BasePass, ProgramType from braket.tasks import QuantumTask from braket.tasks.quantum_task_batch import QuantumTaskBatch @@ -26,7 +26,7 @@ def __init__( self, backend: str = "default", noise_model: Optional[NoiseModel] = None, - emulator_passes: Iterable[EmulationPass] = None, + emulator_passes: Iterable[BasePass] = None, **kwargs, ): Device.__init__(self, name=kwargs.get("name", "DeviceEmulator"), status="AVAILABLE") @@ -143,13 +143,13 @@ def run_passes( self, task_specification: ProgramType, apply_noise_model: bool = True ) -> ProgramType: """ - Passes the input program through all EmulationPass objects contained in this + Passes the input program through all Pass objects contained in this emulator and applies the emulator's noise model, if it exists, before returning the compiled program. Args: task_specification (ProgramType): The input program to validate and - compile based on this emulator's EmulationPasses + compile based on this emulator's Passes apply_noise_model (bool): If true, apply this emulator's noise model to the compiled program before returning the final program. @@ -167,7 +167,7 @@ def run_passes( def validate(self, task_specification: ProgramType) -> None: """ - Runs only EmulationPasses that are ValidationPass, i.e. all non-modifying + Runs only Passes that are ValidationPass, i.e. all non-modifying validation passes on the input program. Args: diff --git a/src/braket/passes/__init__.py b/src/braket/passes/__init__.py new file mode 100644 index 000000000..1c9e6397f --- /dev/null +++ b/src/braket/passes/__init__.py @@ -0,0 +1 @@ +from braket.passes.base_pass import BasePass, ProgramType # noqa: F40 diff --git a/src/braket/emulation/emulation_passes/emulation_pass.py b/src/braket/passes/base_pass.py similarity index 93% rename from src/braket/emulation/emulation_passes/emulation_pass.py rename to src/braket/passes/base_pass.py index 9bbd62b4f..62b486a60 100644 --- a/src/braket/emulation/emulation_passes/emulation_pass.py +++ b/src/braket/passes/base_pass.py @@ -4,7 +4,7 @@ ProgramType = TypeVar("ProgramType") -class EmulationPass(ABC, Generic[ProgramType]): +class BasePass(ABC, Generic[ProgramType]): @abstractmethod def run(self, program: ProgramType) -> ProgramType: """ diff --git a/test/unit_tests/braket/emulation/test_emulator.py b/test/unit_tests/braket/emulation/test_emulator.py index 8fe68b2c2..e532037e3 100644 --- a/test/unit_tests/braket/emulation/test_emulator.py +++ b/test/unit_tests/braket/emulation/test_emulator.py @@ -10,11 +10,11 @@ from braket.default_simulator import DensityMatrixSimulator, StateVectorSimulator from braket.devices import local_simulator from braket.emulation import Emulator -from braket.emulation.emulation_passes import EmulationPass, ProgramType from braket.emulation.emulation_passes.gate_device_passes import GateValidator, QubitCountValidator +from braket.passes import BasePass, ProgramType -class AlwaysFailPass(EmulationPass[ProgramType]): +class AlwaysFailPass(BasePass[ProgramType]): def run(self, program: ProgramType): raise ValueError("This pass always raises an error.") From e3ca8a5db3b96bc5a103465d907d7aa57956f729 Mon Sep 17 00:00:00 2001 From: Nagji Date: Fri, 2 Aug 2024 14:16:01 -0700 Subject: [PATCH 89/91] change: Remove mentions of 'emulation' from the BasePass module. --- src/braket/passes/base_pass.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/braket/passes/base_pass.py b/src/braket/passes/base_pass.py index 62b486a60..395eb3758 100644 --- a/src/braket/passes/base_pass.py +++ b/src/braket/passes/base_pass.py @@ -8,13 +8,14 @@ class BasePass(ABC, Generic[ProgramType]): @abstractmethod def run(self, program: ProgramType) -> ProgramType: """ - Runs the emulator pass on the provided program. + Runs a pass on the provided program. Args: - program (ProgramType): The program to run the emulator pass on. + program (ProgramType): The program to run the pass on. Returns: - ProgramType: The program after the emulator pass has been applied. + ProgramType: The program after the pass has been applied. Same type as the input + program. Raises: NotImplementedError: Method not implemented. From baccbb1c2543db04beea8531986dec65bda3a4c8 Mon Sep 17 00:00:00 2001 From: Nagji Date: Fri, 2 Aug 2024 14:21:36 -0700 Subject: [PATCH 90/91] change: Fix style in BasePass.py --- src/braket/passes/base_pass.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/passes/base_pass.py b/src/braket/passes/base_pass.py index 395eb3758..d5c36aea6 100644 --- a/src/braket/passes/base_pass.py +++ b/src/braket/passes/base_pass.py @@ -14,7 +14,7 @@ def run(self, program: ProgramType) -> ProgramType: program (ProgramType): The program to run the pass on. Returns: - ProgramType: The program after the pass has been applied. Same type as the input + ProgramType: The program after the pass has been applied. Same type as the input program. Raises: From 5aaa69c41fb1d886b44029a6f953b5010dcc509e Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:54:52 -0500 Subject: [PATCH 91/91] Update aws_noise_models.py --- src/braket/aws/aws_noise_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/aws/aws_noise_models.py b/src/braket/aws/aws_noise_models.py index 5a4f9223f..925447698 100644 --- a/src/braket/aws/aws_noise_models.py +++ b/src/braket/aws/aws_noise_models.py @@ -30,7 +30,7 @@ calibration data and must be hardcoded. """ _QPU_GATE_DURATIONS = { - Devices.Rigetti.AspenM3: { + Devices.Rigetti.Ankaa2: { "single_qubit_gate_duration": 40e-9, "two_qubit_gate_duration": 240e-9, },