# 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.

import json

import numpy as np
import pytest

import braket.ir.jaqcd as ir
from braket.circuits import Circuit, Instruction, Noise, QubitSet
from braket.circuits.free_parameter import FreeParameter
from braket.circuits.serialization import (
    IRType,
    OpenQASMSerializationProperties,
    QubitReferenceType,
)
from braket.ir.jaqcd.shared_models import (
    DampingProbability,
    DampingSingleProbability,
    DoubleControl,
    DoubleTarget,
    MultiProbability,
    MultiTarget,
    SingleControl,
    SingleProbability,
    SingleProbability_34,
    SingleProbability_1516,
    SingleTarget,
    TripleProbability,
    TwoDimensionalMatrixList,
)

testdata = [
    (Noise.BitFlip, "bit_flip", ir.BitFlip, [SingleTarget, SingleProbability], {}),
    (Noise.PhaseFlip, "phase_flip", ir.PhaseFlip, [SingleTarget, SingleProbability], {}),
    (Noise.Depolarizing, "depolarizing", ir.Depolarizing, [SingleTarget, SingleProbability_34], {}),
    (
        Noise.AmplitudeDamping,
        "amplitude_damping",
        ir.AmplitudeDamping,
        [SingleTarget, DampingProbability],
        {},
    ),
    (
        Noise.GeneralizedAmplitudeDamping,
        "generalized_amplitude_damping",
        ir.GeneralizedAmplitudeDamping,
        [SingleTarget, DampingProbability, DampingSingleProbability],
        {},
    ),
    (
        Noise.PhaseDamping,
        "phase_damping",
        ir.PhaseDamping,
        [SingleTarget, DampingProbability],
        {},
    ),
    (
        Noise.TwoQubitDepolarizing,
        "two_qubit_depolarizing",
        ir.TwoQubitDepolarizing,
        [DoubleTarget, SingleProbability_1516],
        {},
    ),
    (
        Noise.TwoQubitDephasing,
        "two_qubit_dephasing",
        ir.TwoQubitDephasing,
        [DoubleTarget, SingleProbability_34],
        {},
    ),
    (
        Noise.TwoQubitPauliChannel,
        "two_qubit_pauli_channel",
        ir.MultiQubitPauliChannel,
        [DoubleTarget, MultiProbability],
        {},
    ),
    (
        Noise.PauliChannel,
        "pauli_channel",
        ir.PauliChannel,
        [SingleTarget, TripleProbability],
        {},
    ),
    (
        Noise.Kraus,
        "kraus",
        ir.Kraus,
        [TwoDimensionalMatrixList, MultiTarget],
        {"input_type": complex},
    ),
    (
        Noise.Kraus,
        "kraus",
        ir.Kraus,
        [TwoDimensionalMatrixList, MultiTarget],
        {"input_type": float},
    ),
    (
        Noise.Kraus,
        "kraus",
        ir.Kraus,
        [TwoDimensionalMatrixList, MultiTarget],
        {"input_type": int},
    ),
]


invalid_kraus_matrices = [
    ([np.array([[1]])]),
    ([np.array([1])]),
    ([np.array([0, 1, 2])]),
    ([np.array([[0, 1], [1, 2], [3, 4]])]),
    ([np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])]),
    ([np.array([[0, 1], [1, 1]])]),
    ([np.array([[1, 0], [0, 1]]), np.array([[0, 1], [1, 0]])]),
    ([np.array([[1, 0], [0, 1]]) * np.sqrt(0.5), np.eye(4) * np.sqrt(0.5)]),
    ([np.eye(8)]),
    ([np.eye(2), np.eye(2), np.eye(2), np.eye(2), np.eye(2)]),
]


def single_target_valid_input(**kwargs):
    return {"target": 2}


def double_target_valid_ir_input(**kwargs):
    return {"targets": [2, 3]}


def double_target_valid_input(**kwargs):
    return {"target1": 2, "target2": 3}


def single_probability_valid_input(**kwargs):
    return {"probability": 0.1234}


