Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions cpp/monoprop/algebra/AlgebraCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include <array>
#include <bit>
#include <cassert>
#include <cstdint>
#include <format>
#include <optional>
Expand All @@ -24,6 +25,7 @@

#include "monoprop/TypeAliases.h"
#include "monoprop/Utilities.h"
#include "monoprop/core/SparseMonomial.h"

namespace monoprop {

Expand All @@ -46,7 +48,7 @@
auto indices_to_bitset(const VecZ &arr) -> Monomial<NumModes> {
Monomial<NumModes> bs;
for (const auto &bit_loc : arr) {
bs.set(2 * NumModes - 1 - bit_loc); // MSb0 convention: index 0 maps to the top bit

Check warning on line 51 in cpp/monoprop/algebra/AlgebraCommon.h

View workflow job for this annotation

GitHub Actions / clang-tidy analysis

'*' has higher precedence than '-'; add parentheses to explicitly specify the order of operations [readability-math-missing-parentheses]
}
return bs;
}
Expand Down Expand Up @@ -95,6 +97,8 @@
return is_paired<NumModes>(indices_to_bitset<NumModes>(mono));
}

// The (k, d) digest form of the predicate above is is_paired(size_t, size_t) in SparseMonomial.h.

template <size_t NumModes, typename Rows>
auto is_fully_paired(const VecZ &inds, const Rows &op) -> VecZ {
VecZ result;
Expand Down Expand Up @@ -142,7 +146,7 @@
const uint64_t pair_mask = even_mask & active_mask;
const uint64_t first_pair = active_word & pair_mask;
const uint64_t second_pair = (active_word >> 1) & pair_mask;
return {static_cast<size_t>(std::popcount(first_pair ^ second_pair)),

Check warning on line 149 in cpp/monoprop/algebra/AlgebraCommon.h

View workflow job for this annotation

GitHub Actions / clang-tidy analysis

use designated initializer list to initialize 'CutoffSums' [modernize-use-designated-initializers]
static_cast<size_t>(std::popcount(active_word)),
static_cast<size_t>(std::popcount(first_pair | second_pair))};
}
Expand All @@ -154,6 +158,27 @@
return {(first_pair ^ second_pair).count(), active_mono.count(), (first_pair | second_pair).count()};
}

// The same sums from a (k, d) digest; no logical_num_modes because the masking above is inert for a
// well-formed monomial (every set bit at physical position >= 2 * (NumModes - logical_num_modes)).
[[nodiscard]] inline constexpr auto cutoff_sums(size_t k, size_t d) noexcept -> CutoffSums {

Check warning on line 163 in cpp/monoprop/algebra/AlgebraCommon.h

View workflow job for this annotation

GitHub Actions / clang-tidy analysis

function 'cutoff_sums' has inline specifier but is implicitly inlined [readability-redundant-inline-specifier]

Check warning on line 163 in cpp/monoprop/algebra/AlgebraCommon.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

A "constexpr" function should not be declared "inline".

See more on https://sonarcloud.io/project/issues?id=Algorithmiq_monoprop&issues=AaAg7b_lotbAUCKQYzvh&open=AaAg7b_lotbAUCKQYzvh&pullRequest=263
return {k - (2 * d), k, k - d};

Check warning on line 164 in cpp/monoprop/algebra/AlgebraCommon.h

View workflow job for this annotation

GitHub Actions / clang-tidy analysis

use designated initializer list to initialize 'CutoffSums' [modernize-use-designated-initializers]
}

// d alone: mode m owns bits (2m, 2m+1) LSb0, so `w & (w >> 1)` masked to even bits counts each
// doubly-occupied mode once. The shift is word-local because a carry would land on odd bit 63.
// Same well-formedness precondition as cutoff_sums(k, d); a monomial built below the active offset by
// hand (majorana_cutoff_tests.cpp:79,101) must keep the bitset overload, which stays the oracle.
template <size_t NumModes>
[[gnu::always_inline]] [[nodiscard]] inline auto paired_mode_count(const Monomial<NumModes> &mono) noexcept -> size_t {
constexpr auto even = even_bits<2 * NumModes, LSb0>();
size_t d = 0;
for (size_t w = 0; w < Monomial<NumModes>::num_words(); ++w) {
const uint64_t word = mono.word(w);
d += static_cast<size_t>(std::popcount(word & (word >> 1) & even.word(w)));
}
return d;
}

// Both cutoffs below keep a fully paired monomial (xor_sum == 0) unconditionally: those are the only
// terms contributing to an expectation value against a product reference state, so bounding them by
// length or support would discard signal.
Expand All @@ -169,6 +194,11 @@
return length_cutoff<NumModes>(mono, cutoff, NumModes);
}

