Hypothesis Algorithms

Last update: 06 Aug 2024 [History] [Edit]

Overview

The task of performing selection on newly reconstructed candidate physics objects is split into two kinds of components: the hypothesis algorithm (HypoAlg), and a set of private tools called hypothesis tools (HypoTools).

The following uses typedef and helper functions from the TrigCompositeUtils namespace. The namespace is omitted below for readability.

The HypoTool

Every chain in the menu implements its selection using a series of HypoTools. There is no specific framework interface for HypoTools. HypoTools are all named identically to the chain whose selection they apply, and the configuration will typically contain multiple HypoTool instances per chain, implementing the chain’s selection at different steps. Each HypoTool instance is owned by a HypoAlg in each step (e.g. a fast calorimetry step, fast-tracking step, precision calorimetry step and precision tracking step). For example, the chain HLT_e20_loose_L1eEM18 would use multiple HypoTools (one per step) having the same tool instance name (HLT_e20_loose_L1eEM18). Because each HypoTool is a private tool, its Athena name consists of the owning HypoAlg instance name and the tool instance name e.g. FastElectronHypoAlg.HLT_e20_loose_L1eEM18, PrecisionElectronHypoAlg.HLT_e20_loose_L1eEM18, etc.

The HypoTool’s name gets converted into a HLT::Identifier that should be an attribute of every HypoTool class:

HLT::Identifier m_decisionId;

It is then set in the constructor initializer list:

m_decisionId( HLT::Identifier::fromToolName( name ) )

The HLT::Identifier allows easy conversion between the string and integer versions of the chain’s name, the integer version - DecisionID, is used by the HypoTool to flag its acceptance.

For a concrete example of a HypoTool, see the TrigEgammaPrecisionElectronHypoTool.

For multi-leg chains, an additional string is added to identify the leg. I.e. for HLT_e10_e20_L12eEM10 the framework would configure one HypoTool with name leg000_HLT_e10_e20_L12eEM10, configured as HLT_e10_L12eEM10, and another HypoTool with name leg001_HLT_e10_e20_L12eEM10, configured as HLT_e20_L12eEM10.

Tip The same HypoTool class is often used in different chains with different property settings. If you want to ensure that the HypoTool is configured explicitly (and not by accident running with default settings), you can use the AthCheckedComponent mix-in class, which checks that at least one property is set explicitly during the tool’s initialize method:

class MyHypoTool : public AthCheckedComponent<AthAlgTool>

The HypoAlg

All HypoAlgs are expected to inherit from the HypoBase class. This base class standardizes the input and output data handle names for decision collections. In addition, the base class enforces the HypoAlgs to be multi-threading ready; that is the execute method is const, which adds constraints to the implementation.

The HypoTools, used by the HypoAlg have to adhere to some user-defined interface. It can be an abstract tool interface, with a set of specific tools each providing an implementation which matches the interface. Or, if only a single implementation of HypoTool is sufficient, the implementation can itself define the interface. See an example TrigEgammaFastElectronHypoAlg.

Making decisions objects in an event

Each HypoAlg must acquire the set of HypoTools that it is responsible for executing. This is done via a private tool handle array which is populated by the menu generation code:

ToolHandleArray< HypoToolInterface > m_hypoTools {this, "HypoTools", {},
  "Tools that perform actual selection"};

In the HypoAlg::initialize method, the tools are retrieved. The key that corresponds to the collection of reconstructed objects that the HypoAlg will be filtering on should also be initialized. If the HypoAlg is running on the output of Event Views (see below), the key must be renounced from the data dependency resolution.

ATH_CHECK( m_hypoTools.retrieve() );
ATH_CHECK( m_objAHandleKey.initialize() );
renounce( m_objAHandleKey ); // Only if using Event Views

The logic in the HypoAlg::execute( const EventContext& context ) const method is responsible for acquiring the decisions of the previous step:

