GraphAtoms 2.0.0__tar.gz → 2.1.0__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 (77) hide show
  1. {graphatoms-2.0.0 → graphatoms-2.1.0}/PKG-INFO +36 -13
  2. {graphatoms-2.0.0 → graphatoms-2.1.0}/README.md +35 -8
  3. {graphatoms-2.0.0 → graphatoms-2.1.0}/pyproject.toml +1 -5
  4. {graphatoms-2.0.0 → graphatoms-2.1.0}/pyproject.toml.orig +1 -5
  5. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/network/_metadata.py +9 -2
  6. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/network/_recorder.py +1 -1
  7. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/runner/_base.py +94 -69
  8. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/runner/_helper.py +12 -1
  9. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/runner/otfkmc.py +1 -0
  10. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/reaction/__init__.py +1 -2
  11. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/reaction/_event.py +8 -0
  12. graphatoms-2.0.0/src/graphatoms/reaction/desorption.py → graphatoms-2.1.0/src/graphatoms/reaction/xxsorption.py +100 -0
  13. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/system/sysGas.py +1 -0
  14. graphatoms-2.0.0/src/graphatoms/reaction/adsorption.py +0 -99
  15. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/LICENSE +0 -0
  16. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/__init__.py +0 -0
  17. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/arrayapi/__init__.py +0 -0
  18. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/arrayapi/compat.py +0 -0
  19. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/arrayapi/typing.py +0 -0
  20. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/dataclasses/__init__.py +0 -0
  21. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/dataclasses/_numpydantic.py +0 -0
  22. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/dataclasses/_pydantic2pyarrow.py +0 -0
  23. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/dataclasses/_pydanticMixin.py +0 -0
  24. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/dataclasses/_pydanticModel.py +0 -0
  25. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/dataclasses/graph.py +0 -0
  26. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/__init__.py +0 -0
  27. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/config/__init__.py +0 -0
  28. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/config/atoms/__init__.py +0 -0
  29. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/config/atoms/aseread.yaml +0 -0
  30. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/config/atoms/octahedron.yaml +0 -0
  31. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/config/bonds/__init__.py +0 -0
  32. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/config/bonds/raw.yaml +0 -0
  33. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/config/calculator/__init__.py +0 -0
  34. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/config/calculator/emt.yaml +0 -0
  35. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/config/calculator/nequip.yaml +0 -0
  36. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/config/example.yaml +0 -0
  37. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/config/run.yaml +0 -0
  38. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/network/__init__.py +0 -0
  39. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/network/_scheduler.py +0 -0
  40. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/parallel/__init__.py +0 -0
  41. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/parallel/_base.py +0 -0
  42. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/parallel/_dask.py +0 -0
  43. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/parallel/_executorlib.py +0 -0
  44. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/parallel/_ray.py +0 -0
  45. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/runner/__init__.py +0 -0
  46. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/runner/_errors.py +0 -0
  47. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/runner/rxngen.py +0 -0
  48. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/enterpoint/view.py +0 -0
  49. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/geometry/__init__.py +0 -0
  50. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/geometry/_bond_list.py +0 -0
  51. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/geometry/_distance_pairs.py +0 -0
  52. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/geometry/_inner_outer.py +0 -0
  53. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/geometry/_neighbor_list.py +0 -0
  54. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/geometry/mic.py +0 -0
  55. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/geometry/rotation.py +0 -0
  56. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/geometry/sample.py +0 -0
  57. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/reaction/reaction.py +0 -0
  58. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/system/__init__.py +0 -0
  59. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/system/atoms.py +0 -0
  60. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/system/bonds.py +0 -0
  61. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/system/database/__init__.py +0 -0
  62. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/system/database/abc.py +0 -0
  63. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/system/database/dict.py +0 -0
  64. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/system/database/dir.py +0 -0
  65. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/system/database/hdf5.py +0 -0
  66. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/system/database/sqliteASE.py +0 -0
  67. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/system/graph.py +0 -0
  68. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/system/sysCluster.py +0 -0
  69. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/system/system.py +0 -0
  70. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/utils/__init__.py +0 -0
  71. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/utils/adsorption.py +0 -0
  72. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/utils/asetools.py +0 -0
  73. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/utils/bytestool.py +0 -0
  74. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/utils/logger.py +0 -0
  75. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/utils/parser.py +0 -0
  76. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/utils/rdutils.py +0 -0
  77. {graphatoms-2.0.0 → graphatoms-2.1.0}/src/graphatoms/utils/subgraph.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphatoms
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: The Chemical Core Class for Graph Theory Analysis.
5
5
  Keywords: chemistry,graph theory,reaction,event,KMC
6
6
  Author: LiuGaoyong
@@ -29,12 +29,8 @@ Requires-Dist: python-snappy>=0.7.3
29
29
  Requires-Dist: tomli ; python_full_version < '3.11'
