Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve docs home page #1002

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/_quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ website:
- title: "Julia core"
contents:
- core/index.qmd
- core/modelconcept.qmd
- core/usage.qmd
- core/validation.qmd
- core/equations.qmd
Expand Down
63 changes: 4 additions & 59 deletions docs/contribute/core.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,15 @@ title: "Julia core development"

# Julia core overview

This section is about the Julia core in `Ribasim.jl`. See the component overview [here](index.qmd) for the context of this computational core.
The computational core is one of the components of Ribasim as illustrated in the [component overview](../index.qmd#sec-components).

`Ribasim.jl` can be divided into 3 parts:
The computational process can be divided in three phases:

- Model initialization
- Running the simulation loop
- Writing the output files

The figure below gives a more detailed description of the simulation loop in the form of a [sequence diagram](https://en.wikipedia.org/wiki/Sequence_diagram). From top to bottom, it contains the following blocks:

- Allocation optimization; activated when the allocation timestep has been passed;
- Control actions; activated when some discrete control callback is triggered;
- Water balance; computing the flows over flow edges happens each timestep;
- Time integration step; done by the integrator from `OrdinaryDiffEq.jl`.

```{mermaid, style="margin-top: 0"}
sequenceDiagram
autonumber
participant Int as Process: Integrator
participant Optim as Process: Allocation optimization
participant Param as Data: Parameters
participant State as Data: State
participant Sim as Process: Water balance
loop Simulation loop (OrdinaryDiffEq.jl)
activate Int
%% Allocation
rect rgb(200, 200, 200)
opt Allocation optimization, per allocation network (JuMP.jl, HiGHS)
activate Optim
Int->>Optim: Callback: allocation timestep has passed
Param-->>Optim: Input
State-->>Optim: Input
Optim->>Optim: Optimize Basin allocations if below target level
Optim->>Optim: Optimize User allocation, per priority
Optim-->>Param: Set allocated flow rates
deactivate Optim
end
end
%% Control
rect rgb(200, 200, 200)
opt Control actions
Int->>Int: DiscreteControl callback
Int-->>Param: Parameter updates by control
end
end
%% water_balance!
rect rgb(200, 200, 200)
activate Sim
State-->>Sim: Input
Param-->>Sim: Input
Sim->>Sim: Compute flows over edges per node type
Sim-->>Param: Set flows
deactivate Sim
end
%% Time integration
rect rgb(200, 200, 200)
State-->>Int: Input
Param-->>Int: Input
Int->>Int: Time integration step
Int-->>State: Update state
end
deactivate Int
end
```
A more detailed sequence diagram of the simulation loop is available at the [core](index.qmd#sec-simulationloop) home page.

# Set up the developer environment

Expand Down Expand Up @@ -101,7 +46,7 @@ Make sure to have the correct environment when opening your IDE by running `pixi
You will want to run the testsuite on a regular basis to check if your changes had unexpected side effects.
It is also a good way to find out if your development environment is set up correctly.

Before the tests can run, you need to [prepare model input](./python.qmd#prepare-model-input).
Before the tests can run, you need to [prepare model input](python.qmd#prepare-model-input).

With the root of the repository as your working directory you can start the REPL with activated `Ribasim` environment by running the following:

Expand Down
2 changes: 1 addition & 1 deletion docs/contribute/qgis.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Then simply call `pixi run test-ribasim-qgis`.
After installing the plugins via `pixi run install-qgis-plugins`.
Extra debugging tools are also installed in QGIS that is installed within your pixi environment.

After you have started `pixi run qgis`, you can make alterations to the python code and use the [Plugin Reloader](https://github.com/borysiasty/plugin_reloader) to reload the plugin without restarting QGIS.
After you have started `pixi run qgis`, you can make alterations to the Python code and use the [Plugin Reloader](https://github.com/borysiasty/plugin_reloader) to reload the plugin without restarting QGIS.
The shortcut in QGIS is `CTRL+F5`.

It is also possible to connect the debugger of Visual Studio Code.
Expand Down
119 changes: 100 additions & 19 deletions docs/core/index.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,120 @@ title: "Julia core"
With the term "core", we mean the computational engine of Ribasim. As detailed in the
[usage](usage.qmd) documentation, it is generally used as a command line tool.

A quick overview of the model concept is available at the [home page](../index.qmd#sec-concept), while a more in depth discussion is available on the [model concept](modelconcept.qmd) page.
The theory is described on the [equations](equations.qmd) page, and more in-depth numerical considerations are described on the [numerical considerations](numerics.qmd) page. As allocation is a large and self-contained part of the Ribasim core, it is described on the separate [allocation](allocation.qmd) page. Input validation is described on the [validation](validation.qmd) page.

The core is implemented in the [Julia programming language](https://julialang.org/), and
can be found in the [Ribasim repository](https://github.com/Deltares/Ribasim) under the
`core/` folder. For developers we also advise to read the
[developer documentation](../contribute/core.qmd).

An overview of all components is given on the [home page](../index.qmd#sec-components)

# The simulation loop {#sec-simulationloop}

The computational process can be divided in three phases:

- Model initialization
- Running the simulation loop
- Writing the output files

The figure below gives a more detailed description of the simulation loop in the form of a [sequence diagram](https://en.wikipedia.org/wiki/Sequence_diagram). From top to bottom, it contains the following blocks:

- Allocation optimization; activated when the allocation timestep has been passed;
- Control actions; activated when some discrete control callback is triggered;
- Water balance; computing the flows over flow edges happens each timestep;
- Time integration step; done by the integrator from `OrdinaryDiffEq.jl`.

```{mermaid}
%%| file: ../assets/c4_component_ribasim.mmd
%%| fig-cap: "Component overview of Ribasim"
sequenceDiagram
autonumber
participant Int as Process: Integrator
participant Optim as Process: Allocation optimization
participant Param as Data: Parameters
participant State as Data: State
participant Sim as Process: Water balance
loop Simulation loop (OrdinaryDiffEq.jl)
activate Int
%% Allocation
rect rgb(200, 200, 200)
opt Allocation optimization, per allocation network (JuMP.jl, HiGHS)
activate Optim
Int->>Optim: Callback: allocation timestep has passed
Param-->>Optim: Input
State-->>Optim: Input
Optim->>Optim: Optimize Basin allocations if below target level
Optim->>Optim: Optimize User allocation, per priority
Optim-->>Param: Set allocated flow rates
deactivate Optim
end
end
%% Control
rect rgb(200, 200, 200)
opt Control actions
Int->>Int: DiscreteControl callback
Int-->>Param: Parameter updates by control
end
end
%% water_balance!
rect rgb(200, 200, 200)
activate Sim
State-->>Sim: Input
Param-->>Sim: Input
Sim->>Sim: Compute flows over edges per node type
Sim-->>Param: Set flows
deactivate Sim
end
%% Time integration
rect rgb(200, 200, 200)
State-->>Int: Input
Param-->>Int: Input
Int->>Int: Time integration step
Int-->>State: Update state
end
deactivate Int
end
```

# The simulation loop
# Nested allocation {#sec-nested-allocation}

The figure below shows a simple flowchart of the simulation in `Ribasim.jl`.
Since water systems may be extensive, like in the Netherlands, Ribasim models may become large networks with over ten thousand nodes.
To keep a proper functioning allocation concept under these circumstances, the modeller can decompose the network domain into a main network and multiple sub-networks.
The allocation will then be conducted in three steps:

1. conduct an inventory of demands from the sub-networks to inlets from the main network,
2. allocate the available water in the main network to the subnetworks inlets,
3. allocate the assigned water within each subnetwork to the individual water users.

The users then will request this updated demand from the rule-based simulation.
Whether this updated demand is indeed abstracted depends on all dry-fall control mechanism implemented in the rule-based simulation.

The following sequence diagram illustrates this calculation process within then allocation phase.

```{mermaid}
flowchart LR
Start((Start))
Init[Initialize model]
Con[Conditional: allocation, control]
Sim[Simulate flows over timestep]
Finished{End of simulation period?}
Done((Done))

Start --> Init
Init --> Con
Con --> Sim
Sim --> Finished
Finished -->|no| Con
Finished -->|yes| Done
```
sequenceDiagram
participant boundary
participant basin
participant user
participant allocation_subNetwork
participant allocation_mainNetwork

user->>allocation_subNetwork: demand
loop
allocation_subNetwork-->>allocation_mainNetwork: demand inventory at inlets
end
user->>allocation_mainNetwork: demand
boundary->>allocation_mainNetwork: source availability
basin->>allocation_mainNetwork: source availability
allocation_mainNetwork-->>allocation_mainNetwork: allocate to inlets (and users)
allocation_mainNetwork->>user: allocated
allocation_mainNetwork->>allocation_subNetwork: allocated
loop
allocation_subNetwork-->>allocation_subNetwork: allocate to users
end
allocation_subNetwork->>user: allocated
user->>basin: abstracted
```

# Coupling

Expand Down
126 changes: 126 additions & 0 deletions docs/core/modelconcept.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
title: Model concept
---
A brief summary of the concept is given on the documentation [home page](../index.qmd#sec-concept).
As indicated, the model concept is organized in three layers:

- a physical layer representing water bodies and associated infrastructure,
- a rule-based control layer to manage the infrastructure, and
- a priority-based allocation layer to take centralized decisions on user abstractions.

# Physical layer

## Water balance equations

The water balance equation for a drainage basin [@enwiki:1099736933] can be defined by a first-order ordinary differential equation (ODE), where the change of the storage $S$ over time is determined by the inflow fluxes minus the outflow fluxes.

$$
\frac{\mathrm{d}S}{\mathrm{d}t} = Q_{in} - Q_{out}
$$

We can split out the fluxes into separate terms, such as precipitation $P$, evapotranspiration $ET$ and runoff $R$.
For now other fluxes are combined into $Q_{rest}$.
If we define all fluxes entering our reservoir as positive, and those leaving the system as negative, all fluxes can be summed up.

$$
\frac{\mathrm{d}S}{\mathrm{d}t} = R + P + ET + Q_{rest}
$$

## Time

The water balance equation can be applied on many timescales; years, weeks, days or hours.
Depending on the application and available data any of these can be the best choice.
In Ribasim, we make use of DifferentialEquations.jl and its [ODE solvers](https://diffeq.sciml.ai/stable/solvers/ode_solve/).
Many of these solvers are based on adaptive time stepping, which means the solver will decide how large the time steps can be depending on the state of the system.

The forcing, like precipitation, is generally provided as a time series.
Ribasim is set up to support unevenly spaced timeseries.
The solver will stop on timestamps where new forcing values are available, so they can be loaded as the new value.

Ribasim is essentially a continuous model, rather than daily or hourly.
If you want to use hourly forcing, you only need to make sure that your forcing data contains hourly updates.
The output frequency can be configured independently.
To be able to write a closed water balance, we accumulate the fluxes.
This way any variations in between timesteps are also included, and we can output in `m³` rather than `m³s⁻¹`.

## Space {#sec-space}

The water balance equation can be applied on different spatial scales.
Besides modelling a single lumped watershed, it allows you to divide the area into a network of connected representative elementary watersheds (REWs) [@REGGIANI1998367].
At this scale global water balance laws can be formulated by means of integration of point-scale conservation equations
over control volumes.
Such an approach makes Ribasim a semi-distributed model.
In this document we typically use the term "basin" to refer to the REW.
Each basin has an associated polygon, and the set of basins is connected to each other as described by a graph, which we call the network.
Below is a representation of both on the map.

![Mozart Local Surface Water polygons and their drainage.](https://user-images.githubusercontent.com/4471859/185932183-62c305e6-bc14-4f3c-a74c-437f831c9145.png)

The network is described as graph.
Flow can be bi-directional, and the graph does not have to be acyclic.

```{mermaid}
graph LR;
A["basin A"] --- B["basin B"];
A --- C["basin C"];
B --- D["basin D"];
C --- D;
```

Internally a directed graph is used.
The direction is defined to be the positive flow direction, and is generally set in the dominant flow direction.
The basins are the nodes of the network graph.
Basin states and properties such storage volume and wetted area are associated with the nodes (A, B, C, D), as are most forcing data such as precipitation, evaporation, or water demand.
Basin connection properties and interbasin flows are associated with the edges (the
lines between A, B, C, and D) instead.

Multiple basins may exist within the same spatial polygon, representing different aspects of the surface water system (perennial ditches, ephemeral ditches, or even surface ponding).
@fig-p, @fig-s, @fig-t show the 25.0 m rasterized primary, secondary, and tertiary surface waters as identified by BRT TOP10NL [@pdoktopnl] in the Hupsel basin.
These systems may represented in multiple ways.

![Hupsel: primary surface water.](https://user-images.githubusercontent.com/13662783/187625163-d0a81bb6-7f55-4ad1-83e2-90ec1ee79740.PNG){#fig-p}

![Hupsel: secondary surface water.](https://user-images.githubusercontent.com/13662783/187625170-1acdfb41-7077-443f-b140-ae18cbf21e53.PNG){#fig-s}

![Hupsel: tertiary surface water.](https://user-images.githubusercontent.com/13662783/187625174-3eec28b5-ddbb-4870-94c3-d9e9a43f8eb4.PNG){#fig-t}

As a single basin (A) containing all surface water, discharging to its downstream basin to the west (B):

```{mermaid}
graph LR;
A["basin A"] --> B["basin B"];
```

Such a system may be capable of representing discharge, but it cannot represent residence times or differences in solute concentrations: within a single basin, a drop of water is mixed instantaneously.
Instead, we may the group primary (P), secondary (S), and tertiary (T) surface waters.
Then T may flow into S, S into P, and P discharges to the downstream basin (B.)

```{mermaid}
graph LR;
T["basin T"] --> S["basin S"];
S --> P["basin P"];
P --> B["basin B"];
```

As each (sub)basin has its own volume, low throughput (high volume, low discharge, long residence time) and high throughput (low volume, high discharge, short residence time) systems
can be represented in a lumped manner; of course, more detail requires more parameters.

## Structures in a water system
In addition to free flowing waterbodies, a watersystem typically has structures to control the flow of water. Ribasim uses connector nodes which simplify the hydraulic behaviour for the
free flowing conditions or structures.
The following type of connector nodes are available for this purpose:

- [TabulatedRatingCurve](.\usage.qmd#sec-tabulated-rating-curve): one-directional flow based on upstream head. Node type typically used for gravity flow conditions either free flowing open water channels
or over a fixed structure.
- [LinearResistance](.\usage.qmd#sec-linear-resistance): bi-directional flow based on head difference and linear resistance. Node type typically used for bi-directional flow
situations or situations where head difference over a structure determines its actual flow capacity.
- [ManningResistance](.\usage.qmd#sec-manning-resistance): bi-directional flow based on head difference and resistance using Manning-Gauckler formula. Same usage as LinearResistance,
providing a better hydrological meaning to the resistance parameterization.
- [Pump](.\usage.qmd#sec-pump): one-directional structure with a set flow rate. Node type typically used in combination with control to force water over the edge.
- [Outlet](.\usage.qmd#sec-outlet): one-directional gravity structure with a set flow rate. Node type typically used in combination with control to force water over the edge, even if
their is a mismatch in actual hydraulic capacity. The node type has an automated mechanism to stop the flow when the head difference is zero.
- [FractionalFlow](.\usage.qmd#sec-fractional-flow): to split an outflow over multiple edges based on a flow fraction. Node type is typically used for diversions or bifurcations with a known and fixed ratio.

The control layer can activate or deactivate nodes, set flow rates for the Pump and Outlet, or choose different parameterizations for TabulatedRatingCurve, LinearResistance, ManningResistance or FractionalFlow.

Connector nodes are required within a Ribasim network to determine the flow exchange between basins.
Loading
Loading