// Digest form, on the same precondition as cutoff_sums(k, d). Width-independent, hence not templated.
[[nodiscard]] inline constexpr auto length_cutoff(size_t k, size_t d, unsigned int cutoff) noexcept -> bool {

Check warning on line 198 in cpp/monoprop/algebra/AlgebraCommon.h

View workflow job for this annotation

GitHub Actions / clang-tidy analysis

function 'length_cutoff' has inline specifier but is implicitly inlined [readability-redundant-inline-specifier]

Check warning on line 198 in cpp/monoprop/algebra/AlgebraCommon.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

A "constexpr" function should not be declared "inline".

See more on https://sonarcloud.io/project/issues?id=Algorithmiq_monoprop&issues=AaAg7b_lotbAUCKQYzvi&open=AaAg7b_lotbAUCKQYzvi&pullRequest=263
return length_keeps(k, d, cutoff);
}

template <size_t NumModes>
auto support_cutoff(const Monomial<NumModes> &mono, unsigned int cutoff, size_t logical_num_modes) -> bool {
const auto sums = cutoff_sums<NumModes>(mono, logical_num_modes);
Expand All @@ -180,6 +210,11 @@
return support_cutoff<NumModes>(mono, cutoff, NumModes);
}

// Digest form, on the same precondition as cutoff_sums(k, d). Width-independent, hence not templated.
[[nodiscard]] inline constexpr auto support_cutoff(size_t k, size_t d, unsigned int cutoff) noexcept -> bool {

Check warning on line 214 in cpp/monoprop/algebra/AlgebraCommon.h

View workflow job for this annotation

GitHub Actions / clang-tidy analysis

function 'support_cutoff' has inline specifier but is implicitly inlined [readability-redundant-inline-specifier]

Check warning on line 214 in cpp/monoprop/algebra/AlgebraCommon.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

A "constexpr" function should not be declared "inline".

See more on https://sonarcloud.io/project/issues?id=Algorithmiq_monoprop&issues=AaAg7b_lotbAUCKQYzvj&open=AaAg7b_lotbAUCKQYzvj&pullRequest=263
return support_keeps(k, d, cutoff);
}

namespace detail {

template <size_t NumModes>
Expand Down Expand Up @@ -242,6 +277,20 @@
return cutoff_fn_(mono);
}

// Same decision without cutoff_sums: the caller already knows k, so only d is computed. No
// popcount early-out, deliberately -- the digest is cheaper than the branch. nullopt if opaque.
auto passes_from_dense(const Monomial<NumModes> &mono, size_t k) const -> std::optional<bool> {
// paired_mode_count has no active_mask, so it agrees with cutoff_sums(mono, L) only above it.
assert(mono.find_first() >= active_bit_offset_() && "monomial has a set bit below its active offset");
if (length_cutoff_ != nullptr) {
return length_keeps(k, paired_mode_count<NumModes>(mono), length_cutoff_->cutoff);
}
if (support_cutoff_ != nullptr) {
return support_keeps(k, paired_mode_count<NumModes>(mono), support_cutoff_->cutoff);
}
return std::nullopt;
}

// Upper bound on the set bits (physical slots) a surviving term can carry, so the store can size
// its packed inline rows. A length cutoff counts set bits directly; a support cutoff counts
// modes/qubits, each spanning two slots, hence the x2.
Expand All @@ -256,6 +305,17 @@
}

private:
// 2 * (NumModes - logical_num_modes) of whichever concrete cutoff is configured; assert-only.
[[nodiscard]] auto active_bit_offset_() const -> size_t {
if (length_cutoff_ != nullptr) {
return 2 * (NumModes - length_cutoff_->logical_num_modes);
}
if (support_cutoff_ != nullptr) {
return 2 * (NumModes - support_cutoff_->logical_num_modes);
}
return 0;
}

const CutoffFn<NumModes> &cutoff_fn_;
const LengthCutoff<NumModes> *length_cutoff_;
const SupportCutoff<NumModes> *support_cutoff_;
Expand Down
35 changes: 35 additions & 0 deletions cpp/monoprop/core/SparseMonomial.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2026 Algorithmiq
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

// The structural cutoffs over a monomial's (k, d) digest: k = popcount, d = modes carrying BOTH
// Majoranas. Lets CutoffEvaluator decide from integers the emit site already has, without cutoff_sums.

#include <cstddef>

