diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bb217d..087c48a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,20 +8,13 @@ daq_setup_environment() find_package(Boost COMPONENTS unit_test_framework REQUIRED) -############################################################################## - daq_add_library( LINK_LIBRARIES ) -############################################################################## - daq_add_python_bindings(*.cpp ) -############################################################################## - daq_add_unit_test(Trgdataformats_test LINK_LIBRARIES) daq_add_unit_test(TriggerCandidateData_test LINK_LIBRARIES) - -############################################################################## +daq_add_unit_test(TriggerDataStructures_test LINK_LIBRARIES) daq_install() diff --git a/include/trgdataformats/TriggerActivityData.hpp b/include/trgdataformats/TriggerActivityData.hpp index 3befff1..bc419fa 100644 --- a/include/trgdataformats/TriggerActivityData.hpp +++ b/include/trgdataformats/TriggerActivityData.hpp @@ -1,6 +1,12 @@ /** * @file TriggerActivityData.hpp * + * This header defines the TriggerActivityData struct, which + * aggregates information about a found set of associate trigger + * primitives (algorithm used, channels and times involved, etc.). It + * does *not* include per-trigger-primitive information, which need to + * be associated with TriggerActivityData in a higher level object. + * * This is part of the DUNE DAQ Application Framework, copyright 2020. * Licensing/copyright details are in the COPYING file that you should have * received with this code. @@ -16,14 +22,14 @@ namespace dunedaq::trgdataformats { struct TriggerActivityData { - enum class Type + enum class Type : int { kUnknown = 0, kTPC = 1, kPDS = 2, }; - enum class Algorithm + enum class Algorithm : int { kUnknown = 0, kSupernova = 1, @@ -39,24 +45,32 @@ struct TriggerActivityData kSWIFT = 11 }; - // Update this version number if there are any changes to the in-memory representation of this class! - static constexpr version_t s_trigger_activity_version = 2; // NOLINT(build/unsigned) - - version_t version = s_trigger_activity_version; // NOLINT(build/unsigned) - timestamp_t time_start = INVALID_TIMESTAMP; - timestamp_t time_end = INVALID_TIMESTAMP; - timestamp_t time_peak = INVALID_TIMESTAMP; - timestamp_t time_activity = INVALID_TIMESTAMP; - channel_t channel_start = INVALID_CHANNEL; // NOLINT(build/unsigned) - channel_t channel_end = INVALID_CHANNEL; // NOLINT(build/unsigned) - channel_t channel_peak = INVALID_CHANNEL; // NOLINT(build/unsigned) + static constexpr version_t s_trigger_activity_version = 2; + static constexpr channel_t s_invalid_channel = std::numeric_limits::max(); + + version_t version = s_trigger_activity_version; + timestamp_t time_start = TypeDefaults::s_invalid_timestamp; + timestamp_t time_end = TypeDefaults::s_invalid_timestamp; + timestamp_t time_peak = TypeDefaults::s_invalid_timestamp; + timestamp_t time_activity = TypeDefaults::s_invalid_timestamp; + channel_t channel_start = s_invalid_channel; + channel_t channel_end = s_invalid_channel; + channel_t channel_peak = s_invalid_channel; uint64_t adc_integral = 0; // NOLINT(build/unsigned) uint16_t adc_peak = 0; // NOLINT(build/unsigned) - detid_t detid = INVALID_DETID; // NOLINT(build/unsigned) - Type type = Type::kUnknown; // NOLINT(build/unsigned) - Algorithm algorithm = Algorithm::kUnknown; // NOLINT(build/unsigned) + detid_t detid = TypeDefaults::s_invalid_detid; + Type type = Type::kUnknown; + Algorithm algorithm = Algorithm::kUnknown; }; - + } // namespace dunedaq::trgdataformats +// This static_assert is meant to alert the developer to bump the +// version if variables are added or removed +static_assert( + dunedaq::trgdataformats::TriggerActivityData::s_trigger_activity_version == 2 && + sizeof(dunedaq::trgdataformats::TriggerActivityData) == 80 + ); + + #endif // TRGDATAFORMATS_INCLUDE_TRGDATAFORMATS_TRIGGERACTIVITYDATA_HPP_ diff --git a/include/trgdataformats/TriggerCandidateData.hpp b/include/trgdataformats/TriggerCandidateData.hpp index a129fd9..b09c831 100644 --- a/include/trgdataformats/TriggerCandidateData.hpp +++ b/include/trgdataformats/TriggerCandidateData.hpp @@ -1,5 +1,12 @@ /** - * @file TriggerCandidate.hpp + * @file TriggerCandidateData.hpp + * + * This header defines the TriggerCandidateData struct, which + * aggregates information about a found set of associated trigger + * activities (general type of candidate, algorithm used, times + * involved, etc.). It does *not* include per-trigger-activity + * information, which need to be associated with TriggerCandidateData + * in a higher level object. * * This is part of the DUNE DAQ Application Framework, copyright 2020. * Licensing/copyright details are in the COPYING file that you should have @@ -11,6 +18,7 @@ #include "trgdataformats/Types.hpp" +#include #include #include #include @@ -19,7 +27,11 @@ namespace dunedaq::trgdataformats { struct TriggerCandidateData { - enum class Type + // If you add an enum to TriggerCandidateData::Type, declare it + // second-to-last (just above kFinalEnum) and ensure its value is +1 + // greater than the enum above it + + enum class Type : int { kUnknown = 0, kTiming = 1, @@ -60,9 +72,10 @@ struct TriggerCandidateData kDTSPulser = 36, kDTSCosmic = 37, kSSPLEDCalibration = 38, + kFinalEnum }; - enum class Algorithm + enum class Algorithm : int { kUnknown = 0, kSupernova = 1, @@ -79,91 +92,36 @@ struct TriggerCandidateData kChannelAdjacency = 12, }; - // Update this version number if there are any changes to the in-memory representation of this class! - static constexpr version_t s_trigger_candidate_version = 3; // NOLINT(build/unsigned) + static constexpr version_t s_trigger_candidate_version = 3; - version_t version = s_trigger_candidate_version; // NOLINT(build/unsigned) - timestamp_t time_start = INVALID_TIMESTAMP; - timestamp_t time_end = INVALID_TIMESTAMP; - timestamp_t time_candidate = INVALID_TIMESTAMP; + version_t version = s_trigger_candidate_version; + timestamp_t time_start = TypeDefaults::s_invalid_timestamp; + timestamp_t time_end = TypeDefaults::s_invalid_timestamp; + timestamp_t time_candidate = TypeDefaults::s_invalid_timestamp; // TODO P. Rodrigues 2021-01-06: This was originally a // std::vector but that messes up the overlay scheme, so // I've changed it for now to be just a detid_t. Need to work out // what to do longer term - detid_t detid; // NOLINT(build/unsigned) + detid_t detid = TypeDefaults::s_invalid_detid; Type type = Type::kUnknown; - Algorithm algorithm = Algorithm::kUnknown; // NOLINT(build/unsigned) + Algorithm algorithm = Algorithm::kUnknown; }; -// This map needs to be updated for each new TC type, as this is used when configuring Trigger Bitwords, affecting -// trigger logic in trigger::MLT -inline std::map -get_trigger_candidate_type_names() -{ - return { - { TriggerCandidateData::Type::kUnknown, "kUnknown" }, - { TriggerCandidateData::Type::kTiming, "kTiming" }, - { TriggerCandidateData::Type::kTPCLowE, "kTPCLowE" }, - { TriggerCandidateData::Type::kSupernova, "kSupernova" }, - { TriggerCandidateData::Type::kRandom, "kRandom" }, - { TriggerCandidateData::Type::kPrescale, "kPrescale" }, - { TriggerCandidateData::Type::kADCSimpleWindow, "kADCSimpleWindow" }, - { TriggerCandidateData::Type::kHorizontalMuon, "kHorizontalMuon" }, - { TriggerCandidateData::Type::kMichelElectron, "kMichelElectron" }, - { TriggerCandidateData::Type::kPlaneCoincidence, "kPlaneCoincidence" }, - { TriggerCandidateData::Type::kDBSCAN, "kDBSCAN" }, - { TriggerCandidateData::Type::kChannelDistance, "kChannelDistance" }, - { TriggerCandidateData::Type::kBundle, "kBundle" }, - { TriggerCandidateData::Type::kCTBFakeTrigger, "kCTBFakeTrigger" }, - { TriggerCandidateData::Type::kCTBBeam, "kCTBBeam" }, - { TriggerCandidateData::Type::kCTBBeamChkvHL, "kCTBBeamChkvHL" }, - { TriggerCandidateData::Type::kCTBCustomD, "kCTBCustomD" }, - { TriggerCandidateData::Type::kCTBCustomE, "kCTBCustomE" }, - { TriggerCandidateData::Type::kCTBCustomF, "kCTBCustomF" }, - { TriggerCandidateData::Type::kCTBCustomG, "kCTBCustomG" }, - { TriggerCandidateData::Type::kCTBBeamChkvHLx, "kCTBBeamChkvHLx" }, - { TriggerCandidateData::Type::kCTBBeamChkvHxL, "kCTBBeamChkvHxL" }, - { TriggerCandidateData::Type::kCTBBeamChkvHxLx, "kCTBBeamChkvHxLx" }, - { TriggerCandidateData::Type::kNeutronSourceCalib, "kNeutronSourceCalib" }, - { TriggerCandidateData::Type::kChannelAdjacency, "kChannelAdjacency" }, - { TriggerCandidateData::Type::kCIBFakeTrigger, "kCIBFakeTrigger" }, - { TriggerCandidateData::Type::kCIBLaserTriggerP1, "kCIBLaserTriggerP1" }, - { TriggerCandidateData::Type::kCIBLaserTriggerP2, "kCIBLaserTriggerP2" }, - { TriggerCandidateData::Type::kCIBLaserTriggerP3, "kCIBLaserTriggerP3" }, - { TriggerCandidateData::Type::kCTBOffSpillSnapshot, "kCTBOffSpillSnapshot" }, - { TriggerCandidateData::Type::kCTBOffSpillCosmicJura, "kCTBOffSpillCosmicJura" }, - { TriggerCandidateData::Type::kCTBOffSpillCRTCosmic, "kCTBOffSpillCRTCosmic" }, - { TriggerCandidateData::Type::kCTBBeamSpillStart, "kCTBBeamSpillStart" }, - { TriggerCandidateData::Type::kCTBBeamSpillSnapshot, "kCTBBeamSpillSnapshot" }, - { TriggerCandidateData::Type::kCTBCustomC, "kCTBCustomC" }, - { TriggerCandidateData::Type::kCTBCustomPulseTrain, "kCTBCustomPulseTrain" }, - { TriggerCandidateData::Type::kDTSPulser, "kDTSPulser" }, - { TriggerCandidateData::Type::kDTSCosmic, "kDTSCosmic" }, - { TriggerCandidateData::Type::kSSPLEDCalibration, "kSSPLEDCalibration" }, - }; -} + inline TriggerCandidateData::Type + string_to_trigger_candidate_type(const std::string& name); -inline int -string_to_trigger_candidate_type(const std::string& name) -{ - for (auto& it : get_trigger_candidate_type_names()) { - if (it.second == name) - return static_cast(it.first); - } - return static_cast(TriggerCandidateData::Type::kUnknown); -} - -inline std::string -trigger_candidate_type_to_string(const TriggerCandidateData::Type& type) -{ - try { - return get_trigger_candidate_type_names().at(type); - } - catch(std::exception &e) { - } - return "kUnknown"; -} + inline std::string + trigger_candidate_type_to_string(const TriggerCandidateData::Type type); } // namespace dunedaq::trgdataformats +#include "detail/TriggerCandidateData.hxx" + +// This static_assert is meant to alert the developer to bump the +// version if variables are added or removed in TriggerCandidateData +static_assert( + dunedaq::trgdataformats::TriggerCandidateData::s_trigger_candidate_version == 3 && + sizeof(dunedaq::trgdataformats::TriggerCandidateData) == 48 + ); + #endif // TRGDATAFORMATS_INCLUDE_TRGDATAFORMATS_TRIGGERCANDIDATEDATA_HPP_ diff --git a/include/trgdataformats/TriggerObjectOverlay.hpp b/include/trgdataformats/TriggerObjectOverlay.hpp index 0f909fb..88b2789 100644 --- a/include/trgdataformats/TriggerObjectOverlay.hpp +++ b/include/trgdataformats/TriggerObjectOverlay.hpp @@ -1,6 +1,16 @@ /** * @file TriggerObjectOverlay.hpp * + * This header defines the "TriggerActivity" and "TriggerCandidate" + * structs, which are instantiations of the TriggerObjectOverlay + * template. Continguous in memory, a TriggerActivity instance is an + * instance of TriggerActivityData followed by n_inputs + * TriggerPrimitive instances. Likewise, a TriggerCandidate instance + * is an instance of TriggerCandidateData followed by n_inputs + * TriggerActivityData (not to be confused with TriggerActivity) + * instances. + * + * * This is part of the DUNE DAQ Application Framework, copyright 2020. * Licensing/copyright details are in the COPYING file that you should have * received with this code. @@ -13,6 +23,9 @@ #include "trgdataformats/TriggerCandidateData.hpp" #include "trgdataformats/TriggerPrimitive.hpp" +#include +#include + namespace dunedaq::trgdataformats { template @@ -21,11 +34,17 @@ struct TriggerObjectOverlay using data_t = DataType; using input_t = InputType; data_t data; - uint64_t n_inputs; + uint64_t n_inputs; // NOLINT(build/unsigned) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" input_t inputs[]; // Non-standard flexible array member, but alternatives are worse #pragma GCC diagnostic pop + + void set_inputs(std::span in) + { + n_inputs = static_cast(in.size()); + std::copy(in.begin(), in.end(), inputs); + } }; using TriggerActivity = TriggerObjectOverlay; diff --git a/include/trgdataformats/TriggerPrimitive.hpp b/include/trgdataformats/TriggerPrimitive.hpp index af28c29..1574b8c 100644 --- a/include/trgdataformats/TriggerPrimitive.hpp +++ b/include/trgdataformats/TriggerPrimitive.hpp @@ -1,6 +1,9 @@ /** * @file TriggerPrimitive.hpp * + * This header defines an overlay struct for trigger primitives, a + * single energy deposition on a TPC or PDS channel. + * * This is part of the DUNE DAQ Application Framework, copyright 2020. * Licensing/copyright details are in the COPYING file that you should have * received with this code. @@ -18,6 +21,8 @@ #include #include +// NOLINTBEGIN(build/unsigned) + namespace dunedaq::trgdataformats { /** @@ -25,7 +30,12 @@ namespace dunedaq::trgdataformats { */ struct TriggerPrimitive { - static constexpr uint8_t s_trigger_primitive_version = 2; + static constexpr version_t s_trigger_primitive_version = 2; + static constexpr std::size_t s_expected_bytes = 24; + + static constexpr uint16_t s_invalid_samples_over_threshold = std::numeric_limits::max(); + static constexpr uint16_t s_invalid_samples_to_peak = std::numeric_limits::max(); + static constexpr channel_t s_invalid_tp_channel = 0xFFFFFF; // TP channel limit is at 24 bytes // Metadata. uint64_t version : 8; @@ -45,16 +55,55 @@ struct TriggerPrimitive TriggerPrimitive() : version(s_trigger_primitive_version) , flag(0) - , detid(INVALID_DETID) - , channel(INVALID_TP_CHANNEL) - , samples_over_threshold(INVALID_SAMPLES_OVER_THRESHOLD) - , time_start(INVALID_TIMESTAMP) - , samples_to_peak(INVALID_SAMPLES_TO_PEAK) + , detid(TypeDefaults::s_invalid_detid) + , channel(s_invalid_tp_channel) + , samples_over_threshold(s_invalid_samples_over_threshold) + , time_start(TypeDefaults::s_invalid_timestamp) + , samples_to_peak(s_invalid_samples_to_peak) , adc_integral(0) , adc_peak(0) {} + + // Despite this being a struct, the getter and setter for timestamp + // brings the struct closer into compliance with the standard + // overlay + + timestamp_t get_timestamp() const + { + return time_start; + } + + void set_timestamp(timestamp_t ts) + { + time_start = ts; + } + + bool operator<(const TriggerPrimitive& other) const { + return std::tie(time_start, channel) < std::tie(other.time_start, other.channel); + } }; +// Basic checks that the bits are arranged as we hope they're arranged + +// TODO John Freeman (jcfree@fnal.gov), Aug-1-2026 + +// In the next two months, take these static_asserts and write up a +// concept which encompasses them - something like +// SafeBitLayoutConcept - and figure out the right package to put it +// in, so that both this and the far detector-specific overlay classes +// can use them + +static_assert(sizeof(TriggerPrimitive) == TriggerPrimitive::s_expected_bytes, + "The actual size of the TriggerPrimitive isn't the expected size; the compiler is likely inserting padding"); + +static_assert(std::endian::native == std::endian::little, + "The TriggerPrimitive bitfield layout assumes little-endian architecture"); + +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); + } // namespace dunedaq::trgdataformats +// NOLINTEND(build/unsigned) + #endif // TRGDATAFORMATS_INCLUDE_TRGDATAFORMATS_TRIGGERPRIMITIVE_HPP_ diff --git a/include/trgdataformats/Types.hpp b/include/trgdataformats/Types.hpp index c265998..c81d46d 100644 --- a/include/trgdataformats/Types.hpp +++ b/include/trgdataformats/Types.hpp @@ -1,6 +1,11 @@ /** * @file Types.hpp * + * This header defines the types used in this package for various + * kinds of data (timestamps, channels, etc.). It also contains some + * predefined constants representing invalid values as well as a "read + * out the whole detector" value. + * * This is part of the DUNE DAQ Application Framework, copyright 2020. * Licensing/copyright details are in the COPYING file that you should have * received with this code. @@ -12,20 +17,26 @@ #include #include -namespace dunedaq::trgdataformats { +// NOLINTBEGIN(build/unsigned) -// A data timestamp in timing system clock ticks (50 MHz for -// ProtoDUNE-I, 62.5 MHz for ProtoDUNE-II and DUNE FD). If/when we -// depend on daqdataformats, we can get these from there -using timestamp_t = uint64_t; // NOLINT +namespace dunedaq::trgdataformats { -constexpr timestamp_t INVALID_TIMESTAMP = std::numeric_limits::max(); +// TODO John Freeman (jcfree@fnal.gov), Jul-16-2026 -using timestamp_diff_t = int64_t; +// In the next month, determine if we can get enough buy in to add +// dependence on the relevant headers from daqdataformats, which will +// address Phil Rodrigues's concerns from five years ago. There's some +// hope for this to the extent Tom Junk emailed me in the spring +// saying this wouldn't be a problem. -constexpr uint16_t INVALID_SAMPLES_OVER_THRESHOLD = std::numeric_limits::max(); -constexpr uint16_t INVALID_SAMPLES_TO_PEAK = std::numeric_limits::max(); + +// timestamp_t represents a data timestamp in timing system clock +// ticks (50 MHz for ProtoDUNE-I, 62.5 MHz for ProtoDUNE-II and DUNE +// FD). If/when we depend on daqdataformats, we can get these from +// there +using timestamp_t = uint64_t; +using timestamp_diff_t = int64_t; // A logical region of the detector from which DS objects may be // formed, eg an APA or a module @@ -33,36 +44,35 @@ constexpr uint16_t INVALID_SAMPLES_TO_PEAK = std::numeric_limits::max( // TODO P. Rodrigues 2021-06-01: it would be nice to have this be just // daqdataformats::GeoID, if/when we can depend on the daqdataformats // package + using detid_t = uint8_t; -constexpr detid_t INVALID_DETID = std::numeric_limits::max(); - -// A detid representing a request to read out the whole detector -constexpr detid_t WHOLE_DETECTOR = INVALID_DETID - 1; - -// A trigger number // TODO P. Rodrigues 2021-06-14: it would be nice to have this be just // daqdataformats::trigger_number_t, if/when we can depend on the daqdataformats // package using trigger_number_t = uint64_t; -constexpr trigger_number_t INVALID_TRIGGER_NUMBER = std::numeric_limits::max(); -// A channel number using channel_t = uint32_t; - -constexpr channel_t INVALID_CHANNEL = std::numeric_limits::max(); - -// TP channel limit is at 24 b. -constexpr uint32_t INVALID_TP_CHANNEL = 0xFFFFFF; - using channel_diff_t = int32_t; -// A version number of an object using version_t = uint8_t; -constexpr version_t INVALID_VERSION = std::numeric_limits::max(); +struct TypeDefaults +{ + static constexpr timestamp_t s_invalid_timestamp { std::numeric_limits::max() }; + static constexpr detid_t s_invalid_detid { std::numeric_limits::max() }; + static constexpr trigger_number_t s_invalid_trigger_number { std::numeric_limits::max() }; +}; +// A detid representing a request to read out the whole detector + constexpr detid_t g_whole_detector {std::numeric_limits::max() - 1}; + + } // namespace dunedaq::trgdataformats +static_assert(dunedaq::trgdataformats::g_whole_detector != dunedaq::trgdataformats::TypeDefaults::s_invalid_detid); + +// NOLINTEND(build/unsigned) + #endif // TRGDATAFORMATS_INCLUDE_TRGDATAFORMATS_TYPES_HPP_ diff --git a/include/trgdataformats/detail/TriggerCandidateData.hxx b/include/trgdataformats/detail/TriggerCandidateData.hxx new file mode 100644 index 0000000..fdfa581 --- /dev/null +++ b/include/trgdataformats/detail/TriggerCandidateData.hxx @@ -0,0 +1,90 @@ + +namespace dunedaq::trgdataformats { + + // tcinfo_t is a constexpr-declarable container of pairs, each pair + // connecting a TriggerCandidateData::Type with its name + using tcinfo_t = std::array, + static_cast(TriggerCandidateData::Type::kFinalEnum)>; + + inline const tcinfo_t& + get_trigger_candidate_type_names() + { + // This container needs to be updated for each new TC type, as this is used + // when configuring Trigger Bitwords, affecting trigger logic in + // trigger::MLT + + static constexpr tcinfo_t names { { + { TriggerCandidateData::Type::kUnknown, "kUnknown" }, + { TriggerCandidateData::Type::kTiming, "kTiming" }, + { TriggerCandidateData::Type::kTPCLowE, "kTPCLowE" }, + { TriggerCandidateData::Type::kSupernova, "kSupernova" }, + { TriggerCandidateData::Type::kRandom, "kRandom" }, + { TriggerCandidateData::Type::kPrescale, "kPrescale" }, + { TriggerCandidateData::Type::kADCSimpleWindow, "kADCSimpleWindow" }, + { TriggerCandidateData::Type::kHorizontalMuon, "kHorizontalMuon" }, + { TriggerCandidateData::Type::kMichelElectron, "kMichelElectron" }, + { TriggerCandidateData::Type::kPlaneCoincidence, "kPlaneCoincidence" }, + { TriggerCandidateData::Type::kDBSCAN, "kDBSCAN" }, + { TriggerCandidateData::Type::kChannelDistance, "kChannelDistance" }, + { TriggerCandidateData::Type::kBundle, "kBundle" }, + { TriggerCandidateData::Type::kCTBFakeTrigger, "kCTBFakeTrigger" }, + { TriggerCandidateData::Type::kCTBBeam, "kCTBBeam" }, + { TriggerCandidateData::Type::kCTBBeamChkvHL, "kCTBBeamChkvHL" }, + { TriggerCandidateData::Type::kCTBCustomD, "kCTBCustomD" }, + { TriggerCandidateData::Type::kCTBCustomE, "kCTBCustomE" }, + { TriggerCandidateData::Type::kCTBCustomF, "kCTBCustomF" }, + { TriggerCandidateData::Type::kCTBCustomG, "kCTBCustomG" }, + { TriggerCandidateData::Type::kCTBBeamChkvHLx, "kCTBBeamChkvHLx" }, + { TriggerCandidateData::Type::kCTBBeamChkvHxL, "kCTBBeamChkvHxL" }, + { TriggerCandidateData::Type::kCTBBeamChkvHxLx, "kCTBBeamChkvHxLx" }, + { TriggerCandidateData::Type::kNeutronSourceCalib, "kNeutronSourceCalib" }, + { TriggerCandidateData::Type::kChannelAdjacency, "kChannelAdjacency" }, + { TriggerCandidateData::Type::kCIBFakeTrigger, "kCIBFakeTrigger" }, + { TriggerCandidateData::Type::kCIBLaserTriggerP1, "kCIBLaserTriggerP1" }, + { TriggerCandidateData::Type::kCIBLaserTriggerP2, "kCIBLaserTriggerP2" }, + { TriggerCandidateData::Type::kCIBLaserTriggerP3, "kCIBLaserTriggerP3" }, + { TriggerCandidateData::Type::kCTBOffSpillSnapshot, "kCTBOffSpillSnapshot" }, + { TriggerCandidateData::Type::kCTBOffSpillCosmicJura, "kCTBOffSpillCosmicJura" }, + { TriggerCandidateData::Type::kCTBOffSpillCRTCosmic, "kCTBOffSpillCRTCosmic" }, + { TriggerCandidateData::Type::kCTBBeamSpillStart, "kCTBBeamSpillStart" }, + { TriggerCandidateData::Type::kCTBBeamSpillSnapshot, "kCTBBeamSpillSnapshot" }, + { TriggerCandidateData::Type::kCTBCustomC, "kCTBCustomC" }, + { TriggerCandidateData::Type::kCTBCustomPulseTrain, "kCTBCustomPulseTrain" }, + { TriggerCandidateData::Type::kDTSPulser, "kDTSPulser" }, + { TriggerCandidateData::Type::kDTSCosmic, "kDTSCosmic" }, + { TriggerCandidateData::Type::kSSPLEDCalibration, "kSSPLEDCalibration" } + } + }; + + + // Check to ensure that if a new TriggerCandidateData::Type is + // created, this function is updated accordingly + + // The string in a default-initialized {type, string} pair will + // boolean-equal "" + static_assert(names[names.size() - 1].second != ""); + + return names; + } + + inline TriggerCandidateData::Type + string_to_trigger_candidate_type(const std::string& name) { + for (const auto& [type, type_name] : get_trigger_candidate_type_names()) { + if (type_name == name) + return type; + } + return TriggerCandidateData::Type::kUnknown; + } + + inline std::string + trigger_candidate_type_to_string(const TriggerCandidateData::Type type) { + try { + const auto& [dummy, type_name] { get_trigger_candidate_type_names().at(static_cast(type)) }; + return std::string(type_name); + + } catch (std::exception &e) { + return "kUnknown"; + } + } + +} // namespace dunedaq::trgdataformats diff --git a/pybindsrc/module.cpp b/pybindsrc/module.cpp index 8f6f021..947650f 100644 --- a/pybindsrc/module.cpp +++ b/pybindsrc/module.cpp @@ -20,6 +20,7 @@ PYBIND11_MODULE(_daq_trgdataformats_py, m) m.doc() = "C++ implementation of the trgdataformats modules"; + register_types(m); register_trigger_primitive(m); // register_trigger_bitwords(m); register_trigger_activity(m); diff --git a/pybindsrc/registrators.hpp b/pybindsrc/registrators.hpp index c2cfc41..d03a53a 100644 --- a/pybindsrc/registrators.hpp +++ b/pybindsrc/registrators.hpp @@ -16,6 +16,7 @@ namespace dunedaq::trgdataformats::python { + void register_types(pybind11::module &); void register_trigger_primitive(pybind11::module &); // void register_trigger_bitwords(pybind11::module &); void register_trigger_activity(pybind11::module &); diff --git a/pybindsrc/trigger_activity.cpp b/pybindsrc/trigger_activity.cpp index 5275844..0ceab0f 100644 --- a/pybindsrc/trigger_activity.cpp +++ b/pybindsrc/trigger_activity.cpp @@ -14,9 +14,9 @@ namespace py = pybind11; -namespace dunedaq { -namespace trgdataformats { -namespace python { +// NOLINTBEGIN(build/unsigned) + +namespace dunedaq::trgdataformats::python { /* Doesn't work @@ -54,14 +54,16 @@ register_trigger_activity(py::module& m) auto tp = *static_cast(info.ptr); return tp; })) + .def_property_readonly_static("s_trigger_activity_version", [](py::object /*self*/) { return TriggerActivityData::s_trigger_activity_version; }) + .def_property_readonly_static("s_invalid_channel", [](py::object /*self*/) { return TriggerActivityData::s_invalid_channel; }) .def_property_readonly("version", [](TriggerActivityData& self) -> uint16_t {return self.version;}) .def_property_readonly("time_start", [](TriggerActivityData& self) -> uint64_t {return self.time_start;}) .def_property_readonly("time_end", [](TriggerActivityData& self) -> uint64_t {return self.time_end;}) .def_property_readonly("time_peak", [](TriggerActivityData& self) -> uint64_t {return self.time_peak;}) .def_property_readonly("time_activity", [](TriggerActivityData& self) -> uint64_t {return self.time_activity;}) - .def_property_readonly("channel_start", [](TriggerActivityData& self) -> int32_t {return self.channel_start;}) - .def_property_readonly("channel_end", [](TriggerActivityData& self) -> int32_t {return self.channel_end;}) - .def_property_readonly("channel_peak", [](TriggerActivityData& self) -> int32_t {return self.channel_peak;}) + .def_property_readonly("channel_start", [](TriggerActivityData& self) -> channel_t {return self.channel_start;}) + .def_property_readonly("channel_end", [](TriggerActivityData& self) -> channel_t {return self.channel_end;}) + .def_property_readonly("channel_peak", [](TriggerActivityData& self) -> channel_t {return self.channel_peak;}) .def_property_readonly("adc_integral", [](TriggerActivityData& self) -> uint32_t {return self.adc_integral;}) .def_property_readonly("adc_peak", [](TriggerActivityData& self) -> uint32_t {return self.adc_peak;}) .def_property_readonly("detid", [](TriggerActivityData& self) -> uint16_t {return self.detid;}) @@ -136,6 +138,10 @@ register_trigger_activity(py::module& m) ; } -} // namespace python -} // namespace trgdataformats -} // namespace dunedaq +} // namespace dunedaq::trgdataformats::python + +static_assert( + dunedaq::trgdataformats::TriggerActivityData::s_trigger_activity_version == 2, + "Version of TriggerActivityData appears to have changed; as a developer please update the Python bindings in this file before updating this static_assert"); + +// NOLINTEND(build/unsigned) diff --git a/pybindsrc/trigger_candidate.cpp b/pybindsrc/trigger_candidate.cpp index 7841e25..cabae41 100644 --- a/pybindsrc/trigger_candidate.cpp +++ b/pybindsrc/trigger_candidate.cpp @@ -14,6 +14,8 @@ namespace py = pybind11; +// NOLINTBEGIN(build/unsigned) + namespace dunedaq { namespace trgdataformats { namespace python { @@ -56,6 +58,7 @@ register_trigger_candidate(py::module& m) auto tp = *static_cast(info.ptr); return tp; })) + .def_property_readonly_static("s_trigger_candidate_version", [](py::object /*self*/) { return TriggerCandidateData::s_trigger_candidate_version; }) .def_property_readonly("version", [](TriggerCandidateData& self) -> uint16_t {return self.version;}) .def_property_readonly("time_start", [](TriggerCandidateData& self) -> uint64_t {return self.time_start;}) .def_property_readonly("time_end", [](TriggerCandidateData& self) -> uint64_t {return self.time_end;}) @@ -169,3 +172,9 @@ register_trigger_candidate(py::module& m) } // namespace python } // namespace trgdataformats } // namespace dunedaq + +static_assert( + dunedaq::trgdataformats::TriggerCandidateData::s_trigger_candidate_version == 3, + "Version of TriggerCandidateData appears to have changed; as a developer please update the Python bindings in this file before updating this static_assert"); + +// NOLINTEND(build/unsigned) diff --git a/pybindsrc/trigger_primitive.cpp b/pybindsrc/trigger_primitive.cpp index b147560..26752b9 100644 --- a/pybindsrc/trigger_primitive.cpp +++ b/pybindsrc/trigger_primitive.cpp @@ -11,6 +11,8 @@ #include #include +// NOLINTBEGIN(build/unsigned) + namespace py = pybind11; namespace dunedaq::trgdataformats::python { @@ -28,18 +30,31 @@ register_trigger_primitive(py::module& m) return tp; } )) .def_property_readonly_static("s_trigger_primitive_version", [](py::object /*self*/) {return TriggerPrimitive::s_trigger_primitive_version;}) + .def_property_readonly_static("s_invalid_samples_over_threshold", [](py::object /*self*/) {return TriggerPrimitive::s_invalid_samples_over_threshold;}) + .def_property_readonly_static("s_invalid_samples_to_peak", [](py::object /*self*/) {return TriggerPrimitive::s_invalid_samples_to_peak;}) + .def_property_readonly_static("s_invalid_tp_channel", [](py::object /*self*/) {return TriggerPrimitive::s_invalid_tp_channel;}) + .def_property_readonly_static("s_expected_bytes", [](py::object /*self*/) {return TriggerPrimitive::s_expected_bytes;}) .def_property_readonly("version", [](TriggerPrimitive& self) -> uint8_t {return self.version;}) + .def_property_readonly("flag", [](TriggerPrimitive& self) -> uint8_t {return self.flag;}) + .def_property_readonly("detid", [](TriggerPrimitive& self) -> uint8_t {return self.detid;}) + .def_property_readonly("channel", [](TriggerPrimitive& self) -> uint32_t {return uint32_t(self.channel);}) + .def_property_readonly("samples_over_threshold", [](TriggerPrimitive& self) -> uint16_t {return self.samples_over_threshold;}) .def_property_readonly("time_start", [](TriggerPrimitive& self) -> uint64_t {return self.time_start;}) .def_property_readonly("samples_to_peak", [](TriggerPrimitive& self) -> uint16_t {return self.samples_to_peak;}) - .def_property_readonly("samples_over_threshold", [](TriggerPrimitive& self) -> uint16_t {return self.samples_over_threshold;}) - .def_property_readonly("channel", [](TriggerPrimitive& self) -> uint32_t {return uint32_t(self.channel);}) .def_property_readonly("adc_integral", [](TriggerPrimitive& self) -> uint32_t {return self.adc_integral;}) .def_property_readonly("adc_peak", [](TriggerPrimitive& self) -> uint16_t {return self.adc_peak;}) - .def_property_readonly("detid", [](TriggerPrimitive& self) -> uint8_t {return self.detid;}) - .def_property_readonly("flag", [](TriggerPrimitive& self) -> uint8_t {return self.flag;}) + .def("get_timestamp", &TriggerPrimitive::get_timestamp) + .def("set_timestamp", &TriggerPrimitive::set_timestamp) + .def("__lt__", &TriggerPrimitive::operator<) .def_static("sizeof", [](){ return sizeof(TriggerPrimitive); }) ; } } // namespace dunedaq::trgdataformats::python + +static_assert( + dunedaq::trgdataformats::TriggerPrimitive::s_trigger_primitive_version == 2, + "Version of TriggerPrimitive appears to have changed; as a developer please update the Python bindings in this file before updating this static_assert"); + +// NOLINTEND(build/unsigned) diff --git a/pybindsrc/types.cpp b/pybindsrc/types.cpp new file mode 100644 index 0000000..0b71c98 --- /dev/null +++ b/pybindsrc/types.cpp @@ -0,0 +1,28 @@ +/** + * @file types.cpp Python bindings for common trgdataformats scalar aliases/constants + * + * This is part of the DUNE DAQ Software Suite, copyright 2020. + * Licensing/copyright details are in the COPYING file that you should have + * received with this code. + */ + +#include "trgdataformats/Types.hpp" + +#include + +namespace py = pybind11; + +namespace dunedaq::trgdataformats::python { + +void +register_types(py::module& m) +{ + py::class_(m, "TypeDefaults") + .def_property_readonly_static("s_invalid_timestamp", [](py::object /*self*/) { return TypeDefaults::s_invalid_timestamp; }) + .def_property_readonly_static("s_invalid_detid", [](py::object /*self*/) { return TypeDefaults::s_invalid_detid; }) + .def_property_readonly_static("s_invalid_trigger_number", [](py::object /*self*/) { return TypeDefaults::s_invalid_trigger_number; }); + + m.attr("g_whole_detector") = py::int_(g_whole_detector); +} + +} // namespace dunedaq::trgdataformats::python diff --git a/scripts/TriggerActivity_binding_test.py b/scripts/TriggerActivity_binding_test.py new file mode 100755 index 0000000..517ced4 --- /dev/null +++ b/scripts/TriggerActivity_binding_test.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 + +from trgdataformats import TriggerActivity, TriggerActivityData + + +def test_trigger_activity_data_defaults() -> int: + ta = TriggerActivityData() + + if ta is None: + print("FAIL: TriggerActivityData default construction returned None") + return 1 + + size = TriggerActivityData.sizeof() + if size != 80: + print(f"FAIL: TriggerActivityData.sizeof() returned {size}, expected 80") + return 1 + + if TriggerActivityData.s_trigger_activity_version != 2: + print( + "FAIL: TriggerActivityData.s_trigger_activity_version expected 2, " + f"got {TriggerActivityData.s_trigger_activity_version}" + ) + return 1 + + if TriggerActivityData.s_invalid_channel != (2**32 - 1): + print( + "FAIL: TriggerActivityData.s_invalid_channel expected 2**32-1, " + f"got {TriggerActivityData.s_invalid_channel}" + ) + return 1 + + if ta.version != 2: + print(f"FAIL: TriggerActivityData.version expected 2, got {ta.version}") + return 1 + + if ta.version != TriggerActivityData.s_trigger_activity_version: + print( + "FAIL: TriggerActivityData.version should match s_trigger_activity_version, " + f"got {ta.version} vs {TriggerActivityData.s_trigger_activity_version}" + ) + return 1 + + if ta.time_start != (2**64 - 1): + print(f"FAIL: TriggerActivityData.time_start expected 2**64-1, got {ta.time_start}") + return 1 + + if ta.time_end != (2**64 - 1): + print(f"FAIL: TriggerActivityData.time_end expected 2**64-1, got {ta.time_end}") + return 1 + + if ta.time_peak != (2**64 - 1): + print(f"FAIL: TriggerActivityData.time_peak expected 2**64-1, got {ta.time_peak}") + return 1 + + if ta.time_activity != (2**64 - 1): + print( + "FAIL: TriggerActivityData.time_activity expected 2**64-1, " + f"got {ta.time_activity}" + ) + return 1 + + if ta.channel_start != TriggerActivityData.s_invalid_channel: + print( + "FAIL: TriggerActivityData.channel_start expected invalid channel, " + f"got {ta.channel_start}" + ) + return 1 + + if ta.channel_end != TriggerActivityData.s_invalid_channel: + print( + "FAIL: TriggerActivityData.channel_end expected invalid channel, " + f"got {ta.channel_end}" + ) + return 1 + + if ta.channel_peak != TriggerActivityData.s_invalid_channel: + print( + "FAIL: TriggerActivityData.channel_peak expected invalid channel, " + f"got {ta.channel_peak}" + ) + return 1 + + if ta.adc_integral != 0: + print(f"FAIL: TriggerActivityData.adc_integral expected 0, got {ta.adc_integral}") + return 1 + + if ta.adc_peak != 0: + print(f"FAIL: TriggerActivityData.adc_peak expected 0, got {ta.adc_peak}") + return 1 + + if ta.detid != 2**8 - 1: + print(f"FAIL: TriggerActivityData.detid expected 2**8 - 1, got {ta.detid}") + return 1 + + if ta.type != TriggerActivityData.Type.kUnknown: + print(f"FAIL: TriggerActivityData.type expected kUnknown, got {ta.type}") + return 1 + + if ta.algorithm != TriggerActivityData.Algorithm.kUnknown: + print(f"FAIL: TriggerActivityData.algorithm expected kUnknown, got {ta.algorithm}") + return 1 + + print("PASS: TriggerActivityData default checks") + return 0 + + +def test_trigger_activity_data_enums() -> int: + # Confirm expected enum names are available through bindings. + enum_checks = [ + TriggerActivityData.Type.kUnknown, + TriggerActivityData.Type.kTPC, + TriggerActivityData.Type.kPDS, + TriggerActivityData.Algorithm.kUnknown, + TriggerActivityData.Algorithm.kSupernova, + TriggerActivityData.Algorithm.kSWIFT, + ] + + if any(value is None for value in enum_checks): + print("FAIL: TriggerActivityData enum exposure appears incomplete") + return 1 + + print("PASS: TriggerActivityData enum exposure") + return 0 + + +def test_trigger_activity_holder_from_bytes() -> int: + payload = bytes(TriggerActivityData.sizeof() + 8) + ta = TriggerActivity(payload) + + if ta.sizeof() != len(payload): + print(f"FAIL: TriggerActivity sizeof mismatch, got {ta.sizeof()}, expected {len(payload)}") + return 1 + + if len(ta) != 0: + print(f"FAIL: TriggerActivity expected length 0 for zero payload, got {len(ta)}") + return 1 + + if ta.n_inputs() != 0: + print(f"FAIL: TriggerActivity n_inputs expected 0 for zero payload, got {ta.n_inputs()}") + return 1 + + if len(ta.get_bytes()) != len(payload): + print("FAIL: TriggerActivity get_bytes length mismatch") + return 1 + + print("PASS: TriggerActivity holder bytes roundtrip with zero-input payload") + return 0 + + +def main() -> int: + failures = 0 + failures += test_trigger_activity_data_defaults() + failures += test_trigger_activity_data_enums() + failures += test_trigger_activity_holder_from_bytes() + + if failures == 0: + print("TriggerActivity binding tests passed") + else: + print(f"TriggerActivity binding tests had {failures} failure(s)") + + return failures + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/TriggerCandidate_binding_test.py b/scripts/TriggerCandidate_binding_test.py new file mode 100755 index 0000000..ebf2b62 --- /dev/null +++ b/scripts/TriggerCandidate_binding_test.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 + +from trgdataformats import ( + TriggerCandidate, + TriggerCandidateData, + string_to_trigger_candidate_type, + trigger_candidate_type_to_string, +) + + +def test_trigger_candidate_data_defaults() -> int: + tc = TriggerCandidateData() + + if tc is None: + print("FAIL: TriggerCandidateData default construction returned None") + return 1 + + size = TriggerCandidateData.sizeof() + if size != 48: + print(f"FAIL: TriggerCandidateData.sizeof() returned {size}, expected 48") + return 1 + + if TriggerCandidateData.s_trigger_candidate_version != 3: + print( + "FAIL: TriggerCandidateData.s_trigger_candidate_version expected 3, " + f"got {TriggerCandidateData.s_trigger_candidate_version}" + ) + return 1 + + if tc.version != 3: + print(f"FAIL: TriggerCandidateData.version expected 3, got {tc.version}") + return 1 + + if tc.version != TriggerCandidateData.s_trigger_candidate_version: + print( + "FAIL: TriggerCandidateData.version should match s_trigger_candidate_version, " + f"got {tc.version} vs {TriggerCandidateData.s_trigger_candidate_version}" + ) + return 1 + + if tc.time_start != (2**64 - 1): + print(f"FAIL: TriggerCandidateData.time_start expected 2**64-1, got {tc.time_start}") + return 1 + + if tc.time_end != (2**64 - 1): + print(f"FAIL: TriggerCandidateData.time_end expected 2**64-1, got {tc.time_end}") + return 1 + + if tc.time_candidate != (2**64 - 1): + print( + "FAIL: TriggerCandidateData.time_candidate expected 2**64-1, " + f"got {tc.time_candidate}" + ) + return 1 + + if tc.detid != 2**8 - 1: + print(f"FAIL: TriggerCandidateData.detid expected 2**8 - 1, got {tc.detid}") + return 1 + + if tc.type != TriggerCandidateData.Type.kUnknown: + print(f"FAIL: TriggerCandidateData.type expected kUnknown, got {tc.type}") + return 1 + + if tc.algorithm != TriggerCandidateData.Algorithm.kUnknown: + print(f"FAIL: TriggerCandidateData.algorithm expected kUnknown, got {tc.algorithm}") + return 1 + + print("PASS: TriggerCandidateData default checks") + return 0 + + +def test_trigger_candidate_enum_and_conversion() -> int: + if trigger_candidate_type_to_string(TriggerCandidateData.Type.kTiming) != "kTiming": + print("FAIL: trigger_candidate_type_to_string(kTiming) mismatch") + return 1 + + if string_to_trigger_candidate_type("kTiming") != TriggerCandidateData.Type.kTiming: + print("FAIL: string_to_trigger_candidate_type('kTiming') mismatch") + return 1 + + if string_to_trigger_candidate_type("notAType") != TriggerCandidateData.Type.kUnknown: + print("FAIL: string_to_trigger_candidate_type('notAType') should return kUnknown") + return 1 + + # Spot-check long-tail enum entries added recently. + for expected in [ + TriggerCandidateData.Type.kCTBOffSpillSnapshot, + TriggerCandidateData.Type.kCTBCustomPulseTrain, + TriggerCandidateData.Type.kSSPLEDCalibration, + ]: + as_str = trigger_candidate_type_to_string(expected) + if string_to_trigger_candidate_type(as_str) != expected: + print(f"FAIL: conversion roundtrip mismatch for enum value '{as_str}'") + return 1 + + print("PASS: TriggerCandidateData enum conversion checks") + return 0 + + +def test_trigger_candidate_holder_from_bytes() -> int: + payload = bytes(TriggerCandidateData.sizeof() + 8) # 8 for the uint64_t n_inputs in TriggerCandidate + tc = TriggerCandidate(payload) + + if tc.sizeof() != len(payload): + print(f"FAIL: TriggerCandidate sizeof mismatch, got {tc.sizeof()}, expected {len(payload)}") + return 1 + + if len(tc) != 0: + print(f"FAIL: TriggerCandidate expected length 0 for zero payload, got {len(tc)}") + return 1 + + if tc.n_inputs() != 0: + print(f"FAIL: TriggerCandidate n_inputs expected 0 for zero payload, got {tc.n_inputs()}") + return 1 + + if len(tc.get_bytes()) != len(payload): + print("FAIL: TriggerCandidate get_bytes length mismatch") + return 1 + + print("PASS: TriggerCandidate holder bytes roundtrip with zero-input payload") + return 0 + + +def main() -> int: + failures = 0 + failures += test_trigger_candidate_data_defaults() + failures += test_trigger_candidate_enum_and_conversion() + failures += test_trigger_candidate_holder_from_bytes() + + if failures == 0: + print("TriggerCandidate binding tests passed") + else: + print(f"TriggerCandidate binding tests had {failures} failure(s)") + + return failures + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/TriggerPrimitive_binding_test.py b/scripts/TriggerPrimitive_binding_test.py new file mode 100755 index 0000000..f6eabfd --- /dev/null +++ b/scripts/TriggerPrimitive_binding_test.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 + +from trgdataformats import TriggerPrimitive + + +def test_construction_and_sizeof() -> int: + tp = TriggerPrimitive() + if tp is None: + print("FAIL: TriggerPrimitive default construction returned None") + return 1 + + size = TriggerPrimitive.sizeof() + if size != TriggerPrimitive.s_expected_bytes: + print( + "FAIL: TriggerPrimitive.sizeof() and TriggerPrimitive.s_expected_bytes mismatch, " + f"got {size} vs {TriggerPrimitive.s_expected_bytes}" + ) + return 1 + + print("PASS: TriggerPrimitive construction and sizeof") + return 0 + + +def test_static_and_default_fields() -> int: + tp = TriggerPrimitive() + + # Providing this check will provide a nudge for developers to update these tests when the version changes + if TriggerPrimitive.s_trigger_primitive_version != 2: + print( + "FAIL: TriggerPrimitive.s_trigger_primitive_version expected 2, " + f"got {TriggerPrimitive.s_trigger_primitive_version}" + ) + return 1 + + if tp.version != TriggerPrimitive.s_trigger_primitive_version: + print(f"FAIL: version mismatch, got {tp.version}") + return 1 + + if tp.flag != 0: + print(f"FAIL: flag expected 0, got {tp.flag}") + return 1 + + if tp.detid != 2**8 - 1: + print(f"FAIL: detid expected 2**8 - 1, got {tp.detid}") + return 1 + + if tp.channel != 0xFFFFFF: + print(f"FAIL: channel expected 0xFFFFFF, got {tp.channel}") + return 1 + + if tp.samples_over_threshold != 2**16 - 1: + print( + "FAIL: samples_over_threshold expected 2**16 - 1, " + f"got {tp.samples_over_threshold}" + ) + return 1 + + if tp.samples_to_peak != 2**16 - 1: + print(f"FAIL: samples_to_peak expected 2**16 - 1, got {tp.samples_to_peak}") + return 1 + + if tp.time_start != (2**64 - 1): + print(f"FAIL: time_start expected 2**64-1, got {tp.time_start}") + return 1 + + if tp.adc_integral != 0: + print(f"FAIL: adc_integral expected 0, got {tp.adc_integral}") + return 1 + + if tp.adc_peak != 0: + print(f"FAIL: adc_peak expected 0, got {tp.adc_peak}") + return 1 + + expected_channel_invalid = 0xFFFFFF + if TriggerPrimitive.s_invalid_samples_over_threshold != 2**16 - 1: + print( + "FAIL: TriggerPrimitive.s_invalid_samples_over_threshold expected 2**16 - 1, " + f"got {TriggerPrimitive.s_invalid_samples_over_threshold}" + ) + return 1 + + if TriggerPrimitive.s_invalid_samples_to_peak != 2**16 - 1: + print( + "FAIL: TriggerPrimitive.s_invalid_samples_to_peak expected 2**16 - 1, " + f"got {TriggerPrimitive.s_invalid_samples_to_peak}" + ) + return 1 + + if TriggerPrimitive.s_invalid_tp_channel != expected_channel_invalid: + print( + "FAIL: TriggerPrimitive.s_invalid_tp_channel expected 0xFFFFFF, " + f"got {TriggerPrimitive.s_invalid_tp_channel}" + ) + return 1 + + if TriggerPrimitive.s_expected_bytes != 24: + print( + "FAIL: TriggerPrimitive.s_expected_bytes expected 24, " + f"got {TriggerPrimitive.s_expected_bytes}" + ) + return 1 + + print("PASS: TriggerPrimitive static/default field checks") + return 0 + + +def test_timestamp_methods_and_ordering() -> int: + tp_a = TriggerPrimitive() + tp_b = TriggerPrimitive() + + if tp_a.get_timestamp() != tp_a.time_start: + print( + "FAIL: TriggerPrimitive get_timestamp() is inconsistent with time_start, " + f"got {tp_a.get_timestamp()} vs {tp_a.time_start}" + ) + return 1 + + tp_a.set_timestamp(100) + if tp_a.get_timestamp() != 100: + print(f"FAIL: TriggerPrimitive get/set timestamp mismatch, got {tp_a.get_timestamp()}") + return 1 + + tp_b.set_timestamp(200) + + if not (tp_a < tp_b): + print("FAIL: TriggerPrimitive ordering expected tp_a < tp_b") + return 1 + + if tp_b < tp_a: + print("FAIL: TriggerPrimitive ordering expected tp_b !< tp_a") + return 1 + + print("PASS: TriggerPrimitive timestamp methods and ordering operator") + return 0 + + +def main() -> int: + failures = 0 + failures += test_construction_and_sizeof() + failures += test_static_and_default_fields() + failures += test_timestamp_methods_and_ordering() + + if failures == 0: + print("TriggerPrimitive binding tests passed") + else: + print(f"TriggerPrimitive binding tests had {failures} failure(s)") + + return failures + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/Types_binding_test.py b/scripts/Types_binding_test.py new file mode 100755 index 0000000..fd16c3f --- /dev/null +++ b/scripts/Types_binding_test.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +from trgdataformats import TypeDefaults, g_whole_detector + + +def test_type_defaults_constants() -> int: + if TypeDefaults.s_invalid_timestamp != (2**64 - 1): + print( + "FAIL: TypeDefaults.s_invalid_timestamp expected 2**64-1, " + f"got {TypeDefaults.s_invalid_timestamp}" + ) + return 1 + + if TypeDefaults.s_invalid_detid != (2**8 - 1): + print( + "FAIL: TypeDefaults.s_invalid_detid expected 2**8 - 1, " + f"got {TypeDefaults.s_invalid_detid}" + ) + return 1 + + if TypeDefaults.s_invalid_trigger_number != (2**64 - 1): + print( + "FAIL: TypeDefaults.s_invalid_trigger_number expected 2**64-1, " + f"got {TypeDefaults.s_invalid_trigger_number}" + ) + return 1 + + print("PASS: TypeDefaults constants") + return 0 + + +def test_whole_detector_constant() -> int: + if g_whole_detector != (TypeDefaults.s_invalid_detid - 1): + print( + "FAIL: g_whole_detector expected invalid_detid-1, " + f"got {g_whole_detector}" + ) + return 1 + + print("PASS: g_whole_detector constant") + return 0 + + +def main() -> int: + failures = 0 + failures += test_type_defaults_constants() + failures += test_whole_detector_constant() + + if failures == 0: + print("Types binding tests passed") + else: + print(f"Types binding tests had {failures} failure(s)") + + return failures + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/trgdataformats_binding_tests.py b/scripts/trgdataformats_binding_tests.py new file mode 100755 index 0000000..ff3aec1 --- /dev/null +++ b/scripts/trgdataformats_binding_tests.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +import subprocess +import sys +from pathlib import Path + +def main() -> int: + script_dir = Path(__file__).resolve().parent + + scripts = [ + "Types_binding_test.py", + "TriggerPrimitive_binding_test.py", + "TriggerActivity_binding_test.py", + "TriggerCandidate_binding_test.py", + ] + + failures = 0 + for script in scripts: + result = subprocess.run([sys.executable, str(script_dir / script)], check=False) + if result.returncode != 0: + failures += 1 + + print("\n=== Summary ===") + if failures == 0: + print("All trgdataformats binding tests passed") + else: + print(f"{failures} binding test script(s) failed") + + return failures + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/unittest/TriggerCandidateData_test.cxx b/unittest/TriggerCandidateData_test.cxx index a133246..9a3a71b 100644 --- a/unittest/TriggerCandidateData_test.cxx +++ b/unittest/TriggerCandidateData_test.cxx @@ -28,16 +28,16 @@ BOOST_AUTO_TEST_CASE(FragmentTypeConversion) static_cast(TriggerCandidateData::Type::kTiming)); BOOST_REQUIRE_EQUAL(trigger_candidate_type_to_string(TriggerCandidateData::Type::kTiming), "kTiming"); - auto type_map = get_trigger_candidate_type_names(); + auto type_container = get_trigger_candidate_type_names(); // sanity check - for (auto& type_pair : type_map) { + for (const auto& [type, type_name] : type_container) { BOOST_TEST_MESSAGE("TriggerCandidateData type " - << int(type_pair.first) << " " << type_pair.second - << " with conversions: " << int(string_to_trigger_candidate_type(type_pair.second)) << " " - << trigger_candidate_type_to_string(type_pair.first)); - BOOST_REQUIRE_EQUAL(static_cast(string_to_trigger_candidate_type(type_pair.second)), - static_cast(type_pair.first)); - BOOST_REQUIRE_EQUAL(trigger_candidate_type_to_string(type_pair.first), type_pair.second); + << int(type) << " " << type_name + << " with conversions: " << int(string_to_trigger_candidate_type(std::string(type_name))) << " " + << trigger_candidate_type_to_string(type)); + BOOST_REQUIRE_EQUAL(static_cast(string_to_trigger_candidate_type(std::string(type_name))), + static_cast(type)); + BOOST_REQUIRE_EQUAL(trigger_candidate_type_to_string(type), type_name); } BOOST_REQUIRE_EQUAL(static_cast(string_to_trigger_candidate_type("thisIsABadFragmentType")), diff --git a/unittest/TriggerDataStructures_test.cxx b/unittest/TriggerDataStructures_test.cxx new file mode 100644 index 0000000..2b81587 --- /dev/null +++ b/unittest/TriggerDataStructures_test.cxx @@ -0,0 +1,159 @@ +/** + * @file TriggerDataStructures_test.cxx + * + * This is part of the DUNE DAQ Application Framework, copyright 2020. + * Licensing/copyright details are in the COPYING file that you should have + * received with this code. + */ + +#include "trgdataformats/TriggerActivityData.hpp" +#include "trgdataformats/TriggerCandidateData.hpp" +#include "trgdataformats/TriggerObjectOverlay.hpp" +#include "trgdataformats/TriggerPrimitive.hpp" +#include "trgdataformats/Types.hpp" + +#define BOOST_TEST_MODULE TriggerDataStructures_test // NOLINT + +#include "boost/test/unit_test.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace dunedaq::trgdataformats; + +BOOST_AUTO_TEST_SUITE(TriggerDataStructures_test) + +BOOST_AUTO_TEST_CASE(TriggerPrimitive_DefaultValues_And_Timestamp) +{ + TriggerPrimitive tp; + + BOOST_REQUIRE_EQUAL(tp.version, TriggerPrimitive::s_trigger_primitive_version); + BOOST_REQUIRE_EQUAL(tp.flag, 0); + BOOST_REQUIRE_EQUAL(tp.detid, TypeDefaults::s_invalid_detid); + BOOST_REQUIRE_EQUAL(tp.channel, TriggerPrimitive::s_invalid_tp_channel); + BOOST_REQUIRE_EQUAL(tp.samples_over_threshold, TriggerPrimitive::s_invalid_samples_over_threshold); + BOOST_REQUIRE_EQUAL(tp.time_start, TypeDefaults::s_invalid_timestamp); + BOOST_REQUIRE_EQUAL(tp.samples_to_peak, TriggerPrimitive::s_invalid_samples_to_peak); + BOOST_REQUIRE_EQUAL(tp.adc_integral, 0); + BOOST_REQUIRE_EQUAL(tp.adc_peak, 0); + + constexpr timestamp_t test_ts = 0x12345678ULL; + tp.set_timestamp(test_ts); + BOOST_REQUIRE_EQUAL(tp.get_timestamp(), test_ts); + BOOST_REQUIRE_EQUAL(tp.time_start, test_ts); +} + +BOOST_AUTO_TEST_CASE(TriggerPrimitive_Ordering) +{ + TriggerPrimitive a; + TriggerPrimitive b; + TriggerPrimitive c; + + a.time_start = 100; + a.channel = 10; + + b.time_start = 100; + b.channel = 8; + + c.time_start = 99; + c.channel = 20; + + std::vector tps{ a, b, c }; + std::sort(tps.begin(), tps.end()); + + BOOST_REQUIRE_EQUAL(tps[0].time_start, 99); + BOOST_REQUIRE_EQUAL(tps[0].channel, 20); + + BOOST_REQUIRE_EQUAL(tps[1].time_start, 100); + BOOST_REQUIRE_EQUAL(tps[1].channel, 8); + + BOOST_REQUIRE_EQUAL(tps[2].time_start, 100); + BOOST_REQUIRE_EQUAL(tps[2].channel, 10); +} + +BOOST_AUTO_TEST_CASE(TriggerActivityData_DefaultValues) +{ + TriggerActivityData ta; + + BOOST_REQUIRE_EQUAL(ta.version, TriggerActivityData::s_trigger_activity_version); + BOOST_REQUIRE_EQUAL(ta.time_start, TypeDefaults::s_invalid_timestamp); + BOOST_REQUIRE_EQUAL(ta.time_end, TypeDefaults::s_invalid_timestamp); + BOOST_REQUIRE_EQUAL(ta.time_peak, TypeDefaults::s_invalid_timestamp); + BOOST_REQUIRE_EQUAL(ta.time_activity, TypeDefaults::s_invalid_timestamp); + BOOST_REQUIRE_EQUAL(ta.channel_start, TriggerActivityData::s_invalid_channel); + BOOST_REQUIRE_EQUAL(ta.channel_end, TriggerActivityData::s_invalid_channel); + BOOST_REQUIRE_EQUAL(ta.channel_peak, TriggerActivityData::s_invalid_channel); + BOOST_REQUIRE_EQUAL(ta.adc_integral, 0); + BOOST_REQUIRE_EQUAL(ta.adc_peak, 0); + BOOST_REQUIRE_EQUAL(ta.detid, TypeDefaults::s_invalid_detid); + BOOST_REQUIRE_EQUAL(static_cast(ta.type), static_cast(TriggerActivityData::Type::kUnknown)); + BOOST_REQUIRE_EQUAL(static_cast(ta.algorithm), static_cast(TriggerActivityData::Algorithm::kUnknown)); +} + +BOOST_AUTO_TEST_CASE(TriggerCandidateData_DefaultValues) +{ + TriggerCandidateData tc{}; + + BOOST_REQUIRE_EQUAL(tc.version, TriggerCandidateData::s_trigger_candidate_version); + BOOST_REQUIRE_EQUAL(tc.time_start, TypeDefaults::s_invalid_timestamp); + BOOST_REQUIRE_EQUAL(tc.time_end, TypeDefaults::s_invalid_timestamp); + BOOST_REQUIRE_EQUAL(tc.time_candidate, TypeDefaults::s_invalid_timestamp); + BOOST_REQUIRE_EQUAL(static_cast(tc.type), static_cast(TriggerCandidateData::Type::kUnknown)); + BOOST_REQUIRE_EQUAL(static_cast(tc.algorithm), static_cast(TriggerCandidateData::Algorithm::kUnknown)); + + BOOST_REQUIRE_EQUAL(tc.detid, TypeDefaults::s_invalid_detid); +} + +BOOST_AUTO_TEST_CASE(Types_DefaultConstants) +{ + BOOST_REQUIRE_EQUAL(TypeDefaults::s_invalid_timestamp, std::numeric_limits::max()); + BOOST_REQUIRE_EQUAL(TypeDefaults::s_invalid_detid, std::numeric_limits::max()); + BOOST_REQUIRE_EQUAL(TypeDefaults::s_invalid_trigger_number, std::numeric_limits::max()); + BOOST_REQUIRE(g_whole_detector != TypeDefaults::s_invalid_detid); + BOOST_REQUIRE_EQUAL(g_whole_detector, + static_cast(std::numeric_limits::max() - static_cast(1))); +} + +BOOST_AUTO_TEST_CASE(TriggerObjectOverlay_AliasesAndLayout) +{ + BOOST_REQUIRE((std::is_same_v)); + BOOST_REQUIRE((std::is_same_v)); + BOOST_REQUIRE((std::is_same_v)); + BOOST_REQUIRE((std::is_same_v)); + + BOOST_REQUIRE_EQUAL(offsetof(TriggerActivity, n_inputs), sizeof(TriggerActivityData)); + BOOST_REQUIRE_EQUAL(offsetof(TriggerCandidate, n_inputs), sizeof(TriggerCandidateData)); +} + +BOOST_AUTO_TEST_CASE(TriggerObjectOverlay_SetInputs) +{ + std::vector tps(3); + tps[0].time_start = 100; + tps[0].channel = 10; + tps[1].time_start = 101; + tps[1].channel = 11; + tps[2].time_start = 102; + tps[2].channel = 12; + + // Recall that the "inputs" in the struct are a flexible array member which needs space allocated for it + const std::size_t nbytes = sizeof(TriggerActivity) + tps.size() * sizeof(TriggerPrimitive); + void* storage_for_ta_overlay = ::operator new(nbytes); + auto* ta = new (storage_for_ta_overlay) TriggerActivity{}; + + ta->set_inputs(tps); + BOOST_REQUIRE_EQUAL(ta->n_inputs, tps.size()); + + for (size_t i = 0; i < tps.size(); ++i) { + BOOST_REQUIRE_EQUAL(tps[i].time_start, ta->inputs[i].time_start); + BOOST_REQUIRE_EQUAL(tps[i].channel, ta->inputs[i].channel); + } +} + +BOOST_AUTO_TEST_SUITE_END()