30
30
  Requires-Dist: tomli-w
31
31
  Requires-Dist: typing-extensions
32
- Requires-Dist: plotext
33
- Requires-Dist: plotext-plus
34
- Requires-Dist: matplotlib
35
32
  Requires-Dist: executorlib
36
33
  Requires-Dist: hydra-core
37
- Requires-Dist: pipefunc
38
34
  Requires-Dist: typer
39
35
  Requires-Dist: loguru
40
36
  Requires-Python: >=3.12
@@ -62,6 +58,14 @@ The `graphatoms` is a Python library designed for chemical graph theory analysis
62
58
  - **Dataclasses**: Pydantic-based data models for type-safe data handling
63
59
  - **Array API Compatibility**: Full support for array API standard for cross-framework compatibility (NumPy, PyTorch, JAX, CuPy, etc.)
64
60
  - **Subgraph Operations**: Backend-agnostic subgraph extraction with relabeling support using array-api-compat and array-api-extra
61
+ - **CLI Entry Points**: Three console scripts for configuration, execution, and inspection:
62
+ - `graphatoms-config`: Resolve and print the Hydra/OmegaConf run configuration
63
+ - `graphatoms-run`: Launch a run
64
+ - `run_type=otfkmc` for on-the-fly kinetic Monte Carlo simulation
65
+ - `run_type=rxngen` for reaction network generation
66
+ - `graphatoms-network`: Inspect and visualize a stored reaction network
67
+ - **Hydra-driven Configuration**: Composable, override-friendly config via Hydra/OmegaConf with grouped groups (`atoms`, `bonds`, `calculator`)
68
+ - **Pluggable Parallel Backends**: Switch executors at the config level — `serial`, `multiprocessing`, `ray`, `dask`, `executorlib` — for distributed/on-the-fly KMC workflows
65
69
 
66
70
  ## Module Structure
67
71
 
@@ -69,18 +73,33 @@ The `graphatoms` is a Python library designed for chemical graph theory analysis
69
73
  src/graphatoms/
70
74
  ├── arrayapi/ # Array API compatibility layer
71
75
  ├── dataclasses/ # Pydantic-based data models
76
+ ├── enterpoint/ # Entry points: CLI, config, runners, network, parallel
77
+ │ ├── config/ # Hydra/OmegaConf configuration (atoms, bonds, calculator)
78
+ │ ├── network/ # Reaction network: scheduler, recorder, metadata
79
+ │ ├── parallel/ # Pluggable executors (serial, multiprocessing, ray, dask, executorlib)
80
+ │ ├── runner/ # Runners (otfkmc, rxngen) and helpers
81
+ │ ├── steps/ # Step primitives for runners
82
+ │ └── view.py # CLI viewer for reactions
72
83
  ├── geometry/ # Geometric operations
73
84
  ├── reaction/ # Reaction classes and KMC events
74
- │ ├── base/ # Abstract base classes
75
- │ ├── event/ # KMC events (adsorption, desorption, reaction)
76
- │ ├── mcmove/ # Monte Carlo moves
77
- │ ├── mdwarpper/ # MD wrapper
78
- │ └── network/ # Reaction network
85
+ │ ├── _event.py # Event base and event info
86
+ │ ├── reaction.py # Reaction class
87
+ │ └── xxsorption.py # Adsorption/Desorption events
79
88
  ├── system/ # Core system classes
80
- │ ├── atoms/ # Atomic structure handling
81
- │ ├── database/ # Database storage backends
82
- │ └── graph/ # Graph-based system representation
89
+ │ ├── atoms.py # Atomic structure handling
90
+ │ ├── bonds.py # Bond list operations
91
+ │ ├── graph.py # Graph-based system representation
92
+ │ ├── system.py # System abstract base
93
+ │ ├── sysCluster.py # Cluster system
94
+ │ ├── sysGas.py # Gas molecule system
95
+ │ └── database/ # Database storage backends (HDF5, SQLite, folder)
83
96
  └── utils/ # Utility functions
97
+ ├── adsorption.py # Adsorption site helper
98
+ ├── asetools.py # ASE-related tools
99
+ ├── bytestool.py # Byte-level helpers
100
+ ├── logger.py # Logging setup
101
+ ├── parser.py # Hydra argument parsing
102
+ ├── rdutils.py # RDKit utilities
84
103
  └── subgraph.py # Array API compatible subgraph operations
