diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 5d191c69b..99b3e8ecb 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -13,7 +13,6 @@ from __future__ import annotations -import warnings from collections.abc import Callable, Iterable from numbers import Number from typing import Any, Optional, TypeVar, Union @@ -51,7 +50,7 @@ QubitReferenceType, SerializationProperties, ) -from braket.circuits.unitary_calculation import calculate_unitary, calculate_unitary_big_endian +from braket.circuits.unitary_calculation import calculate_unitary_big_endian from braket.default_simulator.openqasm.interpreter import Interpreter from braket.ir.jaqcd import Program as JaqcdProgram from braket.ir.openqasm import Program as OpenQasmProgram @@ -1391,52 +1390,6 @@ def _add_fixed_argument_calibrations( ) return additional_calibrations - def as_unitary(self) -> np.ndarray: - r""" - Returns the unitary matrix representation, in little endian format, of the entire circuit. - *Note*: The performance of this method degrades with qubit count. It might be slow for - qubit count > 10. - - Returns: - ndarray: A numpy array with shape (2^qubit_count, 2^qubit_count) representing the - circuit as a unitary. *Note*: For an empty circuit, an empty numpy array is - returned (`array([], dtype=complex128)`) - - Warnings: - This method has been deprecated, please use to_unitary() instead. - The unitary returned by this method is *little-endian*; the first qubit in the circuit - is the _least_ significant. For example, a circuit `Circuit().h(0).x(1)` will yield the - unitary :math:`X(1) \otimes H(0)`. - - Raises: - TypeError: If circuit is not composed only of `Gate` instances, - i.e. a circuit with `Noise` operators will raise this error. - - Examples: - >>> circ = Circuit().h(0).cnot(0, 1) - >>> circ.as_unitary() - array([[ 0.70710678+0.j, 0.70710678+0.j, 0. +0.j, - 0. +0.j], - [ 0. +0.j, 0. +0.j, 0.70710678+0.j, - -0.70710678+0.j], - [ 0. +0.j, 0. +0.j, 0.70710678+0.j, - 0.70710678+0.j], - [ 0.70710678+0.j, -0.70710678+0.j, 0. +0.j, - 0. +0.j]]) - """ - warnings.warn( - "Matrix returned will have qubits in little-endian order; " - "This method has been deprecated. Please use to_unitary() instead.", - category=DeprecationWarning, - ) - - qubits = self.qubits - if not qubits: - return np.zeros(0, dtype=complex) - qubit_count = max(qubits) + 1 - - return calculate_unitary(qubit_count, self.instructions) - def to_unitary(self) -> np.ndarray: """ Returns the unitary matrix representation of the entire circuit. diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py index d51a378cf..ebc3c7878 100644 --- a/src/braket/circuits/unitary_calculation.py +++ b/src/braket/circuits/unitary_calculation.py @@ -23,77 +23,6 @@ from braket.registers.qubit_set import QubitSet -def _einsum_subscripts(targets: QubitSet, qubit_count: int) -> str: - target_count = len(targets) - - gate_left_indexes = list(range(target_count)) - un_left_indexes = list(range(target_count, target_count + qubit_count)) - un_right_indexes = list(range(target_count + qubit_count, target_count + 2 * qubit_count)) - - gate_right_indexes = [un_left_indexes[-1 - target] for target in targets] - - result_left_indexes = un_left_indexes.copy() - for pos, target in enumerate(targets): - result_left_indexes[-1 - target] = gate_left_indexes[pos] - - return ( - gate_left_indexes + gate_right_indexes, - un_left_indexes + un_right_indexes, - result_left_indexes + un_right_indexes, - ) - - -def calculate_unitary(qubit_count: int, instructions: Iterable[Instruction]) -> np.ndarray: - """ - Returns the unitary matrix representation for all the `instructions` with a given - `qubit_count`. - *Note*: The performance of this method degrades with qubit count. It might be slow for - qubit count > 10. - - Args: - qubit_count (int): Total number of qubits, enough for all the `instructions`. - instructions (Iterable[Instruction]): The instructions for which the unitary matrix - will be calculated. - - Returns: - np.ndarray: A numpy array with shape (2^qubit_count, 2^qubit_count) representing the - `instructions` as a unitary. - - Raises: - TypeError: If `instructions` is not composed only of `Gate` instances, - i.e. a circuit with `Noise` operators will raise this error. - Any `CompilerDirective` instructions will be ignored, as these should - not affect the unitary representation of the circuit. - """ - unitary = np.eye(2**qubit_count, dtype=complex) - un_tensor = np.reshape(unitary, qubit_count * [2, 2]) - - for instr in instructions: - if isinstance(instr.operator, CompilerDirective): - continue - - if not isinstance(instr.operator, Gate): - raise TypeError("Only Gate operators are supported to build the unitary") - - matrix = instr.operator.to_matrix() - targets = instr.target - - gate_indexes, un_indexes, result_indexes = _einsum_subscripts(targets, qubit_count) - gate_matrix = np.reshape(matrix, len(targets) * [2, 2]) - - un_tensor = np.einsum( - gate_matrix, - gate_indexes, - un_tensor, - un_indexes, - result_indexes, - dtype=complex, - casting="no", - ) - - return np.reshape(un_tensor, 2 * [2**qubit_count]) - - def calculate_unitary_big_endian( instructions: Iterable[Instruction], qubits: QubitSet ) -> np.ndarray: diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index fd2170a52..5824967b8 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -1906,419 +1906,6 @@ def test_circuit_to_ir_invalid_inputs( assert exc.value.args[0] == expected_message -def test_as_unitary_empty_instructions_returns_empty_array(): - circ = Circuit() - circ.as_unitary() == [] - - -@pytest.mark.parametrize( - "circuit", - [ - (Circuit().phaseshift(0, 0.15).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))), - (Circuit().cnot(1, 0).apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1))), - ( - Circuit() - .x(1) - .i(2) - .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[1]) - ), - ( - Circuit() - .x(1) - .i(2) - .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[2]) - ), - (Circuit().x(1).i(2).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))), - (Circuit().x(1).apply_gate_noise(noise.Noise.BitFlip(probability=0.1)).i(2)), - ( - Circuit() - .y(1) - .z(2) - .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[1]) - ), - ( - Circuit() - .y(1) - .z(2) - .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[2]) - ), - (Circuit().y(1).z(2).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))), - (Circuit().y(1).apply_gate_noise(noise.Noise.BitFlip(probability=0.1)).z(2)), - ( - Circuit() - .cphaseshift(2, 1, 0.15) - .si(3) - .apply_gate_noise( - noise.Noise.TwoQubitDepolarizing(probability=0.1), target_qubits=[1, 2] - ) - ), - ( - Circuit() - .cphaseshift(2, 1, 0.15) - .apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1)) - .si(3) - ), - ], -) -def test_as_unitary_noise_raises_error(circuit): - with pytest.raises(TypeError): - circuit.as_unitary() - - -def test_as_unitary_parameterized(): - theta = FreeParameter("theta") - circ = Circuit().rx(angle=theta, target=0) - with pytest.raises(TypeError): - assert np.allclose(circ.as_unitary()) - - -def test_as_unitary_noise_not_apply_returns_expected_unitary(recwarn): - circuit = ( - Circuit() - .cphaseshift(2, 1, 0.15) - .si(3) - .apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1), target_qubits=[1, 3]) - ) - - assert len(recwarn) == 1 - assert str(recwarn[0].message).startswith("Noise is not applied to any gate") - - assert np.allclose( - circuit.as_unitary(), - np.kron(gates.Si().to_matrix(), np.kron(gates.CPhaseShift(0.15).to_matrix(), np.eye(2))), - ) - - -def test_as_unitary_with_compiler_directives_returns_expected_unitary(): - circuit = Circuit().add_verbatim_box(Circuit().cphaseshift(2, 1, 0.15).si(3)) - assert np.allclose( - circuit.as_unitary(), - np.kron(gates.Si().to_matrix(), np.kron(gates.CPhaseShift(0.15).to_matrix(), np.eye(2))), - ) - - -@pytest.mark.parametrize( - "circuit,expected_unitary", - [ - (Circuit().h(0), gates.H().to_matrix()), - (Circuit().h(0).add_result_type(ResultType.Probability(target=[0])), gates.H().to_matrix()), - (Circuit().x(0), gates.X().to_matrix()), - (Circuit().y(0), gates.Y().to_matrix()), - (Circuit().z(0), gates.Z().to_matrix()), - (Circuit().s(0), gates.S().to_matrix()), - (Circuit().si(0), gates.Si().to_matrix()), - (Circuit().t(0), gates.T().to_matrix()), - (Circuit().ti(0), gates.Ti().to_matrix()), - (Circuit().v(0), gates.V().to_matrix()), - (Circuit().vi(0), gates.Vi().to_matrix()), - (Circuit().rx(0, 0.15), gates.Rx(0.15).to_matrix()), - (Circuit().ry(0, 0.15), gates.Ry(0.15).to_matrix()), - (Circuit().rz(0, 0.15), gates.Rz(0.15).to_matrix()), - (Circuit().phaseshift(0, 0.15), gates.PhaseShift(0.15).to_matrix()), - (Circuit().cnot(1, 0), gates.CNot().to_matrix()), - (Circuit().cnot(1, 0).add_result_type(ResultType.StateVector()), gates.CNot().to_matrix()), - (Circuit().swap(1, 0), gates.Swap().to_matrix()), - (Circuit().swap(0, 1), gates.Swap().to_matrix()), - (Circuit().iswap(1, 0), gates.ISwap().to_matrix()), - (Circuit().iswap(0, 1), gates.ISwap().to_matrix()), - (Circuit().pswap(1, 0, 0.15), gates.PSwap(0.15).to_matrix()), - (Circuit().pswap(0, 1, 0.15), gates.PSwap(0.15).to_matrix()), - (Circuit().xy(1, 0, 0.15), gates.XY(0.15).to_matrix()), - (Circuit().xy(0, 1, 0.15), gates.XY(0.15).to_matrix()), - (Circuit().cphaseshift(1, 0, 0.15), gates.CPhaseShift(0.15).to_matrix()), - (Circuit().cphaseshift00(1, 0, 0.15), gates.CPhaseShift00(0.15).to_matrix()), - (Circuit().cphaseshift01(1, 0, 0.15), gates.CPhaseShift01(0.15).to_matrix()), - (Circuit().cphaseshift10(1, 0, 0.15), gates.CPhaseShift10(0.15).to_matrix()), - (Circuit().cy(1, 0), gates.CY().to_matrix()), - (Circuit().cz(1, 0), gates.CZ().to_matrix()), - (Circuit().xx(1, 0, 0.15), gates.XX(0.15).to_matrix()), - (Circuit().yy(1, 0, 0.15), gates.YY(0.15).to_matrix()), - (Circuit().zz(1, 0, 0.15), gates.ZZ(0.15).to_matrix()), - (Circuit().ccnot(2, 1, 0), gates.CCNot().to_matrix()), - ( - Circuit() - .ccnot(2, 1, 0) - .add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[1])), - gates.CCNot().to_matrix(), - ), - (Circuit().ccnot(1, 2, 0), gates.CCNot().to_matrix()), - (Circuit().cswap(2, 1, 0), gates.CSwap().to_matrix()), - (Circuit().cswap(2, 0, 1), gates.CSwap().to_matrix()), - (Circuit().h(1), np.kron(gates.H().to_matrix(), np.eye(2))), - (Circuit().x(1).i(2), np.kron(np.eye(2), np.kron(gates.X().to_matrix(), np.eye(2)))), - ( - Circuit().y(1).z(2), - np.kron(gates.Z().to_matrix(), np.kron(gates.Y().to_matrix(), np.eye(2))), - ), - (Circuit().rx(1, 0.15), np.kron(gates.Rx(0.15).to_matrix(), np.eye(2))), - ( - Circuit().ry(1, 0.15).i(2), - np.kron(np.eye(2), np.kron(gates.Ry(0.15).to_matrix(), np.eye(2))), - ), - ( - Circuit().rz(1, 0.15).s(2), - np.kron(gates.S().to_matrix(), np.kron(gates.Rz(0.15).to_matrix(), np.eye(2))), - ), - (Circuit().pswap(2, 1, 0.15), np.kron(gates.PSwap(0.15).to_matrix(), np.eye(2))), - (Circuit().pswap(1, 2, 0.15), np.kron(gates.PSwap(0.15).to_matrix(), np.eye(2))), - ( - Circuit().xy(2, 1, 0.15).i(3), - np.kron(np.eye(2), np.kron(gates.XY(0.15).to_matrix(), np.eye(2))), - ), - ( - Circuit().xy(1, 2, 0.15).i(3), - np.kron(np.eye(2), np.kron(gates.XY(0.15).to_matrix(), np.eye(2))), - ), - ( - Circuit().cphaseshift(2, 1, 0.15).si(3), - np.kron( - gates.Si().to_matrix(), np.kron(gates.CPhaseShift(0.15).to_matrix(), np.eye(2)) - ), - ), - (Circuit().ccnot(3, 2, 1), np.kron(gates.CCNot().to_matrix(), np.eye(2))), - (Circuit().ccnot(2, 3, 1), np.kron(gates.CCNot().to_matrix(), np.eye(2))), - ( - Circuit().cswap(3, 2, 1).i(4), - np.kron(np.eye(2), np.kron(gates.CSwap().to_matrix(), np.eye(2))), - ), - ( - Circuit().cswap(3, 1, 2).i(4), - np.kron(np.eye(2), np.kron(gates.CSwap().to_matrix(), np.eye(2))), - ), - ( - Circuit().cswap(3, 2, 1).t(4), - np.kron(gates.T().to_matrix(), np.kron(gates.CSwap().to_matrix(), np.eye(2))), - ), - ( - Circuit().cswap(3, 1, 2).t(4), - np.kron(gates.T().to_matrix(), np.kron(gates.CSwap().to_matrix(), np.eye(2))), - ), - (Circuit().h(0).h(0), gates.I().to_matrix()), - (Circuit().h(0).x(0), np.dot(gates.X().to_matrix(), gates.H().to_matrix())), - (Circuit().x(0).h(0), np.dot(gates.H().to_matrix(), gates.X().to_matrix())), - ( - Circuit().y(0).z(1).cnot(1, 0), - np.dot(gates.CNot().to_matrix(), np.kron(gates.Z().to_matrix(), gates.Y().to_matrix())), - ), - ( - Circuit().z(0).y(1).cnot(1, 0), - np.dot(gates.CNot().to_matrix(), np.kron(gates.Y().to_matrix(), gates.Z().to_matrix())), - ), - ( - Circuit().z(0).y(1).cnot(1, 0).cnot(2, 1), - np.dot( - np.dot( - np.dot( - np.kron(gates.CNot().to_matrix(), np.eye(2)), - np.kron(np.eye(2), gates.CNot().to_matrix()), - ), - np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)), - ), - np.kron(np.eye(4), gates.Z().to_matrix()), - ), - ), - ( - Circuit().z(0).y(1).cnot(1, 0).ccnot(2, 1, 0), - np.dot( - np.dot( - np.dot( - gates.CCNot().to_matrix(), - np.kron(np.eye(2), gates.CNot().to_matrix()), - ), - np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)), - ), - np.kron(np.eye(4), gates.Z().to_matrix()), - ), - ), - ( - Circuit().cnot(0, 1), - np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ( - Circuit().ccnot(0, 1, 2), - np.array( - [ - [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ( - Circuit().ccnot(1, 0, 2), - np.array( - [ - [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ( - Circuit().ccnot(0, 2, 1), - np.array( - [ - [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ( - Circuit().ccnot(2, 0, 1), - np.array( - [ - [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ( - Circuit().s(0).v(1).cnot(0, 1).cnot(1, 2), - np.dot( - np.dot( - np.dot( - np.kron( - np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - np.eye(2), - ), - np.kron( - np.eye(2), - np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ), - np.kron(np.kron(np.eye(2), gates.V().to_matrix()), np.eye(2)), - ), - np.kron(np.eye(4), gates.S().to_matrix()), - ), - ), - ( - Circuit().z(0).y(1).cnot(0, 1).ccnot(0, 1, 2), - np.dot( - np.dot( - np.dot( - np.array( - [ - [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], - ], - dtype=complex, - ), - np.kron( - np.eye(2), - np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ), - np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)), - ), - np.kron(np.eye(4), gates.Z().to_matrix()), - ), - ), - ( - Circuit().z(0).y(1).cnot(0, 1).ccnot(2, 0, 1), - np.dot( - np.dot( - np.dot( - np.array( - [ - [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - np.kron( - np.eye(2), - np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ), - np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)), - ), - np.kron(np.eye(4), gates.Z().to_matrix()), - ), - ), - ], -) -def test_as_unitary_one_gate_returns_expected_unitary(circuit, expected_unitary): - assert np.allclose(circuit.as_unitary(), expected_unitary) - - def test_to_unitary_empty_instructions_returns_empty_array(): circ = Circuit() circ.to_unitary() == []