fix: 🧑🔬 use std::optional for only_rotate_len_k argument#210
std::optional for only_rotate_len_k argument#210Conversation
Assisted-by: GitHubCopilot:gpt-5.4 Co-authored-by: robertodr <3708689+robertodr@users.noreply.github.com>
Assisted-by: GitHubCopilot:gpt-5.4 Co-authored-by: robertodr <3708689+robertodr@users.noreply.github.com>
Assisted-by: GitHubCopilot:gpt-5.4 Co-authored-by: robertodr <3708689+robertodr@users.noreply.github.com>
|
Docs preview: https://pr-210.monoprop-docs.pages.dev |
Co-authored-by: Roberto Di Remigio Eikås <robertodr@users.noreply.github.com> Signed-off-by: Roberto Di Remigio Eikås <robertodr@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #210 +/- ##
==========================================
+ Coverage 97.58% 97.71% +0.12%
==========================================
Files 14 14
Lines 745 743 -2
Branches 99 98 -1
==========================================
- Hits 727 726 -1
+ Misses 13 12 -1
Partials 5 5
Flags with carried forward coverage won't be shown. Click here to find out more. |
There was a problem hiding this comment.
Pull request overview
This PR aligns the only_rotate_len_k gate-application length cap semantics across Python ↔ nanobind ↔ C++ by replacing the C++ int sentinel with std::optional<int> and treating std::nullopt as “no cap”.
Changes:
- Update the C++ propagation API and internal evolution codepaths to use
std::optional<int>foronly_rotate_len_k. - Adjust nanobind defaults to
std::nulloptso PythonNonecan cross the binding boundary without sentinel conversion. - Add/adjust documentation and tests around
None/uncapped behavior.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_only_rotate_k.py |
Adds None to the validation parametrizations for only_rotate_len_k. |
src/monoprop/monomial_propagator.py |
Switches validation helper to preserve None (no None → 0 correction) and passes optional through to the engine. |
src/monoprop/bindings/binder.h |
Changes nanobind defaults for only_rotate_len_k to std::nullopt. |
docs/content/docs/features/cutoff.mdx |
Documents only_rotate_len_k and clarifies that omitting / passing None is unrestricted. |
cpp/monoprop/detail/monomial_propagator/MonomialPropagatorImpl.h |
Threads std::optional<int> through build/propagate paths and gate loop plumbing. |
cpp/monoprop/detail/evolution/layer_build/Scan.h |
Updates scan gating logic to accept std::optional<int> and interpret nullopt as uncapped. |
cpp/monoprop/detail/evolution/layer_build/FusedApply.h |
Updates comments to reflect “no length cap” rather than k==0 sentinel behavior. |
cpp/monoprop/detail/evolution/layer_build/Engine.h |
Updates fused scaling eligibility from k==0 to “no length cap” (nullopt). |
cpp/include/monoprop/MonomialPropagator.h |
Changes public C++ API defaults to std::nullopt and documents uncapped semantics. |
AGENTS.md |
Updates architecture notes to reflect std::optional<int> / std::nullopt semantics. |
Suppressed comments (2)
src/monoprop/monomial_propagator.py:186
- 🤖 AI text below 🤖
The docstring says the upper-bound check only applies when the propagator “knows its qubit count (i.e. on a PauliPropagator)”, but the implementation always enforcesonly_rotate_len_k <= 2 * self._system_sizefor all propagators. Please update the docstring to match the actual validation rule to avoid misleading users.
Must be positive, and at most ``2 * num_qubits`` when the propagator knows its qubit count
(i.e. on a [PauliPropagator][monoprop.pauli_propagator.PauliPropagator]).
cpp/monoprop/detail/monomial_propagator/MonomialPropagatorImpl.h:693
- 🤖 AI text below 🤖
Same asbuild_graph:propagateacceptsstd::optional<int> only_rotate_len_kbut doesn’t validate it. A negative value will be cast tosize_tand behave like “no cap”, which is likely unintended and differs from the Python validation.
auto MonomialPropagator<NumModes>::propagate(const std::vector<VecZ> &majoranas,
const VecZ ¶meter_mapping,
const VecD &gen_coeffs,
const VecD ¶meters,
std::optional<int> only_rotate_len_k) -> void {
| const size_t i = w.base + tz; | ||
| const size_t mono_pop = op.store->popcount(i); | ||
| if (mono_pop > static_cast<size_t>(only_rotate_len_k)) { | ||
| if (mono_pop > static_cast<size_t>(*only_rotate_len_k)) { |
There was a problem hiding this comment.
What happens when you try to cast to size_t but the value is a nullopt?
There was a problem hiding this comment.
in the else branch only_rotate_len_k is guaranteed to have a value, since const bool word_aligned_cos = !only_rotate_len_k; is false in that case.
|
std::optional for only_rotate_len_k argument



Fix #6
🤖 AI text below 🤖
only_rotate_len_kused an integer sentinel in C++ despite being optional in Python. This madeNonecross the binding boundary as0instead of preserving optional semantics.Changes
C++ API
std::optional<int>throughout propagation.std::nulloptas unrestricted gate application.Python bindings
std::nullopt.Nonethrough without conversion.Coverage and documentation
Nonefor both propagation modes.