Menu

Tree [1f6814] master /
 History

HTTPS access


File Date Author Commit
 elderpt 2025-03-18 Michael Reese Michael Reese [4b70ce] fix messed up names for ArrayHist1dFiller
 examples 2025-04-03 Michael Reese Michael Reese [5fc393] update documentation for std.condition_polygon2...
 m4 2014-08-25 Michael Reese Michael Reese [0850a8] initial commit
 plugins 2025-03-13 Michael Reese Michael Reese [23c2d6] improve std.array_properties
 .gitignore 2016-08-11 Michael Reese Michael Reese [46f90f] added possibility to make 2d images from an array
 AUTHORS 2014-08-25 Michael Reese Michael Reese [0850a8] initial commit
 AllCombinations.svg 2024-10-21 Michael Reese Michael Reese [886a09] basic syntax description is complete
 COPYING 2014-08-25 Michael Reese Michael Reese [0850a8] initial commit
 ChangeLog 2014-08-25 Michael Reese Michael Reese [0850a8] initial commit
 INSTALL 2024-01-20 Michael Reese Michael Reese [e7c8ee] add calc processor to std plugin
 IndexPairs.svg 2024-10-21 Michael Reese Michael Reese [886a09] basic syntax description is complete
 Makefile.am 2025-03-07 Michael Reese Michael Reese [89bc5a] add std.array_properties
 NEWS 2014-08-25 Michael Reese Michael Reese [0850a8] initial commit
 PositionalPairs.svg 2024-10-21 Michael Reese Michael Reese [886a09] basic syntax description is complete
 README.md 2025-04-03 Michael Reese Michael Reese [1f6814] fix typo
 SelfExcludingCombinations.svg 2024-10-21 Michael Reese Michael Reese [886a09] basic syntax description is complete
 autogen.sh 2014-08-25 Michael Reese Michael Reese [0850a8] initial commit
 configure.ac 2024-01-31 Michael Reese Michael Reese [376788] remove all boost dependencies
 elderpt.hpp 2020-01-18 Michael Reese Michael Reese [0968ca] add source block for config files
 elderpt.pc.in 2014-08-25 Michael Reese Michael Reese [0850a8] initial commit
 elderpt_c.h 2025-01-18 Michael Reese Michael Reese [b4d578] add one more C interface function and improve T...
 logo.png 2016-08-29 Michael Reese Michael Reese [294919] added png version of the logo
 logo.svg 2016-08-25 Michael Reese Michael Reese [8d0a77] added logo (1st version
 logo2.png 2016-08-30 Michael Reese Michael Reese [15f892] logo2 changed
 logo2.svg 2016-08-30 Michael Reese Michael Reese [15f892] logo2 changed

Read Me

Summary

Elder-PT is a software framework, that facilitates the task of event-by-event data analysis.
The basic concept is that the process of data analysis can be mapped onto a graph where data processing happens in the nodes and data flows along edges between nodes.
The framework provides:
1) a parser for analysis graph configuration files
2) an API to write processing nodes that can be inserted as nodes into the analysis graph
3) a library of frequently used processing nodes

The framework can coupled to data analysis software like Go4 for data visualization.

Contents

Documentation

a wiki page is here: https://sourceforge.net/p/elderpt/wiki/Home/

The description of the configuration file syntax is here

The descriptoin of the processors in th standard library is here

Get the Software from sourceforge repository

1) git clone git://git.code.sf.net/p/elderpt/code elderpt-code
2) cd elderpt-code
3) ./autogen.sh
4) ./configure
5) make
6) make install

The syntax of the Elder configuration files

Technically, Elder configuration files define a directed acyclic graph with nodes and edges. The components of the graph are taken from libraries.

Execution

The graph is used for event-by-event data processing. For each event, all nodes in the graph are executed in the order of their appearance in the configuration file.
After the execution of any processor, data is copied along all edges that originate in that node.

Libraries

In order to use a library in the configuration file, it must first be loaded by writing using <library-name>.
Whenever the config file parser encounters such a line, it will dynamically load an elderpt plugin library with the name libelderpt<library-name>.so.
After that, the components (modules and processors) of that library can be used in the configuration file.

Data types

The type of any input or output of any node is defined by the implementation of the node type and should be part of its documentation. Elder knows the following two data types.

Value type

Value types in Elder are a combination of a 64-bit floating point value and a boolean valid flag

value valid

in the implementation of a processor node can check if an input value was valid and omit calculations if not all needed values are valid.
Likewise it can tell processor node further down in the data flow path (defined by edges) that an output couldn't be calculated.

Array type

Array types in Elder are lists of index-value pairs, where each value is a 64-bit floating point number, and the index is an integer value between 0 and R-1. R defines the range of allowed indices in the array and is called index range. The size of an array is the number of index-value pairs in the list. The size can vary from event to event. Empty arrays have a size of zero. There is no upper limit for the size of an array. Each index-value pair has a position in the array, but the position is not explicitly accessible in the configuration file.

