GraphAtoms 2.3.1__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 (92) hide show
  1. {graphatoms-2.3.1 → graphatoms-2.3.2}/PKG-INFO +1 -1
  2. {graphatoms-2.3.1 → graphatoms-2.3.2}/pyproject.toml +1 -1
  3. {graphatoms-2.3.1 → graphatoms-2.3.2}/pyproject.toml.orig +1 -1
  4. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/dataclasses/_pydanticMixin.py +38 -20
  5. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/network/_clsnetwork.py +15 -7
  6. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/network/_metadata.py +5 -1
  7. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/network/_scheduler.py +9 -2
  8. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/common/_base.py +6 -3
  9. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/common/_helper.py +7 -5
  10. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/general/otfkmc.py +5 -1
  11. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/geometry/sample.py +7 -1
  12. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/system/database/abc.py +8 -5
  13. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/utils/adsorption.py +6 -2
  14. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/utils/bytestool.py +52 -1
  15. {graphatoms-2.3.1 → graphatoms-2.3.2}/README.md +0 -0
  16. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/LICENSE +0 -0
  17. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/__init__.py +0 -0
  18. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/arrayapi/__init__.py +0 -0
  19. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/arrayapi/compat.py +0 -0
  20. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/arrayapi/typing.py +0 -0
  21. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/dataclasses/__init__.py +0 -0
  22. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/dataclasses/_numpydantic.py +0 -0
  23. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/dataclasses/_pydantic2pyarrow.py +0 -0
  24. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/dataclasses/_pydanticModel.py +0 -0
  25. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/dataclasses/graph.py +0 -0
  26. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/__init__.py +0 -0
  27. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/__init__.py +0 -0
  28. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/atoms/__init__.py +0 -0
  29. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/atoms/aseread.yaml +0 -0
  30. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/atoms/octahedron.yaml +0 -0
  31. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/bonds/__init__.py +0 -0
  32. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/bonds/raw.yaml +0 -0
  33. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/calculator/__init__.py +0 -0
  34. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/calculator/emt.yaml +0 -0
  35. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/calculator/nequip.yaml +0 -0
  36. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/example.yaml +0 -0
  37. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/config/run.yaml +0 -0
  38. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/network/__init__.py +0 -0
  39. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/network/_commands.py +0 -0
  40. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/network/_recorder.py +0 -0
  41. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/parallel/__init__.py +0 -0
  42. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/parallel/_base.py +0 -0
  43. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/parallel/_dask.py +0 -0
  44. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/parallel/_executorlib.py +0 -0
  45. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/parallel/_ray.py +0 -0
  46. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/__init__.py +0 -0
  47. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/common/__init__.py +0 -0
  48. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/common/_errors.py +0 -0
  49. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/general/__init__.py +0 -0
  50. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/general/_expl.py +0 -0
  51. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/general/rxngen.py +0 -0
  52. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/__init__.py +0 -0
  53. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/_helperv2.py +0 -0
  54. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/general/__init__.py +0 -0
  55. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/general/_expl.py +0 -0
  56. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/general/otfkmc.py +0 -0
  57. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/general/rxngen.py +0 -0
  58. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/ray/__init__.py +0 -0
  59. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/ray/_expl.py +0 -0
  60. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/ray/_helper.py +0 -0
  61. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/ray/otfkmc.py +0 -0
  62. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/enterpoint/runner/v2/ray/rxngen.py +0 -0
  63. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/geometry/__init__.py +0 -0
  64. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/geometry/_bond_list.py +0 -0
  65. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/geometry/_distance_pairs.py +0 -0
  66. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/geometry/_inner_outer.py +0 -0
  67. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/geometry/_neighbor_list.py +0 -0
  68. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/geometry/mic.py +0 -0
  69. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/geometry/rotation.py +0 -0
  70. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/reaction/__init__.py +0 -0
  71. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/reaction/_event.py +0 -0
  72. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/reaction/reaction.py +0 -0
  73. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/reaction/xxsorption.py +0 -0
  74. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/system/__init__.py +0 -0
  75. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/system/atoms.py +0 -0
  76. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/system/bonds.py +0 -0
  77. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/system/database/__init__.py +0 -0
  78. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/system/database/dict.py +0 -0
  79. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/system/database/dir.py +0 -0
  80. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/system/database/hdf5.py +0 -0
  81. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/system/database/sqliteASE.py +0 -0
  82. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/system/graph.py +0 -0
  83. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/system/sysCluster.py +0 -0
  84. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/system/sysGas.py +0 -0
  85. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/system/system.py +0 -0
  86. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/utils/__init__.py +0 -0
  87. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/utils/aseqhull.py +0 -0
  88. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/utils/asetools.py +0 -0
  89. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/utils/logger.py +0 -0
  90. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/utils/parser.py +0 -0
  91. {graphatoms-2.3.1 → graphatoms-2.3.2}/src/graphatoms/utils/rdutils.py +0 -0
  92. {graphatoms-2.3.1 → 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.1
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.1"
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"]
@@ -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.1"
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
@@ -27,6 +27,7 @@ from graphatoms.reaction import (
27
27
  )
28
28
  from graphatoms.system import Cluster, Gas, SysGraph, System
29
29
  from graphatoms.system.database import DatabaseABC, get_db
30
+ from graphatoms.utils.bytestool import atomic_write, atomic_write_text
30
31
 
31
32
  __all__ = [
32
33
  "MetaData",
@@ -88,8 +89,9 @@ class ReactionNetwork(Mapping[str, tuple[EventInfo, EventBase]]):
88
89
  + " Please delete it or use `restart=True`."
89
90
  )
90
91
  self.__path.mkdir(parents=True, exist_ok=True)
91
- self.__path_exploration_recorder.write_text(
92
- RecorderInfo.get_csv_title() + "\n"
92
+ atomic_write_text(
93
+ self.__path_exploration_recorder,
94
+ RecorderInfo.get_csv_title() + "\n",
93
95
  )
94
96
  self.__recorder = Recorder()
95
97
  self.__scheduler = Scheduler()
@@ -130,6 +132,7 @@ class ReactionNetwork(Mapping[str, tuple[EventInfo, EventBase]]):
130
132
  config: Config,
131
133
  *args,
132
134
  restart: bool | None = None,
135
+ remote_retry_times: float = 2.0,
133
136
  remote_mode: bool = False,
134
137
  **kwargs,
135
138
  ) -> Self: # type: ignore
@@ -152,7 +155,7 @@ class ReactionNetwork(Mapping[str, tuple[EventInfo, EventBase]]):
152
155
  )
