General guidelines¶
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.
Naming Conventions¶
Please obey the following naming conventions:
Function names are generally in camelCase, e.g.
myFunctionDoesSomething()
.Variable names are lower case, with underscores separating words, e.g.
var
ormy_very_cool_var
.Objects (structs) are UpperCase, e.g.
MyObject
.Macros are all-upper, e.g.
MY_MACRO_DOES_SOMETHING
.The variable names
x
andv
typically mean a 3-vector of position or velocity, either in box or halo-centric coordinates. The variablepos
is 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.
Coding pracices¶
A few general rules for coding in SPARTA:
Logging and errors: Console output, warnings, and errors are performed with internal functions, the
printf
or similar functions should never be used. For logging, useoutput
macro:output(1, "[%4d] My message\n", proc);
Here,
1
is thelog_level
which 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 thelog_level
parameter. The second parameter is the message itself, followed by a variable argument list just as forprintf
. 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_type
which can be set by the user toIGNORE
,WARNING
, orERROR
. 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);
The
__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. Theerror()
function gracefully ends SPARTA.Memory management:: Never ever ever use the
malloc
orcalloc
functions! 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 thememAlloc()
function:float *ptr; ptr = (float *) memAlloc(__FFL__, MID_MYFLOATARRAY, sizeof(float) * n_floats);
The identifier
MID_MYFLOATARRAY
should be unique and needs to be defined as part of theMID_NAMES
list insrc/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 thememFree()
function: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
memRealloc()
function.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
CAREFUL
andPARANOID
macros:#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 usingPARANOID
which is, by default, turned off.
Global variables¶
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 freeGlobalMemory()
.
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 thesrc
tree should ever include a file intools
. 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
config
struct. 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;
Debugging Helpers¶
In addition to the log output mentioned above, SPARTA provides tools for debugging:
Halo debugging: If the
DEBUG_HALO
macro is set to a number not0
, 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_TRACER
macro 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:
Type |
Long name |
Abbreviation |
Code |
Runs to |
---|---|---|---|---|
Tracer |
|
|
|
|
Result |
|
|
|
|
Analysis |
|
|
|
|
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.
Common pitfalls¶
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
src/halo.c
.