diff --git a/core/src/callback.jl b/core/src/callback.jl index df7f7734c..f5a347448 100644 --- a/core/src/callback.jl +++ b/core/src/callback.jl @@ -110,6 +110,15 @@ function integrate_flows!(u, t, integrator)::Nothing user_demand.realized_bmi[i] += 0.5 * (flow[flow_idx] + flow_prev[flow_idx]) * dt end + # *Demand realized flow for output + for (edge, value) in allocation.mean_realized_flows + if edge[1] !== edge[2] + value += + 0.5 * (get_flow(graph, edge..., 0) + get_flow_prev(graph, edge..., 0)) * dt + allocation.mean_realized_flows[edge] = value + end + end + # Allocation source flows for (edge, value) in allocation.mean_input_flows if edge[1] == edge[2] @@ -128,15 +137,6 @@ function integrate_flows!(u, t, integrator)::Nothing end end - # Realized demand flows - for (edge, value) in allocation.mean_realized_flows - if edge[1] !== edge[2] - value += - 0.5 * (get_flow(graph, edge..., 0) + get_flow_prev(graph, edge..., 0)) * dt - allocation.mean_realized_flows[edge] = value - end - end - copyto!(flow_prev, flow) copyto!(vertical_flux_prev, vertical_flux) return nothing diff --git a/docs/_quarto.yml b/docs/_quarto.yml index d168a1080..d540b6803 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -49,6 +49,7 @@ website: - dev/python.qmd - guides/addnode.qmd - guides/release.qmd + - guides/bmi.qmd - title: "Background" contents: diff --git a/docs/guides/bmi.qmd b/docs/guides/bmi.qmd new file mode 100644 index 000000000..7d840d10d --- /dev/null +++ b/docs/guides/bmi.qmd @@ -0,0 +1,46 @@ +# Basic Model Interface (BMI) + +For runtime data exchange and coupling with other kernels, the Julia kernel is wrapped in a Python API (`ribasim_api`) which implements the Basic Modelling Interface [BMI](https://bmi-spec.readthedocs.io/en/latest/). + +## Functions + +The following functions are available to interact with the Ribasim model" + +signature | description +------------------------- | ------------- +`initialize(config_path)` | Initialize a model from the path to the TOML configuration file +`finalize()` | Write all results to the configured files +`get_current_time()` | Get the current time of the Ribasim simulation +`get_end_time()` | Get the final time of the Ribasim simulation in seconds +`get_start_time()` | Get the start time of the Ribasim simulation (`0.0`) +`get_time_step()` | Get the proposed next internal Ribasim timestep +`get_time_units()` | Get the time unit (`s`) +`get_value_ptr(string)` | Get the pointer to a Ribasim internal array (see below) +`update()` | Perform a Ribasim internal time step +`update_until(time)` | Set Ribasim internal timesteps until the specified time + +Depending on what is specified in the Ribasim TOML configuration file, Ribasim can internally have adaptive (non-constant) timesteps. `update_until` will always try to progress the Ribasim simulation to exactly the time specified. This however can fail for algorithms that only support a fixed timestep if that timestep does not fit into the interval until the specified time an integer amount of times. + +## Memory pointers + +The following pointers to memory containing Ribasim internal arrays are given via the BMI using `get_value_ptr(string)`: + +string | meaning | type | unit | temporal type | writable | sorted by +------------------------------- | -------------------------------------- | ------- | ------------ | --------------------- | -------- |---------- +`basin.storage` | storage per basin | Float64 | $m^3$ | instantaneous | no | basin node ID +`basin.level` | level per basin | Float64 | $m$ | instantaneous | no | basin node ID +`basin.infiltration` | infiltration flux per basin | Float64 | $m^3 s^{-1}$ | forward fill | yes | basin node ID +`basin.drainage` | drainage flux per basin | Float64 | $m^3 s^{-1}$ | forward fill | yes | basin node ID +`basin.infiltration_integrated` | cumulative infiltration per basin | Float64 | $m^3$ | integrated from start | yes | basin node ID +`basin.drainage_integrated` | cumulative drainage per basin | Float64 | $m^3$ | integrated from start | yes | basin node ID +`basin.subgrid_level` | subgrid level | Float64 | $m$ | instantaneous | no | subgrid ID +`user_demand.demand` | demand per node ID per priority | Float64 | $m^3 s^{-1}$ | forward fill | yes | user_demand node ID, priority index +`user_demand.realized` | cumulative intake flow per user | Float64 | $m^3$ | integrated from start | yes | user_demand node ID + +Additional notes: + +- `user_demand.demand` yields the only 2D array, the other arrays are 1D. This array is indexed as `(node_idx, priority_idx)` in Julia, which stores arrays column-major +- The index of e.g. basins and user demand nodes needs to be inferred from the Ribasim input. The same holds for `priority_idx`, which is global over all subnetworks +- The data being writable means that Ribasim takes into account the possibility that the data is updated outiside the Ribasim core +- Although the `*_integrated` and `*_realized` data is writable, this doesn't affect the Ribasim simulation. This integrated data is only computed for the BMI, and can be set to $0$ via the BMI to avoid accuracy problems when the values get too large. +- Different from what is exposed via the BMI, the basin forcings and realized user demands are averaged over the allocation timestep and saveat interval respectively. diff --git a/docs/guides/contributing.qmd b/docs/guides/contributing.qmd index 2f787dee9..dc4976bca 100644 --- a/docs/guides/contributing.qmd +++ b/docs/guides/contributing.qmd @@ -4,7 +4,7 @@ title: "Contributing" Ribasim welcomes contributions. -There is developer documentation for the [Julia core](core.qmd), [Python tooling](python.qmd), and the [QGIS plugin](qgis.qmd). +There is developer documentation for the [Julia core](core.qmd), the [Basic Model Interface (BMI)](bmi.qmd), [Python tooling](python.qmd), and the [QGIS plugin](qgis.qmd). A guide on how to add a new node type to both is written in [adding node types](addnode.qmd). [Release process](release.qmd) describes the steps to follow when creating a new Ribasim release.