From 7f63b0f65202eeceb130b79c1b00690ab29ce2ae Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Wed, 28 Aug 2024 15:04:59 -0400 Subject: [PATCH 01/35] [skip ci] Added Kitaev model Hamiltonian --- pennylane/spin/__init__.py | 3 +- pennylane/spin/lattice.py | 69 +++++++++++++++++++++++++-- pennylane/spin/spin_hamiltonian.py | 66 ++++++++++++++++++++++++- tests/spin/test_lattice.py | 71 +++++++++++++++++++++++++++ tests/spin/test_spin_hamiltonian.py | 74 ++++++++++++++++++++++++++++- 5 files changed, 276 insertions(+), 7 deletions(-) diff --git a/pennylane/spin/__init__.py b/pennylane/spin/__init__.py index 8401e77b35c..3af1c026b95 100644 --- a/pennylane/spin/__init__.py +++ b/pennylane/spin/__init__.py @@ -16,4 +16,5 @@ """ from .lattice import Lattice -from .spin_hamiltonian import fermi_hubbard, heisenberg, transverse_ising +from .spin_hamiltonian import (fermi_hubbard, heisenberg, kitaev, + transverse_ising) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 9c823d8bc73..35560132543 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -69,6 +69,7 @@ def __init__( positions=None, boundary_condition=False, neighbour_order=1, + custom_edges=None, distance_tol=1e-5, ): @@ -109,10 +110,19 @@ def __init__( n_sl = len(self.positions) self.n_sites = math.prod(n_cells) * n_sl self.lattice_points, lattice_map = self._generate_grid(neighbour_order) + if custom_edges is None: + cutoff = ( + neighbour_order * math.max(math.linalg.norm(self.vectors, axis=1)) + distance_tol + ) + edges = self._identify_neighbours(cutoff) + self.edges = Lattice._generate_true_edges(edges, lattice_map, neighbour_order) + else: + if neighbour_order > 1: + raise ValueError( + "custom_edges and neighbour_order cannot be specified at the same time" + ) + self.edges = self.get_custom_edges(custom_edges, lattice_map) - cutoff = neighbour_order * math.max(math.linalg.norm(self.vectors, axis=1)) + distance_tol - edges = self._identify_neighbours(cutoff) - self.edges = Lattice._generate_true_edges(edges, lattice_map, neighbour_order) self.edges_indices = [(v1, v2) for (v1, v2, color) in self.edges] def _identify_neighbours(self, cutoff): @@ -187,7 +197,58 @@ def _generate_grid(self, neighbour_order): lattice_points.append(point) lattice_map.append(node_index) - return math.array(lattice_points), math.array(lattice_map) + return math.array(lattice_points), lattice_map + + def get_custom_edges(self, custom_edges, lattice_map): + """Generates the edges described in `custom_edges` for all unit cells.""" + + if not all([len(edge) in (1, 2) for edge in custom_edges]): + raise TypeError( + """ + custom_edges must be a list of tuples of length 1 or 2. + Every tuple must contain two lattice indices to represent the edge + and can optionally include a list to represent the operation and coefficient for that edge. + """ + ) + + edges = [] + n_sl = len(self.positions) + nsites_axis = math.cumprod([n_sl, *self.n_cells[:0:-1]])[::-1] + + for i, custom_edge in enumerate(custom_edges): + edge = custom_edge[0] + + if edge[0] >= self.n_sites or edge[1] >= self.n_sites: + raise ValueError( + f"The edge {edge} has vertices greater than n_sites, {self.n_sites}" + ) + + edge_operation = custom_edge[1] if len(custom_edge) == 2 else i + map_edge1 = lattice_map.index(edge[0]) + map_edge2 = lattice_map.index(edge[1]) + edge_distance = self.lattice_points[map_edge2] - self.lattice_points[map_edge1] + v1, v2 = math.mod(edge, n_sl) + dist_cell = (edge_distance + self.positions[v1] - self.positions[v2]) @ math.linalg.inv( + self.vectors + ) + dist_cell = math.asarray(math.rint(dist_cell), dtype=int) + edge_ranges = [] + for idx, cell in enumerate(self.n_cells): + if self.boundary_condition[idx]: + edge_ranges.append(range(0, cell)) + else: + edge_ranges.append( + range( + math.maximum(0, -dist_cell[idx]), cell - math.maximum(0, dist_cell[idx]) + ) + ) + + for cell in itertools.product(*edge_ranges): + node1_idx = math.dot(math.mod(cell, self.n_cells), nsites_axis) + v1 + node2_idx = math.dot(math.mod(cell + dist_cell, self.n_cells), nsites_axis) + v2 + edges.append((node1_idx, node2_idx, edge_operation)) + + return edges def add_edge(self, edge_indices): r"""Adds a specific edge based on the site index without translating it. diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index fb48f51e3d5..c4ee9dfab52 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -19,7 +19,7 @@ from pennylane import X, Y, Z, math from pennylane.fermi import FermiWord -from .lattice import _generate_lattice +from .lattice import Lattice, _generate_lattice # pylint: disable=too-many-arguments @@ -298,3 +298,67 @@ def fermi_hubbard( qubit_ham = qml.qchem.qubit_observable(hamiltonian, mapping=mapping) return qubit_ham.simplify() + + +def kitaev(n_cells, coupling=None, boundary_condition=False): + r"""Generates the Kitaev Hamiltonian on the Honeycomb lattice. + + The Hamiltonian is represented as: + + .. math:: + + \hat{H} = K_x.\sum_{}\sigma_i^x\sigma_j^x + K_y.\sum_{}\sigma_i^y\sigma_j^y + K_z.\sum_{}\sigma_i^z\sigma_j^z + + where :math:`K_x`, :math:`K_y`, :math:`K_z` are the coupling constants defined for the Hamiltonian, and ``i,j`` represent + the indices for neighbouring spins. + + Args: + n_cells (List[int]): Number of cells in each direction of the grid. + coupling (List[math.array[float]]): Coupling between spins, it is a list of length 3. + Default value is [1.0, 1.0, 1.0]. + boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice axes, + default is ``False`` indicating open boundary condition. + + Returns: + pennylane.LinearCombination: Hamiltonian for the Kitaev model. + + **Example** + + >>> n_cells = [2,2] + >>> k = [0.5, 0.6, 0.7] + >>> spin_ham = qml.spin.kitaev(n_cells, coupling=k) + >>> spin_ham + + """ + + if coupling is None: + coupling = [1.0, 1.0, 1.0] + + if len(coupling) != 3: + raise ValueError("The coupling parameter should be a list of length 3") + + vectors = [[1, 0], [0.5, 0.75**0.5]] + positions = [[0, 0], [0.5, 0.5 / 3**0.5]] + custom_edges = [ + [(0, 1), ("XX", coupling[0])], + [(1, 2), ("YY", coupling[1])], + [(1, n_cells[1] * 2), ("ZZ", coupling[2])], + ] + + lattice = Lattice( + n_cells=n_cells[0:2], + vectors=vectors, + positions=positions, + boundary_condition=boundary_condition, + custom_edges=custom_edges, + ) + print(lattice.edges) + opmap = {"X": X, "Y": Y, "Z": Z} + hamiltonian = 0.0 * qml.I(0) + for edge in lattice.edges: + v1, v2 = edge[0:2] + op1, op2 = edge[2][0] + coeff = edge[2][1] + hamiltonian += coeff * (opmap[op1](v1) @ opmap[op2](v2)) + + return hamiltonian.simplify() diff --git a/tests/spin/test_lattice.py b/tests/spin/test_lattice.py index 6576a396f3d..1b0eefc0bd5 100644 --- a/tests/spin/test_lattice.py +++ b/tests/spin/test_lattice.py @@ -14,6 +14,8 @@ """ Unit tests for functions and classes needed for construct a lattice. """ +import re + import numpy as np import pytest @@ -539,3 +541,72 @@ def test_shape_error(): lattice = "Octagon" with pytest.raises(ValueError, match="Lattice shape, 'Octagon' is not supported."): _generate_lattice(lattice=lattice, n_cells=n_cells) + + +def test_neighbour_order_error(): + r"""Test that an error is raised if neighbour order is greater than 1 when custom_edges are provided.""" + + vectors = [[0, 1], [1, 0]] + n_cells = [3, 3] + custom_edges = [[(0, 1)], [(0, 5)], [(0, 4)]] + with pytest.raises( + ValueError, match="custom_edges and neighbour_order cannot be specified at the same time" + ): + Lattice(n_cells=n_cells, vectors=vectors, neighbour_order=2, custom_edges=custom_edges) + + +def test_custom_edge_type_error(): + r"""Test that an error is raised if custom_edges are not provided as a list of length 1 or 2.""" + + vectors = [[0, 1], [1, 0]] + n_cells = [3, 3] + custom_edges = [[(0, 1), 1, 3], [(0, 5)], [(0, 4)]] + with pytest.raises(TypeError, match="custom_edges must be a list of tuples of length 1 or 2."): + Lattice(n_cells=n_cells, vectors=vectors, custom_edges=custom_edges) + + +def test_custom_edge_value_error(): + r"""Test that an error is raised if the custom_edges contains an edge with site_index greater than number of sites""" + + vectors = [[0, 1], [1, 0]] + n_cells = [3, 3] + custom_edges = [[(0, 1)], [(0, 5)], [(0, 12)]] + with pytest.raises( + ValueError, match=re.escape("The edge (0, 12) has vertices greater than n_sites, 9") + ): + Lattice(n_cells=n_cells, vectors=vectors, custom_edges=custom_edges) + + +@pytest.mark.parametrize( + # expected_edges here were obtained manually + ("vectors", "positions", "n_cells", "custom_edges", "expected_edges"), + [ + ( + [[0, 1], [1, 0]], + [[0, 0]], + [3, 3], + [[(0, 1)], [(0, 5)], [(0, 4)]], + [(0, 1, 0), (0, 5, 1), (0, 4, 2), (1, 2, 0), (3, 4, 0), (0, 4, 2), (1, 5, 2)], + ), + ( + [[0, 1], [1, 0]], + [[0, 0]], + [3, 4], + [[(0, 1)], [(1, 4)], [(1, 5)]], + [(0, 1, 0), (1, 2, 0), (2, 3, 0), (1, 4, 1), (2, 5, 1), (0, 4, 2), (2, 6, 2)], + ), + ( + [[1, 0], [0.5, np.sqrt(3) / 2]], + [[0.5, 0.5 / 3**0.5], [1, 1 / 3**0.5]], + [2, 2], + [[(0, 1)], [(1, 2)], [(1, 5)]], + [(2, 3, 0), (4, 5, 0), (1, 2, 1), (5, 6, 1), (1, 5, 2), (3, 7, 2)], + ), + ], +) +def test_custom_edges(vectors, positions, n_cells, custom_edges, expected_edges): + r"""Test that the edges are added as per custom_edges provided""" + lattice = Lattice( + n_cells=n_cells, vectors=vectors, positions=positions, custom_edges=custom_edges + ) + assert np.all(np.isin(expected_edges, lattice.edges)) diff --git a/tests/spin/test_spin_hamiltonian.py b/tests/spin/test_spin_hamiltonian.py index 72ae1243149..8d67da52510 100644 --- a/tests/spin/test_spin_hamiltonian.py +++ b/tests/spin/test_spin_hamiltonian.py @@ -21,7 +21,7 @@ import pennylane as qml from pennylane import I, X, Y, Z -from pennylane.spin import fermi_hubbard, heisenberg, transverse_ising +from pennylane.spin import fermi_hubbard, heisenberg, kitaev, transverse_ising def test_coupling_error(): @@ -780,3 +780,75 @@ def test_fermi_hubbard_hamiltonian_matrix(shape, n_cells, t, coulomb, expected_h fermi_hub_ham = fermi_hubbard(lattice=shape, n_cells=n_cells, hopping=t, coulomb=coulomb) qml.assert_equal(fermi_hub_ham, expected_ham) + + +def test_coupling_error_kitaev(): + r"""Test that an error is raised when the provided coupling shape is wrong for + Kitaev Hamiltonian.""" + with pytest.raises( + ValueError, + match=re.escape("The coupling parameter should be a list of length 3"), + ): + kitaev(n_cells=[3, 4], coupling=[1.0, 2.0]) + + +@pytest.mark.parametrize( + # expected_ham here was obtained manually + ("n_cells", "j", "boundary_condition", "expected_ham"), + [ + ( + [2, 2, 1], + None, + False, + 1.0 * (Z(1) @ Z(4)) + + 1.0 * (Z(3) @ Z(6)) + + 1.0 * (X(0) @ X(1)) + + 1.0 * (X(2) @ X(3)) + + 1.0 * (X(4) @ X(5)) + + 1.0 * (X(6) @ X(7)) + + 1.0 * (Y(1) @ Y(2)) + + 1.0 * (Y(5) @ Y(6)), + ), + ( + [2, 2], + [0.5, 0.6, 0.7], + False, + 0.7 * (Z(1) @ Z(4)) + + 0.7 * (Z(3) @ Z(6)) + + 0.5 * (X(0) @ X(1)) + + 0.5 * (X(2) @ X(3)) + + 0.5 * (X(4) @ X(5)) + + 0.5 * (X(6) @ X(7)) + + 0.6 * (Y(1) @ Y(2)) + + 0.6 * (Y(5) @ Y(6)), + ), + ( + [2, 3], + [0.1, 0.2, 0.3], + True, + 0.3 * (Z(1) @ Z(6)) + + 0.3 * (Z(3) @ Z(8)) + + 0.3 * (Z(5) @ Z(10)) + + 0.3 * (Z(0) @ Z(7)) + + 0.3 * (Z(2) @ Z(9)) + + 0.3 * (Z(4) @ Z(11)) + + 0.1 * (X(0) @ X(1)) + + 0.1 * (X(2) @ X(3)) + + 0.1 * (X(4) @ X(5)) + + 0.1 * (X(6) @ X(7)) + + 0.1 * (X(8) @ X(9)) + + 0.1 * (X(10) @ X(11)) + + 0.2 * (Y(1) @ Y(2)) + + 0.2 * (Y(3) @ Y(4)) + + 0.2 * (Y(0) @ Y(5)) + + 0.2 * (Y(7) @ Y(8)) + + 0.2 * (Y(9) @ Y(10)) + + 0.2 * (Y(11) @ Y(6)), + ), + ], +) +def test_kitaev_hamiltonian(n_cells, j, boundary_condition, expected_ham): + r"""Test that the correct Hamiltonian is generated""" + kitaev_ham = kitaev(n_cells=n_cells, coupling=j, boundary_condition=boundary_condition) + + qml.assert_equal(kitaev_ham, expected_ham) From 756dd7696010216a8c73213e3cac7d5efec6687c Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Wed, 28 Aug 2024 15:10:18 -0400 Subject: [PATCH 02/35] Update pennylane/spin/spin_hamiltonian.py --- pennylane/spin/spin_hamiltonian.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index c4ee9dfab52..bacee06854c 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -352,7 +352,6 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): boundary_condition=boundary_condition, custom_edges=custom_edges, ) - print(lattice.edges) opmap = {"X": X, "Y": Y, "Z": Z} hamiltonian = 0.0 * qml.I(0) for edge in lattice.edges: From ed029a525bdfa3263893ad9167263738619c77fa Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Tue, 3 Sep 2024 11:26:39 -0400 Subject: [PATCH 03/35] Cleaned up some --- pennylane/spin/lattice.py | 30 ++++--- pennylane/spin/spin_hamiltonian.py | 127 ++++++++++++++++------------ tests/spin/test_spin_hamiltonian.py | 21 +++++ 3 files changed, 113 insertions(+), 65 deletions(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 35560132543..3f19b85acdd 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -33,13 +33,14 @@ class Lattice: n_cells (list[int]): Number of cells in each direction of the grid. vectors (list[list[float]]): Primitive vectors for the lattice. positions (list[list[float]]): Initial positions of spin cites. Default value is - ``[[0.0]*number of dimensions]``. + ``[[0.0]`` :math:`\times` ``number of dimensions]``. + boundary_condition (bool or list[bool]): Defines boundary conditions different lattice axes, default is ``False`` indicating open boundary condition. neighbour_order (int): Specifies the interaction level for neighbors within the lattice. Default is 1 (nearest neighbour). distance_tol (float): Distance below which spatial points are considered equal for the - purpose of identifying nearest neighbours, default value is 1e-5. + purpose of identifying nearest neighbours. Default value is 1e-5. Raises: TypeError: @@ -53,13 +54,15 @@ class Lattice: Lattice object **Example** + >>> n_cells = [2,2] >>> vectors = [[0, 1], [1, 0]] >>> boundary_condition = [True, False] >>> lattice = qml.spin.Lattice(n_cells, vectors, >>> boundary_condition=boundary_condition) - >>> print(lattice.edges) + >>> lattice.edges [(2, 3, 0), (0, 2, 0), (1, 3, 0), (0, 1, 0)] + """ def __init__( @@ -128,7 +131,7 @@ def __init__( def _identify_neighbours(self, cutoff): r"""Identifies the connections between lattice points and returns the unique connections based on the neighbour_order. This function uses KDTree to identify neighbours, which - follows depth first search traversal.""" + follows depth-first search traversal.""" tree = KDTree(self.lattice_points) indices = tree.query_ball_tree(tree, cutoff) @@ -228,10 +231,10 @@ def get_custom_edges(self, custom_edges, lattice_map): map_edge2 = lattice_map.index(edge[1]) edge_distance = self.lattice_points[map_edge2] - self.lattice_points[map_edge1] v1, v2 = math.mod(edge, n_sl) - dist_cell = (edge_distance + self.positions[v1] - self.positions[v2]) @ math.linalg.inv( - self.vectors - ) - dist_cell = math.asarray(math.rint(dist_cell), dtype=int) + translation_vector = ( + edge_distance + self.positions[v1] - self.positions[v2] + ) @ math.linalg.inv(self.vectors) + translation_vector = math.asarray(math.rint(translation_vector), dtype=int) edge_ranges = [] for idx, cell in enumerate(self.n_cells): if self.boundary_condition[idx]: @@ -239,13 +242,16 @@ def get_custom_edges(self, custom_edges, lattice_map): else: edge_ranges.append( range( - math.maximum(0, -dist_cell[idx]), cell - math.maximum(0, dist_cell[idx]) + math.maximum(0, -translation_vector[idx]), + cell - math.maximum(0, translation_vector[idx]), ) ) for cell in itertools.product(*edge_ranges): node1_idx = math.dot(math.mod(cell, self.n_cells), nsites_axis) + v1 - node2_idx = math.dot(math.mod(cell + dist_cell, self.n_cells), nsites_axis) + v2 + node2_idx = ( + math.dot(math.mod(cell + translation_vector, self.n_cells), nsites_axis) + v2 + ) edges.append((node1_idx, node2_idx, edge_operation)) return edges @@ -377,7 +383,7 @@ def _kagome(n_cells, boundary_condition=False, neighbour_order=1): # TODO Check the efficiency of this function with a dictionary instead. def _generate_lattice(lattice, n_cells, boundary_condition=False, neighbour_order=1): - r"""Generates the lattice object for given shape and n_cells. + r"""Generates the lattice object for a given shape and n_cells. Args: lattice (str): Shape of the lattice. Input Values can be ``'chain'``, ``'square'``, ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``. @@ -386,7 +392,7 @@ def _generate_lattice(lattice, n_cells, boundary_condition=False, neighbour_orde neighbour_order (int): Specifies the interaction level for neighbors within the lattice. Default is 1 (nearest neighbour). Returns: - lattice object + lattice object. """ lattice_shape = lattice.strip().lower() diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index bacee06854c..f62d8ad6316 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -27,7 +27,7 @@ def transverse_ising( lattice, n_cells, coupling=1.0, h=1.0, boundary_condition=False, neighbour_order=1 ): - r"""Generates the transverse-field Ising model on a lattice. + r"""Generates the Hamiltonian for the transverse-field Ising model on a lattice. The Hamiltonian is represented as: @@ -39,20 +39,21 @@ def transverse_ising( transverse magnetic field and ``i,j`` represent the indices for neighbouring spins. Args: - lattice (str): Shape of the lattice. Input Values can be ``'chain'``, ``'square'``, - ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``. - n_cells (List[int]): Number of cells in each direction of the grid. - coupling (float or List[float] or List[math.array[float]]): Coupling between spins, it can be a - number, a list of length equal to ``neighbour_order`` or a square matrix of size - ``(num_spins, num_spins)``. Default value is 1.0. - h (float): Value of external magnetic field. Default is 1.0. - boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice axes, - default is ``False`` indicating open boundary condition. - neighbour_order (int): Specifies the interaction level for neighbors within the lattice. - Default is 1, indicating nearest neighbours. + lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``, + ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``. + n_cells (List[int]): Number of cells in each direction of the grid. + coupling (float or List[float] or List[math.array[float]]): Coupling between spins. It can + be a number, a list of length equal to ``neighbour_order`` or a square matrix of shape + ``(num_spins, num_spins)``, where ``num_spins`` is the total number of spins. Default + value is 1.0. + h (float): Value of external magnetic field. Default is 1.0. + boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice + axes, default is ``False`` indicating open boundary condition. + neighbour_order (int): Specifies the interaction level for neighbors within the lattice. + Default is 1, indicating nearest neighbours. Returns: - pennylane.LinearCombination: Hamiltonian for the transverse-field ising model. + ~ops.op_math.Sum: Hamiltonian for the transverse-field Ising model. **Example** @@ -61,12 +62,16 @@ def transverse_ising( >>> h = 0.1 >>> spin_ham = qml.spin.transverse_ising("square", n_cells, coupling=j, h=h) >>> spin_ham + ( -0.5 * (Z(0) @ Z(1)) + -0.5 * (Z(0) @ Z(2)) + -0.5 * (Z(1) @ Z(3)) + -0.5 * (Z(2) @ Z(3)) - + -0.1 * X(0) + -0.1 * X(1) - + -0.1 * X(2) + -0.1 * X(3) + + -0.1 * X(0) + + -0.1 * X(1) + + -0.1 * X(2) + + -0.1 * X(3) + ) """ lattice = _generate_lattice(lattice, n_cells, boundary_condition, neighbour_order) @@ -98,7 +103,7 @@ def transverse_ising( def heisenberg(lattice, n_cells, coupling=None, boundary_condition=False, neighbour_order=1): - r"""Generates the Heisenberg model on a lattice. + r"""Generates the Hamiltonian for the Heisenberg model on a lattice. The Hamiltonian is represented as: @@ -106,22 +111,23 @@ def heisenberg(lattice, n_cells, coupling=None, boundary_condition=False, neighb \hat{H} = J\sum_{}(\sigma_i^x\sigma_j^x + \sigma_i^y\sigma_j^y + \sigma_i^z\sigma_j^z) - where ``J`` is the coupling constant defined for the Hamiltonian, and ``i,j`` represent the indices for neighbouring spins. + where ``J`` is the coupling constant defined for the Hamiltonian, and ``i,j`` represent the + indices for neighbouring spins. Args: - lattice (str): Shape of the lattice. Input Values can be ``'chain'``, ``'square'``, ``'rectangle'``, - ``'honeycomb'``, ``'triangle'``, or ``'kagome'``. - n_cells (List[int]): Number of cells in each direction of the grid. - coupling (List[List[float]] or List[math.array[float]]): Coupling between spins, it can be a 2D array - of shape (neighbour_order, 3) or a 3D array of shape 3 * number of spins * number of spins. - Default value is [1.0, 1.0, 1.0]. - boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice axes, - default is ``False`` indicating open boundary condition. - neighbour_order (int): Specifies the interaction level for neighbors within the lattice. - Default is 1, indicating nearest neighbours. + lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``, + ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``. + n_cells (List[int]): Number of cells in each direction of the grid. + coupling (List[List[float]] or List[math.array[float]]): Coupling between spins. It can be a + 2D array of shape ``(neighbour_order, 3)`` or a 3D array of shape + ``(3, num_spins, num_spins)``, where ``num_spins`` is the total number of spins. + boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice + axes, default is ``False`` indicating open boundary condition. + neighbour_order (int): Specifies the interaction level for neighbors within the lattice. + Default is 1, indicating nearest neighbours. Returns: - pennylane.LinearCombination: Hamiltonian for the heisenberg model. + ~ops.op_math.Sum: Hamiltonian for the heisenberg model. **Example** @@ -129,6 +135,7 @@ def heisenberg(lattice, n_cells, coupling=None, boundary_condition=False, neighb >>> j = [[0.5, 0.5, 0.5]] >>> spin_ham = qml.spin.heisenberg("square", n_cells, coupling=j) >>> spin_ham + ( 0.5 * (X(0) @ X(1)) + 0.5 * (Y(0) @ Y(1)) + 0.5 * (Z(0) @ Z(1)) @@ -141,7 +148,7 @@ def heisenberg(lattice, n_cells, coupling=None, boundary_condition=False, neighb + 0.5 * (X(2) @ X(3)) + 0.5 * (Y(2) @ Y(3)) + 0.5 * (Z(2) @ Z(3)) - + ) """ lattice = _generate_lattice(lattice, n_cells, boundary_condition, neighbour_order) @@ -187,7 +194,7 @@ def fermi_hubbard( neighbour_order=1, mapping="jordan_wigner", ): - r"""Generates the Fermi-Hubbard model on a lattice. + r"""Generates the Hamiltonian for the Fermi-Hubbard model on a lattice. The Hamiltonian is represented as: @@ -195,37 +202,41 @@ def fermi_hubbard( \hat{H} = -t\sum_{, \sigma}(c_{i\sigma}^{\dagger}c_{j\sigma}) + U\sum_{i}n_{i \uparrow} n_{i\downarrow} - where ``t`` is the hopping term representing the kinetic energy of electrons, ``U`` is the on-site Coulomb interaction, - representing the repulsion between electrons, ``i,j`` represent the indices for neighbouring spins, ``\sigma`` - is the spin degree of freedom, and ``n_{i \uparrow}, n_{i \downarrow}`` are number operators for spin-up and - spin-down fermions at site ``i``. - This function assumes there are two fermions with opposite spins on each lattice site. + where ``t`` is the hopping term representing the kinetic energy of electrons, ``U`` is the + on-site Coulomb interaction, representing the repulsion between electrons, ``i,j`` represent the + indices for neighbouring spins, :math:`\sigma` is the spin degree of freedom, and + :math:`n_{i \uparrow}, n_{i \downarrow}` are number operators for spin-up and spin-down fermions + at site ``i``. This function assumes there are two fermions with opposite spins on each lattice + site. Args: - lattice (str): Shape of the lattice. Input Values can be ``'chain'``, ``'square'``, - ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``. - n_cells (List[int]): Number of cells in each direction of the grid. - hopping (float or List[float] or List[math.array(float)]): Hopping strength between neighbouring sites, it can be a - number, a list of length equal to ``neighbour_order`` or a square matrix of size - ``(num_spins, num_spins)``. Default value is 1.0. - coulomb (float or List[float]): Coulomb interaction between spins, it can be a constant or a list of length ``num_spins``. - boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice axes, - default is ``False`` indicating open boundary condition. - neighbour_order (int): Specifies the interaction level for neighbors within the lattice. - Default is 1, indicating nearest neighbours. - mapping (str): Specifies the fermion-to-qubit mapping. Input values can be - ``'jordan_wigner'``, ``'parity'`` or ``'bravyi_kitaev'``. + lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``, + ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``. + n_cells (List[int]): Number of cells in each direction of the grid. + hopping (float or List[float] or List[math.array(float)]): Hopping strength between + neighbouring sites, it can be a number, a list of length equal to ``neighbour_order`` or + a square matrix of size ``(num_spins, num_spins)``, where ``num_spins`` is the total + number of spins. Default value is 1.0. + coulomb (float or List[float]): Coulomb interaction between spins. It can be a constant or a + list of length equal to number of spins. + boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice + axes, default is ``False`` indicating open boundary condition. + neighbour_order (int): Specifies the interaction level for neighbors within the lattice. + Default is 1, indicating nearest neighbours. + mapping (str): Specifies the fermion-to-qubit mapping. Input values can be + ``'jordan_wigner'``, ``'parity'`` or ``'bravyi_kitaev'``. Returns: - pennylane.operator: Hamiltonian for the Fermi-Hubbard model. + ~ops.op_math.Sum: Hamiltonian for the Fermi-Hubbard model. **Example** >>> n_cells = [2] >>> h = [0.5] - >>> u = [1.0] + >>> u = 1.0 >>> spin_ham = qml.spin.fermi_hubbard("chain", n_cells, hopping=h, coulomb=u) >>> spin_ham + ( -0.25 * (Y(0) @ Z(1) @ Y(2)) + -0.25 * (X(0) @ Z(1) @ X(2)) + 0.5 * I(0) @@ -237,7 +248,7 @@ def fermi_hubbard( + -0.25 * Z(3) + -0.25 * Z(2) + 0.25 * (Z(2) @ Z(3)) - + ) """ lattice = _generate_lattice(lattice, n_cells, boundary_condition, neighbour_order) @@ -301,7 +312,7 @@ def fermi_hubbard( def kitaev(n_cells, coupling=None, boundary_condition=False): - r"""Generates the Kitaev Hamiltonian on the Honeycomb lattice. + r"""Generates the Hamiltonian for the Kitaev model on the Honeycomb lattice. The Hamiltonian is represented as: @@ -320,7 +331,7 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): default is ``False`` indicating open boundary condition. Returns: - pennylane.LinearCombination: Hamiltonian for the Kitaev model. + ~ops.op_math.Sum: Hamiltonian for the Kitaev model. **Example** @@ -328,6 +339,16 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): >>> k = [0.5, 0.6, 0.7] >>> spin_ham = qml.spin.kitaev(n_cells, coupling=k) >>> spin_ham + ( + 0.5 * (X(0) @ X(1)) + + 0.5 * (X(2) @ X(3)) + + 0.5 * (X(4) @ X(5)) + + 0.5 * (X(6) @ X(7)) + + 0.6 * (Y(1) @ Y(2)) + + 0.6 * (Y(5) @ Y(6)) + + 0.7 * (Z(1) @ Z(4)) + + 0.7 * (Z(3) @ Z(6)) + ) """ diff --git a/tests/spin/test_spin_hamiltonian.py b/tests/spin/test_spin_hamiltonian.py index 8d67da52510..305c006f8e2 100644 --- a/tests/spin/test_spin_hamiltonian.py +++ b/tests/spin/test_spin_hamiltonian.py @@ -845,6 +845,27 @@ def test_coupling_error_kitaev(): + 0.2 * (Y(9) @ Y(10)) + 0.2 * (Y(11) @ Y(6)), ), + ( + [2, 3], + [0.1, 0.2, 0.3], + [True, False], + 0.3 * (Z(1) @ Z(6)) + + 0.3 * (Z(3) @ Z(8)) + + 0.3 * (Z(5) @ Z(10)) + + 0.3 * (Z(0) @ Z(7)) + + 0.3 * (Z(2) @ Z(9)) + + 0.3 * (Z(4) @ Z(11)) + + 0.1 * (X(0) @ X(1)) + + 0.1 * (X(2) @ X(3)) + + 0.1 * (X(4) @ X(5)) + + 0.1 * (X(6) @ X(7)) + + 0.1 * (X(8) @ X(9)) + + 0.1 * (X(10) @ X(11)) + + 0.2 * (Y(1) @ Y(2)) + + 0.2 * (Y(3) @ Y(4)) + + 0.2 * (Y(7) @ Y(8)) + + 0.2 * (Y(9) @ Y(10)), + ), ], ) def test_kitaev_hamiltonian(n_cells, j, boundary_condition, expected_ham): From 1db7eb59af9631a92387bd84102725fabc415ffa Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Wed, 4 Sep 2024 08:56:08 -0400 Subject: [PATCH 04/35] docstring fix --- pennylane/spin/__init__.py | 3 +-- pennylane/spin/spin_hamiltonian.py | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pennylane/spin/__init__.py b/pennylane/spin/__init__.py index 3af1c026b95..0040dcf958b 100644 --- a/pennylane/spin/__init__.py +++ b/pennylane/spin/__init__.py @@ -16,5 +16,4 @@ """ from .lattice import Lattice -from .spin_hamiltonian import (fermi_hubbard, heisenberg, kitaev, - transverse_ising) +from .spin_hamiltonian import fermi_hubbard, heisenberg, kitaev, transverse_ising diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index f62d8ad6316..25befbb4547 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -317,8 +317,11 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): The Hamiltonian is represented as: .. math:: - - \hat{H} = K_x.\sum_{}\sigma_i^x\sigma_j^x + K_y.\sum_{}\sigma_i^y\sigma_j^y + K_z.\sum_{}\sigma_i^z\sigma_j^z + \begin{align*} + \hat{H} = K_x.\sum_{\langle i,j \rangle \epsilon X}\sigma_i^x\sigma_j^x + + K_y.\sum_{\langle i,j \rangle \epsilon Y}\sigma_i^y\sigma_j^y + + K_z.\sum_{\langle i,j \rangle \epsilon Z}\sigma_i^z\sigma_j^z + \end{align*} where :math:`K_x`, :math:`K_y`, :math:`K_z` are the coupling constants defined for the Hamiltonian, and ``i,j`` represent the indices for neighbouring spins. From c15a6f54232608b47c61e53581320fc54be3d588 Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Wed, 4 Sep 2024 10:18:47 -0400 Subject: [PATCH 05/35] Update pennylane/spin/spin_hamiltonian.py Co-authored-by: Utkarsh --- pennylane/spin/spin_hamiltonian.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index 25befbb4547..17913ab71f8 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -318,9 +318,9 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): .. math:: \begin{align*} - \hat{H} = K_x.\sum_{\langle i,j \rangle \epsilon X}\sigma_i^x\sigma_j^x + - K_y.\sum_{\langle i,j \rangle \epsilon Y}\sigma_i^y\sigma_j^y + - K_z.\sum_{\langle i,j \rangle \epsilon Z}\sigma_i^z\sigma_j^z + \hat{H} = K_x.\sum_{\langle i,j \rangle \in X}\sigma_i^x\sigma_j^x + + K_y.\sum_{\langle i,j \rangle \in Y}\sigma_i^y\sigma_j^y + + K_z.\sum_{\langle i,j \rangle \in Z}\sigma_i^z\sigma_j^z \end{align*} where :math:`K_x`, :math:`K_y`, :math:`K_z` are the coupling constants defined for the Hamiltonian, and ``i,j`` represent From 702864f076a93f36d7e43f84086d9479e34765a0 Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Thu, 5 Sep 2024 08:42:48 -0400 Subject: [PATCH 06/35] Added custom_edges example --- pennylane/spin/lattice.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 2bf99c35485..815179b33f2 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -39,6 +39,12 @@ class Lattice: default is ``False`` indicating open boundary condition. neighbour_order (int): Specifies the interaction level for neighbors within the lattice. Default is 1 (nearest neighbour). + custom_edges (list(list(tuples))): Specifies the edges to be added in the lattice. + Default value is None, which adds the edges based on neighbour_order. + Each element in the list is for a separate edge, and can contain 1 or 2 tuples. + First tuple contains the index of the starting and ending vertex of the edge. + Second tuple is optional and contains the operator on that edge and coefficient + of that operator. distance_tol (float): Distance below which spatial points are considered equal for the purpose of identifying nearest neighbours. Default value is 1e-5. @@ -203,7 +209,22 @@ def _generate_grid(self, neighbour_order): return math.array(lattice_points), lattice_map def get_custom_edges(self, custom_edges, lattice_map): - """Generates the edges described in `custom_edges` for all unit cells.""" + """Generates the edges described in `custom_edges` for all unit cells. + + **Example** + + Generates a square lattice with a single diagonal and assigns a different operation + to horizontal, vertical, and diagonal edges. + >>> n_cells = [3,3] + >>> vectors = [[1, 0], [0,1]] + >>> custom_edges = [ + [(0, 1), ("XX", 0.1)], + [(0, 3), ("YY", 0.2)], + [(0, 4), ("XY", 0.3)], + ] + >>> lattice = Lattice(n_cells=n_cells, vectors=vectors, custom_edges=custom_edges) + + """ if not all([len(edge) in (1, 2) for edge in custom_edges]): raise TypeError( From 4a4d6bf6c156cab053e64d89cb0b6505255babef Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Thu, 5 Sep 2024 08:48:54 -0400 Subject: [PATCH 07/35] docstring fix --- pennylane/spin/spin_hamiltonian.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index 17913ab71f8..b2b0f4e847e 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -323,7 +323,8 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): K_z.\sum_{\langle i,j \rangle \in Z}\sigma_i^z\sigma_j^z \end{align*} - where :math:`K_x`, :math:`K_y`, :math:`K_z` are the coupling constants defined for the Hamiltonian, and ``i,j`` represent + where :math:`K_x`, :math:`K_y`, :math:`K_z` are the coupling constants defined for the Hamiltonian, + ``X``, ``Y``, ``Z``, represent the distinct edges in the Honeycomb lattice, and ``i,j`` represent the indices for neighbouring spins. Args: From e766e2e11f03f60147fe221c4e841282d00e6138 Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Thu, 5 Sep 2024 10:43:35 -0400 Subject: [PATCH 08/35] docstring fix --- pennylane/spin/spin_hamiltonian.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index b2b0f4e847e..885e0204730 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -324,8 +324,9 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): \end{align*} where :math:`K_x`, :math:`K_y`, :math:`K_z` are the coupling constants defined for the Hamiltonian, - ``X``, ``Y``, ``Z``, represent the distinct edges in the Honeycomb lattice, and ``i,j`` represent - the indices for neighbouring spins. + and :math:`X`, :math:`Y`, :math:`Z` represent the set of edges in the Honeycomb lattice between spins + :math:`i` and :math:`j` with real-space bond directions :math:`[0, 1], [\frac{\sqrt{3}}{2}, \frac{1}{2}], + \frac{\sqrt{3}}{2}, -\frac{1}{2}]`, respectively. Args: n_cells (List[int]): Number of cells in each direction of the grid. From 9f6e9e490851d0f30f55054ed50d83c91aa5e83b Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Fri, 6 Sep 2024 05:10:45 -0400 Subject: [PATCH 09/35] Update pennylane/spin/lattice.py Co-authored-by: Austin Huang <65315367+austingmhuang@users.noreply.github.com> --- pennylane/spin/lattice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 815179b33f2..209683b196c 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -229,7 +229,7 @@ def get_custom_edges(self, custom_edges, lattice_map): if not all([len(edge) in (1, 2) for edge in custom_edges]): raise TypeError( """ - custom_edges must be a list of tuples of length 1 or 2. + The elements of custom_edges should be lists of length 1 or 2. Inside said lists should be a tuple that contains two lattice indices to represent the edge and, optionally, a tuple that represents the operation and coefficient for that edge. Every tuple must contain two lattice indices to represent the edge and can optionally include a list to represent the operation and coefficient for that edge. """ From f47b3ecbe1ce029093f59dfee9a164f7d4c65d77 Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Fri, 6 Sep 2024 05:11:16 -0400 Subject: [PATCH 10/35] Update pennylane/spin/spin_hamiltonian.py Co-authored-by: Austin Huang <65315367+austingmhuang@users.noreply.github.com> --- pennylane/spin/spin_hamiltonian.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index 885e0204730..28f0b8734d6 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -329,10 +329,10 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): \frac{\sqrt{3}}{2}, -\frac{1}{2}]`, respectively. Args: - n_cells (List[int]): Number of cells in each direction of the grid. - coupling (List[math.array[float]]): Coupling between spins, it is a list of length 3. + n_cells (list[int]): Number of cells in each direction of the grid. + coupling (Optional[list[math.array[float]]]): Coupling between spins, it is a list of length 3. Default value is [1.0, 1.0, 1.0]. - boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice axes, + boundary_condition (bool | list[bool]): Defines boundary conditions for different lattice axes, default is ``False`` indicating open boundary condition. Returns: From 902be928933bf87743c6369207bee89757939802 Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Fri, 6 Sep 2024 05:11:29 -0400 Subject: [PATCH 11/35] Update pennylane/spin/lattice.py Co-authored-by: Austin Huang <65315367+austingmhuang@users.noreply.github.com> --- pennylane/spin/lattice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 209683b196c..81fb98c3df7 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -39,7 +39,7 @@ class Lattice: default is ``False`` indicating open boundary condition. neighbour_order (int): Specifies the interaction level for neighbors within the lattice. Default is 1 (nearest neighbour). - custom_edges (list(list(tuples))): Specifies the edges to be added in the lattice. + custom_edges (Optional[list(list(tuples))]): Specifies the edges to be added in the lattice. Default value is None, which adds the edges based on neighbour_order. Each element in the list is for a separate edge, and can contain 1 or 2 tuples. First tuple contains the index of the starting and ending vertex of the edge. From 201fab91d9aba3b603a2bb5c7be58bcc74276356 Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Fri, 6 Sep 2024 05:11:39 -0400 Subject: [PATCH 12/35] Update pennylane/spin/lattice.py Co-authored-by: Austin Huang <65315367+austingmhuang@users.noreply.github.com> --- pennylane/spin/lattice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 81fb98c3df7..a16fc4afa6b 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -35,7 +35,7 @@ class Lattice: positions (list[list[float]]): Initial positions of spin cites. Default value is ``[[0.0]`` :math:`\times` ``number of dimensions]``. - boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice axes, + boundary_condition (bool | list[bool]): Defines boundary conditions for different lattice axes, default is ``False`` indicating open boundary condition. neighbour_order (int): Specifies the interaction level for neighbors within the lattice. Default is 1 (nearest neighbour). From d3ced1166d945bcc2eae287b9b4222bfd021548d Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Fri, 6 Sep 2024 09:23:37 -0400 Subject: [PATCH 13/35] Addressed comments --- pennylane/spin/spin_hamiltonian.py | 13 +++++++++---- tests/spin/test_spin_hamiltonian.py | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index 28f0b8734d6..623c3e9f0ea 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -312,7 +312,8 @@ def fermi_hubbard( def kitaev(n_cells, coupling=None, boundary_condition=False): - r"""Generates the Hamiltonian for the Kitaev model on the Honeycomb lattice. + r"""Generates the Hamiltonian for the `Kitaev model `_ + on the Honeycomb lattice. The Hamiltonian is represented as: @@ -330,11 +331,15 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): Args: n_cells (list[int]): Number of cells in each direction of the grid. - coupling (Optional[list[math.array[float]]]): Coupling between spins, it is a list of length 3. + coupling (Optional[list[float] | tensor_like(float)]): Coupling between spins, it is a list of length 3. Default value is [1.0, 1.0, 1.0]. - boundary_condition (bool | list[bool]): Defines boundary conditions for different lattice axes, + boundary_condition (Optional[bool | list[bool]]): Defines boundary conditions for different lattice axes, default is ``False`` indicating open boundary condition. + Raises: + TypeError: + if ``coupling`` doesn't have correct dimensions. + Returns: ~ops.op_math.Sum: Hamiltonian for the Kitaev model. @@ -361,7 +366,7 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): coupling = [1.0, 1.0, 1.0] if len(coupling) != 3: - raise ValueError("The coupling parameter should be a list of length 3") + raise ValueError("The coupling parameter should be a list of length 3.") vectors = [[1, 0], [0.5, 0.75**0.5]] positions = [[0, 0], [0.5, 0.5 / 3**0.5]] diff --git a/tests/spin/test_spin_hamiltonian.py b/tests/spin/test_spin_hamiltonian.py index c320d1205ec..959e05acc6a 100644 --- a/tests/spin/test_spin_hamiltonian.py +++ b/tests/spin/test_spin_hamiltonian.py @@ -789,7 +789,7 @@ def test_coupling_error_kitaev(): Kitaev Hamiltonian.""" with pytest.raises( ValueError, - match=re.escape("The coupling parameter should be a list of length 3"), + match=re.escape("The coupling parameter should be a list of length 3."), ): kitaev(n_cells=[3, 4], coupling=[1.0, 2.0]) From 53868270c88d01ea85e6e4ab28ededb8d3dbbfa4 Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Sun, 8 Sep 2024 05:37:04 -0400 Subject: [PATCH 14/35] Update pennylane/spin/lattice.py Co-authored-by: Utkarsh --- pennylane/spin/lattice.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index a16fc4afa6b..c7da4a93091 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -258,15 +258,10 @@ def get_custom_edges(self, custom_edges, lattice_map): translation_vector = math.asarray(math.rint(translation_vector), dtype=int) edge_ranges = [] for idx, cell in enumerate(self.n_cells): - if self.boundary_condition[idx]: - edge_ranges.append(range(0, cell)) - else: - edge_ranges.append( - range( - math.maximum(0, -translation_vector[idx]), - cell - math.maximum(0, translation_vector[idx]), - ) - ) + t_point = 0 if self.boundary_condition[idx] else translation_vector[idx] + edge_ranges.append( + range(math.maximum(0, -t_point), cell - math.maximum(0, t_point)) + ) for cell in itertools.product(*edge_ranges): node1_idx = math.dot(math.mod(cell, self.n_cells), nsites_axis) + v1 From c285ec7e4a990b0e26bba57de07e4d71b05ea0ab Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Sun, 8 Sep 2024 05:37:22 -0400 Subject: [PATCH 15/35] Update pennylane/spin/spin_hamiltonian.py Co-authored-by: Utkarsh --- pennylane/spin/spin_hamiltonian.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index 623c3e9f0ea..84ec957474a 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -333,8 +333,8 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): n_cells (list[int]): Number of cells in each direction of the grid. coupling (Optional[list[float] | tensor_like(float)]): Coupling between spins, it is a list of length 3. Default value is [1.0, 1.0, 1.0]. - boundary_condition (Optional[bool | list[bool]]): Defines boundary conditions for different lattice axes, - default is ``False`` indicating open boundary condition. + boundary_condition (Optional[bool | list[bool]]): Defines boundary conditions for different lattice axes. + The default is ``False``, indicating open boundary conditions for all. Raises: TypeError: From 09727358cffc1c86fdb129c2a3298d3852725e7c Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Mon, 9 Sep 2024 06:49:24 -0400 Subject: [PATCH 16/35] fixed docstring for custom_edges --- pennylane/spin/lattice.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index c7da4a93091..8c8c0573146 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -130,7 +130,7 @@ def __init__( raise ValueError( "custom_edges and neighbour_order cannot be specified at the same time" ) - self.edges = self.get_custom_edges(custom_edges, lattice_map) + self.edges = self._get_custom_edges(custom_edges, lattice_map) self.edges_indices = [(v1, v2) for (v1, v2, color) in self.edges] @@ -208,9 +208,18 @@ def _generate_grid(self, neighbour_order): return math.array(lattice_points), lattice_map - def get_custom_edges(self, custom_edges, lattice_map): + def _get_custom_edges(self, custom_edges, lattice_map): """Generates the edges described in `custom_edges` for all unit cells. + Args: + custom_edges (Optional[list(list(tuples))]): Specifies the edges to be added in the lattice. + Default value is None, which adds the edges based on neighbour_order. + Each element in the list is for a separate edge, and can contain 1 or 2 tuples. + First tuple contains the index of the starting and ending vertex of the edge. + Second tuple is optional and contains the operator on that edge and coefficient + of that operator. + lattice_map (list[int]): A list to represent the node number for each lattice_point. + **Example** Generates a square lattice with a single diagonal and assigns a different operation From 60491e23bc8e5981d323954b901ecc95fad33e71 Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Mon, 9 Sep 2024 07:10:41 -0400 Subject: [PATCH 17/35] Fixed tests --- tests/spin/test_lattice.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/spin/test_lattice.py b/tests/spin/test_lattice.py index 1b0eefc0bd5..b53c3aeea62 100644 --- a/tests/spin/test_lattice.py +++ b/tests/spin/test_lattice.py @@ -561,7 +561,9 @@ def test_custom_edge_type_error(): vectors = [[0, 1], [1, 0]] n_cells = [3, 3] custom_edges = [[(0, 1), 1, 3], [(0, 5)], [(0, 4)]] - with pytest.raises(TypeError, match="custom_edges must be a list of tuples of length 1 or 2."): + with pytest.raises( + TypeError, match="The elements of custom_edges should be lists of length 1 or 2." + ): Lattice(n_cells=n_cells, vectors=vectors, custom_edges=custom_edges) From af8c601d0e668e0d6d850ec6a6e9c0a23c7718fb Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Mon, 9 Sep 2024 07:44:29 -0400 Subject: [PATCH 18/35] Updated changelog --- doc/releases/changelog-dev.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 8f116a7d610..ced75b6f822 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -4,6 +4,10 @@

