Skip to content

Fitting a function with a Hamiltonian ansatz

In the analog QCL tutorial we used analog blocks to learn a function of interest. The analog blocks are a direct abstraction of device execution with global addressing. However, we may want to directly program an Hamiltonian-level ansatz to have a finer control on our model. In Qadence this can easily be done through digital-analog programs. In this tutorial we will solve a simple QCL problem with this approach.

Setting up the problem

The example problem considered is to fit a function of interest in a specified range. Below we define and plot the function \(f(x)=x^5\).

import torch
import matplotlib.pyplot as plt

# Function to fit:
def f(x):
    return x**5

xmin = -1.0
xmax = 1.0
n_test = 100

x_test = torch.linspace(xmin, xmax, steps = n_test)
y_test = f(x_test)

plt.plot(x_test, y_test)
plt.xlim((-1.1, 1.1))
plt.ylim((-1.1, 1.1))
2025-05-26T11:54:27.233702 image/svg+xml Matplotlib v3.10.3, https://matplotlib.org/

Digital-Analog Ansatz

We start by defining the register of qubits. The topology we use now will define the interactions in the entangling Hamiltonian. As an example, we can define a rectangular lattice with 6 qubits.

from qadence import Register

reg = Register.rectangular_lattice(
    qubits_row = 3,
    qubits_col = 2,
)

Inspired by the Ising interaction mode of Rydberg atoms, we can now define an interaction Hamiltonian as \(\mathcal{H}_{ij}=\frac{1}{r_{ij}^6}N_iN_j\), where \(N_i=(1/2)(I_i-Z_i)\) is the number operator and and \(r_{ij}\) is the distance between qubits \(i\) and \(j\). We can easily instatiate this interaction Hamiltonian from the register information:

from qadence import N, add

def h_ij(i: int, j: int):
    return N(i)@N(j)

h_int = add(h_ij(*edge)/r**6 for edge, r in reg.edge_distances.items())

To build the digital-analog ansatz we can make use of the standard hea function by specifying we want to use the Strategy.SDAQC and passing the Hamiltonian we created as the entangler, as see in the QML constructors tutorial. The entangling operation will be replaced by the evolution of this Hamiltonian HamEvo(h_int, t), where the time parameter t is considered to be a variational parameter at each layer.

from qadence import hea, Strategy, RX, RY

depth = 2

da_ansatz = hea(
    n_qubits = reg.n_qubits,
    depth = depth,
    operations = [RX, RY, RX],
    entangler = h_int,
    strategy = Strategy.SDAQC,
)

