httk-atomistic 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- httk/atomistic/__init__.py +98 -0
- httk/atomistic/cell.py +79 -0
- httk/atomistic/cell_api.py +21 -0
- httk/atomistic/cell_backend.py +20 -0
- httk/atomistic/cell_class.py +37 -0
- httk/atomistic/cell_class_view.py +40 -0
- httk/atomistic/cell_like.py +16 -0
- httk/atomistic/cell_params.py +94 -0
- httk/atomistic/cell_params_view.py +75 -0
- httk/atomistic/cell_primitive.py +56 -0
- httk/atomistic/cell_primitive_view.py +35 -0
- httk/atomistic/cell_view.py +21 -0
- httk/atomistic/elements.py +156 -0
- httk/atomistic/py.typed +0 -0
- httk/atomistic/sites.py +49 -0
- httk/atomistic/sites_api.py +21 -0
- httk/atomistic/sites_backend.py +20 -0
- httk/atomistic/sites_class.py +37 -0
- httk/atomistic/sites_class_view.py +40 -0
- httk/atomistic/sites_like.py +9 -0
- httk/atomistic/sites_primitive.py +56 -0
- httk/atomistic/sites_primitive_view.py +35 -0
- httk/atomistic/sites_view.py +21 -0
- httk/atomistic/species.py +87 -0
- httk/atomistic/species_api.py +51 -0
- httk/atomistic/species_backend.py +20 -0
- httk/atomistic/species_class.py +60 -0
- httk/atomistic/species_class_view.py +51 -0
- httk/atomistic/species_like.py +9 -0
- httk/atomistic/species_primitive.py +85 -0
- httk/atomistic/species_primitive_view.py +55 -0
- httk/atomistic/species_view.py +21 -0
- httk/atomistic/structure.py +102 -0
- httk/atomistic/structure_api.py +40 -0
- httk/atomistic/structure_backend.py +20 -0
- httk/atomistic/structure_like.py +15 -0
- httk/atomistic/structure_primitive.py +115 -0
- httk/atomistic/structure_primitive_view.py +50 -0
- httk/atomistic/structure_simple.py +52 -0
- httk/atomistic/structure_simple_view.py +40 -0
- httk/atomistic/structure_view.py +21 -0
- httk_atomistic-0.1.0.dist-info/METADATA +46 -0
- httk_atomistic-0.1.0.dist-info/RECORD +46 -0
- httk_atomistic-0.1.0.dist-info/WHEEL +5 -0
- httk_atomistic-0.1.0.dist-info/licenses/LICENSE +661 -0
- httk_atomistic-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
httk-atomistic: crystal structure representations for httk v2.
|
|
3
|
+
|
|
4
|
+
Provides the Structure domain and its component families (Cell, Sites, Species),
|
|
5
|
+
each following the httk-core view/backend pattern. A Structure holds a ``cell``, a
|
|
6
|
+
``sites``, a tuple of ``species``, and a ``species_at_sites``; each component has a
|
|
7
|
+
class representation and a primitive representation convertible through views. ASU and
|
|
8
|
+
exact-vector numerics are planned follow-ups.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .cell import Cell
|
|
12
|
+
from .cell_api import CellAPI
|
|
13
|
+
from .cell_backend import CellBackend
|
|
14
|
+
from .cell_class import CellClass
|
|
15
|
+
from .cell_class_view import CellClassView
|
|
16
|
+
from .cell_like import CellLike
|
|
17
|
+
from .cell_params import CellParams
|
|
18
|
+
from .cell_params_view import CellParamsView
|
|
19
|
+
from .cell_primitive import CellPrimitive
|
|
20
|
+
from .cell_primitive_view import CellPrimitiveView
|
|
21
|
+
from .cell_view import CellView
|
|
22
|
+
from .elements import SYMBOLS, atomic_number, symbol_of
|
|
23
|
+
from .sites import Sites
|
|
24
|
+
from .sites_api import SitesAPI
|
|
25
|
+
from .sites_backend import SitesBackend
|
|
26
|
+
from .sites_class import SitesClass
|
|
27
|
+
from .sites_class_view import SitesClassView
|
|
28
|
+
from .sites_like import SitesLike
|
|
29
|
+
from .sites_primitive import SitesPrimitive
|
|
30
|
+
from .sites_primitive_view import SitesPrimitiveView
|
|
31
|
+
from .sites_view import SitesView
|
|
32
|
+
from .species import Species
|
|
33
|
+
from .species_api import SpeciesAPI
|
|
34
|
+
from .species_backend import SpeciesBackend
|
|
35
|
+
from .species_class import SpeciesClass
|
|
36
|
+
from .species_class_view import SpeciesClassView
|
|
37
|
+
from .species_like import SpeciesLike
|
|
38
|
+
from .species_primitive import SpeciesPrimitive
|
|
39
|
+
from .species_primitive_view import SpeciesPrimitiveView
|
|
40
|
+
from .species_view import SpeciesView
|
|
41
|
+
from .structure import Structure
|
|
42
|
+
from .structure_api import StructureAPI
|
|
43
|
+
from .structure_backend import StructureBackend
|
|
44
|
+
from .structure_like import StructureLike
|
|
45
|
+
from .structure_primitive import StructurePrimitive
|
|
46
|
+
from .structure_primitive_view import StructurePrimitiveView
|
|
47
|
+
from .structure_simple import StructureSimple
|
|
48
|
+
from .structure_simple_view import StructureSimpleView
|
|
49
|
+
from .structure_view import StructureView
|
|
50
|
+
|
|
51
|
+
StructureBackend.backend_classes = [StructureSimple, StructurePrimitive]
|
|
52
|
+
CellBackend.backend_classes = [CellClass, CellPrimitive, CellParams]
|
|
53
|
+
SitesBackend.backend_classes = [SitesClass, SitesPrimitive]
|
|
54
|
+
SpeciesBackend.backend_classes = [SpeciesClass, SpeciesPrimitive]
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
"Structure",
|
|
58
|
+
"StructureLike",
|
|
59
|
+
"StructureAPI",
|
|
60
|
+
"StructureBackend",
|
|
61
|
+
"StructureView",
|
|
62
|
+
"StructureSimple",
|
|
63
|
+
"StructurePrimitive",
|
|
64
|
+
"StructureSimpleView",
|
|
65
|
+
"StructurePrimitiveView",
|
|
66
|
+
"Cell",
|
|
67
|
+
"CellLike",
|
|
68
|
+
"CellAPI",
|
|
69
|
+
"CellBackend",
|
|
70
|
+
"CellView",
|
|
71
|
+
"CellClass",
|
|
72
|
+
"CellPrimitive",
|
|
73
|
+
"CellParams",
|
|
74
|
+
"CellClassView",
|
|
75
|
+
"CellPrimitiveView",
|
|
76
|
+
"CellParamsView",
|
|
77
|
+
"Sites",
|
|
78
|
+
"SitesLike",
|
|
79
|
+
"SitesAPI",
|
|
80
|
+
"SitesBackend",
|
|
81
|
+
"SitesView",
|
|
82
|
+
"SitesClass",
|
|
83
|
+
"SitesPrimitive",
|
|
84
|
+
"SitesClassView",
|
|
85
|
+
"SitesPrimitiveView",
|
|
86
|
+
"Species",
|
|
87
|
+
"SpeciesLike",
|
|
88
|
+
"SpeciesAPI",
|
|
89
|
+
"SpeciesBackend",
|
|
90
|
+
"SpeciesView",
|
|
91
|
+
"SpeciesClass",
|
|
92
|
+
"SpeciesPrimitive",
|
|
93
|
+
"SpeciesClassView",
|
|
94
|
+
"SpeciesPrimitiveView",
|
|
95
|
+
"SYMBOLS",
|
|
96
|
+
"atomic_number",
|
|
97
|
+
"symbol_of",
|
|
98
|
+
]
|
httk/atomistic/cell.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The Cell class for httk-atomistic.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Cell:
|
|
10
|
+
"""
|
|
11
|
+
A crystallographic cell: the 3x3 matrix of cell (basis) vectors.
|
|
12
|
+
|
|
13
|
+
A Cell holds three cell vectors as the rows of a 3x3 ``matrix`` and exposes the
|
|
14
|
+
basic derived quantities ``lengths`` (the row norms), ``angles`` (the crystallographic
|
|
15
|
+
``alpha``/``beta``/``gamma`` in degrees), and ``volume`` (the absolute determinant).
|
|
16
|
+
|
|
17
|
+
Note: the numeric values are stored as interim nested tuples of floats and the
|
|
18
|
+
derived quantities use plain float arithmetic. They are intended to be replaced by
|
|
19
|
+
the httk exact vector representation fairly soon; keep numeric access behind the
|
|
20
|
+
``matrix`` accessor so that change stays contained.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
_matrix: tuple[tuple[float, ...], ...]
|
|
24
|
+
|
|
25
|
+
def __init__(self, matrix: Sequence[Sequence[float]]) -> None:
|
|
26
|
+
norm = tuple(tuple(float(x) for x in row) for row in matrix)
|
|
27
|
+
if len(norm) != 3 or any(len(row) != 3 for row in norm):
|
|
28
|
+
raise ValueError("Cell matrix must be a 3x3 sequence")
|
|
29
|
+
self._matrix = norm
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def matrix(self) -> tuple[tuple[float, ...], ...]:
|
|
33
|
+
"""The 3x3 cell vectors as nested float tuples (one vector per row)."""
|
|
34
|
+
return self._matrix
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def lengths(self) -> tuple[float, float, float]:
|
|
38
|
+
"""The lengths of the three cell vectors (the row norms)."""
|
|
39
|
+
rows = self._matrix
|
|
40
|
+
return (
|
|
41
|
+
math.sqrt(sum(x * x for x in rows[0])),
|
|
42
|
+
math.sqrt(sum(x * x for x in rows[1])),
|
|
43
|
+
math.sqrt(sum(x * x for x in rows[2])),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def angles(self) -> tuple[float, float, float]:
|
|
48
|
+
"""
|
|
49
|
+
The cell angles ``(alpha, beta, gamma)`` in degrees.
|
|
50
|
+
|
|
51
|
+
Following the crystallographic convention, ``alpha`` is the angle between rows
|
|
52
|
+
``b`` and ``c``, ``beta`` between ``a`` and ``c``, and ``gamma`` between ``a``
|
|
53
|
+
and ``b``.
|
|
54
|
+
"""
|
|
55
|
+
a, b, c = self._matrix
|
|
56
|
+
return (self._angle(b, c), self._angle(a, c), self._angle(a, b))
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def volume(self) -> float:
|
|
60
|
+
"""The cell volume, the absolute value of the determinant of ``matrix``."""
|
|
61
|
+
(a0, a1, a2), (b0, b1, b2), (c0, c1, c2) = self._matrix
|
|
62
|
+
det = a0 * (b1 * c2 - b2 * c1) - a1 * (b0 * c2 - b2 * c0) + a2 * (b0 * c1 - b1 * c0)
|
|
63
|
+
return abs(det)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def _angle(u: Sequence[float], v: Sequence[float]) -> float:
|
|
67
|
+
dot = sum(ui * vi for ui, vi in zip(u, v))
|
|
68
|
+
nu = math.sqrt(sum(ui * ui for ui in u))
|
|
69
|
+
nv = math.sqrt(sum(vi * vi for vi in v))
|
|
70
|
+
cosine = max(-1.0, min(1.0, dot / (nu * nv)))
|
|
71
|
+
return math.degrees(math.acos(cosine))
|
|
72
|
+
|
|
73
|
+
def __eq__(self, other: object) -> bool:
|
|
74
|
+
if not isinstance(other, Cell):
|
|
75
|
+
return NotImplemented
|
|
76
|
+
return self._matrix == other._matrix
|
|
77
|
+
|
|
78
|
+
def __repr__(self) -> str:
|
|
79
|
+
return f"Cell(matrix={self._matrix!r})"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The minimal canonical cell interface for httk-atomistic.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CellAPI(ABC):
|
|
9
|
+
"""
|
|
10
|
+
Abstract base class for the canonical cell interface.
|
|
11
|
+
|
|
12
|
+
It declares the single ``matrix`` accessor (the 3x3 cell vectors) that every cell
|
|
13
|
+
backend produces from its own native representation and every cell view builds its
|
|
14
|
+
presentation from. This is the single interchange format; there is no pairwise
|
|
15
|
+
conversion between backends.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def matrix(self) -> tuple[tuple[float, ...], ...]:
|
|
21
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The abstract base class for all cell backends in httk-atomistic.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, ClassVar
|
|
6
|
+
|
|
7
|
+
from httk.core import Backend
|
|
8
|
+
|
|
9
|
+
from .cell_api import CellAPI
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CellBackend(Backend["CellBackend"], CellAPI):
|
|
13
|
+
"""
|
|
14
|
+
Abstract base class for all backends of cell data.
|
|
15
|
+
|
|
16
|
+
Concrete backends carry a native representation and produce the canonical 3x3
|
|
17
|
+
``matrix`` declared by ``CellAPI`` from it.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
backend_classes: ClassVar[list[type[Backend[Any]]]]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backend wrapping a Cell in the class representation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .cell import Cell
|
|
8
|
+
from .cell_backend import CellBackend
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CellClass(CellBackend):
|
|
12
|
+
"""
|
|
13
|
+
Backend for a cell backed by an actual ``Cell`` object.
|
|
14
|
+
|
|
15
|
+
Its ``matrix`` accessor delegates to the wrapped Cell, and ``unwrap`` returns that
|
|
16
|
+
Cell.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
_cell: Cell
|
|
20
|
+
|
|
21
|
+
# Cannot type annotate __new__ as `Self | None` for some reason
|
|
22
|
+
def __new__(cls, obj: Any, **hints: Any) -> Any:
|
|
23
|
+
if not isinstance(obj, Cell):
|
|
24
|
+
return None
|
|
25
|
+
if hints and hints.get("kind", "class") != "class":
|
|
26
|
+
return None
|
|
27
|
+
return super().__new__(cls)
|
|
28
|
+
|
|
29
|
+
def __init__(self, obj: Cell, **hints: Any) -> None:
|
|
30
|
+
self._cell = obj
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def matrix(self) -> tuple[tuple[float, ...], ...]:
|
|
34
|
+
return self._cell.matrix
|
|
35
|
+
|
|
36
|
+
def unwrap(self) -> Any:
|
|
37
|
+
return self._cell
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A view presenting any cell backend as a Cell (the class representation).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Self
|
|
6
|
+
|
|
7
|
+
from httk.core import unwrap
|
|
8
|
+
|
|
9
|
+
from .cell import Cell
|
|
10
|
+
from .cell_backend import CellBackend
|
|
11
|
+
from .cell_like import CellLike
|
|
12
|
+
from .cell_view import CellView
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CellClassView(CellView, Cell):
|
|
16
|
+
"""
|
|
17
|
+
A view presenting an underlying cell backend as a ``Cell``.
|
|
18
|
+
|
|
19
|
+
This view is a genuine ``Cell``, so it can be passed anywhere a Cell is accepted.
|
|
20
|
+
Its matrix is built eagerly from the backend on construction.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
_backend: CellBackend
|
|
24
|
+
|
|
25
|
+
def __new__(cls, obj: CellLike, **hints: Any) -> Self:
|
|
26
|
+
if isinstance(obj, cls):
|
|
27
|
+
return obj
|
|
28
|
+
backend = cls._prepare_backend(obj, hints)
|
|
29
|
+
instance = super().__new__(cls)
|
|
30
|
+
# Cell is mutable, so its state is initialized here in __new__ (keeping __init__ a no-op),
|
|
31
|
+
# so that rewrapping an existing view via cls(view) does not re-initialize it.
|
|
32
|
+
Cell.__init__(instance, backend.matrix)
|
|
33
|
+
instance._backend = backend
|
|
34
|
+
return instance
|
|
35
|
+
|
|
36
|
+
def __init__(self, obj: CellLike, **hints: Any) -> None:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def unwrap(self) -> Any:
|
|
40
|
+
return unwrap(self._backend)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The accepted-input union for cell functions in httk-atomistic.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from . import cell, cell_backend, cell_view
|
|
8
|
+
|
|
9
|
+
type CellLike = (
|
|
10
|
+
cell_backend.CellBackend
|
|
11
|
+
| cell_view.CellView
|
|
12
|
+
| cell.Cell
|
|
13
|
+
| tuple[Any, Any, Any]
|
|
14
|
+
| tuple[Any, Any, Any, Any, Any, Any]
|
|
15
|
+
| list[Any]
|
|
16
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backend wrapping cell parameters (a, b, c, alpha, beta, gamma).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .cell_backend import CellBackend
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _is_number(value: Any) -> bool:
|
|
12
|
+
return isinstance(value, (int, float)) and not isinstance(value, bool)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _is_params(obj: Any) -> bool:
|
|
16
|
+
return isinstance(obj, (list, tuple)) and len(obj) == 6 and all(_is_number(x) for x in obj)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _params_to_matrix(params: tuple[float, ...]) -> tuple[tuple[float, ...], ...]:
|
|
20
|
+
a, b, c, alpha, beta, gamma = params
|
|
21
|
+
cos_alpha = math.cos(math.radians(alpha))
|
|
22
|
+
cos_beta = math.cos(math.radians(beta))
|
|
23
|
+
cos_gamma = math.cos(math.radians(gamma))
|
|
24
|
+
sin_gamma = math.sin(math.radians(gamma))
|
|
25
|
+
cy = (cos_alpha - cos_beta * cos_gamma) / sin_gamma
|
|
26
|
+
cz_sq = 1.0 - cos_beta * cos_beta - cy * cy
|
|
27
|
+
return (
|
|
28
|
+
(a, 0.0, 0.0),
|
|
29
|
+
(b * cos_gamma, b * sin_gamma, 0.0),
|
|
30
|
+
(c * cos_beta, c * cy, c * math.sqrt(cz_sq)),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CellParams(CellBackend):
|
|
35
|
+
"""
|
|
36
|
+
Backend for a cell backed by cell parameters ``(a, b, c, alpha, beta, gamma)``.
|
|
37
|
+
|
|
38
|
+
The native representation is a flat length-6 list or tuple of the cell-vector
|
|
39
|
+
lengths ``a``/``b``/``c`` and the angles ``alpha``/``beta``/``gamma`` in degrees.
|
|
40
|
+
The ``matrix`` is derived lazily and cached using the standard crystallographic
|
|
41
|
+
orientation convention (the first cell vector along x, the second in the xy-plane);
|
|
42
|
+
since parameters carry no orientation, converting a cell to parameters and back
|
|
43
|
+
reproduces its lengths, angles, and volume, but not its original orientation.
|
|
44
|
+
``unwrap`` returns the original raw object.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
_raw: Any
|
|
48
|
+
_params: tuple[float, ...]
|
|
49
|
+
_matrix_cache: tuple[tuple[float, ...], ...] | None
|
|
50
|
+
|
|
51
|
+
# Cannot type annotate __new__ as `Self | None` for some reason
|
|
52
|
+
def __new__(cls, obj: Any, **hints: Any) -> Any:
|
|
53
|
+
if hints and hints.get("kind", "params") != "params":
|
|
54
|
+
return None
|
|
55
|
+
if not _is_params(obj):
|
|
56
|
+
return None
|
|
57
|
+
return super().__new__(cls)
|
|
58
|
+
|
|
59
|
+
def __init__(self, obj: Any, **hints: Any) -> None:
|
|
60
|
+
params = tuple(float(x) for x in obj)
|
|
61
|
+
a, b, c, alpha, beta, gamma = params
|
|
62
|
+
if a <= 0.0 or b <= 0.0 or c <= 0.0:
|
|
63
|
+
raise ValueError("Cell parameter lengths a, b, c must be positive")
|
|
64
|
+
if not all(0.0 < angle < 180.0 for angle in (alpha, beta, gamma)):
|
|
65
|
+
raise ValueError("Cell parameter angles alpha, beta, gamma must be strictly between 0 and 180 degrees")
|
|
66
|
+
cos_alpha = math.cos(math.radians(alpha))
|
|
67
|
+
cos_beta = math.cos(math.radians(beta))
|
|
68
|
+
cos_gamma = math.cos(math.radians(gamma))
|
|
69
|
+
volume_factor = (
|
|
70
|
+
1.0
|
|
71
|
+
- cos_alpha * cos_alpha
|
|
72
|
+
- cos_beta * cos_beta
|
|
73
|
+
- cos_gamma * cos_gamma
|
|
74
|
+
+ 2.0 * cos_alpha * cos_beta * cos_gamma
|
|
75
|
+
)
|
|
76
|
+
if volume_factor <= 0.0:
|
|
77
|
+
raise ValueError("Cell parameter angles do not describe a valid (non-degenerate) cell")
|
|
78
|
+
self._raw = obj
|
|
79
|
+
self._params = params
|
|
80
|
+
self._matrix_cache = None
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def matrix(self) -> tuple[tuple[float, ...], ...]:
|
|
84
|
+
if self._matrix_cache is None:
|
|
85
|
+
self._matrix_cache = _params_to_matrix(self._params)
|
|
86
|
+
return self._matrix_cache
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def params(self) -> tuple[float, ...]:
|
|
90
|
+
"""The stored ``(a, b, c, alpha, beta, gamma)`` as a tuple of floats (angles in degrees)."""
|
|
91
|
+
return self._params
|
|
92
|
+
|
|
93
|
+
def unwrap(self) -> Any:
|
|
94
|
+
return self._raw
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A view presenting any cell backend as cell parameters (a, b, c, alpha, beta, gamma).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Self
|
|
6
|
+
|
|
7
|
+
from httk.core import unwrap
|
|
8
|
+
|
|
9
|
+
from .cell import Cell
|
|
10
|
+
from .cell_backend import CellBackend
|
|
11
|
+
from .cell_like import CellLike
|
|
12
|
+
from .cell_view import CellView
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CellParamsView(CellView, tuple):
|
|
16
|
+
"""
|
|
17
|
+
A view presenting an underlying cell backend as cell parameters.
|
|
18
|
+
|
|
19
|
+
This view is a genuine flat 6-tuple ``(a, b, c, alpha, beta, gamma)`` with the
|
|
20
|
+
angles in degrees, built eagerly and immutable, with the elements also available
|
|
21
|
+
as the named properties ``a``/``b``/``c``/``alpha``/``beta``/``gamma``.
|
|
22
|
+
Parameters carry no orientation, so converting a cell to parameters is lossy:
|
|
23
|
+
reconstructing a cell from this view reproduces the lengths, angles, and volume,
|
|
24
|
+
but not the original cell-vector orientation.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
_backend: CellBackend
|
|
28
|
+
|
|
29
|
+
def __new__(cls, obj: CellLike, **hints: Any) -> Self:
|
|
30
|
+
if isinstance(obj, cls):
|
|
31
|
+
return obj
|
|
32
|
+
backend = cls._prepare_backend(obj, hints)
|
|
33
|
+
params = getattr(backend, "params", None)
|
|
34
|
+
if params is None:
|
|
35
|
+
reference = Cell(backend.matrix)
|
|
36
|
+
params = reference.lengths + reference.angles
|
|
37
|
+
instance = super().__new__(cls, params)
|
|
38
|
+
instance._backend = backend
|
|
39
|
+
return instance
|
|
40
|
+
|
|
41
|
+
def __init__(self, obj: CellLike, **hints: Any) -> None:
|
|
42
|
+
super().__init__()
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def a(self) -> float:
|
|
46
|
+
"""The length of the first cell vector."""
|
|
47
|
+
return self[0]
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def b(self) -> float:
|
|
51
|
+
"""The length of the second cell vector."""
|
|
52
|
+
return self[1]
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def c(self) -> float:
|
|
56
|
+
"""The length of the third cell vector."""
|
|
57
|
+
return self[2]
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def alpha(self) -> float:
|
|
61
|
+
"""The angle between the second and third cell vectors, in degrees."""
|
|
62
|
+
return self[3]
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def beta(self) -> float:
|
|
66
|
+
"""The angle between the first and third cell vectors, in degrees."""
|
|
67
|
+
return self[4]
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def gamma(self) -> float:
|
|
71
|
+
"""The angle between the first and second cell vectors, in degrees."""
|
|
72
|
+
return self[5]
|
|
73
|
+
|
|
74
|
+
def unwrap(self) -> Any:
|
|
75
|
+
return unwrap(self._backend)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backend wrapping a raw 3x3 cell-vector matrix.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .cell_backend import CellBackend
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _is_number(value: Any) -> bool:
|
|
11
|
+
return isinstance(value, (int, float)) and not isinstance(value, bool)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _is_3x3(matrix: Any) -> bool:
|
|
15
|
+
if not isinstance(matrix, (list, tuple)) or len(matrix) != 3:
|
|
16
|
+
return False
|
|
17
|
+
for row in matrix:
|
|
18
|
+
if not isinstance(row, (list, tuple)) or len(row) != 3:
|
|
19
|
+
return False
|
|
20
|
+
if not all(_is_number(x) for x in row):
|
|
21
|
+
return False
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CellPrimitive(CellBackend):
|
|
26
|
+
"""
|
|
27
|
+
Backend for a cell backed by a raw 3x3 list or tuple of numbers.
|
|
28
|
+
|
|
29
|
+
The native representation is a 3x3 nested list or tuple of cell vectors (one vector
|
|
30
|
+
per row). The ``matrix`` is derived lazily and cached, and ``unwrap`` returns the
|
|
31
|
+
original raw object.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
_raw: Any
|
|
35
|
+
_matrix_cache: tuple[tuple[float, ...], ...] | None
|
|
36
|
+
|
|
37
|
+
# Cannot type annotate __new__ as `Self | None` for some reason
|
|
38
|
+
def __new__(cls, obj: Any, **hints: Any) -> Any:
|
|
39
|
+
if hints and hints.get("kind", "primitive") != "primitive":
|
|
40
|
+
return None
|
|
41
|
+
if not _is_3x3(obj):
|
|
42
|
+
return None
|
|
43
|
+
return super().__new__(cls)
|
|
44
|
+
|
|
45
|
+
def __init__(self, obj: Any, **hints: Any) -> None:
|
|
46
|
+
self._raw = obj
|
|
47
|
+
self._matrix_cache = None
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def matrix(self) -> tuple[tuple[float, ...], ...]:
|
|
51
|
+
if self._matrix_cache is None:
|
|
52
|
+
self._matrix_cache = tuple(tuple(float(x) for x in row) for row in self._raw)
|
|
53
|
+
return self._matrix_cache
|
|
54
|
+
|
|
55
|
+
def unwrap(self) -> Any:
|
|
56
|
+
return self._raw
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A view presenting any cell backend as a raw 3-row tuple of cell vectors.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Self
|
|
6
|
+
|
|
7
|
+
from httk.core import unwrap
|
|
8
|
+
|
|
9
|
+
from .cell_backend import CellBackend
|
|
10
|
+
from .cell_like import CellLike
|
|
11
|
+
from .cell_view import CellView
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CellPrimitiveView(CellView, tuple):
|
|
15
|
+
"""
|
|
16
|
+
A view presenting an underlying cell backend as a raw 3x3 matrix.
|
|
17
|
+
|
|
18
|
+
This view is a genuine tuple of three cell-vector rows, built eagerly and immutable.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
_backend: CellBackend
|
|
22
|
+
|
|
23
|
+
def __new__(cls, obj: CellLike, **hints: Any) -> Self:
|
|
24
|
+
if isinstance(obj, cls):
|
|
25
|
+
return obj
|
|
26
|
+
backend = cls._prepare_backend(obj, hints)
|
|
27
|
+
instance = super().__new__(cls, backend.matrix)
|
|
28
|
+
instance._backend = backend
|
|
29
|
+
return instance
|
|
30
|
+
|
|
31
|
+
def __init__(self, obj: CellLike, **hints: Any) -> None:
|
|
32
|
+
super().__init__()
|
|
33
|
+
|
|
34
|
+
def unwrap(self) -> Any:
|
|
35
|
+
return unwrap(self._backend)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The abstract base class for all cell views in httk-atomistic.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import ClassVar, Self
|
|
6
|
+
|
|
7
|
+
from httk.core import View
|
|
8
|
+
|
|
9
|
+
from .cell_backend import CellBackend
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CellView(View[CellBackend]):
|
|
13
|
+
"""
|
|
14
|
+
Abstract base class for all views of cell data.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
_backend_base_cls: ClassVar[type[CellBackend]] = CellBackend # type: ignore[type-abstract]
|
|
18
|
+
_view_base_cls: ClassVar[type[Self]]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
CellView._view_base_cls = CellView
|