mchammer-moves 0.6.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.
- mchammer_moves-0.6.0/LICENSE +21 -0
- mchammer_moves-0.6.0/PKG-INFO +273 -0
- mchammer_moves-0.6.0/README.md +247 -0
- mchammer_moves-0.6.0/pyproject.toml +65 -0
- mchammer_moves-0.6.0/setup.cfg +4 -0
- mchammer_moves-0.6.0/src/mchammer_moves/__init__.py +64 -0
- mchammer_moves-0.6.0/src/mchammer_moves/ensemble.py +679 -0
- mchammer_moves-0.6.0/src/mchammer_moves/moves/__init__.py +7 -0
- mchammer_moves-0.6.0/src/mchammer_moves/moves/base.py +88 -0
- mchammer_moves-0.6.0/src/mchammer_moves/moves/cyclic_reflection.py +181 -0
- mchammer_moves-0.6.0/src/mchammer_moves/moves/cyclic_shift.py +146 -0
- mchammer_moves-0.6.0/src/mchammer_moves/moves/index_set_swap.py +222 -0
- mchammer_moves-0.6.0/src/mchammer_moves/moves/multi_pair_swap.py +186 -0
- mchammer_moves-0.6.0/src/mchammer_moves/moves/pair_swap.py +108 -0
- mchammer_moves-0.6.0/src/mchammer_moves/moves/site_permutation.py +221 -0
- mchammer_moves-0.6.0/src/mchammer_moves/py.typed +0 -0
- mchammer_moves-0.6.0/src/mchammer_moves.egg-info/PKG-INFO +273 -0
- mchammer_moves-0.6.0/src/mchammer_moves.egg-info/SOURCES.txt +30 -0
- mchammer_moves-0.6.0/src/mchammer_moves.egg-info/dependency_links.txt +1 -0
- mchammer_moves-0.6.0/src/mchammer_moves.egg-info/requires.txt +9 -0
- mchammer_moves-0.6.0/src/mchammer_moves.egg-info/top_level.txt +1 -0
- mchammer_moves-0.6.0/tests/test_boltzmann_sampling.py +63 -0
- mchammer_moves-0.6.0/tests/test_cyclic_reflection.py +254 -0
- mchammer_moves-0.6.0/tests/test_cyclic_shift.py +284 -0
- mchammer_moves-0.6.0/tests/test_ensemble.py +428 -0
- mchammer_moves-0.6.0/tests/test_index_set_swap.py +389 -0
- mchammer_moves-0.6.0/tests/test_move_dispatcher.py +163 -0
- mchammer_moves-0.6.0/tests/test_multi_pair_swap.py +266 -0
- mchammer_moves-0.6.0/tests/test_pair_swap.py +314 -0
- mchammer_moves-0.6.0/tests/test_picklability.py +36 -0
- mchammer_moves-0.6.0/tests/test_site_permutation.py +242 -0
- mchammer_moves-0.6.0/tests/test_wang_landau_ensemble.py +355 -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,273 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mchammer-moves
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Custom Monte Carlo moves for icet/mchammer.
|
|
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: icet>=3.2
|
|
18
|
+
Requires-Dist: numpy
|
|
19
|
+
Requires-Dist: ase
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
22
|
+
Requires-Dist: scipy; extra == "dev"
|
|
23
|
+
Requires-Dist: mypy; extra == "dev"
|
|
24
|
+
Requires-Dist: ruff; extra == "dev"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# mchammer-moves
|
|
28
|
+
|
|
29
|
+
Custom Monte Carlo trial moves for [icet/mchammer](https://icet.materialsmodeling.org/).
|
|
30
|
+
The `Move` base class defines a sampler-agnostic proposal contract; ensemble
|
|
31
|
+
adapters consume moves and handle acceptance, bookkeeping, and data-container
|
|
32
|
+
integration for canonical and Wang-Landau sampling, without modification of the
|
|
33
|
+
mchammer source or downstream wrappers such as `mchammer-pt`.
|
|
34
|
+
|
|
35
|
+
The package provides:
|
|
36
|
+
|
|
37
|
+
- a `Move` abstract base class for user-defined trial moves;
|
|
38
|
+
- five built-in moves:
|
|
39
|
+
- `PairSwap` — the standard canonical two-site swap;
|
|
40
|
+
- `MultiPairSwap` — `k` site-disjoint pair swaps applied as one
|
|
41
|
+
atomic proposal; useful when single-pair swaps are kinetically
|
|
42
|
+
blocked between adjacent minima in deep basins;
|
|
43
|
+
- `CyclicShift` — single-step shift of the species pattern along a
|
|
44
|
+
user-supplied index cycle, with periodic boundaries within the
|
|
45
|
+
cycle; useful for row or ring translations on chain-like or
|
|
46
|
+
ring-like sublattices;
|
|
47
|
+
- `CyclicReflection` — long-range reflection of the species pattern
|
|
48
|
+
along an index cycle around a randomly-chosen pivot; complements
|
|
49
|
+
`CyclicShift`'s nearest-neighbour shifts by enabling species to
|
|
50
|
+
hop across a chain in a single accepted move;
|
|
51
|
+
- `IndexSetSwap` — swaps occupations between two equal-length index
|
|
52
|
+
sets drawn uniformly from a user-supplied list of groups; a
|
|
53
|
+
generic primitive for chain-, motif-, or layer-swap moves;
|
|
54
|
+
- `SitePermutation` — applies a caller-supplied permutation of site
|
|
55
|
+
occupations, drawn uniformly from a list of operations, with an
|
|
56
|
+
unconditional forward/inverse direction draw; covers reflections
|
|
57
|
+
(e.g. across a `<100>` plane), point inversion, and proper or
|
|
58
|
+
improper rotations of any order;
|
|
59
|
+
- `CustomCanonicalEnsemble`, a drop-in replacement for
|
|
60
|
+
`mchammer.ensembles.CanonicalEnsemble` that draws moves from a
|
|
61
|
+
user-supplied weighted list and tracks per-move acceptance;
|
|
62
|
+
- `CustomWangLandauEnsemble`, a drop-in replacement for
|
|
63
|
+
`mchammer.ensembles.WangLandauEnsemble` with the same weighted-move
|
|
64
|
+
dispatch, plus per-move window-vs-WL rejection classification;
|
|
65
|
+
- `MoveDispatcher`, the shared weighted-selection and per-move
|
|
66
|
+
bookkeeping engine used by both ensemble adapters.
|
|
67
|
+
|
|
68
|
+
Installation (editable):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install -e .
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Quick start
|
|
75
|
+
|
|
76
|
+
`structure`, `ce`, and `cycles` below are placeholders for your atoms
|
|
77
|
+
object, cluster expansion, and chain definitions respectively; the
|
|
78
|
+
package contains no system-specific geometry, so you supply them
|
|
79
|
+
yourself.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from mchammer.calculators import ClusterExpansionCalculator
|
|
83
|
+
from mchammer_moves import CustomCanonicalEnsemble, CyclicShift, PairSwap
|
|
84
|
+
|
|
85
|
+
calc = ClusterExpansionCalculator(structure, ce)
|
|
86
|
+
|
|
87
|
+
ensemble = CustomCanonicalEnsemble(
|
|
88
|
+
structure=structure,
|
|
89
|
+
calculator=calc,
|
|
90
|
+
temperature=600.0,
|
|
91
|
+
moves=[
|
|
92
|
+
(PairSwap(sublattice_index=0), 1.0),
|
|
93
|
+
(CyclicShift(cycles=cycles), 0.05),
|
|
94
|
+
],
|
|
95
|
+
)
|
|
96
|
+
ensemble.run(10_000)
|
|
97
|
+
|
|
98
|
+
print(ensemble.acceptance_rates())
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Use with `mchammer-pt`
|
|
102
|
+
|
|
103
|
+
`mchammer-pt` (v0.2+) accepts a custom ensemble class via its native
|
|
104
|
+
`ensemble_cls=` parameter, with constructor arguments forwarded via
|
|
105
|
+
`ensemble_kwargs=`:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from mchammer_pt import CanonicalParallelTempering
|
|
109
|
+
from mchammer_moves import CustomCanonicalEnsemble, CyclicShift, PairSwap
|
|
110
|
+
|
|
111
|
+
with CanonicalParallelTempering.process_pool(
|
|
112
|
+
cluster_expansion=ce,
|
|
113
|
+
atoms=initial_structure,
|
|
114
|
+
temperatures=temperatures,
|
|
115
|
+
block_size=block_size,
|
|
116
|
+
random_seed=42,
|
|
117
|
+
ensemble_cls=CustomCanonicalEnsemble,
|
|
118
|
+
ensemble_kwargs={
|
|
119
|
+
"moves": [
|
|
120
|
+
(PairSwap(sublattice_index=anion_sl), 1.0),
|
|
121
|
+
(CyclicShift(cycles=cycles), 0.05),
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
) as pt:
|
|
125
|
+
history = pt.run(n_cycles=N_CYCLES)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Per-move acceptance and null-proposal rates are recorded into each
|
|
129
|
+
replica's `mchammer.BaseDataContainer` at every
|
|
130
|
+
`ensemble_data_write_interval` as `<move>_acceptance_rate` and
|
|
131
|
+
`<move>_null_rate` columns, so they survive the `ProcessPool`
|
|
132
|
+
boundary and are recoverable from the HDF5 bundle written by
|
|
133
|
+
`mchammer-pt` without observer forwarding. The two are tracked
|
|
134
|
+
separately: a move that returns `None` (e.g. a `PairSwap` on a
|
|
135
|
+
single-species sublattice, a `MultiPairSwap` on a sublattice with
|
|
136
|
+
fewer than `k` of one species, an `IndexSetSwap` whose drawn pair
|
|
137
|
+
already holds identical occupations) increments the null counter rather
|
|
138
|
+
than the rejection counter, so `null_rate` distinguishes a
|
|
139
|
+
structurally-infeasible move (`null_rate ≈ 1`) from a
|
|
140
|
+
low-temperature trapped chain (`acceptance_rate ≈ 0`,
|
|
141
|
+
`null_rate ≈ 0`).
|
|
142
|
+
|
|
143
|
+
For multiprocess runs, `CustomCanonicalEnsemble` and every `Move`
|
|
144
|
+
subclass must be importable by fully qualified name in spawn workers
|
|
145
|
+
(i.e. defined in `.py` module files, not in `__main__` or notebook
|
|
146
|
+
cells). `mchammer-pt`'s `ProcessPool` rejects interactive-`__main__`
|
|
147
|
+
and function-local classes up-front.
|
|
148
|
+
|
|
149
|
+
## Use with Wang-Landau
|
|
150
|
+
|
|
151
|
+
`CustomWangLandauEnsemble` accepts the same `moves` list as
|
|
152
|
+
`CustomCanonicalEnsemble` and forwards all other parameters to
|
|
153
|
+
`WangLandauEnsemble`:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from mchammer.calculators import ClusterExpansionCalculator
|
|
157
|
+
from mchammer_moves import CustomWangLandauEnsemble, PairSwap
|
|
158
|
+
|
|
159
|
+
calc = ClusterExpansionCalculator(structure, ce)
|
|
160
|
+
|
|
161
|
+
mc = CustomWangLandauEnsemble(
|
|
162
|
+
structure=structure,
|
|
163
|
+
calculator=calc,
|
|
164
|
+
energy_spacing=0.1,
|
|
165
|
+
moves=[
|
|
166
|
+
(PairSwap(sublattice_index=0), 1.0),
|
|
167
|
+
],
|
|
168
|
+
energy_limit_left=-100.0,
|
|
169
|
+
energy_limit_right=-90.0,
|
|
170
|
+
)
|
|
171
|
+
mc.run(1_000_000)
|
|
172
|
+
|
|
173
|
+
print(mc.acceptance_rates())
|
|
174
|
+
print(mc.rejection_breakdown())
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Per-move acceptance, null, window-rejection, and WL-rejection rates are
|
|
178
|
+
recorded into the `WangLandauDataContainer` at every
|
|
179
|
+
`ensemble_data_write_interval` as `<move>_acceptance_rate`,
|
|
180
|
+
`<move>_null_rate`, `<move>_window_rejection_rate`, and
|
|
181
|
+
`<move>_wl_rejection_rate` columns. The `rejection_breakdown()` method
|
|
182
|
+
provides cumulative window-vs-WL rejection counts for interactive use.
|
|
183
|
+
|
|
184
|
+
`<move>_acceptance_rate` and `<move>_null_rate` use total proposals
|
|
185
|
+
(accepted + rejected + null) as the denominator. `<move>_window_rejection_rate`
|
|
186
|
+
and `<move>_wl_rejection_rate` use classified in-window rejections as the
|
|
187
|
+
denominator — they do not share a denominator with the first two columns and
|
|
188
|
+
do not sum with them to any fixed value.
|
|
189
|
+
|
|
190
|
+
Rejection classification is only performed once the walker has reached
|
|
191
|
+
the energy window. Pre-window search-phase rejections are counted in the
|
|
192
|
+
aggregate `MoveStats.rejected` counter but not broken down further.
|
|
193
|
+
|
|
194
|
+
## Constructing cycles for `CyclicShift`
|
|
195
|
+
|
|
196
|
+
`CyclicShift` expects a list of *cycles*, where each cycle is a list of
|
|
197
|
+
site indices in the order along which species are to be shifted. Cycles
|
|
198
|
+
may have any length and may differ in length from one another; the move
|
|
199
|
+
treats each cycle as periodic in itself (the last site wraps to the
|
|
200
|
+
first). The supplied indices are opaque labels — there is no requirement
|
|
201
|
+
that they correspond to physically collinear sites.
|
|
202
|
+
|
|
203
|
+
The package contains no system-specific geometry. Cycle construction is
|
|
204
|
+
the caller's responsibility. The recipe for a typical anion-ordered
|
|
205
|
+
ReO3-type supercell, where each cycle corresponds to a one-dimensional
|
|
206
|
+
chain of anion sites, is:
|
|
207
|
+
|
|
208
|
+
1. Identify a single-axis chain of anion sites — for example, all sites of
|
|
209
|
+
the form `(i, 0, 0), (i, 0, 1), …, (i, 0, N-1)` along the *z* axis at
|
|
210
|
+
`(x=i, y=0)` — and list their flat site indices in geometric order.
|
|
211
|
+
2. Repeat for each starting `(x, y)` to obtain the full set of *z*-cycles.
|
|
212
|
+
3. Repeat the procedure for *x*-cycles and *y*-cycles if your problem has
|
|
213
|
+
chain ordering along multiple axes.
|
|
214
|
+
4. Pass the combined list to `CyclicShift(cycles=...)`.
|
|
215
|
+
|
|
216
|
+
For NbO2F at 6×6×6, the relevant cycles are anion chains along each cubic
|
|
217
|
+
axis (108 cycles per axis, 324 cycles total). See the integration script
|
|
218
|
+
in the `data_NbO2F` project for a concrete construction.
|
|
219
|
+
|
|
220
|
+
## Detailed balance
|
|
221
|
+
|
|
222
|
+
All built-in moves have proposal probabilities that depend only on lattice
|
|
223
|
+
geometry and composition, not on the current configuration:
|
|
224
|
+
|
|
225
|
+
- `PairSwap`: at fixed canonical composition, the number of distinct-species
|
|
226
|
+
pairs on a sublattice is composition-invariant, so the probability of
|
|
227
|
+
selecting any specific pair is symmetric in the forward and reverse
|
|
228
|
+
directions.
|
|
229
|
+
- `MultiPairSwap`: each pair is drawn by picking site 1 uniformly from
|
|
230
|
+
the non-used sublattice sites and site 2 uniformly from the non-used
|
|
231
|
+
sites of differing species. Summed over the `k!` orderings of the
|
|
232
|
+
same site-disjoint pair-set, the forward and reverse proposal
|
|
233
|
+
probabilities are equal: composition is invariant under any valid
|
|
234
|
+
swap, and the dependence on already-used sites cancels by symmetry
|
|
235
|
+
between the two directions.
|
|
236
|
+
- `CyclicShift`: a cycle and direction are chosen uniformly at random.
|
|
237
|
+
The reverse of a `+1` shift along cycle *c* is a `-1` shift along the
|
|
238
|
+
same cycle, with the same selection probability.
|
|
239
|
+
- `CyclicReflection`: a cycle and integer pivot are chosen uniformly
|
|
240
|
+
at random. Cyclic reflection is an involution, so the reverse of a
|
|
241
|
+
reflection along `(c, p)` is the same reflection along `(c, p)`,
|
|
242
|
+
with the same selection probability.
|
|
243
|
+
- `IndexSetSwap`: an unordered pair of index sets is drawn uniformly
|
|
244
|
+
from `C(N, 2)` distinct pairs. Selection probability depends only
|
|
245
|
+
on the fixed list of index sets, not on the configuration, so
|
|
246
|
+
`P(A → B) = P(B → A)` directly. The optional
|
|
247
|
+
`require_matching_composition` filter (off by default) does not
|
|
248
|
+
break this: swapping any pair only exchanges the two groups'
|
|
249
|
+
contents, so the multiset of compositions held across the groups
|
|
250
|
+
is invariant under the move, and a pair filtered out in one
|
|
251
|
+
direction is also filtered out in the other.
|
|
252
|
+
- `SitePermutation`: an operation is drawn uniformly from the fixed
|
|
253
|
+
list, then applied forward or inverted, each with probability one
|
|
254
|
+
half. The applied-permutation multiset is closed under inversion with
|
|
255
|
+
equal weights, so `P(A → B) = P(B → A)` for any permutation.
|
|
256
|
+
|
|
257
|
+
Standard Metropolis acceptance therefore satisfies detailed balance for any
|
|
258
|
+
weighted combination of these moves. A symmetry test that empirically
|
|
259
|
+
verifies this property is provided in the test suite for each move and
|
|
260
|
+
should be the first thing you run when adding a new move.
|
|
261
|
+
|
|
262
|
+
For Wang-Landau sampling, `CustomWangLandauEnsemble` replaces the
|
|
263
|
+
Metropolis criterion with the WL entropy-based acceptance condition
|
|
264
|
+
inherited from `WangLandauEnsemble`. The symmetric-proposal property
|
|
265
|
+
of each move still holds, so the WL algorithm's convergence guarantees
|
|
266
|
+
are preserved for any weighted combination of the built-in moves.
|
|
267
|
+
|
|
268
|
+
## Running tests
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
pip install -e ".[dev]"
|
|
272
|
+
pytest -q
|
|
273
|
+
```
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# mchammer-moves
|
|
2
|
+
|
|
3
|
+
Custom Monte Carlo trial moves for [icet/mchammer](https://icet.materialsmodeling.org/).
|
|
4
|
+
The `Move` base class defines a sampler-agnostic proposal contract; ensemble
|
|
5
|
+
adapters consume moves and handle acceptance, bookkeeping, and data-container
|
|
6
|
+
integration for canonical and Wang-Landau sampling, without modification of the
|
|
7
|
+
mchammer source or downstream wrappers such as `mchammer-pt`.
|
|
8
|
+
|
|
9
|
+
The package provides:
|
|
10
|
+
|
|
11
|
+
- a `Move` abstract base class for user-defined trial moves;
|
|
12
|
+
- five built-in moves:
|
|
13
|
+
- `PairSwap` — the standard canonical two-site swap;
|
|
14
|
+
- `MultiPairSwap` — `k` site-disjoint pair swaps applied as one
|
|
15
|
+
atomic proposal; useful when single-pair swaps are kinetically
|
|
16
|
+
blocked between adjacent minima in deep basins;
|
|
17
|
+
- `CyclicShift` — single-step shift of the species pattern along a
|
|
18
|
+
user-supplied index cycle, with periodic boundaries within the
|
|
19
|
+
cycle; useful for row or ring translations on chain-like or
|
|
20
|
+
ring-like sublattices;
|
|
21
|
+
- `CyclicReflection` — long-range reflection of the species pattern
|
|
22
|
+
along an index cycle around a randomly-chosen pivot; complements
|
|
23
|
+
`CyclicShift`'s nearest-neighbour shifts by enabling species to
|
|
24
|
+
hop across a chain in a single accepted move;
|
|
25
|
+
- `IndexSetSwap` — swaps occupations between two equal-length index
|
|
26
|
+
sets drawn uniformly from a user-supplied list of groups; a
|
|
27
|
+
generic primitive for chain-, motif-, or layer-swap moves;
|
|
28
|
+
- `SitePermutation` — applies a caller-supplied permutation of site
|
|
29
|
+
occupations, drawn uniformly from a list of operations, with an
|
|
30
|
+
unconditional forward/inverse direction draw; covers reflections
|
|
31
|
+
(e.g. across a `<100>` plane), point inversion, and proper or
|
|
32
|
+
improper rotations of any order;
|
|
33
|
+
- `CustomCanonicalEnsemble`, a drop-in replacement for
|
|
34
|
+
`mchammer.ensembles.CanonicalEnsemble` that draws moves from a
|
|
35
|
+
user-supplied weighted list and tracks per-move acceptance;
|
|
36
|
+
- `CustomWangLandauEnsemble`, a drop-in replacement for
|
|
37
|
+
`mchammer.ensembles.WangLandauEnsemble` with the same weighted-move
|
|
38
|
+
dispatch, plus per-move window-vs-WL rejection classification;
|
|
39
|
+
- `MoveDispatcher`, the shared weighted-selection and per-move
|
|
40
|
+
bookkeeping engine used by both ensemble adapters.
|
|
41
|
+
|
|
42
|
+
Installation (editable):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
`structure`, `ce`, and `cycles` below are placeholders for your atoms
|
|
51
|
+
object, cluster expansion, and chain definitions respectively; the
|
|
52
|
+
package contains no system-specific geometry, so you supply them
|
|
53
|
+
yourself.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from mchammer.calculators import ClusterExpansionCalculator
|
|
57
|
+
from mchammer_moves import CustomCanonicalEnsemble, CyclicShift, PairSwap
|
|
58
|
+
|
|
59
|
+
calc = ClusterExpansionCalculator(structure, ce)
|
|
60
|
+
|
|
61
|
+
ensemble = CustomCanonicalEnsemble(
|
|
62
|
+
structure=structure,
|
|
63
|
+
calculator=calc,
|
|
64
|
+
temperature=600.0,
|
|
65
|
+
moves=[
|
|
66
|
+
(PairSwap(sublattice_index=0), 1.0),
|
|
67
|
+
(CyclicShift(cycles=cycles), 0.05),
|
|
68
|
+
],
|
|
69
|
+
)
|
|
70
|
+
ensemble.run(10_000)
|
|
71
|
+
|
|
72
|
+
print(ensemble.acceptance_rates())
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Use with `mchammer-pt`
|
|
76
|
+
|
|
77
|
+
`mchammer-pt` (v0.2+) accepts a custom ensemble class via its native
|
|
78
|
+
`ensemble_cls=` parameter, with constructor arguments forwarded via
|
|
79
|
+
`ensemble_kwargs=`:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from mchammer_pt import CanonicalParallelTempering
|
|
83
|
+
from mchammer_moves import CustomCanonicalEnsemble, CyclicShift, PairSwap
|
|
84
|
+
|
|
85
|
+
with CanonicalParallelTempering.process_pool(
|
|
86
|
+
cluster_expansion=ce,
|
|
87
|
+
atoms=initial_structure,
|
|
88
|
+
temperatures=temperatures,
|
|
89
|
+
block_size=block_size,
|
|
90
|
+
random_seed=42,
|
|
91
|
+
ensemble_cls=CustomCanonicalEnsemble,
|
|
92
|
+
ensemble_kwargs={
|
|
93
|
+
"moves": [
|
|
94
|
+
(PairSwap(sublattice_index=anion_sl), 1.0),
|
|
95
|
+
(CyclicShift(cycles=cycles), 0.05),
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
) as pt:
|
|
99
|
+
history = pt.run(n_cycles=N_CYCLES)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Per-move acceptance and null-proposal rates are recorded into each
|
|
103
|
+
replica's `mchammer.BaseDataContainer` at every
|
|
104
|
+
`ensemble_data_write_interval` as `<move>_acceptance_rate` and
|
|
105
|
+
`<move>_null_rate` columns, so they survive the `ProcessPool`
|
|
106
|
+
boundary and are recoverable from the HDF5 bundle written by
|
|
107
|
+
`mchammer-pt` without observer forwarding. The two are tracked
|
|
108
|
+
separately: a move that returns `None` (e.g. a `PairSwap` on a
|
|
109
|
+
single-species sublattice, a `MultiPairSwap` on a sublattice with
|
|
110
|
+
fewer than `k` of one species, an `IndexSetSwap` whose drawn pair
|
|
111
|
+
already holds identical occupations) increments the null counter rather
|
|
112
|
+
than the rejection counter, so `null_rate` distinguishes a
|
|
113
|
+
structurally-infeasible move (`null_rate ≈ 1`) from a
|
|
114
|
+
low-temperature trapped chain (`acceptance_rate ≈ 0`,
|
|
115
|
+
`null_rate ≈ 0`).
|
|
116
|
+
|
|
117
|
+
For multiprocess runs, `CustomCanonicalEnsemble` and every `Move`
|
|
118
|
+
subclass must be importable by fully qualified name in spawn workers
|
|
119
|
+
(i.e. defined in `.py` module files, not in `__main__` or notebook
|
|
120
|
+
cells). `mchammer-pt`'s `ProcessPool` rejects interactive-`__main__`
|
|
121
|
+
and function-local classes up-front.
|
|
122
|
+
|
|
123
|
+
## Use with Wang-Landau
|
|
124
|
+
|
|
125
|
+
`CustomWangLandauEnsemble` accepts the same `moves` list as
|
|
126
|
+
`CustomCanonicalEnsemble` and forwards all other parameters to
|
|
127
|
+
`WangLandauEnsemble`:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from mchammer.calculators import ClusterExpansionCalculator
|
|
131
|
+
from mchammer_moves import CustomWangLandauEnsemble, PairSwap
|
|
132
|
+
|
|
133
|
+
calc = ClusterExpansionCalculator(structure, ce)
|
|
134
|
+
|
|
135
|
+
mc = CustomWangLandauEnsemble(
|
|
136
|
+
structure=structure,
|
|
137
|
+
calculator=calc,
|
|
138
|
+
energy_spacing=0.1,
|
|
139
|
+
moves=[
|
|
140
|
+
(PairSwap(sublattice_index=0), 1.0),
|
|
141
|
+
],
|
|
142
|
+
energy_limit_left=-100.0,
|
|
143
|
+
energy_limit_right=-90.0,
|
|
144
|
+
)
|
|
145
|
+
mc.run(1_000_000)
|
|
146
|
+
|
|
147
|
+
print(mc.acceptance_rates())
|
|
148
|
+
print(mc.rejection_breakdown())
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Per-move acceptance, null, window-rejection, and WL-rejection rates are
|
|
152
|
+
recorded into the `WangLandauDataContainer` at every
|
|
153
|
+
`ensemble_data_write_interval` as `<move>_acceptance_rate`,
|
|
154
|
+
`<move>_null_rate`, `<move>_window_rejection_rate`, and
|
|
155
|
+
`<move>_wl_rejection_rate` columns. The `rejection_breakdown()` method
|
|
156
|
+
provides cumulative window-vs-WL rejection counts for interactive use.
|
|
157
|
+
|
|
158
|
+
`<move>_acceptance_rate` and `<move>_null_rate` use total proposals
|
|
159
|
+
(accepted + rejected + null) as the denominator. `<move>_window_rejection_rate`
|
|
160
|
+
and `<move>_wl_rejection_rate` use classified in-window rejections as the
|
|
161
|
+
denominator — they do not share a denominator with the first two columns and
|
|
162
|
+
do not sum with them to any fixed value.
|
|
163
|
+
|
|
164
|
+
Rejection classification is only performed once the walker has reached
|
|
165
|
+
the energy window. Pre-window search-phase rejections are counted in the
|
|
166
|
+
aggregate `MoveStats.rejected` counter but not broken down further.
|
|
167
|
+
|
|
168
|
+
## Constructing cycles for `CyclicShift`
|
|
169
|
+
|
|
170
|
+
`CyclicShift` expects a list of *cycles*, where each cycle is a list of
|
|
171
|
+
site indices in the order along which species are to be shifted. Cycles
|
|
172
|
+
may have any length and may differ in length from one another; the move
|
|
173
|
+
treats each cycle as periodic in itself (the last site wraps to the
|
|
174
|
+
first). The supplied indices are opaque labels — there is no requirement
|
|
175
|
+
that they correspond to physically collinear sites.
|
|
176
|
+
|
|
177
|
+
The package contains no system-specific geometry. Cycle construction is
|
|
178
|
+
the caller's responsibility. The recipe for a typical anion-ordered
|
|
179
|
+
ReO3-type supercell, where each cycle corresponds to a one-dimensional
|
|
180
|
+
chain of anion sites, is:
|
|
181
|
+
|
|
182
|
+
1. Identify a single-axis chain of anion sites — for example, all sites of
|
|
183
|
+
the form `(i, 0, 0), (i, 0, 1), …, (i, 0, N-1)` along the *z* axis at
|
|
184
|
+
`(x=i, y=0)` — and list their flat site indices in geometric order.
|
|
185
|
+
2. Repeat for each starting `(x, y)` to obtain the full set of *z*-cycles.
|
|
186
|
+
3. Repeat the procedure for *x*-cycles and *y*-cycles if your problem has
|
|
187
|
+
chain ordering along multiple axes.
|
|
188
|
+
4. Pass the combined list to `CyclicShift(cycles=...)`.
|
|
189
|
+
|
|
190
|
+
For NbO2F at 6×6×6, the relevant cycles are anion chains along each cubic
|
|
191
|
+
axis (108 cycles per axis, 324 cycles total). See the integration script
|
|
192
|
+
in the `data_NbO2F` project for a concrete construction.
|
|
193
|
+
|
|
194
|
+
## Detailed balance
|
|
195
|
+
|
|
196
|
+
All built-in moves have proposal probabilities that depend only on lattice
|
|
197
|
+
geometry and composition, not on the current configuration:
|
|
198
|
+
|
|
199
|
+
- `PairSwap`: at fixed canonical composition, the number of distinct-species
|
|
200
|
+
pairs on a sublattice is composition-invariant, so the probability of
|
|
201
|
+
selecting any specific pair is symmetric in the forward and reverse
|
|
202
|
+
directions.
|
|
203
|
+
- `MultiPairSwap`: each pair is drawn by picking site 1 uniformly from
|
|
204
|
+
the non-used sublattice sites and site 2 uniformly from the non-used
|
|
205
|
+
sites of differing species. Summed over the `k!` orderings of the
|
|
206
|
+
same site-disjoint pair-set, the forward and reverse proposal
|
|
207
|
+
probabilities are equal: composition is invariant under any valid
|
|
208
|
+
swap, and the dependence on already-used sites cancels by symmetry
|
|
209
|
+
between the two directions.
|
|
210
|
+
- `CyclicShift`: a cycle and direction are chosen uniformly at random.
|
|
211
|
+
The reverse of a `+1` shift along cycle *c* is a `-1` shift along the
|
|
212
|
+
same cycle, with the same selection probability.
|
|
213
|
+
- `CyclicReflection`: a cycle and integer pivot are chosen uniformly
|
|
214
|
+
at random. Cyclic reflection is an involution, so the reverse of a
|
|
215
|
+
reflection along `(c, p)` is the same reflection along `(c, p)`,
|
|
216
|
+
with the same selection probability.
|
|
217
|
+
- `IndexSetSwap`: an unordered pair of index sets is drawn uniformly
|
|
218
|
+
from `C(N, 2)` distinct pairs. Selection probability depends only
|
|
219
|
+
on the fixed list of index sets, not on the configuration, so
|
|
220
|
+
`P(A → B) = P(B → A)` directly. The optional
|
|
221
|
+
`require_matching_composition` filter (off by default) does not
|
|
222
|
+
break this: swapping any pair only exchanges the two groups'
|
|
223
|
+
contents, so the multiset of compositions held across the groups
|
|
224
|
+
is invariant under the move, and a pair filtered out in one
|
|
225
|
+
direction is also filtered out in the other.
|
|
226
|
+
- `SitePermutation`: an operation is drawn uniformly from the fixed
|
|
227
|
+
list, then applied forward or inverted, each with probability one
|
|
228
|
+
half. The applied-permutation multiset is closed under inversion with
|
|
229
|
+
equal weights, so `P(A → B) = P(B → A)` for any permutation.
|
|
230
|
+
|
|
231
|
+
Standard Metropolis acceptance therefore satisfies detailed balance for any
|
|
232
|
+
weighted combination of these moves. A symmetry test that empirically
|
|
233
|
+
verifies this property is provided in the test suite for each move and
|
|
234
|
+
should be the first thing you run when adding a new move.
|
|
235
|
+
|
|
236
|
+
For Wang-Landau sampling, `CustomWangLandauEnsemble` replaces the
|
|
237
|
+
Metropolis criterion with the WL entropy-based acceptance condition
|
|
238
|
+
inherited from `WangLandauEnsemble`. The symmetric-proposal property
|
|
239
|
+
of each move still holds, so the WL algorithm's convergence guarantees
|
|
240
|
+
are preserved for any weighted combination of the built-in moves.
|
|
241
|
+
|
|
242
|
+
## Running tests
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
pip install -e ".[dev]"
|
|
246
|
+
pytest -q
|
|
247
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mchammer-moves"
|
|
7
|
+
version = "0.6.0"
|
|
8
|
+
description = "Custom Monte Carlo moves for icet/mchammer."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Benjamin J. Morgan", email = "b.j.morgan@bath.ac.uk" }]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"icet>=3.2",
|
|
16
|
+
"numpy",
|
|
17
|
+
"ase",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Topic :: Scientific/Engineering",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = [
|
|
31
|
+
"pytest>=7",
|
|
32
|
+
"scipy",
|
|
33
|
+
"mypy",
|
|
34
|
+
"ruff",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["src"]
|
|
39
|
+
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
testpaths = ["tests"]
|
|
42
|
+
|
|
43
|
+
[tool.mypy]
|
|
44
|
+
python_version = "3.11"
|
|
45
|
+
packages = ["mchammer_moves"]
|
|
46
|
+
strict = true
|
|
47
|
+
warn_return_any = false
|
|
48
|
+
|
|
49
|
+
# `mchammer` (icet) ships without a `py.typed` marker, so mypy
|
|
50
|
+
# treats it as an untyped third-party dependency. The exact error
|
|
51
|
+
# code mypy assigns depends on how the package is installed
|
|
52
|
+
# (`import-untyped` for site-packages installs, `import-not-found`
|
|
53
|
+
# for editable / source-tree installs); ignoring missing imports
|
|
54
|
+
# here covers both and removes the need for per-import
|
|
55
|
+
# `# type: ignore` suppressions that drift across mypy versions.
|
|
56
|
+
[[tool.mypy.overrides]]
|
|
57
|
+
module = ["mchammer.*"]
|
|
58
|
+
ignore_missing_imports = true
|
|
59
|
+
|
|
60
|
+
[tool.ruff]
|
|
61
|
+
target-version = "py311"
|
|
62
|
+
line-length = 88
|
|
63
|
+
|
|
64
|
+
[tool.ruff.lint]
|
|
65
|
+
select = ["E", "F", "W", "I", "B", "UP"]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Custom Monte Carlo moves for icet/mchammer.
|
|
2
|
+
|
|
3
|
+
Provides a small framework for plugging user-defined trial moves into
|
|
4
|
+
mchammer sampling. The :class:`Move` base class defines a
|
|
5
|
+
sampler-agnostic proposal contract; ensemble adapters consume moves
|
|
6
|
+
and handle acceptance, bookkeeping, and data-container integration:
|
|
7
|
+
|
|
8
|
+
* :class:`Move` — abstract base class for trial moves.
|
|
9
|
+
* :class:`PairSwap` — standard two-site canonical swap on a sublattice.
|
|
10
|
+
* :class:`MultiPairSwap` — ``k`` site-disjoint pair swaps applied as
|
|
11
|
+
one atomic proposal, for larger jumps in configuration space than a
|
|
12
|
+
single pair swap can provide.
|
|
13
|
+
* :class:`CyclicShift` — single-step cyclic shift of species along
|
|
14
|
+
one of a user-supplied set of index cycles. Useful for row /
|
|
15
|
+
ring translations on lattice sublattices with chain-like or
|
|
16
|
+
ring-like topology when standard single-site swaps are
|
|
17
|
+
kinetically blocked.
|
|
18
|
+
* :class:`CyclicReflection` — long-range reflection of the species
|
|
19
|
+
pattern along an index cycle around a randomly-chosen pivot;
|
|
20
|
+
complement to ``CyclicShift``'s nearest-neighbour shifts.
|
|
21
|
+
* :class:`IndexSetSwap` — generic group-permutation primitive that
|
|
22
|
+
swaps occupations between two equal-length index sets drawn
|
|
23
|
+
uniformly from a user-supplied list.
|
|
24
|
+
* :class:`SitePermutation` — applies a caller-supplied permutation of
|
|
25
|
+
site occupations (reflections, point inversion, rotations) drawn
|
|
26
|
+
uniformly from a list, applying each operation or its inverse with
|
|
27
|
+
equal probability so detailed balance holds for any permutation.
|
|
28
|
+
* :class:`MoveDispatcher` — weighted move selection and per-move
|
|
29
|
+
bookkeeping, used internally by ensemble adapters.
|
|
30
|
+
* :class:`CustomCanonicalEnsemble` — drop-in replacement for
|
|
31
|
+
:class:`mchammer.ensembles.CanonicalEnsemble` that draws moves from a
|
|
32
|
+
user-supplied weighted list and tracks per-move acceptance.
|
|
33
|
+
* :class:`CustomWangLandauEnsemble` — drop-in replacement for
|
|
34
|
+
:class:`mchammer.ensembles.WangLandauEnsemble` with the same
|
|
35
|
+
weighted-move dispatch, plus window-vs-WL rejection classification.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from mchammer_moves.ensemble import (
|
|
39
|
+
CustomCanonicalEnsemble,
|
|
40
|
+
CustomWangLandauEnsemble,
|
|
41
|
+
MoveDispatcher,
|
|
42
|
+
MoveStats,
|
|
43
|
+
)
|
|
44
|
+
from mchammer_moves.moves.base import Move
|
|
45
|
+
from mchammer_moves.moves.cyclic_reflection import CyclicReflection
|
|
46
|
+
from mchammer_moves.moves.cyclic_shift import CyclicShift
|
|
47
|
+
from mchammer_moves.moves.index_set_swap import IndexSetSwap
|
|
48
|
+
from mchammer_moves.moves.multi_pair_swap import MultiPairSwap
|
|
49
|
+
from mchammer_moves.moves.pair_swap import PairSwap
|
|
50
|
+
from mchammer_moves.moves.site_permutation import SitePermutation
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"CustomCanonicalEnsemble",
|
|
54
|
+
"CustomWangLandauEnsemble",
|
|
55
|
+
"CyclicReflection",
|
|
56
|
+
"CyclicShift",
|
|
57
|
+
"IndexSetSwap",
|
|
58
|
+
"Move",
|
|
59
|
+
"MoveDispatcher",
|
|
60
|
+
"MoveStats",
|
|
61
|
+
"MultiPairSwap",
|
|
62
|
+
"PairSwap",
|
|
63
|
+
"SitePermutation",
|
|
64
|
+
]
|