def single_probability_34_valid_input(**kwargs):
    return {"probability": 0.1234}


def single_probability_1516_valid_input(**kwargs):
    return {"probability": 0.1234}


def damping_single_probability_valid_input(**kwargs):
    return {"probability": 0.1234}


def damping_probability_valid_input(**kwargs):
    return {"gamma": 0.1234}


def triple_probability_valid_input(**kwargs):
    return {"probX": 0.1234, "probY": 0.1324, "probZ": 0.1423}


def single_control_valid_input(**kwargs):
    return {"control": 0}


def double_control_valid_ir_input(**kwargs):
    return {"controls": [0, 1]}


def double_control_valid_input(**kwargs):
    return {"control1": 0, "control2": 1}


def multi_target_valid_input(**kwargs):
    return {"targets": [5]}


def two_dimensional_matrix_list_valid_ir_input(**kwargs):
    return {"matrices": [[[[0, 0], [1, 0]], [[1, 0], [0, 0]]]]}


def two_dimensional_matrix_list_valid_input(**kwargs):
    input_type = kwargs.get("input_type")
    return {
        "matrices": [np.array([[input_type(0), input_type(1)], [input_type(1), input_type(0)]])]
    }


def multi_probability_valid_input(**kwargs):
    return {"probabilities": {"XX": 0.1}}


def multi_probability_invalid_input(**kwargs):
    return {"probabilities": {"XX": 1.1}}


valid_ir_switcher = {
    "SingleTarget": single_target_valid_input,
    "DoubleTarget": double_target_valid_ir_input,
    "SingleProbability": single_probability_valid_input,
    "SingleProbability_34": single_probability_34_valid_input,
    "SingleProbability_1516": single_probability_1516_valid_input,
    "DampingProbability": damping_probability_valid_input,
    "DampingSingleProbability": damping_single_probability_valid_input,
    "TripleProbability": triple_probability_valid_input,
    "MultiProbability": multi_probability_valid_input,
    "SingleControl": single_control_valid_input,
    "DoubleControl": double_control_valid_ir_input,
    "MultiTarget": multi_target_valid_input,
    "TwoDimensionalMatrixList": two_dimensional_matrix_list_valid_ir_input,
}


valid_subroutine_switcher = dict(
    valid_ir_switcher,
    **{
        "TwoDimensionalMatrixList": two_dimensional_matrix_list_valid_input,
        "DoubleTarget": double_target_valid_input,
        "DoubleControl": double_control_valid_input,
    },
)


def create_valid_ir_input(irsubclasses):
    input = {}
    for subclass in irsubclasses:
        input.update(valid_ir_switcher.get(subclass.__name__, lambda: "Invalid subclass")())
    return input


def create_valid_subroutine_input(irsubclasses, **kwargs):
    input = {}
    for subclass in irsubclasses:
        input.update(
            valid_subroutine_switcher.get(subclass.__name__, lambda: "Invalid subclass")(**kwargs)
        )
    return input


def create_valid_target_input(irsubclasses):
    input = {}
    qubit_set = []
    # based on the concept that control goes first in target input
    for subclass in irsubclasses:
        if subclass == SingleTarget:
            qubit_set.extend(list(single_target_valid_input().values()))
        elif subclass == DoubleTarget:
            qubit_set.extend(list(double_target_valid_ir_input().values()))
        elif subclass == MultiTarget:
            qubit_set.extend(list(multi_target_valid_input().values()))
        elif subclass == SingleControl:
            qubit_set = list(single_control_valid_input().values()) + qubit_set
        elif subclass == DoubleControl:
            qubit_set = list(double_control_valid_ir_input().values()) + qubit_set
        elif any(
            subclass == i
            for i in [
                SingleProbability,
                SingleProbability_34,
                SingleProbability_1516,
                DampingSingleProbability,
                DampingProbability,
                TripleProbability,
                TwoDimensionalMatrixList,
                MultiProbability,
            ]
        ):
            pass
        else:
            raise ValueError("Invalid subclass")
    input["target"] = QubitSet(qubit_set)
    return input


