GraphAtoms 2.3.0__tar.gz → 2.3.2__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.2}/PKG-INFO +1 -1
  2. {graphatoms-2.3.0 → graphatoms-2.3.2}/pyproject.toml +2 -2
  3. {graphatoms-2.3.0 → graphatoms-2.3.2}/pyproject.toml.orig +2 -2
  4. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/dataclasses/_pydanticMixin.py +38 -20
  5. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/network/__init__.py +2 -0
  6. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/network/_clsnetwork.py +231 -84
  7. graphatoms-2.3.2/src/graphatoms/enterpoint/network/_commands.py +139 -0
  8. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/network/_metadata.py +52 -2
  9. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/network/_recorder.py +17 -0
  10. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/network/_scheduler.py +13 -2
  11. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/parallel/_ray.py +23 -4
  12. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/common/_base.py +6 -3
  13. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/common/_helper.py +7 -5
  14. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/general/otfkmc.py +5 -1
  15. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/general/_expl.py +11 -0
  16. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/ray/_expl.py +8 -9
  17. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/ray/_helper.py +4 -4
  18. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/geometry/sample.py +7 -1
  19. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/reaction/_event.py +56 -18
  20. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/system/bonds.py +9 -5
  21. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/system/database/abc.py +9 -5
  22. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/system/sysCluster.py +44 -6
  23. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/utils/adsorption.py +6 -2
  24. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/utils/bytestool.py +52 -1
  25. graphatoms-2.3.0/src/graphatoms/enterpoint/view.py +0 -118
  26. {graphatoms-2.3.0 → graphatoms-2.3.2}/README.md +0 -0
  27. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/LICENSE +0 -0
  28. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/__init__.py +0 -0
  29. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/arrayapi/__init__.py +0 -0
  30. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/arrayapi/compat.py +0 -0
  31. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/arrayapi/typing.py +0 -0
  32. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/dataclasses/__init__.py +0 -0
  33. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/dataclasses/_numpydantic.py +0 -0
  34. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/dataclasses/_pydantic2pyarrow.py +0 -0
  35. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/dataclasses/_pydanticModel.py +0 -0
  36. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/dataclasses/graph.py +0 -0
  37. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/__init__.py +0 -0
  38. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/__init__.py +0 -0
  39. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/atoms/__init__.py +0 -0
  40. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/atoms/aseread.yaml +0 -0
  41. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/atoms/octahedron.yaml +0 -0
  42. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/bonds/__init__.py +0 -0
  43. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/bonds/raw.yaml +0 -0
  44. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/calculator/__init__.py +0 -0
  45. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/calculator/emt.yaml +0 -0
  46. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/calculator/nequip.yaml +0 -0
  47. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/example.yaml +0 -0
  48. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/run.yaml +0 -0
  49. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/parallel/__init__.py +0 -0
  50. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/parallel/_base.py +0 -0
  51. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/parallel/_dask.py +0 -0
  52. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/parallel/_executorlib.py +0 -0
  53. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/__init__.py +0 -0
  54. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/common/__init__.py +0 -0
  55. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/common/_errors.py +0 -0
  56. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/general/__init__.py +0 -0
  57. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/general/_expl.py +0 -0
  58. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/general/rxngen.py +0 -0
  59. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/__init__.py +0 -0
  60. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/_helperv2.py +0 -0
  61. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/general/__init__.py +0 -0
  62. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/general/otfkmc.py +0 -0
  63. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/general/rxngen.py +0 -0
  64. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/ray/__init__.py +0 -0
  65. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/ray/otfkmc.py +0 -0
  66. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/ray/rxngen.py +0 -0
  67. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/geometry/__init__.py +0 -0
  68. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/geometry/_bond_list.py +0 -0
  69. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/geometry/_distance_pairs.py +0 -0
  70. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/geometry/_inner_outer.py +0 -0
  71. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/geometry/_neighbor_list.py +0 -0
  72. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/geometry/mic.py +0 -0
  73. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/geometry/rotation.py +0 -0
  74. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/reaction/__init__.py +0 -0
  75. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/reaction/reaction.py +0 -0
  76. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/reaction/xxsorption.py +0 -0
  77. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/system/__init__.py +0 -0
  78. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/system/atoms.py +0 -0
  79. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/system/database/__init__.py +0 -0
  80. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/system/database/dict.py +0 -0
  81. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/system/database/dir.py +0 -0
  82. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/system/database/hdf5.py +0 -0
  83. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/system/database/sqliteASE.py +0 -0
  84. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/system/graph.py +0 -0
  85. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/system/sysGas.py +0 -0
  86. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/system/system.py +0 -0
  87. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/utils/__init__.py +0 -0
  88. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/utils/aseqhull.py +0 -0
  89. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/utils/asetools.py +0 -0
  90. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/utils/logger.py +0 -0
  91. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/utils/parser.py +0 -0
  92. {graphatoms-2.3.0 → graphatoms-2.3.2}/src/graphatoms/utils/rdutils.py +0 -0
  93. {graphatoms-2.3.0 → graphatoms-2.3.2}/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.2
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.2"
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.2"
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" }]
@@ -229,7 +229,11 @@ class __Json(PydanticConvertFactoryMixin):
229
229
  indent=indent,
