GraphAtoms 0.0.2__tar.gz → 0.0.3__tar.gz

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 (40) hide show
  1. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/common/abc.py +109 -2
  2. graphatoms-0.0.3/GraphAtoms/common/string.py +138 -0
  3. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms.egg-info/PKG-INFO +20 -11
  4. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms.egg-info/SOURCES.txt +1 -0
  5. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms.egg-info/requires.txt +22 -9
  6. {graphatoms-0.0.2 → graphatoms-0.0.3}/PKG-INFO +20 -11
  7. {graphatoms-0.0.2 → graphatoms-0.0.3}/pyproject.toml +29 -11
  8. {graphatoms-0.0.2 → graphatoms-0.0.3}/tests/test_abc.py +35 -2
  9. graphatoms-0.0.3/tests/test_rotation.py +44 -0
  10. graphatoms-0.0.2/GraphAtoms/common/string.py +0 -33
  11. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/__init__.py +0 -0
  12. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/common/__init__.py +0 -0
  13. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/common/error.py +0 -0
  14. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/common/geometry.py +0 -0
  15. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/common/ndarray.py +0 -0
  16. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/common/ndarray.pyi +0 -0
  17. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/common/rotation.py +0 -0
  18. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/__init__.py +0 -0
  19. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_atomic.py +0 -0
  20. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_bonded.py +0 -0
  21. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_graph.py +0 -0
  22. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_mConnComp.py +0 -0
  23. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_mDataPyG.py +0 -0
  24. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_mFreeE.py +0 -0
  25. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_mIGraph.py +0 -0
  26. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_mNetworKit.py +0 -0
  27. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_mNetworkX.py +0 -0
  28. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_mRDKit.py +0 -0
  29. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_mRustworkX.py +0 -0
  30. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_sysCluster.py +0 -0
  31. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_sysGas.py +0 -0
  32. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms/containner/_system.py +0 -0
  33. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms.egg-info/dependency_links.txt +0 -0
  34. {graphatoms-0.0.2 → graphatoms-0.0.3}/GraphAtoms.egg-info/top_level.txt +0 -0
  35. {graphatoms-0.0.2 → graphatoms-0.0.3}/LICENSE +0 -0
  36. {graphatoms-0.0.2 → graphatoms-0.0.3}/README.md +0 -0
  37. {graphatoms-0.0.2 → graphatoms-0.0.3}/setup.cfg +0 -0
  38. {graphatoms-0.0.2 → graphatoms-0.0.3}/tests/test_container.py +0 -0
  39. {graphatoms-0.0.2 → graphatoms-0.0.3}/tests/test_mRDKit.py +0 -0
  40. {graphatoms-0.0.2 → graphatoms-0.0.3}/tests/test_utils.py +0 -0
@@ -1,13 +1,27 @@
1
1
  # ruff: noqa: D100 D102
2
+ import sys
2
3
  from collections.abc import Sequence
3
- from typing import override
4
+ from pickle import dumps, loads
5
+ from typing import Annotated, override
4
6
 
5
7
  import numpy as np
6
8
  import pydantic
9
+ import tomli_w as toml_w
10
+ import yaml
7
11
  from joblib import dump, load
8
12
  from numpy.typing import ArrayLike
9
13
  from typing_extensions import Self
10
14
 