For each index-value pair in the list, the meaning of the index and the value is context dependent.
The index can for example refer to the index of a detector in a detector array.
The value can for example refer to amplitude of a signal from the respective detector.

Example of an array with index range 9 and size 7

index 4 5 5 0 0 3 8
value 1.0 3.3 4.1 6.8 0.1 7.4 2.1
(position) 0 1 2 3 4 5 6

The index-value pair at position 0 has index=4 and value=1.0, the index-value pair at position 3 has index=0 and value=6.8.

Example of an array with index range 2 and size 5

index 1 0 0 1 1
value 1.0 3.3 4.1 6.8 0.1
(position) 0 1 2 3 4

Example of an empy array with size 0

index
value
(position)

Edges and assignments

Edges define the data flow in the directed graph. They point from an input or output of a sender node to an input of a receiver node.

Edges are created inside of a processor. Assignment are done with left arrow <- indicating the direction of the data flow. There must be a white space left and right of the arrow. The left hand side of the assignment must be an input of the processor node, and the right hand side of the assignment must be the name of the sending node, followed by a dot and the name of any output or input of the sending node, or a module name if the sending node is a crate node. A complete assignment may look like this input <- sender.output. If the output is an array, it must be followed by square brackets containing one number or a range, e.g. input[4] <- sender.output[4] or input[1:4] <- sender.output[1:4].

Assignment rules define how assignment between the different data types is handled.

The configuration file syntax enforces an acyclic graph by only allowing referring to sender nodes that are already defined above of the assignment.

Node types

Nodes are places where data manipulation, data reduction, data analysis happens. There are two classes of nodes:
1)
Crate nodes with configurable set of module unpackers. Each unpacker has an unpacker-type that specifies how to decode specific parts of the subevent raw data. Each unpacker has one output that is similar to the array type of a processor node (see below).
2) Processor nodes nodes with a specific processor-type that has a number of inputs and outputs. Each input and output has one of two different data types: value type and array type.

Processor nodes

The syntax to create a processor node is

processor <processor-name> <library-name>.<processor-type>[(<parameter-list>)]
  <input-name> <- <sender-processor-name>.<input-or-output-name>
  <input-name> <- <sender-create-name>.<module-name>.<named_channel_offset>
  ...
  <input-array-name>[<index>] <- <sender-processor-name>.<input-or-output-name>[<index>]
  <input-name> <- <sender-create-name>.<module-name>.<named_channel_offset>[<index>]
  <input-name> <- <sender-create-name>.<module-name>[<index>].<named_channel_offset>
  ...
  <input-array-name>[<index-range>] <- <sender-processor-name>.<input-or-output-name>[<index-range>]
  <input-name> <- <sender-create-name>.<module-name>.<named_channel_offset>[<index-range>]
  <input-name> <- <sender-create-name>.<module-name>[<index-range>].<named_channel_offset>

  histogram <input-or-output-name>[("<label>")] [ <number-of-bins>[,<left>,<right>] ] [in <histogram-directory-name>]
  histogram <input-or-output-array-name>[("<label>")] [ <number-of-bins>[,<left>,<right>] ]
  histogram <input-or-output-name>[("<label>")]:<input-or-output-name>[("<label>")] [ <number-of-bins>[,<left>,<right>]:<number-of-bins>[,<left>,<right>] ]
  histogram <input-or-output-name>[("<label>")]::<input-or-output-name>[("<label>")] [ <number-of-bins>[,<left>,<right>]:<number-of-bins>[,<left>,<right>] ]
  histogram <input-or-output-name>[("<label>")]:::<input-or-output-name>[("<label>")] [ <number-of-bins>[,<left>,<right>]:<number-of-bins>[,<left>,<right>] ]
  histogram <input-or-output-name>[("<label>")]::::<input-or-output-name>[("<label>")] [ <number-of-bins>[,<left>,<right>]:<number-of-bins>[,<left>,<right>] ]

  ratemeter <input-or-output-name>:<input-or-output-name> <number-of-bins>,...
  waveform [<number-of-sampels>] [persistent] <input-or-output-array-name> [in <histogram-directory-name>]
  picture  <input-or-output-array-name> <width>:<height> [in <histogram-directory-name>]
end
  • One line of processor node header, starting with the processor keyword
  • Multiple lines of
  • assignments (which define the edges of the graph and direct the data flow)
  • histogram/ratemeter/waveform/picture definitions
  • One line containing only the end keyword

