GraphAtoms 2.1.0__tar.gz → 2.1.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 (84) hide show
  1. {graphatoms-2.1.0 → graphatoms-2.1.1}/PKG-INFO +1 -1
  2. {graphatoms-2.1.0 → graphatoms-2.1.1}/pyproject.toml +1 -1
  3. {graphatoms-2.1.0 → graphatoms-2.1.1}/pyproject.toml.orig +1 -1
  4. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/config/__init__.py +1 -4
  5. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/network/__init__.py +51 -23
  6. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/network/_metadata.py +3 -1
  7. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/network/_recorder.py +84 -6
  8. graphatoms-2.1.1/src/graphatoms/enterpoint/runner/__init__.py +32 -0
  9. graphatoms-2.1.1/src/graphatoms/enterpoint/runner/common/__init__.py +21 -0
  10. graphatoms-2.1.1/src/graphatoms/enterpoint/runner/common/_base.py +262 -0
  11. {graphatoms-2.1.0/src/graphatoms/enterpoint/runner → graphatoms-2.1.1/src/graphatoms/enterpoint/runner/common}/_helper.py +7 -7
  12. graphatoms-2.1.1/src/graphatoms/enterpoint/runner/general/__init__.py +0 -0
  13. graphatoms-2.1.0/src/graphatoms/enterpoint/runner/_base.py → graphatoms-2.1.1/src/graphatoms/enterpoint/runner/general/_expl.py +13 -258
  14. {graphatoms-2.1.0/src/graphatoms/enterpoint/runner → graphatoms-2.1.1/src/graphatoms/enterpoint/runner/general}/otfkmc.py +12 -6
  15. {graphatoms-2.1.0/src/graphatoms/enterpoint/runner → graphatoms-2.1.1/src/graphatoms/enterpoint/runner/general}/rxngen.py +2 -2
  16. graphatoms-2.1.1/src/graphatoms/enterpoint/runner/ray/__init__.py +0 -0
  17. graphatoms-2.1.1/src/graphatoms/enterpoint/runner/ray/_expl.py +36 -0
  18. graphatoms-2.1.1/src/graphatoms/enterpoint/runner/ray/otfkmc.py +9 -0
  19. graphatoms-2.1.1/src/graphatoms/enterpoint/runner/ray/rxngen.py +5 -0
  20. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/view.py +4 -4
  21. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/system/bonds.py +20 -13
  22. graphatoms-2.1.0/src/graphatoms/enterpoint/runner/__init__.py +0 -24
  23. {graphatoms-2.1.0 → graphatoms-2.1.1}/README.md +0 -0
  24. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/LICENSE +0 -0
  25. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/__init__.py +0 -0
  26. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/arrayapi/__init__.py +0 -0
  27. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/arrayapi/compat.py +0 -0
  28. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/arrayapi/typing.py +0 -0
  29. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/dataclasses/__init__.py +0 -0
  30. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/dataclasses/_numpydantic.py +0 -0
  31. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/dataclasses/_pydantic2pyarrow.py +0 -0
  32. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/dataclasses/_pydanticMixin.py +0 -0
  33. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/dataclasses/_pydanticModel.py +0 -0
  34. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/dataclasses/graph.py +0 -0
  35. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/__init__.py +0 -0
  36. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/config/atoms/__init__.py +0 -0
  37. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/config/atoms/aseread.yaml +0 -0
  38. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/config/atoms/octahedron.yaml +0 -0
  39. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/config/bonds/__init__.py +0 -0
  40. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/config/bonds/raw.yaml +0 -0
  41. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/config/calculator/__init__.py +0 -0
  42. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/config/calculator/emt.yaml +0 -0
  43. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/config/calculator/nequip.yaml +0 -0
  44. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/config/example.yaml +0 -0
  45. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/config/run.yaml +0 -0
  46. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/network/_scheduler.py +0 -0
  47. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/parallel/__init__.py +0 -0
  48. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/parallel/_base.py +0 -0
  49. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/parallel/_dask.py +0 -0
  50. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/parallel/_executorlib.py +0 -0
  51. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/enterpoint/parallel/_ray.py +0 -0
  52. {graphatoms-2.1.0/src/graphatoms/enterpoint/runner → graphatoms-2.1.1/src/graphatoms/enterpoint/runner/common}/_errors.py +0 -0
  53. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/geometry/__init__.py +0 -0
  54. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/geometry/_bond_list.py +0 -0
  55. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/geometry/_distance_pairs.py +0 -0
  56. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/geometry/_inner_outer.py +0 -0
  57. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/geometry/_neighbor_list.py +0 -0
  58. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/geometry/mic.py +0 -0
  59. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/geometry/rotation.py +0 -0
  60. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/geometry/sample.py +0 -0
  61. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/reaction/__init__.py +0 -0
  62. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/reaction/_event.py +0 -0
  63. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/reaction/reaction.py +0 -0
  64. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/reaction/xxsorption.py +0 -0
  65. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/system/__init__.py +0 -0
  66. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/system/atoms.py +0 -0
  67. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/system/database/__init__.py +0 -0
  68. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/system/database/abc.py +0 -0
  69. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/system/database/dict.py +0 -0
  70. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/system/database/dir.py +0 -0
  71. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/system/database/hdf5.py +0 -0
  72. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/system/database/sqliteASE.py +0 -0
  73. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/system/graph.py +0 -0
  74. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/system/sysCluster.py +0 -0
  75. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/system/sysGas.py +0 -0
  76. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/system/system.py +0 -0
  77. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/utils/__init__.py +0 -0
  78. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/utils/adsorption.py +0 -0
  79. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/utils/asetools.py +0 -0
  80. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/utils/bytestool.py +0 -0
  81. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/utils/logger.py +0 -0
  82. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/utils/parser.py +0 -0
  83. {graphatoms-2.1.0 → graphatoms-2.1.1}/src/graphatoms/utils/rdutils.py +0 -0
  84. {graphatoms-2.1.0 → graphatoms-2.1.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.1.0
3
+ Version: 2.1.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.1.0"
7
+ version = "2.1.1"
8
8
  readme = "README.md"
9
9
  description = "The Chemical Core Class for Graph Theory Analysis."
10
10
  license-files = ["src/LICENSE"]
@@ -12,7 +12,7 @@ graphatoms-network = "graphatoms.enterpoint.view:main"
12
12
  graphatoms-run = "graphatoms.enterpoint.runner:run"
13
13
  [project]
14
14
  name = "graphatoms"
15
- version = "2.1.0"
15
+ version = "2.1.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" }]
@@ -66,7 +66,7 @@ class Config:
66
66
 