def create_valid_noise_class_input(irsubclasses, **kwargs):
    input = {}
    if SingleProbability in irsubclasses:
        input.update(single_probability_valid_input())
    if SingleProbability_34 in irsubclasses:
        input.update(single_probability_34_valid_input())
    if SingleProbability_1516 in irsubclasses:
        input.update(single_probability_1516_valid_input())
    if DampingSingleProbability in irsubclasses:
        input.update(damping_single_probability_valid_input())
    if DampingProbability in irsubclasses:
        input.update(damping_probability_valid_input())
    if TripleProbability in irsubclasses:
        input.update(triple_probability_valid_input())
    if MultiProbability in irsubclasses:
        input.update(multi_probability_valid_input())
    if TwoDimensionalMatrixList in irsubclasses:
        input.update(two_dimensional_matrix_list_valid_input(**kwargs))
    return input


def create_valid_instruction_input(testclass, irsubclasses, **kwargs):
    input = create_valid_target_input(irsubclasses)
    input["operator"] = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs))
    return input


def calculate_qubit_count(irsubclasses):
    qubit_count = 0
    for subclass in irsubclasses:
        if subclass == SingleTarget:
            qubit_count += 1
        elif subclass == DoubleTarget:
            qubit_count += 2
        elif subclass == SingleControl:
            qubit_count += 1
        elif subclass == DoubleControl:
            qubit_count += 2
        elif subclass == MultiTarget:
            qubit_count += 3
        elif any(
            subclass == i
            for i in [
                SingleProbability,
                SingleProbability_34,
                SingleProbability_1516,
                DampingSingleProbability,
                DampingProbability,
                TripleProbability,
                MultiProbability,
                TwoDimensionalMatrixList,
            ]
        ):
            pass
        else:
            raise ValueError("Invalid subclass")
    return qubit_count


@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata)
def test_ir_noise_level(testclass, subroutine_name, irclass, irsubclasses, kwargs):
    expected = irclass(**create_valid_ir_input(irsubclasses))
    actual = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)).to_ir(
        **create_valid_target_input(irsubclasses)
    )
    assert actual == expected


@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata)
def test_ir_instruction_level(testclass, subroutine_name, irclass, irsubclasses, kwargs):
    expected = irclass(**create_valid_ir_input(irsubclasses))
    instruction = Instruction(**create_valid_instruction_input(testclass, irsubclasses, **kwargs))
    actual = instruction.to_ir()
    assert actual == expected


@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata)
def test_noise_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwargs):
    qubit_count = calculate_qubit_count(irsubclasses)
    subroutine = getattr(Circuit(), subroutine_name)
    assert subroutine(**create_valid_subroutine_input(irsubclasses, **kwargs)) == Circuit(
        Instruction(**create_valid_instruction_input(testclass, irsubclasses, **kwargs))
    )
    if qubit_count == 1:
        multi_targets = [0, 1, 2]
        instruction_list = []
        for target in multi_targets:
            instruction_list.append(
                Instruction(
                    operator=testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)),
                    target=target,
                )
            )
        subroutine = getattr(Circuit(), subroutine_name)
        subroutine_input = {"target": multi_targets}
        if SingleProbability in irsubclasses:
            subroutine_input.update(single_probability_valid_input())
        if SingleProbability_34 in irsubclasses:
            subroutine_input.update(single_probability_34_valid_input())
        if SingleProbability_1516 in irsubclasses:
            subroutine_input.update(single_probability_1516_valid_input())
        if DampingSingleProbability in irsubclasses:
            subroutine_input.update(damping_single_probability_valid_input())
        if DampingProbability in irsubclasses:
            subroutine_input.update(damping_probability_valid_input())
        if TripleProbability in irsubclasses:
            subroutine_input.update(triple_probability_valid_input())
        if MultiProbability in irsubclasses:
            subroutine_input.update(multi_probability_valid_input())

        circuit1 = subroutine(**subroutine_input)
        circuit2 = Circuit(instruction_list)
        assert circuit1 == circuit2


