GraphAtoms 2.3.0__tar.gz → 2.3.1__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 (93) hide show
  1. {graphatoms-2.3.0 → graphatoms-2.3.1}/PKG-INFO +1 -1
  2. {graphatoms-2.3.0 → graphatoms-2.3.1}/pyproject.toml +2 -2
  3. {graphatoms-2.3.0 → graphatoms-2.3.1}/pyproject.toml.orig +2 -2
  4. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/network/__init__.py +2 -0
  5. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/network/_clsnetwork.py +220 -81
  6. graphatoms-2.3.1/src/graphatoms/enterpoint/network/_commands.py +139 -0
  7. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/network/_metadata.py +47 -1
  8. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/network/_recorder.py +17 -0
  9. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/network/_scheduler.py +4 -0
  10. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/parallel/_ray.py +23 -4
  11. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/v2/general/_expl.py +11 -0
  12. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/v2/ray/_expl.py +8 -9
  13. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/v2/ray/_helper.py +4 -4
  14. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/reaction/_event.py +56 -18
  15. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/system/bonds.py +9 -5
  16. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/system/database/abc.py +1 -0
  17. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/system/sysCluster.py +44 -6
  18. graphatoms-2.3.0/src/graphatoms/enterpoint/view.py +0 -118
  19. {graphatoms-2.3.0 → graphatoms-2.3.1}/README.md +0 -0
  20. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/LICENSE +0 -0
  21. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/__init__.py +0 -0
  22. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/arrayapi/__init__.py +0 -0
  23. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/arrayapi/compat.py +0 -0
  24. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/arrayapi/typing.py +0 -0
  25. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/dataclasses/__init__.py +0 -0
  26. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/dataclasses/_numpydantic.py +0 -0
  27. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/dataclasses/_pydantic2pyarrow.py +0 -0
  28. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/dataclasses/_pydanticMixin.py +0 -0
  29. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/dataclasses/_pydanticModel.py +0 -0
  30. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/dataclasses/graph.py +0 -0
  31. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/__init__.py +0 -0
  32. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/config/__init__.py +0 -0
  33. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/config/atoms/__init__.py +0 -0
  34. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/config/atoms/aseread.yaml +0 -0
  35. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/config/atoms/octahedron.yaml +0 -0
  36. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/config/bonds/__init__.py +0 -0
  37. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/config/bonds/raw.yaml +0 -0
  38. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/config/calculator/__init__.py +0 -0
  39. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/config/calculator/emt.yaml +0 -0
  40. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/config/calculator/nequip.yaml +0 -0
  41. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/config/example.yaml +0 -0
  42. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/config/run.yaml +0 -0
  43. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/parallel/__init__.py +0 -0
  44. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/parallel/_base.py +0 -0
  45. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/parallel/_dask.py +0 -0
  46. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/parallel/_executorlib.py +0 -0
  47. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/__init__.py +0 -0
  48. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/common/__init__.py +0 -0
  49. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/common/_base.py +0 -0
  50. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/common/_errors.py +0 -0
  51. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/common/_helper.py +0 -0
  52. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/general/__init__.py +0 -0
  53. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/general/_expl.py +0 -0
  54. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/general/otfkmc.py +0 -0
  55. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/general/rxngen.py +0 -0
  56. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/v2/__init__.py +0 -0
  57. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/v2/_helperv2.py +0 -0
  58. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/v2/general/__init__.py +0 -0
  59. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/v2/general/otfkmc.py +0 -0
  60. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/v2/general/rxngen.py +0 -0
  61. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/v2/ray/__init__.py +0 -0
  62. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/v2/ray/otfkmc.py +0 -0
  63. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/enterpoint/runner/v2/ray/rxngen.py +0 -0
  64. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/geometry/__init__.py +0 -0
  65. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/geometry/_bond_list.py +0 -0
  66. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/geometry/_distance_pairs.py +0 -0
  67. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/geometry/_inner_outer.py +0 -0
  68. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/geometry/_neighbor_list.py +0 -0
  69. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/geometry/mic.py +0 -0
  70. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/geometry/rotation.py +0 -0
  71. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/geometry/sample.py +0 -0
  72. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/reaction/__init__.py +0 -0
  73. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/reaction/reaction.py +0 -0
  74. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/reaction/xxsorption.py +0 -0
  75. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/system/__init__.py +0 -0
  76. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/system/atoms.py +0 -0
  77. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/system/database/__init__.py +0 -0
  78. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/system/database/dict.py +0 -0
  79. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/system/database/dir.py +0 -0
  80. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/system/database/hdf5.py +0 -0
  81. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/system/database/sqliteASE.py +0 -0
  82. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/system/graph.py +0 -0
  83. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/system/sysGas.py +0 -0
  84. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/system/system.py +0 -0
  85. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/utils/__init__.py +0 -0
  86. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/utils/adsorption.py +0 -0
  87. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/utils/aseqhull.py +0 -0
  88. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/utils/asetools.py +0 -0
  89. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/utils/bytestool.py +0 -0
  90. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/utils/logger.py +0 -0
  91. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/utils/parser.py +0 -0
  92. {graphatoms-2.3.0 → graphatoms-2.3.1}/src/graphatoms/utils/rdutils.py +0 -0
  93. {graphatoms-2.3.0 → graphatoms-2.3.1}/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.0
