Skip to content

Commit 18eb5f3

Browse files
authored
DPL Analysis: Introducing conditional expressions (#6753)
`ifnode(condition, then, else)` operation is added to expressions. These can be nested and all three arguments can be arbitrary valid expressions. `condition` needs to have boolean result, `then` and `else` should return similar types (ideally the same - both floats, or both boolean, etc.). A `conditionalExpressions.cxx` tutorial example is added (note that it uses bitwise operations in filter expression and thus will only work as is with arrow > 3).
1 parent bd46d51 commit 18eb5f3

8 files changed

Lines changed: 334 additions & 44 deletions

File tree

Analysis/Tutorials/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,9 @@ o2_add_dpl_workflow(multiprocess-example
228228
JOB_POOL analysis
229229
PUBLIC_LINK_LIBRARIES O2::Framework O2::AnalysisCore O2::AnalysisDataModel
230230
COMPONENT_NAME AnalysisTutorial)
231+
232+
o2_add_dpl_workflow(conditional-expressions
233+
SOURCES src/conditionalExpressions.cxx
234+
JOB_POOL analysis
235+
PUBLIC_LINK_LIBRARIES O2::Framework O2::AnalysisCore O2::AnalysisDataModel
236+
COMPONENT_NAME AnalysisTutorial)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
///
12+
/// \brief Demonstration of conditions in filter expressions
13+
14+
#include "Framework/runDataProcessing.h"
15+
#include "Framework/AnalysisTask.h"
16+
17+
using namespace o2;
18+
using namespace o2::framework;
19+
using namespace o2::framework::expressions;
20+
21+
struct ConditionalExpressions {
22+
Configurable<bool> useFlags{"useFlags", false, "Switch to enable using track flags for selection"};
23+
Filter trackFilter = nabs(aod::track::eta) < 0.9f && aod::track::pt > 0.5f && ifnode(useFlags.node() == true, (aod::track::flags & static_cast<uint32_t>(o2::aod::track::ITSrefit)) != 0u, true);
24+
OutputObj<TH2F> etapt{TH2F("etapt", ";#eta;#p_{T}", 201, -2.1, 2.1, 601, 0, 60.1)};
25+
void process(aod::Collision const&, soa::Filtered<soa::Join<aod::Tracks, aod::TracksExtra>> const& tracks)
26+
{
27+
for (auto& track : tracks) {
28+
etapt->Fill(track.eta(), track.pt());
29+
}
30+
}
31+
};
32+
33+
struct BasicOperations {
34+
Configurable<bool> useFlags{"useFlags", false, "Switch to enable using track flags for selection"};
35+
Filter trackFilter = nabs(aod::track::eta) < 0.9f && aod::track::pt > 0.5f;
36+
OutputObj<TH2F> etapt{TH2F("etapt", ";#eta;#p_{T}", 201, -2.1, 2.1, 601, 0, 60.1)};
37+
void process(aod::Collision const&, soa::Filtered<soa::Join<aod::Tracks, aod::TracksExtra>> const& tracks)
38+
{
39+
for (auto& track : tracks) {
40+
if (useFlags) {
41+
if ((track.flags() & o2::aod::track::ITSrefit) != 0u) {
42+
etapt->Fill(track.eta(), track.pt());
43+
}
44+
} else {
45+
etapt->Fill(track.eta(), track.pt());
46+
}
47+
}
48+
}
49+
};
50+
51+
WorkflowSpec defineDataProcessing(ConfigContext const& cfgc)
52+
{
53+
return WorkflowSpec{
54+
adaptAnalysisTask<ConditionalExpressions>(cfgc),
55+
adaptAnalysisTask<BasicOperations>(cfgc)};
56+
}

Framework/Core/include/Framework/BasicOps.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ enum BasicOp : unsigned int {
4141
Acos,
4242
Atan,
4343
Abs,
44-
BitwiseNot
44+
BitwiseNot,
45+
Conditional
4546
};
4647
} // namespace o2::framework
4748

