mchammer-pt 0.27.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 (111) hide show
  1. mchammer_pt-0.27.0/LICENSE +21 -0
  2. mchammer_pt-0.27.0/PKG-INFO +321 -0
  3. mchammer_pt-0.27.0/README.md +286 -0
  4. mchammer_pt-0.27.0/mchammer_pt/__init__.py +106 -0
  5. mchammer_pt-0.27.0/mchammer_pt/analysis/__init__.py +30 -0
  6. mchammer_pt-0.27.0/mchammer_pt/analysis/_partition.py +101 -0
  7. mchammer_pt-0.27.0/mchammer_pt/analysis/coexistence.py +952 -0
  8. mchammer_pt-0.27.0/mchammer_pt/analysis/dos.py +225 -0
  9. mchammer_pt-0.27.0/mchammer_pt/analysis/observables.py +333 -0
  10. mchammer_pt-0.27.0/mchammer_pt/base.py +318 -0
  11. mchammer_pt-0.27.0/mchammer_pt/callbacks.py +433 -0
  12. mchammer_pt-0.27.0/mchammer_pt/canonical.py +671 -0
  13. mchammer_pt-0.27.0/mchammer_pt/checkpoint.py +615 -0
  14. mchammer_pt-0.27.0/mchammer_pt/cli/__init__.py +0 -0
  15. mchammer_pt-0.27.0/mchammer_pt/cli/coexistence.py +175 -0
  16. mchammer_pt-0.27.0/mchammer_pt/cli/reassemble.py +306 -0
  17. mchammer_pt-0.27.0/mchammer_pt/cli/reweight.py +141 -0
  18. mchammer_pt-0.27.0/mchammer_pt/cli/reweight_observables.py +169 -0
  19. mchammer_pt-0.27.0/mchammer_pt/cli/stitch.py +402 -0
  20. mchammer_pt-0.27.0/mchammer_pt/cli/stitch_multirun.py +274 -0
  21. mchammer_pt-0.27.0/mchammer_pt/cli/stitch_observables.py +137 -0
  22. mchammer_pt-0.27.0/mchammer_pt/contrib/__init__.py +50 -0
  23. mchammer_pt-0.27.0/mchammer_pt/diagnostics.py +146 -0
  24. mchammer_pt-0.27.0/mchammer_pt/exchange.py +83 -0
  25. mchammer_pt-0.27.0/mchammer_pt/history.py +452 -0
  26. mchammer_pt-0.27.0/mchammer_pt/parallel/__init__.py +0 -0
  27. mchammer_pt-0.27.0/mchammer_pt/parallel/_builder.py +151 -0
  28. mchammer_pt-0.27.0/mchammer_pt/parallel/_comms.py +157 -0
  29. mchammer_pt-0.27.0/mchammer_pt/parallel/_imports.py +98 -0
  30. mchammer_pt-0.27.0/mchammer_pt/parallel/_worker.py +412 -0
  31. mchammer_pt-0.27.0/mchammer_pt/parallel/backend.py +444 -0
  32. mchammer_pt-0.27.0/mchammer_pt/parallel/processes.py +1804 -0
  33. mchammer_pt-0.27.0/mchammer_pt/parallel/serial.py +888 -0
  34. mchammer_pt-0.27.0/mchammer_pt/replica.py +383 -0
  35. mchammer_pt-0.27.0/mchammer_pt/seeding/__init__.py +12 -0
  36. mchammer_pt-0.27.0/mchammer_pt/seeding/anchoring.py +79 -0
  37. mchammer_pt-0.27.0/mchammer_pt/seeding/bookkeeping.py +64 -0
  38. mchammer_pt-0.27.0/mchammer_pt/seeding/params.py +60 -0
  39. mchammer_pt-0.27.0/mchammer_pt/seeding/search.py +463 -0
  40. mchammer_pt-0.27.0/mchammer_pt/seeding/walk.py +72 -0
  41. mchammer_pt-0.27.0/mchammer_pt/testing.py +237 -0
  42. mchammer_pt-0.27.0/mchammer_pt/wl.py +1412 -0
  43. mchammer_pt-0.27.0/mchammer_pt/wl_coordinator.py +674 -0
  44. mchammer_pt-0.27.0/mchammer_pt/wl_ensemble.py +414 -0
  45. mchammer_pt-0.27.0/mchammer_pt/wl_initial_structures.py +84 -0
  46. mchammer_pt-0.27.0/mchammer_pt/wl_merge_diagnostics.py +35 -0
  47. mchammer_pt-0.27.0/mchammer_pt/wl_observable_recorder.py +271 -0
  48. mchammer_pt-0.27.0/mchammer_pt/wl_replica.py +1065 -0
  49. mchammer_pt-0.27.0/mchammer_pt/wl_result.py +196 -0
  50. mchammer_pt-0.27.0/mchammer_pt/wl_window_group.py +493 -0
  51. mchammer_pt-0.27.0/mchammer_pt.egg-info/PKG-INFO +321 -0
  52. mchammer_pt-0.27.0/mchammer_pt.egg-info/SOURCES.txt +109 -0
  53. mchammer_pt-0.27.0/mchammer_pt.egg-info/dependency_links.txt +1 -0
  54. mchammer_pt-0.27.0/mchammer_pt.egg-info/entry_points.txt +7 -0
  55. mchammer_pt-0.27.0/mchammer_pt.egg-info/requires.txt +20 -0
  56. mchammer_pt-0.27.0/mchammer_pt.egg-info/top_level.txt +1 -0
  57. mchammer_pt-0.27.0/pyproject.toml +83 -0
  58. mchammer_pt-0.27.0/setup.cfg +4 -0
  59. mchammer_pt-0.27.0/tests/test_allow_kwargs_mismatch.py +279 -0
  60. mchammer_pt-0.27.0/tests/test_analysis_observables.py +546 -0
  61. mchammer_pt-0.27.0/tests/test_base.py +497 -0
  62. mchammer_pt-0.27.0/tests/test_boltzmann_sampling.py +98 -0
  63. mchammer_pt-0.27.0/tests/test_callbacks.py +267 -0
  64. mchammer_pt-0.27.0/tests/test_canonical.py +605 -0
  65. mchammer_pt-0.27.0/tests/test_checkpoint.py +972 -0
  66. mchammer_pt-0.27.0/tests/test_cli_coexistence.py +257 -0
  67. mchammer_pt-0.27.0/tests/test_cli_reassemble.py +333 -0
  68. mchammer_pt-0.27.0/tests/test_cli_reweight.py +127 -0
  69. mchammer_pt-0.27.0/tests/test_cli_reweight_observables.py +152 -0
  70. mchammer_pt-0.27.0/tests/test_cli_stitch.py +743 -0
  71. mchammer_pt-0.27.0/tests/test_cli_stitch_multirun.py +271 -0
  72. mchammer_pt-0.27.0/tests/test_cli_stitch_observables.py +339 -0
  73. mchammer_pt-0.27.0/tests/test_coexistence.py +1030 -0
  74. mchammer_pt-0.27.0/tests/test_comms.py +197 -0
  75. mchammer_pt-0.27.0/tests/test_contrib.py +67 -0
  76. mchammer_pt-0.27.0/tests/test_diagnostics.py +163 -0
  77. mchammer_pt-0.27.0/tests/test_dos.py +368 -0
  78. mchammer_pt-0.27.0/tests/test_exchange.py +99 -0
  79. mchammer_pt-0.27.0/tests/test_history.py +517 -0
  80. mchammer_pt-0.27.0/tests/test_imports.py +79 -0
  81. mchammer_pt-0.27.0/tests/test_in_process_worker.py +84 -0
  82. mchammer_pt-0.27.0/tests/test_integration.py +111 -0
  83. mchammer_pt-0.27.0/tests/test_pools.py +1574 -0
  84. mchammer_pt-0.27.0/tests/test_progress.py +596 -0
  85. mchammer_pt-0.27.0/tests/test_replica.py +262 -0
  86. mchammer_pt-0.27.0/tests/test_reweight_observables.py +184 -0
  87. mchammer_pt-0.27.0/tests/test_seeding_anchoring.py +50 -0
  88. mchammer_pt-0.27.0/tests/test_seeding_bookkeeping.py +59 -0
  89. mchammer_pt-0.27.0/tests/test_seeding_params.py +57 -0
  90. mchammer_pt-0.27.0/tests/test_seeding_public_api.py +40 -0
  91. mchammer_pt-0.27.0/tests/test_seeding_search_validation.py +170 -0
  92. mchammer_pt-0.27.0/tests/test_seeding_validation.py +31 -0
  93. mchammer_pt-0.27.0/tests/test_seeding_walk.py +52 -0
  94. mchammer_pt-0.27.0/tests/test_version.py +23 -0
  95. mchammer_pt-0.27.0/tests/test_wl.py +2640 -0
  96. mchammer_pt-0.27.0/tests/test_wl_coordinator.py +831 -0
  97. mchammer_pt-0.27.0/tests/test_wl_ensemble.py +435 -0
  98. mchammer_pt-0.27.0/tests/test_wl_frozen_measurement.py +123 -0
  99. mchammer_pt-0.27.0/tests/test_wl_initial_structures.py +88 -0
  100. mchammer_pt-0.27.0/tests/test_wl_measure.py +624 -0
  101. mchammer_pt-0.27.0/tests/test_wl_measurement_e2e.py +619 -0
  102. mchammer_pt-0.27.0/tests/test_wl_merge_diagnostics.py +380 -0
  103. mchammer_pt-0.27.0/tests/test_wl_observable_recorder.py +452 -0
  104. mchammer_pt-0.27.0/tests/test_wl_observable_recording.py +250 -0
  105. mchammer_pt-0.27.0/tests/test_wl_pool.py +2288 -0
  106. mchammer_pt-0.27.0/tests/test_wl_replica.py +1454 -0
  107. mchammer_pt-0.27.0/tests/test_wl_replica_observables.py +402 -0
  108. mchammer_pt-0.27.0/tests/test_wl_result.py +495 -0
  109. mchammer_pt-0.27.0/tests/test_wl_window_group.py +947 -0
  110. mchammer_pt-0.27.0/tests/test_worker.py +134 -0
  111. mchammer_pt-0.27.0/tests/test_worker_builders.py +272 -0
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Benjamin J. Morgan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,321 @@
1
+ Metadata-Version: 2.4
2
+ Name: mchammer-pt
3
+ Version: 0.27.0
4
+ Summary: Replica-exchange orchestrators for mchammer: canonical parallel tempering and multi-walker replica-exchange Wang-Landau
5
+ Author-email: "Benjamin J. Morgan" <b.j.morgan@bath.ac.uk>
6
+ License-Expression: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Scientific/Engineering
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: numpy
18
+ Requires-Dist: pandas
19
+ Requires-Dist: scipy
20
+ Requires-Dist: ase
21
+ Requires-Dist: icet>=3.2
22
+ Requires-Dist: h5py
23
+ Provides-Extra: custom-moves
24
+ Requires-Dist: mchammer-moves>=0.6.0; extra == "custom-moves"
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest; extra == "dev"
27
+ Requires-Dist: mypy; extra == "dev"
28
+ Requires-Dist: ruff; extra == "dev"
29
+ Requires-Dist: pandas-stubs; extra == "dev"
30
+ Requires-Dist: scipy-stubs; extra == "dev"
31
+ Requires-Dist: mchammer-pt[custom-moves]; extra == "dev"
32
+ Provides-Extra: plot
33
+ Requires-Dist: matplotlib; extra == "plot"
34
+ Dynamic: license-file
35
+
36
+ # mchammer-pt
37
+
38
+ Replica-exchange orchestrators for [`mchammer`](https://icet.materialsmodeling.org/)
39
+ Monte Carlo with `icet` cluster expansions: canonical-ensemble
40
+ parallel tempering across a temperature ladder, and replica-exchange
41
+ Wang-Landau (REWL) across an energy-window ladder, with optional
42
+ multiple walkers per window.
43
+
44
+ For an architecture overview, see [docs/architecture.md](docs/architecture.md).
45
+
46
+ ## Why
47
+
48
+ `mchammer`'s canonical ensemble samples at a single temperature. Large
49
+ supercells with competing ordered basins can trap the chain in local
50
+ minima; a single-temperature chain may never visit the other basin.
51
+ Parallel tempering runs `N` replicas at different temperatures and
52
+ periodically proposes configuration swaps between adjacent replicas,
53
+ so a high-temperature chain can cross barriers and deliver escape
54
+ paths to the colder chains.
55
+
56
+ `mchammer`'s single-walker Wang-Landau samples a fixed energy window,
57
+ but on rugged density-of-states landscapes the walker spends long
58
+ stretches near the window edges and the fill-factor schedule stalls
59
+ before the histogram is flat. REWL splits the target energy range
60
+ into overlapping windows and proposes configuration swaps between
61
+ adjacent windows weighted by their within-window density-of-states
62
+ ratio, so each walker mixes faster inside its own window and the
63
+ combined run converges in wall-clock time that scales with window
64
+ width rather than total energy range. Each window optionally runs
65
+ multiple walkers in lockstep that share the flatness gate and merge
66
+ their entropy estimates, further reducing the random-walk variance
67
+ that drives Wang-Landau's per-window convergence cost.
68
+
69
+ ## Features
70
+
71
+ - `CanonicalParallelTempering` — canonical-ensemble PT with an
72
+ arbitrary temperature ladder.
73
+ - `WangLandauParallelTempering` — replica-exchange Wang-Landau
74
+ (REWL) on top of icet's `WangLandauEnsemble`. Each window owns a
75
+ fixed energy range; adjacent windows attempt configuration swaps
76
+ with a within-window density-of-states ratio for acceptance.
77
+ `n_walkers_per_window` (scalar or per-window sequence) runs
78
+ multiple WL walkers inside the same window, sharing the flatness
79
+ gate and merging entropies across the group — straightforward to
80
+ configure here, not exposed by raw icet. To use the
81
+ Belardinelli-Pereyra 1/t schedule, pass
82
+ `ensemble_kwargs={'schedule': '1_over_t'}`; the default
83
+ `schedule='halving'` gives the standard WL fill-factor scheme.
84
+ Serial and process-parallel backends as for the canonical
85
+ orchestrator; checkpoint/resume into either pool kind.
86
+ - `mchammer_pt.analysis.dos.stitch_entropy` and
87
+ `reweight_canonical_from_dos` post-process REWL output: stitch the
88
+ per-window ln g(E) curves into a single density of states (working
89
+ in log space, with bin-index matching that survives ULP-level
90
+ energy drift between windows), then evaluate canonical
91
+ thermodynamic observables on a user-supplied temperature grid.
92
+ - `mchammer-pt-stitch` and `mchammer-pt-reweight` console scripts
93
+ expose the same pipeline from the command line, reading either an
94
+ mchammer-pt checkpoint HDF5 or `WangLandauDataContainer` files
95
+ directly.
96
+ - Serial and multiprocessing backends, swappable via a single
97
+ constructor argument.
98
+ - Custom Monte Carlo moves: pass any `mchammer.CanonicalEnsemble`
99
+ subclass via `ensemble_cls=`, with extra constructor arguments
100
+ forwarded via `ensemble_kwargs=`. Custom `_do_trial_step` overrides
101
+ ride the PT machinery without subclassing `Replica`.
102
+ - Per-replica `mchammer.BaseObserver` attachment on both serial and
103
+ process-parallel pools, with each replica receiving its own
104
+ observer copy. Three attach paths cover the spectrum: pass an
105
+ observer instance for the common case (`attach_observer`), a class
106
+ plus constructor arguments when picklable (`attach_observer_class`),
107
+ or a top-level factory that constructs the observer inside each
108
+ worker — required for observers like `ClusterCountObserver` whose
109
+ constructors take icet `ClusterSpace` objects that do not pickle
110
+ (`attach_observer_factory`). The factory reloads the
111
+ `ClusterExpansion` from disk via
112
+ `ClusterExpansion.read(replica.cluster_expansion_path)`;
113
+ `ProcessPool` auto-populates the path on every worker.
114
+ - HDF5 output bundling one `mchammer.BaseDataContainer` per replica plus
115
+ a compact `ExchangeHistory` of per-pair swap statistics and
116
+ replica-label trajectories.
117
+ - Round-trip count and integrated-autocorrelation-time diagnostics
118
+ as pure functions over the run output.
119
+ - `ExchangeCallback` protocol for PT-level events (with `ExchangePrinter`
120
+ and `SwapRateTracker` built-ins).
121
+ - `CycleCallback` protocol for per-cycle hooks, with `ProgressPrinter`
122
+ built-in for periodic stderr progress lines on long runs (cycle,
123
+ percent, elapsed, ETA, swap-acceptance rates).
124
+ - `CheckpointWriter` cycle callback and
125
+ `CanonicalParallelTempering.resume(...)` for crash-safe long runs
126
+ and bit-identical continuation across `pt.run()` calls (after
127
+ `ExchangeHistory.concatenate`). Same payload also written by
128
+ `pt.save_checkpoint(path)` and via the existing
129
+ `data_container_file=` constructor kwarg.
130
+ - `mchammer_pt.testing.assert_boltzmann_sampling` — public utility for
131
+ pinning the empirical stationary distribution of a custom
132
+ `CanonicalEnsemble` subclass against an analytic Boltzmann fixture.
133
+ Downstream packages providing custom moves can use this to pin
134
+ stationarity correctness against the same anchor as mchammer-pt's
135
+ own test suite.
136
+
137
+ ## Install
138
+
139
+ pip install -e .
140
+
141
+ Requires Python 3.11+ and `icet>=3.2` (installed automatically from
142
+ PyPI).
143
+
144
+ Optional dev tooling: `pip install -e '.[dev]'` adds `pytest`,
145
+ `mypy`, `ruff`.
146
+
147
+ ## Quickstart
148
+
149
+ ```python
150
+ from ase.build import bulk
151
+ from icet import ClusterExpansion
152
+ from mchammer_pt import CanonicalParallelTempering
153
+
154
+ ce = ClusterExpansion.read("my_ce.ce")
155
+ atoms = bulk("Cu", "fcc", a=4.0, cubic=True).repeat((4, 4, 4))
156
+ # ... decorate atoms with the correct composition ...
157
+
158
+ pt = CanonicalParallelTempering(
159
+ cluster_expansion=ce,
160
+ atoms=atoms,
161
+ temperatures=[100, 200, 350, 550, 800, 1200, 1800, 2700],
162
+ block_size=1000,
163
+ random_seed=0,
164
+ data_container_file="pt.h5",
165
+ )
166
+
167
+ # Optional: live progress on stderr for long runs.
168
+ from mchammer_pt import ProgressPrinter
169
+ pt.attach_cycle_callback(ProgressPrinter(interval=100))
170
+
171
+ pt.run(n_cycles=200)
172
+
173
+ # Diagnostics.
174
+ from mchammer_pt import (
175
+ round_trip_counts,
176
+ swap_acceptance_rates,
177
+ energy_autocorrelation_time,
178
+ )
179
+ print("acceptance:", swap_acceptance_rates(pt.history))
180
+ print("round-trips:", round_trip_counts(pt.history.replica_labels_per_cycle))
181
+ # The single-argument form above is for one walker per rung (canonical PT
182
+ # and single-walker REWL). For multi-walker REWL pass the window mapping,
183
+ # which the history carries (so it works on a run read back from disk):
184
+ # round_trip_counts(pt.history.replica_labels_per_cycle,
185
+ # pt.history.window_of_position)
186
+ for r in range(len(pt.pool)):
187
+ tau = energy_autocorrelation_time(pt.history.energies_per_cycle[:, r])
188
+ print(f"replica {r}: tau = {tau:.1f} cycles")
189
+ ```
190
+
191
+ For multiprocess parallelism, use the `process_pool` classmethod:
192
+
193
+ ```python
194
+ with CanonicalParallelTempering.process_pool(
195
+ cluster_expansion=ce,
196
+ atoms=atoms,
197
+ temperatures=[200, 400, 800, 1600],
198
+ block_size=1000,
199
+ random_seed=0,
200
+ ) as pt:
201
+ pt.run(n_cycles=200)
202
+ ```
203
+
204
+ The factory handles seed spawning, writing the CE to a managed temp
205
+ directory, and constructing a `ProcessPool` at the same ladder as
206
+ the orchestrator. See `examples/03_parallel_workers.py`.
207
+
208
+ Observer attachment is supported on both `SerialPool` and `ProcessPool`.
209
+ See the Features list above for the three attach paths and when to use each.
210
+
211
+ For custom Monte Carlo moves, subclass `mchammer.CanonicalEnsemble`
212
+ and pass via `ensemble_cls=`:
213
+
214
+ ```python
215
+ from mchammer.ensembles import CanonicalEnsemble
216
+
217
+ class MyMove(CanonicalEnsemble):
218
+ def _do_trial_step(self) -> int:
219
+ # ... your custom move ...
220
+ return super()._do_trial_step()
221
+
222
+ with CanonicalParallelTempering.process_pool(
223
+ cluster_expansion=ce,
224
+ atoms=atoms,
225
+ temperatures=[200, 400, 800, 1600],
226
+ block_size=1000,
227
+ random_seed=0,
228
+ ensemble_cls=MyMove,
229
+ ) as pt:
230
+ pt.run(n_cycles=200)
231
+ ```
232
+
233
+ Spawn workers re-import the class by fully qualified name, so define
234
+ the subclass in a `.py` module file rather than a Jupyter cell. See
235
+ `examples/05_custom_ensemble.py` for a complete worked example.
236
+
237
+ ### Wang-Landau parallel tempering
238
+
239
+ For Wang-Landau parallel tempering, build per-window starting
240
+ configurations whose energies lie inside their assigned windows,
241
+ then drive `WangLandauParallelTempering.from_bin_count` (or pass
242
+ explicit `windows=` for non-uniform splits):
243
+
244
+ ```python
245
+ from mchammer_pt import WangLandauParallelTempering
246
+
247
+ # `per_window_atoms` is a list[Atoms], one per window, with each
248
+ # entry's energy in the corresponding window. Generating these
249
+ # is the user's responsibility — typically a short pilot MC run.
250
+ pt = WangLandauParallelTempering.from_bin_count(
251
+ cluster_expansion=ce,
252
+ atoms=per_window_atoms,
253
+ n_bins=4,
254
+ energy_spacing=1.0,
255
+ minimum_energy=-32.0,
256
+ maximum_energy=32.0,
257
+ overlap=4,
258
+ block_size=len(per_window_atoms[0]) * 1000,
259
+ random_seed=0,
260
+ )
261
+ pt.run(n_cycles=500)
262
+ ```
263
+
264
+ `pt.run(...)` exits early once every replica reports `converged`.
265
+ `WangLandauParallelTempering.process_pool(...)` spawns one OS
266
+ process per replica. `save_checkpoint(path)` / `resume(path, ...)`
267
+ / `resume_process_pool(path, ...)` mirror the canonical surface.
268
+ Observers attach the same way as on the canonical pool (via
269
+ `pt.attach_observer(...)` or, for the class and factory paths,
270
+ directly on `pt.pool`); each replica's recorded observable
271
+ trajectory ends up in its `WangLandauDataContainer`, ready for
272
+ icet's `get_average_observables_wl` against the stitched ln g(E).
273
+
274
+ Stitch the per-window ln g(E) curves into a single density of
275
+ states, then reweight onto a canonical temperature grid:
276
+
277
+ ```python
278
+ from mchammer_pt.analysis.dos import (
279
+ reweight_canonical_from_dos,
280
+ stitch_entropy,
281
+ )
282
+
283
+ per_window = [r.get_entropy() for r in pt.results()]
284
+ stitched, errors = stitch_entropy(per_window, energy_spacing=1.0)
285
+ canonical = reweight_canonical_from_dos(
286
+ stitched, temperatures=[100, 200, 400, 800, 1600],
287
+ )
288
+ ```
289
+
290
+ The same pipeline is available from the command line via the
291
+ `mchammer-pt-stitch` and `mchammer-pt-reweight` console scripts, which
292
+ read either an mchammer-pt checkpoint HDF5 or
293
+ `WangLandauDataContainer` files directly. Pass `--multi-run` with two
294
+ or more checkpoints to merge independent seeds of the same system into
295
+ one consensus DOS (each window is merged across runs before
296
+ stitching). For production runs on a new system, plan
297
+ to validate the recovered DOS against ground truth (e.g. by
298
+ brute-force enumeration on a small case, or against an analytic
299
+ result) before trusting downstream thermodynamic averages.
300
+
301
+ ## Examples
302
+
303
+ - `examples/01_basic_canonical.py` — self-contained run on a toy Cu/Au CE.
304
+ - `examples/02_custom_callback.py` — writing your own `ExchangeCallback`.
305
+ - `examples/03_parallel_workers.py` — PT with the `ProcessPool`.
306
+ - `examples/04_equilibrium_sampling.py` – discarding the initial burn-in period for equilibrium sampling.
307
+ - `examples/05_custom_ensemble.py` — PT with a custom
308
+ `CanonicalEnsemble` subclass.
309
+ - `examples/06_progress_monitoring.py` — live progress on stderr for
310
+ long runs via `ProgressPrinter`.
311
+ - `examples/07_resume.py` — checkpoint and resume a PT run, with
312
+ bit-identical continuation.
313
+ - `examples/08_rewl.py` — replica-exchange Wang-Landau on a 4x4
314
+ 2D Ising model, with per-window seeding and DOS stitching.
315
+ - `examples/09_dos_postprocessing.py` — stitching REWL output into
316
+ a single ln g(E) and reweighting onto a canonical temperature
317
+ grid via `mchammer_pt.analysis.dos`.
318
+
319
+ ## License
320
+
321
+ MIT.
@@ -0,0 +1,286 @@
1
+ # mchammer-pt
2
+
3
+ Replica-exchange orchestrators for [`mchammer`](https://icet.materialsmodeling.org/)
4
+ Monte Carlo with `icet` cluster expansions: canonical-ensemble
5
+ parallel tempering across a temperature ladder, and replica-exchange
6
+ Wang-Landau (REWL) across an energy-window ladder, with optional
7
+ multiple walkers per window.
8
+
9
+ For an architecture overview, see [docs/architecture.md](docs/architecture.md).
10
+
11
+ ## Why
12
+
13
+ `mchammer`'s canonical ensemble samples at a single temperature. Large
14
+ supercells with competing ordered basins can trap the chain in local
15
+ minima; a single-temperature chain may never visit the other basin.
16
+ Parallel tempering runs `N` replicas at different temperatures and
17
+ periodically proposes configuration swaps between adjacent replicas,
18
+ so a high-temperature chain can cross barriers and deliver escape
19
+ paths to the colder chains.
20
+
21
+ `mchammer`'s single-walker Wang-Landau samples a fixed energy window,
22
+ but on rugged density-of-states landscapes the walker spends long
23
+ stretches near the window edges and the fill-factor schedule stalls
24
+ before the histogram is flat. REWL splits the target energy range
25
+ into overlapping windows and proposes configuration swaps between
26
+ adjacent windows weighted by their within-window density-of-states
27
+ ratio, so each walker mixes faster inside its own window and the
28
+ combined run converges in wall-clock time that scales with window
29
+ width rather than total energy range. Each window optionally runs
30
+ multiple walkers in lockstep that share the flatness gate and merge
31
+ their entropy estimates, further reducing the random-walk variance
32
+ that drives Wang-Landau's per-window convergence cost.
33
+
34
+ ## Features
35
+
36
+ - `CanonicalParallelTempering` — canonical-ensemble PT with an
37
+ arbitrary temperature ladder.
38
+ - `WangLandauParallelTempering` — replica-exchange Wang-Landau
39
+ (REWL) on top of icet's `WangLandauEnsemble`. Each window owns a
40
+ fixed energy range; adjacent windows attempt configuration swaps
41
+ with a within-window density-of-states ratio for acceptance.
42
+ `n_walkers_per_window` (scalar or per-window sequence) runs
43
+ multiple WL walkers inside the same window, sharing the flatness
44
+ gate and merging entropies across the group — straightforward to
45
+ configure here, not exposed by raw icet. To use the
46
+ Belardinelli-Pereyra 1/t schedule, pass
47
+ `ensemble_kwargs={'schedule': '1_over_t'}`; the default
48
+ `schedule='halving'` gives the standard WL fill-factor scheme.
49
+ Serial and process-parallel backends as for the canonical
50
+ orchestrator; checkpoint/resume into either pool kind.
51
+ - `mchammer_pt.analysis.dos.stitch_entropy` and
52
+ `reweight_canonical_from_dos` post-process REWL output: stitch the
53
+ per-window ln g(E) curves into a single density of states (working
54
+ in log space, with bin-index matching that survives ULP-level
55
+ energy drift between windows), then evaluate canonical
56
+ thermodynamic observables on a user-supplied temperature grid.
57
+ - `mchammer-pt-stitch` and `mchammer-pt-reweight` console scripts
58
+ expose the same pipeline from the command line, reading either an
59
+ mchammer-pt checkpoint HDF5 or `WangLandauDataContainer` files
60
+ directly.
61
+ - Serial and multiprocessing backends, swappable via a single
62
+ constructor argument.
63
+ - Custom Monte Carlo moves: pass any `mchammer.CanonicalEnsemble`
64
+ subclass via `ensemble_cls=`, with extra constructor arguments
65
+ forwarded via `ensemble_kwargs=`. Custom `_do_trial_step` overrides
66
+ ride the PT machinery without subclassing `Replica`.
67
+ - Per-replica `mchammer.BaseObserver` attachment on both serial and
68
+ process-parallel pools, with each replica receiving its own
69
+ observer copy. Three attach paths cover the spectrum: pass an
70
+ observer instance for the common case (`attach_observer`), a class
71
+ plus constructor arguments when picklable (`attach_observer_class`),
72
+ or a top-level factory that constructs the observer inside each
73
+ worker — required for observers like `ClusterCountObserver` whose
74
+ constructors take icet `ClusterSpace` objects that do not pickle
75
+ (`attach_observer_factory`). The factory reloads the
76
+ `ClusterExpansion` from disk via
77
+ `ClusterExpansion.read(replica.cluster_expansion_path)`;
78
+ `ProcessPool` auto-populates the path on every worker.
79
+ - HDF5 output bundling one `mchammer.BaseDataContainer` per replica plus
80
+ a compact `ExchangeHistory` of per-pair swap statistics and
81
+ replica-label trajectories.
82
+ - Round-trip count and integrated-autocorrelation-time diagnostics
83
+ as pure functions over the run output.
84
+ - `ExchangeCallback` protocol for PT-level events (with `ExchangePrinter`
85
+ and `SwapRateTracker` built-ins).
86
+ - `CycleCallback` protocol for per-cycle hooks, with `ProgressPrinter`
87
+ built-in for periodic stderr progress lines on long runs (cycle,
88
+ percent, elapsed, ETA, swap-acceptance rates).
89
+ - `CheckpointWriter` cycle callback and
90
+ `CanonicalParallelTempering.resume(...)` for crash-safe long runs
91
+ and bit-identical continuation across `pt.run()` calls (after
92
+ `ExchangeHistory.concatenate`). Same payload also written by
93
+ `pt.save_checkpoint(path)` and via the existing
94
+ `data_container_file=` constructor kwarg.
95
+ - `mchammer_pt.testing.assert_boltzmann_sampling` — public utility for
96
+ pinning the empirical stationary distribution of a custom
97
+ `CanonicalEnsemble` subclass against an analytic Boltzmann fixture.
98
+ Downstream packages providing custom moves can use this to pin
99
+ stationarity correctness against the same anchor as mchammer-pt's
100
+ own test suite.
101
+
102
+ ## Install
103
+
104
+ pip install -e .
105
+
106
+ Requires Python 3.11+ and `icet>=3.2` (installed automatically from
107
+ PyPI).
108
+
109
+ Optional dev tooling: `pip install -e '.[dev]'` adds `pytest`,
110
+ `mypy`, `ruff`.
111
+
112
+ ## Quickstart
113
+
114
+ ```python
115
+ from ase.build import bulk
116
+ from icet import ClusterExpansion
117
+ from mchammer_pt import CanonicalParallelTempering
118
+
119
+ ce = ClusterExpansion.read("my_ce.ce")
120
+ atoms = bulk("Cu", "fcc", a=4.0, cubic=True).repeat((4, 4, 4))
121
+ # ... decorate atoms with the correct composition ...
122
+
123
+ pt = CanonicalParallelTempering(
124
+ cluster_expansion=ce,
125
+ atoms=atoms,
126
+ temperatures=[100, 200, 350, 550, 800, 1200, 1800, 2700],
127
+ block_size=1000,
128
+ random_seed=0,
129
+ data_container_file="pt.h5",
130
+ )
131
+
132
+ # Optional: live progress on stderr for long runs.
133
+ from mchammer_pt import ProgressPrinter
134
+ pt.attach_cycle_callback(ProgressPrinter(interval=100))
135
+
136
+ pt.run(n_cycles=200)
137
+
138
+ # Diagnostics.
139
+ from mchammer_pt import (
140
+ round_trip_counts,
141
+ swap_acceptance_rates,
142
+ energy_autocorrelation_time,
143
+ )
144
+ print("acceptance:", swap_acceptance_rates(pt.history))
145
+ print("round-trips:", round_trip_counts(pt.history.replica_labels_per_cycle))
146
+ # The single-argument form above is for one walker per rung (canonical PT
147
+ # and single-walker REWL). For multi-walker REWL pass the window mapping,
148
+ # which the history carries (so it works on a run read back from disk):
149
+ # round_trip_counts(pt.history.replica_labels_per_cycle,
150
+ # pt.history.window_of_position)
151
+ for r in range(len(pt.pool)):
152
+ tau = energy_autocorrelation_time(pt.history.energies_per_cycle[:, r])
153
+ print(f"replica {r}: tau = {tau:.1f} cycles")
154
+ ```
155
+
156
+ For multiprocess parallelism, use the `process_pool` classmethod:
157
+
158
+ ```python
159
+ with CanonicalParallelTempering.process_pool(
160
+ cluster_expansion=ce,
161
+ atoms=atoms,
162
+ temperatures=[200, 400, 800, 1600],
163
+ block_size=1000,
164
+ random_seed=0,
165
+ ) as pt:
166
+ pt.run(n_cycles=200)
167
+ ```
168
+
169
+ The factory handles seed spawning, writing the CE to a managed temp
170
+ directory, and constructing a `ProcessPool` at the same ladder as
171
+ the orchestrator. See `examples/03_parallel_workers.py`.
172
+
173
+ Observer attachment is supported on both `SerialPool` and `ProcessPool`.
174
+ See the Features list above for the three attach paths and when to use each.
175
+
176
+ For custom Monte Carlo moves, subclass `mchammer.CanonicalEnsemble`
177
+ and pass via `ensemble_cls=`:
178
+
179
+ ```python
180
+ from mchammer.ensembles import CanonicalEnsemble
181
+
182
+ class MyMove(CanonicalEnsemble):
183
+ def _do_trial_step(self) -> int:
184
+ # ... your custom move ...
185
+ return super()._do_trial_step()
186
+
187
+ with CanonicalParallelTempering.process_pool(
188
+ cluster_expansion=ce,
189
+ atoms=atoms,
190
+ temperatures=[200, 400, 800, 1600],
191
+ block_size=1000,
192
+ random_seed=0,
193
+ ensemble_cls=MyMove,
194
+ ) as pt:
195
+ pt.run(n_cycles=200)
196
+ ```
197
+
198
+ Spawn workers re-import the class by fully qualified name, so define
199
+ the subclass in a `.py` module file rather than a Jupyter cell. See
200
+ `examples/05_custom_ensemble.py` for a complete worked example.
201
+
202
+ ### Wang-Landau parallel tempering
203
+
204
+ For Wang-Landau parallel tempering, build per-window starting
205
+ configurations whose energies lie inside their assigned windows,
206
+ then drive `WangLandauParallelTempering.from_bin_count` (or pass
207
+ explicit `windows=` for non-uniform splits):
208
+
209
+ ```python
210
+ from mchammer_pt import WangLandauParallelTempering
211
+
212
+ # `per_window_atoms` is a list[Atoms], one per window, with each
213
+ # entry's energy in the corresponding window. Generating these
214
+ # is the user's responsibility — typically a short pilot MC run.
215
+ pt = WangLandauParallelTempering.from_bin_count(
216
+ cluster_expansion=ce,
217
+ atoms=per_window_atoms,
218
+ n_bins=4,
219
+ energy_spacing=1.0,
220
+ minimum_energy=-32.0,
221
+ maximum_energy=32.0,
222
+ overlap=4,
223
+ block_size=len(per_window_atoms[0]) * 1000,
224
+ random_seed=0,
225
+ )
226
+ pt.run(n_cycles=500)
227
+ ```
228
+
229
+ `pt.run(...)` exits early once every replica reports `converged`.
230
+ `WangLandauParallelTempering.process_pool(...)` spawns one OS
231
+ process per replica. `save_checkpoint(path)` / `resume(path, ...)`
232
+ / `resume_process_pool(path, ...)` mirror the canonical surface.
233
+ Observers attach the same way as on the canonical pool (via
234
+ `pt.attach_observer(...)` or, for the class and factory paths,
235
+ directly on `pt.pool`); each replica's recorded observable
236
+ trajectory ends up in its `WangLandauDataContainer`, ready for
237
+ icet's `get_average_observables_wl` against the stitched ln g(E).
238
+
239
+ Stitch the per-window ln g(E) curves into a single density of
240
+ states, then reweight onto a canonical temperature grid:
241
+
242
+ ```python
243
+ from mchammer_pt.analysis.dos import (
244
+ reweight_canonical_from_dos,
245
+ stitch_entropy,
246
+ )
247
+
248
+ per_window = [r.get_entropy() for r in pt.results()]
249
+ stitched, errors = stitch_entropy(per_window, energy_spacing=1.0)
250
+ canonical = reweight_canonical_from_dos(
251
+ stitched, temperatures=[100, 200, 400, 800, 1600],
252
+ )
253
+ ```
254
+
255
+ The same pipeline is available from the command line via the
256
+ `mchammer-pt-stitch` and `mchammer-pt-reweight` console scripts, which
257
+ read either an mchammer-pt checkpoint HDF5 or
258
+ `WangLandauDataContainer` files directly. Pass `--multi-run` with two
259
+ or more checkpoints to merge independent seeds of the same system into
260
+ one consensus DOS (each window is merged across runs before
261
+ stitching). For production runs on a new system, plan
262
+ to validate the recovered DOS against ground truth (e.g. by
263
+ brute-force enumeration on a small case, or against an analytic
264
+ result) before trusting downstream thermodynamic averages.
265
+
266
+ ## Examples
267
+
268
+ - `examples/01_basic_canonical.py` — self-contained run on a toy Cu/Au CE.
269
+ - `examples/02_custom_callback.py` — writing your own `ExchangeCallback`.
270
+ - `examples/03_parallel_workers.py` — PT with the `ProcessPool`.
271
+ - `examples/04_equilibrium_sampling.py` – discarding the initial burn-in period for equilibrium sampling.
272
+ - `examples/05_custom_ensemble.py` — PT with a custom
273
+ `CanonicalEnsemble` subclass.
274
+ - `examples/06_progress_monitoring.py` — live progress on stderr for
275
+ long runs via `ProgressPrinter`.
276
+ - `examples/07_resume.py` — checkpoint and resume a PT run, with
277
+ bit-identical continuation.
278
+ - `examples/08_rewl.py` — replica-exchange Wang-Landau on a 4x4
279
+ 2D Ising model, with per-window seeding and DOS stitching.
280
+ - `examples/09_dos_postprocessing.py` — stitching REWL output into
281
+ a single ln g(E) and reweighting onto a canonical temperature
282
+ grid via `mchammer_pt.analysis.dos`.
283
+
284
+ ## License
285
+
286
+ MIT.