From 9b48d5490f5c7234d18bcaf0bfb11ab3270db11b Mon Sep 17 00:00:00 2001 From: Abby Mitchell <23662430+javabster@users.noreply.github.com> Date: Mon, 4 Sep 2023 03:40:34 -0400 Subject: [PATCH] Add `HexagonalLattice` Class (#1027) * First attempt at hex lattice class * Added hexagonal Lattice class * Update qiskit_nature/second_q/hamiltonians/lattices/hexagonal_lattice.py Co-authored-by: Matthew Treinish * added tests * simplified test * fixed copyright * use more performant rustworkx functions * Apply suggestions from code review Co-authored-by: Max Rossmannek * Make changes requested in review * Added default positions * :sparkles: lint! :sparkles: * remove heavy hex arg * fix mypy issues * Fix typing * increased rustworkx * fixed mypy again? * lint: fix mypy * lint: update headers * docs: Add reno --------- Co-authored-by: Matthew Treinish Co-authored-by: Max Rossmannek Co-authored-by: Max Rossmannek --- .../hamiltonians/lattices/__init__.py | 3 + .../lattices/hexagonal_lattice.py | 122 ++++++++++++++++++ ...dd-hexagonal-lattice-a981f1b5c832a154.yaml | 21 +++ requirements.txt | 2 +- .../lattices/test_hexagonal_lattice.py | 86 ++++++++++++ 5 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 qiskit_nature/second_q/hamiltonians/lattices/hexagonal_lattice.py create mode 100644 releasenotes/notes/add-hexagonal-lattice-a981f1b5c832a154.yaml create mode 100644 test/second_q/hamiltonians/lattices/test_hexagonal_lattice.py diff --git a/qiskit_nature/second_q/hamiltonians/lattices/__init__.py b/qiskit_nature/second_q/hamiltonians/lattices/__init__.py index c2bf26a85..ad7dba19f 100644 --- a/qiskit_nature/second_q/hamiltonians/lattices/__init__.py +++ b/qiskit_nature/second_q/hamiltonians/lattices/__init__.py @@ -24,6 +24,7 @@ SquareLattice TriangularLattice HyperCubicLattice + HexagonalLattice KagomeLattice """ @@ -34,6 +35,7 @@ from .line_lattice import LineLattice from .square_lattice import SquareLattice from .triangular_lattice import TriangularLattice +from .hexagonal_lattice import HexagonalLattice from .kagome_lattice import KagomeLattice __all__ = [ @@ -44,5 +46,6 @@ "SquareLattice", "TriangularLattice", "HyperCubicLattice", + "HexagonalLattice", "KagomeLattice", ] diff --git a/qiskit_nature/second_q/hamiltonians/lattices/hexagonal_lattice.py b/qiskit_nature/second_q/hamiltonians/lattices/hexagonal_lattice.py new file mode 100644 index 000000000..59e06208b --- /dev/null +++ b/qiskit_nature/second_q/hamiltonians/lattices/hexagonal_lattice.py @@ -0,0 +1,122 @@ +# This code is part of a Qiskit project. +# +# (C) Copyright IBM 2021, 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""The hexagonal lattice""" + +from __future__ import annotations + +from rustworkx import generators # type: ignore[attr-defined] + +from .lattice import Lattice + + +class HexagonalLattice(Lattice): + """Hexagonal lattice.""" + + def __init__( + self, + rows: int, + cols: int, + edge_parameter: complex = 1.0, + onsite_parameter: complex = 0.0, + ) -> None: + """ + Args: + rows: Number of hexagons in the x direction. + cols: Number of hexagons in the y direction. + edge_parameter: Weight on all the edges, specified as a single value. + Defaults to 1.0. + onsite_parameter: Weight on the self-loops, which are edges connecting a node to itself. + Defaults to 0.0. + """ + self._rows = rows + self._cols = cols + self._edge_parameter = edge_parameter + self._onsite_parameter = onsite_parameter + + graph = generators.hexagonal_lattice_graph(rows, cols, multigraph=False) + + # Add edge weights + for idx in range(graph.num_edges()): + graph.update_edge_by_index(idx, self._edge_parameter) + + # Add self loops + for node in range(graph.num_nodes()): + graph.add_edges_from([(node, node, self._onsite_parameter)]) + + super().__init__(graph) + + self.pos = self._default_position() + + @property + def edge_parameter(self) -> complex: + """Weights on all edges. + + Returns: + the parameter for the edges. + """ + return self._edge_parameter + + @property + def onsite_parameter(self) -> complex: + """Weight on the self-loops (edges connecting a node to itself). + + Returns: + the parameter for the self-loops. + """ + return self._onsite_parameter + + def _default_position(self) -> dict[int, tuple[int, int]]: + """Return a dictionary of default positions for visualization of + a one- or two-dimensional lattice. + + Returns: + A dictionary where the keys are the labels of lattice points, and the values are + two-dimensional coordinates. + """ + pos = {} + rowlen = 2 * self._rows + 2 + collen = self._cols + 1 + x_adjust = 0 + + for i in range(collen): + x_adjust += 1 + for j in range(rowlen): + idx = i * rowlen + j - 1 + x = i + + # plot the y coords to form heavy hex shape + if i == 0: + y = j - 1 + elif (self._cols % 2 == 0) and (i == self._cols): + y = j + 1 + else: + y = j + + # even numbered nodes in the first, last and odd numbered columns need to be + # shifted to the right + if i == 0 or (i == self._cols) or (i % 2 != 0): + if idx % 2 == 0: + x = i + x_adjust + else: + x = i + i + # odd numbered nodes that aren't in the first, last or odd numbered columns + # need to be shifted to the right + else: + if idx % 2 == 0: + x = i + i + else: + x = i + x_adjust + + pos[idx] = (x, y) + + return pos diff --git a/releasenotes/notes/add-hexagonal-lattice-a981f1b5c832a154.yaml b/releasenotes/notes/add-hexagonal-lattice-a981f1b5c832a154.yaml new file mode 100644 index 000000000..bd282142f --- /dev/null +++ b/releasenotes/notes/add-hexagonal-lattice-a981f1b5c832a154.yaml @@ -0,0 +1,21 @@ +--- +features: + - | + Adds a new lattice class, :class:`~qiskit_nature.second_q.hamiltonians.lattices.HexagonalLattice` + for the generation of hexagonal lattices. + + You construct a hexagonal lattice by specifying the number of rows and columns of hexagons. + You can also specify the edge- and on-site-parameters. + + Below is a simple example to illustrate this: + + .. code-block:: python + + from qiskit_nature.second_q.hamiltonians.lattices import HexagonalLattice + + lattice = HexagonalLattice( + 2, + 3, + edge_parameter=1.0, + onsite_parameter=1.5, + ) diff --git a/requirements.txt b/requirements.txt index 2b3173294..9f5c87b98 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,4 @@ psutil>=5 setuptools>=40.1.0 typing_extensions h5py -rustworkx +rustworkx>=0.12 diff --git a/test/second_q/hamiltonians/lattices/test_hexagonal_lattice.py b/test/second_q/hamiltonians/lattices/test_hexagonal_lattice.py new file mode 100644 index 000000000..7ba6f1a12 --- /dev/null +++ b/test/second_q/hamiltonians/lattices/test_hexagonal_lattice.py @@ -0,0 +1,86 @@ +# This code is part of a Qiskit project. +# +# (C) Copyright IBM 2021, 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test for HexgonalLattice.""" +from test import QiskitNatureTestCase +import numpy as np +from numpy.testing import assert_array_equal +from rustworkx import PyGraph, is_isomorphic # type: ignore[attr-defined] +from qiskit_nature.second_q.hamiltonians.lattices import HexagonalLattice + + +class TestHexagonalLattice(QiskitNatureTestCase): + """Test HexagonalLattice""" + + def test_init(self): + """Test init.""" + rows = 1 + cols = 2 + edge_parameter = 0 + 1.42j + onsite_parameter = 1.0 + weighted_edge_list = [ + (0, 1, 1.42j), + (1, 2, 1.42j), + (3, 4, 1.42j), + (4, 5, 1.42j), + (5, 6, 1.42j), + (7, 8, 1.42j), + (8, 9, 1.42j), + (0, 3, 1.42j), + (2, 5, 1.42j), + (4, 7, 1.42j), + (6, 9, 1.42j), + (0, 0, 1.0), + (1, 1, 1.0), + (2, 2, 1.0), + (3, 3, 1.0), + (4, 4, 1.0), + (5, 5, 1.0), + (6, 6, 1.0), + (7, 7, 1.0), + (8, 8, 1.0), + (9, 9, 1.0), + ] + + hexa = HexagonalLattice(rows, cols, edge_parameter, onsite_parameter) + + with self.subTest("Check the graph."): + target_graph = PyGraph(multigraph=False) + target_graph.add_nodes_from(range(10)) + target_graph.add_edges_from(weighted_edge_list) + self.assertTrue( + is_isomorphic(hexa.graph, target_graph, edge_matcher=lambda x, y: x == y) + ) + + with self.subTest("Check the number of nodes."): + self.assertEqual(hexa.num_nodes, 10) + + with self.subTest("Check the set of nodes."): + self.assertSetEqual(set(hexa.node_indexes), set(range(10))) + + with self.subTest("Check the set of weights."): + target_set = set(weighted_edge_list) + self.assertSetEqual(set(hexa.weighted_edge_list), target_set) + + with self.subTest("Check the adjacency matrix."): + target_matrix = np.zeros((10, 10), dtype=complex) + + indices = [(a, b) for a, b, _ in weighted_edge_list] + + for idx1, idx2 in indices: + target_matrix[idx1, idx2] = 0 + 1.42j + + target_matrix -= target_matrix.T + + np.fill_diagonal(target_matrix, 1.0) + + assert_array_equal(hexa.to_adjacency_matrix(weighted=True), target_matrix)