153
156
  break
154
157
  except Exception as e:
155
- time.sleep(0.5)
158
+ time.sleep(abs(remote_retry_times))
156
159
  err = e
157
160
  if err is not None:
158
161
  raise RuntimeError(
@@ -535,8 +538,11 @@ class ReactionNetwork(Mapping[str, tuple[EventInfo, EventBase]]):
535
538
  msg = f"Event {event._string()} failed "
536
539
  msg += f"to simplify. because of {e}"
537
540
  fname = self.__path / "event-simplify-fail.pkl"
538
- fname.with_suffix(".err").write_text(msg)
539
- fname.write_bytes(pickle.dumps(event))
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
+ )
540
546
  event.R.write_npz(fname.with_suffix(".R.npz"))
541
547
  event.P.write_npz(fname.with_suffix(".P.npz"))
542
548
  if event.G is not None:
@@ -789,8 +795,10 @@ if __name__ == "__main__":
789
795
  if "merge" not in zzzz_path.name:
790
796
  p = zzzz_path / "event"
791
797
  (p / "recorder.json").unlink(missing_ok=True)
792
- with open(p / "recorder.json", "w") as f:
793
- json.dump({}, f, indent=4)
798
+ atomic_write_text(
799
+ p / "recorder.json",
800
+ json.dumps({}, indent=4),
801
+ )
794
802
  network_lst.append(
795
803
  ReactionNetwork(
796
804
  zzzz_path / "event",
@@ -10,6 +10,7 @@ from graphatoms.dataclasses import OurBaseModel
10
10
  from graphatoms.enterpoint.config import Config, EventConfig
11
11
  from graphatoms.reaction import EventBase, EventInfo
12
12
  from graphatoms.system.bonds import matchmode2nmatch
13
+ from graphatoms.utils.bytestool import atomic_write
13
14
 
14
15
  __all__ = ["MetaData"]
15
16
 
@@ -187,7 +188,10 @@ class MetaData(BaseModel):
187
188
 
188
189
  def persistence(self, path: Path | str) -> None:
189
190
  self.basic.write_json(Path(path) / "metadata-basic.json")
190
- self.table.dataframe.to_feather(Path(path) / "metadata-table.feather")
191
+ atomic_write(
192
+ Path(path) / "metadata-table.feather",
193
+ writer=lambda p: self.table.dataframe.to_feather(p),
194
+ )
191
195
 
192
196
  @classmethod
193
197
  def from_storage(cls, path: Path | str) -> Self:
@@ -9,6 +9,7 @@ from sklearn.metrics.pairwise import cosine_similarity
9
9
 
10
10
  from graphatoms.dataclasses._numpydantic import NDArray
11
11
  from graphatoms.system import SysGraph # type: ignore
12
+ from graphatoms.utils.bytestool import atomic_write
12
13
 
13
14
 
14
15
  class Scheduler(BaseModel):
@@ -35,7 +36,10 @@ class Scheduler(BaseModel):
35
36
  save: Callable = np.savez_compressed if compress else np.savez
36
37
  dct = {f"{k}_is_fixed": v for k, v in self.is_fixed.items()}
37
38
  dct = {f"{k}_dR": v for k, v in self.dR.items()} | dct
38
- save(filename, allow_pickle=False, **dct)
39
+ atomic_write(
40
+ target=filename,
41
+ writer=lambda p: save(p, allow_pickle=False, **dct),
42
+ )
39
43
  return filename
40
44
 
41
45
  @classmethod
@@ -151,5 +155,8 @@ if __name__ == "__main__":
151
155
  plt.title("Blue: Keep; Red: Skip")
152
156
 
153
157
  p = Path(__file__).with_suffix(".png")
154
- plt.savefig(p)
158
+ atomic_write(
159
+ p,
160
+ writer=lambda p: plt.savefig(p),
161
+ )
155
162
  obj.write_npz(p.with_suffix(".npz"))
@@ -26,6 +26,7 @@ from graphatoms.enterpoint.config.atoms import AseReadAtomsConfig
26
26
  from graphatoms.enterpoint.network import ReactionNetwork
27
27
  from graphatoms.enterpoint.parallel import get_executor
28
28
  from graphatoms.system import Cluster, Gas, System
29
+ from graphatoms.utils.bytestool import atomic_write_text
29
30
  from graphatoms.utils.parser import hydra_parse
30
31
 
31
32
  from ._helper import helper_optimization
@@ -241,8 +242,10 @@ class RunnerABC:
241
242
 
242
243
  def persist_gas_number_dct(self) -> None:
243
244
  """Persist the gas number dct to file."""
244
- with open(self.__gas_number_path, "w") as f:
245
- json.dump(self.gas_number_dct, f, indent=4)
245
+ atomic_write_text(
246
+ self.__gas_number_path,
247
+ json.dumps(self.gas_number_dct, indent=4),
248
+ )
246
249
 
247
250
  def load_gas_number_dct(self) -> None:
248
251
  """Load the gas number dct from file."""
@@ -376,7 +379,7 @@ class RunnerABC:
376
379
  values: list[Cluster] = []
377
380
  keys: list[tuple[bool, int, str]] = []
378
381
  for core in system.get_site_core(max_ncore=max_ncore): # type: ignore
379
- idx_core = np.unique(np.where(core)).astype(int)
382
+ idx_core = np.unique(np.where(core)).astype(int) # type: ignore
380
383
  values.append(
381
384
  Cluster.from_select(
382
385
  system,
@@ -4,12 +4,14 @@ These helper functions only run calculation, and check the result.
4
4
  They do not check whether the calculation is necessary and output logger.
5
5
  """
6
6
 
7
+ from pathlib import Path
7
8
  from time import perf_counter
8
9
  from typing import Any, Literal
9
10
 
10
11
  import numpy as np
11
12
  from ase import Atoms
12
13
  from ase.calculators.calculator import Calculator
14
+ from ase.io.extxyz import write_xyz as write_extxyz
13
15
 
14
16
  from graphatoms.enterpoint.config import Config
15
17
  from graphatoms.enterpoint.network import ReactionNetwork
@@ -18,6 +20,7 @@ from graphatoms.system import Cluster, Gas, SysGraph, System
18
20
  from graphatoms.system.atoms import Structure
19
21
  from graphatoms.utils import asetools
20
22
  from graphatoms.utils.adsorption import Helper
23
+ from graphatoms.utils.bytestool import atomic_write
21
24
  from graphatoms.utils.parser import hydra_parse
22
25
 
23
26
  from ._errors import CheckVibrationFailed, HelperException, OptimizationFailed
@@ -178,11 +181,10 @@ def helper_optimization(
178
181
  deep=deep_copy,
179
182
  )
180
183
  except Exception as e:
181
- lst[0].write(f"debug-{graph_label}.xyz", format="extxyz", append=False)
182
- for i in range(1, len(lst)):
183
- lst[i].write(
184
- f"debug-{graph_label}.xyz", format="extxyz", append=True
185
- )
184
+ atomic_write(
185
+ Path(f"debug-{graph_label}.xyz"),
186
+ writer=lambda p: write_extxyz(p, lst),
187
+ )
186
188
  raise e
187
189
  if (not allow_not_connected) and (not result.is_connected):
188
190
  e = HelperException(
@@ -17,6 +17,7 @@ from graphatoms.enterpoint.runner.common._helper import (
17
17
  helper_match,
18
18
  )
19
19
  from graphatoms.reaction import Adsorption, Desorption, Reaction
20
+ from graphatoms.utils.bytestool import atomic_write
20
21
 
21
22
  from ._expl import ExplorationBase
22
23
 
@@ -92,7 +93,10 @@ class OTFKMC(ExplorationBase):
92
93
  # -------------------------------------------
93
94
  system = self.get_system_for(atoms)
94
95
  data = [i.model_dump() for i in df_data]
95
- pd.DataFrame(data).to_csv(self.__info_path)
96
+ atomic_write(
97
+ self.__info_path,
98
+ writer=lambda p: pd.DataFrame(data).to_csv(p),
99
+ )
96
100
  self.__traj.write(system.to_ase())
97
101
  if df_data[-1].time > float(self.config.max_times):
98
102
  self.logger.info(
@@ -56,6 +56,8 @@ if __name__ == "__main__":
56
56
  from matplotlib import pyplot as plt
57
57
  from matplotlib.axes import Axes
58
58
 
59
+ from graphatoms.utils.bytestool import atomic_write
60
+
59
61
  N = 100
60
62
 
61
63
  fig, axes = plt.subplots(1, 2, figsize=(8, 4.5), dpi=150)
@@ -76,4 +78,8 @@ if __name__ == "__main__":
76
78
  ax.set_xticks([])
77
79
  ax.set_yticks([])
78
80
  plt.tight_layout()
79
- fig.savefig(Path(__file__).with_suffix(".png"))
81
+
82
+ atomic_write(
83
+ Path(__file__).with_suffix(".png"),
84
+ writer=lambda p: fig.savefig(p),
85
+ )
@@ -1,11 +1,14 @@
1
1
  from abc import abstractmethod
2
2
  from collections.abc import Mapping, MutableSet
3
+ from pathlib import Path
3
4
  from typing import override
4
5
 
5
6
  import numpy as np
6
7
  from ase import Atoms
8
+ from ase.io.extxyz import write_xyz as write_extxyz
7
9
 
8
10
  from graphatoms.system import SysGraph
11
+ from graphatoms.utils.bytestool import atomic_write
9
12
 
10
13
 
11
14
  class DatabaseABC(Mapping[str, Atoms], MutableSet[str]):
@@ -63,11 +66,11 @@ class DatabaseABC(Mapping[str, Atoms], MutableSet[str]):
63
66
  if not np.all(np.abs(vdiff) < check_threshold):
64
67
  # Save some files for debug.
65
68
  old = SysGraph.from_ase(old_value)
66
- for v, append in [
67
- (old_value, False),
68
- (value.to_ase(), True),
69
- ]:
70
- v.write(f"{key}-debug.xyz", "extxyz", append=append)
69
+ lst = [old_value, value.to_ase()]
70
+ atomic_write(
71
+ Path(f"{key}-debug.xyz"),
72
+ writer=lambda p: write_extxyz(p, lst),
73
+ )
71
74
  value.write_npz(f"{key}-new.npz") # type: ignore
72
75
  old.write_npz(f"{key}-old.npz") # type: ignore
73
76
  raise ValueError(
@@ -23,10 +23,11 @@ from scipy.spatial.transform import Rotation
23
23
  from graphatoms.geometry import neighbor_list
24
24
  from graphatoms.geometry.sample import fibonacci_lattice
25
25
  from graphatoms.system import Cluster, Gas, System
26
+ from graphatoms.utils.asetools import call_optimization as optimize
27
+ from graphatoms.utils.bytestool import atomic_write
26
28
  from graphatoms.utils.rdutils import rdmol2ase, smiles2rdmol
27
29
 
28
30
  matplotlib.use("Agg")
29
- from .asetools import call_optimization as optimize
30
31
 
31
32
 
32
33
  #########################################
@@ -786,5 +787,8 @@ def plot(atoms: Atoms, pngfname: Path) -> None:
786
787
  )
787
788
  ax.set_xticks([])
788
789
  ax.set_yticks([])
789
- fig.savefig(pngfname.with_suffix(".png"))
790
+ atomic_write(
791
+ pngfname.with_suffix(".png"),
792
+ writer=lambda p: fig.savefig(p),
793
+ )
790
794
  plt.close(fig)
@@ -2,11 +2,14 @@
2
2
 
3
3
  import gzip
4
4
  import hashlib
5
+ import os
5
6
  import string
7
+ import uuid
6
8
  import zlib
7
9
  from collections.abc import Callable
8
10
  from functools import partial
9
- from typing import Annotated
11
+ from pathlib import Path
12
+ from typing import Annotated, Any
10
13
 
11
14
  from numpy.random import choice
12
15
  from pydantic import Field, validate_call
@@ -179,6 +182,54 @@ def random_string(n: int) -> str:
179
182
  return "".join(choice(pool, size=int(n)))
180
183
 
181
184
 
185
+ def _fsync_dir(path: Path) -> None:
186
+ """Best-effort fsync of a directory so a rename is durable (POSIX)."""
187
+ try:
188
+ fd = os.open(str(path), os.O_RDONLY)
189
+ except OSError:
190
+ return
191
+ try:
192
+ os.fsync(fd)
193
+ except OSError:
194
+ pass
195
+ finally:
196
+ os.close(fd)
197
+
198
+
199
+ def atomic_write(target: Path, writer: Callable[[Path], Any]) -> None:
200
+ """Write `target` atomically.
201
+
202
+ The `writer` callback receives a temporary path in the same
203
+ directory and must create/write the file there. The temp file is
204
+ fsynced and then os.replace()d onto `target`. Because both paths
205
+ live in the same directory, os.replace is atomic on POSIX.
206
+
207
+ The temp file keeps `target.suffix`, so callers such as
208
+ ``np.savez`` that auto-append ".npz" behave correctly.
209
+ """
210
+ target = Path(target)
211
+ target.parent.mkdir(parents=True, exist_ok=True)
212
+ file_stem = f".{target.stem}.tmp.{os.getpid()}{uuid.uuid4().hex}"
213
+ tmp = target.parent / f"{file_stem}{target.suffix}"
214
+ try:
215
+ writer(tmp)
216
+ # ensure the file is synced to disk
217
+ with open(tmp, "rb") as f:
218
+ os.fsync(f.fileno())
219
+ os.replace(tmp, target)
220
+ _fsync_dir(target.parent)
221
+ except Exception:
222
+ try:
223
+ tmp.unlink()
224
+ except FileNotFoundError:
225
+ pass
226
+ raise
227
+
228
+
229
+ def atomic_write_text(target: Path, text: str, encoding="utf-8") -> None:
230
+ atomic_write(target, lambda p: p.write_text(text, encoding=encoding))
231
+
232
+
182
233
  #######################################################################
183
234
  # Test
184
235
  #######################################################################
File without changes
File without changes