{"id":50088582,"url":"https://github.com/bjmorgan/mchammer-moves","last_synced_at":"2026-05-22T21:35:29.589Z","repository":{"id":354395604,"uuid":"1223387209","full_name":"bjmorgan/mchammer-moves","owner":"bjmorgan","description":"Custom Monte Carlo moves for icet/mchammer canonical sampling","archived":false,"fork":false,"pushed_at":"2026-05-11T14:55:59.000Z","size":124,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-11T16:08:26.666Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bjmorgan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-28T09:20:39.000Z","updated_at":"2026-05-11T15:12:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bjmorgan/mchammer-moves","commit_stats":null,"previous_names":["bjmorgan/mchammer-moves"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bjmorgan/mchammer-moves","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bjmorgan%2Fmchammer-moves","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bjmorgan%2Fmchammer-moves/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bjmorgan%2Fmchammer-moves/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bjmorgan%2Fmchammer-moves/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bjmorgan","download_url":"https://codeload.github.com/bjmorgan/mchammer-moves/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bjmorgan%2Fmchammer-moves/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33370124,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-21T12:23:38.849Z","status":"online","status_checked_at":"2026-05-22T02:00:06.671Z","response_time":265,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-22T21:35:28.485Z","updated_at":"2026-05-22T21:35:29.546Z","avatar_url":"https://github.com/bjmorgan.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mchammer-moves\n\nCustom Monte Carlo trial moves for [icet/mchammer](https://icet.materialsmodeling.org/).\nThe `Move` base class defines a sampler-agnostic proposal contract; ensemble\nadapters consume moves and handle acceptance, bookkeeping, and data-container\nintegration for canonical and Wang-Landau sampling, without modification of the\nmchammer source or downstream wrappers such as `mchammer-pt`.\n\nThe package provides:\n\n- a `Move` abstract base class for user-defined trial moves;\n- five built-in moves:\n  - `PairSwap` — the standard canonical two-site swap;\n  - `MultiPairSwap` — `k` site-disjoint pair swaps applied as one\n    atomic proposal; useful when single-pair swaps are kinetically\n    blocked between adjacent minima in deep basins;\n  - `CyclicShift` — single-step shift of the species pattern along a\n    user-supplied index cycle, with periodic boundaries within the\n    cycle; useful for row or ring translations on chain-like or\n    ring-like sublattices;\n  - `CyclicReflection` — long-range reflection of the species pattern\n    along an index cycle around a randomly-chosen pivot; complements\n    `CyclicShift`'s nearest-neighbour shifts by enabling species to\n    hop across a chain in a single accepted move;\n  - `IndexSetSwap` — swaps occupations between two equal-length index\n    sets drawn uniformly from a user-supplied list of groups; a\n    generic primitive for chain-, motif-, or layer-swap moves;\n- `CustomCanonicalEnsemble`, a drop-in replacement for\n  `mchammer.ensembles.CanonicalEnsemble` that draws moves from a\n  user-supplied weighted list and tracks per-move acceptance;\n- `CustomWangLandauEnsemble`, a drop-in replacement for\n  `mchammer.ensembles.WangLandauEnsemble` with the same weighted-move\n  dispatch, plus per-move window-vs-WL rejection classification;\n- `MoveDispatcher`, the shared weighted-selection and per-move\n  bookkeeping engine used by both ensemble adapters.\n\nInstallation (editable):\n\n```bash\npip install -e .\n```\n\nTo use the parallel-tempering integration, install the `pt` extra\n(pulls `mchammer-pt` v0.2.0 from GitHub until it is on PyPI):\n\n```bash\npip install -e \".[pt]\"\n```\n\n## Quick start\n\n`structure`, `ce`, and `cycles` below are placeholders for your atoms\nobject, cluster expansion, and chain definitions respectively; the\npackage contains no system-specific geometry, so you supply them\nyourself.\n\n```python\nfrom mchammer.calculators import ClusterExpansionCalculator\nfrom mchammer_moves import CustomCanonicalEnsemble, CyclicShift, PairSwap\n\ncalc = ClusterExpansionCalculator(structure, ce)\n\nensemble = CustomCanonicalEnsemble(\n    structure=structure,\n    calculator=calc,\n    temperature=600.0,\n    moves=[\n        (PairSwap(sublattice_index=0), 1.0),\n        (CyclicShift(cycles=cycles), 0.05),\n    ],\n)\nensemble.run(10_000)\n\nprint(ensemble.acceptance_rates())\n```\n\n## Use with `mchammer-pt`\n\n`mchammer-pt` (v0.2+) accepts a custom ensemble class via its native\n`ensemble_cls=` parameter, with constructor arguments forwarded via\n`ensemble_kwargs=`:\n\n```python\nfrom mchammer_pt import CanonicalParallelTempering\nfrom mchammer_moves import CustomCanonicalEnsemble, CyclicShift, PairSwap\n\nwith CanonicalParallelTempering.process_pool(\n    cluster_expansion=ce,\n    atoms=initial_structure,\n    temperatures=temperatures,\n    block_size=block_size,\n    random_seed=42,\n    ensemble_cls=CustomCanonicalEnsemble,\n    ensemble_kwargs={\n        \"moves\": [\n            (PairSwap(sublattice_index=anion_sl), 1.0),\n            (CyclicShift(cycles=cycles), 0.05),\n        ],\n    },\n) as pt:\n    history = pt.run(n_cycles=N_CYCLES)\n```\n\nPer-move acceptance and null-proposal rates are recorded into each\nreplica's `mchammer.BaseDataContainer` at every\n`ensemble_data_write_interval` as `\u003cmove\u003e_acceptance_rate` and\n`\u003cmove\u003e_null_rate` columns, so they survive the `ProcessPool`\nboundary and are recoverable from the HDF5 bundle written by\n`mchammer-pt` without observer forwarding. The two are tracked\nseparately: a move that returns `None` (e.g. a `PairSwap` on a\nsingle-species sublattice, a `MultiPairSwap` on a sublattice with\nfewer than `k` of one species, an `IndexSetSwap` whose drawn pair\nalready holds identical occupations) increments the null counter rather\nthan the rejection counter, so `null_rate` distinguishes a\nstructurally-infeasible move (`null_rate ≈ 1`) from a\nlow-temperature trapped chain (`acceptance_rate ≈ 0`,\n`null_rate ≈ 0`).\n\nFor multiprocess runs, `CustomCanonicalEnsemble` and every `Move`\nsubclass must be importable by fully qualified name in spawn workers\n(i.e. defined in `.py` module files, not in `__main__` or notebook\ncells). `mchammer-pt`'s `ProcessPool` rejects interactive-`__main__`\nand function-local classes up-front.\n\n## Use with Wang-Landau\n\n`CustomWangLandauEnsemble` accepts the same `moves` list as\n`CustomCanonicalEnsemble` and forwards all other parameters to\n`WangLandauEnsemble`:\n\n```python\nfrom mchammer.calculators import ClusterExpansionCalculator\nfrom mchammer_moves import CustomWangLandauEnsemble, PairSwap\n\ncalc = ClusterExpansionCalculator(structure, ce)\n\nmc = CustomWangLandauEnsemble(\n    structure=structure,\n    calculator=calc,\n    energy_spacing=0.1,\n    moves=[\n        (PairSwap(sublattice_index=0), 1.0),\n    ],\n    energy_limit_left=-100.0,\n    energy_limit_right=-90.0,\n    schedule=\"1_over_t\",\n)\nmc.run(1_000_000)\n\nprint(mc.acceptance_rates())\nprint(mc.rejection_breakdown())\n```\n\nPer-move acceptance, null, window-rejection, and WL-rejection rates are\nrecorded into the `WangLandauDataContainer` at every\n`ensemble_data_write_interval` as `\u003cmove\u003e_acceptance_rate`,\n`\u003cmove\u003e_null_rate`, `\u003cmove\u003e_window_rejection_rate`, and\n`\u003cmove\u003e_wl_rejection_rate` columns. The `rejection_breakdown()` method\nprovides cumulative window-vs-WL rejection counts for interactive use.\n\n`\u003cmove\u003e_acceptance_rate` and `\u003cmove\u003e_null_rate` use total proposals\n(accepted + rejected + null) as the denominator. `\u003cmove\u003e_window_rejection_rate`\nand `\u003cmove\u003e_wl_rejection_rate` use classified in-window rejections as the\ndenominator — they do not share a denominator with the first two columns and\ndo not sum with them to any fixed value.\n\nRejection classification is only performed once the walker has reached\nthe energy window. Pre-window search-phase rejections are counted in the\naggregate `MoveStats.rejected` counter but not broken down further.\n\n## Constructing cycles for `CyclicShift`\n\n`CyclicShift` expects a list of *cycles*, where each cycle is a list of\nsite indices in the order along which species are to be shifted. Cycles\nmay have any length and may differ in length from one another; the move\ntreats each cycle as periodic in itself (the last site wraps to the\nfirst). The supplied indices are opaque labels — there is no requirement\nthat they correspond to physically collinear sites.\n\nThe package contains no system-specific geometry. Cycle construction is\nthe caller's responsibility. The recipe for a typical anion-ordered\nReO3-type supercell, where each cycle corresponds to a one-dimensional\nchain of anion sites, is:\n\n1. Identify a single-axis chain of anion sites — for example, all sites of\n   the form `(i, 0, 0), (i, 0, 1), …, (i, 0, N-1)` along the *z* axis at\n   `(x=i, y=0)` — and list their flat site indices in geometric order.\n2. Repeat for each starting `(x, y)` to obtain the full set of *z*-cycles.\n3. Repeat the procedure for *x*-cycles and *y*-cycles if your problem has\n   chain ordering along multiple axes.\n4. Pass the combined list to `CyclicShift(cycles=...)`.\n\nFor NbO2F at 6×6×6, the relevant cycles are anion chains along each cubic\naxis (108 cycles per axis, 324 cycles total). See the integration script\nin the `data_NbO2F` project for a concrete construction.\n\n## Detailed balance\n\nAll built-in moves have proposal probabilities that depend only on lattice\ngeometry and composition, not on the current configuration:\n\n- `PairSwap`: at fixed canonical composition, the number of distinct-species\n  pairs on a sublattice is composition-invariant, so the probability of\n  selecting any specific pair is symmetric in the forward and reverse\n  directions.\n- `MultiPairSwap`: each pair is drawn by picking site 1 uniformly from\n  the non-used sublattice sites and site 2 uniformly from the non-used\n  sites of differing species. Summed over the `k!` orderings of the\n  same site-disjoint pair-set, the forward and reverse proposal\n  probabilities are equal: composition is invariant under any valid\n  swap, and the dependence on already-used sites cancels by symmetry\n  between the two directions.\n- `CyclicShift`: a cycle and direction are chosen uniformly at random.\n  The reverse of a `+1` shift along cycle *c* is a `-1` shift along the\n  same cycle, with the same selection probability.\n- `CyclicReflection`: a cycle and integer pivot are chosen uniformly\n  at random. Cyclic reflection is an involution, so the reverse of a\n  reflection along `(c, p)` is the same reflection along `(c, p)`,\n  with the same selection probability.\n- `IndexSetSwap`: an unordered pair of index sets is drawn uniformly\n  from `C(N, 2)` distinct pairs. Selection probability depends only\n  on the fixed list of index sets, not on the configuration, so\n  `P(A → B) = P(B → A)` directly. The optional\n  `require_matching_composition` filter (off by default) does not\n  break this: swapping any pair only exchanges the two groups'\n  contents, so the multiset of compositions held across the groups\n  is invariant under the move, and a pair filtered out in one\n  direction is also filtered out in the other.\n\nStandard Metropolis acceptance therefore satisfies detailed balance for any\nweighted combination of these moves. A symmetry test that empirically\nverifies this property is provided in the test suite for each move and\nshould be the first thing you run when adding a new move.\n\nFor Wang-Landau sampling, `CustomWangLandauEnsemble` replaces the\nMetropolis criterion with the WL entropy-based acceptance condition\ninherited from `WangLandauEnsemble`. The symmetric-proposal property\nof each move still holds, so the WL algorithm's convergence guarantees\nare preserved for any weighted combination of the built-in moves.\n\n## Running tests\n\n```bash\npip install -e \".[dev]\"\npytest -q\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbjmorgan%2Fmchammer-moves","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbjmorgan%2Fmchammer-moves","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbjmorgan%2Fmchammer-moves/lists"}