# Combining data: pairs, triplets and beyond

You can use helper functions from `ASoAHelpers.h`

to get combinations of elements (collisions, tracks, …). There are 3 basic combinations policies available. Assuming that we want to get pairs of elements from tables with sizes (5, 6):

`CombinationsFullIndexPolicy`

:

Row numbers of elements: (0, 0), …, (0, 5), (1, 0), …, (1, 5), …, (4, 0), …, (4, 5)`CombinationsUpperIndexPolicy`

:

Row numbers of elements: (0, 0), …, (0, 5), (1, 1), …, (1, 5), …, (4, 4), (4, 5)

- no repetitions of pairs like (0, 1) and (1, 0)

`CombinationsStrictlyUpperIndexPolicy`

:

Row numbers of elements: (0, 1), …, (0, 5), (1, 2), …, (1, 5), …, (3, 5)

- max position: (table size - distance from the rightmost iterator) = (4, 6), that's why the last pair is (3, 5) and not (4, 5)
- no repetitions of pairs like (0, 1) and (1, 0)
- no repeated positions within a single tuple, e.g., (0, 0)

The number of elements in a combination is deduced from the number of arguments passed to `combinations()`

call. For example, to get pairs of tracks from the same source one must specify `tracks`

table twice:

```
struct MyTask : AnalysisTask {
void process(Tracks const& tracks) {
for (auto& [t0, t1] : combinations(CombinationsStrictlyUpperIndexPolicy(tracks, tracks))) {
float pt = t0.pt();
...
}
}
};
```

The combination can consist of elements from different tables (of different kinds):

```
struct MyTask : AnalysisTask {
void process(Tracks const& tracks, TracksCov const& covs) {
for (auto& [t0, c1] : combinations(CombinationsFullIndexPolicy(tracks, covs))) {
...
}
}
};
```

## Combinations with filters

It is possible to specify a filter in the argument list, and only matching combinations are output. Currently, the filter is applied to each element separately, so it is slower than simple combinations over an already filtered table. Note that for filter version the input tables are mentioned twice, both in the policy constructor and in the `combinations()`

call itself.

```
struct MyTask : AnalysisTask {
void process(Tracks const& tracks1, Tracks const& tracks2) {
Filter triplesFilter = track::eta < 0;
for (auto& [t0, t1, t2] : combinations(CombinationsFullIndexPolicy(tracks1, tracks2, tracks2), triplesFilter, tracks1, tracks2, tracks2)) {
// Triples of tracks, each of them with eta < 0
...
}
}
};
```

## Block / binned combination policies

Block policies allow for generating tuples of elements according to the binning policy provided by the user. The binning policy calculates bin numbers for the input elements and groups the elements by bins. Then, the block combinations output tuples of elements from the same bin. Analogously to basic policies, we have full / upper / strictly upper block combinations.

Different tables:

`CombinationsBlockUpperIndexPolicy`

`CombinationsBlockFullIndexPolicy`

Performance-efficient policies for getting tuples of elements from the same table:

`CombinationsBlockUpperSameIndexPolicy`

`CombinationsBlockFullSameIndexPolicy`

`CombinationsBlockStrictlyUpperSameIndexPolicy`

### Binning policies

There are 2 binning policies:

`FlexibleBinningPolicy`

`ColumnBinningPolicy`

together with the base class`BinningPolicyBase`

which contains methods for calculating bins for given data.

`FlexibleBinningPolicy`

can be defined by both table columns and lambda functions while `ColumnBinningPolicy`

accepts only columns. The column policy is used in varioux examples in the event mixing tutorial, while the last task of the tutorial depicts how to utilize flexible binning.

Besides these differences, a binning policy accepts an array of bins (C++ vectors), and a bool specifying whether under- and overflow values should be ignored. If it is set to true, all underflow and overflow values are assigned to a dummy bin `-1`

. The first non-underflow bin is 0. If the bool is false, then the values that are underflow in all dimensions are included in the bin 0, the first non-underflow bin is 1, and there are more bins for under- and overflow values from specific dimensions.

Note that Binning Policy is defined only for 1-, 2-, and 3-dimensional binning. If you want to bin objects based on 4 or more properties, you need to write yourself a class inheriting from Binning Policy with customized `getBin()`

function.

Additionally, there is a `NoBinningPolicy`

