Developer Guidelines#

This section provides guidelines for developers of simtools. Great that you want to contribute! Please contact first the simtools team using one of the contact points listed at the entry page of this documentation before working on simtools.

Simtools follows generally the development guidelines of CTAO and ctapipe (see CTAO development documentation and ctapipe development).

Project setup#

The main code repository for simtools is on GitHub: gammasim/simtools

The main directories of simtools are:

Python version#

The simtools package is currently developed for Python >=3.11.

Contributing code#

It is recommended to discuss any code changes with the simtools team before starting to implement them (e.g., by opening an issue on GitHub).

The following steps outline how to contribute code to simtools:

  1. Set up your coding environment as outlined in the getting started section.

  2. Start a new feature branch from the main branch (git checkout -b new-branch-name).

  3. Implement your code changes.

  4. Add unit tests for new modules and functions (see unit test section).

  5. Commit your code changes (use meaningful commit messages) and push them to GitHub.

  6. Create a draft pull request on GitHub when all features are implemented.

  7. Wait for the CI tests to finish and address any issues that arise.

  8. After successful tests, mark the pull request as ready for review.

  9. Wait for a review of your code and address any issues that arise.

  10. After successful review, the pull request can be merged into the main branch.

Note the guidelines on pull requests.

Writing Applications#

Applications are command-line tools that should be built of the simtools library. Application should not include complex algorithm, this should be done at the module level.

All applications should follow the same structure:

def main():

    # application name
    label = Path(__file__).stem
    # short description of the application
    description = "...."
    # short help on how to use the application
    usage = "....."

    # configuration handling (from command line, config file, etc)
    config = Configurator(label=label, description=description, usage=usage)
    ...
    args_dict, db_dict = config.initialize()

    # generic logger
    logger = logging.getLogger()
    logger.setLevel(gen.get_log_level_from_user(args_dict["log_level"]))

    # application code follows
    ...

Application handling should be done using the Configurator class, which allows to set configurations from the command-line options, configuration files, or environmental variables. Check the commandline_parser module for generic command-line arguments before introducing new ones in applications

The documentation of application uses the in-line doc string.

Adding an applications requires the following changes:

Dependencies#

Dependencies on external packages should be kept to a minimum. Packages are listed twice:

Some of the packages installed are used for the development only and not needed for executing simtools application (see the ordering in sections in pyproject.toml).

Integration with CORSIKA and sim_telarray#

CORSIKA and sim_telarray are external tools to simtools. Their integration should be minimally coupled with the rest of the package. The modules that depend directly on these tools should be connected to the rest of the package through interfaces. This way, it will be easier to replace these tools in the future.

One example of this approach is simulator module, which connects to the tools used to manage and run simulations.

Data files#

Data files should be kept outside of the simtools repository with the exception of files required for units tests. These files should be kept at minimum and are stored in the tests/resources directory.

Data files required by integration tests are downloaded during testing from the simulation model database.

Some auxiliary files can be found in the data directory. Note that this is under review and might go away in near future.

Input validation#

Warning

This section is deprecated and will be removed in the future.

Any configurable inputs (e.g. physical parameters) to modules must have them validated. The validation assures that the units, type and format are correct and also allow for default values.

The configurable input must be passed to classes through a dictionary or a YAML file. In the case of a dictionary the parameter is generally called config_data, in the case of a YAML file, config_file.

The function gen.collect_data_from_yaml_or_dict must be used to read these arguments. It identifies which case was given and reads it accordingly, returning a dictionary. It also raises an exception in case none are given and not allow_empty.

The validation of the input is done by the function value_conversion.validate_config_data, which receives the dictionary with the collected input and a parameter dictionary. The parameter dictionary is read from a parameter YAML file in the data/parameters directory. The file is read through the function io.get_data_file(“parameters”, filename) (see data files section).

Parameter YAML files contain the list of parameters to be validated and its properties. See an example below:

zenith_angle:
  len: 1
  unit: !astropy.units.Unit {unit: deg}
  default: !astropy.units.Quantity
    value: 20
    unit: !astropy.units.Unit {unit: deg}
  names: ['zenith', 'theta']
  • len gives the length of the input. If null, any len is accepted.

  • unit is the astropy unit

  • default must have the same len

  • names is a list of acceptable input names. The key in the returned dict will have the name given at the definition of the block (zenith_angle in this example)