GraphAtoms 0.0.1__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.3/GraphAtoms/__init__.py +8 -0
  2. graphatoms-0.0.3/GraphAtoms/common/__init__.py +4 -0
  3. graphatoms-0.0.3/GraphAtoms/common/abc.py +392 -0
  4. graphatoms-0.0.3/GraphAtoms/common/error.py +16 -0
  5. graphatoms-0.0.3/GraphAtoms/common/geometry.py +376 -0
  6. graphatoms-0.0.3/GraphAtoms/common/ndarray.py +3 -0
  7. graphatoms-0.0.3/GraphAtoms/common/ndarray.pyi +5 -0
  8. graphatoms-0.0.3/GraphAtoms/common/rotation.py +101 -0
  9. graphatoms-0.0.3/GraphAtoms/common/string.py +138 -0
  10. graphatoms-0.0.3/GraphAtoms/containner/__init__.py +20 -0
  11. graphatoms-0.0.3/GraphAtoms/containner/_atomic.py +366 -0
  12. graphatoms-0.0.3/GraphAtoms/containner/_bonded.py +90 -0
  13. graphatoms-0.0.3/GraphAtoms/containner/_graph.py +704 -0
  14. graphatoms-0.0.3/GraphAtoms/containner/_mConnComp.py +45 -0
  15. graphatoms-0.0.3/GraphAtoms/containner/_mDataPyG.py +87 -0
  16. graphatoms-0.0.3/GraphAtoms/containner/_mFreeE.py +117 -0
  17. graphatoms-0.0.3/GraphAtoms/containner/_mIGraph.py +269 -0
  18. graphatoms-0.0.3/GraphAtoms/containner/_mNetworKit.py +12 -0
  19. graphatoms-0.0.3/GraphAtoms/containner/_mNetworkX.py +12 -0
  20. graphatoms-0.0.3/GraphAtoms/containner/_mRDKit.py +337 -0
  21. graphatoms-0.0.3/GraphAtoms/containner/_mRustworkX.py +12 -0
  22. graphatoms-0.0.3/GraphAtoms/containner/_sysCluster.py +121 -0
  23. graphatoms-0.0.3/GraphAtoms/containner/_sysGas.py +81 -0
  24. graphatoms-0.0.3/GraphAtoms/containner/_system.py +15 -0
  25. {graphatoms-0.0.1 → graphatoms-0.0.3}/GraphAtoms.egg-info/PKG-INFO +20 -12
  26. graphatoms-0.0.3/GraphAtoms.egg-info/SOURCES.txt +37 -0
  27. {graphatoms-0.0.1 → graphatoms-0.0.3}/GraphAtoms.egg-info/requires.txt +22 -10
  28. {graphatoms-0.0.1 → graphatoms-0.0.3}/PKG-INFO +20 -12
  29. {graphatoms-0.0.1 → graphatoms-0.0.3}/pyproject.toml +29 -17
  30. {graphatoms-0.0.1 → graphatoms-0.0.3}/tests/test_abc.py +35 -2
  31. graphatoms-0.0.3/tests/test_rotation.py +44 -0
  32. graphatoms-0.0.1/GraphAtoms.egg-info/SOURCES.txt +0 -12
  33. {graphatoms-0.0.1 → graphatoms-0.0.3}/GraphAtoms.egg-info/dependency_links.txt +0 -0
  34. {graphatoms-0.0.1 → graphatoms-0.0.3}/GraphAtoms.egg-info/top_level.txt +0 -0
  35. {graphatoms-0.0.1 → graphatoms-0.0.3}/LICENSE +0 -0
  36. {graphatoms-0.0.1 → graphatoms-0.0.3}/README.md +0 -0
  37. {graphatoms-0.0.1 → graphatoms-0.0.3}/setup.cfg +0 -0
  38. {graphatoms-0.0.1 → graphatoms-0.0.3}/tests/test_container.py +0 -0
  39. {graphatoms-0.0.1 → graphatoms-0.0.3}/tests/test_mRDKit.py +0 -0
  40. {graphatoms-0.0.1 → graphatoms-0.0.3}/tests/test_utils.py +0 -0
