From 1d3ef70a6d2146fe9fdb0aee856563b2bc364a29 Mon Sep 17 00:00:00 2001 From: Stellogic Date: Thu, 24 Jul 2025 16:08:27 +0800 Subject: [PATCH 1/7] feat: implement Heisenberg and Rydberg Hamiltonian generation --- tensorcircuit/templates/hamiltonians.py | 146 ++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 tensorcircuit/templates/hamiltonians.py diff --git a/tensorcircuit/templates/hamiltonians.py b/tensorcircuit/templates/hamiltonians.py new file mode 100644 index 00000000..8155c2ad --- /dev/null +++ b/tensorcircuit/templates/hamiltonians.py @@ -0,0 +1,146 @@ +import tensorcircuit as tc +from scipy.sparse import coo_matrix +import numpy as np + +import typing + +from .lattice import AbstractLattice + + +def generate_heisenberg_hamiltonian( + lattice: AbstractLattice, j_coupling: float = 1.0 +) -> coo_matrix: + """ + Generates the sparse matrix of the Heisenberg Hamiltonian for a given lattice. + + The Heisenberg Hamiltonian is defined as: + H = J * Σ_{} (X_i X_j + Y_i Y_j + Z_i Z_j) + where the sum is over all unique nearest-neighbor pairs . + + :param lattice: An instance of a class derived from AbstractLattice, + which provides the geometric information of the system. + :type lattice: AbstractLattice + :param j_coupling: The coupling constant for the Heisenberg interaction. Defaults to 1.0. + :type j_coupling: float, optional + :return: The Hamiltonian represented as a SciPy COO sparse matrix. + :rtype: coo_matrix + """ + num_sites = lattice.num_sites + if num_sites == 0: + return coo_matrix((0, 0)) + + neighbor_pairs = lattice.get_neighbor_pairs(k=1, unique=True) + if not neighbor_pairs: + return coo_matrix((2**num_sites, 2**num_sites)) + + pauli_map = {"X": 1, "Y": 2, "Z": 3} + + ls: typing.List[typing.List[int]] = [] + weights: typing.List[float] = [] + + for i, j in neighbor_pairs: + xx_string = [0] * num_sites + xx_string[i] = pauli_map["X"] + xx_string[j] = pauli_map["X"] + ls.append(xx_string) + weights.append(j_coupling) + + yy_string = [0] * num_sites + yy_string[i] = pauli_map["Y"] + yy_string[j] = pauli_map["Y"] + ls.append(yy_string) + weights.append(j_coupling) + + zz_string = [0] * num_sites + zz_string[i] = pauli_map["Z"] + zz_string[j] = pauli_map["Z"] + ls.append(zz_string) + weights.append(j_coupling) + + hamiltonian_matrix = tc.quantum.PauliStringSum2COO( + ls, weight=weights, numpy=True + ) + + return hamiltonian_matrix + + + + +def generate_rydberg_hamiltonian( + lattice: AbstractLattice, omega: float, delta: float, c6: float +) -> coo_matrix: + """ + Generates the sparse matrix of the Rydberg atom array Hamiltonian. + + The Hamiltonian is defined as: + H = Σ_i (Ω/2)X_i - Σ_i δ(1 - Z_i)/2 + Σ_{i 1e-9: + z_string = [0] * num_sites + z_string[i] = pauli_map["Z"] + ls.append(z_string) + weights.append(float(z_coefficients[i])) + + hamiltonian_matrix = tc.quantum.PauliStringSum2COO( + ls, weight=weights, numpy=True + ) + + return hamiltonian_matrix \ No newline at end of file From 8caf8e0a6e2357adc7256ee8a5256849d904ec2b Mon Sep 17 00:00:00 2001 From: Stellogic Date: Thu, 24 Jul 2025 16:36:24 +0800 Subject: [PATCH 2/7] feat: implement Heisenberg and Rydberg Hamiltonian generation --- tensorcircuit/templates/hamiltonians.py | 19 ++-- tests/test_hamiltonians.py | 132 ++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 tests/test_hamiltonians.py diff --git a/tensorcircuit/templates/hamiltonians.py b/tensorcircuit/templates/hamiltonians.py index 8155c2ad..9a3a1243 100644 --- a/tensorcircuit/templates/hamiltonians.py +++ b/tensorcircuit/templates/hamiltonians.py @@ -1,8 +1,9 @@ -import tensorcircuit as tc +import typing +from typing import cast from scipy.sparse import coo_matrix import numpy as np +import tensorcircuit as tc -import typing from .lattice import AbstractLattice @@ -57,13 +58,9 @@ def generate_heisenberg_hamiltonian( ls.append(zz_string) weights.append(j_coupling) - hamiltonian_matrix = tc.quantum.PauliStringSum2COO( - ls, weight=weights, numpy=True - ) - - return hamiltonian_matrix - + hamiltonian_matrix = tc.quantum.PauliStringSum2COO(ls, weight=weights, numpy=True) + return cast(coo_matrix ,hamiltonian_matrix) def generate_rydberg_hamiltonian( @@ -139,8 +136,6 @@ def generate_rydberg_hamiltonian( ls.append(z_string) weights.append(float(z_coefficients[i])) - hamiltonian_matrix = tc.quantum.PauliStringSum2COO( - ls, weight=weights, numpy=True - ) + hamiltonian_matrix = tc.quantum.PauliStringSum2COO(ls, weight=weights, numpy=True) - return hamiltonian_matrix \ No newline at end of file + return cast(coo_matrix,hamiltonian_matrix) diff --git a/tests/test_hamiltonians.py b/tests/test_hamiltonians.py new file mode 100644 index 00000000..a4427c32 --- /dev/null +++ b/tests/test_hamiltonians.py @@ -0,0 +1,132 @@ +import pytest +import numpy as np + +from tensorcircuit.templates.lattice import ( + ChainLattice, + SquareLattice, + CustomizeLattice, +) +from tensorcircuit.templates.hamiltonians import ( + generate_heisenberg_hamiltonian, + generate_rydberg_hamiltonian, +) + +PAULI_X = np.array([[0, 1], [1, 0]], dtype=complex) +PAULI_Y = np.array([[0, -1j], [1j, 0]], dtype=complex) +PAULI_Z = np.array([[1, 0], [0, -1]], dtype=complex) +PAULI_I = np.eye(2, dtype=complex) + + +class TestHeisenbergHamiltonian: + """ + Test suite for the generate_heisenberg_hamiltonian function. + """ + + def test_empty_lattice(self): + """ + Test that an empty lattice produces a 0x0 matrix. + """ + empty_lattice = CustomizeLattice( + dimensionality=2, identifiers=[], coordinates=[] + ) + h = generate_heisenberg_hamiltonian(empty_lattice) + assert h.shape == (0, 0) + assert h.nnz == 0 + + def test_single_site(self): + """ + Test that a single-site lattice (no bonds) produces a 2x2 zero matrix. + """ + single_site_lattice = ChainLattice(size=(1,), pbc=False) + h = generate_heisenberg_hamiltonian(single_site_lattice) + assert h.shape == (2, 2) + assert h.nnz == 0 + + def test_two_sites_chain(self): + """ + Test a two-site chain against a manually calculated Hamiltonian. + This is the most critical test for scientific correctness. + """ + lattice = ChainLattice(size=(2,), pbc=False) + j_coupling = -1.5 # Test with a non-trivial coupling constant + h_generated = generate_heisenberg_hamiltonian(lattice, j_coupling=j_coupling) + + # Manually construct the expected Hamiltonian: H = J * (X_0X_1 + Y_0Y_1 + Z_0Z_1) + xx = np.kron(PAULI_X, PAULI_X) + yy = np.kron(PAULI_Y, PAULI_Y) + zz = np.kron(PAULI_Z, PAULI_Z) + h_expected = j_coupling * (xx + yy + zz) + + assert h_generated.shape == (4, 4) + assert np.allclose(h_generated.toarray(), h_expected) + + def test_square_lattice_properties(self): + """ + Test properties of a larger lattice (2x2 square) without full matrix comparison. + """ + lattice = SquareLattice(size=(2, 2), pbc=True) # 4 sites, 8 bonds with PBC + h = generate_heisenberg_hamiltonian(lattice, j_coupling=1.0) + + assert h.shape == (16, 16) + assert h.nnz > 0 + h_dense = h.toarray() + assert np.allclose(h_dense, h_dense.conj().T) + + +class TestRydbergHamiltonian: + """ + Test suite for the generate_rydberg_hamiltonian function. + """ + + def test_single_site_rydberg(self): + """ + Test a single atom, which should only have driving and detuning terms. + """ + lattice = ChainLattice(size=(1,), pbc=False) + omega, delta, c6 = 2.0, 0.5, 100.0 + h_generated = generate_rydberg_hamiltonian(lattice, omega, delta, c6) + + h_expected = (omega / 2.0) * PAULI_X + (delta / 2.0) * PAULI_Z + + assert h_generated.shape == (2, 2) + assert np.allclose(h_generated.toarray(), h_expected) + + def test_two_sites_rydberg(self): + """ + Test a two-site chain for Rydberg Hamiltonian, including interaction. + """ + lattice = ChainLattice(size=(2,), pbc=False, lattice_constant=1.5) + omega, delta, c6 = 1.0, -0.5, 10.0 + h_generated = generate_rydberg_hamiltonian(lattice, omega, delta, c6) + + v_ij = c6 / (1.5**6) + + h1 = (omega / 2.0) * (np.kron(PAULI_X, PAULI_I) + np.kron(PAULI_I, PAULI_X)) + z0_coeff = delta / 2.0 - v_ij / 4.0 + z1_coeff = delta / 2.0 - v_ij / 4.0 + h2 = z0_coeff * np.kron(PAULI_Z, PAULI_I) + z1_coeff * np.kron(PAULI_I, PAULI_Z) + h3 = (v_ij / 4.0) * np.kron(PAULI_Z, PAULI_Z) + + h_expected = h1 + h2 + h3 + + assert h_generated.shape == (4, 4) + assert np.allclose(h_generated.toarray(), h_expected) + + def test_zero_distance_robustness(self): + """ + Test that the function does not crash when two atoms have zero distance. + """ + lattice = CustomizeLattice( + dimensionality=2, + identifiers=[0, 1], + coordinates=[[0.0, 0.0], [0.0, 0.0]], + ) + + try: + h = generate_rydberg_hamiltonian(lattice, omega=1.0, delta=1.0, c6=1.0) + # The X terms contribute 8 non-zero elements. + # The Z terms (Z0+Z1) have diagonal elements that cancel out, + # resulting in only 2 non-zero elements. Total nnz = 8 + 2 = 10. + assert h.nnz == 10 + except ZeroDivisionError: + pytest.fail("The function failed to handle zero distance between sites.") From 5d4cd1d4a6c6ad0bf92116162429c39edda306a1 Mon Sep 17 00:00:00 2001 From: Stellogic Date: Thu, 24 Jul 2025 16:48:43 +0800 Subject: [PATCH 3/7] feat: implement Heisenberg and Rydberg Hamiltonian generation --- tensorcircuit/templates/hamiltonians.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tensorcircuit/templates/hamiltonians.py b/tensorcircuit/templates/hamiltonians.py index 9a3a1243..90d0b89c 100644 --- a/tensorcircuit/templates/hamiltonians.py +++ b/tensorcircuit/templates/hamiltonians.py @@ -3,8 +3,6 @@ from scipy.sparse import coo_matrix import numpy as np import tensorcircuit as tc - - from .lattice import AbstractLattice From 7681a986599aeaf5f24e7b6e42e6f54b84cc9248 Mon Sep 17 00:00:00 2001 From: Stellogic Date: Thu, 24 Jul 2025 17:04:03 +0800 Subject: [PATCH 4/7] feat: fix black problem --- tensorcircuit/templates/hamiltonians.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorcircuit/templates/hamiltonians.py b/tensorcircuit/templates/hamiltonians.py index 90d0b89c..363c7721 100644 --- a/tensorcircuit/templates/hamiltonians.py +++ b/tensorcircuit/templates/hamiltonians.py @@ -58,7 +58,7 @@ def generate_heisenberg_hamiltonian( hamiltonian_matrix = tc.quantum.PauliStringSum2COO(ls, weight=weights, numpy=True) - return cast(coo_matrix ,hamiltonian_matrix) + return cast(coo_matrix, hamiltonian_matrix) def generate_rydberg_hamiltonian( @@ -136,4 +136,4 @@ def generate_rydberg_hamiltonian( hamiltonian_matrix = tc.quantum.PauliStringSum2COO(ls, weight=weights, numpy=True) - return cast(coo_matrix,hamiltonian_matrix) + return cast(coo_matrix, hamiltonian_matrix) From e8b1283b84cd66f6eaed81e4e115604a9053f9a4 Mon Sep 17 00:00:00 2001 From: Stellogic Date: Fri, 25 Jul 2025 21:56:05 +0800 Subject: [PATCH 5/7] refactor(templates): Improve hamiltonian functions based on review This commit refactors the hamiltonian generation functions to be more robust, flexible, and aligned with the project's backend-agnostic architecture, based on feedback from the recent code review. Key changes include: - **Backend Agnosticism:** The `PauliStringSum2COO` call now uses `numpy=False`, ensuring the returned sparse matrix is a native tensor of the currently active backend (TensorFlow, JAX, etc.). The return type hints have been updated to `Any` to reflect this. - **Anisotropic Heisenberg Model:** The `heisenberg_hamiltonian` function now accepts a list or tuple for `j_coupling`, allowing for the definition of anisotropic models with different Jx, Jy, and Jz values. - **Test Suite Enhancement:** Tests have been updated to use `tc.backend.to_dense` for backend-agnostic validation. A new test case for the anisotropic Heisenberg model has also been added to ensure its correctness. - **Code Cleanup:** A redundant `float()` cast was removed from the Rydberg hamiltonian logic. --- tensorcircuit/templates/hamiltonians.py | 90 ++++++++++++++----------- tests/test_hamiltonians.py | 55 ++++++++++----- 2 files changed, 88 insertions(+), 57 deletions(-) diff --git a/tensorcircuit/templates/hamiltonians.py b/tensorcircuit/templates/hamiltonians.py index 363c7721..95a2eb42 100644 --- a/tensorcircuit/templates/hamiltonians.py +++ b/tensorcircuit/templates/hamiltonians.py @@ -1,14 +1,24 @@ import typing -from typing import cast -from scipy.sparse import coo_matrix +from typing import Any, List, Tuple, Union import numpy as np +from tensorcircuit.cons import dtypestr, backend import tensorcircuit as tc from .lattice import AbstractLattice -def generate_heisenberg_hamiltonian( - lattice: AbstractLattice, j_coupling: float = 1.0 -) -> coo_matrix: +def _create_empty_sparse_matrix(shape: Tuple[int, int]) -> Any: + """ + Helper function to create a backend-agnostic empty sparse matrix. + """ + indices = tc.backend.convert_to_tensor(backend.zeros((0, 2), dtype="int32")) + values = tc.backend.convert_to_tensor(backend.zeros((0,), dtype=dtypestr)) # type: ignore + return tc.backend.coo_sparse_matrix(indices=indices, values=values, shape=shape) # type: ignore + + +def heisenberg_hamiltonian( + lattice: AbstractLattice, + j_coupling: Union[float, List[float], Tuple[float, ...]] = 1.0, +) -> Any: """ Generates the sparse matrix of the Heisenberg Hamiltonian for a given lattice. @@ -19,51 +29,49 @@ def generate_heisenberg_hamiltonian( :param lattice: An instance of a class derived from AbstractLattice, which provides the geometric information of the system. :type lattice: AbstractLattice - :param j_coupling: The coupling constant for the Heisenberg interaction. Defaults to 1.0. - :type j_coupling: float, optional - :return: The Hamiltonian represented as a SciPy COO sparse matrix. - :rtype: coo_matrix + :param j_coupling: The coupling constants. Can be a single float for an + isotropic model (Jx=Jy=Jz) or a list/tuple of 3 floats for an + anisotropic model (Jx, Jy, Jz). Defaults to 1.0. + :type j_coupling: Union[float, List[float], Tuple[float, ...]], optional + :return: The Hamiltonian as a backend-agnostic sparse matrix. + :rtype: Any """ num_sites = lattice.num_sites - if num_sites == 0: - return coo_matrix((0, 0)) - neighbor_pairs = lattice.get_neighbor_pairs(k=1, unique=True) - if not neighbor_pairs: - return coo_matrix((2**num_sites, 2**num_sites)) + + if isinstance(j_coupling, (float, int)): + js = [float(j_coupling)] * 3 + else: + if len(j_coupling) != 3: + raise ValueError("j_coupling must be a float or a list/tuple of 3 floats.") + js = [float(j) for j in j_coupling] + + if num_sites == 0 or not neighbor_pairs: + return _create_empty_sparse_matrix(shape=(2**num_sites, 2**num_sites)) pauli_map = {"X": 1, "Y": 2, "Z": 3} ls: typing.List[typing.List[int]] = [] weights: typing.List[float] = [] + pauli_terms = ["X", "Y", "Z"] for i, j in neighbor_pairs: - xx_string = [0] * num_sites - xx_string[i] = pauli_map["X"] - xx_string[j] = pauli_map["X"] - ls.append(xx_string) - weights.append(j_coupling) - - yy_string = [0] * num_sites - yy_string[i] = pauli_map["Y"] - yy_string[j] = pauli_map["Y"] - ls.append(yy_string) - weights.append(j_coupling) - - zz_string = [0] * num_sites - zz_string[i] = pauli_map["Z"] - zz_string[j] = pauli_map["Z"] - ls.append(zz_string) - weights.append(j_coupling) + for idx, pauli_char in enumerate(pauli_terms): + if abs(js[idx]) > 1e-9: + string = [0] * num_sites + string[i] = pauli_map[pauli_char] + string[j] = pauli_map[pauli_char] + ls.append(string) + weights.append(js[idx]) - hamiltonian_matrix = tc.quantum.PauliStringSum2COO(ls, weight=weights, numpy=True) + hamiltonian_matrix = tc.quantum.PauliStringSum2COO(ls, weight=weights, numpy=False) - return cast(coo_matrix, hamiltonian_matrix) + return hamiltonian_matrix -def generate_rydberg_hamiltonian( +def rydberg_hamiltonian( lattice: AbstractLattice, omega: float, delta: float, c6: float -) -> coo_matrix: +) -> Any: """ Generates the sparse matrix of the Rydberg atom array Hamiltonian. @@ -84,12 +92,12 @@ def generate_rydberg_hamiltonian( :type delta: float :param c6: The Van der Waals interaction coefficient (C6). :type c6: float - :return: The Hamiltonian represented as a SciPy COO sparse matrix. - :rtype: coo_matrix + :return: The Hamiltonian as a backend-agnostic sparse matrix. + :rtype: Any """ num_sites = lattice.num_sites if num_sites == 0: - return coo_matrix((0, 0)) + return _create_empty_sparse_matrix(shape=(1, 1)) pauli_map = {"X": 1, "Y": 2, "Z": 3} ls: typing.List[typing.List[int]] = [] @@ -132,8 +140,8 @@ def generate_rydberg_hamiltonian( z_string = [0] * num_sites z_string[i] = pauli_map["Z"] ls.append(z_string) - weights.append(float(z_coefficients[i])) + weights.append(z_coefficients[i]) # type: ignore - hamiltonian_matrix = tc.quantum.PauliStringSum2COO(ls, weight=weights, numpy=True) + hamiltonian_matrix = tc.quantum.PauliStringSum2COO(ls, weight=weights, numpy=False) - return cast(coo_matrix, hamiltonian_matrix) + return hamiltonian_matrix diff --git a/tests/test_hamiltonians.py b/tests/test_hamiltonians.py index a4427c32..28c1ba33 100644 --- a/tests/test_hamiltonians.py +++ b/tests/test_hamiltonians.py @@ -1,5 +1,7 @@ import pytest import numpy as np +import tensorcircuit as tc + from tensorcircuit.templates.lattice import ( ChainLattice, @@ -7,8 +9,8 @@ CustomizeLattice, ) from tensorcircuit.templates.hamiltonians import ( - generate_heisenberg_hamiltonian, - generate_rydberg_hamiltonian, + heisenberg_hamiltonian, + rydberg_hamiltonian, ) PAULI_X = np.array([[0, 1], [1, 0]], dtype=complex) @@ -19,7 +21,7 @@ class TestHeisenbergHamiltonian: """ - Test suite for the generate_heisenberg_hamiltonian function. + Test suite for the heisenberg_hamiltonian function. """ def test_empty_lattice(self): @@ -29,8 +31,8 @@ def test_empty_lattice(self): empty_lattice = CustomizeLattice( dimensionality=2, identifiers=[], coordinates=[] ) - h = generate_heisenberg_hamiltonian(empty_lattice) - assert h.shape == (0, 0) + h = heisenberg_hamiltonian(empty_lattice) + assert h.shape == (1, 1) assert h.nnz == 0 def test_single_site(self): @@ -38,7 +40,7 @@ def test_single_site(self): Test that a single-site lattice (no bonds) produces a 2x2 zero matrix. """ single_site_lattice = ChainLattice(size=(1,), pbc=False) - h = generate_heisenberg_hamiltonian(single_site_lattice) + h = heisenberg_hamiltonian(single_site_lattice) assert h.shape == (2, 2) assert h.nnz == 0 @@ -49,7 +51,7 @@ def test_two_sites_chain(self): """ lattice = ChainLattice(size=(2,), pbc=False) j_coupling = -1.5 # Test with a non-trivial coupling constant - h_generated = generate_heisenberg_hamiltonian(lattice, j_coupling=j_coupling) + h_generated = heisenberg_hamiltonian(lattice, j_coupling=j_coupling) # Manually construct the expected Hamiltonian: H = J * (X_0X_1 + Y_0Y_1 + Z_0Z_1) xx = np.kron(PAULI_X, PAULI_X) @@ -58,24 +60,24 @@ def test_two_sites_chain(self): h_expected = j_coupling * (xx + yy + zz) assert h_generated.shape == (4, 4) - assert np.allclose(h_generated.toarray(), h_expected) + assert np.allclose(tc.backend.to_dense(h_generated), h_expected) def test_square_lattice_properties(self): """ Test properties of a larger lattice (2x2 square) without full matrix comparison. """ lattice = SquareLattice(size=(2, 2), pbc=True) # 4 sites, 8 bonds with PBC - h = generate_heisenberg_hamiltonian(lattice, j_coupling=1.0) + h = heisenberg_hamiltonian(lattice, j_coupling=1.0) assert h.shape == (16, 16) assert h.nnz > 0 - h_dense = h.toarray() + h_dense = tc.backend.to_dense(h) assert np.allclose(h_dense, h_dense.conj().T) class TestRydbergHamiltonian: """ - Test suite for the generate_rydberg_hamiltonian function. + Test suite for the rydberg_hamiltonian function. """ def test_single_site_rydberg(self): @@ -84,12 +86,12 @@ def test_single_site_rydberg(self): """ lattice = ChainLattice(size=(1,), pbc=False) omega, delta, c6 = 2.0, 0.5, 100.0 - h_generated = generate_rydberg_hamiltonian(lattice, omega, delta, c6) + h_generated = rydberg_hamiltonian(lattice, omega, delta, c6) h_expected = (omega / 2.0) * PAULI_X + (delta / 2.0) * PAULI_Z assert h_generated.shape == (2, 2) - assert np.allclose(h_generated.toarray(), h_expected) + assert np.allclose(tc.backend.to_dense(h_generated), h_expected) def test_two_sites_rydberg(self): """ @@ -97,7 +99,7 @@ def test_two_sites_rydberg(self): """ lattice = ChainLattice(size=(2,), pbc=False, lattice_constant=1.5) omega, delta, c6 = 1.0, -0.5, 10.0 - h_generated = generate_rydberg_hamiltonian(lattice, omega, delta, c6) + h_generated = rydberg_hamiltonian(lattice, omega, delta, c6) v_ij = c6 / (1.5**6) @@ -110,7 +112,9 @@ def test_two_sites_rydberg(self): h_expected = h1 + h2 + h3 assert h_generated.shape == (4, 4) - assert np.allclose(h_generated.toarray(), h_expected) + h_generated_dense = tc.backend.to_dense(h_generated) + + assert np.allclose(h_generated_dense, h_expected) def test_zero_distance_robustness(self): """ @@ -123,10 +127,29 @@ def test_zero_distance_robustness(self): ) try: - h = generate_rydberg_hamiltonian(lattice, omega=1.0, delta=1.0, c6=1.0) + h = rydberg_hamiltonian(lattice, omega=1.0, delta=1.0, c6=1.0) # The X terms contribute 8 non-zero elements. # The Z terms (Z0+Z1) have diagonal elements that cancel out, # resulting in only 2 non-zero elements. Total nnz = 8 + 2 = 10. assert h.nnz == 10 except ZeroDivisionError: pytest.fail("The function failed to handle zero distance between sites.") + + def test_anisotropic_heisenberg(self): + """ + Test the anisotropic Heisenberg model with different Jx, Jy, Jz. + """ + lattice = ChainLattice(size=(2,), pbc=False) + j_coupling = [-1.0, 0.5, 2.0] # Jx, Jy, Jz + h_generated = heisenberg_hamiltonian(lattice, j_coupling=j_coupling) + + # Manually construct the expected Hamiltonian + jx, jy, jz = j_coupling + xx = np.kron(PAULI_X, PAULI_X) + yy = np.kron(PAULI_Y, PAULI_Y) + zz = np.kron(PAULI_Z, PAULI_Z) + h_expected = jx * xx + jy * yy + jz * zz + + h_generated_dense = tc.backend.to_dense(h_generated) + assert h_generated_dense.shape == (4, 4) + assert np.allclose(h_generated_dense, h_expected) From 11f116274192657fad748218624682290235612c Mon Sep 17 00:00:00 2001 From: Stellogic Date: Sat, 26 Jul 2025 11:08:38 +0800 Subject: [PATCH 6/7] refactor(templates): Improve hamiltonian Internal API Usage: I've switched to importing backend and dtypestr from .cons and PauliStringSum2COO from ..quantum as you suggested, avoiding direct calls to the top-level tc package. Error Handling: For both heisenberg_hamiltonian and rydberg_hamiltonian, I've added a check to raise a ValueError if num_sites is 0, which is indeed more explicit. --- tensorcircuit/templates/hamiltonians.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tensorcircuit/templates/hamiltonians.py b/tensorcircuit/templates/hamiltonians.py index 95a2eb42..47acbfbe 100644 --- a/tensorcircuit/templates/hamiltonians.py +++ b/tensorcircuit/templates/hamiltonians.py @@ -2,7 +2,7 @@ from typing import Any, List, Tuple, Union import numpy as np from tensorcircuit.cons import dtypestr, backend -import tensorcircuit as tc +from ..quantum import PauliStringSum2COO from .lattice import AbstractLattice @@ -10,9 +10,9 @@ def _create_empty_sparse_matrix(shape: Tuple[int, int]) -> Any: """ Helper function to create a backend-agnostic empty sparse matrix. """ - indices = tc.backend.convert_to_tensor(backend.zeros((0, 2), dtype="int32")) - values = tc.backend.convert_to_tensor(backend.zeros((0,), dtype=dtypestr)) # type: ignore - return tc.backend.coo_sparse_matrix(indices=indices, values=values, shape=shape) # type: ignore + indices = backend.convert_to_tensor(backend.zeros((0, 2), dtype="int32")) + values = backend.convert_to_tensor(backend.zeros((0,), dtype=dtypestr)) # type: ignore + return backend.coo_sparse_matrix(indices=indices, values=values, shape=shape) # type: ignore def heisenberg_hamiltonian( @@ -46,8 +46,10 @@ def heisenberg_hamiltonian( raise ValueError("j_coupling must be a float or a list/tuple of 3 floats.") js = [float(j) for j in j_coupling] - if num_sites == 0 or not neighbor_pairs: + if not neighbor_pairs: return _create_empty_sparse_matrix(shape=(2**num_sites, 2**num_sites)) + if num_sites == 0: + raise ValueError("Cannot generate a Hamiltonian for a lattice with zero sites.") pauli_map = {"X": 1, "Y": 2, "Z": 3} @@ -64,7 +66,7 @@ def heisenberg_hamiltonian( ls.append(string) weights.append(js[idx]) - hamiltonian_matrix = tc.quantum.PauliStringSum2COO(ls, weight=weights, numpy=False) + hamiltonian_matrix = PauliStringSum2COO(ls, weight=weights, numpy=False) return hamiltonian_matrix @@ -97,7 +99,7 @@ def rydberg_hamiltonian( """ num_sites = lattice.num_sites if num_sites == 0: - return _create_empty_sparse_matrix(shape=(1, 1)) + raise ValueError("Cannot generate a Hamiltonian for a lattice with zero sites.") pauli_map = {"X": 1, "Y": 2, "Z": 3} ls: typing.List[typing.List[int]] = [] @@ -132,6 +134,11 @@ def rydberg_hamiltonian( ls.append(zz_string) weights.append(coefficient) + # The interaction term V_ij * n_i * n_j, when expanded using + # n_i = (1-Z_i)/2, becomes (V_ij/4)*(I - Z_i - Z_j + Z_i*Z_j). + # This contributes a positive term (+V_ij/4) to the ZZ interaction, + # but negative terms (-V_ij/4) to the single-site Z_i and Z_j operators. + z_coefficients[i] -= coefficient z_coefficients[j] -= coefficient @@ -142,6 +149,6 @@ def rydberg_hamiltonian( ls.append(z_string) weights.append(z_coefficients[i]) # type: ignore - hamiltonian_matrix = tc.quantum.PauliStringSum2COO(ls, weight=weights, numpy=False) + hamiltonian_matrix = PauliStringSum2COO(ls, weight=weights, numpy=False) return hamiltonian_matrix From aa8973588213a8236d36242a1d948bc580b0b6cd Mon Sep 17 00:00:00 2001 From: Stellogic Date: Sat, 26 Jul 2025 17:47:02 +0800 Subject: [PATCH 7/7] refactor(templates): Apply review suggestions to hamiltonians.py This commit addresses code review feedback on the new hamiltonians module. - Switched from absolute to relative imports for internal modules (`..cons`, `..quantum`) to improve modularity. - Cleaned up and unified `typing` imports for better code style and consistency. --- tensorcircuit/templates/hamiltonians.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tensorcircuit/templates/hamiltonians.py b/tensorcircuit/templates/hamiltonians.py index 47acbfbe..0382b11d 100644 --- a/tensorcircuit/templates/hamiltonians.py +++ b/tensorcircuit/templates/hamiltonians.py @@ -1,7 +1,6 @@ -import typing from typing import Any, List, Tuple, Union import numpy as np -from tensorcircuit.cons import dtypestr, backend +from ..cons import dtypestr, backend from ..quantum import PauliStringSum2COO from .lattice import AbstractLattice @@ -53,8 +52,8 @@ def heisenberg_hamiltonian( pauli_map = {"X": 1, "Y": 2, "Z": 3} - ls: typing.List[typing.List[int]] = [] - weights: typing.List[float] = [] + ls: List[List[int]] = [] + weights: List[float] = [] pauli_terms = ["X", "Y", "Z"] for i, j in neighbor_pairs: @@ -102,8 +101,8 @@ def rydberg_hamiltonian( raise ValueError("Cannot generate a Hamiltonian for a lattice with zero sites.") pauli_map = {"X": 1, "Y": 2, "Z": 3} - ls: typing.List[typing.List[int]] = [] - weights: typing.List[float] = [] + ls: List[List[int]] = [] + weights: List[float] = [] for i in range(num_sites): x_string = [0] * num_sites