Style

In this section we describe the style conventions and guidelines for SUNDIALS source code.

Formatting

All new code added to SUNDIALS should be formatted with clang-format for C/C++, fprettify for Fortran, cmake-format for CMake, and black for Python. The .clang-format file in the root of the project defines our configuration for clang-format. We use the default fprettify settings, except we use 2-space indentation. The .cmake-format.py file in the root of the project defines our configuration for cmake-format. We also use the default black settings.

To apply clang-format, fprettify, cmake-format, and black you can run:

./scripts/format.sh <path to directories or files to format>

Warning

The output of clang-format is sensitive to the clang-format version. We recommend that you use version 17.0.4, which can be installed from source or with Spack. Alternatively, when you open a pull request on GitHub, an action will run clang-format on the code. If any formatting is required, the action will fail. Commenting with the magic keyword /autofix will kick off a GitHub action which will automatically apply the formatting changes needed.

If clang-format breaks lines in a way that is unreadable, use // to break the line. For example, sometimes (mostly in C++ code) you may have code like this:

MyClass::callAFunctionOfSorts::doSomething().doAnotherThing().doSomethingElse();

That you would like to format as (for readability):

MyObject::callAFunctionOfSorts()
      .doSomething()
      .doAnotherThing()
      .doSomethingElse();

Clang-format might produce something like:

MyObject::callAFunctionOfSorts().doSomething().doAnotherThing()
      .doSomethingElse();

unless you add the //

MyObject::callAFunctionOfSorts()
      .doSomething()       //
      .doAnotherThing()    //
      .doSomethingElse();  //

There are other scenarios (e.g., a function call with a lot of parameters) where doing this type of line break is useful for readability too.

Output

For consistent formatting of sunrealtype, the following macros are available.

SUN_FORMAT_E

A format specifier for scientific notation. This should be used when displaying arrays, matrices, and tables where fixed width alignment aids with readability.

Example usage:

for (i = 0; i < N; i++) {
   fprintf(outfile, SUN_FORMAT_E "\n", xd[i]);
}
SUN_FORMAT_G

A format specifier for scientific or standard notation, whichever is more compact. It is more reader-friendly than SUN_FORMAT_E and should be used in all cases not covered by that macro.

Example usage:

SUNLogInfo(sunctx->logger, "label", "x = " SUN_FORMAT_G, x);
SUN_FORMAT_SG

Like SUN_FORMAT_G but with a leading plus or minus sign.

To aid in printing statistics in functions like CVodePrintAllStats(), the following utility functions are available.

void sunfprintf_real(FILE *fp, SUNOutputFormat fmt, sunbooleantype start, const char *name, sunrealtype value)

Writes a sunrealtype value to a file pointer using the specified format.

Parameters:
  • fp – The output file pointer.

  • fmt – The output format.

  • startSUNTRUE if the value is the first in a series of statistics, and SUNFALSE otherwise.

  • name – The name of the statistic.

  • value – The value of the statistic.

void sunfprintf_long(FILE *fp, SUNOutputFormat fmt, sunbooleantype start, const char *name, long value)

Writes a long value to a file pointer using the specified format.

Parameters:
  • fp – The output file pointer.

  • fmt – The output format.

  • startSUNTRUE if the value is the first in a series of statistics, and SUNFALSE otherwise.

  • name – The name of the statistic.

  • value – The value of the statistic.

void sunfprintf_long_array(FILE *fp, SUNOutputFormat fmt, sunbooleantype start, const char *name, long *value, size_t count)

Writes an array of long values to a file pointer using the specified format.

Parameters:
  • fp – The output file pointer.

  • fmt – The output format.

  • startSUNTRUE if the value is the first in a series of statistics, and SUNFALSE otherwise.

  • name – The name of the statistic.

  • value – Pointer to the array.

  • count – The number of elements in the array.

Logging

