This document summarizes the most important guidelines for coding into SPARTA, but it is by no means complete. Please have a look at existing code and, if in doubt, email the developer.
General Code Style¶
Please obey the following conventions when adding code to SPARTA:
The maximum width of a code line should not exceed 100 characters.
SPARTA is generously commented, please comment your code if its function is not obvious.
Please obey the following naming conventions:
Function names are generally in camelCase, e.g.
Variable names are lower case, with underscores separating words, e.g.
Objects (structs) are UpperCase, e.g.
Macros are all-upper, e.g.
The variable names
vtypically mean a 3-vector of position or velocity, either in box or halo-centric coordinates. The variable
posis used for general 1D or 3D positions.
In general, the preference is to use long function, variable, or macro names if that makes the code easier to read.
A few general rules for coding in SPARTA:
Logging and errors: Console output, warnings, and errors are performed with internal functions, the
printfor similar functions should never be used. For logging, use
output(1, "[%4d] My message\n", proc);
log_levelwhich indicates how important this message is. If zero, the message is always output, if one, only if the user has chosen log level 1 or higher (the recommended setting). See Run-time configuration parameters for details on the
log_levelparameter. The second parameter is the message itself, followed by a variable argument list just as for
printf. Note that one should always output the process the message came from using the syntax above, or a preceding
[Main]if the message is output by the main process. Similarly, we can issue a warning with a certain log level:
warning(0, "[%4d] Something serious went wrong (wrong number: %.1f)!\n", proc, wrong_number);
This function is allowed but not the best standard: the preferred function is:
warningOrError(__FFL__, config.err_my_type, "My message %d.\n", d, my_param);
This function checks the config parameter
err_my_typewhich can be set by the user to
ERROR. The correct response is then automatically triggered. Finally, if things get bad, best to stop SPARTA and throw an error. The coding philosophy has always been to be relatively liberal with those to make sure that bugs get detected early:
if (x > 1.0) error(__FFL__, "Game over, x should not be %.2e.\n", x);
__FFL__macro adds the filename, function name, and line number which are printed with the error message, making it easy to locate where the error came from. The
error()function gracefully ends SPARTA.
Memory management:: Never ever ever use the
callocfunctions! SPARTA has a light-weight memory management system that keeps track of each allocated byte. This system has two main advantages: first, it automatically detects memory leaks (memory that should be cleaned up after a snapshot but wasn’t) and second, it outputs information about which fields take up the most memory. The latter information is critical when trying to understand why certain code snippets crash, and when deciding the number of cores / nodes to run on.
Internally, this framework still uses
malloc, so any of the common errors from that function can occur (e.g., when the system is out of memory). To reserve memory, use the
float *ptr; ptr = (float *) memAlloc(__FFL__, MID_MYFLOATARRAY, sizeof(float) * n_floats);
MID_MYFLOATARRAYshould be unique and needs to be defined as part of the
src/global.h. The name should be as clear as possible regarding what this fields was used for. At the end of a code run, the field will appear in the memory summary. The fields that use the most memory are also displayed after every snapshot. To free the memory, use the
memFree(__FFL__, MID_MYFLOATARRAY, ptr, sizeof(float) * n_floats);
If this function is not called, or if the wrong memory field is used, or if the number of bytes does not match, the memory system will output a warning. There is also a
Sanity checks: All checks that are non-essential (in the sense that they are superfluous once a piece of code is bug-free) should be wrapped using the
#if CAREFUL if (idx >= array_len) error(__FFL__, "Index too large, %d.\n", idx); #endif
The user can deactivate all checks wholesale by switching off
CAREFUL, though this is not generally recommended. If a check takes significant computation time, it should instead be wrapped using
PARANOIDwhich is, by default, turned off.
Global variables are generally discouraged in SPARTA, but the code framework makes a few noteworthy exceptions. For example, the config is a unique struct that is used in virtually every function, and passing it to each function would unnecessarily inflate the interface definitions. Similarly, the basic MPI variables (process ID, number of processes etc) are global.
When introducing a global variable, note that it must be added to the restart routine in
src/io/io_restart.c. If the variable has a fixed memory size, this amounts to a simple line of
code; if it contains dynamically allocated fields, those fields should be created in the
allocateGlobalMemory() function in
src/global.c, and deallocated in
Main Code vs. Tools¶
The main SPARTA code and executable are distinguished from other runtime executables, which are
called “Tools”. For example, MORIA is a tool. The code base for tools is not in
src but in
tools so that code that is not general-purpose does not crowd the main SPARTA code directory.
When developing tools, there are a few rules:
Tools can use code from SPARTA (i.e., code in
src), but not vice versa. That is, no file under the
srctree should ever include a file in
tools. If functionality is shared, it should be implemented in general-purpose routines in the SPARTA codebase, which are then called by the tool code.
All files in a tool code should live in a separate folder such as
tools/mytool/and carry the same name as a prefix, e.g.
tools/mytool/mytool_main.c. This avoids any naming conflicts.
SPARTA contains a number of global variables, most notably the
configstruct. These variables will almost invariably be inherited by tools, and their names cannot be shadowed. The SPARTA config may or may not need to be initialized to some extent if certain SPARTA functions are used. However, tools should ideally not have any global variables themselves.
Some SPARTA functions rely on MPI. If such functions are to be used, the developer of a tool will need to start an MPI environment. Very often, however, it suffices to set the relevant global variables:
proc = 0; n_proc = 1; is_main_proc = 1;
In addition to the log output mentioned above, SPARTA provides tools for debugging:
Halo debugging: If the
DEBUG_HALOmacro is set to a number not
0, information is printed about halos that match this ID or whose original ID, parent ID, or descendant ID matches it. This tool is useful for understanding the trajectory of a halo through the various code elements.
Tracer debugging: Similarly, the
DEBUG_TRACERmacro can be used to follow a tracer, including all the tracer results working on it. If a tracer appears in multiple halos, all occurrences will be output, specifying the halo they occurred in.
Looping over objects¶
Tracers, results, and analyses are kept in arrays that often need to be looped over. The variables for doing so are standardized:
Each element also has its own index given as a three-letter abbreviation, e.g.
PTL for particle
tracers. See Analyzing SPARTA output for a complete list.
Periodic boundary conditions: whenever you add or subtract positions in the simulation box, you need to worry about periodic boundary conditions. See the functions in
src/geometry.c, very likely there is a function that takes care of this issue for you.
When switching between box and halo-centric coordinates, note the different :doc:’intro_conventions_units` (comoving Mpc vs. physical kpc). There are functions to convert between the two in