230
230
  )
231
231
  )
232
- filename.write_text(self.to_str(**kwargs), "utf-8")
232
+ bytesutils.atomic_write_text(
233
+ target=filename,
234
+ text=self.to_str(**kwargs),
235
+ encoding="utf-8",
236
+ )
233
237
  return filename
234
238
 
235
239
  @classmethod
@@ -260,13 +264,15 @@ class __Toml(PydanticConvertFactoryMixin):
260
264
  numpy_ndarray_compatible=True,
261
265
  )
262
266
  )
263
- with filename.open("wb") as f:
264
- toml_w.dump(
267
+ bytesutils.atomic_write_text(
268
+ target=filename,
269
+ text=toml_w.dumps(
265
270
  self.to_dict(**kwargs),
266
- f,
267
271
  indent=indent,
268
272
  multiline_strings=True,
269
- )
273
+ ),
274
+ encoding="utf-8",
275
+ )
270
276
  return filename
271
277
 
272
278
  @classmethod
@@ -301,16 +307,17 @@ class __Yaml(PydanticConvertFactoryMixin):
301
307
  numpy_ndarray_compatible=True,
302
308
  )
303
309
  )
304
- with filename.open("w", encoding="utf-8") as f:
305
- f.write(
306
- OmegaConf.to_yaml(
307
- self.to_dict(**kwargs),
308
- # f,
309
- # encoding="utf-8",
310
- # # default_flow_style=False,
311
- # indent=4,
312
- )
313
- )
310
+ bytesutils.atomic_write_text(
311
+ target=filename,
312
+ text=OmegaConf.to_yaml(
313
+ self.to_dict(**kwargs),
314
+ # f,
315
+ # encoding="utf-8",
316
+ # # default_flow_style=False,
317
+ # indent=4,
318
+ ),
319
+ encoding="utf-8",
320
+ )
314
321
  return filename
315
322
 
316
323
  @classmethod
@@ -345,10 +352,14 @@ class __Npz(PydanticConvertFactoryMixin):
345
352
  numpy_ndarray_compatible=True,
346
353
  )
347
354
  )
348
- (np.savez_compressed if compress else np.savez)(
349
- filename,
350
- allow_pickle=False,
351
- **self.to_dict(**kwargs),
355
+ func = np.savez_compressed if compress else np.savez
356
+ bytesutils.atomic_write(
357
+ target=filename,
358
+ writer=lambda p: func(
359
+ p,
360
+ allow_pickle=False,
361
+ **self.to_dict(**kwargs),
362
+ ),
352
363
  )
353
364
  return filename
354
365
 
@@ -411,7 +422,14 @@ class __Pickle(PydanticConvertFactoryMixin):
411
422
  numpy_ndarray_compatible=True,
412
423
  )
413
424
  )
414
- dump(self.to_dict(**kwargs), filename, compress=compress)
425
+ bytesutils.atomic_write(
426
+ target=filename,
427
+ writer=lambda p: dump(
428
+ value=self.to_dict(**kwargs),
429
+ filename=p,
430
+ compress=compress, # type: ignore
431
+ ),
432
+ )
415
433
  return filename
416
434
 
417
435
  @classmethod
