SciPy

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 or my_very_cool_var.

  • Objects (structs) are UpperCase, e.g. MyObject.

  • Macros are all-upper, e.g. MY_MACRO_DOES_SOMETHING.

  • The variable names x and v typically mean a 3-vector of position or velocity, either in box or halo-centric coordinates. The variable pos 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, use output macro:

    output(1, "[%4d] My message\n", proc);
    

    Here, 1 is the log_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 the log_level parameter. 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_type which can be set by the user to IGNORE, WARNING, or 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);
    

    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. The error() function gracefully ends SPARTA.

  • Memory management:: Never ever ever use the malloc or calloc 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 the memAlloc() 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 the MID_NAMES list in 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() 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 and PARANOID 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 using PARANOID 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 the src tree 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 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 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_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

tracer

tcr

tt

NTT

Result

result

res

rs

NRS

Analysis

analysis

anl

al

NAL

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.