Skip to content

Results are limited to the current section: Qadence

Product news

Solving a 1D ODE

In this tutorial we will show how to use Qadence to solve a basic 1D Ordinary Differential Equation (ODE) with a QNN using Differentiable Quantum Circuits (DQC) 1.

Consider the following non-linear ODE and boundary condition:

dfdx=5×(4x3+x22x12),f(0)=0 \frac{df}{dx}= 5\times(4x^3+x^2-2x-\frac12), \qquad f(0)=0

It admits an exact solution:

f(x)=5×(x4+13x3x212x) f(x)=5\times(x^4+\frac13x^3-x^2-\frac12x)

Our goal will be to find this solution for x[1,1]x\in[-1, 1].

import torch
def dfdx_equation(x: torch.Tensor) -> torch.Tensor:
"""Derivative as per the equation."""
return 5*(4*x**3 + x**2 - 2*x - 0.5)

For the purpose of this tutorial, we will compute the derivative of the circuit using torch.autograd. The point of the DQC algorithm is to use differentiable circuits with parameter shift rules. In Qadence, PSR is implemented directly as custom overrides of the derivative function in the autograd engine, and thus we can later change the derivative method for the model itself if we wish.

def calc_deriv(outputs: torch.Tensor, inputs: torch.Tensor) -> torch.Tensor:
"""Compute a derivative of model that learns f(x), computes df/dx using torch.autograd."""
grad = torch.autograd.grad(
outputs=outputs,
inputs=inputs,
grad_outputs = torch.ones_like(inputs),
create_graph = True,
retain_graph = True,
)[0]
return grad

The essential part of solving this problem is to define the right loss function to represent our goal. In this case, we want to define a model that has the capacity to learn the target solution, and we want to minimize: - The derivative of this model in comparison with the exact derivative in the equation; - The output of the model at the boundary in comparison with the value for the boundary condition;

We can write it like so:

# Mean-squared error as the comparison criterion
criterion = torch.nn.MSELoss()
def loss_fn(model: torch.nn.Module, inputs: torch.Tensor) -> torch.Tensor:
"""Loss function encoding the problem to solve."""
# Equation loss
model_output = model(inputs)
deriv_model = calc_deriv(model_output, inputs)
deriv_exact = dfdx_equation(inputs)
ode_loss = criterion(deriv_model, deriv_exact)
# Boundary loss, f(0) = 0
boundary_model = model(torch.tensor([[0.0]]))
boundary_exact = torch.tensor([[0.0]])
boundary_loss = criterion(boundary_model, boundary_exact)
return ode_loss + boundary_loss

Different loss criterions could be considered, and we could also play with the balance between the sum of the two loss terms. For now, let's proceed with the definition above.

Note that so far we have not used any quantum specific assumption, and we could in principle use the same loss function with a classical neural network.

Now, we can finally use Qadence to write a QNN. We will use a feature map to encode the input values, a trainable ansatz circuit, and an observable to measure as the output.

from qadence import feature_map, hea, chain
from qadence import QNN, QuantumCircuit, Z
from qadence.types import BasisSet, ReuploadScaling
n_qubits = 3
depth = 3
# Feature map
fm = feature_map(
n_qubits = n_qubits,
param = "x",
fm_type = BasisSet.CHEBYSHEV,
reupload_scaling = ReuploadScaling.TOWER,
)
# Ansatz
ansatz = hea(n_qubits = n_qubits, depth = depth)
# Observable
observable = Z(0)
circuit = QuantumCircuit(n_qubits, chain(fm, ansatz))
model = QNN(circuit = circuit, observable = observable, inputs = ["x"])

We used a Chebyshev feature map with a tower-like scaling of the input reupload, and a standard hardware-efficient ansatz. You can check the qml constructors tutorial to see how you can customize these components. In the observable, for now we consider the simple case of measuring the magnetization of the first qubit.