Processor node header

  • The processor keyword must be followed by the <processor-name> which may contain slashes / which allow to organize the histograms created within the processor in directories. For example a processor name could be TimeOfFlight/Start or TimeOfFlight/Stop. Processor names must be unique within one configuration file.
  • After processor name, the processor type must be specified. This is done by referring to a library <library-name>, followed by a dot ., followed by the <processor-name>. Library names must be unique within one configuration file. Two distinct libraries may contain a processor with the same name, i.e. the processor type always requires the library name in front of it.
  • Optionally, the processor type may be followed by parenthesis with a comma separated list of parameters. The meaning of parameters depends on the processor type and should be described in the documentation of the processor type.

Assignment rules

Most processors need input data in order to do something reasonable. Input data can be taken from unpacker modules in crate nodes or from inputs or outputs of other processor nodes. Assigning a processor input to a data source defines the edges of the data flow graph.

Because of the two different data types for inputs and outputs, the following assignments rules are defined for all combinations of assignments between the available types:
1) value-to-value (input <- sender.output): if the valid flag of sender.output is true, its 64-bit value will be copied to input and the valid flag of input will be set to true.
2) value-to-array (input[4] <- sender.output): if the valid flag of sender.output is true, an index-value-pair will be appended to input, the index will be the number in square brackets (4 in this case), and the value will be the 64-bit floating point value of sender.output.
3) array-to-value (input <- sender.output[4]): the list of index-value-pairs in the sender.output array will be iterated and if an index is found that matches the given number in square brackets (4 in this case), the 64-bit value of input will be set to the respective value in the matching index-value-pair and the valid flag of input will be set to true. If multiple index-value-pairs match the number in square brackets the value of input will be overwritten and in the end it will be equal to the value of the last matching index-value-pair. If no matching index is found in the array, the valid flag of input will not be set to true by this assignment.
4) array-to-array (input[4] <- sender.output[5]): the list of index-value-pairs in the sender.output array will be iterated and if an index is found that matches the given number in square brackets (5 in this case), the matching index-value-pair is appended to the input array, but the appended index will be the number given in square brackets after input. I.e. a re-assignment of indices is possible.

Multiple assignment syntax

For convenience there is a short way of writing multiple array-to-array assignments: input[4:8] <- sender.output[5:9]: each pair of colon-separated numbers in square brackets defines a range. For input the range contains the numbers 4,5,6,7,8, for sender.output the range contains the numbers 5,6,7,8,9. Such an assignment is only valid if both ranges contain the same amount of numbers. The assignment is executed by simultaneously iterating both ranges and perform the assignment as described above for each index of input and sender.output, i.e. this example is a shorter way of writing the 5 array-to-array assignments:

1) input[4] <- sender.output[5]
2) input[5] <- sender.output[6]
3) input[6] <- sender.output[7]
4) input[7] <- sender.output[8]
5) input[8] <- sender.output[9]

Any range defined in square brackets may also define a decreasing sequence of numbers (input[8:4] <- sender.output[5:9]), in which case the assignments are

1) input[8] <- sender.output[5]
2) input[7] <- sender.output[6]
3) input[6] <- sender.output[7]
4) input[5] <- sender.output[8]
5) input[4] <- sender.output[9]

Assignment from unpacker modules in crate nodes

If the sender is a crate node, the assignment examples above would correspond to a crate with name sender containing a module with name output.

crate sender
    module output <library-name>.<unpacker-type>
end 

There is a slight difference in assignments from unpackers in crates, because a module might deliver multiple values per physical input channel, e.g. amplitude and time, or amplitude and an overflow/underflow flag.

    input_value[8]    <- sender.output.amplitude[5]
    input_overflow[8] <- sender.output.overflow_flag[5]
    input_time[8]     <- sender.output.time[5]

A second syntax variant is allowed and the meaning is exactly the same

    input_value[8]    <- sender.output[5].amplitude
    input_overflow[8] <- sender.output[5].overflow_flag
    input_time[8]     <- sender.output[5].time

Data visualization

Any input or output of a processor can be visualized using various ways, depending on the data type:

labels for the histogram axis

For all histograms, one can optionally rename the histogram axis behind the name of the value or array using the syntax: <value-name>("<label>")
The label may contain white spaces and special characters. For example one can write

    histogram position("particle position [mm]")
    histogram energy("kinetic energy [MeV]")
    histogram energy("kinetic energy [MeV]"):position("particle position [mm]");

If no label is specified in this way the axis will be named like the name of the value or array.

1D histograms of values

A histogram is defined by the keyword histogram followed by the name of a value.
For each event the valid flag of the value is checked. If true, the 64-bit floating point number is converted into a bin index and that bin content is incremented by 1.

There are three different ways to specify the number of bins and the range of a histogram.

1) histogram with fixed range
    histogram <value-name> <number-of-bins>,<left>,<right> [lazy] [ in <histogram-directory-name> ]

Such a histogram is immediately created when parsing the configuration file.
If the histogram should be crated only when it is filled for the first time, the lazy keyword can be added.

2) histogram with automatic range
    histogram <value-name> <bin-width>,<border-fraction>,<max-number-of-bins>,<buffer-size> [ in <histogram-directory-name> ]

