diff --git a/Tracking/components/ExtractTrackParams.cpp b/Tracking/components/ExtractTrackParams.cpp new file mode 100644 index 00000000..5d41e9c9 --- /dev/null +++ b/Tracking/components/ExtractTrackParams.cpp @@ -0,0 +1,140 @@ +#include "utils.hpp" + +#include +#include + +#include + +#include "k4FWCore/Transformer.h" + +#include "Gaudi/Property.h" + +#include +#include +#include + +// Which type of collection we are reading +using FloatColl = podio::UserDataCollection; +using TrackColl = edm4hep::TrackCollection; +using TS = edm4hep::TrackState; +using TP = edm4hep::TrackParams; +using edm4hep::utils::detail::to_index; + +struct TrackParamExtractor final + : k4FWCore::MultiTransformer( + const TrackColl&, const TrackColl&)> { + TrackParamExtractor(const std::string& name, ISvcLocator* svcLoc) + : MultiTransformer(name, svcLoc, + { + KeyValues("InputSiTracks", {"SiTracks"}), + KeyValues("InputCluTracks", {"ClupatraTracks"}), + }, + { + KeyValues("OutCollSiD0", {"SiTrackD0"}), + KeyValues("OutCollSiPhi", {"SiTrackPhi"}), + KeyValues("OutCollSiOmega", {"SiTrackOmega"}), + KeyValues("OutCollSiZ0", {"SiTrackZ0"}), + KeyValues("OutCollSiTanL", {"SiTrackTanL"}), + KeyValues("OutCollCluD0", {"CluTrackD0"}), + KeyValues("OutCollCluPhi", {"CluTrackPhi"}), + KeyValues("OutCollCluOmega", {"CluTrackOmega"}), + KeyValues("OutCollCluZ0", {"CluTrackZ0"}), + KeyValues("OutCollCluTanL", {"CluTrackTanL"}), + // uncertainties + KeyValues("OutCollSiUncD0", {"SiTrackUncD0"}), + KeyValues("OutCollSiUncPhi", {"SiTrackUncPhi"}), + KeyValues("OutCollSiUncOmega", {"SiTrackUncOmega"}), + KeyValues("OutCollSiUncZ0", {"SiTrackUncZ0"}), + KeyValues("OutCollSiUncTanL", {"SiTrackUncTanL"}), + KeyValues("OutCollCluUncD0", {"CluTrackUncD0"}), + KeyValues("OutCollCluUncPhi", {"CluTrackUncPhi"}), + KeyValues("OutCollCluUncOmega", {"CluTrackUncOmega"}), + KeyValues("OutCollCluUncZ0", {"CluTrackUncZ0"}), + KeyValues("OutCollCluUncTanL", {"CluTrackUncTanL"}), + }) {} + + // This is the function that will be called to transform the data + // Note that the function has to be const, as well as the collections + // we get from the input + std::tuple + operator()(const TrackColl& inSiTracks, const TrackColl& inCluTracks) const override { + + printInStars(this, "New Event", n_stars); + + debug() << "Received SiTracks collection with " << inSiTracks.size() << " tracks" << endmsg; + debug() << "Received ClupatraTracks collection with " << inCluTracks.size() << " tracks" << endmsg; + + // Check that both collections are of the same size + if (inSiTracks.size() != inCluTracks.size()) { + fatal() << "Track collections have different sizes: SiTracks (" << inSiTracks.size() << ") and ClupatraTracks (" + << inCluTracks.size() << ")." << endmsg; + } + + // Process both collections (SiTracks and ClupatraTracks) at the same time + std::tuple siColls; + std::tuple cluColls; + std::tuple siUncColls; + std::tuple cluUncColls; + + for (size_t i = 0; i < inSiTracks.size(); ++i) { + + // Process SiTrack + const auto oSiTrackStateIP = getOTrackAtIP(inSiTracks[i], "SiTrack"); + if (oSiTrackStateIP.has_value()) { + // values + std::get(siColls).push_back(oSiTrackStateIP->D0); + std::get(siColls).push_back(oSiTrackStateIP->phi); + std::get(siColls).push_back(oSiTrackStateIP->omega); + std::get(siColls).push_back(oSiTrackStateIP->Z0); + std::get(siColls).push_back(oSiTrackStateIP->tanLambda); + // uncertainties + std::get(siUncColls).push_back(getSigmaVar(*oSiTrackStateIP, TP::d0)); + std::get(siUncColls).push_back(getSigmaVar(*oSiTrackStateIP, TP::phi)); + std::get(siUncColls).push_back(getSigmaVar(*oSiTrackStateIP, TP::omega)); + std::get(siUncColls).push_back(getSigmaVar(*oSiTrackStateIP, TP::z0)); + std::get(siUncColls).push_back(getSigmaVar(*oSiTrackStateIP, TP::tanLambda)); + } + + // Process CluTrack + const auto oCluTrackStateIP = getOTrackAtIP(inCluTracks[i], "CluTrack"); + if (oCluTrackStateIP.has_value()) { + std::get(cluColls).push_back(oCluTrackStateIP->D0); + std::get(cluColls).push_back(oCluTrackStateIP->phi); + std::get(cluColls).push_back(oCluTrackStateIP->omega); + std::get(cluColls).push_back(oCluTrackStateIP->Z0); + std::get(cluColls).push_back(oCluTrackStateIP->tanLambda); + // uncertainties + std::get(cluUncColls).push_back(getSigmaVar(*oCluTrackStateIP, TP::d0)); + std::get(cluUncColls).push_back(getSigmaVar(*oCluTrackStateIP, TP::phi)); + std::get(cluUncColls).push_back(getSigmaVar(*oCluTrackStateIP, TP::omega)); + std::get(cluUncColls).push_back(getSigmaVar(*oCluTrackStateIP, TP::z0)); + std::get(cluUncColls).push_back(getSigmaVar(*oCluTrackStateIP, TP::tanLambda)); + } + } + + return std::tuple_cat(std::move(siColls), std::move(cluColls), std::move(siUncColls), std::move(cluUncColls)); + }; + +private: + Gaudi::Property n_stars{this, "nStars", 20, "line-width of message in star box"}; + + std::optional getOTrackAtIP(const edm4hep::Track& track, const std::string& trackType) const { + + // assuming there is only one Track State at IP + auto trackAtIP = std::ranges::find(track.getTrackStates(), TS::AtIP, &TS::location); + if (trackAtIP != track.getTrackStates().end()) { + verbose() << fmt::format("Track at IP found for {}.", trackType) << endmsg; + return *trackAtIP; + } else { + fatal() << fmt::format("No track at IP found for {}!", trackType) << endmsg; + return std::nullopt; + } + } + + float getSigmaVar(const edm4hep::TrackState& ts, const TP var) const { return std::sqrt(ts.getCovMatrix(var, var)); } +}; +DECLARE_COMPONENT(TrackParamExtractor) diff --git a/Tracking/components/TrackD0Printer.cpp b/Tracking/components/TrackD0Printer.cpp new file mode 100644 index 00000000..d4a9104a --- /dev/null +++ b/Tracking/components/TrackD0Printer.cpp @@ -0,0 +1,92 @@ +#include "utils.hpp" + +#include "edm4hep/Track.h" +#include "edm4hep/TrackCollection.h" +#include "edm4hep/TrackState.h" +#include "podio/RelationRange.h" + +#include "Gaudi/Property.h" +#include "GaudiKernel/MsgStream.h" + +#include "k4FWCore/Consumer.h" + +#include + +#include +#include +#include +#include +#include +#include + +// Type aliases for improved readability +using TrackColl = edm4hep::TrackCollection; +using TP = edm4hep::TrackParams; +using TS = edm4hep::TrackState; + +// Consumer that processes track collections and prints phi values +struct TrackD0Printer final : k4FWCore::Consumer { + // Constructor: define the input collections (ClupatraTracks and SiTracks) + TrackD0Printer(const std::string& name, ISvcLocator* svcLoc) + : Consumer(name, svcLoc, + { + KeyValues("InputSiTracks", {"SiTracks"}), + KeyValues("InputCluTracks", {"ClupatraTracks"}), + }) {} + + // This function will be called to process the data + void operator()(const TrackColl& inSiTracks, const TrackColl& inCluTracks) const override { + + printInStars(this, "New Event", n_stars); + + debug() << "Received SiTracks collection with " << inSiTracks.size() << " tracks" << endmsg; + debug() << "Received ClupatraTracks collection with " << inCluTracks.size() << " tracks" << endmsg; + + // Check that both collections are of the same size + if (inSiTracks.size() != inCluTracks.size()) { + fatal() << "Track collections have different sizes: SiTracks (" << inSiTracks.size() << ") and ClupatraTracks (" + << inCluTracks.size() << "). Exiting!" << endmsg; + return; + } + + // Process both collections (SiTracks and ClupatraTracks) at the same time + for (size_t i = 0; i < inSiTracks.size(); ++i) { + + // Process SiTrack + processTrack(inSiTracks[i], "SiTrack"); + + // Process CluTrack + processTrack(inCluTracks[i], "CluTrack"); + } + } + +private: + Gaudi::Property n_stars{this, "nStars", 20, "line-width of message in star box"}; + + float getSigmaPhi(const edm4hep::TrackState& ts) const { return std::sqrt(ts.getCovMatrix(TP::phi, TP::phi)); } + + std::string printValueUnc(const std::string& strTrType, const std::string& vName, const int maxLabelWidth, + const float varValue) const { + return fmt::format("{:<10}{:>{}}{:>10.5f}", strTrType, vName, maxLabelWidth, varValue); + } + + // Helper function to process each track (either SiTrack or CluTrack) + void processTrack(const edm4hep::Track& track, const std::string& trackType) const { + + std::string varName = "phi"; + std::string sigmaVarName = "sigma " + varName; + int maxVarWidth = std::max(varName.size(), sigmaVarName.size()) + 2; + + // assuming there is only one Track State at IP + if (auto trackAtIP = std::ranges::find(track.getTrackStates(), TS::AtIP, &TS::location); + trackAtIP != track.getTrackStates().end()) { + info() << printValueUnc(trackType, varName, maxVarWidth, trackAtIP->phi) << endmsg; + info() << printValueUnc(trackType, sigmaVarName, maxVarWidth, getSigmaPhi(*trackAtIP)) << endmsg; + } else { + fatal() << fmt::format("No track at IP found for {}!", trackType) << endmsg; + } + } +}; + +// Declare the consumer component +DECLARE_COMPONENT(TrackD0Printer) diff --git a/Tracking/include/utils.hpp b/Tracking/include/utils.hpp index 9a731685..d6c3743b 100644 --- a/Tracking/include/utils.hpp +++ b/Tracking/include/utils.hpp @@ -20,6 +20,8 @@ #include "extension/MutableTrack.h" #include "extension/TrackCollection.h" +#include + //=== Others === #include #include @@ -189,4 +191,9 @@ int getHypotesisCharge(int pdg); TMatrixDSym computeTrackStateCovMatrix(TVectorD stateTrack, TVectorD params, TVector3 referencePoint, double timeError, TMatrixDSym statecovMatrix); -#endif // UTILS_HPP \ No newline at end of file +/** + * @brief Print the message in a block of stars ('*') at level DEBUG + */ +void printInStars(const Gaudi::Algorithm* thisAlg, const std::string& msg, const int lineWidth); + +#endif // UTILS_HPP diff --git a/Tracking/options/ExtractTrackParams.py b/Tracking/options/ExtractTrackParams.py new file mode 100644 index 00000000..6196beae --- /dev/null +++ b/Tracking/options/ExtractTrackParams.py @@ -0,0 +1,51 @@ +import os +from pathlib import Path + +from commonArgParsing import add_common_args, detModNames, registry +from Configurables import TrackParamExtractor +from Gaudi.Configuration import INFO, VERBOSE +from k4FWCore import ApplicationMgr, IOSvc +from k4FWCore.parseArgs import parser + +ARGS = add_common_args(parser).parse_known_args()[0] +assert len(ARGS.detectorModels) == 1, ( + f"Only provide one detector model! You provided {ARGS.detectorModels}" +) +ARGS.detectorModels = ARGS.detectorModels[0] + +FILE_SUFFIX = ".edm4hep.root" +PROCESSOR_NAME = "TrackParamExtractor" +BASE_PATH = Path(os.getenv("prmDir", Path.home() / "promotion")) +IN_OUT_BASE_PATH = BASE_PATH / "data" / PROCESSOR_NAME +CORE_PATH = f"{ARGS.version}_{detModNames[ARGS.detectorModels]}" + +# assert that the input path exists +INPUT_PATH = (IN_OUT_BASE_PATH / "input_data" / f"{CORE_PATH}_REC").with_suffix(FILE_SUFFIX) +assert INPUT_PATH.exists(), f"ERROR: The input path ({INPUT_PATH}) does not exist!" + +iosvc = IOSvc() +iosvc.Input = str(INPUT_PATH) +iosvc.Output = str( + (IN_OUT_BASE_PATH / "out_track_params" / f"{CORE_PATH}_track_params").with_suffix(FILE_SUFFIX) +) +# iosvc.outputCommands = ["drop *", "keep SiTrackPhi"] + +printer = TrackParamExtractor(PROCESSOR_NAME, nStars=40) +printer.OutputLevel = VERBOSE + +# the collection name with the SiTracks differs between ILC and FCC models +if registry.get(ARGS.detectorModels).at_fcc: + printer.InputSiTracks = ["SiTracksCT"] + SI_TRACK_COLL_NAME = "SiTracksCT" +else: + SI_TRACK_COLL_NAME = "SiTracks" +iosvc.CollectionNames = ["ClupatraTracks", SI_TRACK_COLL_NAME] + + +ApplicationMgr( + TopAlg=[printer], + EvtSel="NONE", + EvtMax=10, + ExtSvc=[iosvc], + OutputLevel=INFO, +) diff --git a/Tracking/options/histTrackParams.py b/Tracking/options/histTrackParams.py new file mode 100644 index 00000000..47f59aea --- /dev/null +++ b/Tracking/options/histTrackParams.py @@ -0,0 +1,285 @@ +############################################# +# call with `python3` NOT `k4run` +############################################# + +from argparse import ArgumentParser +from itertools import product +from os import getenv +from pathlib import Path + +import awkward as ak +import matplotlib as mpl +import matplotlib.pyplot as plt +import numpy as np +import uproot + +from commonArgParsing import add_common_args, detModNames, registry +from plotting import my_line_styles +from utils import is_outlier + +plt.style.use(["seaborn-v0_8-colorblind", "vics_basic"]) + +threshold_outlier_detection = 4 + +my_hist_type = "step" +my_line_width = 2.5 +my_n_bins = 30 + +############################################# +# arg parsing +############################################# + +# import common args +parser = add_common_args(ArgumentParser()) +parser.add_argument( + "--mode", + choices=["nominal", "uncertainty", "both"], + default="nominal", + help="Choose what to process: 'nominal' (default), 'uncertainty', or 'both'.", +) +parser.add_argument("--debug", action="store_true") +parser.add_argument("--rm-outliers", action="store_true") +# add plotting options +plot_opts = parser.add_argument_group("Plotting opts", "which plots should be shown") +plot_opts.add_argument( + "--track", + action="store_true", + help="Show difference between Silicon and Clupatra tracks", +) +plot_opts.add_argument( + "--detmods", action="store_true", help="Show difference between detector models" +) + +# parse args +args = parser.parse_known_args()[0] +assert len(parser.parse_known_args()[1]) == 0, ( + f"Unknown args provided: {parser.parse_known_args()[1]}" +) + + +############################################# +# Lists to build branch names to be analyzed +############################################# + +track_types = ["SiTrack", "CluTrack"] +var_similar = ["Phi", "Omega", "TanL"] +var_spread = ["D0", "Z0"] +var_names = var_similar + var_spread + +############################################# +# extract data from root file +############################################# +data = {} +for detMod in args.detectorModels: + corePath = Path(f"{args.version}_{detModNames[detMod]}") + + # strings to build path + processor = "TrackParamExtractor" + basePath = Path(getenv("dtDir", str(Path.home() / "promotion" / "data"))) + + # build vars based on above vars + keys = [f"{trackName}{varName}" for trackName, varName in product(track_types, var_names)] + in_file = basePath / processor / corePath.with_suffix(".edm4hep.root") + + with uproot.open(str(in_file) + ":events") as events: + # regex to match desired branch names + regex = ( + f"/^({'|'.join(track_types)}){'(Unc)?' if args.mode != 'nominal' else ''}" + + f"({'|'.join(var_names)})$/" + ) + if args.debug: + print(f"Regex to match branches we are interested in: {regex}") + data[detMod] = events.arrays(filter_name=regex, library="pd") + for var in var_names: + data[detMod][f"d_{var}"] = ( + data[detMod][f"{track_types[0]}{var}"] - data[detMod][f"{track_types[1]}{var}"] + ) + if args.debug: + print("Matched branches are:") + print(data[detMod].columns) + + +############################################# +# plotting funcs +############################################# +def process_data_for_hist(data, det_mod, var_column_name, rm_outliers, thresh_outlier_detection): + # TODO: remove comment? + """ + Process the data for plotting, handling outliers if required. + + Args: + data: The full data dictionary. + det_mod: The detector model name. + var_column_name: The variable name. + rm_outliers: Boolean flag to remove outliers. + thresh_outlier_detection: Threshold for outlier detection. + + Returns: + numpy array: Processed data ready for plotting. + """ + data_array = ak.to_numpy(ak.flatten(data[det_mod][f"{var_column_name}"])) + + if rm_outliers: + # Remove outliers + return data_array[~is_outlier(data_array, thresh=thresh_outlier_detection)] + + return data_array + + +def debug_info_outlier_removal( + data, thresh, args, var_list, fixed_data_key, det_mod_variable: bool +): + if args.rm_outliers and args.debug: + star_string = "*" * 50 + print(f"\n\n{star_string}\nOutlier threshold value is: {thresh}\n{star_string}") + for current_var in var_list: + data_to_clean = ( + data[current_var][fixed_data_key] + if det_mod_variable + else data[fixed_data_key][current_var] + ) + pre_processed_data = ak.to_numpy(ak.flatten(data_to_clean)) + print( + f"{np.sum(is_outlier(pre_processed_data))} outliers will be removed out of {len(pre_processed_data)} values for {current_var}" + ) + + +# general plotting func +def plot_track_param_hist(data, thresh_outlier_detection, args, var, labels, hist_args): + if var: # TODO: support for var_group case + debug_info_outlier_removal( + data, + thresh_outlier_detection, + args, + args.detectorModels, + var, + det_mod_variable=True, + ) + with mpl.rc_context({"axes.titlesize": 18}): + plt.figure() + plt.hist( + **hist_args, + bins=my_n_bins, + histtype=my_hist_type, + linewidth=my_line_width, + linestyle=my_line_styles, + ) + plt.xlabel(labels["xlabel"] if "xlabel" in labels else None) + plt.ylabel("Frequency") + plt.suptitle(labels["suptitle"]) + plt.title(labels["title"]) + plt.legend() + plt.show() + + +# plotting func for collective plot of group of vars +def plot_track_param_hist_var_groups(data, thresh_outlier_detection, args, det_mod, labels, group): + hist_args_diff_detmods = { + "x": [ + process_data_for_hist( + data, + det_mod, + f"d_{var_name}", + args.rm_outliers, + thresh_outlier_detection, + ) + for var_name in group + ], + "label": group, + } + plot_track_param_hist( + data, + thresh_outlier_detection, + args, + None, + labels, + hist_args_diff_detmods, + ) + + +# plotting func for collective plot of all det mods +def plot_track_param_hist_diff_detmods(data, thresh_outlier_detection, args, var, labels): + hist_args_diff_detmods = { + "x": [ + process_data_for_hist(data, det_mod, var, args.rm_outliers, thresh_outlier_detection) + for det_mod in args.detectorModels + ], + "label": [registry.get(det_mod).get_name(args.detname) for det_mod in args.detectorModels], + } + plot_track_param_hist( + data, + thresh_outlier_detection, + args, + var, + labels, + hist_args_diff_detmods, + ) + + +############################################# +# actual plotting (calling the funcs) +############################################# + +if args.mode != "nominal": # uncertainty or both + for track_type in [track_types[0]]: + for var in var_names: + # define labels + uncertainty_labels = { + "xlabel": r"$\sigma$", + "suptitle": rf"$\sigma$({var})", + "title": rf"single $\mu$, {track_type} {' (no outliers)' if args.rm_outliers else ''}", + } + # plotting ;) + plot_track_param_hist_diff_detmods( + data, + threshold_outlier_detection, + args, + f"{track_type}Unc{var}", + uncertainty_labels, + ) + + +if args.mode != "uncertainty": # nominal or both + if args.track: + for det_mod in args.detectorModels: + for group in [var_spread, var_similar]: + # define labels + nominal_var_groups_labels = { + "xlabel": r"$\Delta$ Si-Clu", + "suptitle": r"$\Delta$ Si-Clu:", + "title": rf"single $\mu$, {registry.get(det_mod).get_name(args.detname)}" + f" {' (no outliers)' if args.rm_outliers else ''}", + } + plot_track_param_hist_var_groups( + data, + threshold_outlier_detection, + args, + det_mod, + nominal_var_groups_labels, + group, + ) + + if args.detmods: + for track_type in track_types: + for var in ["D0", "Omega"]: + nominal_diff_det_mods_labels = { + "suptitle": f"Diff DetMods: {var}", + "title": rf"single $\mu$, {track_type}" + f" {' (no outliers)' if args.rm_outliers else ''}", + } + plot_track_param_hist_diff_detmods( + data, + threshold_outlier_detection, + args, + f"{track_type}{var}", + nominal_diff_det_mods_labels, + ) + + +############################################# +# commands to access cov Matrix +############################################# +# import ROOT +# ROOT.gInterpreter.LoadFile("edm4hep/utils/cov_matrix_utils.h") +# # ... +# edm4hep.utils.get_cov_value(cov_m, edm4hep.TrackParams.d0, edm4hep.TrackParams.d0) diff --git a/Tracking/options/plotting.py b/Tracking/options/plotting.py new file mode 100644 index 00000000..39ddb2bc --- /dev/null +++ b/Tracking/options/plotting.py @@ -0,0 +1,8 @@ +# general plotting options +my_line_styles = [ + "solid", + "dashed", + (0, (3, 1, 1, 1)), # "densely dashdotted" + (0, (3, 1, 1, 1, 1, 1)), # "densely dashdotdotted", + "dashdot", +] diff --git a/Tracking/options/printD0.py b/Tracking/options/printD0.py new file mode 100644 index 00000000..e6c5dd3c --- /dev/null +++ b/Tracking/options/printD0.py @@ -0,0 +1,24 @@ +from pathlib import Path + +from Configurables import TrackD0Printer +from Gaudi.Configuration import INFO +from k4FWCore import ApplicationMgr, IOSvc + +iosvc = IOSvc() +iosvc.Input = str( + Path.home() + / "promotion/code/ILDConfig/StandardConfig/production/data/test_tracking_3_detmods_V02_REC.edm4hep.root" +) + +iosvc.CollectionNames = ["SiTracks", "ClupatraTracks"] + +printer = TrackD0Printer("TrackD0Printer", nStars=40) +printer.OutputLevel = INFO + +ApplicationMgr( + TopAlg=[printer], + EvtSel="NONE", + EvtMax=10, + ExtSvc=[iosvc], + OutputLevel=INFO, +) diff --git a/Tracking/options/utils.py b/Tracking/options/utils.py new file mode 100644 index 00000000..8bfb7f9a --- /dev/null +++ b/Tracking/options/utils.py @@ -0,0 +1,35 @@ +import numpy as np + + +def is_outlier(points, thresh=3.5): + """ + Returns a boolean array with True if points are outliers and False + otherwise. + + Parameters: + ----------- + points : An numobservations by numdimensions array of observations + thresh : The modified z-score to use as a threshold. Observations with + a modified z-score (based on the median absolute deviation) greater + than this value will be classified as outliers. + + Returns: + -------- + mask : A numobservations-length boolean array. + + References: + ---------- + Boris Iglewicz and David Hoaglin (1993), "Volume 16: How to Detect and + Handle Outliers", The ASQC Basic References in Quality Control: + Statistical Techniques, Edward F. Mykytka, Ph.D., Editor. + """ + if len(points.shape) == 1: + points = points[:, None] + median = np.median(points, axis=0) + diff = np.sum((points - median) ** 2, axis=-1) + diff = np.sqrt(diff) + med_abs_deviation = np.median(diff) + + modified_z_score = 0.6745 * diff / med_abs_deviation + + return modified_z_score > thresh diff --git a/Tracking/src/utils.cpp b/Tracking/src/utils.cpp index fe17829e..9644173d 100644 --- a/Tracking/src/utils.cpp +++ b/Tracking/src/utils.cpp @@ -1,5 +1,7 @@ #include "utils.hpp" +#include + dd4hep::rec::LayeredCalorimeterData* getExtension(unsigned int includeFlag, unsigned int excludeFlag) { dd4hep::rec::LayeredCalorimeterData* theExtension = 0; @@ -353,3 +355,9 @@ TMatrixDSym computeTrackStateCovMatrix(TVectorD stateTrack, TVectorD params, TVe return covarianceTrackState; } + +void printInStars(const Gaudi::Algorithm* thisAlg, const std::string& msg, const int lineWidth) { + thisAlg->debug() << fmt::format("{:*^{}}", "", lineWidth) << endmsg; + thisAlg->debug() << fmt::format("{:*^{}}", msg, lineWidth) << endmsg; + thisAlg->debug() << fmt::format("{:*^{}}", "", lineWidth) << endmsg; +}