Skip to content

Commit

Permalink
Add a filter algorithm (key4hep#210)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmcarcell authored Jul 25, 2024
1 parent da375fa commit 971ae2d
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 3 deletions.
27 changes: 27 additions & 0 deletions k4FWCore/include/k4FWCore/FilterPredicate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2014-2024 Key4hep-Project.
*
* This file is part of Key4hep.
* See https://key4hep.github.io/key4hep-doc/ for further info.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "Gaudi/Functional/FilterPredicate.h"
#include "k4FWCore/FunctionalUtils.h"

namespace k4FWCore {

template <typename Signature>
using FilterPredicate = Gaudi::Functional::FilterPredicate<Signature, details::BaseClass_t>;
}
47 changes: 45 additions & 2 deletions k4FWCore/include/k4FWCore/FunctionalUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "k4FWCore/DataWrapper.h"
#include "podio/CollectionBase.h"

#include "GaudiKernel/DataObjectHandle.h"
#include "GaudiKernel/EventContext.h"
#include "GaudiKernel/ThreadLocalContext.h"

Expand All @@ -42,14 +43,18 @@ namespace k4FWCore {
namespace details {

// This function will be used to modify std::shared_ptr<podio::CollectionBase> to the actual collection type
template <typename T, typename P> auto maybeTransformToEDM4hep(P& arg) { return arg; }

template <typename T, typename P>
requires std::same_as<P, std::vector<std::string, std::shared_ptr<podio::CollectionBase>>>
auto maybeTransformToEDM4hep(P& arg) {
return arg;
}

template <typename T, typename P>
requires(!std::is_same_v<P, std::shared_ptr<podio::CollectionBase>>)
const auto& maybeTransformToEDM4hep(const P& arg) {
return arg;
}

template <typename T, typename P>
requires std::is_base_of_v<podio::CollectionBase, P>
const auto& maybeTransformToEDM4hep(P* arg) {
Expand Down Expand Up @@ -216,6 +221,44 @@ namespace k4FWCore {
return out;
}

// Functional handles
template <typename T> class FunctionalDataObjectReadHandle : public ::details::ReadHandle<T> {
template <typename... Args, std::size_t... Is>
FunctionalDataObjectReadHandle(std::tuple<Args...>&& args, std::index_sequence<Is...>)
: FunctionalDataObjectReadHandle(std::get<Is>(std::move(args))...) {}

public:
/// Autodeclaring constructor with property name, mode, key and documentation.
/// @note the use std::enable_if is required to avoid ambiguities
template <typename OWNER, typename K, typename = std::enable_if_t<std::is_base_of_v<IProperty, OWNER>>>
FunctionalDataObjectReadHandle(OWNER* owner, std::string propertyName, K key = {}, std::string doc = "")
: ::details::ReadHandle<T>(owner, Gaudi::DataHandle::Reader, std::move(propertyName), std::move(key),
std::move(doc)) {}

template <typename... Args>
FunctionalDataObjectReadHandle(std::tuple<Args...>&& args)
: FunctionalDataObjectReadHandle(std::move(args), std::index_sequence_for<Args...>{}) {}

const T& get() const;
};

template <typename T> const T& FunctionalDataObjectReadHandle<T>::get() const {
auto dataObj = this->fetch();
if (!dataObj) {
throw GaudiException("Cannot retrieve \'" + this->objKey() + "\' from transient store.",
this->m_owner ? this->owner()->name() : "no owner", StatusCode::FAILURE);
}
auto ptr = dynamic_cast<AnyDataWrapper<std::shared_ptr<podio::CollectionBase>>*>(dataObj);
return maybeTransformToEDM4hep<T>(ptr->getData());
}

struct BaseClass_t {
template <typename T> using InputHandle = FunctionalDataObjectReadHandle<T>;
// template <typename T> using OutputHandle = DataObjectWriteHandle<T>;

using BaseClass = Gaudi::Algorithm;
};

} // namespace details
} // namespace k4FWCore

Expand Down
6 changes: 5 additions & 1 deletion test/k4FWCoreTest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ add_test_with_env(Testk4runVerboseOutput options/TestArgs.py --verbose PROPERTIE
add_test_with_env(Testk4runHelpOnly options/TestArgs.py --help PROPERTIES PASS_REGULAR_EXPRESSION "show this help message and exit")


# WARNING: If test names are changed, they also need to be changed below for the dependencies of
# the FunctionalCheckFiles test

add_test_with_env(FunctionalMemory options/ExampleFunctionalMemory.py)
add_test_with_env(FunctionalMTMemory options/ExampleFunctionalMTMemory.py)
add_test_with_env(FunctionalMultipleMemory options/ExampleFunctionalMultipleMemory.py)
Expand All @@ -131,9 +134,10 @@ add_test_with_env(FunctionalTransformerRuntimeEmpty options/ExampleFunctionalTra
add_test_with_env(FunctionalTransformerRuntimeCollectionsMultiple options/ExampleFunctionalTransformerRuntimeCollectionsMultiple.py)
add_test_with_env(FunctionalTransformerHist options/ExampleFunctionalTransformerHist.py)
add_test_with_env(FunctionalCollectionMerger options/ExampleFunctionalCollectionMerger.py)
add_test_with_env(FunctionalFilterFile options/ExampleFunctionalFilterFile.py)

add_test(NAME FunctionalCheckFiles COMMAND python3 ${CMAKE_CURRENT_LIST_DIR}/options/CheckOutputFiles.py)
set_tests_properties(FunctionalCheckFiles PROPERTIES DEPENDS "FunctionalFile;FunctionalMTFile;FunctionalMultipleFile;FunctionalOutputCommands;FunctionalProducerAbsolutePath;FunctionalTransformerRuntimeEmpty;FunctionalMix;FunctionalMixIOSvc;FunctionalTransformerHist;FunctionalCollectionMerger")
set_tests_properties(FunctionalCheckFiles PROPERTIES DEPENDS "FunctionalFile;FunctionalMTFile;FunctionalMultipleFile;FunctionalOutputCommands;FunctionalProducerAbsolutePath;FunctionalTransformerRuntimeEmpty;FunctionalMix;FunctionalMixIOSvc;FunctionalTransformerHist;FunctionalCollectionMerger;FunctionalFilterFile")

# Do this after checking the files not to overwrite them
add_test_with_env(FunctionalFile_toolong options/ExampleFunctionalFile.py -n 999 PROPERTIES DEPENDS FunctionalCheckFiles PASS_REGULAR_EXPRESSION
Expand Down
14 changes: 14 additions & 0 deletions test/k4FWCoreTest/options/CheckOutputFiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ def check_collections(filename, names):
raise RuntimeError("Collections in frame do not match expected collections")


def check_events(filename, number):
print(f'Checking file "{filename}" for {number} events')
podio_reader = podio.root_io.Reader(filename)
frames = podio_reader.get("events")
if len(frames) != number:
print(f"File {filename} has {len(frames)} events but {number} are expected")
raise RuntimeError("Number of events does not match expected number")


check_collections("functional_transformer.root", ["MCParticles", "NewMCParticles"])
check_collections(
"functional_transformer_multiple.root",
Expand Down Expand Up @@ -140,3 +149,8 @@ def check_collections(filename, names):
ev = frames[0]
if len(ev.get("NewMCParticles")) != 4:
raise RuntimeError(f"Expected 4 NewMCParticles but got {len(ev.get('NewMCParticles'))}")

check_events(
"functional_filter.root",
5,
)
52 changes: 52 additions & 0 deletions test/k4FWCoreTest/options/ExampleFunctionalFilterFile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#
# Copyright (c) 2014-2024 Key4hep-Project.
#
# This file is part of Key4hep.
# See https://key4hep.github.io/key4hep-doc/ for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# This is an creating some collections and filtering after
# to check that the contents of the file are the expected ones

from Gaudi.Configuration import INFO
from Configurables import (
ExampleFunctionalTransformer,
ExampleFunctionalProducer,
ExampleFunctionalFilter,
)
from k4FWCore import ApplicationMgr, IOSvc
from Configurables import EventDataSvc

iosvc = IOSvc("IOSvc")
iosvc.output = "functional_filter.root"

transformer = ExampleFunctionalTransformer(
"Transformer", InputCollection=["MCParticles"], OutputCollection=["NewMCParticles"]
)

producer = ExampleFunctionalProducer("Producer", OutputCollection=["MCParticles"])

filt = ExampleFunctionalFilter(
"Filter",
InputCollection="NewMCParticles",
)

ApplicationMgr(
TopAlg=[producer, transformer, filt],
EvtSel="NONE",
EvtMax=10,
ExtSvc=[EventDataSvc("EventDataSvc")],
OutputLevel=INFO,
)
52 changes: 52 additions & 0 deletions test/k4FWCoreTest/src/components/ExampleFunctionalFilter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2014-2024 Key4hep-Project.
*
* This file is part of Key4hep.
* See https://key4hep.github.io/key4hep-doc/ for further info.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "Gaudi/Accumulators.h"
#include "edm4hep/MCParticleCollection.h"

#include "k4FWCore/FilterPredicate.h"

#include <string>

struct ExampleFunctionalFilter final : k4FWCore::FilterPredicate<bool(const edm4hep::MCParticleCollection& input)> {
// The pair in KeyValue can be changed from python and it corresponds
// to the name of the input collection
ExampleFunctionalFilter(const std::string& name, ISvcLocator* svcLoc)
: FilterPredicate(name, svcLoc, KeyValue("InputCollection", {"MCParticles"})) {}

// This is the function that will be called to check if the event passes
// Note that the function has to be const, as well as the collections
// we get from the input
bool operator()(const edm4hep::MCParticleCollection& input) const override {
++m_counter;
// Only pass for half of the events
bool condition = m_counter.sum() % 2;
if (condition) {
info() << "Event passed" << endmsg;
} else {
info() << "Event did not pass" << endmsg;
}
return condition;
}

// Thread-safe counter
mutable Gaudi::Accumulators::Counter<> m_counter{this, "Counter"};
};

DECLARE_COMPONENT(ExampleFunctionalFilter)

0 comments on commit 971ae2d

Please sign in to comment.