Four numbers after <value-name> specify a histogram that is created after accumulating <buffer-size> valid values in a buffer and determining the minimum and the maximum of the buffered values. The center of the created histogram will be at 0.5*maximum*minimum. Its left border will be at minimum-(maximum-minimum)*<border-fraction>. Its right border will be at maximum+(maximum-minimum)*<border-fraction>. The bin width is specified by <bin-width unless the number of bins exceeds <max-number-of-bins>, in which case the histogram will be created with that many bins.

3) histogram with automatic range
    histogram <value-name> <number-of-bins> [ in <histogram-directory-name> ]

This is equivalent to

    histogram <value-name> 0,0.1,<number-of_bins>,400 [ in <histogram-directory-name> ]

i.e. the histogram will buffer 400 valid values before creation, and it will always have <number-of-bins> bins.

overwrite histogram directory

By default the histogram will be created with a name that is the concatenation of <processor-name>, underscore _, and <value_name>. That means histograms of different processors are never in the same directory. Sometimes it is desirable to regroup histograms from similar processors in a single directory. This can be achieved by writing in <histogram-directory-name> after the histogram definition. That will replace a specific part the in histogram name, the substring before the last slash /, with <histogram-directory-name>

Given this example:

for $i in 0 1 2 
    processor detectors/gamma_array/detector_$i gamma_library.gamma_detector
        # ...
        histogram energy 
    end
end 

The resulting histogram names will be in a separate directory

detectors/gamma_array/detector_0/detector_0_energy
detectors/gamma_array/detector_1/detector_1_energy
detectors/gamma_array/detector_2/detector_2_energy

By writing in detector after the histogram definition like this

for $i in 0 1 2 
    processor detectors/gamma_array/detector_$i gamma_library.gamma_detector
        # ...
        histogram energy in detector
    end
end 

The string before the last / in the histogram name will be replaced with detector

detectors/gamma_array/detector/detector_0_energy
detectors/gamma_array/detector/detector_1_energy
detectors/gamma_array/detector/detector_2_energy

which makes all histograms appear in the same directory in the GUI frontend.

1D histograms of arrays

1D histograms of arrays are created in the exact same way as 1D histograms of values.

The only difference is, that as many histograms are created as there are allowed indices in that arrays. If the arrays has an index range of 10, that many histograms will be created and the name of each histogram will be appended with the index.
Optionally, the keyword lazy can be added. For histograms with fixed range this will prevent the creation of all histograms in the beginning of the analysis. With lazy the histogram for a specific index is only created when it is filled for the first time.

If creating separate histograms is not wanted, but rather a sum histogram for all indices, this can be achieved with a separate tuple processor containing an array with index range of 1 like this:

processor separate_histograms std.tuple(my_array_values[10])
    my_array_values[0:9] <- whatever_source.values[0:9]
    histogram my_array_values # 10 separate histograms
end
processor sum_histogram std.tuple(my_array_values[1])
    for $i in [0:9]
        my_array_values[0] <- separate_histograms.my_array_values[$i]
    end
    histogram my_array_values # one single sum histogram
end 
2D histograms of two values

2D histograms of values are created in the exact same way as 1D histograms of values

The only difference is that there are two values names separated by a colon <value-name>:<value-name> and two sets of numbers specifying the binning and axis ranges also separated by a colon, e.g. <number-of-bins>:<number-of-bins>.

All 3 variants of specifying the binning and axis range are allowed, but both axis must have the same variant.
Also note, that in case of the the automatic binning, the <buffer-size> is only specified once for both values.

These are the three allowed variants

    histogram <value-name-1>:<value-name-2> <number-of-bins-1>:<number-of-bins-2>
    histogram <value-name-1>:<value-name-2> <number-of-bins-1>,<left-1>,<right-1>:<number-of-bins-2>,<bottom-2>,<top-2>
    histogram <value-name-1>:<value-name-2> <bin-width-1>,<border-fraction-1>,<max-number-of-bins-1>:<bin-width-2>,<border-fraction-2>,<max-number-of-bins-2>,<buffer>
2D histograms of two arrays

2D histograms of arrays are created in the exact same way as 2D histograms of two values.

In contrast to the 1D histograms of arrays only a single histogram is created.
There are four meaningful ways of creating a 2d histogram from two arrays. They are distinguished by the number of colons between the two <array-names>

Positional pairs (one colon)
    histogram <array-name-1>:<array-name-2>

PositionalPairs

The index field in both arrays is ignored. Both arrays are iterated over their positions up to the highest position in the shorter of the two arrays. At each position both values at the respective position in each array are taken to find the 2d-bin that is incremented.

This is most useful for special purpose processor implementations that fill correlated numbers into two arrays in a specific order (so that the index can be ignored).

Index pairs (two colons)
    histogram <array-name-1>::<array-name-2>

IndexPairs

The index field in both arrays is used to find the value pairs that determine the 2d-bin that is incremented.

This can be used for example to plot energy-time correlations where two different information from the same detector must be filled into the same histogram. In this case, indices in both arrays (energy and time) should refer to the detector number.

    histogram E_gamma::T_gamma
Self excluding combinations (three colons)
    histogram <array-name-1>:::<array-name-2>

Combinations

All pairs of arrays values with different indices are build to determine the 2d-bins that are incremented.

This can be used for example to create a gamma-gamma-coincidence matrix of a gamma detector array where each of the coincident energies in one detector should be correlated with the energies in all other detectors, but not with itself.

    histogram E_gamma:::E_gamma
All combinations (four colons)
    histogram <array-name-1>::::<array-name-2>

AllCombinations

All possible pairs of array values are used to determine the 2d-bins that are incremented. The indices are ignored.

This can be used for example to make a gamma-gamma-coincidence matrix of two different detector groups, e.g. one ring under forward angle and and another ring under backward angle with respect to a particle beam. In this case any of the energies from the forward ring should be correlated with any energy from the backward ring.

    histogram E_forward::::E_backward
1D histograms of two values as ratemeter

Ratemeters provide a way to plot a ratio of two values A/B versus B. Examples are:
- particle rate vs. time is one of the most common applications for this

  ratemeter particle_number:time    
  • ratio of two particle counters vs one of the particle counters
  ratemeter detectorA:detectorB    

The syntax for ratemeter is

    ratemeter <value-name-A>:<value-name-B> [<bin-width>,<width>] [in <histogram-directory-name]

the bin width controls the resolution of the resulting time series. In the first example with particle_number and time a bin width of 0.1 would result in a time series resolution of 0.1 units of time. Resolution below 1 only makes sense for continuous values (such as time) and not for discrete values such as number of particles.

Histograms are used to visualize the time series data.

1D histograms of arrays as waveforms

If the values of an array directly represent a time series, for example a waveform from a fast sampling ADC, this can be visualized with the waveform keyword.

In this case the position of the array entries is used as bin index, and the value of the array entries is used as bin content. The index of the array is ignored.

The syntax for waveform is

    waveform <array-name> [in <histogram-directory-name>]
2D histograms of arrays as pictures

If the values of an array directly represent values in a 2d field with given width and height, the array can be plotted as picture using the following syntax

The syntax for picture is:

    picture <array-name> <width>:<height> [in <histogram-directory-name>]

The x and y coordinates are determined as
- x = position % width
- y = position / width

And the bin content in the histogram is set to the value in the array at that position. Array indices are ignored.

Crate nodes

The syntax to create a crate node is

crate <crate-name>
  [ procid    <procid> ]
  [ triggers  <list-of-triggers>  ]
  [ types     <list-of-types>     ]
  [ subtypes  <list-of-subtypes>  ]
  [ controls  <list-of-controls>  ]
  [ subcrates <list-of-subcrates> ]

  module <module-name> <library-name>.<unpacker-type> [ triggers <list-of-triggers> ]
  ...
  module <module-name> <library-name>.<unpacker-type> [ triggers <list-of-triggers> ]

  [ hitpattern ]
  [ print ]
end
  • procid is a number that is matched against the procid of any subevent in each event. If a subevent has a matching procid the unpackers defined in this crate are subsequently applied to the raw data of this subevent. If the procid is missing, each subevent is processed by the crate and the unpacking stops after this crate, i.e. no other crate further down in the analysis sees the data. This can be used to implement special data preprocessors in the form of unpackers, for example a module that stitches separate timestamped events together into a new combined event and forwards this combined event into the rest of the analysis.
  • triggers is optional. If given a subevent matches only if its trigger matches one of the numbers in <list-of-triggers>. The list is a white-space separated list of integers.
  • types is optional. If given a subevent matches only if its type matches one of <list-of-types>.
  • subtypes is optional. If given a subevent matches only if its subtype matches one <list-of-subtypes>.
  • controls is optional. If given a subevent matches only if its subtype matches one <list-of-controls>.
  • subcrates is optional. If given a subevent matches only if its subtype matches one <list-of-subcrates>.
  • module adds an instance of type <unpacker> to the list of unpackers that consume the raw data of any matching subevent. The <module-name> must be unique in one crate. The <library-name> must be added to the configuration file with using <library-name>. An optional number of triggers can be specified after triggers. If an unpacker has a trigger list, it is only applied to the matching raw data if the trigger of the subevent matches one of the listed triggers. An unlimited number of unpackers can be defined.
  • hitpattern is optional. If given, a hitpattern histogram is created for each unpacker. Each bin of the hitpattern histogram represents one output channel of the unpacker and is filled when that channel had data.
  • print is optional and prints the raw data of a matching subevent as hex values onto stdout.

Data unpacking

Any subevent that matches the given procid, type, subtype, trigger will be unpacked.
The unpacking works by passing the raw data buffer to the first module.
The module unpacker will consume some part of the raw data buffer until it is done with unpacking.
Then, it will return a reduced data buffer, which is then passed to the next module, until either the raw data buffer is empty, or there are no more modules in the crate block.
If a module is followed by a list of triggers, it will be skipped if the event trigger is not found in the <list-of-triggers>.

For loops

For loops can be used to avoid repetitive definitions. Any number of lines can be included in a for block with the following syntax:

for <search> in <replacement-1> { <replacement-i> }
    # any number of lines
end

If there are N replacements defined, the lines enclosed in the for block will be copied N times. In the i-th copy of the block, the <search> pattern is replaced with <replacement-i>.
There are no restrictions on the <search> pattern. It is recommended to start <search> pattern with a dollar sign (e.g. $i, $channel, or $detector_number) to avoid accidental text replacements.

There is another form of a for loop using the range syntax

for <search> in [<start_index>:<stop_index>]
    # any number of lines
end

where <start_index> and <stop_index> must be integer values.

Index calculations and formatting

Calculations

If calculations on indices are needed, the elder config file allows to make basic integer calculations with +,-,*,/,% operators.
A calculation must be enclosed in curly braces and the complete expression (including the braces) will be replaced by the result of the calculation.
Divisions are integer divisions (e.g. 7/3=2, 7%3=1).

For example {4+4} will be replaced with 8.

Applications are in range based for loops, such as

for $channel in [0:15]
    processor averages/channel_$channel std.calc(average=0.5*(value1+value2))
        value1 <- sender.value[{2*$channel}]    # sender.value has index range of 32
        value2 <- sender.value[{2*$channel+1}]  # i.e. indices 0...31 are allowed
    end
end

Formatting

A format specifier can optionally be added before the actual expression.
For example, if the integer number 23 should be formatted using four digits with leading zeros 0023 it can be done using the syntax {%04,23}. This means: format the output to be at least 4 characters wide and fill leading 0 if it is too short.
Any character can be used as a filling character.
This can also be used to reformat the variables of for loops like this:

for $i in [0:9]
    processor analysis/rng_{%03,$i} std.rand_uniform
    end
end

File import

More complex configurations can be split into multiple files.
It is possible to import other configuration files by writing

import subsystem.config

This is like pasting the content of subsystem.config at that position in the file.

The Elder standard library

The elder standard library contains a small set of useful processors and unpackers.
In order to use the standard library in a configuration file one has to write

using std

before using any processors or unpackers from that library.

processors

std.tuple

The processor std.tuple can be used to combine different data and make correlation plots with them
Each parameter in the parameter list names one input of the tuple processor: (value1,value1,value2)
Array inputs are defined with square brackets containing the index range: (value,array1[8],array2[16]).

processor correlations/E_T std.tuple(E,T)
    E <- gamma_energy.value
    T <- gamma_time.value
    histogram E:T
end
processor correlations/gamma_gamma std.tuple(HPGe[8],LaBr[9])
    HPGe[0:7] <- gamma_array_HPGe.energy[0:7]
    LaBr[0:8] <- gamma_array_LaBr.energy[0:8]
    histogram HPGe::::LaBr
end

std.calc

The processor std.calc can be used to do simple calculations, for example time-of-flight from two timing detectors. Each parameter in the comma separated parameter list defines a calculation. Each identifier on the right hand side of the equals sign is an input to the processor. The value on the left hand side of the equals sign is an output of the processor.

processor calculations/ToF std.calc(dT1=T1_stop-T1_start,dT2=T2_stop-T2_start)
    T1_start <- ToF_scintillator1_start.time
    T1_stop  <- ToF_scintillator1_stop.time
    T2_start <- ToF_scintillator2_start.time
    T2_stop  <- ToF_scintillator2_stop.time
    histogram dT1
    histogram dT2
end

std.delay

The processor std.delay can be used to remember information from the previous event and use it in the current event. This might be useful to calculate waiting time distributions between successive events.
The inputs are specified in the parameter list, similar to how it is done in std.tuple.
for each input or input_array, an output or output array is created with the _delayed added to the name.

processor timestamp_delay std.delay(timestamp)
  timestamp <- event.time
end
processor waiting_time std.calc(dT=Tnow-Tbefore)
  Tnow    <- timestamp_delay.timestamp
  Tbefore <- timestamp_delay.timestamp_delayed
  histogram dT
end

std.array_properties

The processor std.array_properties can be used to analyze arrays. It takes an arbitrary number of input arrays that can be specified in the parameter list, and creates for each of the input arrays four output arrays. Three of them have a size equal to the index range of the respective input array:
- name_multiplicity: Each entry in the array contains the index multiplicity of the input array as value.
- name_index: Each entry in the array contains the index as value. This is useful for histogramming the other properties.
- name_sum: Each entry in the array contains the sum of all values with that index as value.

