refactor(cpp)!: ♻️ take the mode count as a runtime width - #308
Open
robertodr wants to merge 1 commit into
Open
Conversation
The engine no longer carries the mode count as a template parameter. A propagator takes its logical width as a constructor argument and sizes its monomial storage from it at runtime, so there is one compiled MonomialPropagator whatever the mode count and no compile-time ceiling on it. Bitset *is* the monomial: the width is data, so a width exists per value rather than per type. Per-word loops go through detail::with_nwords, which dispatches a runtime word count to a compile-time-unrolled arm in place of the if constexpr branches a compile-time width allowed. The binding generators go with it -- tools/generate-binders.py and tools/generate-dispatch.py emitted one instantiation per mode tier, and bindings.cpp is now a real file rather than a configured template. Dense-row terms and energies are byte-identical to the compile-time-width engine across every baseline case, and the support-form backend still agrees with them as term sets and to rtol 1e-10. The per-gate kernel seam and the support-form query record that recover the lost per-term specialization are not here; they follow separately. Assisted-by: ClaudeCode:claude-opus-5
robertodr
requested review from
adamglos92,
diagonal-hamiltonian,
fpietra and
ludmilaasb
as code owners
August 29, 2026 16:43
5 tasks
|
Docs preview: https://pr-308.monoprop-docs.pages.dev |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## split/04-sparse-row-store #308 +/- ##
=============================================================
- Coverage 97.70% 97.58% -0.13%
=============================================================
Files 14 14
Lines 742 746 +4
Branches 98 98
=============================================================
+ Hits 725 728 +3
- Misses 12 13 +1
Partials 5 5
Flags with carried forward coverage won't be shown. Click here to find out more. |
|
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



🤖 AI text below 🤖
Summary
The engine no longer carries the mode count as a template parameter. A propagator takes its logical width as a constructor argument and sizes its monomial storage from it at runtime, so there is one compiled
MonomialPropagatorwhatever the mode count and no compile-time ceiling on it.Bitsetis the monomial: the width is data, not a template parameter, so a width exists per value rather than per type. Construct with one (Bitset(num_bits)), take a mode count frommono.size() / 2, recover a width with an instance call. Per-word loops go throughdetail::with_nwords, which dispatches a runtime word count to a compile-time-unrolled arm, in place of theif constexprbranches a compile-time width allowed.That removes the binding generators with it.
tools/generate-binders.pyandtools/generate-dispatch.pyexisted to emit one instantiation per mode tier;bindings.cppis a real file now rather than a configured template, so the nanobind version the module reports arrives asmonoprop_NANOBIND_VERSIONfrom CMake.Stacked on #305.
Changes
Bitset/Monomial: runtime width,detail::with_nwordsdispatch,heap_bytes()for the spilled regime. Only words[0, num_words())hold a value — the inline tail above them is left indeterminate so a copy costs the operand's own width.MonomialPropagator:num_modesis a constructor argument;detail::storage_modes_for()derives the storage width from it, rounded up to a whole 32-mode block.with_algebrabinds a runtimeBasiswithout a width; the algebra policies lose theirNumModesparameter.MonomialPropagator.inlbecomesMonomialPropagator.cpp— there is nothing left to instantiate per width.tools/generate-binders.py,tools/generate-dispatch.py,tools/_binding_layout.py,bindings.cpp.in,tests/test_binding_layout.py.ModeEmbedding/WIDE_EMBEDDING(tests/cases.py) andtest_utils::embed_caserelabel a fixture into 260 logical / 288 storage modes — nine words per monomial, pastBitset's eight inline ones. The map is monotone, so the fixture's exact energy still applies and the wide run owes the narrow run's evolved operator term for term.detail/partition/StagedCollect.h: one result per partition written from the partitions' own masters. The staging vector is there for theboolcase only —std::vector<bool>is bit-packed, so concurrent writes to disjoint logical elements can tear the same word.Verification
capture-baselinevs main's golden, dense rows--compare --tol 1e-10, sparse vs dense (incl. the new wide case)ctestsparse-rows)pytest,autoandmonoprop_ROW_STORE=sparseprekover the changed filesThe byte-wise dense gate is the load-bearing one: the width stops being a type, and no term, coefficient, iteration order or energy moves.
Two pre-existing
monoprop-bench-toolstest_memory.pyfailures are unrelated — they fail identically on a pristinemaincheckout and pass in isolation.Not in this PR
The per-gate kernel seam (
TermProduct.h,WordKernel<W>,with_kernel_width) and the support-form query record follow in #309. Both are written against the runtime width introduced here, so they cannot precede it. Until they land the per-term instruction count runs above the compile-time-width engine — about a fifth of which reaches cycles.Checklist
docs/,CONTRIBUTING.md) if neededCHANGELOG/ release notes updated if applicableAI/LLM disclosure