StatusCode HypoAlg::execute( const EventContext& context ) const {
  auto previousDecisionsHandle = SG::makeHandle( decisionInput(), context );
  // Creates the mutable output DecisionContainer and registers it to StoreGate.
  SG::WriteHandle<DecisionContainer> outputHandle 
    = createAndStore(decisionOutput(), context ); 
  DecisionContainer* decisions = outputHandle.ptr();

The HypoAlg obtains a collection of Decision objects from the previous step (with the initial set being derived from the L1 system), each storing a DecisionIDContainer of passing chains (as integer DecisionIDs). It additionally obtains reconstructed objects from the current step.

The job of the HypoAlg is to create a new Decision per object reconstructed in the current step, associate this with Decision object(s) from the previous step, and subject each object to all HypoTool instances. The HypoAlg should structure the data required by the HypoTool to perform their selection efficiently.

Here is an example custom structure to be passed to a HypoTool, it contains:

  • the set of passed DecisionIDs for this object from the previous step,
  • Event Data Model object(s) on which the HypoTool operates to obtain its decision,
  • a mutable decision object onto which the HypoTool can append its own DecisionID, should it pass the object.
// This can be a simple struct as it is only used to
// communicate efficiently between the HypoAlg and HypoTool
struct ExampleHypoInfo {
    // The set of positive decisions made at the previous step 
    // (this is a std::set<DecisionID> which is a std::set<uint32_t>)
    const DecisionIDContainer previousDecisionIDs;
    // A physics object that is needed by HypoTool to decide
    const ObjA* electron; 
    // another object that is necessary for selections
    const ObjB* cluster; 
    // and whatever else is necessary for tools
    // ... 
    // Place to store DecisionIDs from this step
    Decision* decision;
  };

The construction of a std::vector<ExampleHypoInfo> by the HypoAlg to be passed to its HypoTools differs depending on if the reconstruction step occurred in the full event context, or was restricted to one or more Event Views.

If the previous reconstruction step happened in Event Views

If the previous reconstruction step was performed in views, the reconstructed objects are extracted via the previousDecision. In this case, the handle for the input collection (m_objAHandleKey) should be renounced from the scheduler during initialization, as mentioned above.

For each reconstructed object, objA, a Decision object is created. A “feature” link must be created from the Decision object to objA. At least one Decision from within the previousDecision collection must be set as the parent of the Decision. This is done either by passing the previousDecision to the newDecisionIn call, or explicitly invoking linkToPrevious.

std::vector<ExampleHypoInfo> hypoToolInput;
for (const Decision* previousDecision: *previousDecisionsHandle) {

  // Extract the View from previousDecision
  // From this, obtain the collection of objA reconstructed within
  const auto viewELInfo = findLink< ViewContainer >( previousDecision, viewString() );
  ATH_CHECK( viewELInfo.isValid() );
  auto objACollectionHandle 
    = ViewHelper::makeHandle( *viewELInfo.link, m_objAHandleKey, context );
  ATH_CHECK( objACollectionHandle.isValid() );

  // Iterate over the object(s) reconstructed in the View, 
  // creating an output Decision object for each
  size_t objACounter = 0;
  for (const ObjectA* objA : *objACollectionHandle) {
    // Create a new output Decision object, d, backed by the 'decisions' container.
    // Links previousDecision as the parent of d.
    Decision* d = newDecisionIn( decisions, previousDecision );

    // Note: it may be necessary to re-map different decision(s) 
    // from the previousDecision container as the parent of d. 
    // If this is the case, then do the following where 
    // INDEX is the index within the previousDecision collection.
    // Decision* d = newDecisionIn( decisions );
    // linkToPrevious(d, decisionInput().key(), INDEX);

    // Obtain an ElementLink to objA and set mandatory feature link
    const ElementLink<ObjAContainer> objAEL 
      = ViewHelper::makeLink<ObjAContainer>( *viewELInfo.link, 
                                             objACollectionHandle, 
                                             objACounter );
    d->setObjectLink<ObjAContainer>( featureString(), objAEL );

    // Obtain anything else the HypoTool will need to make its decision.
    const ObjB* objb = ...;

    // Extract the set of previousDecisionIDs from the previousDecision.
    // If you needed to re-map the previous decision(s), then call decisionIDs once
    // for each previous decision
    DecisionIDContainer previousDecisionIDs;
    decisionIDs(previousDecision, previousDecisionIDs);

    // Collect all the required information for the tool together in a handy struct 
    hypoToolInput.push_back( ExampleHypoInfo{previousDecisionIDs, objA, objB, d} );

    ++objACounter;
  }
}

If the previous reconstruction step happened in the global event context

If the reconstruction was performed in the global event context (not in Views) the reconstructed objects have to be acquired using the input handles and matched to the previous decisions.

std::vector<ExampleHypoInfo> hypoToolInput;
const DecisionContainer* previousDecisions = *previousDecisionsHandle;
auto objACollectionHandle = SG::makeHandle( m_objAHandleKey, context );
for (const ObjectA* objA : *objACollectionHandle) {

  // Associate this object with one or more Decision objects from previousDecision
  // Here we assume that we expect to identify a single parent.
  const Decision* previousDecision = nullptr;
  for (const Decision* testDecision : *previousDecisionsHandle) {
    // associatedWithPreviousStep is some user-implemented matching logic
    // E.g. this could match to a RoIDescriptor 
    // retrieved from testDecision using findLink
    if (associatedWithPreviousStep(testDecision, objA)) {
      previousDecision = testDecision;
      break;
    }
  }
  if (!previousDecision) {
    ATH_MSG_ERROR("Unable to associate a previous decision.");
    return StatusCode::FAILURE;
  }

  // Create a new output Decision object, d, backed by the 'decisions' container.
  // Links previousDecision as the parent of d.
  Decision* d = newDecisionIn( decisions, previousDecision );

  // Obtain an ElementLink to objA and set mandatory feature link
  const ElementLink<ObjAContainer> objAEL 
    = ElementLink<ObjAContainer>( *objACollectionHandle, objA->index() );
  d->setObjectLink<ObjAContainer>( featureString(), objAEL );

  // Obtain anything else the HypoTool will need to make its decision.
  const ObjB* objb = ...;

  // Extract the set of previousDecisionIDs from the previousDecision.
  // If you needed to re-map the previous decision(s), then call decisionIDs once
  // for each previous decision
  DecisionIDContainer previousDecisionIDs;
  decisionIDs(previousDecision, previousDecisionIDs);

  // Collect all the required information for the tool together in a handy struct 
  hypoToolInput.push_back( ExampleHypoInfo{previousDecisionIDs, objA, objB, d} );
}

Running the HypoTools

Once the std::vector<ExampleHypoInfo> is prepared it has to be passed to all owned HypoTools then call the hypoBaseOutputProcessing function of the base class to validate that your Hypo has followed all of the rules and print out debug information.

//example code in the HypoAlg
 for ( auto & tool: m_hypoTools ) {
    ATH_CHECK( tool->decide( hypoToolInput ) );    
  } 
  
  ATH_CHECK( hypoBaseOutputProcessing(outputHandle) );  

The tool will typically iterate over the std::vector<ExampleHypoInfo> and, for every entry for which the previous decision was positive, evaluate the selection code and store any positive decisions.

std::vector<ExampleHypoInfo*> positive( input.size() );
for ( ExampleHypoInfo& hypoInfo: input ) {
  // Only consider objects which passed the 
  // corresponding HypoTool in the previous step.
  // This logic MUST be applied by your tool.
  if ( hypoInfo.previousDecisionsIDs.count( m_decisionId.numeric() ).count() > 0 ) {
    if (passSelection(hypoInfo)) { // User-defined
      addDecisionID( m_decisionId.numeric(), infoPtr->decision );
    }
  }
}

An example of such arrangement can be found in: Fast Electron in files: TrigEgammaPrecisionElectronHypoTool.h/cxx and TrigEgammaFastElectronHypoAlg.h/cxx

Monitoring

For how to add monitoring in the HypoTool and HypoAlg see the respective section about online monitoring.

Keep in mind that in the typical configuration there will be many HypoTools and thus adding any monitoring in them will result in multiple histograms that is potentially an issue for online monitoring information gathering.

To mitigate against this, you should check the monGroups list in the chain’s dictionary in your HypoTool generation function and and only enable monitoring in a HypoTool if an online monitoring group is present for the chain.

Subsequent Processing

The output of all HypoAlg instances is further processed by ComboHypoAlg instances, here combinatorial requirements of the chains are enforced. See the ComboHypo page for more.