7. Assumed Usage

It might be obvious or not, but the NA64sw is designed having certain workflow organisations in mind. Taking into account the variety of possible usage scenarios this is a flexible framework that can facilitate analysis needs by its own, or be used as embeddable library or extension in both cases providing software piecies in a granular manner.

In this chapter we describe some (pretty common and generic) tips of C/C++ project organization followed by an example of performing detector study. For instance, we consider ECAL calibration based on LMS method based on pseudo-inversion matrix with SVD decomposition done by standalone tool in the environment providing external build of NA64sw (e.g. on CVMFS share at CERN LXPLUS). We use NA64sw to read the data.

7.1. Setting up the project

The project we consider here is maintained as standalone repository. In most cases this is a preferable usage scenario as user should not deal with NA64sw codebase and can focus on their particular problem. So, assume that NA64sw installation exists on the target host (on CERN LXPLUS it is one of the deployments at /cvmfs/na64.cern.ch/sft/ share).

7.2. Structuring the project

NA64sw does not assume any particular structure of user’s projects and gets linked just as any other library sets. The only restriction made by co-operating/using NA64sw might come from the fact that all the extensions are loaded from shared libraries (.so), so one should consider linking their code in the library at some point.

For those who are not familiar with how typical C/C++ project is organized, the general tips are:

  • Keep implementation files in src/ and headers in include/ dirs

  • For C projects provide a distinguishable naming suffix for headers, as it may be required at some point of the project’s lifecycle to install the headers in system-wide dir (we choose ecp-*.h in the example below). Alternatively, it can be a dir instead of naming suffix.

  • Provide a Makefile (or maintain a CMakeLists.txt to automatize the builds), keep/maintain dependency management

  • Keep reasonable logical splitting between software components in your repo. It is a nice practice to explicitly mark your executables within the structure by Main*.cc suffix/creating a dedicated dir for it.

You might want to split NA64sw-related entities from the main project structure: to make a dedicated source files for handlers (by using name suffix or dedicated folder) and data source class(es). But this is just an optional recommendation as soon as them are compiled and linked into .so file.

7.3. Config files

Besides of run-configs (ones provided to pipeline app with -r,--run option argument) and data source configs (provided with -i,--input-cfg) NA64sw facilitates logging system and variety of calibration utils that can be (re)configured in one way or another. The logging system is configured with config file that is provided by option -J,--log-cfg, the calibration config can be overriden by -c,--calibrations. User may want to customize these files slightly and keep it within their projects repo.

7.4. C/C++ Integration

In general, no special remarks needed to integrate your project with NA64sw as it is organized as most of the C/C++ distributions. Below are some tips for the integration.

7.4.1. Project location

Let us start with creating a repo folder for new project. This is not required, but users often may want to maintain their projects by certain VCS.

$ mkdir ~/projects/ecal-calibration
$ cd ~/projects/ecal-calibration

We created a folder for the project. You may want to init a git repo in this folder or use your existing project. The structure of project is arbitrary and shall suit your needs.

Next, you may want to:

  1. use pipeline application to produce some data with your data treatment requirements,

  2. create custom handlers and sources to write your own low-level additions to data analysis,

  3. write a custom extension based on the pipeline that is capable to steer the pipeline execution.

For 1st scenario of simply running the pipeline, you would like to just store the .yaml run-configs somewhere and that’s it. Structure the config files at your convenience.

For the rest of scenarios one should consider making some sort of C/C++ project. Typical project organization structure (often proposed by modern IDEs) is totally fine with NA64sw with latter being a just one another dependency. All the linkage information is accessible via pkg-config, an easy to-use (but rather simplistic) dependency-management tool provided by most UNIXes. Below are two cases, a simple Makefile, and snippet for CMake.

7.4.2. Makefile or plain shell script

Basic utilization within the plain shell invocation was considered in chapter devoted to custom handlers writing. It is enough to append your build flags with ones provided by pkg-config na64sw --cflags --libs command once proper environment is activated (as was described in first chapter). For multifile projects one may consider modifying rules with something like:

obj/my-src.o: src/my-src.cc
    g++ $(shell pkg-config na64sw --cflags --libs) $(CXXFLAGS) -fPIC src/my-src.cc -o obj/my-src.o

One may also consider project-wide linking by appendind CXXFLAGS with the result of $(shell pkg-config na64sw --cflags --libs) command.

Note, that to link a library then a rule must be declared. Something like:

libMyHandlers.so: obj/myHandlerOne.o obj/myHandlerTwo.o
    g++ obj/myHandlerOne.o obj/myHandlerTwo.o -shared -o libMyHandlers.so

The resulting lib libMyHandlers.so then becomes an extension module that can be utilized from within a pipeline app by -m.

