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

feature: Factory methods for AHS AtomArrangments by AbdullahKazi500 #989

Closed
194 changes: 166 additions & 28 deletions src/braket/ahs/atom_arrangement.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,29 @@
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

from __future__ import annotations
"""
A module for representing and manipulating atom arrangements for Analog
Hamiltonian Simulation.

This module provides classes and functions to create and handle atom
arrangements, including different lattice structures such as square,
rectangular, decorated Bravais, and honeycomb lattices.

Typical usage example:

AbdullahKazi500 marked this conversation as resolved.
Show resolved Hide resolved
canvas_boundary_points = [(0, 0), (7.5e-5, 0), (7.5e-5, 7.5e-5), (0, 7.5e-5)]
square_lattice = AtomArrangement.from_square_lattice(4e-6,
canvas_boundary_points)
"""

from __future__ import annotations
from collections.abc import Iterator
from dataclasses import dataclass
from decimal import Decimal
from enum import Enum
from numbers import Number
from typing import Union

from typing import Union, Tuple, List
import numpy as np

from braket.ahs.discretization_types import DiscretizationError, DiscretizationProperties
AbdullahKazi500 marked this conversation as resolved.
Show resolved Hide resolved


Expand All @@ -33,8 +45,7 @@ class SiteType(Enum):
@dataclass
class AtomArrangementItem:
"""Represents an item (coordinate and metadata) in an atom arrangement."""

coordinate: tuple[Number, Number]
coordinate: Tuple[Number, Number]
site_type: SiteType

def _validate_coordinate(self) -> None:
Expand All @@ -55,69 +66,71 @@ def __post_init__(self) -> None:


class AtomArrangement:
"""Represents a set of coordinates that can be used as a register to an
Analog Hamiltonian Simulation.
"""
def __init__(self):
"""Represents a set of coordinates that can be used as a register to an
AnalogHamiltonianSimulation.
"""
self._sites = []