67
67
  calculator: CalcConfig = MISSING
68
68
 
69
- outputs: str = "./zzz"
69
+ outputs: str = "."
70
70
  max_steps: int = 100000
71
71
  max_times: float = 1000.0 # seconds
72
72
  run_type: str = "otfkmc" # otfkmc, rxngen
@@ -98,8 +98,6 @@ cs.store(group="calculator", name="calc_nequip", node=NequipConfig)
98
98
  version_base=None,
99
99
  )
100
100
  def print_config(cfg: Config) -> None:
101
- from shutil import rmtree
102
-
103
101
  print(OmegaConf.to_yaml(cfg))
104
102
  print("""
105
103
  hydra:
@@ -107,4 +105,3 @@ hydra:
107
105
  dir: ${outputs}
108
106
  verbose: ${debug}
109
107
  """)
110
- rmtree(Path(cfg.outputs))
@@ -4,8 +4,14 @@ from pathlib import Path
4
4
  from typing import Literal
5
5
 
6
6
  from graphatoms.enterpoint.config import EventConfig
7
- from graphatoms.reaction import EventBase, EventInfo, Reaction
8
- from graphatoms.system import Cluster, SysGraph
7
+ from graphatoms.reaction import (
8
+ Adsorption,
9
+ Desorption,
10
+ EventBase,
11
+ EventInfo,
12
+ Reaction,
13
+ )
14
+ from graphatoms.system import Gas, SysGraph
9
15
  from graphatoms.system.database import DatabaseABC, get_db
