diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index f28cf4f33..754ca803f 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -11,6 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import copy import math from typing import List, Optional, Sequence, Union @@ -87,6 +88,16 @@ def bind_values(self, **kwargs): """ raise NotImplementedError + def adjoint(self) -> List[Gate]: + """Returns the adjoint of this gate as a singleton list. + + Returns: + List[Gate]: A list containing the gate with negated angle. + """ + new = copy.copy(self) + new._parameters = [-angle for angle in self._parameters] + return [new] + def __eq__(self, other): if isinstance(other, AngledGate): if isinstance(self.angle, FreeParameterExpression): diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index f421af2de..0a12d8a91 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -144,10 +144,9 @@ def depth(self) -> int: return self._moments.depth @property - def instructions(self) -> Iterable[Instruction]: + def instructions(self) -> List[Instruction]: """Iterable[Instruction]: Get an `iterable` of instructions in the circuit.""" - - return self._moments.values() + return list(self._moments.values()) @property def result_types(self) -> List[ResultType]: @@ -1031,6 +1030,22 @@ def _flatten(addable): return self + def adjoint(self) -> Circuit: + """Returns the adjoint of this circuit. + + This is the adjoint of every instruction of the circuit, in reverse order. Result types, + and consequently basis rotations will stay in the same order at the end of the circuit. + + Returns: + Circuit: The adjoint of the circuit. + """ + circ = Circuit() + for instr in reversed(self.instructions): + circ.add(instr.adjoint()) + for result_type in self._result_types: + circ.add_result_type(result_type) + return circ + def diagram(self, circuit_diagram_class=AsciiCircuitDiagram) -> str: """ Get a diagram for the current circuit. @@ -1050,9 +1065,9 @@ def to_ir(self) -> Program: If the circuit is sent over the wire, this method is called before it is sent. Returns: - (Program): An AWS quantum circuit description program in JSON format. + Program: A Braket quantum circuit description program in JSON format. """ - ir_instructions = [instr.to_ir() for instr in self.instructions] + ir_instructions = [instruction.to_ir() for instruction in self.instructions] ir_results = [result_type.to_ir() for result_type in self.result_types] ir_basis_rotation_instructions = [ instr.to_ir() for instr in self.basis_rotation_instructions @@ -1192,10 +1207,10 @@ def __add__(self, addable: AddableTypes) -> Circuit: def __repr__(self) -> str: if not self.result_types: - return f"Circuit('instructions': {list(self.instructions)})" + return f"Circuit('instructions': {self.instructions})" else: return ( - f"Circuit('instructions': {list(self.instructions)}" + f"Circuit('instructions': {self.instructions}" + f", 'result_types': {self.result_types})" ) @@ -1205,8 +1220,7 @@ def __str__(self): def __eq__(self, other): if isinstance(other, Circuit): return ( - list(self.instructions) == list(other.instructions) - and self.result_types == other.result_types + self.instructions == other.instructions and self.result_types == other.result_types ) return NotImplemented diff --git a/src/braket/circuits/compiler_directive.py b/src/braket/circuits/compiler_directive.py index 87b1f8189..f6b7a9361 100644 --- a/src/braket/circuits/compiler_directive.py +++ b/src/braket/circuits/compiler_directive.py @@ -11,6 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +from __future__ import annotations + from typing import Sequence, Tuple from braket.circuits.operator import Operator @@ -41,6 +43,19 @@ def ascii_symbols(self) -> Tuple[str, ...]: """Tuple[str, ...]: Returns the ascii symbols for the compiler directive.""" return self._ascii_symbols + def counterpart(self) -> CompilerDirective: + """Returns the "opposite" counterpart to this compiler directive. + + For example, the counterpart of a directive that starts a box + is the directive that ends the box. + + Returns: + CompilerDirective: The counterpart compiler directive + """ + raise NotImplementedError( + f"Compiler directive {self.name} does not have counterpart implemented" + ) + def to_ir(self, *args, **kwargs): raise NotImplementedError("to_ir has not been implemented yet.") diff --git a/src/braket/circuits/compiler_directives.py b/src/braket/circuits/compiler_directives.py index 8e0c0dc3e..d17a2e387 100644 --- a/src/braket/circuits/compiler_directives.py +++ b/src/braket/circuits/compiler_directives.py @@ -24,6 +24,9 @@ class StartVerbatimBox(CompilerDirective): def __init__(self): super().__init__(["StartVerbatim"]) + def counterpart(self) -> CompilerDirective: + return EndVerbatimBox() + def to_ir(self, *args, **kwargs): return ir.StartVerbatimBox.construct() @@ -37,5 +40,8 @@ class EndVerbatimBox(CompilerDirective): def __init__(self): super().__init__(["EndVerbatim"]) + def counterpart(self) -> CompilerDirective: + return StartVerbatimBox() + def to_ir(self, *args, **kwargs): return ir.EndVerbatimBox.construct() diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index b8a897c5d..bb9c32c81 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -11,7 +11,9 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import Any, Optional, Sequence +from __future__ import annotations + +from typing import Any, List, Optional, Sequence, Tuple from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit_set import QubitSet @@ -41,6 +43,16 @@ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + def adjoint(self) -> List[Gate]: + """Returns a list of gates that implement the adjoint of this gate. + + This is a list because some gates do not have an inverse defined by a single existing gate. + + Returns: + List[Gate]: The gates comprising the adjoint of this gate. + """ + raise NotImplementedError(f"Gate {self.name} does not have adjoint implemented") + def to_ir(self, target: QubitSet) -> Any: """Returns IR object of quantum operator and target @@ -51,16 +63,19 @@ def to_ir(self, target: QubitSet) -> Any: """ raise NotImplementedError("to_ir has not been implemented yet.") + @property + def ascii_symbols(self) -> Tuple[str, ...]: + """Tuple[str, ...]: Returns the ascii symbols for the quantum operator.""" + return self._ascii_symbols + def __eq__(self, other): - if isinstance(other, Gate): - return self.name == other.name - return False + return isinstance(other, Gate) and self.name == other.name def __repr__(self): - return f"{self.name}('qubit_count': {self.qubit_count})" + return f"{self.name}('qubit_count': {self._qubit_count})" @classmethod - def register_gate(cls, gate: "Gate"): + def register_gate(cls, gate: Gate): """Register a gate implementation by adding it into the Gate class. Args: diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 2593dde58..ad13b822d 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -11,7 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import Iterable, Union +from typing import Iterable, List, Union import numpy as np from sympy import Float @@ -39,6 +39,7 @@ 3. Register the class with the `Gate` class via `Gate.register_gate()`. """ + # Single qubit gates # @@ -48,6 +49,9 @@ class H(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["H"]) + def adjoint(self) -> List[Gate]: + return [H()] + def to_ir(self, target: QubitSet): return ir.H.construct(target=target[0]) @@ -85,6 +89,9 @@ class I(Gate): # noqa: E742, E261 def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["I"]) + def adjoint(self) -> List[Gate]: + return [I()] + def to_ir(self, target: QubitSet): return ir.I.construct(target=target[0]) @@ -122,6 +129,9 @@ class X(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["X"]) + def adjoint(self) -> List[Gate]: + return [X()] + def to_ir(self, target: QubitSet): return ir.X.construct(target=target[0]) @@ -159,6 +169,9 @@ class Y(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Y"]) + def adjoint(self) -> List[Gate]: + return [Y()] + def to_ir(self, target: QubitSet): return ir.Y.construct(target=target[0]) @@ -196,6 +209,9 @@ class Z(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Z"]) + def adjoint(self) -> List[Gate]: + return [Z()] + def to_ir(self, target: QubitSet): return ir.Z.construct(target=target[0]) @@ -233,11 +249,13 @@ class S(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["S"]) + def adjoint(self) -> List[Gate]: + return [Si()] + def to_ir(self, target: QubitSet): return ir.S.construct(target=target[0]) def to_matrix(self) -> np.ndarray: - return np.array([[1.0, 0.0], [0.0, 1.0j]], dtype=complex) @staticmethod @@ -271,6 +289,9 @@ class Si(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Si"]) + def adjoint(self) -> List[Gate]: + return [S()] + def to_ir(self, target: QubitSet): return ir.Si.construct(target=target[0]) @@ -308,6 +329,9 @@ class T(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["T"]) + def adjoint(self) -> List[Gate]: + return [Ti()] + def to_ir(self, target: QubitSet): return ir.T.construct(target=target[0]) @@ -345,6 +369,9 @@ class Ti(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Ti"]) + def adjoint(self) -> List[Gate]: + return [T()] + def to_ir(self, target: QubitSet): return ir.Ti.construct(target=target[0]) @@ -382,6 +409,9 @@ class V(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["V"]) + def adjoint(self) -> List[Gate]: + return [Vi()] + def to_ir(self, target: QubitSet): return ir.V.construct(target=target[0]) @@ -419,6 +449,9 @@ class Vi(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Vi"]) + def adjoint(self) -> List[Gate]: + return [V()] + def to_ir(self, target: QubitSet): return ir.Vi.construct(target=target[0]) @@ -704,6 +737,9 @@ class CNot(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "X"]) + def adjoint(self) -> List[Gate]: + return [CNot()] + def to_ir(self, target: QubitSet): return ir.CNot.construct(control=target[0], target=target[1]) @@ -749,6 +785,9 @@ class Swap(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["SWAP", "SWAP"]) + def adjoint(self) -> List[Gate]: + return [Swap()] + def to_ir(self, target: QubitSet): return ir.Swap.construct(targets=[target[0], target[1]]) @@ -794,6 +833,9 @@ class ISwap(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["ISWAP", "ISWAP"]) + def adjoint(self) -> List[Gate]: + return [self, self, self] + def to_ir(self, target: QubitSet): return ir.ISwap.construct(targets=[target[0], target[1]]) @@ -1235,6 +1277,9 @@ class CV(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "V"]) + def adjoint(self) -> List[Gate]: + return [self, self, self] + def to_ir(self, target: QubitSet): return ir.CV.construct(control=target[0], target=target[1]) @@ -1280,6 +1325,9 @@ class CY(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "Y"]) + def adjoint(self) -> List[Gate]: + return [CY()] + def to_ir(self, target: QubitSet): return ir.CY.construct(control=target[0], target=target[1]) @@ -1325,6 +1373,9 @@ class CZ(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "Z"]) + def adjoint(self) -> List[Gate]: + return [CZ()] + def to_ir(self, target: QubitSet): return ir.CZ.construct(control=target[0], target=target[1]) @@ -1362,6 +1413,9 @@ class ECR(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["ECR", "ECR"]) + def adjoint(self) -> List[Gate]: + return [ECR()] + def to_ir(self, target: QubitSet): return ir.ECR.construct(targets=[target[0], target[1]]) @@ -1638,6 +1692,9 @@ class CCNot(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "C", "X"]) + def adjoint(self) -> List[Gate]: + return [CCNot()] + def to_ir(self, target: QubitSet): return ir.CCNot.construct(controls=[target[0], target[1]], target=target[2]) @@ -1688,6 +1745,9 @@ class CSwap(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "SWAP", "SWAP"]) + def adjoint(self) -> List[Gate]: + return [CSwap()] + def to_ir(self, target: QubitSet): return ir.CSwap.construct(control=target[0], targets=[target[1], target[2]]) @@ -1759,6 +1819,9 @@ def __init__(self, matrix: np.ndarray, display_name: str = "U"): def to_matrix(self): return np.array(self._matrix) + def adjoint(self) -> List[Gate]: + return [Unitary(self._matrix.conj().T, display_name=f"({self.ascii_symbols})^†")] + def to_ir(self, target: QubitSet): return ir.Unitary.construct( targets=[qubit for qubit in target], diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index 5c2d2c9b3..4e6a0ba85 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -13,8 +13,10 @@ from __future__ import annotations -from typing import Dict, Tuple +from typing import Dict, List, Tuple +from braket.circuits.compiler_directive import CompilerDirective +from braket.circuits.gate import Gate from braket.circuits.operator import Operator from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit import QubitInput @@ -31,7 +33,7 @@ class Instruction: def __init__(self, operator: InstructionOperator, target: QubitSetInput = None): """ - InstructionOperator includes objects of type `Gate` only. + InstructionOperator includes objects of type `Gate` and `Noise` only. Args: operator (InstructionOperator): Operator for the instruction. @@ -81,6 +83,24 @@ def target(self) -> QubitSet: """ return self._target + def adjoint(self) -> List[Instruction]: + """Returns a list of Instructions implementing adjoint of this instruction's own operator + + This operation only works on Gate operators and compiler directives. + + Returns: + List[Instruction]: A list of new instructions that comprise the adjoint of this operator + + Raises: + NotImplementedError: If `operator` is not of type `Gate` or `CompilerDirective` + """ + operator = self._operator + if isinstance(operator, Gate): + return [Instruction(gate, self._target) for gate in operator.adjoint()] + elif isinstance(operator, CompilerDirective): + return [Instruction(operator.counterpart(), self._target)] + raise NotImplementedError(f"Adjoint not supported for {operator}") + def to_ir(self): """ Converts the operator into the canonical intermediate representation. diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 1946e236e..f6953db42 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -50,6 +50,18 @@ def test_angle_setter(angled_gate): angled_gate.angle = 0.14 +def test_adjoint(angled_gate): + assert angled_gate.adjoint() == [AngledGate(angle=-0.15, qubit_count=1, ascii_symbols=["foo"])] + + +def test_adjoint_parameterized(): + theta = FreeParameter("theta") + angled_gate = AngledGate(angle=(theta + 1), qubit_count=1, ascii_symbols=["foo"]) + assert angled_gate.adjoint() == [ + AngledGate(angle=-(theta + 1), qubit_count=1, ascii_symbols=["foo"]) + ] + + def test_equality(angled_gate): gate = AngledGate(angle=0.15, qubit_count=1, ascii_symbols=["bar"]) other_gate = AngledGate(angle=0.3, qubit_count=1, ascii_symbols=["foo"]) diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py index 643c4d9a8..61c3ad87d 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -31,17 +31,14 @@ def test_empty_circuit(): def test_one_gate_one_qubit(): circ = Circuit().h(0) expected = ("T : |0|", " ", "q0 : -H-", "", "T : |0|") - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_one_gate_one_qubit_rotation(): circ = Circuit().rx(angle=3.14, target=0) # Column formats to length of the gate plus the ascii representation for the angle. expected = ("T : | 0 |", " ", "q0 : -Rx(3.14)-", "", "T : | 0 |") - expected = "\n".join(expected) - - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_one_gate_one_qubit_rotation_with_parameter(): @@ -57,8 +54,7 @@ def test_one_gate_one_qubit_rotation_with_parameter(): "", "Unassigned parameters: {theta}.", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_one_gate_one_qubit_rotation_with_unicode(): @@ -74,8 +70,7 @@ def test_one_gate_one_qubit_rotation_with_unicode(): "", "Unassigned parameters: {θ}.", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_one_gate_one_qubit_rotation_with_parameter_assigned(): @@ -90,9 +85,7 @@ def test_one_gate_one_qubit_rotation_with_parameter_assigned(): "", "T : | 0 |", ) - expected = "\n".join(expected) - print(AsciiCircuitDiagram.build_diagram(new_circ)) - assert AsciiCircuitDiagram.build_diagram(new_circ) == expected + _assert_correct_diagram(new_circ, expected) def test_qubit_width(): @@ -106,8 +99,7 @@ def test_qubit_width(): "", "T : |0|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_gate_width(): @@ -128,8 +120,7 @@ def to_ir(self, target): "", "T : |0| 1 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_time_width(): @@ -174,8 +165,7 @@ def test_time_width(): "", "T : |0|1|2|3|4|5|6|7|8|9|10|11|12|13|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_connector_across_two_qubits(): @@ -193,8 +183,7 @@ def test_connector_across_two_qubits(): "", "T : |0|1|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_overlapping_qubits(): @@ -212,8 +201,7 @@ def test_overlapping_qubits(): "", "T : | 0 |1|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_overlapping_qubits_angled_gates(): @@ -231,8 +219,7 @@ def test_overlapping_qubits_angled_gates(): "", "T : | 0 |1|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_connector_across_gt_two_qubits(): @@ -250,8 +237,7 @@ def test_connector_across_gt_two_qubits(): "", "T : | 0 |1|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_connector_across_non_used_qubits(): @@ -269,8 +255,7 @@ def test_connector_across_non_used_qubits(): "", "T : | 0 |1|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_1q_no_preceding(): @@ -282,8 +267,7 @@ def test_verbatim_1q_no_preceding(): "", "T : | 0 |1| 2 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_1q_preceding(): @@ -295,8 +279,7 @@ def test_verbatim_1q_preceding(): "", "T : |0| 1 |2| 3 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_1q_following(): @@ -308,8 +291,7 @@ def test_verbatim_1q_following(): "", "T : | 0 |1| 2 |3|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_2q_no_preceding(): @@ -323,8 +305,7 @@ def test_verbatim_2q_no_preceding(): "", "T : | 0 |1|2| 3 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_2q_preceding(): @@ -338,8 +319,7 @@ def test_verbatim_2q_preceding(): "", "T : |0| 1 |2|3| 4 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_2q_following(): @@ -353,8 +333,7 @@ def test_verbatim_2q_following(): "", "T : | 0 |1|2| 3 |4|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_3q_no_preceding(): @@ -370,8 +349,7 @@ def test_verbatim_3q_no_preceding(): "", "T : | 0 |1|2|3| 4 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_3q_preceding(): @@ -387,8 +365,7 @@ def test_verbatim_3q_preceding(): "", "T : |0| 1 |2|3|4| 5 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_3q_following(): @@ -404,8 +381,7 @@ def test_verbatim_3q_following(): "", "T : | 0 |1|2|3| 4 |5|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_different_qubits(): @@ -423,8 +399,7 @@ def test_verbatim_different_qubits(): "", "T : |0| 1 |2| 3 |4|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_qubset_qubits(): @@ -442,8 +417,7 @@ def test_verbatim_qubset_qubits(): "", "T : |0|1|2| 3 |4| 5 |6|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_ignore_non_gates(): @@ -467,8 +441,7 @@ def to_ir(self, target): "", "T : |0|1|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_result_types_target_none(): @@ -482,8 +455,7 @@ def test_result_types_target_none(): "", "T : |0|Result Types|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_result_types_target_some(): @@ -505,8 +477,7 @@ def test_result_types_target_some(): "", "T : |0| Result Types |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_additional_result_types(): @@ -524,8 +495,7 @@ def test_additional_result_types(): "", "Additional result types: StateVector, Amplitude(110,001)", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_multiple_result_types(): @@ -551,8 +521,7 @@ def test_multiple_result_types(): "", "T : | 0 |1| Result Types |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_multiple_result_types_with_state_vector_amplitude(): @@ -582,8 +551,7 @@ def test_multiple_result_types_with_state_vector_amplitude(): "", "Additional result types: Amplitude(0001), StateVector", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_multiple_result_types_with_custom_hermitian_ascii_symbol(): @@ -616,8 +584,7 @@ def test_multiple_result_types_with_custom_hermitian_ascii_symbol(): "", "T : | 0 |1| Result Types |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_noise_1qubit(): @@ -631,8 +598,7 @@ def test_noise_1qubit(): "", "T : | 0 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_noise_2qubit(): @@ -648,5 +614,8 @@ def test_noise_2qubit(): "", "T : | 0 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) + + +def _assert_correct_diagram(circ, expected): + assert AsciiCircuitDiagram.build_diagram(circ) == "\n".join(expected) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 7538d2a1b..5deb820ab 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -75,14 +75,14 @@ def bell_pair(prob): def test_repr_instructions(h): - expected = f"Circuit('instructions': {list(h.instructions)})" + expected = f"Circuit('instructions': {h.instructions})" assert repr(h) == expected def test_repr_result_types(cnot_prob): circuit = cnot_prob expected = ( - f"Circuit('instructions': {list(circuit.instructions)}" + f"Circuit('instructions': {circuit.instructions}" + f", 'result_types': {circuit.result_types})" ) assert repr(circuit) == expected @@ -123,7 +123,7 @@ def test_call_with_result_type(prob): assert new_circ == expected and not new_circ.parameters assert new_circ.observables_simultaneously_measurable - assert list(new_circ.result_types) == [prob] + assert new_circ.result_types == [prob] def test_call_one_param_not_bound(): @@ -166,28 +166,28 @@ def test_call_with_default_parameter_val(): def test_add_result_type_default(prob): circ = Circuit().add_result_type(prob) assert circ.observables_simultaneously_measurable - assert list(circ.result_types) == [prob] + assert circ.result_types == [prob] def test_add_result_type_with_mapping(prob): expected = [ResultType.Probability([10, 11])] circ = Circuit().add_result_type(prob, target_mapping={0: 10, 1: 11}) assert circ.observables_simultaneously_measurable - assert list(circ.result_types) == expected + assert circ.result_types == expected def test_add_result_type_with_target(prob): expected = [ResultType.Probability([10, 11])] circ = Circuit().add_result_type(prob, target=[10, 11]) assert circ.observables_simultaneously_measurable - assert list(circ.result_types) == expected + assert circ.result_types == expected def test_add_result_type_already_exists(): expected = [ResultType.StateVector()] circ = Circuit(expected).add_result_type(expected[0]) assert circ.observables_simultaneously_measurable - assert list(circ.result_types) == expected + assert circ.result_types == expected def test_add_result_type_observable_conflict_target(): @@ -302,19 +302,19 @@ def test_add_result_type_with_target_and_mapping(prob): def test_add_instruction_default(cnot_instr): circ = Circuit().add_instruction(cnot_instr) - assert list(circ.instructions) == [cnot_instr] + assert circ.instructions == [cnot_instr] def test_add_instruction_with_mapping(cnot_instr): expected = [Instruction(Gate.CNot(), [10, 11])] circ = Circuit().add_instruction(cnot_instr, target_mapping={0: 10, 1: 11}) - assert list(circ.instructions) == expected + assert circ.instructions == expected def test_add_instruction_with_target(cnot_instr): expected = [Instruction(Gate.CNot(), [10, 11])] circ = Circuit().add_instruction(cnot_instr, target=[10, 11]) - assert list(circ.instructions) == expected + assert circ.instructions == expected def test_add_multiple_single_qubit_instruction(h_instr): @@ -487,6 +487,24 @@ def test_add_with_circuit_with_target(bell_pair): assert circ == expected +def test_adjoint(): + circ = Circuit().s(0).add_verbatim_box(Circuit().rz(0, 0.123)).expectation(Observable.X(), 0) + expected = Circuit() + expected.add_verbatim_box(Circuit().rz(0, -0.123)) + expected.si(0) + expected.expectation(Observable.X(), 0) + actual = circ.adjoint() + assert actual == expected + assert circ == expected.adjoint() + assert circ == actual.adjoint() + + +def test_adjoint_subcircuit_free_parameter(): + circ = Circuit().h(0).add_circuit(Circuit().s(0).rz(0, FreeParameter("theta")).adjoint()).x(0) + expected = Circuit().h(0).rz(0, -FreeParameter("theta")).si(0).x(0) + assert circ == expected + + def test_circuit_copy(h, bell_pair, cnot_instr): original = Circuit().add(h).add(bell_pair).add(cnot_instr) copy = original.copy() @@ -1664,12 +1682,12 @@ def test_depth_setter(h): def test_instructions_getter(h): - assert list(h.instructions) == list(h._moments.values()) + assert h.instructions == list(h._moments.values()) @pytest.mark.xfail(raises=AttributeError) def test_instructions_setter(h, h_instr): - h.instructions = iter([h_instr]) + h.instructions = [h_instr] def test_moments_getter(h): diff --git a/test/unit_tests/braket/circuits/test_compiler_directive.py b/test/unit_tests/braket/circuits/test_compiler_directive.py index 4a971e857..bd1f2d95d 100644 --- a/test/unit_tests/braket/circuits/test_compiler_directive.py +++ b/test/unit_tests/braket/circuits/test_compiler_directive.py @@ -45,6 +45,11 @@ def test_name(compiler_directive): assert compiler_directive.name == expected +@pytest.mark.xfail(raises=NotImplementedError) +def test_counterpart_not_implemented_by_default(compiler_directive): + compiler_directive.counterpart() + + @pytest.mark.xfail(raises=NotImplementedError) def test_to_ir_not_implemented_by_default(compiler_directive): compiler_directive.to_ir(None) diff --git a/test/unit_tests/braket/circuits/test_compiler_directives.py b/test/unit_tests/braket/circuits/test_compiler_directives.py index 13de3d002..80e485f8e 100644 --- a/test/unit_tests/braket/circuits/test_compiler_directives.py +++ b/test/unit_tests/braket/circuits/test_compiler_directives.py @@ -18,18 +18,23 @@ from braket.circuits.compiler_directive import CompilerDirective testdata = [ - (compiler_directives.StartVerbatimBox, ir.StartVerbatimBox), - (compiler_directives.EndVerbatimBox, ir.EndVerbatimBox), + (compiler_directives.StartVerbatimBox, ir.StartVerbatimBox, compiler_directives.EndVerbatimBox), + (compiler_directives.EndVerbatimBox, ir.EndVerbatimBox, compiler_directives.StartVerbatimBox), ] -@pytest.mark.parametrize("testclass,irclass", testdata) -def test_to_ir(testclass, irclass): +@pytest.mark.parametrize("testclass,irclass,counterpart", testdata) +def test_counterpart(testclass, irclass, counterpart): + assert testclass().counterpart() == counterpart() + + +@pytest.mark.parametrize("testclass,irclass,counterpart", testdata) +def test_to_ir(testclass, irclass, counterpart): assert testclass().to_ir() == irclass() -@pytest.mark.parametrize("testclass,irclass", testdata) -def test_equality(testclass, irclass): +@pytest.mark.parametrize("testclass,irclass,counterpart", testdata) +def test_equality(testclass, irclass, counterpart): op1 = testclass() op2 = testclass() assert op1 == op2 diff --git a/test/unit_tests/braket/circuits/test_gate.py b/test/unit_tests/braket/circuits/test_gate.py index dd54ef442..8dd4297cb 100644 --- a/test/unit_tests/braket/circuits/test_gate.py +++ b/test/unit_tests/braket/circuits/test_gate.py @@ -25,6 +25,11 @@ def test_is_operator(gate): assert isinstance(gate, QuantumOperator) +@pytest.mark.xfail(raises=NotImplementedError) +def test_adjoint_not_implemented_by_default(gate): + gate.adjoint() + + @pytest.mark.xfail(raises=NotImplementedError) def test_to_ir_not_implemented_by_default(gate): gate.to_ir(None) @@ -49,13 +54,13 @@ def test_matrix_equivalence_non_gate(): def test_str(gate): - expected = "{}('qubit_count': {})".format(gate.name, gate.qubit_count) + expected = f"{gate.name}('qubit_count': {gate.qubit_count})" assert str(gate) == expected def test_str_angle(): gate = Gate.Rx(0.5) - expected = "{}('angle': {}, 'qubit_count': {})".format(gate.name, gate.angle, gate.qubit_count) + expected = f"{gate.name}('angle': {gate.angle}, 'qubit_count': {gate.qubit_count})" assert str(gate) == expected diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 100fa41cf..ed4bfa84c 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -11,6 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import functools + import numpy as np import pytest @@ -307,6 +309,15 @@ def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwar assert subroutine(**subroutine_input) == Circuit(instruction_list) +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) +def test_gate_adjoint_expansion_correct(testclass, subroutine_name, irclass, irsubclasses, kwargs): + gate = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) + matrices = [elem.to_matrix() for elem in gate.adjoint()] + matrices.append(gate.to_matrix()) + identity = np.eye(2**gate.qubit_count) + assert np.isclose(functools.reduce(lambda a, b: a @ b, matrices), identity).all() + + @pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) def test_gate_to_matrix(testclass, subroutine_name, irclass, irsubclasses, kwargs): gate1 = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) diff --git a/test/unit_tests/braket/circuits/test_instruction.py b/test/unit_tests/braket/circuits/test_instruction.py index cef23fafe..b0e403457 100644 --- a/test/unit_tests/braket/circuits/test_instruction.py +++ b/test/unit_tests/braket/circuits/test_instruction.py @@ -13,7 +13,7 @@ import pytest -from braket.circuits import Gate, Instruction, Qubit, QubitSet +from braket.circuits import Gate, Instruction, Noise, Qubit, QubitSet, compiler_directives @pytest.fixture @@ -79,6 +79,22 @@ def test_target_setter(instr): instr.target = QubitSet(0) +def test_adjoint_gate(): + instr = Instruction(Gate.S(), 0) + adj = instr.adjoint() + assert adj == [Instruction(Gate.Si(), 0)] + + +def test_adjoint_compiler_directive(): + instr = Instruction(compiler_directives.StartVerbatimBox()).adjoint() + assert instr == [Instruction(compiler_directives.EndVerbatimBox())] + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_adjoint_unsupported(): + Instruction(Noise.BitFlip(0.1), 0).adjoint() + + def test_str(instr): expected = "Instruction('operator': {}, 'target': {})".format(instr.operator, instr.target) assert str(instr) == expected