Source code for pennylane.templates.layers.gate_fabric
# 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 quantum-number-preserving GateFabric template.
"""
# pylint: disable-msg=too-many-branches,too-many-arguments,protected-access
import numpy as np
import pennylane as qml
from pennylane.operation import AnyWires, Operation
[docs]class GateFabric(Operation):
r"""Implements a local, expressive, and quantum-number-preserving ansatz proposed by
`Anselmetti et al. (2021) <https://doi.org/10.1088/1367-2630/ac2cb3>`_.
This template prepares the :math:`N`-qubit trial state by applying :math:`D` layers of gate-fabric blocks
:math:`\hat{U}_{GF}(\vec{\theta},\vec{\phi})` to the Hartree-Fock state in the Jordan-Wigner basis
.. math::
\vert \Psi(\vec{\theta},\vec{\phi})\rangle =
\hat{U}_{GF}^{(D)}(\vec{\theta}_{D},\vec{\phi}_{D}) \ldots
\hat{U}_{GF}^{(2)}(\vec{\theta}_{2},\vec{\phi}_{2})
\hat{U}_{GF}^{(1)}(\vec{\theta}_{1},\vec{\phi}_{1}) \vert HF \rangle,
where each of the gate fabric blocks :math:`\hat{U}_{GF}(\vec{\theta},\vec{\phi})` is comprised of two-parameter four-qubit
gates :math:`\hat{Q}(\theta, \phi)` that act on four nearest-neighbour qubits. The circuit implementing a
single layer of the gate fabric block for :math:`N = 8` is shown in the figure below:
.. figure:: ../../_static/templates/layers/gate_fabric_layer.png
:align: center
:width: 100%
:target: javascript:void(0);
The gate element :math:`\hat{Q}(\theta, \phi)` (`Anselmetti et al. (2021) <https://doi.org/10.1088/1367-2630/ac2cb3>`_)
is composed of a four-qubit spin-adapted spatial orbital rotation gate, which is implemented by the :class:`~.OrbitalRotation()`
operation and a four-qubit diagonal pair-exchange gate, which is equivalent to the :class:`~.DoubleExcitation()`
operation. In addition to these two gates, the gate element :math:`\hat{Q}(\theta, \phi)` can also include an optional
constant :math:`\hat{\Pi} \in \{\hat{I}, \text{OrbitalRotation}(\pi)\}` gate.
.. figure:: ../../_static/templates/layers/q_gate_decompositon.png
:align: center
:width: 75%
:target: javascript:void(0);
|
The four-qubit :class:`~.DoubleExcitation()` and :class:`~.OrbitalRotation()` gates given here are equivalent to the
:math:`\text{QNP}_{PX}(\theta)` and :math:`\text{QNP}_{OR}(\phi)` gates presented in
`Anselmetti et al. (2021) <https://doi.org/10.1088/1367-2630/ac2cb3>`_,
respectively. Moreover, regardless of the choice of :math:`\hat{\Pi}`, this gate fabric will exactly preserve the number of particles
and total spin of the state.
Args:
weights (tensor_like): Array of weights of shape ``(D, L, 2)``\,
where ``D`` is the number of gate fabric layers and ``L = N/2-1``
is the number of :math:`\hat{Q}(\theta, \phi)` gates per layer with N being the total number of qubits.
wires (Iterable): wires that the template acts on.
init_state (tensor_like): iterable of shape ``(len(wires),)``\, representing the input Hartree-Fock state
in the Jordan-Wigner representation.
include_pi (boolean): If True, the optional constant :math:`\hat{\Pi}` gate is set to :math:`\text{OrbitalRotation}(\pi)`.
Default value is :math:`\hat{I}`.
.. details::
:title: Usage Details
#. The number of wires :math:`N` has to be equal to the number of
spin-orbitals included in the active space, and should be even.
#. The number of trainable parameters scales linearly with the number of layers as
:math:`2 D (N/2-1)`.
An example of how to use this template is shown below:
.. code-block:: python
import pennylane as qml
from pennylane import numpy as np
# Build the electronic Hamiltonian
symbols = ["H", "H"]
coordinates = np.array([0.0, 0.0, -0.6614, 0.0, 0.0, 0.6614])
H, qubits = qml.qchem.molecular_hamiltonian(symbols, coordinates)
# Define the Hartree-Fock state
electrons = 2
ref_state = qml.qchem.hf_state(electrons, qubits)
# Define the device
dev = qml.device('default.qubit', wires=qubits)
# Define the ansatz
@qml.qnode(dev)
def ansatz(weights):
qml.GateFabric(weights, wires=[0,1,2,3],
init_state=ref_state, include_pi=True)
return qml.expval(H)
# Get the shape of the weights for this template
layers = 2
shape = qml.GateFabric.shape(n_layers=layers, n_wires=qubits)
# Initialize the weight tensors
np.random.seed(42)
weights = np.random.random(size=shape)
# Define the optimizer
opt = qml.GradientDescentOptimizer(stepsize=0.4)
# Store the values of the cost function
energy = [ansatz(weights)]
# Store the values of the circuit weights
angle = [weights]
max_iterations = 100
conv_tol = 1e-06
for n in range(max_iterations):
weights, prev_energy = opt.step_and_cost(ansatz, weights)
energy.append(ansatz(weights))
angle.append(weights)
conv = np.abs(energy[-1] - prev_energy)
if n % 2 == 0:
print(f"Step = {n}, Energy = {energy[-1]:.8f} Ha")
if conv <= conv_tol:
break
print("\n" f"Final value of the ground-state energy = {energy[-1]:.8f} Ha")
print("\n" f"Optimal value of the circuit parameters = {angle[-1]}")
.. code-block:: none
Step = 0, Energy = -0.87007254 Ha
Step = 2, Energy = -1.13107530 Ha
Step = 4, Energy = -1.13611971 Ha
Step = 6, Energy = -1.13618810 Ha
Final value of the ground-state energy = -1.13618903 Ha
Optimal value of the circuit parameters = [[[ 0.60328427 0.41850407]]
[[ 0.85581129 -0.24522642]]]
**Parameter shape**
The shape of the weights argument can be computed by the static method
:meth:`~.GateFabric.shape` and used when creating randomly
initialised weight tensors:
.. code-block:: python
shape = GateFabric.shape(n_layers=2, n_wires=4)
weights = np.random.random(size=shape)
>>> weights.shape
(2, 1, 2)
"""
num_wires = AnyWires
grad_method = None
def __init__(self, weights, wires, init_state, include_pi=False, id=None):
if len(wires) < 4:
raise ValueError(
f"This template requires the number of qubits to be greater than four; got wires {wires}"
)
if len(wires) % 2:
raise ValueError(
f"This template requires an even number of qubits; got {len(wires)} wires"
)
shape = qml.math.shape(weights)
if len(shape) != 3:
raise ValueError(f"Weights tensor must be 3-dimensional; got shape {shape}")
len_wire_pattern = int((len(wires) / 2) - 1)
if shape[1] != len_wire_pattern:
raise ValueError(
f"Weights tensor must have second dimension of length {len_wire_pattern}; got {shape[1]}"
)
if shape[2] != 2:
raise ValueError(
f"Weights tensor must have third dimension of length 2; got {shape[2]}"
)
self._hyperparameters = {
"init_state": tuple(init_state),
"include_pi": include_pi,
}
super().__init__(weights, wires=wires, id=id)
@property
def num_params(self):
return 1
[docs] @staticmethod
def compute_decomposition(
weights, wires, init_state, include_pi
): # pylint: disable=arguments-differ
r"""Representation of the operator as a product of other operators.
.. math:: O = O_1 O_2 \dots O_n.
.. seealso:: :meth:`~.GateFabric.decomposition`.
Args:
weights (tensor_like): Array of weights of shape ``(D, L, 2)``,
where ``D`` is the number of gate fabric layers and ``L = N/2-1``
is the number of :math:`\hat{Q}(\theta, \phi)` gates per layer with N being the total number of qubits.
wires (Any or Iterable[Any]): wires that the operator acts on.
init_state (tensor_like): iterable of shape ``(len(wires),)``\, representing the input Hartree-Fock state
in the Jordan-Wigner representation.
include_pi (boolean): If ``True``, the optional constant :math:`\hat{\Pi}` gate is set to :math:`\text{OrbitalRotation}(\pi)`.
Default value is :math:`\hat{I}`.
Returns:
list[.Operator]: decomposition of the operator
**Example**
>>> weights = torch.tensor([[[0.3, 1.]]])
>>> qml.GateFabric.compute_decomposition(weights, wires=["a", "b", "c", "d"], init_state=[0, 1, 0, 1], include_pi=False)
[BasisEmbedding(wires=['a', 'b', 'c', 'd']),
DoubleExcitation(tensor(0.3000), wires=['a', 'b', 'c', 'd']),
OrbitalRotation(tensor(1.), wires=['a', 'b', 'c', 'd'])]
"""
op_list = []
n_layers = qml.math.shape(weights)[0]
wire_pattern = [
wires[i : i + 4] for i in range(0, len(wires), 4) if len(wires[i : i + 4]) == 4
]
if len(wires) > 4:
wire_pattern += [
wires[i : i + 4] for i in range(2, len(wires), 4) if len(wires[i : i + 4]) == 4
]
op_list.append(qml.BasisEmbedding(init_state, wires=wires))
for layer in range(n_layers):
for idx, wires_ in enumerate(wire_pattern):
if include_pi:
op_list.append(qml.OrbitalRotation(np.pi, wires=wires_))
op_list.append(qml.DoubleExcitation(weights[layer][idx][0], wires=wires_))
op_list.append(qml.OrbitalRotation(weights[layer][idx][1], wires=wires_))
return op_list
[docs] @staticmethod
def shape(n_layers, n_wires):
r"""Returns the shape of the weight tensor required for this template.
Args:
n_layers (int): number of layers
n_wires (int): number of qubits
Returns:
tuple[int]: shape
"""
if n_wires < 4:
raise ValueError(
f"This template requires the number of qubits to be at least four; got 'n_wires' = {n_wires}"
)
if n_wires % 2:
raise ValueError(
f"This template requires an even number of qubits; got 'n_wires' = {n_wires}"
)
return n_layers, n_wires // 2 - 1, 2
_modules/pennylane/templates/layers/gate_fabric
Download Python script
Download Notebook
View on GitHub