Calculating the metric_tensor mid-circuit

Hello,

I’m trying to calculate the metric_tensor of a subset of my total circuit before measurement takes place. In particular, in my code below, I want to calculate it after U1(params). My code is as follows:

def get_observables(N):
    observables = []

    # Coupling operators
    for i in range(N-1):
        observables.append(qml.PauliZ(i) @ qml.PauliZ(i+1))

    # Identity operator
    for i in range(N):
        observables.append(qml.Identity(i))

    return observables

def get_coeffs(params, N):
    coeffs = []

    # Coupling coeffs
    for i in range(N-1):
        coeffs.append((params[0])**2/params[1])

    # Constant coeffs
    for i in range(N):
        coeffs.append(params[1])

    return coeffs

def create_Hamiltonian(params):

    coeffs = get_coeffs(params, nqubits)
    obs = get_observables(nqubits)
    
    H = qml.Hamiltonian(coeffs, obs, id='QP')

    return H

def create_params(L, scale = 0.1):
    
    params = np.array([], requires_grad=True)

    for i in range(L):
        J = scale*np.random.uniform()
        O = 1.0
        theta = scale*np.random.uniform()
        
        params = np.append(params, [J, O, theta], requires_grad=True)
            
    return params

dev = qml.device("default.qubit", wires=nqubits, shots=None)

def U1(params):
    start_index = 0
    num_trotter_steps = 10

    for i in range(L):
        new_params = params[start_index:start_index + 3]
        H = create_Hamiltonian(new_params[0:2])
        qml.evolve(H, num_steps = num_trotter_steps)
        for j in range(nqubits):
            qml.RX(new_params[2], wires=j)
        start_index += 3 # Put state_index in again

@qml.qnode(dev)
def circuit(params, phi):

    U1(params)

    for z in range(nqubits): # Perturbation
        qml.RY(phi[0], wires = z)

    qml.adjoint(U1)(params)

    expectation_values = [qml.expval(qml.PauliY(wires=i)) for i in range(nqubits)]

    return expectation_values # List of expected values of every input qubit

This is the output of my qml.about()

Name: PennyLane
Version: 0.33.1
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/PennyLaneAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /.../python3.11/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml, typing-extensions
Required-by: PennyLane-Lightning

Platform info:           macOS-13.4.1-x86_64-i386-64bit
Python version:          3.11.5
Numpy version:           1.26.2
Scipy version:           1.11.4
Installed devices:
- default.gaussian (PennyLane-0.33.1)
- default.mixed (PennyLane-0.33.1)
- default.qubit (PennyLane-0.33.1)
- default.qubit.autograd (PennyLane-0.33.1)
- default.qubit.jax (PennyLane-0.33.1)
- default.qubit.legacy (PennyLane-0.33.1)
- default.qubit.tf (PennyLane-0.33.1)
- default.qubit.torch (PennyLane-0.33.1)
- default.qutrit (PennyLane-0.33.1)
- null.qubit (PennyLane-0.33.1)
- lightning.qubit (PennyLane-Lightning-0.33.1)

Any help would be appreciated. Thanks in advance!

Hey @NickGut0711! Welcome to the forum :slight_smile:

You can use qml.metric_tensor: qml.metric_tensor — PennyLane 0.33.0 documentation. However, it’ll need a QNode as input, so you can do something like this:

@qml.qnode(dev)
def U1_for_metric_tensor(params):
    U1(params)
    return # return some measurement

metric_tensor = qml.metric_tensor(U1_for_metric_tensor)(params)

Let me know if this helps!

Thank you for the response and the warm welcome!

The code I put above will be run as follows:

  1. Evaluate the circuit for each “U1 layer”, L
  2. Optimize the parameters of the L layer
  3. Calculate the metric_tensor (with the fully optimized parameters) after every U1(params)

How do I go about this? After my optimization should I just find the optimal parameters, construct a “new U1” QNode based on those params, and calculate the metric tensor of that? Is this the most efficient way to do this? Thanks!

I don’t think you need to create a “new U1” QNode every time the parameters change. You can have a predefined QNode that takes the parameters in as an argument :slight_smile: