diff --git a/fcl/reco/Stage0/Run2/partial/decodeTrigger_icarus.fcl b/fcl/reco/Stage0/Run2/partial/decodeTrigger_icarus.fcl index 373d762a5..d041c0023 100644 --- a/fcl/reco/Stage0/Run2/partial/decodeTrigger_icarus.fcl +++ b/fcl/reco/Stage0/Run2/partial/decodeTrigger_icarus.fcl @@ -1,4 +1,5 @@ #include "services_common_icarus.fcl" +#include "channelmapping_icarus.fcl" #include "rootoutput_icarus.fcl" #include "decoderdefs_icarus.fcl" @@ -11,6 +12,7 @@ services: { @table::icarus_geometry_services DetectorClocksService: @local::icarus_detectorclocks + IICARUSChannelMap: @local::icarus_channelmappinggservice # from channelmapping_icarus.fcl } diff --git a/icaruscode/Decode/ChannelMapping/ChannelMapDumper.cxx b/icaruscode/Decode/ChannelMapping/ChannelMapDumper.cxx index 58b4c063a..396268ddf 100644 --- a/icaruscode/Decode/ChannelMapping/ChannelMapDumper.cxx +++ b/icaruscode/Decode/ChannelMapping/ChannelMapDumper.cxx @@ -168,12 +168,22 @@ void dumpPMTmapping(icarusDB::IICARUSChannelMapProvider const& mapping) { << " includes " << digitizerChannels.size() << " LArSoft channels between " << digitizerChannels.front().channelID << " and " << digitizerChannels.back().channelID - << " [board channel index in brackets]:"; - Pager pager{ 8 }; + << " [board channel info in brackets]:"; + Pager pager{ 3 }; for(auto const & chInfo: digitizerChannels) { if (pager.nextHasNewLine()) log << "\n "; - log << " " << std::setw(3) << chInfo.channelID - << " [" << std::setw(3) << chInfo.digitizerChannelNo << "]"; + log << " " << std::setw(3) << chInfo.channelID + << " [" << chInfo.digitizerLabel << "@" + << std::setfill('0') << std::setw(2) << chInfo.digitizerChannelNo; + if (chInfo.hasLVDSinfo()) { + log << ", LVDS:" << chInfo.LVDSconnector << "-" + << std::setw(2) << chInfo.LVDSbit; + } + if (chInfo.hasAdderInfo()) { + log << ", adder:" << chInfo.adderConnector << "-" + << std::setw(2) << chInfo.adderBit; + } + log << "]"; } // for channel } // for fragment diff --git a/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx b/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx index a89b1ec9a..3c8a20aeb 100644 --- a/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx +++ b/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx @@ -1,7 +1,7 @@ /** * @file icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx * @brief Interface with ICARUS channel mapping PostGres database. - * @author T. Usher (factorised by Gianluca Petrillo, petrillo@slac.stanford.edu) + * @author T. Usher (factorised by G. Petrillo, petrillo@slac.stanford.edu) * @see icaruscode/Decode/ChannelMapping/ChannelMapPostGres.h */ @@ -230,15 +230,15 @@ int icarusDB::ChannelMapPostGres::BuildTPCFragmentIDToReadoutIDMap // Note that the fragment ID is stored in the database as a string which // reads as a hex number, meaning we have to read back as a string and // decode to get the numerical value - Expected const fragmentIDString + util::Expected const fragmentIDString = getStringFromTuple(tuple, FragmentIDcolumn); if (!fragmentIDString) { - throw myException() << "Error (code: " << fragmentIDString.code() + throw myException() << "Error (code: " << fragmentIDString.error() << " on row " << row << ") retrieving TPC fragment ID from channel mapping database\n"; } - unsigned int const fragmentID = std::stol(fragmentIDString, nullptr, 16); + unsigned int const fragmentID = std::stol(*fragmentIDString, nullptr, 16); if (!(fragmentID & tpcIdentifier)) continue; if (fragmentBoardMap.find(fragmentID) == fragmentBoardMap.end()) { @@ -251,16 +251,17 @@ int icarusDB::ChannelMapPostGres::BuildTPCFragmentIDToReadoutIDMap } // build the flange name - Expected const chimneyNoString = getStringFromTuple(tuple, ChimneyNoColumn); + util::Expected const chimneyNoString + = getStringFromTuple(tuple, ChimneyNoColumn); if (!chimneyNoString) { - throw myException() << "Error (code: " << chimneyNoString.code() + throw myException() << "Error (code: " << chimneyNoString.error() << " on row " << row << ") retrieving chimney number from channel mapping database\n"; } - Expected const TPCIDstring = getStringFromTuple(tuple, TPCIDcolumn); + util::Expected const TPCIDstring = getStringFromTuple(tuple, TPCIDcolumn); if (!TPCIDstring) { - throw myException() << "Error (code: " << TPCIDstring.code() + throw myException() << "Error (code: " << TPCIDstring.error() << " on row " << row << ") retrieving TPC tag from channel mapping database\n"; } @@ -358,14 +359,14 @@ int icarusDB::ChannelMapPostGres::BuildTPCReadoutBoardToChannelMap } // Recover the plane identifier - Expected const fragmentBuffer + util::Expected const fragmentBuffer = getStringFromTuple(tuple, PlaneIdentifierColumn); if (!fragmentBuffer) { - throw myException() << "Error (code: " << fragmentBuffer.code() + throw myException() << "Error (code: " << fragmentBuffer.error() << " on row " << row << ") reading plane type\n"; } // Make sure lower case... (sigh...) - unsigned int const plane = TPCplaneIdentifierToPlane(fragmentBuffer); + unsigned int const plane = TPCplaneIdentifierToPlane(*fragmentBuffer); if (plane >= 3) { mf::LogError{ "ChannelMapSQLite" } << "YIKES!!! Plane is " << plane << " for channel " << channelID << " with type " @@ -415,11 +416,13 @@ int icarusDB::ChannelMapPostGres::BuildPMTFragmentToDigitizerChannelMap // input is C-strings; we force the other term of comparison to C++ strings using namespace std::string_literals; auto const [ - ChannelIDcolumn, LaserChannelColumn, DigitizerColumn, - DigitizerChannelNoColumn, FragmentIDcolumn + LaserChannelColumn, DigitizerChannelColumn, ChannelIDcolumn, + FragmentIDcolumn, DigitizerLabelColumn, LVDSconnectorColumn, + AdderConnectorColumn ]= details::WDAPositionFinder{ getTuple(dataset, 0) }( - "channel_id"s, "light_fiber_label"s, "digitizer_label"s, - "digitizer_ch_number"s, "fragment_id"s + "light_fiber_label"s, "digitizer_ch_number"s, "channel_id"s, + "fragment_id"s, "digitizer_label"s, "FPGA_connector_DIO"s, + "adder_connector_DIO"s ); // Ok, now we can start extracting the information @@ -432,16 +435,18 @@ int icarusDB::ChannelMapPostGres::BuildPMTFragmentToDigitizerChannelMap int error = 0; - // nice... and currently unused - Expected const digitizerLabel = getStringFromTuple(tuple, DigitizerColumn); + PMTChannelInfo_t chInfo; + auto const nFields = static_cast(getNfields(tuple)); + + // digitizer label + util::Expected const digitizerLabel + = getStringFromTuple(tuple, DigitizerLabelColumn); if (!digitizerLabel) { - throw myException() << "Error (code: " << digitizerLabel.code() + throw myException() << "Error (code: " << digitizerLabel.error() << " on row " << row << ") retrieving PMT digitizer from channel mapping database\n"; } - - - PMTChannelInfo_t chInfo; + chInfo.digitizerLabel = *digitizerLabel; // fragment id unsigned long const fragmentID @@ -453,7 +458,7 @@ int icarusDB::ChannelMapPostGres::BuildPMTFragmentToDigitizerChannelMap // digitizer channel number chInfo.digitizerChannelNo - = getLongValue(tuple, DigitizerChannelNoColumn, &error); + = getLongValue(tuple, DigitizerChannelColumn, &error); if (error) { throw myException() << "Error (code: " << error << " on row " << row << ") reading PMT readout board channel number\n"; @@ -466,15 +471,50 @@ int icarusDB::ChannelMapPostGres::BuildPMTFragmentToDigitizerChannelMap << ") reading PMT channel ID\n"; } // laser channel number - Expected laserChannelLabel = getStringFromTuple(tuple, LaserChannelColumn); + util::Expected laserChannelLabel + = getStringFromTuple(tuple, LaserChannelColumn); if (!laserChannelLabel) { - throw myException() << "Error (code: " << laserChannelLabel.code() + throw myException() << "Error (code: " << laserChannelLabel.error() << " on row " << row << ") retrieving PMT laser channel from channel mapping database\n"; } // will throw on error: chInfo.laserChannelNo = std::stol(laserChannelLabel.value().substr(2)); + // LVDS connector and bit + if (LVDSconnectorColumn < nFields) { + util::Expected LVDSconnectorLabel + = getStringFromTuple(tuple, LVDSconnectorColumn); + if (!LVDSconnectorLabel) { + throw myException() << "Error (code: " << LVDSconnectorLabel.error() + << " on row " << row + << ") retrieving LVDS connector from channel mapping database\n"; + } + if (!LVDSconnectorLabel->empty() && (*LVDSconnectorLabel != "-")) { + auto const [ LVDSconnector, LVDSbit ] + = splitIntegers<2, unsigned short int>(*LVDSconnectorLabel, "-"); + chInfo.LVDSconnector = LVDSconnector; + chInfo.LVDSbit = LVDSbit; + } + } + + // adder connector and bit + if (AdderConnectorColumn < nFields) { + util::Expected adderConnectorLabel + = getStringFromTuple(tuple, AdderConnectorColumn); + if (!adderConnectorLabel) { + throw myException() << "Error (code: " << adderConnectorLabel.error() + << " on row " << row + << ") retrieving adder connector from channel mapping database\n"; + } + if (!adderConnectorLabel->empty() && (*adderConnectorLabel != "-")) { + auto const [ adderConnector, adderBit ] + = splitIntegers<2, unsigned short int>(*adderConnectorLabel, "-"); + chInfo.adderConnector = adderConnector; + chInfo.adderBit = adderBit; + } + } + // fill the map fragmentToDigitizerChannelMap[fragmentID].push_back(std::move(chInfo)); @@ -705,8 +745,8 @@ bool icarusDB::ChannelMapPostGres::printDatasetError // ----------------------------------------------------------------------------- template -auto icarusDB::ChannelMapPostGres::getStringFromTuple - (details::WDATuple const& tuple, std::size_t column) -> Expected +util::Expected icarusDB::ChannelMapPostGres::getStringFromTuple + (details::WDATuple const& tuple, std::size_t column) { int error = 0; std::string buffer(BufferSize, '\0'); diff --git a/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.h b/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.h index 8413b887d..e7f16332e 100644 --- a/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.h +++ b/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.h @@ -1,7 +1,7 @@ /** * @file icaruscode/Decode/ChannelMapping/ChannelMapPostGres.h * @brief Interface with ICARUS channel mapping PostGres database. - * @author T. Usher (factorised by Gianluca Petrillo, petrillo@slac.stanford.edu) + * @author T. Usher (factorised by G. Petrillo, petrillo@slac.stanford.edu) * @see icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx */ @@ -12,6 +12,7 @@ // ICARUS libraries #include "icaruscode/Decode/ChannelMapping/IChannelMapping.h" #include "icaruscode/Decode/ChannelMapping/RunPeriods.h" +#include "icaruscode/Utilities/Expected.h" #include "icarusalg/Utilities/mfLoggingClass.h" // framework libraries @@ -133,33 +134,6 @@ class icarusDB::ChannelMapPostGres virtual int BuildSideCRTCalibrationMap(SideCRTChannelToCalibrationMap&) const override; private: - - /// Loosely based on C++-20 `std::expected`. - template - class Expected { - std::variant fValue; - public: - constexpr Expected(E e = E{}): fValue{ std::move(e) } {} - constexpr Expected(T t): fValue{ std::move(t) } {} - - Expected& operator= (E e) { fValue = std::move(e); return *this; } - Expected& operator= (T t) { fValue = std::move(t); return *this; } - template - T& emplace(Args&&... args) - { return fValue.emplace(std::forward(args)...); } - - bool has_value() const { return std::holds_alternative(fValue); } - - E code() const { return std::get(fValue); } - T const& value() const { return std::get(fValue); } - - operator T const& () const { return value(); } - T const& operator*() const { return value(); } - - explicit operator bool() const { return has_value(); } - - }; // Expected - // --- BEGIN --- Configuration parameters ------------------------------------ @@ -225,7 +199,7 @@ class icarusDB::ChannelMapPostGres /// Returns the string on the specified `column` of the `tuple`. /// @return the requested string, or an error code from libwda on error template - static Expected getStringFromTuple + static util::Expected getStringFromTuple (details::WDATuple const& tuple, std::size_t column); /// Returns an exception object pre-approved with our signature. diff --git a/icaruscode/Decode/ChannelMapping/ChannelMapSQLite.cxx b/icaruscode/Decode/ChannelMapping/ChannelMapSQLite.cxx index 355e9e66f..cf2454e25 100644 --- a/icaruscode/Decode/ChannelMapping/ChannelMapSQLite.cxx +++ b/icaruscode/Decode/ChannelMapping/ChannelMapSQLite.cxx @@ -27,6 +27,7 @@ // C++ standard libraries #include // std::transform() #include +#include #include #include // std::tolower() @@ -450,11 +451,13 @@ int icarusDB::ChannelMapSQLite::buildPMTFragmentToDigitizerChannelMap_callback // input is C-strings; we force the other term of comparison to C++ strings using namespace std::string_literals; auto const [ - LaserChannelColumn, DigitizerChannelColumn, ChannelIDcolumn, - FragmentIDcolumn + LaserChannelColumn, DigitizerChannelColumn, ChannelIDcolumn, + FragmentIDcolumn, DigitizerLabelColumn, LVDSconnectorColumn, + AdderConnectorColumn ] = icarus::ns::util::PositionFinder{ gsl::span{azColName, (unsigned) argc} }( - "light_fiber_label"s, "digitizer_ch_number"s, "channel_id"s, - "fragment_id"s + "light_fiber_label"s, "digitizer_ch_number"s, "channel_id"s, + "fragment_id"s, "digitizer_label"s, "FPGA_connector_DIO"s, + "adder_connector_DIO"s ); auto& fragmentToDigitizerChannelMap @@ -462,7 +465,9 @@ int icarusDB::ChannelMapSQLite::buildPMTFragmentToDigitizerChannelMap_callback // Start extracting info unsigned int const fragmentID = std::stol(argv[FragmentIDcolumn]); - unsigned int const digitizerChannelNo = std::stol(argv[DigitizerChannelColumn]); + std::string digitizerLabel = argv[DigitizerLabelColumn]; + unsigned int const digitizerChannelNo + = std::stol(argv[DigitizerChannelColumn]); unsigned int const channelID = std::stol(argv[ChannelIDcolumn]); // Read the laser channel; format is `L-`. is int from [1-41] @@ -476,10 +481,40 @@ int icarusDB::ChannelMapSQLite::buildPMTFragmentToDigitizerChannelMap_callback << "Failed to convert laser channel '" << laserChannelLabel << "' into a channel number for channel " << channelID << "!\n"; } + + // connector bits; if the column is not present or if the value is empty or + // just "-", fall back to invalid values + auto const isValidConnectorBit = [argv,argc](std::size_t column) + { + if (column >= (std::size_t) argc) return false; + std::string_view const value = argv[column]; + return !value.empty() && (value != "-"); + }; + + // PMT discriminated signal connector and bit; + auto const [ LVDSconnector, LVDSbit ] + = isValidConnectorBit(LVDSconnectorColumn) + ? splitIntegers<2, unsigned short int>(argv[LVDSconnectorColumn], "-") + : std::array + { PMTChannelInfo_t::NoConnector, PMTChannelInfo_t::NoConnectorBit } + ; + + // adder discriminated signal connector and bit + // if the column is not present or if the value is empty or "-", fall back + auto const [ adderConnector, adderBit ] + = isValidConnectorBit(AdderConnectorColumn) + ? splitIntegers<2, unsigned short int>(argv[AdderConnectorColumn], "-") + : std::array + { PMTChannelInfo_t::NoConnector, PMTChannelInfo_t::NoConnectorBit } + ; // Fill the map - fragmentToDigitizerChannelMap[fragmentID].push_back - ({ digitizerChannelNo, channelID, laserChannel }); + fragmentToDigitizerChannelMap[fragmentID].push_back({ // C++20: name members + std::move(digitizerLabel), digitizerChannelNo, + channelID, laserChannel, + LVDSconnector, LVDSbit, + adderConnector, adderBit + }); return 0; } // ...::ChannelMapSQLite::buildPMTFragmentToDigitizerChannelMap_callback() diff --git a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h index ac0802999..6c9f66c1f 100644 --- a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h +++ b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h @@ -71,6 +71,13 @@ namespace icarusDB { = std::numeric_limits::max(); static constexpr unsigned int NoLaserChannel = std::numeric_limits::max(); + static constexpr unsigned short int NoConnector + = std::numeric_limits::max(); + static constexpr unsigned short int NoConnectorBit + = std::numeric_limits::max(); + + /// Label of the digitizer this channel is on (e.g. `WW-TOP-A`). + std::string digitizerLabel; /// Number of the channel within its digitizer. unsigned int digitizerChannelNo = NoDigitizerChannel; @@ -81,6 +88,25 @@ namespace icarusDB { /// Number of laser channel shining into this PMT. unsigned int laserChannelNo = NoLaserChannel; + /// Number of the connector with the LVDS signal of this channel. + unsigned short int LVDSconnector = NoConnector; + + /// Number of the connector bit carrying the LVDS signal of this channel. + unsigned short int LVDSbit = NoConnectorBit; + + /// Number of the connector with the adder signal including this channel. + unsigned short int adderConnector = NoConnector; + + /// Number of the connector bit carrying the adder signal including this + /// channel. + unsigned short int adderBit = NoConnectorBit; + + /// Returns whether the LVDS connector bit information is available. + constexpr bool hasLVDSinfo() const { return LVDSbit != NoConnectorBit; } + + /// Returns whether the adder connector bit information is available. + constexpr bool hasAdderInfo() const { return adderBit != NoConnectorBit; } + /// Sorting by channel ID. constexpr bool operator< (PMTChannelInfo_t const& other) const noexcept { return channelID < other.channelID; } diff --git a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.h b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.h index 66f511e0e..080308d8e 100644 --- a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.h +++ b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.h @@ -31,6 +31,21 @@ namespace icarusDB { * @tparam ChMapAlg type of channel mapping helper to be used * * + * Caches + * ------- + * + * This implementation relies on a database backend reading the full information + * from the database and caching the result, every time a new run period is + * requested. To help users who in turn cache information from this object to + * track whether _their_ caches are invalidated by such occurrences, this object + * uses the `util::CacheCounter` facility to version every cache. + * Users will want to use the `util::CacheGuard` tool to monitor the cache. + * Three caches are tracked, with tags `"TPC"`, `"PMT"` and `"CRT"`, plus + * the overall cache tracking (tagged with an empty name `""`). + * At the moment of writing, the three caches are actually updated all at the + * same times. + * + * * Configuration parameters * ========================= * diff --git a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.tcc b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.tcc index 3aea619d8..01397887d 100644 --- a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.tcc +++ b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.tcc @@ -29,6 +29,7 @@ icarusDB::ICARUSChannelMapProviderBase::ICARUSChannelMapProviderBase , fDiagnosticOutput { config.DiagnosticOutput() } , fChannelMappingAlg { config.ChannelMappingTool() } { + addCacheTags({ "TPC", "PMT", "CRT" }); // all caches updated at the same time } @@ -265,13 +266,16 @@ void icarusDB::ICARUSChannelMapProviderBase::readFromDatabase() { mfLogInfo() << "Building the channel mapping"; + updateCacheID("TPC"); + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // TPC fragment-based mapping cet::cpu_timer theClockFragmentIDs; theClockFragmentIDs.start(); fTPCFragmentToReadoutMap.clear(); - if (fChannelMappingAlg.BuildTPCFragmentIDToReadoutIDMap(fTPCFragmentToReadoutMap)) - { + if ( + fChannelMappingAlg.BuildTPCFragmentIDToReadoutIDMap(fTPCFragmentToReadoutMap) + ) { throw myException() << "Cannot recover the TPC fragment ID channel map from the database.\n"; } @@ -311,6 +315,8 @@ void icarusDB::ICARUSChannelMapProviderBase::readFromDatabase() { << ", Readout IDs time: " << readoutIDsTime; + updateCacheID("PMT"); + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // PMT channel mapping fPMTFragmentToDigitizerMap.clear(); @@ -331,6 +337,8 @@ void icarusDB::ICARUSChannelMapProviderBase::readFromDatabase() { } + updateCacheID("CRT"); + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Side CRT channel mapping fCRTChannelIDToHWtoSimMacAddressPairMap.clear(); @@ -408,7 +416,7 @@ constexpr unsigned int icarusDB::ICARUSChannelMapProviderBase::PMTfrag // protest if this is a fragment not from the PMT; // but make an exception for old PMT fragment IDs (legacy) - assert(((fragmentID & ~0xFF) == 0x00) || ((fragmentID & ~0xFF) == 0x20)); + assert(((fragmentID & ~0xFF) == 0x0000) || ((fragmentID & ~0xFF) == 0x2000)); return fragmentID & 0xFF; diff --git a/icaruscode/Decode/ChannelMapping/IChannelMapping.h b/icaruscode/Decode/ChannelMapping/IChannelMapping.h index 2be79235d..afc137c2c 100644 --- a/icaruscode/Decode/ChannelMapping/IChannelMapping.h +++ b/icaruscode/Decode/ChannelMapping/IChannelMapping.h @@ -12,12 +12,16 @@ #include "icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h" // LArSoft libraries +#include "larcorealg/CoreUtils/counter.h" #include "larcorealg/CoreUtils/enumerate.h" // C/C++ standard libraries #include // std::transform() #include #include +#include +#include // std::make_error_code() +#include // std::from_char() #include // std::tolower() @@ -126,11 +130,61 @@ class icarusDB::IChannelMapping { return PlaneNames.size(); } + /// Splits `s` into `N` integers of type `Integer` separated by `sep`. + /// @return an array of `N` integers of type `Integer` extracted from `s` + template + static std::array splitIntegers + (std::string const& s, std::string const& sep); + // --- END ----- Utility functions for the implementation -------------------- }; // icarusDB::IChannelMapping +// ----------------------------------------------------------------------------- +// --- template implementation +// ----------------------------------------------------------------------------- +template +std::array icarusDB::IChannelMapping::splitIntegers + (std::string const& s, std::string const& sep) +{ + std::array values; + + auto convert = [](std::string_view s) + { + Integer value; + std::from_chars_result const res + = std::from_chars(s.cbegin(), s.cend(), value); + if (res.ec != std::errc{}) { + std::error_code ec = std::make_error_code(res.ec); + throw std::runtime_error{ "Failed to convert '" + std::string{ s } + + "' into an integer: " + ec.message() + " (code: " + + std::to_string(ec.value()) + ")." + }; + } + return value; + }; + + std::string_view const sv{ s }; + std::size_t c = 0; // cursor + for (std::size_t const i: util::counter(N - 1)) { + std::size_t sc = sv.find(sep, c); + if (sc == std::string_view::npos) { + throw std::runtime_error{ "Separator '" + std::string{ sep } + + "' not found after number #" + std::to_string(i) + " of string '" + + std::string{ s } + "'" + }; + } + values[i] = convert(sv.substr(c, sc)); + c = sc + sep.length(); + } // for + + values[N - 1] = convert(sv.substr(c)); + + return values; +} // icarusDB::IChannelMapping::splitIntegers() + + // ----------------------------------------------------------------------------- #endif // ICARUSCODE_DECODE_CHANNELMAPPING_ICHANNELMAPPING_H diff --git a/icaruscode/Decode/ChannelMapping/IICARUSChannelMap.h b/icaruscode/Decode/ChannelMapping/IICARUSChannelMap.h index 4735c2098..338896d61 100644 --- a/icaruscode/Decode/ChannelMapping/IICARUSChannelMap.h +++ b/icaruscode/Decode/ChannelMapping/IICARUSChannelMap.h @@ -66,6 +66,14 @@ namespace icarusDB { class IICARUSChannelMap; }; class icarusDB::IICARUSChannelMap : virtual public icarusDB::IICARUSChannelMapProvider { + public: + + /// Type of the service provider. + using provider_type = icarusDB::IICARUSChannelMapProvider; + + /// Returns the service provider. + provider_type const* provider() const { return this; } + }; diff --git a/icaruscode/Decode/ChannelMapping/IICARUSChannelMapProvider.h b/icaruscode/Decode/ChannelMapping/IICARUSChannelMapProvider.h index 9546a2cb9..cdc37113d 100644 --- a/icaruscode/Decode/ChannelMapping/IICARUSChannelMapProvider.h +++ b/icaruscode/Decode/ChannelMapping/IICARUSChannelMapProvider.h @@ -10,6 +10,7 @@ // ICARUS libraries #include "icaruscode/Decode/ChannelMapping/RunPeriods.h" #include "icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h" +#include "icaruscode/Utilities/CacheCounter.h" // C/C++ standard libraries #include @@ -39,9 +40,12 @@ namespace icarusDB { class IICARUSChannelMapProvider; } * expected that implementations cache results in their final form and provide * direct access to that cache. * - * + * The interface includes cache tracking support via `util::CacheCounter`. + * Implementations are expected to maintain the tracking of the cache with the + * default (empty) name; by default the cache will be always considered + * outdated. */ -class icarusDB::IICARUSChannelMapProvider { +class icarusDB::IICARUSChannelMapProvider: public util::CacheCounter { public: diff --git a/icaruscode/Decode/ChannelMapping/channelmapping_icarus.fcl b/icaruscode/Decode/ChannelMapping/channelmapping_icarus.fcl index 215f2953e..57d991e2f 100644 --- a/icaruscode/Decode/ChannelMapping/channelmapping_icarus.fcl +++ b/icaruscode/Decode/ChannelMapping/channelmapping_icarus.fcl @@ -14,7 +14,7 @@ ChannelMappingPostGres: { # for icarusDB::ChannelMapSQLite: ChannelMappingSQLite: { - DBFileName: "ChannelMapICARUS_20230829.db" + DBFileName: "ChannelMapICARUS_20240318.db" CalibDBFileName: "crt_gain_reco_data" Tag: @local::ICARUS_Calibration_GlobalTags.crt_gain_reco_data } diff --git a/icaruscode/Decode/DecoderTools/CMakeLists.txt b/icaruscode/Decode/DecoderTools/CMakeLists.txt index f0c7bddf6..9d58722ca 100644 --- a/icaruscode/Decode/DecoderTools/CMakeLists.txt +++ b/icaruscode/Decode/DecoderTools/CMakeLists.txt @@ -27,6 +27,8 @@ set( TOOL_LIBRARIES icarus_signal_processing::Detection icarus_signal_processing::Filters icaruscode::TPC_Utilities_SignalShapingICARUSService_service + icaruscode::PMT_Trigger_Algorithms + icaruscode::Decode_ChannelMapping icaruscode::Decode_DecoderTools icaruscode::Decode_DecoderTools_Dumpers icaruscode::Utilities diff --git a/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc b/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc index 4953e4cd3..a53e2cf2b 100644 --- a/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc +++ b/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc @@ -24,6 +24,8 @@ #include "lardata/DetectorInfoServices/DetectorClocksService.h" #include "lardataalg/DetectorInfo/DetectorTimings.h" #include "lardataalg/Utilities/quantities/spacetime.h" // util::quantities::nanosecond +#include "larcorealg/CoreUtils/enumerate.h" +#include "larcorealg/CoreUtils/counter.h" #include "lardataobj/RawData/ExternalTrigger.h" #include "lardataobj/RawData/TriggerData.h" // raw::Trigger #include "lardataobj/Simulation/BeamGateInfo.h" @@ -36,12 +38,17 @@ #include "icaruscode/Decode/DecoderTools/Dumpers/FragmentDumper.h" // dumpFragment() #include "icaruscode/Decode/DecoderTools/details/KeyedCSVparser.h" #include "icaruscode/Decode/DecoderTools/details/KeyValuesData.h" +#include "icaruscode/Decode/ChannelMapping/IICARUSChannelMap.h" +#include "icaruscode/Decode/ChannelMapping/IICARUSChannelMapProvider.h" +#include "icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h" +#include "icaruscode/Utilities/CacheCounter.h" // util::CacheGuard #include "icarusalg/Utilities/BinaryDumpUtils.h" // hexdump() DEBUG #include #include #include // std::setw(), std::setfill() #include +#include #include #include @@ -158,21 +165,37 @@ namespace daq * the trigger window; only the first one becomes the global * trigger, but we still keep the count of how many happen. * Its value is `0` when trigger happened from elsewhere. + * * `triggerLogicBits`: whether the trigger on this cryostat was from + * adder logic, trigger logic or both. The definition of "both" + * is taken by the hardware, based on an allowed overlapping time + * between the signals of the different logics, properly delayed. * * `LVDSstatus`: information per PMT wall (i.e. TPC; east first, then * west) of the LVDS signals of the discriminated PMT pairs at the * time of the global trigger. All bits are `0` when trigger - * happened elsewhere. Otherwise, the encoding is currently - * implemented in terms of hardware connectors as follows: - * * east wall: `0000` - * * west wall: `0000` + * happened elsewhere. Otherwise, the encoding implements the + * prescription documented in `sbn::ExtraTriggerInfo` using + * information from the PMT channel mapping service + * (`icarusDB::IICARUSChannelMap`) if it was configured. Otherwise, + * the encoding is not guaranteed to be correct, and it is + * implemented in terms of hardware connector ports as follows: + * * east wall: `0000` + * * west wall: `0000` * * For the expected matching with PMT, see the documentation of * `sbn::ExtraTriggerInfo::CryostatInfo::LVDSstatus`. * * `sectorStatus`: information for detector sector (one per TPC), * reporting the discrimination status of the adder signals at - * the time of the global trigger: `00000000 00NnnssS`, with `S` - * (least significant bit) the southernmost adder, and then - * northward until the northernmost one, `N`. + * the time of the global trigger. All bits are `0` when trigger + * happened elsewhere. Otherwise, the encoding implements the + * prescription documented in `sbn::ExtraTriggerInfo` using + * information from the PMT channel mapping service + * (`icarusDB::IICARUSChannelMap`) if it was configured. Otherwise, + * the encoding is not guaranteed to be correct, and it is + * implemented extracting bits from port 3 of the connectors: + * * east wall: `0000'0000'00ss'snnn` with = `0000'0sss` + * and = `0000'0nnn` + * * west wall: `0000'0000'00ss'snnn` with = `0000'0sss` + * and = `0000'0nnn` * * Information may be missing. If a count is not available, its value is * set to `0` (which is an invalid value because their valid range starts @@ -211,6 +234,11 @@ namespace daq * module) to be used. Specifying its tag is mandatory, but if it is * explicitly specified empty, the decoder will try to work around its * absence. + * * `AllowDefaultLVDSmap` (flag, default: `false`): if set, when PMT channel + * mapping service is not available a legacy encoding pattern will be used + * for the `LVDSstatus` bits, and a warning will be printed. If unset and + * the PMT channel mapping database is not available, an exception will be + * thrown. * * `DiagnosticOutput` (flag, default: `false`): prints on console trigger * data diagnostics (including a full dump of the parsed content). * * `Debug` (flag, default: `false`): prints on console decoding debug @@ -245,6 +273,7 @@ namespace daq ExtraInfoPtr fTriggerExtra; BeamGateInfoPtr fBeamGateInfo; art::InputTag fTriggerConfigTag; ///< Data product with hardware trigger configuration. + bool fAllowDefaultLVDSmap; ///< Allow LVDS mapping without mapping database. bool fDiagnosticOutput; ///< Produces large number of diagnostic messages, use with caution! bool fDebug; ///< Use this for debugging this tool @@ -252,9 +281,17 @@ namespace daq detinfo::DetectorTimings const fDetTimings; ///< Detector clocks and timings. + /// PMT channel mapping service provider. + icarusDB::IICARUSChannelMapProvider const* fChannelMap = nullptr; + + ///< Tracks the cache of `IICARUSChannelMapProvider`. + util::CacheGuard fChannelMapCacheGuard; + /// Cached pointer to the trigger configuration of the current run, if any. icarus::TriggerConfiguration const* fTriggerConfiguration = nullptr; + /// Map of LVDS bits. + std::optional fPMTpairMap; /// Creates a `ICARUSTriggerInfo` from a generic fragment. icarus::ICARUSTriggerV3Fragment makeTriggerFragment @@ -295,17 +332,30 @@ namespace daq { return static_cast(a) - static_cast(b); } /// Fills one `cryostat` information of `sbn::ExtraTriggerInfo`. - static sbn::ExtraTriggerInfo::CryostatInfo unpackPrimitiveBits( + sbn::ExtraTriggerInfo::CryostatInfo unpackPrimitiveBits( std::size_t cryostat, bool firstEvent, unsigned long int counts, - std::uint64_t connectors01, std::uint64_t connectors23 - ); + std::uint64_t connectors01, std::uint64_t connectors23, + sbn::bits::triggerLogicMask triggerLogic + ) const; + /// Encodes all the LVDS bits for the specified `cryostat`. + std::array encodeLVDSbits( + short int cryostat, + std::uint64_t connector01word, std::uint64_t connector23word + ) const; + + /// Encodes all the adder bits for the specified `cryostat`. + std::array encodeSectorBits( + short int cryostat, + std::uint64_t connector01word, std::uint64_t connector23word + ) const; + /// Encodes the `connectorWord` LVDS bits from the specified `cryostat` /// and `connector` into the format required by `sbn::ExtraTriggerInfo`. - static std::uint64_t encodeLVDSbits + static std::uint64_t encodeLVDSbitsLegacy (short int cryostat, short int connector, std::uint64_t connectorWord); - static std::uint16_t encodeSectorBits + static std::uint16_t encodeSectorBitsLegacy (short int cryostat, short int connector, std::uint64_t connectorWord); /// Returns the `nBits` bits of `value` from `startBit` on. @@ -315,6 +365,9 @@ namespace daq /// Returns the beam type corresponding to the specified trigger `source`. static sim::BeamType_t simGateType(sbn::triggerSource source); + /// Returns `s` with non-printable characters replaced. + static std::string sanitize(std::string const& s); + }; @@ -325,8 +378,24 @@ namespace daq TriggerDecoderV3::TriggerDecoderV3(fhicl::ParameterSet const &pset) : fDetTimings { art::ServiceHandle()->DataForJob() } + , fChannelMapCacheGuard{ "PMT" } // track the PMT cache only { this->configure(pset); + try { + fChannelMap + = art::ServiceHandle()->provider(); + } + catch (art::Exception const& e) { + if ((e.categoryCode() != art::errors::ServiceNotFound) + || !fAllowDefaultLVDSmap + ) { + throw; + } + mf::LogWarning{ "TriggerDecoderV3" } + << "PMT channel mapping service not available: the correct order" + " of bits in sbn::ExtraTriggerInfo::cryostats is not guaranteed."; + } + if (fChannelMap) fChannelMapCacheGuard.setCache(*fChannelMap); } @@ -348,6 +417,7 @@ namespace daq void TriggerDecoderV3::configure(fhicl::ParameterSet const &pset) { fTriggerConfigTag = pset.get("TrigConfigLabel"); + fAllowDefaultLVDSmap = pset.get("AllowDefaultLVDSmap", false); fDiagnosticOutput = pset.get("DiagnosticOutput", false); fDebug = pset.get("Debug", false); if (pset.has_key("TimeOffset")) { @@ -450,6 +520,24 @@ namespace daq : &(run.getProduct(fTriggerConfigTag)) ; + // refresh the LVDS bit map + if (fChannelMap && fChannelMapCacheGuard.update()) { + // old versions of the database do not have all needed information; + // this is going to be supported since the older run periods will never + // have that information. + fPMTpairMap.emplace(*fChannelMap); + if (!fPMTpairMap->hasMap(icarus::trigger::LVDSbitMaps::Map::PMTpairs)) { + mf::LogWarning("TriggerDecoder") + << "PMT pair mapping could not be extracted." + << " A legacy, hard-coded one will be used."; + } + if (!fPMTpairMap->hasMap(icarus::trigger::LVDSbitMaps::Map::Adders)) { + mf::LogWarning("TriggerDecoder") + << "Adder mapping could not be extracted." + << " A legacy, hard-coded one will be used."; + } + } + } // TriggerDecoderV3::setupRun() @@ -556,7 +644,7 @@ namespace daq << parsedData << std::endl; if (fDebug) { // this grows tiresome quickly when processing many events - std::cout << "Trigger packet content:\n" << data + std::cout << "Trigger packet content:\n" << sanitize(data) << "\nFull trigger fragment dump:" << sbndaq::dumpFragment(fragment) << std::endl; } @@ -702,30 +790,45 @@ namespace daq locationMask = mask(sbn::triggerLocation::CryoEast); else if(triggerLocation == 2) locationMask = mask(sbn::triggerLocation::CryoWest); - else if(triggerLocation == 7) + else if(triggerLocation >= 3) // should be 7 locationMask = mask(sbn::triggerLocation::CryoEast, sbn::triggerLocation::CryoWest); fTriggerExtra->triggerLocationBits = locationMask; // // fill sbn::ExtraTriggerInfo::cryostats // - auto setCryoInfo = [&extra=*fTriggerExtra,isFirstEvent=(triggerID <= 1)] - (std::size_t cryo, icarus::KeyValuesData const& data) + auto setCryoInfo = [ + this,&extra=*fTriggerExtra,isFirstEvent=(triggerID <= 1),data=parsedData + ] + (std::size_t cryo) { - std::string const SIDE = (cryo == sbn::ExtraTriggerInfo::EastCryostat) + std::string const Side + = (cryo == sbn::ExtraTriggerInfo::EastCryostat) ? "EAST": "WEST"; + std::string const CrSide = (cryo == sbn::ExtraTriggerInfo::EastCryostat) ? "Cryo1 EAST": "Cryo2 WEST"; + // trigger logic: 0x01=adders; 0x02=majority; 0x07=both + std::string const triggerLogicKey = "MJ_Adder Source " + Side; + int const triggerLogicCode = data.hasItem(triggerLogicKey) + ? data.getItem(triggerLogicKey).getNumber(0): 0; + sbn::bits::triggerLogicMask triggerLogicMask; + if(triggerLogicCode == 1) + triggerLogicMask = mask(sbn::triggerLogic::PMTAnalogSum); + else if(triggerLogicCode == 2) + triggerLogicMask = mask(sbn::triggerLogic::PMTPairMajority); + else if(triggerLogicCode >= 3) // should be 7 + triggerLogicMask = mask(sbn::triggerLogic::PMTAnalogSum, sbn::triggerLogic::PMTPairMajority); + extra.cryostats[cryo] = unpackPrimitiveBits( cryo, isFirstEvent, - data.getItem(SIDE + " counts").getNumber(0), - data.getItem(SIDE + " Connector 0 and 1").getNumber(0, 16), - data.getItem(SIDE + " Connector 2 and 3").getNumber(0, 16) + data.getItem(CrSide + " counts").getNumber(0), + data.getItem(CrSide + " Connector 0 and 1").getNumber(0, 16), + data.getItem(CrSide + " Connector 2 and 3").getNumber(0, 16), + triggerLogicMask ); }; - if (triggerLocation & 1) - setCryoInfo(sbn::ExtraTriggerInfo::EastCryostat, parsedData); - if (triggerLocation & 2) - setCryoInfo(sbn::ExtraTriggerInfo::WestCryostat, parsedData); + if (triggerLocation & 1) setCryoInfo(sbn::ExtraTriggerInfo::EastCryostat); + if (triggerLocation & 2) setCryoInfo(sbn::ExtraTriggerInfo::WestCryostat); // // absolute time trigger (raw::ExternalTrigger) @@ -809,29 +912,27 @@ namespace daq sbn::ExtraTriggerInfo::CryostatInfo TriggerDecoderV3::unpackPrimitiveBits( std::size_t cryostat, bool firstEvent, unsigned long int counts, - std::uint64_t connectors01, std::uint64_t connectors23 - ) { + std::uint64_t connectors01, std::uint64_t connectors23, + sbn::bits::triggerLogicMask triggerLogic + ) const { sbn::ExtraTriggerInfo::CryostatInfo cryoInfo; // there is (or was?) a bug on the first event in the run, // which would make this triggerCount wrong cryoInfo.triggerCount = firstEvent? 0UL: counts, - cryoInfo.LVDSstatus = { - encodeLVDSbits(cryostat, 2 /* any of the connectors */, connectors23), - encodeLVDSbits(cryostat, 0 /* any of the connectors */, connectors01) - }; + cryoInfo.LVDSstatus = encodeLVDSbits(cryostat, connectors01, connectors23); - cryoInfo.sectorStatus = { - encodeSectorBits(cryostat, 2 /* any of the connectors */, connectors23), - encodeSectorBits(cryostat, 0 /* any of the connectors */, connectors01) - }; + cryoInfo.sectorStatus + = encodeSectorBits(cryostat, connectors01, connectors23); + + cryoInfo.triggerLogicBits = static_cast(triggerLogic); return cryoInfo; } // TriggerDecoderV3::unpackPrimitiveBits() - std::uint64_t TriggerDecoderV3::encodeLVDSbits + std::uint64_t TriggerDecoderV3::encodeLVDSbitsLegacy (short int cryostat, short int connector, std::uint64_t connectorWord) { /* @@ -851,9 +952,182 @@ namespace daq assert((connectorWord & 0x00FF'FFFF'00FF'FFFF) == ((msw << 32ULL) | lsw)); std::swap(lsw, msw); return (msw << 32ULL) | lsw; + } // TriggerDecoderV3::encodeLVDSbitsLegacy() + + + std::array + TriggerDecoderV3::encodeLVDSbits( + short int cryostat, + std::uint64_t connector01word, std::uint64_t connector23word + ) const { + + if (!fPMTpairMap + || !fPMTpairMap->hasMap(icarus::trigger::LVDSbitMaps::Map::PMTpairs) + ) { + // use legacy approach + return { + encodeLVDSbitsLegacy + (cryostat, sbn::ExtraTriggerInfo::EastPMTwall, connector01word), + encodeLVDSbitsLegacy + (cryostat, sbn::ExtraTriggerInfo::WestPMTwall, connector23word) + }; + } // if no channel mapping + + std::array outputWords; + outputWords.fill(0); + + /* + * The (first) two `LVDSstatus` 64-bit words are initialized according to + * the prescription in the class documentation: LVDS bits from PMT with + * lower channel ID end in lower (least significant) bits in `LVDSstatus`. + * + * The algorithm will fill the bits of the output words in sequence, + * each time picking the value from the appropriate bits in the input + * connector words. The correct bit is determined by a precooked map. + */ + + // connector words have the first connector in the most significant 32 bits: + std::array const connectorBits { + static_cast(connector01word >> 32ULL & 0x00FF'FFFFULL), + static_cast(connector01word & 0x00FF'FFFFULL), + static_cast(connector23word >> 32ULL & 0x00FF'FFFFULL), + static_cast(connector23word & 0x00FF'FFFFULL) + }; + + // --- BEGIN DEBUG ----- + mf::LogTrace debugLog{ "TriggerDecoderV3" }; + debugLog << "Cryostat " << cryostat << ":"; + for (auto const [ iConn, word ]: util::enumerate(connectorBits)) + debugLog << " [" << iConn << "]=0x" << std::hex << word << std::dec; + // --- END DEBUG ------- + + auto const mask + = [](auto bitNo){ return 1ULL << static_cast(bitNo); }; + + using icarus::trigger::PMTpairBitID; + for (std::size_t const PMTwall: + { sbn::ExtraTriggerInfo::EastPMTwall, sbn::ExtraTriggerInfo::WestPMTwall } + ) { + + for (auto const bit: util::counter(64)) { + + PMTpairBitID const bitID{ (unsigned) cryostat, PMTwall, bit }; + + icarus::trigger::LVDSHWbitID const source + = fPMTpairMap->bitSource(bitID).source; + + if (!source) continue; // bit not mapped to anything + + assert(source.cryostat == cryostat); + bool const bitValue + = connectorBits.at(source.connector) & mask(source.bit); + + if (bitValue) { + outputWords[PMTwall] |= mask(bit); + debugLog << "\nLogic LVDS bit " << bitID << " from HW bit " << source; + } + + } // bit + } // PMT wall + + // --- BEGIN DEBUG ----- + for (auto const [ iWord, word ]: util::enumerate(outputWords)) { + debugLog<< "\nPMT pair status[" << cryostat << "][" << iWord << "]=" + << icarus::ns::util::bin(word) + << " (0x" << std::hex << word << std::dec << ")"; + } + // --- END DEBUG ------- + + return outputWords; } // TriggerDecoderV3::encodeLVDSbits() + std::array + TriggerDecoderV3::encodeSectorBits( + short int cryostat, + std::uint64_t connector01word, std::uint64_t connector23word + ) const { + + if (!fPMTpairMap + || !fPMTpairMap->hasMap(icarus::trigger::LVDSbitMaps::Map::Adders) + ) { + // use legacy approach + return { + encodeSectorBitsLegacy(cryostat, 2, connector23word), + encodeSectorBitsLegacy(cryostat, 0, connector01word) + }; + } // if no channel mapping + + std::array outputWords; + outputWords.fill(0); + + /* + * The (first) two `LVDSstatus` 64-bit words are initialized according to + * the prescription in the class documentation: LVDS bits from PMT with + * lower channel ID end in lower (least significant) bits in `LVDSstatus`. + * + * The algorithm will fill the bits of the output words in sequence, + * each time picking the value from the appropriate bits in the input + * connector words. The correct bit is determined by a precooked map. + */ + + // connector words have the first connector in the most significant 32 bits: + std::array const connectorBits { + static_cast(connector01word >> 32ULL & 0xFF00'0000ULL), + static_cast(connector01word & 0xFF00'0000ULL), + static_cast(connector23word >> 32ULL & 0xFF00'0000ULL), + static_cast(connector23word & 0xFF00'0000ULL) + }; + + // --- BEGIN DEBUG ----- + mf::LogTrace debugLog{ "TriggerDecoderV3" }; + debugLog << "Cryostat " << cryostat << ":"; + for (auto const [ iConn, word ]: util::enumerate(connectorBits)) + debugLog << " [" << iConn << "]=0x" << std::hex << word << std::dec; + // --- END DEBUG ------- + + auto const mask + = [](auto bitNo){ return 1ULL << static_cast(bitNo); }; + + using icarus::trigger::AdderBitID; + for (std::size_t const PMTwall: + { sbn::ExtraTriggerInfo::EastPMTwall, sbn::ExtraTriggerInfo::WestPMTwall } + ) { + + for (auto const bit: util::counter(16)) { + + AdderBitID const bitID{ (unsigned) cryostat, PMTwall, bit }; + + icarus::trigger::LVDSHWbitID const source + = fPMTpairMap->bitSource(bitID).source; + + if (!source) continue; // bit not mapped to anything + + assert(source.cryostat == cryostat); + bool const bitValue + = connectorBits.at(source.connector) & mask(source.bit); + + if (bitValue) { + outputWords[PMTwall] |= mask(bit); + debugLog + << "\nLogic adder bit " << bitID << " from HW bit " << source; + } + + } // bit + } // PMT wall + + // --- BEGIN DEBUG ----- + for (auto const [ iWord, word ]: util::enumerate(outputWords)) { + debugLog<< "\nAdder status[" << cryostat << "][" << iWord << "]=" + << icarus::ns::util::bin(word) + << " (0x" << std::hex << word << std::dec << ")"; + } + // --- END DEBUG ------- + + return outputWords; + } // TriggerDecoderV3::encodeSectorBits() + + template constexpr T TriggerDecoderV3::bits(T value) { constexpr T nBitsMask = (nBits == sizeof(T)*8)? ~T{0}: T((1 << nBits) - 1); @@ -861,7 +1135,7 @@ namespace daq } - std::uint16_t TriggerDecoderV3::encodeSectorBits + std::uint16_t TriggerDecoderV3::encodeSectorBitsLegacy (short int cryostat, short int connector, std::uint64_t connectorWord) { /* @@ -871,8 +1145,7 @@ namespace daq * * connector 2: west wall south, LSB the south-most one * * connector 3: west wall north, LSB the south-most one * Target: - * * [0]: 00000000 00nnnsss (east wall) - * * [1]: 00000000 00nnnsss (west wall) + * * 00000000 00sssnnn (both east and wall) */ // connector (32 bit): 00000SSS LVDSLVDS LVDSLVDS LVDSLVDS @@ -880,12 +1153,11 @@ namespace daq constexpr std::size_t FirstSectorBit = 27; constexpr std::size_t SectorBitsPerConnector = 3; - // TODO check the order of the bits: [ ] the blocks of 3 and [ ] within each block return static_cast( - (bits(connectorWord) << SectorBitsPerConnector) - | (bits(connectorWord) ) + (bits(connectorWord) << SectorBitsPerConnector) + | (bits(connectorWord)) ); - } // TriggerDecoderV3::encodeSectorBits() + } // TriggerDecoderV3::encodeSectorBitsLegacy() sim::BeamType_t TriggerDecoderV3::simGateType(sbn::triggerSource source) @@ -906,6 +1178,35 @@ namespace daq } // TriggerDecoderV3::simGateType() + std::string TriggerDecoderV3::sanitize(std::string const& s) { + + static char const HexChar[] = "012345789ABCDEF"; + + std::string ss; + ss.reserve(s.size()); + + for (char c: s) { + if (!std::isprint(static_cast(c)) && (c != '\n')) { + ss += '<'; + switch (c) { + case '\x00': ss += "NUL"; break; + case '\x09': ss += "TAB"; break; + case '\x1E': ss += "ESC"; break; + default: + ss += HexChar[(c >> 4) & 0xF]; + ss += HexChar[c & 0xF]; + } // switch + ss += '>'; + } + else + ss.push_back(c); + + } // for + + return ss; + } // TriggerDecoderV3::sanitize() + + DEFINE_ART_CLASS_TOOL(TriggerDecoderV3) } diff --git a/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx b/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx new file mode 100644 index 000000000..b59a92fc1 --- /dev/null +++ b/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx @@ -0,0 +1,419 @@ +/** + * @file icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx + * @brief Utilities for mapping CAEN V1730B LVDS channels to PMT channels. + * @author Gianluca Petrillo (petrillo@slac.stanford.edu) + * @date March 10, 2024 + * @see icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h + * + */ + +// library header +#include "icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h" + +// ICARUS libraries +#include "icaruscode/Decode/ChannelMapping/IICARUSChannelMapProvider.h" +#include "icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h" + +// framework libraries +#include "larcorealg/CoreUtils/enumerate.h" +#include "larcorealg/CoreUtils/counter.h" +#include "messagefacility/MessageLogger/MessageLogger.h" +#include "cetlib_except/exception.h" + +// C/C++ standard libraries +#include // std::sort() +#include // std::dec, std::hex +#include // std::div() +#include + + +// ----------------------------------------------------------------------------- +// --- icarus::trigger::PMTpairBitID implementation +// ----------------------------------------------------------------------------- +std::ostream& icarus::trigger::operator<< + (std::ostream& out, PMTpairBitID const& bit) +{ + return out << static_cast(bit); +} + + +// ----------------------------------------------------------------------------- +// --- icarus::trigger::LVDSHWbitID implementation +// ----------------------------------------------------------------------------- +std::ostream& icarus::trigger::operator<< + (std::ostream& out, LVDSHWbitID const& bit) +{ + if (!bit) return out << ""; + auto const fillch = out.fill(); // width is reset to 0, fill character is not + out << bit.cryostat << '/' << bit.connector << '/' + << std::setw(2) << std::setfill('0') << bit.bit << std::setfill(fillch); + return out; +} + + +// ----------------------------------------------------------------------------- +auto icarus::trigger::LVDSHWbitID::fromIndex(Index_t index) -> LVDSHWbitID { + auto const [ cryostat, cb ] = std::div(index, NConnectorsPerCryostat * 32); + auto const [ connector, bit ] = std::div(cb, 32); + return { + (unsigned short int) cryostat, + (Connector_t) connector, + (ConnectorBit_t) bit + }; +} // icarus::trigger::LVDSHWbitID::fromIndex() + + +// ----------------------------------------------------------------------------- +// --- icarus::trigger::LVDSbitMaps implementation +// ----------------------------------------------------------------------------- +bool icarus::trigger::LVDSbitMaps::hasMap(Map map) const { + switch (map) { + case Map::PMTpairs: return !fLVDSinfoMap.empty(); + case Map::Adders: return !fAdderInfoMap.empty(); + default: + throw Exception{ Errors::Error } + << "Logic error: hasMap() does not support map type #" + << static_cast(map) << "!\n"; + } // switch +} // icarus::trigger::LVDSbitMaps::hasMap() + + +// ----------------------------------------------------------------------------- +auto icarus::trigger::LVDSbitMaps::bitSource(PMTpairBitID const& bit) const + -> LVDSinfo_t +{ + return fLVDSinfoMap.at(bit.index()); +} + + +// ----------------------------------------------------------------------------- +auto icarus::trigger::LVDSbitMaps::bitSource(AdderBitID const& bit) const + -> AdderInfo_t +{ + return fAdderInfoMap.at(bit.index()); +} + + +// ----------------------------------------------------------------------------- +std::vector> +icarus::trigger::LVDSbitMaps::channelPairs() const { + + std::vector> pairs; + for (LVDSinfo_t const& info: fLVDSinfoMap) { + if (!info.channels.empty()) pairs.push_back(info.channels); + } + + return pairs; +} + + +// ----------------------------------------------------------------------------- +void icarus::trigger::LVDSbitMaps::buildAllMaps + (icarusDB::IICARUSChannelMapProvider const& channelMap) +{ + buildMaps(channelMap, { Map::PMTpairs, Map::Adders }); +} + + +// ----------------------------------------------------------------------------- +void icarus::trigger::LVDSbitMaps::buildMaps( + icarusDB::IICARUSChannelMapProvider const& channelMap, + std::initializer_list maps +) { + + fPMTchannelInfo = buildChannelInfo(channelMap); + + for (Map map: maps) { + switch (map) { + case Map::PMTpairs: + fLVDSinfoMap = buildPMTpairSourceMap(channelMap); + break; + case Map::Adders: + fAdderInfoMap = buildAdderSourceMap(channelMap); + break; + } // switch + } // for + +} // icarus::trigger::LVDSbitMaps::buildMaps() + + +// ----------------------------------------------------------------------------- +auto icarus::trigger::LVDSbitMaps::buildChannelInfo + (icarusDB::IICARUSChannelMapProvider const& channelMap) const + -> std::vector +{ + std::vector PMTchannelDB; + + mf::LogTrace debugLog{ "LVDSbitMaps" }; + debugLog << "LVDSbitMaps::buildChannelInfo() collecting channel info:"; + + for (unsigned int const fragmentID: util::counter(0x2000, 0x2018)) { + if (!channelMap.hasPMTDigitizerID(fragmentID)) { + throw Exception{ Errors::NoPMTfragment } + << "PMT fragment ID 0x" << std::hex << fragmentID << std::dec + << " not available in the channel mapping database.\n"; + } + + for (icarusDB::PMTChannelInfo_t const& info + : channelMap.getPMTchannelInfo(fragmentID) + ) { + unsigned short int const cryostat = info.channelID / NChannelsPerCryostat; + + debugLog << "\n[#" << PMTchannelDB.size() << "] CH=" << info.channelID + << " " << cryostat << "/" << info.LVDSconnector << "/" << info.LVDSbit; + + LVDSHWbitID const PMTpairBit = info.hasLVDSinfo() + ? LVDSHWbitID{ cryostat, info.LVDSconnector, info.LVDSbit } + : LVDSHWbitID{} + ; + LVDSHWbitID const adderBit = info.hasAdderInfo() + ? LVDSHWbitID{ cryostat, info.adderConnector, info.adderBit } + : LVDSHWbitID{} + ; + + PMTchannelDB.push_back(LVDSchannel_t{ + info.channelID // .channel + , PMTpairBit // .PMTpairSource + , adderBit // .adderSource + }); + } // for channel in fragment + } // for fragment ID + + assert(PMTchannelDB.size() == NChannels); // insider knowledge... + + std::sort(PMTchannelDB.begin(), PMTchannelDB.end()); + + return PMTchannelDB; + +} // icarus::trigger::LVDSbitMaps::buildChannelInfo() + + +// ----------------------------------------------------------------------------- +auto icarus::trigger::LVDSbitMaps::buildPMTpairSourceMap + (icarusDB::IICARUSChannelMapProvider const& channelMap) const + -> std::vector +{ + // note that this function must support also the case where there is no PMT + // pair information in the channel mapping database, in which case all + // connector information will be "invalid" and protocol demands an empty map + + assert(fPMTchannelInfo.size() == NChannels); // insider knowledge... + + /* + * The logic of the filling is a bit complicated. Class documentation explains + * it and shows it to be: + * + * east cryostat, east wall: `0x00aAbBcC'00dDeEfF`, channel `0` the most + * significant bit in `a` (bit #55) and channel `45` the one in `d` (bit 23); + * east cryostat, west wall: `0x00aAbBcC'00dDeEfF`, channel `90` the most + * significant bit in `a` (bit #55) and channel `135` the one in `d` (bit 23); + * west cryostat, east wall: `0x00aAbBcC'00dDeEfF`, channel `180` the most + * significant bit in `a` (bit #55) and channel `225` the one in `d` (bit 23); + * west cryostat, west wall: `0x00aAbBcC'00dDeEfF`, channel `270` the most + * significant bit in `a` (bit #55) and channel `315` the one in `d` (bit 23). + * + * How we get there: + * * we sort the filling with an ordered progression of channels, lowest + * first, and because most LVDS bit are associated to two channels, we + * track the channels we already covered when processing the other PMT + * channel of the pair, and skip them when met those LVDS bit again + * * the destination bit is also an ordered progression within the word, + * but inverted because the _most_ significant bit is assigned to the + * _lowest_ channels + */ + std::vector sourceMap{ PMTpairBitID::NIndices() }; + std::vector HWtoLogicID{ LVDSHWbitID::NIndices() }; + + unsigned int nBits = 0; + for (LVDSchannel_t const& info: fPMTchannelInfo) { // in PMT channel order + + if (!info.PMTpairSource) continue; // skip invalid + + LVDSHWbitID::Index_t const sourceIndex = info.PMTpairSource.index(); + + PMTpairBitID logicBit = HWtoLogicID[sourceIndex]; + + LVDSinfo_t* mapInfo = logicBit? &(sourceMap[logicBit.index()]): nullptr; + if (!mapInfo) { // new bit: set the source + // which logic bit to set: + logicBit.cryostat = info.PMTpairSource.cryostat; + assert(logicBit.cryostat == info.channel / NChannelsPerCryostat); + logicBit.PMTwall + = info.channel/(NChannelsPerCryostat/PMTpairBitID::NPMTwallsPerCryostat) + % PMTpairBitID::NCryostats; + logicBit.statusBit = 47 - (nBits++ % 48); // 0 => 47, 1 => 46,..., 47 => 0 + if (logicBit.statusBit >= 24) + logicBit.statusBit += 8; // 0 => 0,..., 23 => 23, 24 => 32, 25 => 33,... + + assert(logicBit); + HWtoLogicID[sourceIndex] = logicBit; + mapInfo = &(sourceMap[logicBit.index()]); + mapInfo->ID = logicBit; + mapInfo->source = info.PMTpairSource; + } + assert(mapInfo); + assert(mapInfo->source == info.PMTpairSource); + + mapInfo->channels.push_back(info.channel); + // check that they are sorted (the input list should be sorted already...) + assert((mapInfo->channels.size() == 1) + || ( + mapInfo->channels[mapInfo->channels.size()-2] + < mapInfo->channels[mapInfo->channels.size()-1] + )); + + } // for fPMTchannelInfo + + + // --- BEGIN DEBUG ----- + mf::LogTrace debugLog{ "LVDSbitMaps" }; + debugLog << "Final PMT pair bit map:"; + for (auto const& [ bit, info ]: util::enumerate(sourceMap)) { + debugLog << "\n[" << std::setw(3) << bit << "] logic=" + << PMTpairBitID::fromIndex(bit) << " => HW="; + if (info.source) { + debugLog << info.source << " (ix=" + << std::setw(3) << info.source.index() << ") " + << info.channels.size() << " channels {"; + for (raw::Channel_t const channel: info.channels) + debugLog << " " << std::setw(3) << channel; + debugLog << " }"; + } + else { + debugLog << "(invalid)"; + } + } + // --- END DEBUG ------- + + assert((nBits == 0) || (nBits == 192)); // 0 if no info in mapping database + return (nBits == 0)? std::vector{}: sourceMap; + +} // icarus::trigger::LVDSbitMaps::buildPMTpairSourceMap() + + +// ----------------------------------------------------------------------------- +auto icarus::trigger::LVDSbitMaps::buildAdderSourceMap + (icarusDB::IICARUSChannelMapProvider const& channelMap) const + -> std::vector +{ + // note that this function must support also the case where there is no adder + // information in the channel mapping database, in which case all connector + // information will be "invalid" and the protocol demands an empty map + + assert(fPMTchannelInfo.size() == NChannels); // insider knowledge... + + /* + * The logic of the filling is a bit complicated. Class documentation explains + * it and shows it to be: + * + * east cryostat, east wall: `0x0a000000'0A000000`, channel `0` the most + * significant bit in `a` (bit #58) and channel `45` the one in `A` (bit 26); + * east cryostat, west wall: `0x0a000000'0A000000`, channel `90` the most + * significant bit in `a` (bit #58) and channel `135` the one in `d` (bit 26); + * west cryostat, east wall: `0x0a000000'0A000000`, channel `180` the most + * significant bit in `a` (bit #58) and channel `225` the one in `d` (bit 26); + * west cryostat, west wall: `0x0a000000'0A000000`, channel `270` the most + * significant bit in `a` (bit #58) and channel `315` the one in `d` (bit 26). + * + * How we get there: + * * we sort the filling with an ordered progression of channels, lowest + * first, and because adder bit are associated to 15 channels, we + * track the channels we already covered when processing the other PMT + * channels of the adder, and skip them when met those LVDS bit again + * * the destination bit is also an ordered progression within the word, + * but inverted because the _most_ significant bit is assigned to the + * _lowest_ channels + */ + std::vector sourceMap{ AdderBitID::NIndices() }; + std::vector HWtoLogicID{ LVDSHWbitID::NIndices() }; + + unsigned int nBits = 0; + for (LVDSchannel_t const& info: fPMTchannelInfo) { // in PMT channel order + + if (!info.adderSource) continue; // skip invalid + + LVDSHWbitID::Index_t const sourceIndex = info.adderSource.index(); + + AdderBitID logicBit = HWtoLogicID[sourceIndex]; + + AdderInfo_t* mapInfo = logicBit? &(sourceMap[logicBit.index()]): nullptr; + if (!mapInfo) { // new bit: set the source + // which logic bit to set: + logicBit.cryostat = info.adderSource.cryostat; + assert(logicBit.cryostat == info.channel / NChannelsPerCryostat); + logicBit.PMTwall + = info.channel / (NChannelsPerCryostat/AdderBitID::NPMTwallsPerCryostat) + % AdderBitID::NCryostats; + logicBit.statusBit = 5 - (nBits++ % 6); // 0/6/... => 5, 1/7/... => 4, ... + + assert(logicBit); + HWtoLogicID[sourceIndex] = logicBit; + mapInfo = &(sourceMap[logicBit.index()]); + mapInfo->ID = logicBit; + mapInfo->source = info.adderSource; + } + assert(mapInfo); + assert(mapInfo->source == info.adderSource); + + mapInfo->channels.push_back(info.channel); + // check that they are sorted (the input list should be sorted already...) + assert((mapInfo->channels.size() == 1) + || ( + mapInfo->channels[mapInfo->channels.size()-2] + < mapInfo->channels[mapInfo->channels.size()-1] + )); + + } // for PMTchannelDB + + + // --- BEGIN DEBUG ----- + mf::LogTrace debugLog{ "LVDSbitMaps" }; + debugLog << "Final adder bit map:"; + for (auto const& [ bit, info ]: util::enumerate(sourceMap)) { + debugLog << "\n[" << std::setw(3) << bit << "] logic=" + << AdderBitID::fromIndex(bit) << " => HW="; + if (info.source) { + debugLog << info.source << " (ix=" + << std::setw(3) << info.source.index() << ") " + << info.channels.size() << " channels {"; + for (raw::Channel_t const channel: info.channels) + debugLog << " " << std::setw(3) << channel; + debugLog << " }"; + } + else { + debugLog << "(invalid)"; + } + } + // --- END DEBUG ------- + + assert((nBits == 0) || (nBits == 24)); // 0 if no info in mapping database + return (nBits == 0)? std::vector{}: sourceMap; + +} // icarus::trigger::LVDSbitMaps::buildAdderSourceMap() + + +// ----------------------------------------------------------------------------- +std::string icarus::trigger::LVDSbitMaps::errorMessage(Errors code) { + using namespace std::string_literals; + switch (code) { + case Errors::NoError: + return "no error"s; + case Errors::Error: + return "(unspecified) error"s; + case Errors::NoDatabase: + return "channel mapping database not configured"s; + case Errors::NoPMTfragment: + return "channel mapping database does not have PMT fragment information"s; + case Errors::NoPairBitInformation: + return "channel mapping database does not have PMT pair bit information"s; + case Errors::NoAdderBitInformation: + return "channel mapping database does not have adder bit information"s; + case Errors::Unknown: + return "unknown error"s; + default: + return "unexpected error code "s + std::to_string(static_cast(code)); + } // switch +} // icarus::trigger::LVDSbitMaps::errorMessage() + + +// ----------------------------------------------------------------------------- diff --git a/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h b/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h new file mode 100644 index 000000000..d074fae4a --- /dev/null +++ b/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h @@ -0,0 +1,629 @@ +/** + * @file icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h + * @brief Utilities for mapping CAEN V1730B LVDS channels to PMT channels. + * @author Gianluca Petrillo (petrillo@slac.stanford.edu) + * @date March 10, 2024 + * @see icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx + * + */ + +#ifndef ICARUSCODE_PMT_TRIGGER_ALGORITHMS_LVDSBITMAPS_H +#define ICARUSCODE_PMT_TRIGGER_ALGORITHMS_LVDSBITMAPS_H + +// LArSoft and framework libraries +#include "lardataobj/RawData/OpDetWaveform.h" // raw::Channel_t +#include "cetlib_except/coded_exception.h" + +// C/C++ standard libraries +#include +#include // std::setw(), std::setfill() +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- +// forward declarations +namespace icarus::trigger { + + struct PMTpairBitID; + struct AdderBitID; + struct LVDSHWbitID; + struct LVDSinfo_t; + struct AdderInfo_t; + + class LVDSbitMaps; + + std::ostream& operator<< (std::ostream& out, PMTpairBitID const& bit); + std::ostream& operator<< (std::ostream& out, LVDSHWbitID const& bit); + +} // namespace icarus::trigger + +namespace icarusDB { class IICARUSChannelMapProvider; } + + +// ----------------------------------------------------------------------------- +/// Identifier of a "logical" LVDS bit for PMT pairs +/// following `sbn::ExtraTriggerInfo` convention. +namespace icarus::trigger::details { + template struct PMTwallBitID; + template + std::ostream& operator<< + (std::ostream& out, PMTwallBitID const& bit); +} +template +struct icarus::trigger::details::PMTwallBitID { + + using BitID_t = PMTwallBitID; ///< This ID type. + using StatusBit_t = unsigned short int; ///< Type for bit in the status word. + using Index_t = unsigned short int; ///< Type for bit ID index. + + /// How many cryostats in the detector. + static constexpr std::size_t NCryostats = 2; + + /// How many PMT walls per cryostat. Each has one status word. + static constexpr std::size_t NPMTwallsPerCryostat = 2; + + /// How many bits in a status word. + static constexpr unsigned short int NStatusWordBits = MaxBits; + + + /// Special value to identify the absence of cryostat information. + static constexpr std::size_t NoCryostat + = std::numeric_limits::max(); + + /// Special value to identify the absence of PMT wall information. + static constexpr std::size_t NoPMTwall + = std::numeric_limits::max(); + + /// Special value to identify the absence of a status bit. + static constexpr StatusBit_t NoStatusBit + = std::numeric_limits::max(); + + /// Special value for an invalid bit index. + static constexpr Index_t InvalidIndex = std::numeric_limits::max(); + + + // --- BEGIN --- Data members -------------------------------------------- + + /// The cryostat the bit comes from (`sbn::ExtraTriggerInfo` convention). + std::size_t cryostat = NoCryostat; + + /// The PMT wall the bit comes from (`sbn::ExtraTriggerInfo` convention). + std::size_t PMTwall = NoPMTwall; + + /// The number of bit in the status word (`0` is the least significant one). + StatusBit_t statusBit = NoStatusBit; + + // --- END ----- Data members -------------------------------------------- + + /// Returns if the cryostat is set; ID is valid even if this is `false`. + constexpr bool hasCryostat() const { return cryostat != NoCryostat; } + + /// Returns if the PMT wall is set; ID is valid even if this is `false`. + constexpr bool hasPMTwall() const { return PMTwall != NoPMTwall; } + + /// Returns an "unique" index for this information. + /// Supports the absence of PMT wall or cryostat (not of bit). + constexpr Index_t index() const + { + if (!isValid()) return InvalidIndex; + std::size_t const nWalls = hasPMTwall() + ? (PMTwall + (hasCryostat()? cryostat: 0) * NPMTwallsPerCryostat): 0; + return statusBit + nWalls * NStatusWordBits; + } + + + /// Returns whether this ID is valid + /// (does not require `hasCryostat()` nor `hasPMTwall()`). + constexpr bool isValid() const { return statusBit != NoStatusBit; } + + /// Returns whether this ID is valid + /// (does not require `hasCryostat()` nor `hasPMTwall()`). + constexpr operator bool() const { return isValid(); } + + + /// Rebuilds an ID object from its index. + static constexpr BitID_t fromIndex(Index_t index); + + /// Returns the number of possible indices (`0` to `NIndices() - 1`). + static constexpr Index_t NIndices() + { return NCryostats * NPMTwallsPerCryostat * NStatusWordBits; } + +}; // icarus::trigger::details::PMTwallBitID + + +// ----------------------------------------------------------------------------- +/// Identifier of a "logical" LVDS bit for PMT pairs +/// following `sbn::ExtraTriggerInfo` convention. +struct icarus::trigger::PMTpairBitID: public details::PMTwallBitID<64> { + + using BaseID_t = details::PMTwallBitID<64>; + + /// Rebuilds an ID object from its index. + static PMTpairBitID fromIndex(Index_t index) + { return { BaseID_t::fromIndex(index) }; } + +}; // icarus::trigger::PMTpairBitID + + +// ----------------------------------------------------------------------------- +/// Identifier of a "logical" LVDS bit for adders +/// following `sbn::ExtraTriggerInfo` convention. +struct icarus::trigger::AdderBitID: public details::PMTwallBitID<16> { + + using BaseID_t = details::PMTwallBitID<16>; + + /// Rebuilds an ID object from its index. + static AdderBitID fromIndex(Index_t index) + { return { BaseID_t::fromIndex(index) }; } + +}; // icarus::trigger::AdderBitID + + +// ----------------------------------------------------------------------------- +/// Identifier of a "hardware" LVDS bit featured in the PMT channel mapping +/// databases. +struct icarus::trigger::LVDSHWbitID { + + using Connector_t = unsigned short int; ///< Type for connector number. + using ConnectorBit_t = unsigned short int; ///< Type for connector bit. + using Index_t = unsigned short int; ///< Type for LVDS bit ID index. + + /// How many cryostats in the detector. + static constexpr unsigned short int NCryostats = PMTpairBitID::NCryostats; + + /// How many 32-bit connectors come out of each cryostat. + static constexpr unsigned short int NConnectorsPerCryostat = 4; + + /// Special value to identify the absence of cryostat information. + static constexpr unsigned short int NoCryostat + = std::numeric_limits::max(); + + /// Special value to identify the absence of a connector. + static constexpr Connector_t NoConnector + = std::numeric_limits::max(); + + /// Special value to identify the absence of a connector bit. + static constexpr ConnectorBit_t NoConnectorBit + = std::numeric_limits::max(); + + /// Special value for an invalid bit index. + static constexpr Index_t InvalidIndex = std::numeric_limits::max(); + + + // --- BEGIN --- Data members -------------------------------------------- + + /// The cryostat the bit comes from (`sbn::ExtraTriggerInfo` convention). + unsigned short int cryostat = NoCryostat; + + /// The number of connector within the appropriate wall. + Connector_t connector = NoConnector; + + /// The bit index within the 32-bit connector word (0 = least significant). + ConnectorBit_t bit = NoConnectorBit; + + // --- END ----- Data members -------------------------------------------- + + /// Returns if the cryostat is set; ID is valid even if this is `false`. + constexpr bool hasCryostat() const { return cryostat != NoCryostat; } + + /// Returns an "unique" index for this information. + constexpr Index_t index() const + { + return isValid() + ? ((hasCryostat()? (cryostat * NConnectorsPerCryostat * 32): 0) + + connector * 32 + bit) + : InvalidIndex; + } + + + /// Returns whether this ID is valid (does not require `hasCryostat()`). + constexpr bool isValid() const + { return (connector != NoConnector) && (bit != NoConnectorBit); } + + /// Returns whether this ID is valid (does not require `hasCryostat()`). + constexpr operator bool() const { return isValid(); } + + + /// Rebuilds an ID object from its index. + static LVDSHWbitID fromIndex(Index_t index); + + /// Returns the number of possible indices (`0` to `NIndices() - 1`). + static constexpr Index_t NIndices() + { return NCryostats * NConnectorsPerCryostat * 32; } + +}; // icarus::trigger::LVDSHWbitID + + +// ----------------------------------------------------------------------------- +/// Information about a PMT pair: source bit and connected channels. +struct icarus::trigger::LVDSinfo_t { + + PMTpairBitID ID; ///< Identifier of this bit. + + LVDSHWbitID source; ///< Identifier of the hardware LVDS bit feeding this one. + + std::vector channels; ///< PMT channels covered by this bit. + + /// Returns whether this information is fully valid (both channel and ID). + operator bool() const { return source; } + + /// Order operator: sorted after the ID. + bool operator< (LVDSinfo_t const& other) const { return ID < other.ID; } + +}; // icarus::trigger::LVDSinfo_t + + +// ----------------------------------------------------------------------------- +/// Description of a (logical) adder bit and additional information. +struct icarus::trigger::AdderInfo_t { + + AdderBitID ID; ///< Identifier of this bit. + + LVDSHWbitID source; ///< Identifier of the hardware LVDS bit feeding this one. + + std::vector channels; ///< PMT channels covered by this bit. + + /// Returns whether this information is fully valid (both channel and ID). + operator bool() const { return source; } + + /// Order operator: sorted after the source index. + bool operator< (AdderInfo_t const& other) const { return ID < other.ID; } + +}; // icarus::trigger::AdderInfo_t + + +// ----------------------------------------------------------------------------- +/** + * @brief Utility to build and maintain ICARUS trigger LVDS bit maps. + * + * This object caches the relations between the LVDS bits. The maps include: + * * from logic LVDS bit ID to hardware LVDS bit ID (see below). + * + * Upon request, the following mappings may be added: + * * from PMT channel to LVDS bit ID (logic), and vice versa. + * + * + * Maps are build based on the information from the PMT hardware channel + * mapping. + * + * + * + * Mapping conventions + * -------------------- + * + * The logic mapping is described in `sbn::ExtraTriggerInfo::LVDSstatus`. + * Compared with that mapping, the PMT pair bit ID are assigned so that + * * the index of the ID, modulo 64 bits, matches the `LVDSstatus` words; + * * cryostats and PMT walls of the IDs are honoured, so that they can be used + * to directly address `LVDSstatus`. + * + * Likewise, the adder bit ID are assigned so that + * * the index of the ID, modulo 6 bits, matches the `sectorStatus` words; + * * cryostats and PMT walls of the IDs are honoured, so that they can be used + * to directly address `sectorStatus`. + * + * This results in the following mapping, "first bit" meaning the most + * significant. + * + * PMT pairs: + * * east cryostat, east wall: `0x00aAbBcC'00dDeEfF`, channel `0` the most + * significant bit in `a` (bit #55) and channel `45` the one in `d` (bit 23); + * * east cryostat, west wall: `0x00aAbBcC'00dDeEfF`, channel `90` the most + * significant bit in `a` (bit #55) and channel `135` the one in `d` (bit 23); + * * west cryostat, east wall: `0x00aAbBcC'00dDeEfF`, channel `180` the most + * significant bit in `a` (bit #55) and channel `225` the one in `d` (bit 23); + * * west cryostat, west wall: `0x00aAbBcC'00dDeEfF`, channel `270` the most + * significant bit in `a` (bit #55) and channel `315` the one in `d` (bit 23). + * + * Adders: + * * east cryostat, east wall: `0b00000000'00abcdef`, with `a` to `f` covering + * adders starting with channel `0`, `15`, 30`, `45`, 60` and `75`; + * * east cryostat, west wall: `0b00000000'00abcdef`, with `a` to `f` covering + * adders starting with channel `90`, `105`, 120`, `135`, 150` and `165`; + * * west cryostat, east wall: `0b00000000'00abcdef`, with `a` to `f` covering + * adders starting with channel `180`, `195`, 210`, `225`, 240` and `255`; + * * west cryostat, west wall: `0b00000000'00abcdef`, with `a` to `f` covering + * adders starting with channel `270`, `285`, 300`, `315`, 330` and `345`. + * + * + * Naming conventions + * ------------------- + * + * Each PMT has a channel ID associated with a location; the relation between + * the channel number value and the position of the PMT is fixed by a + * convention (from LArSoft). This is a "logical" channel number. + * On the other side, each PMT has a unique identifier that was assigned by + * the ICARUS PMT Working Group and is used as key in an hardware database. + * This is the PMT hardware channel number. These two channel numbers may be + * (and in fact, are) different. + * A similar distinction is made of the LVDS signals. + * + * * Eight LVDS signals with discriminated PMT pair information are emitted by + * each CAEN V1730B PMT readout board. These bits are collected into four + * 64-bit words, one for each of the four PMT walls. The word values are + * stored in `sbn::ExtraTriggerInfo`, which also defines the convention + * relating each of the bits in the 64-bit words with the physical location of + * the PMT feeding the bit. This is equivalent to the logical PMT channel + * number described above, and here we call it a "logical LVDS bit ID". + * Analyzers will typically deal with this logical ID only. It is described by + * `icarus::trigger::PMTpairBitID` data type. + * * The assembly of the bits in hardware includes the combination of 8-bit + * information from groups of three PMT readout boards, physically using LVDS + * format and for that often just dubbed "the LVDS signals", via custom LVDS + * boxes into 32-bit cables and connectors, with the excess 8 bits in each of + * them partly used for other information. + * Like in the case of single PMT, we define a "hardware LVDS bit ID" + * representing the position of a bit in the 32-bit word in the connector, + * and identifying the connector. The relation of these bits with the actual + * PMT channels is encoded in the hardware database; analyzers typically don't + * have to deal with them, because the trigger decoding software will + * translate them into the `sbn::ExtraTriggerInfo` convention (using, in fact, + * this `LVDSbitMaps` object for the mapping). The data type devoted to this + * concept is `LVDSHWbitID`. Note that the NIM FPGA boards combine two 32-bit + * connector words into 64-bit words for transportation. These 64-bit words + * are stored in the trigger DAQ fragment. At first look, these 64-bit words + * are similar to the ones described in the previous bullet, but their bits + * are in a different relation with the actual readout boards and PMT. + * Also note that the decoding of these 64-bit words from the FPGA require, + * on top oft he information from this mapping, the information of where each + * connector is located within the word (hint: connectors 0 and 2 are stored + * as the most significant 32-bit word, 1 and 3 as the least significant one). + * + */ +class icarus::trigger::LVDSbitMaps { + + public: + + // --- BEGIN --- Hard-coded geometry ----------------------------------------- + /// @name Good old hard-coded ICARUS geometry. + /// @{ + + /// Total number of PMT channels in the detector. + static constexpr unsigned int NChannels = 360; + + /// Number of PMT channels in each cryostat. + static constexpr unsigned int NChannelsPerCryostat = 180; + + /// Number of cryostats in the detector. + static constexpr std::size_t NCryostats = NChannels / NChannelsPerCryostat; + + ///@} + // --- END ----- Hard-coded geometry ----------------------------------------- + + /// Error codes from this object. See `errorMessage()` code for descriptions. + enum class Errors { + NoError, ///< No error condition. + Error, ///< An unspecified error condition. + NoDatabase, ///< No channel mapping database access. + NoPMTfragment, ///< Missing a required PMT fragment information. + NoPairBitInformation, ///< No PMT pair bit information in the database. + NoAdderBitInformation, ///< No adder bit information in the database. + Unknown ///< Unknown error. + }; + + /// Supported maps. + enum class Map { + PMTpairs, ///< Map of PMT pairs ("LVDS"). + Adders, ///< Map of adder signals. + }; + + /// Returns a predefined message describing the specified error `code`. + static std::string errorMessage(Errors code); + + /// Type of exception thrown by this object. + using Exception = cet::coded_exception; + + /// Default constructor: maps empty until `rebuild()` is called. + LVDSbitMaps() = default; + + /// Constructor: builds all the maps based on the specified PMT channel map. + LVDSbitMaps(icarusDB::IICARUSChannelMapProvider const& channelMap) + { buildAllMaps(channelMap); } + + /// Constructor: builds the specified maps based on the specified PMT channel + /// map. + LVDSbitMaps( + icarusDB::IICARUSChannelMapProvider const& channelMap, + std::initializer_list maps + ) + { buildMaps(channelMap, std::move(maps)); } + + + // --- BEGIN ----- Query interface ----------------------------------------- + /// @name Query interface. + /// @{ + + /// Returns whether the specified map is present. + bool hasMap(Map map) const; + + /** + * @brief Returns the source bit for the specified encoded PMT pair bit. + * @param bit ID of the logical PMT pair bit to get the source information of + * @return the number of hardware connector and bit where to find the value + * @throws std::out_of_range if the specified bit is not in the map + * + * The output `connector`/`bit` bits are sorted according to the prescription + * of `sbn::ExtraTriggerInfo`, which requires the least significant bit to + * have the most upstream PMT pair (i.e. the lowest channels). + * + * The returned information includes: + * * the cryostat the bit comes from (the same as the one in `bit`); + * * the hardware connector number the bit comes from; + * * the hardware bit (wire) in the connector which carries the `bit` value; + * * the PMT channels covered by the bit. + * + * See `buildPMTpairSourceMap()` for details of the mapping definitions. + */ + LVDSinfo_t bitSource(PMTpairBitID const& bit) const; + + + /** + * @brief Returns the source bit for the specified encoded adder bit. + * @param bit ID of the logical adder bit to get the source information of + * @return the number of hardware connector and bit where to find the value + * @throws std::out_of_range if the specified bit is not in the map + * + * The output `connector`/`bit` bits are sorted according to the prescription + * of `sbn::ExtraTriggerInfo`, which requires the least significant bit to + * have the most upstream adder signal (i.e. the lowest channels). + * + * The returned information includes: + * * the cryostat the bit comes from (the same as the one in `bit`); + * * the hardware connector number the bit comes from; + * * the hardware bit (wire) in the connector which carries the `bit` value; + * * the PMT channels covered by the bit. + * + * See `buildAdderSourceMap()` for details of the mapping definitions. + */ + AdderInfo_t bitSource(AdderBitID const& bit) const; + + + /** + * @brief Returns a list of channels paired in a single LVDS. + * @return a list of vectors of one or two PMT channel ID + * + * This method recreates the pairing of PMT channels into LVDS bits. + * The order of the "pairs" follows the order of the logic bits. + * These "pairs" can actually contain either two or one ID; empty pairs + * (from invalid bits) are not included. + */ + std::vector> channelPairs() const; + + /// @} + // --- END ------- Query interface ----------------------------------------- + + + // --- BEGIN ----- Map updates --------------------------------------------- + + /// Rebuilds all the LVDS maps based on the specified channel map status. + void rebuild(icarusDB::IICARUSChannelMapProvider const& channelMap) + { buildAllMaps(channelMap); } + + // --- END ------- Map updates --------------------------------------------- + + + private: + + /// Description of a LVDS bit and association to a PMT channel. + /// @note This record does not include the logical LVDS bit ID it provides + /// information of. + struct LVDSchannel_t { + + /// Special value identifying no channel set. + static constexpr raw::Channel_t NoChannel + = std::numeric_limits::max(); + + raw::Channel_t channel = NoChannel; ///< ID of an associated PMT channel. + + LVDSHWbitID PMTpairSource; ///< Identifier of the PMT pair bit source. + LVDSHWbitID adderSource; ///< Identifier of the adder bit source. + + /// Returns an "unique" index for the LVDS source. + constexpr unsigned short int index() const { return PMTpairSource.index(); } + + /// Returns whether this information is fully valid (both channel and ID). + operator bool() const + { return (channel != NoChannel) && PMTpairSource && adderSource; } + + /// Order operator: sorted after the channel ID only. + bool operator< (LVDSchannel_t const& other) const + { return channel < other.channel; } + + }; // LVDSchannel_t + + + /// Cached information of each PMT (sorted by PMT channel). + std::vector fPMTchannelInfo; + + /// For each PMT pair bit index, the HW LVDS ID and information are stored. + std::vector fLVDSinfoMap; + + /// For each adder bit ID, adder information is stored. + std::vector fAdderInfoMap; + + + /// Builds all the maps + void buildAllMaps(icarusDB::IICARUSChannelMapProvider const& channelMap); + + /// Builds the specified maps. If any error occurs, an `Exception` is thrown. + void buildMaps( + icarusDB::IICARUSChannelMapProvider const& channelMap, + std::initializer_list maps + ); + + /** + * @brief Fills a list of PMT channels and their relevant information + * @param channelMap service provider to associate each PMT channel to LVDS + * @return the list, sorted by PMT channel ID + */ + std::vector buildChannelInfo + (icarusDB::IICARUSChannelMapProvider const& channelMap) const; + + /** + * @brief Builds the LVDS map connecting each bit to its hardware source. + * @param channelMap service provider to associate each PMT channel to LVDS + * @return a map + * + * The map is associating the unique PMT pair bit ID (`PMTpairBitID::index()`) + * and the information of the LVDS ID matching that ID. + * The information includes the source in the hardware connector of the bit + * and the involved channels (see `LVDSinfo_t` for more). + */ + std::vector buildPMTpairSourceMap + (icarusDB::IICARUSChannelMapProvider const& channelMap) const; + + /** + * @brief Builds the adder map connecting each bit to its hardware source. + * @param channelMap service provider to associate each PMT channel to LVDS + * @return a map + * + * The map is associating the unique adder bit ID (`AdderBitID::index()`) + * and the information of the hardware LVDS ID matching that ID. + * The information includes the source in the hardware connector of the bit + * and the involved channels (see `AdderInfo_t` for more). + * + */ + std::vector buildAdderSourceMap + (icarusDB::IICARUSChannelMapProvider const& channelMap) const; + + +}; // icarus::trigger::LVDSbitMaps + + +// ----------------------------------------------------------------------------- +// --- template implementation +// ----------------------------------------------------------------------------- +// --- icarus::trigger::PMTwallBitID +// ----------------------------------------------------------------------------- +template +constexpr auto icarus::trigger::details::PMTwallBitID::fromIndex + (Index_t index) -> BitID_t +{ + auto const [ cryostat, wb ] + = std::div(index, NPMTwallsPerCryostat * NStatusWordBits); + auto const [ wall, bit ] = std::div(wb, NStatusWordBits); + return { (std::size_t) cryostat, (std::size_t) wall, (StatusBit_t) bit }; +} // icarus::trigger::PMTwallBitID<>::fromIndex() + + +// ----------------------------------------------------------------------------- +template +std::ostream& icarus::trigger::details::operator<< + (std::ostream& out, PMTwallBitID const& bit) +{ + static constexpr char Side[] = "EW"; + auto const fillch = out.fill(); // width is reset to 0, fill character is not + out << (bit.hasCryostat()? Side[bit.cryostat]: '?') + << '/' << (bit.hasPMTwall()? Side[bit.PMTwall]: '?') + << '/' << std::setw(2) << std::setfill('0') << bit.statusBit + << std::setfill(fillch); + return out; +} + + +// ----------------------------------------------------------------------------- + +#endif // ICARUSCODE_PMT_TRIGGER_ALGORITHMS_LVDSBITMAPS_H diff --git a/icaruscode/TPC/Compression/producecompressedjob.fcl b/icaruscode/TPC/Compression/producecompressedjob.fcl index ac584c5a5..b5085e5cc 100644 --- a/icaruscode/TPC/Compression/producecompressedjob.fcl +++ b/icaruscode/TPC/Compression/producecompressedjob.fcl @@ -1,4 +1,5 @@ #include "services_common_icarus.fcl" +#include "channelmapping_icarus.fcl" #include "ProduceCompressed.fcl" @@ -7,16 +8,7 @@ process_name: TestReprocessRaw services: { @table::icarus_basic_services - IICARUSChannelMap: { - ChannelMappingTool: { - CalibDBFileName: "crt_gain_reco_data" - DBFileName: "ChannelMapICARUS_20230829.db" - Tag: "v1r0" - tool_type: "ChannelMapSQLite" - } - DiagnosticOutput: false - service_provider: "ICARUSChannelMap" - } + IICARUSChannelMap: @local::icarus_channelmappinggservice } source: diff --git a/icaruscode/Utilities/CacheCounter.h b/icaruscode/Utilities/CacheCounter.h new file mode 100644 index 000000000..575f4a9aa --- /dev/null +++ b/icaruscode/Utilities/CacheCounter.h @@ -0,0 +1,283 @@ +/** + * @file icaruscode/Utilities/CacheCounter.h + * @brief Simple utility to track cache changes. + * @author Gianluca Petrillo (petrillo@slac.standord.edu) + * @date March 11, 2024 + * + * This library is header only. + */ + +#ifndef ICARUSCODE_UTILITIES_CACHECOUNTER_H +#define ICARUSCODE_UTILITIES_CACHECOUNTER_H + +// C/C++ standard libraries +#include +#include +#include +#include +#include // std::logic_error + + +// ----------------------------------------------------------------------------- +namespace util { + class CacheCounter; + class CacheGuard; +} + +// ----------------------------------------------------------------------------- +/** + * @brief Counter of cache versions. + * @see `util::CacheGuard` + * + * This simple class contains an index of cache IDs. + * A cache ID is a number that represents the status of a cache. + * + * Multiple caches are supported by a single object instance. + * Each cache is represented by a "tag", or name. The cache with empty name is + * special in that it represent the whole set of caches: when updating it, all + * the other cache IDs are also updated. + * + * Note that the special ID value `NoCache` represents the idea that the cache + * is never up to date and always needs to be refreshed (functionally equivalent + * to not having any cache). + * + * See `util::CacheGuard` for a usage example. + */ +class util::CacheCounter { + + public: + + using CacheID_t = unsigned long; ///< Type of cache identifier. + using CacheTag_t = std::string; ///< Type of cache name. + + /// ID specifying that cache is not present yet. + static constexpr CacheID_t NoCache = 0; + + /// Constructor: prepares the ID of the default cache tag. + CacheCounter() { fCacheID.emplace("", NoCache); } + + /// Constructor: prepares the ID of the caches with the specified tags. + CacheCounter(std::initializer_list tags) + : CacheCounter{} + { addCacheTags(std::move(tags)); } + + /// Adds tags. The ID of existing ones are not changed. + void addCacheTags(std::initializer_list tags) + { for (CacheTag_t const& tag: tags) fCacheID.emplace(tag, NoCache); } + + /// Returns the current ID of the cache with the specified tag. + CacheID_t cacheID(CacheTag_t const& tag) const { return fCacheID.at(tag); } + + /// Returns whether the specified tag is tracked. + bool hasTag(CacheTag_t const& tag) const { return fCacheID.count(tag) > 0; } + + /// Returns whether the cache `tag` was updated since it has `sinceID` ID. + bool wasCacheUpdatedSince(CacheTag_t const& tag, CacheID_t sinceID) const + { return cacheID(tag) > sinceID; } + + /// Returns whether the default cache was updated since it has `sinceID` ID. + bool wasCacheUpdatedSince(CacheID_t sinceID) const + { return wasCacheUpdatedSince({}, sinceID); } + + + protected: + + /// ID of the current caches. + mutable std::map> fCacheID; + + /// Increments the counter of the specified cache tag. + /// The default (empty) tag increments all. + CacheID_t updateCacheID(CacheTag_t const& cacheTag = "") const + { + if (cacheTag.empty()) { + for (auto& ID: fCacheID) if (!ID.first.empty()) ++(ID.second); + } + else ++fCacheID.at(""); // always update the default cache + return ++fCacheID.at(cacheTag); // return only the ID from the argument + } + +}; // util::CacheCounter + + +// ----------------------------------------------------------------------------- +/** + * @brief Tracks the status of a cache. + * @see `util::CacheCounter` + * + * This class tracks the status of a cache, represented by an integer (ID). + * The cache is supposed to update (increment) that ID value each time it + * updates the cache content itself. This object tracks that ID and reports + * whether it has changed since the last time. + * + * There are two elements of the interface: + * * `update()` unconditionally acquires the current ID of the cache; it also + * returns whether it's a new ID (like `wasUpdated()`). + * * `wasUpdated()` compares the current cache ID to the one acquired by the + * last `update()` call, and returns whether the current cache is newer. + * + * While the cache ID can be any number, and the interface allows to directly + * provide the cache ID, nonetheless this class has a special relationship with + * `util::CacheCounter`: it can point to a specific tag name of any cache, + * or it can point to a specific cache and tag, with a simpler interface. + * If a `CacheGuard` object needs to track a specific cache or cache tag, that + * must be specified at construction time. + * + * Note that the object always starts with no update. + * + * Also note that a cache ID of `util::CacheCounter::NoCache` is specially + * treated and a cache with that ID will never be considered up to date. + * + * + * Example + * -------- + * + * A database access class performs standard queries reading time-dependent + * data from a database. It caches the result of the query, and it updates + * the cache when a time is requested that is not the one present in the cache. + * This class also derives from `util::CacheCounter` to offer the cache updating + * track capability. + * + * An analysis class uses the data from the database to produce results. + * It stores the small amount of data derived by the database that is needed + * for the computation, and it needs to refetch or recompute that data when + * its local cache is outdated. To do this it has a `util::CacheGuard` data + * member bound to the database access class above. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * class DatabaseQuery: public util::CacheCounter { + * + * bool fetchData() + * { + * // ... + * return true; + * } + * + * void updateCache() { if (fetchData()) updateCacheID(); } + * + * public: + * + * using Data_t = ... ; + * + * Data_t query(); + * + * }; + * + * DatabaseQuery DBquery; + * + * + * class DataUser { + * + * util::CacheGuard fDatabaseQueryCacheGuard; + * DatabaseQuery::Data_t fCachedQueryResult; + * + * DatabaseQuery::Data_t const& queryResults() + * { + * if (fDatabaseQueryCacheGuard.update()) + * fCachedQueryResult = DBquery.query(); + * return fCachedQueryResult; + * } + * + * + * public: + * + * DataUser(): fDatabaseQueryCache{ DBquery } {} + * + * void compute() + * { + * DatabaseQuery::Data_t const& data = queryResults(); + * // ... + * } + * + * }; // DataUser + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ +class util::CacheGuard { + + /// Pointer to the cache to monitor by default. + CacheCounter const* fDefaultCache = nullptr; + + /// Tag of the cache to monitor. + CacheCounter::CacheTag_t fCacheTag; + + /// The ID of the cache at the last check. + CacheCounter::CacheID_t fLastCacheID = CacheCounter::NoCache; + + /// Returns the default cache object, throwing `std::logic_error` if not set. + CacheCounter const& defaultCache() const + { + if (!fDefaultCache) + throw std::logic_error{ "util::CacheGuard default cache was not set." }; + return *fDefaultCache; + } + + /// Changes the default cache. + void chooseCache(CacheCounter const* cache) + { fDefaultCache = cache; fLastCacheID = CacheCounter::NoCache; } + + public: + + /// Constructor: prepares monitoring of cache `tag`. + CacheGuard(CacheCounter::CacheTag_t tag = ""): fCacheTag{ std::move(tag) } {} + + /// Constructor: prepares monitoring of cache `tag` of the specified object. + CacheGuard(CacheCounter const& cache, CacheCounter::CacheTag_t tag = "") + : fDefaultCache{ &cache }, fCacheTag{ std::move(tag) } {} + + /// Changes the default cache. + void setCache(CacheCounter const& cache) { chooseCache(&cache); } + + /// Removes the default cache. + void unsetCache() { chooseCache(nullptr); } + + + /// Returns the ID of the cache at the last update. + CacheCounter::CacheID_t lastUpdateID() const { return fLastCacheID; } + + + /// @{ + + /// Returns whether the cache, which has `cacheID` ID, was updated since the + /// last `update()` call. + bool wasUpdated(CacheCounter::CacheID_t cacheID) const + { return (cacheID != CacheCounter::NoCache) && (cacheID > fLastCacheID); } + + /// Returns whether the `cache` was updated since the last `update()` call. + bool wasUpdated(CacheCounter const& cache) const + { return wasUpdated(cache.cacheID(fCacheTag)); } + + /// Returns whether the default cache was updated since the last `update()` + /// call. + bool wasUpdated() const { return wasUpdated(defaultCache()); } + + /// @} + + + /// @{ + + /// Returns if the cache was outdated, and at the same time updates its ID. + bool update(CacheCounter::CacheID_t cacheID) + { + if (cacheID == CacheCounter::NoCache) return true; // no tracking + if (fLastCacheID == cacheID) return false; + fLastCacheID = cacheID; + return true; + } + + /// Returns if the cache was outdated, and at the same time updates its ID. + bool update(CacheCounter const& cache) + { return update(cache.cacheID(fCacheTag)); } + + /// Returns if the default cache was outdated, and at the same time updates + /// its ID. + bool update() { return update(defaultCache()); } + + /// @} + +}; // util::CacheGuard + + +// ----------------------------------------------------------------------------- + +#endif // ICARUSCODE_UTILITIES_CACHECOUNTER_H diff --git a/icaruscode/Utilities/Expected.h b/icaruscode/Utilities/Expected.h new file mode 100644 index 000000000..3995812d2 --- /dev/null +++ b/icaruscode/Utilities/Expected.h @@ -0,0 +1,84 @@ +/** + * @file icaruscode/Utilities/Expected.h + * @brief Class providing a value or an error. + * @author petrillo@slac,stanford.edu + * @date March 13, 2024 + * + * This library is header-only. + */ + +#ifndef ICARUSCODE_UTILITIES_EXPECTED_H +#define ICARUSCODE_UTILITIES_EXPECTED_H + +// C/C++ standard libraries +#include +#include // std::move(), std::forward() + + +// ----------------------------------------------------------------------------- +namespace util { template class Expected; } + +/** + * @brief Class holding a value or an error. + * @tparam T type of the value + * @tparam E (default: `int`) type of the error + * + * This class is loosely based on C++-23 `std::expected`. When C++23 is + * available, this class should be removed. + * The main difference is the default value of the error type `E`, which is not + * present at all in the standard (so the upgrade path would be to define + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * namespace util { + * template + * using Expected = std::expected; + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +template +class util::Expected { + + std::variant fValue; ///< The stored value or error. + + public: + + /// Constructor: starts with a default-constructed error value. + constexpr Expected(E e = E{}): fValue{ std::move(e) } {} + + /// Constructor: starts with a copy of the value `t`. + constexpr Expected(T t): fValue{ std::move(t) } {} + + Expected& operator= (E e) { fValue = std::move(e); return *this; } + Expected& operator= (T t) { fValue = std::move(t); return *this; } + + /// Constructs the expected _value_ in place. + template + T& emplace(Args&&... args) + { return fValue.emplace(std::forward(args)...); } + + /// Returns whether a value is stored (as opposed to an error). + bool has_value() const { return std::holds_alternative(fValue); } + + /// Returns the error (undefined behaviour when storing a value). + E error() const { return std::get(fValue); } + + /// Returns the value (undefined behaviour when storing an error). + T const& value() const { return std::get(fValue); } + + /// Returns a pointer to the value (`nullptr` when storing an error). + T const* valuePtr() const { return has_value()? &value(): nullptr; } + + /// Implicit conversion to value (undefined behaviour if storing an error). + T const& operator*() const { return value(); } + + /// Implicit conversion to value (undefined behaviour if storing an error). + T const* operator-> () const { return valuePtr(); } + + /// Returns whether a value is stored (as opposed to an error). + explicit operator bool() const { return has_value(); } + +}; // util::Expected + + +// ----------------------------------------------------------------------------- + +#endif // ICARUSCODE_UTILITIES_EXPECTED_H