def add(
def add(
self,
coordinate: Union[tuple[Number, Number], np.ndarray],
site_type: SiteType = SiteType.FILLED,
) -> AtomArrangement:
"""Add a coordinate to the atom arrangement.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using Google formatted docstrings in this repo (see more here: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings), please don't remove these empty lines.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for many other removed lines in the docstrings below.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peterkomar-aws Hi peter resolved all the issues let me know if something is left

Args:
Args:
coordinate (Union[tuple[Number, Number], ndarray]): The coordinate of the
atom (in meters). The coordinates can be a numpy array of shape (2,)
or a tuple of int, float, Decimal
site_type (SiteType): The type of site. Optional. Default is FILLED.


Returns:
AtomArrangement: returns self (to allow for chaining).
"""
self._sites.append(AtomArrangementItem(tuple(coordinate), site_type))
return self

def coordinate_list(self, coordinate_index: Number) -> list[Number]:
def coordinate_list(self, coordinate_index: Number) -> List[Number]:
"""Returns all the coordinates at the given index.

Args:
coordinate_index (Number): The index to get for each coordinate.

Returns:
list[Number]:The list of coordinates at the given index.

List[Number]: The list of coordinates at the given index.
Example:
To get a list of all x-coordinates: coordinate_list(0)
To get a list of all y-coordinates: coordinate_list(1)
"""
return [site.coordinate[coordinate_index] for site in self._sites]

def __iter__(self) -> Iterator:
return self._sites.__iter__()
return iter(self._sites)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this change? What edge case do you have in mind?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of changing the iteration method in this context is to ensure that the method returns a proper iterator over the _sites list.

return self._sites.iter()
This directly calls the iter method of the list object stored in _sites.
return iter(self._sites)
This uses the built-in iter() function to create an iterator from the list _sites.
Both are valid and functionally equivalent. The main goal is to return an iterator for the _sites list


def __len__(self):
return self._sites.__len__()
return len(self._sites)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this change? What edge case do you have in mind?


def discretize(self, properties: DiscretizationProperties) -> AtomArrangement:
"""Creates a discretized version of the atom arrangement,
rounding all site coordinates to the closest multiple of the
resolution. The types of the sites are unchanged.

"""Creates a discretized version of the atom arrangement, rounding all
site coordinates to the closest multiple of the resolution. The types
of the sites are unchanged.
Args:
properties (DiscretizationProperties): Capabilities of a device that represent the
resolution with which the device can implement the parameters.

properties (DiscretizationProperties): Capabilities of a device
that represent the resolution with which the device can
implement the parameters.

Raises:
DiscretizationError: If unable to discretize the program.

Returns:
AtomArrangement: A new discretized atom arrangement.
"""
try:
position_res = properties.lattice.geometry.positionResolution
position_res = properties.lattice.positionResolution
discretized_arrangement = AtomArrangement()
for site in self._sites:
new_coordinates = tuple(
Expand All @@ -127,3 +140,128 @@ def discretize(self, properties: DiscretizationProperties) -> AtomArrangement:
return discretized_arrangement
except Exception as e:
raise DiscretizationError(f"Failed to discretize register {e}") from e

# Factory methods for lattice structures
@classmethod
def from_square_lattice(cls, lattice_constant: float, canvas_boundary_points: List[Tuple[float, float]]) -> AtomArrangement:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you have the RectangularCanvas defined, let's pass that as an argument instead of the list of tuples.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, since square lattice is a subset of rectangular lattices, you can implement this factory method by calling the rectangular lattice constructor with dx = dy = lattice_constant arguments.

"""Create an atom arrangement with a square lattice."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add detailed docstrings listing what each is.

arrangement = cls()
x_min, y_min = canvas_boundary_points[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The [0] indexing assumes too much about how the points are ordered in the input, it can fail if the user passes the points in different order. Yet another reason to require a RectangularCanvas object as an argument.

x_max, y_max = canvas_boundary_points[2]
x_range = np.arange(x_min, x_max, lattice_constant)
y_range = np.arange(y_min, y_max, lattice_constant)
for x in x_range:
for y in y_range:
arrangement.add((x, y))
return arrangement

@classmethod
def from_rectangular_lattice(cls, dx: float, dy: float, canvas_boundary_points: List[Tuple[float, float]]) -> AtomArrangement:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, let's use the RectangularCanvas type as the type of the argument.

"""Create an atom arrangement with a rectangular lattice."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add detailed docstrings listing what each is.

arrangement = cls()
x_min, y_min = canvas_boundary_points[0]
x_max, y_max = canvas_boundary_points[2]
for x in np.arange(x_min, x_max, dx):
for y in np.arange(y_min, y_max, dy):
arrangement.add((x, y))
return arrangement

@classmethod
def from_decorated_bravais_lattice(cls, lattice_vectors: List[Tuple[float, float]], decoration_points: List[Tuple[float, float]], canvas_boundary_points: List[Tuple[float, float]]) -> AtomArrangement:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, let's use the RectangularCanvas and add docstrings.

"""Create an atom arrangement with a decorated Bravais lattice."""
arrangement = cls()
vec_a, vec_b = np.array(lattice_vectors[0]), np.array(lattice_vectors[1])
x_min, y_min = canvas_boundary_points[0]
x_max, y_max = canvas_boundary_points[2]
i = 0
while (origin := i * vec_a)[0] < x_max:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few issues with the implementation here:

  1. To fill in the entire canvas, depending on the values of vec_a and vec_b, it may not be enough to start from i=0 and j=0. E.g. if vec_a = (1,1) and vec_b = (-1, 1), your current implementation leaves a large empty triangle at the lower right side on a rectangular canvas.
  2. When determining where to stop adding points, your implementation first determines which unit cells to add based on whether point is inside the canvas. This misses adding decoration points from unit cells whose point variable is outside of the canvas, but the decoration points are inside the canvas boundary.
  3. If the user provides unusual, or meaningless vec_a, vec_b, or decoration_points, we should catch it. Validation is needed to be implemented here.

Getting the implementation of this method right is the crux of this issue. We can start from an inefficient but working solution, and worry about efficiency later, but the current implementation is not filling the canvas properly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peterkomar-aws this is a good catch need to think about it

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few issues with the implementation here:

  1. To fill in the entire canvas, depending on the values of vec_a and vec_b, it may not be enough to start from i=0 and j=0. E.g. if vec_a = (1,1) and vec_b = (-1, 1), your current implementation leaves a large empty triangle at the lower right side on a rectangular canvas.
  2. When determining where to stop adding points, your implementation first determines which unit cells to add based on whether point is inside the canvas. This misses adding decoration points from unit cells whose point variable is outside of the canvas, but the decoration points are inside the canvas boundary.
  3. If the user provides unusual, or meaningless vec_a, vec_b, or decoration_points, we should catch it. Validation is needed to be implemented here.

Getting the implementation of this method right is the crux of this issue. We can start from an inefficient but working solution, and worry about efficiency later, but the current implementation is not filling the canvas properly.

to address this issue we can implement something like this

import numpy as np
from typing import List, Tuple, Union
from collections.abc import Iterator
from dataclasses import dataclass
from decimal import Decimal
from enum import Enum
from numbers import Number

class SiteType(Enum):
    VACANT = "Vacant"
    FILLED = "Filled"

@dataclass
class AtomArrangementItem:
    coordinate: Tuple[Number, Number]
    site_type: SiteType

    def _validate_coordinate(self) -> None:
        if len(self.coordinate) != 2:
            raise ValueError(f"{self.coordinate} must be of length 2")
        for idx, num in enumerate(self.coordinate):
            if not isinstance(num, Number):
                raise TypeError(f"{num} at position {idx} must be a number")

    def _validate_site_type(self) -> None:
        allowed_site_types = {SiteType.FILLED, SiteType.VACANT}
        if self.site_type not in allowed_site_types:
            raise ValueError(f"{self.site_type} must be one of {allowed_site_types}")

    def __post_init__(self) -> None:
        self._validate_coordinate()
        self._validate_site_type()

class AtomArrangement:
    def __init__(self):
        self._sites = []

    def add(self, coordinate: Union[Tuple[Number, Number], np.ndarray], site_type: SiteType = SiteType.FILLED) -> AtomArrangement:
        self._sites.append(AtomArrangementItem(tuple(coordinate), site_type))
        return self

    def coordinate_list(self, coordinate_index: Number) -> List[Number]:
        return [site.coordinate[coordinate_index] for site in self._sites]

    def __iter__(self) -> Iterator:
        return iter(self._sites)

    def __len__(self):
        return len(self._sites)

    def discretize(self, properties: DiscretizationProperties) -> AtomArrangement:
        try:
            position_res = properties.lattice.positionResolution
            discretized_arrangement = AtomArrangement()
            for site in self._sites:
                new_coordinates = tuple(
                    round(Decimal(c) / position_res) * position_res for c in site.coordinate
                )
                discretized_arrangement.add(new_coordinates, site.site_type)
            return discretized_arrangement
        except Exception as e:
            raise DiscretizationError(f"Failed to discretize register {e}") from e

    @classmethod
    def from_decorated_bravais_lattice(cls, lattice_vectors: List[Tuple[float, float]], decoration_points: List[Tuple[float, float]], canvas_boundary_points: List[Tuple[float, float]]) -> AtomArrangement:
        """Create an atom arrangement with a decorated Bravais lattice."""
        # Validate input
        if len(lattice_vectors) != 2:
            raise ValueError("Two lattice vectors are required.")
        if any(len(vec) != 2 for vec in lattice_vectors):
            raise ValueError("Each lattice vector must have two components.")
        if any(len(dp) != 2 for dp in decoration_points):
            raise ValueError("Each decoration point must have two components.")
        if len(canvas_boundary_points) != 4:
            raise ValueError("Canvas boundary points should define a rectangle with 4 points.")

        arrangement = cls()
        vec_a, vec_b = np.array(lattice_vectors[0]), np.array(lattice_vectors[1])
        x_min, y_min = canvas_boundary_points[0]
        x_max, y_max = canvas_boundary_points[2]

        def is_within_canvas(point: np.ndarray) -> bool:
            return x_min <= point[0] <= x_max and y_min <= point[1] <= y_max

        # Determine number of steps needed in each direction to fill the canvas
        n_steps_x = int(np.ceil((x_max - x_min) / np.linalg.norm(vec_a)))
        n_steps_y = int(np.ceil((y_max - y_min) / np.linalg.norm(vec_b)))

        for i in range(-n_steps_x, n_steps_x + 1):
            for j in range(-n_steps_y, n_steps_y + 1):
                origin = i * vec_a + j * vec_b
                for dp in decoration_points:
                    decorated_point = origin + np.array(dp)
                    if is_within_canvas(decorated_point):
                        arrangement.add(tuple(decorated_point))

        return arrangement

@dataclass
class LatticeGeometry:
    positionResolution: Decimal

@dataclass
class DiscretizationProperties:
    lattice: LatticeGeometry

class DiscretizationError(Exception):
    pass

# Example usage
if __name__ == "__main__":
    canvas_boundary_points = [(0, 0), (7.5e-5, 0), (7.5e-5, 7.5e-5), (0, 7.5e-5)]

    # Create lattice structures
    decorated_bravais_lattice = AtomArrangement.from_decorated_bravais_lattice(
        [(4e-6, 0), (0, 4e-6)],
        [(1e-6, 1e-6)],
        canvas_boundary_points
    )

    # Validate lattice structure
    def validate_lattice(arrangement, lattice_type):
        num_sites = len(arrangement)
        min_distance = None
        for i, atom1 in enumerate(arrangement):
            for j, atom2 in enumerate(arrangement):
                if i != j:
                    distance = np.linalg.norm(np.array(atom1.coordinate) - np.array(atom2.coordinate))
                    if min_distance is None or distance < min_distance:
                        min_distance = distance
        print(f"Lattice Type: {lattice_type}")
        print(f"Number of lattice points: {num_sites}")
        print(f"Minimum distance between lattice points: {min_distance:.2e} meters")
        print("-" * 40)

    validate_lattice(decorated_bravais_lattice, "Decorated Bravais Lattice")

j = 0
while (point := origin + j * vec_b)[1] < y_max:
if x_min <= point[0] <= x_max and y_min <= point[1] <= y_max:
for dp in decoration_points:
decorated_point = point + np.array(dp)
if x_min <= decorated_point[0] <= x_max and y_min <= decorated_point[1] <= y_max:
arrangement.add(tuple(decorated_point))
j += 1
i += 1
return arrangement

@classmethod
def from_honeycomb_lattice(cls, lattice_constant: float, canvas_boundary_points: List[Tuple[float, float]]) -> AtomArrangement:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add docstrings.

"""Create an atom arrangement with a honeycomb lattice."""
a1 = (lattice_constant, 0)
a2 = (lattice_constant / 2, lattice_constant * np.sqrt(3) / 2)
decoration_points = [(0, 0), (lattice_constant / 2, lattice_constant * np.sqrt(3) / 6)]
return cls.from_decorated_bravais_lattice([a1, a2], decoration_points, canvas_boundary_points)

@classmethod
def from_bravais_lattice(cls, lattice_vectors: List[Tuple[float, float]], canvas_boundary_points: List[Tuple[float, float]]) -> AtomArrangement:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add docstrings.

"""Create an atom arrangement with a Bravais lattice."""
return cls.from_decorated_bravais_lattice(lattice_vectors, [(0, 0)], canvas_boundary_points)


@dataclass
class LatticeGeometry:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed, once you remove the redefinitions of DiscretizationProperties

"""Represents the geometry of a lattice with its position resolution."""
positionResolution: Decimal


@dataclass
class DiscretizationProperties:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not redefine DiscretizationProperties in this module, instead use the existing definition from the discretization_types.py module.

"""Represents the discretization properties of a device."""
lattice: LatticeGeometry


class DiscretizationError(Exception):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not redefine DiscretizationError in this module, instead use the existing definition from the discretization_types.py module.

"""Exception raised for errors in the discretization process."""
pass


class RectangularCanvas:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add docstrigs to this class and its methods

"""Represents a rectangular canvas boundary."""
def __init__(self, bottom_left: Tuple[float, float], top_right: Tuple[float, float]):
self.bottom_left = bottom_left
self.top_right = top_right

def is_within(self, point: Tuple[float, float]) -> bool:
"""Check if a point is within the canvas boundaries."""
x_min, y_min = self.bottom_left
x_max, y_max = self.top_right
x, y = point
return x_min <= x <= x_max and y_min <= y <= y_max


# Example usage
if __name__ == "__main__":
canvas_boundary_points = [(0, 0), (7.5e-5, 0), (7.5e-5, 7.5e-5), (0, 7.5e-5)]

# Create lattice structures
square_lattice = AtomArrangement.from_square_lattice(4e-6, canvas_boundary_points)
rectangular_lattice = AtomArrangement.from_rectangular_lattice(3e-6, 2e-6, canvas_boundary_points)
decorated_bravais_lattice = AtomArrangement.from_decorated_bravais_lattice([(4e-6, 0), (0, 4e-6)], [(1e-6, 1e-6)], canvas_boundary_points)
honeycomb_lattice = AtomArrangement.from_honeycomb_lattice(4e-6, canvas_boundary_points)
bravais_lattice = AtomArrangement.from_bravais_lattice([(4e-6, 0), (0, 4e-6)], canvas_boundary_points)

# Validation function
def validate_lattice(arrangement, lattice_type):
"""Validate the lattice structure."""
num_sites = len(arrangement)
min_distance = None
for i, atom1 in enumerate(arrangement):
for j, atom2 in enumerate(arrangement):
if i != j:
distance = np.linalg.norm(np.array(atom1.coordinate) - np.array(atom2.coordinate))
if min_distance is None or distance < min_distance:
min_distance = distance
print(f"Lattice Type: {lattice_type}")
print(f"Number of lattice points: {num_sites}")
print(f"Minimum distance between lattice points: {min_distance:.2e} meters")
print("-" * 40)

# Validate lattice structures
validate_lattice(square_lattice, "Square Lattice")
validate_lattice(rectangular_lattice, "Rectangular Lattice")
validate_lattice(decorated_bravais_lattice, "Decorated Bravais Lattice")
validate_lattice(honeycomb_lattice, "Honeycomb Lattice")
validate_lattice(bravais_lattice, "Bravais Lattice")

110 changes: 110 additions & 0 deletions test/unit_tests/braket/ahs/test_atom_arrangement.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,113 @@ def test_discretize(default_atom_arrangement, position_res, expected_x, expected
def test_invalid_discretization_properties(default_atom_arrangement):
properties = "not-a-valid-discretization-property"
default_atom_arrangement.discretize(properties)



import numpy as np
import pytest
from braket.ahs.atom_arrangement import AtomArrangement

@pytest.fixture
def canvas_boundary_points():
return [(0, 0), (7.5e-5, 0), (7.5e-5, 7.5e-5), (0, 7.5e-5)]


def test_from_square_lattice(canvas_boundary_points):
lattice_constant = 4e-6
square_lattice = AtomArrangement.from_square_lattice(lattice_constant, canvas_boundary_points)
expected_sites = [
(x, y)
for x in np.arange(0, 7.5e-5, lattice_constant)
for y in np.arange(0, 7.5e-5, lattice_constant)
]
assert len(square_lattice) == len(expected_sites)
for site in square_lattice:
assert site.coordinate in expected_sites


def test_from_rectangular_lattice(canvas_boundary_points):
dx = 4e-6
dy = 6e-6
rectangular_lattice = AtomArrangement.from_rectangular_lattice(dx, dy, canvas_boundary_points)
expected_sites = [
(x, y)
for x in np.arange(0, 7.5e-5, dx)
for y in np.arange(0, 7.5e-5, dy)
]
assert len(rectangular_lattice) == len(expected_sites)
for site in rectangular_lattice:
assert site.coordinate in expected_sites


def test_from_honeycomb_lattice(canvas_boundary_points):
lattice_constant = 6e-6
honeycomb_lattice = AtomArrangement.from_honeycomb_lattice(lattice_constant, canvas_boundary_points)
a1 = (lattice_constant, 0)
a2 = (lattice_constant / 2, lattice_constant * np.sqrt(3) / 2)
decoration_points = [(0, 0), (lattice_constant / 2, lattice_constant * np.sqrt(3) / 6)]
expected_sites = []
vec_a, vec_b = np.array(a1), np.array(a2)
x_min, y_min = 0, 0
x_max, y_max = 7.5e-5, 7.5e-5
i = 0
while (origin := i * vec_a)[0] < x_max:
j = 0
while (point := origin + j * vec_b)[1] < y_max:
if x_min <= point[0] <= x_max and y_min <= point[1] <= y_max:
for dp in decoration_points:
decorated_point = point + np.array(dp)
if x_min <= decorated_point[0] <= x_max and y_min <= decorated_point[1] <= y_max:
expected_sites.append(tuple(decorated_point))
j += 1
i += 1
assert len(honeycomb_lattice) == len(expected_sites)
for site in honeycomb_lattice:
assert site.coordinate in expected_sites


def test_from_bravais_lattice(canvas_boundary_points):
a1 = (0, 5e-6)
a2 = (3e-6, 4e-6)
bravais_lattice = AtomArrangement.from_bravais_lattice([a1, a2], canvas_boundary_points)
expected_sites = []
vec_a, vec_b = np.array(a1), np.array(a2)
x_min, y_min = 0, 0
x_max, y_max = 7.5e-5, 7.5e-5
i = 0
while (origin := i * vec_a)[0] < x_max:
j = 0
while (point := origin + j * vec_b)[1] < y_max:
if x_min <= point[0] <= x_max and y_min <= point[1] <= y_max:
expected_sites.append(tuple(point))
j += 1
i += 1
assert len(bravais_lattice) == len(expected_sites)
for site in bravais_lattice:
assert site.coordinate in expected_sites


def test_from_decorated_bravais_lattice(canvas_boundary_points):
a1 = (0, 10e-6)
a2 = (6e-6, 8e-6)
basis = [(0, 0), (0, 5e-6), (4e-6, 2e-6)]
decorated_bravais_lattice = AtomArrangement.from_decorated_bravais_lattice([a1, a2], basis, canvas_boundary_points)
expected_sites = []
vec_a, vec_b = np.array(a1), np.array(a2)
x_min, y_min = 0, 0
x_max, y_max = 7.5e-5, 7.5e-5
i = 0
while (origin := i * vec_a)[0] < x_max:
j = 0
while (point := origin + j * vec_b)[1] < y_max:
if x_min <= point[0] <= x_max and y_min <= point[1] <= y_max:
for dp in basis:
decorated_point = point + np.array(dp)
if x_min <= decorated_point[0] <= x_max and y_min <= decorated_point[1] <= y_max:
expected_sites.append(tuple(decorated_point))
j += 1
i += 1
assert len(decorated_bravais_lattice) == len(expected_sites)
for site in decorated_bravais_lattice:
assert site.coordinate in expected_sites

Loading