-
Notifications
You must be signed in to change notification settings - Fork 188
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
179 additions
and
0 deletions.
There are no files selected for viewing
125 changes: 125 additions & 0 deletions
125
_static/demonstration_assets/barren_gadgets/barren_gadgets.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import pennylane as qml | ||
from pennylane import numpy as np | ||
|
||
|
||
class PerturbativeGadgets: | ||
""" Class to generate the gadget Hamiltonian corresponding to a given | ||
computational hamiltonian according to the gadget construction derived | ||
by Faehrmann & Cichy | ||
Args: | ||
perturbation_factor (float) : parameter controlling the magnitude of the | ||
perturbation (aa pre-factor to \lambda_max) | ||
""" | ||
def __init__(self, perturbation_factor=1): | ||
self.perturbation_factor = perturbation_factor | ||
|
||
def gadgetize(self, Hamiltonian, target_locality=3): | ||
"""Generation of the perturbative gadget equivalent of the given | ||
Hamiltonian according to the proceedure in Cichy, Fährmann et al. | ||
Args: | ||
Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose | ||
into more local terms | ||
target_locality (int > 2) : desired locality of the resulting | ||
gadget Hamiltonian | ||
Returns: | ||
Hgad (qml.Hamiltonian) : gadget Hamiltonian | ||
""" | ||
# checking for unaccounted for situations | ||
self.run_checks(Hamiltonian, target_locality) | ||
computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) | ||
|
||
# total qubit count, updated progressively when adding ancillaries | ||
total_qubits = computational_qubits | ||
#TODO: check proper convergence guarantee | ||
gap = 1 | ||
perturbation_norm = np.sum(np.abs(Hamiltonian.coeffs)) \ | ||
+ computational_terms * (computational_locality - 1) | ||
lambda_max = gap / (4 * perturbation_norm) | ||
l = self.perturbation_factor * lambda_max | ||
sign_correction = (-1)**(computational_locality % 2 + 1) | ||
# creating the gadget Hamiltonian | ||
coeffs_anc = [] | ||
coeffs_pert = [] | ||
obs_anc = [] | ||
obs_pert = [] | ||
ancillary_register_size = int(computational_locality / (target_locality - 2)) | ||
for str_count, string in enumerate(Hamiltonian.ops): | ||
previous_total = total_qubits | ||
total_qubits += ancillary_register_size | ||
# Generating the ancillary part | ||
for anc_q in range(previous_total, total_qubits): | ||
coeffs_anc += [0.5, -0.5] | ||
obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] | ||
# Generating the perturbative part | ||
for anc_q in range(ancillary_register_size): | ||
term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) | ||
term = qml.operation.Tensor(term, *string.non_identity_obs[ | ||
(target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) | ||
obs_pert.append(term) | ||
coeffs_pert += [l * sign_correction * Hamiltonian.coeffs[str_count]] \ | ||
+ [l] * (ancillary_register_size - 1) | ||
coeffs = coeffs_anc + coeffs_pert | ||
obs = obs_anc + obs_pert | ||
Hgad = qml.Hamiltonian(coeffs, obs) | ||
return Hgad | ||
|
||
def get_params(self, Hamiltonian): | ||
""" retrieving the parameters n, k and r from the given Hamiltonian | ||
Args: | ||
Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the | ||
relevant parameters | ||
Returns: | ||
computational_qubits (int) : total number of qubits acted upon by | ||
the Hamiltonian | ||
computational_locality (int) : maximum number of qubits acted upon | ||
by a single term of the Hamiltonian | ||
computational_terms (int) : number of terms in the sum | ||
composing the Hamiltonian | ||
""" | ||
# checking how many qubits the Hamiltonian acts on | ||
computational_qubits = len(Hamiltonian.wires) | ||
# getting the number of terms in the Hamiltonian | ||
computational_terms = len(Hamiltonian.ops) | ||
# getting the locality, assuming all terms have the same | ||
computational_locality = max([len(Hamiltonian.ops[s].non_identity_obs) | ||
for s in range(computational_terms)]) | ||
return computational_qubits, computational_locality, computational_terms | ||
|
||
def run_checks(self, Hamiltonian, target_locality): | ||
""" method to check a few conditions for the correct application of | ||
the methods | ||
Args: | ||
Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest | ||
target_locality (int > 2) : desired locality of the resulting | ||
gadget Hamiltonian | ||
Returns: | ||
None | ||
""" | ||
computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) | ||
computational_qubits = len(Hamiltonian.wires) | ||
if computational_qubits != Hamiltonian.wires[-1] + 1: | ||
raise Exception('The studied computational Hamiltonian is not acting on ' + | ||
'the first {} qubits. '.format(computational_qubits) + | ||
'Decomposition not implemented for this case') | ||
# Check for same string lengths | ||
localities=[] | ||
for string in Hamiltonian.ops: | ||
localities.append(len(string.non_identity_obs)) | ||
if len(np.unique(localities)) > 1: | ||
raise Exception('The given Hamiltonian has terms with different locality.' + | ||
' Gadgetization not implemented for this case') | ||
# validity of the target locality given the computational locality | ||
if target_locality < 3: | ||
raise Exception('The target locality can not be smaller than 3') | ||
ancillary_register_size = computational_locality / (target_locality - 2) | ||
if int(ancillary_register_size) != ancillary_register_size: | ||
raise Exception('The locality of the Hamiltonian and the target' + | ||
' locality are not compatible. The gadgetization' + | ||
' with "unfull" ancillary registers is not' + | ||
' supported yet. Please choose such that the' + | ||
' computational locality is divisible by the' + | ||
' target locality - 2') | ||
|
||
|
||
|
54 changes: 54 additions & 0 deletions
54
_static/demonstration_assets/barren_gadgets/layered_ansatz.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import pennylane as qml | ||
from pennylane import numpy as np | ||
|
||
""" Based on the SimplifiedTwoDesign template from pennylane | ||
https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html | ||
as proposed in `Cerezo et al. (2021) <https://doi.org/10.1038/s41467-021-21728-w>`_. | ||
but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. | ||
""" | ||
|
||
def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): | ||
|
||
n_layers = qml.math.shape(weights)[0] | ||
op_list = [] | ||
|
||
# initial rotations | ||
for i in range(len(wires)): | ||
op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) | ||
|
||
# generating the rotation sequence | ||
if gate_sequence is None: | ||
gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) | ||
|
||
# repeated layers | ||
for layer in range(n_layers): | ||
|
||
# even layer of entanglers | ||
even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] | ||
for i, wire_pair in enumerate(even_wires): | ||
op_list.append(qml.CZ(wires=wire_pair)) | ||
op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) | ||
op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) | ||
# op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) | ||
# op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) | ||
|
||
# odd layer of entanglers | ||
odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] | ||
for i, wire_pair in enumerate(odd_wires): | ||
op_list.append(qml.CZ(wires=wire_pair)) | ||
op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) | ||
op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) | ||
# op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) | ||
# op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) | ||
|
||
return op_list | ||
|
||
def generate_random_gate_sequence(shape): | ||
gate_set = [qml.RX, qml.RY, qml.RZ] | ||
return np.random.choice(gate_set, size=shape) | ||
|
||
def get_parameter_shape(n_layers, n_wires): | ||
if n_wires == 1: | ||
return [(n_wires,), (n_layers,)] | ||
return [(n_wires,), (n_layers, n_wires - 1, 2)] | ||
|