from qadence.draw import display
# display(circuit)
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
%3
cluster_ce98b47c72e14daf90d78b2de2bc9aaa
HEA
cluster_f60c0014046a4d5c89c324c415ed392c
Tower Chebyshev FM
c8bac5b50b2e408994969a1ddbf39002
0
f7900543523b40eaa4ff34da33e278ac
RX(1.0*acos(x))
c8bac5b50b2e408994969a1ddbf39002--f7900543523b40eaa4ff34da33e278ac
2a84b602e4bf4b7cbc93d6596d8ea9f1
1
418fcfb5e14640918075e78d3a83e914
RX(theta₀)
f7900543523b40eaa4ff34da33e278ac--418fcfb5e14640918075e78d3a83e914
b7cb6b23abc6472985d3ead06adab1dd
RY(theta₃)
418fcfb5e14640918075e78d3a83e914--b7cb6b23abc6472985d3ead06adab1dd
4593ca3c01df4653bdbc204190aafaca
RX(theta₆)
b7cb6b23abc6472985d3ead06adab1dd--4593ca3c01df4653bdbc204190aafaca
f4c3fee0c9fa441ca209d423293bb809
4593ca3c01df4653bdbc204190aafaca--f4c3fee0c9fa441ca209d423293bb809
3e4099afe6924f93ad6a74b871e5465c
f4c3fee0c9fa441ca209d423293bb809--3e4099afe6924f93ad6a74b871e5465c
1ee7f0e776c9470f975687e7db8ec7aa
RX(theta₉)
3e4099afe6924f93ad6a74b871e5465c--1ee7f0e776c9470f975687e7db8ec7aa
ed028b53d6c040e8a3bb9953df0a3e6d
RY(theta₁₂)
1ee7f0e776c9470f975687e7db8ec7aa--ed028b53d6c040e8a3bb9953df0a3e6d
5981a1cad78a4ec2bcbfdbd0ef9b31ed
RX(theta₁₅)
ed028b53d6c040e8a3bb9953df0a3e6d--5981a1cad78a4ec2bcbfdbd0ef9b31ed
604d8f8b440f4b4caf98a4d9ac9f12b2
5981a1cad78a4ec2bcbfdbd0ef9b31ed--604d8f8b440f4b4caf98a4d9ac9f12b2
e1370b3796b54be7a78f19872c6e9e38
604d8f8b440f4b4caf98a4d9ac9f12b2--e1370b3796b54be7a78f19872c6e9e38
d8d348b9807548e3863fb56bb949b8fe
RX(theta₁₈)
e1370b3796b54be7a78f19872c6e9e38--d8d348b9807548e3863fb56bb949b8fe
7ae7a61007ce49048958b2c39d13c45d
RY(theta₂₁)
d8d348b9807548e3863fb56bb949b8fe--7ae7a61007ce49048958b2c39d13c45d
0ae8a4715a9d48db84498da112aadd4e
RX(theta₂₄)
7ae7a61007ce49048958b2c39d13c45d--0ae8a4715a9d48db84498da112aadd4e
af237dc0b9a94b93ae6f61c14afb307a
0ae8a4715a9d48db84498da112aadd4e--af237dc0b9a94b93ae6f61c14afb307a
567160e61d514c698d0a5d534cddf0d5
af237dc0b9a94b93ae6f61c14afb307a--567160e61d514c698d0a5d534cddf0d5
578442a657474eaaa5ec57e18f74adec
567160e61d514c698d0a5d534cddf0d5--578442a657474eaaa5ec57e18f74adec
598f0da5882942b9bbc512786c111c5e
530bd396c7e74e728b7fc90ec66cdee5
RX(2.0*acos(x))
2a84b602e4bf4b7cbc93d6596d8ea9f1--530bd396c7e74e728b7fc90ec66cdee5
3aa4c4690d5d42a49246719b6e3cf501
2
a0a2f75382e6468798ab1149510490c2
RX(theta₁)
530bd396c7e74e728b7fc90ec66cdee5--a0a2f75382e6468798ab1149510490c2
b4974e8fe4334c65861871d867edaa9c
RY(theta₄)
a0a2f75382e6468798ab1149510490c2--b4974e8fe4334c65861871d867edaa9c
8e0b3bbec7d8494c8a16ba4d798d113e
RX(theta₇)
b4974e8fe4334c65861871d867edaa9c--8e0b3bbec7d8494c8a16ba4d798d113e
7304d1c77dbd424287be39c64e95c411
X
8e0b3bbec7d8494c8a16ba4d798d113e--7304d1c77dbd424287be39c64e95c411
7304d1c77dbd424287be39c64e95c411--f4c3fee0c9fa441ca209d423293bb809
375367ba94be4ebeacf31881b2c5010a
7304d1c77dbd424287be39c64e95c411--375367ba94be4ebeacf31881b2c5010a
031ce4474a154f48b5b5c151d1d0ce3f
RX(theta₁₀)
375367ba94be4ebeacf31881b2c5010a--031ce4474a154f48b5b5c151d1d0ce3f
5dbd743dca794e9f901069ee2ce5e88f
RY(theta₁₃)
031ce4474a154f48b5b5c151d1d0ce3f--5dbd743dca794e9f901069ee2ce5e88f
e5f26c1719e14dad949c6a6ff80e3b1b
RX(theta₁₆)
5dbd743dca794e9f901069ee2ce5e88f--e5f26c1719e14dad949c6a6ff80e3b1b
7c754ae87d7c4b3fba03a03bd65a54d8
X
e5f26c1719e14dad949c6a6ff80e3b1b--7c754ae87d7c4b3fba03a03bd65a54d8
7c754ae87d7c4b3fba03a03bd65a54d8--604d8f8b440f4b4caf98a4d9ac9f12b2
57167c8d44c745a7a2a1a6ba27e4ca6f
7c754ae87d7c4b3fba03a03bd65a54d8--57167c8d44c745a7a2a1a6ba27e4ca6f
e2fa0b1a8a904f4a9d686bc9c7e29e51
RX(theta₁₉)
57167c8d44c745a7a2a1a6ba27e4ca6f--e2fa0b1a8a904f4a9d686bc9c7e29e51
ec8eedf4c3b04ded8c303aa1e52c32db
RY(theta₂₂)
e2fa0b1a8a904f4a9d686bc9c7e29e51--ec8eedf4c3b04ded8c303aa1e52c32db
1e8c852bf69444ca92484473a9a9772a
RX(theta₂₅)
ec8eedf4c3b04ded8c303aa1e52c32db--1e8c852bf69444ca92484473a9a9772a
cadc9532b7de406ba500b3c0ee5c58d0
X
1e8c852bf69444ca92484473a9a9772a--cadc9532b7de406ba500b3c0ee5c58d0
cadc9532b7de406ba500b3c0ee5c58d0--af237dc0b9a94b93ae6f61c14afb307a
d448746f56094225b2db58b69e6ffc76
cadc9532b7de406ba500b3c0ee5c58d0--d448746f56094225b2db58b69e6ffc76
d448746f56094225b2db58b69e6ffc76--598f0da5882942b9bbc512786c111c5e
d89386ad28c642b3836665d261729102
0f4b08fe04054dc9a235f264d4885c3f
RX(3.0*acos(x))
3aa4c4690d5d42a49246719b6e3cf501--0f4b08fe04054dc9a235f264d4885c3f
8906416b7b564614bf67987039e06b1b
RX(theta₂)
0f4b08fe04054dc9a235f264d4885c3f--8906416b7b564614bf67987039e06b1b
1a8242bf6f0c41608e4f04742cdeb178
RY(theta₅)
8906416b7b564614bf67987039e06b1b--1a8242bf6f0c41608e4f04742cdeb178
d71dd0f9cb854b4aa4c0eaedd8208848
RX(theta₈)
1a8242bf6f0c41608e4f04742cdeb178--d71dd0f9cb854b4aa4c0eaedd8208848
44c801042bf04dd6aa99e6a1aabce278
d71dd0f9cb854b4aa4c0eaedd8208848--44c801042bf04dd6aa99e6a1aabce278
cb747a7c058a4361998d859ff838760a
X
44c801042bf04dd6aa99e6a1aabce278--cb747a7c058a4361998d859ff838760a
cb747a7c058a4361998d859ff838760a--375367ba94be4ebeacf31881b2c5010a
1eaf9778cef9404e86703565f42d9cc3
RX(theta₁₁)
cb747a7c058a4361998d859ff838760a--1eaf9778cef9404e86703565f42d9cc3
2c14c13e81364a9f9f4cb51e7282aa10
RY(theta₁₄)
1eaf9778cef9404e86703565f42d9cc3--2c14c13e81364a9f9f4cb51e7282aa10
5740586d100f4ec49349d1f99596e6ba
RX(theta₁₇)
2c14c13e81364a9f9f4cb51e7282aa10--5740586d100f4ec49349d1f99596e6ba
342bda19b4f94d7daa5bceb6779d5f35
5740586d100f4ec49349d1f99596e6ba--342bda19b4f94d7daa5bceb6779d5f35
a08dfa649eab47a49052240aa32af497
X
342bda19b4f94d7daa5bceb6779d5f35--a08dfa649eab47a49052240aa32af497
a08dfa649eab47a49052240aa32af497--57167c8d44c745a7a2a1a6ba27e4ca6f
1af81c40f1f9493e86095312cf7e2b0e
RX(theta₂₀)
a08dfa649eab47a49052240aa32af497--1af81c40f1f9493e86095312cf7e2b0e
5a644cebac6a4c7d84cd9cafe250a0cc
RY(theta₂₃)
1af81c40f1f9493e86095312cf7e2b0e--5a644cebac6a4c7d84cd9cafe250a0cc
416d6c0f28cf47c1830fbecbe05bbc3e
RX(theta₂₆)
5a644cebac6a4c7d84cd9cafe250a0cc--416d6c0f28cf47c1830fbecbe05bbc3e
92625d217d604fe18dce8ef9e6e1e510
416d6c0f28cf47c1830fbecbe05bbc3e--92625d217d604fe18dce8ef9e6e1e510
12968140efbe4d32a7e49a95d77ea2e3
X
92625d217d604fe18dce8ef9e6e1e510--12968140efbe4d32a7e49a95d77ea2e3
12968140efbe4d32a7e49a95d77ea2e3--d448746f56094225b2db58b69e6ffc76
12968140efbe4d32a7e49a95d77ea2e3--d89386ad28c642b3836665d261729102