7.4.3. CMake

For CMake consider the following snippet:

# Include this to use `pkg_search_module()':
find_package(PkgConfig)
# Resolve na64sw package variables (it will be prefixed with PC_na64sw_...):
pkg_search_module(PC_na64sw REQUIRED na64sw)
# Resolve `na64app` library and `na64sw-config.h` header as hints for
# library and include dirs
find_path( NA64SW_INCLUDE_DIR
        NAMES na64sw-config.h
        PATHS ${PC_na64sw_INCLUDE_DIRS} )
find_library( NA64SW_LIB
        NAMES na64app
        PATHS ${PC_na64sw_LIBRARY_DIRS} )
# Use native CMake `find_package_handle_standard_args()' function to handle
# includes, libraries and stuff:
find_package_handle_standard_args(na64sw
        REQUIRED_VARS ${NA64SW_LIB} ${NA64SW_INCLUDE_DIR})

Notes:

  • If you would like to have NA64sw as an optional dependency, consider using pkg_check_modules() instead of pkg_search_module().

  • If linking versus na64app lib is redundant (might be true in some fine-grained and esoteric cases) consider elaborating the snippet above to look for only the needed libraries of the framework.

It is planned (but is not a topmost priority so far) to have a generated CMake module with namespaces in a future.

Starting from scratch, one may use a simple Makefile and then migrate to CMake, as the project gets elaborated.

7.5. Example

Let us consider a project consisting of:

  • A handler for the pipeline that does a pretty simple job of generating binary files with excerpt of event data (maximum amplitude and sum of SADC hit raw data);

  • A solver application capable to read the binary data and perform the computation. It does not rely on any NA64sw routines, however it expects the data to be provided in certain format;

  • A generator application that provides some sort of random data to test the solver (in certain format);

  • A set of BASH scripts and HTCondor submission files to perform multi-staged calibration procedure on set of files

  • A Makefile that builds the project.

First, let’s make a usual C/C++ project, to put emphasis then on the differences that might be introduced by NA64sw framework integration.

7.5.1. An ordinary C/C++ boilerplate project example

In the newly created project dir we start by creating somewhat standard structure for C/C++ projects:

$ mkdir src include obj
$ touch Makefile

One can initialize a Git repo, add a README file and so on.

Then we write a MC-generator and solver applications which have certain shared functionality (both apps are using the same data exchange codec). We also place entry points for both applications in the project’s root, so overall project source distribution looks like:

$ tree
.
├── include/
│   ├── ecp-cell.h
│   ├── ecp-cumulist.h
│   ├── ecp-file.h
│   ├── ecp-generator.h
│   ├── ecp-solver.h
│   └── ecp-types.h
├── main-generator.c
├── main-resolve.c
├── Makefile
├── obj/
└── src/
    ├── cell.c
    ├── cumulist.c
    ├── solver.c
    ├── solver-lms.c
    ├── file.c
    ├── generator.c
    └── profile-types.c

The corresponding Makefile that builds both applications sharing some common object files (one can build a shared library from it, but for our little project this seems to be redundant):

CFLAGS+=-Wall -g -ggdb -Iinclude/ -Wfatal-errors $(shell gsl-config --cflags)

all: ecp-generate ecp-resolve

obj/main-%.o: main-%.c
    $(CC) -x c $(CFLAGS) -c -o $@ $^

obj/%.o: src/%.c
    $(CC) -x c $(CFLAGS) -c -o $@ $^

exec/ecp-generate: obj/generator.o \
              obj/cell.o \
              obj/file.o \
              obj/main-generator.o
    g++ -o $@ $^ -lgsl

exec/ecp-resolve: obj/generator.o \
             obj/cell.o \
             obj/file.o \
             obj/cumulist.o \
             obj/solver.o \
             obj/solver-lms.o \
             obj/main-resolve.o
    g++ $(shell gsl-config --libs) -o $@ $^

clean:
    rm -f obj/*
    rm -f exec/ecp-generate exec/ecp-resolve

.PHONY: all clean

This is quite ordinary Makefile building two applications linked with GSL library, with clean target. The executables are placed into exec/ dir and there is no install target meaning that executables are supposed to run from within project’s root dir – a typical cheak’n’dirty project for fast scientific calculus.

7.5.2. Adding NA64sw handler

We create a directory specific for NA64sw-related configs:

$ mkdir na64sw-run

and put there some .yaml files corresponding to different stages of calibration procedure.

We also add a loadable handler module into src/ (but one may prefer another location) by adding a following target to a Makefile:

libSADCDumpMaxAndSum.so: src/handler-SADCDump.cc
            g++ $^ $(shell pkg-config na64sw --cflags --libs) -shared -fPIC -o $@

… (to be continued)