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-03-13T17:13:55.176855 image/svg+xml Matplotlib v3.10.1, 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_f5266b90ab5448a79797beba70bf4ff7 cluster_7bd7a35666a94dc5987f81a60ff0251f 98aa2c7fe3794176a47a5f5b68d12651 0 6cb851fedda642fbb56e73c2f2bed50b RX(theta₀) 98aa2c7fe3794176a47a5f5b68d12651--6cb851fedda642fbb56e73c2f2bed50b a56cf4c280e942a496aaf2be21597fb1 1 15f15c2844764a06855a49035ba4104e RY(theta₆) 6cb851fedda642fbb56e73c2f2bed50b--15f15c2844764a06855a49035ba4104e eef1937f71aa429c95384721d16b1521 RX(theta₁₂) 15f15c2844764a06855a49035ba4104e--eef1937f71aa429c95384721d16b1521 ef5d4961f9024cffb24ecf105000d995 eef1937f71aa429c95384721d16b1521--ef5d4961f9024cffb24ecf105000d995 9143b1f94b95487f9b6460cacaf838ce RX(theta₁₈) ef5d4961f9024cffb24ecf105000d995--9143b1f94b95487f9b6460cacaf838ce 4ec28930f2ae425a9a50ac947560edc9 RY(theta₂₄) 9143b1f94b95487f9b6460cacaf838ce--4ec28930f2ae425a9a50ac947560edc9 1f843a3cf0144fe39ddade3273b44ef4 RX(theta₃₀) 4ec28930f2ae425a9a50ac947560edc9--1f843a3cf0144fe39ddade3273b44ef4 a2dd19bfb8c143708f32eeb30bd6d9d7 1f843a3cf0144fe39ddade3273b44ef4--a2dd19bfb8c143708f32eeb30bd6d9d7 1f20487aa40b4179b3c26fb35059f5ae a2dd19bfb8c143708f32eeb30bd6d9d7--1f20487aa40b4179b3c26fb35059f5ae ccbc600357464199bbf7698f4a259f83 b6435ce271c64c76b8482300abe4889b RX(theta₁) a56cf4c280e942a496aaf2be21597fb1--b6435ce271c64c76b8482300abe4889b 8b1d6e73d7a34aa4a8f150e0628d1db5 2 cad4495022f84fe38e9cb800637f118a RY(theta₇) b6435ce271c64c76b8482300abe4889b--cad4495022f84fe38e9cb800637f118a 70690912655f45f6b201a909a23090b2 RX(theta₁₃) cad4495022f84fe38e9cb800637f118a--70690912655f45f6b201a909a23090b2 9c3ffbe7252b4a208c3c791b65326589 70690912655f45f6b201a909a23090b2--9c3ffbe7252b4a208c3c791b65326589 321053bc19f843c28261782e40c5b5ac RX(theta₁₉) 9c3ffbe7252b4a208c3c791b65326589--321053bc19f843c28261782e40c5b5ac 8db30f9d9e8241558d3a7023a0826c73 RY(theta₂₅) 321053bc19f843c28261782e40c5b5ac--8db30f9d9e8241558d3a7023a0826c73 86846ba5bf784da8a486b71536c37265 RX(theta₃₁) 8db30f9d9e8241558d3a7023a0826c73--86846ba5bf784da8a486b71536c37265 f971f8ae30f2441286b68812c449d831 86846ba5bf784da8a486b71536c37265--f971f8ae30f2441286b68812c449d831 f971f8ae30f2441286b68812c449d831--ccbc600357464199bbf7698f4a259f83 204264602b384edfa8991fc4908ac80c d74c24d8904a4464bd8952710a582fc2 RX(theta₂) 8b1d6e73d7a34aa4a8f150e0628d1db5--d74c24d8904a4464bd8952710a582fc2 6c0a1475da25435c8d20a801f0b563cd 3 77e24859a9654bb9babddfbe5955f764 RY(theta₈) d74c24d8904a4464bd8952710a582fc2--77e24859a9654bb9babddfbe5955f764 68dc08090d2241f58be4a56c5afb9222 RX(theta₁₄) 77e24859a9654bb9babddfbe5955f764--68dc08090d2241f58be4a56c5afb9222 360bb7aacb9047cd9d71655fc1080a25 HamEvo 68dc08090d2241f58be4a56c5afb9222--360bb7aacb9047cd9d71655fc1080a25 9ad985e135c845faa2015c1150a67c10 RX(theta₂₀) 360bb7aacb9047cd9d71655fc1080a25--9ad985e135c845faa2015c1150a67c10 efb2f1b917284a23882370e6848d1486 RY(theta₂₆) 9ad985e135c845faa2015c1150a67c10--efb2f1b917284a23882370e6848d1486 ae0c02919cba41d39cd65e597289ab3e RX(theta₃₂) efb2f1b917284a23882370e6848d1486--ae0c02919cba41d39cd65e597289ab3e 851da8781b4140a0a949ff5f5b0cd9c8 HamEvo ae0c02919cba41d39cd65e597289ab3e--851da8781b4140a0a949ff5f5b0cd9c8 851da8781b4140a0a949ff5f5b0cd9c8--204264602b384edfa8991fc4908ac80c cfb987a3d6ac421cbbac5867477b1dc9 08d24f15ee00451a982156925373165f RX(theta₃) 6c0a1475da25435c8d20a801f0b563cd--08d24f15ee00451a982156925373165f c0a2cfb6e2a746e0b5390dda0b423867 4 472de18fa08849b2863cb7becc20fc6b RY(theta₉) 08d24f15ee00451a982156925373165f--472de18fa08849b2863cb7becc20fc6b 96b4429980a544e6a0a594f7cf7447b4 RX(theta₁₅) 472de18fa08849b2863cb7becc20fc6b--96b4429980a544e6a0a594f7cf7447b4 40a5cae89f71488f932f22e94c45b312 t = theta_t₀ 96b4429980a544e6a0a594f7cf7447b4--40a5cae89f71488f932f22e94c45b312 69a67dad1da2435caecc27aa87551ead RX(theta₂₁) 40a5cae89f71488f932f22e94c45b312--69a67dad1da2435caecc27aa87551ead de97d0339d1341deacabf54abd162475 RY(theta₂₇) 69a67dad1da2435caecc27aa87551ead--de97d0339d1341deacabf54abd162475 671db70cedf845da9bc9f41699b5cc89 RX(theta₃₃) de97d0339d1341deacabf54abd162475--671db70cedf845da9bc9f41699b5cc89 036e7851bbc246c395283ba9aefae727 t = theta_t₁ 671db70cedf845da9bc9f41699b5cc89--036e7851bbc246c395283ba9aefae727 036e7851bbc246c395283ba9aefae727--cfb987a3d6ac421cbbac5867477b1dc9 ef3ef7603cb740cca8946ae84e5645ed 7f2d5a590db649ce980674de2e04fb56 RX(theta₄) c0a2cfb6e2a746e0b5390dda0b423867--7f2d5a590db649ce980674de2e04fb56 52b7edc1b4994357819f290970cb6f28 5 6d267482cec8483aa1e7630a50ebb13d RY(theta₁₀) 7f2d5a590db649ce980674de2e04fb56--6d267482cec8483aa1e7630a50ebb13d 9e0f135d75ec429a9bcfb08f6744054f RX(theta₁₆) 6d267482cec8483aa1e7630a50ebb13d--9e0f135d75ec429a9bcfb08f6744054f c0ba0e07bf8546eda55d595a1ab61294 9e0f135d75ec429a9bcfb08f6744054f--c0ba0e07bf8546eda55d595a1ab61294 dba29916a1b245e4ba38b32820f14911 RX(theta₂₂) c0ba0e07bf8546eda55d595a1ab61294--dba29916a1b245e4ba38b32820f14911 3a6a3f12a13941e0a4d4c30b3de0a3d1 RY(theta₂₈) dba29916a1b245e4ba38b32820f14911--3a6a3f12a13941e0a4d4c30b3de0a3d1 66697e266dc04d339a68698b0007b005 RX(theta₃₄) 3a6a3f12a13941e0a4d4c30b3de0a3d1--66697e266dc04d339a68698b0007b005 5204569052d149e6994c714a5ed6e56f 66697e266dc04d339a68698b0007b005--5204569052d149e6994c714a5ed6e56f 5204569052d149e6994c714a5ed6e56f--ef3ef7603cb740cca8946ae84e5645ed 5bdb1159d8da44dda240af84cdb0b47d 02ae9608745d4dbfa04704097c580f78 RX(theta₅) 52b7edc1b4994357819f290970cb6f28--02ae9608745d4dbfa04704097c580f78 d6bfd8fa9f504d97aee2a7f4f7f5734f RY(theta₁₁) 02ae9608745d4dbfa04704097c580f78--d6bfd8fa9f504d97aee2a7f4f7f5734f 721e22b1589f467c949409f49b31e4fd RX(theta₁₇) d6bfd8fa9f504d97aee2a7f4f7f5734f--721e22b1589f467c949409f49b31e4fd 9d04e54e1f8342e8909e00ab2bbd8a57 721e22b1589f467c949409f49b31e4fd--9d04e54e1f8342e8909e00ab2bbd8a57 95b5f78430fb404c8ee8a32605101f5c RX(theta₂₃) 9d04e54e1f8342e8909e00ab2bbd8a57--95b5f78430fb404c8ee8a32605101f5c f63a39b88c6a47dab20a9b4693ce1a37 RY(theta₂₉) 95b5f78430fb404c8ee8a32605101f5c--f63a39b88c6a47dab20a9b4693ce1a37 cfdf86c30531407e8470e431346f63c1 RX(theta₃₅) f63a39b88c6a47dab20a9b4693ce1a37--cfdf86c30531407e8470e431346f63c1 c13f0ca96a8947a7a7d18e856a95b891 cfdf86c30531407e8470e431346f63c1--c13f0ca96a8947a7a7d18e856a95b891 c13f0ca96a8947a7a7d18e856a95b891--5bdb1159d8da44dda240af84cdb0b47d

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-03-13T17:14:03.859883 image/svg+xml Matplotlib v3.10.1, https://matplotlib.org/