85
104
  ```
86
105
 
@@ -103,6 +122,10 @@ src/graphatoms/
103
122
  - pydantic >= 2.10
104
123
  - python-snappy >= 0.7.3
105
124
  - loguru
125
+ - pandas >= 2
126
+ - scipy >= 1.10
127
+ - typer
128
+ - executorlib
106
129
 
107
130
  ## Installation
108
131
 
@@ -17,6 +17,14 @@ The `graphatoms` is a Python library designed for chemical graph theory analysis
17
17
  - **Dataclasses**: Pydantic-based data models for type-safe data handling
18
18
  - **Array API Compatibility**: Full support for array API standard for cross-framework compatibility (NumPy, PyTorch, JAX, CuPy, etc.)
19
19
  - **Subgraph Operations**: Backend-agnostic subgraph extraction with relabeling support using array-api-compat and array-api-extra
20
+ - **CLI Entry Points**: Three console scripts for configuration, execution, and inspection:
21
+ - `graphatoms-config`: Resolve and print the Hydra/OmegaConf run configuration
22
+ - `graphatoms-run`: Launch a run
23
+ - `run_type=otfkmc` for on-the-fly kinetic Monte Carlo simulation
24
+ - `run_type=rxngen` for reaction network generation
25
+ - `graphatoms-network`: Inspect and visualize a stored reaction network
26
+ - **Hydra-driven Configuration**: Composable, override-friendly config via Hydra/OmegaConf with grouped groups (`atoms`, `bonds`, `calculator`)
27
+ - **Pluggable Parallel Backends**: Switch executors at the config level — `serial`, `multiprocessing`, `ray`, `dask`, `executorlib` — for distributed/on-the-fly KMC workflows
20
28
 
21
29
  ## Module Structure
22
30
 
@@ -24,18 +32,33 @@ The `graphatoms` is a Python library designed for chemical graph theory analysis
24
32
  src/graphatoms/
25
33
  ├── arrayapi/ # Array API compatibility layer
26
34
  ├── dataclasses/ # Pydantic-based data models
35
+ ├── enterpoint/ # Entry points: CLI, config, runners, network, parallel
36
+ │ ├── config/ # Hydra/OmegaConf configuration (atoms, bonds, calculator)
37
+ │ ├── network/ # Reaction network: scheduler, recorder, metadata
38
+ │ ├── parallel/ # Pluggable executors (serial, multiprocessing, ray, dask, executorlib)
39
+ │ ├── runner/ # Runners (otfkmc, rxngen) and helpers
40
+ │ ├── steps/ # Step primitives for runners
41
+ │ └── view.py # CLI viewer for reactions
27
42
  ├── geometry/ # Geometric operations
28
43
  ├── reaction/ # Reaction classes and KMC events
29
- │ ├── base/ # Abstract base classes
30
- │ ├── event/ # KMC events (adsorption, desorption, reaction)
31
- │ ├── mcmove/ # Monte Carlo moves
32
- │ ├── mdwarpper/ # MD wrapper
33
- │ └── network/ # Reaction network
44
+ │ ├── _event.py # Event base and event info
45
+ │ ├── reaction.py # Reaction class
46
+ │ └── xxsorption.py # Adsorption/Desorption events
34
47
  ├── system/ # Core system classes
35
- │ ├── atoms/ # Atomic structure handling
36
- │ ├── database/ # Database storage backends
37
- │ └── graph/ # Graph-based system representation
48
+ │ ├── atoms.py # Atomic structure handling
49
+ │ ├── bonds.py # Bond list operations
50
+ │ ├── graph.py # Graph-based system representation
51
+ │ ├── system.py # System abstract base
52
+ │ ├── sysCluster.py # Cluster system
53
+ │ ├── sysGas.py # Gas molecule system
54
+ │ └── database/ # Database storage backends (HDF5, SQLite, folder)
38
55
  └── utils/ # Utility functions
56
+ ├── adsorption.py # Adsorption site helper
57
+ ├── asetools.py # ASE-related tools
58
+ ├── bytestool.py # Byte-level helpers
59
+ ├── logger.py # Logging setup
60
+ ├── parser.py # Hydra argument parsing
61
+ ├── rdutils.py # RDKit utilities
39
62
  └── subgraph.py # Array API compatible subgraph operations
40
63
  ```
41
64
 
@@ -58,6 +81,10 @@ src/graphatoms/
58
81
  - pydantic >= 2.10
59
82
  - python-snappy >= 0.7.3
60
83
  - loguru
84
+ - pandas >= 2
85
+ - scipy >= 1.10
86
+ - typer
87
+ - executorlib
61
88
 
62
89
  ## Installation
63
90
 
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "graphatoms"
7
- version = "2.0.0"
7
+ version = "2.1.0"
8
8
  readme = "README.md"
9
9
  description = "The Chemical Core Class for Graph Theory Analysis."
10
10
  license-files = ["src/LICENSE"]
