Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Lattice class and transverse-field Ising Hamiltonian #6106

Merged
merged 40 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
703d1fb
Lattice class and template spin Hamiltonians
ddhawan11 Aug 15, 2024
da3af95
Docstring formatting
ddhawan11 Aug 15, 2024
847273e
Shortened the PR and added more tests
ddhawan11 Aug 16, 2024
1df7f0c
Cleaned up some
ddhawan11 Aug 19, 2024
42b994a
Update pennylane/spin/__init__.py
ddhawan11 Aug 19, 2024
dbdb677
Update pennylane/spin/lattice.py
ddhawan11 Aug 19, 2024
b139df7
Update pennylane/spin/__init__.py
ddhawan11 Aug 20, 2024
d42e6ed
Update pennylane/spin/lattice.py
ddhawan11 Aug 20, 2024
d6df727
Update pennylane/spin/lattice.py
ddhawan11 Aug 20, 2024
dcee93b
Update pennylane/spin/spin_hamiltonian.py
ddhawan11 Aug 20, 2024
981c0fb
[skip ci] Addressed reviews
ddhawan11 Aug 20, 2024
932b823
[skip ci] updated change-log
ddhawan11 Aug 20, 2024
2fe07b2
Merge branch 'master' into spin_hamiltonian
ddhawan11 Aug 20, 2024
7bae9d2
[skip ci] Fixed a test
ddhawan11 Aug 20, 2024
b89147d
Update pennylane/spin/lattice.py
ddhawan11 Aug 21, 2024
91fde16
Update pennylane/spin/spin_hamiltonian.py
ddhawan11 Aug 21, 2024
196fca2
Update pennylane/spin/spin_hamiltonian.py
ddhawan11 Aug 21, 2024
104a3b8
Update pennylane/spin/spin_hamiltonian.py
ddhawan11 Aug 21, 2024
d0541bf
Update pennylane/spin/lattice.py
ddhawan11 Aug 21, 2024
a9d6177
Update pennylane/spin/lattice.py
ddhawan11 Aug 21, 2024
532d95f
Update pennylane/spin/spin_hamiltonian.py
ddhawan11 Aug 21, 2024
768d7cd
Update pennylane/spin/spin_hamiltonian.py
ddhawan11 Aug 21, 2024
a18af1a
Addressed reviews
ddhawan11 Aug 21, 2024
e3aca61
vectorized some code
ddhawan11 Aug 21, 2024
ac0e960
vectorized some code
ddhawan11 Aug 21, 2024
c7260a0
Increased test coverage
ddhawan11 Aug 21, 2024
6439a47
Increased test coverage
ddhawan11 Aug 21, 2024
a81fe95
Addressed review comments
ddhawan11 Aug 21, 2024
6b388a4
Update pennylane/spin/lattice.py
ddhawan11 Aug 21, 2024
9b98c45
Update pennylane/spin/lattice.py
ddhawan11 Aug 21, 2024
3849ae0
Update pennylane/spin/spin_hamiltonian.py
ddhawan11 Aug 21, 2024
856acd0
[skip ci] addressed comments
ddhawan11 Aug 21, 2024
794d4a1
add minor doc fixes
soranjh Aug 21, 2024
a3f76db
add minor doc fixes
soranjh Aug 21, 2024
20d1735
add minor doc fixes
soranjh Aug 21, 2024
683bbcd
Fixed tests
ddhawan11 Aug 22, 2024
930b4a7
Merge branch 'master' into spin_hamiltonian
ddhawan11 Aug 22, 2024
260c60c
Fixed CI
ddhawan11 Aug 22, 2024
bf65324
Added more test coverage
ddhawan11 Aug 22, 2024
ad9dc4c
Minor doc fix
ddhawan11 Aug 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@

<h4>Creating spin Hamiltonians 🧑‍🎨</h4>