print(html_string(da_ansatz))
%3 cluster_ca81672ba2aa45df801d71f8820e78c6 cluster_8cbe0955deb944e19fd9264e67fecf51 2701af28b0064a5384400d3a30b26cc2 0 e979c5492ecd4b54a4f8147f7e22ed4c RX(theta₀) 2701af28b0064a5384400d3a30b26cc2--e979c5492ecd4b54a4f8147f7e22ed4c d2bd206109a54383a60d7fc7199ddba9 1 20e996a313564647b7ea533023b77e32 RY(theta₆) e979c5492ecd4b54a4f8147f7e22ed4c--20e996a313564647b7ea533023b77e32 c23eb00f7844458ead4c732524fc2a8d RX(theta₁₂) 20e996a313564647b7ea533023b77e32--c23eb00f7844458ead4c732524fc2a8d 068bf29d550d4fb699e24b4405fb93f3 c23eb00f7844458ead4c732524fc2a8d--068bf29d550d4fb699e24b4405fb93f3 ded76728226d4fefbf8b4d1760b7b009 RX(theta₁₈) 068bf29d550d4fb699e24b4405fb93f3--ded76728226d4fefbf8b4d1760b7b009 7c429b5e464c45469f556b5d368e2623 RY(theta₂₄) ded76728226d4fefbf8b4d1760b7b009--7c429b5e464c45469f556b5d368e2623 45518f88580c4085b9f9ba04f4a6e2dd RX(theta₃₀) 7c429b5e464c45469f556b5d368e2623--45518f88580c4085b9f9ba04f4a6e2dd 299d5c5077ec49e4a0edceae90bdfa00 45518f88580c4085b9f9ba04f4a6e2dd--299d5c5077ec49e4a0edceae90bdfa00 5365676b4c2c4d5d9562cb0652d1d624 299d5c5077ec49e4a0edceae90bdfa00--5365676b4c2c4d5d9562cb0652d1d624 952ba115508f40f1ad6f4d977fde1373 ce6f69f8c12a432a9fa77ef2da7b1472 RX(theta₁) d2bd206109a54383a60d7fc7199ddba9--ce6f69f8c12a432a9fa77ef2da7b1472 1f02401d2cdc4d32b256ae63f5a93d19 2 3dbfca7c616e48a580aa5160128d0b79 RY(theta₇) ce6f69f8c12a432a9fa77ef2da7b1472--3dbfca7c616e48a580aa5160128d0b79 3a50483c377048eca62b3e73e16f8d80 RX(theta₁₃) 3dbfca7c616e48a580aa5160128d0b79--3a50483c377048eca62b3e73e16f8d80 cc69fbba3cc2422eac0ee488a401ba4b 3a50483c377048eca62b3e73e16f8d80--cc69fbba3cc2422eac0ee488a401ba4b fb267e0f224e4c18a710ff0e6be54b0c RX(theta₁₉) cc69fbba3cc2422eac0ee488a401ba4b--fb267e0f224e4c18a710ff0e6be54b0c 502a9975b0d64661a6450ef8c7f56d81 RY(theta₂₅) fb267e0f224e4c18a710ff0e6be54b0c--502a9975b0d64661a6450ef8c7f56d81 39c71969581f4b8a94395aa61e0219d2 RX(theta₃₁) 502a9975b0d64661a6450ef8c7f56d81--39c71969581f4b8a94395aa61e0219d2 0739299dc222477eb60c480cc91346dd 39c71969581f4b8a94395aa61e0219d2--0739299dc222477eb60c480cc91346dd 0739299dc222477eb60c480cc91346dd--952ba115508f40f1ad6f4d977fde1373 fc222e771bf5440398726fc94757699f 89226113074849fa93864e9bcc62ebd3 RX(theta₂) 1f02401d2cdc4d32b256ae63f5a93d19--89226113074849fa93864e9bcc62ebd3 71bf03d3abdc4587a8e8abc4ca05d788 3 c71ccbf5fe7747d19f49072c15e88f04 RY(theta₈) 89226113074849fa93864e9bcc62ebd3--c71ccbf5fe7747d19f49072c15e88f04 c7fb12a1e1a74bb3a6d9ac3316eee627 RX(theta₁₄) c71ccbf5fe7747d19f49072c15e88f04--c7fb12a1e1a74bb3a6d9ac3316eee627 b29dd1779a3c45dbbc1031e7b8ea7c5e HamEvo c7fb12a1e1a74bb3a6d9ac3316eee627--b29dd1779a3c45dbbc1031e7b8ea7c5e 1f87717d635549d891f74b0f7e95f62d RX(theta₂₀) b29dd1779a3c45dbbc1031e7b8ea7c5e--1f87717d635549d891f74b0f7e95f62d 1e98925093da465698102ccbe62eb023 RY(theta₂₆) 1f87717d635549d891f74b0f7e95f62d--1e98925093da465698102ccbe62eb023 ce748a4f712d46edb83028aefaae57df RX(theta₃₂) 1e98925093da465698102ccbe62eb023--ce748a4f712d46edb83028aefaae57df 3ceb5a3751ed4d79ba7654bf8abf4efe HamEvo ce748a4f712d46edb83028aefaae57df--3ceb5a3751ed4d79ba7654bf8abf4efe 3ceb5a3751ed4d79ba7654bf8abf4efe--fc222e771bf5440398726fc94757699f b2b27600ae404638bb2bfffb7f718630 80e6194dc5744a25a4844be0ef1e7b7e RX(theta₃) 71bf03d3abdc4587a8e8abc4ca05d788--80e6194dc5744a25a4844be0ef1e7b7e 4e8808b405ed4eea9c4125fec877710c 4 0755516f8241407a85976ee6ce4dcded RY(theta₉) 80e6194dc5744a25a4844be0ef1e7b7e--0755516f8241407a85976ee6ce4dcded 0f81f1e4dd524bab9e55cf4b3f0d9cd6 RX(theta₁₅) 0755516f8241407a85976ee6ce4dcded--0f81f1e4dd524bab9e55cf4b3f0d9cd6 deb35613eada4a00952f510559f638ee t = theta_t₀ 0f81f1e4dd524bab9e55cf4b3f0d9cd6--deb35613eada4a00952f510559f638ee e239a3d3f3544bc385a43c5ed6ec104e RX(theta₂₁) deb35613eada4a00952f510559f638ee--e239a3d3f3544bc385a43c5ed6ec104e d2f357912bb34afda6a40a94b9fdbbb5 RY(theta₂₇) e239a3d3f3544bc385a43c5ed6ec104e--d2f357912bb34afda6a40a94b9fdbbb5 eb0562640a184300854d89681f6354a3 RX(theta₃₃) d2f357912bb34afda6a40a94b9fdbbb5--eb0562640a184300854d89681f6354a3 74d89d6cc7ea4fa7a8e11f3e536351fb t = theta_t₁ eb0562640a184300854d89681f6354a3--74d89d6cc7ea4fa7a8e11f3e536351fb 74d89d6cc7ea4fa7a8e11f3e536351fb--b2b27600ae404638bb2bfffb7f718630 d136e0f0d05e48c8a3a7aab22ec4a723 3567633cacc846f0954a52f6d03ed99a RX(theta₄) 4e8808b405ed4eea9c4125fec877710c--3567633cacc846f0954a52f6d03ed99a 08a5624be5a34ff8b89ad9a71fb306b7 5 ff5401e339f14d9ba1ea708e73422c04 RY(theta₁₀) 3567633cacc846f0954a52f6d03ed99a--ff5401e339f14d9ba1ea708e73422c04 de6ea1afd34d4832aeb7acc9b03c4de9 RX(theta₁₆) ff5401e339f14d9ba1ea708e73422c04--de6ea1afd34d4832aeb7acc9b03c4de9 67ec538ca3f44b5180eb7897e75c6d83 de6ea1afd34d4832aeb7acc9b03c4de9--67ec538ca3f44b5180eb7897e75c6d83 edfa2dde45ee4792a3e6a239e61c5fb9 RX(theta₂₂) 67ec538ca3f44b5180eb7897e75c6d83--edfa2dde45ee4792a3e6a239e61c5fb9 bba9b113b2e049a5a769677e8189211e RY(theta₂₈) edfa2dde45ee4792a3e6a239e61c5fb9--bba9b113b2e049a5a769677e8189211e 8628c9f746fd4f958cf5c4856932649a RX(theta₃₄) bba9b113b2e049a5a769677e8189211e--8628c9f746fd4f958cf5c4856932649a 236eeb39618342f6b2761e82bcf9b384 8628c9f746fd4f958cf5c4856932649a--236eeb39618342f6b2761e82bcf9b384 236eeb39618342f6b2761e82bcf9b384--d136e0f0d05e48c8a3a7aab22ec4a723 c706f7fdad704cc9be160f5c46e583be a2a32d77b9ba48288460127b180ac560 RX(theta₅) 08a5624be5a34ff8b89ad9a71fb306b7--a2a32d77b9ba48288460127b180ac560 39c84044fa994ca89cb9a4c15f3618a8 RY(theta₁₁) a2a32d77b9ba48288460127b180ac560--39c84044fa994ca89cb9a4c15f3618a8 0651007f49984a60976e26e94d66e08a RX(theta₁₇) 39c84044fa994ca89cb9a4c15f3618a8--0651007f49984a60976e26e94d66e08a 640694cd5ca64a79a5f2560991670e9e 0651007f49984a60976e26e94d66e08a--640694cd5ca64a79a5f2560991670e9e 93cab68fca6249e9b29ff1286f486434 RX(theta₂₃) 640694cd5ca64a79a5f2560991670e9e--93cab68fca6249e9b29ff1286f486434 67009309c57145ed8135beb65d70843b RY(theta₂₉) 93cab68fca6249e9b29ff1286f486434--67009309c57145ed8135beb65d70843b 0ca9a1ad00884e1f9e59b2bea4efbe29 RX(theta₃₅) 67009309c57145ed8135beb65d70843b--0ca9a1ad00884e1f9e59b2bea4efbe29 97b6590b8e9649febab021d68000e3f4 0ca9a1ad00884e1f9e59b2bea4efbe29--97b6590b8e9649febab021d68000e3f4 97b6590b8e9649febab021d68000e3f4--c706f7fdad704cc9be160f5c46e583be