New features since last release

+* Function is added for generating spin Hamiltonian for + [Kitaev](Improvements 🛠 * Improve unit testing for capturing of nested control flows. From cd42fde10d4cda7ef778907313ebdeaad9c0511f Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Mon, 9 Sep 2024 08:28:28 -0400 Subject: [PATCH 19/35] Documentation build fix --- doc/releases/changelog-dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index ced75b6f822..e6c574bdc3b 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -5,7 +5,7 @@

New features since last release

* Function is added for generating spin Hamiltonian for - [Kitaev](Improvements 🛠 From 8dc021b8e93d7d1f81ebb64519e7088c0e13b8fc Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:50:08 -0400 Subject: [PATCH 20/35] Update doc/releases/changelog-dev.md Co-authored-by: Austin Huang <65315367+austingmhuang@users.noreply.github.com> --- doc/releases/changelog-dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index e6c574bdc3b..1828f5dd198 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -4,7 +4,7 @@

New features since last release

-* Function is added for generating spin Hamiltonian for +* Function is added for generating the spin Hamiltonian for the [Kitaev](https://arxiv.org/pdf/2406.06625) model on a lattice. [(#6174)](https://github.com/PennyLaneAI/pennylane/pull/6174) From e35099090abde19748d8846495300fa6c1e4c000 Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:14:17 -0400 Subject: [PATCH 21/35] Update pennylane/spin/spin_hamiltonian.py Co-authored-by: Utkarsh --- pennylane/spin/spin_hamiltonian.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index 84ec957474a..eeb22c257f6 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -337,8 +337,7 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): The default is ``False``, indicating open boundary conditions for all. Raises: - TypeError: - if ``coupling`` doesn't have correct dimensions. + ValueError: if ``coupling`` doesn't have correct dimensions. Returns: ~ops.op_math.Sum: Hamiltonian for the Kitaev model. From a8288e63e6fafb776e9cfe7239c26f3b1ff5a97c Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:14:34 -0400 Subject: [PATCH 22/35] Update pennylane/spin/spin_hamiltonian.py Co-authored-by: Austin Huang <65315367+austingmhuang@users.noreply.github.com> --- pennylane/spin/spin_hamiltonian.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index eeb22c257f6..22c1f2682d4 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -333,7 +333,7 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): n_cells (list[int]): Number of cells in each direction of the grid. coupling (Optional[list[float] | tensor_like(float)]): Coupling between spins, it is a list of length 3. Default value is [1.0, 1.0, 1.0]. - boundary_condition (Optional[bool | list[bool]]): Defines boundary conditions for different lattice axes. + boundary_condition (bool | list[bool]): Defines boundary conditions for different lattice axes. The default is ``False``, indicating open boundary conditions for all. Raises: From d80c4942bb234133513025531589a1b938610f79 Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Mon, 16 Sep 2024 11:17:24 -0400 Subject: [PATCH 23/35] Update pennylane/spin/lattice.py Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> --- pennylane/spin/lattice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 8c8c0573146..38e965b8bc9 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -42,7 +42,7 @@ class Lattice: custom_edges (Optional[list(list(tuples))]): Specifies the edges to be added in the lattice. Default value is None, which adds the edges based on neighbour_order. Each element in the list is for a separate edge, and can contain 1 or 2 tuples. - First tuple contains the index of the starting and ending vertex of the edge. + First tuple contains the indices of the starting and ending vertices of the edge. Second tuple is optional and contains the operator on that edge and coefficient of that operator. distance_tol (float): Distance below which spatial points are considered equal for the From 5b940d42f0da443801b08d023334064b62c3cf70 Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Mon, 16 Sep 2024 11:17:32 -0400 Subject: [PATCH 24/35] Update pennylane/spin/lattice.py Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> --- pennylane/spin/lattice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 38e965b8bc9..46709633ce9 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -40,7 +40,7 @@ class Lattice: neighbour_order (int): Specifies the interaction level for neighbors within the lattice. Default is 1 (nearest neighbour). custom_edges (Optional[list(list(tuples))]): Specifies the edges to be added in the lattice. - Default value is None, which adds the edges based on neighbour_order. + Default value is ``None``, which adds the edges based on ``neighbour_order``. Each element in the list is for a separate edge, and can contain 1 or 2 tuples. First tuple contains the indices of the starting and ending vertices of the edge. Second tuple is optional and contains the operator on that edge and coefficient From acd399de9fd1b552dbcf841b25a3c1a34062df01 Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Mon, 16 Sep 2024 11:17:40 -0400 Subject: [PATCH 25/35] Update pennylane/spin/lattice.py Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> --- pennylane/spin/lattice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 46709633ce9..2c3e56e298e 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -128,7 +128,7 @@ def __init__( else: if neighbour_order > 1: raise ValueError( - "custom_edges and neighbour_order cannot be specified at the same time" + "custom_edges and neighbour_order cannot be specified at the same time." ) self.edges = self._get_custom_edges(custom_edges, lattice_map) From 9b4579e0734bafb696fbba40f0f67308476628d5 Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Mon, 16 Sep 2024 11:17:51 -0400 Subject: [PATCH 26/35] Update pennylane/spin/lattice.py Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> --- pennylane/spin/lattice.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 2c3e56e298e..4f43007bc2f 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -120,9 +120,7 @@ def __init__( self.n_sites = math.prod(n_cells) * n_sl self.lattice_points, lattice_map = self._generate_grid(neighbour_order) if custom_edges is None: - cutoff = ( - neighbour_order * math.max(math.linalg.norm(self.vectors, axis=1)) + distance_tol - ) + cutoff = neighbour_order * math.max(math.linalg.norm(self.vectors, axis=1)) + distance_tol edges = self._identify_neighbours(cutoff) self.edges = Lattice._generate_true_edges(edges, lattice_map, neighbour_order) else: From c4db4ebbb5cbac6d5e2363f63751efc7ca640f40 Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Mon, 16 Sep 2024 12:57:06 -0400 Subject: [PATCH 27/35] Addressed comments --- doc/releases/changelog-dev.md | 2 +- pennylane/spin/lattice.py | 47 +++++++++++++++++++++++++----- pennylane/spin/spin_hamiltonian.py | 21 +++++++------ tests/spin/test_lattice.py | 3 +- 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 1828f5dd198..1a0afe3a917 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -5,7 +5,7 @@

New features since last release

* Function is added for generating the spin Hamiltonian for the - [Kitaev](https://arxiv.org/pdf/2406.06625) model on a lattice. + [Kitaev](https://arxiv.org/abs/cond-mat/0506438) model on a lattice. [(#6174)](https://github.com/PennyLaneAI/pennylane/pull/6174)

Improvements 🛠

diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 4f43007bc2f..375e484ee50 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -38,13 +38,13 @@ class Lattice: boundary_condition (bool | list[bool]): Defines boundary conditions for different lattice axes, default is ``False`` indicating open boundary condition. neighbour_order (int): Specifies the interaction level for neighbors within the lattice. - Default is 1 (nearest neighbour). + Default is 1 (nearest neighbour). This cannot be greater than 1 if custom_edges is defined. custom_edges (Optional[list(list(tuples))]): Specifies the edges to be added in the lattice. Default value is ``None``, which adds the edges based on ``neighbour_order``. Each element in the list is for a separate edge, and can contain 1 or 2 tuples. First tuple contains the indices of the starting and ending vertices of the edge. Second tuple is optional and contains the operator on that edge and coefficient - of that operator. + of that operator. Default value is the index of edge in custom_edges list. distance_tol (float): Distance below which spatial points are considered equal for the purpose of identifying nearest neighbours. Default value is 1e-5. @@ -120,13 +120,15 @@ def __init__( self.n_sites = math.prod(n_cells) * n_sl self.lattice_points, lattice_map = self._generate_grid(neighbour_order) if custom_edges is None: - cutoff = neighbour_order * math.max(math.linalg.norm(self.vectors, axis=1)) + distance_tol + cutoff = ( + neighbour_order * math.max(math.linalg.norm(self.vectors, axis=1)) + distance_tol + ) edges = self._identify_neighbours(cutoff) self.edges = Lattice._generate_true_edges(edges, lattice_map, neighbour_order) else: if neighbour_order > 1: raise ValueError( - "custom_edges and neighbour_order cannot be specified at the same time." + "custom_edges cannot be specified if neighbour_order argument is set to greater than 1." ) self.edges = self._get_custom_edges(custom_edges, lattice_map) @@ -218,6 +220,9 @@ def _get_custom_edges(self, custom_edges, lattice_map): of that operator. lattice_map (list[int]): A list to represent the node number for each lattice_point. + Returns: + List of edges. + **Example** Generates a square lattice with a single diagonal and assigns a different operation @@ -228,15 +233,36 @@ def _get_custom_edges(self, custom_edges, lattice_map): [(0, 1), ("XX", 0.1)], [(0, 3), ("YY", 0.2)], [(0, 4), ("XY", 0.3)], - ] - >>> lattice = Lattice(n_cells=n_cells, vectors=vectors, custom_edges=custom_edges) + ] + >>> lattice = qml.spin.Lattice(n_cells=n_cells, vectors=vectors, custom_edges=custom_edges) + >>> lattice.edges + [(0, 1, ('XX', 0.1)), + (1, 2, ('XX', 0.1)), + (3, 4, ('XX', 0.1)), + (4, 5, ('XX', 0.1)), + (6, 7, ('XX', 0.1)), + (7, 8, ('XX', 0.1)), + (0, 3, ('YY', 0.2)), + (1, 4, ('YY', 0.2)), + (2, 5, ('YY', 0.2)), + (3, 6, ('YY', 0.2)), + (4, 7, ('YY', 0.2)), + (5, 8, ('YY', 0.2)), + (0, 4, ('XY', 0.3)), + (1, 5, ('XY', 0.3)), + (3, 7, ('XY', 0.3)), + (4, 8, ('XY', 0.3)) + ] """ if not all([len(edge) in (1, 2) for edge in custom_edges]): raise TypeError( """ - The elements of custom_edges should be lists of length 1 or 2. Inside said lists should be a tuple that contains two lattice indices to represent the edge and, optionally, a tuple that represents the operation and coefficient for that edge. + The elements of custom_edges should be lists of length 1 or 2. + Inside said lists should be a tuple that contains two lattice + indices to represent the edge and, optionally, a tuple that represents + the operation and coefficient for that edge. Every tuple must contain two lattice indices to represent the edge and can optionally include a list to represent the operation and coefficient for that edge. """ @@ -255,14 +281,20 @@ def _get_custom_edges(self, custom_edges, lattice_map): ) edge_operation = custom_edge[1] if len(custom_edge) == 2 else i + + # Finds the coordinates of lattice vertices to be connected map_edge1 = lattice_map.index(edge[0]) map_edge2 = lattice_map.index(edge[1]) edge_distance = self.lattice_points[map_edge2] - self.lattice_points[map_edge1] + + # Calculates the number of unit cells that a given edge spans in each direction v1, v2 = math.mod(edge, n_sl) translation_vector = ( edge_distance + self.positions[v1] - self.positions[v2] ) @ math.linalg.inv(self.vectors) translation_vector = math.asarray(math.rint(translation_vector), dtype=int) + + # Finds the minimum and maximum range for a given edge based on boundary_conditions edge_ranges = [] for idx, cell in enumerate(self.n_cells): t_point = 0 if self.boundary_condition[idx] else translation_vector[idx] @@ -270,6 +302,7 @@ def _get_custom_edges(self, custom_edges, lattice_map): range(math.maximum(0, -t_point), cell - math.maximum(0, t_point)) ) + # Finds the indices for starting and ending vertices of the edge for cell in itertools.product(*edge_ranges): node1_idx = math.dot(math.mod(cell, self.n_cells), nsites_axis) + v1 node2_idx = ( diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index 22c1f2682d4..15f6b59671c 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -312,10 +312,9 @@ def fermi_hubbard( def kitaev(n_cells, coupling=None, boundary_condition=False): - r"""Generates the Hamiltonian for the `Kitaev model `_ - on the Honeycomb lattice. + r"""Generates the Hamiltonian for the Kitaev model on the Honeycomb lattice. - The Hamiltonian is represented as: + The `Kitaev `_ model Hamiltonian is represented as: .. math:: \begin{align*} @@ -349,14 +348,14 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): >>> spin_ham = qml.spin.kitaev(n_cells, coupling=k) >>> spin_ham ( - 0.5 * (X(0) @ X(1)) - + 0.5 * (X(2) @ X(3)) - + 0.5 * (X(4) @ X(5)) - + 0.5 * (X(6) @ X(7)) - + 0.6 * (Y(1) @ Y(2)) - + 0.6 * (Y(5) @ Y(6)) - + 0.7 * (Z(1) @ Z(4)) - + 0.7 * (Z(3) @ Z(6)) + 0.5 * (X(0) @ X(1)) + + 0.5 * (X(2) @ X(3)) + + 0.5 * (X(4) @ X(5)) + + 0.5 * (X(6) @ X(7)) + + 0.6 * (Y(1) @ Y(2)) + + 0.6 * (Y(5) @ Y(6)) + + 0.7 * (Z(1) @ Z(4)) + + 0.7 * (Z(3) @ Z(6)) ) """ diff --git a/tests/spin/test_lattice.py b/tests/spin/test_lattice.py index b53c3aeea62..61838a1be08 100644 --- a/tests/spin/test_lattice.py +++ b/tests/spin/test_lattice.py @@ -550,7 +550,8 @@ def test_neighbour_order_error(): n_cells = [3, 3] custom_edges = [[(0, 1)], [(0, 5)], [(0, 4)]] with pytest.raises( - ValueError, match="custom_edges and neighbour_order cannot be specified at the same time" + ValueError, + match="custom_edges cannot be specified if neighbour_order argument is set to greater than 1.", ): Lattice(n_cells=n_cells, vectors=vectors, neighbour_order=2, custom_edges=custom_edges) From f600efb047204e7ee3013a06a464a90ab59453d2 Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Thu, 19 Sep 2024 01:53:12 -0400 Subject: [PATCH 28/35] minor docstring fix --- pennylane/spin/spin_hamiltonian.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index 15f6b59671c..b2466ece2b9 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -319,8 +319,8 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): .. math:: \begin{align*} \hat{H} = K_x.\sum_{\langle i,j \rangle \in X}\sigma_i^x\sigma_j^x + - K_y.\sum_{\langle i,j \rangle \in Y}\sigma_i^y\sigma_j^y + - K_z.\sum_{\langle i,j \rangle \in Z}\sigma_i^z\sigma_j^z + \:\: K_y.\sum_{\langle i,j \rangle \in Y}\sigma_i^y\sigma_j^y + + \:\: K_z.\sum_{\langle i,j \rangle \in Z}\sigma_i^z\sigma_j^z \end{align*} where :math:`K_x`, :math:`K_y`, :math:`K_z` are the coupling constants defined for the Hamiltonian, From 775ba90a4388a8a454f188a6bee9a4ee450b6c3e Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Fri, 20 Sep 2024 07:27:36 -0400 Subject: [PATCH 29/35] Addressed comments --- pennylane/spin/lattice.py | 3 ++- pennylane/spin/spin_hamiltonian.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 375e484ee50..1c7b11d4c88 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -282,7 +282,8 @@ def _get_custom_edges(self, custom_edges, lattice_map): edge_operation = custom_edge[1] if len(custom_edge) == 2 else i - # Finds the coordinates of lattice vertices to be connected + # Finds the coordinates of starting and ending vertices of the edge + # and the vector distance between the coordinates map_edge1 = lattice_map.index(edge[0]) map_edge2 = lattice_map.index(edge[1]) edge_distance = self.lattice_points[map_edge2] - self.lattice_points[map_edge1] diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index b2466ece2b9..b35003a28fe 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -330,9 +330,9 @@ def kitaev(n_cells, coupling=None, boundary_condition=False): Args: n_cells (list[int]): Number of cells in each direction of the grid. - coupling (Optional[list[float] | tensor_like(float)]): Coupling between spins, it is a list of length 3. + coupling (Optional[list[float] or tensor_like(float)]): Coupling between spins, it is a list of length 3. Default value is [1.0, 1.0, 1.0]. - boundary_condition (bool | list[bool]): Defines boundary conditions for different lattice axes. + boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice axes. The default is ``False``, indicating open boundary conditions for all. Raises: From 81c9766e7bd33f725942502409dd80314b746e3f Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Fri, 20 Sep 2024 08:37:50 -0400 Subject: [PATCH 30/35] used dictionary for lattice mapping --- pennylane/spin/lattice.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 1c7b11d4c88..14a6137f55d 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -130,6 +130,7 @@ def __init__( raise ValueError( "custom_edges cannot be specified if neighbour_order argument is set to greater than 1." ) + lattice_map = dict(zip(lattice_map, self.lattice_points)) self.edges = self._get_custom_edges(custom_edges, lattice_map) self.edges_indices = [(v1, v2) for (v1, v2, color) in self.edges] @@ -284,9 +285,9 @@ def _get_custom_edges(self, custom_edges, lattice_map): # Finds the coordinates of starting and ending vertices of the edge # and the vector distance between the coordinates - map_edge1 = lattice_map.index(edge[0]) - map_edge2 = lattice_map.index(edge[1]) - edge_distance = self.lattice_points[map_edge2] - self.lattice_points[map_edge1] + vertex1 = lattice_map[edge[0]] + vertex2 = lattice_map[edge[1]] + edge_distance = vertex2 - vertex1 # Calculates the number of unit cells that a given edge spans in each direction v1, v2 = math.mod(edge, n_sl) From f12a17349366d98b4e1ddd5df0cf2a366582c1bf Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Fri, 20 Sep 2024 08:40:16 -0400 Subject: [PATCH 31/35] Update doc/releases/changelog-dev.md --- doc/releases/changelog-dev.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 6db86325d6f..db055529904 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -12,7 +12,6 @@ Von Neumann entanglement entropy of a quantum state. [(#5911)](https://github.com/PennyLaneAI/pennylane/pull/5911) -

Improvements 🛠

* PennyLane is now compatible with NumPy 2.0. From 3eadc14c8524dc2735a454600d2986e187581cfd Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Fri, 20 Sep 2024 08:41:17 -0400 Subject: [PATCH 32/35] Update pennylane/spin/lattice.py --- pennylane/spin/lattice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 14a6137f55d..79612e4b9f3 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -35,7 +35,7 @@ class Lattice: positions (list[list[float]]): Initial positions of spin cites. Default value is ``[[0.0]`` :math:`\times` ``number of dimensions]``. - boundary_condition (bool | list[bool]): Defines boundary conditions for different lattice axes, + boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice axes, default is ``False`` indicating open boundary condition. neighbour_order (int): Specifies the interaction level for neighbors within the lattice. Default is 1 (nearest neighbour). This cannot be greater than 1 if custom_edges is defined. From b45dd17670871eb917125c33a310d4c74464cd4a Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Fri, 20 Sep 2024 13:28:42 -0400 Subject: [PATCH 33/35] resolved conflicts --- doc/releases/changelog-dev.md | 7 +- pennylane/spin/__init__.py | 2 +- pennylane/spin/lattice.py | 33 +- pennylane/spin/spin_hamiltonian.py | 301 +++++++++++++++- tests/spin/test_spin_hamiltonian.py | 521 +++++++++++++++++++++++++++- 5 files changed, 844 insertions(+), 20 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index db055529904..0abe65716b0 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -8,6 +8,11 @@ [Kitaev](https://arxiv.org/abs/cond-mat/0506438) model on a lattice. [(#6174)](https://github.com/PennyLaneAI/pennylane/pull/6174) +* Functions are added for generating spin Hamiltonians for [Emery] + (https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.58.2794) and + [Haldane](https://journals.aps.org/prl/pdf/10.1103/PhysRevLett.61.2015) models on a lattice. + [(#6201)](https://github.com/PennyLaneAI/pennylane/pull/6201/) + * A new `qml.vn_entanglement_entropy` measurement process has been added which measures the Von Neumann entanglement entropy of a quantum state. [(#5911)](https://github.com/PennyLaneAI/pennylane/pull/5911) @@ -184,9 +189,9 @@ This release contains contributions from (in alphabetical order): Guillermo Alonso, Utkarsh Azad, -Diksha Dhawan, Astral Cai, Isaac De Vlugt, +Diksha Dhawan, Lillian M. A. Frederiksen, Pietropaolo Frisoni, Emiliano Godinez, diff --git a/pennylane/spin/__init__.py b/pennylane/spin/__init__.py index 0040dcf958b..0b4b32fa492 100644 --- a/pennylane/spin/__init__.py +++ b/pennylane/spin/__init__.py @@ -16,4 +16,4 @@ """ from .lattice import Lattice -from .spin_hamiltonian import fermi_hubbard, heisenberg, kitaev, transverse_ising +from .spin_hamiltonian import emery, fermi_hubbard, haldane, heisenberg, kitaev, transverse_ising diff --git a/pennylane/spin/lattice.py b/pennylane/spin/lattice.py index 79612e4b9f3..964fb9f08cc 100644 --- a/pennylane/spin/lattice.py +++ b/pennylane/spin/lattice.py @@ -257,17 +257,23 @@ def _get_custom_edges(self, custom_edges, lattice_map): """ - if not all([len(edge) in (1, 2) for edge in custom_edges]): - raise TypeError( - """ - The elements of custom_edges should be lists of length 1 or 2. - Inside said lists should be a tuple that contains two lattice - indices to represent the edge and, optionally, a tuple that represents - the operation and coefficient for that edge. - Every tuple must contain two lattice indices to represent the edge - and can optionally include a list to represent the operation and coefficient for that edge. - """ - ) + for edge in custom_edges: + if len(edge) not in (1, 2): + raise TypeError( + """ + The elements of custom_edges should be lists of length 1 or 2. + Inside said lists should be a tuple that contains two lattice + indices to represent the edge and, optionally, a tuple that represents + the operation and coefficient for that edge. + Every tuple must contain two lattice indices to represent the edge + and can optionally include a list to represent the operation and coefficient for that edge. + """ + ) + + if edge[0][0] >= self.n_sites or edge[0][1] >= self.n_sites: + raise ValueError( + f"The edge {edge[0]} has vertices greater than n_sites, {self.n_sites}" + ) edges = [] n_sl = len(self.positions) @@ -276,11 +282,6 @@ def _get_custom_edges(self, custom_edges, lattice_map): for i, custom_edge in enumerate(custom_edges): edge = custom_edge[0] - if edge[0] >= self.n_sites or edge[1] >= self.n_sites: - raise ValueError( - f"The edge {edge} has vertices greater than n_sites, {self.n_sites}" - ) - edge_operation = custom_edge[1] if len(custom_edge) == 2 else i # Finds the coordinates of starting and ending vertices of the edge diff --git a/pennylane/spin/spin_hamiltonian.py b/pennylane/spin/spin_hamiltonian.py index b35003a28fe..fd518cb7101 100644 --- a/pennylane/spin/spin_hamiltonian.py +++ b/pennylane/spin/spin_hamiltonian.py @@ -21,7 +21,7 @@ from .lattice import Lattice, _generate_lattice -# pylint: disable=too-many-arguments +# pylint: disable=too-many-arguments, too-many-branches def transverse_ising( @@ -311,6 +311,305 @@ def fermi_hubbard( return qubit_ham.simplify() +def emery( + lattice, + n_cells, + hopping=1.0, + coulomb=1.0, + intersite_coupling=1.0, + boundary_condition=False, + neighbour_order=1, + mapping="jordan_wigner", +): + r"""Generates the Hamiltonian for the Emery model on a lattice. + + The `Hamiltonian `_ for the `Emery model `_ is represented as: + + .. math:: + \begin{align*} + \hat{H} & = -\sum_{\langle i,j \rangle, \sigma} t_{ij}(c_{i\sigma}^{\dagger}c_{j\sigma}) + + \sum_{i}U_{i}n_{i \uparrow} n_{i\downarrow} + \sum_{}V_{ij}(n_{i \uparrow} + + n_{i \downarrow})(n_{j \uparrow} + n_{j \downarrow})\ , + \end{align*} + + where :math:`t_{ij}` is the hopping term representing the kinetic energy of electrons, + :math:`U_{i}` is the on-site Coulomb interaction, representing the repulsion between electrons, + :math:`V_{ij}` is the intersite coupling, :math:`i, j` are the indices for neighbouring spins, + :math:`\sigma` is the spin degree of freedom, and :math:`n_{k \uparrow}`, :math:`n_{k \downarrow}` + are number operators for spin-up and spin-down fermions at site :math:`k`. + This function assumes there are two fermions with opposite spins on each lattice site. + + Args: + lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``, + ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``. + n_cells (list[int]): Number of cells in each direction of the grid. + hopping (float or list[float] or tensor_like[float]): Hopping strength between + neighbouring sites. It can be a number, a list of length equal to ``neighbour_order`` or + a square matrix of shape ``(n_sites, n_sites)``, where ``n_sites`` is the total + number of sites. Default value is 1.0. + coulomb (float or list[float]): Coulomb interaction between spins. It can be a constant or a + list of length equal to number of spins. + intersite_coupling (float or list[float] or tensor_like[float]): Interaction strength between spins on + neighbouring sites. It can be a number, a list with length equal to ``neighbour_order`` or + a square matrix of size ``(n_sites, n_sites)``, where ``n_sites`` is the total + number of sites. Default value is 1.0. + boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice + axes. Default is ``False`` indicating open boundary condition. + neighbour_order (int): Specifies the interaction level for neighbors within the lattice. + Default is 1, indicating nearest neighbours. + mapping (str): Specifies the fermion-to-qubit mapping. Input values can be + ``'jordan_wigner'``, ``'parity'`` or ``'bravyi_kitaev'``. + + Raises: + ValueError: + If ``hopping``, ``coulomb``, or ``intersite_coupling`` doesn't have correct dimensions, + or if ``mapping`` is not available. + + Returns: + ~ops.op_math.Sum: Hamiltonian for the Emery model. + + **Example** + + >>> n_cells = [2] + >>> h = [0.5] + >>> u = 1.0 + >>> v = 0.2 + >>> spin_ham = qml.spin.emery("chain", n_cells, hopping=h, coulomb=u, + intersite_coupling=v) + >>> spin_ham + ( + -0.25 * (Y(0) @ Z(1) @ Y(2)) + + -0.25 * (X(0) @ Z(1) @ X(2)) + + 0.7000000000000002 * I(0) + + -0.25 * (Y(1) @ Z(2) @ Y(3)) + + -0.25 * (X(1) @ Z(2) @ X(3)) + + -0.35 * Z(1) + + -0.35 * Z(0) + + 0.25 * (Z(0) @ Z(1)) + + -0.35 * Z(3) + + -0.35 * Z(2) + + 0.25 * (Z(2) @ Z(3)) + + 0.05 * (Z(0) @ Z(2)) + + 0.05 * (Z(0) @ Z(3)) + + 0.05 * (Z(1) @ Z(2)) + + 0.05 * (Z(1) @ Z(3)) + ) + + """ + + lattice = _generate_lattice(lattice, n_cells, boundary_condition, neighbour_order) + + hopping = ( + math.asarray([hopping]) + if isinstance(hopping, (int, float, complex)) + else math.asarray(hopping) + ) + intersite_coupling = ( + math.asarray([intersite_coupling]) + if isinstance(intersite_coupling, (int, float, complex)) + else math.asarray(intersite_coupling) + ) + + if hopping.shape not in [(neighbour_order,), (lattice.n_sites, lattice.n_sites)]: + raise ValueError( + f"The hopping parameter should be a number or an " + f"array of shape ({neighbour_order},) or ({lattice.n_sites},{lattice.n_sites})." + ) + + if intersite_coupling.shape not in [(neighbour_order,), (lattice.n_sites, lattice.n_sites)]: + raise ValueError( + f"The intersite_coupling parameter should be a number or " + f"an array of shape ({neighbour_order},) or ({lattice.n_sites},{lattice.n_sites})." + ) + + spin = 2 + hopping_term = 0.0 * FermiWord({}) + intersite_term = 0.0 * FermiWord({}) + for i, j, order in lattice.edges: + hop = hopping[order] if hopping.shape == (neighbour_order,) else hopping[i][j] + + for s in range(spin): + s1 = i * spin + s + s2 = j * spin + s + hopping_term -= hop * ( + FermiWord({(0, s1): "+", (1, s2): "-"}) + FermiWord({(0, s2): "+", (1, s1): "-"}) + ) + + intersite = ( + intersite_coupling[order] + if intersite_coupling.shape == (neighbour_order,) + else intersite_coupling[i][j] + ) + intersite_term += ( + intersite + * ( + FermiWord({(0, i * spin): "+", (1, i * spin): "-"}) + + FermiWord({(0, i * spin + 1): "+", (1, i * spin + 1): "-"}) + ) + * ( + FermiWord({(0, j * spin): "+", (1, j * spin): "-"}) + + FermiWord({(0, j * spin + 1): "+", (1, j * spin + 1): "-"}) + ) + ) + + if isinstance(coulomb, (int, float, complex)): + coulomb = math.ones(lattice.n_sites) * coulomb + + coulomb_term = 0.0 * FermiWord({}) + for i in range(lattice.n_sites): + up_spin = i * spin + down_spin = i * spin + 1 + coulomb_term += coulomb[i] * FermiWord( + {(0, up_spin): "+", (1, up_spin): "-", (2, down_spin): "+", (3, down_spin): "-"} + ) + + hamiltonian = hopping_term + coulomb_term + intersite_term + + if mapping not in ["jordan_wigner", "parity", "bravyi_kitaev"]: + raise ValueError( + f"The '{mapping}' transformation is not available." + f"Please set mapping to 'jordan_wigner', 'parity', or 'bravyi_kitaev'." + ) + qubit_ham = qml.qchem.qubit_observable(hamiltonian, mapping=mapping) + + return qubit_ham.simplify() + + +def haldane( + lattice, + n_cells, + hopping=1.0, + hopping_next=1.0, + phi=1.0, + boundary_condition=False, + mapping="jordan_wigner", +): + r"""Generates the Hamiltonian for the Haldane model on a lattice. + + The `Hamiltonian `_ for the `Haldane model `_ is represented as: + + .. math:: + + \begin{align*} + \hat{H} & = -\sum_{\langle i,j \rangle}t_{ij}^{1} + (c_{i\sigma}^\dagger c_{j\sigma} + c_{j\sigma}^\dagger c_{i\sigma}) + - \sum_{\langle\langle i,j \rangle\rangle, \sigma} t_{ij}^{2} + \left( e^{i\phi_{ij}} c_{i\sigma}^\dagger c_{j\sigma} + e^{-i\phi_{ij}} c_{j\sigma}^\dagger c_{i\sigma} \right) + \end{align*} + + where :math:`t^{1}_{ij}` is the hopping term representing the hopping amplitude between neighbouring + sites :math:`\langle i,j \rangle`, :math:`t^{2}_{ij}` is the hopping amplitude between next-nearest neighbours :math:`\langle \langle i,j \rangle \rangle`, :math:`\phi_{ij}` is the phase + factor that breaks time-reversal symmetry in the system, and :math:`\sigma` is the spin degree of freedom. + This function assumes there are two fermions with opposite spins on each lattice site. + + Args: + lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``, + ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``. + n_cells (list[int]): Number of cells in each direction of the grid. + hopping (float or tensor_like[float]): Hopping strength between + nearest neighbouring sites. It can be a number, or + a square matrix of size ``(n_sites, n_sites)``, where ``n_sites`` is the total + number of sites. Default value is 1.0. + hopping_next (float or tensor_like[float]): Hopping strength between next-nearest + neighbouring sites. It can be a number, or + a square matrix of size ``(n_sites, n_sites)``, where ``n_sites`` is the total + number of sites. Default value is 1.0. + phi (float or tensor_like[float]): Phase factor in the system. It can be a number, or + a square matrix of size ``(n_sites, n_sites)``, where ``n_sites`` is the total + number of sites. Default value is 1.0. + boundary_condition (bool or list[bool]): Defines boundary conditions for different lattice + axes. Default is ``False`` indicating open boundary condition. + mapping (str): Specifies the fermion-to-qubit mapping. Input values can be + ``'jordan_wigner'``, ``'parity'`` or ``'bravyi_kitaev'``. + + Raises: + ValueError: + If ``hopping``, ``hopping_next``, or ``phi`` doesn't have correct dimensions, + or if ``mapping`` is not available. + + Returns: + ~ops.op_math.Sum: Hamiltonian for the Haldane model. + + **Example** + + >>> n_cells = [2] + >>> h1 = 0.5 + >>> h2 = 1.0 + >>> phi = 0.1 + >>> spin_ham = qml.spin.haldane("chain", n_cells, hopping=h1, hopping_next=h2, phi=phi) + >>> spin_ham + ( + -0.25 * (Y(0) @ Z(1) @ Y(2)) + + -0.25 * (X(0) @ Z(1) @ X(2)) + + -0.25 * (Y(1) @ Z(2) @ Y(3)) + + -0.25 * (X(1) @ Z(2) @ X(3)) + ) + + """ + + lattice = _generate_lattice(lattice, n_cells, boundary_condition, neighbour_order=2) + + hopping = ( + math.asarray([hopping]) + if isinstance(hopping, (int, float, complex)) + else math.asarray(hopping) + ) + hopping_next = ( + math.asarray([hopping_next]) + if isinstance(hopping_next, (int, float, complex)) + else math.asarray(hopping_next) + ) + phi = math.asarray([phi]) if isinstance(phi, (int, float, complex)) else math.asarray(phi) + + if hopping.shape not in [(1,), (lattice.n_sites, lattice.n_sites)]: + raise ValueError( + f"The hopping parameter should be a constant or an array of shape ({lattice.n_sites},{lattice.n_sites})." + ) + + if hopping_next.shape not in [(1,), (lattice.n_sites, lattice.n_sites)]: + raise ValueError( + f"The hopping_next parameter should be a constant or an array of shape ({lattice.n_sites},{lattice.n_sites})." + ) + + if phi.shape not in [(1,), (lattice.n_sites, lattice.n_sites)]: + raise ValueError( + f"The phi parameter should be a constant or an array of shape ({lattice.n_sites},{lattice.n_sites})." + ) + + spin = 2 + hamiltonian = 0.0 * FermiWord({}) + for i, j, order in lattice.edges: + + hop1 = hopping[0] if hopping.shape == (1,) else hopping[i][j] + hop2 = hopping_next[0] if hopping_next.shape == (1,) else hopping_next[i][j] + phi_term = phi[0] if phi.shape == (1,) else phi[i][j] + + for s in range(spin): + s1 = i * spin + s + s2 = j * spin + s + if order == 0: + hamiltonian -= hop1 * ( + FermiWord({(0, s1): "+", (1, s2): "-"}) + + FermiWord({(0, s2): "+", (1, s1): "-"}) + ) + else: + hamiltonian -= hop2 * ( + math.exp(1j * phi_term) * FermiWord({(0, s1): "+", (1, s2): "-"}) + ) + hamiltonian -= hop2 * ( + math.exp(-1j * phi_term) * FermiWord({(0, s2): "+", (1, s1): "-"}) + ) + + if mapping not in ["jordan_wigner", "parity", "bravyi_kitaev"]: + raise ValueError( + f"The '{mapping}' transformation is not available." + f"Please set mapping to 'jordan_wigner', 'parity', or 'bravyi_kitaev'." + ) + qubit_ham = qml.qchem.qubit_observable(hamiltonian, mapping=mapping) + + return qubit_ham.simplify() + + def kitaev(n_cells, coupling=None, boundary_condition=False): r"""Generates the Hamiltonian for the Kitaev model on the Honeycomb lattice. diff --git a/tests/spin/test_spin_hamiltonian.py b/tests/spin/test_spin_hamiltonian.py index 959e05acc6a..2bfe98a9332 100644 --- a/tests/spin/test_spin_hamiltonian.py +++ b/tests/spin/test_spin_hamiltonian.py @@ -21,8 +21,9 @@ import pennylane as qml from pennylane import I, X, Y, Z -from pennylane.spin import fermi_hubbard, heisenberg, kitaev, transverse_ising +from pennylane.spin import emery, fermi_hubbard, haldane, heisenberg, kitaev, transverse_ising +# pylint: disable=too-many-arguments pytestmark = pytest.mark.usefixtures("new_opmath_only") @@ -784,6 +785,524 @@ def test_fermi_hubbard_hamiltonian_matrix(shape, n_cells, t, coulomb, expected_h qml.assert_equal(fermi_hub_ham, expected_ham) +def test_interaction_parameter_error_emery(): + r"""Test that an error is raised when the provided interaction parameters are of wrong shape for + emery Hamiltonian.""" + n_cells = [4, 4] + lattice = "Square" + with pytest.raises( + ValueError, + match=re.escape( + "The hopping parameter should be a number or an array of shape (1,) or (16,16)" + ), + ): + emery(lattice=lattice, n_cells=n_cells, hopping=[1.0, 2.0], neighbour_order=1) + + with pytest.raises( + ValueError, + match=re.escape( + "The intersite_coupling parameter should be a number or an array of shape (1,) or (16,16)" + ), + ): + emery( + lattice=lattice, + n_cells=n_cells, + hopping=[1.0], + intersite_coupling=[1.0, 2.0], + neighbour_order=1, + ) + + +def test_mapping_error_emery(): + r"""Test that an error is raised when unsupported mapping is provided""" + n_cells = [4, 4] + lattice = "Square" + with pytest.raises(ValueError, match="The 'bk_sf' transformation is not available."): + emery(lattice=lattice, n_cells=n_cells, mapping="bk_sf") + + +@pytest.mark.parametrize( + # expected_ham here was obtained manually + ("shape", "n_cells", "t", "u", "v", "boundary_condition", "expected_ham"), + [ + ( + "square", + [2, 2], + -1.23, + 2.34, + 1.42, + False, + (0.615 + 0j) * (Y(0) @ Z(1) @ Y(2)) + + (0.615 + 0j) * (X(0) @ Z(1) @ X(2)) + + (0.615 + 0j) * (Y(1) @ Z(2) @ Y(3)) + + (0.615 + 0j) * (X(1) @ Z(2) @ X(3)) + + (0.615 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Y(4)) + + (0.615 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ X(4)) + + (0.615 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Y(5)) + + (0.615 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ X(5)) + + (0.615 + 0j) * (Y(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (0.615 + 0j) * (X(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (0.615 + 0j) * (Y(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + (0.615 + 0j) * (X(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (0.615 + 0j) * (Y(4) @ Z(5) @ Y(6)) + + (0.615 + 0j) * (X(4) @ Z(5) @ X(6)) + + (0.615 + 0j) * (Y(5) @ Z(6) @ Y(7)) + + (0.615 + 0j) * (X(5) @ Z(6) @ X(7)) + + (8.020000000000005 + 0j) * I(0) + + (-2.005 + 0j) * Z(1) + + (-2.005 + 0j) * Z(0) + + (0.585 + 0j) * (Z(0) @ Z(1)) + + (-2.005 + 0j) * Z(3) + + (-2.005 + 0j) * Z(2) + + (0.585 + 0j) * (Z(2) @ Z(3)) + + (-2.005 + 0j) * Z(5) + + (-2.005 + 0j) * Z(4) + + (0.585 + 0j) * (Z(4) @ Z(5)) + + (-2.005 + 0j) * Z(7) + + (-2.005 + 0j) * Z(6) + + (0.585 + 0j) * (Z(6) @ Z(7)) + + (0.355 + 0j) * (Z(0) @ Z(2)) + + (0.355 + 0j) * (Z(0) @ Z(3)) + + (0.355 + 0j) * (Z(1) @ Z(2)) + + (0.355 + 0j) * (Z(1) @ Z(3)) + + (0.355 + 0j) * (Z(0) @ Z(4)) + + (0.355 + 0j) * (Z(0) @ Z(5)) + + (0.355 + 0j) * (Z(1) @ Z(4)) + + (0.355 + 0j) * (Z(1) @ Z(5)) + + (0.355 + 0j) * (Z(2) @ Z(6)) + + (0.355 + 0j) * (Z(2) @ Z(7)) + + (0.355 + 0j) * (Z(3) @ Z(6)) + + (0.355 + 0j) * (Z(3) @ Z(7)) + + (0.355 + 0j) * (Z(4) @ Z(6)) + + (0.355 + 0j) * (Z(4) @ Z(7)) + + (0.355 + 0j) * (Z(5) @ Z(6)) + + (0.355 + 0j) * (Z(5) @ Z(7)), + ), + ( + "Chain", + [4], + -1.23, + 2.34, + 1.42, + True, + (0.615 + 0j) * (Y(0) @ Z(1) @ Y(2)) + + (0.615 + 0j) * (X(0) @ Z(1) @ X(2)) + + (0.615 + 0j) * (Y(1) @ Z(2) @ Y(3)) + + (0.615 + 0j) * (X(1) @ Z(2) @ X(3)) + + (0.615 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (0.615 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (0.615 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + (0.615 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (0.615 + 0j) * (Y(2) @ Z(3) @ Y(4)) + + (0.615 + 0j) * (X(2) @ Z(3) @ X(4)) + + (0.615 + 0j) * (Y(3) @ Z(4) @ Y(5)) + + (0.615 + 0j) * (X(3) @ Z(4) @ X(5)) + + (0.615 + 0j) * (Y(4) @ Z(5) @ Y(6)) + + (0.615 + 0j) * (X(4) @ Z(5) @ X(6)) + + (0.615 + 0j) * (Y(5) @ Z(6) @ Y(7)) + + (0.615 + 0j) * (X(5) @ Z(6) @ X(7)) + + (8.020000000000005 + 0j) * I(0) + + (-2.005 + 0j) * Z(1) + + (-2.005 + 0j) * Z(0) + + (0.585 + 0j) * (Z(0) @ Z(1)) + + (-2.005 + 0j) * Z(3) + + (-2.005 + 0j) * Z(2) + + (0.585 + 0j) * (Z(2) @ Z(3)) + + (-2.005 + 0j) * Z(5) + + (-2.005 + 0j) * Z(4) + + (0.585 + 0j) * (Z(4) @ Z(5)) + + (-2.005 + 0j) * Z(7) + + (-2.005 + 0j) * Z(6) + + (0.585 + 0j) * (Z(6) @ Z(7)) + + (0.355 + 0j) * (Z(0) @ Z(2)) + + (0.355 + 0j) * (Z(0) @ Z(3)) + + (0.355 + 0j) * (Z(1) @ Z(2)) + + (0.355 + 0j) * (Z(1) @ Z(3)) + + (0.355 + 0j) * (Z(0) @ Z(6)) + + (0.355 + 0j) * (Z(0) @ Z(7)) + + (0.355 + 0j) * (Z(1) @ Z(6)) + + (0.355 + 0j) * (Z(1) @ Z(7)) + + (0.355 + 0j) * (Z(2) @ Z(4)) + + (0.355 + 0j) * (Z(2) @ Z(5)) + + (0.355 + 0j) * (Z(3) @ Z(4)) + + (0.355 + 0j) * (Z(3) @ Z(5)) + + (0.355 + 0j) * (Z(4) @ Z(6)) + + (0.355 + 0j) * (Z(4) @ Z(7)) + + (0.355 + 0j) * (Z(5) @ Z(6)) + + (0.355 + 0j) * (Z(5) @ Z(7)), + ), + ], +) +def test_emery_hamiltonian(shape, n_cells, t, u, v, boundary_condition, expected_ham): + r"""Test that the correct Emery Hamiltonian is generated.""" + + emery_ham = emery( + lattice=shape, + n_cells=n_cells, + hopping=t, + coulomb=u, + intersite_coupling=v, + boundary_condition=boundary_condition, + ) + + qml.assert_equal(emery_ham, expected_ham) + + +@pytest.mark.parametrize( + # expected_ham here was obtained manually. + ("shape", "n_cells", "t", "u", "v", "expected_ham"), + [ + ( + "chain", + [4], + [[0, 1, 0, 0], [1, 0, 1, 0], [0, 1, 0, 1], [0, 0, 1, 0]], + 0.1, + 0, + -0.5 * (Y(0) @ Z(1) @ Y(2)) + + -0.5 * (X(0) @ Z(1) @ X(2)) + + 0.1 * I(0) + + -0.5 * (Y(1) @ Z(2) @ Y(3)) + + -0.5 * (X(1) @ Z(2) @ X(3)) + + -0.5 * (Y(2) @ Z(3) @ Y(4)) + + -0.5 * (X(2) @ Z(3) @ X(4)) + + -0.5 * (Y(3) @ Z(4) @ Y(5)) + + -0.5 * (X(3) @ Z(4) @ X(5)) + + -0.5 * (Y(4) @ Z(5) @ Y(6)) + + -0.5 * (X(4) @ Z(5) @ X(6)) + + -0.5 * (Y(5) @ Z(6) @ Y(7)) + + -0.5 * (X(5) @ Z(6) @ X(7)) + + -0.025 * Z(1) + + -0.025 * Z(0) + + 0.025 * (Z(0) @ Z(1)) + + -0.025 * Z(3) + + -0.025 * Z(2) + + 0.025 * (Z(2) @ Z(3)) + + -0.025 * Z(5) + + -0.025 * Z(4) + + 0.025 * (Z(4) @ Z(5)) + + -0.025 * Z(7) + + -0.025 * Z(6) + + 0.025 * (Z(6) @ Z(7)), + ), + ( + "square", + [2, 2], + [[0, 0.5, 0.5, 0], [0.5, 0, 0, 0.5], [0.5, 0, 0, 0.5], [0, 0.5, 0.5, 0]], + [-1.0, 0.0, 1.0, 0], + 0, + -0.25 * (Y(0) @ Z(1) @ Y(2)) + + -0.25 * (X(0) @ Z(1) @ X(2)) + + -0.25 * (Y(1) @ Z(2) @ Y(3)) + + -0.25 * (X(1) @ Z(2) @ X(3)) + + -0.25 * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Y(4)) + + -0.25 * (X(0) @ Z(1) @ Z(2) @ Z(3) @ X(4)) + + -0.25 * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Y(5)) + + -0.25 * (X(1) @ Z(2) @ Z(3) @ Z(4) @ X(5)) + + -0.25 * (Y(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + -0.25 * (X(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + -0.25 * (Y(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + -0.25 * (X(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + -0.25 * (Y(4) @ Z(5) @ Y(6)) + + -0.25 * (X(4) @ Z(5) @ X(6)) + + -0.25 * (Y(5) @ Z(6) @ Y(7)) + + -0.25 * (X(5) @ Z(6) @ X(7)) + + 0.25 * Z(1) + + 0.25 * Z(0) + + -0.25 * (Z(0) @ Z(1)) + + -0.25 * Z(5) + + -0.25 * Z(4) + + 0.25 * (Z(4) @ Z(5)), + ), + ( + "square", + [2, 2], + 0.1, + [-1.0, 0.0, 1.0, 0], + [[0, 0.5, 0.5, 0], [0.5, 0, 0, 0.5], [0.5, 0, 0, 0.5], [0, 0.5, 0.5, 0]], + -0.05 * (Y(0) @ Z(1) @ Y(2)) + + -0.05 * (X(0) @ Z(1) @ X(2)) + + -0.05 * (Y(1) @ Z(2) @ Y(3)) + + -0.05 * (X(1) @ Z(2) @ X(3)) + + -0.05 * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Y(4)) + + -0.05 * (X(0) @ Z(1) @ Z(2) @ Z(3) @ X(4)) + + -0.05 * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Y(5)) + + -0.05 * (X(1) @ Z(2) @ Z(3) @ Z(4) @ X(5)) + + -0.05 * (Y(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + -0.05 * (X(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + -0.05 * (Y(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + -0.05 * (X(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + -0.05 * (Y(4) @ Z(5) @ Y(6)) + + -0.05 * (X(4) @ Z(5) @ X(6)) + + -0.05 * (Y(5) @ Z(6) @ Y(7)) + + -0.05 * (X(5) @ Z(6) @ X(7)) + + 2.0 * I(0) + + -0.25 * Z(1) + + -0.25 * Z(0) + + -0.25 * (Z(0) @ Z(1)) + + -0.5 * Z(3) + + -0.5 * Z(2) + + -0.75 * Z(5) + + -0.75 * Z(4) + + 0.25 * (Z(4) @ Z(5)) + + -0.5 * Z(7) + + -0.5 * Z(6) + + 0.125 * (Z(0) @ Z(2)) + + 0.125 * (Z(0) @ Z(3)) + + 0.125 * (Z(1) @ Z(2)) + + 0.125 * (Z(1) @ Z(3)) + + 0.125 * (Z(0) @ Z(4)) + + 0.125 * (Z(0) @ Z(5)) + + 0.125 * (Z(1) @ Z(4)) + + 0.125 * (Z(1) @ Z(5)) + + 0.125 * (Z(2) @ Z(6)) + + 0.125 * (Z(2) @ Z(7)) + + 0.125 * (Z(3) @ Z(6)) + + 0.125 * (Z(3) @ Z(7)) + + 0.125 * (Z(4) @ Z(6)) + + 0.125 * (Z(4) @ Z(7)) + + 0.125 * (Z(5) @ Z(6)) + + 0.125 * (Z(5) @ Z(7)), + ), + ], +) +def test_emery_hamiltonian_matrix(shape, n_cells, t, u, v, expected_ham): + r"""Test that the correct Emery Hamiltonian is generated when interaction parameters are provided as a matrix""" + + emery_ham = emery(lattice=shape, n_cells=n_cells, hopping=t, coulomb=u, intersite_coupling=v) + + qml.assert_equal(emery_ham, expected_ham) + + +def test_hopping_error_haldane(): + r"""Test that an error is raised when the shape of provided interaction parameters is wrong for + Haldane Hamiltonian.""" + n_cells = [4, 4] + lattice = "Square" + with pytest.raises( + ValueError, + match=re.escape("The hopping parameter should be a constant or an array of shape (16,16)"), + ): + haldane(lattice=lattice, n_cells=n_cells, hopping=[1.0, 2.0]) + + with pytest.raises( + ValueError, + match=re.escape( + "The hopping_next parameter should be a constant or an array of shape (16,16)" + ), + ): + haldane(lattice=lattice, n_cells=n_cells, hopping=1.0, hopping_next=[0.5, 0.6, 0.7]) + + with pytest.raises( + ValueError, + match=re.escape("The phi parameter should be a constant or an array of shape (16,16)"), + ): + haldane(lattice=lattice, n_cells=n_cells, hopping=1.0, hopping_next=0.1, phi=[0.5, 0.6]) + + +def test_mapping_error_haldane(): + r"""Test that an error is raised when unsupported mapping is provided""" + n_cells = [4, 4] + lattice = "Square" + with pytest.raises(ValueError, match="The 'bk_sf' transformation is not available."): + haldane(lattice=lattice, n_cells=n_cells, mapping="bk_sf") + + +@pytest.mark.parametrize( + # expected_ham here was obtained manually. + ("shape", "n_cells", "t1", "t2", "phi", "boundary_condition", "expected_ham"), + [ + ( + "chain", + [4], + -1.23, + 2.34, + 1.0, + True, + (0.615 + 0j) * (Y(0) @ Z(1) @ Y(2)) + + (0.615 + 0j) * (X(0) @ Z(1) @ X(2)) + + (0.615 + 0j) * (Y(1) @ Z(2) @ Y(3)) + + (0.615 + 0j) * (X(1) @ Z(2) @ X(3)) + + (0.615 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (0.615 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (0.615 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + (0.615 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (0.615 + 0j) * (Y(2) @ Z(3) @ Y(4)) + + (0.615 + 0j) * (X(2) @ Z(3) @ X(4)) + + (0.615 + 0j) * (Y(3) @ Z(4) @ Y(5)) + + (0.615 + 0j) * (X(3) @ Z(4) @ X(5)) + + (0.615 + 0j) * (Y(4) @ Z(5) @ Y(6)) + + (0.615 + 0j) * (X(4) @ Z(5) @ X(6)) + + (0.615 + 0j) * (Y(5) @ Z(6) @ Y(7)) + + (0.615 + 0j) * (X(5) @ Z(6) @ X(7)) + + (-0.9845210522252389 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ X(4)) + + (-0.6321536978657235 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Y(4)) + + (-0.6321536978657235 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ X(4)) + + (0.9845210522252389 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ Y(4)) + + (-0.9845210522252389 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ X(5)) + + (-0.6321536978657235 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Y(5)) + + (-0.6321536978657235 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ X(5)) + + (0.9845210522252389 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ Y(5)) + + (-0.9845210522252389 + 0j) * (Y(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (-0.6321536978657235 + 0j) * (Y(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (-0.6321536978657235 + 0j) * (X(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (0.9845210522252389 + 0j) * (X(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (-0.9845210522252389 + 0j) * (Y(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (-0.6321536978657235 + 0j) * (Y(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + (-0.6321536978657235 + 0j) * (X(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (0.9845210522252389 + 0j) * (X(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)), + ), + ( + "square", + [2, 2], + -1.23, + 2.34, + [ + [0.0, 1.0, 1.0, 1.41421356], + [1.0, 0.0, 1.41421356, 1.0], + [1.0, 1.41421356, 0.0, 1.0], + [1.41421356, 1.0, 1.0, 0.0], + ], + False, + (0.615 + 0j) * (Y(0) @ Z(1) @ Y(2)) + + (0.615 + 0j) * (X(0) @ Z(1) @ X(2)) + + (0.615 + 0j) * (Y(1) @ Z(2) @ Y(3)) + + (0.615 + 0j) * (X(1) @ Z(2) @ X(3)) + + (0.615 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Y(4)) + + (0.615 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ X(4)) + + (0.615 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Y(5)) + + (0.615 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ X(5)) + + (0.615 + 0j) * (Y(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (0.615 + 0j) * (X(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (0.615 + 0j) * (Y(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + (0.615 + 0j) * (X(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (0.615 + 0j) * (Y(4) @ Z(5) @ Y(6)) + + (0.615 + 0j) * (X(4) @ Z(5) @ X(6)) + + (0.615 + 0j) * (Y(5) @ Z(6) @ Y(7)) + + (0.615 + 0j) * (X(5) @ Z(6) @ X(7)) + + (-1.1556861568115007 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (-0.182454122875488 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (-0.182454122875488 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (1.1556861568115007 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (-1.1556861568115007 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (-0.182454122875488 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + (-0.182454122875488 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (1.1556861568115007 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + (-1.1556861568115007 + 0j) * (Y(2) @ Z(3) @ X(4)) + + (-0.182454122875488 + 0j) * (Y(2) @ Z(3) @ Y(4)) + + (-0.182454122875488 + 0j) * (X(2) @ Z(3) @ X(4)) + + (1.1556861568115007 + 0j) * (X(2) @ Z(3) @ Y(4)) + + (-1.1556861568115007 + 0j) * (Y(3) @ Z(4) @ X(5)) + + (-0.182454122875488 + 0j) * (Y(3) @ Z(4) @ Y(5)) + + (-0.182454122875488 + 0j) * (X(3) @ Z(4) @ X(5)) + + (1.1556861568115007 + 0j) * (X(3) @ Z(4) @ Y(5)), + ), + ( + "square", + [2, 2], + [[0, 0.5, 0.5, 0], [0.5, 0, 0, 0.5], [0.5, 0, 0, 0.5], [0, 0.5, 0.5, 0]], + 1.0, + [ + [0.0, 1.0, 1.0, 1.41421356], + [1.0, 0.0, 1.41421356, 1.0], + [1.0, 1.41421356, 0.0, 1.0], + [1.41421356, 1.0, 1.0, 0.0], + ], + False, + (-0.25 + 0j) * (Y(0) @ Z(1) @ Y(2)) + + (-0.25 + 0j) * (X(0) @ Z(1) @ X(2)) + + (-0.25 + 0j) * (Y(1) @ Z(2) @ Y(3)) + + (-0.25 + 0j) * (X(1) @ Z(2) @ X(3)) + + (-0.25 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Y(4)) + + (-0.25 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ X(4)) + + (-0.25 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Y(5)) + + (-0.25 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ X(5)) + + (-0.25 + 0j) * (Y(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (-0.25 + 0j) * (X(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (-0.25 + 0j) * (Y(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + (-0.25 + 0j) * (X(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (-0.25 + 0j) * (Y(4) @ Z(5) @ Y(6)) + + (-0.25 + 0j) * (X(4) @ Z(5) @ X(6)) + + (-0.25 + 0j) * (Y(5) @ Z(6) @ Y(7)) + + (-0.25 + 0j) * (X(5) @ Z(6) @ X(7)) + + (-0.4938829729963678 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (-0.07797184738268718 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (-0.07797184738268718 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (0.4938829729963678 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (-0.4938829729963678 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (-0.07797184738268718 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + (-0.07797184738268718 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (0.4938829729963678 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + (-0.4938829729963678 + 0j) * (Y(2) @ Z(3) @ X(4)) + + (-0.07797184738268718 + 0j) * (Y(2) @ Z(3) @ Y(4)) + + (-0.07797184738268718 + 0j) * (X(2) @ Z(3) @ X(4)) + + (0.4938829729963678 + 0j) * (X(2) @ Z(3) @ Y(4)) + + (-0.4938829729963678 + 0j) * (Y(3) @ Z(4) @ X(5)) + + (-0.07797184738268718 + 0j) * (Y(3) @ Z(4) @ Y(5)) + + (-0.07797184738268718 + 0j) * (X(3) @ Z(4) @ X(5)) + + (0.4938829729963678 + 0j) * (X(3) @ Z(4) @ Y(5)), + ), + ( + "square", + [2, 2], + [[0, 0.5, 0.5, 0], [0.5, 0, 0, 0.5], [0.5, 0, 0, 0.5], [0, 0.5, 0.5, 0]], + [[0, 0, 0, 0.5], [0, 0, 0.5, 0], [0, 0.5, 0, 0], [0.5, 0.0, 0.0, 0]], + [ + [0.0, 1.0, 1.0, 1.41421356], + [1.0, 0.0, 1.41421356, 1.0], + [1.0, 1.41421356, 0.0, 1.0], + [1.41421356, 1.0, 1.0, 0.0], + ], + False, + (-0.25 + 0j) * (Y(0) @ Z(1) @ Y(2)) + + (-0.25 + 0j) * (X(0) @ Z(1) @ X(2)) + + (-0.25 + 0j) * (Y(1) @ Z(2) @ Y(3)) + + (-0.25 + 0j) * (X(1) @ Z(2) @ X(3)) + + (-0.25 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Y(4)) + + (-0.25 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ X(4)) + + (-0.25 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Y(5)) + + (-0.25 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ X(5)) + + (-0.25 + 0j) * (Y(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (-0.25 + 0j) * (X(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (-0.25 + 0j) * (Y(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + (-0.25 + 0j) * (X(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (-0.25 + 0j) * (Y(4) @ Z(5) @ Y(6)) + + (-0.25 + 0j) * (X(4) @ Z(5) @ X(6)) + + (-0.25 + 0j) * (Y(5) @ Z(6) @ Y(7)) + + (-0.25 + 0j) * (X(5) @ Z(6) @ X(7)) + + (-0.2469414864981839 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (-0.03898592369134359 + 0j) * (Y(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (-0.03898592369134359 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ X(6)) + + (0.2469414864981839 + 0j) * (X(0) @ Z(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Y(6)) + + (-0.2469414864981839 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (-0.03898592369134359 + 0j) * (Y(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + (-0.03898592369134359 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ X(7)) + + (0.2469414864981839 + 0j) * (X(1) @ Z(2) @ Z(3) @ Z(4) @ Z(5) @ Z(6) @ Y(7)) + + (-0.2469414864981839 + 0j) * (Y(2) @ Z(3) @ X(4)) + + (-0.03898592369134359 + 0j) * (Y(2) @ Z(3) @ Y(4)) + + (-0.03898592369134359 + 0j) * (X(2) @ Z(3) @ X(4)) + + (0.2469414864981839 + 0j) * (X(2) @ Z(3) @ Y(4)) + + (-0.2469414864981839 + 0j) * (Y(3) @ Z(4) @ X(5)) + + (-0.03898592369134359 + 0j) * (Y(3) @ Z(4) @ Y(5)) + + (-0.03898592369134359 + 0j) * (X(3) @ Z(4) @ X(5)) + + (0.2469414864981839 + 0j) * (X(3) @ Z(4) @ Y(5)), + ), + ], +) +def test_haldane_hamiltonian_matrix(shape, n_cells, t1, t2, phi, boundary_condition, expected_ham): + r"""Test that the correct Haldane Hamiltonian is generated.""" + + haldane_ham = haldane( + lattice=shape, + n_cells=n_cells, + hopping=t1, + hopping_next=t2, + phi=phi, + boundary_condition=boundary_condition, + ) + + qml.assert_equal(haldane_ham, expected_ham) + + def test_coupling_error_kitaev(): r"""Test that an error is raised when the provided coupling shape is wrong for Kitaev Hamiltonian.""" From d9f9144ab861ebce665c27839e56b0fcdf4ac103 Mon Sep 17 00:00:00 2001 From: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Date: Fri, 20 Sep 2024 13:40:54 -0400 Subject: [PATCH 34/35] Update tests/spin/test_lattice.py --- tests/spin/test_lattice.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/spin/test_lattice.py b/tests/spin/test_lattice.py index c6697799a01..ff51c40fa2a 100644 --- a/tests/spin/test_lattice.py +++ b/tests/spin/test_lattice.py @@ -862,7 +862,6 @@ def test_custom_edges(vectors, positions, n_cells, custom_edges, expected_edges) n_cells=n_cells, vectors=vectors, positions=positions, custom_edges=custom_edges ) assert np.all(np.isin(expected_edges, lattice.edges)) -======= def test_dimension_error(): r"""Test that an error is raised if wrong dimension is provided for a given lattice shape.""" n_cells = [5, 5, 5] From 631307f3fb77028543635b2bf6d3afc82a469d3b Mon Sep 17 00:00:00 2001 From: ddhawan11 Date: Fri, 20 Sep 2024 13:48:19 -0400 Subject: [PATCH 35/35] Fixed black --- tests/spin/test_lattice.py | 5 ++++- tests/spin/test_spin_hamiltonian.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/spin/test_lattice.py b/tests/spin/test_lattice.py index ff51c40fa2a..4f8484945ca 100644 --- a/tests/spin/test_lattice.py +++ b/tests/spin/test_lattice.py @@ -792,6 +792,7 @@ def test_shape_error(): with pytest.raises(ValueError, match="Lattice shape, 'Octagon' is not supported."): _generate_lattice(lattice=lattice, n_cells=n_cells) + def test_neighbour_order_error(): r"""Test that an error is raised if neighbour order is greater than 1 when custom_edges are provided.""" @@ -862,6 +863,8 @@ def test_custom_edges(vectors, positions, n_cells, custom_edges, expected_edges) n_cells=n_cells, vectors=vectors, positions=positions, custom_edges=custom_edges ) assert np.all(np.isin(expected_edges, lattice.edges)) + + def test_dimension_error(): r"""Test that an error is raised if wrong dimension is provided for a given lattice shape.""" n_cells = [5, 5, 5] @@ -1070,4 +1073,4 @@ def test_lattice_points_templates(shape, n_cells, expected_points): r"""Test that the correct lattice points are generated for a given template.""" lattice = _generate_lattice(lattice=shape, n_cells=n_cells) - assert np.allclose(expected_points, lattice.lattice_points) \ No newline at end of file + assert np.allclose(expected_points, lattice.lattice_points) diff --git a/tests/spin/test_spin_hamiltonian.py b/tests/spin/test_spin_hamiltonian.py index 066b8db9389..1d489e2a124 100644 --- a/tests/spin/test_spin_hamiltonian.py +++ b/tests/spin/test_spin_hamiltonian.py @@ -1302,6 +1302,7 @@ def test_haldane_hamiltonian_matrix(shape, n_cells, t1, t2, phi, boundary_condit qml.assert_equal(haldane_ham, expected_ham) + def test_coupling_error_kitaev(): r"""Test that an error is raised when the provided coupling shape is wrong for Kitaev Hamiltonian."""