Use the macros below to add informational and debugging messages to SUNDIALS code rather than adding #ifdef SUNDIALS_LOGGING_<level> / #endif blocks containing calls to SUNLogger_QueueMsg(). Error and warning messages are handled through package-specific ProcessError functions or the SUNAssert and SUNCheck macros.

The logging macros help ensure messages follow the required format (see §1.6.2) used by the suntools Python module to parse logging output (see §1.6.3). For informational and debugging output the log message payload (the part after the brackets) must be either be a comma-separated list of key-value pairs with the key and value separated by an equals sign with a space on either side e.g.,

/* log an informational message */
SUNLogInfo(sunctx->logger, "begin-step", "t = " SUN_FORMAT_G ", h = " SUN_FORMAT_G, t, h);

/* log a debugging message */
SUNLogDebug(sunctx->logger, "error-estimates",
            "eqm1 = " SUN_FORMAT_G ", eq = " SUN_FORMAT_G ", eqp1 = " SUN_FORMAT_G,
            eqm1, eq, eqp1);

or the name of a vector/array followed by (:) = with each vector/array entry written to a separate line e.g., a vector may be logged with

SUNLogExtraDebugVec(sunctx->logger, "new-solution", ynew, "ynew(:) =");

where the message can contain format specifiers e.g., if Fe is an array of vectors you may use

SUNLogExtraDebugVec(sunctx->logger, "stage-explicit-rhs", Fe[i], "Fe_%d(:) =", i);

The log parser organizes the logging output into Python dictionaries and lists of dictionaries. To denote different logging regions, begin- and end- message labels of the form <begin|end>-<region-name>[-list] are used. The log parsing tool will automatically open/close the dictionary (default) or list of dictionaries (as indicated by the optional -list suffix) for the region corresponding to region-name. This convention enables adding new logging regions to the code without needing to update the logging parser. There are also three special region markers that are handled separately:

  • begin|end-step-attempt – opens/closes step attempt dictionaries and opens/closes lists within the current step to hold logging output for different time levels (i.e., with MRI methods) or problem partitions (i.e., with splitting methods) based on the current time level and partition counters.

  • begin|end-fast-steps – increments/decrements the current time level counter.

  • begin|end-partition-list – increments/decrements the current partition counter and opens/closes the partitions list.

Logging messages that do not start with begin- or end- correspond to output for the active region and are added to the open output dictionary.

Logging Example

As an example consider the information logging output from a CVODE time step using a fixed-point iteration for the nonlinear solve. The logging line

SUNLogInfo(CV_LOGGER, "begin-step-attempt", "step = %li, tn = " SUN_FORMAT_G ",
           h = " SUN_FORMAT_G ", q = %d", cv_mem->cv_nst + 1, cv_mem->cv_tn,
           cv_mem->cv_h, cv_mem->cv_q);

creates a new step attempt dictionary and adds the data in the payload to the dictionary:

{
  step : 1
  tn : 0.0
  h : 0.002
}

Inside the step attempt, the logging line

SUNLogInfo(CV_LOGGER, "begin-nonlinear-solve", "tol = " SUN_FORMAT_G,
           cv_mem->cv_tq[4]);

adds the nonlinear-solve key to the step attempt dictionary, activates the nonlinear solver output dictionary, and adds the data in the payload to the dictionary:

{
  step : 1
  tn : 0.0
  h : 0.002
  nonlinear-solve :
  {
    tol : 0.01
  }
}

In the nonlinear solver, the logging line

SUNLogInfo(NLS->sunctx->logger, "begin-iterations-list", "");

adds the iterations key to the nonlinear-solve dictionary and activates a new output dictionary in the iterations list:

{
  step : 1
  tn : 0.0
  h : 0.002
  nonlinear-solve :
  {
    tol : 0.01
    iterations :
    [
      { }
    ]
  }
}

Within the nonlinear solver iteration loop, the logging line

SUNLogInfo(NLS->sunctx->logger, "nonlinear-iterate",
           "cur-iter = %d, update-norm = " SUN_FORMAT_G,
           FP_CONTENT(NLS)->niters, N_VWrmsNorm(delta, w));

