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-06-04T09:57:26.489884 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_f98f12a3f45e4973b7d2d66dffdab479 cluster_34f3ab53e9e64f6e9264405c8ef0f9b9 fd2080b378e74b50aebb68422f62da07 0 332ad0b1ffeb4b6aaf24468a00064737 RX(theta₀) fd2080b378e74b50aebb68422f62da07--332ad0b1ffeb4b6aaf24468a00064737 7142994b47464587a143b09269bf1fb1 1 322d6e0c7a044520b7208f56fd8502d4 RY(theta₆) 332ad0b1ffeb4b6aaf24468a00064737--322d6e0c7a044520b7208f56fd8502d4 0c63b4d0ff1f4fc2acdfaa0d435893bd RX(theta₁₂) 322d6e0c7a044520b7208f56fd8502d4--0c63b4d0ff1f4fc2acdfaa0d435893bd f13167a466f749bf8d779ef7f1238f2c 0c63b4d0ff1f4fc2acdfaa0d435893bd--f13167a466f749bf8d779ef7f1238f2c cae0855ca7704f608a8d0ddb6d7f199d RX(theta₁₈) f13167a466f749bf8d779ef7f1238f2c--cae0855ca7704f608a8d0ddb6d7f199d 41ed3d1d77054a92869482ab02ed2e3d RY(theta₂₄) cae0855ca7704f608a8d0ddb6d7f199d--41ed3d1d77054a92869482ab02ed2e3d ff35fb6ce0054ada8be14ccbb216af8d RX(theta₃₀) 41ed3d1d77054a92869482ab02ed2e3d--ff35fb6ce0054ada8be14ccbb216af8d fb03bf48153748d48b89a988779556c3 ff35fb6ce0054ada8be14ccbb216af8d--fb03bf48153748d48b89a988779556c3 c143f45c1d64428191418a249a63f90b fb03bf48153748d48b89a988779556c3--c143f45c1d64428191418a249a63f90b f35a93568fee403a92c7c1763e501ae1 3b8e72dcce6f4443992ecd9e2bd6b47a RX(theta₁) 7142994b47464587a143b09269bf1fb1--3b8e72dcce6f4443992ecd9e2bd6b47a 046d4d0af4684422bd02f3dcf5c165b7 2 736439cfd0e54f2daede9aae06a43537 RY(theta₇) 3b8e72dcce6f4443992ecd9e2bd6b47a--736439cfd0e54f2daede9aae06a43537 2a184777770e4914a12ba71bf403c38d RX(theta₁₃) 736439cfd0e54f2daede9aae06a43537--2a184777770e4914a12ba71bf403c38d f84f5a1c8e7f409bb9acdfc566dddb30 2a184777770e4914a12ba71bf403c38d--f84f5a1c8e7f409bb9acdfc566dddb30 d3066dbfc3ea45f4bfca6f2eaf0e316a RX(theta₁₉) f84f5a1c8e7f409bb9acdfc566dddb30--d3066dbfc3ea45f4bfca6f2eaf0e316a b9178293140445d8b6c3b6f68e64f3e6 RY(theta₂₅) d3066dbfc3ea45f4bfca6f2eaf0e316a--b9178293140445d8b6c3b6f68e64f3e6 6172796b30204b4998706d9cbac9b47e RX(theta₃₁) b9178293140445d8b6c3b6f68e64f3e6--6172796b30204b4998706d9cbac9b47e 826cf0550d8b455baaf3618b4a5f9d03 6172796b30204b4998706d9cbac9b47e--826cf0550d8b455baaf3618b4a5f9d03 826cf0550d8b455baaf3618b4a5f9d03--f35a93568fee403a92c7c1763e501ae1 e8c5f4c7c11243d5b996cfc0b8b465ab aa1eb49498a944359b13a7ceeeb2148d RX(theta₂) 046d4d0af4684422bd02f3dcf5c165b7--aa1eb49498a944359b13a7ceeeb2148d 11c43b9a965d443093ad672cba3e8964 3 5a6c6617e9f945408c512fc6a7d83623 RY(theta₈) aa1eb49498a944359b13a7ceeeb2148d--5a6c6617e9f945408c512fc6a7d83623 a9bb7f84860041708f6b1d1926846448 RX(theta₁₄) 5a6c6617e9f945408c512fc6a7d83623--a9bb7f84860041708f6b1d1926846448 2d47efc54a404f98a5d5382f37d6253e HamEvo a9bb7f84860041708f6b1d1926846448--2d47efc54a404f98a5d5382f37d6253e e892c4a743e345229d9c0e2c1106a98f RX(theta₂₀) 2d47efc54a404f98a5d5382f37d6253e--e892c4a743e345229d9c0e2c1106a98f c0ad875059314f7fbfc0d9952a231938 RY(theta₂₆) e892c4a743e345229d9c0e2c1106a98f--c0ad875059314f7fbfc0d9952a231938 b4807ec2a296490b8b66ea5a0eaf2f0c RX(theta₃₂) c0ad875059314f7fbfc0d9952a231938--b4807ec2a296490b8b66ea5a0eaf2f0c 54f3ae13db6249b4bba5ffbfc850b75d HamEvo b4807ec2a296490b8b66ea5a0eaf2f0c--54f3ae13db6249b4bba5ffbfc850b75d 54f3ae13db6249b4bba5ffbfc850b75d--e8c5f4c7c11243d5b996cfc0b8b465ab fac47118359e4adb877a1bb00cea482f 84133980af2f49e7949a23aeb913bae6 RX(theta₃) 11c43b9a965d443093ad672cba3e8964--84133980af2f49e7949a23aeb913bae6 9392caa4f6bd41378979b3008548cb57 4 af27fa20b9444f94bf77b83309ea7bd7 RY(theta₉) 84133980af2f49e7949a23aeb913bae6--af27fa20b9444f94bf77b83309ea7bd7 c2aeff72efb94acf85f54bc24992e37d RX(theta₁₅) af27fa20b9444f94bf77b83309ea7bd7--c2aeff72efb94acf85f54bc24992e37d f466db493a3a4783ad1df03a482f34b9 t = theta_t₀ c2aeff72efb94acf85f54bc24992e37d--f466db493a3a4783ad1df03a482f34b9 aec025f5a03644fc9d016a43e311e630 RX(theta₂₁) f466db493a3a4783ad1df03a482f34b9--aec025f5a03644fc9d016a43e311e630 029a7425a0fd44ce8365deddd22b128f RY(theta₂₇) aec025f5a03644fc9d016a43e311e630--029a7425a0fd44ce8365deddd22b128f 63aed91daf2348e9b0da177cb82c18c1 RX(theta₃₃) 029a7425a0fd44ce8365deddd22b128f--63aed91daf2348e9b0da177cb82c18c1 21e4ef26788445c9ba62f46eedfa86ba t = theta_t₁ 63aed91daf2348e9b0da177cb82c18c1--21e4ef26788445c9ba62f46eedfa86ba 21e4ef26788445c9ba62f46eedfa86ba--fac47118359e4adb877a1bb00cea482f 31387ace57de4b819239bfaa17415d1c 9e113f59f2f84ec6a82712a991a95a3c RX(theta₄) 9392caa4f6bd41378979b3008548cb57--9e113f59f2f84ec6a82712a991a95a3c 78aae563dcc14a2baef4a99845bb6ad1 5 afbc649a4bd740a5ad743ada27df2cc7 RY(theta₁₀) 9e113f59f2f84ec6a82712a991a95a3c--afbc649a4bd740a5ad743ada27df2cc7 b199fd84f6fb43069bbfce67d94ec9d9 RX(theta₁₆) afbc649a4bd740a5ad743ada27df2cc7--b199fd84f6fb43069bbfce67d94ec9d9 071d39aa6f8a4073bbaab28af66bd71b b199fd84f6fb43069bbfce67d94ec9d9--071d39aa6f8a4073bbaab28af66bd71b c1eef34d47444d459db4a77352f0382e RX(theta₂₂) 071d39aa6f8a4073bbaab28af66bd71b--c1eef34d47444d459db4a77352f0382e 04dbc6efa2134066aa4384712bc646df RY(theta₂₈) c1eef34d47444d459db4a77352f0382e--04dbc6efa2134066aa4384712bc646df 372a7a3c48e94d8d97989f896a4e8d9b RX(theta₃₄) 04dbc6efa2134066aa4384712bc646df--372a7a3c48e94d8d97989f896a4e8d9b d4827bbbdb224829993e1b382bbab916 372a7a3c48e94d8d97989f896a4e8d9b--d4827bbbdb224829993e1b382bbab916 d4827bbbdb224829993e1b382bbab916--31387ace57de4b819239bfaa17415d1c c05fc38ee85c4b4a8fcc4c660de9144f 57ae76e194c14234ba8a0800f7b9859f RX(theta₅) 78aae563dcc14a2baef4a99845bb6ad1--57ae76e194c14234ba8a0800f7b9859f 9eb0cb7a80af48fb9d27db5fb7ef4fa0 RY(theta₁₁) 57ae76e194c14234ba8a0800f7b9859f--9eb0cb7a80af48fb9d27db5fb7ef4fa0 e465a3f4975e4f3e81f539a67cec1f5d RX(theta₁₇) 9eb0cb7a80af48fb9d27db5fb7ef4fa0--e465a3f4975e4f3e81f539a67cec1f5d 11eb6eb706d847a7bdd333a5b9e5b8de e465a3f4975e4f3e81f539a67cec1f5d--11eb6eb706d847a7bdd333a5b9e5b8de 1366fc8a7a514a3c8eeb759ec730ee1c RX(theta₂₃) 11eb6eb706d847a7bdd333a5b9e5b8de--1366fc8a7a514a3c8eeb759ec730ee1c e7114f5693ec43d69fc45c14f253c4fc RY(theta₂₉) 1366fc8a7a514a3c8eeb759ec730ee1c--e7114f5693ec43d69fc45c14f253c4fc 3e4cf94bf6ef4aa7a91c421404bc772f RX(theta₃₅) e7114f5693ec43d69fc45c14f253c4fc--3e4cf94bf6ef4aa7a91c421404bc772f 6ee4cab1b8974d48b4b5e4f191660d6d 3e4cf94bf6ef4aa7a91c421404bc772f--6ee4cab1b8974d48b4b5e4f191660d6d 6ee4cab1b8974d48b4b5e4f191660d6d--c05fc38ee85c4b4a8fcc4c660de9144f

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-06-04T09:57:34.919024 image/svg+xml Matplotlib v3.10.3, https://matplotlib.org/