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

[TEMPLATES] Rewrite subroutines as operations #1192

Merged
merged 11 commits into from
Apr 8, 2021
Merged
Show file tree
Hide file tree
Changes from 6 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
6 changes: 3 additions & 3 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -537,14 +537,14 @@
1: ──RY(1.35)──╰X──RY(0.422)──╰X──┤
```

- The embedding templates, as well as `BasicEntanglerLayers`, are now classes inheriting
- The subroutine templates, as well as `BasicEntanglerLayers`, are now classes inheriting
from `Operation`, and define the ansatz in their `expand()` method. This
change does not affect the user interface.
[(#1138)](https://github.com/PennyLaneAI/pennylane/pull/1138)
[(#1156)](https://github.com/PennyLaneAI/pennylane/pull/1156)

For convenience, `BasicEntanglerLayers` has a method that returns the shape of the
trainable parameter tensor, i.e.,
For convenience, some templates now have a method that returns the expected
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enumerate which ones?

shape of the trainable parameter tensor, i.e.,

```python
shape = qml.templates.BasicEntanglerLayers.shape(n_layers=2, n_wires=4)
Expand Down
2 changes: 1 addition & 1 deletion doc/code/qml_templates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@ Utility functions for quantum Monte Carlo
.. automodapi:: pennylane.templates.subroutines.qmc
:no-heading:
:no-main-docstr:
:skip: QuantumMonteCarlo, Operation, Wires
:skip: QuantumMonteCarlo, Operation
100 changes: 52 additions & 48 deletions pennylane/templates/subroutines/approx_time_evolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
r"""
Contains the ``ApproxTimeEvolution`` template.
Contains the ApproxTimeEvolution template.
"""
# pylint: disable-msg=too-many-branches,too-many-arguments,protected-access
import pennylane as qml
from pennylane.templates.decorator import template
from pennylane.operation import Operation, AnyWires


def _preprocess(hamiltonian):
"""Validate and pre-process inputs as follows:

* Check that the hamiltonian is of the correct type.

Args:
hamiltonian (qml.vqe.vqe.Hamiltonian): Hamiltonian to simulate
"""

if not isinstance(hamiltonian, qml.vqe.vqe.Hamiltonian):
raise ValueError(
"hamiltonian must be of type pennylane.Hamiltonian, got {}".format(
type(hamiltonian).__name__
)
)


@template
def ApproxTimeEvolution(hamiltonian, time, n):
class ApproxTimeEvolution(Operation):
r"""Applies the Trotterized time-evolution operator for an arbitrary Hamiltonian, expressed in terms
of Pauli gates.

Expand Down Expand Up @@ -87,9 +69,6 @@ def ApproxTimeEvolution(hamiltonian, time, n):
time (int or float): The time of evolution, namely the parameter :math:`t` in :math:`e^{- i H t}`.
n (int): The number of Trotter steps used when approximating the time-evolution operator.

Raises:
ValueError: if inputs do not have the correct format

.. UsageDetails::

The template is used inside a qnode:
Expand Down Expand Up @@ -117,38 +96,63 @@ def circuit(time):
[-0.41614684 -0.41614684]
"""

_preprocess(hamiltonian)
num_params = 3
num_wires = AnyWires
par_domain = "A"

pauli = {"Identity": "I", "PauliX": "X", "PauliY": "Y", "PauliZ": "Z"}
def __init__(self, hamiltonian, time, n, do_queue=True):

theta = []
pauli_words = []
wires = []
if not isinstance(hamiltonian, qml.vqe.vqe.Hamiltonian):
raise ValueError(
"hamiltonian must be of type pennylane.Hamiltonian, got {}".format(
type(hamiltonian).__name__
)
)

for i, term in enumerate(hamiltonian.ops):
# extract the wires that the op acts on
wire_list = [term.wires for term in hamiltonian.ops]
unique_wires = list(set(wire_list))

word = ""
super().__init__(hamiltonian, time, n, wires=unique_wires, do_queue=do_queue)

try:
if isinstance(term.name, str):
word = pauli[term.name]
def expand(self):

if isinstance(term.name, list):
word = "".join(pauli[j] for j in term.name)
hamiltonian = self.parameters[0]
time = self.parameters[1]
n = self.parameters[2]

