|
| 1 | +import numpy as np |
| 2 | + |
| 3 | +# Define 1-qubit Pauli matrices |
| 4 | +I = np.array([[1,0],[0,1]], dtype=complex) |
| 5 | +X = np.array([[0,1],[1,0]], dtype=complex) |
| 6 | +Y = np.array([[0,-1j],[1j,0]], dtype=complex) |
| 7 | +Z = np.array([[1,0],[0,-1]], dtype=complex) |
| 8 | + |
| 9 | +def get_neighbors(L): |
| 10 | + """List of nearest-neighbor pairs (i,j) for an LxL square lattice (open boundaries).""" |
| 11 | + neighbors = [] |
| 12 | + for i in range(L): |
| 13 | + for j in range(L): |
| 14 | + idx = i*L + j |
| 15 | + if j < L-1: |
| 16 | + neighbors.append((idx, idx+1)) |
| 17 | + if i < L-1: |
| 18 | + neighbors.append((idx, idx+L)) |
| 19 | + return neighbors |
| 20 | + |
| 21 | +def build_ising_hamiltonian(L, J, h): |
| 22 | + """Construct the 2^N x 2^N transverse-field Ising Hamiltonian for LxL spins.""" |
| 23 | + N = L*L |
| 24 | + dim = 2**N |
| 25 | + H = np.zeros((dim, dim), dtype=complex) |
| 26 | + for (i,j) in get_neighbors(L): |
| 27 | + # Build tensor-product for -J * Z_i Z_j |
| 28 | + ops = [Z if k==i or k==j else I for k in range(N)] |
| 29 | + term = ops[0] |
| 30 | + for op in ops[1:]: |
| 31 | + term = np.kron(term, op) |
| 32 | + H += -J * term |
| 33 | + for i in range(N): |
| 34 | + # -h * X_i term |
| 35 | + ops = [X if k==i else I for k in range(N)] |
| 36 | + term = ops[0] |
| 37 | + for op in ops[1:]: |
| 38 | + term = np.kron(term, op) |
| 39 | + H += -h * term |
| 40 | + return H |
| 41 | + |
| 42 | +def build_heisenberg_hamiltonian(L, J): |
| 43 | + """Construct the 2^N x 2^N isotropic Heisenberg Hamiltonian for LxL spins.""" |
| 44 | + N = L*L |
| 45 | + dim = 2**N |
| 46 | + H = np.zeros((dim, dim), dtype=complex) |
| 47 | + for (i,j) in get_neighbors(L): |
| 48 | + # Add J*(X_iX_j + Y_iY_j + Z_iZ_j) |
| 49 | + for P in (X, Y, Z): |
| 50 | + ops = [P if k==i or k==j else I for k in range(N)] |
| 51 | + term = ops[0] |
| 52 | + for op in ops[1:]: |
| 53 | + term = np.kron(term, op) |
| 54 | + H += J * term |
| 55 | + return H |
| 56 | + |
| 57 | +def apply_RY(state, qubit, theta): |
| 58 | + """Apply a single-qubit RY(theta) rotation on the specified qubit of the state.""" |
| 59 | + N = int(np.log2(len(state))) |
| 60 | + new_state = np.zeros_like(state, dtype=complex) |
| 61 | + cos = np.cos(theta/2); sin = np.sin(theta/2) |
| 62 | + # Loop over basis states in binary; if qubit bit is 0, mix with its partner state |
| 63 | + for b in range(len(state)): |
| 64 | + if ((b >> qubit) & 1) == 0: |
| 65 | + partner = b | (1 << qubit) # flip that qubit bit |
| 66 | + new_state[b] += cos * state[b] - sin * state[partner] |
| 67 | + new_state[partner] += sin * state[b] + cos * state[partner] |
| 68 | + return new_state |
| 69 | + |
| 70 | +def apply_CNOT(state, control, target): |
| 71 | + """Apply a CNOT gate (control -> target) on the specified qubits of the state.""" |
| 72 | + new_state = np.zeros_like(state, dtype=complex) |
| 73 | + for b in range(len(state)): |
| 74 | + # If control bit is 1, flip the target bit |
| 75 | + if ((b >> control) & 1) == 1: |
| 76 | + b_new = b ^ (1 << target) |
| 77 | + else: |
| 78 | + b_new = b |
| 79 | + new_state[b_new] += state[b] |
| 80 | + return new_state |
| 81 | + |
| 82 | +def ansatz_state(params, L): |
| 83 | + """Construct the variational ansatz state for parameters `params` on an LxL lattice.""" |
| 84 | + N = L*L |
| 85 | + psi = np.zeros(2**N, dtype=complex) |
| 86 | + psi[0] = 1.0 # start in |00...0> |
| 87 | + # Apply RY on each qubit with corresponding parameter |
| 88 | + for i, theta in enumerate(params): |
| 89 | + psi = apply_RY(psi, i, theta) |
| 90 | + # Apply CNOT entanglers on each neighbor pair |
| 91 | + for (i,j) in get_neighbors(L): |
| 92 | + psi = apply_CNOT(psi, i, j) |
| 93 | + return psi |
| 94 | + |
| 95 | +import scipy.optimize as opt |
| 96 | + |
| 97 | +def energy_expectation(params, H, L): |
| 98 | + """Compute expectation <psi(params)|H|psi(params)> for Hamiltonian H.""" |
| 99 | + psi = ansatz_state(params, L) |
| 100 | + # <psi|H|psi> using complex conjugate transpose |
| 101 | + return np.vdot(psi, H.dot(psi)).real |
| 102 | + |
| 103 | +# Example: find ground energy of 2x2 transverse Ising (J=1.0, h=0.5) |
| 104 | +L = 2 |
| 105 | +J = 1.0 |
| 106 | +h = 0.5 |
| 107 | +H_ising = build_ising_hamiltonian(L, J, h) |
| 108 | +# Initial random parameters |
| 109 | +np.random.seed(0) |
| 110 | +init_params = 0.1 * np.random.randn(L*L) |
| 111 | +# Optimize parameters to minimize energy |
| 112 | +result = opt.minimize(lambda th: energy_expectation(th, H_ising, L), |
| 113 | + init_params, method='COBYLA') |
| 114 | +estimated_energy = result.fun |
| 115 | + |
| 116 | + |
| 117 | + |
| 118 | +import matplotlib.pyplot as plt |
| 119 | + |
| 120 | +# Example: Vary h for 2x2 Ising, J=1.0 |
| 121 | +hs = np.linspace(0.0, 2.0, 5) |
| 122 | +energies_vqe = [] |
| 123 | +for h_val in hs: |
| 124 | + H2 = build_ising_hamiltonian(2, 1.0, h_val) |
| 125 | + res = opt.minimize(lambda th: energy_expectation(th, H2, 2), |
| 126 | + init_params, method='COBYLA') |
| 127 | + energies_vqe.append(res.fun) |
| 128 | + |
| 129 | +plt.plot(hs, energies_vqe, marker='o') |
| 130 | +plt.xlabel('Transverse field h') |
| 131 | +plt.ylabel('Ground-state energy (VQE)') |
| 132 | +plt.title('2x2 Transverse-Field Ising (J=1)') |
| 133 | +plt.show() |
| 134 | + |
0 commit comments