# 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 __future__ import annotations
import itertools
import braket.ir.jaqcd as braket_instruction
import numpy as np
from braket.default_simulator.operation import KrausOperation
from braket.default_simulator.operation_helpers import (
_from_braket_instruction,
check_cptp,
check_matrix_dimensions,
ir_matrix_to_ndarray,
)
[docs]
class BitFlip(KrausOperation):
"""Bit Flip noise channel"""
def __init__(self, targets, probability):
self._targets = tuple(targets)
self._probability = probability
@property
def matrices(self) -> list[np.ndarray]:
k0 = np.sqrt(1 - self._probability) * np.array([[1, 0], [0, 1]])
k1 = np.sqrt(self._probability) * np.array([[0, 1], [1, 0]])
return [k0, k1]
@property
def targets(self) -> tuple[int, ...]:
return self._targets
@property
def probability(self):
return self._probability
@_from_braket_instruction.register(braket_instruction.BitFlip)
def _bit_flip(instruction) -> BitFlip:
return BitFlip([instruction.target], instruction.probability)
[docs]
class PhaseFlip(KrausOperation):
"""Phase Flip noise channel"""
def __init__(self, targets, probability):
self._targets = tuple(targets)
self._probability = probability
@property
def matrices(self) -> list[np.ndarray]:
k0 = np.sqrt(1 - self._probability) * np.array([[1.0, 0.0], [0.0, 1.0]])
k1 = np.sqrt(self._probability) * np.array([[1.0, 0.0], [0.0, -1.0]])
return [k0, k1]
@property
def targets(self) -> tuple[int, ...]:
return self._targets
@property
def probability(self):
return self._probability
@_from_braket_instruction.register(braket_instruction.PhaseFlip)
def _phase_flip(instruction) -> PhaseFlip:
return PhaseFlip([instruction.target], instruction.probability)
[docs]
class PauliChannel(KrausOperation):
"""Pauli noise channel"""
def __init__(self, targets, probX, probY, probZ):
self._targets = tuple(targets)
self._probX = probX
self._probY = probY
self._probZ = probZ
@property
def matrices(self) -> list[np.ndarray]:
K0 = np.sqrt(1 - self._probX - self._probY - self._probZ) * np.array(
[[1.0, 0.0], [0.0, 1.0]]
)
K1 = np.sqrt(self._probX) * np.array([[0.0, 1.0], [1.0, 0.0]])
K2 = np.sqrt(self._probY) * np.array([[0.0, -1.0j], [1.0j, 0.0]])
K3 = np.sqrt(self._probZ) * np.array([[1.0, 0.0], [0.0, -1.0]])
return [K0, K1, K2, K3]
@property
def targets(self) -> tuple[int, ...]:
return self._targets
@property
def probabilities(self):
return [self._probX, self._probY, self._probZ]
@_from_braket_instruction.register(braket_instruction.PauliChannel)
def _pauli_channel(instruction) -> PauliChannel:
return PauliChannel(
[instruction.target], instruction.probX, instruction.probY, instruction.probZ
)
[docs]
class Depolarizing(KrausOperation):
"""Depolarizing noise channel"""
def __init__(self, targets, probability):
self._targets = tuple(targets)
self._probability = probability
@property
def matrices(self) -> list[np.ndarray]:
K0 = np.sqrt(1 - self._probability) * np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex)
K1 = np.sqrt(self._probability / 3) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex)
K2 = (
np.sqrt(self._probability / 3) * 1j * np.array([[0.0, -1.0], [1.0, 0.0]], dtype=complex)
)
K3 = np.sqrt(self._probability / 3) * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex)
return [K0, K1, K2, K3]
@property
def targets(self) -> tuple[int, ...]:
return self._targets
@property
def probability(self):
return self._probability
@_from_braket_instruction.register(braket_instruction.Depolarizing)
def _depolarizing(instruction) -> Depolarizing:
return Depolarizing([instruction.target], instruction.probability)
[docs]
class TwoQubitDepolarizing(KrausOperation):
"""Two-qubit Depolarizing noise channel"""
def __init__(self, targets, probability):
self._targets = tuple(targets)
self._probability = probability
@property
def matrices(self) -> list[np.ndarray]:
SI = np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex)
SX = np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex)
SY = np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex)
SZ = np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex)
K_list_single = [SI, SX, SY, SZ]
K_list = [np.kron(i, j) for i in K_list_single for j in K_list_single]
K_list[0] *= np.sqrt(1 - self._probability)
K_list[1:] = [np.sqrt(self._probability / 15) * i for i in K_list[1:]]
return K_list
@property
def targets(self) -> tuple[int, ...]:
return self._targets
@property
def probability(self):
return self._probability
@_from_braket_instruction.register(braket_instruction.TwoQubitDepolarizing)
def _two_qubit_depolarizing(instruction) -> TwoQubitDepolarizing:
return TwoQubitDepolarizing(instruction.targets, instruction.probability)
[docs]
class TwoQubitDephasing(KrausOperation):
"""Two-qubit Dephasing noise channel"""
def __init__(self, targets, probability):
self._targets = tuple(targets)
self._probability = probability
@property
def matrices(self) -> list[np.ndarray]:
SI = np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex)
SZ = np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex)
K0 = np.sqrt(1 - self._probability) * np.kron(SI, SI)
K1 = np.sqrt(self._probability / 3) * np.kron(SI, SZ)
K2 = np.sqrt(self._probability / 3) * np.kron(SZ, SI)
K3 = np.sqrt(self._probability / 3) * np.kron(SZ, SZ)
return [K0, K1, K2, K3]
@property
def targets(self) -> tuple[int, ...]:
return self._targets
@property
def probability(self):
return self._probability
@_from_braket_instruction.register(braket_instruction.TwoQubitDephasing)
def _two_qubit_dephasing(instruction) -> TwoQubitDephasing:
return TwoQubitDephasing(instruction.targets, instruction.probability)
[docs]
class AmplitudeDamping(KrausOperation):
"""Amplitude Damping noise channel"""
def __init__(self, targets, gamma):
self._targets = tuple(targets)
self._gamma = gamma
@property
def matrices(self) -> list[np.ndarray]:
K0 = np.array([[1.0, 0.0], [0.0, np.sqrt(1 - self._gamma)]], dtype=complex)
K1 = np.array([[0.0, np.sqrt(self._gamma)], [0.0, 0.0]], dtype=complex)
return [K0, K1]
@property
def targets(self) -> tuple[int, ...]:
return self._targets
@property
def gamma(self):
return self._gamma
@_from_braket_instruction.register(braket_instruction.AmplitudeDamping)
def _amplitude_damping(instruction) -> AmplitudeDamping:
return AmplitudeDamping([instruction.target], instruction.gamma)
[docs]
class GeneralizedAmplitudeDamping(KrausOperation):
"""Generalized Amplitude Damping noise channel"""
def __init__(self, targets, gamma, probability):
self._targets = tuple(targets)
self._gamma = gamma
self._probability = probability
@property
def matrices(self) -> list[np.ndarray]:
K0 = np.sqrt(self._probability) * np.array(
[[1.0, 0.0], [0.0, np.sqrt(1 - self._gamma)]], dtype=complex
)
K1 = np.sqrt(self._probability) * np.array(
[[0.0, np.sqrt(self._gamma)], [0.0, 0.0]], dtype=complex
)
K2 = np.sqrt(1 - self._probability) * np.array(
[[np.sqrt(1 - self._gamma), 0.0], [0.0, 1.0]]
)
K3 = np.sqrt(1 - self._probability) * np.array([[0.0, 0.0], [np.sqrt(self._gamma), 0.0]])
return [K0, K1, K2, K3]
@property
def targets(self) -> tuple[int, ...]:
return self._targets
@property
def probability(self):
return self._probability
@property
def gamma(self):
return self._gamma
@_from_braket_instruction.register(braket_instruction.GeneralizedAmplitudeDamping)
def _generalized_amplitude_damping(instruction) -> GeneralizedAmplitudeDamping:
return GeneralizedAmplitudeDamping(
[instruction.target], instruction.gamma, instruction.probability
)
[docs]
class PhaseDamping(KrausOperation):
"""Phase Damping noise channel"""
def __init__(self, targets, gamma):
self._targets = tuple(targets)
self._gamma = gamma
@property
def matrices(self) -> list[np.ndarray]:
K0 = np.array([[1.0, 0.0], [0.0, np.sqrt(1 - self._gamma)]], dtype=complex)
K1 = np.array([[0.0, 0.0], [0.0, np.sqrt(self._gamma)]], dtype=complex)
return [K0, K1]
@property
def targets(self) -> tuple[int, ...]:
return self._targets
@property
def gamma(self):
return self._gamma
@_from_braket_instruction.register(braket_instruction.PhaseDamping)
def _phase_damping(instruction) -> PhaseDamping:
return PhaseDamping([instruction.target], instruction.gamma)
[docs]
class Kraus(KrausOperation):
"""Arbitrary quantum channel that evolve a density matrix through the operator-sum
formalism with the provided matrices as Kraus operators.
"""
def __init__(self, targets, matrices):
self._targets = tuple(targets)
clone = [np.array(matrix, dtype=complex) for matrix in matrices]
for matrix in clone:
check_matrix_dimensions(matrix, self._targets)
check_cptp(clone)
self._matrices = clone
@property
def matrices(self) -> list[np.ndarray]:
return self._matrices
@property
def targets(self) -> tuple[int, ...]:
return self._targets
@_from_braket_instruction.register(braket_instruction.Kraus)
def _kraus(instruction) -> Kraus:
return Kraus(
instruction.targets, [ir_matrix_to_ndarray(matrix) for matrix in instruction.matrices]
)
[docs]
class TwoQubitPauliChannel(KrausOperation):
"""Two qubit Pauli noise channel"""
_paulis = {
"I": np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex),
"X": np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex),
"Y": np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex),
"Z": np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex),
}
_tensor_products_strings = itertools.product(_paulis.keys(), repeat=2)
_names_list = ["".join(x) for x in _tensor_products_strings]
def __init__(self, targets, probabilities):
self._targets = tuple(targets)
self.probabilities = probabilities
total_prob = sum(self.probabilities.values())
K_list = [np.sqrt(1 - total_prob) * np.identity(4)] # identity
for pstring in self._names_list[1:]: # ignore "II"
if pstring in self.probabilities:
mat = np.sqrt(self.probabilities[pstring]) * np.kron(
self._paulis[pstring[0]], self._paulis[pstring[1]]
)
K_list.append(mat)
else:
K_list.append(np.zeros((4, 4)))
self._matrices = K_list
@property
def matrices(self) -> list[np.ndarray]:
return self._matrices
@property
def targets(self) -> tuple[int, ...]:
return self._targets
@_from_braket_instruction.register(braket_instruction.MultiQubitPauliChannel)
def _two_qubit_pauli_channel(instruction) -> TwoQubitPauliChannel:
return TwoQubitPauliChannel(instruction.targets, instruction.probabilities)