3
+ Version: 2.3.1
4
4
  Summary: The Chemical Core Class for Graph Theory Analysis.
5
5
  Keywords: chemistry,graph theory,reaction,event,KMC
6
6
  Author: LiuGaoyong
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "graphatoms"
7
- version = "2.3.0"
7
+ version = "2.3.1"
8
8
  readme = "README.md"
9
9
  description = "The Chemical Core Class for Graph Theory Analysis."
10
10
  license-files = ["src/LICENSE"]
@@ -56,7 +56,7 @@ Issues = "https://github.com/LiuGaoyong/GraphAtoms/issues/"
56
56
 
57
57
  [project.scripts]
58
58
  graphatoms-config = "graphatoms.enterpoint.config:print_config"
59
- graphatoms-network = "graphatoms.enterpoint.view:main"
59
+ graphatoms-network = "graphatoms.enterpoint.network:main"
60
60
  graphatoms-run = "graphatoms.enterpoint.runner:run"
61
61
 
62
62
  [[project.authors]]
@@ -8,11 +8,11 @@ Repository = "https://github.com/LiuGaoyong/GraphAtoms"
8
8
  Issues = "https://github.com/LiuGaoyong/GraphAtoms/issues/"
9
9
  [project.scripts]
10
10
  graphatoms-config = "graphatoms.enterpoint.config:print_config"
11
- graphatoms-network = "graphatoms.enterpoint.view:main"
11
+ 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.0"
15
+ version = "2.3.1"
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" }]
@@ -1,4 +1,5 @@
1
1
  from ._clsnetwork import ReactionNetwork
2
+ from ._commands import main
2
3
  from ._metadata import MetaData
3
4
  from ._recorder import Recorder
4
5
  from ._scheduler import Scheduler
@@ -8,4 +9,5 @@ __all__ = [
8
9
  "Recorder",
9
10
  "Scheduler",
10
11
  "ReactionNetwork",
12
+ "main",
11
13
  ]
@@ -1,12 +1,23 @@
1
1
  import pickle
2
2
  import time
3
+ from collections.abc import Iterator, Mapping
3
4
  from pathlib import Path
4
- from typing import Literal, Self
5
+ from typing import Literal, Self, override
5
6
 
6
7
  import numpy as np
7
8
  from ase import Atoms
8
9
 
9
10
  from graphatoms.enterpoint.config import Config, EventConfig