@@ -42,12 +42,8 @@ dependencies = [
42
42
  "tomli; python_version < '3.11'",
43
43
  "tomli-w",
44
44
  "typing-extensions",
45
- "plotext",
46
- "plotext-plus",
47
- "matplotlib",
48
45
  "executorlib",
49
46
  "hydra-core",
50
- "pipefunc",
51
47
  "typer",
52
48
  "loguru",
53
49
  ]
@@ -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.0.0"
15
+ version = "2.1.0"
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" }]
@@ -60,12 +60,8 @@ dependencies = [
60
60
  "typing-extensions",
61
61
 
62
62
  # Other
63
- "plotext",
64
- "plotext-plus",
65
- "matplotlib",
66
63
  "executorlib",
67
64
  "hydra-core",
68
- "pipefunc",
69
65
  "typer",
70
66
  "loguru",
71
67
  ]
@@ -110,6 +110,7 @@ class _MetaDataTable(BaseModel):
110
110
  *,
111
111
  cluster_key: str | None = None,
112
112
  system_key: str | None = None,
113
+ exclude_gas: bool = False,
113
114
  **kwargs,
114
115
  ) -> int:
115
116
  if cluster_key is not None:
@@ -117,7 +118,10 @@ class _MetaDataTable(BaseModel):
117
118
  [
118
119
  self.count[i]
119
120
  for i, ck in enumerate(self.for_cluster)
120
- if ck == cluster_key
121
+ if (
122
+ ck == cluster_key
123
+ and ((not exclude_gas) or self.key_g[i] is None)
124
+ )
121
125
  ]
122
126
  + [0]
123
127
  )
@@ -126,7 +130,10 @@ class _MetaDataTable(BaseModel):
126
130
  [
127
131
  self.count[i]
128
132
  for i, sk in enumerate(self.for_system)
129
- if sk == system_key
133
+ if (
134
+ sk == system_key
135
+ and ((not exclude_gas) or self.key_g[i] is None)
136
+ )
130
137
  ]
131
138
  + [0]
132
139
  )
@@ -106,7 +106,7 @@ class _OldNewRecorder(OurBaseModel):
106
106
  class Recorder(OurBaseModel):
107
107
  system: set[str] = set()
108
108
  cluster: dict[str, _OldNewRecorder] = defaultdict(_OldNewRecorder)
109
- adsorption: set[str] = set()
109
+ adsorption: dict[str, _OldNewRecorder] = defaultdict(_OldNewRecorder)
110
110
  bulk: set[str] = set()
111
111
 
112
112
  @override
@@ -164,6 +164,7 @@ class RunnerABC:
164
164
  ),
165
165
  raise_when_fail=True,
166
166
  allow_hash_change=False,
167
+ allow_not_connected=False,
167
168
  run_vibration=True,
168
169
  deep_copy=True,
169
170
  )
@@ -370,6 +371,7 @@ class ExplorationABC(RunnerABC):
370
371
  graph_label=label,
371
372
  graph=sysgraph,
372
373
  allow_hash_change=False,
374
+ allow_not_connected=False,
373
375
  raise_when_fail=False,
374
376
  )
375
377
  )
@@ -441,10 +443,13 @@ class ExplorationABC(RunnerABC):
441
443
  system_key=system_key,
442
444
  )
443
445
  if len(lst_4adsorption) > 0:
444
- self._second_step_adsorption(
445
- lst_4adsorption,
446
- system_key=system_key,
447
- )
446
+ for cluster in lst_4adsorption:
447
+ for gas in self.gas_lst:
448
+ self._second_step_adsorption(
449
+ gas=gas,
450
+ cluster=cluster,
451
+ system_key=system_key,
452
+ )
448
453
  if len(lst_4surface) > 0:
449
454
  for cluster in lst_4surface:
450
455
  self._second_step_surface(
@@ -579,6 +584,7 @@ class ExplorationABC(RunnerABC):
579
584
  confidence=confidence,
580
585
  min_found=self.network.metadata.table.get_minconut_for(
581
586
  cluster_key=cluster_key,
587
+ exclude_gas=True,
582
588
  ),
583
589
  ):
584
590
  msg = f"Finish exploration for {cluster_key} with "
@@ -588,103 +594,122 @@ class ExplorationABC(RunnerABC):
588
594
  for future in futures:
589
595
  self.executor.cancel(future)
590
596
  break
591
-
592
597
  self.network.persistence()
593
598
 
594
599
  def _second_step_adsorption(
595
600
  self,
596
- lst: list[Cluster],
601
+ gas: Gas,
602
+ cluster: Cluster,
597
603
  system_key: str = "",
598
604
  ) -> None:
599
605
  """The second step for the on-the-fly KMC simulation."""
