DiscreteControl

Set parameters of other nodes based on model state conditions (e.g. Basin level). The table below shows which parameters are controllable for a given node type.

Code
using Ribasim
using DataFrames: DataFrame
using MarkdownTables

node_names = Symbol[]
controllable_parameters = String[]

for node_type in fieldtypes(Ribasim.ParametersIndependent)
    if node_type <: Ribasim.AbstractParameterNode
        node_name = nameof(node_type)
        controllable_fields = Ribasim.controllablefields(node_name)
        controllable_fields = sort!(string.(controllable_fields))
        if node_name == :TabulatedRatingCurve
            controllable_fields = map(
                s -> replace(s, "table" => "q(h) relationship (given by level, flow_rate)"),
                controllable_fields,
            )
        end
        if !isempty(controllable_fields)
            push!(node_names, Ribasim.snake_case(node_name))
            push!(controllable_parameters, join(controllable_fields, ", "))
        end
    end
end

df = DataFrame(:node => node_names, :controllable_parameters => controllable_parameters)

markdown_table(df)
Precompiling packages...
   2044.5 msQuartoNotebookWorkerTablesExt (serial)
  1 dependency successfully precompiled in 2 seconds
Precompiling packages...
   1120.5 msQuartoNotebookWorkerLaTeXStringsExt (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
   1120.8 msQuartoNotebookWorkerJSONExt (serial)
  1 dependency successfully precompiled in 1 seconds
node controllable_parameters
linear_resistance resistance
manning_resistance manning_n
tabulated_rating_curve q(h) relationship (given by level, flow_rate)
pump flow_rate
outlet flow_rate
pid_control derivative, integral, proportional, target

1 Tables

1.1 Variable

The compound variable schema defines linear combinations of variables which can be used in conditions. This means that this schema defines new variables with the given compound_variable_id that look like \[ \text{weight}_1 * \text{variable}_1 + \text{weight}_2 * \text{variable}_2 + \ldots, \]

which can be for instance an average or a difference of variables. If a variable comes from a time-series, a look ahead \(\Delta t\) can be supplied.

column type unit restriction
node_id Int32 -
compound_variable_id Int32 -
listen_node_id Int32 - cannot be a Junction
variable String - must be “level”, “storage” or “flow_rate”
weight Float64 - (optional, default 1.0)
look_ahead Float64 \(\text{s}\) Only on transient boundary conditions, non-negative (optional, default 0.0).

These variables can be listened to:

  • The storage of a Basin
  • The level of a Basin
  • The level of a LevelBoundary (supports look ahead)
  • The flow rate through one of these node types: Pump, Outlet, TabulatedRatingCurve, LinearResistance, ManningResistance
  • The flow rate of a FlowBoundary (supports look ahead)

1.2 Condition

The condition schema defines conditions of the form the discrete_control node with this node_id listens to whether the variable given by the node_id and compound_variable_id is greater than threshold_high (at time \(t\)) when the condition was false previously, or threshold_low when true previously. Using threshold_low is optional and defaults to threshold_high. When set to a different value—smaller than or equal to threshold_high—it defines a deadband and thus enables hysteresis. In equation form:

\[ condition=\begin{cases} \text{weight}_1 * \text{variable}_1 + \text{weight}_2 * \text{variable}_2 + \ldots > \text{greater\_than}(t), !condition\_{prev} \\ \text{weight}_1 * \text{variable}_1 + \text{weight}_2 * \text{variable}_2 + \ldots >= \text{less\_than}(t), condition\_{prev} \end{cases} \]

Multiple conditions with different threshold_high and/or threshold_low values can be defined on the same compound_variable, each with their own unique condition_id.

Note the strict inequality ‘\(>\)’ in the equation above. This means for instance that if a simulation starts with a compound variable exactly at the threshold_high value, the condition is false. The same holds for the threshold_low value, using ‘\(>=\)’, so the deadband is inclusive of the thresholds.

column type unit restriction
node_id Int32 - -
compound_variable_id Int32 - -
condition_id Int32 - -
threshold_high Float64 various -
threshold_low Float64 various (optional), <= threshold_high
time DateTime - (optional)

When no (or only a single) time is supplied for a given condition_id, the threshold_high value of this condition is unchanging over time. When multiple times are supplied, in between the given times the threshold_high and threshold_low values are block interpolated (forward fill), and outside they are constant given by the nearest time value.

1.3 Logic

The logic schema defines which control states are triggered based on the truth of the conditions a DiscreteControl node listens to. DiscreteControl is applied in the Julia core as follows:

  • During the simulation it is checked whether the truth of any of the conditions changes.
  • When a condition changes, the corresponding DiscreteControl node ID is retrieved (node_id in the condition schema above).
  • The truth value of all the conditions this DiscreteControl node listens to are retrieved, in the order of the condition IDs. This is then converted into a string of “T” for true and “F” for false. This string we call the truth state.
  • The table below determines for the given DiscreteControl node ID and truth state what the corresponding control state is.
  • For all the nodes this DiscreteControl node affects (as given by the “control” links in Links / static), their parameters are set to those parameters in NodeType / static corresponding to the determined control state.
column type unit restriction
node_id Int32 -
truth_state String - Consists of the characters “T” (true), “F” (false) and “*” (any)
control_state String - -