GraphAtoms 2.3.2__tar.gz → 2.3.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 (92) hide show
  1. {graphatoms-2.3.2 → graphatoms-2.3.3}/PKG-INFO +2 -2
  2. {graphatoms-2.3.2 → graphatoms-2.3.3}/pyproject.toml +28 -28
  3. {graphatoms-2.3.2 → graphatoms-2.3.3}/pyproject.toml.orig +28 -28
  4. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/dataclasses/_pydanticModel.py +1 -1
  5. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/network/_metadata.py +38 -2
  6. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/network/_scheduler.py +5 -6
  7. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/parallel/__init__.py +22 -6
  8. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/parallel/_base.py +24 -5
  9. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/parallel/_ray.py +35 -12
  10. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/__init__.py +10 -14
  11. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/general/rxngen.py +24 -14
  12. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/geometry/_bond_list.py +1 -2
  13. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/geometry/_neighbor_list.py +6 -3
  14. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/system/graph.py +18 -4
  15. {graphatoms-2.3.2 → graphatoms-2.3.3}/README.md +0 -0
  16. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/LICENSE +0 -0
  17. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/__init__.py +0 -0
  18. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/arrayapi/__init__.py +0 -0
  19. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/arrayapi/compat.py +0 -0
  20. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/arrayapi/typing.py +0 -0
  21. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/dataclasses/__init__.py +0 -0
  22. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/dataclasses/_numpydantic.py +0 -0
  23. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/dataclasses/_pydantic2pyarrow.py +0 -0
  24. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/dataclasses/_pydanticMixin.py +0 -0
  25. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/dataclasses/graph.py +0 -0
  26. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/__init__.py +0 -0
  27. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/config/__init__.py +0 -0
  28. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/config/atoms/__init__.py +0 -0
  29. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/config/atoms/aseread.yaml +0 -0
  30. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/config/atoms/octahedron.yaml +0 -0
  31. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/config/bonds/__init__.py +0 -0
  32. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/config/bonds/raw.yaml +0 -0
  33. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/config/calculator/__init__.py +0 -0
  34. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/config/calculator/emt.yaml +0 -0
  35. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/config/calculator/nequip.yaml +0 -0
  36. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/config/example.yaml +0 -0
  37. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/config/run.yaml +0 -0
  38. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/network/__init__.py +0 -0
  39. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/network/_clsnetwork.py +0 -0
  40. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/network/_commands.py +0 -0
  41. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/network/_recorder.py +0 -0
  42. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/parallel/_dask.py +0 -0
  43. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/parallel/_executorlib.py +0 -0
  44. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/common/__init__.py +0 -0
  45. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/common/_base.py +0 -0
  46. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/common/_errors.py +0 -0
  47. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/common/_helper.py +0 -0
  48. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/general/__init__.py +0 -0
  49. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/general/_expl.py +0 -0
  50. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/general/otfkmc.py +0 -0
  51. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/v2/__init__.py +0 -0
  52. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/v2/_helperv2.py +0 -0
  53. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/v2/general/__init__.py +0 -0
  54. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/v2/general/_expl.py +0 -0
  55. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/v2/general/otfkmc.py +0 -0
  56. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/v2/general/rxngen.py +0 -0
  57. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/v2/ray/__init__.py +0 -0
  58. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/v2/ray/_expl.py +0 -0
  59. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/v2/ray/_helper.py +0 -0
  60. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/v2/ray/otfkmc.py +0 -0
  61. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/enterpoint/runner/v2/ray/rxngen.py +0 -0
  62. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/geometry/__init__.py +0 -0
  63. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/geometry/_distance_pairs.py +0 -0
  64. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/geometry/_inner_outer.py +0 -0
  65. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/geometry/mic.py +0 -0
  66. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/geometry/rotation.py +0 -0
  67. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/geometry/sample.py +0 -0
  68. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/reaction/__init__.py +0 -0
  69. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/reaction/_event.py +0 -0
  70. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/reaction/reaction.py +0 -0
  71. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/reaction/xxsorption.py +0 -0
  72. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/system/__init__.py +0 -0
  73. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/system/atoms.py +0 -0
  74. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/system/bonds.py +0 -0
  75. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/system/database/__init__.py +0 -0
  76. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/system/database/abc.py +0 -0
  77. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/system/database/dict.py +0 -0
  78. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/system/database/dir.py +0 -0
  79. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/system/database/hdf5.py +0 -0
  80. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/system/database/sqliteASE.py +0 -0
  81. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/system/sysCluster.py +0 -0
  82. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/system/sysGas.py +0 -0
  83. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/system/system.py +0 -0
  84. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/utils/__init__.py +0 -0
  85. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/utils/adsorption.py +0 -0
  86. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/utils/aseqhull.py +0 -0
  87. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/utils/asetools.py +0 -0
  88. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/utils/bytestool.py +0 -0
  89. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/utils/logger.py +0 -0
  90. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/utils/parser.py +0 -0
  91. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/utils/rdutils.py +0 -0
  92. {graphatoms-2.3.2 → graphatoms-2.3.3}/src/graphatoms/utils/subgraph.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphatoms
3
- Version: 2.3.2
3
+ Version: 2.3.3
4
4
  Summary: The Chemical Core Class for Graph Theory Analysis.
5
5
  Keywords: chemistry,graph theory,reaction,event,KMC
6
6
  Author: LiuGaoyong
@@ -14,12 +14,12 @@ Requires-Dist: ase
14
14
  Requires-Dist: pymatgen>2023.6
15
15
  Requires-Dist: rdkit>=2025
16
16
  Requires-Dist: numpy>=2,<3
17
- Requires-Dist: pandas>=2,<4
18
17
  Requires-Dist: scipy>=1.10,<2
19
18
  Requires-Dist: scikit-learn>=1.5
20
19
  Requires-Dist: array-api-compat>=1.10
21
20
  Requires-Dist: array-api-extra>=0.10
22
21
  Requires-Dist: pyarrow
22
+ Requires-Dist: pandas
23
23
  Requires-Dist: igraph>=0.11
24
24
  Requires-Dist: h5py>=3.16
25
25
  Requires-Dist: numpydantic
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "graphatoms"
7
- version = "2.3.2"
7
+ version = "2.3.3"
8
8
  readme = "README.md"
9
9
  description = "The Chemical Core Class for Graph Theory Analysis."
10
10
  license-files = ["src/LICENSE"]