@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata)
def test_noise_to_matrix(testclass, subroutine_name, irclass, irsubclasses, kwargs):
    noise1 = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs))
    noise2 = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs))
    assert all(isinstance(matrix, np.ndarray) for matrix in noise1.to_matrix())
    assert all(np.allclose(m1, m2) for m1, m2 in zip(noise1.to_matrix(), noise2.to_matrix()))


@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata)
def test_fixed_qubit_count(testclass, subroutine_name, irclass, irsubclasses, kwargs):
    fixed_qubit_count = testclass.fixed_qubit_count()
    if fixed_qubit_count is not NotImplemented:
        noise = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs))
        assert noise.qubit_count == fixed_qubit_count


@pytest.mark.parametrize(
    "parameterized_noise",
    [
        (Noise.BitFlip(0.1)),
        (Noise.BitFlip(FreeParameter("alpha"))),
        (Noise.PhaseFlip(0.1)),
        (Noise.PhaseFlip(FreeParameter("alpha"))),
        (Noise.Depolarizing(0.1)),
        (Noise.Depolarizing(FreeParameter("alpha"))),
        (Noise.AmplitudeDamping(0.1)),
        (Noise.AmplitudeDamping(FreeParameter("alpha"))),
        (Noise.GeneralizedAmplitudeDamping(0.1, 0.2)),
        (Noise.GeneralizedAmplitudeDamping(FreeParameter("alpha"), FreeParameter("beta"))),
        (Noise.PhaseDamping(0.1)),
        (Noise.PhaseDamping(FreeParameter("alpha"))),
        (Noise.TwoQubitDepolarizing(0.1)),
        (Noise.TwoQubitDepolarizing(FreeParameter("alpha"))),
        (Noise.TwoQubitDephasing(0.1)),
        (Noise.TwoQubitDephasing(FreeParameter("alpha"))),
        (Noise.TwoQubitPauliChannel({"XX": 0.1, "YY": 0.2})),
        (Noise.TwoQubitPauliChannel({"XX": FreeParameter("x"), "YY": FreeParameter("y")})),
        (Noise.PauliChannel(0.1, 0.2, 0.3)),
        (Noise.PauliChannel(FreeParameter("x"), FreeParameter("y"), FreeParameter("z"))),
    ],
)
def test_serialization(parameterized_noise):
    serialized = parameterized_noise.to_dict()
    serialized_str = json.dumps(serialized)
    deserialized_dict = json.loads(serialized_str)
    deserialized = Noise.from_dict(deserialized_dict)
    assert deserialized == parameterized_noise


@pytest.mark.parametrize(
    "parameterized_noise, params, expected_noise",
    [
        (Noise.BitFlip(FreeParameter("alpha")), {"alpha": 0.1}, Noise.BitFlip(0.1)),
        (Noise.PhaseFlip(FreeParameter("alpha")), {"alpha": 0.1}, Noise.PhaseFlip(0.1)),
        (Noise.Depolarizing(FreeParameter("alpha")), {"alpha": 0.1}, Noise.Depolarizing(0.1)),
        (
            Noise.AmplitudeDamping(FreeParameter("alpha")),
            {"alpha": 0.1},
            Noise.AmplitudeDamping(0.1),
        ),
        (
            Noise.GeneralizedAmplitudeDamping(FreeParameter("alpha"), FreeParameter("beta")),
            {"alpha": 0.1},
            Noise.GeneralizedAmplitudeDamping(0.1, FreeParameter("beta")),
        ),
        (Noise.PhaseDamping(FreeParameter("alpha")), {"alpha": 0.1}, Noise.PhaseDamping(0.1)),
        (
            Noise.TwoQubitDepolarizing(FreeParameter("alpha")),
            {"alpha": 0.1},
            Noise.TwoQubitDepolarizing(0.1),
        ),
        (
            Noise.TwoQubitDephasing(FreeParameter("alpha")),
            {"alpha": 0.1},
            Noise.TwoQubitDephasing(0.1),
        ),
        (
            Noise.TwoQubitPauliChannel({"XX": FreeParameter("x"), "YY": FreeParameter("y")}),
            {"x": 0.1},
            Noise.TwoQubitPauliChannel({"XX": 0.1, "YY": FreeParameter("y")}),
        ),
        (
            Noise.PauliChannel(FreeParameter("x"), FreeParameter("y"), FreeParameter("z")),
            {"x": 0.1, "z": 0.2},
            Noise.PauliChannel(0.1, FreeParameter("y"), 0.2),
        ),
    ],
)
def test_parameter_binding(parameterized_noise, params, expected_noise):
    result_noise = parameterized_noise.bind_values(**params)
    assert result_noise == expected_noise


