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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ if(BUILD_TESTING)
endif()
endmacro()

add_june_test(test_domain_communicator_detail "tests/test_domain_communicator_detail.cpp")
add_june_test(test_run_dir "tests/test_run_dir.cpp")
add_june_test(test_world_state "tests/test_world_state.cpp")
add_june_test(test_infection "tests/test_infection.cpp;src/loaders/disease_loader.cpp")
Expand Down
4 changes: 1 addition & 3 deletions include/parallel/domain_communicator.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,7 @@ class DomainCommunicator {
// if the record was skipped (person not owned, already infected, or no
// disease loaded).
std::optional<PendingInfection> applyOnePendingInfection(
PersonId pid, PersonId infector_id, double t, uint8_t v_type,
uint8_t enc_type_id, VenueId v_id, uint8_t infector_symptom_id,
uint8_t transmission_mode_index);
const PendingInfection& pending);

WorldState& world_;
const Config& config_;
Expand Down
43 changes: 43 additions & 0 deletions include/parallel/domain_communicator_detail.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#ifdef USE_MPI

#include <cstring>
#include <tuple>
#include <type_traits>

// Field-level pack/unpack used by both translation units of
Expand Down Expand Up @@ -30,6 +31,48 @@ inline const char* unpackField(const char* ptr, T& v) {
return ptr + sizeof(T);
}

// A record type's wire field list written once, deriving size, pack, and
// unpack from it. Only covers plain packField-able members; fields needing
// custom transcoding (e.g. an enum narrowed to a fixed-width wire type) or
// variable-length tails stay outside and are composed around pack()/unpack().
template <typename T, typename... Members>
class WireRecord {
public:
constexpr explicit WireRecord(Members T::*... members)
: members_(members...) {}

static constexpr int size() { return (static_cast<int>(sizeof(Members)) + ... + 0); }

char* pack(char* ptr, const T& obj) const {
return packMembers(ptr, obj, std::index_sequence_for<Members...>{});
}

const char* unpack(const char* ptr, T& obj) const {
return unpackMembers(ptr, obj, std::index_sequence_for<Members...>{});
}

private:
template <std::size_t... I>
char* packMembers(char* ptr, const T& obj, std::index_sequence<I...>) const {
((ptr = packField(ptr, obj.*std::get<I>(members_))), ...);
return ptr;
}

template <std::size_t... I>
const char* unpackMembers(const char* ptr, T& obj,
std::index_sequence<I...>) const {
((ptr = unpackField(ptr, obj.*std::get<I>(members_))), ...);
return ptr;
}

std::tuple<Members T::*...> members_;
};

template <typename T, typename... Members>
constexpr auto makeWireRecord(Members T::*... members) {
return WireRecord<T, Members...>(members...);
}

} // namespace june::domain_comm_detail

#endif // USE_MPI
59 changes: 36 additions & 23 deletions src/parallel/domain_communicator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

#include "parallel/domain_communicator.h"

#include <cstddef>
#include <cstring>
#include <iostream>
#include <type_traits>
#include <vector>

#include "parallel/domain_communicator_detail.h"
Expand All @@ -13,29 +15,49 @@

namespace {

using june::domain_comm_detail::makeWireRecord;
using june::domain_comm_detail::packField;
using june::domain_comm_detail::unpackField;

// Fixed-size header of the visitor wire format (everything before the
// integrated_infectiousness payload): 4+4+4+4+1+1+4+1+2+8 = 33 bytes.
// Fixed header of the visitor wire format (everything before the
// integrated_infectiousness payload); the variable-length per-mode payload
// is appended manually after this, outside WireRecord.
// Total wire size = VISITOR_WIRE_HEADER + num_modes * sizeof(double).
constexpr int VISITOR_WIRE_HEADER = 33;
constexpr auto kVisitorWire = makeWireRecord(
&june::Domain::VisitorData::person_id,
&june::Domain::VisitorData::home_rank, &june::Domain::VisitorData::venue_id,
&june::Domain::VisitorData::subset_idx,
&june::Domain::VisitorData::is_infected,
&june::Domain::VisitorData::is_infectious,
&june::Domain::VisitorData::immunity_level,
&june::Domain::VisitorData::encounter_type_id,
&june::Domain::VisitorData::symptom_id,
&june::Domain::VisitorData::time_in_stage);
constexpr int VISITOR_WIRE_HEADER = kVisitorWire.size();
// Tripwire: VisitorData's trailing integrated_infectiousness is a
// std::vector<double> packed manually as a count-known-elsewhere tail (not
// via WireRecord), and fields after it (newly_infected etc.) are pure
// return data never on the wire, so sizeof(VisitorData) isn't a useful
// proxy here. offsetof(integrated_infectiousness) instead marks where the
// fixed header covered by kVisitorWire ends - it moves if a field is
// added/removed/resized anywhere before the tail.
// offsetof is only standard-guaranteed for standard-layout types; guard that
// assumption explicitly so a future member (e.g. a std::set) that breaks it
// fails loudly here rather than degrading to a silent -Winvalid-offsetof.
static_assert(std::is_standard_layout_v<june::Domain::VisitorData>,
"VisitorData must stay standard-layout for the offsetof check "
"below to be well-defined");
static_assert(offsetof(june::Domain::VisitorData, integrated_infectiousness) ==
40,
"VisitorData's fixed-header region changed - check kVisitorWire "
"covers every field, then update this literal");
inline int visitorWireSize(int num_modes) {
return VISITOR_WIRE_HEADER + num_modes * static_cast<int>(sizeof(double));
}

char* packVisitor(char* ptr, const june::Domain::VisitorData& v,
int num_modes) {
ptr = packField(ptr, v.person_id);
ptr = packField(ptr, v.home_rank);
ptr = packField(ptr, v.venue_id);
ptr = packField(ptr, v.subset_idx);
ptr = packField(ptr, v.is_infected);
ptr = packField(ptr, v.is_infectious);
ptr = packField(ptr, v.immunity_level);
ptr = packField(ptr, v.encounter_type_id);
ptr = packField(ptr, v.symptom_id);
ptr = packField(ptr, v.time_in_stage);
ptr = kVisitorWire.pack(ptr, v);
// Variable-length per-mode payload. The vector must be exactly num_modes
// long; sender and receiver agree on this from disease->numModes(), which
// is loaded identically on every rank. Mismatches indicate a config bug
Expand All @@ -56,16 +78,7 @@ char* packVisitor(char* ptr, const june::Domain::VisitorData& v,

const char* unpackVisitor(const char* ptr, june::Domain::VisitorData& v,
int num_modes) {
ptr = unpackField(ptr, v.person_id);
ptr = unpackField(ptr, v.home_rank);
ptr = unpackField(ptr, v.venue_id);
ptr = unpackField(ptr, v.subset_idx);
ptr = unpackField(ptr, v.is_infected);
ptr = unpackField(ptr, v.is_infectious);
ptr = unpackField(ptr, v.immunity_level);
ptr = unpackField(ptr, v.encounter_type_id);
ptr = unpackField(ptr, v.symptom_id);
ptr = unpackField(ptr, v.time_in_stage);
ptr = kVisitorWire.unpack(ptr, v);
v.integrated_infectiousness.assign(num_modes, 0.0);
if (num_modes > 0) {
std::memcpy(v.integrated_infectiousness.data(), ptr,
Expand Down
Loading
Loading