adds information to the active iterations dictionary:

{
  step : 1
  tn : 0.0
  h : 0.002
  nonlinear-solve :
  {
    tol : 0.01
    iterations :
    [
      {
        cur-iter : 1
        update-norm : 0.02
      }
    ]
  }
}

At the end of each nonlinear iteration, logging output such as

SUNLogInfoIf(FP_CONTENT(NLS)->curiter < FP_CONTENT(NLS)->maxiters - 1,
             NLS->sunctx->logger, "end-iterations-list",
             "status = continue");

if the iteration should continue or

SUNLogInfo(NLS->sunctx->logger, "end-iterations-list", "status = success");

if the solve was successful will close the active output dictionary in the iterations list and reactivate the output dictionary for the enclosing scope (i.e., the nonlinear solve region). If additional iterations are necessary, the logging line

SUNLogInfo(NLS->sunctx->logger, "begin-iterations-list", "");

will append and activate a new output dictionary to the iterations list. This continues until the nonlinear solve is complete and a logging line such as

SUNLogInfo(CV_LOGGER, "end-nonlinear-solve", "status = success, iters = %li",
           nni_inc);

updates and closes the nonlinear solver output dictionary which reactivates the step attempt dictionary:

{
  step : 1
  tn : 0.0
  h : 0.002
  nonlinear-solve :
  {
    tol : 0.01
    iterations :
    [
      {
        cur-iter : 1
        update-norm : 0.02
        status : continue
      }
      {
        cur-iter : 2
        update-norm : 0.002
        status : success
      }
    ]
    status : success
    iters : 2
  }
}

Finally, a logging call such as

SUNLogInfo(CV_LOGGER, "end-step-attempt",
           "status = success, dsm = " SUN_FORMAT_G, dsm);

closes the step attempt dictionary:

{
  step : 1
  tn : 0.0
  h : 0.002
  nonlinear-solve :
  {
    tol : 0.01
    iterations :
    [
      {
        cur-iter : 1
        update-norm : 0.02
        status : continue
      }
      {
        cur-iter : 2
        update-norm : 0.002
        status : success
      }
    ]
    status : success
    iters : 2
  }
  status : success
  dsm : 1.0e-3
}

This processes then repeats for the next step attempt.

Logging Macros

Added in version 7.2.0.

To log informational messages use the following macros:

SUNLogInfo(logger, label, msg_txt, ...)

When information logging is enabled this macro expands to a call to SUNLogger_QueueMsg() to log an informational message. Otherwise, this expands to nothing.

Parameters:
  • logger – the SUNLogger to handle the message.

  • label – the const char* message label.

  • msg_txt – the const char* message text, may contain format specifiers.

  • ... – the arguments for format specifiers in msg_txt.

SUNLogInfoIf(condition, logger, label, msg_txt, ...)

When information logging is enabled this macro expands to a conditional call to SUNLogger_QueueMsg() to log an informational message. Otherwise, this expands to nothing.

Parameters:
  • condition – a boolean expression that determines if the log message should be queued.

  • logger – the SUNLogger to handle the message.

  • label – the const char* message label.

  • msg_txt – the const char* message text, may contain format. specifiers.

  • ... – the arguments for format specifiers in msg_txt.

To log debugging messages use the following macros:

SUNLogDebug(logger, label, msg_txt, ...)

When debugging logging is enabled this macro expands to a call to SUNLogger_QueueMsg() to log a debug message. Otherwise, this expands to nothing.

Parameters:
  • logger – the SUNLogger to handle the message.

  • label – the const char* message label.

  • msg_txt – the const char* message text, may contain format. specifiers.

  • ... – the arguments for format specifiers in msg_txt.

SUNLogDebugIf(condition, logger, label, msg_txt, ...)

When debugging logging is enabled this macro expands to a conditional call to SUNLogger_QueueMsg() to log a debug message. Otherwise, this expands to nothing.

