|
| 1 | +import pytest |
| 2 | +import numpy as np |
| 3 | +import tensorcircuit as tc |
| 4 | + |
| 5 | + |
| 6 | +from tensorcircuit.templates.lattice import ( |
| 7 | + ChainLattice, |
| 8 | + SquareLattice, |
| 9 | + CustomizeLattice, |
| 10 | +) |
| 11 | +from tensorcircuit.templates.hamiltonians import ( |
| 12 | + heisenberg_hamiltonian, |
| 13 | + rydberg_hamiltonian, |
| 14 | +) |
| 15 | + |
| 16 | +PAULI_X = np.array([[0, 1], [1, 0]], dtype=complex) |
| 17 | +PAULI_Y = np.array([[0, -1j], [1j, 0]], dtype=complex) |
| 18 | +PAULI_Z = np.array([[1, 0], [0, -1]], dtype=complex) |
| 19 | +PAULI_I = np.eye(2, dtype=complex) |
| 20 | + |
| 21 | + |
| 22 | +class TestHeisenbergHamiltonian: |
| 23 | + """ |
| 24 | + Test suite for the heisenberg_hamiltonian function. |
| 25 | + """ |
| 26 | + |
| 27 | + def test_empty_lattice(self): |
| 28 | + """ |
| 29 | + Test that an empty lattice produces a 0x0 matrix. |
| 30 | + """ |
| 31 | + empty_lattice = CustomizeLattice( |
| 32 | + dimensionality=2, identifiers=[], coordinates=[] |
| 33 | + ) |
| 34 | + h = heisenberg_hamiltonian(empty_lattice) |
| 35 | + assert h.shape == (1, 1) |
| 36 | + assert h.nnz == 0 |
| 37 | + |
| 38 | + def test_single_site(self): |
| 39 | + """ |
| 40 | + Test that a single-site lattice (no bonds) produces a 2x2 zero matrix. |
| 41 | + """ |
| 42 | + single_site_lattice = ChainLattice(size=(1,), pbc=False) |
| 43 | + h = heisenberg_hamiltonian(single_site_lattice) |
| 44 | + assert h.shape == (2, 2) |
| 45 | + assert h.nnz == 0 |
| 46 | + |
| 47 | + def test_two_sites_chain(self): |
| 48 | + """ |
| 49 | + Test a two-site chain against a manually calculated Hamiltonian. |
| 50 | + This is the most critical test for scientific correctness. |
| 51 | + """ |
| 52 | + lattice = ChainLattice(size=(2,), pbc=False) |
| 53 | + j_coupling = -1.5 # Test with a non-trivial coupling constant |
| 54 | + h_generated = heisenberg_hamiltonian(lattice, j_coupling=j_coupling) |
| 55 | + |
| 56 | + # Manually construct the expected Hamiltonian: H = J * (X_0X_1 + Y_0Y_1 + Z_0Z_1) |
| 57 | + xx = np.kron(PAULI_X, PAULI_X) |
| 58 | + yy = np.kron(PAULI_Y, PAULI_Y) |
| 59 | + zz = np.kron(PAULI_Z, PAULI_Z) |
| 60 | + h_expected = j_coupling * (xx + yy + zz) |
| 61 | + |
| 62 | + assert h_generated.shape == (4, 4) |
| 63 | + assert np.allclose(tc.backend.to_dense(h_generated), h_expected) |
| 64 | + |
| 65 | + def test_square_lattice_properties(self): |
| 66 | + """ |
| 67 | + Test properties of a larger lattice (2x2 square) without full matrix comparison. |
| 68 | + """ |
| 69 | + lattice = SquareLattice(size=(2, 2), pbc=True) # 4 sites, 8 bonds with PBC |
| 70 | + h = heisenberg_hamiltonian(lattice, j_coupling=1.0) |
| 71 | + |
| 72 | + assert h.shape == (16, 16) |
| 73 | + assert h.nnz > 0 |
| 74 | + h_dense = tc.backend.to_dense(h) |
| 75 | + assert np.allclose(h_dense, h_dense.conj().T) |
| 76 | + |
| 77 | + |
| 78 | +class TestRydbergHamiltonian: |
| 79 | + """ |
| 80 | + Test suite for the rydberg_hamiltonian function. |
| 81 | + """ |
| 82 | + |
| 83 | + def test_single_site_rydberg(self): |
| 84 | + """ |
| 85 | + Test a single atom, which should only have driving and detuning terms. |
| 86 | + """ |
| 87 | + lattice = ChainLattice(size=(1,), pbc=False) |
| 88 | + omega, delta, c6 = 2.0, 0.5, 100.0 |
| 89 | + h_generated = rydberg_hamiltonian(lattice, omega, delta, c6) |
| 90 | + |
| 91 | + h_expected = (omega / 2.0) * PAULI_X + (delta / 2.0) * PAULI_Z |
| 92 | + |
| 93 | + assert h_generated.shape == (2, 2) |
| 94 | + assert np.allclose(tc.backend.to_dense(h_generated), h_expected) |
| 95 | + |
| 96 | + def test_two_sites_rydberg(self): |
| 97 | + """ |
| 98 | + Test a two-site chain for Rydberg Hamiltonian, including interaction. |
| 99 | + """ |
| 100 | + lattice = ChainLattice(size=(2,), pbc=False, lattice_constant=1.5) |
| 101 | + omega, delta, c6 = 1.0, -0.5, 10.0 |
| 102 | + h_generated = rydberg_hamiltonian(lattice, omega, delta, c6) |
| 103 | + |
| 104 | + v_ij = c6 / (1.5**6) |
| 105 | + |
| 106 | + h1 = (omega / 2.0) * (np.kron(PAULI_X, PAULI_I) + np.kron(PAULI_I, PAULI_X)) |
| 107 | + z0_coeff = delta / 2.0 - v_ij / 4.0 |
| 108 | + z1_coeff = delta / 2.0 - v_ij / 4.0 |
| 109 | + h2 = z0_coeff * np.kron(PAULI_Z, PAULI_I) + z1_coeff * np.kron(PAULI_I, PAULI_Z) |
| 110 | + h3 = (v_ij / 4.0) * np.kron(PAULI_Z, PAULI_Z) |
| 111 | + |
| 112 | + h_expected = h1 + h2 + h3 |
| 113 | + |
| 114 | + assert h_generated.shape == (4, 4) |
| 115 | + h_generated_dense = tc.backend.to_dense(h_generated) |
| 116 | + |
| 117 | + assert np.allclose(h_generated_dense, h_expected) |
| 118 | + |
| 119 | + def test_zero_distance_robustness(self): |
| 120 | + """ |
| 121 | + Test that the function does not crash when two atoms have zero distance. |
| 122 | + """ |
| 123 | + lattice = CustomizeLattice( |
| 124 | + dimensionality=2, |
| 125 | + identifiers=[0, 1], |
| 126 | + coordinates=[[0.0, 0.0], [0.0, 0.0]], |
| 127 | + ) |
| 128 | + |
| 129 | + try: |
| 130 | + h = rydberg_hamiltonian(lattice, omega=1.0, delta=1.0, c6=1.0) |
| 131 | + # The X terms contribute 8 non-zero elements. |
| 132 | + # The Z terms (Z0+Z1) have diagonal elements that cancel out, |
| 133 | + # resulting in only 2 non-zero elements. Total nnz = 8 + 2 = 10. |
| 134 | + assert h.nnz == 10 |
| 135 | + except ZeroDivisionError: |
| 136 | + pytest.fail("The function failed to handle zero distance between sites.") |
| 137 | + |
| 138 | + def test_anisotropic_heisenberg(self): |
| 139 | + """ |
| 140 | + Test the anisotropic Heisenberg model with different Jx, Jy, Jz. |
| 141 | + """ |
| 142 | + lattice = ChainLattice(size=(2,), pbc=False) |
| 143 | + j_coupling = [-1.0, 0.5, 2.0] # Jx, Jy, Jz |
| 144 | + h_generated = heisenberg_hamiltonian(lattice, j_coupling=j_coupling) |
| 145 | + |
| 146 | + # Manually construct the expected Hamiltonian |
| 147 | + jx, jy, jz = j_coupling |
| 148 | + xx = np.kron(PAULI_X, PAULI_X) |
| 149 | + yy = np.kron(PAULI_Y, PAULI_Y) |
| 150 | + zz = np.kron(PAULI_Z, PAULI_Z) |
| 151 | + h_expected = jx * xx + jy * yy + jz * zz |
| 152 | + |
| 153 | + h_generated_dense = tc.backend.to_dense(h_generated) |
| 154 | + assert h_generated_dense.shape == (4, 4) |
| 155 | + assert np.allclose(h_generated_dense, h_expected) |
0 commit comments