diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 888472d61..77f6e98e7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,31 +1,37 @@ -name: Publish Documentation - +name: Deploy production docs on: push: - branches: - - "master" + branches: [master] + +permissions: + contents: write jobs: - build: + build-and-deploy: runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Install Dependencies - run: sudo apt-get update && sudo apt-get install -y doxygen graphviz python3-pip + - name: Install system deps + run: | + sudo apt-get update + sudo apt-get install -y doxygen graphviz + python3 -m pip install --upgrade pip + python3 -m pip install mkdocs mkdocs-material pymdown-extensions - - name: Install MkDocs + - name: Build docs run: | - pip install mkdocs mkdocs-material pymdown-extensions + make clean document - - name: Generate Documentation - run: make document + - name: Ensure .nojekyll in site root + run: touch site/.nojekyll - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 + - name: Deploy to gh-pages + uses: JamesIves/github-pages-deploy-action@v4 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_branch: gh-pages - publish_dir: site \ No newline at end of file + branch: gh-pages + folder: site + # Keep PR previews published under pr-preview/ + clean: true + clean-exclude: pr-preview/ + force: false diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml new file mode 100644 index 000000000..58eee5161 --- /dev/null +++ b/.github/workflows/link-check.yml @@ -0,0 +1,42 @@ +name: Broken Link Check + +on: + pull_request: + schedule: + - cron: '0 5 * * 1' # Weekly, Mondays at 05:00 UTC + workflow_dispatch: + +permissions: + contents: read + +jobs: + link-check: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Run lychee + uses: lycheeverse/lychee-action@v2 + with: + args: >- + --include-fragments + --no-progress + --verbose + --timeout 20 + --max-redirects 10 + --max-retries 3 + --retry-wait-time 2 + --format markdown + --exclude https://doi.org/* + . + fail: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload lychee report + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: lychee-report + path: lychee diff --git a/.gitignore b/.gitignore index 6878673ac..294e0696f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ transpose .doxygen.stamp .mkdocs.stamp site/* +site_preview/* # documentation docs/api diff --git a/CITATION.cff b/CITATION.cff index 7f2962c83..6d07b6878 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,17 +1,20 @@ cff-version: 1.2.0 title: "SIPNET: Simple Photosynthesis and Evapotranspiration Model" -version: 2.0..0 +version: 2.0.0 abstract: > SIPNET (Simple Photosynthesis and Evapotranspiration Model) is a process-based ecosystem model designed to simulate carbon and water fluxes and (optionally) agricultural management events in terrestrial ecosystems. - SIPNET is used for research in ecosystem ecology, carbon and greenhouse gas accounting, + SIPNET is used for research in ecosystem ecology, agriculture, carbon and greenhouse gas accounting, data assimilation, and model benchmarking. Model development and recent features are detailed in the CHANGELOG and relevant literature. license: BSD-3-Clause message: "Please cite SIPNET using the metadata from 'preferred-citation'." type: software authors: + - family-names: Michael + given-names: Longfritz + orcid: "https://orcid.org/0009-0009-6024-8116" - family-names: Sacks given-names: William J. orcid: "https://orcid.org/0000-0003-2902-5263" @@ -27,9 +30,6 @@ authors: - family-names: Schimel given-names: David S. orcid: "https://orcid.org/0000-0003-3473-8065" - - family-names: LeBauer - given-names: David S. - orcid: "https://orcid.org/0000-0001-7228-053X" - family-names: Kooper given-names: Rob orcid: "https://orcid.org/0000-0002-5781-7287" @@ -39,10 +39,11 @@ authors: - family-names: Fer given-names: Istem orcid: "https://orcid.org/0000-0001-8236-303X" - - family-names: (other contributors) - given-names: See https://github.com/PecanProject/sipnet/graphs/contributors - - family-names: Michael - given-names: Longfritz + - family-names: Black + given-names: Chris + - family-names: LeBauer + given-names: David S. + orcid: "https://orcid.org/0000-0001-7228-053X" repository-code: https://github.com/PecanProject/sipnet keywords: @@ -55,9 +56,11 @@ keywords: preferred-citation: type: software title: "SIPNET: Simple Photosynthesis and Evapotranspiration Model" - version: 1.3.0 + version: 2.0.0 url: https://github.com/PecanProject/sipnet authors: + - family-names: Michael + given-names: Longfritz - family-names: Sacks given-names: William J. - family-names: Moore @@ -68,18 +71,14 @@ preferred-citation: given-names: Bobby H. - family-names: Schimel given-names: David S. - - family-names: LeBauer - given-names: David S. - family-names: Kooper given-names: Rob - family-names: Dietze given-names: Michael C. - family-names: Fer given-names: Istem - - family-names: Michael - given-names: Longfritz - - family-names: (other contributors) - given-names: See https://github.com/PecanProject/sipnet/graphs/contributors + - family-names: LeBauer + given-names: David S. references: - type: article diff --git a/Makefile b/Makefile index 84fdb08a3..9571ca412 100755 --- a/Makefile +++ b/Makefile @@ -85,7 +85,8 @@ SIPNET_TEST_DIRS:=$(shell find tests/sipnet -type d -mindepth 1 -maxdepth 1) SIPNET_TEST_DIRS_RUN:= $(addsuffix .run, $(SIPNET_TEST_DIRS)) SIPNET_TEST_DIRS_CLEAN:= $(addsuffix .clean, $(SIPNET_TEST_DIRS)) -test: $(SIPNET_TEST_DIRS) $(COMMON_LIB) $(SIPNET_LIB) +# Build unit test executables (does not run them) +testbuild: $(SIPNET_TEST_DIRS) $(COMMON_LIB) $(SIPNET_LIB) # The dash in the build command tells make to continue if there are errors, allowing cleanup $(SIPNET_TEST_DIRS): $(SIPNET_LIB) $(COMMON_LIB) @@ -105,7 +106,7 @@ $(SIPNET_TEST_DIRS_CLEAN): cleanall: clean testclean .PHONY: all clean help document exec cleanall \ - test $(SIPNET_TEST_DIRS) $(SIPNET_TEST_DIRS_RUN) testclean $(SIPNET_TEST_DIRS_CLEAN) testrun + test testbuild $(SIPNET_TEST_DIRS) $(SIPNET_TEST_DIRS_RUN) testclean $(SIPNET_TEST_DIRS_CLEAN) testrun smoke unit help: @echo "Available make targets:" @@ -118,11 +119,24 @@ help: @echo " clean - Remove compiled files, executables, and documentation" @echo " depend - Generate build dependency information for source files and append to Makefile" @echo " === Tests ===" - @echo " test - Build the unit tests" - @echo " testrun - Run the unit tests" + @echo " test - Build + run unit tests, then smoke tests" + @echo " testbuild - Build the unit tests (does not run)" + @echo " testrun - Run the unit tests (per-directory runners)" + @echo " unit - Build and run unit tests (wrapper with summary)" + @echo " smoke - Run smoke tests (end-to-end diffs)" @echo " testclean - Clean build artifacts and executables from the unit tests" @echo " cleanall - Run both clean and testclean" +# Convenience test targets +unit: sipnet testbuild + ./tools/run_unit_tests.sh + +smoke: sipnet + ./tests/smoke/run_smoke.sh + +# Combined verification target +test: sipnet testbuild + ./tools/run_unit_tests.sh && ./tests/smoke/run_smoke.sh # Make sure this target and subsequent comment remain at the bottom of this file depend:: diff --git a/docs/.alternate-model-structure-ideas.md b/docs/.alternate-model-structure-ideas.md index f165f43b0..68c01c8f1 100644 --- a/docs/.alternate-model-structure-ideas.md +++ b/docs/.alternate-model-structure-ideas.md @@ -17,7 +17,16 @@ $$ Heterotrophic respiration ($R_h$) is a function of soil carbon content ($C_S$), a scaling factor ($K_h$), the $Q_{10}$ factor, and the ratio of soil moisture ($W$) to water holding capacity ($W_e$). -## Flooding +## Irrigation + +### Moisture based irrigation + +Currently, irrigation is specified as an amount. + +Additional approach based on farmer behavior: +To represent an irrigation program that sets a moisture range and turns irrigation on at a lower moisture threshold and of at an upper threshold. This could be done internally by directly changing the soil moisture content - while keeping track of the amount of water added to report in events.out. + +### Flooding There are multiple options for representing flooding. @@ -42,17 +51,17 @@ K could be a fixed value like 2 or 10. Or a site level parameter if warranted (e ### Variables (Pools, Fluxes, and Parameters) -| Symbol | Description | -|----------------|-----------------------------------------------------------------------------| -| $$ R_{\text{leach,NO3}} $$ | Rate of nitrate leaching | -| $$ R_{\text{NH4,fert}} $$ | Rate of ammonium fertilization input | -| $$ R_{\text{NO3,fert}} $$ | Rate of nitrate fertilization input +| Symbol | Description | +| -------------------------- | ------------------------------------ | +| $$ R_{\text{leach,NO3}} $$ | Rate of nitrate leaching | +| $$ R_{\text{NH4,fert}} $$ | Rate of ammonium fertilization input | +| $$ R_{\text{NO3,fert}} $$ | Rate of nitrate fertilization input | ### Subscripts (Temporal, Spatial, or Contextual Identifiers) -| Subscript | Description | -|--------------- |----------------------------------------------------------------------------- | -| $$ _\text{nitr} $$ | nitrification | -| $$ _\text{denitr} $$ | denitrification +| Subscript | Description | +| -------------------- | --------------- | +| $$ _\text{nitr} $$ | nitrification | +| $$ _\text{denitr} $$ | denitrification | ### Soil Ammonium @@ -363,15 +372,15 @@ Some options considered: - Adapt the parabolic $D_\text{temp, A}$ from SIPNET (Braswell et al 2005) -| Function | Formula | Parameters | Notes | -|-------------------------------------|-----------------------------------------------------------------------------------------|-------------------------------------|------------------------------------------------------------------------------------------------| -| **Beta Function** | $\left( \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)^\beta \cdot \left( 1 - \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)^\gamma$ | 4 ($\beta, \gamma, f_{\text{WHC,min}}, f_{\text{WHC,max}}$) | Scaled to [0, 1] within $f_{\text{WHC,min}}$ and $f_{\text{WHC,max}}$. Peak shape controlled by $\beta$ and $\gamma$. | -| **Gaussian Function** | $e^{-\frac{\left( \frac{f_{\text{WHC}} - \mu}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)^2}{2\sigma^2}}$ | 3 ($\mu, \sigma, f_{\text{WHC,max}}, f_{\text{WHC,min}}$) | Fixed maximum of 1. Centered at $\mu$, scaled to $f_{\text{WHC,min}}$ and $f_{\text{WHC,max}}$. Symmetric around $\mu$. | -| **Piecewise Linear Function** | $\begin{cases} m_1 \left( \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right) + c_1, & f_{\text{WHC}} \leq f_{\text{WHC,mid}} \\ m_2 \left( \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right) + c_2, & f_{\text{WHC}} > f_{\text{WHC,mid}} \end{cases}$ | 6 ($m_1, c_1, m_2, c_2, f_{\text{WHC,min}}, f_{\text{WHC,max}}$) | Two linear segments split at $f_{\text{WHC,mid}}$. Scaled to $f_{\text{WHC,min}}$ and $f_{\text{WHC,max}}$. | -| **Double Exponential Function** | $a_1 e^{b_1 \left( \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)} + a_2 e^{b_2 \left( \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)}$ | 4 ($a_1, b_1, a_2, b_2$) | Scaled exponential components to $f_{\text{WHC,min}}$ and $f_{\text{WHC,max}}$. | -| **Piecewise Double Exponential** | $\begin{cases} (1 - e^{-k_1(f_{\text{WHC}} - f_{\text{WHC,min}})}), & f_{\text{WHC}} \leq f_{\text{WHC,opt}} \\ e^{-k_2(f_{\text{WHC}} - f_{\text{WHC,opt}})}, & f_{\text{WHC}} > f_{\text{WHC,opt}} \end{cases}$ | 4 ($k_1, k_2, f_{\text{WHC,opt}}, f_{\text{WHC,min}}$) | Piecewise rise (left of $f_{\text{WHC,opt}}$) and asymmetric exponential decay (right). Scaled to range. | -| **Double Logistic Function** | $\frac{1}{1 + e^{-k_1 \left( \frac{f_{\text{WHC}} - x_1}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)}} + \frac{1}{1 + e^{-k_2 \left( \frac{f_{\text{WHC}} - x_2}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)}}$ | 6 ($k_1, x_1, k_2, x_2, f_{\text{WHC,min}}, f_{\text{WHC,max}}$) | Scaled logistic transitions split at $x_1$ and $x_2$. Scaled to $f_{\text{WHC,min}}$ and $f_{\text{WHC,max}}$. | -| **Parabolic (SIPNET)** | $a \left( \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} - b \right)^2 + c$ | 5 ($a, b, c, f_{\text{WHC,min}}, f_{\text{WHC,max}}$) | Scaled to $f_{\text{WHC,min}}$ and $f_{\text{WHC,max}}$. Derived from SIPNET temperature-response function. | +| Function | Formula | Parameters | Notes | +| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| **Beta Function** | $\left( \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)^\beta \cdot \left( 1 - \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)^\gamma$ | 4 ($\beta, \gamma, f_{\text{WHC,min}}, f_{\text{WHC,max}}$) | Scaled to [0, 1] within $f_{\text{WHC,min}}$ and $f_{\text{WHC,max}}$. Peak shape controlled by $\beta$ and $\gamma$. | +| **Gaussian Function** | $e^{-\frac{\left( \frac{f_{\text{WHC}} - \mu}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)^2}{2\sigma^2}}$ | 3 ($\mu, \sigma, f_{\text{WHC,max}}, f_{\text{WHC,min}}$) | Fixed maximum of 1. Centered at $\mu$, scaled to $f_{\text{WHC,min}}$ and $f_{\text{WHC,max}}$. Symmetric around $\mu$. | +| **Piecewise Linear Function** | $\begin{cases} m_1 \left( \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right) + c_1, & f_{\text{WHC}} \leq f_{\text{WHC,mid}} \\ m_2 \left( \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right) + c_2, & f_{\text{WHC}} > f_{\text{WHC,mid}} \end{cases}$ | 6 ($m_1, c_1, m_2, c_2, f_{\text{WHC,min}}, f_{\text{WHC,max}}$) | Two linear segments split at $f_{\text{WHC,mid}}$. Scaled to $f_{\text{WHC,min}}$ and $f_{\text{WHC,max}}$. | +| **Double Exponential Function** | $a_1 e^{b_1 \left( \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)} + a_2 e^{b_2 \left( \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)}$ | 4 ($a_1, b_1, a_2, b_2$) | Scaled exponential components to $f_{\text{WHC,min}}$ and $f_{\text{WHC,max}}$. | +| **Piecewise Double Exponential** | $\begin{cases} (1 - e^{-k_1(f_{\text{WHC}} - f_{\text{WHC,min}})}), & f_{\text{WHC}} \leq f_{\text{WHC,opt}} \\ e^{-k_2(f_{\text{WHC}} - f_{\text{WHC,opt}})}, & f_{\text{WHC}} > f_{\text{WHC,opt}} \end{cases}$ | 4 ($k_1, k_2, f_{\text{WHC,opt}}, f_{\text{WHC,min}}$) | Piecewise rise (left of $f_{\text{WHC,opt}}$) and asymmetric exponential decay (right). Scaled to range. | +| **Double Logistic Function** | $\frac{1}{1 + e^{-k_1 \left( \frac{f_{\text{WHC}} - x_1}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)}} + \frac{1}{1 + e^{-k_2 \left( \frac{f_{\text{WHC}} - x_2}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} \right)}}$ | 6 ($k_1, x_1, k_2, x_2, f_{\text{WHC,min}}, f_{\text{WHC,max}}$) | Scaled logistic transitions split at $x_1$ and $x_2$. Scaled to $f_{\text{WHC,min}}$ and $f_{\text{WHC,max}}$. | +| **Parabolic (SIPNET)** | $a \left( \frac{f_{\text{WHC}} - f_{\text{WHC,min}}}{f_{\text{WHC,max}} - f_{\text{WHC,min}}} - b \right)^2 + c$ | 5 ($a, b, c, f_{\text{WHC,min}}, f_{\text{WHC,max}}$) | Scaled to $f_{\text{WHC,min}}$ and $f_{\text{WHC,max}}$. Derived from SIPNET temperature-response function. | ## Effect of SOM on Soil Water Dynamics diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 549e4b3d1..f29d0941c 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,43 +1,41 @@ # Contributing to SIPNET -We welcome contributions to SIPNET. This document outlines the process for contributing to the SIPNET project. +We welcome contributions. This page is the one‑stop guide for developers. -All contributors are expected to adhere to the PEcAn Project [Code of Conduct](https://github.com/PecanProject/pecan/blob/develop/CODE_OF_CONDUCT.md). +All contributors must follow the project [Code of Conduct](CODE_OF_CONDUCT.md). -## Setup - -1. Clone this repository: +## Developer Quickstart +- Clone the repository: ```bash git clone git@github.com:PecanProject/sipnet + cd sipnet ``` - -2. Run the setup script (once per clone): - - ```bash - tools/setup.sh - ``` - -The `tools/setup.sh` script verifies that Python ≥ 3.8 is available, -ensures that `clang-format`, `clang-tidy`, and `git clang-format` are -installed (it automatically installs them on macOS and prints installation -instructions for Ubuntu/Debian), and then copies the clang pre‑commit hook into -`.git/hooks`. - -The pre-commit hook will check formatting on every commit; if issues are found it -aborts the commit so you can run `git clang-format` and re‑stage the -changes. - -*Note – running the script is unnecessary for documentation‑only edits, -but it will save you time whenever you touch C/C++ code.* - +- Setup (once per clone, described below): + ```bash + tools/setup.sh + ``` +- Build: + ```bash + make + ``` +- Run a sample: + ```bash + cd tests/smoke/niwot + ../../../sipnet -i sipnet.in + ``` + +**Setup Script** The `tools/setup.sh` script verifies that Python ≥ 3.8 is available and that `clang-format`, `clang-tidy`, and `git clang-format` are installed. Automatically installs `clang` tools on macOS and prints installation instructions for Ubuntu/Debian. Then copies the clang pre‑commit hook into `.git/hooks` so that code formatting is checked on every commit. + +_Note – running `tools/setup.sh` is not necessary for documentation‑only edits, +but it will save you time whenever you touch C/C++ code._ ## GitHub Workflow -#### Branches +### Branches The `master` branch is the default branch for SIPNET. Development should be done in feature branches. Feature branches should be named to clearly indicate the purpose, and may be combined with an associated issue, e.g. `ISSUE#-feature-name`. -#### Pull Requests +### Pull Requests Pull requests should be made from feature branches to the `master` branch. @@ -56,14 +54,15 @@ Pull requests must pass all required checks to be merged into master, including ## Code Format & Style -We follow the standard LLVM/Clang formatting rules. Formatting is automatic, and you rarely have to think about them: +We follow the standard LLVM/Clang formatting rules. Formatting is automated with a pre-commit hook, so you wil rarely have to think about them. + +To set up formatting and static analysis checks: 1. **Run the setup script once** (see *Setup* above). It installs a **pre‑commit hook** that blocks any commit whose C/C++ files are not already formatted. 2. **If the hook stops your commit**, run: - ```bash # format only what you just staged git clang-format @@ -84,86 +83,90 @@ If `git clang-format` fails because not all changes are staged (likely a `git co The hook and CI will tell you what to fix. -## Documentation - -What goes in **Doxygen**: -- Documentation for functions, classes, and parameters. - -What goes in **docs/*md**: -- User guides and tutorials. -- Documentation of equations, theoretical basis, and parameters. +If a commit is blocked, format staged changes: + ```bash + git clang-format + git add -u && git commit + ``` + To reformat all modified files: `git clang-format -f`. +- Optional local static analysis: + ```bash + clang-tidy path/to/file.c + ``` -### Building the Documentation with `mkdocs` -Documentation is located at https://pecanproject.github.io/sipnet/, and can be rebuilt using `mkdocs`. A brief summary -of use is listed here, or see the Getting Started page for `mkdocs` [here](https://www.mkdocs.org/getting-started/) for -more information. - -Issue the following command to install `mkdocs` and the third-party extensions usedin SIPNET: -``` -pip install mkdocs mkdocs-material pymdown-extensions -``` -The `material` theme can be found [here](https://github.com/squidfunk/mkdocs-material). - -MkDocs comes with a built-in dev-server that lets you preview your documentation as you work on it. Make sure you're -in the same directory as the mkdocs.yml configuration file, and then start the server by running the mkdocs serve -command: - -``` -$ mkdocs serve -INFO - Building documentation... -INFO - Cleaning site directory -INFO - Documentation built in 0.22 seconds -INFO - [15:50:43] Watching paths for changes: 'docs', 'mkdocs.yml' -INFO - [15:50:43] Serving on http://127.0.0.1:8000/ -``` -Open up http://127.0.0.1:8000/ in your browser, and you'll see the documentation home page. - -The dev-server also supports auto-reloading, and will rebuild your documentation whenever anything in the configuration -file, documentation directory, or theme directory changes. - -If the structure of the documentation has changed (e.g., adding, moving, removing, or renaming pages), update `mkdocs.yml` in the root -directory to reflect these changes and issue this command to rebuild: +## Documentation -``` -mkdocs build +- Build API docs (Doxygen) and site (MkDocs): + ```bash + make document + ``` +- Live preview while editing docs: + ```bash + pip install mkdocs mkdocs-material pymdown-extensions + mkdocs serve + # open http://127.0.0.1:8000/ + ``` + Update `mkdocs.yml` if you add/move pages. + +- Build API docs (Doxygen) and site (MkDocs): + ```bash + make document + ``` + Outputs: `docs/api/` and `site/`. +- Live preview while editing docs: + ```bash + pip install mkdocs mkdocs-material pymdown-extensions + mkdocs serve + # open http://127.0.0.1:8000/ + ``` + Update `mkdocs.yml` if you add/move pages. + +## Build + +```bash +make ``` -## Compiling SIPNET binaries +- See the project [README.md](README.md) for quick start. Use `make help` for all targets. -SIPNET uses `make` to build the model and documentation. There are also miscellaneous targets for running analysis workflows: -```sh -# build SIPNET executable -make sipnet -# build documentation -make document -# clean up build artifacts -make clean -# list all make commands -make help -``` ## Testing -Any new features (that are worth keeping!) should be covered by tests. - -SIPNET also uses `make` to build and run its unit tests. This can be done with the following commands: -```shell -# Compile tests -make test -# Run tests -make testrun -# Clean after tests are run -make testclean +New features require tests. + +- Unit tests: + ```bash + make unit + # or: make testbuild && ./tools/run_unit_tests.sh + ``` +- Smoke tests: + ```bash + make smoke + ``` +- Full check (unit + smoke): + ```bash + make test + ``` + +- Clean up unit test artifacts: + ```bash + make testclean + ``` + +Per‑suite runners live under `tests/sipnet/*`: +```bash +make -C tests/sipnet/test_events_infrastructure run ``` ## Releases -- Use [Semantic Versioning v2](https://semver.org/) for SIPNET releases. - - Tag releases with the version number `vX.Y.Z`. - - Update version in the following files: - - `CITATION.cff` - - `src/sipnet/version.h` - - `docs/CHANGELOG.md` - - Update `PROJECT_NUMBER` in `docs/Doxyfile` -- Include content from `docs/CHANGELOG.md` file in release description. +- Use [Semantic Versioning v2](https://semver.org/) for SIPNET releases. +- Tag the git commit associated with the release `vX.Y.Z`. +- Update versions in: + - `CITATION.cff` + - `src/sipnet/version.h` + - `docs/CHANGELOG.md` + - `docs/Doxyfile` (`PROJECT_NUMBER`) +- Run tests (`make test`). +- Publish the GitHub release; include `docs/CHANGELOG.md` content in release notes. diff --git a/docs/README.md b/docs/README.md index ef4b39b3e..09dc28f68 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,10 +1,18 @@ # SIPNET -SIPNET (Simplified Photosynthesis and Evapotranspiration Model) is an ecosystem model designed to efficiently simulate -carbon and water dynamics. Originally developed for assimilation of eddy covariance flux data in forest ecosystems, -current development is focused on representing carbon balance and GHG fluxes and agricultural management practices. +SIPNET (Simplified Photosynthesis and Evapotranspiration Model) is an ecosystem model designed to efficiently simulate carbon and water dynamics. Originally developed for assimilation of eddy covariance flux data in forest ecosystems, current development is focused on representing carbon balance and GHG fluxes and agricultural management practices. -## Quick Start +[](https://doi.org/10.5281/zenodo.17148669) + + +## Getting Started + +### Requirements + +- `gcc` or `clang` +- `make` + +### Quick Start 1. Clone the repository: ```bash @@ -25,66 +33,44 @@ current development is focused on representing carbon balance and GHG fluxes and cat niwot.out ``` -## Getting Started - -### Installing - -See the quickstart above. - -### Executing the Program - -[Link to cli docs, when they get written] - -[Link to description of all the input files] - -### Running Tests - -#### Smoke Tests - -From the root sipnet directory, run: - -```bash -./tests/smoke/run_smoke.sh -``` - -The end of the output from that script should be: - -```shell -======================= -SUMMARY: -Skipped directories: 0 -SIPNET OUTPUT: -Passed: 5/5 -Failed: 0 -EVENT OUTPUT: -Passed: 5/5 -Failed: 0 -CONFIG OUTPUT: -Passed: 5/5 -Failed: 0 -======================= -``` - -#### Unit Tests - -Build and run the unit tests with the script: - -```bash -./tools/run_unit_tests.sh +### Run Tests + +- Unit tests: + ```bash + make unit + # or: make testbuild && ./tools/run_unit_tests.sh + ``` +- Smoke tests: + ```bash + make smoke + # or: ./tests/smoke/run_smoke.sh + ``` +- Full tests (build + unit + smoke): + ```bash + make test + ``` +- Clean up test artifacts: + ```bash + make testclean ``` -The summary block at the end should show PASSED for each test. - ## Documentation -Documentation for SIPNET is published at [pecanproject.github.io/sipnet](https://pecanproject.github.io/sipnet/), which is built using `mkdocs`. See -the [Documentation section](CONTRIBUTING.md#documentation) in the CONTRIBUTING page for more information +Documentation for SIPNET is published at [pecanproject.github.io/sipnet](https://pecanproject.github.io/sipnet/), which is built using `mkdocs`. +See the [Documentation section](CONTRIBUTING.md#documentation) in the CONTRIBUTING page for more information about how to write and compile the documentation. +Key pages: + +- [Model inputs](user-guide/model-inputs.md): Input files and configuration. +- [Model outputs](user-guide/model-outputs.md): Output files and structure. +- [Parameters](parameters.md): Model parameters and settings. +- [Model structure](developer-guide/model-structure.md): Overview of code organization. + ## Contributing -See the main [Contributing](CONTRIBUTING.md) page +See the main [Contributing](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md) pages for details on how to contribute to SIPNET. ## License -Distributed under the BSD 3-Clause license. See [LICENSE](https://github.com/PecanProject/sipnet/blob/master/LICENSE) for more information. +Distributed under the BSD 3-Clause license. See [LICENSE](../LICENSE) for more information. diff --git a/docs/developer-guide/code-structure.md b/docs/developer-guide/code-structure.md index e69de29bb..9e2c37118 100644 --- a/docs/developer-guide/code-structure.md +++ b/docs/developer-guide/code-structure.md @@ -0,0 +1,87 @@ +# Code Structure + +This guide documents how state is advanced each timestep and the conventions that keep flux calculations pure and pool updates centralized. + +## Timestep Phases (in `updateState()`) + +1) Initialize fluxes +- Zero all `fluxes.*` and `fluxes.event*`. + +2) Compute fluxes (pure calculations) +- `calculateFluxes()` computes photosynthesis, respiration, water/snow, etc. +- `processEvents()` converts scheduled/instant events to `fluxes.event*` deltas (no pool mutation). +- `soilDegradation()` and other biogeochemical modules compute additional flux rates only (no pool mutation). +- No function in this phase mutates `envi.*` or `trackers.*`. + +3) Apply pool updates (single place) +- `applyPoolUpdates()` is the only code that changes `envi.*`. +- For each pool P: ΔP = (sum of rate fluxes to P) * climate.length + (sum of `fluxes.event*` deltas to P). +- Apply bounds, conservation, and cross-pool constraints here. + +4) Trackers and running means +- `updateTrackers()` uses timestep-integrated values (rate * climate.length) plus event deltas. + +5) Output +- `outputState()` and any optional diagnostics/logging. + +### Pseudocode outline + +- updateState(): + - zeroFluxes() + - calculateFluxes() // pure rates + - processEvents() // sets fluxes.event* deltas only + - soilDegradation() // pure rates + - applyPoolUpdates() // the only place that mutates envi.* + - updateTrackers() + - outputState() + +## Mutability Rules (must-follow) + +- Only `applyPoolUpdates()` may change `envi.*`. +- Flux calculators: + - May read `envi.*`, `params.*`, `ctx.*`, `climate.*`. + - May write `fluxes.*` (rates) and `fluxes.event*` (event deltas). + - Must not mutate `envi.*`, `trackers.*`, or perform I/O as logic side-effects. +- Events never change pools directly; they only add to `fluxes.event*`. + +## Units and Integration + +- Rate fluxes in `fluxes.*` are per-day rates (pool units per day). +- Event deltas in `fluxes.event*` are direct pool deltas (same units as pools), not rates. +- Integration per pool each timestep: + - Δpool_from_rates = (sum of relevant `fluxes.*`) * climate.length + - Δpool_from_events = (sum of relevant `fluxes.event*`) + - pool += Δpool_from_rates + Δpool_from_events + +## Naming Conventions + +- envi.* State variables (pools, water, snow, canopy, soil layers). +- fluxes.* Per-day flux rates computed in the flux phase. +- fluxes.event* Instantaneous/event deltas to be applied during pool update. +- trackers.* Integrated timestep values, cumulative sums, yearly aggregates. +- params.* Fixed run parameters (immutable during a run). +- ctx.* Feature flags / configuration switches. +- climate.* Forcing for the current timestep (e.g., length, met drivers). +- diag.* Optional transient diagnostics (no side effects on state). + +Name fluxes by direction and target, e.g., `fluxes.NPP`, `fluxes.soilRespiration`, `fluxes.leafLitterToSoil`, `fluxes.eventHarvestC`. Prefer “to/from” clarity for transfers. + +## Pool Update Responsibilities + +- Apply all additions/removals in a consistent order if constraints require it (e.g., water first if it bounds biochemical rates next step). +- Enforce invariants: + - No negative pools; clamp with tracked deficits and warnings if needed. + - Mass conservation across linked pools (e.g., C/N stoichiometry) with balanced cross-pool transfers. +- Centralize any event-specific application here (e.g., harvest removing biomass, adding residues). + +## Adding a New Flux or Event + +- Rates: add a `fluxes.*` variable, compute it in a flux function, and integrate it in `applyPoolUpdates()`. +- Events: add a `fluxes.event*` delta, accumulate in `processEvents()`, apply it in `applyPoolUpdates()`. +- Do not mutate `envi.*` in calculators or event processors. + +## Logging & Errors + +- Use `logError()` and `logWarning()` (not printf) so tests can capture output. +- Messages should include timestep context: year, day, event type (if relevant), and the offending value(s). +- Emit warnings on clamping, conservation corrections, or unexpected negative fluxes. diff --git a/docs/developer-guide/logging.md b/docs/developer-guide/logging.md index 7dbf94f59..4be19fbf9 100644 --- a/docs/developer-guide/logging.md +++ b/docs/developer-guide/logging.md @@ -1,32 +1,46 @@ # Logging -SIPNET's logger is defined in `common/logger.h` and implemented in `common/logger.c`. +SIPNET's logger is a small wrapper around `printf` that adds standard prefixes and, for internal errors, the source file and line number. It is defined in `common/logging.h` and implemented in `common/logging.c`. -It provides a simple interface for logging messages at different levels (e.g., debug, info, warning, error). +## Levels -The use of logger functions is preferred over `printf` because ... -It is appropriate to use printf when ... - -## Logging Levels - -- **logDebug**: Information useful during development or debugging. -- **logInfo**: General information about the program's execution, such as successful initialization or key milestones. -- **logWarning**: Non-critical issues that might require attention but do not stop execution. Example: deprecated parameters or ignored input. -- **logError**: Critical issues that prevent the program from continuing correctly. Example: missing required parameters or internal errors. +- 0: Quiet-able (suppressed by `--quiet`). +- 1: Always on (not suppressed). +- 2: Always on and includes `file:line`. ## Usage -1. Include the logger header in your source file: +- logInfo: Level 0; routine progress, configuration summaries, expected state changes. +- logWarning: Level 0; Recoverable issues or surprises; fallbacks, deprecated/ignored inputs. +- logTest: Level 1; Deterøinistic messages for tests/CI; not user-facing. +- logError: Level 1; Non-recoverable problems preventing correct operation; abort/exit or skip major task. +- logInternalError: Level 2; Errors that should never happen; include details and ask to report. + +1. Include the header: ```c - #include "common/logger.h" + #include "common/logging.h" ``` - -2. Use the logging functions to log messages at different levels: +2. Log messages: ```c - // Log messages at different levels - logDebug("This is a debug message"); - logInfo("This is an info message"); - logWarning("This is a warning message"); - logError("This is an error message"); + logInfo("Initialized OK\n"); + logWarning("Deprecated parameter: %s\n", name); + logTest("Iteration %d\n", i); + logError("Missing required parameter: %s\n", key); + logInternalError("Unexpected state: %d\n", code); ``` +3. Example outputs: + ``` + [INFO ] Initialized OK + [WARNING] Deprecated parameter: foo + [TEST ] Iteration 12 + [ERROR ] Missing required parameter: bar + [ERROR (INTERNAL)] (myfile.c:123) Unexpected state: 5 + ``` + + +## Notes: + +- Each log prints a fixed prefix (e.g., `[INFO ]`, `[WARNING]`, `[ERROR ]`). +- Messages use `printf`-style formatting. Include `\n` yourself if you want a newline. +- Level 2 (`logInternalError`) prints file:line; levels 0–1 print just the prefix. diff --git a/docs/developer-guide/testing.md b/docs/developer-guide/testing.md new file mode 100644 index 000000000..6a5d13a13 --- /dev/null +++ b/docs/developer-guide/testing.md @@ -0,0 +1,85 @@ +# Testing + +## Summary + +**What to Test** + +- Core functionality of SIPNET, including parsing, event handling, and input processing. +- System behavior under various scenarios, including edge cases. + + +**Types of tests:** + +- Unit tests in `tests/sipnet/*`: C tests for parsers, events, inputs, and infrastructure. +- Smoke tests in `tests/smoke/*`: short end‑to‑end runs that validate outputs against committed references. + + +## Unit Tests + +- Build all unit tests only: + ```bash + make testbuild + ``` +- Run all unit tests (wrapper with summary): + ```bash + make unit + # or: ./tools/run_unit_tests.sh + # or: make testrun (per-directory runners) + ``` + +What to expect: + +- List of tests that have been run, with their individual results and exit code. +- A summary table of each test with its status (PASS/FAIL). + +Run a single suite or test: + +- By directory (common during development): + ```bash + make -C tests/sipnet/test_events_infrastructure + make -C tests/sipnet/test_events_infrastructure run + ``` +- Directly (after building): + ```bash + cd tests/sipnet/test_events_types + ./testEventIrrigation + ``` + +Add a unit test: +- Add a `*.c` test file in the appropriate `tests/sipnet//` folder and list it in that folder’s `Makefile` (variable `TEST_CFILES`). The shared helpers live in `tests/utils/`. + +## Smoke Tests + +Run all smoke tests: +```bash +make smoke +# or: ./tests/smoke/run_smoke.sh +``` + +What it does: + +- Runs SIPNET in each directory under `tests/smoke/`. (`cd tests/smoke/