@@ -27,12 +27,12 @@ dependencies = [
27
27
  "pymatgen>2023.6",
28
28
  "rdkit>=2025",
29
29
  "numpy>=2,<3",
30
- "pandas>=2,<4",
31
30
  "scipy>=1.10,<2",
32
31
  "scikit-learn>=1.5",
33
32
  "array-api-compat>=1.10",
34
33
  "array_api-extra>=0.10",
35
34
  "pyarrow",
35
+ "pandas",
36
36
  "igraph>=0.11",
37
37
  "h5py>=3.16",
38
38
  "numpydantic",
@@ -146,29 +146,29 @@ benchmark = "pytest -s src/tests-benchmark/*.py"
146
146
 
147
147
  [tool.pixi.dependencies]
148
148
  python = ">=3.12"
149
- pytest = ">=9.0.3,<10"
150
- array-api-compat = ">=1.15.0,<2"
151
- array-api-extra = ">=0.11.0,<0.12"
152
- array-api-strict = ">=2.5,<3"
153
- ase = ">=3.28.0,<4"
154
- pymatgen = ">=2026.5.4,<2027"
155
- rdkit = ">=2025.9.5,<2027"
156
- openbabel = ">=3.1.1,<4"
157
- pyarrow = ">=21.0.0,<25"
158
- scikit-learn = ">=1.8.0,<2"
159
- python-igraph = ">=0.11.9,<0.12"
160
- numpy = ">=2.0.0,<3"
161
- pydantic = ">=2.10.0,<3"
162
- numpydantic = ">=1.8.1,<2"
163
- python-snappy = ">=0.7.3,<0.8"
164
- h5py = ">=3.16.0,<4"
165
- hydra-core = ">=1.3.2,<2"
166
- tomli-w = ">=1.2.0,<2"
167
- loguru = ">=0.7.3,<0.8"
168
- jupyterlab = ">=4.5.8,<5"
169
- joblib = ">=1.1.0,<2"
170
- ovld = ">=0.5.15,<0.6"
171
- pytest-xdist = ">=3.8.0,<4"
172
- executorlib = ">=1.8,<2"
173
- typer = ">=0.20,<1"
174
- grpcio = ">=1.52.0,<2"
149
+ pytest = ">=8"
150
+ array-api-compat = ">=1.15.0"
151
+ array-api-extra = ">=0.11.0"
152
+ array-api-strict = ">=2.5"
153
+ ase = ">=3.28.0"
154
+ pymatgen = ">=2026.5.4"
155
+ rdkit = ">=2025.9.5"
156
+ openbabel = ">=3.1.1"
157
+ pyarrow = ">=21.0.0"
158
+ scikit-learn = ">=1.8.0"
159
+ python-igraph = ">=0.11.9"
160
+ numpy = ">=2.0.0"
161
+ pydantic = ">=2.10.0"
162
+ numpydantic = ">=1.8.1"
163
+ python-snappy = ">=0.7.3"
164
+ h5py = ">=3.16"
165
+ hydra-core = ">=1.3.0"
166
+ tomli-w = ">=1.2.0"
167
+ loguru = ">=0.7.3"
168
+ jupyterlab = ">=4.5.8"
169
+ joblib = ">=1.1.0"
170
+ ovld = ">=0.5.15"
171
+ pytest-xdist = ">=3.8.0"
172
+ executorlib = ">=1.8"
173
+ typer = ">=0.20"
174
+ grpcio = ">=1.52.0"
@@ -12,7 +12,7 @@ graphatoms-network = "graphatoms.enterpoint.network:main"
12
12
  graphatoms-run = "graphatoms.enterpoint.runner:run"
13
13
  [project]
14
14
  name = "graphatoms"
15
- version = "2.3.2"
15
+ version = "2.3.3"
16
16
  readme = "README.md"
17
17
  description = "The Chemical Core Class for Graph Theory Analysis."
18
18
  authors = [{ name = "LiuGaoyong", email = "liugaoyong_88@163.com" }]
@@ -39,12 +39,12 @@ dependencies = [
39
39
 
40
40
  # Scientific Computing
41
41
  "numpy>=2,<3",
42
- "pandas>=2,<4",
43
42
  "scipy>=1.10,<2",
44
43
  "scikit-learn>=1.5",
45
44
  "array-api-compat>=1.10",
46
45
  "array_api-extra>=0.10",
47
46
  "pyarrow",
47
+ "pandas",
48
48
 
49
49
  # Graph Theory
50
50
  "igraph>=0.11", # high performance graph library based on C
@@ -145,29 +145,29 @@ benchmark = "pytest -s src/tests-benchmark/*.py"
145
145
 
146
146
  [tool.pixi.dependencies]
147
147
  python = ">=3.12"
148
- pytest = ">=9.0.3,<10"
149
- array-api-compat = ">=1.15.0,<2"
150
- array-api-extra = ">=0.11.0,<0.12"
151
- array-api-strict = ">=2.5,<3"
152
- ase = ">=3.28.0,<4"
153
- pymatgen = ">=2026.5.4,<2027"
154
- rdkit = ">=2025.9.5,<2027"
155
- openbabel = ">=3.1.1,<4"
156
- pyarrow = ">=21.0.0,<25"
157
- scikit-learn = ">=1.8.0,<2"
158
- python-igraph = ">=0.11.9,<0.12"
159
- numpy = ">=2.0.0,<3"
160
- pydantic = ">=2.10.0,<3"
161
- numpydantic = ">=1.8.1,<2"
162
- python-snappy = ">=0.7.3,<0.8"
163
- h5py = ">=3.16.0,<4"
164
- hydra-core = ">=1.3.2,<2"
165
- tomli-w = ">=1.2.0,<2"
166
- loguru = ">=0.7.3,<0.8"
167
- jupyterlab = ">=4.5.8,<5"
168
- joblib = ">=1.1.0,<2"
169
- ovld = ">=0.5.15,<0.6"
170
- pytest-xdist = ">=3.8.0,<4"
171
- executorlib = ">=1.8,<2"
172
- typer = ">=0.20,<1"
173
- grpcio = ">=1.52.0,<2"
148
+ pytest = ">=8"
149
+ array-api-compat = ">=1.15.0"
150
+ array-api-extra = ">=0.11.0"
151
+ array-api-strict = ">=2.5"
152
+ ase = ">=3.28.0"
153
+ pymatgen = ">=2026.5.4"
154
+ rdkit = ">=2025.9.5"
155
+ openbabel = ">=3.1.1"
156
+ pyarrow = ">=21.0.0"
157
+ scikit-learn = ">=1.8.0"
158
+ python-igraph = ">=0.11.9"
159
+ numpy = ">=2.0.0"
160
+ pydantic = ">=2.10.0"
161
+ numpydantic = ">=1.8.1"
162
+ python-snappy = ">=0.7.3"
163
+ h5py = ">=3.16"
164
+ hydra-core = ">=1.3.0"
165
+ tomli-w = ">=1.2.0"
166
+ loguru = ">=0.7.3"
167
+ jupyterlab = ">=4.5.8"
168
+ joblib = ">=1.1.0"
169
+ ovld = ">=0.5.15"
170
+ pytest-xdist = ">=3.8.0"
171
+ executorlib = ">=1.8"
172
+ typer = ">=0.20"
173
+ grpcio = ">=1.52.0"
@@ -126,7 +126,7 @@ class OurBaseModel(PydanticFactoryMixin, _IndexMaskMixin):
126
126
  if v0.shape != v1.shape:
127
127
  return False
128
128
  if np.all(v0 == v1):
129
- return True
129
+ continue
130
130
  elif not np.allclose(v0, v1):
131
131
  return False
132
132
  else:
@@ -1,6 +1,7 @@
1
1
  import dataclasses as dc
2
+ import types
2
3
  from pathlib import Path
3
- from typing import Any, Self, override
4
+ from typing import Any, Self, Union, get_args, get_origin, override
4
5
 
5
6
  import numpy as np
6
7
  import pandas as pd
@@ -15,6 +16,25 @@ from graphatoms.utils.bytestool import atomic_write
15
16
  __all__ = ["MetaData"]
16
17
 
17
18
 
19
+ def _is_list_of_optional_str(annotation: Any) -> bool:
20
+ """Whether the annotation is ``list[str | None]``."""
21
+ if get_origin(annotation) is not list:
22
+ return False
23
+ element = get_args(annotation)[0]
24
+ if get_origin(element) not in (Union, types.UnionType):
25
+ return False
26
+ return set(get_args(element)) == {str, type(None)}
27
+
28
+
29
+ def _missing_to_none(value: Any) -> Any:
30
+ """Convert a missing value marker of pandas to ``None``."""
31
+ if value is None or value is pd.NA:
32
+ return None
33
+ if isinstance(value, float) and np.isnan(value):
34
+ return None
35
+ return value
36
+
37
+
18
38
  class _MetaDataBasic(OurBaseModel, EventConfig):
19
39
  gas_sticking: dict[str, NonNegativeFloat] = {}
20
40
 
@@ -109,7 +129,23 @@ class _MetaDataTable(BaseModel):
109
129
 
110
130
  @classmethod
111
131
  def from_dataframe(cls, df: pd.DataFrame) -> Self:
112
- return cls(**{k: df[k].to_list() for k in df.columns})
132
+ """Create the table from a DataFrame.
133
+
134
+ Note:
135
+ Since pandas 3.0, a string column containing missing values is
136
+ read back as the new default string dtype (``str``), whose
137
+ missing value marker is ``numpy.nan`` instead of ``None``.
138
+ Such ``nan`` values are rejected by the ``str | None`` fields,
139
+ so they are converted back to ``None`` here.
140
+ """
141
+ data: dict[str, list[Any]] = {}
142
+ for k in df.columns:
143
+ values: list[Any] = df[k].to_list()
144
+ field = cls.__pydantic_fields__.get(k)
145
+ if field is not None and _is_list_of_optional_str(field.annotation):
146
+ values = [_missing_to_none(v) for v in values]
147
+ data[k] = values
148
+ return cls(**data)
113
149
 
114
150
  def merge(self, other: Self, idxs: list[int] | None = None) -> None:
115
151
  if idxs is None:
@@ -46,10 +46,11 @@ class Scheduler(BaseModel):
46
46
  @validate_call
47
47
  def read_npz(cls, filename: FilePath, *args, **kwargs) -> Self:
48
48
  data: dict[str, dict[str, NDArray]] = defaultdict(dict)
49
- dct: Mapping[str, NDArray] = np.load(filename)
50
- for k, v in dct.items():
51
- k2, k1 = k.split("_")
52
- data[k1][k2] = v
49
+ with np.load(filename) as npz:
50
+ dct: Mapping[str, NDArray] = npz
51
+ for k, v in dct.items():
52
+ k2, k1 = k.rsplit("_", 1)
53
+ data[k1][k2] = v
53
54
  return cls(**data)
54
55
 
55
56
  def can_be_skip(
@@ -62,7 +63,6 @@ class Scheduler(BaseModel):
62
63
  **kwargs,
63
64
  ) -> tuple[bool, float]:
64
65
  diffpositions = np.asarray(diffpositions, dtype=float)
65
- print(diffpositions)
66
66
  assert 0 <= thetacutoff <= 180, "thetacutoff must be in 0-180"
67
67
  if isinstance(graph, str):
68
68
  if is_fixed is None:
@@ -110,7 +110,6 @@ class Scheduler(BaseModel):
110
110
  cos: np.ndarray = cosine_similarity(dR, dR_new)
111
111
  if np.max(cos) <= np.cos(np.deg2rad(thetacutoff)):
112
112
  self.dR[key] = np.vstack((dR, dR_new)) # row_stack
113
- print(self.dR[key])
114
113
  return False, np.max(cos)
115
114
  return True, np.max(cos)
116
115
 
@@ -10,7 +10,7 @@ from graphatoms.enterpoint.parallel._base import (
10
10
  )
11
11
  from graphatoms.enterpoint.parallel._dask import DaskExecutor
12
12
  from graphatoms.enterpoint.parallel._executorlib import ExecutorLibExecutor
13
- from graphatoms.enterpoint.parallel._ray import _RAY_AVAILABLE, RayExecutor, ray
13
+ from graphatoms.enterpoint.parallel._ray import RayExecutor, ray
14
14
 
15
15
  __all__ = [
16
16
  "SerialExecutor",
@@ -18,6 +18,14 @@ __all__ = [
18
18
  "ParallelExecutorABC",
19
19
  "get_executor",
20
20
  ]
21
+ BACKENDS = [
22
+ "serial",
23
+ "multiprocessing",
24
+ "ray",
25
+ "dask",
26
+ "executorlib",
27
+ "joblib",
28
+ ]
21
29
 
22
30
 
23
31
  def get_executor(
@@ -40,7 +48,11 @@ def get_executor(
40
48
  backend = str(backend).lower()
41
49
  if backend in ("ray",):
42
50
  return RayExecutor(nworkers=nworkers, **kwargs) # type: ignore[arg-type]
43
- elif backend in ("dask",):
51
+ # `ncpus_for_tasks` only applies to the ray backend; `RunnerABC` passes
52
+ # it unconditionally, so drop it for the other backends instead of
53
+ # forwarding it to `ProcessPoolExecutor`.
54
+ kwargs.pop("ncpus_for_tasks", None)
55
+ if backend in ("dask",):
44
56
  return DaskExecutor(nworkers=nworkers, **kwargs) # type: ignore[arg-type]
45
57
  elif backend in ("executorlib",):
46
58
  return ExecutorLibExecutor( # type: ignore[arg-type]
@@ -53,10 +65,7 @@ def get_executor(
53
65
  nworkers = None
54
66
  return MultiprocessingExecutor(nworkers=nworkers, **kwargs) # type: ignore[call-arg]
55
67
  else:
56
- raise ValueError(
57
- f"Unknown backend: {backend} Available: "
58
- f"{['serial', 'multiprocessing', 'ray', 'dask', 'executorlib']}"
59
- )
68
+ raise ValueError(f"Unknown backend: {backend} Available: {BACKENDS}")
60
69
 
61
70
 
62
71
  def executor_map(
@@ -69,12 +78,19 @@ def executor_map(
69
78
  ) -> Sequence[Any]:
70
79
  if isinstance(executor, RayExecutor):
71
80
  dct: dict[int, ray.ObjectRef] = {}
81
+ # Hold a strong reference to every deduplicated object. Keying the
82
+ # cache on `id(v)` alone is only safe while the objects stay alive;
83
+ # if an intermediate object were collected, CPython could hand its
84
+ # `id` to a *different* object and the wrong value would be
85
+ # substituted into a task.
86
+ keepalive: list[Any] = []
72
87
  remote_params: list[dict[str, ray.ObjectRef]] = []
73
88
  for params in fn_params:
74
89
  item: dict[str, ray.ObjectRef] = {}
75
90
  for k, v in params.items():
76
91
  if id(v) not in dct:
77
92
  dct[id(v)] = ray.put(v)
93
+ keepalive.append(v)
78
94
  item[k] = dct[id(v)]
79
95
  remote_params.append(item)
80
96
  fn_params = remote_params
@@ -111,12 +111,21 @@ class MultiprocessingExecutor(ParallelExecutorABC):
111
111
  return_when=cf.FIRST_COMPLETED,
112
112
  timeout=timeout,
113
113
  )
114
- self.__executor.map
115
- return self.get(list(done)[0]), undone # type: ignore[override]
114
+ if len(done) == 0:
115
+ raise TimeoutError(
116
+ "concurrent.futures.wait() returned no finished task "
117
+ f"within timeout={timeout!r}."
118
+ )
119
+ # `FIRST_COMPLETED` may return *several* futures at once. Only one
120
+ # result can be returned by this API, so the other already-finished
121
+ # futures must be pushed back into the remaining list; otherwise their
122
+ # results are silently dropped by callers that loop until the list is
123
+ # empty.
124
+ done = list(done)
125
+ return self.get(done[0]), list(undone) + done[1:] # type: ignore[override]
116
126
 
117
127
  @override
118
128
  def map(self, fn, *iterables, timeout=None, chunksize=1) -> list[Any]:
119
- print(iterables)
120
129
  return list(
121
130
  self.__executor.map(
122
131
  fn,
@@ -128,6 +137,16 @@ class MultiprocessingExecutor(ParallelExecutorABC):
128
137
 
129
138
 
130
139
  class SerialExecutor(MultiprocessingExecutor):
140
+ """Run tasks one at a time.
141
+
142
+ .. warning::
143
+ This is *not* an in-process executor: it delegates to a
144
+ single-worker :class:`~concurrent.futures.ProcessPoolExecutor`, so
145
+ every task and its arguments must be picklable and each call runs in a
146
+ forked child process. See the project bug report for the implications
147
+ under Slurm.
148
+ """
149
+
131
150
  @override
132
- def __init__(self, *, nworkers=None, **kwargs) -> None:
133
- super().__init__(nworkers=1, **kwargs)
151
+ def __init__(self, *a, **kw) -> None:
152
+ super().__init__(nworkers=1)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import weakref
3
5
  from collections.abc import Callable
@@ -18,6 +20,17 @@ if TYPE_CHECKING:
18
20
  import ray # type: ignore
19
21
 
20
22
 
23
+ ###############################################################################
24
+ # NOTE: `from __future__ import annotations` above is required, *not* optional.
25
+ # `ray` is an optional dependency (it is only a dev dependency in
26
+ # pyproject.toml), so when it is absent this module sets `ray = None`. Without
27
+ # postponed evaluation the `-> ray.ObjectRef` annotations below would be
28
+ # evaluated eagerly at class-definition time and raise
29
+ # `AttributeError: 'NoneType' object has no attribute 'ObjectRef'`, which makes
30
+ # `import graphatoms.enterpoint.parallel` (and therefore `graphatoms-run`)
31
+ # fail on any installation that does not happen to have ray installed.
32
+
33
+
21
34
  class RayExecutor(ParallelExecutorABC):
22
35
  """An executor backed by :mod:`ray`."""
23
36
 
@@ -35,15 +48,12 @@ class RayExecutor(ParallelExecutorABC):
35
48
  raise ImportError(
36
49
  "ray is not installed. Install it with: pip install ray"
37
50
  )
38
- try:
39
- if not ray.is_initialized(): # type: ignore
40
- ray.init( # type: ignore
41
- ignore_reinit_error=True,
42
- num_cpus=nworkers,
43
- **kwargs,
44
- )
45
- except Exception:
46
- pass
51
+ if not ray.is_initialized(): # type: ignore
52
+ ray.init( # type: ignore
53
+ ignore_reinit_error=True,
54
+ num_cpus=nworkers,
55
+ **kwargs,
56
+ )
47
57
 
48
58
  @override
49
59
  def submit(
@@ -68,7 +78,7 @@ class RayExecutor(ParallelExecutorABC):
68
78
  func_ref = weakref.ref(func) # type: ignore
69
79
  except TypeError:
70
80
 
71
- def func_ref(): # type: ignore
81
+ def func_ref() -> Callable[..., Any]: # type: ignore
72
82
  return func
73
83
 
74
84
  self._remote_fn_cache[key] = (func_ref, remote_fn) # type: ignore
@@ -90,8 +100,21 @@ class RayExecutor(ParallelExecutorABC):
90
100
  timeout: float | None = None,
91
101
  **kwargs,
92
102
  ) -> tuple[Any, list[ray.ObjectRef]]: # type: ignore
93
- done, unfinished = ray.wait(futures, timeout=timeout) # type: ignore
94
- return ray.get(done)[0], unfinished # type: ignore
103
+ done, unfinished = ray.wait( # type: ignore
104
+ futures,
105
+ timeout=timeout,
106
+ num_returns=1,
107
+ **kwargs,
108
+ )
109
+ if len(done) == 0:
110
+ raise TimeoutError(
111
+ "ray.wait() returned no finished task "
112
+ f"within timeout={timeout!r}."
113
+ )
114
+ # `ray.wait` defaults to `num_returns=1`, but a caller may ask for
115
+ # more. Keep every finished ref accounted for so that no completed
116
+ # result is silently dropped from the caller's work queue.
117
+ return ray.get(done[0]), list(unfinished) + list(done[1:]) # type: ignore
95
118
 
96
119
  @override
97
120
  def map(self, fn, *iterables, timeout=None, chunksize=1) -> list[Any]:
@@ -7,12 +7,18 @@ import hydra
7
7
 
8
8
  from graphatoms.enterpoint.config import CONFIG_DIR, Config
9
9
 
10
- # from .general.otfkmc import OTFKMC as OnTheFlyKMC
11
- # from .general.rxngen import ReactionNetworkGenerator
12
10
  from .v2.general.otfkmc import OTFKMC as OnTheFlyKMC
13
11
  from .v2.general.rxngen import ReactionNetworkGenerator
14
- from .v2.ray.otfkmc import RayOTFKMC as RayOnTheFlyKMC
15
- from .v2.ray.rxngen import RayReactionNetworkGenerator
12
+
13
+ # NOTE: the experimental ray actors under `.v2.ray` are intentionally *not*
14
+ # imported here. They import `ray` unconditionally at module import time,
15
+ # while ray is only a dev dependency, so importing them would make the
16
+ # `graphatoms-run` console script unusable on any installation without ray.
17
+ # The `parallel: ray` backend itself lives in
18
+ # `graphatoms.enterpoint.parallel` and is selected through the config; these
19
+ # actor-based runners are still work in progress.
20
+ # from .v2.ray.otfkmc import RayOTFKMC as RayOnTheFlyKMC
21
+ # from .v2.ray.rxngen import RayReactionNetworkGenerator
16
22
 
17
23
 
18
24
  @hydra.main(
@@ -23,17 +29,7 @@ from .v2.ray.rxngen import RayReactionNetworkGenerator
23
29
  def run(cfg: Config) -> None:
24
30
  if str(cfg.run_type).lower() == "otfkmc":
25
31
  OnTheFlyKMC(config=cfg).run()
26
- return
27
- if str(cfg.parallel).lower() == "ray":
28
- RayOnTheFlyKMC(config=cfg).run()
29
- else:
30
- OnTheFlyKMC(config=cfg).run()
31
32
  elif str(cfg.run_type).lower() == "rxngen":
32
33
  ReactionNetworkGenerator(config=cfg).run()
33
- return
34
- if str(cfg.parallel).lower() == "ray":
35
- RayReactionNetworkGenerator(config=cfg).run()
36
- else:
37
- ReactionNetworkGenerator(config=cfg).run()
38
34
  else:
39
35
  raise ValueError(f" run_type `{cfg.run_type}` is not supported.")
@@ -40,34 +40,44 @@ class ReactionNetworkGenerator(ExplorationBase):
40
40
  if not self.config.restart:
41
41
  self.__match_apply_recorder = MatchApplyRecorder()
42
42
  self.__match_apply_recorder.persist(self.__path)
43
- self.__system_dct: dict[str, System] = {
43
+ self.__initial_system_dct: dict[str, System] = {
44
44
  sys.get_key_for_metadata(use_positions_uuid=False): sys
45
45
  for sys in self.get_batch_system_for()
46
46
  }
47
47
  else:
48
- data = self.__path.read_text()
49
- obj = MatchApplyRecorder.model_validate_json(data)
50
- self.__match_apply_recorder: MatchApplyRecorder = obj
51
- self.__system_dct: dict[str, System] = {
52
- k: System.from_ase(
53
- v,
54
- parse_bonds=self.config.bonds, # type: ignore
55
- parse_atoms_is_outer_or_not=True,
48
+ self.__match_apply_recorder = (
49
+ MatchApplyRecorder.model_validate_json(
50
+ json_data=self.__path.read_text(),
56
51
  )
57
- for k, v in self.network.db_system.items()
58
- }
52
+ )
53
+ self.__initial_system_dct = self.__system_dct_from_db
54
+
55
+ @property
56
+ def __explred_systems(self) -> set[str]:
57
+ return set(self.__match_apply_recorder.record.keys())
58
+
59
+ @property
60
+ def __system_dct_from_db(self) -> dict[str, System]:
61
+ return {
62
+ k: System.from_ase(
63
+ v,
64
+ parse_bonds=self.config.bonds, # type: ignore
65
+ parse_atoms_is_outer_or_not=True,
66
+ )
67
+ for k, v in self.network.db_system.items()
68
+ }
59
69
 
60
70
  @override
61
71
  def run(self) -> None:
62
72
  """Run the simulation for the given number of steps."""
63
- system_dct: dict[str, System] = self.__system_dct
73
+ system_dct: dict[str, System] = self.__initial_system_dct
64
74
  for self.__istep in range(self.config.max_steps):
65
75
  un_explred_systems: dict[str, System] = {
66
76
  k: v
67
77
  for k, v in system_dct.items()
68
- if k not in self.network.db_system
78
+ if k not in self.__explred_systems
69
79
  }
70
- if len(system_dct) == 0:
80
+ if len(un_explred_systems) == 0:
71
81
  msg = f"Istep={self.__istep}: No system left to explore."
72
82
  self.logger.info(self._reformat_message(msg))
73
83
  break
@@ -117,7 +117,7 @@ def bond_list(
117
117
  )
118
118
  matrix = sp.csr_matrix(matrix, dtype=int)
119
119
  if infer_order:
120
- raise NotImplementedError("infer_order is not implemented.")
120
+ raise NotImplementedError("infer_order is not implemented now.")
121
121
  edges = sp.coo_array(matrix).coords
122
122
  get_adjacency_by_rdkit(
123
123
  numbers=np.array(
@@ -133,7 +133,6 @@ def bond_list(
133
133
  target=edges[1],
134
134
  infer_order=False,
135
135
  )
136
-
137
136
  return matrix
138
137
 
139
138
 
@@ -64,13 +64,17 @@ def _matscipy_neighbor_list(
64
64
  sorted: bool = True,
65
65
  ) -> tuple[np.ndarray, ...]:
66
66
  try:
67
- pass # type: ignore
67
+ from matscipy.neighbours import ( # type: ignore # noqa: F401
68
+ neighbour_list as _matscipy_neighbour_list,
69
+ )
68
70
  except Exception:
69
71
  raise Exception(
70
72
  "Please install `matscipy` to speed up " #
71
73
  "the calculation of neighbor list.",
72
74
  )
73
- raise NotImplementedError
75
+ raise NotImplementedError(
76
+ "The `matscipy` neighbor-list backend is not implemented yet."
77
+ )
74
78
 
75
79
 
76
80
  #############################################################
@@ -213,7 +217,6 @@ def neighbor_list(
213
217
  max_nbins=int(max_nbins),
214
218
  ) # type: ignore
215
219
  elif backend == "matscipy":
216
- raise NotImplementedError
217
220
  return _matscipy_neighbor_list(
218
221
  quantities=quantities,
219
222
  a=a,
@@ -463,10 +463,24 @@ class SysGraph(BondGraph, Structure, AtomTag, GasMixin):
463
463
  # for bond attribute
464
464
  df = graph.get_edge_dataframe()
465
465
  assert len(df.columns) >= 2, df.columns
466
- dct["pair"] = df[df.columns[:2]].to_numpy()
467
- for k in set(df.columns[2:]) & {"order", "distance"}:
468
- if not df[k].isnull().all():
469
- dct[k] = df[k].to_numpy()
466
+ pair = df[df.columns[:2]].to_numpy()
467
+ attrs = {
468
+ k: df[k].to_numpy()
469
+ for k in set(df.columns[2:]) & {"order", "distance"}
470
+ if not df[k].isnull().all()
471
+ }
472
+ if not graph.is_directed():
473
+ # An undirected igraph keeps one edge per bond, while
474
+ # `BondGraph.pair` uses the symmetric (i, j) / (j, i) form.
475
+ # Rebuild it from the edge list here so the round-trip is
476
+ # lossless without storing a second copy of the bond list on
477
+ # the graph object.
478
+ pair = np.vstack([pair, pair[:, ::-1]])
479
+ perm = np.lexsort((pair[:, 1], pair[:, 0]))
480
+ pair = pair[perm]
481
+ attrs = {k: np.concatenate([v, v])[perm] for k, v in attrs.items()}
482
+ dct["pair"] = pair
483
+ dct.update(attrs)
470
484
  return cls.from_dict(dct, **kwargs)
471
485
 
472
486
  def to_igraph(self, *args, **kwargs) -> IGraph:
File without changes
File without changes