From eebe607042e2069e3dd99895b90903aa246d187c Mon Sep 17 00:00:00 2001 From: Nick Papior Date: Wed, 11 Oct 2023 11:05:01 +0200 Subject: [PATCH] added new/to for the Lattice This makes Lattice work exactly the same as Geometry. Also adde Lattice.to["cuboid"] Signed-off-by: Nick Papior --- CHANGELOG.md | 3 + src/sisl/__init__.py | 3 + src/sisl/lattice.py | 126 ++++++++++++++++++++++++++++----- src/sisl/tests/test_lattice.py | 10 ++- 4 files changed, 124 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5d762edbe..14c9e4651e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ we hit release version 1.0.0. ## [0.14.3] - YYYY-MM-DD +### Added +- added `Lattice.to` and `Lattice.new` to function the same + as `Geometry`, added Lattice.to["cuboid"] ## [0.14.2] - 2023-10-04 diff --git a/src/sisl/__init__.py b/src/sisl/__init__.py index c782930239..6ab8395258 100644 --- a/src/sisl/__init__.py +++ b/src/sisl/__init__.py @@ -147,6 +147,9 @@ # Since __getitem__ always instantiate the class, we have to use the # contained lookup table. Geometry.new.register(BaseSile, Geometry.new._dispatchs[str]) +Geometry.to.register(BaseSile, Geometry.to._dispatchs[str]) +Lattice.new.register(BaseSile, Lattice.new._dispatchs[str]) +Lattice.to.register(BaseSile, Lattice.to._dispatchs[str]) # Import the default geom structure # This enables: diff --git a/src/sisl/lattice.py b/src/sisl/lattice.py index 160bb51b28..c12ce5dcbe 100644 --- a/src/sisl/lattice.py +++ b/src/sisl/lattice.py @@ -12,12 +12,15 @@ import warnings from numbers import Integral from typing import TYPE_CHECKING, Tuple, Union +from pathlib import Path import numpy as np from numpy import dot, ndarray from . import _array as _a from . import _plot as plt +from ._dispatcher import AbstractDispatch, ClassDispatcher, TypeDispatcher +from ._dispatch_class import _ToNew from ._internal import set_module from ._lattice import cell_invert, cell_reciprocal from ._math_small import cross3, dot3 @@ -34,7 +37,12 @@ @set_module("sisl") -class Lattice: +class Lattice(_ToNew, + new=ClassDispatcher("new", + instance_dispatcher=TypeDispatcher), + to=ClassDispatcher("to", + type_dispatcher=None) + ): r""" A cell class to retain lattice vectors and a supercell structure The supercell structure is comprising the *primary* unit-cell and neighbouring @@ -102,6 +110,7 @@ def origin(self, origin): """ Set origin for the cell """ self._origin[:] = origin + @deprecation("toCuboid is deprecated, please use lattice.to['cuboid'](...) instead.") def toCuboid(self, orthogonal=False): """ A cuboid with vectors as this unit-cell and center with respect to its origin @@ -110,20 +119,7 @@ def toCuboid(self, orthogonal=False): orthogonal : bool, optional if true the cuboid has orthogonal sides such that the entire cell is contained """ - if not orthogonal: - return Cuboid(self.cell.copy(), self.center() + self.origin) - def find_min_max(cmin, cmax, new): - for i in range(3): - cmin[i] = min(cmin[i], new[i]) - cmax[i] = max(cmax[i], new[i]) - cell = self.cell - cmin = cell.min(0) - cmax = cell.max(0) - find_min_max(cmin, cmax, cell[[0, 1], :].sum(0)) - find_min_max(cmin, cmax, cell[[0, 2], :].sum(0)) - find_min_max(cmin, cmax, cell[[1, 2], :].sum(0)) - find_min_max(cmin, cmax, cell.sum(0)) - return Cuboid(cmax - cmin, self.center() + self.origin) + return self.to[Cuboid](orthogonal=orthogonal) def parameters(self, rad=False) -> Tuple[float, float, float, float, float, float]: r""" Cell parameters of this cell in 3 lengths and 3 angles @@ -1185,8 +1181,106 @@ def __plot__(self, axis=None, axes=False, *args, **kwargs): return axes +new_dispatch = Lattice.new +to_dispatch = Lattice.to + +# Define base-class for this +class LatticeNewDispatcher(AbstractDispatch): + """ Base dispatcher from class passing arguments to Geometry class + + This forwards all `__call__` calls to `dispatch` + """ + + def __call__(self, *args, **kwargs): + return self.dispatch(*args, **kwargs) + +class LatticeNewLatticeDispatcher(LatticeNewDispatcher): + def dispatch(self, lattice): + return lattice +new_dispatch.register(Lattice, LatticeNewLatticeDispatcher) + +class LatticeNewAseDispatcher(LatticeNewDispatcher): + def dispatch(self, aseg, **kwargs): + cell = aseg.get_cell() + nsc = [3 if pbc else 1 for pbc in aseg.pbc] + return Lattice(cell, nsc=nsc) +new_dispatch.register("ase", LatticeNewAseDispatcher) + +# currently we can't ensure the ase Atoms type +# to get it by type(). That requires ase to be importable. +try: + from ase import Cell as ase_Cell + new_dispatch.register(ase_Cell, LatticeNewAseDispatcher) + # ensure we don't pollute name-space + del ase_Cell +except Exception: + pass + +class LatticeNewFileDispatcher(LatticeNewDispatcher): + def dispatch(self, *args, **kwargs): + """ Defer the `Lattice.read` method by passing down arguments """ + return self._obj.read(*args, **kwargs) +new_dispatch.register(str, LatticeNewFileDispatcher) +new_dispatch.register(Path, LatticeNewFileDispatcher) +# see sisl/__init__.py for new_dispatch.register(BaseSile, ...) + + +class LatticeToDispatcher(AbstractDispatch): + """ Base dispatcher from class passing from Lattice class """ + @staticmethod + def _ensure_object(obj): + if isinstance(obj, type): + raise TypeError(f"Dispatcher on {obj} must not be called on the class.") + +class LatticeToAseDispatcher(LatticeToDispatcher): + def dispatch(self, **kwargs): + from ase import Cell as ase_Cell + lattice = self._obj + self._ensure_object(lattice) + return ase_Cell(lattice.cell.copy()) + +to_dispatch.register("ase", LatticeToAseDispatcher) + +class LatticeToSileDispatcher(LatticeToDispatcher): + def dispatch(self, *args, **kwargs): + lattice = self._obj + self._ensure_object(lattice) + return lattice.write(*args, **kwargs) +to_dispatch.register("str", LatticeToSileDispatcher) +to_dispatch.register("path", LatticeToSileDispatcher) +# to do geom.to[Path](path) +to_dispatch.register(str, LatticeToSileDispatcher) +to_dispatch.register(Path, LatticeToSileDispatcher) + +class LatticeToCuboidDispatcher(LatticeToDispatcher): + def dispatch(self, *args, orthogonal=False, **kwargs): + lattice = self._obj + self._ensure_object(lattice) + cell = lattice.cell.copy() + offset = lattice.center() + self.origin + if not orthogonal: + return Cuboid(cell, offset) + + def find_min_max(cmin, cmax, new): + for i in range(3): + cmin[i] = min(cmin[i], new[i]) + cmax[i] = max(cmax[i], new[i]) + cmin = cell.min(0) + cmax = cell.max(0) + find_min_max(cmin, cmax, cell[[0, 1], :].sum(0)) + find_min_max(cmin, cmax, cell[[0, 2], :].sum(0)) + find_min_max(cmin, cmax, cell[[1, 2], :].sum(0)) + find_min_max(cmin, cmax, cell.sum(0)) + return Cuboid(cmax - cmin, offset) + +to_dispatch.register("cuboid", LatticeToCuboidDispatcher) +to_dispatch.register(Cuboid, LatticeToCuboidDispatcher) + + +# Remove references +del new_dispatch, to_dispatch + -# same reference class SuperCell(Lattice): """ Deprecated class, please use `Lattice` instead """ def __init__(self, *args, **kwargs): diff --git a/src/sisl/tests/test_lattice.py b/src/sisl/tests/test_lattice.py index 305e08d76e..d1721e4066 100644 --- a/src/sisl/tests/test_lattice.py +++ b/src/sisl/tests/test_lattice.py @@ -449,9 +449,15 @@ def test_plane2(): def test_tocuboid_simple(): lattice = Lattice([1, 1, 1, 90, 90, 90]) - c1 = lattice.toCuboid() + with pytest.warns(sisl.SislDeprecation): + c1 = lattice.toCuboid() assert np.allclose(lattice.cell, c1._v) - c2 = lattice.toCuboid(True) + c1 = lattice.to["cuboid"]() + assert np.allclose(lattice.cell, c1._v) + with pytest.warns(sisl.SislDeprecation): + c2 = lattice.toCuboid(True) + assert np.allclose(c1._v, c2._v) + c2 = lattice.to["cuboid"](True) assert np.allclose(c1._v, c2._v)