From 213631c27c06e58dbf92f8285f54f0bb5b697966 Mon Sep 17 00:00:00 2001 From: Gerhard Raven Date: Thu, 27 Nov 2025 14:20:27 +0100 Subject: [PATCH] streamline tagging functors implementation --- Phys/FunctorCore/include/Functors/Composite.h | 82 +++++++------------ Phys/FunctorCore/python/Functors/__init__.py | 43 ++++++---- Phys/FunctorCore/tests/src/TestFunctors.cpp | 47 ++++++----- 3 files changed, 82 insertions(+), 90 deletions(-) diff --git a/Phys/FunctorCore/include/Functors/Composite.h b/Phys/FunctorCore/include/Functors/Composite.h index ba565e7895c..faea6f35972 100644 --- a/Phys/FunctorCore/include/Functors/Composite.h +++ b/Phys/FunctorCore/include/Functors/Composite.h @@ -295,66 +295,42 @@ namespace Functors::Composite { }; inline const auto ComputeDecayLengthSignificance = ComputeDecayLengthSignificance_t{}; - namespace detail { - /** @brief Invoke a Tagger member function, after obtaining the the right Tagger */ - class TaggerFn : public Function { - LHCb::Tagger::TaggerType m_tagger; - LHCb::Tagger const* getTagger( LHCb::FlavourTags const& ftags, LHCb::Particle const& part ) const { - // find the FlavourTag whose taggedB matches `part` - auto ft = std::ranges::find_if( - ftags, - [&part]( LHCb::Particle const* taggedB ) { - return taggedB == &part || - ( taggedB->momentum() == part.momentum() && taggedB->referencePoint() == part.referencePoint() ); - }, - &LHCb::FlavourTag::taggedB ); - if ( ft == ftags.end() ) return nullptr; - // find the tagger of the requested type from the taggers of this taggedB - auto t = std::ranges::find( ( *ft )->taggers(), m_tagger, &LHCb::Tagger::type ); - if ( t == ( *ft )->taggers().end() ) return nullptr; - return &( *t ); - } + /** @brief Obtain a specified Tagger object */ + class FlavourTag : public Function { + LHCb::Tagger::TaggerType m_tagger; + LHCb::Tagger const* getTagger( LHCb::FlavourTags const& ftags, LHCb::Particle const& part ) const { + // find the FlavourTag whose taggedB matches `part` + auto ft = std::ranges::find_if( + ftags, + [&part]( LHCb::Particle const* taggedB ) { + return taggedB == &part || + ( taggedB->momentum() == part.momentum() && taggedB->referencePoint() == part.referencePoint() ); + }, + &LHCb::FlavourTag::taggedB ); + if ( ft == ftags.end() ) return nullptr; + // find the tagger of the requested type from the taggers of this taggedB + auto t = std::ranges::find( ( *ft )->taggers(), m_tagger, &LHCb::Tagger::type ); + if ( t == ( *ft )->taggers().end() ) return nullptr; + return &( *t ); + } - public: - TaggerFn( LHCb::Tagger::TaggerType tagger_type ) : m_tagger{ tagger_type } {} - TaggerFn( std::string_view tagger_name ) { parse( m_tagger, tagger_name ).orThrow( "Unknown Flavour Tagger" ); } - TaggerFn( std::string const& tagger_name ) : TaggerFn{ std::string_view{ tagger_name } } {} - TaggerFn( int tagger_type ) : TaggerFn{ LHCb::Tagger::TaggerType( tagger_type ) } {} - - template - R invoke( LHCb::FlavourTags const& ftags, LHCb::Particle const& d, R ( LHCb::Tagger::*fn )() const ) const { - // get the flavourtag for `d` from `ftags`, and then, if that succeeds, apply the function `fn` to it. - // if not, apply the function fn to a default constructed LHCb::Tagger object - // see 'Optional.h' for the definition of `and_then` - return and_then( getTagger( ftags, d ), fn ).value_or( ( LHCb::Tagger{}.*fn )() ); // TODO: drop 'value_or' and - // return Optional - } - }; - } // namespace detail + public: + FlavourTag( LHCb::Tagger::TaggerType tagger_type ) : m_tagger{ tagger_type } {} + FlavourTag( std::string_view tagger_name ) { parse( m_tagger, tagger_name ).orThrow( "Unknown Flavour Tagger" ); } + FlavourTag( std::string const& tagger_name ) : FlavourTag{ std::string_view{ tagger_name } } {} + FlavourTag( int tagger_type ) : FlavourTag{ LHCb::Tagger::TaggerType( tagger_type ) } {} - /** @brief Get the tagging decision from specific FlavourTag tagger. */ - struct TaggingDecision : detail::TaggerFn { - static constexpr auto name() { return "TaggingDecision"; } - auto operator()( LHCb::FlavourTags const& ftags, LHCb::Particle const& d ) const { - return detail::TaggerFn::invoke( ftags, d, &LHCb::Tagger::decision ); + LHCb::Tagger const* operator()( LHCb::FlavourTags const& ftags, LHCb::Particle const& d ) const { + return getTagger( ftags, d ); } }; + /** @brief Get the tagging decision from specific FlavourTag tagger. */ + constexpr auto TaggingMVAOutput = GenericFunctor{ "TaggingMVAOutput", &LHCb::Tagger::mvaValue }; /** @brief Get the tagging mistag rate from specific FlavourTag tagger. */ - struct TaggingMistag : detail::TaggerFn { - static constexpr auto name() { return "TaggingMistag"; } - auto operator()( LHCb::FlavourTags const& ftags, LHCb::Particle const& d ) const { - return detail::TaggerFn::invoke( ftags, d, &LHCb::Tagger::omega ); - } - }; - + constexpr auto TaggingMistag = GenericFunctor{ "TaggingMistag", &LHCb::Tagger::omega }; /** @brief Get the tagging MVA output from specific FlavourTag tagger. */ - struct TaggingMVAOutput : detail::TaggerFn { - static constexpr auto name() { return "TaggingMVAOutput"; } - auto operator()( LHCb::FlavourTags const& ftags, LHCb::Particle const& d ) const { - return detail::TaggerFn::invoke( ftags, d, &LHCb::Tagger::mvaValue ); - } - }; + constexpr auto TaggingDecision = GenericFunctor{ "TaggingDecision", &LHCb::Tagger::decision }; namespace BTracking { diff --git a/Phys/FunctorCore/python/Functors/__init__.py b/Phys/FunctorCore/python/Functors/__init__.py index 3b1311515b9..c36a5b07b1c 100644 --- a/Phys/FunctorCore/python/Functors/__init__.py +++ b/Phys/FunctorCore/python/Functors/__init__.py @@ -4418,6 +4418,28 @@ ALLPV_FD._F = MAGNITUDE @ ( ) +def FLAVOURTAG(FTags: DataHandle, TaggerName: Union[str, int]): + """Flavour tagging decision + + Functor's call operator expects particle-like input. + + Args: + TypeName: Name of the user-defined tagger type + FTags: DataHandle of the flavour tags + """ + return FLAVOURTAG._F(TaggerName).bind(TES(FTags), FORWARDARGS) + + +FLAVOURTAG._F = Functor( + "FLAVOURTAG", + "Composite::FlavourTag", + "Return the FlavourTag of a given tagging type from all tag results. ", + Params=[ + ("TaggerName", "Tagger type of which the decision is returned.", (str, int)) + ], +) + + def TaggingDecision(FTags: DataHandle, TaggerName: Union[str, int]): """Flavour tagging decision @@ -4427,16 +4449,13 @@ def TaggingDecision(FTags: DataHandle, TaggerName: Union[str, int]): TypeName: Name of the user-defined tagger type FTags: DataHandle of the flavour tags """ - return VALUE_OR(-99) @ TaggingDecision._F(TaggerName).bind(TES(FTags), FORWARDARGS) + return VALUE_OR(-99) @ TaggingDecision._F @ FLAVOURTAG(FTags, TaggerName) TaggingDecision._F = Functor( "TAGGING_DECISION", "Composite::TaggingDecision", - "Return the decision of a given tagging type from all tag results. ", - Params=[ - ("TaggerName", "Tagger type of which the decision is returned.", (str, int)) - ], + "Return the decision of a given tagger ", ) @@ -4449,16 +4468,13 @@ def TaggingMistag(FTags: DataHandle, TaggerName: Union[str, int]): TypeName: Name of the user-defined tagger type FTags: DataHandle of the flavour tags """ - return VALUE_OR(-99) @ TaggingMistag._F(TaggerName).bind(TES(FTags), FORWARDARGS) + return VALUE_OR(-99) @ TaggingMistag._F @ FLAVOURTAG(TaggerName, FTags) TaggingMistag._F = Functor( "TAGGING_MISTAG", "Composite::TaggingMistag", - "Return the mistag rate of a given tagging type from all tag results. ", - Params=[ - ("TaggerName", "Tagger type of which the mistag rate is returned.", (str, int)) - ], + "Return the mistag rate of a given tagger ", ) @@ -4471,16 +4487,13 @@ def TaggingMVAOutput(FTags: DataHandle, TaggerName: Union[str, int]): TypeName: Name of the user-defined tagger type FTags: DataHandle of the flavour tags """ - return VALUE_OR(-99) @ TaggingMVAOutput._F(TaggerName).bind(TES(FTags), FORWARDARGS) + return VALUE_OR(-99) @ TaggingMVAOutput._F @ FLAVOURTAG(TaggerName, FTags) TaggingMVAOutput._F = Functor( "TAGGING_MVAOUTPUT", "Composite::TaggingMVAOutput", - "Return the mva output of a given tagging type from all tag results. ", - Params=[ - ("TaggerName", "Tagger type of which the mva value is returned.", (str, int)) - ], + "Return the mva output of a given tagger ", ) diff --git a/Phys/FunctorCore/tests/src/TestFunctors.cpp b/Phys/FunctorCore/tests/src/TestFunctors.cpp index 2a4954d86a7..6a5189570f6 100644 --- a/Phys/FunctorCore/tests/src/TestFunctors.cpp +++ b/Phys/FunctorCore/tests/src/TestFunctors.cpp @@ -2152,14 +2152,14 @@ BOOST_AUTO_TEST_CASE( test_ft_functors ) { TaggerA.setDecision( LHCb::FlavourTag::TagResult::b ); TaggerA.setOmega( 0.2 ); - LHCb::FlavourTag* flavourTagA = new LHCb::FlavourTag(); + LHCb::FlavourTag* flavourTagA = new LHCb::FlavourTag{}; flavourTagA->setTaggedB( &particleA ); flavourTagA->addTagger( TaggerA ); auto TaggerB = LHCb::Tagger{}.setType( inputtype_type[i] ).setDecision( LHCb::FlavourTag::TagResult::bbar ).setOmega( 0.15 ); - LHCb::FlavourTag* flavourTagB = new LHCb::FlavourTag; + LHCb::FlavourTag* flavourTagB = new LHCb::FlavourTag{}; flavourTagB->setTaggedB( &particleB ); flavourTagB->addTagger( TaggerB ); @@ -2169,41 +2169,44 @@ BOOST_AUTO_TEST_CASE( test_ft_functors ) { allTagger.insert( flavourTagA ); allTagger.insert( flavourTagB ); + // NOTE: THIS CANNOT WORK: it passes ownership for a 2nd time... TagAOnly.insert( flavourTagA ); TagBOnly.insert( flavourTagB ); - const auto TaggingMistag_str = Functors::Composite::TaggingMistag{ inputtype_str[i] }; - const auto TaggingDecision_str = Functors::Composite::TaggingDecision{ inputtype_str[i] }; - const auto TaggingMistag_int = Functors::Composite::TaggingMistag{ inputtype_int[i] }; - const auto TaggingDecision_int = Functors::Composite::TaggingDecision{ inputtype_int[i] }; - - const auto defaultOmega = LHCb::FlavourTag{}.omega(); - const auto defaultDecision = LHCb::FlavourTag{}.decision(); + const auto TaggingMistag_str_ = + chain( Functors::Composite::TaggingMistag, Functors::Composite::FlavourTag{ inputtype_str[i] } ); + const auto TaggingMistag_str = trivial_prepare( TaggingMistag_str_ ); + const auto TaggingDecision_str_ = + chain( Functors::Composite::TaggingDecision, Functors::Composite::FlavourTag{ inputtype_str[i] } ); + const auto TaggingDecision_str = trivial_prepare( TaggingDecision_str_ ); + const auto TaggingMistag_int_ = + chain( Functors::Composite::TaggingMistag, Functors::Composite::FlavourTag{ inputtype_int[i] } ); + const auto TaggingMistag_int = trivial_prepare( TaggingMistag_int_ ); + const auto TaggingDecision_int_ = + chain( Functors::Composite::TaggingDecision, Functors::Composite::FlavourTag{ inputtype_int[i] } ); + const auto TaggingDecision_int = trivial_prepare( TaggingDecision_int_ ); // check int input and str input return the expected misTag rate BOOST_CHECK_EQUAL( TaggingMistag_str( allTagger, particleA ), TaggingMistag_int( allTagger, particleA ) ); // check A and B return their expected misTag rate - BOOST_CHECK_EQUAL( TaggingMistag_str( allTagger, particleA ), TaggerA.omega() ); - BOOST_CHECK_EQUAL( TaggingMistag_str( allTagger, particleB ), TaggerB.omega() ); + BOOST_CHECK_EQUAL( TaggingMistag_str( allTagger, particleA ).value(), TaggerA.omega() ); + BOOST_CHECK_EQUAL( TaggingMistag_str( allTagger, particleB ).value(), TaggerB.omega() ); - // check A and B returns default misTag rate when their taggers are not in the KeyedContainer - BOOST_CHECK_EQUAL( TaggingMistag_str( TagBOnly, particleA ), defaultOmega ); - BOOST_CHECK_EQUAL( TaggingMistag_str( TagAOnly, particleB ), defaultOmega ); + // check A and B returns nullopt when their taggers are not in the KeyedContainer + BOOST_CHECK( !TaggingMistag_str( TagBOnly, particleA ).has_value() ); + BOOST_CHECK( !TaggingMistag_str( TagAOnly, particleB ).has_value() ); // check int input and str input return the expected misTag rate BOOST_CHECK_EQUAL( TaggingDecision_str( allTagger, particleA ), TaggingDecision_int( allTagger, particleA ) ); // check A and B return their expected decision - BOOST_CHECK_EQUAL( TaggingDecision_str( allTagger, particleA ), TaggerA.decision() ); - BOOST_CHECK_EQUAL( TaggingDecision_str( allTagger, particleB ), TaggerB.decision() ); - - // check A and B returns default decision when their taggers are not in the KeyedContainer - BOOST_CHECK_EQUAL( TaggingDecision_str( TagBOnly, particleA ), defaultDecision ); - BOOST_CHECK_EQUAL( TaggingDecision_str( TagAOnly, particleB ), defaultDecision ); + BOOST_CHECK_EQUAL( TaggingDecision_str( allTagger, particleA ).value(), TaggerA.decision() ); + BOOST_CHECK_EQUAL( TaggingDecision_str( allTagger, particleB ).value(), TaggerB.decision() ); - delete flavourTagA; - delete flavourTagB; + // check A and B returns nullopt when their taggers are not in the KeyedContainer + BOOST_CHECK( !TaggingDecision_str( TagBOnly, particleA ).has_value() ); + BOOST_CHECK( !TaggingDecision_str( TagAOnly, particleB ).has_value() ); } } -- GitLab