600
- start: float = perf_counter()
606
+ cluster_key = cluster.get_key_for_metadata(True)
607
+ gas_key = gas.get_key_for_metadata(False)
608
+
609
+ # ---------------------------------------------
610
+ # check if the adsorption has been explored
611
+ # ---------------------------------------------
612
+ graph_label = f"{cluster_key}_{gas_key}"
613
+ if graph_label in self.network.recorder.adsorption:
614
+ msg = f"The adsorption for {cluster_key} with "
615
+ msg += f"{gas_key} has been explored."
616
+ self.logger.info(self._reformat_message(msg))
617
+ return
618
+
619
+ # ---------------------------------------------
620
+ # build helper for adsorption
621
+ # ---------------------------------------------
622
+ assert cluster.ncore > 0, "graph must have core"
623
+ adsorption_helper = Helper(
624
+ atoms=cluster.to_ase(
625
+ exclude_bond_attibutes=True,
626
+ exclude_energetics=True,
627
+ ).copy(),
628
+ adsorbate=gas,
629
+ core=np.unique(cluster.idx_core),
630
+ nfibonacci=int(self.config.exploration.nfibonacci),
631
+ use_direct=True,
632
+ use_raw=True,
633
+ )
634
+
635
+ # ---------------------------------------------
636
+ # submit adsorption tasks to executor
637
+ # ---------------------------------------------
601
638
  futures: list = []
