Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: adding MultiQubitPauliChannel to ir #103

Merged
merged 2 commits into from
Jan 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"flake8",
"isort",
"jsonschema==3.2.0",
"numpy",
"pre-commit",
"pylint",
"pytest",
Expand Down
1 change: 1 addition & 0 deletions src/braket/ir/jaqcd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
I,
ISwap,
Kraus,
MultiQubitPauliChannel,
PauliChannel,
PhaseDamping,
PhaseFlip,
Expand Down
25 changes: 24 additions & 1 deletion src/braket/ir/jaqcd/instructions.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
DampingSingleProbability,
DoubleControl,
DoubleTarget,
MultiProbability,
MultiTarget,
SingleControl,
SingleProbability,
Expand Down Expand Up @@ -766,7 +767,7 @@ class Type(str, Enum):

class PauliChannel(SingleTarget, TripleProbability):
"""
Genearal Pauli noise channel.
A single qubit Pauli noise channel.

Attributes:
type (str): The instruction type. default = "pauli_channel". (type) is
Expand All @@ -783,6 +784,28 @@ class Type(str, Enum):
type = Type.pauli_channel


class MultiQubitPauliChannel(DoubleTarget, MultiProbability):
"""
Multi-qubit Pauli noise channel.

Attributes:
type (str): The instruction type. default = "multi_qubit_pauli_channel". (type) is
optional. This should be unique among all instruction types.
target (int): The target qubit(s). This is list of intergers >= 0.
The length of the list must match the length of the Pauli strings provided.

Examples:
>>> MultiQubitPauliChannel(target=1, probabilities={"X": 0.1})
>>> MultiQubitPauliChannel(target=[0,1], probabilities={"XY": 0.1})
>>> MultiQubitPauliChannel(target=[0,1,2], probabilities={"XYZ": 0.1})
"""

class Type(str, Enum):
multi_qubit_pauli_channel = "multi_qubit_pauli_channel"

type = Type.multi_qubit_pauli_channel


class Depolarizing(SingleTarget, SingleProbability_34):
"""
Depolarizing noise channel.
Expand Down
50 changes: 49 additions & 1 deletion src/braket/ir/jaqcd/shared_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 Optional, Union
from typing import Dict, Optional, Union

from pydantic import BaseModel, confloat, conint, conlist, constr, root_validator

Expand Down Expand Up @@ -233,6 +233,54 @@ def validate_probabilities(cls, values):
return values


class MultiProbability(BaseModel):
"""A multi-value-probability parameter set for the Pauli noise channel.

Attributes:
probabilities [Dict[str, float]]: The coefficients of the Pauli channel

Examples:
>>> MultiProbability(probabilities={"X": 0.1})
>>> MultiProbability(probabilities={"XY": 0.1, "YX": 0.01})
"""

probabilities: Dict[
constr(regex="^[IXYZ]+$", min_length=1), confloat(ge=float("0.0"), le=float("1.0"))
]

@root_validator
def validate_probabilities(cls, values):
"""
Pydantic uses the validation subsystem to create objects.
This custom validator has the purpose to ensure sum(probabilities) <= 1
and that the lengths of each Pauli string are equal.
"""

probabilities = values.get("probabilities")
if not probabilities:
raise ValueError("Pauli dictionary must not be empty.")

qubit_count = len(list(probabilities)[0])

if qubit_count * "I" in probabilities.keys():
i = qubit_count * "I"
raise ValueError(
f"{i} is not allowed as a key. Please enter only non-identity Pauli strings."
)

for pauli_string, prob in probabilities.items():
if len(pauli_string) != qubit_count:
raise ValueError("Length of each Pauli string must be equal to number of qubits.")

total_prob = sum(probabilities.values())
if total_prob > 1.0 or total_prob < 0.0:
raise ValueError(
f"Total probability must be a real number in the interval [0, 1]. Total probability was {total_prob}." # noqa: E501
)

return values


class TwoDimensionalMatrix(BaseModel):
"""
Two-dimensional non-empty matrix.
Expand Down
87 changes: 87 additions & 0 deletions test/unit_tests/braket/ir/jaqcd/test_multi_probability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# 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 itertools import product

import numpy as np
import pytest

from braket.ir.jaqcd.shared_models import MultiProbability

np.random.seed(1)


def pauli_strings(n_qubits: int):
pauli_strings = list(map(lambda x: "".join(x), product(["I", "X", "Y", "Z"], repeat=n_qubits)))
return pauli_strings[1:] # remove identity term


def random_noise(n_qubits: int):
return np.random.dirichlet(np.ones(4**n_qubits))[1:] # remove identity term


@pytest.mark.parametrize("n_qubits", [1, 2, 3])
class TestMultiProbability:
"""A class with common parameters, `param1` and `param2`."""

def test_multiprobability(self, n_qubits):
paulis = dict(zip(pauli_strings(n_qubits), random_noise(n_qubits)))
MultiProbability(probabilities=paulis)

def test_multiprobability_less_probabilities(self, n_qubits):
paulis = dict(zip(pauli_strings(n_qubits), random_noise(n_qubits)))
del paulis["X" * n_qubits]
del paulis["Z" * n_qubits]
MultiProbability(probabilities=paulis)

@pytest.mark.xfail(raises=ValueError)
@pytest.mark.parametrize("str", ["T", "s", "x"])
def test_multiprobability_non_pauli(self, n_qubits, str):
paulis = dict(zip(pauli_strings(n_qubits), random_noise(n_qubits)))
paulis[str * n_qubits] = 0.0
MultiProbability(probabilities=paulis)

@pytest.mark.xfail(raises=ValueError)
@pytest.mark.parametrize("value", [12, -0.1, 1.1, np.inf, None])
def test_multiprobability_non_float(self, n_qubits, value):
paulis = dict(zip(pauli_strings(n_qubits), random_noise(n_qubits)))
paulis["X" * n_qubits] = value
MultiProbability(probabilities=paulis)

@pytest.mark.xfail(raises=ValueError)
def test_multiprobability_empty(self, n_qubits):
MultiProbability(probabilities={})

@pytest.mark.xfail(raises=ValueError)
def test_multiprobability_identity(self, n_qubits):
paulis = dict(zip(pauli_strings(n_qubits), random_noise(n_qubits)))
paulis["I" * n_qubits] = 0.1
MultiProbability(probabilities=paulis)

@pytest.mark.xfail(raises=ValueError)
def test_multiprobability_pauli_equal_lengths(self, n_qubits):
paulis = dict(zip(pauli_strings(n_qubits), random_noise(n_qubits)))
paulis["X"] = 0.0
paulis["XY"] = 0.0
MultiProbability(probabilities=paulis)

@pytest.mark.xfail(raises=ValueError)
def test_multiprobability_sum_over_one(self, n_qubits):
paulis = dict(zip(pauli_strings(n_qubits), 10 * random_noise(n_qubits)))
MultiProbability(probabilities=paulis)

@pytest.mark.xfail(raises=ValueError)
def test_multiprobability_sum_under_zero(self, n_qubits):
paulis = dict(zip(pauli_strings(n_qubits), random_noise(n_qubits)))
paulis["Z" * n_qubits] = -0.9
MultiProbability(probabilities=paulis)