1 """Heisenberg VQE example and shot-noise energy estimator. 3 This example constructs a Heisenberg-style Hamiltonian (ZZ/XX/YY couplings 4 plus local Z fields), builds an instance of the C++-backed 5 `qgd_Variational_Quantum_Eigensolver_Base` wrapper, applies a parameterized 6 ansatz, and demonstrates both exact (ideal) and shot-noise energy estimation. 8 The file shows how to format the term lists passed to the C++ wrapper: 9 `zz_terms`, `xx_terms`, `yy_terms` each contain items of the form 10 `{"i": int, "j": int, "coeff": float}` and `z_terms` contains 11 `{"i": int, "coeff": float}`. The shot-noise API expects a dictionary 12 containing these lists together with `shots`, `p_readout`, and `seed`. 14 This script is intended for interactive runs and manual inspection. For 15 lightweight automated checks that do not require compiling the native 16 extension, see `tests/VQE/test_shot_noise_measurement.py` which provides 17 fallback implementations and fast unit tests. 22 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
'../../')))
26 from qiskit.quantum_info
import SparsePauliOp
27 from networkx.generators.random_graphs
import random_regular_graph
29 np.set_printoptions(linewidth=200)
39 """Create a Heisenberg-like Hamiltonian and a Pauli-format oplist. 41 The Hamiltonian built is: 42 H = sum_{(i,j) in topology} [ Jz Z_i Z_j + Jx X_i X_j + Jy Y_i Y_j ] 48 Number of qubits in the system. 49 h : float or array-like, optional 50 Local Z field magnitude (scalar for uniform field or array-like for 51 per-qubit fields). Default 0.5. 52 topology : sequence of (i, j) pairs, optional 53 Edge list describing which qubit pairs are coupled. If ``None``, a 54 nearest-neighbour ring topology is used. 55 Jz, Jx, Jy : float, optional 56 Coupling strengths for the ZZ, XX and YY terms respectively. 60 H_sparse : scipy.sparse.csr_matrix 61 The full Hamiltonian as a sparse matrix in the computational (Z) 62 basis. Suitable for small-system exact calculations. 64 A list of tuples describing Pauli terms in the form expected by the 65 wrapper and by :class:`qiskit.quantum_info.SparsePauliOp`: 66 ("ZZ", [i, j], coeff), ("XX", [i, j], coeff), ("YY", [i, j], coeff), 67 and ("Z", [i], coeff). 71 The returned ``oplist`` is used to build dictionaries passed to the C++ 72 wrapper shot-noise routine (see ``zz_terms_for_dict`` etc. in 73 :func:`main`). The sparse Hamiltonian is convenient for exact energy 74 evaluation using ``scipy.sparse.linalg.eigsh`` for small systems. 78 topology = [[i, (i + 1) % n_qubits]
for i
in range(n_qubits)]
82 h_vec = np.full(n_qubits, float(h))
84 h_vec = np.asarray(h, dtype=float)
91 oplist.append((
"ZZ", [i, j], float(Jz)))
94 oplist.append((
"XX", [i, j], float(Jx)))
97 oplist.append((
"YY", [i, j], float(Jy)))
100 for i
in range(n_qubits):
101 oplist.append((
"Z", [i], float(h_vec[i])))
104 H_sparse = SparsePauliOp.from_sparse_list(oplist, num_qubits=n_qubits)
105 H_sparse = H_sparse.to_matrix(sparse=
True).tocsr()
107 return H_sparse, oplist
111 """Run a full VQE example and shot-noise energy estimation. 114 - Build a Heisenberg Hamiltonian and decompose it to term lists 115 consumable by the C++ wrapper. 116 - Instantiate the wrapped VQE object and configure optimizer/ansatz. 117 - Generate and apply the circuit to obtain the final state |psi>. 118 - Compute exact (ideal) energies and per-term contributions. 119 - Run the shot-noise estimator via the wrapper and print a summary. 121 This function prints human-readable diagnostics. For automated tests 122 prefer using the unit-tests under ``tests/VQE`` which include 123 lightweight fallbacks when the native extension is unavailable. 126 from squander.variational_quantum_eigensolver.qgd_Variational_Quantum_Eigensolver_Base
import (
127 qgd_Variational_Quantum_Eigensolver_Base
as Variational_Quantum_Eigensolver,
142 zz_terms_for_dict = [
143 {
"i": t[1][0],
"j": t[1][1],
"coeff": float(t[2])}
144 for t
in oplist
if t[0] ==
"ZZ" 147 xx_terms_for_dict = [
148 {
"i": t[1][0],
"j": t[1][1],
"coeff": float(t[2])}
149 for t
in oplist
if t[0] ==
"XX" 152 yy_terms_for_dict = [
153 {
"i": t[1][0],
"j": t[1][1],
"coeff": float(t[2])}
154 for t
in oplist
if t[0] ==
"YY" 158 {
"i": t[1][0],
"coeff": float(t[2])}
159 for t
in oplist
if t[0] ==
"Z" 163 zz_ops = [(t[0], t[1], t[2])
for t
in oplist
if t[0] ==
"ZZ"]
164 xx_ops = [(t[0], t[1], t[2])
for t
in oplist
if t[0] ==
"XX"]
165 yy_ops = [(t[0], t[1], t[2])
for t
in oplist
if t[0] ==
"YY"]
166 z_ops = [(t[0], t[1], t[2])
for t
in oplist
if t[0] ==
"Z"]
169 SparsePauliOp.from_sparse_list(zz_ops, num_qubits=n_qubits)
170 .to_matrix(sparse=
True)
175 SparsePauliOp.from_sparse_list(xx_ops, num_qubits=n_qubits)
176 .to_matrix(sparse=
True)
181 SparsePauliOp.from_sparse_list(yy_ops, num_qubits=n_qubits)
182 .to_matrix(sparse=
True)
187 SparsePauliOp.from_sparse_list(z_ops, num_qubits=n_qubits)
188 .to_matrix(sparse=
True)
195 eigvals, _ = sp.sparse.linalg.eigsh(Hamiltonian, k=1, which=
"SA")
196 target_e = float(np.real(eigvals[0]))
201 config = {
"max_inner_iterations": 200,
"batch_size": 64,
"convergence_length": 10}
202 VQE = Variational_Quantum_Eigensolver(Hamiltonian, n_qubits, config, accelerator_num=0)
203 VQE.set_Optimizer(
"COSINE")
204 VQE.set_Ansatz(
"HEA_ZYZ")
205 VQE.Generate_Circuit(layers=2, inner_blocks=1)
207 param_num = VQE.get_Parameter_Num()
208 parameters = np.random.randn(param_num) * 2 * np.pi
209 VQE.set_Optimized_Parameters(parameters)
212 state = np.zeros(1 << n_qubits, dtype=np.complex128)
216 VQE.set_Initial_State(state)
219 VQE.apply_to(parameters, state)
222 prob_sum = float(np.sum(np.abs(state) ** 2))
223 if not np.isclose(prob_sum, 1.0, atol=1e-9):
224 raise RuntimeError(f
"State not normalized after apply_to: sum|amp|^2 = {prob_sum}")
226 if Hamiltonian.shape != (len(state), len(state)):
228 f
"Dimension mismatch: H is {Hamiltonian.shape}, state length {len(state)} " 229 f
"(expected {(1<<n_qubits)})." 235 print(
"\n================== Final VQE state |Ïâ© (Z basis) ==================")
236 print(f
"(Only amplitudes with |amp| > 1e-3 are shown; norm^2 = {prob_sum:.12f})\n")
239 for idx, amp
in enumerate(state):
240 if np.abs(amp) > threshold:
241 bitstring = format(idx, f
"0{n_qubits}b")
242 print(f
"|{bitstring}> : {amp.real:+.6f} + {amp.imag:+.6f}i")
243 print(
"==================================================================\n")
248 ideal_energy = VQE.Expectation_value_of_energy_real(state, state)
249 energy_ref = float(np.vdot(state, Hamiltonian.dot(state)).real)
250 diff = ideal_energy - energy_ref
256 return float(np.vdot(state, part_H.dot(state)).real)
262 E_sum_parts = E_zz + E_xx + E_yy + E_z
264 print(
"\n============================================================")
265 print(
"ð§® Quantum Heisenberg VQE Simulation Results")
266 print(
"============================================================")
267 print(f
"Target ground state eigenvalue: {target_e:.8f}")
268 print(f
"Ideal energy â¨Ï|H|Ïâ© (wrapper): {ideal_energy:.8f} | direct: {energy_ref:.8f} | Î = {diff:.2e}")
270 print(
"\n--- Decomposition of â¨Ï|H|Ïâ© into ZZ / XX / YY / Z parts (ideal) ---")
271 print(f
"E_ZZ = {E_zz: .8f}")
272 print(f
"E_XX = {E_xx: .8f}")
273 print(f
"E_YY = {E_yy: .8f}")
274 print(f
"E_Z = {E_z: .8f}")
275 print(f
"E_ZZ + E_XX + E_YY + E_Z = {E_sum_parts: .8f}")
286 "p_readout": p_readout,
287 "zz_terms": zz_terms_for_dict,
288 "xx_terms": xx_terms_for_dict,
289 "yy_terms": yy_terms_for_dict,
290 "z_terms": z_terms_for_dict,
297 max_zz_idx = max(max(t[
"i"], t[
"j"])
for t
in zz_terms_for_dict)
if zz_terms_for_dict
else -1
298 max_xx_idx = max(max(t[
"i"], t[
"j"])
for t
in xx_terms_for_dict)
if xx_terms_for_dict
else -1
299 max_yy_idx = max(max(t[
"i"], t[
"j"])
for t
in yy_terms_for_dict)
if yy_terms_for_dict
else -1
300 max_z_idx = max(t[
"i"]
for t
in z_terms_for_dict)
if z_terms_for_dict
else -1
302 print(
"------------------------------------------------------------")
303 print(f
"Qubits: {n_qubits}")
304 print(f
"ZZ terms: {len(zz_terms_for_dict)} (max index: {max_zz_idx})")
305 print(f
"XX terms: {len(xx_terms_for_dict)} (max index: {max_xx_idx})")
306 print(f
"YY terms: {len(yy_terms_for_dict)} (max index: {max_yy_idx})")
307 print(f
"Z terms: {len(z_terms_for_dict)} (max index: {max_z_idx})")
308 print(f
"State shape: {state.shape} | Hamiltonian shape: {Hamiltonian.shape}")
309 print(f
"Shots per measurement basis (Z, X, and Y): {shots}")
315 result = VQE.Expectation_Value_Shot_Noise(input_dict)
317 mean_energy = result[
"mean"]
318 variance = result[
"variance"]
319 std_error = result[
"std_error"]
320 delta = mean_energy - ideal_energy
322 print(
"------------------------------------------------------------")
323 print(f
"Shot-noise energy ({shots} shots, {p_readout*100:.2f}% readout error): {mean_energy:.6f}")
324 print(f
"Variance: {variance:.6f} | Std. error: {std_error:.6f}")
325 print(f
"Difference (noisy â ideal): {delta:+.6f}")
330 rel_change = (delta / ideal_energy) * 100
if abs(ideal_energy) > 1e-10
else 0.0
331 direction =
"decrease" if delta < 0
else "increase" 332 print(
"------------------------------------------------------------")
333 print(f
"Summary: Noise introduced a {direction} of {abs(delta):.6f} " 334 f
"in energy ({abs(rel_change):.2f}% relative change).")
335 print(
"============================================================\n")
338 if __name__ ==
"__main__":
def generate_zz_xx_hamiltonian(n_qubits, h=0.5, topology=None, Jz=1.0, Jx=1.0, Jy=1.0)