def test_parameterized_noise():
    noise = Noise.PauliChannel(FreeParameter("a"), 0.2, FreeParameter("b"))
    assert noise.probX == FreeParameter("a")
    assert noise.probY == 0.2
    assert noise.probZ == FreeParameter("b")


# Additional Unitary noise tests


@pytest.mark.parametrize("matrices", invalid_kraus_matrices)
def test_kraus_invalid_matrix(matrices):
    with pytest.raises(ValueError):
        Noise.Kraus(matrices=matrices)


def test_kraus_matrix_target_size_mismatch():
    with pytest.raises(ValueError):
        Circuit().kraus(
            matrices=[np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])],
            targets=[0],
        )


@pytest.mark.parametrize(
    "probs",
    [
        {"X": -0.1},
        {"XY": 1.1},
        {"TX": 0.1},
        {"X": 0.5, "Y": 0.6},
        {"X": 0.1, "YY": 0.2},
        {"II": 0.9, "XX": 0.1},
    ],
)
def test_invalid_values_pauli_channel_two_qubit(probs):
    with pytest.raises(ValueError):
        Noise.TwoQubitPauliChannel(probs)


@pytest.mark.parametrize(
    "probs",
    [
        {"XY": 0.1},
        {"XX": 0.1, "ZZ": 0.2},
    ],
)
def test_valid_values_pauli_channel_two_qubit(probs):
    noise = Noise.TwoQubitPauliChannel(probs)
    assert len(noise.to_matrix()) == 16