10
16
 
11
17
  from ._metadata import MetaData
@@ -14,14 +20,14 @@ from ._recorder import Recorder
14
20
  from ._scheduler import Scheduler
15
21
 
16
22
  __all__ = [
17
- "RxNet",
18
23
  "MetaData",
19
24
  "Recorder",
20
25
  "Scheduler",
26
+ "ReactionNetwork",
21
27
  ]
22
28
 
23
29
 
24
- class RxNet:
30
+ class ReactionNetwork:
25
31
  def __init__(
26
32
  self,
27
33
  path: Path | str,
@@ -81,16 +87,26 @@ class RxNet:
81
87
  self.metadata: MetaData = MetaData()
82
88
 
83
89
  # initialize the databases
84
- lst: list[DatabaseABC] = [
90
+ (
91
+ self.db_ts,
92
+ self.db_gas,
93
+ self.db_minima,
94
+ self.db_cluster,
95
+ self.db_system,
96
+ ) = [
85
97
  get_db(
86
98
  path=path,
87
99
  format=format,
88
100
  prefix=prefix,
89
101
  append=restart,
90
102
  )
91
- for prefix in ["ts", "gas", "minima"]
103
+ for prefix in ["ts", "gas", "minima", "cluster", "system"]
92
104
  ]
93
- self.db_ts, self.db_gas, self.db_minima = lst
105
+ assert isinstance(self.db_ts, DatabaseABC)
106
+ assert isinstance(self.db_gas, DatabaseABC)
107
+ assert isinstance(self.db_minima, DatabaseABC)
108
+ assert isinstance(self.db_cluster, DatabaseABC)
109
+ assert isinstance(self.db_system, DatabaseABC)
94
110
 
95
111
  def summary(self) -> str:
96
112
  """Return the summary of the network."""
@@ -102,16 +118,24 @@ class RxNet:
102
118
  self.scheduler.write_npz(self.__path / "scheduler.npz")
103
119
  self.metadata.persistence(self.__path)
104
120
 
105
- def read(self, key: str) -> tuple[EventInfo, EventBase]:
121
+ def read_event(self, key: str) -> tuple[EventInfo, EventBase]:
122
+ """Read the event from the database."""
106
123
  if self.metadata.has(key):
107
124
  info = self.metadata.read(key)
108
125
  if info.key_g is not None:
109
- raise NotImplementedError("Ads/Des is not supported.")
126
+ assert info.key_t is None
127
+ g = Gas.from_ase(self.db_gas[info.key_g])
128
+ r = SysGraph.from_ase(self.db_minima[info.key_r])
129
+ p = SysGraph.from_ase(self.db_minima[info.key_p])
130
+ if r.natoms < p.natoms:
131
+ return info, Adsorption(G=g, R=r, P=p)
132
+ else:
133
+ return info, Desorption(G=g, R=r, P=p)
110
134
  else:
111
135
  assert info.key_t is not None
112
- ts = Cluster.from_ase(self.db_ts[info.key_t])
113
- r = Cluster.from_ase(self.db_minima[info.key_r])
114
- p = Cluster.from_ase(self.db_minima[info.key_p])
136
+ ts = SysGraph.from_ase(self.db_ts[info.key_t])
137
+ r = SysGraph.from_ase(self.db_minima[info.key_r])
138
+ p = SysGraph.from_ase(self.db_minima[info.key_p])
115
139
  return info, Reaction(T=ts, R=r, P=p)
116
140
  else:
117
141
  raise ValueError(f"Event {key} is not in the database.")
@@ -143,7 +167,7 @@ class RxNet:
143
167
  fname.write_bytes(pickle.dumps(event))
144
168
  fname.with_suffix(".err").write_text(msg)
145
169
  raise ValueError(msg)
146
- is_new = self._write(
170
+ is_new = self.write_event(
147
171
  event,
148
172
  for_cluster,
149
173
  for_system,
@@ -154,7 +178,7 @@ class RxNet:
154
178
  else:
155
179
  raise ValueError(f"Unknown event type: {type(event)}")
156
180
 
157
- def _write(
181
+ def write_event(
158
182
  self,
159
183
  event: EventBase,
160
184
  for_cluster: str,
@@ -170,12 +194,12 @@ class RxNet:
170
194
  else:
171
195
  self.metadata.write(event, for_cluster, for_system)
172
196
  try:
173
- self.__write(event.R, "minima")
174
- self.__write(event.P, "minima")
197
+ self.write_sysgraph(event.R, "minima")
198
+ self.write_sysgraph(event.P, "minima")
175
199
  if event.G is not None:
176
- self.__write(event.G, "gas")
200
+ self.write_sysgraph(event.G, "gas")
177
201
  if event.T is not None:
178
- self.__write(event.T, "ts")
202
+ self.write_sysgraph(event.T, "ts")
179
203
  if persist:
180
204
  self.persistence()
181
205
  return True
@@ -201,17 +225,21 @@ class RxNet:
201
225
  self.persistence()
202
226
  raise e
203
227
 
204
- def __write(
228
+ def write_sysgraph(
205
229
  self,
206
230
  sysgraph: SysGraph,
207
- type: Literal["minima", "ts", "gas"] | str,
231
+ type: Literal["minima", "ts", "gas", "cluster", "system"] | str,
208
232
  ) -> bool:
209
233
  """Return True if the value is new, False otherwise."""
210
- if type == "minima":
234
+ if type.lower() == "minima":
211
235
  return self.db_minima.add(sysgraph, check_positions=True)
212
- elif type == "ts":
236
+ elif type.lower() == "ts":
213
237
  return self.db_ts.add(sysgraph, check_positions=True)
214
- elif type == "gas":
238
+ elif type.lower() == "gas":
215
239
  return self.db_gas.add(sysgraph, check_positions=False)
240
+ elif type.lower() == "system":
241
+ return self.db_system.add(sysgraph, check_positions=True)
242
+ elif type.lower() == "cluster":
243
+ return self.db_cluster.add(sysgraph, check_positions=True)
216
244
  else:
217
245
  raise ValueError(f"Unknown type: {type}")
@@ -14,6 +14,7 @@ from pydantic import (
14
14
  from graphatoms.dataclasses import OurBaseModel
15
15
  from graphatoms.enterpoint.config import EventConfig
16
16
  from graphatoms.reaction import EventBase, EventInfo
17
+ from graphatoms.system.bonds import matchmode2nmatch
17
18
 
18
19
  __all__ = ["MetaData"]
19
20
 
@@ -181,7 +182,8 @@ class MetaData(BaseModel):
181
182
  names = ["rxn_key", "is_forward", "nmatched", "rate"]
182
183
  for (rxn_key, is_forward), single_match_res in matching_dct.items():
183
184
  i: int = self.table.key_rxn.index(rxn_key)
184
- nmatched = int(single_match_res.shape[0])
185
+ # Eliminate the overestimation caused by permutation symmetry.
186
+ nmatched = matchmode2nmatch(single_match_res)
185
187
  if is_forward:
186
188
  rate = self.table.rate_forword[i]
187
189
  else:
@@ -23,14 +23,15 @@ Ref:
23
23
  """
24
24
 
25
25
  from collections import defaultdict
26
- from typing import override
26
+ from datetime import datetime
27
+ from typing import Annotated, override
27
28
 
28
29
  import pydantic
29
30
 
30
31
  from graphatoms.dataclasses import OurBaseModel # type: ignore
31
32
 
32
33
 
33
- class _OldNewRecorder(OurBaseModel):
34
+ class _RecorderBase(pydantic.BaseModel):
34
35
  old: pydantic.NonNegativeInt = 0
35
36
  new: pydantic.NonNegativeInt = 0
36
37
  fail: pydantic.NonNegativeInt = 0
@@ -47,6 +48,70 @@ class _OldNewRecorder(OurBaseModel):
47
48
  """
48
49
  return sum([self.old, self.new, self.fail])
49
50
 
51
+ class RecorderInfo(_RecorderBase):
52
+ create_at: datetime = pydantic.Field(default_factory=lambda: datetime.now())
53
+ confidence: Annotated[float, pydantic.Field(ge=0.5, lt=1.0)]
54
+ for_cluster: str = pydantic.Field(default="")
55
+ for_system: str = pydantic.Field(default="")
56
+ for_gas: str = pydantic.Field(default="")
57
+
58
+ @classmethod
59
+ def get_csv_title(cls) -> str:
60
+ return ",".join(
61
+ [
62
+ "old",
63
+ "new",
64
+ "fail",
65
+ "total",
66
+ "skip",
67
+ "continuous_old",
68
+ "continuous_nonnew",
69
+ "confidence",
70
+ "create_at",
71
+ "for_cluster",
72
+ "for_system",
73
+ "for_gas",
74
+ ]
75
+ )
76
+
77
+ def to_csv_line(self) -> str:
78
+ return ",".join(
79
+ [
80
+ f"{self.old}",
81
+ f"{self.new}",
82
+ f"{self.fail}",
83
+ f"{self.total}",
84
+ f"{self.skip}",
85
+ f"{self.continuous_old}",
86
+ f"{self.continuous_nonnew}",
87
+ f"{self.confidence}",
88
+ f"{self.create_at.strftime('%Y-%m-%d %H:%M:%S.%f')}",
89
+ f"{self.for_cluster}",
90
+ f"{self.for_system}",
91
+ f"{self.for_gas}",
92
+ ]
93
+ )
94
+
95
+
96
+ class _OldNewRecorder(OurBaseModel, _RecorderBase):
97
+ @pydantic.validate_call
98
+ def get_williams_confidence(
99
+ self, min_found: pydantic.NonNegativeInt
100
+ ) -> Annotated[float, pydantic.Field(ge=0.5, lt=1.0)]:
101
+ """The Williams confidence formula.
102
+
103
+ Args:
104
+ min_found: The minimum time found for all events.
105
+
106
+ Returns:
107
+ The confidence value.
108
+ """
109
+ m = self.continuous_nonnew
110
+ factor = min_found / self.total
111
+ factor2 = 1 / (1 + factor)
112
+ value = 1 / (1 + factor2**m)
113
+ return value
114
+
50
115
  @pydantic.validate_call
51
116
  def exploration_can_be_finished(
52
117
  self,
@@ -61,10 +126,7 @@ class _OldNewRecorder(OurBaseModel):
61
126
  "min_found must be provided for Williams"
62
127
  + " formula (i.e. confidence < 1)."
63
128
  )
64
- m = self.continuous_nonnew
65
- factor = min_found / self.total
66
- factor2 = 1 / (1 + factor)
67
- value = 1 / (1 + factor2**m)
129
+ value = self.get_williams_confidence(min_found)
68
130
  else:
69
131
  # Use Xu-Henkelman formula
70
132
  value = self.continuous_old
@@ -119,8 +181,10 @@ class Recorder(OurBaseModel):
119
181
 
120
182
 
121
183
  if __name__ == "__main__":
184
+ from io import StringIO
122
185
  from pathlib import Path
123
186
 
187
+ import pandas as pd
124
188
  obj = Recorder()
125
189
  obj.system.add("fdsafs")
126
190
  obj.cluster["fdsafs"].new += 1
@@ -128,3 +192,17 @@ if __name__ == "__main__":
128
192
  print(obj)
129
193
  print(repr(obj))
130
194
  obj.write_json(Path("a.json"))
195
+
196
+ io = StringIO()
197
+ io.write(RecorderInfo.get_csv_title() + "\n")
198
+ a = RecorderInfo(confidence=0.6)
199
+ io.write(RecorderInfo(confidence=0.5).to_csv_line() + "\n")
200
+ io.write(RecorderInfo(confidence=0.5).to_csv_line() + "\n")
201
+ io.write(a.to_csv_line() + "\n")
202
+
203
+ s = io.getvalue()
204
+ print(s)
205
+ df = pd.read_csv(StringIO(s))
206
+ print("df:")
207
+ print(df)
208
+ print(df.sort_values(by="create_at", ascending=True))
@@ -0,0 +1,32 @@
1
+ import os
2
+
3
+ os.environ["HYDRA_FULL_ERROR"] = "1"
4
+
5
+ import hydra
6
+
7
+ from graphatoms.enterpoint.config import CONFIG_DIR, Config
8
+
9
+ from .general.otfkmc import OTFKMC as OnTheFlyKMC
10
+ from .general.rxngen import ReactionNetworkGenerator
11
+ from .ray.otfkmc import RayOTFKMC as RayOnTheFlyKMC
12
+ from .ray.rxngen import RayReactionNetworkGenerator
13
+
14
+
15
+ @hydra.main(
16
+ config_path=CONFIG_DIR.as_posix(),
17
+ config_name="run",
18
+ version_base=None,
19
+ )
20
+ def run(cfg: Config) -> None:
21
+ if str(cfg.run_type).lower() == "otfkmc":
22
+ if str(cfg.parallel).lower() == "ray":
23
+ RayOnTheFlyKMC(config=cfg).run()
24
+ else:
25
+ OnTheFlyKMC(config=cfg).run()
26
+ elif str(cfg.run_type).lower() == "rxngen":
27
+ if str(cfg.parallel).lower() == "ray":
28
+ RayReactionNetworkGenerator(config=cfg).run()
29
+ else:
30
+ ReactionNetworkGenerator(config=cfg).run()
31
+ else:
32
+ raise ValueError(f" run_type `{cfg.run_type}` is not supported.")
@@ -0,0 +1,21 @@
1
+ from ._base import RunnerABC
2
+ from ._errors import CheckVibrationFailed, HelperException, OptimizationFailed
3
+ from ._helper import (
4
+ helper_adsorption,
5
+ helper_apply,
6
+ helper_dimer,
7
+ helper_match,
8
+ helper_optimization,
9
+ )
10
+
11
+ __all__ = [
12
+ "RunnerABC",
13
+ "CheckVibrationFailed",
14
+ "HelperException",
15
+ "OptimizationFailed",
16
+ "helper_adsorption",
17
+ "helper_apply",
18
+ "helper_dimer",
19
+ "helper_match",
20
+ "helper_optimization",
21
+ ]
@@ -0,0 +1,262 @@
1
+ import os
2
+ import sys
3
+ from abc import abstractmethod
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ os.environ["LOGURU_FORMAT"] = LOGURU_FORMAT = (
8
+ "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green>"
9
+ + " | <level>{level: ^8}</level> | "
10
+ + "<level>{message}</level>"
11
+ )
12
+
13
+ import hydra
14
+ import numpy as np
15
+ from ase import Atoms
16
+ from loguru._logger import Core, Logger
17
+ from omegaconf import DictConfig, OmegaConf
18
+
19
+ from graphatoms.enterpoint.config import Config
20
+ from graphatoms.enterpoint.network import ReactionNetwork
21
+ from graphatoms.enterpoint.parallel import get_executor
22
+ from graphatoms.system import ( # type: ignore # type: ignore
23
+ Gas,
24
+ System,
25
+ )
26
+ from graphatoms.utils.parser import hydra_parse
27
+
28
+ from ._helper import helper_optimization
29
+
30
+
31
+ class RunnerABC:
32
+ """The base class for all classes.
33
+
34
+ It provides:
35
+ 1. basic configuration (omegaconf.DictConfig)
36
+ 2. output directory (pathlib.Path)
37
+ """
38
+
39
+ __LOGURU_FORMAT_LENGTH: int = 37
40
+ __LOGURU_TOTAL_LENGTH: int = 120
41
+
42
+ @classmethod
43
+ def _reformat_message(cls, msg: str) -> str:
44
+ return msg
45
+ len_msg = cls.__LOGURU_TOTAL_LENGTH - cls.__LOGURU_FORMAT_LENGTH
46
+ lst = wrap_line(msg, width_min=len_msg, width_max=len_msg + 20)
47
+ return ("\n" + " " * cls.__LOGURU_FORMAT_LENGTH).join(lst)
48
+
49
+ def __init__(self, *, config: Config) -> None:
50
+ assert isinstance(config, DictConfig | Config)
51
+ self.config: Config = config
52
+ self.path = Path(config.outputs)
53
+ self.path.mkdir(parents=True, exist_ok=True)
54
+
55
+ # Logger Configuration
56
+ loglevel = str(config.loglevel).upper()
57
+ try:
58
+ hydracfg = hydra.core.hydra_config.HydraConfig.get() # type: ignore
59
+ outlogfile = str(hydracfg.job_logging.handlers.file.filename)
60
+ except Exception:
61
+ outlogfile = hydracfg = None
62
+ outlogfile = config.logfile
63
+ assert outlogfile is not None
64
+ outlogfile = str(outlogfile)
65
+
66
+ # Logger Setting
67
+ self.logger = log = Logger(
68
+ core=Core(),
69
+ exception=None,
70
+ depth=0,
71
+ record=False,
72
+ lazy=False,
73
+ colors=False,
74
+ raw=False,
75
+ capture=True,
76
+ patchers=[],
77
+ extra={},
78
+ )
79
+ log.add(sys.stderr, level=loglevel, format=LOGURU_FORMAT)
80
+ logname = Path(outlogfile).name
81
+ if logname != "-":
82
+ logfile = self.path.joinpath(logname)
83
+ log.add(logfile, level=loglevel, format=LOGURU_FORMAT)
84
+
85
+ # logging directory configuration
86
+ if hydracfg is not None:
87
+ output_dir = hydracfg.runtime.output_dir
88
+ else:
89
+ output_dir = config.outputs
90
+ output_dir = Path(output_dir).absolute()
91
+ self._log_length = int( # the maximum length of the line in loguru
92
+ self.__LOGURU_TOTAL_LENGTH # 80
93
+ - self.__LOGURU_FORMAT_LENGTH
94
+ )
95
+ log.info("=" * self._log_length)
96
+ log.info("The Configuration:\n" + OmegaConf.to_yaml(config))
97
+ log.info(f"Working floder : {os.getcwd()}")
98
+ log.info(f"self.path floder : {self.path}")
99
+ log.info(f"Output floder : {output_dir}")
100
+ log.info(f"Output logfile : {outlogfile}")
101
+ log.info(f"Output loglevel : {loglevel.upper()}")
102
+ log.info("=" * self._log_length)
103
+
104
+ # restart/initialize configuration
105
+ self.network: ReactionNetwork = ReactionNetwork(
106
+ path=self.path / "event",
107
+ config=config.event,
108
+ format=config.event.db_format,
109
+ restart=config.restart,
110
+ )
111
+
112
+ # check parallel mode
113
+ parallel = str(config.parallel).lower()
114
+ if hydracfg is not None and hydracfg.mode == "MULTIRUN":
115
+ if parallel != "serial":
116
+ msg = (
117
+ "Please delete '--multirun,-m' option "
118
+ "when running this script. The multirun "
119
+ "mode is not supported because this program "
120
+ f"will be parallelized by '{parallel}' innerly."
121
+ )
122
+ self.logger.error(self._reformat_message(msg))
123
+ raise ValueError(msg)
124
+ if parallel not in [
125
+ "serial",
126
+ "multiprocessing",
127
+ "ray",
128
+ "dask",
129
+ "executorlib",
130
+ ]:
131
+ msg = (
132
+ f"Invalid parallel mode: {parallel}. Please choose one from "
133
+ "'serial', 'multiprocessing', 'ray', 'dask', 'executorlib'."
134
+ )
135
+ self.logger.error(self._reformat_message(msg))
136
+ raise ValueError(msg)
137
+ pworkers = int(self.config.parallel_workers)
138
+ pworkers: int | None = None if pworkers <= 0 else pworkers
139
+ self.executor = get_executor(parallel, pworkers)
140
+
141
+ self.__gas_lst: list[Gas] = []
142
+ for gas_info in self.network.metadata.basic.gas_info_lst:
143
+ gas, _, cost_time = helper_optimization(
144
+ config=self.config,
145
+ graph=Gas.from_name(
146
+ gas_info.name,
147
+ sticking=gas_info.sticking,
148
+ pressure=gas_info.pressure,
149
+ parse_bonds=self.config.bonds, # type: ignore
150
+ ),
151
+ raise_when_fail=True,
152
+ allow_hash_change=False,
153
+ allow_not_connected=False,
154
+ run_vibration=True,
155
+ deep_copy=True,
156
+ )
157
+ if isinstance(gas, Gas):
158
+ self.logger.info(
159
+ self._reformat_message(
160
+ f"Optimized gas({gas_info.name}) "
161
+ f"in {cost_time:.2f} seconds."
162
+ )
163
+ )
164
+ self.__gas_lst.append(gas)
165
+ self.network.db_gas.add(gas)
166
+ else:
167
+ msg = f"Failed to optimize gas({gas_info.name})."
168
+ self.logger.error(self._reformat_message(msg))
169
+ raise ValueError(msg)
170
+ # persist the network for restart. [gas list]
171
+ self.network.persistence()
172
+
173
+ @property
174
+ def gas_lst(self) -> list[Gas]:
175
+ if len(self.network.metadata.basic.gas_info_lst) != 0:
176
+ return self.__gas_lst
177
+ else:
178
+ return []
179
+
180
+ @abstractmethod
181
+ def run(self, *args, **kwargs) -> Any:
182
+ """Run the class."""
183
+
184
+ def get_system_for(self, inp: Atoms | None) -> System:
185
+ if inp is None:
186
+ # parse system for first step
187
+ try:
188
+ result: System = hydra_parse(self.config.system, System)
189
+ except Exception:
190
+ atoms = hydra_parse(self.config.system, Atoms)
191
+ result: System = System.from_ase(
192
+ atoms=atoms,
193
+ parse_bonds=self.config.bonds, # type: ignore
194
+ parse_atoms_is_outer_or_not=True,
195
+ )
196
+ if self.config.system.get("attach_is_adsorbate", True):
197
+ result = result.model_copy(
198
+ update=dict(
199
+ is_adsorbate=np.zeros_like(
200
+ result.is_outer,
201
+ dtype=bool,
202
+ )
203
+ ),
204
+ deep=False,
205
+ )
206
+ elif isinstance(inp, Atoms):
207
+ result: System = System.from_ase(
208
+ atoms=inp,
209
+ parse_bonds=self.config.bonds, # type: ignore
210
+ parse_atoms_is_outer_or_not=True,
211
+ )
212
+ else:
213
+ msg = f"Unknown type of input: {type(inp)}"
214
+ self.logger.error(self._reformat_message(msg))
215
+ raise ValueError(msg)
216
+
217
+ assert isinstance(result, System)
218
+ assert result.pair is not None
219
+ assert result.is_outer is not None
220
+ self.logger.info(self._reformat_message(f"Read the system: {result}"))
221
+ return result
222
+
223
+
224
+ def wrap_line(
225
+ text: str,
226
+ width_min: int = 80,
227
+ width_max: int = 100,
228
+ ) -> list[str]:
229
+ """Make a line of text fit in a given width.
230
+
231
+ Args:
232
+ text: The text to wrap.
233
+ width_min: The minimum width of the line.
234
+ width_max: The maximum width of the line.
235
+
236
+ Returns:
237
+ The wrapped lines.
238
+ """
239
+ words = text.split()
240
+ if not words:
241
+ return [""]
242
+
243
+ lines = []
244
+ cur = ""
245
+ for w in words:
246
+ if not cur:
247
+ cur = w
248
+ elif len(cur) + 1 + len(w) <= width_max:
249
+ cur += " " + w
250
+ else:
251
+ lines.append(cur)
252
+ cur = w
253
+ if cur:
254
+ lines.append(cur)
255
+
256
+ if len(lines) >= 2 and len(lines[-1]) < width_min:
257
+ merged = lines[-2] + " " + lines[-1]
258
+ if len(merged) <= width_max:
259
+ lines[-2] = merged
260
+ lines.pop()
261
+
262
+ return lines