One of the output arrays has an index range of 1, and a size equal to the input array:
- name_hitpattern: Each entry in the output array has as value the index of the input array. This can be used to create a hit pattern histogram.

In addition, one output is created for each input array which contains the size of the input array.

processor analyze/adc_properties std.array_properties(multihit_adc[16])
    multihit_adc[0:15] <- crate1.adc1.amplitude[0:15]
    histogram multihit_adc_index:multihit_adc_multiplicity 16,0,16:20,0,20
    histogram multihit_adc_index:multihit_adc_sum          16,0,16:400,0,40000
end

std.condition_window1d

The processor std.condition_window1d can be used to test if a value falls into a given window.
It emits one of three outputs (inside,outsize,invalid).

It has the following inputs:

  • x: the value is tested against the values x_window[0] and x_window[1].

It has the following outputs:

  • inside: this output is valid only if input x was inside x_window
  • outside: this output is valid only if input x was not inside x_window
  • invalid: this output is valid only if input x was not valid, i.e. not assigned or got an invalid assignment in this event
  • statistics: this output set to 0 for invalid x, 1 if x was valid but outside the window, 2 if x was valid and inside the window.

It has the following conditions defined:

  • x_window: this condition has two parameters, the left and right limits of a window. These 2 values are stored in a file with the same name as the processor with the ending ".con". The filename is gate/for_value1.con in the example below. The values in the file are updated whenever the blue left-arrow in the Go4 condition editor is pressed.

Example

The following analysis.config can be found in examples/condition_window1d.
It filters random numbers from the std.rand_uniform processor using a std.condition_window1d and fills the random values in two different histograms depending if they are inside or outside the window:

using std

processor source/value1 std.rand_uniform
end

processor gate/for_value1 std.condition_window1d
  x <- source/value1.value
  histogram x | x_window 
end

processor gated/value1_inside std.gated_tuple(value)
  value <- gate/for_value1.x
  condition <- gate/for_value1.inside
  histogram value_valid
end 
processor gated/value1_outside std.gated_tuple(value)
  value <- gate/for_value1.x
  condition <- gate/for_value1.outside
  histogram value_valid
end 

When used with Go4, the following condition plot is available and can be manipulated using the Go4 GUI.
Condition

With such a window position, the analysis creates the two histograms:

Gated

Note that any other value or array can be gated with the output of std.condition_window1d by assigning them to a std.gated_tuple processor and apply the inside/outside outputs of std.condition_window1d to the condition input of std.gated_tupel.

std.condition_window2d

The processor std.condition_window2d can be used to test if a pair of two values falls into a given window.
It emits one of two outputs if the value is inside or outside the window.

It has the following inputs:

  • x: the value (x,y) is tested against the xy-window
  • y: the value (x,y) is tested against the xy-window

It has the following outputs:

  • inside: this output is valid only if input (x,y) was inside xy-window
  • outside: this output is valid only if input (x,y) was not inside xy-window
  • invalid: this output is valid only if input x or y was not valid, i.e. not assigned or got an invalid assignment in this event
  • statistics: this output set to 0 for invalid x or y, 1 if x and y were both valid but outside the window, 2 if x and y were both valid and inside the window.

It has the following conditions defined:

  • xy_window: this condition has four parameters: left, right, bottom, top. These 4 values are stored in a file with the same name as the processor with the ending ".con". The filename is gate/for_values.con in the example below. The values in the file are updated whenever the blue left-arrow in the Go4 condition editor is pressed.

Example

The following analysis.config can be found in examples/condition_window2d.
It filters a pair of random numbers from two std.rand_uniform processors using a std.condition_window2d and correlates the two random numbers in a histogram only if they were inside the xy-window:

using std

processor source/value1 std.rand_uniform
end
processor source/value2 std.rand_uniform
end

processor gate/for_values std.condition_window2d
  x <- source/value1.value
  y <- source/value2.value
  histogram x:y | xy_window 100,0,1:100,0,1
end

processor gated/value1_inside std.gated_tuple(value1,value2)
  value1 <- gate/for_values.x
  value2 <- gate/for_values.y
  condition <- gate/for_values.inside
  histogram value1_valid:value2_valid  100,0,1:100,0,1
end 

When used with Go4, the following condition plot is available and can be manipulated using the Go4 GUI.
Condition

With such a window position, the analysis creates this 2D-histogram which is only filled in areas covered by the xy-window.

Gated

Note that any other value or array can be gated with the output of std.condition_window1d by assigning them to a std.gated_tuple processor and apply the inside/outside outputs of std.condition_window2d to the condition input of std.gated_tupel.

std.condition_polygon2d

The processor std.condition_polygon2d can be used to test if a pair of two values falls into a given polygon.
It emits one of two outputs if the value is inside or outside the polygon.

