Development
Guide for contributing to Thor.
Setup
Prerequisites
- All installation requirements
- uv for Python dependencies
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:
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-timeFP.ip(VAR): cast to the compile-timeIP(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 todocs/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 requiresgit add -fsince.gitignoreblocks*.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:
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):
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:
Uploading
Stage the file anywhere on disk, then run:
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
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\briefDoxygen 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.