Skip to content

Latest commit

 

History

History
349 lines (262 loc) · 14.1 KB

File metadata and controls

349 lines (262 loc) · 14.1 KB

Coding Guidelines

Motivation

Coding style is important. A clean, consistent style leads to code that is more readable, debuggable, and maintainable. To this end, we prescribe (and proscribe) a variety of practices. Our goal is to encourage agile but reasoned development of code that can be easily understood by others.

C/C++ foundational guidelines: this document uses as its foundation the coding guidelines developed by Stroustrup and Sutter.

Python foundational guidelines: the PEP 8 guidelines are to be followed for python code development.

The both of these foundational sets of guidelines are extensive and where the Basilisk document is silent these guidelines will prevail.

These are guidelines, not rules. With very few exceptions, this document does not completely ban any particular C/C++ or Python pattern or feature, but rather describes best practice, to be used in the large majority of cases. When deviating from the guidelines given here, just be sure to consider your options carefully, and to document your reasoning, in the code.

Above all else, be consistent. Follow this guide whenever possible, but if you are editing a package written by someone else, follow the existing stylistic conventions in that package (unless you are retrofitting the whole package to follow this guide, for which you deserve an award).

Contribution Guidelines: see the file CONTRIBUTING.md for additional information on making code to contribute back to the Basilisk repository.

What about non-conforming code?

Some Basilisk code was written prior to the release (and updates) of this style guide. Thus, the codebase may contain code that doesn’t conform to this guide. The following advice is intended for the developer working with non-conforming code:

  1. All new code should conform to this guide.
  2. Unless you have copious free time, don’t undertake converting the existing codebase to conform to this guide.
  3. If you are the author of a non-conforming package, try to find time to update the code to conform.
  4. If you are doing minor edits to non-conforming code, follow the existing stylistic conventions in that code (if any). Don’t mix styles.
  5. If you are doing major work on non-conforming code, take the opportunity to re-style it to conform to this guide.

Naming

The following shortcuts are used in this document to denote naming schemes:

  • CamelCased: The name starts with a capital letter, and has a capital letter for each new word, with no underscores.
  • camelCased: Like CamelCase, but with a lower-case first letter
  • under_scored: The name uses only lower-case letters, with words separated by underscores.
  • ALL_CAPITALS: All capital letters, with words separated by underscores.

General Guidelines

Variables

No single letter variables. The only exceptions are ‘i’ as an iteration index and a select list of mathematical symbols.

Naming Variable for Mathematics

The following section specifies general guidelines for the naming of variable to be used in code which implements mathematical operations. The naming convention is heavily influenced by the textbook Analytical Mechanics of Space Systems by Schaub and Junkins.

Indicating Reference Frames

A vector variable expressed with components in a reference frame \cal B, is represented with the variable name followed by an underscore and a capital letter denoting the frame {}^{\cal B}\bf v as vector_B.

An angular rate variable expressed in one frame \cal B with respect to a second \cal R, where components are expressed in the frame \cal B, {}^{\cal B}\pmb\omega_{\mathcal{B}/\mathcal{R}}, is given as omega_BR_B.

MRP’s and DCM’s

A direction cosine matrix is expressed as [BN], a mapping of an \cal N frame vector into a \cal B frame vector, is written dcm_BN. Similarly for the Modified Rodrigues Parameters (MRP) attitude parameterization the \pmb\sigma_{\mathcal{B}/\mathcal{N}} is written sigma_BN.

Warning

If you are using the Intel Eigen library library to do linear algebra, the mapping from an attitude description such as quaternions or MRPs to a direction cosine matrix (DCM) using .toRotationMatrix() will return [NB], not [BN].

Inertia Tensor

The Inertia tensor [I_C] of the hub about the point C is defined in the body frame \cal B components using the variable IHubPntC_B.

Derivatives

The first and second time derivatives of scalar (\dot{x}, \ddot{x}) or vector (\dot{\bf{x}}, \ddot{\bf{x}}) quantities, respectively are written as xDot and xDDot.

The first and second time derivatives with respect to a variable other than time should use the same pattern as time derivatives but with a different modifier. For example, f '(x) and f ''(x) are written as xPrime and xDPrime respectively.

Common Usage Examples

  • Position vector from \cal N to \cal B in inertial frame components {}^{\cal N} \bf r_{\mathcal{B/N}}: r_BN_N
  • Inertial time derivative of position vector from \cal N to \cal B in inertial frame components {}^{\cal N} \dot{\bf r}_{\cal B/N}: rDot_BN_N
  • Time derivative with respect to the body of position vector from B to H in body frame components {}^{\cal B} \bf r'_{H/B}: rPrime_HB_B
  • Unit direction vector from B to S in body frame components {}^{\cal B} \hat{\bf s}_{S/B}: sHat_SB_B
  • Inertial time derivative of body angular rate with respect to the inertial frame in body frame components {}^{\cal B} \dot{\pmb\omega}_{\mathcal{B}/\mathcal{N}}: omegaDot_BN_B
  • DCM of the body frame with respect to the inertial frame [BN]: dcm_BN

Modules

Messages

Variables holding message names are to be composed in the following manner.

SomeMsg_C descriptionInMsg;                      // C interface to input msg
SomeMsg_C descriptionOutMsg;                     // C interface to output msg
ReadFunctor<SomeMsgPayload> descriptionInMsg;    // C++ interface to input message
Message<SomeMsgPayload> descriptionOutMsg;       // C++ interface to output message
  • SomeMsgPayload: message structure definition
  • In (Out): indicates the direction of the message with respect to the module.
  • Msg: explicitly identifies the variable as a message.