It has the following inputs:

  • x: the value (x,y) is tested against the polygon
  • y: the value (x,y) is tested against the polygon

It has the following outputs:

  • inside: this output is valid only if (x,y) was inside the polygon
  • outside: this output is valid only if input (x,y) was not inside the polygon
  • invalid: this output is valid only if input x or y was not valid, i.e. not assigned or got an invalid assignment in this event
  • statistics: this output set to 0 for invalid x or y, 1 if x and y were both valid but outside the window, 2 if x and y were both valid and inside the window.

It has the following conditions defined:

  • xy_polygon: this condition has 2N parameters defining the vertices of a polygon in 2 dimensions: x0,y0, x1,y1, x2,y2, ... , xN-1,yN-1. These xy-coordinates are stored in a file with the same name as the processor with the ending ".con". The filename is gate/for_values.con in the example below. The values in the file are updated whenever the blue left-arrow in the Go4 condition editor is pressed.

Example

The following analysis.config can be found in examples/condition_polygon2d.
It filters a pair of random numbers from two std.rand_uniform processors using a std.condition_window2d and correlates the two random numbers in a histogram only if they were inside the polygon:

using std

processor source/value1 std.rand_uniform
end
processor source/value2 std.rand_uniform
end

processor gate/for_values std.condition_polygon2d
  x <- source/value1.value
  y <- source/value2.value
  histogram x:y | xy_polygon
end

processor gated/value1_inside std.gated_tuple(value1,value2)
  value1 <- gate/for_values.x
  value2 <- gate/for_values.y
  condition <- gate/for_values.inside
  histogram value1_valid:value2_valid  100,0,1:100,0,1
end 

When used with Go4, the following condition plot is available and can be manipulated using the Go4 GUI.
Condition

With such a window position, the analysis creates this 2D-histogram which is only filled in areas covered by the xy-window.

Gated

Note that any other value or array can be gated with the output of std.condition_window1d by assigning them to a std.gated_tuple processor and apply the inside/outside outputs of std.condition_window2d to the condition input of std.gated_tupel.

std.gated_tuple

The processor std.gated_tuple is similar to std.tuple, but has one additional input condition which causes all inputs to be copied to outputs with _valid to their name if condition is valid. It has one additional output condition_valid, which is set to 0 when condition was not valid, and to 1 if condition was valid. This output can be used to evaluate the ratio of valid and invalid for condition.

Combining multiple conditions (AND/OR)

The following analysis.config can be found in examples/combining_conditions.
It defines two independent std.condition_window2d to filter some random data. Then the logical AND and the logical OR of the two conditions is used to filter the input data.

The std.calc processor is used to make a logical AND of two conditions. This works because std.calc requires all inputs to be valid to compute the result. Otherwise the output of std.calc remains invalid for this event.

The logical OR of multiple condition can be realized with multiple assignment into the same input. The assignment rules specify that in this case the input will be valid if one or more of the assignments contain valid data.

using std

processor source/value1 std.rand_uniform
end
processor source/value2 std.rand_uniform
end

processor gate/window1 std.condition_window2d
  x <- source/value1.value
  y <- source/value2.value
  histogram x:y | xy_window 
end

processor gate/window2 std.condition_window2d
  x <- source/value1.value
  y <- source/value2.value
  histogram x:y | xy_window 
end

# use std.calc procressor to create a logical AND of two (or more) conditions
processor gate/window1_AND_window2 std.calc(valid=window1*window2)
  window1 <- gate/window1.inside
  window2 <- gate/window2.inside
end

processor gated/correlation_OR std.gated_tuple(x,y)
  # multiple assigments into the "condition" input behaves like an OR of the two gates
  condition <- gate/window1.inside
  condition <- gate/window2.inside
  x <- source/value1.value
  y <- source/value2.value

  histogram x_valid:y_valid 100,0,1:100,0,1
  histogram condition_valid 2,0,2
end


processor gated/correlation_AND std.gated_tuple(xa[1],ya[1])
  condition <- gate/window1_AND_window2.valid # take the valid output of std.calc as an AND of both conditions
  xa[0] <- source/value1.value
  ya[0] <- source/value2.value

  histogram xa_valid:ya_valid 100,0,1:100,0,1
  histogram condition_valid 2,0,2
end

When used with Go4, the following condition plots are available and can be manipulated using the Go4 GUI.
The gated/correlation_AND is only filled where both conditions are true (the overlap of both windows), and gated/correlation_OR is filled when either of the two conditions is true (the union of both window).
Condition

Unpackers

The standard library provides these two unpackers that can be useful if unexpected additional words are in the raw data. E.g. buffer markers that are not considered by normal unpackers.

std.single32bit

This is an unpacker that consumes one 32-bit word of raw data.

std.multiple32bit

This is an unpacker that has one integer number as parameter. It reads and consumes so many 32-bit word of raw data.