Architecture Rules#
These rules apply to new and changed code. Reviewers refer to them.
Layers and imports#
The package is organized in layers. A module may import only from its own or a
lower layer. Library code never imports simtools.applications, and
production code never imports test helpers.
Import-linter enforces the package layers, and Ruff checks private cross-module
imports. The ignore_imports list of [tool.importlinter] in pyproject.toml
is a ratchet: reduce it as the architecture cleanup proceeds and do not add
entries.
5 applications/ thin CLI wrappers
4 workflows simulate, production, science_tests, reporting, derive_*
3 simulation engines corsika, simtel, ray_tracing, camera, job_execution
2 model model (site/telescope/array), model_repository, layout
1 data io, data_model (schemas, metadata), sim_events readers
0 foundation utils, constants, names, units
visualization may import 0-2 only; receives data, never computes it
Plotting modules receive arrays, tables or dataclasses and draw them. Rebinning, statistics and comparison metrics belong in the module that computes them, so they can be tested without matplotlib.
Adding is data, not code#
Prefer data over code for anything that is expected to grow:
A new model parameter is a schema in
schemas/model_parameters/plus model data. Simulator-specific names and formats belong in schema fields (simulation_software,internal_parameter_name). Add code only for a new output format.A shared file product (suffix, default name) is an entry in
resources/file_types.yml.A simple line or histogram plot is a
plot_tablesconfiguration.
Extension points#
Each axis where simtools grows has one extension point:
Axis |
Extension point |
|---|---|
Model parameter |
schema YAML in |
Simulation configuration writer and result reader |
|
Ray-tracing backend |
|
Event-file format |
|
Model-repository storage |
|
Execution backend |
|
Plot |
|
Application |
|
New extension points use a small typing.Protocol and a module-level dict
mapping names to implementations (with lazy imports for optional
dependencies). Do not add register_* functions, factories or plug-in
machinery outside these axes. Engine-specific vocabulary (CORSIKA keywords,
sim_telarray option names) stays inside the engine’s package; other code uses
physics terms.
Adding a simple plot#
Use plot_tables.plot to render a line or histogram plot from library code. The
configuration follows plot_configuration.metaschema.yml; table data can come
from an ECSV file or model parameters. For example, with response.ecsv in the
data directory:
from pathlib import Path
from simtools.visualization import plot_tables
config = {
"type": "example_response",
"title": "Detector response",
"xtitle": "Wavelength [nm]",
"ytitle": "Response",
"tables": [
{
"file_name": "response.ecsv",
"label": "Response",
"column_x": "wavelength",
"column_y": "efficiency",
}
],
}
plot_tables.plot(config, Path("response.png"), data_path=Path("data"))
The same configuration can be stored as plot_configuration in a model
parameter schema for plots generated by simtools applications.
Public API#
A name is public when it is documented in the API reference and does not start
with an underscore. A leading underscore means private to its module: do not
import it from another module; make it public or move it instead. Use
__all__ only in package __init__.py files that re-export names; do not
define per-module __all__ lists.
Before v1.0, API changes do not keep compatibility aliases. Remove or rename names directly and list each change in the changelog.
Test policy#
Unit tests do not use checked-in or downloaded test resources. Generate inputs in
tmp_test_directoryfrom the packaged schemas; reduced-event and trigger-histogram tables come fromtests/unit_tests/builders/tables.py, and in-memory model repositories fromMemoryStorage.Mock only true external boundaries: external programs (CORSIKA, sim_telarray, container engines, HTCondor), network access, model sources (through the shared model-reader fixtures in
tests/unit_tests/conftest.py), and time, UUID or randomness when determinism is needed. Usemonkeypatch.setenvfor environment variables.Do not add patches of other
simtools.*internals. Test public behavior; shape code as plain functions on arrays, tables or dataclasses so that it needs no mocks.File-format compatibility with real CORSIKA and sim_telarray output belongs in integration tests.