class which can be used in case you already have bin numbers in your data and you do not want to calculate any new bins. This class has an empty constructor templated with the type of the column to consider. It will take one by one the values from that column as bin numbers. There is a tutorial example with `NoBinningPolicy`

in tracksCombinations.cxx.

Block policies have 2 additional parameters: `outsider`

and `categoryNeighbours`

.

`Outsider`

is the number of a bin that should be ignored. Usually, one uses BinningPolicy with `ignoreOverflows`

set to `true`

, and then the block policy with `outsider`

set to `-1`

, so as to obtain block combinations without under- and overflow values.

`CategoryNeighbours`

is the number of the consecutive following elements a given element is combined with. For performance reasons, tuples of elements traditionally are not generated over the whole bin, but over several much smaller intervals.

Example: `categoryNeighbours = 4`

, the bin contains elements at rows: 1, 3, 5, 6, 10, 13, 16, 19

Strictly upper pairs (different colors mark pairs from different 5-element combinations intervals): (1, 3), …, (1, 10), (3, 5), …, (6, 10), (3, 5), …, (10, 13), (5, 6), …, (13, 16), …

Note that some pairs get repeated, e.g., (3, 5).

To get the behavior without sliding windows, set category neighbours to a very high value.

Below, you can see a full example of block combinations in an analysis task:

```
struct BinnedTrackCombinations {
std::vector<double> xBins{VARIABLE_WIDTH, -0.064, -0.062, -0.060, 0.066, 0.068, 0.070, 0.072};
std::vector<double> yBins{VARIABLE_WIDTH, -0.320, -0.301, -0.300, 0.330, 0.340, 0.350, 0.360};
BinningPolicy<aod::track::X, aod::track::Y> trackBinning{{xBins, yBins}, true};
void process(aod::Tracks const& tracks)
{
// Strictly upper tracks binned by x and y position
for (auto& [t0, t1] : selfCombinations(trackBinning, 5, -1, tracks, tracks)) { ... }
}
};
```

## Helper functions (shortcuts)

Accepts only the same tables, applies block strictly upper policy:

```
selfCombinations(binningPolicy, categoryNeighbours, outsider, tables...)
// equivalent to combinations(CombinationsBlockStrictlyUpperSameIndexPolicy(binningPolicy, categoryNeighbours, outsider, tables...))
```

Pairs / triples of block strictly upper combinations from the same table:

```
selfPairCombinations(binningPolicy, categoryNeighbours, outsider, table)
selfTripleCombinations(binningPolicy, categoryNeighbours, outsider, table)
```

If tables are the same, applies block strictly upper, otherwise block upper policy:

```
combinations(binningPolicy, categoryNeighbours, outsider, tables...)
```

If tables are the same, applies strictly upper, otherwise upper policy:

```
combinations(tables...)
combinations(filter, tables...)
```

Pairs / triples of strictly upper combinations from the same table:

```
pairCombinations(table)
tripleCombinations(table)
```

Applies selected combination policy

```
combinations(combinationPolicy)
```

You can see some combinations examples in the tracksCombinations.cxx tutorial.

The tracks in dataset might be implicitly ordered. As a result, for example, you can observe slight asymmetries in same-event 2-particle correlations obtained with `StrictlyUpperIndexPolicy`

. The recommended solutions are:

- Randomly swap tracks in pairs or
- Use
`FullIndexPolicy`

and apply additional condition to exclude pairs with the same element repeated ((0, 0), (1, 1), and so on). At the end, you need to apply proper statistic corrections in your analysis, as you count each unique pair twice.

## Weighted combinations

You might need to calculate weights for your event mixing. You can get useful variables:

`currentWindowNeighbours()`

– the number of other collisions to pair with; it is smaller if we are at the end of the bin or sliding window- bool
`isNewWindow()`

– true only for the first pair from each sliding window

**NOTE:** The same number of `currentWindowNeighbours`

is returned for all kinds of block combinations but the interpretation is different:

- Strictly upper: the first element will is paired with exactly
`currentWindowNeighbours`

other elements. - Upper: the first element is paired with
`currentWindowNeighbours + 1`

elements, including itself. - Full:
`currentWindowNeighbours + 1`

pairs with the first element in the first position (`c1`

) + there are other combinations with the first element at other positions.

Code examples: event mixing test, Jan Fiete's correlations.