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 +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.17148669.svg)](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/ && ../../../sipnet -i sipnet.in`). +- Compares generated `sipnet.out`, `events.out`, and `sipnet.config` to the committed versions using `git diff`. +- Prints a summary of passes/fails and a skip count. + +Updating references intentionally: + +- If changes are expected and outputs are correct, review diffs and commit the updated `*.out` files (and any config changes). The script’s final message explains this workflow. + +Notes: + +- Some scenarios are temporarily skipped via a `skip` marker file. +- `events.out` is a debug artifact listing applied event deltas per timestep; authoritative model state is in `sipnet.out`. + +## Logging in Tests + +Use `logTest()`, `logError()`, and friends from `src/common/logging.h` for deterministic output captured by tests. Avoid `printf` in new code. + +## CI and Docs + +- The smoke tests are designed to be fast and run in CI. +- Run everything locally as CI would: + ```bash + make test + ``` +- API docs and site: `make document` builds Doxygen under `docs/api/` and the MkDocs site under `site/`. diff --git a/docs/model-structure.md b/docs/model-structure.md index b5d89ec5f..75a44ab1f 100644 --- a/docs/model-structure.md +++ b/docs/model-structure.md @@ -27,6 +27,12 @@ Goal: simplified biogeochemical model that is capable of simulating GHG balance, Start as simple as possible, add complexity as needed. When new features are considered, they should be evaluated alongside other possible model improvements that have been considered, and the overall list of project needs. +Model state is updated in the following order: + +1. Calculate fluxes — compute the model's native fluxes +2. Process events — convert events to per‑day fluxes and accumulate into fluxes. +3. Update pools — pools are updated from the accumulated fluxes and pool‑specific updates. + ### Notes on notation: Fluxes are denoted by $F$, except that respiration is denoted by $R$ following convention and previous descriptions of SIPNET. @@ -384,7 +390,7 @@ Because we expect $N_2O$ emissions will be dominated by fertilizer N inputs, we A new fixed parameter $K_\text{vol}$ will represent the proportion of $N_\text{min}$ that is volatilized as $N_2O$. $$ -F^N_\mathrm{N_2O vol} = K_\text{vol} \cdot N_\text{min} \cdot D_{\text{temp}} \cdot D_{\text{water}R_H} \tag{17}\label{eq:n2o_vol} +F^N_\mathrm{vol} = K_\text{vol} \cdot N_\text{min} \cdot D_{\text{temp}} \cdot D_{\text{water}R_H} \tag{17}\label{eq:n_vol} $$ ### $\frak{Nitrogen \ Leaching \ F^N_\text{leach}}$ @@ -448,32 +454,83 @@ If this scheme is too simple, we can adjust either the conditions under which N ## Water Dynamics - ### Soil Water Storage $$ \begin{aligned} -\frac{dW_{\text{soil}}}{dt} &= f_{\text{intercept}} \cdot \Bigl( F^W_{\text{precip}} + F^W_{\text{canopy irrigation}} \Bigr)\\[1mm] -&\quad + \mathfrak{F^W_{\text{soil irrigation}}} - F^W_{\text{drainage}} - F^W_{\text{transpiration}} +\frac{dW_{\text{soil}}}{dt} &= + (1 - f_{\text{intercept}})\,F^W_{\text{precip}} + + F^W_{\text{irrig,soil}} + - F^W_{\text{drainage}} + - F^W_{\text{trans}} \end{aligned} \tag{Braswell A4}\label{eq:A4} $$ -The change in soil water content $(W_{\text{soil}})$ is determined by precipitation $F^W_{\text{precip}}$ and losses due to drainage $F^W_{\text{drainage}}$ and transpiration $F^W_{\text{transpiration}}$. +The term $(1-f_{\text{intercept}})F^W_{\text{precip}}$ is the portion of gross precipitation that reaches the soil (i.e. infiltration from precipitation). Intercepted water (fraction $f_{\text{intercept}}$ of precipitation or canopy‑applied irrigation) is assumed to evaporate the same day and therefore never enters $W_{\text{soil}}$ and does not appear in \eqref{eq:A4}. $F^W_{\text{trans}}$ here is identical to $F^W_{\text{transpiration}}$ used elsewhere. -$F^W_{\text{precip}}$ is the precipitation rate prescribed at each time step in the `.clim` file and fraction of precipitation intercepted by the canopy $f_{\text{intercept}}$. +### Drainage +Under well-drained conditions, drainage occurs when soil water content $(W_{\text{soil}})$ exceeds the soil water holding capacity $(W_{\text{WHC}})$. Beyond this point, additional water drains off at a rate controlled by the drainage parameter $f_{\text{drain}}$ defined as the fraction of soil water that can be removed in one day. For well drained soils, this $f_{\text{drain}}=1$. Setting $f_{\text{drain}}<1$ reduces the rate of drainage. Flooding can be simulated by requiring a combination of a low $f_{\text{drain}}$ and sufficient $F^W_\text{irrig|precip,soil}$ to maintain flooded conditions. +$$ +F^W_{\text{drainage}} = f_\text{drain} \cdot \max(W_{\text{soil}} - W_{\text{WHC}}, 0) \tag{23}\label{eq:drainage} +$$ -### $\frak{Drainage}$ +This is adapted from the original SIPNET formulation (Braswell et al 2005), adding a new parameter that controls the drainage rate. -Under well-drained conditions, drainage occurs when soil water content $(W_{\text{soil}})$ exceeds the soil water holding capacity $(W_{\text{WHC}})$. Beyond this point, additional water drains off at a rate controlled by the drainage parameter $f_{\text{drain}}$. For well drained soils, this $f_{\text{drain}}=1$. Setting $f_{\text{drain}}<1$ reduced the rate of drainage, and flooding will will require a combination of a low $f_{\text{drain}}$ and sufficient size and / or frequency of $F^W_\text{irrigation}$ to maintain flooded conditions. +### Precipitation +We define $F^W_{\text{precip}} = P$ as gross (measured) precipitation depth. The fraction reaching the soil is: $$ -F^W_{\text{drainage}} = f_\text{drain} \cdot \max(W_{\text{soil}} - W_{\text{WHC}}, 0) \tag{23}\label{eq:drainage} +F^W_{\text{precip,soil}} = (1 - f_{\text{intercept}})\,F^W_{\text{precip}} $$ -This is adapted from the original SIPNET formulation (Braswell et al 2005), adding a new parameter that controls the drainage rate. +$F^W_{\text{precip,soil}}$ is added to soil water in equation \eqref{eq:A4}. + +### Evapotranspiration + +$$ +ET = E + T +$$ + +Evapotranspiration ($ET$) is calculated as the sum of evaporation ($E$) and transpiration ($T$), which are defined below: + +### Evaporation + +There are two components of evaporation: (1) immediate evaporation from intercepted precipitation or canopy irrigation and (2) soil surface evaporation. + +**Interception (Immediate Evaporation)** + +$$ +F^W_{\text{intercept,evap}} = f_{\text{intercept}}\,(F^W_{\text{precip}} + F^W_{\text{irrig,canopy}}) +$$ + +**Soil Evaporation** + +Soil evaporation is computed as: + +$$ +F^W_{\text{soil,evap}} = +\frac{\rho C_p}{\gamma}\frac{1}{\lambda} +\frac{\text{VPD}_\text{soil}}{r_d + r_{\text{soil}}} +$$ + +where: +$$ +r_d = \frac{\text{rdConst}}{u}, \qquad +r_{\text{soil}} = \exp\!\left(r_{\text{soil},1} - r_{\text{soil},2}\frac{W_{\text{soil}}}{W_{\text{WHC}}}\right) +$$ + +Negative (condensation) values are clipped to zero. If snow > 0 then $F^W_{\text{soil,evap}}=0$. + +#### Evaporation + +Total evaporation is calculated as the sum of intercepted water, soil evaporation, and sublimation: + +$$ +E = F^W_{\text{trans}} + F^W_{\text{intercept,evap}} + F^W_{\text{soil,evap}} + F^W_{\text{sublim}} +$$ ### Transpiration @@ -588,12 +645,12 @@ Where $\beta$ and $\gamma$ are parameters that control the shape of the curve, a For the relationship between $N_2O$ flux and soil moisture, Wang et al (2023) suggest a Gaussian function. -## $\frak{Agronomic \ Management \ Events}$ +## Agronomic Management Events All management events are specified in the `events.in`. Each event is a separate record that includes the date of the event, the type of event, and associated parameters. -### $\frak{Fertilizer}$ and Organic Matter Additions +### Fertilizer and Organic Matter Additions Additions of Mineral N, Organic N, and Organic C are added directly to their respective pools via the fluxes $F^N_{\text{fert,min}}$, $F^N_{\text{fert,org}},$ and $F^C_{\text{fert,org}}$ that are specified @@ -659,28 +716,45 @@ $$ This amount is then added to the litter flux in equation \eqref{eq:litter_flux}. -### $\frak{Irrigation}$ +### Irrigation Event parameters: * Irrigation rate $(F^W_{\text{irrigation}})$, cm/day -* Irrigation type indicator $(I_{\text{irrigation}})$: - * Canopy irrigation (0): Water applied to the canopy, simulating rainfall. +* Irrigation type indicator $(I_{\text{irrigation}}\in {0,1})$: + * Canopy irrigation (0): Water applied to the canopy. * Soil irrigation (1): Water directly added to the soil. - +The irrigation that that reaches the soil water pool is: +$$ +F^W_{\text{irrig,soil}} = +\begin{cases} +(1 - f_{\text{intercept}}) \, F^W_{\text{irrig}}, & I_{\text{irrigation}} = 0 \\ +F^W_{\text{irrig}}, & I_{\text{irrigation}} = 1 +\end{cases} +\tag{30}\label{eq:irrig_soil} +$$ + +Irrigation that is immediately evaporated: +$$ +F^W_{\text{irrig,evap}} = +\begin{cases} +f_{\text{intercept}} \, F^W_{\text{irrig}}, & I_{\text{irrigation}} = 0 \\ +0, & I_{\text{irrigation}} = 1 +\end{cases} +\tag{29}\label{eq:irrig_evap} +$$ -**Canopy irrigation** is simulated in the same way as precipitation, where a fraction of irrigation is intercepted and evaporated, and the remainder is added to the soil water pool. -**Soil irrigation** adds water directly to the soil pool without interception. Flooded furrow irrigation' is a special case of soil irrigation, with a high rate of irrigation. @@ -182,17 +182,15 @@ Run-time parameters can change from one run to the next, or when the model is st ### Soil respiration parameters -| | Symbol | Parameter Name | Definition | Units | notes | -| --- | ----------------- | ------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------------- | -| 30 | $K_\text{litter}$ | litterBreakdownRate | rate at which litter is converted to soil / respired at 0°C and max soil moisture | g C broken down \* g^-1 litter C \* day^-1 | read in as per-year rate | -| 31 | | fracLitterRespired | of the litter broken down, fraction respired (the rest is transferred to soil pool) | | | -| 32 | $K_{dec}$ | baseSoilResp | Soil respiration rate at $0 ^{\circ}\text{C}$ and moisture saturated soil | g C respired \* g$^{-1}$ soil C \* day$^{-1}$ | read in as per-year rate | -| 34 | $Q_{10s}$ | soilRespQ10 | Soil respiration Q10 | | scalar determining effect of temp on soil respiration | -| 39 | | soilRespMoistEffect | scalar determining effect of moisture on soil resp. | | | -| | | baseMicrobeResp | | | | -| | | | | | | - -| new | $f_{\textrm{till}}$ | tillageEff | Effect of tillage on decomposition that exponentially decays over time | fraction | Per‑event in `events.in`; 0 = no effect | +| | Symbol | Parameter Name | Definition | Units | notes | +| --- | ------------------- | ------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------------- | +| 30 | $K_\text{litter}$ | litterBreakdownRate | rate at which litter is converted to soil / respired at 0°C and max soil moisture | g C broken down \* g^-1 litter C \* day^-1 | read in as per-year rate | +| 31 | | fracLitterRespired | of the litter broken down, fraction respired (the rest is transferred to soil pool) | | | +| 32 | $K_{dec}$ | baseSoilResp | Soil respiration rate at $0 ^{\circ}\text{C}$ and moisture saturated soil | g C respired \* g$^{-1}$ soil C \* day$^{-1}$ | read in as per-year rate | +| 34 | $Q_{10s}$ | soilRespQ10 | Soil respiration Q10 | | scalar determining effect of temp on soil respiration | +| 39 | | soilRespMoistEffect | scalar determining effect of moisture on soil resp. | | | +| | | baseMicrobeResp | | | | +| new | $f_{\textrm{till}}$ | tillageEff | Effect of tillage on decomposition that exponentially decays over time | fraction | Per‑event in `events.in`; 0 = no effect | - $R_{dec}$: Rate of decomposition $(\text{day}^{-1})$ - $Q_{10dec}$: Temperature coefficient for $R_{dec}$ (unitless) @@ -281,99 +279,6 @@ Run-time parameters can change from one run to the next, or when the model is st --> -## Run-time Options - -Configuration settings are applied in the following order of precedence: - -1. Default values built into SIPNET -2. Values from the configuration file -3. Command-line arguments - -Thus, command-line arguments override settings in the configuration file, and configuration file settings override default values. - -### Input / Output Options - -| Option | Default | Description | -| ------------ | --------- | ------------------------------------- | -| `input-file` | sipnet.in | Name of input config file | -| `file-name` | sipnet | Prefix of climate and parameter files | - -### Output Flags - -| Option | Default | Description | -| ------------------- | ------- | -------------------------------------------------------------- | -| `do-main-output` | on | Print time series of all output variables to `.out` | -| `do-single-outputs` | off | Print outputs one variable per file (e.g. `.NEE`) | -| `dump-config` | on | Print final config to `.config` | -| `print-header` | on | Whether to print header row in output files | -| `quiet` | off | Suppress info and warning message | - -### Model Flags - -| Option | Default | Description | -| ------------- | ------- | ---------------------------------------------------------------------------------------- | -| `events` | on | Enable event handling. | -| `gdd` | on | Use growing degree days to determine leaf growth. | -| `growth-resp` | off | Explicitly model growth respiration, rather than including with maintenance respiration. | -| `leaf-water` | off | Calculate leaf pool and evaporate from that pool. | -| `litter-pool` | off | Enable litter pool in addition to single soil carbon pool. | -| `microbes` | off | Enable microbe modeling. | -| `snow` | on | Keep track of snowpack, rather than assuming all precipitation is liquid. | -| `soil-phenol` | off | Use soil temperature to determine leaf growth. | -| `water-hresp` | on | Whether soil moisture affects heterotrophic respiration. | - -Note the following restrictions on these options: - - `soil-phenol` and `gdd` may not both be turned on - -### Command Line Arguments - -Command-line arguments can be used to specify run-time options when starting SIPNET. The syntax is as follows: - -``` -sipnet [options] -``` - -Where `[options]` can include any of the run-time options listed above. -Flags use the syntax `--flag` to turn them on, or `--no-flag` to turn them off. Other options are specified by `--option value`. - -See `sipnet --help` for a full list of available command-line options. - -### Configuration File Format - -SIPNET reads a configuration file that specifies run-time options without using command-line arguments. By default, SIPNET looks for a file named `sipnet.in` in the current directory. These will be overwritten by command-line arguments if specified. - -The configuration file uses a simple key-value format, `option = value`, -with one option per line; comments follow `#`. Flags are specified as 0 for off and 1 for on. - -#### Example Configuration File - -Note that case is ignored for parameter names, as well as dashes and underscores. - -``` -# Base filename (used for derived filenames) -FILE_NAME = mysite - -# Output options -DO_MAIN_OUTPUT = 1 -DO_SINGLE_OUTPUTS = 0 -DUMP_CONFIG = 1 -PRINT_HEADER = 1 -QUIET = 0 - -# Model options -EVENTS = 1 -GDD = 1 -GROWTH_RESP = 0 -LEAF_WATER = 0 -LITTER_POOL = 0 -MICROBES = 0 -SNOW = 1 -SOIL_PHENOL = 0 -WATER_HRESP = 1 -``` - -When `DUMP_CONFIG` is on, SIPNET will output the final configuration (after applying all settings from defaults, configuration file, and command line) to a file named `.config`. - ## Hard-coded Values | Parameter | Value | Description | @@ -394,7 +299,7 @@ When `DUMP_CONFIG` is on, SIPNET will output the final configuration (after appl ### Run Settings -See [Run-time Options](#run-time-options) above. +See [Run-time Parameters](#run-time-parameters) above. Multi-site runs, sensitivity tests, and Monte Carlo runs are no longer supported. Typically these analyses are handled using the [PEcAn Framework](https://pecanproject.org/). @@ -671,27 +576,3 @@ year day type param_name=delta[,param_name=delta,...] 2024 70 irrig fluxes.immedEvap=2.50,envi.soilWater=2.50 2024 200 harv env.litter=4.25,envi.plantLeafC=-1.39,envi.plantWoodC=-1.63,envi.fineRootC=-2.52,envi.coarseRootC=-2.97 ``` - - - diff --git a/docs/user-guide/model-inputs.md b/docs/user-guide/model-inputs.md new file mode 100644 index 000000000..b80c37084 --- /dev/null +++ b/docs/user-guide/model-inputs.md @@ -0,0 +1,289 @@ +# Model Inputs + +There are three main types of input files needed to run SIPNET: +1. `sipnet.param`: Model parameter file. +2. `.clim`: Climate file, provides weather data for each time step of the simulation period. +3. `events.in`: (Optional) Agronomic events. +4. Run time options (command line arguments or config file `sipnet.in`) + +## Parameters and Initial Conditions + +Both initial conditions and parameters are specified in a file named `sipnet.param`. + +The SIPNET parameter file (`sipnet.param`) specifies model parameters and their properties for each simulation. +Each line in the file corresponds to a single parameter and contains five or six space-separated values. + +| Column | Description | +| -------------- | ------------------------------------------ | +| Parameter Name | Name of the parameter | +| Value | Value of the parameter to use in the model | + +### Example `sipnet.param` file + +Column names are not used, but are: + +``` +param_name value +``` + +The first lines in `sipnet.param` could be: + +``` +plantWoodInit 110 +laiInit 0 +litterInit 200 +soilInit 7000 +litterWFracInit 0.5 +soilWFracInit 0.6 +snowInit 1 +microbeInit 0.5 +fineRootFrac 0.2 +coarseRootFrac 0.2 +aMax 95 +aMaxFrac 0.85 +... +``` + +## Climate + +For each step of the model, the following inputs are needed. These are provided in a file named `.clim` with the following columns: + +| col | parameter | description | units | notes | +| --- | --------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | +| 1 | year | year of start of this timestep | | integer, e.g. 2010 | +| 2 | day | day of start of this timestep | Day of year | 1 = Jan 1 | +| 3 | time | time of start of this timestep | hours after midnight | e.g. noon = 12.0, midnight = 0.0, can be a fraction | +| 4 | length | length of this timestep | days | variable-length timesteps allowed, typically not used | +| 5 | tair | avg. air temp for this time step | degrees Celsius | | +| 6 | tsoil | average soil temperature for this time step | degrees Celsius | can be estimated from Tair | +| 7 | par | average photosynthetically active radiation (PAR) for this time step | $\text{Einsteins} \cdot m^{-2} \text{ground area} \cdot \text{time step}^{-1}$ | input is in Einsteins \* m^-2 ground area, summed over entire time step | +| 8 | precip | total precip. for this time step | cm | input is in mm; water equivilant - either rain or snow | +| 9 | vpd | average vapor pressure deficit | kPa | input is in Pa, can be calculated from air temperature and relative humidity. | +| 10 | vpdSoil | average vapor pressure deficit between soil and air | kPa | input is in Pa ; differs from vpd in that saturation vapor pressure is calculated using Tsoil rather than Tair | +| 11 | vPress | average vapor pressure in canopy airspace | kPa | input is in Pa | +| 12 | wspd | avg. wind speed | m/s | | + +Note: An older format for this file included location as the first column and soilWetness as the last column. Files with this older format can still be read by sipnet: +* SIPNET will print a warning indicating that it is ignoring the obsolete columns +* If there is more than one location specified in the file, SIPNET will error and halt + +### Example `sipnet.clim` file: + +Column names are not used, but are: + +``` +loc year day time length tair tsoil par precip vpd vpdSoil vPress wspd +``` + +**Half-hour time step** + +``` +0 1998 305 0.00 -1800 1.9000 1.2719 0.0000 0.0000 109.5364 77.5454 726.6196 1.6300 +0 1998 305 0.50 -1800 1.9000 1.1832 0.0000 0.0000 109.5364 73.1254 726.6196 1.6300 +0 1998 305 1.00 -1800 2.0300 1.1171 0.0000 0.0000 110.4243 63.9567 732.5092 0.6800 +0 1998 305 1.50 -1800 2.0300 1.0439 0.0000 0.0000 110.4243 60.3450 732.5092 0.6800 +``` + +**Variable time step** + +``` +0 1998 305 0.00 0.292 1.5 0.8 0.0000 0.0000 105.8 70.1 711.6 0.9200 +0 1998 305 7.00 0.417 3.6 1.8 5.6016 0.0000 125.7 23.5 809.4 1.1270 +0 1998 305 17.00 0.583 1.9 1.3 0.0000 0.0000 108.1 75.9 732.7 1.1350 +0 1998 306 7.00 0.417 2.2 1.4 2.7104 1.0000 114.1 71.6 741.8 0.9690 +``` + +## Agronomic Events + +Agronomic (management) events are read from an `events.in` file. This file specifies one event per line: + +| col | parameter | description | units | notes | +| --- | ----------- | ------------------------------------ | ------ | ------------------------------------------------ | +| 1 | year | Year of the event | | e.g. 2025 | +| 2 | day | Day of year of the event | DOY | 1 = Jan 1 | +| 3 | event_type | Event type code | | one of: `plant`, `harv`, `till`, `fert`, `irrig` | +| 4…n | event_param | Type‑specific parameters (see below) | varies | Order depends on event type | + +Rules: + +- File must be in chronological order. Ties (same day multiple events) are allowed and processed in file order. +- Events are specified at day resolution (no sub‑daily timestamp). +- Every (year, day) appearing in `events.in` must have at least one corresponding climate record; otherwise SIPNET errors. +- Values in `events.in` are instantaneous amounts (cm or mass/area) applied on the date and time of the first climate record matching that event's year and day. + +See subsections below for details and parameter definitions for each event type. + +### Irrigation + +| parameter | col | req? | description | +| --------- | :---: | :---: | ------------------------------------------- | +| amount | 5 | Y | Amount added (cm) | +| method | 6 | Y | 0=canopy
1=soil
2=flood (placeholder) | + +Model representation: An irrigation event increases soil moisture. A fraction of canopy irrigation is immediately evaporated. + +Specifically: + +- For `method=soil`, this amount of water is added directly to the `soilWater` state variable +- For `method=canopy`, a fraction of the irrigation water (determined by input param `immedEvapFrac`) is added to the flux state variable `immedEvap`, with the remainder going to `soilWater`. +- Initial implementation assumes that LITTER_WATER is not on. This might be revisited at a later date. + + +### Fertilization + +| parameter | col | req? | description | +| --------- | :---: | :---: | ----------- | +| org-N | 5 | Y | g N / m2 | +| org-C | 6 | Y | g C / m2 | +| min-N | 7 | Y | g N / m2 | + + + - Model representation: increases size of mineral N and litter C and N. Urea-N is assumed to be mineral N. + + - The code that generates `events.in` will handle conversion from fertilizer amount and type to mass of N and C allocated to different pools. In PEcAn this is done by the `PEcAn.SIPNET::write.configs.SIPNET()` function. + +### Tillage + +| parameter | col | req? | description | +| -------------------------------- | :---: | :---: | ------------------- | +| tillageEff $(f_{\textrm{till}})$ | 5 | Y | Adjustment to $R_H$ | + +- Model representation: + - Transient increase in decomposition rate by $f_{\text{,tillage}}$ that exponentially decays over time. + - Multiple tillage events are additive. + +### Planting + +| parameter | col | req? | description | +| ------------- | :---: | :---: | -------------------------------------------- | +| leaf-C | 5 | Y | C added to leaf pool (g C / m2) | +| wood-C | 6 | Y | C added to above-ground wood pool (g C / m2) | +| fine-root-C | 7 | Y | C added to fine root pool (g C / m2) | +| coarse-root-C | 8 | Y | C added to coarse root pool (g C / m2) | + +- Model representation: + - Date of event is the date of emergence, not the date of actual planting + - Increases size of carbon pools by the amount of each respective parameter + - $N$ pools are calculated from $CN$ stoichiometric ratios. +- notes: PFT (crop type) is not an input parameter for a planting event because SIPNET only represents a single PFT. + +### Harvest + +| parameter | col | req? | description | +| ---------------------------------------------------------- | :---: | :---: | --------------------- | +| fraction of aboveground biomass removed | 5 | Y | | +| fraction of belowground biomass removed | 6 | N | default = 0 | +| fraction of aboveground biomass transferred to litter pool | 7 | N | default = 1 - removed | +| fraction of belowground biomass transferred to litter pool | 8 | N | default = 1 - removed | + +- model representation: + - biomass C and N pools are either removed or added to litter + - for annuals or plants terminated, no biomass remains (col 5 + col 7 = 1 and col 6 + col 8 = 1). + - for perennials, some biomass may remain (col 5 + col 7 <= 1 and col 6 + col 8 <= 1; remainder is living). + - root biomass is only removed for root crops + +### Example of `events.in` file: + +``` +2022 35 till 0.2 # tilled on day 35, f_till = 0.2 (20% boost to rate term) +2022 40 till 0.1 # tilled on day 40, adds 0.1 to f_till +2022 40 irrig 5 1 # 5cm canopy irrigation on day 40 applied to soil +2022 40 fert 0 0 10 # fertilized with 10 g / m2 N_min on day 40 of 2022 +2022 50 plant 10 3 2 5 # plant emergence on day 50 with 10/3/2/4 g C / m2, respectively, added to the leaf/wood/fine root/coarse root pools +2022 250 harv 0.1 # harvest 10% of aboveground plant biomass on day 250 +``` + +## Run-time Options + +Configuration settings are applied in the following order of precedence: + +1. Default values built into SIPNET +2. Values from the configuration file +3. Command-line arguments + +Thus, command-line arguments override settings in the configuration file, and configuration file settings override default values. + +### Input / Output Options + +| Option | Default | Description | +| ------------ | --------- | ------------------------------------- | +| `input-file` | sipnet.in | Name of input config file | +| `file-name` | sipnet | Prefix of climate and parameter files | + +### Output Flags + +| Option | Default | Description | +| ------------------- | ------- | -------------------------------------------------------------- | +| `do-main-output` | on | Print time series of all output variables to `.out` | +| `do-single-outputs` | off | Print outputs one variable per file (e.g. `.NEE`) | +| `dump-config` | on | Print final config to `.config` | +| `print-header` | on | Whether to print header row in output files | +| `quiet` | off | Suppress info and warning message | + +### Model Flags + +| Option | Default | Description | +| ------------- | ------- | ---------------------------------------------------------------------------------------- | +| `events` | on | Enable event handling. | +| `gdd` | on | Use growing degree days to determine leaf growth. | +| `growth-resp` | off | Explicitly model growth respiration, rather than including with maintenance respiration. | +| `leaf-water` | off | Calculate leaf pool and evaporate from that pool. | +| `litter-pool` | off | Enable litter pool in addition to single soil carbon pool. | +| `microbes` | off | Enable microbe modeling. | +| `snow` | on | Keep track of snowpack, rather than assuming all precipitation is liquid. | +| `soil-phenol` | off | Use soil temperature to determine leaf growth. | +| `water-hresp` | on | Whether soil moisture affects heterotrophic respiration. | + +Note the following restrictions on these options: + - `soil-phenol` and `gdd` may not both be turned on + +### Command Line Arguments + +Command-line arguments can be used to specify run-time options when starting SIPNET. The syntax is as follows: + +``` +sipnet [options] +``` + +Where `[options]` can include any of the run-time options listed above. +Flags use the syntax `--flag` to turn them on, or `--no-flag` to turn them off. Other options are specified by `--option value`. + +See `sipnet --help` for a full list of available command-line options. + +### Configuration File Format + +SIPNET reads a configuration file that specifies run-time options without using command-line arguments. By default, SIPNET looks for a file named `sipnet.in` in the current directory. These will be overwritten by command-line arguments if specified. + +The configuration file uses a simple key-value format, `option = value`, +with one option per line; comments follow `#`. Flags are specified as 0 for off and 1 for on. + +#### Example Configuration File + +Note that case is ignored for parameter names, as well as dashes and underscores. + +``` +# Base filename (used for derived filenames) +FILE_NAME = mysite + +# Output options +DO_MAIN_OUTPUT = 1 +DO_SINGLE_OUTPUTS = 0 +DUMP_CONFIG = 1 +PRINT_HEADER = 1 +QUIET = 0 + +# Model options +EVENTS = 1 +GDD = 1 +GROWTH_RESP = 0 +LEAF_WATER = 0 +LITTER_POOL = 0 +MICROBES = 0 +SNOW = 1 +SOIL_PHENOL = 0 +WATER_HRESP = 1 +``` + +When `DUMP_CONFIG` is on, SIPNET will output the final configuration (after applying all settings from defaults, configuration file, and command line) to a file named `.config`. \ No newline at end of file diff --git a/docs/user-guide/model-outputs.md b/docs/user-guide/model-outputs.md new file mode 100644 index 000000000..bfc2bb902 --- /dev/null +++ b/docs/user-guide/model-outputs.md @@ -0,0 +1,79 @@ + +# Outputs + +There are two main output files generated by SIPNET: +1. `sipnet.out`: Model state variables and fluxes at each timestep. +2. `events.out`: Contains a record of agronomic events processed during the simulation (if event handling is enabled). + +## Model Outputs + +The `sipnet.out` file contains a time series of state variables and fluxes from the simulation. + +| # | Symbol | Output Name | Definition / Notes | Units | +| --- | ---------------- | ------------------- | ------------------------------------------------------------------------- | ------------ | +| 1 | | year | Year of start of timestep | (integer) | +| 2 | | day | Day-of-year of start of timestep | (integer) | +| 3 | | time | Hour-of-day (fractional) at start of timestep | hours | +| 4 | | plantWoodC | Woody plant carbon | g C m$^{-2}$ | +| 5 | | plantLeafC | Leaf carbon | g C m$^{-2}$ | +| 6 | | soil | (Single) soil organic carbon (or mineral soil C pool) | g C m$^{-2}$ | +| 7 | | microbeC | Microbial carbon (0 if microbes flag off) | g C m$^{-2}$ | +| 8 | | coarseRootC | Coarse root carbon | g C m$^{-2}$ | +| 9 | | fineRootC | Fine root carbon | g C m$^{-2}$ | +| 10 | | litter | Litter carbon (0 if litter pool disabled) | g C m$^{-2}$ | +| 11 | | soilWater | Soil water content | cm | +| 12 | $f_{\text{WHC}}$ | soilWetnessFrac | Soil water as fraction of holding capacity | unitless | +| 13 | | snow | Snow water equivalent | cm | +| 14 | | npp | Net primary production for timestep | g C m$^{-2}$ | +| 15 | | nee | Net ecosystem exchange (sign convention per code: - (NPP - RH)) | g C m$^{-2}$ | +| 16 | | cumNEE | Cumulative NEE since simulation start | g C m$^{-2}$ | +| 17 | $GPP$ | gpp | Gross primary production | g C m$^{-2}$ | +| 18 | | rAboveground | Aboveground autotrophic respiration (leaves + wood) | g C m$^{-2}$ | +| 19 | $R_H$ | rSoil | Heterotrophic respiration (maintenance + microbe terms per configuration) | g C m$^{-2}$ | +| 20 | | rRoot | Root (autotrophic) respiration | g C m$^{-2}$ | +| 21 | | ra | Total autotrophic respiration (rAboveground + rRoot) | g C m$^{-2}$ | +| 22 | | rh | Total heterotrophic respiration (litter + soil components) | g C m$^{-2}$ | +| 23 | $R$ | rtot | Total ecosystem respiration (ra + rh) | g C m$^{-2}$ | +| 24 | | evapotranspiration | ET (transpiration + immedEvap + evaporation + sublimation) for timestep | cm | +| 25 | | fluxestranspiration | Transpiration component only | cm | + +An example output file can be found in [tests/smoke/sipnet.out](https://github.com/PecanProject/sipnet/blob/master/tests/smoke/niwot/sipnet.out). + +``` +Notes: PlantWoodC, PlantLeafC, Soil and Litter in g C/m^2; Water and Snow in cm; SoilWetness is fraction of WHC; +year day time plantWoodC plantLeafC soil microbeC coarseRootC fineRootC litter litterWater soilWater soilWetnessFrac snow npp nee cumNEE gpp rAboveground rSoil rRoot ra rh rtot evapotranspiration fluxestranspiration +1998 305 0.00 5759.77 1133.88 16000.06 8.00 1919.90 1919.64 0.500 6.00 0.500 0.00 -0.32 0.74 0.74 0.00 0.164 0.578 0.159 0.324 0.419 0.742 0.00302126 0.0000 +1998 305 7.00 5759.63 1133.71 16000.08 8.00 1919.77 1919.10 0.500 5.99 0.500 0.00 -0.30 0.97 1.71 0.22 0.271 0.917 0.251 0.522 0.666 1.188 0.00240544 0.0022 +1998 305 17.00 5759.16 1133.48 16000.15 8.00 1919.57 1918.37 0.500 5.99 0.499 0.00 -0.67 1.56 3.27 0.00 0.338 1.219 0.335 0.673 0.884 1.557 0.00662149 0.0000 +``` + +## Events output + +When event handling is enabled, SIPNET will create a file named `events.out`. + +This file is designed primarily for _testing and debugging_. +It contains one row for each agronomic event that is processed. +Each row lists the year, day, event type, and parameter name/value pairs. +The name/value pairs represent the state variables that are directly changed by an event, recording the change (delta) applied to each. + +Information in `events.out` can, in principle, be reconstructed or inferred from `events.in` and `sipnet.out` though this may be confounded if simultaneous events affect the same variable. + +Still, _`sipnet.out` is the authoritative source_ for information about system state and evolution in time, including responses to events. + +Below is an example `events.out`, with header enabled for clarity. +Note the delimiters: spaces separate columns, commas separate name/value pairs, and `=` map names with their values (deltas). + +``` +year day type param_name=delta[,param_name=delta,...] +2023 65 plant envi.plantLeafC=3.00,envi.plantWoodC=4.00,envi.fineRootC=5.00,envi.coarseRootC=6.00 +2023 70 irrig envi.soilWater=5.00 +2023 200 harv env.litter=5.46,envi.plantLeafC=-5.93,envi.plantWoodC=-4.75,envi.fineRootC=-3.73,envi.coarseRootC=-3.89 +2024 65 plant envi.plantLeafC=3.00,envi.plantWoodC=5.00,envi.fineRootC=7.00,envi.coarseRootC=9.00 +2024 70 irrig fluxes.immedEvap=2.50,envi.soilWater=2.50 +2024 200 harv env.litter=4.25,envi.plantLeafC=-1.39,envi.plantWoodC=-1.63,envi.fineRootC=-2.52,envi.coarseRootC=-2.97 +``` diff --git a/mkdocs.yml b/mkdocs.yml index c22b300e5..d9355d3ad 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,10 +7,13 @@ nav: - Model Specification: model-structure.md - User Guide: - Running SIPNET: user-guide/running-sipnet.md + - Model Inputs: user-guide/model-inputs.md + - Model Outputs: user-guide/model-outputs.md - Developer Guide: - Code Structure: developer-guide/code-structure.md - Adding New Command Line Options: developer-guide/cli-options.md - Logging: developer-guide/logging.md + - Testing: developer-guide/testing.md - Contributing: CONTRIBUTING.md - API Reference: api/index.html - Changelog: CHANGELOG.md @@ -21,6 +24,7 @@ theme: features: - content.action.edit - content.code.copy + - navigation.expand icon: edit: material/pencil palette: @@ -47,3 +51,8 @@ extra: repo_url: https://github.com/PecanProject/sipnet edit_uri: edit/master/docs/ + +# Use relative URLs so previews under subpaths work +use_directory_urls: true +# Avoid absolute site_url; let MkDocs generate relative asset paths +site_url: "" diff --git a/tools/run_unit_tests.sh b/tools/run_unit_tests.sh index 39341b8ef..6af3ec601 100755 --- a/tools/run_unit_tests.sh +++ b/tools/run_unit_tests.sh @@ -15,8 +15,8 @@ YELLOW="\033[1;33m" RESET="\033[0m" # Step 1: Build everything first -echo "Running make test..." -if ! make test; then +echo "Running make testbuild..." +if ! make testbuild; then echo -e "${RED}Build failed, exiting.${RESET}" exit 1 fi