Skip to content

Commit

Permalink
Merge branch 'master' into trainable_params-as-list
Browse files Browse the repository at this point in the history
  • Loading branch information
dwierichs committed Nov 17, 2021
2 parents 821dead + e465f6f commit d5b99d5
Show file tree
Hide file tree
Showing 49 changed files with 659 additions and 434 deletions.
4 changes: 4 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
but setting the `trainable_params` with a set works exactly as before.
[(#1xxx)](https://github.com/PennyLaneAI/pennylane/pull/1xxx)

* The `num_params` attribute in the operator class is now dynamic. This makes it easier
to define operator subclasses with a flexible number of parameters.
[(#1898)](https://github.com/PennyLaneAI/pennylane/pull/1898)

* The static method `decomposition()`, formerly in the `Operation` class, has
been moved to the base `Operator` class.
[(#1873)](https://github.com/PennyLaneAI/pennylane/pull/1873)
Expand Down
53 changes: 23 additions & 30 deletions pennylane/devices/tests/test_compare_default_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def circuit(theta, phi):

@pytest.mark.parametrize("ret", ["expval", "var"])
def test_random_circuit(self, device, tol, ret):
"""Test that the expectation value of a random circuit is correct"""
"""Compare the result of a random circuit to default.qubit"""
n_wires = 2
dev = device(n_wires)
dev_def = qml.device("default.qubit", wires=n_wires)
Expand Down Expand Up @@ -185,7 +185,7 @@ def circuit(weights):
assert np.allclose(grad(weights), grad_def(weights), atol=tol(dev.shots))

def test_four_qubit_random_circuit(self, device, tol):
"""Test a four-qubit random circuit with the whole set of possible gates"""
"""Compare a four-qubit random circuit with lots of different gates to default.qubit"""
n_wires = 4
dev = device(n_wires)
dev_def = qml.device("default.qubit", wires=n_wires)
Expand All @@ -197,26 +197,26 @@ def test_four_qubit_random_circuit(self, device, tol):
pytest.skip("Device is in non-analytical mode.")

gates = [
qml.PauliX,
qml.PauliY,
qml.PauliZ,
qml.S,
qml.T,
qml.RX,
qml.RY,
qml.RZ,
qml.Hadamard,
qml.Rot,
qml.CRot,
qml.Toffoli,
qml.SWAP,
qml.CSWAP,
qml.U1,
qml.U2,
qml.U3,
qml.CRX,
qml.CRY,
qml.CRZ,
qml.PauliX(wires=0),
qml.PauliY(wires=1),
qml.PauliZ(wires=2),
qml.S(wires=3),
qml.T(wires=0),
qml.RX(2.3, wires=1),
qml.RY(1.3, wires=2),
qml.RZ(3.3, wires=3),
qml.Hadamard(wires=0),
qml.Rot(0.1, 0.2, 0.3, wires=1),
qml.CRot(0.1, 0.2, 0.3, wires=[2, 3]),
qml.Toffoli(wires=[0, 1, 2]),
qml.SWAP(wires=[1, 2]),
qml.CSWAP(wires=[1, 2, 3]),
qml.U1(1.0, wires=0),
qml.U2(1.0, 2.0, wires=2),
qml.U3(1.0, 2.0, 3.0, wires=3),
qml.CRX(0.1, wires=[1, 2]),
qml.CRY(0.2, wires=[2, 3]),
qml.CRZ(0.3, wires=[3, 1]),
]

layers = 3
Expand All @@ -229,14 +229,7 @@ def circuit():
np.random.seed(1967)
for gates in gates_per_layers:
for gate in gates:
params = list(np.pi * np.random.rand(gate.num_params))
rnd_wires = np.random.choice(range(n_wires), size=gate.num_wires, replace=False)
gate(
*params,
wires=[
int(w) for w in rnd_wires
] # make sure we do not address wires as 0-d arrays
)
qml.apply(gate)
return qml.expval(qml.PauliZ(0))

qnode_def = qml.QNode(circuit, dev_def)
Expand Down
36 changes: 24 additions & 12 deletions pennylane/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,6 @@ class Operator(abc.ABC):
The following class attributes must be defined for all Operators:
* :attr:`~.Operator.num_params`
* :attr:`~.Operator.num_wires`
* :attr:`~.Operator.par_domain`
Expand Down Expand Up @@ -387,11 +386,6 @@ def eigvals(self):
"""
return self._eigvals(*self.parameters)

@property
@abc.abstractmethod
def num_params(self):
"""Number of parameters the operator takes."""

@property
@abc.abstractmethod
def num_wires(self):
Expand Down Expand Up @@ -483,6 +477,16 @@ def __init__(self, *params, wires=None, do_queue=True, id=None):
if wires is None:
raise ValueError("Must specify the wires that {} acts on".format(self.name))

self._num_params = len(params)
# Check if the expected number of parameters coincides with the one received.
# This is always true for the default `Operator.num_params` property, but
# subclasses may overwrite it to define a fixed expected value.
if len(params) != self.num_params:
raise ValueError(
"{}: wrong number of parameters. "
"{} parameters passed, {} expected.".format(self.name, len(params), self.num_params)
)

if isinstance(wires, Wires):
self._wires = wires
else:
Expand All @@ -499,12 +503,6 @@ def __init__(self, *params, wires=None, do_queue=True, id=None):
"{} wires given, {} expected.".format(self.name, len(self._wires), self.num_wires)
)

if len(params) != self.num_params:
raise ValueError(
"{}: wrong number of parameters. "
"{} parameters passed, {} expected.".format(self.name, len(params), self.num_params)
)

self.data = list(params) #: list[Any]: parameters of the operator

if do_queue:
Expand All @@ -517,6 +515,20 @@ def __repr__(self):
return "{}({}, wires={})".format(self.name, params, self.wires.tolist())
return "{}(wires={})".format(self.name, self.wires.tolist())

@property
def num_params(self):
"""Number of trainable parameters that this operator expects to be fed via the
dynamic `*params` argument.
By default, this property returns as many parameters as were used for the
operator creation. If the number of parameters for an operator subclass is fixed,
this property can be overwritten to return the fixed value.
Returns:
int: number of parameters
"""
return self._num_params

@property
def wires(self):
"""Wires of this operator.
Expand Down
5 changes: 4 additions & 1 deletion pennylane/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,16 @@ class Identity(CVObservable, Operation):
simulators should always be equal to 1.
"""
num_wires = 1
num_params = 0
par_domain = None
grad_method = None

ev_order = 1
eigvals = np.array([1, 1])

@property
def num_params(self):
return 0

def label(self, decimals=None, base_label=None):
return base_label or "I"

Expand Down
40 changes: 32 additions & 8 deletions pennylane/ops/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,14 @@ class AmplitudeDamping(Channel):
gamma (float): amplitude damping probability
wires (Sequence[int] or int): the wire the channel acts on
"""
num_params = 1
num_wires = 1
par_domain = "R"
grad_method = "F"

@property
def num_params(self):
return 1

@classmethod
def _kraus_matrices(cls, *params):
gamma = params[0]
Expand Down Expand Up @@ -111,11 +114,14 @@ class GeneralizedAmplitudeDamping(Channel):
p (float): excitation probability
wires (Sequence[int] or int): the wire the channel acts on
"""
num_params = 2
num_wires = 1
par_domain = "R"
grad_method = "F"

@property
def num_params(self):
return 2

@classmethod
def _kraus_matrices(cls, *params):
gamma, p = params
Expand Down Expand Up @@ -164,11 +170,14 @@ class PhaseDamping(Channel):
gamma (float): phase damping probability
wires (Sequence[int] or int): the wire the channel acts on
"""
num_params = 1
num_wires = 1
par_domain = "R"
grad_method = "F"

@property
def num_params(self):
return 1

@classmethod
def _kraus_matrices(cls, *params):
gamma = params[0]
Expand Down Expand Up @@ -223,12 +232,15 @@ class DepolarizingChannel(Channel):
p (float): Each Pauli gate is applied with probability :math:`\frac{p}{3}`
wires (Sequence[int] or int): the wire the channel acts on
"""
num_params = 1
num_wires = 1
par_domain = "R"
grad_method = "A"
grad_recipe = ([[1, 0, 1], [-1, 0, 0]],)

@property
def num_params(self):
return 1

@classmethod
def _kraus_matrices(cls, *params):
p = params[0]
Expand Down Expand Up @@ -272,12 +284,15 @@ class BitFlip(Channel):
p (float): The probability that a bit flip error occurs.
wires (Sequence[int] or int): the wire the channel acts on
"""
num_params = 1
num_wires = 1
par_domain = "R"
grad_method = "A"
grad_recipe = ([[1, 0, 1], [-1, 0, 0]],)

@property
def num_params(self):
return 1

@classmethod
def _kraus_matrices(cls, *params):
p = params[0]
Expand Down Expand Up @@ -339,11 +354,14 @@ class ResetError(Channel):
p_1 (float): The probability that a reset to 1 error occurs.
wires (Sequence[int] or int): the wire the channel acts on
"""
num_params = 2
num_wires = 1
par_domain = "R"
grad_method = "F"

@property
def num_params(self):
return 2

@classmethod
def _kraus_matrices(cls, *params):
p_0, p_1 = params[0], params[1]
Expand Down Expand Up @@ -394,12 +412,15 @@ class PhaseFlip(Channel):
p (float): The probability that a phase flip error occurs.
wires (Sequence[int] or int): the wire the channel acts on
"""
num_params = 1
num_wires = 1
par_domain = "R"
grad_method = "A"
grad_recipe = ([[1, 0, 1], [-1, 0, 0]],)

@property
def num_params(self):
return 1

@classmethod
def _kraus_matrices(cls, *params):
p = params[0]
Expand Down Expand Up @@ -429,7 +450,6 @@ class QubitChannel(Channel):
K_list (list[array[complex]]): List of Kraus matrices
wires (Union[Wires, Sequence[int], or int]): the wire(s) the operation acts on
"""
num_params = 1
num_wires = AnyWires
par_domain = "L"
grad_method = None
Expand Down Expand Up @@ -460,6 +480,10 @@ def __init__(self, *params, wires=None, do_queue=True):
if not np.allclose(Kraus_sum, np.eye(K_list[0].shape[0])):
raise ValueError("Only trace preserving channels can be applied.")

@property
def num_params(self):
return 1

@classmethod
def _kraus_matrices(cls, *params):
K_list = params[0]
Expand Down
Loading

0 comments on commit d5b99d5

Please sign in to comment.