Skip to content

Development

Guide for contributing to Thor.

Setup

Prerequisites

Clone and Build

git clone --recursive https://github.com/cbyrohl/thor.git
cd thor

# Install dev dependencies
uv sync --group=ci-tests --group=docs

# Debug build
cmake -S . -B build \
  -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_CXX_COMPILER=acpp

cmake --build build

Memory Usage

Compilation can require large amounts of memory. If you run out of memory during the build, reduce the number of concurrent builders by adding -j1 to the build command:

cmake --build build -j1

Project Structure

thor/
├── src/
│   ├── datasets/           # Dataset implementations
│   ├── drivers/            # Simulation drivers
│   ├── interactors/        # Define photon interactions
│   ├── outputprocessors/   # Output handling
│   ├── photongenerators/   # MC package spawning
│   ├── rngs/               # Random number generators
│   └── main.cpp            # Entry point
├── tests/                  # Unit tests (Catch2)
├── regression_tests/       # End-to-end simulation tests
├── scripts/                # Utility scripts (PractRand testing, etc.)
└── extern/                 # (submodule) dependencies

Key Components

  • Datasets (src/datasets/): Computational meshes and geometry
  • Drivers (src/drivers/): Simulation algorithms (MCRT, ray tracing)

Code Style

C++20 with SYCL, formatted via ./.clang-format.

  • sp(VAR), dp(VAR), fp(VAR): cast to single, double, or compile-time FP.
  • ip(VAR): cast to the compile-time IP (32- or 64-bit signed integer).
  • Mark device-callable functions with SYCL_EXTERNAL.

Testing

The project includes both unit and regression tests. For a detailed explanation of the testing frameworks and how to run tests, please see the Testing documentation.

Documentation media assets

Where a media file lives depends on whether it should version with the repository:

  • Videos and animations (*.webm, *.mp4, animated *.webp) are never committed. They are hosted on the rolling GitHub release at thor-rt/docs-assets and referenced from markdown as full URLs.
  • Raster figures (*.png) also go to the docs-assets release by default, especially large or presentation-style renders. Commit a PNG to docs/assets/images/ only when it should track a code revision (benchmarks, validation plots, cookbook figures regenerated from committed scripts) or when it is small and stable. Committing one requires git add -f since .gitignore blocks *.png. The friction is deliberate: a committed PNG should be a conscious decision.
  • Vector figures (*.svg) are committed normally; they are small and diff as text.

Figure style (scripts/thorplot.py)

Plots that go into the docs should use the shared house style so every figure looks consistent. scripts/thorplot.py provides the THOR look — a minimal, slide-style aesthetic (light by default; dark=True for the navy variant that matches the docs dark mode). It offers two colour strategies: a fixed per-code map (code_color) for neutral comparisons, and a "hero" treatment (hero_colors, bold accent for one series + muted peers) for THOR-centric figures. Plus small helpers:

import thorplot as tp
with tp.style():                     # or tp.style(dark=True)
    fig, ax = plt.subplots(figsize=tp.FIGSIZE_WIDE)
    ax.plot(x, y, color=tp.code_color("thor"))
    tp.strip_axes(ax); tp.legend_top(ax)
tp.save_doc_figure(fig, "my_plot")   # -> docs/assets/images/my_plot.svg

save_doc_figure writes SVG by default — vector-crisp at any zoom, smaller than PNG for line plots, and (unlike *.png) not blocked by .gitignore, so it commits without -f and diffs as text. Pass fmt="png" for raster-heavy figures (volume renders, dense scatter) where SVG would balloon.

Generate both a light and a dark version and reference them with mkdocs-material's light/dark suffixes so the figure follows the page theme:

![alt](assets/images/my_plot.svg#only-light)
![alt](assets/images/my_plot_dark.svg#only-dark)

scripts/plot_benchmark.py is a worked example (it renders the Neufeld benchmark figure both ways).

The docs page stretches every image to the content-column width, so figures authored at different widths display their fonts at different sizes. The full-width reference is tp.DOC_FULL_WIDTH (12.2 in). Author full-column figures at that width; for anything narrower, embed at the proportional column width so fonts render at the intended size (save_doc_figure prints the attribute to use):

![alt](assets/images/my_hexbin.png#only-light){ width="57%" }

For 2D coordinate maps (imshow panels), the house look is minimal: no ticks, axis labels, or frames — a scale bar replaces the axes, and colorbars are frameless with markless ticks. The helpers are in thorplot too:

with tp.style(dark=dark):
    fig, axes = plt.subplots(1, 2, constrained_layout=True)
    for ax, m, title in zip(axes, maps, titles):
        im = ax.imshow(m, extent=extent, norm=norm, cmap="plasma")
        tp.imshow_panel(ax, title)       # strip ticks/labels/frame
    tp.scalebar(axes[0], 10.0, "10 pMpc")  # one bar; panels share the scale
    tp.colorbar_minimal(fig, im, axes, label=r"$\langle L \rangle$ [pMpc]")
    tp.suptitle_left(fig, "...", dark=dark)

Figure-generating scripts that only need data committed to this repo live in scripts/plot_*.py. Scripts that need external snapshots or long runs live in the companion thor_setups repo (setups/<topic>/python/, importing thorplot from a side-by-side thor checkout). In that case record the provenance next to the embed in the page source:

<!-- figure-source: thor_setups/setups/coherence_length (plot_tracer_stats.py) -->

Uploading

Stage the file anywhere on disk, then run:

./scripts/upload-docs-asset.sh path/to/banner.webp path/to/clip.webm

The script bootstraps the docs-assets release on first use, uploads with --clobber (re-uploads overwrite), and prints the canonical URLs to paste into markdown.

URL pattern

https://github.com/thor-rt/docs-assets/releases/download/docs-assets/<filename>

Naming rules

  • Filenames are unique on the release. Don't re-upload to the same name unless you intend to replace the live content for every page that references it; bump a version suffix (banner_v2.webp) when in doubt.
  • Orphaned assets stay until cleaned up explicitly: gh release delete-asset docs-assets --repo thor-rt/docs-assets <filename>.

Sanity checks

# What's currently on the release
gh release view docs-assets --repo thor-rt/docs-assets --json assets -q '.assets[].name'

# What URLs the docs reference
grep -rhoE 'releases/download/docs-assets/[A-Za-z0-9._-]+' docs/ | sort -u

If something is in the second list but not the first, it's a broken image on the deployed site. If only in the first, it's an orphan.

AI Authorship Attribution

This project uses AI-assisted development (Claude Code) extensively. AI-generated code is tagged for transparency:

  • Functions: [AI-Claude] tag at the end of the \brief Doxygen line
  • Tests: [AI] tag in Catch2 test tags, e.g., [mytag][AI]
  • Shell scripts: [AI-Claude] at end of first description comment line
  • Python modules: [AI-Claude] at end of first docstring line

Tags are removed after in-depth human review and approval.

Contributing

Contribute by submitting pull requests from your fork and the new feature branch.

Report issues at GitHub Issues.