602
- for cluster in lst:
603
- for gas in self.gas_lst:
604
- cluster_key = cluster.get_key_for_metadata(True)
605
- gas_key = gas.get_key_for_metadata(False)
606
-
607
- # ---------------------------------------------
608
- # check if the adsorption has been explored
609
- # ---------------------------------------------
610
- label = f"{cluster_key}_{gas_key}"
611
- if label in self.network.recorder.adsorption:
612
- msg = f"The adsorption for {cluster_key} with "
613
- msg += f"{gas_key} has been explored."
614
- self.logger.info(self._reformat_message(msg))
615
- continue
616
- # ---------------------------------------------
617
- # build helper for adsorption
618
- # ---------------------------------------------
619
- assert cluster.ncore > 0, "graph must have core"
620
- adsorption_helper = Helper(
621
- atoms=cluster.to_ase(
622
- exclude_bond_attibutes=True,
623
- exclude_energetics=True,
624
- ).copy(),
625
- adsorbate=gas,
626
- core=np.unique(cluster.idx_core),
627
- nfibonacci=int(self.config.exploration.nfibonacci),
628
- use_direct=True,
629
- use_raw=True,
630
- )
631
- # ---------------------------------------------
632
- # submit adsorption tasks to executor
633
- # ---------------------------------------------
634
- self.network.recorder.adsorption.add(label)
635
- nrun: int = adsorption_helper.nrun
636
- nrun: int = min(nrun, self.config.exploration.maxtry)
637
- for irun in range(nrun):
638
- futures.append(
639
- self.executor.submit(
640
- helper_adsorption,
641
- config=self.config,
642
- graph=cluster,
643
- gas=gas,
644
- irun=irun,
645
- graph_label=None,
646
- allow_hash_change=True,
647
- raise_when_fail=False,
648
- deep_copy=True,
649
- )
650
- )
651
- self.logger.info(
652
- self._reformat_message(
653
- f"Submit {nrun} adsorption tasks for "
654
- f"{cluster_key} with {gas_key}"
655
- )
639
+ start: float = perf_counter()
640
+ nrun: int = adsorption_helper.nrun
641
+ maxtry: int = self.config.exploration.maxtry
642
+ if nrun <= maxtry:
643
+ lst = np.asarray(range(nrun))
644
+ else:
645
+ lst = np.random.randint(nrun, size=maxtry)
646
+ np.random.shuffle(lst)
647
+ for irun in lst:
648
+ futures.append(
649
+ self.executor.submit(
650
+ helper_adsorption,
651
+ config=self.config,
652
+ graph=cluster,
653
+ gas=gas,
654
+ irun=irun,
655
+ graph_label=graph_label,
656
+ allow_hash_change=True,
657
+ raise_when_fail=False,
658
+ deep_copy=True,
656
659
  )
657
-
660
+ )
658
661
  self.logger.info(
659
662
  self._reformat_message(
660
- f"Submit {len(futures)} adsorption tasks by "
663
+ f"Submit {nrun} adsorption tasks by "
661
664
  f"{perf_counter() - start:.2f} seconds"
665
+ f" for {cluster_key} with {gas_key}"
662
666
  )
663
667
  )
664
668
 
665
669
  # -----------------------------------------------------------
666
670
  # wait for the adsorption tasks to finish
667
671
  # -----------------------------------------------------------
672
+ old_new = self.network.recorder.adsorption[graph_label]
673
+ confidence = self.config.exploration.maxconfidence
668
674
  while len(futures) > 0:
669
675
  future_result, futures = self.executor.wait(futures) # type: ignore
670
- event, label, cost_time, _ = future_result
671
- label = self.network.found(
676
+ event, _, cost_time, _ = future_result
677
+ event_label = self.network.found(
672
678
  event,
673
679
  for_cluster=None,
674
680
  for_system=system_key,
675
681
  persist=True,
676
682
  )
677
- if label.startswith("fail"):
678
- msg = f"Adsorption search {label} by {cost_time:.2f}"
683
+ if event_label.startswith("fail"):
684
+ msg = f"Adsorption search {event_label} by {cost_time:.2f}"
679
685
  msg += f" seconds because of {event}"
680
686
  self.logger.info(self._reformat_message(msg))
687
+ old_new.found_fail()
681
688
  else:
682
689
  msg = "Adsorption search successfully, and got "
683
- msg += f"{label} by {cost_time:.2f} seconds."
684
- if str(event) not in label:
690
+ msg += f"{event_label} by {cost_time:.2f} seconds."
691
+ if str(event) not in event_label:
685
692
  msg += f" Simplify original {event} by threshold "
686
693
  msg += f"{self.config.event.simplified_threshold:.2f}"
687
694
  self.logger.info(self._reformat_message(msg))
695
+ if "new" in event_label:
696
+ old_new.found_new()
697
+ else:
698
+ old_new.found_old()
699
+ if old_new.exploration_can_be_finished(
700
+ confidence=confidence,
701
+ min_found=self.network.metadata.table.get_minconut_for(
702
+ cluster_key=cluster_key,
703
+ exclude_gas=False,
704
+ ),
705
+ ):
706
+ msg = f"Finish exploration for {cluster_key} and {gas_key}"
707
+ msg += f"with confidence {confidence:.2f}. {len(futures)}"
708
+ msg += " adsorption tasks left. They will be canceled."
709
+ self.logger.info(self._reformat_message(msg))
710
+ for future in futures:
711
+ self.executor.cancel(future)
712
+ break
688
713
  self.network.persistence()
689
714
 
690
715
  def _second_step_bulk(
@@ -87,6 +87,7 @@ def helper_optimization(
87
87
  *,
88
88
  graph_label: Any | None = None,
89
89
  allow_hash_change: bool = True,
90
+ allow_not_connected: bool = False,
90
91
  raise_when_fail: bool = False,
91
92
  run_vibration: bool = True,
92
93
  deep_copy: bool = True,
@@ -152,6 +153,15 @@ def helper_optimization(
152
153
  parse_bonds=config.bonds, # type: ignore
153
154
  deep=deep_copy,
154
155
  )
156
+ if (not allow_not_connected) and (not result.is_connected):
157
+ e = HelperException(
158
+ msg="graph is not connected after optimization",
159
+ label=graph_label,
160
+ )
161
+ if raise_when_fail:
162
+ raise e
163
+ else:
164
+ return str(e), graph_label, perf_counter() - start
155
165
  if not allow_hash_change and graph.hash != result.hash:
156
166
  e = HelperException(
157
167
  msg="hash changed after optimization",
@@ -362,6 +372,7 @@ def helper_dimer(
362
372
  # ------------------------------------------------------------
363
373
  cost_time = perf_counter() - start
364
374
  if not product_result.is_connected:
375
+ # TODO: parse Desorption
365
376
  e = HelperException(
366
377
  msg="product is not connected",
367
378
  label=graph_label,
@@ -442,7 +453,7 @@ def helper_adsorption(
442
453
  """
443
454
  if graph_label is None:
444
455
  graph_label = graph.get_key_for_metadata()
445
- graph_label += f"_{gas.get_key_for_metadata(False)}"
456
+ graph_label += f"_{gas.get_key_for_metadata(False)}"
446
457
 
447
458
  # -----------------------------------------
448
459
  # call adsorption initial positions
@@ -71,6 +71,7 @@ class OTFKMC(ExplorationABC):
71
71
 
72
72
  while True:
73
73
  otfkmc_info = OTFKMCInfo()
74
+ self.istep = len(df_data) - 1
74
75
  self.logger.info("=" * self._log_length)
75
76
  self.logger.info(f"Step {self.istep} Start")
76
77
  self.logger.info("-" * self._log_length)
@@ -1,9 +1,8 @@
1
1
  # ruff: noqa: F401
2
2
 
3
- from graphatoms.reaction.adsorption import Adsorption
4
- from graphatoms.reaction.desorption import Desorption
5
3
  from graphatoms.reaction._event import EventBase, EventInfo
6
4
  from graphatoms.reaction.reaction import ReactionER, ReactionLH
5
+ from graphatoms.reaction.xxsorption import Adsorption, Desorption
7
6
 
8
7
  __all__ = [
9
8
  "EventBase",
@@ -224,6 +224,10 @@ class RTGP(OurFrozenModel):
224
224
  # print(f"geom_moved: {geom_moved}")
225
225
 
226
226
  moved: np.ndarray = np.asarray(list(graph_node_moved), dtype=int)
227
+ nmax = max(len(self.R), len(self.P))
228
+ nmin = min(len(self.R), len(self.P))
229
+ gas_moved = list(range(nmin, nmax))
230
+ moved = np.setdiff1d(moved, gas_moved)
227
231
  pos = self.R.positions[moved, :].reshape(-1, len(moved), 3)
228
232
  v = self.R.positions[:, np.newaxis, :] - pos
229
233
  d = np.linalg.norm(v, axis=-1).min(-1)
@@ -234,6 +238,10 @@ class RTGP(OurFrozenModel):
234
238
  if i is None or isinstance(i, Gas):
235
239
  rtgp.append(i)
236
240
  elif isinstance(i, SysGraph):
241
+ if len(i) == nmax:
242
+ sub = np.append(sub, gas_moved)
243
+ else:
244
+ sub = np.setdiff1d(sub, gas_moved)
237
245
  rtgp.append(
238
246
  Cluster.select(
239
247
  i,
@@ -9,6 +9,101 @@ from graphatoms.reaction._event import EventBase, h, kB
9
9
  from graphatoms.system.system import System
10
10
 
11
11
 
12
+ class Adsorption(EventBase):
13
+ @model_validator(mode="after")
14
+ def __check_something(self) -> Self:
15
+ assert self.G is not None, "The gas must be not None."
16
+ assert self.T is None, "The transition state must be None."
17
+ assert len(self.P) == len(self.R) + len(self.G), (
18
+ "The product state must be the sum of the " #
19
+ "reactant state and the gas molecule."
20
+ )
21
+ return self
22
+
23
+ @property
24
+ @override
25
+ def reversed(self) -> "Desorption":
26
+ return Desorption(R=self.P, G=self.G, T=self.T, P=self.R)
27
+
28
+ @override
29
+ def apply(
30
+ self,
31
+ atoms: System | Atoms,
32
+ *args,
33
+ matched_indxs: list[int] | np.ndarray | None = None,
34
+ **kwargs,
35
+ ) -> tuple[Atoms, float]:
36
+ atoms, rmsd = super().apply(atoms, *args, matched_indxs, **kwargs)
37
+ patoms: Atoms = self.P.to_ase(exclude_energetics=True)
38
+ mask = np.arange(len(self.P)) >= len(self.R)
39
+ atoms.extend(patoms[mask])
40
+ return atoms, rmsd
41
+
42
+ @override
43
+ def get_Ea(self, *args, **kwargs) -> float: # type: ignore
44
+ """The adsorption reaction does not have an activation energy.
45
+
46
+ Returns:
47
+ np.inf
48
+ """
49
+ return np.inf
50
+
51
+ @override
52
+ def get_dE(
53
+ self,
54
+ temperature: float = 300.0,
55
+ *args,
56
+ pressure: float | None = None,
57
+ **kwargs,
58
+ ) -> float:
59
+ assert self.G is not None, "The gas must be not None."
60
+ e_P = self.P.get_free_energy(fqmin=30.0, temp=temperature)
61
+ e_R = self.R.get_free_energy(fqmin=30.0, temp=temperature)
62
+ if pressure is None:
63
+ pressure = self.G.pressure
64
+ assert pressure is not None, "The pressure must be not None."
65
+ e_G = self.G.get_free_energy(
66
+ fqmin=30.0,
67
+ temp=temperature,
68
+ pressure=pressure,
69
+ )
70
+ return e_P - e_R - e_G
71
+
72
+ @override
73
+ def get_rate(
74
+ self,
75
+ temperature: float = 300.0,
76
+ *args,
77
+ pressure: float | None = None,
78
+ sticking: float | None = None,
79
+ **kwargs,
80
+ ) -> float:
81
+ """Get the rate of the reaction by the collision theory.
82
+
83
+ Ref: Dominic R. Alfonso; Kinetic Monte Carlo Simul-
84
+ ation of CO Adsorption on Sulfur-Covered Pd(100).
85
+ J. Phys. Chem. A 2014, 118, 7306-7313.
86
+ Eq:
87
+ S*A
88
+ rate = -------------------
89
+ sqrt(2*pi*m*kB*T)
90
+ """
91
+ assert self.G is not None, "The gas must be not None."
92
+ kBT = kB * temperature
93
+ m = np.sum(MASS[self.G.numbers])
94
+ A = abs(self.G.area + self.R.area - self.P.area) / 2.0
95
+
96
+ if sticking is None:
97
+ sticking = self.G.sticking
98
+ assert sticking is not None, (
99
+ "The sticking coefficient must be not None."
100
+ )
101
+ if pressure is None:
102
+ pressure = self.G.pressure
103
+ assert pressure is not None, "The pressure must be not None."
104
+ return (sticking * A * pressure) / np.sqrt(2 * np.pi * m * kBT)
105
+
106
+
12
107
  class Desorption(EventBase):
13
108
  @model_validator(mode="after")
14
109
  def __check_something(self) -> Self:
@@ -20,6 +115,11 @@ class Desorption(EventBase):
20
115
  )
21
116
  return self
22
117
 
118
+ @property
119
+ @override
120
+ def reversed(self) -> "Adsorption":
121
+ return Adsorption(R=self.P, G=self.G, T=self.T, P=self.R)
122
+
23
123
  @override
24
124
  def apply(
25
125
  self,
@@ -179,6 +179,7 @@ class Gas(System):
179
179
  with warnings.catch_warnings():
180
180
  warnings.simplefilter("ignore")
181
181
  return IdealGasThermo(
182
+ atoms=Atoms(self.numbers, self.positions),
182
183
  vib_energies=evib, # type: ignore
183
184
  geometry=self.__geometry_type,
184
185
  symmetrynumber=self.nsymmetry,
@@ -1,99 +0,0 @@
1
- from typing import Self, override
2
-
3
- import numpy as np
4
- from ase import Atoms
5
- from ase.data import atomic_masses as MASS
6
- from pydantic import model_validator
7
-
8
- from graphatoms.reaction._event import EventBase, kB
9
- from graphatoms.system.system import System
10
-
11
-
12
- class Adsorption(EventBase):
13
- @model_validator(mode="after")
14
- def __check_something(self) -> Self:
15
- assert self.G is not None, "The gas must be not None."
16
- assert self.T is None, "The transition state must be None."
17
- assert len(self.P) == len(self.R) + len(self.G), (
18
- "The product state must be the sum of the " #
19
- "reactant state and the gas molecule."
20
- )
21
- return self
22
-
23
- @override
24
- def apply(
25
- self,
26
- atoms: System | Atoms,
27
- *args,
28
- matched_indxs: list[int] | np.ndarray | None = None,
29
- **kwargs,
30
- ) -> tuple[Atoms, float]:
31
- atoms, rmsd = super().apply(atoms, *args, matched_indxs, **kwargs)
32
- patoms: Atoms = self.P.to_ase(exclude_energetics=True)
33
- mask = np.arange(len(self.P)) >= len(self.R)
34
- atoms.extend(patoms[mask])
35
- return atoms, rmsd
36
-
37
- @override
38
- def get_Ea(self, *args, **kwargs) -> float: # type: ignore
39
- """The adsorption reaction does not have an activation energy.
40
-
41
- Returns:
42
- np.inf
43
- """
44
- return np.inf
45
-
46
- @override
47
- def get_dE(
48
- self,
49
- temperature: float = 300.0,
50
- *args,
51
- pressure: float | None = None,
52
- **kwargs,
53
- ) -> float:
54
- assert self.G is not None, "The gas must be not None."
55
- e_P = self.P.get_free_energy(fqmin=30.0, temp=temperature)
56
- e_R = self.R.get_free_energy(fqmin=30.0, temp=temperature)
57
- if pressure is None:
58
- pressure = self.G.pressure
59
- assert pressure is not None, "The pressure must be not None."
60
- e_G = self.G.get_free_energy(
61
- fqmin=30.0,
62
- temp=temperature,
63
- pressure=pressure,
64
- )
65
- return e_P - e_R - e_G
66
-
67
- @override
68
- def get_rate(
69
- self,
70
- temperature: float = 300.0,
71
- *args,
72
- pressure: float | None = None,
73
- sticking: float | None = None,
74
- **kwargs,
75
- ) -> float:
76
- """Get the rate of the reaction by the collision theory.
77
-
78
- Ref: Dominic R. Alfonso; Kinetic Monte Carlo Simul-
79
- ation of CO Adsorption on Sulfur-Covered Pd(100).
80
- J. Phys. Chem. A 2014, 118, 7306-7313.
81
- Eq:
82
- S*A
83
- rate = -------------------
84
- sqrt(2*pi*m*kB*T)
85
- """
86
- assert self.G is not None, "The gas must be not None."
87
- kBT = kB * temperature
88
- m = np.sum(MASS[self.G.numbers])
89
- A = abs(self.G.area + self.R.area - self.P.area) / 2.0
90
-
91
- if sticking is None:
92
- sticking = self.G.sticking
93
- assert sticking is not None, (
94
- "The sticking coefficient must be not None."
95
- )
96
- if pressure is None:
97
- pressure = self.G.pressure
98
- assert pressure is not None, "The pressure must be not None."
99
- return (sticking * A * pressure) / np.sqrt(2 * np.pi * m * kBT)
File without changes