Variables holding data from a read message are to be composed in the following manner.

SomeMsgPayload descriptionInBuffer;
  • description: description of the data.
  • In (Out): indicates the direction of the data being written to the buffer with respect to the module.
  • Buffer: explicitly identifies the variable as having a data buffer functionality.

Message Definitions

The C based messages are stored in src/architecture/msgPayloadDefC as a *.h file. The C++ messages are stored in src/architecture/msgPayloadDefCpp as a *.h file. The file name uses Upper Camel Case and should be identical to the message name within the file. The last letters should be MsgPayload. For example, a particular spacecraft sensor message could be named SpecialSensorMsgPayload.h. The contents could be

#ifndef SPECIAL_SENSOR_MESSAGE2_H
#define SPECIAL_SENSOR_MESSAGE2_H

/*! @brief Describe the purpose of the message */
typedef struct {
    double sensorOutput_B[3];   //!<        sensor vector in B frame components */
    double sensorSignal;        //!<        raw sensor signal
    int status;                 //!<        sensor status flag
}SpecialSensorMsgPayload;

#endif

When running the Basilisk setup command python3 conanfile.py the related message interface files are then automatially created and included in the project.

C/C++ Exceptions

  • Currently no language specific exceptions

Python Exceptions

  • Variables are to be lower camelCase. This is done to maintain consistency across the C/C++ and Python code bases which are interfaced via SWIG.
  • Inline comments are accepted so long as they are kept brief.
  • Binary operator spaces will be adhered to as specified in PEP 8, however, not for math symbols operations. E.g. no spaces are included around *, /, +, -, etc
# Yes
x = (4*9/2)-1
# No
x = (4 * 9 / 2) - 1

Running Unit and Integrated Tests

Prior to any code being pushed back to the Basilisk repo all unit and integrated tests must pass. The default packages include pytest which is a program that can run a series of tests on python scripts that begin with test_. The package pytest-xdist is also installed by default and allows these tests to be run in a multi-threaded manner using the argument -n auto.

Further, Basilisk uses the Google gtest suite to run unit tests on C or C++ support libraries. To run all the unit and integrated test open a terminal window and change your working directory to the root basilisk directory. Activate the python virtual environment if needed. Next, run the command:

$ python run_all_test.py

This executes the pytest and gtest checks. It also executes all Rust workspace tests when Basilisk was configured with --rustModules True and Cargo is available on PATH. If either condition is not met, the script reports that the Rust tests were skipped and continues. All tests that run should pass. If not all Basilisk modules are built (i.e. the build process turned off the opNav option), then some Python tests will show up as skipped.

Guarding Tests for Optional Build Features

Tests that require an optional Basilisk build feature must query the configured build metadata with Basilisk.hasBuildFeature(). Do not infer build capabilities by catching ImportError. Import probing can hide a broken build where a feature was enabled but its module failed to import.

hasBuildFeature() reports capabilities supplied either by the core Basilisk artifact or by an installed optional Basilisk distribution. The same guard therefore works for a monolithic source build and for a split-wheel installation. Optional distributions must match the core version; a mismatch raises RuntimeError rather than silently skipping the affected tests. Once the guard reports that a feature is present, import its module normally so a broken installation still fails during test collection.

If every test in a file requires the same optional feature, use a module-level pytestmark and import the optional modules conditionally:

import pytest

from Basilisk import hasBuildFeature

mujocoEnabled = hasBuildFeature("mujoco")
pytestmark = pytest.mark.skipif(
    not mujocoEnabled,
    reason="Requires Basilisk built with --mujoco True",
)
if mujocoEnabled:
    from Basilisk.simulation import mujoco


def test_mujoco_feature():
    scene = mujoco.MJScene("<mujoco/>")
    # Test the feature.

A module-level pytestmark applies to every test collected from that file, including parameterized tests. It does not stop module-level statements from running during test collection, which is why the optional import must still be conditional. When the feature is enabled, import it normally and allow import failures to surface.

If a file contains both core tests and tests for an optional feature, guard only the feature-specific tests:

import pytest

from Basilisk import hasBuildFeature

mujocoEnabled = hasBuildFeature("mujoco")
if mujocoEnabled:
    from Basilisk.simulation import mujoco


def test_core_feature():
    # This test runs in every build configuration.
    pass


@pytest.mark.skipif(
    not mujocoEnabled,
    reason="Requires Basilisk built with --mujoco True",
)
def test_mujoco_feature():
    scene = mujoco.MJScene("<mujoco/>")
    # Test the optional feature.

Scenario test wrappers must also conditionally import a scenario when the scenario imports an unavailable optional module at module scope. The supported feature names are vizInterface, opNav, and mujoco. Required Python dependencies, such as Pillow, must be imported normally; a missing required dependency is an environment error and must not cause the test to be skipped.

Optional developer performance benchmarks are documented separately in :ref:`performanceBenchmarks`. Benchmark timing runs are opt-in speed investigations. The lightweight benchmark smoke tests only check that benchmark entry points still execute, and may be included in the default pytest collection paths.

If you want to use pytest to generate a validation HTML report, then the pytest-html package is used. In a terminal window, make your working directory basilisk/src. Next, run pytest by adding the --html argument followed by the path to where to generate the report html folder. It is recommended to put this inside a folder as HTML support folder will be created:

$ pytest --html report/report.html

Formatting Code Files using pre-commit and clang-format

If you are developing new code to contribute back to Basilisk it must follow the :ref:`codingGuidelines`. This requires using the pre-commit and clang-format packages. The file CONTRIBUTING.md explains how to setup and use these code formating tools.