Analysis modules in SPARTA function like plugins that operate on a halo-by-halo basis. Each halo can have zero, one, or more instances of any type of analysis record attached to it. Each analysis contains function pointers which can be NULL (in which case they are ignored) or point to functions that are executed at pre-defined times when processing halos. To add a new analysis module, please follow these steps:

• In global.h: add a three-letter key for index of this analysis (e.g., RSP for splashback). This code will be used throughout the code and output files, so it is important to give it in an intuitive, memorable name.

• In global_types.h: add a memory structure. Any analysis produces some data that need to be stored in memory and/or output to disk at the end of the run. This memory is characterized by a struct called Analysis<YourAnalysisName>. At the very least, this struct must contain the field HaloID halo_id; so that the analysis can be assigned to the correct halo. All fields must be of a size defined at compile-time, i.e., cannot contain pointers to memory that is assigned later or variable-size arrays. It is good practice to reduce the memory footprint of your analysis as much as possible, for example by watching the order of your variable definitions (because the C compiler will pack variables into blocks of the same type).

• In global_types.h: the new struct needs to be added in a few more places that are easiest to find by searching for a previously defined analysis. These include the definition of the enum for DynArray types, the forward declaration of the struct (e.g., typedef struct AnalysisProfiles AnalysisProfiles_;), the addition into the DynArray memory pointers (e.g., AnalysisProfiles_ *al_prf;), and the addition of a function to create a DynArray of this type.

• In global_types.c: implement the new analysis type in the AnalysisTypeProperties array. The fields include a long and short name (the latter is the three-letter code defined above, but lowercase), the DynArray type you defined in global_types.h, the size of the analysis struct, and (most critically) function pointers to the actual analysis functions. Those should be defined in a file that you will add, e.g., #include "analyses/analysis_profiles.h".

• In global_types.c: define how dynamic arrays of the new type are managed. Below the previously defined analyses, add three new constants that define the initial allocation size (typically one for analyses, especially if there can only be one per halo!), the increase size, and the threshold for decreasing the size. If the allocation will always be zero or one analyses, those numbers are irrelevant. Add your new constants to the DA_IA, DA_AF, DA_DF, and DA_SIZE arrays below. Please be careful with the order!

• In global_types.c: implement the function to add a new analysis to a DynArray, addAnalysis<YourAnalysis>. The implementation should follow the other analyses, with the types replaced by your new analysis and pointing to a new initialization function (that we will implement below).

• Add code files analysis_<youranalysis>.c and analysis_<youranalysis>.h in src/analyses. It may be easiest to copy the corresponding files from previously implemented analyses and to edit them.

• In analysis_<youranalysis>.h: Make sure to secure the all definitions with a #pragma so that they are not compiled if your analysis is switched off.

• In analysis_<youranalysis>.h: define at least two functions, namely the initialization (void initAnalysisProfiles(AnalysisProfiles *al_prf);) and at least one analysis routine (e.g., runAnalysisProfiles(...)).

• In analysis_<youranalysis>.c: implement the initialization function by editing the types. At the very least, you must initialize the halo ID with a line like al_prf->halo_id = INVALID_ID;.

• In analysis_<youranalysis>.c: add and/or edit the analysis function(s). For now, you can leave it/them blank.

• In build/sparta.h: add a switch for your analysis, ANALYSIS_YOURANALYSIS. If set to 0, your analysis is off (and should not be compiled!). If it is 1, your analysis is on. If necessary, add more switches that allow the user to define which calculations are performed and which fields are output to disk.

• In global.h: make sure all #pragma are set correctly. For example, if your new analysis needs particles, make sure DO_READ_PARTICLES is set true if your analysis is on. Similarly, raise an error if some other results or analyses are incompatible with yours. Of course, your analysis should be as robust as possible to other choices made by the user.

• In config.c/printConfig(): add a line to output the memory size of your new analysis object.

Note

The compile-time parameters such as DO_ANALYSIS_XXX should be confined to the files and code segments listed above. If they are used anywhere else in the code, that is a sign that your code needs to be structured differently!

At this point, attempt to compile SPARTA, ideally with all analyses except your new one switched off. Test that the code reacts to your compile switches as expected, e.g., that it throws errors if there are incompatibilities and so on. Run SPARTA with your analysis on and ensure that no errors occur.

You are now ready to implement the code in the actual analysis function(s). If your analysis demands user-defined config parameters, please follow the steps outlined in Adding a run-time configuration parameter to add them.