feat(cpp): ✨ select the support-form row store per propagator - #305
Open
robertodr wants to merge 2 commits into
Open
feat(cpp): ✨ select the support-form row store per propagator#305robertodr wants to merge 2 commits into
robertodr wants to merge 2 commits into
Conversation
|
Docs preview: https://pr-305.monoprop-docs.pages.dev |
robertodr
force-pushed
the
split/04-sparse-row-store
branch
from
August 29, 2026 14:36
942ed08 to
0202e3f
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## split/02-row-hash-table #305 +/- ##
========================================================
Coverage 97.70% 97.70%
========================================================
Files 14 14
Lines 742 742
Branches 98 98
========================================================
Hits 725 725
Misses 12 12
Partials 5 5
Flags with carried forward coverage won't be shown. Click here to find out more. |
SparseRowStore is a third row representation: each row is a fixed-width list of the modes it occupies plus one word carrying two bits per occupied mode. CodesAlgebra.h is the structural algebra over that word -- one function per dense counterpart, reading the codes word instead of looping over storage words, plus sparse_toggle, the product M (+) G as one merge over two ascending lane arrays. Not yet selected by anything. MPOperator still holds a single OperatorIndex, and neither RowAccess nor the layer-build scan knows this store exists; monoprop_SPARSE_ROW_MIN_MODES is defined and preferred_for_modes() reads it, but no caller asks. Wiring it in is the next change. Two pieces of shared machinery move to RowHashTable.h beside the index both stores key rows through: geometric_row_capacity, the 1.5x growth rule, and spilled_rows_bytes, the side-map accounting. A single number in two stores would otherwise be correctable in one and not the other, which would skew operator_memory_breakdown() for one backend only. splitmix_finalize comes out of SplitmixHash<Bitset<N>>::mix, whose body never depended on the width: the row hash accumulates over (mode, code) slots and so needs the mix with no Bitset in hand. That value routes MPI ownership, so having one definition rather than two matters more than the tidiness. Verified out of tree against the compile-time-width engine: 3000 random monomials round-trip through row(), popcount(), find(), find_batch(), for_each_position(), clone() and resized(); and over 5000 monomials the codes forms of cutoff_sums, is_paired, pauli_y_count, pauli_anticommutes and pauli_rotation_sign agree with the dense ones exactly. OperatorIndex comes out inert after the two helpers move -- identical iteration order, find results, clone order and memory_bytes. Assisted-by: ClaudeCode:claude-opus-5
robertodr
force-pushed
the
split/04-sparse-row-store
branch
from
August 29, 2026 14:59
0202e3f to
3c22661
Compare
Wires SparseRowStore and CodesAlgebra into the engine: a propagator now picks a row backend once at construction and everything from the scan to the inserts is templated on it. MPOperator holds one pointer per backend with exactly one non-null and binds the live one via with_store -- once per layer, inside build_layer, never per term, since the scan asks the store for a row per anticommuting term. LayerBuildEngine, fused_find_and_collect and probe_incoming_queries take the store as a template parameter; off that path the forwarding accessors pay one well-predicted branch. There is no accessor handing out a store, because there is no one type to hand out, so MonomialPropagator::indexing() is replaced by for_each_term() and num_local_terms(). The choice comes from the mode count against monoprop_SPARSE_ROW_MIN_MODES; monoprop_ROW_STORE=dense|sparse forces it process-wide, and an unrecognized value throws rather than falling back to auto -- the point of setting it is to know which backend ran. Every C++ case is registered a second time with the sparse backend forced (the sparse-rows ctest label, 541 cases where there were 270): every fixture is below the crossover, so without that the support-form backend would ship untested. row_store_selection_tests.cpp is what fails if the variable stops reaching the propagator. Gates: the dense capture is byte-identical to main's golden baseline, and the sparse capture agrees with it as term sets plus rtol 1e-10 (just diff-baseline-sparse). Assisted-by: ClaudeCode:claude-opus-5
robertodr
marked this pull request as ready for review
August 29, 2026 15:38
robertodr
requested review from
diagonal-hamiltonian,
fpietra and
ludmilaasb
as code owners
August 29, 2026 15:38
|
This was referenced Aug 29, 2026
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 🤖
Stacked on #303 — review that first.
Summary
Fourth of five PRs carved out of #226. Adds the support-form row store, the algebra that reads it, and the machinery that selects it: a propagator now picks a row backend once at construction, and everything from the scan to the inserts is templated on it.
SparseRowStorekeeps a row as fixed-width mode lanes plus one 2-bit-per-slotcodesword, instead of a dense monomial's storage words.CodesAlgebra.his the structural algebra over that word — one function per dense counterpart — plussparse_toggle, the productM ⊕ Gas a single merge over two ascending lane arrays. It is exact, not an approximation, so both forms have to keep agreeing term for term;codes_algebra_tests.cppandcodes_product_tests.cppare that comparison, over the fixtures and over randomized rows at several widths.MPOperatorholds one pointer per backend with exactly one non-null and binds the live one viawith_store— once per layer, insidebuild_layer, never per term, since the scan asks the store for a row per anticommuting term.LayerBuildEngine,fused_find_and_collectandprobe_incoming_queriestake the store as a template parameter; off that path the forwarding accessors pay one well-predicted branch. There is no accessor handing out a store, because there is no one type to hand out, soMonomialPropagator::indexing()is replaced byfor_each_term()andnum_local_terms().Which backend comes from the mode count against
monoprop_SPARSE_ROW_MIN_MODES— a build-time constant derived from whetherARCH_FLAGis actually emitted rather than from the option that asks for it, because what moves the crossover is the target ISA.monoprop_ROW_STORE=dense|sparseforces it process-wide, and an unrecognized value throws rather than falling back toauto: the point of setting it is to know which backend ran.Every C++ case is now registered a second time with the sparse backend forced — the
sparse-rowsctest label, 541 cases where there were 270. Every fixture is below the crossover, so without that the support-form backend would ship untested.row_store_selection_tests.cppis what fails if the variable stops reaching the propagator.Gates
just diff-baseline: the dense capture is byte-identical tomain's golden baseline. Nothing on the shipping path moved.just diff-baseline-sparse: the sparse capture agrees with it as term sets plus rtol1e-10across all 34 records. The two backends hash differently and so accumulate in a different order by design, which is why this one is a tolerance check.ctest: 541/541, both backends.autoand undermonoprop_ROW_STORE=sparse(twomonoprop-bench-toolsmemory-measurement failures are pre-existing onmainand unrelated).Changes
cpp/monoprop/detail/operator/SparseRowStore.h,cpp/monoprop/algebra/CodesAlgebra.h: new.cpp/monoprop/detail/operator/MPOperator.h: dual store,with_store,set_store,rows_are_sparse, forwarding accessors.cpp/monoprop/detail/operator/RowAccess.h: the four accessors over the third backend.cpp/monoprop/detail/evolution/layer_build/{Engine,Scan,Resolve}.h: templated on the store;build_layeris the single binding site.cpp/monoprop/detail/EnvConfig.h,MonomialPropagator:monoprop_ROW_STOREand the per-propagator choice.cpp/monoprop/Bitset.h: hoistsplitmix_finalizeintonamespace monoprop— the row hash mixes(mode, code)slots with noBitsetin hand. The value routes MPI ownership, so it stays bit-identical.cpp/monoprop/detail/operator/RowHashTable.h:geometric_row_capacity/spilled_rows_bytes, now shared by both stores.CMakeLists.txt: derive and propagatemonoprop_SPARSE_ROW_MIN_MODES.cpp/tests/boostAddTests.cmake,boost-test.cmake: thesparse-rowsvariant pass, with its own MPI rank list so dense coverage growth does not multiply sparsempiexeclaunches.RandomMonomial.handTestOperator.h.justfile:diff-baseline-sparse,test-sparse-rows.benches/conftest.py,report.py: record and rendermonoprop_row_store(asked) besiderow_store_effective(ran).AGENTS.md,README.md,docs/{building,testing}.mdx.Not in this PR
The sparse query record —
QueryKeysFor,query_payload_words_for, the escape tail — has no user until the per-gate kernel seam (TermProduct.h) lands, because every product is still computed densely. It goes with PR 05 alongside that seam and the retained-key arena.Checklist
docs/,CONTRIBUTING.md) if neededCHANGELOG/ release notes updated if applicableAI/LLM disclosure