* The function ``transverse_ising`` is added to generate transverse-field Ising Hamiltonian.
[(#6106)](https://github.com/PennyLaneAI/pennylane/pull/6106)

<h3>Improvements 🛠</h3>

<h4>A Prep-Select-Prep template</h4>
Expand Down Expand Up @@ -379,6 +382,7 @@ Ahmed Darwish,
Astral Cai,
Yushao Chen,
Ahmed Darwish,
Diksha Dhawan
Maja Franz,
Lillian M. A. Frederiksen,
Pietropaolo Frisoni,
Expand Down
2 changes: 2 additions & 0 deletions pennylane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@

from pennylane.devices.device_constructor import device, refresh_devices

import pennylane.spin

# Look for an existing configuration file
default_config = Configuration("config.toml")

Expand Down
19 changes: 19 additions & 0 deletions pennylane/spin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2018-2024 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This module provides the functionality to create spin Hamiltonians.
"""

from .lattice import Lattice
from .spin_hamiltonian import transverse_ising
347 changes: 347 additions & 0 deletions pennylane/spin/lattice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,347 @@
# Copyright 2018-2024 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This file contains functions and classes to create a
:class:`~pennylane.spin.Lattice` object. This object stores all
the necessary information about a lattice.
"""
import itertools

from scipy.spatial import KDTree

from pennylane import math

# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=use-a-generator, too-few-public-methods


class Lattice:
r"""Constructs a Lattice object.

Args:
soranjh marked this conversation as resolved.
Show resolved Hide resolved
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]``.
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.

Raises:
TypeError:
if ``n_cells`` contains numbers other than positive integers.
ValueError:
if ``positions`` doesn't have a dimension of 2.
if ``vectors`` doesn't have a dimension of 2 or the length of vectors is not equal to the number of vectors.
if ``boundary_condition`` is not a bool or a list of bools with length equal to the number of vectors

Returns:
Lattice object
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved

**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)
[(2, 3, 0), (0, 2, 0), (1, 3, 0), (0, 1, 0)]
"""
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved

def __init__(
self,
n_cells,
vectors,
positions=None,
boundary_condition=False,
neighbour_order=1,
distance_tol=1e-5,
):

if not all(isinstance(l, int) for l in n_cells) or any(l <= 0 for l in n_cells):
raise TypeError("Argument `n_cells` must be a list of positive integers")

self.vectors = math.asarray(vectors)

if self.vectors.ndim != 2:
raise ValueError(f"The dimensions of vectors array must be 2, got {self.vectors.ndim}.")

if self.vectors.shape[0] != self.vectors.shape[1]:
raise ValueError("The number of primitive vectors must match their length")

if positions is None:
positions = math.zeros(self.vectors.shape[0])[None, :]
self.positions = math.asarray(positions)

if self.positions.ndim != 2:
raise ValueError(
f"The dimensions of positions array must be 2, got {self.positions.ndim}."
)

if isinstance(boundary_condition, bool):
boundary_condition = [boundary_condition] * len(n_cells)

if not all(isinstance(b, bool) for b in boundary_condition) or len(
boundary_condition
) != len(n_cells):
raise ValueError(
"Argument 'boundary_condition' must be a bool or a list of bools with length equal to number of vectors"
)

self.n_cells = math.asarray(n_cells)
self.n_dim = len(n_cells)
self.boundary_condition = boundary_condition

n_sl = len(self.positions)
self.n_sites = math.prod(n_cells) * n_sl
self.lattice_points, lattice_map = self._generate_grid(neighbour_order)

cutoff = neighbour_order * math.max(math.linalg.norm(self.vectors, axis=1)) + distance_tol
edges = self._identify_neighbours(cutoff)
self._generate_true_edges(edges, lattice_map, neighbour_order)
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved
self.edges_indices = [(v1, v2) for (v1, v2, color) in self.edges]

def _identify_neighbours(self, cutoff):
soranjh marked this conversation as resolved.
Show resolved Hide resolved
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."""

tree = KDTree(self.lattice_points)
indices = tree.query_ball_tree(tree, cutoff)
# Number to scale the distance, needed to sort edges into appropriate bins, it is currently
# set as a multiple of expected denominators.
bin_density = 2 ^ 5 * 3 ^ 3 * 5 ^ 2 * 7 * 11 * 13
unique_pairs = set()
edges = {}
for i, neighbours in enumerate(indices):
for neighbour in neighbours:
if neighbour != i:
pair = (min(i, neighbour), max(i, neighbour))
if pair not in unique_pairs:
unique_pairs.add(pair)
dist = math.linalg.norm(
self.lattice_points[i] - self.lattice_points[neighbour]
)
obliviateandsurrender marked this conversation as resolved.
Show resolved Hide resolved
scaled_dist = math.rint(dist * bin_density)

if scaled_dist not in edges:
edges[scaled_dist] = []
edges[scaled_dist].append((i, neighbour))

edges = [value for _, value in sorted(edges.items())]
return edges

def _generate_true_edges(self, edges, map, neighbour_order):
soranjh marked this conversation as resolved.
Show resolved Hide resolved
r"""Modifies the edges to remove hidden nodes and create connections based on boundary_conditions"""
soranjh marked this conversation as resolved.
Show resolved Hide resolved

self.edges = []
for i, edge in enumerate(edges):
if i >= neighbour_order:
break
for e1, e2 in edge:
true_edge = (min(map[e1], map[e2]), max(map[e1], map[e2]), i)
if true_edge not in self.edges:
self.edges.append(true_edge)

def _generate_grid(self, neighbour_order):
"""Generates the coordinates of all lattice sites and their indices.

Args:
neighbour_order (int): Specifies the interaction level for neighbors within the lattice.

Returns:
lattice_points: The coordinates of all lattice sites.
lattice_map: A list to represent the node number for each lattice_point.
"""

n_sl = len(self.positions)
wrap_grid = math.where(self.boundary_condition, neighbour_order, 0)

ranges_dim = [range(-wrap_grid[i], Lx + wrap_grid[i]) for i, Lx in enumerate(self.n_cells)]
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved
ranges_dim.append(range(n_sl))
nsites_axis = math.cumprod([n_sl, *self.n_cells[:0:-1]])[::-1]
lattice_points = []
lattice_map = []

for Lx in itertools.product(*ranges_dim):
point = math.dot(Lx[:-1], self.vectors) + self.positions[Lx[-1]]
node_index = math.dot(math.mod(Lx[:-1], self.n_cells), nsites_axis) + Lx[-1]
lattice_points.append(point)
lattice_map.append(node_index)

return math.array(lattice_points), math.array(lattice_map)

def add_edge(self, edge_indices):
r"""Adds a specific edge based on the site index without translating it.

Args:
edge_indices: List of edges to be added.
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved

Returns:
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved
Updates the edges attribute to include provided edges.
"""

for edge_index in edge_indices:
edge_index = tuple(edge_index)
if len(edge_index) > 3 or len(edge_index) < 2:
raise TypeError("Length of the tuple representing each edge can only be 2 or 3.")

if len(edge_index) == 2:
if edge_index in self.edges_indices:
raise ValueError("Edge is already present")
new_edge = (*edge_index, 0)
else:
if edge_index in self.edges:
raise ValueError("Edge is already present")
new_edge = edge_index

self.edges.append(new_edge)


def _chain(n_cells, boundary_condition=False, neighbour_order=1):
r"""Generates a chain lattice"""
vectors = [[1]]
n_cells = n_cells[0:1]
lattice_chain = Lattice(
n_cells=n_cells,
vectors=vectors,
neighbour_order=neighbour_order,
boundary_condition=boundary_condition,
)
return lattice_chain


def _square(n_cells, boundary_condition=False, neighbour_order=1):
r"""Generates a square lattice"""
vectors = [[1, 0], [0, 1]]
positions = [[0, 0]]
n_cells = n_cells[0:2]
lattice_square = Lattice(
n_cells=n_cells,
vectors=vectors,
positions=positions,
neighbour_order=neighbour_order,
boundary_condition=boundary_condition,
)

return lattice_square


def _rectangle(n_cells, boundary_condition=False, neighbour_order=1):
r"""Generates a rectangle lattice"""
vectors = [[1, 0], [0, 1]]
positions = [[0, 0]]

n_cells = n_cells[0:2]
lattice_rec = Lattice(
n_cells=n_cells,
vectors=vectors,
positions=positions,
neighbour_order=neighbour_order,
boundary_condition=boundary_condition,
)

return lattice_rec


def _honeycomb(n_cells, boundary_condition=False, neighbour_order=1):
r"""Generates a honeycomb lattice"""
vectors = [[1, 0], [0.5, math.sqrt(3) / 2]]
positions = [[0, 0], [0.5, 0.5 / 3**0.5]]

n_cells = n_cells[0:2]
lattice_honeycomb = Lattice(
n_cells=n_cells,
vectors=vectors,
positions=positions,
neighbour_order=neighbour_order,
boundary_condition=boundary_condition,
)

return lattice_honeycomb


def _triangle(n_cells, boundary_condition=False, neighbour_order=1):
r"""Generates a triangular lattice"""
vectors = [[1, 0], [0.5, math.sqrt(3) / 2]]
positions = [[0, 0]]

n_cells = n_cells[0:2]
obliviateandsurrender marked this conversation as resolved.
Show resolved Hide resolved
lattice_triangle = Lattice(
n_cells=n_cells,
vectors=vectors,
positions=positions,
neighbour_order=neighbour_order,
boundary_condition=boundary_condition,
)

return lattice_triangle


def _kagome(n_cells, boundary_condition=False, neighbour_order=1):
r"""Generates a kagome lattice"""
vectors = [[1, 0], [0.5, math.sqrt(3) / 2]]
positions = [[0.0, 0], [-0.25, math.sqrt(3) / 4], [0.25, math.sqrt(3) / 4]]

n_cells = n_cells[0:2]
lattice_kagome = Lattice(
n_cells=n_cells,
vectors=vectors,
positions=positions,
neighbour_order=neighbour_order,
boundary_condition=boundary_condition,
)

return lattice_kagome


# 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.

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.
boundary_condition (bool or list[bool]): Defines boundary conditions, False for open boundary condition, each element represents the axis for lattice. It defaults to False.
neighbour_order (int): Specifies the interaction level for neighbors within the lattice. Default is 1 (nearest neighbour).

Returns:
lattice object
"""

lattice_shape = lattice.strip().lower()

if lattice_shape not in ["chain", "square", "rectangle", "honeycomb", "triangle", "kagome"]:
raise ValueError(
f"Lattice shape, '{lattice}' is not supported."
f"Please set lattice to: chain, square, rectangle, honeycomb, triangle, or kagome"
)

if lattice_shape == "chain":
lattice = _chain(n_cells, boundary_condition, neighbour_order)
elif lattice_shape == "square":
lattice = _square(n_cells, boundary_condition, neighbour_order)
elif lattice_shape == "rectangle":
lattice = _rectangle(n_cells, boundary_condition, neighbour_order)
elif lattice_shape == "honeycomb":
lattice = _honeycomb(n_cells, boundary_condition, neighbour_order)
elif lattice_shape == "triangle":
lattice = _triangle(n_cells, boundary_condition, neighbour_order)
elif lattice_shape == "kagome":
lattice = _kagome(n_cells, boundary_condition, neighbour_order)
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved

return lattice
Loading
Loading