""" This module implements base circuit layouts for all the models used"""
import pennylane as qml
from pennylane import numpy as np
from typing import Tuple, List
from numbers import Number
##############
[docs]def wall_gate(
active_wires: List[int],
gate: qml.operation.Operator,
params: List[Number] = [],
index: int = 0,
samerot: bool = False,
) -> int:
"""
Apply rotations for all the wires (active_wires)
Parameters
----------
active_wires : np.ndarray
Array of the wires to apply the rotations to
gate : qml.ops.qubit.parametric_ops
Qubit operator to apply
params : list
List of parameters of the whole circuit
index : int
Starting index for this block of operators
samerot : bool
if True -> The rotations are not independet of each other but the same
Returns
-------
int
Updated index value
"""
if len(params) > 0:
if not samerot:
for i, spin in enumerate(active_wires):
gate(params[index + i], wires=int(spin))
return index + i + 1
else:
for spin in active_wires:
gate(params[index], wires=int(spin))
return index + 1
else:
for spin in active_wires:
gate(wires=int(spin))
return index
[docs]def wall_cgate_serial(
active_wires: List[int],
cgate: qml.operation.Operator,
params: List[Number] = [],
index: int = 0,
going_down: bool = True,
) -> int:
"""
Apply drop-down controlled rotations for all the wires (active_wires)
Parameters
----------
active_wires : np.ndarray
Array of the wires to apply the rotations to
cgate : Pennylane gate (parametrized or not)
Qubit controlled operator to apply
params : list
List of parameters of the whole circuit
index : int
Starting index for this block of operators
going_down : bool
if True -> top - down, if False -> down - top
Returns
-------
int
Updated index value
"""
if len(params) > 0:
for i, (spin, spin_next) in enumerate(zip(active_wires, active_wires[1:])):
if going_down:
cgate(params[index + i], wires=[int(spin), int(spin_next)])
else:
cgate(params[index + i], wires=[int(spin_next), int(spin)])
return index + i + 1
else:
for spin, spin_next in zip(active_wires, active_wires[1:]):
if going_down:
cgate(wires=[int(spin), int(spin_next)])
else:
cgate(wires=[int(spin_next), int(spin)])
return index
[docs]def wall_cgate_all(
active_wires: List[int],
cgate: qml.operation.Operator,
params: List[Number] = [],
index: int = 0,
going_down: bool = True,
) -> int:
"""
Apply controlled rotations across all the wires (active_wires)
Parameters
----------
active_wires : np.ndarray
Array of the wires to apply the rotations to
cgate : Pennylane gate (parametrized or not)
Qubit controlled operator to apply
params : list
List of parameters of the whole circuit
index : int
Starting index for this block of operators
going_down : bool
if True -> top - down, if False -> down - top
Returns
-------
int
Updated index value
"""
if len(params) > 0:
i = 0
for k, spin in enumerate(active_wires):
for spin_next in active_wires[k + 1 :]:
if going_down:
cgate(params[index + i], wires=[int(spin), int(spin_next)])
else:
cgate(params[index + i], wires=[int(spin_next), int(spin)])
i += 1
return index + i + 1
else:
for k, spin in enumerate(active_wires):
for spin_next in active_wires[k + 1 :]:
if going_down:
cgate(wires=[int(spin), int(spin_next)])
else:
cgate(wires=[int(spin_next), int(spin)])
return index
[docs]def wall_cgate_nextneighbour(
active_wires: List[int],
cgate: qml.operation.Operator,
params: List[Number] = [],
index: int = 0,
going_down: bool = True,
) -> int:
"""
Apply drop-down controlled rotations establishing next-neighbour entanglement
Parameters
----------
active_wires : np.ndarray
Array of the wires to apply the rotations to
cgate : Pennylane gate (parametrized or not)
Qubit controlled operator to apply
params : list
List of parameters of the whole circuit
index : int
Starting index for this block of operators
going_down : bool
if True -> top - down, if False -> down - top
Returns
-------
int
Updated index value
"""
if len(params) > 0:
for i, (spin, spin_next) in enumerate(zip(active_wires, active_wires[2:])):
if going_down:
cgate(params[index + i], wires=[int(spin), int(spin_next)])
else:
cgate(params[index + i], wires=[int(spin_next), int(spin)])
return index + i + 1
else:
for spin, spin_next in zip(active_wires, active_wires[2:]):
if going_down:
cgate(wires=[int(spin), int(spin_next)])
else:
cgate(wires=[int(spin_next), int(spin)])
return index
[docs]def circuit_ID9(active_wires: List[int], params: List[Number], index: int = 0) -> int:
"""
Basic block for VQE
Parameters
----------
active_wires : np.ndarray
Array of the wires to apply the rotations to
params : list
List of parameters of the whole circuit
index : int
Starting index for this block of operators
Returns
-------
int
Updated index value
"""
wall_gate(active_wires, qml.Hadamard)
wall_cgate_serial(active_wires, qml.CNOT)
index = wall_gate(active_wires, qml.RY, params, index)
return index
[docs]def pooling(
active_wires: List[int],
qmlrot_func: qml.operation.Operator,
params: List[Number],
index: int = 0,
) -> Tuple[int, List[int]]:
"""
Pooling block for the QCNN
Parameters
----------
active_wires : np.ndarray
Array of wires that are not measured during a previous pooling
qmlrot_func : function
Pennylane Gate function to apply
params: np.ndarray
Array of parameters/rotation for the circuit
index: int
Index from where to pick the elements from the params array
Returns
-------
int
Updated starting index of params array for further rotations
np.ndarray
Updated array of active wires (not measured)
"""
isodd = True
if len(active_wires) % 2 == 0:
isodd = False
for wire_meas, wire_next in zip(active_wires[0::2], active_wires[1::2]):
m_0 = qml.measure(int(wire_meas))
qml.cond(m_0 == 0, qmlrot_func)(params[index], wires=int(wire_next))
qml.cond(m_0 == 1, qmlrot_func)(params[index + 1], wires=int(wire_next))
index = index + 2
# Removing measured wires from active_wires:
active_wires = np.delete(active_wires, np.where(active_wires == wire_meas))
# ---- > If the number of wires is odd, the last wires is not pooled
# so we apply a gate
if isodd:
qmlrot_func(params[index], wires=int(active_wires[-1]))
index = index + 1
return index, active_wires
[docs]def convolution(active_wires: List[int], params: List[Number], index: int = 0) -> int:
"""
Convolution block for the QCNN
Parameters
----------
active_wires : np.ndarray
Array of wires that are not measured during a previous pooling
params: np.ndarray
Array of parameters/rotation for the circuit
index: int
Index from where to pick the elements from the params array
Returns
-------
int
Updated starting index of params array for further rotations
"""
if len(active_wires) > 1:
# Rotation Groups 2
for wire1, wire2 in zip(active_wires[0::2], active_wires[1::2]):
qml.RX(params[index], wires=int(wire1))
qml.RX(params[index], wires=int(wire2))
index += 1
if len(active_wires) % 2 != 0:
qml.RX(params[index], wires=int(active_wires[-1]))
index += 1
# CNOTS Groups 1
for wire1, wire2 in zip(active_wires[1::2], active_wires[2::2]):
qml.CNOT(wires=[int(wire1), int(wire2)])
qml.Barrier()
# Rotation Groups 1
qml.RX(params[index], wires=int(active_wires[0]))
index += 1
for wire1, wire2 in zip(active_wires[1::2], active_wires[2::2]):
qml.RX(params[index], wires=int(wire1))
qml.RX(params[index], wires=int(wire2))
index += 1
if len(active_wires) % 2 == 0:
qml.RX(params[index], wires=int(active_wires[-1]))
index += 1
# CNOTS Groups 2
for wire1, wire2 in zip(active_wires[0::2], active_wires[1::2]):
qml.CNOT(wires=[int(wire1), int(wire2)])
index = wall_gate(active_wires, qml.RY, params, index)
return index
[docs]def encoder_block(wires: List[int], wires_trash: List[int], shift: int = 0):
"""
Applies CX between a wire and a trash wire for each
wire/trashwire
Parameters
----------
wires : np.ndarray
Array of the indexes of non-trash qubits
wires_trash : np.ndarray
Array of the indexes of trash qubits (np.1dsetdiff(np.arange(N),wires))
shift : int
Shift value for connections between wires and trash wires
"""
# Connection between trash wires
trash_uniques = []
for wire in wires_trash:
wire_target = wire + 1 + shift
if wire_target > wires_trash[-1]:
wire_target = wires_trash[0] + wire_target - wires_trash[-1] - 1
if wire_target == wire:
wire_target += 1
if wire_target > wires_trash[-1]:
wire_target = wires_trash[0] + wire_target - wires_trash[-1] - 1
if not [wire_target, wire] in trash_uniques:
qml.CZ(wires=[int(wire), int(wire_target)])
trash_uniques.append([wire, wire_target])
# Connections wires -> trash_wires
for idx, wire in enumerate(wires):
trash_idx = idx + shift
while trash_idx > len(wires_trash) - 1:
trash_idx = trash_idx - len(wires_trash)
qml.CNOT(wires=[int(wire), int(wires_trash[trash_idx])])
[docs]def encoder_circuit(
wires: List[int],
wires_trash: List[int],
active_wires: List[int],
params: List[Number],
index: int = 0,
) -> int:
"""
Encoder circuit for encoder and autoencoder
Parameters
----------
wires : np.ndarray
Array of the indexes of non-trash qubits
wires_trash : np.ndarray
Array of the indexes of trash qubits (np.1dsetdiff(np.arange(N),wires))
active_wires : np.ndarray
wires U wires_trash
params: np.ndarray
Array of parameters/rotation for the circuit
index: int
Index from where to pick the elements from the params array
Returns
-------
int
Updated index value
"""
index = wall_gate(active_wires, qml.RY, params, index=index)
for shift in range(3):
encoder_block(wires, wires_trash, shift)
qml.Barrier()
index = wall_gate(active_wires, qml.RZ, params, index=index)
return index