Framework/Core/include/Framework/Configurable.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
#include <vector>
1616
namespace o2::framework
1717
{
18+
namespace expressions
19+
{
20+
struct PlaceholderNode;
21+
}
22+
1823
template <typename T, ConfigParamKind K>
1924
struct ConfigurableBase {
2025
ConfigurableBase(std::string const& name, T&& defaultValue, std::string const& help)
@@ -68,6 +73,10 @@ struct Configurable : IP {
6873
: IP{name, std::forward<T>(defaultValue), help}
6974
{
7075
}
76+
auto node()
77+
{
78+
return expressions::PlaceholderNode{*this};
79+
}
7180
};
7281

7382
template <typename T, ConfigParamKind K = ConfigParamKind::kGeneric>

Framework/Core/include/Framework/ExpressionHelpers.h

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ namespace o2::framework::expressions
2020
{
2121
/// a map between BasicOp and gandiva node definitions
2222
/// note that logical 'and' and 'or' are created separately
23-
static std::array<std::string, BasicOp::BitwiseNot + 1> basicOperationsMap = {
23+
static std::array<std::string, BasicOp::Conditional + 1> basicOperationsMap = {
2424
"and",
2525
"or",
2626
"add",
@@ -48,7 +48,8 @@ static std::array<std::string, BasicOp::BitwiseNot + 1> basicOperationsMap = {
4848
"acosf",
4949
"atanf",
5050
"absf",
51-
"bitwise_not"};
51+
"bitwise_not",
52+
"if"};
5253

5354
struct DatumSpec {
5455
/// datum spec either contains an index, a value of a literal or a binding label
@@ -72,17 +73,21 @@ bool operator==(DatumSpec const& lhs, DatumSpec const& rhs);
7273
std::ostream& operator<<(std::ostream& os, DatumSpec const& spec);
7374

7475
struct ColumnOperationSpec {
76+
size_t index = 0;
7577
BasicOp op;
7678
DatumSpec left;
7779
DatumSpec right;
80+
DatumSpec condition;
7881
DatumSpec result;
7982
atype::type type = atype::NA;
8083
ColumnOperationSpec() = default;
81-
// TODO: extend this to support unary ops seamlessly
82-
explicit ColumnOperationSpec(BasicOp op_) : op{op_},
83-
left{},
84-
right{},
85-
result{}
84+
explicit ColumnOperationSpec(BasicOp op_, size_t index_ = 0)
85+
: index{index_},
86+
op{op_},
87+
left{},
88+
right{},
89+
condition{},
90+
result{}
8691
{
8792
switch (op) {
8893
case BasicOp::LogicalOr:
@@ -110,6 +115,10 @@ struct NodeRecord {
110115
Node* node_ptr = nullptr;
111116
size_t index = 0;
112117
explicit NodeRecord(Node* node_, size_t index_) : node_ptr(node_), index{index_} {}
118+
bool operator!=(NodeRecord const& rhs)
119+
{
120+
return this->node_ptr != rhs.node_ptr;
121+
}
113122
};
114123
} // namespace o2::framework::expressions
115124

Framework/Core/include/Framework/Expressions.h

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ struct OpNode {
128128
/// A placeholder node for simple type configurable
129129
struct PlaceholderNode : LiteralNode {
130130
template <typename T>
131-
PlaceholderNode(Configurable<T> v) : LiteralNode{v.value}, name{v.name}
131+
PlaceholderNode(Configurable<T> const& v) : LiteralNode{v.value}, name{v.name}
132132
{
133133
if constexpr (variant_trait_v<typename std::decay<T>::type> != VariantType::Unknown) {
134134
retrieve = [](InitContext& context, std::string const& name) { return LiteralNode::var_t{context.options().get<T>(name.c_str())}; };
@@ -146,40 +146,54 @@ struct PlaceholderNode : LiteralNode {
146146
LiteralNode::var_t (*retrieve)(InitContext&, std::string const& name);
147147
};
148148

149+
/// A conditional node
150+
struct ConditionalNode {
151+
};
152+
149153
/// A generic tree node
150154
struct Node {
151-
Node(LiteralNode v) : self{v}, left{nullptr}, right{nullptr}
155+
Node(LiteralNode v) : self{v}, left{nullptr}, right{nullptr}, condition{nullptr}
152156
{
153157
}
154158

155-
Node(PlaceholderNode v) : self{v}, left{nullptr}, right{nullptr}
159+
Node(PlaceholderNode v) : self{v}, left{nullptr}, right{nullptr}, condition{nullptr}
156160
{
157161
}
158162

159-
Node(Node&& n) : self{n.self}, left{std::move(n.left)}, right{std::move(n.right)}
163+
Node(Node&& n) : self{n.self}, left{std::move(n.left)}, right{std::move(n.right)}, condition{std::move(n.condition)}
160164
{
161165
}
162166

163-
Node(BindingNode n) : self{n}, left{nullptr}, right{nullptr}
167+
Node(BindingNode n) : self{n}, left{nullptr}, right{nullptr}, condition{nullptr}
164168
{
165169
}
166170

171+
Node(ConditionalNode op, Node&& then_, Node&& else_, Node&& condition_)
172+
: self{op},
173+
left{std::make_unique<Node>(std::move(then_))},
174+
right{std::make_unique<Node>(std::move(else_))},
175+
condition{std::make_unique<Node>(std::move(condition_))} {}
176+
167177
Node(OpNode op, Node&& l, Node&& r)
168178
: self{op},
169179
left{std::make_unique<Node>(std::move(l))},
170-
right{std::make_unique<Node>(std::move(r))} {}
180+
right{std::make_unique<Node>(std::move(r))},
181+
condition{nullptr} {}
171182

172183
Node(OpNode op, Node&& l)
173184
: self{op},
174185
left{std::make_unique<Node>(std::move(l))},
175-
right{nullptr} {}
186+
right{nullptr},
187+
condition{nullptr} {}
176188

177189
/// variant with possible nodes
178-
using self_t = std::variant<LiteralNode, BindingNode, OpNode, PlaceholderNode>;
190+
using self_t = std::variant<LiteralNode, BindingNode, OpNode, PlaceholderNode, ConditionalNode>;
179191
self_t self;
192+
size_t index = 0;
180193
/// pointers to children
181194
std::unique_ptr<Node> left;
182195
std::unique_ptr<Node> right;
196+
std::unique_ptr<Node> condition;
183197
};
184198

185199
/// overloaded operators to build the tree from an expression
@@ -319,20 +333,84 @@ inline Node nbitwise_not(Node left)
319333
return Node{OpNode{BasicOp::BitwiseNot}, std::move(left)};
320334
}
321335

336+
/// conditionals
337+
template <typename C, typename T, typename E>
338+
inline Node ifnode(C condition_, T then_, E else_)
339+
{
340+
return Node{ConditionalNode{}, std::move(then_), std::move(else_), std::move(condition_)};
341+
}
342+
343+
template <>
344+
inline Node ifnode(Node condition_, Node then_, Node else_)
345+
{
346+
return Node{ConditionalNode{}, std::move(then_), std::move(else_), std::move(condition_)};
347+
}
348+
349+
template <typename L, std::enable_if_t<std::is_integral<L>::value || std::is_floating_point<L>::value, bool> = true>
350+
inline Node ifnode(Node condition_, Node then_, L else_)
351+
{
352+
return Node{ConditionalNode{}, std::move(then_), LiteralNode{else_}, std::move(condition_)};
353+
}
354+
355+
template <typename L, std::enable_if_t<std::is_integral<L>::value || std::is_floating_point<L>::value, bool> = true>
356+
inline Node ifnode(Node condition_, L then_, Node else_)
357+
{
358+
return Node{ConditionalNode{}, LiteralNode{then_}, std::move(else_), std::move(condition_)};
359+
}
360+
361+
template <typename L1, typename L2, std::enable_if_t<(std::is_integral<L1>::value || std::is_floating_point<L1>::value) && (std::is_integral<L2>::value || std::is_floating_point<L2>::value), bool> = true>
362+
inline Node ifnode(Node condition_, L1 then_, L2 else_)
363+
{
364+
return Node{ConditionalNode{}, LiteralNode{then_}, LiteralNode{else_}, std::move(condition_)};
365+
}
366+
367+
template <typename T>
368+
inline Node ifnode(Configurable<T> condition_, Node then_, Node else_)
369+
{
370+
return Node{ConditionalNode{}, std::move(then_), std::move(else_), PlaceholderNode{condition_}};
371+
}
372+
373+
template <typename L>
374+
inline Node ifnode(Node condition_, Node then_, Configurable<L> else_)
375+
{
376+
return Node{ConditionalNode{}, std::move(then_), PlaceholderNode{else_}, std::move(condition_)};
377+
}
378+
379+
template <typename L>
380+
inline Node ifnode(Node condition_, Configurable<L> then_, Node else_)
381+
{
382+
return Node{ConditionalNode{}, PlaceholderNode{then_}, std::move(else_), std::move(condition_)};
383+
}
384+
385+
template <typename L1, typename L2>
386+
inline Node ifnode(Node condition_, Configurable<L1> then_, Configurable<L2> else_)
387+
{
388+
return Node{ConditionalNode{}, PlaceholderNode{then_}, PlaceholderNode{else_}, std::move(condition_)};
389+
}
390+
322391
/// A struct, containing the root of the expression tree
323392
struct Filter {
324-
Filter(Node&& node_) : node{std::make_unique<Node>(std::move(node_))} {}
325-
Filter(Filter&& other) : node{std::move(other.node)} {}
393+
Filter(Node&& node_) : node{std::make_unique<Node>(std::move(node_))}
394+
{
395+
(void)designateSubtrees(node.get());
396+
}
397+
398+
Filter(Filter&& other) : node{std::move(other.node)}
399+
{
400+
(void)designateSubtrees(node.get());
401+
}
326402
std::unique_ptr<Node> node;
403+
404+
size_t designateSubtrees(Node* node, size_t index = 0);
327405
};
328406

329407
using Projector = Filter;
330408

331409
using Selection = std::shared_ptr<gandiva::SelectionVector>;
332410
/// Function for creating gandiva selection from our internal filter tree
333-
Selection createSelection(std::shared_ptr<arrow::Table> table, Filter const& expression);
411+
Selection createSelection(std::shared_ptr<arrow::Table> const& table, Filter const& expression);
334412
/// Function for creating gandiva selection from prepared gandiva expressions tree
335-
Selection createSelection(std::shared_ptr<arrow::Table> table, std::shared_ptr<gandiva::Filter> gfilter);
413+
Selection createSelection(std::shared_ptr<arrow::Table> const& table, std::shared_ptr<gandiva::Filter> gfilter);
336414

337415
struct ColumnOperationSpec;
338416
using Operations = std::vector<ColumnOperationSpec>;

0 commit comments

Comments
 (0)