diff --git a/Phys/SelAlgorithms/src/MLServiceAlg.cpp b/Phys/SelAlgorithms/src/MLServiceAlg.cpp index 4dd41678b5b3d4663a048b16537593171206f03a..ef42f37dea24caf8844a8639843b6432015bc5a4 100644 --- a/Phys/SelAlgorithms/src/MLServiceAlg.cpp +++ b/Phys/SelAlgorithms/src/MLServiceAlg.cpp @@ -27,6 +27,9 @@ namespace { using Transformer1D = LHCb::Algorithm::Transformer; template using Transformer2D = LHCb::Algorithm::Transformer; + template + using Transformer2DTo1D = + LHCb::Algorithm::Transformer; struct Funct1D { constexpr static auto PropertyName = "MVAInput"; @@ -172,9 +175,81 @@ private: ServiceHandle m_mlservice{ this, "MLService", "ONNXRuntimeSvc" }; }; -using MLServiceAlg_Relation1D = MLServiceAlg2D>; -using MLServiceAlg_Relation2D = MLServiceAlg2D>; +template +class MLServiceAlg2DTo1D final : public with_functors, Funct2D> { +public: + using BaseClass = with_functors, Funct2D>; + using KeyValue = BaseClass::KeyValue; + using OutputTable = Rel1DTable; + + MLServiceAlg2DTo1D( const std::string& name, ISvcLocator* pSvc ) + : BaseClass::with_functors( name, pSvc, { KeyValue{ "InputFrom", "" }, KeyValue{ "InputRelations", "" } }, + KeyValue{ "OutputRelations", "" } ) {} + + StatusCode initialize() override { + return BaseClass::initialize().andThen( [&]() { + m_model = m_mlservice->load( m_modelfile ); + m_input_shape = m_mlservice->input_shape( m_model ); + m_output_shape = m_mlservice->output_shape( m_model ); + if ( !( ( m_input_shape.size() > 2 ) && ( m_input_shape.size() == ( m_output_shape.size() + 1 ) ) ) ) + throw GaudiException( "Input and output dimensions don't match the assumption that one goes from 2D table to " + "1D table where the TO dimension (N) is part of the model, so of the shape N_FROM x N_TO " + "x N_FEATURES -> N_FROM, so N_FROM is batch size, N_TO is dynamic input size", + "MLServiceAlg2DTo1D", StatusCode::FAILURE ); + } ); + } + + OutputTable operator()( EventContext const& ctx, LHCb::Particle::Range const& inputs_from, + InputTable const& input_table ) const override { + OutputTable output_table; + + // prepare input feature functor + unsigned nFeatures = m_input_shape[2]; + auto const& fun = this->template getFunctor(); + + // we don't batch here as there is no guaruantee the TO size is constant per FROM object + for ( auto from : inputs_from ) { + auto const input_relations = input_table.relations( from ); + unsigned nRelations = input_relations.size(); + + Input model_inputs; + model_inputs.reserve( nRelations * nFeatures ); + for ( auto relation : input_relations ) { + auto input = fun( *relation.from(), *relation.to() ); + model_inputs.insert( model_inputs.end(), input.begin(), input.end() ); + } + + std::vector model_outputs( m_output_shape[1] ); + m_mlservice->evaluate( ctx, m_model, model_inputs, model_outputs, { 0, nRelations, nFeatures } ); + + output_table.relate( from, model_outputs ).ignore(); + } + + return output_table; + } + +private: + // main properties + Gaudi::Property m_modelfile{ this, "ModelFile" }; + + // miscellaneous + LHCb::ML::IModelService::Token m_model; + + LHCb::ML::IModelService::Shape m_input_shape; + LHCb::ML::IModelService::Shape m_output_shape; + + // services + ServiceHandle m_mlservice{ this, "MLService", "ONNXRuntimeSvc" }; +}; + +using MLServiceAlg_Relation1D = MLServiceAlg2D>; +using MLServiceAlg_Relation2D = MLServiceAlg2D>; +using MLServiceAlg_2DTo1D_Relation1D = MLServiceAlg2DTo1D>; +using MLServiceAlg_2DTo1D_Relation2D = MLServiceAlg2DTo1D>; DECLARE_COMPONENT_WITH_ID( MLServiceAlg, "MLServiceAlg" ) DECLARE_COMPONENT_WITH_ID( MLServiceAlg_Relation1D, "MLServiceAlg_Relation1D" ) DECLARE_COMPONENT_WITH_ID( MLServiceAlg_Relation2D, "MLServiceAlg_Relation2D" ) + +DECLARE_COMPONENT_WITH_ID( MLServiceAlg_2DTo1D_Relation1D, "MLServiceAlg_2DTo1D_Relation1D" ) +DECLARE_COMPONENT_WITH_ID( MLServiceAlg_2DTo1D_Relation2D, "MLServiceAlg_2DTo1D_Relation2D" )