11
+ from graphatoms.enterpoint.network._metadata import MetaData
12
+ from graphatoms.enterpoint.network._metadata import (
13
+ _MetaDataBasic as MetaDataBasic,
14
+ )
15
+ from graphatoms.enterpoint.network._recorder import (
16
+ OldNewRecorder4Confidence,
17
+ Recorder,
18
+ RecorderInfo,
19
+ )
20
+ from graphatoms.enterpoint.network._scheduler import Scheduler
10
21
  from graphatoms.reaction import (
11
22
  Adsorption,
12
23
  Desorption,
@@ -17,11 +28,6 @@ from graphatoms.reaction import (
17
28
  from graphatoms.system import Cluster, Gas, SysGraph, System
18
29
  from graphatoms.system.database import DatabaseABC, get_db
19
30
 
20
- from ._metadata import MetaData
21
- from ._metadata import _MetaDataBasic as MetaDataBasic
22
- from ._recorder import OldNewRecorder4Confidence, Recorder, RecorderInfo
23
- from ._scheduler import Scheduler
24
-
25
31
  __all__ = [
26
32
  "MetaData",
27
33
  "Recorder",
@@ -31,7 +37,7 @@ __all__ = [
31
37
  NETWORK_PATH_PREFIX = "event"
32
38
 
33
39
 
34
- class ReactionNetwork:
40
+ class ReactionNetwork(Mapping[str, tuple[EventInfo, EventBase]]):
35
41
  DB_USE_POSITION_UUID_DICT: dict[str, bool] = {
36
42
  "ts": True,
37
43
  "minima": True,
@@ -57,8 +63,8 @@ class ReactionNetwork:
57
63
  assert format in (
58
64
  "sqlite",
59
65
  "directory",
60
- "dir",
61
66
  "folder",
67
+ "dir",
62
68
  "h5",
63
69
  "hdf5",
64
70
  ), f"The format of `{format}` is not supported for restart."
@@ -118,6 +124,170 @@ class ReactionNetwork:
118
124
  assert isinstance(self.__db_cluster, DatabaseABC)
119
125
  assert isinstance(self.__db_system, DatabaseABC)
120
126
 
127
+ @classmethod
128
+ def from_config(
129
+ cls,
130
+ config: Config,
131
+ *args,
132
+ restart: bool | None = None,
133
+ remote_mode: bool = False,
134
+ **kwargs,
135
+ ) -> Self: # type: ignore
136
+ """Create a network from a config file."""
137
+ if remote_mode is True:
138
+ # When remote_mode is True, some files may not exist or in write.
139
+ # We need to try to create the network again.
140
+ err = None
141
+ for _ in range(10):
142
+ try:
143
+ return cls(
144
+ path=Path(config.outputs) / NETWORK_PATH_PREFIX,
145
+ restart=restart
146
+ if restart is not None
147
+ else config.restart,
148
+ format=config.event.db_format,
149
+ remote_mode=remote_mode,
150
+ config=config.event,
151
+ **kwargs,
152
+ )
153
+ break
154
+ except Exception as e:
155
+ time.sleep(0.5)
156
+ err = e
157
+ if err is not None:
158
+ raise RuntimeError(
159
+ f"Failed to create the network in remote mode. {err}"
160
+ )
161
+ else:
162
+ return cls(
163
+ path=Path(config.outputs) / NETWORK_PATH_PREFIX,
164
+ restart=restart if restart is not None else config.restart,
165
+ format=config.event.db_format,
166
+ remote_mode=remote_mode,
167
+ config=config.event,
168
+ **kwargs,
169
+ )
170
+
171
+ @classmethod
172
+ def merge(
173
+ cls,
174
+ *networks: Self,
175
+ new_path: Path | str,
176
+ new_format: str | Literal["dir", "h5", "sqlite"] = "dir",
177
+ **kwargs,
178
+ ) -> Self:
179
+ """Merge the networks.
180
+
181
+ Note: the recorder will be set empty.
182
+ """
183
+ if len(networks) <= 1:
184
+ raise ValueError("At least two network is required.")
185
+ result = cls(
186
+ path=Path(new_path),
187
+ restart=False,
188
+ config=networks[0].metadata.basic,
189
+ format=new_format,
190
+ )
191
+ result.metadata.basic.db_format = new_format
192
+ for network in networks[1:]:
193
+ try:
194
+ network.metadata.basic.check_consistency(
195
+ result.metadata.basic,
196
+ exclude_keys={"db_format", "gas_sticking"},
197
+ )
198
+ for k, v in network.metadata.basic.gas_sticking.items():
199
+ if k not in result.metadata.basic.gas_sticking:
200
+ result.metadata.basic.gas_sticking[k] = v
201
+ else:
202
+ v0 = result.metadata.basic.gas_sticking[k]
203
+ assert v0 == v, (
204
+ f"Failed to merge the networks because the "
205
+ f"gas sticking of {k} is not the same as "
206
+ f"input. self={v0}, input={v}"
207
+ )
208
+ result.metadata.basic.gas_sticking[k] = v
209
+ except Exception as e:
210
+ raise ValueError(
211
+ f"Failed to merge the networks because "
212
+ f"of check consistency failed: {e}"
213
+ )
214
+ for k, use_pos_uuid in cls.DB_USE_POSITION_UUID_DICT.items():
215
+ db_res: DatabaseABC = getattr(result, f"db_{k}")
216
+ db: DatabaseABC = getattr(network, f"db_{k}")
217
+ for _, v in db.items():
218
+ graph = SysGraph.from_ase(v)
219
+ graph_key = graph.get_key_for_metadata(use_pos_uuid)
220
+ if graph_key not in db_res:
221
+ db_res._save(graph_key, graph)
222
+ for i, rxn in enumerate(network.metadata.table.key_rxn):
223
+ if rxn not in result.metadata.table.key_rxn:
224
+ key_r = network.metadata.table.key_r[i]
225
+ key_p = network.metadata.table.key_p[i]
226
+ key_g = network.metadata.table.key_g[i]
227
+ key_ts = network.metadata.table.key_t[i]
228
+ if key_r not in result.db_minima:
229
+ print(
230
+ f"Find invalid key: {rxn}\n"
231
+ f" Missing key of reactant: {key_r}\n",
232
+ flush=True,
233
+ )
234
+ continue
235
+ elif key_g is not None and key_g not in result.db_gas:
236
+ print(
237
+ f"Find invalid key: {rxn}\n"
238
+ f" Missing key of gas: {key_g}\n",
239
+ flush=True,
240
+ )
241
+ continue
242
+ elif key_ts is not None and key_ts not in result.db_ts:
243
+ print(
244
+ f"Find invalid key: {rxn}\n"
245
+ f" Missing key of transition state: {key_ts}\n",
246
+ flush=True,
247
+ )
248
+ continue
249
+ elif key_p is not None and key_p not in result.db_minima:
250
+ print(
251
+ f"Find invalid key: {rxn}\n"
252
+ f" Missing key of product: {key_p}\n",
253
+ flush=True,
254
+ )
255
+ continue
256
+ result.metadata.table.merge( # Output duplicate info
257
+ network.metadata.table, idxs=[i]
258
+ )
259
+ n = len(result.metadata.table.key_rxn)
260
+ print(f"Merge networks({n} events) done.", flush=True)
261
+ result.scheduler.clear()
262
+ result.recorder.clear()
263
+ result.persistence()
264
+ return result
265
+
266
+ @classmethod
267
+ def reformat_message(cls, msg: str) -> str:
268
+ return msg
269
+ __LOGURU_FORMAT_LENGTH: int = 37
270
+ __LOGURU_TOTAL_LENGTH: int = 120
271
+ len_msg = __LOGURU_TOTAL_LENGTH - __LOGURU_FORMAT_LENGTH
272
+ lst = wrap_line(msg, width_min=len_msg, width_max=len_msg + 20)
273
+ return ("\n" + " " * __LOGURU_FORMAT_LENGTH).join(lst)
274
+
275
+ @override
276
+ def __len__(self) -> int:
277
+ return len(self.metadata.table.key_rxn)
278
+
279
+ @override
280
+ def __contains__(self, key: str) -> bool: # type: ignore
281
+ return key in self.metadata.table.key_rxn
282
+
283
+ @override
284
+ def __getitem__(self, key: str) -> tuple[EventInfo, EventBase]:
285
+ return self.read_event(key)
286
+
287
+ @override
288
+ def __iter__(self) -> Iterator[str]:
289
+ yield from self.metadata.table.key_rxn
290
+
121
291
  @property
122
292
  def path(self) -> Path:
123
293
  return self.__path
@@ -186,76 +356,6 @@ class ReactionNetwork:
186
356
  )
187
357
  return self.__db_ts
188
358
 
189
- @classmethod
190
- def from_config(
191
- cls,
192
- config: Config,
193
- *args,
194
- restart: bool | None = None,
195
- remote_mode: bool = False,
196
- **kwargs,
197
- ) -> Self: # type: ignore
198
- """Create a network from a config file."""
199
- if remote_mode is True:
200
- # When remote_mode is True, some files may not exist or in write.
201
- # We need to try to create the network again.
202
- err = None
203
- for _ in range(10):
204
- try:
205
- return cls(
206
- path=Path(config.outputs) / NETWORK_PATH_PREFIX,
207
- restart=restart
208
- if restart is not None
209
- else config.restart,
210
- format=config.event.db_format,
211
- remote_mode=remote_mode,
212
- config=config.event,
213
- **kwargs,
214
- )
215
- break
216
- except Exception as e:
217
- time.sleep(0.5)
218
- err = e
219
- if err is not None:
220
- raise RuntimeError(
221
- f"Failed to create the network in remote mode. {err}"
222
- )
223
- else:
224
- return cls(
225
- path=Path(config.outputs) / NETWORK_PATH_PREFIX,
226
- restart=restart if restart is not None else config.restart,
227
- format=config.event.db_format,
228
- remote_mode=remote_mode,
229
- config=config.event,
230
- **kwargs,
231
- )
232
-
233
- @classmethod
234
- def merge(
235
- cls,
236
- *networks: Self,
237
- new_path: Path | str,
238
- new_format: str | Literal["dir", "h5", "sqlite"] = "dir",
239
- **kwargs,
240
- ) -> Self:
241
- """Merge the networks."""
242
- raise NotImplementedError("Merge the networks.")
243
- cls(
244
- path=new_path,
245
- restart=False,
246
- config=networks[0].metadata.basic,
247
- format=networks[0].__format,
248
- )
249
-
250
- @classmethod
251
- def reformat_message(cls, msg: str) -> str:
252
- return msg
253
- __LOGURU_FORMAT_LENGTH: int = 37
254
- __LOGURU_TOTAL_LENGTH: int = 120
255
- len_msg = __LOGURU_TOTAL_LENGTH - __LOGURU_FORMAT_LENGTH
256
- lst = wrap_line(msg, width_min=len_msg, width_max=len_msg + 20)
257
- return ("\n" + " " * __LOGURU_FORMAT_LENGTH).join(lst)
258
-
259
359
  def exploration_can_be_finished(
260
360
  self,
261
361
  confidence: float,
@@ -311,7 +411,8 @@ class ReactionNetwork:
311
411
  msg += f" #Minima :{len(self.__db_minima)}\n"
312
412
  msg += f" #Gas :{len(self.__db_gas)}\n"
313
413
  msg += f" #TS :{len(self.__db_ts)}\n"
314
- msg += f"#Event :{len(self.__metadata.table)}\n"
414
+ msg += f"#Event :{len(self.__metadata.table)}\n"
415
+ msg += f"Configuration :{self.__metadata.basic.to_str(indent=4)}\n"
315
416
  return msg
316
417
 
317
418
  def persistence(self) -> None:
@@ -397,6 +498,7 @@ class ReactionNetwork:
397
498
  *,
398
499
  for_system: str | None = None,
399
500
  persist: bool = True,
501
+ env_hop: int = 1, # TODO: make this into whole configuration
400
502
  **kwargs,
401
503
  ) -> Literal["new", "old", "fail"] | str:
402
504
  """Record the event as found.
@@ -425,13 +527,22 @@ class ReactionNetwork:
425
527
  simplified_threshold = self.__metadata.basic.simplified_threshold
426
528
  if simplified_threshold > 0:
427
529
  try:
428
- event = event.simplify(simplified_threshold)
530
+ event = event.simplify(
531
+ env_radius=simplified_threshold,
532
+ env_hop=env_hop,
533
+ )
429
534
  except Exception as e:
430
535
  msg = f"Event {event._string()} failed "
431
536
  msg += f"to simplify. because of {e}"
432
537
  fname = self.__path / "event-simplify-fail.pkl"
433
- fname.write_bytes(pickle.dumps(event))
434
538
  fname.with_suffix(".err").write_text(msg)
539
+ fname.write_bytes(pickle.dumps(event))
540
+ event.R.write_npz(fname.with_suffix(".R.npz"))
541
+ event.P.write_npz(fname.with_suffix(".P.npz"))
542
+ if event.G is not None:
543
+ event.G.write_npz(fname.with_suffix(".G.npz"))
544
+ if event.T is not None:
545
+ event.T.write_npz(fname.with_suffix(".T.npz"))
435
546
  raise ValueError(msg)
436
547
  if self.write_event(
437
548
  event,
@@ -659,3 +770,31 @@ def wrap_line(
659
770
  lines.pop()
660
771
 
661
772
  return lines
773
+
774
+
775
+ if __name__ == "__main__":
776
+ import json
777
+ import shutil
778
+ from pathlib import Path
779
+
780
+ this_dir = Path(__file__).parent
781
+ root_path = this_dir.parent.parent.parent.parent
782
+ print(root_path)
783
+
784
+ new_path = root_path / "zzzz-merged" / "event"
785
+ shutil.rmtree(new_path.parent, ignore_errors=True)
786
+
787
+ network_lst: list[ReactionNetwork] = []
788
+ for zzzz_path in root_path.glob("zzzz-*"):
789
+ if "merge" not in zzzz_path.name:
790
+ p = zzzz_path / "event"
791
+ (p / "recorder.json").unlink(missing_ok=True)
792
+ with open(p / "recorder.json", "w") as f:
793
+ json.dump({}, f, indent=4)
794
+ network_lst.append(
795
+ ReactionNetwork(
796
+ zzzz_path / "event",
797
+ restart=True,
798
+ )
799
+ )
800
+ new_network = ReactionNetwork.merge(*network_lst, new_path=new_path)
@@ -0,0 +1,139 @@
1
+ import pathlib
2
+ from typing import Annotated
3
+
4
+ import pandas as pd
5
+ import typer
6
+ from ase import Atoms
7
+ from ase.visualize import view as _ase_view
8
+
9
+ from graphatoms.enterpoint.network import ReactionNetwork as Net
10
+ from graphatoms.system import SysGraph
11
+
12
+ __p_help__ = "Path to the network directory."
13
+ Path = Annotated[str, typer.Option("--path", "-p", help=__p_help__)]
14
+ pd.set_option("display.max_colwidth", None) # don't truncate cell contents
15
+ pd.set_option("display.max_columns", None) # show all columns
16
+ pd.set_option("display.max_rows", None) # show all rows
17
+ pd.set_option("display.width", None) # don't wrap lines
18
+
19
+
20
+ # --------------
21
+ # Main command
22
+ # --------------
23
+ main = typer.Typer(no_args_is_help=True)
24
+
25
+
26
+ @main.command()
27
+ def summary(path: Path = ".") -> None:
28
+ assert pathlib.Path(path).exists()
29
+ net = Net(path, restart=True, format="dir")
30
+ print(net.summary())
31
+
32
+
33
+ @main.command(no_args_is_help=True)
34
+ def merge(
35
+ args: Annotated[
36
+ list[str],
37
+ typer.Argument(
38
+ help=f"{Net.__name__} directories to merge (one or more)"
39
+ ),
40
+ ],
41
+ path: Path = "./event",
42
+ db_format: Annotated[
43
+ str,
44
+ typer.Option(
45
+ "--db-format",
46
+ "-f",
47
+ help="Database format in the network.",
48
+ ),
49
+ ] = "dir",
50
+ ) -> None:
51
+ assert db_format == "dir", "Only `dir` format is supported now."
52
+ lst = [Net(p, restart=True, format=db_format) for p in args]
53
+ Net.merge(*lst, new_path=path)
54
+
55
+
56
+ # ----------------
57
+ # Table subcommand
58
+ # ----------------
59
+ show = typer.Typer(no_args_is_help=True)
60
+ main.add_typer(show, name="show")
61
+
62
+
63
+ @show.command(name="reaction")
64
+ def show_reaction(arg: str, path: Path = ".") -> tuple[int, Net]:
65
+ net = Net(path, restart=True, format="dir")
66
+ df = net.metadata.table.dataframe
67
+ try:
68
+ i = int(arg)
69
+ except ValueError:
70
+ i = net.metadata.table.key_rxn.index(arg)
71
+ print(df.iloc[i])
72
+ return i, net
73
+
74
+
75
+ @show.command()
76
+ def table(path: Path = ".") -> None:
77
+ assert pathlib.Path(path).exists()
78
+ net = Net(path, restart=True, format="dir")
79
+ df = net.metadata.table.dataframe
80
+ col = [0, 5, 6, 7, 8, 11, 12]
81
+ print(df[df.columns[col]])
82
+
83
+
84
+ # ----------------
85
+ # View subcommand
86
+ # ----------------
87
+ view = typer.Typer(no_args_is_help=True)
88
+
89
+ main.add_typer(view, name="view")
90
+
91
+
92
+ @view.command(name="reaction")
93
+ def view_reaction(
94
+ name: str,
95
+ path: Path = ".",
96
+ forward: bool = True,
97
+ ) -> None:
98
+ idx, net = show_reaction(name, path)
99
+ key_rxn = net.metadata.table.key_rxn[idx]
100
+ _, rxn = net.read_event(key_rxn)
101
+ if not forward:
102
+ rxn = rxn.reversed
103
+ return rxn.view()
104
+
105
+
106
+ @view.command(name="structure")
107
+ def view_structure(
108
+ name_or_path: str,
109
+ path: Path = ".",
110
+ ) -> None:
111
+ try:
112
+ fname = pathlib.Path(name_or_path)
113
+ if fname.name.count("*") == 0:
114
+ assert fname.exists() and fname.is_file()
115
+ return _ase_view(SysGraph.read_npz(fname).to_ase())
116
+ else:
117
+ return _ase_view(
118
+ [
119
+ SysGraph.read_npz(i).to_ase()
120
+ for i in fname.parent.glob(fname.name)
121
+ ]
122
+ )
123
+ except ValueError:
124
+ name = name_or_path
125
+
126
+ assert pathlib.Path(path).exists()
127
+ net = Net(path, restart=True, format="dir")
128
+ atoms: Atoms | None = None
129
+ for type in ("gas", "ts", "minima", "cluster", "system"):
130
+ if net.contain_sysgraph(name, type=type):
131
+ atoms = net.read_atoms(name, type=type)
132
+ break
133
+ if atoms is None:
134
+ raise ValueError(f"structure {name} is not in the database.")
135
+ return _ase_view(atoms)
136
+
137
+
138
+ if __name__ == "__main__":
139
+ main()
@@ -21,12 +21,23 @@ class _MetaDataBasic(OurBaseModel, EventConfig):
21
21
  def from_config(cls, config: Config | EventConfig) -> Self:
22
22
  if isinstance(config, Config):
23
23
  config = config.event
24
+ if not isinstance(config, dict):
25
+ if isinstance(config, EventConfig):
26
+ config = dc.asdict(config) # type: ignore
27
+ elif isinstance(config, _MetaDataBasic):
28
+ config = config.to_dict()
24
29
  return cls(**config) # type: ignore
25
30
 
26
- def check_consistency(self, config: EventConfig | Self) -> None:
31
+ def check_consistency(
32
+ self,
33
+ config: EventConfig | Self,
34
+ exclude_keys: set[str] = {"db_format"},
35
+ ) -> None:
27
36
  msg = r"The keys of {} must be the same as input, "
28
37
  msg += r"but we got {}(from self) and {}(from input)"
29
38
  for k in self.__pydantic_fields__:
39
+ if k in exclude_keys:
40
+ continue
30
41
  v1 = getattr(self, k)
31
42
  v2 = getattr(config, k)
32
43
  if not isinstance(v1, dict):
@@ -99,6 +110,41 @@ class _MetaDataTable(BaseModel):
99
110
  def from_dataframe(cls, df: pd.DataFrame) -> Self:
100
111
  return cls(**{k: df[k].to_list() for k in df.columns})
101
112
 
113
+ def merge(self, other: Self, idxs: list[int] | None = None) -> None:
114
+ if idxs is None:
115
+ idxs = list(range(len(other)))
116
+ for i in idxs:
117
+ k = other.key_rxn[i]
118
+ if k not in self.key_rxn:
119
+ self.key_rxn.append(k)
120
+ self.key_r.append(other.key_r[i])
121
+ self.key_g.append(other.key_g[i])
122
+ self.key_t.append(other.key_t[i])
123
+ self.key_p.append(other.key_p[i])
124
+ self.Ea_forword.append(other.Ea_forword[i])
125
+ self.rate_forword.append(other.rate_forword[i])
126
+ self.rate_reversed.append(other.rate_reversed[i])
127
+ self.Ea_reversed.append(other.Ea_reversed[i])
128
+ self.for_cluster.append(other.for_cluster[i])
129
+ self.for_system.append(other.for_system[i])
130
+ self.count.append(other.count[i])
131
+ self.dE.append(other.dE[i])
132
+ else:
133
+ idx = self.key_rxn.index(k)
134
+ self.count[idx] += other.count[i]
135
+ len(self.key_r[idx])
136
+ print(
137
+ f"Find duplicate key: {k}\n"
138
+ f" dE: (old={self.dE[idx]},new={other.dE[i]})\n"
139
+ f" Ea: (old={self.Ea_forword[idx]},"
140
+ f"new={other.Ea_forword[i]})\n"
141
+ f" key_r:(old={self.key_r[idx]},new={other.key_r[i]})\n"
142
+ f" key_g:(old={self.key_g[idx]},new={other.key_g[i]})\n"
143
+ f" key_t:(old={self.key_t[idx]},new={other.key_t[i]})\n"
144
+ f" key_p:(old={self.key_p[idx]},new={other.key_p[i]})\n",
145
+ flush=True,
146
+ )
147
+
102
148
  def get_minconut_for(
103
149
  self,
104
150
  *,
@@ -22,6 +22,7 @@ Ref:
22
22
  Fe-H system. PhD thesis, University of Cambridge, 2024.
23
23
  """
24
24
 
25
+ from abc import abstractmethod
25
26
  from collections import defaultdict
26
27
  from datetime import datetime
27
28
  from typing import Annotated, Any, Self, override
@@ -180,16 +181,32 @@ class RecorderBase(OurBaseModel):
180
181
  + "have been explored"
181
182
  )
182
183
 
184
+ @abstractmethod
185
+ def clear(self) -> None:
186
+ self.cluster.clear()
187
+
183
188
 
184
189
  class Recorder(RecorderBase):
185
190
  adsorption: set[str] = set()
186
191
  bulk: set[str] = set()
187
192
 
193
+ @override
194
+ def clear(self) -> None:
195
+ self.adsorption.clear()
196
+ self.bulk.clear()
197
+ super().clear()
198
+
188
199
 
189
200
  class RecorderV2(RecorderBase):
190
201
  adsorption: dict[tuple[str, str], set[str]] = defaultdict(set)
191
202
  bulk: dict[str, set[str]] = defaultdict(set)
192
203
 
204
+ @override
205
+ def clear(self) -> None:
206
+ self.adsorption.clear()
207
+ self.bulk.clear()
208
+ super().clear()
209
+
193
210
  def exploration_can_be_finished(
194
211
  self,
195
212
  cluster_key: str,
@@ -20,6 +20,10 @@ class Scheduler(BaseModel):
20
20
  msg = " ".join(f"{k}:{len(v)}dR" for k, v in self.dR.items())
21
21
  return f"{self.__class__.__name__}({msg})"
22
22
 
23
+ def clear(self) -> None:
24
+ self.is_fixed.clear()
25
+ self.dR.clear()
26
+
23
27
  @validate_call
24
28
  def write_npz(
25
29
  self,