Hi,
I have a parametrized quantum circuit (PQC) where I measure probs()
at the end of it, and I want to get it’s derivatives with respect to the input vector (and not to the parameters).
I tried using jacobian(circuit)(input_points, params)
, but it has the following problem:
For each probability, I need to get the derivative with respect to a single or several points in the input vector, this means that using the jacobian function (or especially jacobian(jacobian())
) is a very costly computation where I don’t need most of it.
Is it possible to specify the probabilities and the corresponding points I want to differentiate with respect to in order to get like a jacobian matrix but sparse?
another problem I have is probably due to my own implementation, but when I optimize I don’t get the results I’m expecting:
In my QC the first step is to use AmplitudeEmbedding(input_points)
. I refer to the input as points on a line (where each state’s amplitude is a location of the point number <#state>). The output oof the circuit is supposed to mimic a function by updating the gates’ parameters, where each probability is an amp encoding of a field in the point (similar to the input).
As I see it, if I differentiate the each output probability from the QC according to the corresponding input_point
, it should be similar to taking spatial derivatives, right? or did I not understand it?
Thanks a lot!
Code:
# Pennylane
import pennylane as qml
from pennylane import numpy as pnp
import matplotlib.pyplot as plt
seed = 42
n_qubits = 5
dev = qml.device('default.qubit')
range_qubits = range(n_qubits)
n_layers = 5
n_input_points = 2 ** n_qubits
steps = 50
stepsize = 0.01
opt = qml.AdamOptimizer(stepsize=stepsize)
n_parameters_per_layer = 2 * (n_qubits - 1)
n_total_parameters = n_layers * n_parameters_per_layer + n_qubits
params = pnp.random.rand(n_total_parameters, requires_grad=True)
def init_input_points():
"""
initializing the collocation points.
"""
x = pnp.linspace(0, pnp.pi, n_input_points) # remove the requires_grad kwarg
xnorm = x / pnp.linalg.norm(x)
xnorm = xnorm + 1e-10 # to avoid division by zero
xnorm = xnorm / pnp.linalg.norm(xnorm)
plt.plot(xnorm)
return xnorm, x
input_points, x = init_input_points()
input_points.requires_grad = True
x.requires_grad = False
cos = pnp.cos(x) # I want the answer to be a sine, so it's derivative is a cosine
cos_norm = cos / pnp.linalg.norm(cos)
def fixedStructureAnsatz(params):
"""
create a hardware efficient, fixed structure parameterized ansatz
on qubits specified by their indices 'qubitindices'.
Args:
params (array): the given the angles for the quantum gates.
"""
n_sublayers = 2
j = 0
# Base layer: apply RZ on all qubits
for q in range_qubits:
qml.RZ(params[j], q)
j += 1
ctrlq = []
trgtq = []
ctrlq.append(range_qubits[:-1:2])
ctrlq.append(range_qubits[1:-1:2])
trgtq.append(range_qubits[1::2])
trgtq.append(range_qubits[2::2])
for l in range(1, n_layers + 1):
for s in range(n_sublayers):
for i in range(len(ctrlq[s])):
qml.CZ([ctrlq[s][i], trgtq[s][i]])
# Edited this to make it work
a = set(ctrlq[s])
a |= set(trgtq[s])
for q in list(a):
qml.RY(params[j], q)
j += 1
@qml.qnode(dev)
def circuit(input_points, params, max_diff=2): # Added max_diff and input_points
qml.AmplitudeEmbedding(features=input_points, wires=range_qubits,
normalize=True) # AmplitudeEmbedding instead of StatePrep
fixedStructureAnsatz(params) # this is some HW-efficient ansatz preperation according to the parameters
return qml.probs(wires=range_qubits)
def loss_fn(input_points, params):
d_psi_dx_vec = qml.jacobian(circuit, argnum=0)(input_points, params)
d_psi_dx_vec = pnp.diag(d_psi_dx_vec)
pdeloss = d_psi_dx_vec - cos_norm
loss = pnp.mean(pdeloss ** 2) # MSE of the pdeloss using pnp instead of np
return loss
def optimization(input_points, params):
best_loss = pnp.inf
for n in range(steps): # Edited this to make it work
[_, params], loss = opt.step_and_cost(loss_fn, input_points, params)
print(f"Step {n}, Loss: {loss}, Params: {params}")
if loss < best_loss:
best_loss = loss
best_params = params.copy()
return best_params, best_loss
best_params, best_loss = optimization(input_points, params)