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))
2024-07-10T14:29:31.653080 image/svg+xml Matplotlib v3.7.5, 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_daf11696d4c14a89a13b6da72472b7f2 cluster_ec8c991dd26e47729be16119d85388ac 46637a7611d74c738ec0c0561ab6ee93 0 4eb7219c6b2a4a5291f1c60e32eb27d1 RX(theta₀) 46637a7611d74c738ec0c0561ab6ee93--4eb7219c6b2a4a5291f1c60e32eb27d1 ac300e8266754708a632f7fe1e7bab4a 1 955816a7298848c38fbab776378b958d RY(theta₆) 4eb7219c6b2a4a5291f1c60e32eb27d1--955816a7298848c38fbab776378b958d 9a675b74294c45688ef0dbe08c52f1b0 RX(theta₁₂) 955816a7298848c38fbab776378b958d--9a675b74294c45688ef0dbe08c52f1b0 43a33e997bb84c0b84236dc8fa548455 9a675b74294c45688ef0dbe08c52f1b0--43a33e997bb84c0b84236dc8fa548455 156ffe4d230f480ea5f583422df3ab25 RX(theta₁₈) 43a33e997bb84c0b84236dc8fa548455--156ffe4d230f480ea5f583422df3ab25 b58c72d56aaf4fc6be663a3d4b15cc92 RY(theta₂₄) 156ffe4d230f480ea5f583422df3ab25--b58c72d56aaf4fc6be663a3d4b15cc92 6bdb03aad5b64859b8bc350a5b3f6d8c RX(theta₃₀) b58c72d56aaf4fc6be663a3d4b15cc92--6bdb03aad5b64859b8bc350a5b3f6d8c 8b03b5f4f0d94c598c93bb250b8fa33c 6bdb03aad5b64859b8bc350a5b3f6d8c--8b03b5f4f0d94c598c93bb250b8fa33c 1614b25b00d344f79cd21fb0e55447ba 8b03b5f4f0d94c598c93bb250b8fa33c--1614b25b00d344f79cd21fb0e55447ba 7470a1d62db24c2e960229ff7a63aea2 0dc4d993692a4bc499cd5e23bd70e7d2 RX(theta₁) ac300e8266754708a632f7fe1e7bab4a--0dc4d993692a4bc499cd5e23bd70e7d2 65847a544dda4fcc8c58be77f5f3ab22 2 39546031817145c0ae95372dd72f8bf1 RY(theta₇) 0dc4d993692a4bc499cd5e23bd70e7d2--39546031817145c0ae95372dd72f8bf1 5845614894294436a3a36cbc98473345 RX(theta₁₃) 39546031817145c0ae95372dd72f8bf1--5845614894294436a3a36cbc98473345 512e0bcfe2fc45c3be9577be32548d4e 5845614894294436a3a36cbc98473345--512e0bcfe2fc45c3be9577be32548d4e 4e39b623fc4e4676a66d4379a5726c14 RX(theta₁₉) 512e0bcfe2fc45c3be9577be32548d4e--4e39b623fc4e4676a66d4379a5726c14 ac79e8f5192f46c4a07629567e99816c RY(theta₂₅) 4e39b623fc4e4676a66d4379a5726c14--ac79e8f5192f46c4a07629567e99816c 425dadc97c934f288801498073de27c5 RX(theta₃₁) ac79e8f5192f46c4a07629567e99816c--425dadc97c934f288801498073de27c5 fa4a6f294b9c4d90a85d79b94b50047b 425dadc97c934f288801498073de27c5--fa4a6f294b9c4d90a85d79b94b50047b fa4a6f294b9c4d90a85d79b94b50047b--7470a1d62db24c2e960229ff7a63aea2 936bca0afc51491ca381045d564728b9 f494e79d545e4254ac5583a3470dc5f1 RX(theta₂) 65847a544dda4fcc8c58be77f5f3ab22--f494e79d545e4254ac5583a3470dc5f1 f2cf752857c342f892260dd92b3e05bb 3 87431dda6f2f4f869ff814624ec134ce RY(theta₈) f494e79d545e4254ac5583a3470dc5f1--87431dda6f2f4f869ff814624ec134ce f016459ac51649989c51c8a3d8cfda36 RX(theta₁₄) 87431dda6f2f4f869ff814624ec134ce--f016459ac51649989c51c8a3d8cfda36 30c48e4134a3487d9444c11d5a88f328 HamEvo f016459ac51649989c51c8a3d8cfda36--30c48e4134a3487d9444c11d5a88f328 9292704db9b046b3816c18148942bf43 RX(theta₂₀) 30c48e4134a3487d9444c11d5a88f328--9292704db9b046b3816c18148942bf43 eb384fd3b22941139dc07d1363e8c321 RY(theta₂₆) 9292704db9b046b3816c18148942bf43--eb384fd3b22941139dc07d1363e8c321 7454288827ac469b842f78824d953682 RX(theta₃₂) eb384fd3b22941139dc07d1363e8c321--7454288827ac469b842f78824d953682 add0bb696efa4462b43fb36982bbdc05 HamEvo 7454288827ac469b842f78824d953682--add0bb696efa4462b43fb36982bbdc05 add0bb696efa4462b43fb36982bbdc05--936bca0afc51491ca381045d564728b9 ea4ff121bbd64c8a8107949c080a1f48 c9cd107d34f8447f8536d5bdf3204445 RX(theta₃) f2cf752857c342f892260dd92b3e05bb--c9cd107d34f8447f8536d5bdf3204445 9614602f62594fd0902eb7ffdf060afc 4 24adc07f07b948378d7c85659213c6c4 RY(theta₉) c9cd107d34f8447f8536d5bdf3204445--24adc07f07b948378d7c85659213c6c4 a6b9c41fcad045edb53b7320f605e1f2 RX(theta₁₅) 24adc07f07b948378d7c85659213c6c4--a6b9c41fcad045edb53b7320f605e1f2 f7ce995d09a84cfc9ff5e324a47f403a t = theta_t₀ a6b9c41fcad045edb53b7320f605e1f2--f7ce995d09a84cfc9ff5e324a47f403a a47eb912f3174747ad72b652ef25b2fa RX(theta₂₁) f7ce995d09a84cfc9ff5e324a47f403a--a47eb912f3174747ad72b652ef25b2fa a9050960cec744a5826c2d4701523957 RY(theta₂₇) a47eb912f3174747ad72b652ef25b2fa--a9050960cec744a5826c2d4701523957 9db9b5fad9e540fe98312b5fd9539df4 RX(theta₃₃) a9050960cec744a5826c2d4701523957--9db9b5fad9e540fe98312b5fd9539df4 707ea4e2cae24312ae976dc6c3ed99b4 t = theta_t₁ 9db9b5fad9e540fe98312b5fd9539df4--707ea4e2cae24312ae976dc6c3ed99b4 707ea4e2cae24312ae976dc6c3ed99b4--ea4ff121bbd64c8a8107949c080a1f48 2bb6a911d752467c932b000abb729586 eabc440b9c2a4cb49e4f753ae09abc2b RX(theta₄) 9614602f62594fd0902eb7ffdf060afc--eabc440b9c2a4cb49e4f753ae09abc2b f9a9017e778e4007b5e9d47135e1e931 5 0ff70af5e0e5477e92b82d83a34f5479 RY(theta₁₀) eabc440b9c2a4cb49e4f753ae09abc2b--0ff70af5e0e5477e92b82d83a34f5479 a2ae695a4cb74226a6a46d6623d73338 RX(theta₁₆) 0ff70af5e0e5477e92b82d83a34f5479--a2ae695a4cb74226a6a46d6623d73338 5208847b35e84b459c19560cdf1d3842 a2ae695a4cb74226a6a46d6623d73338--5208847b35e84b459c19560cdf1d3842 b29ff033ef494e8cbb69040b11cd7ef4 RX(theta₂₂) 5208847b35e84b459c19560cdf1d3842--b29ff033ef494e8cbb69040b11cd7ef4 ae0175904a4d472499b6c57b3bb95572 RY(theta₂₈) b29ff033ef494e8cbb69040b11cd7ef4--ae0175904a4d472499b6c57b3bb95572 5b71f7d9e0b349d7a4f016fc2744743e RX(theta₃₄) ae0175904a4d472499b6c57b3bb95572--5b71f7d9e0b349d7a4f016fc2744743e 1e62defcaafd4c1085fdbfea84f94ada 5b71f7d9e0b349d7a4f016fc2744743e--1e62defcaafd4c1085fdbfea84f94ada 1e62defcaafd4c1085fdbfea84f94ada--2bb6a911d752467c932b000abb729586 e7ec01c2c5bb421abb455a186186c17c 4bed592f626b4791855a625554eb833d RX(theta₅) f9a9017e778e4007b5e9d47135e1e931--4bed592f626b4791855a625554eb833d b6bd054f6a4a440891da80c2d7f7e4ea RY(theta₁₁) 4bed592f626b4791855a625554eb833d--b6bd054f6a4a440891da80c2d7f7e4ea 2f0909dec7794898a7df57979152e175 RX(theta₁₇) b6bd054f6a4a440891da80c2d7f7e4ea--2f0909dec7794898a7df57979152e175 e7d4b3052b8a498c8127c0b1f071184e 2f0909dec7794898a7df57979152e175--e7d4b3052b8a498c8127c0b1f071184e d8c2d4a0076f4bdd803e025539413875 RX(theta₂₃) e7d4b3052b8a498c8127c0b1f071184e--d8c2d4a0076f4bdd803e025539413875 f39b6ed7f47a427e9dde2c06d861716b RY(theta₂₉) d8c2d4a0076f4bdd803e025539413875--f39b6ed7f47a427e9dde2c06d861716b 45ac444e341143268de0aac93168d9ca RX(theta₃₅) f39b6ed7f47a427e9dde2c06d861716b--45ac444e341143268de0aac93168d9ca e12ea76ef5e544d9bb401a6b8ad3732a 45ac444e341143268de0aac93168d9ca--e12ea76ef5e544d9bb401a6b8ad3732a e12ea76ef5e544d9bb401a6b8ad3732a--e7ec01c2c5bb421abb455a186186c17c

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))
2024-07-10T14:29:37.022289 image/svg+xml Matplotlib v3.7.5, https://matplotlib.org/