@@ -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,
@@ -16,11 +27,7 @@ from graphatoms.reaction import (
16
27
  )
17
28
  from graphatoms.system import Cluster, Gas, SysGraph, System
18
29
  from graphatoms.system.database import DatabaseABC, get_db
19
-
20
- from ._metadata import MetaData
21
- from ._metadata import _MetaDataBasic as MetaDataBasic
22
- from ._recorder import OldNewRecorder4Confidence, Recorder, RecorderInfo
23
- from ._scheduler import Scheduler
30
+ from graphatoms.utils.bytestool import atomic_write, atomic_write_text
24
31
 
25
32
  __all__ = [
26
33
  "MetaData",
@@ -31,7 +38,7 @@ __all__ = [
31
38
  NETWORK_PATH_PREFIX = "event"
32
39
 
33
40
 
34
- class ReactionNetwork:
41
+ class ReactionNetwork(Mapping[str, tuple[EventInfo, EventBase]]):
35
42
  DB_USE_POSITION_UUID_DICT: dict[str, bool] = {
36
43
  "ts": True,
37
44
  "minima": True,
@@ -57,8 +64,8 @@ class ReactionNetwork:
57
64
  assert format in (
58
65
  "sqlite",
59
66
  "directory",
60
- "dir",
61
67
  "folder",
68
+ "dir",
62
69
  "h5",
63
70
  "hdf5",
64
71
  ), f"The format of `{format}` is not supported for restart."
@@ -82,8 +89,9 @@ class ReactionNetwork:
82
89
  + " Please delete it or use `restart=True`."
83
90
  )
84
91
  self.__path.mkdir(parents=True, exist_ok=True)
85
- self.__path_exploration_recorder.write_text(
86
- RecorderInfo.get_csv_title() + "\n"
92
+ atomic_write_text(
93
+ self.__path_exploration_recorder,
94
+ RecorderInfo.get_csv_title() + "\n",
87
95
  )
88
96
  self.__recorder = Recorder()
89
97
  self.__scheduler = Scheduler()
@@ -118,6 +126,171 @@ class ReactionNetwork:
118
126
  assert isinstance(self.__db_cluster, DatabaseABC)
119
127
  assert isinstance(self.__db_system, DatabaseABC)
120
128
 
129
+ @classmethod
130
+ def from_config(
131
+ cls,
132
+ config: Config,
133
+ *args,
134
+ restart: bool | None = None,
135
+ remote_retry_times: float = 2.0,
136
+ remote_mode: bool = False,
137
+ **kwargs,
138
+ ) -> Self: # type: ignore
139
+ """Create a network from a config file."""
140
+ if remote_mode is True:
141
+ # When remote_mode is True, some files may not exist or in write.
142
+ # We need to try to create the network again.
143
+ err = None
144
+ for _ in range(10):
145
+ try:
146
+ return cls(
147
+ path=Path(config.outputs) / NETWORK_PATH_PREFIX,
148
+ restart=restart
149
+ if restart is not None
150
+ else config.restart,
151
+ format=config.event.db_format,
152
+ remote_mode=remote_mode,
153
+ config=config.event,
154
+ **kwargs,
155
+ )
156
+ break
157
+ except Exception as e:
158
+ time.sleep(abs(remote_retry_times))
159
+ err = e
160
+ if err is not None:
161
+ raise RuntimeError(
162
+ f"Failed to create the network in remote mode. {err}"
163
+ )
164
+ else:
165
+ return cls(
166
+ path=Path(config.outputs) / NETWORK_PATH_PREFIX,
167
+ restart=restart if restart is not None else config.restart,
168
+ format=config.event.db_format,
169
+ remote_mode=remote_mode,
170
+ config=config.event,
171
+ **kwargs,
172
+ )
173
+
174
+ @classmethod
175
+ def merge(
176
+ cls,
177
+ *networks: Self,
178
+ new_path: Path | str,
179
+ new_format: str | Literal["dir", "h5", "sqlite"] = "dir",
180
+ **kwargs,
181
+ ) -> Self:
182
+ """Merge the networks.
183
+
184
+ Note: the recorder will be set empty.
185
+ """
186
+ if len(networks) <= 1:
187
+ raise ValueError("At least two network is required.")
188
+ result = cls(
189
+ path=Path(new_path),
190
+ restart=False,
191
+ config=networks[0].metadata.basic,
192
+ format=new_format,
193
+ )
194
+ result.metadata.basic.db_format = new_format
195
+ for network in networks[1:]:
196
+ try:
197
+ network.metadata.basic.check_consistency(
198
+ result.metadata.basic,
199
+ exclude_keys={"db_format", "gas_sticking"},
200
+ )
201
+ for k, v in network.metadata.basic.gas_sticking.items():
202
+ if k not in result.metadata.basic.gas_sticking:
203
+ result.metadata.basic.gas_sticking[k] = v
204
+ else:
205
+ v0 = result.metadata.basic.gas_sticking[k]
206
+ assert v0 == v, (
207
+ f"Failed to merge the networks because the "
208
+ f"gas sticking of {k} is not the same as "
209
+ f"input. self={v0}, input={v}"
210
+ )
211
+ result.metadata.basic.gas_sticking[k] = v
212
+ except Exception as e:
213
+ raise ValueError(
214
+ f"Failed to merge the networks because "
215
+ f"of check consistency failed: {e}"
216
+ )
217
+ for k, use_pos_uuid in cls.DB_USE_POSITION_UUID_DICT.items():
218
+ db_res: DatabaseABC = getattr(result, f"db_{k}")
219
+ db: DatabaseABC = getattr(network, f"db_{k}")
220
+ for _, v in db.items():
221
+ graph = SysGraph.from_ase(v)
222
+ graph_key = graph.get_key_for_metadata(use_pos_uuid)
223
+ if graph_key not in db_res:
224
+ db_res._save(graph_key, graph)
225
+ for i, rxn in enumerate(network.metadata.table.key_rxn):
226
+ if rxn not in result.metadata.table.key_rxn:
227
+ key_r = network.metadata.table.key_r[i]
228
+ key_p = network.metadata.table.key_p[i]
229
+ key_g = network.metadata.table.key_g[i]
230
+ key_ts = network.metadata.table.key_t[i]
231
+ if key_r not in result.db_minima:
232
+ print(
233
+ f"Find invalid key: {rxn}\n"
234
+ f" Missing key of reactant: {key_r}\n",
235
+ flush=True,
236
+ )
237
+ continue
238
+ elif key_g is not None and key_g not in result.db_gas:
239
+ print(
240
+ f"Find invalid key: {rxn}\n"
241
+ f" Missing key of gas: {key_g}\n",
242
+ flush=True,
243
+ )
244
+ continue
245
+ elif key_ts is not None and key_ts not in result.db_ts:
246
+ print(
247
+ f"Find invalid key: {rxn}\n"
248
+ f" Missing key of transition state: {key_ts}\n",
249
+ flush=True,
250
+ )
251
+ continue
252
+ elif key_p is not None and key_p not in result.db_minima:
253
+ print(
254
+ f"Find invalid key: {rxn}\n"
255
+ f" Missing key of product: {key_p}\n",
256
+ flush=True,
257
+ )
258
+ continue
259
+ result.metadata.table.merge( # Output duplicate info
260
+ network.metadata.table, idxs=[i]
261
+ )
262
+ n = len(result.metadata.table.key_rxn)
263
+ print(f"Merge networks({n} events) done.", flush=True)
264
+ result.scheduler.clear()
265
+ result.recorder.clear()
266
+ result.persistence()
267
+ return result
268
+
269
+ @classmethod
270
+ def reformat_message(cls, msg: str) -> str:
271
+ return msg
272
+ __LOGURU_FORMAT_LENGTH: int = 37
273
+ __LOGURU_TOTAL_LENGTH: int = 120
274
+ len_msg = __LOGURU_TOTAL_LENGTH - __LOGURU_FORMAT_LENGTH
275
+ lst = wrap_line(msg, width_min=len_msg, width_max=len_msg + 20)
276
+ return ("\n" + " " * __LOGURU_FORMAT_LENGTH).join(lst)
277
+
278
+ @override
279
+ def __len__(self) -> int:
280
+ return len(self.metadata.table.key_rxn)
281
+
282
+ @override
283
+ def __contains__(self, key: str) -> bool: # type: ignore
284
+ return key in self.metadata.table.key_rxn
285
+
286
+ @override
287
+ def __getitem__(self, key: str) -> tuple[EventInfo, EventBase]:
288
+ return self.read_event(key)
289
+
290
+ @override
291
+ def __iter__(self) -> Iterator[str]:
292
+ yield from self.metadata.table.key_rxn
293
+
121
294
  @property
122
295
  def path(self) -> Path:
123
296
  return self.__path
@@ -186,76 +359,6 @@ class ReactionNetwork:
186
359
  )
187
360
  return self.__db_ts
188
361
 
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
362
  def exploration_can_be_finished(
260
363
  self,
261
364
  confidence: float,
@@ -311,7 +414,8 @@ class ReactionNetwork:
311
414
  msg += f" #Minima :{len(self.__db_minima)}\n"
312
415
  msg += f" #Gas :{len(self.__db_gas)}\n"
313
416
  msg += f" #TS :{len(self.__db_ts)}\n"
314
- msg += f"#Event :{len(self.__metadata.table)}\n"
417
+ msg += f"#Event :{len(self.__metadata.table)}\n"
418
+ msg += f"Configuration :{self.__metadata.basic.to_str(indent=4)}\n"
315
419
  return msg
316
420
 
317
421
  def persistence(self) -> None:
@@ -397,6 +501,7 @@ class ReactionNetwork:
397
501
  *,
398
502
  for_system: str | None = None,
399
503
  persist: bool = True,
504
+ env_hop: int = 1, # TODO: make this into whole configuration
400
505
  **kwargs,
401
506
  ) -> Literal["new", "old", "fail"] | str:
402
507
  """Record the event as found.
@@ -425,13 +530,25 @@ class ReactionNetwork:
425
530
  simplified_threshold = self.__metadata.basic.simplified_threshold
426
531
  if simplified_threshold > 0:
427
532
  try:
428
- event = event.simplify(simplified_threshold)
533
+ event = event.simplify(
534
+ env_radius=simplified_threshold,
535
+ env_hop=env_hop,
536
+ )
429
537
  except Exception as e:
430
538
  msg = f"Event {event._string()} failed "
431
539
  msg += f"to simplify. because of {e}"
432
540
  fname = self.__path / "event-simplify-fail.pkl"
433
- fname.write_bytes(pickle.dumps(event))
434
- fname.with_suffix(".err").write_text(msg)
541
+ atomic_write_text(fname.with_suffix(".err"), msg)
542
+ atomic_write(
543
+ fname,
544
+ writer=lambda p: p.write_bytes(pickle.dumps(event)),
545
+ )
546
+ event.R.write_npz(fname.with_suffix(".R.npz"))
547
+ event.P.write_npz(fname.with_suffix(".P.npz"))
548
+ if event.G is not None:
549
+ event.G.write_npz(fname.with_suffix(".G.npz"))
550
+ if event.T is not None:
551
+ event.T.write_npz(fname.with_suffix(".T.npz"))
435
552
  raise ValueError(msg)
436
553
  if self.write_event(
437
554
  event,
@@ -659,3 +776,33 @@ def wrap_line(
659
776
  lines.pop()
660
777
 
661
778
  return lines
779
+
780
+
781
+ if __name__ == "__main__":
782
+ import json
783
+ import shutil
784
+ from pathlib import Path
785
+
786
+ this_dir = Path(__file__).parent
787
+ root_path = this_dir.parent.parent.parent.parent
788
+ print(root_path)
789
+
790
+ new_path = root_path / "zzzz-merged" / "event"
791
+ shutil.rmtree(new_path.parent, ignore_errors=True)
792
+
793
+ network_lst: list[ReactionNetwork] = []
794
+ for zzzz_path in root_path.glob("zzzz-*"):
795
+ if "merge" not in zzzz_path.name:
796
+ p = zzzz_path / "event"
797
+ (p / "recorder.json").unlink(missing_ok=True)
798
+ atomic_write_text(
799
+ p / "recorder.json",
800
+ json.dumps({}, indent=4),
801
+ )
802
+ network_lst.append(
803
+ ReactionNetwork(
804
+ zzzz_path / "event",
805
+ restart=True,
806
+ )
807
+ )
808
+ 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()