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.
Files changed (46) hide show
  1. httk/atomistic/__init__.py +98 -0
  2. httk/atomistic/cell.py +79 -0
  3. httk/atomistic/cell_api.py +21 -0
  4. httk/atomistic/cell_backend.py +20 -0
  5. httk/atomistic/cell_class.py +37 -0
  6. httk/atomistic/cell_class_view.py +40 -0
  7. httk/atomistic/cell_like.py +16 -0
  8. httk/atomistic/cell_params.py +94 -0
  9. httk/atomistic/cell_params_view.py +75 -0
  10. httk/atomistic/cell_primitive.py +56 -0
  11. httk/atomistic/cell_primitive_view.py +35 -0
  12. httk/atomistic/cell_view.py +21 -0
  13. httk/atomistic/elements.py +156 -0
  14. httk/atomistic/py.typed +0 -0
  15. httk/atomistic/sites.py +49 -0
  16. httk/atomistic/sites_api.py +21 -0
  17. httk/atomistic/sites_backend.py +20 -0
  18. httk/atomistic/sites_class.py +37 -0
  19. httk/atomistic/sites_class_view.py +40 -0
  20. httk/atomistic/sites_like.py +9 -0
  21. httk/atomistic/sites_primitive.py +56 -0
  22. httk/atomistic/sites_primitive_view.py +35 -0
  23. httk/atomistic/sites_view.py +21 -0
  24. httk/atomistic/species.py +87 -0
  25. httk/atomistic/species_api.py +51 -0
  26. httk/atomistic/species_backend.py +20 -0
  27. httk/atomistic/species_class.py +60 -0
  28. httk/atomistic/species_class_view.py +51 -0
  29. httk/atomistic/species_like.py +9 -0
  30. httk/atomistic/species_primitive.py +85 -0
  31. httk/atomistic/species_primitive_view.py +55 -0
  32. httk/atomistic/species_view.py +21 -0
  33. httk/atomistic/structure.py +102 -0
  34. httk/atomistic/structure_api.py +40 -0
  35. httk/atomistic/structure_backend.py +20 -0
  36. httk/atomistic/structure_like.py +15 -0
  37. httk/atomistic/structure_primitive.py +115 -0
  38. httk/atomistic/structure_primitive_view.py +50 -0
  39. httk/atomistic/structure_simple.py +52 -0
  40. httk/atomistic/structure_simple_view.py +40 -0
  41. httk/atomistic/structure_view.py +21 -0
  42. httk_atomistic-0.1.0.dist-info/METADATA +46 -0
  43. httk_atomistic-0.1.0.dist-info/RECORD +46 -0
  44. httk_atomistic-0.1.0.dist-info/WHEEL +5 -0
  45. httk_atomistic-0.1.0.dist-info/licenses/LICENSE +661 -0
  46. 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