15
+ from GraphAtoms.common.string import (
16
+ SUPPORTED_COMPRESS_FORMATS,
17
+ compress,
18
+ decompress,
19
+ )
20
+
21
+ if sys.version_info < (3, 11):
22
+ import tomli as tomllib
23
+ else:
24
+ import tomllib
11
25
  __all__ = [
12
26
  "BaseModel",
13
27
  "NpzPklBaseModel",
@@ -15,6 +29,8 @@ __all__ = [
15
29
 
16
30
 
17
31
  class BaseMixin:
32
+ """The base mixin class which provides some useful classmethods."""
33
+
18
34
  @staticmethod
19
35
  def get_mask_or_index(k: ArrayLike, n: int) -> np.ndarray:
20
36
  if np.isscalar(k):
@@ -89,6 +105,35 @@ class BaseModel(pydantic.BaseModel):
89
105
  f = getattr(self, f"write_{self.__get_format(fname)}")
90
106
  return f(fname, **kwargs)
91
107
 
108
+ @pydantic.validate_call
109
+ def as_bytes(
110
+ self,
111
+ compressformat: SUPPORTED_COMPRESS_FORMATS = "snappy",
112
+ compresslevel: Annotated[int, pydantic.Field(ge=0, le=9)] = 0,
113
+ ) -> bytes:
114
+ """Return the json bytes of this object."""
115
+ return compress(
116
+ dumps(self.model_dump(exclude_none=True)),
117
+ format=compressformat, # type: ignore
118
+ compresslevel=compresslevel,
119
+ )
120
+
121
+ @classmethod
122
+ @pydantic.validate_call
123
+ def from_bytes(
124
+ cls,
125
+ data: bytes,
126
+ compressformat: SUPPORTED_COMPRESS_FORMATS = "snappy",
127
+ ) -> Self:
128
+ return cls.model_validate(
129
+ loads(
130
+ decompress(
131
+ data,
132
+ format=compressformat, # type: ignore
133
+ )
134
+ )
135
+ )
136
+
92
137
  @pydantic.validate_call
93
138
  def write_json(
94
139
  self,
@@ -97,7 +142,11 @@ class BaseModel(pydantic.BaseModel):
97
142
  **kwargs,
98
143
  ) -> pydantic.FilePath:
99
144
  filename.write_text(
100
- self.model_dump_json(indent=indent, **kwargs),
145
+ self.model_dump_json(
146
+ indent=indent,
147
+ exclude_none=True,
148
+ **kwargs,
149
+ ),
101
150
  encoding="utf-8",
102
151
  )
103
152
  return filename
@@ -113,6 +162,64 @@ class BaseModel(pydantic.BaseModel):
113
162
  return f(fname, **kwargs)
114
163
 
115
164
 
165
+ class ExtendedBaseModel(BaseModel):
166
+ """A extended base class for creating Pydantic models.
167
+
168
+ This class support many file formats:
169
+ json by `pydantic`
170
+ yaml/yml by `pyyaml`
171
+ toml by `tomli_w`, `tomli`, & `toml`
172
+ """
173
+
174
+ @pydantic.validate_call
175
+ def write_toml(
176
+ self,
177
+ filename: pydantic.FilePath | pydantic.NewPath,
178
+ indent: int = 4,
179
+ **kwargs,
180
+ ) -> pydantic.FilePath:
181
+ kwargs["exclude_none"] = True
182
+ with filename.open("wb") as f:
183
+ toml_w.dump(
184
+ self.model_dump(mode="python", **kwargs),
185
+ f,
186
+ indent=indent,
187
+ multiline_strings=True,
188
+ )
189
+ return filename
190
+
191
+ @pydantic.validate_call
192
+ def write_yaml(
193
+ self,
194
+ filename: pydantic.FilePath | pydantic.NewPath,
195
+ indent: int | None = 2,
196
+ **kwargs,
197
+ ) -> pydantic.FilePath:
198
+ with filename.open("w", encoding="utf-8") as f:
199
+ yaml.safe_dump(
200
+ self.model_dump(mode="python", **kwargs),
201
+ f,
202
+ encoding="utf-8",
203
+ default_flow_style=False,
204
+ indent=indent,
205
+ )
206
+ return filename
207
+
208
+ @classmethod
209
+ @pydantic.validate_call
210
+ def read_yaml(cls, filename: pydantic.FilePath, **kwargs) -> Self:
211
+ with filename.open(encoding="utf-8") as f:
212
+ data = yaml.safe_load(f)
213
+ return cls.model_validate(data, **kwargs)
214
+
215
+ @classmethod
216
+ @pydantic.validate_call
217
+ def read_toml(cls, filename: pydantic.FilePath, **kwargs) -> Self:
218
+ with filename.open("rb") as f:
219
+ data = tomllib.load(f)
220
+ return cls.model_validate(data, **kwargs)
221
+
222
+
116
223
  class NpzPklBaseModel(BaseModel):
117
224
  """A extended base class for creating Pydantic models.
118
225
 
@@ -0,0 +1,138 @@
1
+ """The utils for strings."""
2
+
3
+ import bz2 # Added in Python 3.3
4
+ import gzip # Added in Python 3.2
5
+ import zlib # Added in Python 3.0
6
+ from collections.abc import Callable
7
+ from functools import partial
8
+ from hashlib import blake2b
9
+ from random import sample
10
+ from string import ascii_lowercase
11
+ from typing import Annotated, Literal
12
+
13
+ import pydantic
14
+ import snappy
15
+ from ase.db.core import convert_str_to_int_float_bool_or_str
16
+
17
+ SUPPORTED_COMPRESS_FORMATS = Literal["z", "gz", "bz2", "snappy"]
18
+
19
+
20
+ class CompressorAndDecompressor:
21
+ """The Compressor and Decompressor."""
22
+
23
+ @pydantic.validate_call
24
+ def __init__( # noqa: D107
25
+ self,
26
+ format: SUPPORTED_COMPRESS_FORMATS = "snappy",
27
+ compresslevel: Annotated[int, pydantic.Field(ge=0, le=9)] = 0,
28
+ ) -> None:
29
+ if format == "z":
30
+ self.__func_compress: Callable[..., bytes] = partial(
31
+ zlib.compress,
32
+ level=compresslevel if compresslevel != 0 else -1,
33
+ )
34
+ self.__func_decompress: Callable[..., bytes] = zlib.decompress
35
+ elif format == "gz":
36
+ self.__func_compress: Callable[..., bytes] = partial(
37
+ gzip.compress,
38
+ compresslevel=compresslevel if compresslevel != 0 else 9,
39
+ )
40
+ self.__func_decompress: Callable[..., bytes] = gzip.decompress
41
+ elif format == "bz2":
42
+ self.__func_compress: Callable[..., bytes] = partial(
43
+ bz2.compress,
44
+ compresslevel=compresslevel if compresslevel != 0 else 9,
45
+ )
46
+ self.__func_decompress: Callable[..., bytes] = bz2.decompress
47
+ elif format == "snappy":
48
+ self.__func_compress: Callable[..., bytes] = snappy.compress
49
+ self.__func_decompress: Callable[..., bytes] = partial(
50
+ snappy.uncompress, decoding=None
51
+ ) # type: ignore
52
+ else:
53
+ import lzma
54
+
55
+ self.__func_compress: Callable[..., bytes] = partial(
56
+ lzma.compress,
57
+ format=lzma.FORMAT_XZ if format == "xz" else lzma.FORMAT_ALONE,
58
+ )
59
+ self.__func_decompress: Callable[..., bytes] = lzma.decompress
60
+
61
+ @pydantic.validate_call
62
+ def compress(self, value: bytes) -> bytes:
63
+ """Return the compressed bytes of the given bytes."""
64
+ return self.__func_compress(value)
65
+
66
+ @pydantic.validate_call
67
+ def decompress(self, value: bytes) -> bytes:
68
+ """Return the decompressed bytes of the given bytes."""
69
+ return self.__func_decompress(value)
70
+
71
+
72
+ def compress(
73
+ value: bytes,
74
+ format: SUPPORTED_COMPRESS_FORMATS = "snappy",
75
+ compresslevel: int = 0,
76
+ ) -> bytes:
77
+ """Return the compressed bytes of the given bytes."""
78
+ return CompressorAndDecompressor(
79
+ format=format,
80
+ compresslevel=compresslevel,
81
+ ).compress(value)
82
+
83
+
84
+ def compress_string(
85
+ str_value: str,
86
+ format: SUPPORTED_COMPRESS_FORMATS = "snappy",
87
+ encoding: str = "utf-8",
88
+ compresslevel: int = 0,
89
+ ) -> bytes:
90
+ """Return the compressed bytes of the given string."""
91
+ return compress(
92
+ str_value.encode(encoding),
93
+ format=format,
94
+ compresslevel=compresslevel,
95
+ )
96
+
97
+
98
+ def decompress(
99
+ value: bytes,
100
+ format: SUPPORTED_COMPRESS_FORMATS = "snappy",
101
+ ) -> bytes:
102
+ """Return the decompressed bytes of the given bytes."""
103
+ return CompressorAndDecompressor(format=format).decompress(value)
104
+
105
+
106
+ def decompress_string(
107
+ value: bytes,
108
+ format: SUPPORTED_COMPRESS_FORMATS = "snappy",
109
+ encoding: str = "utf-8",
110
+ ) -> str:
111
+ """Return the decompressed string of the given bytes."""
112
+ return decompress(value, format=format).decode(encoding)
113
+
114
+
115
+ def random_string(length: int = 6) -> str:
116
+ """Return the random string that has the given length."""
117
+ return "".join(sample(ascii_lowercase, int(length)))
118
+
119
+
120
+ def _hash(str_value: str, digest_size: int = 6) -> str:
121
+ obj = blake2b(
122
+ str_value.encode("ascii"),
123
+ digest_size=int(digest_size / 2),
124
+ )
125
+ return obj.hexdigest()
126
+
127
+
128
+ def hash_string(str_value: str, digest_size: int = 6) -> str:
129
+ """Return the hashing string of the given string.
130
+
131
+ Note: ensure the result cannot be convert to bool, int and float.
132
+ """
133
+ result: str = _hash(str_value, digest_size=digest_size)
134
+ value = convert_str_to_int_float_bool_or_str(result)
135
+ while not isinstance(value, str):
136
+ result = _hash(f"{value}_{result}", digest_size)
137
+ value = convert_str_to_int_float_bool_or_str(result)
138
+ return result
@@ -1,28 +1,37 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GraphAtoms
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: The Chemical Core Class for Graph Theory Analysis & Graph Neural Network.
5
5
  Author-email: LiuGaoyong <liugaoyong_88@163.com>
6
6
  Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  Requires-Dist: ase
10
+ Requires-Dist: pymatgen>=2025
11
+ Requires-Dist: rdkit>=2025
12
+ Requires-Dist: dscribe>=2.1
13
+ Requires-Dist: vesin
14
+ Requires-Dist: ase-extension>=0.5; sys_platform != "win32"
15
+ Requires-Dist: numba>=0.61
16
+ Requires-Dist: numpy>=2.0
17
+ Requires-Dist: pandas
18
+ Requires-Dist: scipy
19
+ Requires-Dist: torch
20
+ Requires-Dist: scikit-learn>=1.5
21
+ Requires-Dist: sparse
22
+ Requires-Dist: torch_geometric
10
23
  Requires-Dist: igraph>=0.11
11
24
  Requires-Dist: networkit==11.1; sys_platform != "linux"
12
25
  Requires-Dist: networkit==11.1.post1; sys_platform == "linux"
13
26
  Requires-Dist: networkx>=3.3
14
- Requires-Dist: numpy
15
- Requires-Dist: numba>=0.61
27
+ Requires-Dist: rustworkx>=0.16
16
28
  Requires-Dist: numpydantic
17
- Requires-Dist: pandas
18
29
  Requires-Dist: pydantic>=2.11
19
- Requires-Dist: rdkit>=2024.9
20
- Requires-Dist: rustworkx>=0.16
21
- Requires-Dist: scikit-learn
22
- Requires-Dist: scipy
23
- Requires-Dist: sparse
24
- Requires-Dist: torch_geometric
25
- Requires-Dist: torch
30
+ Requires-Dist: python-snappy>=0.7.3
31
+ Requires-Dist: pyyaml
32
+ Requires-Dist: sqlmodel
33
+ Requires-Dist: tomli-w
34
+ Requires-Dist: tomli; python_version < "3.11"
26
35
  Requires-Dist: typing-extensions
27
36
  Dynamic: license-file
28
37
 
@@ -33,4 +33,5 @@ GraphAtoms/containner/_system.py
33
33
  tests/test_abc.py
34
34
  tests/test_container.py
35
35
  tests/test_mRDKit.py
36
+ tests/test_rotation.py
36
37
  tests/test_utils.py
@@ -1,22 +1,35 @@
1
1
  ase
2
- igraph>=0.11
3
- networkx>=3.3
4
- numpy
2
+ pymatgen>=2025
3
+ rdkit>=2025
4
+ dscribe>=2.1
5
+ vesin
5
6
  numba>=0.61
6
- numpydantic
7
+ numpy>=2.0
7
8
  pandas
8
- pydantic>=2.11
9
- rdkit>=2024.9
10
- rustworkx>=0.16
11
- scikit-learn
12
9
  scipy
10
+ torch
11
+ scikit-learn>=1.5
13
12
  sparse
14
13
  torch_geometric
15
- torch
14
+ igraph>=0.11
15
+ networkx>=3.3
16
+ rustworkx>=0.16
17
+ numpydantic
18
+ pydantic>=2.11
19
+ python-snappy>=0.7.3
20
+ pyyaml
21
+ sqlmodel
22
+ tomli-w
16
23
  typing-extensions
17
24
 
25
+ [:python_version < "3.11"]
26
+ tomli
27
+
18
28
  [:sys_platform != "linux"]
19
29
  networkit==11.1
20
30
 
31
+ [:sys_platform != "win32"]
32
+ ase-extension>=0.5
33
+
21
34
  [:sys_platform == "linux"]
22
35
  networkit==11.1.post1
@@ -1,28 +1,37 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GraphAtoms
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: The Chemical Core Class for Graph Theory Analysis & Graph Neural Network.
5
5
  Author-email: LiuGaoyong <liugaoyong_88@163.com>
6
6
  Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  Requires-Dist: ase
10
+ Requires-Dist: pymatgen>=2025
11
+ Requires-Dist: rdkit>=2025
12
+ Requires-Dist: dscribe>=2.1
13
+ Requires-Dist: vesin
14
+ Requires-Dist: ase-extension>=0.5; sys_platform != "win32"
15
+ Requires-Dist: numba>=0.61
16
+ Requires-Dist: numpy>=2.0
17
+ Requires-Dist: pandas
18
+ Requires-Dist: scipy
19
+ Requires-Dist: torch
20
+ Requires-Dist: scikit-learn>=1.5
21
+ Requires-Dist: sparse
22
+ Requires-Dist: torch_geometric
10
23
  Requires-Dist: igraph>=0.11
11
24
  Requires-Dist: networkit==11.1; sys_platform != "linux"
12
25
  Requires-Dist: networkit==11.1.post1; sys_platform == "linux"
13
26
  Requires-Dist: networkx>=3.3
14
- Requires-Dist: numpy
15
- Requires-Dist: numba>=0.61
27
+ Requires-Dist: rustworkx>=0.16
16
28
  Requires-Dist: numpydantic
17
- Requires-Dist: pandas
18
29
  Requires-Dist: pydantic>=2.11
19
- Requires-Dist: rdkit>=2024.9
20
- Requires-Dist: rustworkx>=0.16
21
- Requires-Dist: scikit-learn
22
- Requires-Dist: scipy
23
- Requires-Dist: sparse
24
- Requires-Dist: torch_geometric
25
- Requires-Dist: torch
30
+ Requires-Dist: python-snappy>=0.7.3
31
+ Requires-Dist: pyyaml
32
+ Requires-Dist: sqlmodel
33
+ Requires-Dist: tomli-w
34
+ Requires-Dist: tomli; python_version < "3.11"
26
35
  Requires-Dist: typing-extensions
27
36
  Dynamic: license-file
28
37
 
@@ -4,29 +4,46 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "GraphAtoms"
7
- version = "0.0.2"
7
+ version = "0.0.3"
8
8
  description = "The Chemical Core Class for Graph Theory Analysis & Graph Neural Network."
9
9
  readme = "README.md"
10
10
  authors = [{ name = "LiuGaoyong", email = "liugaoyong_88@163.com" }]
11
11
  requires-python = ">=3.10"
12
12
  dependencies = [
13
+
14
+ # Chemoinformatics
13
15
  "ase",
16
+ "pymatgen>=2025",
17
+ "rdkit>=2025",
18
+ "dscribe>=2.1", # transforming atomic structures into fixed-size numerical fingerprints
19
+ "vesin", # fast neighbor lists for atomistic systems (C++ & TorchScript)
20
+ "ase-extension>=0.5; sys_platform != 'win32'", # fast neighborlist(Rust)
21
+
22
+ # Scientific Computing
23
+ "numba>=0.61",
24
+ "numpy>=2.0",
25
+ "pandas",
26
+ "scipy",
27
+ "torch",
28
+ "scikit-learn>=1.5",
29
+ "sparse",
30
+
31
+ # Graph Theory
32
+ "torch_geometric", # PyTorch Based Graph Neural Network Library
14
33
  "igraph>=0.11", # high performance graph library based on C
15
34
  "networkit==11.1; sys_platform != 'linux'", # high performance graph library based on C++
16
35
  "networkit==11.1.post1; sys_platform == 'linux'", # high performance graph library based on C++
17
36
  "networkx>=3.3", # General-purpose graph library by pure Python
18
- "numpy",
19
- "numba>=0.61",
37
+ "rustworkx>=0.16", # NetworkX-like graph library based on Rust
38
+
39
+ # Data Model
20
40
  "numpydantic",
21
- "pandas",
22
41
  "pydantic>=2.11",
23
- "rdkit>=2024.9",
24
- "rustworkx>=0.16", # NetworkX-like graph library based on Rust
25
- "scikit-learn",
26
- "scipy",
27
- "sparse",
28
- "torch_geometric",
29
- "torch",
42
+ "python-snappy>=0.7.3",
43
+ "pyyaml", # YAML parser & writer
44
+ "sqlmodel", # SQL + pydantic
45
+ "tomli-w", # TOML writer
46
+ "tomli; python_version < '3.11'", # use tomllib in python>=3.11
30
47
  "typing-extensions",
31
48
  ]
32
49
  [dependency-groups]
@@ -36,6 +53,7 @@ dev = ["ruff>=0.11", "pytest>=8.3", "pytest-xdist", "ipython"]
36
53
  [tool.uv]
37
54
  package = true
38
55
  [[tool.uv.index]]
56
+ name = 'tuna'
39
57
  url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" # TUNA
40
58
  default = true
41
59
 
@@ -1,4 +1,5 @@
1
1
  # ruff: noqa: D100, D101, D102, D103
2
+ import itertools
2
3
  import warnings
3
4
  from pathlib import Path
4
5
  from pprint import pprint
@@ -7,7 +8,7 @@ from tempfile import TemporaryDirectory
7
8
  import numpy as np
8
9
  import pytest
9
10
 
10
- from GraphAtoms.common.abc import BaseModel, NpzPklBaseModel
11
+ from GraphAtoms.common.abc import BaseModel, ExtendedBaseModel, NpzPklBaseModel
11
12
  from GraphAtoms.common.ndarray import NDArray
12
13
 
13
14
 
@@ -16,6 +17,11 @@ class MockNpzBaseModel(NpzPklBaseModel):
16
17
  v: float = 5
17
18
 
18
19
 
20
+ class MockExtendedBaseModel(ExtendedBaseModel):
21
+ arr: list[str] = ["1", "sd"]
22
+ v: float = 5
23
+
24
+
19
25
  class Test_ABC_Pydantic_Model:
20
26
  def test_json_schema(self):
21
27
  print()
@@ -38,10 +44,37 @@ class Test_ABC_Pydantic_Model:
38
44
  np.testing.assert_array_equal(obj.arr, new_obj.arr) # type: ignore
39
45
  assert new_obj == obj
40
46
 
47
+ @pytest.mark.parametrize("format", ["yaml", "toml", "json"])
48
+ def test_convert(self, format: str) -> None:
49
+ self.__run(MockExtendedBaseModel(), MockExtendedBaseModel, format)
50
+
41
51
  @pytest.mark.parametrize("format", ["pkl", "npz", "json"])
42
52
  def test_convert_numpy(self, format: str) -> None:
43
53
  self.__run(MockNpzBaseModel(), MockNpzBaseModel, format)
44
54
 
45
55
 
56
+ @pytest.mark.parametrize("cls", [MockExtendedBaseModel, MockNpzBaseModel])
57
+ @pytest.mark.parametrize(
58
+ "fmt,level",
59
+ [
60
+ # ("xz", 0),
61
+ # ("lzma", 0),
62
+ ("snappy", 0),
63
+ ]
64
+ + sorted(
65
+ itertools.product(
66
+ ["z", "gz", "bz2"],
67
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
68
+ )
69
+ ),
70
+ )
71
+ def test_compress(fmt, level: int, cls: type[BaseModel]) -> None:
72
+ obj = cls()
73
+ b = obj.as_bytes(compressformat=fmt, compresslevel=level)
74
+ obj2 = cls.from_bytes(b, compressformat=fmt)
75
+ print(fmt, level, len(b), sep="\t")
76
+ assert obj == obj2
77
+
78
+
46
79
  if __name__ == "__main__":
47
- pytest.main([__file__, "-vv", "-s", "--maxfail=1"])
80
+ pytest.main([__file__, "-s", "--maxfail=1"])
@@ -0,0 +1,44 @@
1
+ import numpy as np
2
+ import pytest
3
+ from ase.build import molecule
4
+
5
+ from GraphAtoms.common.rotation import Rot, kabsch, rotate
6
+
7
+
8
+ def test_rotation() -> None: # noqa: D103
9
+ ben = molecule("C6H6")
10
+ points = ben.positions + np.random.random(3)
11
+ center = points.mean(axis=0)
12
+ print(f"Points center: {center}")
13
+
14
+ rotation = Rot.random()
15
+ print(f"Random rotation:\n{rotation.as_quat()}")
16
+
17
+ rotated = rotate(points, rotation, center=[0, 0, 0])
18
+ print(f"Rotated points around zero point:\n{rotated}")
19
+ assert pytest.approx(rotated.mean(axis=0)) != center
20
+
21
+ rotated = rotate(points, rotation, center)
22
+ print(f"Rotated points around center point:\n{rotated}")
23
+ assert pytest.approx(rotated.mean(axis=0)) == center
24
+
25
+ # Default center point as the geometry center:
26
+ rotated = rotate(points, rotation)
27
+ print(f"Rotated points around center point:\n{rotated}")
28
+ assert pytest.approx(rotated.mean(axis=0)) == center
29
+
30
+
31
+ def test_kabsch() -> None: # noqa: D103
32
+ B = molecule("C6H6").positions + np.random.random(3)
33
+ R, T = Rot.random(), np.random.random(3)
34
+
35
+ A = rotate(B, R) + T
36
+ assert pytest.approx((A - T).mean(axis=0)) == B.mean(axis=0)
37
+
38
+ R0, T0, rmsd = kabsch(A, B)
39
+ assert isinstance(T0, np.ndarray)
40
+ assert pytest.approx(T0) == T
41
+ assert isinstance(R0, Rot)
42
+ assert pytest.approx(R0.as_matrix()) == R.as_matrix()
43
+ assert rmsd <= 1e-5
44
+ assert pytest.approx(rotate(B, R0) + T0) == A
@@ -1,33 +0,0 @@
1
- """The utils for strings."""
2
-
3
- from hashlib import blake2b
4
- from random import sample
5
- from string import ascii_lowercase
6
-
7
- from ase.db.core import convert_str_to_int_float_bool_or_str
8
-
9
-
10
- def random_string(length: int = 6) -> str:
11
- """Return the random string that has the given length."""
12
- return "".join(sample(ascii_lowercase, int(length)))
13
-
14
-
15
- def _hash(str_value: str, digest_size: int = 6) -> str:
16
- obj = blake2b(
17
- str_value.encode("ascii"),
18
- digest_size=int(digest_size / 2),
19
- )
20
- return obj.hexdigest()
21
-
22
-
23
- def hash_string(str_value: str, digest_size: int = 6) -> str:
24
- """Return the hashing string of the given string.
25
-
26
- Note: ensure the result cannot be convert to bool, int and float.
27
- """
28
- result: str = _hash(str_value, digest_size=digest_size)
29
- value = convert_str_to_int_float_bool_or_str(result)
30
- while not isinstance(value, str):
31
- result = _hash(f"{value}_{result}", digest_size)
32
- value = convert_str_to_int_float_bool_or_str(result)
33
- return result
File without changes
File without changes
File without changes