Higher order gradient with the device default.mixed

Hi, I am trying the get the second-order gradient during the training. I started with the default.qubit device and it worked properly. However, when I changed it to the default, I mixed to see the effect of the noise on the performance. The output of the second order gradient calculation is None.

Does anyone know what is the reason for this and help me out? Is it because it is not supported for other devices so far?

Thanks in advance :slight_smile:

Hey @Double_Fan! Is it possible for you to supply your code?

Hey @isaacdevlugt, Here is the code. The value of dx2 is None with the default.mixed device

class Model():
    def __init__(self, n_layers=20, **kwargs):
        super().__init__(**kwargs)
        self.circuit = Circuit()
        self.train_op = tf.keras.optimizers.Adam(learning_rate=0.001)
        self.x_extra = tf.Variable(x_extra, trainable = False)

    def run(self, X):
        return self.circuit(X)
    
    def get_loss(self, X, Y):
        predicts = self.run(X)
        loss1 = tf.math.reduce_mean(tf.math.square(predicts-Y))
        
        with tf.GradientTape(persistent=True) as tape:
            tape.watch(self.x_extra)
            y_extra = self.run(self.x_extra)
            dx = tape.gradient(y_extra, self.x_extra)
            dx2 = tape.gradient(dx, self.x_extra)
            
  
        return loss1   
    
    def get_grad(self,X,Y):
        with tf.GradientTape(persistent=True) as tape:
            tape.watch(self.circuit.variables)
            L = self.get_loss(X,Y) 
            g = tape.gradient(L, self.circuit.variables)
        return g
    
    def network_learn(self,X,Y):
        g = self.get_grad(X,Y)
        self.train_op.apply_gradients(zip(g, self.circuit.variables))

It looks like there are missing bits of code that I need to create an instance of the Model class :thinking:. Can you send an entire working example?

Here is an example, in which the x, y, and x_extra can be some random values, e.g. they are all equal to 0.5.

dev = qml.device("default.mixed", wires=1)

@qml.qnode(dev, interface="tf")
def qcircuit(params, x):
    qml.Rot(0, x[0], 0, wires=0)    
    qml.Rot(params[0],params[1],params[2], wires=0)
    return qml.expval(qml.PauliZ(0))

class Circuit(tf.keras.layers.Layer):
    def __init__(self):
        super(Circuit, self).__init__()
        theta_init = tf.random_uniform_initializer(minval=0.0, maxval=np.pi)
        self.params = tf.Variable(initial_value=theta_init(shape=(3,), dtype="float32"), trainable=True, name="thetas")
      
    def call(self, inputs):
        preds = []
        for i in range(inputs.shape[0]):
            pred = qcircuit(self.params, inputs[i])
            preds.append([pred])
        return tf.convert_to_tensor(preds, dtype=tf.float32)

class Circuit(tf.keras.layers.Layer):
    def __init__(self):
        super(Circuit, self).__init__()
        theta_init = tf.random_uniform_initializer(minval=0.0, maxval=np.pi)
        self.params = tf.Variable(initial_value=theta_init(shape=(3,), dtype="float32"), trainable=True, name="thetas")
      
    def call(self, inputs):
        preds = []
        for i in range(inputs.shape[0]):
            pred = qcircuit(self.params, inputs[i])
            preds.append([pred])
        return tf.convert_to_tensor(preds, dtype=tf.float32)

model= Model()
model.network_learn(x,y)

Are you able to share a minimal example with inputs and outputs along with your exact error message?

For the inputs, you can try the following:

x = np.array([[0.5]])
y = np.array([[0.5]])
x_extra = np.array([[0.5]])

Given the device ‘default.mixed’, if you print the value of dx2, it is None, but if you change the device to ‘default.qubit’, you will get a real value. I have no idea what is the reason for that.

Hey @Double_Fan! I can’t seem to reproduce your error. Here is the code that I’m running and the outputs:

import pennylane as qml
import tensorflow as tf

from pennylane import numpy as np

print(qml.__version__) # 0.24.0 for me
print(tf.__version__) # 2.9.1 for me

dev = qml.device("default.mixed", wires=1)

@qml.qnode(dev, interface="tf")
def qcircuit(params, x):
    qml.Rot(0, x[0], 0, wires=0)    
    qml.Rot(params[0],params[1],params[2], wires=0)
    return qml.expval(qml.PauliZ(0))

class Circuit(tf.keras.layers.Layer):
    def __init__(self):
        super(Circuit, self).__init__()
        theta_init = tf.random_uniform_initializer(minval=0.0, maxval=np.pi)
        self.params = tf.Variable(initial_value=theta_init(shape=(3,), dtype="float32"), trainable=True, name="thetas")
      
    def call(self, inputs):
        preds = []
        for i in range(inputs.shape[0]):
            pred = qcircuit(self.params, inputs[i])
            preds.append([pred])
        return tf.convert_to_tensor(preds, dtype=tf.float32)

class Circuit(tf.keras.layers.Layer):
    def __init__(self):
        super(Circuit, self).__init__()
        theta_init = tf.random_uniform_initializer(minval=0.0, maxval=np.pi)
        self.params = tf.Variable(initial_value=theta_init(shape=(3,), dtype="float32"), trainable=True, name="thetas")
      
    def call(self, inputs):
        preds = []
        for i in range(inputs.shape[0]):
            pred = qcircuit(self.params, inputs[i])
            preds.append([pred])
        return tf.convert_to_tensor(preds, dtype=tf.float32)

class Model():
    def __init__(self, n_layers=20, **kwargs):
        super().__init__(**kwargs)
        self.circuit = Circuit()
        self.train_op = tf.keras.optimizers.Adam(learning_rate=0.001)
        self.x_extra = tf.Variable(x_extra, trainable = False)

    def run(self, X):
        return self.circuit(X)
    
    def get_loss(self, X, Y):
        predicts = self.run(X)
        loss1 = tf.math.reduce_mean(tf.math.square(predicts-Y))
        
        with tf.GradientTape(persistent=True) as tape:
            tape.watch(self.x_extra)
            y_extra = self.run(self.x_extra)
            dx = tape.gradient(y_extra, self.x_extra)
            dx2 = tape.gradient(dx, self.x_extra)
        
        print(dx2)
  
        return loss1   
    
    def get_grad(self,X,Y):
        with tf.GradientTape(persistent=True) as tape:
            tape.watch(self.circuit.variables)
            L = self.get_loss(X,Y) 
            g = tape.gradient(L, self.circuit.variables)
        return g
    
    def network_learn(self,X,Y):
        g = self.get_grad(X,Y)
        print(g)
        self.train_op.apply_gradients(zip(g, self.circuit.variables))

x = np.array([[0.5]])
y = np.array([[0.5]])
x_extra = np.array([[0.5]])

model= Model()
model.network_learn(x,y)

"""
Outputs:

From print(dx2):

tf.Tensor([[-0.88553823]], shape=(1, 1), dtype=float64) 

From print(g):

[<tf.Tensor: shape=(3,), dtype=float32, numpy=array([ 5.7883937e-02, -1.7684884e-02,  3.4694470e-18], dtype=float32)>] # print(g)
"""

Hi, @isaacdevlugt, Thanks a lot :clap:. It works now. I guess it is because of the version of pennylane I was using.

1 Like

Awesome! Glad it was an easy fix :slight_smile: