|
| 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