Hi,

Iβm currently working on the Variational Quantum Linear Solver tutorial, and I would like to generalize it to any vector b and matrix A.

Is there anyway to do that with pennylane?

Best regards

Hi,

Iβm currently working on the Variational Quantum Linear Solver tutorial, and I would like to generalize it to any vector b and matrix A.

Is there anyway to do that with pennylane?

Best regards

Hi @Ramy! You can definitely do that.

You would need to redefine the function `U_b`

and `CA`

defined in the Circuits of the quantum linear problem section.

You will probably also need to edit the `variational_block`

function. The `StronglyEntanglingLayers`

is a good place to start.

And finally, you will need to change the hyperparameters, with the new number of qubits.

Let me know if this works

Hi @christina,

Thanks for your response!

I know that these functions need to be edited (U_b and CA) but Iβm still new to pennylane so I donβt know how to do that. Is there a function similar to initialize function in qiskit which prepares any arbitrary state?

Thank you!

I will check that

1 Like

Hi @christina again,

For the state preparation, I think it is clear now, but Iβm still confused with the matrix part. Suppose I have a hermitian matrix and I would like to convert it into operations, Do you have any idea on how to do it?

Regards

Hi @Ramy!

Are you looking for the `qml.utils.decompose_hamiltonian`

function?

This function takes an Hermitian matrix, and returns a tuple of coefficients and Pauli operators:

```
>>> A = np.array( ... [[-2, -2+1j, -2, -2], [-2-1j, 0, 0, -1], [-2, 0, -2, -1], [-2, -1, -1, 0]])
>>> coeffs, obs_list = decompose_hamiltonian(A)
>>> coeffs [-1.0, -1.5, -0.5, -1.0, -1.5, -1.0, -0.5, 1.0, -0.5, -0.5]
```

We can use the output coefficients and tensor Pauli terms to construct a `Hamiltonian`

:

```
>>> H = qml.Hamiltonian(coeffs, obs_list)
>>> print(H)
(-1.0) [I0 I1]
+ (-1.5) [X1]
+ (-0.5) [Y1]
+ (-1.0) [Z1]
+ (-1.5) [X0]
+ (-1.0) [X0 X1]
+ (-0.5) [X0 Z1]
+ (1.0) [Y0 Y1]
+ (-0.5) [Z0 X1]
+ (-0.5) [Z0 Y1]
```

1 Like

Thank you for your response, I think this is what I need.

just to make sure, it should work with qml.ctrl right?

Hi @Ramy, you might have to provide more details in terms of how you would like to use it with `qml.ctrl`

Hi @josh, Iβm trying to generalize Variational Quantum Linear for any Hermitian matrix A and arbitrary state b.

Hi @Ramy,

Thanks for the questions! `qml.ctrl`

is mainly meant to be used with quantum operations, rather than an observable or Hamiltonian. How could `qml.ctrl`

fit into your use case of generalizing the Variational Quantum Linear solver?

1 Like

Hi @antalszava, thanks for the clarification!

Actually, Iβm not sure if it can be used or not.

My original question was how to generalize the Variational Quantum Linear solver for any hermition matrix?

Hi @Ramy,

The previously mentioned components are useful in achieving that.

- For changing
`U_b`

, the proposed`qml.MottonenStatePreparation`

can be used. - For changing
`CA`

, first letβs have a look at how we can decompose an Hermitian matrix A. We can represent A as A = \sum_{I}C_I\hat{P}_I

where C_I are numerical coefficients, and P_I are Pauli

βwordsβ, products of Pauli operators of different qubits \hat{P}_I=\prod_{i=1}^N\hat{\sigma}_i^{(I)}

where

\hat{\sigma}_i^{(I)} is one of the \hat{x}, \hat{y}, \hat{z} Pauli operators or identity \hat{e} for the i^{th} qubit.

(As described in the introduction of this reference for qubit Hamiltonians, i.e., Hermitian operators).

To get this decomposition, the `decompose_hamiltonian`

function mentioned by Josh can be used as basis. The main difference, from how this function is being used usually, is that weβd create a Hamiltonian class using `qml.Hamiltonian`

. A `qml.Hamiltonian`

object is usually used with `qml.ExpvalCost`

to return the expectation value of the Pauli words.

