Skip to content
Merged
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
3 changes: 2 additions & 1 deletion cpp/monoprop/detail/evolution/layer_build/Engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,8 @@ struct LayerBuildEngine {
}
// kNotFound == kMissingIndex == size_t max, so one bound check covers both.
if (found[j] < op_size) {
if (is_leader_pass) {
// Freshly inserted partners (found >= combined_size) skip the mark: combined_size bounds it.
if (is_leader_pass && found[j] < combined_size) {
matched.mark(found[j]);
}
sink.self_hit(srcs[j], found[j], phases[j], v_src);
Expand Down
68 changes: 67 additions & 1 deletion cpp/tests/evolution_detail_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,49 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// MatchedEpochSet and CutoffContext driven directly, not through build_layer.
// MatchedEpochSet, CutoffContext and the self-resolve mark guard driven directly, not through build_layer.

#include <boost/test/unit_test.hpp>

#include <cstddef>
#include <cstdint>
#include <limits>
#include <utility>
#include <vector>

#include "monoprop/TypeAliases.h"
#include "monoprop/algebra/Algebra.h"
#include "monoprop/detail/evolution/CutoffContext.h"
#include "monoprop/detail/evolution/layer_build/Common.h"
#include "monoprop/detail/evolution/layer_build/Engine.h"
#include "monoprop/detail/operator/MPOperator.h"

using namespace monoprop;
using monoprop::detail::CutoffContext;
using monoprop::detail::MatchedEpochSet;

namespace {

// resolve_range_ touches only wants_values and self_hit, so the engine drives without the cross-rank sink surface.
struct RecordingSink {
static constexpr bool wants_values = false;
std::vector<std::pair<size_t, size_t>> hits; // (src, found)
auto self_hit(size_t src, size_t found, int /*phase*/, double /*v_src*/) -> void { hits.emplace_back(src, found); }
};

// append_term writes a row only; find_batch needs the hash index, which insert_absent_terms populates.
auto indexed_op(const std::vector<Monomial<8>> &terms) -> detail::MPOperator<8> {
detail::MPOperator<8> op;
detail::insert_absent_terms<8>(
op,
terms.size(),
[&](size_t k) -> const Monomial<8> & { return terms[k]; },
[&](size_t k, size_t base) { assign_row<8>(*op.store, base + k, terms[k]); });
return op;
}

} // namespace

BOOST_AUTO_TEST_CASE(matched_epoch_begin_gate_clears_all) {
MatchedEpochSet set;
set.begin_gate(5);
Expand Down Expand Up @@ -74,6 +102,44 @@ BOOST_AUTO_TEST_CASE(matched_epoch_u32_wrap_resets) {
BOOST_TEST(set.is_marked(2));
}

// A self-resolve hit whose index the store only grew into after construction is a real hit -- it must reach
// the sink -- but it is outside the matched set, whose array is sized to combined_size.
BOOST_AUTO_TEST_CASE(self_resolve_mark_bounded_by_combined_size) {
std::vector<Monomial<8>> terms;
for (size_t i = 0; i < 6; ++i) {
terms.push_back(indices_to_bitset<8>({i, i + 8}));
}
detail::MPOperator<8> op = indexed_op(terms);
const size_t combined_size = 4; // rows 4 and 5 stand for terms this layer inserted after construction

MatchedEpochSet matched;
// Pre-grown past combined_size on purpose: an unguarded mark then lands in an observable slot rather
// than past the end of epoch_, where it would be silent undefined behaviour.
matched.begin_gate(op.size());

detail::LayerBuildEngine<8, RecordingSink> eng(op,
mpi::Comm{},
/*R_=*/1,
/*my_rank_=*/0,
matched,
combined_size,
RecordingSink{});
detail::query_push<8>(eng.queries_r[0], terms[1], 1);
detail::query_push<8>(eng.queries_r[0], terms[5], -1);
eng.src_idx_r[0] = {0, 2};

eng.resolve_self_queries(/*is_leader_pass=*/true);

// Both keys are in the store, so both resolve as hits and neither may be deferred as a miss.
BOOST_TEST(eng.deferred_self_misses.empty());
BOOST_TEST_REQUIRE(eng.sink.hits.size() == 2U);
BOOST_TEST(eng.sink.hits[0].second == 1U);
BOOST_TEST(eng.sink.hits[1].second == 5U);
BOOST_TEST(matched.is_marked(1));
BOOST_TEST(!matched.is_marked(4));
BOOST_TEST(!matched.is_marked(5));
}

BOOST_AUTO_TEST_CASE(cutoff_context_abs_coeff_for) {
const VecD coeffs{-3.0, 2.0, 0.0};

Expand Down
Loading