{"id":51462590,"url":"https://github.com/codenlighten/ising-converter","last_synced_at":"2026-07-06T07:01:17.020Z","repository":{"id":361522034,"uuid":"1253684201","full_name":"codenlighten/ising-converter","owner":"codenlighten","description":"Convert combinatorial problems to Ising/QUBO form and solve them with a fast Rust simulated-annealing \u0026 parallel-tempering kernel.","archived":false,"fork":false,"pushed_at":"2026-05-31T02:41:41.000Z","size":126,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-31T04:07:58.596Z","etag":null,"topics":["combinatorial-optimization","dimod","ising","maturin","monte-carlo","optimization","parallel-tempering","pyo3","python","qubo","rust","simulated-annealing","spin-glass"],"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/codenlighten.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-05-29T17:59:18.000Z","updated_at":"2026-05-31T02:41:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/codenlighten/ising-converter","commit_stats":null,"previous_names":["codenlighten/ising-converter"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/codenlighten/ising-converter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codenlighten%2Fising-converter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codenlighten%2Fising-converter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codenlighten%2Fising-converter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codenlighten%2Fising-converter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codenlighten","download_url":"https://codeload.github.com/codenlighten/ising-converter/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codenlighten%2Fising-converter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35180933,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-06T02:00:07.184Z","response_time":106,"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":["combinatorial-optimization","dimod","ising","maturin","monte-carlo","optimization","parallel-tempering","pyo3","python","qubo","rust","simulated-annealing","spin-glass"],"created_at":"2026-07-06T07:01:16.072Z","updated_at":"2026-07-06T07:01:17.011Z","avatar_url":"https://github.com/codenlighten.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ising_lab\n\n[![CI](https://github.com/codenlighten/ising-converter/actions/workflows/ci.yml/badge.svg)](https://github.com/codenlighten/ising-converter/actions/workflows/ci.yml)\n\nA Python lab for converting combinatorial problems into **Ising / QUBO** form and\nsolving them with a fast **Rust** kernel (simulated annealing and parallel\ntempering), backed by a benchmark harness, a best-known-optimum registry, and\n[dimod](https://docs.ocean.dwavesys.com/) interop.\n\nThe Ising energy is\n\n```\nH(s) = sum_i h_i s_i + sum_{(i,j) in edges} J_ij s_i s_j,    s_i in {-1, +1}\n```\n\nand the QUBO form is the binary (`x_i in {0, 1}`) equivalent, related by\n`x_i = (1 + s_i) / 2`.\n\n## Features\n\n- **Rust kernel** (`src/lib.rs`, exposed as `ising_lab._kernel`)\n  - `simulated_anneal` — geometric beta schedule, parallel reads via rayon\n  - `parallel_tempering` / `parallel_tempering_diagnostic` / `parallel_tempering_with_betas`\n  - `parallel_tempering_houdayer` — PT with Houdayer isoenergetic cluster moves\n  - `population_annealing` — sequential Monte Carlo with Boltzmann resampling\n  - `population_annealing_icm` — PA + Houdayer cluster moves (strongest on 3D EA)\n  - `belief_propagation` — deterministic sum-product inference (marginals, Bethe free energy)\n  - `brute_force_ground_state` / `brute_force_min_energy` (exact, N ≤ 30)\n  - Deterministic for a fixed `seed`, regardless of thread scheduling.\n- **Problem encoders** (`ising_lab.problems`) — reference implementations of the\n  recipes in Lucas (2014), *Ising formulations of many NP problems*: max-cut,\n  number partitioning, TSP, graph coloring, knapsack, cardinality constraints.\n- **QUBO model** (`ising_lab.QUBO`, `qubo_to_ising`) with an exact energy check.\n- **Benchmark harness** (`ising_lab.benchmarks`) — Sherrington–Kirkpatrick (SK)\n  and Edwards–Anderson (EA) spin-glass suites, time-to-solution (TTS) metrics,\n  CSV/JSON export, and beta-ladder auto-tuners (equal-acceptance and\n  Katzgraber–Trebst–Huse–Troyer feedback-optimized).\n- **Optimum registry** (`ising_lab.OptimumRegistry`) — a monotone-in-energy,\n  JSON-persisted record of the best solution known per instance.\n- **dimod interop** (`ising_lab.dimod_adapter`) — convert any BQM to/from an\n  `IsingModel`, and expose the SA/PT kernels as `dimod.Sampler` subclasses.\n\n## Install\n\nThe project is built with [maturin](https://www.maturin.rs/). Requires Python\n≥ 3.9 and a Rust toolchain.\n\n```bash\npython -m venv .venv \u0026\u0026 source .venv/bin/activate\npip install maturin\nmaturin develop --release          # build the Rust kernel and install editable\n\n# optional extras\npip install -e \".[dev]\"            # pytest + maturin + dimod\npip install -e \".[dimod]\"          # dimod only (for the interop layer)\n```\n\n## Quickstart\n\n```python\nimport ising_lab as il\nfrom ising_lab.problems import max_cut\nfrom ising_lab.qubo import spins_to_bits\n\n# Max-cut on a 4-cycle. Returns (model, offset); cut_size = -(energy + offset).\nmodel, offset = max_cut(4, [(0, 1), (1, 2), (2, 3), (3, 0)])\n\nresults = il.simulated_anneal(model, num_sweeps=500, num_reads=20, seed=7)\nstate, energy = min(results, key=lambda r: r[1])\nprint(\"cut size:\", -(energy + offset))   # -\u003e 4.0\nprint(\"partition:\", spins_to_bits(state))\n```\n\nParallel tempering on a frustrated instance:\n\n```python\nresults = il.parallel_tempering(\n    model, num_sweeps=1000, num_replicas=8,\n    beta_min=0.1, beta_max=10.0, num_reads=10, seed=42,\n)\nbest_energy = min(e for _, e in results)\n```\n\n## Benchmarking\n\n```python\nfrom ising_lab.benchmarks import sk_suite, benchmark, wrap_sa, wrap_pt, records_to_csv\nfrom ising_lab import OptimumRegistry\n\ninstances = sk_suite(sizes=[12, 16], instances_per_size=5, distribution=\"binary\")\nreg = OptimumRegistry(\"results/sk_registry.json\")\n\nrecords = benchmark(\n    {\"sa\": wrap_sa(num_sweeps=1000), \"pt\": wrap_pt(num_sweeps=1000)},\n    instances,\n    num_reads=50,\n    registry=reg,        # seeds/records best-known truth; brute-forces N \u003c= 30\n)\nreg.save()\nrecords_to_csv(records, \"results/sk_sa_vs_pt.csv\")\n```\n\nEach `BenchmarkRecord` carries best/median/mean energy, wall time, success\nprobability against ground truth, and `tts_99` (time-to-solution at 99%\nconfidence). See `scripts/` for the benchmark runs whose outputs live in\n`results/`.\n\n## dimod interop\n\n```python\nimport dimod\nfrom ising_lab.dimod_adapter import SimulatedAnnealingSampler, to_bqm\n\nsampler = SimulatedAnnealingSampler()\nsampleset = sampler.sample(my_bqm, num_sweeps=1000, num_reads=100, seed=1)\n\n# or wrap an external dimod sampler (e.g. neal) into the benchmark harness:\nfrom ising_lab.benchmarks import wrap_dimod\n```\n\n## Cluster moves and the Parisi yardstick\n\nTwo physics-aware tools for the hard spin-glass regime (`scripts/bench_houdayer.py`):\n\n- **Houdayer-PT** (`parallel_tempering_houdayer`) layers **isoenergetic cluster\n  moves** on parallel tempering. It runs two replica lanes and flips connected\n  clusters of disagreeing spins, tunnelling through barriers single-spin flips\n  cannot cross. This is effective on **sparse / finite-dimensional** graphs —\n  the 3D Edwards–Anderson lattice, which is also the regime of hardware\n  spin-glass annealers. On a fully connected SK instance the disagreement graph\n  percolates into one cluster and the move degenerates to a trivial global swap,\n  so use plain `parallel_tempering` there. At matched compute on L=8 (N=512)\n  Gaussian 3D EA, Houdayer-PT reaches lower mean energy on 8/8 benchmark\n  instances (see `results/houdayer_vs_pt_ea3d.json`).\n\n- **Population annealing** (`population_annealing`) carries a *population* of\n  replicas and anneals β upward, **resampling** by the Boltzmann factor\n  `exp(−Δβ·E)` at each step (low-energy replicas multiply, high-energy ones die)\n  before equilibrating with Metropolis sweeps. On hard 3D EA Gaussian glasses it\n  is the strongest sampler here by a wide margin: on L=8 (N=512) it reaches\n  energies ~28 units lower than parallel tempering **even when PT is given 16×\n  the sweeps** (~50× the wall time), winning on 5/5 benchmark instances\n  (`results/population_vs_pt_ea3d.json`).\n\n- **Population annealing + cluster moves** (`population_annealing_icm`) is the\n  Wang–Machta–Katzgraber combination — Houdayer isoenergetic cluster moves\n  between random pairs of the PA population at each temperature, the literature's\n  strongest classical method for 3D EA glasses. Honest finding: it improves on\n  plain PA but *incrementally* (~1–1.4 energy units typical at L=8, better on\n  4/5 instances at matched compute — `results/pa_icm_vs_pa_ea3d.json`), because\n  PA's resampling already does most of the work. A no-op on fully connected SK.\n\n- **Belief propagation** (`belief_propagation`, `ising_lab.inference`) is a\n  deterministic message-passing alternative to Monte Carlo. It returns spin\n  marginals and the **Bethe free energy** — observables the samplers don't\n  directly produce — **exact on trees** (validated to machine precision; a\n  2000-node tree converges in ~0.05 s, where brute force would need 2²⁰⁰⁰\n  evaluations), and the Bethe approximation on loopy graphs. Honest limitation:\n  as a *ground-state* heuristic it is weak on frustrated loopy glasses — rounding\n  its marginals on 3D EA lands far above population annealing (it may \"converge\"\n  to a useless fixed point). That failure is exactly what loop-corrected BP and\n  tensor-network methods exist to fix; BP here is the correct, fast inference\n  core to build those on, not a 3D-glass optimizer.\n\n- **Loop-corrected BP** (`loop_corrected_free_energy`) implements the\n  Chertkov–Chernyak loop series: `Z = Z_Bethe · (1 + Σ over generalized loops)`,\n  truncated at simple cycles, each cycle contributing\n  `Π_{(ij)∈C} χ_ij / √((1−m_i²)(1−m_j²))`. It is **exact on a single cycle**\n  (a ring — validated to machine precision) and a large, systematic improvement\n  on loopy graphs: on brute-forceable 3×3–4×4 frustrated grids it cuts the\n  free-energy error by ~3×–600× (`results/loop_bp_free_energy.json`). Honest\n  limitation: the truncation drops higher-order generalized loops, so the gain\n  shrinks with graph density and can slightly *overshoot* when Bethe is already\n  near-exact — a principled leading-order correction, not a black box.\n\n- **Parisi density as truth** (`sk_parisi_reference_energy`,\n  `PARISI_SK_ENERGY_DENSITY`). The SK ground-state energy density converges to\n  the analytically known Parisi constant, `E₀/N → −0.7632`, i.e.\n  `E₀ ≈ −0.7632·N^{3/2}` for these un-normalized couplings. This gives an\n  **absolute** large-N yardstick where brute force is hopeless — finite systems\n  sit just above it by an O(N^{−2/3}) finite-size correction\n  (`results/sk_parisi_convergence.json`).\n\n```python\nimport ising_lab as il\nfrom ising_lab.benchmarks import ea_instance, sk_instance, sk_energy_density\n\n# Cluster-move PT on a 3D EA lattice\ninst = ea_instance(8, seed=0, dimension=3, distribution=\"gaussian\")\nres = il.parallel_tempering_houdayer(\n    inst.model, num_sweeps=2000, num_replicas=24, icm_every=5, num_reads=16, seed=0,\n)\n\n# How close is a sample to the Parisi thermodynamic-limit density?\nsk = sk_instance(400, seed=0, distribution=\"gaussian\")\nr = il.parallel_tempering(sk.model, num_sweeps=8000, num_replicas=24, num_reads=16, seed=0)\nprint(sk_energy_density(min(e for _, e in r), 400))   # -\u003e approaches -0.7632\n```\n\n## Results: method comparison\n\nA unified benchmark across every method (`scripts/bench_all_methods.py`,\nartifact in `results/all_methods_comparison.json`). The honest summary: the best\nmethod depends on the regime, and the gaps grow with problem hardness.\n\n**Sherrington–Kirkpatrick, exact truth** (success probability / median TTS₉₉).\nAll replica methods solve these; population annealing has the best\ntime-to-solution.\n\n| method        | N=16 success | N=16 TTS₉₉ | N=24 success | N=24 TTS₉₉ |\n|---------------|:------------:|:----------:|:------------:|:----------:|\n| SA            | 96 %         | 0.0001 s   | 96 %         | 0.0003 s   |\n| PT            | 100 %        | 0.0018 s   | 100 %        | 0.0033 s   |\n| Houdayer-PT   | 100 %        | 0.0041 s   | 100 %        | 0.0074 s   |\n| **PA**        | **100 %**    | **0.0007 s** | **100 %**  | **0.0012 s** |\n\n**3D Edwards–Anderson, Gaussian, L=6 (N=216)** — the sparse-lattice regime of\nhardware annealers (mean best / mean typical energy / wall time, 5 instances):\n\n| method        | mean best | mean typical | wall   |\n|---------------|:---------:|:------------:|:------:|\n| SA            | −363.3    | −359.0       | 0.04 s |\n| PT            | −356.1    | −352.9       | 0.53 s |\n| Houdayer-PT   | −357.5    | −354.4       | 1.32 s |\n| **PA**        | **−363.6**| **−363.1**   | 0.17 s |\n| BP (baseline) | +16.9     | —            | 0.00 s |\n\nPA wins on both best and (especially) typical-run energy; the margin over PT\nwidens at larger L (at L=8 it reaches energies ~28 units lower than PT given 16×\nthe sweeps — see `results/population_vs_pt_ea3d.json`). SA is competitive on\nbest-of at this smaller size; belief propagation, rounded to a configuration, is\nnot an optimizer here.\n\n**Sherrington–Kirkpatrick, N=200** (beyond brute force; energy density vs the\nParisi constant −0.7632). All samplers tie at −0.7453 — a +0.018 gap that is the\nexpected O(N^{−2/3}) finite-size correction, not sampler error. On mean-field SK\nthe landscape is easy enough that method choice barely matters.\n\n### Time-to-solution scaling (PA vs PT)\n\nThe rigorous version of \"PA beats PT\": optimal **work-to-solution** in\nhardware-independent Monte Carlo sweep units (Rønnow-style — budget scanned, the\nminimum taken), on 3D EA Gaussian, median over 5 instances per size\n(`scripts/bench_tts_scaling.py`, `results/tts_scaling_ea3d.json`):\n\n| L | N   | PA W\\*    | PT W\\*       | PT / PA |\n|---|-----|----------:|-------------:|--------:|\n| 3 | 27  | 1,200     | 16,000       | 13×     |\n| 4 | 64  | 3,250     | 55,746       | 17×     |\n| 5 | 125 | 8,764     | 11,189,425   | **1,277×** |\n| 6 | 216 | 80,483    | *unreached*  | ∞       |\n\nPA's work-to-solution grows gracefully with N; PT's diverges — a roughly\nconstant ~15× edge at small sizes blows up to \u003e1000× by N=125, and by N=216 PT\nfails to reach the best-known energy within the tested budget (≤256k sweep\nunits) while PA solves it. Sweep units remove the multicore-parallelism\nconfound, so this is an *algorithmic* separation, not a hardware artifact.\n\nHonest caveats: (i) the scan optimizes over per-attempt *sweeps* at fixed\nPA population/replica counts, so both W\\* values are upper bounds on each\nmethod's globally-optimal TTS; the robust conclusion is the *separation*, not\nthe absolute numbers. (ii) \"Unreached\" means PT did not reach the\n**heavy-PA-derived** best-known energy (validated to equal brute force only at\nN≤27), not that PT cannot find the true ground state. (iii) PT's large-N W\\*\nrests on few successes per 60 reads, so it is noisy to a factor of ~2 — far\nsmaller than the gap.\n\n## Competing with D-Wave?\n\nD-Wave's strongest published *optimization* scaling-advantage claim (Munoz Bauza\n\u0026 Lidar, *Phys. Rev. Lett.* 134, 160601, 2025) is measured **against PT-ICM** —\nparallel tempering with isoenergetic cluster moves, i.e. this lab's\n`parallel_tempering_houdayer`. We independently find that **population annealing\nbeats PT-ICM** by ~6× at N=64 and unboundedly by N=216 on 3D EA, and by ~37× at\nN=64 up to unbounded by N=144 on a **2D degree-5 high-precision proxy** of their\nown instance class (work-to-solution; `results/pa_vs_pticm_tts_ea3d.json`,\n`results/pa_vs_pticm_degree5_2d.json`). That motivates a falsifiable hypothesis —\na PA baseline could erode the reported QA advantage — but it is **not** a\nrefutation: our graph is a proxy (not D-Wave's exact QAC graph), our PT-ICM may be\nunder-tuned, the metric differs (sweep units vs wall-clock time-to-epsilon), and\nbeating PT-ICM is not beating QA. The precise claim, caveats, and a roadmap to an honest\nhead-to-head are in [docs/DWAVE_COMPARISON.md](docs/DWAVE_COMPARISON.md).\n\n## Development\n\n```bash\nmaturin develop --release    # rebuild after editing src/lib.rs\npytest -q                    # 52 tests\ncargo clippy --release       # lint the kernel (clean)\n```\n\n## Layout\n\n```\nsrc/lib.rs                 Rust kernel (SA, PT, brute force, IsingModel)\npython/ising_lab/          Python package\n  qubo.py                  QUBO model + Ising conversion\n  problems.py              combinatorial problem encoders\n  benchmarks.py            SK/EA suites, harness, beta-ladder tuners\n  registry.py              best-known-optimum registry\n  dimod_adapter.py         BQM \u003c-\u003e IsingModel, dimod.Sampler wrappers\ntests/                     pytest suite\nscripts/                   benchmark runners\nresults/                   benchmark outputs (CSV/JSON)\n```\n\n## References\n\n- A. Lucas, *Ising formulations of many NP problems*, Front. Phys. 2 (2014).\n- Katzgraber, Trebst, Huse, Troyer, *Feedback-optimized parallel tempering Monte\n  Carlo*, Phys. Rev. E 73, 056704 (2006).\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodenlighten%2Fising-converter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodenlighten%2Fising-converter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodenlighten%2Fising-converter/lists"}