Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
757dd75
Add CMake configuration file
philippeverley Aug 29, 2025
b240c9c
Split declarations and definitions
philippeverley Sep 1, 2025
5df5855
Moved constants to dedicated header
philippeverley Sep 4, 2025
054ede9
Moved define directives to dedicated header
philippeverley Sep 4, 2025
74c3c73
Add multi-platform build workflow
philippeverley Nov 25, 2025
86ed917
Add workflow_dispatch in build.yml for manual trigger
philippeverley Nov 25, 2025
63c355a
Comment windows-latest platform
philippeverley Nov 25, 2025
8970de9
Cache cmake and gsl for build job
philippeverley Nov 26, 2025
d9d74c9
Merge pull request #21 from troll-model/11-cmake-multi-platform-autom…
Nov 26, 2025
5dbd181
Bugfix in build workflow for gsl install with macos-latest
philippeverley Nov 26, 2025
eb06eb1
Simplify build workflow skipping apt update and brew update
philippeverley Nov 26, 2025
a08b482
Bugfix build workflow (#25)
Nov 26, 2025
db3c8c8
12 include lint checks (#23)
philippeverley Nov 26, 2025
9ea0966
sanity checks restructured
taut-and-yare Nov 27, 2025
531dda2
WIP: cache untested
taut-and-yare Nov 27, 2025
06be2a7
WIP: starting to thread ctx
taut-and-yare Nov 27, 2025
8b94cee
WIP: grid until sites
taut-and-yare Nov 27, 2025
5a2c5c5
sanity checks (#27)
philippeverley Nov 27, 2025
a703451
Merge branch 'dev' into 20-move-global-variables-to-context-structure
taut-and-yare Nov 27, 2025
6038b2e
forward declaration fix
taut-and-yare Nov 28, 2025
d526aae
reimplemented parameter assignment
taut-and-yare Nov 28, 2025
06ac457
added comments to new implementation
taut-and-yare Nov 28, 2025
47d560c
sanity: clean up test scripts and artifacts
taut-and-yare Mar 12, 2026
848d4b3
WIP params: implement species parameter registry
taut-and-yare Mar 12, 2026
1e74ad3
Revert "WIP params: implement species parameter registry"
taut-and-yare Mar 12, 2026
fccd262
refactor: remove dead AssignValueGlobal function
taut-and-yare Mar 12, 2026
0db580a
refactor: lift Context to file scope, thread into RegisterParameters
taut-and-yare Mar 12, 2026
7eb5071
refactor: migrate TimeState globals into ctx.time
taut-and-yare Mar 12, 2026
b55dee5
refactor: migrate Grid sub-struct globals into ctx.grid
taut-and-yare Mar 12, 2026
27b97ff
refactor: migrate ModelOptions sub-struct globals into ctx.opt
taut-and-yare Mar 12, 2026
18941a3
refactor: migrate FileIO and InputBuffers globals into ctx.fileio/ctx…
taut-and-yare Mar 12, 2026
971dea0
refactor: migrate Diagnostics sub-struct globals into ctx.diag
taut-and-yare Mar 12, 2026
a23f4e6
refactor: migrate CrownGeometry sub-struct globals into ctx.crown
taut-and-yare Mar 12, 2026
d4bdb93
refactor: migrate Climate sub-struct globals into ctx.climate
taut-and-yare Mar 12, 2026
effdfd8
refactor: migrate SpeciesState sub-struct globals into ctx.species
taut-and-yare Mar 12, 2026
a3ee388
refactor: replace AssignValueSpecies if/else chain with lambda registry
taut-and-yare Mar 12, 2026
e5db056
refactor: migrate LookupTables sub-struct globals into ctx.lookup
taut-and-yare Mar 12, 2026
a90005b
refactor: migrate Intraspecific sub-struct globals into ctx.intra
taut-and-yare Mar 12, 2026
f493085
refactor: migrate Soil globals into ctx.soil (sub-struct 11)
taut-and-yare Mar 12, 2026
2724dc7
refactor: migrate PointCloud globals into ctx.pc
taut-and-yare Mar 12, 2026
959a935
refactor: migrate RNG and covariance globals into ctx.rng
taut-and-yare Mar 12, 2026
cdd48cd
refactor: extend ctx.intra with sigma/corr/cov intraspecific params
taut-and-yare Mar 12, 2026
25d67b8
phase2: migrate SimParams and SimFields globals to ctx.params / ctx.f…
taut-and-yare Mar 12, 2026
624f3ed
phase3 step0: make troll.hpp safe for multi-TU inclusion
taut-and-yare Mar 12, 2026
aa2a16f
phase3: split Species methods into src/species.cpp
taut-and-yare Mar 12, 2026
e88f7b9
phase3: split crown/global functions into src/crown.cpp
taut-and-yare Mar 13, 2026
13a5b87
phase3: split Tree methods into src/tree.cpp
taut-and-yare Mar 13, 2026
8e12f35
phase3: split lookup table functions into src/lookup_tables.cpp
taut-and-yare Mar 13, 2026
bdd402b
phase3: split input functions into src/input.cpp
taut-and-yare Mar 13, 2026
08775fd
phase3: split memory/init functions into src/memory.cpp
taut-and-yare Mar 13, 2026
076fa63
phase3: split output functions into src/output.cpp
taut-and-yare Mar 13, 2026
4c08740
phase3: split simulation functions into src/simulation.cpp
taut-and-yare Mar 13, 2026
60777ab
phase4: migrate fstream globals into ctx.out (OutputConfig)
taut-and-yare Mar 13, 2026
934f6e2
phase4 step1: add Context &ctx to crown, lookup, species functions
taut-and-yare Mar 13, 2026
576f22a
phase4 step2: add Context &ctx to input functions
taut-and-yare Mar 13, 2026
8c417bd
phase4 step3: add Context &ctx to InitialiseOutputStreams, FreeMem, I…
taut-and-yare Mar 13, 2026
3d23932
phase4 step4: add Context &ctx to simulation functions
taut-and-yare Mar 13, 2026
dbd62c2
phase4 step7: remove global Context ctx, make local in main()
taut-and-yare Mar 13, 2026
7aacd2c
move S and T into Context; extract Species/Tree to own headers
taut-and-yare Mar 13, 2026
f577969
add Context &ctx to Output_ABC functions; fix gslrng bug
taut-and-yare Mar 13, 2026
6bff016
added 1000 iteration target
taut-and-yare Mar 14, 2026
9d1c170
added documentation file
taut-and-yare Mar 14, 2026
3af1650
removed stale file
taut-and-yare Mar 14, 2026
1fba340
Typos in REFACTORING.md
taut-and-yare Mar 14, 2026
8b5fde7
Typo
taut-and-yare Mar 14, 2026
2c8543e
Fix typos and enhance clarity in documentation
taut-and-yare Mar 14, 2026
dbc6d63
removed stale comments
taut-and-yare Mar 16, 2026
5d13af6
Merge branch 'dev' into monolith-split
taut-and-yare Mar 19, 2026
21d5965
gcc error fix
taut-and-yare Mar 19, 2026
6670f49
one more gcc fix
taut-and-yare Mar 19, 2026
4263fa5
Update REFACTORING.md with MPI
taut-and-yare Mar 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,21 @@ MESSAGE(STATUS "GSL found")
MESSAGE(STATUS " - version ${GSL_VERSION}")
MESSAGE(STATUS " - included files located in ${GSL_INCLUDE_DIRS}")

add_executable(${PROJECT_NAME} src/troll.cpp src/lookUpTables_cache.cpp)

add_compile_options(-w)
add_executable(${PROJECT_NAME}
src/troll.cpp
src/species.cpp
src/crown.cpp
src/tree.cpp
src/lookup_tables.cpp
src/input.cpp
src/memory.cpp
src/output.cpp
src/simulation.cpp
src/lookUpTables_cache.cpp
src/params/param_registry.cpp
)

# Include directories
target_include_directories(${PROJECT_NAME} PRIVATE
Expand All @@ -33,6 +47,13 @@ add_custom_target(sanity
COMMENT "Running sanity check (bit-by-bit comparison)"
)

# sanity check — 1000 iterations
add_custom_target(sanity1000
COMMAND ${CMAKE_SOURCE_DIR}/sanity/check_1000.sh
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/sanity
COMMENT "Running sanity check — 1000 iterations (bit-by-bit comparison)"
)

# Make 'test' depend on 'sanity' (and any other tests in the future)
add_custom_target(test DEPENDS sanity)

Expand Down
191 changes: 191 additions & 0 deletions REFACTORING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# Refactoring notes

> This documents the refactoring process of `src/troll.cpp` from a ~10 000-line monolith into a modular, context-threaded codebase.

---

## Starting point: the monolith

Before this refactor, the entire simulation lived in a single file. It had ~10 000 lines and ~200 file-scope global variables — parameters, field arrays, output streams, RNG state, and the `Species`/`Tree` vectors were all declared at file scope.

---

## The quick dev-test: `make sanity`

Before any code was moved, a regression test was set up: `make sanity` compiles both the original TROLL v4.0 monolith and the refactored version, runs both on the same inputs, and compares the 41 output files.

Lookup tables were cached for repeated checks to speed things up.

This quick-check (20 iterations) was run after every commit to ensure nothing "broke".

In the end 1000 iterations were tried too successfully.

---

## Phase 1 — Constant/flag extraction (the easy part)

Initially, three small headers were split out:
- `include/troll_defines.hpp` — compile-time feature flags (`#define`/`#undef WATER`, `Output_ABC`, `CACHE_LUT`, etc.)
- `include/constants.hpp` — physical constants (`PARSEC`, `kBoltzmann`, etc.)
- `include/troll.hpp` — the remaining global declarations (later reduced further)

---

## Phase 2 — The `Context` struct

A single `Context` struct was introduced in `include/context.hpp`, grouping all ~200 globals into 17 named sub-structs:

| Sub-struct | Contents |
|---|---|
| `FileIO` | Input/output file path char buffers |
| `InputBuffers` | Raw `argv` pointers for command-line parsing |
| `OutputConfig` | All `fstream` output file objects |
| `ModelOptions` | Boolean feature flags (`_BASICTREEFALL`, `_NDD`, etc.) |
| `Grid` | Site count, dimensions, spatial arrays, HEIGHT, SBORD |
| `TimeState` | `iter`, `nbiter`, `iterperyear`, `timestep`, etc. |
| `Climate` | Daily climate vectors, per-step scalars, yearly means |
| `LookupTables` | All `LookUp_*` pointer arrays and their bin counts |
| `Soil` | ~25 soil/water variables and 3-D field arrays |
| `Diagnostics` | Live/dead tree counts, DBH histograms |
| `PointCloud` | LiDAR simulation parameters |
| `RNGState` | GSL RNG pointer, covariance matrix/vectors |
| `SpeciesState` | Seed arrays, species probabilities |
| `Intraspecific` | 10×10000 trait deviation arrays, sigmas, correlations |
| `CrownGeometry` | Gap fraction, crown shape, visual extent bounds |
| `SimParams` | 93 simulation parameters (light, CO2, allometry, mortality…) |
| `SimFields` | `LAI3D`, `Thurt[3]` dynamic field arrays |

`vector<Species> S` and `vector<Tree> T` are direct fields on `Context`, not wrapped in a sub-struct (see Phase 5).

**Grouping?**

Being unaware of the scientific context, quite some guessing took place here: variables that are always read and written together, or that form a concept, belong together. Hope it makes sense...

The grouping also controls scope. A function that only queries the grid does not need to know that `Soil` or `RNGState` exist.

**A note on performance**

`OutputConfig` is placed **last** in `Context`. `fstream` objects are big and rarely accessed during the simulation. Placing them last keeps the frequently-accessed fields (`grid`, `time`, `params`, `field`) at low offsets for better caching, otherwise it was observably slower.

---

## Phase 3 — Splitting `troll.cpp`

`troll.cpp` was split into eight translation units:

| File | What it contains | Lines |
|---|---|---|
| `src/troll.cpp` | `main()` only — orchestration | ~318 |
| `src/species.cpp` | `Species::Species()`, `Species::Init()` | ~54 |
| `src/tree.cpp` | All `Tree::*` methods | ~3355 |
| `src/crown.cpp` | Crown geometry and light calculation helpers | ~353 |
| `src/lookup_tables.cpp` | `InitialiseLookUpTables`, LAImax, intraspecific LUTs | ~414 |
| `src/input.cpp` | All `ReadInput*` functions, species/pointcloud assignment | ~842 |
| `src/memory.cpp` | `AllocMem`, `FreeMem`, `Initialise`, output stream init, ABC init | ~1183 |
| `src/output.cpp` | `Average`, all `Output*` functions, close functions | ~3189 |
| `src/simulation.cpp` | `Evolution`, `UpdateSeeds`, `UpdateField`, `FillSeed`, `RecruitTree`, treefalls | ~745 |

**Grouping?**

Again, fingers crossed for my splitting decisions.

---

## Phase 4 — Threading `Context` by reference

`Context ctx` was made a local variable in `main()`. Every function that previously touched a global was updated to accept `Context &ctx` as its first argument and access state through it.


This threading was done, function-group by function-group (`crown`, `lookup`, `species`, then `input`, then `memory`/`output`/`simulation`). Each was independently sanity-tested.

---

## Phase 5 — Species and Tree arrays

- `Species` class declaration was extracted from `troll.hpp` into `include/species.hpp`.
- `Tree` class declaration (and the `leafFluxes` struct) was extracted into `include/tree.hpp`.
- `include/context.hpp` was updated to include: `vector<Species> S` and `vector<Tree> T` as direct `Context` fields.
- All `S[i]` / `T[i]` call sites across all `.cpp` files were replaced with `ctx.S[i]` / `ctx.T[i]`.


---

## The parameter registry

The 300-line `AssignValueGlobal` if/else chain (which assigned values to globals after reading `global_inputs.txt`) was replaced by a registry in `src/params/param_registry.cpp`.

Each parameter is registered once with its name, target pointer, min value, max value, and default value.

A single `SetParameter<T>` template validates and assigns.

The same pattern was later applied to species parameters (`BuildSpeciesRegistry` / `AssignSpeciesParam` in `src/input.cpp`).

**A note: Context before input**

The registry binds directly to `ctx.*` fields via pointers, so it requires a `Context &ctx` at registration time. `RegisterParameters(ctx)` must be called early in `main()`, before any file is read.

## Summary

| Dimension | Before | After |
|---|---|---|
| Largest single file | ~10 000 lines (`troll.cpp`) | ~3355 lines (`tree.cpp`) |
| Global variable count | ~200 file-scope | 3 (MPI, disabled by default) |
| Parameter assignment | 300-line if/else chain | Registry, 93 entries |
| Function signatures | No explicit state | `Context &ctx` first argument |
| Multi-TU compilation | Single translation unit | 9 translation units, parallel build |
| Testability | One global simulation state | `Context` is a local value; two contexts = two simulations |
| Data flow | Implicit (any function, any global) | Explicit (visible in call site) |

---

## TODOs?

### MPI globals

`mpi_rank`, `mpi_size`, and `easympi_rank` remain as file-scope globals in `troll.cpp`. MPI support is disabled by default (`#undef MPI`). Moving these into `Context` would require threading them through functions that are not currently MPI-aware.

In a conversation here [https://github.com/troll-model/TROLL/pull/22#issuecomment-3580696619] I saw that maybe it should be removed? TBD

### Conditional-compilation diagnostics

`#ifdef Output_ABC` and `#ifdef CHECK_CARBON` blocks remain conditional. The ABC functions were updated to accept `Context &ctx`, but the ABC globals (`chm_field_*`, `transmittance_*`, moving averages) are **not** in `Context` yet

### Local variable renames (marked with `// RENAMED:` comments)

During the split (Phase 3) and the `Context` threading (Phase 4), several local variables and function parameters were renamed to avoid shadowing same-named `ctx.params.*` fields. Each renamed site carries a `// RENAMED:` comment explaining the original name and why it changed.

The affected names are:

| Original name | New name | Location | Reason |
|---|---|---|---|
| `alpha` | `alpha_vgm` | `src/input.cpp` | Van Genuchten–Mualem soil parameter; distinct from `ctx.params.alpha` (apparent quantum yield) |
| `alpha` | `alpha_crown` | `src/tree.cpp` | FvCB canopy integration ratio; distinct from `ctx.params.alpha` |
| `dens` | `dens_layer` | `src/crown.cpp` (params + locals) | Per-layer crown density; was shadowing `ctx.params.dens` |
| `dens` | `dens_lowerlayer` | `src/crown.cpp` (locals) | Base-layer density from `GetDensitiesGradient`; distinct from the `dens_layer` output parameter |

**We can probably revert back if needed**

After threading `Context`, the former globals are accessed as e.g. `ctx.params.alpha`, a local `alpha` can no longer shadow them.

---

### Confidence ###

### The scientist's validation ###

So far the only test that I haven't broken anything is the sanity check described above. It does compare row by row, so per-step divergencies are checked, as well as final values. I tried it at 20 iterations while developing and at 1000 once when I finished. Perhaps a bigger number should be tried? Not sure how the model is behaving in time. Or with different inputs/initial params etc.

Most certainly, you, if you are reading this, will have more ideas than me on how to better test the "science-correctness" of this refactoring.


### The developer's validation ###

Compilation warnings are not worrying (one could fix/remove them though). So, in combination with the sanity checks performed and the knowledge that I mostly "moved things around" I am quite confident this is sound.


### A note on a tricky bug (hope there's no more of these that just didn't come up) ###

During the refactoring, one bug was very tricky to find:
CloseOutputs only looped i < 10, leaving output[11], output[31], output[32] (and others) never closed. Those were flushed automatically when `exit(0)` triggered global destructor cleanup — because `ctx` was global. With `ctx` local, `exit(0)` skiped local destructors, so those buffers were silently lost. Perhaps we should check if similar things exist elsewhere.


Loading
Loading