QNGOptimizer LinAlgErr: Singlular Matrix

I am getting a LinAlgError: Singular matrix when trying to run QNGOptimizer on my circuit.
Here is the code for making the circuit and the hamiltonian:

dev = qml.device("default.qubit", wires=8)
connectivity = [(1, 2), (0, 6), (2, 3), (0, 7), (3, 4), (0, 1), (4, 5), (0, 2), (5, 6), (0, 3), (6, 7), (0, 4), (7, 1), (0, 5)]

def twoQubit(params, wires=8):
    for (index, (i, j)) in enumerate(connectivity):
        start = index * 15

        qml.U3(params[start], params[start+1], params[start+2], wires=i)
        qml.U3(params[start+3], params[start+4], params[start+5], wires=j)
        
        qml.CNOT(wires=[j, i])

        qml.RZ(params[start+6], wires=i)
        qml.RY(params[start+7], wires=j)

        qml.CNOT(wires=[i, j])

        qml.RY(params[start+8], wires=j)

        qml.CNOT(wires=[j, i])

        qml.U3(params[start+9], params[start+10], params[start+11], wires=i)
        qml.U3(params[start+12], params[start+13], params[start+14], wires=j)
op_list = [(qml.pauli.utils.string_to_pauli_word(singleEntry)) for singleEntry in returnStringObservable(1, 8)]
H = qml.Hamiltonian([-1]*len(op_list), op_list)

@qml.qnode(dev)
def cost_fn(params):
    twoQubit(params)
    return qml.expval(H)
init_params = np.array(np.random.normal(0, 0.1, 15*14), requires_grad=True)

opt = qml.QNGOptimizer(stepsize=step_size, approx="block-diag")

opt.step_and_cost(cost_fn, init_params)

The error stack is:

----> 3 opt.step_and_cost(cost_fn, init_params)

File ~\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\pennylane\optimize\qng.py:204, in QNGOptimizer.step_and_cost(self, qnode, grad_fn, recompute_tensor, metric_tensor_fn, *args, **kwargs)
    199     self.metric_tensor = self.metric_tensor + self.lam * qml.math.eye(
    200         size, like=_metric_tensor
    201     )
    203 g, forward = self.compute_grad(qnode, args, kwargs, grad_fn=grad_fn)
--> 204 new_args = np.array(self.apply_grad(g, args), requires_grad=True)
    206 if forward is None:
    207     forward = qnode(*args, **kwargs)

File ~\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\pennylane\optimize\qng.py:278, in QNGOptimizer.apply_grad(self, grad, args)
    276 grad_flat = np.array(list(_flatten(grad)))
    277 x_flat = np.array(list(_flatten(args)))
--> 278 x_new_flat = x_flat - self.stepsize * np.linalg.solve(self.metric_tensor, grad_flat)
    279 return unflatten(x_new_flat, args)

File ~\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\pennylane\numpy\wrapper.py:117, in tensor_wrapper.<locals>._wrapped(*args, **kwargs)
    114         tensor_kwargs["requires_grad"] = _np.any([i.requires_grad for i in tensor_args])
    116 # evaluate the original object
...
File ~\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\numpy\linalg\linalg.py:89, in _raise_linalgerror_singular(err, flag)
     88 def _raise_linalgerror_singular(err, flag):
---> 89     raise LinAlgError("Singular matrix")

LinAlgError: Singular matrix

The optimizer runs well with other circuits but not this. I usually use qiskit but switched to pennylane because of its quantum natural gradient optimizer and would really appreciate any help here

My hermitian looks as follows:

  (-1) [X1]
+ (-1) [X2]
+ (-1) [X3]
+ (-1) [X4]
+ (-1) [X5]
+ (-1) [X6]
+ (-1) [X7]
+ (-1) [Z1 Z2]
+ (-1) [Z2 Z3]
+ (-1) [Z3 Z4]
+ (-1) [Z4 Z5]
+ (-1) [Z5 Z6]
+ (-1) [Z6 Z7]
+ (-1) [Z1 Z7]

Hi @omersajid9,

