Skip to content

Commit b6ce899

Browse files
Merge pull request #204 from iris-hep/documentation_updates
Documentation updates
2 parents 0226db9 + ba7b473 commit b6ce899

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,6 @@ xAOD/getting_setup
3434
xAOD/calibration
3535
xAOD/select
3636
xAOD/where
37+
xAOD/metadata
3738
3839
```

docs/xAOD/metadata.md

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# Using .MetaData()
2+
3+
Some analyses require more complex logic than what .Select() and .Where() can provide. In such cases, the .MetaData() operator allows C++ code to be injected directly into the query, executing as if it were written natively in the EventLoop code. Use of .MetaData() and the following examples is recommended only when specific analysis tasks cannot be achieved with .Select() or .Where().
4+
5+
This section is not intended for a first pass through the documentation.
6+
7+
## When to Use .MetaData()
8+
9+
Some values in an analysis are unable to be acquired using the functional style of FuncADL. Some of these cases are as follows:
10+
11+
- An example of this is when the value needed is returned by reference/pointer and not returned by the function. This is a time that the .MetaData() operator is needed.
12+
- Adding a selection tool
13+
14+
## Adding a Basic C++ Function
15+
16+
This example demonstrates how to use FuncADL to inject and run C++ code by implementing a simple function that squares input values. Although squaring a number can easily be done with the .Select() operator, this example uses it to illustrate the basic process of executing C++ code within FuncADL.
17+
18+
The added complexity of injecting a C++ function arises because the .MetaData() operator cannot be used inline like .Select() or .Where() in previous examples. This is due to the need to create a callable function that can be invoked within the FuncADL query. Showing the end result can help illustrate this process:
19+
20+
```python
21+
squared_numbers = (query
22+
.Select(lambda e: {
23+
"squared": square(2)
24+
})
25+
)
26+
```
27+
28+
In this example, the function square() must exist in Python for the query to run without errors. To achieve this, a dummy function is created and linked to a function containing the .MetaData() operator. Both functions must be set up before creating the query.
29+
30+
The first step to setting up these functions is that several additional imports are required, and a type is defined for later use.
31+
32+
```python
33+
import ast
34+
from func_adl import ObjectStream, func_adl_callable
35+
from typing import Tuple, TypeVar
36+
T = TypeVar("T")
37+
```
38+
39+
Next, the dummy function is created and linked to the function that injects .MetaData() into the query using the `@func_adl_callable` decorator. The `square_callable` function will be defined in the following part.
40+
41+
```python
42+
@func_adl_callable(square_callable)
43+
def square(x: int) -> int:
44+
"""Take an input number and return that value squared.
45+
46+
Args:
47+
x (int): The value to square
48+
49+
Returns:
50+
int: result of squaring the input
51+
"""
52+
...
53+
```
54+
55+
Next, a function is defined to add the C++ function to the query. This must be done inside a function rather than directly in the query so that Python can recognize the previously defined function. The function takes the ObjectStream to which .MetaData() is applied, along with an ast.Call object, which is passed through unchanged.
56+
57+
```python
58+
def square_callable(
59+
s: ObjectStream[T], a: ast.Call
60+
) -> Tuple[ObjectStream[T], ast.Call]:
61+
new_s = s.MetaData(
62+
{
63+
"metadata_type": "add_cpp_function",
64+
"name": "square",
65+
"code": [
66+
"int result = x * x;\n"
67+
],
68+
"result": "result",
69+
"include_files": [],
70+
"arguments": ["x"],
71+
"return_type": "int",
72+
}
73+
)
74+
return new_s, a
75+
```
76+
77+
Next, the .MetaData() operator is called on the current ObjectStream, adding the metadata to the stream. In this case, the metadata being added is the C++ function. When adding a C++ function, the function name, code body, arguments, and return type must be specified.
78+
79+
In this example, the input integer is named x. It is multiplied by itself to produce the squared value, which is assigned to the variable result. The metadata sets result as the output, with a return type of int. It is essential that the dummy function and the C++ function share the same name; otherwise, an error will occur.
80+
81+
With these two functions defined, the target query can now be executed.
82+
83+
This call gives this result for a dataset with 1410 events:
84+
85+
```python
86+
[{squared: 4},
87+
{squared: 4},
88+
...,
89+
{squared: 4},
90+
{squared: 4}]
91+
--------------
92+
type: 1410 * {
93+
squared: int32
94+
}
95+
```
96+
97+
## Adding a C++ Function from an Analysis
98+
99+
The code implemented using the .MetaData() operator is analysis-specific and is therefore not detailed in this documentation. However, an example from an analysis can illustrate how similar code might be structured.
100+
101+
In the example analysis, track summary values are required. The summaryValue() function returns a boolean indicating whether the value exists and provides the value via a reference argument. Because the function does not return the value directly, it cannot be used by FuncADL on its own. A C++ function using .MetaData() is required to access this information.
102+
103+
The following code sets up the function that will be called in the FuncADL query:
104+
105+
```python
106+
107+
from func_adl_servicex_xaodr25.xAOD.trackparticle_v1 import TrackParticle_v1
108+
from func_adl_servicex_xaodr25.xaod import xAOD, add_enum_info
109+
110+
def track_summary_value_callable(
111+
s: ObjectStream[T], a: ast.Call
112+
) -> Tuple[ObjectStream[T], ast.Call]:
113+
"""The trackSummary method returns true/false if the value is there,
114+
and alter an argument passed by reference. In short, this isn't functional,
115+
so it won't work in `func_adl`. This wraps it to make it "work".
116+
117+
Args:
118+
s (ObjectStream[T]): The stream we are operating against
119+
a (ast.Call): The actual call
120+
121+
Returns:
122+
Tuple[ObjectStream[T], ast.Call]: Return the updated stream with the metadata code.
123+
"""
124+
new_s = s.MetaData(
125+
{
126+
"metadata_type": "add_cpp_function",
127+
"name": "track_summary_value",
128+
"code": [
129+
"uint8_t result;\n"
130+
"xAOD::SummaryType st (static_cast<xAOD::SummaryType>(value_selector));\n"
131+
"if (!(*trk).summaryValue(result, st)) {\n"
132+
" result = -1;\n"
133+
"}\n"
134+
],
135+
"result": "result",
136+
"include_files": [],
137+
"arguments": ["trk", "value_selector"],
138+
"return_type": "int",
139+
}
140+
)
141+
new_s = add_enum_info(new_s, "SummaryType")
142+
return new_s, a
143+
144+
145+
@func_adl_callable(track_summary_value_callable)
146+
def track_summary_value(trk: TrackParticle_v1, value_selector: xAOD.SummaryType) -> int:
147+
"""Call the `trackSummary` method on a track.
148+
149+
* Return the value of the value_selector for the track
150+
* If it isn't present, return -1.
151+
152+
Args:
153+
trk (TrackParticle_v1): The track we are operating against
154+
value_selector (int): Which value (pixel holes, etc.)
155+
156+
Returns:
157+
int: Value requested or -1 if not available.
158+
"""
159+
...
160+
161+
```
162+
163+
The code to then run this example is as follows:
164+
165+
```python
166+
track_values = (query
167+
.Select(lambda e: {
168+
"track_SCTHits": [
169+
track_summary_value(t, xAOD.SummaryType.numberOfSCTHits)
170+
for t in e.TrackParticles("InDetTrackParticles")
171+
],
172+
})
173+
)
174+
```

0 commit comments

Comments
 (0)