namespace monoprop {

// xor_sum = k - 2d, popcount_sum = k, or_sum = k - d; a fully paired monomial is kept unconditionally.
[[nodiscard]] inline constexpr auto is_paired(size_t k, size_t d) noexcept -> bool {

Check warning on line 25 in cpp/monoprop/core/SparseMonomial.h

View workflow job for this annotation

GitHub Actions / clang-tidy analysis

function 'is_paired' has inline specifier but is implicitly inlined [readability-redundant-inline-specifier]

Check warning on line 25 in cpp/monoprop/core/SparseMonomial.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

A "constexpr" function should not be declared "inline".

See more on https://sonarcloud.io/project/issues?id=Algorithmiq_monoprop&issues=AaAg7b_AotbAUCKQYzve&open=AaAg7b_AotbAUCKQYzve&pullRequest=263
return k == 2 * d;
}
[[nodiscard]] inline constexpr auto length_keeps(size_t k, size_t d, size_t cutoff) noexcept -> bool {

Check warning on line 28 in cpp/monoprop/core/SparseMonomial.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

A "constexpr" function should not be declared "inline".

See more on https://sonarcloud.io/project/issues?id=Algorithmiq_monoprop&issues=AaAg7b_AotbAUCKQYzvf&open=AaAg7b_AotbAUCKQYzvf&pullRequest=263
return k == 2 * d || k <= cutoff;
}
[[nodiscard]] inline constexpr auto support_keeps(size_t k, size_t d, size_t cutoff) noexcept -> bool {

Check warning on line 31 in cpp/monoprop/core/SparseMonomial.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

A "constexpr" function should not be declared "inline".

See more on https://sonarcloud.io/project/issues?id=Algorithmiq_monoprop&issues=AaAg7b_AotbAUCKQYzvg&open=AaAg7b_AotbAUCKQYzvg&pullRequest=263
return k == 2 * d || k - d <= cutoff;
}

} // namespace monoprop
61 changes: 2 additions & 59 deletions cpp/monoprop/detail/evolution/layer_build/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,24 +98,8 @@ struct FusedContract {
std::vector<HalfRotationRec> cross_half; // R>1: one half per cross-rank query (resolver +φ, querier −φ)
};

// Queries ride flat VecZ buffers: kQueryWords elements per query (W monomial words + one ±1 phase word).
// The source index is not in the payload — the resolver answers by position; the querier holds src_idx_r[r][q].
template <size_t NumModes>
inline constexpr size_t kQueryWords = mpi_detail::kWords<NumModes> + 1;

// Fused query+value record width (R>1): the plain query record plus one trailing word holding the source's
// pre-cos coeff (v_src, bit-cast from double), so query + value ride a single alltoallv instead of two.
template <size_t NumModes>
inline constexpr size_t kQueryWordsFused = kQueryWords<NumModes> + 1;

// The unsigned-int intermediate normalizes the ±1 sign bit into a fixed 32-bit pattern so the round-trip
// is exact for any VecZ element width. Edit encode/decode as a pair.
inline auto encode_phase(int phase) -> size_t {
return static_cast<size_t>(static_cast<unsigned int>(phase));
}
inline auto decode_phase(size_t word) -> int {
return static_cast<int>(static_cast<unsigned int>(word));
}
// Queries ride flat VecZ buffers in one VARIABLE-WIDTH format (SparseQuery): no stride exists, so every
// offset comes from QueryCodec's walk. The source index is not on the wire; the querier holds src_idx_r.

// bit_cast, not a conversion, so v_src arrives over the wire bit-identical.
static_assert(sizeof(size_t) == sizeof(double), "fused query value word assumes 64-bit VecZ element");
Expand All @@ -126,45 +110,4 @@ inline auto decode_value(size_t word) -> double {
return std::bit_cast<double>(word);
}

template <size_t NumModes>
inline auto query_push(VecZ &buf, const Monomial<NumModes> &mono, int phase) -> void {
mpi_detail::append_monomial_words<NumModes>(mono, buf);
buf.push_back(encode_phase(phase));
}

// The mono + phase words occupy the same leading offsets in the plain and fused record, so readers differ
// only in the per-record stride QW (defaulted to the plain width).
template <size_t NumModes, size_t QW = kQueryWords<NumModes>>
inline auto query_read(const VecZ &buf, size_t q, Monomial<NumModes> &mono_out, int &phase_out) -> void {
const size_t base = q * QW;
mono_out = mpi_detail::read_monomial_from_words<NumModes>(buf, base);
phase_out = decode_phase(buf[base + mpi_detail::kWords<NumModes>]);
}

// No monomial reconstruction: process_responses needs only the phase.
template <size_t NumModes, size_t QW = kQueryWords<NumModes>>
inline auto query_phase(const VecZ &buf, size_t q) -> int {
return decode_phase(buf[q * QW + mpi_detail::kWords<NumModes>]);
}

template <size_t NumModes>
inline auto query_value(const VecZ &buf, size_t q) -> double {
return decode_value(buf[q * kQueryWordsFused<NumModes> + mpi_detail::kWords<NumModes> + 1]);
}

// Requires v.size() == q.size()/kQueryWords: exactly one value per query record.
template <size_t NumModes>
inline auto build_fused_query_value(const VecZ &q, const std::vector<double> &v, VecZ &out) -> void {
constexpr size_t W = kQueryWords<NumModes>;
const size_t nq = q.empty() ? 0 : q.size() / W;
out.clear();
out.reserve(nq * kQueryWordsFused<NumModes>);
for (size_t i = 0; i < nq; ++i) {
out.insert(out.end(),
q.begin() + static_cast<std::ptrdiff_t>(i * W),
q.begin() + static_cast<std::ptrdiff_t>((i + 1) * W));
out.push_back(encode_value(v[i]));
}
}

} // namespace monoprop::detail
Loading
Loading