Parameters:
  • condition – a boolean expression that determines if the log message should be queued.

  • logger – the SUNLogger to handle the message.

  • label – the const char* message label.

  • msg_txt – the const char* message text, may contain format. specifiers.

  • ... – the arguments for format specifiers in msg_txt.

To log extra debugging messages use the following macros:

SUNLogExtraDebug(logger, label, msg_txt, ...)

When extra debugging logging is enabled, this macro expands to a call to SUNLogger_QueueMsg() to log an extra debug message. Otherwise, this expands to nothing.

Parameters:
  • logger – the SUNLogger to handle the message.

  • label – the const char* message label.

  • msg_txt – the const char* message text, may contain format specifiers.

  • ... – the arguments for format specifiers in msg_txt.

SUNLogExtraDebugIf(condition, logger, label, msg_txt, ...)

When extra debugging logging is enabled, this macro expands to a conditional call to SUNLogger_QueueMsg() to log an extra debug message. Otherwise, this expands to nothing.

Parameters:
  • condition – a boolean expression that determines if the log message should be queued.

  • logger – the SUNLogger to handle the message.

  • label – the const char* message label.

  • msg_txt – the const char* message text, may contain format specifiers.

  • ... – the arguments for format specifiers in msg_txt.

SUNLogExtraDebugVec(logger, label, vec, msg_txt, ...)

When extra debugging logging is enabled, this macro expands to a call to SUNLogger_QueueMsg() and N_VPrintFile() to log an extra debug message and output the vector data. Otherwise, this expands to nothing.

Parameters:
  • logger – the SUNLogger to handle the message.

  • label – the const char* message label.

  • vec – the N_Vector to print.

  • msg_txt – the const char* message text, may contain format specifiers.

  • ... – the arguments for format specifiers in msg_txt.

SUNLogExtraDebugVecIf(condition, logger, label, vec, msg_txt, ...)

When extra debugging logging is enabled, this macro expands to a conditional call to SUNLogger_QueueMsg() and N_VPrintFile() to log an extra debug message and output the vector data. Otherwise, this expands to nothing.

Parameters:
  • condition – a boolean expression that determines if the log message should be queued.

  • logger – the SUNLogger to handle the message.

  • label – the const char* message label.

  • vec – the N_Vector to print.

  • msg_txt – the const char* message text, may contain format specifiers.

  • ... – the arguments for format specifiers in msg_txt.

SUNLogExtraDebugVecArray(logger, label, nvecs, vecs, msg_txt)

When extra debugging logging is enabled, this macro expands to a loop calling SUNLogger_QueueMsg() and N_VPrintFile() for each vector in the vector array to log an extra debug message and output the vector data. Otherwise, this expands to nothing.

Parameters:
  • logger – the SUNLogger to handle the message.

  • label – the const char* message label.

  • nvecs – the int number of vectors to print.

  • vecs – the N_Vector* (vector array) to print.

  • msg_txt – the const char* message text, must contain a format specifier for the vector array index.

Warning

The input parameter msg_txt must include a format specifier for the vector array index (of type int) only e.g.,

SUNLogExtraDebugVecArray(logger, "YS-vector-array", "YS[%d](:) =", YS, 5);

Struct Accessor Macros

Since many SUNDIALS structs use a type-erased (i.e., void*) “content” pointer, a common idiom occurring in SUNDIALS code is extracting the content, casting it to its original type, and then accessing the struct member of interest. To ensure readability, it is recommended to use locally (to the source file in question) defined macros GET_CONTENT and IMPL_MEMBER like the following example:

#define GET_CONTENT(S)       ((SUNAdjointCheckpointScheme_Fixed_Content)S->content)
#define IMPL_MEMBER(S, prop) (GET_CONTENT(S)->prop)

SUNAdjointCheckpointScheme self;
IMPL_MEMBER(self, current_insert_step_node)   = step_data_node;
IMPL_MEMBER(self, step_num_of_current_insert) = step_num;