Skip to content

Commit

Permalink
Merge branch 'master' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
actions-user committed Feb 1, 2024
2 parents 27bb36d + b950e4e commit 31ffebb
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 0 deletions.
125 changes: 125 additions & 0 deletions _static/demonstration_assets/barren_gadgets/barren_gadgets.py
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 _static/demonstration_assets/barren_gadgets/layered_ansatz.py
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)]

0 comments on commit 31ffebb

Please sign in to comment.