# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from functools import lru_cache, singledispatch
from typing import Union
import numpy as np
from braket.default_simulator.operation import GateOperation, KrausOperation
[docs]
def from_braket_instruction(instruction) -> Union[GateOperation, KrausOperation]:
"""Instantiates the concrete `GateOperation` or `KrausOperation` object from the
specified Braket instruction.
Args:
instruction: instruction for a circuit specified using the `braket.ir.jacqd` format.
Returns:
Union[GateOperation, KrausOperation]: instance of the concrete GateOperation or
KrausOperation class corresponding to the specified instruction.
Raises:
NotImplementedError: If no concrete `GateOperation` or `KrausOperation` class has been
registered for the instruction type.
"""
return _from_braket_instruction(instruction)
@singledispatch
def _from_braket_instruction(instruction):
raise NotImplementedError(f"Instruction {instruction} not recognized")
[docs]
@lru_cache()
def pauli_eigenvalues(num_qubits: int) -> np.ndarray:
"""The eigenvalues of Pauli operators and their tensor products.
Args:
num_qubits (int): the number of qubits the operator acts on
Returns:
np.ndarray: the eigenvalues of a Pauli product operator of the given size
"""
if num_qubits == 1:
return np.array([1, -1])
return np.concatenate([pauli_eigenvalues(num_qubits - 1), -pauli_eigenvalues(num_qubits - 1)])
[docs]
def ir_matrix_to_ndarray(matrix: list[list[list[float]]]) -> np.ndarray:
"""Converts a JAQCD matrix into a numpy array.
Args:
matrix (list[list[list[float]]]: The IR representation of a matrix
Returns:
np.ndarray: The numpy ndarray representation of the matrix
"""
return np.array([[complex(element[0], element[1]) for element in row] for row in matrix])
[docs]
def check_matrix_dimensions(matrix: np.ndarray, targets: tuple[int, ...]) -> None:
"""Checks that the matrix is of the correct shape to act on the targets.
Args:
matrix (np.ndarray): The matrix to check
targets (tuple[int, ...]): The target qubits the matrix acts on
Raises:
ValueError: If the matrix is not a square matrix or operates on a space
of different dimension than that generated by the target qubits
"""
if len(matrix.shape) != 2 or matrix.shape[0] != matrix.shape[1]:
raise ValueError(f"{matrix} is not a two-dimensional square matrix")
dimension = 2 ** len(targets)
if dimension != matrix.shape[0]:
raise ValueError(
f"`matrix` operates on space of dimension {matrix.shape[0]} instead of {dimension}"
)
[docs]
def check_unitary(matrix: np.ndarray):
"""Checks that the given matrix is unitary.
Args:
matrix (np.ndarray): The matrix to check
Raises:
ValueError: If the matrix is not unitary
"""
if not np.allclose(np.eye(len(matrix)), matrix.dot(matrix.T.conj())):
raise ValueError(f"{matrix} is not unitary")
[docs]
def check_hermitian(matrix: np.ndarray):
"""Checks that the given matrix is Hermitian.
Args:
matrix (np.ndarray): The matrix to check
Raises:
ValueError: If the matrix is not Hermitian
"""
if not np.allclose(matrix, matrix.T.conj()):
raise ValueError(f"{matrix} is not Hermitian")
[docs]
def check_cptp(matrices: list[np.ndarray]):
"""Checks that the given matrices define a CPTP map.
Args:
matrices (list[np.ndarray]): The matrices to check
Raises:
ValueError: If the matrices do not define a CPTP map
"""
E = sum([np.matmul(matrix.T.conjugate(), matrix) for matrix in matrices])
if not np.allclose(E, np.eye(*E.shape)):
raise ValueError(f"{matrices} do not define a CPTP map")