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

Add canonical 2-complete linear swap network #3447

Merged
merged 31 commits into from Dec 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2742b01
fermionic swap rotation
obliviateandsurrender Sep 20, 2022
2023ff7
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
obliviateandsurrender Nov 17, 2022
468b245
add changelog
obliviateandsurrender Nov 17, 2022
935bf3a
make codecov happy
obliviateandsurrender Nov 17, 2022
c1ad935
Merge branch 'master' into fswap-op
obliviateandsurrender Nov 17, 2022
4174854
empty commit for CI
obliviateandsurrender Nov 17, 2022
1f1bbb2
Merge branch 'fswap-op' of https://github.com/PennyLaneAI/pennylane i…
obliviateandsurrender Nov 17, 2022
76d4346
make sphinx happy
obliviateandsurrender Nov 17, 2022
554aeca
fix typo in changelog
obliviateandsurrender Nov 17, 2022
a4df719
Merge branch 'master' into fswap-op
obliviateandsurrender Nov 18, 2022
7c71024
add suggestions
obliviateandsurrender Nov 22, 2022
6c737c6
Merge branch 'fswap-op' of https://github.com/PennyLaneAI/pennylane i…
obliviateandsurrender Nov 22, 2022
1b4cf92
Merge branch 'master' into fswap-op
obliviateandsurrender Nov 22, 2022
c221d24
add review suggestions
obliviateandsurrender Nov 29, 2022
639c776
use `cast_like`
obliviateandsurrender Nov 29, 2022
8cd39ec
Merge branch 'master' into fswap-op
obliviateandsurrender Nov 29, 2022
595baef
add `TwoLocalSwapNetwork`
obliviateandsurrender Nov 30, 2022
0f8f9bb
fix black
obliviateandsurrender Nov 30, 2022
3dcfcb0
add tests and documentations
obliviateandsurrender Nov 30, 2022
a702771
fix black
obliviateandsurrender Nov 30, 2022
42b0a3b
black again
obliviateandsurrender Nov 30, 2022
487898c
make codecov happy
obliviateandsurrender Nov 30, 2022
0638b66
happy black
obliviateandsurrender Nov 30, 2022
f22e9ca
Merge branch 'master' into two-local-swap-network
obliviateandsurrender Nov 30, 2022
574f032
add review suggestions
obliviateandsurrender Dec 1, 2022
3100fa1
add suggestions
obliviateandsurrender Dec 13, 2022
cad563a
Merge branch 'master' into two-local-swap-network
obliviateandsurrender Dec 13, 2022
568769d
Merge branch 'master' into two-local-swap-network
obliviateandsurrender Dec 15, 2022
8425064
add review suggestions
obliviateandsurrender Dec 20, 2022
ac55b87
Merge branch 'master' into two-local-swap-network
obliviateandsurrender Dec 20, 2022
29f160b
Merge branch 'master' into two-local-swap-network
obliviateandsurrender Dec 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file added doc/_static/templates/swap_networks/ccl2.jpeg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions doc/introduction/templates.rst
Expand Up @@ -174,6 +174,20 @@ can be broadcast with the shape and connectivity of tensor networks.

<div style='clear:both'></div>

Swap networks
--------------

Swap network templates perform qubit routing with linear cost, providing a quadratic advantage in
circuit depth for carrying out all pair-wise interactions between qubits.
obliviateandsurrender marked this conversation as resolved.
Show resolved Hide resolved

.. gallery-item::
:description: :doc:`Canonical 2-Complete Linear Swap Network <../code/api/pennylane.TwoLocalSwapNetwork>`
:figure: _static/templates/swap_networks/ccl2.jpeg
obliviateandsurrender marked this conversation as resolved.
Show resolved Hide resolved

.. raw:: html

<div style='clear:both'></div>

.. _intro_ref_temp_qchem:

Other subroutines
Expand Down
31 changes: 28 additions & 3 deletions doc/releases/changelog-dev.md
Expand Up @@ -4,6 +4,33 @@

<h3>New features since last release</h3>

