|
|
@ -7,11 +7,11 @@ from typing import Union, Dict, Optional, Sequence, Any
|
|
|
|
import copy
|
|
|
|
import copy
|
|
|
|
from abc import ABCMeta, abstractmethod
|
|
|
|
from abc import ABCMeta, abstractmethod
|
|
|
|
|
|
|
|
|
|
|
|
import numpy # type: ignore
|
|
|
|
import numpy
|
|
|
|
from numpy.typing import ArrayLike
|
|
|
|
from numpy.typing import ArrayLike, NDArray
|
|
|
|
|
|
|
|
|
|
|
|
from .error import PatternError
|
|
|
|
from .error import PatternError
|
|
|
|
from .utils import rotation_matrix_2d, vector2, AutoSlots
|
|
|
|
from .utils import rotation_matrix_2d, AutoSlots
|
|
|
|
from .traits import LockableImpl, Copyable, Scalable, Rotatable, Mirrorable
|
|
|
|
from .traits import LockableImpl, Copyable, Scalable, Rotatable, Mirrorable
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -23,7 +23,7 @@ class Repetition(Copyable, Rotatable, Mirrorable, Scalable, metaclass=ABCMeta):
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
@abstractmethod
|
|
|
|
@abstractmethod
|
|
|
|
def displacements(self) -> numpy.ndarray:
|
|
|
|
def displacements(self) -> NDArray[numpy.float64]:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
An Nx2 ndarray specifying all offsets generated by this repetition
|
|
|
|
An Nx2 ndarray specifying all offsets generated by this repetition
|
|
|
|
"""
|
|
|
|
"""
|
|
|
@ -44,7 +44,7 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
|
|
|
'_a_count',
|
|
|
|
'_a_count',
|
|
|
|
'_b_count')
|
|
|
|
'_b_count')
|
|
|
|
|
|
|
|
|
|
|
|
_a_vector: numpy.ndarray
|
|
|
|
_a_vector: NDArray[numpy.float64]
|
|
|
|
""" Vector `[x, y]` specifying the first lattice vector of the grid.
|
|
|
|
""" Vector `[x, y]` specifying the first lattice vector of the grid.
|
|
|
|
Specifies center-to-center spacing between adjacent elements.
|
|
|
|
Specifies center-to-center spacing between adjacent elements.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
@ -52,7 +52,7 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
|
|
|
_a_count: int
|
|
|
|
_a_count: int
|
|
|
|
""" Number of instances along the direction specified by the `a_vector` """
|
|
|
|
""" Number of instances along the direction specified by the `a_vector` """
|
|
|
|
|
|
|
|
|
|
|
|
_b_vector: Optional[numpy.ndarray]
|
|
|
|
_b_vector: Optional[NDArray[numpy.float64]]
|
|
|
|
""" Vector `[x, y]` specifying a second lattice vector for the grid.
|
|
|
|
""" Vector `[x, y]` specifying a second lattice vector for the grid.
|
|
|
|
Specifies center-to-center spacing between adjacent elements.
|
|
|
|
Specifies center-to-center spacing between adjacent elements.
|
|
|
|
Can be `None` for a 1D array.
|
|
|
|
Can be `None` for a 1D array.
|
|
|
@ -100,8 +100,8 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
|
|
|
raise PatternError(f'Repetition has too-small b_count: {b_count}')
|
|
|
|
raise PatternError(f'Repetition has too-small b_count: {b_count}')
|
|
|
|
|
|
|
|
|
|
|
|
object.__setattr__(self, 'locked', False)
|
|
|
|
object.__setattr__(self, 'locked', False)
|
|
|
|
self.a_vector = a_vector
|
|
|
|
self.a_vector = a_vector # type: ignore # setter handles type conversion
|
|
|
|
self.b_vector = b_vector
|
|
|
|
self.b_vector = b_vector # type: ignore # setter handles type conversion
|
|
|
|
self.a_count = a_count
|
|
|
|
self.a_count = a_count
|
|
|
|
self.b_count = b_count
|
|
|
|
self.b_count = b_count
|
|
|
|
self.locked = locked
|
|
|
|
self.locked = locked
|
|
|
@ -122,11 +122,11 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
|
|
|
|
|
|
|
|
|
|
|
# a_vector property
|
|
|
|
# a_vector property
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def a_vector(self) -> numpy.ndarray:
|
|
|
|
def a_vector(self) -> NDArray[numpy.float64]:
|
|
|
|
return self._a_vector
|
|
|
|
return self._a_vector
|
|
|
|
|
|
|
|
|
|
|
|
@a_vector.setter
|
|
|
|
@a_vector.setter
|
|
|
|
def a_vector(self, val: vector2):
|
|
|
|
def a_vector(self, val: ArrayLike) -> None:
|
|
|
|
if not isinstance(val, numpy.ndarray):
|
|
|
|
if not isinstance(val, numpy.ndarray):
|
|
|
|
val = numpy.array(val, dtype=float)
|
|
|
|
val = numpy.array(val, dtype=float)
|
|
|
|
|
|
|
|
|
|
|
@ -136,11 +136,11 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
|
|
|
|
|
|
|
|
|
|
|
# b_vector property
|
|
|
|
# b_vector property
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def b_vector(self) -> numpy.ndarray:
|
|
|
|
def b_vector(self) -> Optional[NDArray[numpy.float64]]:
|
|
|
|
return self._b_vector
|
|
|
|
return self._b_vector
|
|
|
|
|
|
|
|
|
|
|
|
@b_vector.setter
|
|
|
|
@b_vector.setter
|
|
|
|
def b_vector(self, val: vector2):
|
|
|
|
def b_vector(self, val: ArrayLike) -> None:
|
|
|
|
if not isinstance(val, numpy.ndarray):
|
|
|
|
if not isinstance(val, numpy.ndarray):
|
|
|
|
val = numpy.array(val, dtype=float, copy=True)
|
|
|
|
val = numpy.array(val, dtype=float, copy=True)
|
|
|
|
|
|
|
|
|
|
|
@ -154,7 +154,7 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
|
|
|
return self._a_count
|
|
|
|
return self._a_count
|
|
|
|
|
|
|
|
|
|
|
|
@a_count.setter
|
|
|
|
@a_count.setter
|
|
|
|
def a_count(self, val: int):
|
|
|
|
def a_count(self, val: int) -> None:
|
|
|
|
if val != int(val):
|
|
|
|
if val != int(val):
|
|
|
|
raise PatternError('a_count must be convertable to an int!')
|
|
|
|
raise PatternError('a_count must be convertable to an int!')
|
|
|
|
self._a_count = int(val)
|
|
|
|
self._a_count = int(val)
|
|
|
@ -165,13 +165,16 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
|
|
|
return self._b_count
|
|
|
|
return self._b_count
|
|
|
|
|
|
|
|
|
|
|
|
@b_count.setter
|
|
|
|
@b_count.setter
|
|
|
|
def b_count(self, val: int):
|
|
|
|
def b_count(self, val: int) -> None:
|
|
|
|
if val != int(val):
|
|
|
|
if val != int(val):
|
|
|
|
raise PatternError('b_count must be convertable to an int!')
|
|
|
|
raise PatternError('b_count must be convertable to an int!')
|
|
|
|
self._b_count = int(val)
|
|
|
|
self._b_count = int(val)
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def displacements(self) -> numpy.ndarray:
|
|
|
|
def displacements(self) -> NDArray[numpy.float64]:
|
|
|
|
|
|
|
|
if self.b_vector is None:
|
|
|
|
|
|
|
|
return numpy.arange(self.a_count)[:, None] * self.a_vector[None, :]
|
|
|
|
|
|
|
|
|
|
|
|
aa, bb = numpy.meshgrid(numpy.arange(self.a_count), numpy.arange(self.b_count), indexing='ij')
|
|
|
|
aa, bb = numpy.meshgrid(numpy.arange(self.a_count), numpy.arange(self.b_count), indexing='ij')
|
|
|
|
return (aa.flatten()[:, None] * self.a_vector[None, :]
|
|
|
|
return (aa.flatten()[:, None] * self.a_vector[None, :]
|
|
|
|
+ bb.flatten()[:, None] * self.b_vector[None, :]) # noqa
|
|
|
|
+ bb.flatten()[:, None] * self.b_vector[None, :]) # noqa
|
|
|
@ -207,7 +210,7 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
|
|
|
self.b_vector[1 - axis] *= -1
|
|
|
|
self.b_vector[1 - axis] *= -1
|
|
|
|
return self
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def get_bounds(self) -> Optional[numpy.ndarray]:
|
|
|
|
def get_bounds(self) -> Optional[NDArray[numpy.float64]]:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
|
|
|
|
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
|
|
|
|
extent of the `Grid` in each dimension.
|
|
|
|
extent of the `Grid` in each dimension.
|
|
|
@ -216,7 +219,7 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
|
|
|
`[[x_min, y_min], [x_max, y_max]]` or `None`
|
|
|
|
`[[x_min, y_min], [x_max, y_max]]` or `None`
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
a_extent = self.a_vector * self.a_count
|
|
|
|
a_extent = self.a_vector * self.a_count
|
|
|
|
b_extent = self.b_vector * self.b_count if self.b_count != 0 else 0
|
|
|
|
b_extent = self.b_vector * self.b_count if (self.b_vector is not None) else 0 # type: Union[NDArray[numpy.float64], float]
|
|
|
|
|
|
|
|
|
|
|
|
corners = ((0, 0), a_extent, b_extent, a_extent + b_extent)
|
|
|
|
corners = ((0, 0), a_extent, b_extent, a_extent + b_extent)
|
|
|
|
xy_min = numpy.min(corners, axis=0)
|
|
|
|
xy_min = numpy.min(corners, axis=0)
|
|
|
@ -296,24 +299,26 @@ class Arbitrary(LockableImpl, Repetition, metaclass=AutoSlots):
|
|
|
|
`[[x0, y0], [x1, y1], ...]`
|
|
|
|
`[[x0, y0], [x1, y1], ...]`
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
_displacements: numpy.ndarray
|
|
|
|
_displacements: NDArray[numpy.float64]
|
|
|
|
""" List of vectors `[[x0, y0], [x1, y1], ...]` specifying the offsets
|
|
|
|
""" List of vectors `[[x0, y0], [x1, y1], ...]` specifying the offsets
|
|
|
|
of the instances.
|
|
|
|
of the instances.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def displacements(self) -> numpy.ndarray:
|
|
|
|
def displacements(self) -> Any: # TODO: mypy#3004 NDArray[numpy.float64]:
|
|
|
|
return self._displacements
|
|
|
|
return self._displacements
|
|
|
|
|
|
|
|
|
|
|
|
@displacements.setter
|
|
|
|
@displacements.setter
|
|
|
|
def displacements(self, val: ArrayLike):
|
|
|
|
def displacements(self, val: ArrayLike) -> None:
|
|
|
|
val = numpy.array(val, float)
|
|
|
|
vala: NDArray[numpy.float64] = numpy.array(vala, dtype=float)
|
|
|
|
val = numpy.sort(val.view([('', val.dtype)] * val.shape[1]), 0).view(val.dtype) # sort rows
|
|
|
|
vala = numpy.sort(vala.view([('', vala.dtype)] * vala.shape[1]), 0).view(vala.dtype) # sort rows
|
|
|
|
self._displacements = val
|
|
|
|
self._displacements = vala
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self,
|
|
|
|
def __init__(
|
|
|
|
displacements: ArrayLike,
|
|
|
|
self,
|
|
|
|
locked: bool = False,):
|
|
|
|
displacements: ArrayLike,
|
|
|
|
|
|
|
|
locked: bool = False,
|
|
|
|
|
|
|
|
) -> None:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Args:
|
|
|
|
Args:
|
|
|
|
displacements: List of vectors (Nx2 ndarray) specifying displacements.
|
|
|
|
displacements: List of vectors (Nx2 ndarray) specifying displacements.
|
|
|
@ -383,7 +388,7 @@ class Arbitrary(LockableImpl, Repetition, metaclass=AutoSlots):
|
|
|
|
self.displacements[1 - axis] *= -1
|
|
|
|
self.displacements[1 - axis] *= -1
|
|
|
|
return self
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def get_bounds(self) -> Optional[numpy.ndarray]:
|
|
|
|
def get_bounds(self) -> Optional[NDArray[numpy.float64]]:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
|
|
|
|
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
|
|
|
|
extent of the `displacements` in each dimension.
|
|
|
|
extent of the `displacements` in each dimension.
|
|
|
|