@pytest.mark.parametrize(
    "noise, serialization_properties, target, expected_ir",
    [
        (
            Noise.BitFlip(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL),
            [3],
            "#pragma braket noise bit_flip(0.5) q[3]",
        ),
        (
            Noise.BitFlip(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL),
            [3],
            "#pragma braket noise bit_flip(0.5) $3",
        ),
        (
            Noise.PhaseFlip(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL),
            [3],
            "#pragma braket noise phase_flip(0.5) q[3]",
        ),
        (
            Noise.PhaseFlip(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL),
            [3],
            "#pragma braket noise phase_flip(0.5) $3",
        ),
        (
            Noise.PauliChannel(0.1, 0.2, 0.3),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL),
            [3],
            "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) q[3]",
        ),
        (
            Noise.PauliChannel(0.1, 0.2, 0.3),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL),
            [3],
            "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) $3",
        ),
        (
            Noise.Depolarizing(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL),
            [3],
            "#pragma braket noise depolarizing(0.5) q[3]",
        ),
        (
            Noise.Depolarizing(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL),
            [3],
            "#pragma braket noise depolarizing(0.5) $3",
        ),
        (
            Noise.TwoQubitDepolarizing(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL),
            [3, 5],
            "#pragma braket noise two_qubit_depolarizing(0.5) q[3], q[5]",
        ),
        (
            Noise.TwoQubitDepolarizing(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL),
            [3, 5],
            "#pragma braket noise two_qubit_depolarizing(0.5) $3, $5",
        ),
        (
            Noise.TwoQubitDephasing(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL),
            [3, 5],
            "#pragma braket noise two_qubit_dephasing(0.5) q[3], q[5]",
        ),
        (
            Noise.TwoQubitDephasing(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL),
            [3, 5],
            "#pragma braket noise two_qubit_dephasing(0.5) $3, $5",
        ),
        (
            Noise.AmplitudeDamping(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL),
            [3],
            "#pragma braket noise amplitude_damping(0.5) q[3]",
        ),
        (
            Noise.AmplitudeDamping(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL),
            [3],
            "#pragma braket noise amplitude_damping(0.5) $3",
        ),
        (
            Noise.GeneralizedAmplitudeDamping(0.5, 0.1),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL),
            [3],
            "#pragma braket noise generalized_amplitude_damping(0.5, 0.1) q[3]",
        ),
        (
            Noise.GeneralizedAmplitudeDamping(0.5, 0.1),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL),
            [3],
            "#pragma braket noise generalized_amplitude_damping(0.5, 0.1) $3",
        ),
        (
            Noise.PhaseDamping(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL),
            [3],
            "#pragma braket noise phase_damping(0.5) q[3]",
        ),
        (
            Noise.PhaseDamping(0.5),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL),
            [3],
            "#pragma braket noise phase_damping(0.5) $3",
        ),
        (
            Noise.Kraus(
                [
                    np.eye(4) * np.sqrt(0.9),
                    np.kron([[1.0, 0.0], [0.0, 1.0]], [[0.0, 1.0], [1.0, 0.0]]) * np.sqrt(0.1),
                ]
            ),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL),
            [3, 5],
            "#pragma braket noise kraus(["
            "[0.9486832980505138, 0, 0, 0], "
            "[0, 0.9486832980505138, 0, 0], "
            "[0, 0, 0.9486832980505138, 0], "
            "[0, 0, 0, 0.9486832980505138]], ["
            "[0, 0.31622776601683794, 0, 0], "
            "[0.31622776601683794, 0, 0, 0], "
            "[0, 0, 0, 0.31622776601683794], "
            "[0, 0, 0.31622776601683794, 0]]) q[3], q[5]",
        ),
        (
            Noise.Kraus(
                [
                    np.eye(4) * np.sqrt(0.9),
                    np.kron([[1.0, 0.0], [0.0, 1.0]], [[0.0, 1.0], [1.0, 0.0]]) * np.sqrt(0.1),
                ]
            ),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL),
            [3, 5],
            "#pragma braket noise kraus(["
            "[0.9486832980505138, 0, 0, 0], "
            "[0, 0.9486832980505138, 0, 0], "
            "[0, 0, 0.9486832980505138, 0], "
            "[0, 0, 0, 0.9486832980505138]], ["
            "[0, 0.31622776601683794, 0, 0], "
            "[0.31622776601683794, 0, 0, 0], "
            "[0, 0, 0, 0.31622776601683794], "
            "[0, 0, 0.31622776601683794, 0]]) $3, $5",
        ),
        (
            Noise.Kraus(
                [
                    np.array([[0.9486833j, 0], [0, 0.9486833j]]),
                    np.array([[0, 0.31622777], [0.31622777, 0]]),
                ]
            ),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL),
            [3],
            "#pragma braket noise kraus(["
            "[0.9486833im, 0], [0, 0.9486833im]], ["
            "[0, 0.31622777], [0.31622777, 0]]) q[3]",
        ),
        (
            Noise.Kraus(
                [
                    np.array([[0.9486833j, 0], [0, 0.9486833j]]),
                    np.array([[0, 0.31622777], [0.31622777, 0]]),
                ]
            ),
            OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL),
            [3],
            "#pragma braket noise kraus(["
            "[0.9486833im, 0], [0, 0.9486833im]], ["
            "[0, 0.31622777], [0.31622777, 0]]) $3",
        ),
    ],
)
def test_noise_to_ir_openqasm(noise, serialization_properties, target, expected_ir):
    assert (
        noise.to_ir(
            target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties
        )
        == expected_ir
    )