If you run print(np.round(qml.metric_tensor(cost_fn, approx="block-diag")(init_params), 8)), you will see that the matrix corresponding to your qnode is in fact singular.

If you look at our demo here you will notice that the metric tensor is not singular.

I think this might be an issue with how you use your parameters in your circuit. If you are able to simplify this you might find the root of the problem.

For instance, in the following circuit you will get a singular matrix because only the first parameter is being used:

dev2 = qml.device('default.qubit',wires=2)

@qml.qnode(dev2)
def circuit2(params):
    qml.RX(params[0],wires=0)
    qml.RY(params[0],wires=1)
    return qml.expval(qml.PauliZ(0)@qml.PauliY(1))

i_param = np.array([0.1,0.2],requires_grad=True)
print(qml.metric_tensor(circuit2, approx="block-diag")(i_param))

However if you change qml.RY(params[0],wires=1) to qml.RY(params[1],wires=1) your matrix is no longer singular.

Please let me know if this helps!

Hi, thank you for your response. Yes, it fixes the issue sometimes but I have noticed that just applying a single qubit gate (for ex. H) on all qubits infront of my ansatz gets the circuit running. Is there any special reason for this?

Hi @omersajid9,
I’m not sure why this is working for you. If you’re not using all of your parameters then you probably still get a singular matrix. However, if you’re adding parametrized gates where you use all of your parameters then maybe that’s why this fixes your issue.

In any case I’m glad you managed to solve your problem!

Hi Catalina and all,

I also get the same error with qml.QNGOptimizer for my circuit when using certain combinations of initial states and initial parameters. I verified that the metric tensor is indeed singular in these cases.

This is the circuit I am using:

n_qubits = 4
dev = qml.device(device, wires=n_qubits)

@qml.qnode(dev)
def circuit(params, state_init):
    # Prepare intial state based on state_init
    qml.StatePrep(state_init, wires=range(n_qubits))
    
    # Apply trainable operations on each qubit
    for i in range(n_qubits):
        qml.Rot(params[i*3], params[i*3 + 1], params[i*3 + 2], wires=i)
  
    # Apply circular entanglement
    for i in range(n_qubits):
        qml.CNOT(wires=[i, (i + 1) % n_qubits])
  
    return qml.state()

In particular, I get singular metric tensors if I start with the states |0000> or |1111> independently of the initial paramaters, or if if I start with the states |0000>+|1111> or |0000>-|1111> with initial parameters set to zero.

I am using all parameters. Why is the metric tensor still singular? What does this mean?

For now, what I have done is add a small metric-tensor-regularization term qml.QNGOptimizer(lam=0.01) such that I don’t have that error.

Hey @joaofbravo,

Can you attach your full code so that I can try to replicate what’s going on?

Hi Isaac. Here it is.

track_entanglement_optimization_.py (6.5 KB)

Interesting :thinking:… When you use the QNGOptimizer, you’re essentially calculating the Fischer information matrix under the hood, so that’s probably the object that’s problematic. That said, I spoke to some people internally here and they said that adding a regularization (lam > 0) is completely fine for this reason!

If you want to dig into why you’re getting singular values, you can use this code to play around:

dev = qml.device("default.qubit")

n_qubits = 2

# Define the quantum circuit
@qml.qnode(dev)
def circuit(params, state_init):
    # Prepare intial state based on state_init
    qml.StatePrep(state_init, wires=range(n_qubits))

    # Apply trainable operations on each qubit
    for i in range(n_qubits):
        qml.Rot(params[i * 3], params[i * 3 + 1], params[i * 3 + 2], wires=i)

    # Apply circular entanglement
    for i in range(n_qubits):
        qml.CNOT(wires=[i, (i + 1) % n_qubits])

    return qml.state()

state_init = np.array([1, 0, 0, 0])
params = np.random.uniform(0, np.pi, size=(10,))

mat = qml.qinfo.quantum_fisher(circuit)(params, state_init)
print(np.linalg.det(mat))

If the determinant (last line above) is zero (in this example, it is), then we have a singular matrix :slight_smile:. Hope this helps!