* Added a new template that implements a canonical 2-complete linear (2-CCL) swap network
described in [arXiv:1905.05118](https://arxiv.org/abs/1905.05118).
[(#3447)](https://github.com/PennyLaneAI/pennylane/pull/3447)

```python3
dev = qml.device('default.qubit', wires=5)
weights = np.random.random(size=TwoLocalSwapNetwork.shape(len(dev.wires)))
obliviateandsurrender marked this conversation as resolved.
Show resolved Hide resolved
acquaintances = lambda index, wires, param: (qml.CRY(param, wires=index)
if np.abs(wires[0]-wires[1]) else qml.CRZ(param, wires=index))
@qml.qnode(dev)
def swap_network_circuit():
qml.templates.TwoLocalSwapNetwork(dev.wires, acquaintances, weights, fermionic=False)
return qml.state()
```

```pycon
>>> print(weights)
tensor([0.20308242, 0.91906199, 0.67988804, 0.81290256, 0.08708985,
0.81860084, 0.34448344, 0.05655892, 0.61781612, 0.51829044], requires_grad=True)
>>> qml.draw(swap_network_circuit, expansion_strategy = 'device')()
0: ─╭●────────╭SWAP─────────────────╭●────────╭SWAP─────────────────╭●────────╭SWAP─┤ State
1: ─╰RY(0.20)─╰SWAP─╭●────────╭SWAP─╰RY(0.09)─╰SWAP─╭●────────╭SWAP─╰RY(0.62)─╰SWAP─┤ State
2: ─╭●────────╭SWAP─╰RY(0.68)─╰SWAP─╭●────────╭SWAP─╰RY(0.34)─╰SWAP─╭●────────╭SWAP─┤ State
3: ─╰RY(0.92)─╰SWAP─╭●────────╭SWAP─╰RY(0.82)─╰SWAP─╭●────────╭SWAP─╰RY(0.52)─╰SWAP─┤ State
4: ─────────────────╰RY(0.81)─╰SWAP─────────────────╰RY(0.06)─╰SWAP─────────────────┤ State
```

* The JAX-JIT interface now supports higher-order gradient computation with the new return types system.
[(#3498)](https://github.com/PennyLaneAI/pennylane/pull/3498)

Expand Down Expand Up @@ -94,12 +121,10 @@
* Child classes of `QuantumScript` now return their own type when using `SomeChildClass.from_queue`.
[(#3501)](https://github.com/PennyLaneAI/pennylane/pull/3501)

<h3>Contributors</h3>

* Fixed typo in calculation error message and comment in operation.py
[(#3536)](https://github.com/PennyLaneAI/pennylane/pull/3536)

<h3>Contributors</h3>
<h3>Contributors</h3>

This release contains contributions from (in alphabetical order):

Expand Down
1 change: 1 addition & 0 deletions pennylane/__init__.py
Expand Up @@ -69,6 +69,7 @@
from pennylane.templates.embeddings import *
from pennylane.templates.layers import *
from pennylane.templates.tensornetworks import *
from pennylane.templates.swapnetworks import *
from pennylane.templates.state_preparations import *
from pennylane.templates.subroutines import *
from pennylane import qaoa
Expand Down
1 change: 1 addition & 0 deletions pennylane/templates/__init__.py
Expand Up @@ -22,3 +22,4 @@
from .subroutines import *
from .state_preparations import *
from .tensornetworks import *
from .swapnetworks import *
19 changes: 19 additions & 0 deletions pennylane/templates/swapnetworks/__init__.py
@@ -0,0 +1,19 @@
# Copyright 2018-2021 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License 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.
"""
Swap network templates perform qubit routing with linear cost, providing a quadratic advantage in
circuit depth for carrying out all pair-wise interactions between qubits.
"""

from .ccl2 import TwoLocalSwapNetwork
224 changes: 224 additions & 0 deletions pennylane/templates/swapnetworks/ccl2.py
@@ -0,0 +1,224 @@
# Copyright 2018-2021 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License 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.
r"""
Contains the TwoLocalSwapNetwork template.
"""

import warnings
import pennylane as qml
import pennylane.numpy as np
from pennylane.operation import Operation, AnyWires
from pennylane.ops import SWAP, FermionicSWAP


class TwoLocalSwapNetwork(Operation):
r"""Apply two-local gate operations using a canonical 2-complete linear (2-CCL) swap network.

Args:
wires (Iterable or Wires): ordered sequence of wires on which the swap network acts
acquaintances (Callable): callable `func(index, wires, param=None, **kwargs)` that returns a two-local operation applied on a pair of logical wires specified by `index` currently stored in physical wires provided by `wires` before they are swapped apart. Parameters for the operation are specified using `param`, and any additional keyword arguments for the callable should be provided using the ``kwargs`` separately
weights (tensor): weight tensor for the parameterized acquaintances of length :math:`N \times (N - 1) / 2`, where `N` is the length of `wires`
fermionic (bool): If ``True``, qubits are realized as fermionic modes and :class:`~.pennylane.FermionicSWAP` with :math:`\phi=\pi` is used instead of :class:`~.pennylane.SWAP`
shift (bool): If ``True``, odd-numbered layers begins from the second qubit instead of first one
**kwargs: additional keyword arguments for `acquaintances`

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

**Example**

.. code-block:: python

>>> import pennylane as qml
>>> dev = qml.device('default.qubit', wires=5)
>>> acquaintances = lambda index, wires, param=None: qml.CNOT(index)
>>> @qml.qnode(dev)
... def swap_network_circuit():
... qml.templates.TwoLocalSwapNetwork(dev.wires, acquaintances, fermionic=True, shift=False)
... return qml.state()
>>> qml.draw(swap_network_circuit, expansion_strategy = 'device')()
0: ─╭●─╭fSWAP(3.14)─────────────────╭●─╭fSWAP(3.14)─────────────────╭●─╭fSWAP(3.14)─┤ State
1: ─╰X─╰fSWAP(3.14)─╭●─╭fSWAP(3.14)─╰X─╰fSWAP(3.14)─╭●─╭fSWAP(3.14)─╰X─╰fSWAP(3.14)─┤ State
2: ─╭●─╭fSWAP(3.14)─╰X─╰fSWAP(3.14)─╭●─╭fSWAP(3.14)─╰X─╰fSWAP(3.14)─╭●─╭fSWAP(3.14)─┤ State
3: ─╰X─╰fSWAP(3.14)─╭●─╭fSWAP(3.14)─╰X─╰fSWAP(3.14)─╭●─╭fSWAP(3.14)─╰X─╰fSWAP(3.14)─┤ State
4: ─────────────────╰X─╰fSWAP(3.14)─────────────────╰X─╰fSWAP(3.14)─────────────────┤ State

.. details::
:title: Usage Details

More complex acquaintances can be utilized with the template. For example:

.. code-block:: python

>>> dev = qml.device('default.qubit', wires=5)
>>> weights = np.random.random(size=qml.TwoLocalSwapNetwork.shape(len(dev.wires)))
>>> print(weights)
tensor([0.20308242, 0.91906199, 0.67988804, 0.81290256, 0.08708985,
0.81860084, 0.34448344, 0.05655892, 0.61781612, 0.51829044], requires_grad=True)
>>> acquaintances = lambda index, wires, param: (qml.CRY(param, wires=index)
... if np.abs(wires[0]-wires[1]) else qml.CRZ(param, wires=index))
>>> @qml.qnode(dev)
... def swap_network_circuit():
... qml.templates.TwoLocalSwapNetwork(dev.wires, acquaintances, weights, fermionic=False)
... return qml.state()
>>> qml.draw(swap_network_circuit, expansion_strategy = 'device')()
0: ─╭●────────╭SWAP─────────────────╭●────────╭SWAP─────────────────╭●────────╭SWAP─┤ State
1: ─╰RY(0.20)─╰SWAP─╭●────────╭SWAP─╰RY(0.09)─╰SWAP─╭●────────╭SWAP─╰RY(0.62)─╰SWAP─┤ State
2: ─╭●────────╭SWAP─╰RY(0.68)─╰SWAP─╭●────────╭SWAP─╰RY(0.34)─╰SWAP─╭●────────╭SWAP─┤ State
3: ─╰RY(0.92)─╰SWAP─╭●────────╭SWAP─╰RY(0.82)─╰SWAP─╭●────────╭SWAP─╰RY(0.52)─╰SWAP─┤ State
4: ─────────────────╰RY(0.81)─╰SWAP─────────────────╰RY(0.06)─╰SWAP─────────────────┤ State

"""

num_wires = AnyWires
grad_method = None

def __init__(
self,
wires,
acquaintances=None,
weights=None,
fermionic=True,
shift=False,
do_queue=True,
id=None,
**kwargs,
): # pylint: disable=too-many-arguments

if len(wires) < 2:
raise ValueError(f"TwoLocalSwapNetwork requires at least 2 wires, got {len(wires)}")

if not callable(acquaintances) and acquaintances is not None:
raise ValueError(
f"Acquaintances must either be a callable or None, got {acquaintances}"
)

if weights is not None and acquaintances is None:
warnings.warn("Weights are being provided without acquaintances")

if (
weights is not None
and acquaintances is not None
and qml.math.shape(weights)[0] != int(len(wires) * (len(wires) - 1) / 2)
):
raise ValueError(
f"Weight tensor must be of length {int(len(wires) * (len(wires) - 1) / 2)}, \
got {qml.math.shape(weights)[0]}"
)

self._weights = weights
self._hyperparameters = {
"acquaintances": acquaintances,
"fermionic": fermionic,
"shift": shift,
**kwargs,
}

if acquaintances is not None and self._weights is not None:
super().__init__(self._weights, wires=wires, do_queue=do_queue, id=id)
else:
super().__init__(wires=wires, do_queue=do_queue, id=id)

@property
def num_params(self):
return (
1
if self._hyperparameters["acquaintances"] is not None and self._weights is not None
else 0
)

@staticmethod
def compute_decomposition(
weights=None, wires=None, acquaintances=None, fermionic=True, shift=False, **kwargs
): # pylint: disable=arguments-differ too-many-arguments
r"""Representation of the operator as a product of other operators.

.. math:: O = O_1 O_2 \dots O_n.

.. seealso:: :meth:`~.TwoLocalSwapNetwork.decomposition`.

Args:
weights (tensor): weight tensor for the parameterized acquaintances of length :math:`N \times (N - 1) / 2`, where `N` is the length of `wires`
wires (Iterable or Wires): ordered sequence of wires on which the swap network acts
acquaintances (Callable): callable `func(index, wires, param=None, **kwargs)` that returns a two-local operation, which is applied on a pair of logical wires specified by `index`. This corresponds to applying the operation on physical wires provided by `wires` before any SWAP gates occurred. Parameters for the operation are specified using `param`, and any additional keyword arguments for the callable should be provided using the ``kwargs`` separately
fermionic (bool): If ``True``, qubits are realized as fermionic modes and :class:`~.pennylane.FermionicSWAP` with :math:`\phi=\pi` is used instead of :class:`~.pennylane.SWAP`
shift (bool): If ``True``, odd-numbered layers begins from the second qubit instead of first one
**kwargs: additional keyword arguments for `acquaintances`

Returns:
list[.Operator]: decomposition of the operator

**Example**

.. code-block:: python

>>> import pennylane as qml
>>> dev = qml.device('default.qubit', wires=5)
>>> acquaintances = lambda index, wires, param=None: qml.CNOT(index)
>>> qml.TwoLocalSwapNetwork.compute_decomposition(wires=dev.wires,
... acquaintances=acquaintances, fermionic=True, shift=False)
[CNOT(wires=[0, 1]), FermionicSWAP(3.141592653589793, wires=[0, 1]),
CNOT(wires=[2, 3]), FermionicSWAP(3.141592653589793, wires=[2, 3]),
CNOT(wires=[1, 2]), FermionicSWAP(3.141592653589793, wires=[1, 2]),
CNOT(wires=[3, 4]), FermionicSWAP(3.141592653589793, wires=[3, 4]),
CNOT(wires=[0, 1]), FermionicSWAP(3.141592653589793, wires=[0, 1]),
CNOT(wires=[2, 3]), FermionicSWAP(3.141592653589793, wires=[2, 3]),
CNOT(wires=[1, 2]), FermionicSWAP(3.141592653589793, wires=[1, 2]),
CNOT(wires=[3, 4]), FermionicSWAP(3.141592653589793, wires=[3, 4]),
CNOT(wires=[0, 1]), FermionicSWAP(3.141592653589793, wires=[0, 1]),
CNOT(wires=[2, 3]), FermionicSWAP(3.141592653589793, wires=[2, 3])]
"""

if wires is None or len(wires) < 2:
raise ValueError(f"TwoLocalSwapNetwork requires at least 2 wires, got {wires}")

op_list = []

wire_order = list(wires).copy()
itrweights = iter([]) if weights is None or acquaintances is None else iter(weights)
for layer in range(len(wires)):
qubit_pairs = [[i, i + 1] for i in range((layer + shift) % 2, len(wires) - 1, 2)]
for i, j in qubit_pairs:
qb1, qb2 = wire_order[i], wire_order[j]
if acquaintances is not None:
op_list.append(
acquaintances(
index=[wires[i], wires[j]],
wires=[qb1, qb2],
param=next(itrweights, None),
**kwargs,
)
)
op_list.append(
SWAP(wires=[wires[i], wires[j]])
if not fermionic
else FermionicSWAP(np.pi, wires=[wires[i], wires[j]])
)
wire_order[i], wire_order[j] = qb2, qb1

return op_list

@staticmethod
def shape(n_wires):
r"""Returns the shape of the weight tensor required for using parameterized acquaintances in the template.
Args:
n_wires (int): Number of qubits
Returns:
tuple[int]: shape
"""

if n_wires < 2:
raise ValueError(f"TwoLocalSwapNetwork requires at least 2 wires, got {n_wires}")

return (int(n_wires * (n_wires - 1) * 0.5),)