@@ -0,0 +1,8 @@
1
+ # ruff: noqa
2
+ from .containner import Cluster, Gas, System
3
+
4
+ __all__ = [
5
+ "Cluster",
6
+ "Gas",
7
+ "System",
8
+ ]
@@ -0,0 +1,4 @@
1
+ """The module for common use.
2
+
3
+ Many other modules depend on this module.
4
+ """
@@ -0,0 +1,392 @@
1
+ # ruff: noqa: D100 D102
2
+ import sys
3
+ from collections.abc import Sequence
4
+ from pickle import dumps, loads
5
+ from typing import Annotated, override
6
+
7
+ import numpy as np
8
+ import pydantic
9
+ import tomli_w as toml_w
10
+ import yaml
11
+ from joblib import dump, load
12
+ from numpy.typing import ArrayLike
13
+ from typing_extensions import Self
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
25
+ __all__ = [
26
+ "BaseModel",
27
+ "NpzPklBaseModel",
28
+ ]
29
+
30
+
31
+ class BaseMixin:
32
+ """The base mixin class which provides some useful classmethods."""
33
+
34
+ @staticmethod
35
+ def get_mask_or_index(k: ArrayLike, n: int) -> np.ndarray:
36
+ if np.isscalar(k):
37
+ if isinstance(k, bool):
38
+ k = [k] * n
39
+ elif isinstance(k, int):
40
+ k = [k]
41
+ else:
42
+ raise TypeError(f"Unsupported type({type(k)}): {k}.")
43
+ subset = np.asarray(k)
44
+ if subset.dtype == bool:
45
+ if subset.size != n:
46
+ raise KeyError(
47
+ f"Except {n} boolean array, but {subset.size} got."
48
+ )
49
+ else:
50
+ subset = np.unique(subset.astype(int))
51
+ if not 0 <= max(subset) < n:
52
+ raise KeyError(
53
+ f"Except 0-{n - 1} integer array, but the "
54
+ f"max({max(subset)}),min({min(subset)}) got."
55
+ )
56
+ return subset.flatten()
57
+
58
+ @classmethod
59
+ def get_index(cls, k: ArrayLike, n: int) -> np.ndarray:
60
+ result = cls.get_mask_or_index(k=k, n=n)
61
+ if result.dtype == bool:
62
+ result = np.arange(n)[result]
63
+ return result.astype(int, copy=False)
64
+
65
+ @classmethod
66
+ def get_mask(cls, k: ArrayLike, n: int) -> np.ndarray:
67
+ result = cls.get_mask_or_index(k=k, n=n)
68
+ if result.dtype != bool:
69
+ result = np.isin(np.arange(n), result)
70
+ return result.astype(bool, copy=False)
71
+
72
+
73
+ class BaseModel(pydantic.BaseModel):
74
+ """A base class for creating Pydantic models.
75
+
76
+ This class support many file formats:
77
+ json by `pydantic`
78
+ """
79
+
80
+ model_config = pydantic.ConfigDict(frozen=True)
81
+
82
+ @override
83
+ def __str__(self) -> str:
84
+ return f"{self.__class__.__name__}({self._string()})"
85
+
86
+ def _string(self) -> str:
87
+ """The string representation of this class.
88
+
89
+ The expression like `str(obj)` will output
90
+ `f"{self.__class__.__name__}({this_value})"`.
91
+ """
92
+ return "object"
93
+
94
+ @staticmethod
95
+ @pydantic.validate_call
96
+ def __get_format(fname: pydantic.FilePath | pydantic.NewPath) -> str:
97
+ format: str = fname.name.split(".")[-1].lower()
98
+ if format == "yml":
99
+ format = "yaml"
100
+ elif format == "pkl":
101
+ format = "pickle"
102
+ return format
103
+
104
+ def write(self, fname: pydantic.FilePath, **kwargs) -> pydantic.FilePath:
105
+ f = getattr(self, f"write_{self.__get_format(fname)}")
106
+ return f(fname, **kwargs)
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
+
137
+ @pydantic.validate_call
138
+ def write_json(
139
+ self,
140
+ filename: pydantic.FilePath | pydantic.NewPath,
141
+ indent: int | None = 4,
142
+ **kwargs,
143
+ ) -> pydantic.FilePath:
144
+ filename.write_text(
145
+ self.model_dump_json(
146
+ indent=indent,
147
+ exclude_none=True,
148
+ **kwargs,
149
+ ),
150
+ encoding="utf-8",
151
+ )
152
+ return filename
153
+
154
+ @classmethod
155
+ @pydantic.validate_call
156
+ def read_json(cls, filename: pydantic.FilePath, **kwargs) -> Self:
157
+ return cls.model_validate_json(filename.read_bytes(), **kwargs)
158
+
159
+ @classmethod
160
+ def read(cls, fname: pydantic.FilePath, **kwargs) -> Self:
161
+ f = getattr(cls, f"read_{cls.__get_format(fname)}")
162
+ return f(fname, **kwargs)
163
+
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
+
223
+ class NpzPklBaseModel(BaseModel):
224
+ """A extended base class for creating Pydantic models.
225
+
226
+ This class support many file formats:
227
+ json by `pydantic`
228
+ pickle by `joblib`
229
+ npz by `numpy`
230
+
231
+ Note: only numpy ndarray & numpy-compatible scalar value supported.
232
+ """
233
+
234
+ @classmethod
235
+ def _convert(cls) -> dict[str, tuple[tuple, str]]:
236
+ """Override if needed to specify the dtype and shape of attributes.
237
+
238
+ Note: must be implemented by children class.
239
+
240
+ Example:
241
+ return dict(a=((None, 3), "uint8"))
242
+ This `None` indicates it can take any shape along the first axis.
243
+ """
244
+ return {}
245
+
246
+ @classmethod
247
+ def _npz_available(cls) -> bool:
248
+ return True
249
+
250
+ @staticmethod
251
+ def __validate_ndarray_and_convert( # noqa: D103
252
+ data: np.ndarray | Sequence,
253
+ shape: Sequence[int | None],
254
+ dtype: str,
255
+ ) -> np.ndarray:
256
+ assert shape.count(None) <= 1, (type(shape), shape)
257
+ shape = tuple(int(i) if i is not None else -1 for i in shape)
258
+ if not isinstance(data, np.ndarray):
259
+ data = np.asarray(data)
260
+ return np.asarray(data, dtype).reshape(shape)
261
+
262
+ @pydantic.model_validator(mode="before")
263
+ @classmethod
264
+ def __convert(cls, data):
265
+ if isinstance(data, dict):
266
+ for key, (shape, dtype) in cls._convert().items():
267
+ if key in data and data[key] is not None:
268
+ kw = dict(data=data[key], shape=shape, dtype=dtype)
269
+ data[key] = cls.__validate_ndarray_and_convert(**kw) # type: ignore
270
+ return data
271
+
272
+ @pydantic.model_validator(mode="after")
273
+ def __check_shape(self) -> Self:
274
+ for k in self.__pydantic_fields__:
275
+ v = getattr(self, k, None)
276
+ if v is None or np.isscalar(v) or isinstance(v, np.ndarray):
277
+ pass
278
+ else:
279
+ raise ValueError(f"Invalid value: {v}")
280
+ return self
281
+
282
+ @override
283
+ def __eq__(self, other) -> bool:
284
+ if not isinstance(other, self.__class__):
285
+ return False
286
+ for k in self.__pydantic_fields__:
287
+ v0 = getattr(self, k, None)
288
+ v1 = getattr(other, k, None)
289
+ if v0 is None or np.isscalar(v0):
290
+ if v0 is None or np.isscalar(v0):
291
+ if v0 != v1:
292
+ return False
293
+ else:
294
+ return False
295
+ elif isinstance(v0, np.ndarray):
296
+ if isinstance(v1, np.ndarray):
297
+ if v0.shape != v1.shape:
298
+ return False
299
+ elif np.any(v0 != v1):
300
+ return False
301
+ else:
302
+ return False
303
+ else:
304
+ raise NotImplementedError(f"Field({k}): {type(v0)}")
305
+ return True
306
+
307
+ @pydantic.validate_call
308
+ def write_pickle(
309
+ self,
310
+ filename: pydantic.FilePath | pydantic.NewPath,
311
+ compress: bool | int | tuple[str, int] = 3,
312
+ **kwargs,
313
+ ) -> pydantic.FilePath:
314
+ """Persist the dictionary of this object into one file.
315
+
316
+ Read more in the reference:
317
+ https://joblib.readthedocs.io/en/latest/generated/joblib.dump.html
318
+
319
+ Args:
320
+ filename (str | Path | TextIOWrapper): The file object or path
321
+ of the file in which it is to be stored. The compression
322
+ method corresponding to one of the supported filename
323
+ extensions ('.z', '.gz', '.bz2', '.xz' or '.lzma')
324
+ will be used automatically.
325
+ compress (bool | int | tuple[str, int], optional):
326
+ Optional compression level for the data.
327
+ 0 or False is no compression.
328
+ Higher value means more compression, but also
329
+ slower read and write times.
330
+ Using a value of 3 is often a good compromise.
331
+ Defaults to 3.
332
+ exclude_defaults (bool): ...
333
+ exclude_none (bool): ...
334
+ **kwargs: ...
335
+
336
+ Note:
337
+ If compress is True, the compression level used is 3.
338
+ If compress is a 2-tuple, the first element must correspond to
339
+ a string between supported compressors (e.g 'zlib', 'gzip',
340
+ 'bz2', 'lzma' 'xz'), the second element must be an integer
341
+ from 0 to 9, corresponding to the compression level.
342
+ """
343
+ kwargs["exclude_none"] = True
344
+ kwargs["exclude_defaults"] = False
345
+ dump(
346
+ self.model_dump(mode="python", **kwargs),
347
+ filename,
348
+ compress=compress, # type: ignore
349
+ )
350
+ return filename
351
+
352
+ @pydantic.validate_call
353
+ def write_npz(
354
+ self,
355
+ filename: pydantic.FilePath | pydantic.NewPath,
356
+ compress: bool = True,
357
+ **kwargs,
358
+ ) -> pydantic.FilePath:
359
+ if not self._npz_available():
360
+ raise RuntimeError(
361
+ "Subclass must have class method named"
362
+ " `_npz_available()` to be True."
363
+ )
364
+ kwargs["exclude_none"] = True
365
+ kwargs["exclude_defaults"] = False
366
+ f_savez = np.savez_compressed if compress else np.savez
367
+ data = self.model_dump(mode="python", **kwargs)
368
+ dct: dict[str, ArrayLike] = {}
369
+ for k, v in data.items():
370
+ if not isinstance(v, dict):
371
+ dct[k] = v
372
+ f_savez(
373
+ filename,
374
+ allow_pickle=False,
375
+ **self.model_dump(mode="python", **kwargs),
376
+ )
377
+ return filename
378
+
379
+ @classmethod
380
+ @pydantic.validate_call
381
+ def read_npz(cls, filename: pydantic.FilePath, **kwargs) -> Self:
382
+ if not cls._npz_available():
383
+ raise RuntimeError(
384
+ "Subclass must have class method named"
385
+ " `_npz_available()` to be True."
386
+ )
387
+ return cls.model_validate(np.load(filename), **kwargs)
388
+
389
+ @classmethod
390
+ @pydantic.validate_call
391
+ def read_pickle(cls, filename: pydantic.FilePath, **kwargs) -> Self:
392
+ return cls.model_validate(load(filename), **kwargs)
@@ -0,0 +1,16 @@
1
+ # ruff: noqa: D100 D101 D102 D103
2
+
3
+
4
+ class NotSupportPeriodicSystem(KeyError):
5
+ """The periodic system is not supported."""
6
+
7
+
8
+ class NotSupportNonOrthorhombicLattice(KeyError):
9
+ """The non-orthorhombic lattice is not supported."""
10
+
11
+
12
+ def not_support_type(data, required_type: str) -> TypeError:
13
+ return TypeError(
14
+ f"Only support type({required_type:s}). But"
15
+ f" the type of input is {type(data)} now."
16
+ )