Now that the model is defined we can proceed with the training. the QNN class can be used like any other torch.nn.Module. Here we write a simple training loop, but you can also look at the ml tools tutorial to use the convenience training functions that Qadence provides.

To train the model, we will select a random set of collocation points uniformly distributed within 1.0<x<1.0-1.0< x <1.0 and compute the loss function for those points.

n_epochs = 200
n_points = 10
xmin = -0.99
xmax = 0.99
optimizer = torch.optim.Adam(model.parameters(), lr = 0.1)
for epoch in range(n_epochs):
optimizer.zero_grad()
# Training data. We unsqueeze essentially making each batch have a single x value.
x_train = (xmin + (xmax-xmin)*torch.rand(n_points, requires_grad = True)).unsqueeze(1)
loss = loss_fn(inputs = x_train, model = model)
loss.backward()
optimizer.step()

Note the values of xx are only picked from x[0.99,0.99]x\in[-0.99, 0.99] since we are using a Chebyshev feature map, and derivative of acos(x)\text{acos}(x) diverges at 1-1 and 11.

import matplotlib.pyplot as plt
def f_exact(x: torch.Tensor) -> torch.Tensor:
return 5*(x**4 + (1/3)*x**3 - x**2 - 0.5*x)
x_test = torch.arange(xmin, xmax, step = 0.01).unsqueeze(1)
result_exact = f_exact(x_test).flatten()
result_model = model(x_test).flatten().detach()
plt.plot(x_test, result_exact, label = "Exact solution")
plt.plot(x_test, result_model, label = " Trained model")
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
2025-06-04T09:58:26.154947
image/svg+xml
Matplotlib v3.10.3, https://matplotlib.org/
*{stroke-linejoin: round; stroke-linecap: butt}