Creating the QuantumModel

The rest of the procedure is the same as any other Qadence workflow. We start by defining a feature map for input encoding and an observable for output decoding.

from qadence import feature_map, BasisSet, ReuploadScaling
from qadence import Z, I

fm = feature_map(
    n_qubits = reg.n_qubits,
    param = "x",
    fm_type = BasisSet.CHEBYSHEV,
    reupload_scaling = ReuploadScaling.TOWER,
)

# Total magnetization
observable = add(Z(i) for i in range(reg.n_qubits))

And we have all the ingredients to initialize the QuantumModel:

from qadence import QuantumCircuit, QuantumModel

circuit = QuantumCircuit(reg, fm, da_ansatz)

model = QuantumModel(circuit, observable = observable)

Training the model

We can now train the model. We use a set of 20 equally spaced training points.

# Chebyshev FM does not accept x = -1, 1
xmin = -0.99
xmax = 0.99
n_train = 20

x_train = torch.linspace(xmin, xmax, steps = n_train)
y_train = f(x_train)

# Initial model prediction
y_pred_initial = model.expectation({"x": x_test}).detach()

And we use a simple custom training loop.

criterion = torch.nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr = 0.1)

n_epochs = 200

def loss_fn(x_train, y_train):
    out = model.expectation({"x": x_train})
    loss = criterion(out.squeeze(), y_train)
    return loss

for i in range(n_epochs):
    optimizer.zero_grad()
    loss = loss_fn(x_train, y_train)
    loss.backward()
    optimizer.step()

Results

Finally we can plot the resulting trained model.

y_pred_final = model.expectation({"x": x_test}).detach()

plt.plot(x_test, y_pred_initial, label = "Initial prediction")
plt.plot(x_test, y_pred_final, label = "Final prediction")
plt.scatter(x_train, y_train, label = "Training points")
plt.xlabel("x")
plt.ylabel("f(x)")
plt.legend()
plt.xlim((-1.1, 1.1))
plt.ylim((-1.1, 1.1))
2025-05-26T11:54:35.528435 image/svg+xml Matplotlib v3.10.3, https://matplotlib.org/