Skip to content

Commit d06fcb0

Browse files
committed
update
1 parent a4e03b9 commit d06fcb0

File tree

4 files changed

+1029
-0
lines changed

4 files changed

+1029
-0
lines changed
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "ceab64a3",
6+
"metadata": {
7+
"editable": true
8+
},
9+
"source": [
10+
"<!-- HTML file automatically generated from DocOnce source (https://github.com/doconce/doconce/)\n",
11+
"doconce format html vqecode44.do.txt -->\n",
12+
"<!-- dom:TITLE: VQE Simulation for a Two-Qubit Hamiltonian -->"
13+
]
14+
},
15+
{
16+
"cell_type": "markdown",
17+
"id": "5d72e03a",
18+
"metadata": {
19+
"editable": true
20+
},
21+
"source": [
22+
"# VQE Simulation for a Two-Qubit Hamiltonian\n",
23+
"**Morten Hjorth-Jensen**, Department of Physics, University of Oslo, Norway\n",
24+
"\n",
25+
"Date: **January 21, 2026**"
26+
]
27+
},
28+
{
29+
"cell_type": "markdown",
30+
"id": "deebcb04",
31+
"metadata": {
32+
"editable": true
33+
},
34+
"source": [
35+
"## Simple Hamiltonian\n",
36+
"We consider the given 4×4 two-qubit Hamiltonian"
37+
]
38+
},
39+
{
40+
"cell_type": "markdown",
41+
"id": "a942189d",
42+
"metadata": {
43+
"editable": true
44+
},
45+
"source": [
46+
"$$\n",
47+
"H = \\begin{pmatrix}\n",
48+
"e_1 + V_z & 0 & 0 & V_x \\\\\n",
49+
"0 & e_2 - V_z & V_x & 0 \\\\\n",
50+
"0 & H_x & e_3 - V_z & 0 \\\\\n",
51+
"H_x & 0 & 0 & e_4 + V_z\n",
52+
"\\end{pmatrix}.\n",
53+
"$$"
54+
]
55+
},
56+
{
57+
"cell_type": "markdown",
58+
"id": "7baed6f3",
59+
"metadata": {
60+
"editable": true
61+
},
62+
"source": [
63+
"Any two-qubit Hamiltonian can be expressed as a linear combination of tensor products of Pauli matrices (including the identity) . In general we write\n",
64+
"$$H = \\sum_{i,j\\in{I,X,Y,Z}} c_{ij},\\sigma_i\\otimes\\sigma_j,$$\n",
65+
"where the coefficients \\(c_{ij} = \\tfrac14\\Tr[(\\sigma_i\\otimes\\sigma_j)H]\\) are real for a Hermitian H. We implement this decomposition in code:"
66+
]
67+
},
68+
{
69+
"cell_type": "code",
70+
"execution_count": 1,
71+
"id": "02c45757",
72+
"metadata": {
73+
"collapsed": false,
74+
"editable": true
75+
},
76+
"outputs": [],
77+
"source": [
78+
"import numpy as np\n",
79+
"\n",
80+
"# Define 2x2 Pauli matrices\n",
81+
"I = np.array([[1,0],[0,1]])\n",
82+
"X = np.array([[0,1],[1,0]])\n",
83+
"Y = np.array([[0,-1j],[1j,0]])\n",
84+
"Z = np.array([[1,0],[0,-1]])\n",
85+
"\n",
86+
"# Example values for constants (can be changed)\n",
87+
"e1, e2, e3, e4 = 0.5, 1.0, 1.5, 2.0\n",
88+
"Vx, Vz, Hx = 0.2, 0.3, 0.4\n",
89+
"\n",
90+
"# Construct the Hamiltonian matrix H\n",
91+
"H = np.zeros((4,4), dtype=complex)\n",
92+
"H[0,0] = e1 + Vz\n",
93+
"H[1,1] = e2 - Vz\n",
94+
"H[2,2] = e3 - Vz\n",
95+
"H[3,3] = e4 + Vz\n",
96+
"H[0,3] = Vx\n",
97+
"H[3,0] = Hx\n",
98+
"H[1,2] = Vx\n",
99+
"H[2,1] = Hx\n",
100+
"\n",
101+
"# Decompose H into Pauli basis coefficients c_{ij}\n",
102+
"paulis = {'I': I, 'X': X, 'Y': Y, 'Z': Z}\n",
103+
"coeffs = {}\n",
104+
"for i, Ai in paulis.items():\n",
105+
" for j, Bj in paulis.items():\n",
106+
" P = np.kron(Ai, Bj)\n",
107+
" c = np.trace(P.dot(H)) / 4.0\n",
108+
" if abs(c) > 1e-8: # keep non-zero terms\n",
109+
" coeffs[(i,j)] = np.real_if_close(c)\n",
110+
"\n",
111+
"# Display non-zero Pauli terms of H\n",
112+
"for (i,j), c in coeffs.items():\n",
113+
" print(f\"{c:+.4f} * {i}⊗{j}\")"
114+
]
115+
},
116+
{
117+
"cell_type": "markdown",
118+
"id": "eea60454",
119+
"metadata": {
120+
"editable": true
121+
},
122+
"source": [
123+
"The code prints the nonzero terms of the Hamiltonian in the Pauli basis. For example, with the above values one finds. yielding"
124+
]
125+
},
126+
{
127+
"cell_type": "markdown",
128+
"id": "697434ba",
129+
"metadata": {
130+
"editable": true
131+
},
132+
"source": [
133+
"$$\n",
134+
"H = 2.5\\,I\\otimes I +0.5\\,X\\otimes X-0.5\\,I\\otimes Z-1.0\\,Z\\otimes I+0.3\\,Z\\otimes Z. T\n",
135+
"$$"
136+
]
137+
},
138+
{
139+
"cell_type": "markdown",
140+
"id": "0681554e",
141+
"metadata": {
142+
"editable": true
143+
},
144+
"source": [
145+
"This matches the matrix and confirms the decomposition in Pauli products."
146+
]
147+
},
148+
{
149+
"cell_type": "markdown",
150+
"id": "722b47ea",
151+
"metadata": {
152+
"editable": true
153+
},
154+
"source": [
155+
"## Ansatz and Expectation Values\n",
156+
"\n",
157+
"We use a simple hardware-efficient ansatz: apply parameterized single-qubit rotations (here R_y(\\theta) and R_z(\\phi) gates) on each qubit, followed by an entangling CNOT gate . Concretely, starting from $|00\\rangle$, we apply\n",
158+
"$U(\\theta_0,\\theta_1,\\phi_0,\\phi_1) = \\bigl(R_z(\\phi_0)R_y(\\theta_0)\\otimes R_z(\\phi_1)R_y(\\theta_1)\\bigr)\\,\\text{CNOT}_{0\\to1}$.\n",
159+
"This prepares a trial state $|\\psi(\\boldsymbol\\theta)\\rangle$. The energy expectation is\n",
160+
"$\\langle E\\rangle = \\langle\\psi|H|\\psi\\rangle = \\sum_{(i,j)} c_{ij}\\,\\langle\\psi|\\sigma_i\\otimes\\sigma_j|\\psi\\rangle$. In other words, we compute the expectation of each Pauli term and sum them . The code below constructs the ansatz state and computes \\langle H\\rangle:"
161+
]
162+
},
163+
{
164+
"cell_type": "code",
165+
"execution_count": 2,
166+
"id": "6e6f9366",
167+
"metadata": {
168+
"collapsed": false,
169+
"editable": true
170+
},
171+
"outputs": [],
172+
"source": [
173+
"# Define single-qubit rotation gates\n",
174+
"def RY(theta):\n",
175+
" return np.array([[np.cos(theta/2), -np.sin(theta/2)],\n",
176+
" [np.sin(theta/2), np.cos(theta/2)]])\n",
177+
"def RZ(theta):\n",
178+
" return np.array([[np.exp(-1j*theta/2), 0],\n",
179+
" [0, np.exp(1j*theta/2)]])\n",
180+
"\n",
181+
"# 4x4 CNOT (control=qubit0, target=qubit1)\n",
182+
"CNOT = np.array([[1,0,0,0],\n",
183+
" [0,1,0,0],\n",
184+
" [0,0,0,1],\n",
185+
" [0,0,1,0]])\n",
186+
"\n",
187+
"# Initial state |00> as a vector\n",
188+
"psi0 = np.kron(np.array([1,0]), np.array([1,0]))\n",
189+
"\n",
190+
"# Function to prepare ansatz state and compute energy\n",
191+
"def energy_of_params(params):\n",
192+
" theta0, theta1, phi0, phi1 = params\n",
193+
" # Apply RY rotations on each qubit\n",
194+
" U1 = np.kron(RY(theta0), RY(theta1))\n",
195+
" psi = U1.dot(psi0)\n",
196+
" # Apply RZ rotations on each qubit\n",
197+
" U2 = np.kron(RZ(phi0), RZ(phi1))\n",
198+
" psi = U2.dot(psi)\n",
199+
" # Apply entangling CNOT gate\n",
200+
" psi = CNOT.dot(psi)\n",
201+
" # Compute ⟨ψ|H|ψ⟩\n",
202+
" return np.real(np.vdot(psi, H.dot(psi)))"
203+
]
204+
},
205+
{
206+
"cell_type": "markdown",
207+
"id": "8d0b3b41",
208+
"metadata": {
209+
"editable": true
210+
},
211+
"source": [
212+
"Here energy$\\_$of$\\_$params(params) returns the variational energy $\\langle\\psi|H|\\psi\\rangle$.\n",
213+
"This function automatically sums over the Pauli terms because we directly use the matrix H; one could\n",
214+
"equivalently $sum c_{ij}\\,\\langle\\psi|\\sigma_i\\otimes\\sigma_j|\\psi\\rangle$ term-by-term.\n",
215+
"By the variational principle, this expectation is always ≥ the true ground-state energy."
216+
]
217+
},
218+
{
219+
"cell_type": "markdown",
220+
"id": "adae4dd0",
221+
"metadata": {
222+
"editable": true
223+
},
224+
"source": [
225+
"## Classical Optimization\n",
226+
"\n",
227+
"We now perform a classical minimization over the parameters $\\{\\theta_0,\\theta_1,\\phi_0,\\phi_1\\}$ to find the lowest energy. We can use, for example, the Nelder–Mead routine from SciPy or a simple gradient descent. For illustration we use SciPy’s minimize (Nelder–Mead) on our 4-parameter ansatz:"
228+
]
229+
},
230+
{
231+
"cell_type": "code",
232+
"execution_count": 3,
233+
"id": "48935bc7",
234+
"metadata": {
235+
"collapsed": false,
236+
"editable": true
237+
},
238+
"outputs": [],
239+
"source": [
240+
"from scipy.optimize import minimize\n",
241+
"\n",
242+
"# Random initial guess for parameters\n",
243+
"init_params = np.random.rand(4) * 2*np.pi\n",
244+
"\n",
245+
"# Minimize the energy\n",
246+
"res = minimize(energy_of_params, init_params, method='Nelder-Mead')\n",
247+
"theta_opt = res.x\n",
248+
"E_vqe = res.fun\n",
249+
"print(\"Optimized parameters:\", theta_opt)\n",
250+
"print(\"VQE ground state energy (approx):\", E_vqe)"
251+
]
252+
},
253+
{
254+
"cell_type": "markdown",
255+
"id": "3b27191a",
256+
"metadata": {
257+
"editable": true
258+
},
259+
"source": [
260+
"After optimization, res.fun holds the minimal energy found. We also compare to the exact ground-state energy (the lowest eigenvalue of H) computed by direct diagonalization :"
261+
]
262+
},
263+
{
264+
"cell_type": "code",
265+
"execution_count": 4,
266+
"id": "6aa2f677",
267+
"metadata": {
268+
"collapsed": false,
269+
"editable": true
270+
},
271+
"outputs": [],
272+
"source": [
273+
"# Exact diagonalization for verification\n",
274+
"eigvals = np.linalg.eigvalsh(H)\n",
275+
"E_exact = np.min(eigvals)\n",
276+
"print(\"Exact ground state energy (lowest eigenvalue):\", E_exact)"
277+
]
278+
},
279+
{
280+
"cell_type": "markdown",
281+
"id": "bb7610ee",
282+
"metadata": {
283+
"editable": true
284+
},
285+
"source": [
286+
"For the sample parameter values above, the output might look like:\n",
287+
"\n",
288+
"In summary: the code defines the Hamiltonian and decomposes it into Pauli tensor terms , constructs a parameterized ansatz (RY, RZ, CNOT) , computes the expectation values of each term , and uses a classical optimizer to minimize the energy. The returned E_vqe is the approximate ground-state energy, which matches the exact eigenvalue for this two-qubit example (as expected for a sufficiently expressive ansatz and a well-behaved optimization)  ."
289+
]
290+
}
291+
],
292+
"metadata": {},
293+
"nbformat": 4,
294+
"nbformat_minor": 5
295+
}

0 commit comments

Comments
 (0)