Clearly, the result is not optimal.

One point to consider when defining the QNN is the possible output range, which is bounded by the spectrum of the chosen observable. For the magnetization of a single qubit, this means that the output is bounded between -1 and 1, which we can clearly see in the plot.

One option would be to define the observable as the total magnetization over all qubits, which would allow a range of -3 to 3.

from qadence import add
observable = add(Z(i) for i in range(n_qubits))
model = QNN(circuit = circuit, observable = observable, inputs = ["x"])
optimizer = torch.optim.Adam(model.parameters(), lr = 0.1)
for epoch in range(n_epochs):
optimizer.zero_grad()
# Training data
x_train = (xmin + (xmax-xmin)*torch.rand(n_points, requires_grad = True)).unsqueeze(1)
loss = loss_fn(inputs = x_train, model = model)
loss.backward()
optimizer.step()

And we again plot the result:

x_test = torch.arange(xmin, xmax, step = 0.01).unsqueeze(1)
result_exact = f_exact(x_test).flatten()
result_model = model(x_test).flatten().detach()
plt.plot(x_test, result_exact, label = "Exact solution")
plt.plot(x_test, result_model, label = "Trained model")
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
2025-06-04T09:58:33.578435
image/svg+xml
Matplotlib v3.10.3, https://matplotlib.org/
*{stroke-linejoin: round; stroke-linecap: butt}