In the tutorial, however, we are applying the unitaries (i.e., the Pauli words from our decomposition) as quantum operations.

Therefore, we can use a custom definition of the `decompose_hamiltonian`

function:

```
def decompose_hamiltonian_unitary_ops(H):
"""Auxiliary function similar to pennylane.utils.decompose_hamiltonian.
This function returns a nested list of
"""
n = int(np.log2(len(H)))
N = 2 ** n
if H.shape != (N, N):
raise ValueError(
"The Hamiltonian should have shape (2**n, 2**n), for any qubit number n>=1"
)
if not np.allclose(H, H.conj().T):
raise ValueError("The Hamiltonian is not Hermitian")
paulis = [qml.Identity, qml.PauliX, qml.PauliY, qml.PauliZ]
ops = []
coeffs = []
for term in itertools.product(paulis, repeat=n):
matrices = [i._matrix() for i in term]
coeff = np.trace(functools.reduce(np.kron, matrices) @ H) / N
coeff = np.real_if_close(coeff).item()
if not np.allclose(coeff, 0):
coeffs.append(coeff)
if not all(t is qml.Identity for t in term) and True:
ops.append([t(i) for i, t in enumerate(term) if t is not qml.Identity])
return coeffs, ops
```

This definition is very similar to the original implementation. The key difference is, that with the original version we are creating the tensor products of Paulis, whereas with this version, we accumulate them in lists that are contained in `ops`

.

Once we have this function, we can decompose arbitrary Hermitian matrices:

```
H = np.array([[-2, -2+1j, -2, -2], [-2-1j, 0, 0, -1], [-2, 0, -2, -1], [-2, -1, -1, 0]])
coeffs, ops_list = decompose_hamiltonian_unitary_ops(H)
```

At this point, we just have to define a quantum function (e.g., called `applying_unitaries`

), that will apply the unitaries that weβve gathered in `ops_list`

. Under the hood, PennyLane keeps track of operations in a quantum function using a concept called queuing. Each operation in the circuit is being queued when an operation is created when calling the quantum function.

For example, the `qml.RY`

and the `qml.PauliZ`

operators are queued in the following example (this example is not required for the solution):

```
def circuit():
qml.RY(0.3, wires=[0])
return qml.expval(qml.PauliZ(0))
```

In our case, however, we have already created our operations, so we have to explicitly queue them:

```
def applying_unitaries():
"""Quantum function that applies the unitaries building up a Hamiltonian"""
for ops in ops_list:
for o in ops:
o.queue()
```

Once we have this quantum function, all is ready to make a controlled version of it using `qml.ctrl`

:

```
# Creating a controlled version for applying the unitaries
# The second argument are the control wire(s) to use
ctrl_ops = qml.ctrl(applying_unitaries, [2])
```

`ctrl_ops`

will return a quantum function that can be used in any QNode. We can check that we have the correct circuit with controlled operations by comparing the original circuit and the new circuit:

```
dev = qml.device('default.qubit', 3)
@qml.qnode(dev)
def applying_unitaries_with_expval():
applying_unitaries()
return qml.expval(qml.PauliZ(0))
applying_unitaries_with_expval()
print(applying_unitaries_with_expval.draw())
@qml.qnode(dev)
def ctrl_circuit_with_expval():
ctrl_ops()
return qml.expval(qml.PauliZ(0))
ctrl_circuit_with_expval()
print(ctrl_circuit_with_expval.draw())
```

```
0: ββXββXββXββYββZββZβββββββββ€ β¨Zβ©
1: ββXββYββZββXββZββYββXββYβββ€
0: ββββββββββββββββXβββXβββββββXβββββββCYββββββββZβββββββZββββββββ€ β¨Zβ©
1: βββXβββCYβββZβββββββββββXβββββββZββββββββCYβββββββXβββββββCYβββ€
2: βββ°Cβββ°CYβββ°Cβββ°Cβββ°Cβββ°Cβββ°Cβββ°Cβββ°CYβββ°CYβββ°Cβββ°Cβββ°Cβββ°CYβββ€
```

To adjust this code to be written as `CA`

in the tutorial, we will likely need to index into the list of operations.

Hope this helps!

1 Like

Thank you very much for the detailed explanation @antalszava!

It will take me some time to go through it but I think it is clear now.

I appreciate it

1 Like