except KeyError as error:
raise ValueError(
"hamiltonian must be written in terms of Pauli matrices, got {}".format(error)
) from error
pauli = {"Identity": "I", "PauliX": "X", "PauliY": "Y", "PauliZ": "Z"}

theta = []
pauli_words = []
wires = []

for i, term in enumerate(hamiltonian.ops):

word = ""

try:
if isinstance(term.name, str):
word = pauli[term.name]

if isinstance(term.name, list):
word = "".join(pauli[j] for j in term.name)

except KeyError as error:
raise ValueError(
"hamiltonian must be written in terms of Pauli matrices, got {}".format(error)
) from error

# Skips terms composed solely of identities
if word.count("I") != len(word):
# skips terms composed solely of identities
if word.count("I") != len(word):
theta.append((2 * time * hamiltonian.coeffs[i]) / n)
pauli_words.append(word)
wires.append(term.wires)

theta.append((2 * time * hamiltonian.coeffs[i]) / n)
pauli_words.append(word)
wires.append(term.wires)
with qml.tape.QuantumTape() as tape:

for i in range(n):
for i in range(n):
for j, term in enumerate(pauli_words):
qml.PauliRot(theta[j], term, wires=wires[j])

for j, term in enumerate(pauli_words):
qml.PauliRot(theta[j], term, wires=wires[j])
return tape
62 changes: 37 additions & 25 deletions pennylane/templates/subroutines/arbitrary_unitary.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
r"""
Contains the ``ArbitraryUnitary`` template.
Contains the ArbitraryUnitary template.
"""
import pennylane as qml
from pennylane.templates.decorator import template
from pennylane.wires import Wires
from pennylane.operation import Operation, AnyWires

_PAULIS = ["I", "X", "Y", "Z"]


def _preprocess(weights, wires):
"""Validate and pre-process inputs as follows:

* Check the shape of the weights tensor.

Args:
weights (tensor_like): trainable parameters of the template
wires (Wires): wires that template acts on
"""
shape = qml.math.shape(weights)
if shape != (4 ** len(wires) - 1,):
raise ValueError(f"Weights tensor must be of shape {(4 ** len(wires) - 1,)}; got {shape}.")


def _tuple_to_word(index_tuple):
"""Convert an integer tuple to the corresponding Pauli word.

Expand Down Expand Up @@ -82,8 +67,7 @@ def _all_pauli_words_but_identity(num_wires):
yield from (_tuple_to_word(idx_tuple) for idx_tuple in _n_k_gray_code(4, num_wires, start=1))


@template
def ArbitraryUnitary(weights, wires):
class ArbitraryUnitary(Operation):
"""Implements an arbitrary unitary on the specified wires.

An arbitrary unitary on :math:`n` wires is parametrized by :math:`4^n - 1`
Expand All @@ -104,11 +88,39 @@ def arbitrary_nearest_neighbour_interaction(weights, wires):
Args:
weights (tensor_like): The angles of the Pauli word rotations, needs to have length :math:`4^n - 1`
where :math:`n` is the number of wires the template acts upon.
wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
a Wires object.
wires (Iterable): wires that the template acts on
"""
wires = Wires(wires)
_preprocess(weights, wires)

for i, pauli_word in enumerate(_all_pauli_words_but_identity(len(wires))):
qml.PauliRot(weights[i], pauli_word, wires=wires)
num_params = 1
num_wires = AnyWires
par_domain = "A"

def __init__(self, weights, wires, do_queue=True):

shape = qml.math.shape(weights)
if shape != (4 ** len(wires) - 1,):
raise ValueError(
f"Weights tensor must be of shape {(4 ** len(wires) - 1,)}; got {shape}."
)

super().__init__(weights, wires=wires, do_queue=do_queue)

def expand(self):

weights = self.parameters[0]

with qml.tape.QuantumTape() as tape:

for i, pauli_word in enumerate(_all_pauli_words_but_identity(len(self.wires))):
qml.PauliRot(weights[i], pauli_word, wires=self.wires)

return tape

@staticmethod
def shape(n_wires):
"""Compute the expected shape of the weights tensor.

Args:
n_wires (int): number of wires that template acts on
"""
return (4 ** n_wires - 1,)
Loading