From 9a191d7fb82d32be87932334301517fc6c5226c5 Mon Sep 17 00:00:00 2001 From: Quarto GHA Workflow Runner Date: Thu, 28 Sep 2023 09:37:39 +0000 Subject: [PATCH] Built site for gh-pages --- .nojekyll | 2 +- build/index.html | 188 ++++----- python/examples.html | 2 +- search.json | 910 +++++++++++++++++++++---------------------- 4 files changed, 551 insertions(+), 551 deletions(-) diff --git a/.nojekyll b/.nojekyll index 29882aae6..1af7f3afe 100644 --- a/.nojekyll +++ b/.nojekyll @@ -1 +1 @@ -40ec1188 \ No newline at end of file +01557de2 \ No newline at end of file diff --git a/build/index.html b/build/index.html index 426f38980..22a40b948 100644 --- a/build/index.html +++ b/build/index.html @@ -250,11 +250,11 @@

solve!
  • BMI.finalize
  • -

    source

    +

    source

    # Ribasim.configModule.

    module config

    Ribasim.config is a submodule of Ribasim to handle the configuration of a Ribasim model. It is implemented using the Configurations package. A full configuration is represented by Config, which is the main API. Ribasim.config is a submodule mainly to avoid name clashes between the configuration sections and the rest of Ribasim.

    -

    source

    +

    source

    @@ -269,24 +269,24 @@

    source

    +

    source

    # Ribasim.ConnectivityType.

    Store the connectivity information

    graphflow, graphcontrol: directed graph with vertices equal to ids flow: store the flow on every flow edge edgeidsflow, edgeidscontrol: get the external edge id from (src, dst) edgeconnectiontypeflow, edgeconnectiontypescontrol: get (srcnodetype, dstnodetype) from edge id

    if autodiff T = DiffCache{SparseArrays.SparseMatrixCSC{Float64, Int64}, Vector{Float64}} else T = SparseMatrixCSC{Float64, Int} end

    -

    source

    +

    source

    # Ribasim.DiscreteControlType.

    nodeid: node ID of the DiscreteControl node; these are not unique but repeated by the amount of conditions of this DiscreteControl node listenfeatureid: the ID of the node/edge being condition on variable: the name of the variable in the condition greaterthan: The threshold value in the condition conditionvalue: The current value of each condition controlstate: Dictionary: node ID => (control state, control state start) logic_mapping: Dictionary: (control node ID, truth state) => control state record: Namedtuple with discrete control information for output

    -

    source

    +

    source

    # Ribasim.FlatVectorType.

    struct FlatVector{T} <: AbstractVector{T}

    A FlatVector is an AbstractVector that iterates the T of a Vector{Vector{T}}.

    Each inner vector is assumed to be of equal length.

    It is similar to Iterators.flatten, though that doesn’t work with the Tables.Column interface, which needs length and getindex support.

    -

    source

    +

    source

    # Ribasim.FlowBoundaryType.

    nodeid: node ID of the FlowBoundary node active: whether this node is active and thus contributes flow flowrate: target flow rate

    -

    source

    +

    source

    # Ribasim.FractionalFlowType.

    Requirements:

      @@ -295,10 +295,10 @@

      source

      +

      source

      # Ribasim.LevelBoundaryType.

      node_id: node ID of the LevelBoundary node active: whether this node is active level: the fixed level of this ‘infinitely big basin’

      -

      source

      +

      source

      # Ribasim.LinearResistanceType.

      Requirements:

        @@ -306,7 +306,7 @@

        source

        +

        source

        # Ribasim.ManningResistanceType.

        This is a simple Manning-Gauckler reach connection.

          @@ -336,39 +336,39 @@

          source

          +

          source

          # Ribasim.ModelType.

          Model(config_path::AbstractString)
           Model(config::Config)

          Initialize a Model.

          The Model struct is an initialized model, combined with the Config used to create it and saved outputs. The Basic Model Interface (BMI) is implemented on the Model. A Model can be created from the path to a TOML configuration file, or a Config object.

          -

          source

          +

          source

          # Ribasim.OutletType.

          nodeid: node ID of the Outlet node active: whether this node is active and thus contributes flow flowrate: target flow rate minflowrate: The minimal flow rate of the outlet maxflowrate: The maximum flow rate of the outlet controlmapping: dictionary from (nodeid, controlstate) to target flow rate ispid_controlled: whether the flow rate of this outlet is governed by PID control

          -

          source

          +

          source

          # Ribasim.PidControlType.

          PID control currently only supports regulating basin levels.

          nodeid: node ID of the PidControl node active: whether this node is active and thus sets flow rates listennodeid: the id of the basin being controlled pidparams: a vector interpolation for parameters changing over time. The parameters are respectively target, proportional, integral, derivative, where the last three are the coefficients for the PID equation. error: the current error; basintarget - currentlevel

          -

          source

          +

          source

          # Ribasim.PumpType.

          nodeid: node ID of the Pump node active: whether this node is active and thus contributes flow flowrate: target flow rate minflowrate: The minimal flow rate of the pump maxflowrate: The maximum flow rate of the pump controlmapping: dictionary from (nodeid, controlstate) to target flow rate ispid_controlled: whether the flow rate of this pump is governed by PID control

          -

          source

          +

          source

          # Ribasim.TabulatedRatingCurveType.

          struct TabulatedRatingCurve{C}

          Rating curve from level to discharge. The rating curve is a lookup table with linear interpolation in between. Relation can be updated in time, which is done by moving data from the time field into the tables, which is done in the update_tabulated_rating_curve callback.

          Type parameter C indicates the content backing the StructVector, which can be a NamedTuple of Vectors or Arrow Primitives, and is added to avoid type instabilities.

          nodeid: node ID of the TabulatedRatingCurve node active: whether this node is active and thus contributes flows tables: The current Q(h) relationships time: The time table used for updating the tables controlmapping: dictionary from (nodeid, controlstate) to Q(h) and/or active state

          -

          source

          +

          source

          # Ribasim.TerminalType.

          node_id: node ID of the Terminal node

          -

          source

          +

          source

          # Ribasim.UserType.

          demand: water flux demand of user over time active: whether this node is active and thus demands water allocated: water flux currently allocated to user returnfactor: the factor in [0,1] of how much of the abstracted water is given back to the system minlevel: The level of the source basin below which the user does not abstract priority: integer > 0, the lower the number the higher the priority of the users demand

          -

          source

          +

          source

          # Ribasim.config.ConfigMethod.

          Config(config_path::AbstractString; kwargs...)

          Parse a TOML file to a Config. Keys can be overruled using keyword arguments. To overrule keys from a subsection, e.g. dt from the solver section, use underscores: solver_dt.

          -

          source

          +

          source

          @@ -377,69 +377,69 @@

          # BasicModelInterface.finalizeMethod.

          BMI.finalize(model::Model)::Model

          Write all output to the configured output files.

          -

          source

          +

          source

          # BasicModelInterface.initializeMethod.

          BMI.initialize(T::Type{Model}, config_path::AbstractString)::Model

          Initialize a Model from the path to the TOML configuration file.

          -

          source

          +

          source

          # BasicModelInterface.initializeMethod.

          BMI.initialize(T::Type{Model}, config::Config)::Model

          Initialize a Model from a Config.

          -

          source

          +

          source

          # CommonSolve.solve!Method.

          solve!(model::Model)::ODESolution

          Solve a Model until the configured endtime.

          -

          source

          +

          source

          # Ribasim.basin_bottomMethod.

          Return the bottom elevation of the basin with index i, or nothing if it doesn’t exist

          -

          source

          +

          source

          # Ribasim.basin_bottomsMethod.

          Get the bottom on both ends of a node. If only one has a bottom, use that for both.

          -

          source

          +

          source

          # Ribasim.create_callbacksMethod.

          Create the different callbacks that are used to store output and feed the simulation with new data. The different callbacks are combined to a CallbackSet that goes to the integrator. Returns the CallbackSet and the SavedValues for flow.

          -

          source

          +

          source

          # Ribasim.create_graphMethod.

          Return a directed graph, and a mapping from source and target nodes to edge fid.

          -

          source

          +

          source

          # Ribasim.create_storage_tablesMethod.

          Read the Basin / profile table and return all area and level and computed storage values

          -

          source

          +

          source

          # Ribasim.datetime_sinceMethod.

          datetime_since(t::Real, t0::DateTime)::DateTime

          Convert a Real that represents the seconds passed since the simulation start to the nearest DateTime. This is used to convert between the solver’s inner float time, and the calendar.

          -

          source

          +

          source

          # Ribasim.discrete_control_affect!Method.

          Change parameters based on the control logic.

          -

          source

          +

          source

          # Ribasim.discrete_control_affect_downcrossing!Method.

          An downcrossing means that a condition (always greater than) becomes false.

          -

          source

          +

          source

          # Ribasim.discrete_control_affect_upcrossing!Method.

          An upcrossing means that a condition (always greater than) becomes true.

          -

          source

          +

          source

          # Ribasim.discrete_control_conditionMethod.

          Listens for changes in condition truths.

          -

          source

          +

          source

          # Ribasim.expand_logic_mappingMethod.

          Replace the truth states in the logic mapping which contain wildcards with all possible explicit truth states.

          -

          source

          +

          source

          # Ribasim.findlastgroupMethod.

          For an element id and a vector of elements ids, get the range of indices of the last consecutive block of id. Returns the empty range 1:0 if id is not in ids.

          #                         1 2 3 4 5 6 7 8 9
           Ribasim.findlastgroup(2, [5,4,2,2,5,2,2,2,1])
           # output
           6:8
          -

          source

          +

          source

          # Ribasim.findsortedMethod.

          Find the index of element x in a sorted collection a. Returns the index of x if it exists, or nothing if it doesn’t. If x occurs more than once, throw an error.

          -

          source

          +

          source

          # Ribasim.formulate!Method.

          Smoothly let the evaporation flux go to 0 when at small water depths Currently at less than 0.1 m.

          -

          source

          +

          source

          # Ribasim.formulate_flow!Method.

          Directed graph: outflow is positive!

          -

          source

          +

          source

          # Ribasim.formulate_flow!Method.

          Conservation of energy for two basins, a and b:

          h_a + v_a^2 / (2 * g) = h_b + v_b^2 / (2 * g) + S_f * L + C / 2 * g * (v_b^2 - v_a^2)
          @@ -467,126 +467,126 @@

          source

          +

          source

          # Ribasim.formulate_flow!Method.

          Directed graph: outflow is positive!

          -

          source

          +

          source

          # Ribasim.get_area_and_levelMethod.

          Compute the area and level of a basin given its storage. Also returns darea/dlevel as it is needed for the Jacobian.

          -

          source

          +

          source

          # Ribasim.get_compressorMethod.

          Get the compressor based on the Output

          -

          source

          +

          source

          # Ribasim.get_fractional_flow_connected_basinsMethod.

          Get the node type specific indices of the fractional flows and basins, that are consecutively connected to a node of given id.

          -

          source

          +

          source

          # Ribasim.get_jac_prototypeMethod.

          Get a sparse matrix whose sparsity matches the sparsity of the Jacobian of the ODE problem. All nodes are taken into consideration, also the ones that are inactive.

          In Ribasim the Jacobian is typically sparse because each state only depends on a small number of other states.

          Note: the name ‘prototype’ does not mean this code is a prototype, it comes from the naming convention of this sparsity structure in the differentialequations.jl docs.

          -

          source

          +

          source

          # Ribasim.get_levelMethod.

          Get the current water level of a node ID. The ID can belong to either a Basin or a LevelBoundary.

          -

          source

          +

          source

          # Ribasim.get_scalar_interpolationMethod.

          Linear interpolation of a scalar with constant extrapolation.

          -

          source

          +

          source

          # Ribasim.get_storage_from_levelMethod.

          Get the storage of a basin from its level.

          -

          source

          +

          source

          # Ribasim.get_storages_and_levelsMethod.

          Get the storage and level of all basins as matrices of nbasin × ntime

          -

          source

          +

          source

          # Ribasim.get_storages_from_levelsMethod.

          Compute the storages of the basins based on the water level of the basins.

          -

          source

          +

          source

          # Ribasim.get_tstopsMethod.

          From an iterable of DateTimes, find the times the solver needs to stop

          -

          source

          +

          source

          # Ribasim.get_valueMethod.

          Get a value for a condition. Currently supports getting levels from basins and flows from flow boundaries.

          -

          source

          +

          source

          # Ribasim.id_indexMethod.

          Get the index of an ID in a set of indices.

          -

          source

          +

          source

          # Ribasim.input_pathMethod.

          Construct a path relative to both the TOML directory and the optional input_dir

          -

          source

          +

          source

          # Ribasim.load_dataMethod.

          load_data(db::DB, config::Config, nodetype::Symbol, kind::Symbol)::Union{Table, Query, Nothing}

          Load data from Arrow files if available, otherwise the GeoPackage. Returns either an Arrow.Table, SQLite.Query or nothing if the data is not present.

          -

          source

          +

          source

          # Ribasim.load_structvectorMethod.

          load_structvector(db::DB, config::Config, ::Type{T})::StructVector{T}

          Load data from Arrow files if available, otherwise the GeoPackage. Always returns a StructVector of the given struct type T, which is empty if the table is not found. This function validates the schema, and enforces the required sort order.

          -

          source

          +

          source

          # Ribasim.nodefieldsMethod.

          Get all node fieldnames of the parameter object.

          -

          source

          +

          source

          # Ribasim.nodetypeMethod.

          From a SchemaVersion(“ribasim.flowboundary.static”, 1) return (:FlowBoundary, :static)

          -

          source

          +

          source

          # Ribasim.output_pathMethod.

          Construct a path relative to both the TOML directory and the optional output_dir

          -

          source

          +

          source

          # Ribasim.parse_static_and_timeMethod.

          Process the data in the static and time tables for a given node type. The ‘defaults’ named tuple dictates how missing data is filled in. ‘time_interpolatables’ is a vector of Symbols of parameter names for which a time interpolation (linear) object must be constructed. The control mapping for DiscreteControl is also constructed in this function. This function currently does not support node states that are defined by more than one row in a table, as is the case for TabulatedRatingCurve.

          -

          source

          +

          source

          # Ribasim.profile_storageMethod.

          Calculate a profile storage by integrating the areas over the levels

          -

          source

          +

          source

          # Ribasim.qh_interpolationMethod.

          From a table with columns nodeid, discharge (Q) and level (h), create a LinearInterpolation from level to discharge for a given nodeid.

          -

          source

          +

          source

          # Ribasim.reduction_factorMethod.

          Function that goes smoothly from 0 to 1 in the interval [0,threshold], and is constant outside this interval.

          -

          source

          +

          source

          # Ribasim.runMethod.

          run(config_file::AbstractString)::Model
           run(config::Config)::Model

          Run a Model, given a path to a TOML configuration file, or a Config object. Running a model includes initialization, solving to the end with [solve!](@ref) and writing output with BMI.finalize.

          -

          source

          +

          source

          # Ribasim.save_flowMethod.

          Copy the current flow to the SavedValues

          -

          source

          +

          source

          # Ribasim.scalar_interpolation_derivativeMethod.

          Derivative of scalar interpolation.

          -

          source

          +

          source

          # Ribasim.seconds_sinceMethod.

          seconds_since(t::DateTime, t0::DateTime)::Float64

          Convert a DateTime to a float that is the number of seconds since the start of the simulation. This is used to convert between the solver’s inner float time, and the calendar.

          -

          source

          +

          source

          # Ribasim.set_current_value!Method.

          From a timeseries table time, load the most recent applicable data into table. table must be a NamedTuple of vectors with all variables that must be loaded. The most recent applicable data is non-NaN data for a given ID that is on or before t.

          -

          source

          +

          source

          # Ribasim.set_static_value!Method.

          Load data from a source table static into a destination table. Data is matched based on the node_id, which is sorted.

          -

          source

          +

          source

          # Ribasim.set_table_row!Method.

          Update table at row index i, with the values of a given row. table must be a NamedTuple of vectors with all variables that must be loaded. The row must contain all the column names that are present in the table. If a value is NaN, it is not set.

          -

          source

          +

          source

          # Ribasim.sorted_table!Method.

          Depending on if a table can be sorted, either sort it or assert that it is sorted.

          Tables loaded from GeoPackage into memory can be sorted. Tables loaded from Arrow files are memory mapped and can therefore not be sorted.

          -

          source

          +

          source

          # Ribasim.update_basinMethod.

          Load updates from ‘Basin / time’ into the parameters

          -

          source

          +

          source

          # Ribasim.update_jac_prototype!Method.

          Method for nodes that do not contribute to the Jacobian

          -

          source

          +

          source

          # Ribasim.update_jac_prototype!Method.

          The controlled basin affects itself and the basins upstream and downstream of the controlled pump affect eachother if there is a basin upstream of the pump. The state for the integral term and the controlled basin affect eachother, and the same for the integral state and the basin upstream of the pump if it is indeed a basin.

          -

          source

          +

          source

          # Ribasim.update_jac_prototype!Method.

          If both the unique node upstream and the unique node downstream of these nodes are basins, then these directly depend on eachother and affect the Jacobian 2x Basins always depend on themselves.

          -

          source

          -

          # Ribasim.update_jac_prototype!Method.

          +

          source

          +

          # Ribasim.update_jac_prototype!Method.

          If both the unique node upstream and the nodes down stream (or one node further if a fractional flow is in between) are basins, then the downstream basin depends on the upstream basin(s) and affect the Jacobian as many times as there are downstream basins Upstream basins always depend on themselves.

          -

          source

          +

          source

          # Ribasim.update_tabulated_rating_curve!Method.

          Load updates from ‘TabulatedRatingCurve / time’ into the parameters

          -

          source

          +

          source

          # Ribasim.valid_discrete_controlMethod.

          Check:

            @@ -594,35 +594,35 @@

            source

            +

            source

            # Ribasim.valid_edge_typesMethod.

            Check that only supported edge types are declared.

            -

            source

            +

            source

            # Ribasim.valid_edgesMethod.

            Test for each node given its node type whether the nodes that

            are downstream (‘down-edge’) of this node are of an allowed type

            -

            source

            +

            source

            # Ribasim.valid_flow_ratesMethod.

            Test whether static or discrete controlled flow rates are indeed non-negative.

            -

            source

            +

            source

            # Ribasim.valid_fractional_flowMethod.

            Check that nodes that have fractional flow outneighbors do not have any other type of outneighbor, that the fractions leaving a node add up to ≈1 and that the fractions are non-negative.

            -

            source

            +

            source

            # Ribasim.valid_n_neighborsMethod.

            Test for each node given its node type whether it has an allowed number of flow/control inneighbors and outneighbors

            -

            source

            +

            source

            # Ribasim.valid_profilesMethod.

            Check whether the profile data has no repeats in the levels and the areas start positive.

            -

            source

            +

            source

            # Ribasim.water_balance!Method.

            The right hand side function of the system of ODEs set up by Ribasim.

            -

            source

            +

            source

            # Ribasim.config.algorithmMethod.

            Create an OrdinaryDiffEqAlgorithm from solver config

            -

            source

            +

            source

            # Ribasim.config.snake_caseMethod.

            Convert a string from CamelCase to snake_case.

            -

            source

            +

            source

            @@ -643,7 +643,7 @@

            source

            +

            source

            @@ -651,10 +651,10 @@

            1.5 Macros

            # Ribasim.config.@addfieldsMacro.

            Add fieldnames with Maybe{String} type to struct expression. Requires (option?) use before it.

            -

            source

            +

            source

            # Ribasim.config.@addnodetypesMacro.

            Add all TableOption subtypes as fields to struct expression. Requires (option?) use before it.

            -

            source

            +

            source

            @@ -702,8 +702,8 @@

            Ribasim.findsorted
          • Ribasim.formulate!
          • Ribasim.formulate_flow!
          • -
          • Ribasim.formulate_flow!
          • Ribasim.formulate_flow!
          • +
          • Ribasim.formulate_flow!
          • Ribasim.get_area_and_level
          • Ribasim.get_compressor
          • Ribasim.get_fractional_flow_connected_basins
          • @@ -735,10 +735,10 @@

            Ribasim.set_table_row!
          • Ribasim.sorted_table!
          • Ribasim.update_basin
          • -
          • Ribasim.update_jac_prototype!
          • -
          • Ribasim.update_jac_prototype!
          • -
          • Ribasim.update_jac_prototype!
          • Ribasim.update_jac_prototype!
          • +
          • Ribasim.update_jac_prototype!
          • +
          • Ribasim.update_jac_prototype!
          • +
          • Ribasim.update_jac_prototype!
          • Ribasim.update_tabulated_rating_curve!
          • Ribasim.valid_discrete_control
          • Ribasim.valid_edge_types
          • diff --git a/python/examples.html b/python/examples.html index afef88f58..d841bb6fc 100644 --- a/python/examples.html +++ b/python/examples.html @@ -548,7 +548,7 @@

            2 Update the basi ax = df_flow.pivot_table(index="time", columns="edge", values="flow_m3d").plot() ax.legend(bbox_to_anchor=(1.3, 1), title="Edge")
            -
            <matplotlib.legend.Legend at 0x7f9e8848a690>
            +
            <matplotlib.legend.Legend at 0x7faf64498690>

            diff --git a/search.json b/search.json index 84e65269a..c3cfb5224 100644 --- a/search.json +++ b/search.json @@ -1,318 +1,353 @@ [ { - "objectID": "python/reference/LinearResistance.html", - "href": "python/reference/LinearResistance.html", - "title": "1 LinearResistance", - "section": "", - "text": "LinearResistance()\nFlow through this connection linearly depends on the level difference between the two connected basins.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with the constant resistances.\nrequired" + "objectID": "build/index.html#modules", + "href": "build/index.html#modules", + "title": "1 API Reference", + "section": "1.1 Modules", + "text": "1.1 Modules\n# Ribasim.Ribasim — Module.\nmodule Ribasim\nRibasim is a water resources model. The computational core is implemented in Julia in the Ribasim package. It is currently mainly designed to be used as an application. To run a simulation from Julia, use Ribasim.run.\nFor more granular access, see:\n\nConfig\nModel\nsolve!\nBMI.finalize\n\nsource\n# Ribasim.config — Module.\nmodule config\nRibasim.config is a submodule of Ribasim to handle the configuration of a Ribasim model. It is implemented using the Configurations package. A full configuration is represented by Config, which is the main API. Ribasim.config is a submodule mainly to avoid name clashes between the configuration sections and the rest of Ribasim.\nsource" }, { - "objectID": "python/reference/LinearResistance.html#parameters", - "href": "python/reference/LinearResistance.html#parameters", - "title": "1 LinearResistance", - "section": "", - "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with the constant resistances.\nrequired" + "objectID": "build/index.html#types", + "href": "build/index.html#types", + "title": "1 API Reference", + "section": "1.2 Types", + "text": "1.2 Types\n# Ribasim.Basin — Type.\nRequirements:\n\nMust be positive: precipitation, evaporation, infiltration, drainage\nIndex points to a Basin\nvolume, area, level must all be positive and monotonic increasing.\n\nType parameter C indicates the content backing the StructVector, which can be a NamedTuple of vectors or Arrow Tables, and is added to avoid type instabilities. The nodeid are Indices to support fast lookup of e.g. currentlevel using ID.\nif autodiff T = DiffCache{Vector{Float64}} else T = Vector{Float64} end\nsource\n# Ribasim.Connectivity — Type.\nStore the connectivity information\ngraphflow, graphcontrol: directed graph with vertices equal to ids flow: store the flow on every flow edge edgeidsflow, edgeidscontrol: get the external edge id from (src, dst) edgeconnectiontypeflow, edgeconnectiontypescontrol: get (srcnodetype, dstnodetype) from edge id\nif autodiff T = DiffCache{SparseArrays.SparseMatrixCSC{Float64, Int64}, Vector{Float64}} else T = SparseMatrixCSC{Float64, Int} end\nsource\n# Ribasim.DiscreteControl — Type.\nnodeid: node ID of the DiscreteControl node; these are not unique but repeated by the amount of conditions of this DiscreteControl node listenfeatureid: the ID of the node/edge being condition on variable: the name of the variable in the condition greaterthan: The threshold value in the condition conditionvalue: The current value of each condition controlstate: Dictionary: node ID => (control state, control state start) logic_mapping: Dictionary: (control node ID, truth state) => control state record: Namedtuple with discrete control information for output\nsource\n# Ribasim.FlatVector — Type.\nstruct FlatVector{T} <: AbstractVector{T}\nA FlatVector is an AbstractVector that iterates the T of a Vector{Vector{T}}.\nEach inner vector is assumed to be of equal length.\nIt is similar to Iterators.flatten, though that doesn’t work with the Tables.Column interface, which needs length and getindex support.\nsource\n# Ribasim.FlowBoundary — Type.\nnodeid: node ID of the FlowBoundary node active: whether this node is active and thus contributes flow flowrate: target flow rate\nsource\n# Ribasim.FractionalFlow — Type.\nRequirements:\n\nfrom: must be (TabulatedRatingCurve,) node\nto: must be (Basin,) node\nfraction must be positive.\n\nnodeid: node ID of the TabulatedRatingCurve node fraction: The fraction in [0,1] of flow the node lets through controlmapping: dictionary from (nodeid, controlstate) to fraction\nsource\n# Ribasim.LevelBoundary — Type.\nnode_id: node ID of the LevelBoundary node active: whether this node is active level: the fixed level of this ‘infinitely big basin’\nsource\n# Ribasim.LinearResistance — Type.\nRequirements:\n\nfrom: must be (Basin,) node\nto: must be (Basin,) node\n\nnodeid: node ID of the LinearResistance node active: whether this node is active and thus contributes flows resistance: the resistance to flow; Q = Δh/resistance controlmapping: dictionary from (nodeid, controlstate) to resistance and/or active state\nsource\n# Ribasim.ManningResistance — Type.\nThis is a simple Manning-Gauckler reach connection.\n\nLength describes the reach length.\nroughness describes Manning’s n in (SI units).\n\nThe profile is described by a trapezoid:\n \\ / ^\n \\ / |\n \\ / | dz\nbottom \\______/ |\n^ <--->\n| dy\n| <------>\n| width\n|\n|\n+ datum (e.g. MSL)\nWith profile_slope = dy / dz. A rectangular profile requires a slope of 0.0.\nRequirements:\n\nfrom: must be (Basin,) node\nto: must be (Basin,) node\nlength > 0\nroughess > 0\nprofile_width >= 0\nprofile_slope >= 0\n(profilewidth == 0) xor (profileslope == 0)\n\nsource\n# Ribasim.Model — Type.\nModel(config_path::AbstractString)\nModel(config::Config)\nInitialize a Model.\nThe Model struct is an initialized model, combined with the Config used to create it and saved outputs. The Basic Model Interface (BMI) is implemented on the Model. A Model can be created from the path to a TOML configuration file, or a Config object.\nsource\n# Ribasim.Outlet — Type.\nnodeid: node ID of the Outlet node active: whether this node is active and thus contributes flow flowrate: target flow rate minflowrate: The minimal flow rate of the outlet maxflowrate: The maximum flow rate of the outlet controlmapping: dictionary from (nodeid, controlstate) to target flow rate ispid_controlled: whether the flow rate of this outlet is governed by PID control\nsource\n# Ribasim.PidControl — Type.\nPID control currently only supports regulating basin levels.\nnodeid: node ID of the PidControl node active: whether this node is active and thus sets flow rates listennodeid: the id of the basin being controlled pidparams: a vector interpolation for parameters changing over time. The parameters are respectively target, proportional, integral, derivative, where the last three are the coefficients for the PID equation. error: the current error; basintarget - currentlevel\nsource\n# Ribasim.Pump — Type.\nnodeid: node ID of the Pump node active: whether this node is active and thus contributes flow flowrate: target flow rate minflowrate: The minimal flow rate of the pump maxflowrate: The maximum flow rate of the pump controlmapping: dictionary from (nodeid, controlstate) to target flow rate ispid_controlled: whether the flow rate of this pump is governed by PID control\nsource\n# Ribasim.TabulatedRatingCurve — Type.\nstruct TabulatedRatingCurve{C}\nRating curve from level to discharge. The rating curve is a lookup table with linear interpolation in between. Relation can be updated in time, which is done by moving data from the time field into the tables, which is done in the update_tabulated_rating_curve callback.\nType parameter C indicates the content backing the StructVector, which can be a NamedTuple of Vectors or Arrow Primitives, and is added to avoid type instabilities.\nnodeid: node ID of the TabulatedRatingCurve node active: whether this node is active and thus contributes flows tables: The current Q(h) relationships time: The time table used for updating the tables controlmapping: dictionary from (nodeid, controlstate) to Q(h) and/or active state\nsource\n# Ribasim.Terminal — Type.\nnode_id: node ID of the Terminal node\nsource\n# Ribasim.User — Type.\ndemand: water flux demand of user over time active: whether this node is active and thus demands water allocated: water flux currently allocated to user returnfactor: the factor in [0,1] of how much of the abstracted water is given back to the system minlevel: The level of the source basin below which the user does not abstract priority: integer > 0, the lower the number the higher the priority of the users demand\nsource\n# Ribasim.config.Config — Method.\nConfig(config_path::AbstractString; kwargs...)\nParse a TOML file to a Config. Keys can be overruled using keyword arguments. To overrule keys from a subsection, e.g. dt from the solver section, use underscores: solver_dt.\nsource" }, { - "objectID": "python/reference/LevelBoundary.html", - "href": "python/reference/LevelBoundary.html", - "title": "1 LevelBoundary", - "section": "", - "text": "LevelBoundary()\nStores water at a given level unaffected by flow, like an infinitely large basin.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable with the constant water levels.\nrequired" + "objectID": "build/index.html#functions", + "href": "build/index.html#functions", + "title": "1 API Reference", + "section": "1.3 Functions", + "text": "1.3 Functions\n# BasicModelInterface.finalize — Method.\nBMI.finalize(model::Model)::Model\nWrite all output to the configured output files.\nsource\n# BasicModelInterface.initialize — Method.\nBMI.initialize(T::Type{Model}, config_path::AbstractString)::Model\nInitialize a Model from the path to the TOML configuration file.\nsource\n# BasicModelInterface.initialize — Method.\nBMI.initialize(T::Type{Model}, config::Config)::Model\nInitialize a Model from a Config.\nsource\n# CommonSolve.solve! — Method.\nsolve!(model::Model)::ODESolution\nSolve a Model until the configured endtime.\nsource\n# Ribasim.basin_bottom — Method.\nReturn the bottom elevation of the basin with index i, or nothing if it doesn’t exist\nsource\n# Ribasim.basin_bottoms — Method.\nGet the bottom on both ends of a node. If only one has a bottom, use that for both.\nsource\n# Ribasim.create_callbacks — Method.\nCreate the different callbacks that are used to store output and feed the simulation with new data. The different callbacks are combined to a CallbackSet that goes to the integrator. Returns the CallbackSet and the SavedValues for flow.\nsource\n# Ribasim.create_graph — Method.\nReturn a directed graph, and a mapping from source and target nodes to edge fid.\nsource\n# Ribasim.create_storage_tables — Method.\nRead the Basin / profile table and return all area and level and computed storage values\nsource\n# Ribasim.datetime_since — Method.\ndatetime_since(t::Real, t0::DateTime)::DateTime\nConvert a Real that represents the seconds passed since the simulation start to the nearest DateTime. This is used to convert between the solver’s inner float time, and the calendar.\nsource\n# Ribasim.discrete_control_affect! — Method.\nChange parameters based on the control logic.\nsource\n# Ribasim.discrete_control_affect_downcrossing! — Method.\nAn downcrossing means that a condition (always greater than) becomes false.\nsource\n# Ribasim.discrete_control_affect_upcrossing! — Method.\nAn upcrossing means that a condition (always greater than) becomes true.\nsource\n# Ribasim.discrete_control_condition — Method.\nListens for changes in condition truths.\nsource\n# Ribasim.expand_logic_mapping — Method.\nReplace the truth states in the logic mapping which contain wildcards with all possible explicit truth states.\nsource\n# Ribasim.findlastgroup — Method.\nFor an element id and a vector of elements ids, get the range of indices of the last consecutive block of id. Returns the empty range 1:0 if id is not in ids.\n# 1 2 3 4 5 6 7 8 9\nRibasim.findlastgroup(2, [5,4,2,2,5,2,2,2,1])\n# output\n6:8\nsource\n# Ribasim.findsorted — Method.\nFind the index of element x in a sorted collection a. Returns the index of x if it exists, or nothing if it doesn’t. If x occurs more than once, throw an error.\nsource\n# Ribasim.formulate! — Method.\nSmoothly let the evaporation flux go to 0 when at small water depths Currently at less than 0.1 m.\nsource\n# Ribasim.formulate_flow! — Method.\nDirected graph: outflow is positive!\nsource\n# Ribasim.formulate_flow! — Method.\nConservation of energy for two basins, a and b:\nh_a + v_a^2 / (2 * g) = h_b + v_b^2 / (2 * g) + S_f * L + C / 2 * g * (v_b^2 - v_a^2)\nWhere:\n\nha, hb are the heads at basin a and b.\nva, vb are the velocities at basin a and b.\ng is the gravitational constant.\nS_f is the friction slope.\nC is an expansion or extraction coefficient.\n\nWe assume velocity differences are negligible (va = vb):\nh_a = h_b + S_f * L\nThe friction losses are approximated by the Gauckler-Manning formula:\nQ = A * (1 / n) * R_h^(2/3) * S_f^(1/2)\nWhere:\n\nWhere A is the cross-sectional area.\nV is the cross-sectional average velocity.\nn is the Gauckler-Manning coefficient.\nR_h is the hydraulic radius.\nS_f is the friction slope.\n\nThe hydraulic radius is defined as:\nR_h = A / P\nWhere P is the wetted perimeter.\nThe average of the upstream and downstream water depth is used to compute cross-sectional area and hydraulic radius. This ensures that a basin can receive water after it has gone dry.\nsource\n# Ribasim.formulate_flow! — Method.\nDirected graph: outflow is positive!\nsource\n# Ribasim.get_area_and_level — Method.\nCompute the area and level of a basin given its storage. Also returns darea/dlevel as it is needed for the Jacobian.\nsource\n# Ribasim.get_compressor — Method.\nGet the compressor based on the Output\nsource\n# Ribasim.get_fractional_flow_connected_basins — Method.\nGet the node type specific indices of the fractional flows and basins, that are consecutively connected to a node of given id.\nsource\n# Ribasim.get_jac_prototype — Method.\nGet a sparse matrix whose sparsity matches the sparsity of the Jacobian of the ODE problem. All nodes are taken into consideration, also the ones that are inactive.\nIn Ribasim the Jacobian is typically sparse because each state only depends on a small number of other states.\nNote: the name ‘prototype’ does not mean this code is a prototype, it comes from the naming convention of this sparsity structure in the differentialequations.jl docs.\nsource\n# Ribasim.get_level — Method.\nGet the current water level of a node ID. The ID can belong to either a Basin or a LevelBoundary.\nsource\n# Ribasim.get_scalar_interpolation — Method.\nLinear interpolation of a scalar with constant extrapolation.\nsource\n# Ribasim.get_storage_from_level — Method.\nGet the storage of a basin from its level.\nsource\n# Ribasim.get_storages_and_levels — Method.\nGet the storage and level of all basins as matrices of nbasin × ntime\nsource\n# Ribasim.get_storages_from_levels — Method.\nCompute the storages of the basins based on the water level of the basins.\nsource\n# Ribasim.get_tstops — Method.\nFrom an iterable of DateTimes, find the times the solver needs to stop\nsource\n# Ribasim.get_value — Method.\nGet a value for a condition. Currently supports getting levels from basins and flows from flow boundaries.\nsource\n# Ribasim.id_index — Method.\nGet the index of an ID in a set of indices.\nsource\n# Ribasim.input_path — Method.\nConstruct a path relative to both the TOML directory and the optional input_dir\nsource\n# Ribasim.load_data — Method.\nload_data(db::DB, config::Config, nodetype::Symbol, kind::Symbol)::Union{Table, Query, Nothing}\nLoad data from Arrow files if available, otherwise the GeoPackage. Returns either an Arrow.Table, SQLite.Query or nothing if the data is not present.\nsource\n# Ribasim.load_structvector — Method.\nload_structvector(db::DB, config::Config, ::Type{T})::StructVector{T}\nLoad data from Arrow files if available, otherwise the GeoPackage. Always returns a StructVector of the given struct type T, which is empty if the table is not found. This function validates the schema, and enforces the required sort order.\nsource\n# Ribasim.nodefields — Method.\nGet all node fieldnames of the parameter object.\nsource\n# Ribasim.nodetype — Method.\nFrom a SchemaVersion(“ribasim.flowboundary.static”, 1) return (:FlowBoundary, :static)\nsource\n# Ribasim.output_path — Method.\nConstruct a path relative to both the TOML directory and the optional output_dir\nsource\n# Ribasim.parse_static_and_time — Method.\nProcess the data in the static and time tables for a given node type. The ‘defaults’ named tuple dictates how missing data is filled in. ‘time_interpolatables’ is a vector of Symbols of parameter names for which a time interpolation (linear) object must be constructed. The control mapping for DiscreteControl is also constructed in this function. This function currently does not support node states that are defined by more than one row in a table, as is the case for TabulatedRatingCurve.\nsource\n# Ribasim.profile_storage — Method.\nCalculate a profile storage by integrating the areas over the levels\nsource\n# Ribasim.qh_interpolation — Method.\nFrom a table with columns nodeid, discharge (Q) and level (h), create a LinearInterpolation from level to discharge for a given nodeid.\nsource\n# Ribasim.reduction_factor — Method.\nFunction that goes smoothly from 0 to 1 in the interval [0,threshold], and is constant outside this interval.\nsource\n# Ribasim.run — Method.\nrun(config_file::AbstractString)::Model\nrun(config::Config)::Model\nRun a Model, given a path to a TOML configuration file, or a Config object. Running a model includes initialization, solving to the end with [solve!](@ref) and writing output with BMI.finalize.\nsource\n# Ribasim.save_flow — Method.\nCopy the current flow to the SavedValues\nsource\n# Ribasim.scalar_interpolation_derivative — Method.\nDerivative of scalar interpolation.\nsource\n# Ribasim.seconds_since — Method.\nseconds_since(t::DateTime, t0::DateTime)::Float64\nConvert a DateTime to a float that is the number of seconds since the start of the simulation. This is used to convert between the solver’s inner float time, and the calendar.\nsource\n# Ribasim.set_current_value! — Method.\nFrom a timeseries table time, load the most recent applicable data into table. table must be a NamedTuple of vectors with all variables that must be loaded. The most recent applicable data is non-NaN data for a given ID that is on or before t.\nsource\n# Ribasim.set_static_value! — Method.\nLoad data from a source table static into a destination table. Data is matched based on the node_id, which is sorted.\nsource\n# Ribasim.set_table_row! — Method.\nUpdate table at row index i, with the values of a given row. table must be a NamedTuple of vectors with all variables that must be loaded. The row must contain all the column names that are present in the table. If a value is NaN, it is not set.\nsource\n# Ribasim.sorted_table! — Method.\nDepending on if a table can be sorted, either sort it or assert that it is sorted.\nTables loaded from GeoPackage into memory can be sorted. Tables loaded from Arrow files are memory mapped and can therefore not be sorted.\nsource\n# Ribasim.update_basin — Method.\nLoad updates from ‘Basin / time’ into the parameters\nsource\n# Ribasim.update_jac_prototype! — Method.\nMethod for nodes that do not contribute to the Jacobian\nsource\n# Ribasim.update_jac_prototype! — Method.\nThe controlled basin affects itself and the basins upstream and downstream of the controlled pump affect eachother if there is a basin upstream of the pump. The state for the integral term and the controlled basin affect eachother, and the same for the integral state and the basin upstream of the pump if it is indeed a basin.\nsource\n# Ribasim.update_jac_prototype! — Method.\nIf both the unique node upstream and the unique node downstream of these nodes are basins, then these directly depend on eachother and affect the Jacobian 2x Basins always depend on themselves.\nsource\n# Ribasim.update_jac_prototype! — Method.\nIf both the unique node upstream and the nodes down stream (or one node further if a fractional flow is in between) are basins, then the downstream basin depends on the upstream basin(s) and affect the Jacobian as many times as there are downstream basins Upstream basins always depend on themselves.\nsource\n# Ribasim.update_tabulated_rating_curve! — Method.\nLoad updates from ‘TabulatedRatingCurve / time’ into the parameters\nsource\n# Ribasim.valid_discrete_control — Method.\nCheck:\n\nwhether control states are defined for discrete controlled nodes;\nWhether the supplied truth states have the proper length;\nWhether look_ahead is only supplied for condition variables given by a time-series.\n\nsource\n# Ribasim.valid_edge_types — Method.\nCheck that only supported edge types are declared.\nsource\n# Ribasim.valid_edges — Method.\nTest for each node given its node type whether the nodes that\nare downstream (‘down-edge’) of this node are of an allowed type\nsource\n# Ribasim.valid_flow_rates — Method.\nTest whether static or discrete controlled flow rates are indeed non-negative.\nsource\n# Ribasim.valid_fractional_flow — Method.\nCheck that nodes that have fractional flow outneighbors do not have any other type of outneighbor, that the fractions leaving a node add up to ≈1 and that the fractions are non-negative.\nsource\n# Ribasim.valid_n_neighbors — Method.\nTest for each node given its node type whether it has an allowed number of flow/control inneighbors and outneighbors\nsource\n# Ribasim.valid_profiles — Method.\nCheck whether the profile data has no repeats in the levels and the areas start positive.\nsource\n# Ribasim.water_balance! — Method.\nThe right hand side function of the system of ODEs set up by Ribasim.\nsource\n# Ribasim.config.algorithm — Method.\nCreate an OrdinaryDiffEqAlgorithm from solver config\nsource\n# Ribasim.config.snake_case — Method.\nConvert a string from CamelCase to snake_case.\nsource" }, { - "objectID": "python/reference/LevelBoundary.html#parameters", - "href": "python/reference/LevelBoundary.html#parameters", - "title": "1 LevelBoundary", - "section": "", - "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable with the constant water levels.\nrequired" + "objectID": "build/index.html#constants", + "href": "build/index.html#constants", + "title": "1 API Reference", + "section": "1.4 Constants", + "text": "1.4 Constants\n# Ribasim.config.algorithms — Constant.\nconst algorithms::Dict{String, Type}\nMap from config string to a supported algorithm type from OrdinaryDiffEq.\nSupported algorithms:\n\nQNDF\nRosenbrock23\nTRBDF2\nRodas5\nKenCarp4\nTsit5\nRK4\nImplicitEuler\nEuler\n\nsource" }, { - "objectID": "python/reference/ManningResistance.html", - "href": "python/reference/ManningResistance.html", - "title": "1 ManningResistance", - "section": "", - "text": "ManningResistance()\nFlow through this connection is estimated by conservation of energy and the Manning-Gauckler formula to estimate friction losses.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with the constant Manning parameters.\nrequired" + "objectID": "build/index.html#macros", + "href": "build/index.html#macros", + "title": "1 API Reference", + "section": "1.5 Macros", + "text": "1.5 Macros\n# Ribasim.config.@addfields — Macro.\nAdd fieldnames with Maybe{String} type to struct expression. Requires (option?) use before it.\nsource\n# Ribasim.config.@addnodetypes — Macro.\nAdd all TableOption subtypes as fields to struct expression. Requires (option?) use before it.\nsource" }, { - "objectID": "python/reference/ManningResistance.html#parameters", - "href": "python/reference/ManningResistance.html#parameters", - "title": "1 ManningResistance", - "section": "", - "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with the constant Manning parameters.\nrequired" + "objectID": "build/index.html#index", + "href": "build/index.html#index", + "title": "1 API Reference", + "section": "1.6 Index", + "text": "1.6 Index\n\nRibasim.Ribasim\nRibasim.config\nRibasim.config.algorithms\nRibasim.Basin\nRibasim.Connectivity\nRibasim.DiscreteControl\nRibasim.FlatVector\nRibasim.FlowBoundary\nRibasim.FractionalFlow\nRibasim.LevelBoundary\nRibasim.LinearResistance\nRibasim.ManningResistance\nRibasim.Model\nRibasim.Outlet\nRibasim.PidControl\nRibasim.Pump\nRibasim.TabulatedRatingCurve\nRibasim.Terminal\nRibasim.User\nRibasim.config.Config\nBasicModelInterface.finalize\nBasicModelInterface.initialize\nBasicModelInterface.initialize\nCommonSolve.solve!\nRibasim.basin_bottom\nRibasim.basin_bottoms\nRibasim.config.algorithm\nRibasim.config.snake_case\nRibasim.create_callbacks\nRibasim.create_graph\nRibasim.create_storage_tables\nRibasim.datetime_since\nRibasim.discrete_control_affect!\nRibasim.discrete_control_affect_downcrossing!\nRibasim.discrete_control_affect_upcrossing!\nRibasim.discrete_control_condition\nRibasim.expand_logic_mapping\nRibasim.findlastgroup\nRibasim.findsorted\nRibasim.formulate!\nRibasim.formulate_flow!\nRibasim.formulate_flow!\nRibasim.formulate_flow!\nRibasim.get_area_and_level\nRibasim.get_compressor\nRibasim.get_fractional_flow_connected_basins\nRibasim.get_jac_prototype\nRibasim.get_level\nRibasim.get_scalar_interpolation\nRibasim.get_storage_from_level\nRibasim.get_storages_and_levels\nRibasim.get_storages_from_levels\nRibasim.get_tstops\nRibasim.get_value\nRibasim.id_index\nRibasim.input_path\nRibasim.load_data\nRibasim.load_structvector\nRibasim.nodefields\nRibasim.nodetype\nRibasim.output_path\nRibasim.parse_static_and_time\nRibasim.profile_storage\nRibasim.qh_interpolation\nRibasim.reduction_factor\nRibasim.run\nRibasim.save_flow\nRibasim.scalar_interpolation_derivative\nRibasim.seconds_since\nRibasim.set_current_value!\nRibasim.set_static_value!\nRibasim.set_table_row!\nRibasim.sorted_table!\nRibasim.update_basin\nRibasim.update_jac_prototype!\nRibasim.update_jac_prototype!\nRibasim.update_jac_prototype!\nRibasim.update_jac_prototype!\nRibasim.update_tabulated_rating_curve!\nRibasim.valid_discrete_control\nRibasim.valid_edge_types\nRibasim.valid_edges\nRibasim.valid_flow_rates\nRibasim.valid_fractional_flow\nRibasim.valid_n_neighbors\nRibasim.valid_profiles\nRibasim.water_balance!\nRibasim.config.@addfields\nRibasim.config.@addnodetypes" }, { - "objectID": "python/reference/Model.html", - "href": "python/reference/Model.html", - "title": "1 Model", + "objectID": "couple/modflow-demo.html", + "href": "couple/modflow-demo.html", + "title": "MODFLOW 6 Demonstration", "section": "", - "text": "Model()\nA full Ribasim model schematisation with all input.\nRibasim model containing the location of the nodes, the edges between the nodes, and the node parametrization.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nmodelname\nstr\nModel name, used in TOML and GeoPackage file name.\nrequired\n\n\nnode\nNode\nThe ID, type and geometry of each node.\nrequired\n\n\nedge\nEdge\nHow the nodes are connected.\nrequired\n\n\nbasin\nBasin\nThe waterbodies.\nrequired\n\n\nfractional_flow\nOptional[FractionalFlow]\nSplit flows into fractions.\nrequired\n\n\nlevel_boundary\nOptional[LevelBoundary]\nBoundary condition specifying the water level.\nrequired\n\n\nflow_boundary\nOptional[FlowBoundary]\nBoundary conditions specifying the flow.\nrequired\n\n\nlinear_resistance\n\nLinear flow resistance.\nrequired\n\n\nmanning_resistance\nOptional[ManningResistance]\nFlow resistance based on the Manning formula.\nrequired\n\n\ntabulated_rating_curve\nOptional[TabulatedRatingCurve]\nTabulated rating curve describing flow based on the upstream water level.\nrequired\n\n\npump\nOptional[Pump]\nPrescribed flow rate from one basin to the other.\nrequired\n\n\noutlet\nOptional[Outlet]\nPrescribed flow rate from one basin to the other.\nrequired\n\n\nterminal\nOptional[Terminal]\nWater sink without state or properties.\nrequired\n\n\ndiscrete_control\nOptional[DiscreteControl]\nDiscrete control logic.\nrequired\n\n\npid_control\nOptional[PidControl]\nPID controller attempting to set the level of a basin to a desired value using a pump/outlet.\nrequired\n\n\nuser\nOptional[User]\nUser node type with demand and priority.\nrequired\n\n\nstarttime\nUnion[str, datetime.datetime]\nStarting time of the simulation.\nrequired\n\n\nendtime\nUnion[str, datetime.datetime]\nEnd time of the simulation.\nrequired\n\n\nsolver\nOptional[Solver]\nSolver settings.\nrequired\n\n\nlogging\nOptional[logging]\nLogging settings.\nrequired\n\n\n\n\n\n\n\n\n\nName\nDescription\n\n\n\n\nfields\nReturn the names of the fields contained in the Model.\n\n\nfrom_toml\nInitialize a model from the TOML configuration file.\n\n\nplot\nPlot the nodes and edges of the model.\n\n\nsort\nSort all input tables as required.\n\n\nvalidate_model\nValidate the model.\n\n\nvalidate_model_node_IDs\nCheck whether the node IDs in the node field correspond to the node IDs on the node type fields.\n\n\nvalidate_model_node_field_IDs\nCheck whether the node IDs of the node_type fields are valid.\n\n\nvalidate_model_node_types\nCheck whether all node types in the node field are valid.\n\n\nwrite\nWrite the contents of the model to a GeoPackage and a TOML configuration file.\n\n\n\n\n\nModel.fields(cls)\nReturn the names of the fields contained in the Model.\n\n\n\nModel.from_toml(path)\nInitialize a model from the TOML configuration file.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\npath\nFilePath\nPath to the configuration TOML file.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nModel\n\n\n\n\n\n\n\n\nModel.plot(self, ax=None)\nPlot the nodes and edges of the model.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nmatplotlib.pyplot.Artist\nAxes on which to draw the plot.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nmatplotlib.pyplot.Artist\n\n\n\n\n\n\n\n\nModel.sort(self)\nSort all input tables as required.\nTables are sorted by “node_id”, unless otherwise specified. Sorting is done automatically before writing the table.\n\n\n\nModel.validate_model(self)\nValidate the model.\nChecks: - Whether all node types in the node field are valid - Whether the node IDs of the node_type fields are valid - Whether the node IDs in the node field correspond to the node IDs on the node type fields\n\n\n\nModel.validate_model_node_IDs(self)\nCheck whether the node IDs in the node field correspond to the node IDs on the node type fields.\n\n\n\nModel.validate_model_node_field_IDs(self)\nCheck whether the node IDs of the node_type fields are valid.\n\n\n\nModel.validate_model_node_types(self)\nCheck whether all node types in the node field are valid.\n\n\n\nModel.write(self, directory)\nWrite the contents of the model to a GeoPackage and a TOML configuration file.\nIf directory does not exist, it is created before writing. The GeoPackage and TOML file will be called {modelname}.gpkg and {modelname}.toml respectively.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\ndirectory\nFilePath\n\nrequired" + "text": "In our test cases, we will work with the Dutch national groundwater model (LHM). For testing, we simplify the top boundary conditions to just three surface water “systems”, from large to small:\n\nPrimary\nSecondary\nTertiary\n\nThese systems are separated in the groundwater model schematization due to the relatively small size of the surface waters (several meters in width) in comparison with relatively coarse cells of the groundwater model (250 m): most cells in the LHM contain more than one surface water, and are included via representative parameters which take the physical scaling into account. In case of sufficiently small cell sizes, no overlap occurs and all surface waters can be represented in a single grid.\nRibasim has no knowledge of these systems unless explicitly separated into different (sub-)basins. In the examples below, these three systems are represented by one basin with a single volume. For the purposes of testing, we have not yet created empirical volume-level relationships for the surface water of every cell extracted from a hydraulic model. Instead, we have chosen an (over)simplified parametrization of the hydraulic properties, which makes it easy to verify the behavior of Ribasim and the coupling procedures:\n\nPrimary, secondary, and tertiary systems have a rectangular profile: the area of the surface water does not change with water level or volume.\nWater depth is constant for a system within a basin.\nWater depth increases linearly with volume. (This follows from 1.)\nWater depth is distributed across the systems in a geometric progression: when the tertiary system contains 0.1 m of water, the secondary system contains 0.2 m, and the primary system contains 0.4 m.\nWater depth is 0.0 m for all systems when basin volume is 0.0. (This follows from 4.)\nThe distribution of water occurs according to the surface water area (width times length) used for the LHM parametrization of river bed conductance.\n\nFor freely draining basins, a simplified storage-discharge relationship has been derived as follows:\n\nThe area of the basin polygon has been multiplied by 1 mm/d (roughly the average precipitation excess in the Netherlands). This yields a “normative discharge”.\nA corresponding normative volume has been chosen, corresponding with water depths of 0.4, 0.2, and 0.1 m for the primary, secondary, and tertiary surface waters respectively.\nA dead storage volume has been chosen, corresponding with water depths of 0.2, 0.1, and 0.05 m for the primary, secondary, and tertiary surface waters respectively. The basin only starts discharging when the storage volume exceeds this value.\n\n\n\n\n\n\n\nNote\n\n\n\nRibasim is not limited to such oversimplified parametrization! Ribasim uses tabulations and therefore supports arbitrary (piecewise linear) volume-depth and volume-discharge relationships.\n\n\nA visual representation of this simplified conceptual schematization is given in Figure 1 and Figure 2.\n\n\n\nFigure 1: Distribution of water depths over the primary, secondary, and tertiary system.\n\n\n\n\n\nFigure 2: Discharge as a function of basin storage volume.\n\n\nAn example of the resulting parameters for a single cell is shown in Table 1. The first row shows the water levels when the basin is empty. The level for primary, secondary, and tertiary are equal to the bottom elevation of the surface waters. The second row shows the volume and levels for water depths of 0.4, 0.2, and 0.1 m. The third row shows the volume and levels for a tenfold larger volume. This results in implausible water levels with depths of 4.0, 2.0 and 1.0 m; the water level in the primary system is over two meters higher than in the tertiary system. In reality, the surface waters would overflow and surface ponding would occur; this mechanism is ignored in the test cases for the sake of simplicity.\n\n\nTable 1: Volume-level table for a single cell in the Hupsel basin.\n\n\nVolume (m3)\nPrimary (m NAP)\nSecondary (m NAP)\nTertiary (m NAP)\n\n\n\n\n0.0\n25.65\n25.83\n26.60\n\n\n6843.1\n26.05\n26.03\n26.70\n\n\n68431.0\n29.65\n27.83\n27.60\n\n\n\n\nFigure 3 shows the volume of the first row of the cell based input for the primary system. Symbology is set to unique values. While water levels differ per cell in this parametrization, the “normative volume” defined above is shared by all cells in a basin.\n\n\n\nFigure 3: Basin normative volume of the primary system.\n\n\nFigure 3 shows the water level corresponding to the normative storage volume based input for the primary system (it corresponds to the value shown in the first row of the primary column in Table 1). We see a clear gradient from west to east: as our simplified parametrization assumes a constant water depth for all cells in a single system, water levels spatially fall and rise with the bottom elevation.\n\n\n\nFigure 4: Water level corresponding to the normative basin volume of the primary system." }, { - "objectID": "python/reference/Model.html#parameters", - "href": "python/reference/Model.html#parameters", - "title": "1 Model", + "objectID": "couple/modflow-demo.html#example-parametrization", + "href": "couple/modflow-demo.html#example-parametrization", + "title": "MODFLOW 6 Demonstration", "section": "", - "text": "Name\nType\nDescription\nDefault\n\n\n\n\nmodelname\nstr\nModel name, used in TOML and GeoPackage file name.\nrequired\n\n\nnode\nNode\nThe ID, type and geometry of each node.\nrequired\n\n\nedge\nEdge\nHow the nodes are connected.\nrequired\n\n\nbasin\nBasin\nThe waterbodies.\nrequired\n\n\nfractional_flow\nOptional[FractionalFlow]\nSplit flows into fractions.\nrequired\n\n\nlevel_boundary\nOptional[LevelBoundary]\nBoundary condition specifying the water level.\nrequired\n\n\nflow_boundary\nOptional[FlowBoundary]\nBoundary conditions specifying the flow.\nrequired\n\n\nlinear_resistance\n\nLinear flow resistance.\nrequired\n\n\nmanning_resistance\nOptional[ManningResistance]\nFlow resistance based on the Manning formula.\nrequired\n\n\ntabulated_rating_curve\nOptional[TabulatedRatingCurve]\nTabulated rating curve describing flow based on the upstream water level.\nrequired\n\n\npump\nOptional[Pump]\nPrescribed flow rate from one basin to the other.\nrequired\n\n\noutlet\nOptional[Outlet]\nPrescribed flow rate from one basin to the other.\nrequired\n\n\nterminal\nOptional[Terminal]\nWater sink without state or properties.\nrequired\n\n\ndiscrete_control\nOptional[DiscreteControl]\nDiscrete control logic.\nrequired\n\n\npid_control\nOptional[PidControl]\nPID controller attempting to set the level of a basin to a desired value using a pump/outlet.\nrequired\n\n\nuser\nOptional[User]\nUser node type with demand and priority.\nrequired\n\n\nstarttime\nUnion[str, datetime.datetime]\nStarting time of the simulation.\nrequired\n\n\nendtime\nUnion[str, datetime.datetime]\nEnd time of the simulation.\nrequired\n\n\nsolver\nOptional[Solver]\nSolver settings.\nrequired\n\n\nlogging\nOptional[logging]\nLogging settings.\nrequired" + "text": "In our test cases, we will work with the Dutch national groundwater model (LHM). For testing, we simplify the top boundary conditions to just three surface water “systems”, from large to small:\n\nPrimary\nSecondary\nTertiary\n\nThese systems are separated in the groundwater model schematization due to the relatively small size of the surface waters (several meters in width) in comparison with relatively coarse cells of the groundwater model (250 m): most cells in the LHM contain more than one surface water, and are included via representative parameters which take the physical scaling into account. In case of sufficiently small cell sizes, no overlap occurs and all surface waters can be represented in a single grid.\nRibasim has no knowledge of these systems unless explicitly separated into different (sub-)basins. In the examples below, these three systems are represented by one basin with a single volume. For the purposes of testing, we have not yet created empirical volume-level relationships for the surface water of every cell extracted from a hydraulic model. Instead, we have chosen an (over)simplified parametrization of the hydraulic properties, which makes it easy to verify the behavior of Ribasim and the coupling procedures:\n\nPrimary, secondary, and tertiary systems have a rectangular profile: the area of the surface water does not change with water level or volume.\nWater depth is constant for a system within a basin.\nWater depth increases linearly with volume. (This follows from 1.)\nWater depth is distributed across the systems in a geometric progression: when the tertiary system contains 0.1 m of water, the secondary system contains 0.2 m, and the primary system contains 0.4 m.\nWater depth is 0.0 m for all systems when basin volume is 0.0. (This follows from 4.)\nThe distribution of water occurs according to the surface water area (width times length) used for the LHM parametrization of river bed conductance.\n\nFor freely draining basins, a simplified storage-discharge relationship has been derived as follows:\n\nThe area of the basin polygon has been multiplied by 1 mm/d (roughly the average precipitation excess in the Netherlands). This yields a “normative discharge”.\nA corresponding normative volume has been chosen, corresponding with water depths of 0.4, 0.2, and 0.1 m for the primary, secondary, and tertiary surface waters respectively.\nA dead storage volume has been chosen, corresponding with water depths of 0.2, 0.1, and 0.05 m for the primary, secondary, and tertiary surface waters respectively. The basin only starts discharging when the storage volume exceeds this value.\n\n\n\n\n\n\n\nNote\n\n\n\nRibasim is not limited to such oversimplified parametrization! Ribasim uses tabulations and therefore supports arbitrary (piecewise linear) volume-depth and volume-discharge relationships.\n\n\nA visual representation of this simplified conceptual schematization is given in Figure 1 and Figure 2.\n\n\n\nFigure 1: Distribution of water depths over the primary, secondary, and tertiary system.\n\n\n\n\n\nFigure 2: Discharge as a function of basin storage volume.\n\n\nAn example of the resulting parameters for a single cell is shown in Table 1. The first row shows the water levels when the basin is empty. The level for primary, secondary, and tertiary are equal to the bottom elevation of the surface waters. The second row shows the volume and levels for water depths of 0.4, 0.2, and 0.1 m. The third row shows the volume and levels for a tenfold larger volume. This results in implausible water levels with depths of 4.0, 2.0 and 1.0 m; the water level in the primary system is over two meters higher than in the tertiary system. In reality, the surface waters would overflow and surface ponding would occur; this mechanism is ignored in the test cases for the sake of simplicity.\n\n\nTable 1: Volume-level table for a single cell in the Hupsel basin.\n\n\nVolume (m3)\nPrimary (m NAP)\nSecondary (m NAP)\nTertiary (m NAP)\n\n\n\n\n0.0\n25.65\n25.83\n26.60\n\n\n6843.1\n26.05\n26.03\n26.70\n\n\n68431.0\n29.65\n27.83\n27.60\n\n\n\n\nFigure 3 shows the volume of the first row of the cell based input for the primary system. Symbology is set to unique values. While water levels differ per cell in this parametrization, the “normative volume” defined above is shared by all cells in a basin.\n\n\n\nFigure 3: Basin normative volume of the primary system.\n\n\nFigure 3 shows the water level corresponding to the normative storage volume based input for the primary system (it corresponds to the value shown in the first row of the primary column in Table 1). We see a clear gradient from west to east: as our simplified parametrization assumes a constant water depth for all cells in a single system, water levels spatially fall and rise with the bottom elevation.\n\n\n\nFigure 4: Water level corresponding to the normative basin volume of the primary system." }, { - "objectID": "python/reference/Model.html#methods", - "href": "python/reference/Model.html#methods", - "title": "1 Model", - "section": "", - "text": "Name\nDescription\n\n\n\n\nfields\nReturn the names of the fields contained in the Model.\n\n\nfrom_toml\nInitialize a model from the TOML configuration file.\n\n\nplot\nPlot the nodes and edges of the model.\n\n\nsort\nSort all input tables as required.\n\n\nvalidate_model\nValidate the model.\n\n\nvalidate_model_node_IDs\nCheck whether the node IDs in the node field correspond to the node IDs on the node type fields.\n\n\nvalidate_model_node_field_IDs\nCheck whether the node IDs of the node_type fields are valid.\n\n\nvalidate_model_node_types\nCheck whether all node types in the node field are valid.\n\n\nwrite\nWrite the contents of the model to a GeoPackage and a TOML configuration file.\n\n\n\n\n\nModel.fields(cls)\nReturn the names of the fields contained in the Model.\n\n\n\nModel.from_toml(path)\nInitialize a model from the TOML configuration file.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\npath\nFilePath\nPath to the configuration TOML file.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nModel\n\n\n\n\n\n\n\n\nModel.plot(self, ax=None)\nPlot the nodes and edges of the model.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nmatplotlib.pyplot.Artist\nAxes on which to draw the plot.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nmatplotlib.pyplot.Artist\n\n\n\n\n\n\n\n\nModel.sort(self)\nSort all input tables as required.\nTables are sorted by “node_id”, unless otherwise specified. Sorting is done automatically before writing the table.\n\n\n\nModel.validate_model(self)\nValidate the model.\nChecks: - Whether all node types in the node field are valid - Whether the node IDs of the node_type fields are valid - Whether the node IDs in the node field correspond to the node IDs on the node type fields\n\n\n\nModel.validate_model_node_IDs(self)\nCheck whether the node IDs in the node field correspond to the node IDs on the node type fields.\n\n\n\nModel.validate_model_node_field_IDs(self)\nCheck whether the node IDs of the node_type fields are valid.\n\n\n\nModel.validate_model_node_types(self)\nCheck whether all node types in the node field are valid.\n\n\n\nModel.write(self, directory)\nWrite the contents of the model to a GeoPackage and a TOML configuration file.\nIf directory does not exist, it is created before writing. The GeoPackage and TOML file will be called {modelname}.gpkg and {modelname}.toml respectively.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\ndirectory\nFilePath\n\nrequired" + "objectID": "couple/modflow-demo.html#example-configuration", + "href": "couple/modflow-demo.html#example-configuration", + "title": "MODFLOW 6 Demonstration", + "section": "2 Example: Configuration", + "text": "2 Example: Configuration\nAn example of the MODFLOW 6 section of TOML configuration required for a coupled run can be seen below:\n[modflow]\nsimulation = \"../data/hupsel/mfsim.nam\"\nmode = \"sequential\"\ntimestep = 86400.0\n\n[modflow.models]\n\n[modflow.models.gwf]\ntype = \"gwf\"\ndataset = \"../data/volume_level_profile-hupsel.nc\"\nbasins = \"basin_id\"\n\n[[modflow.models.gwf.bounds]]\nriver = \"RIV_P\"\ndrain = \"DRN_P\"\nprofile = \"profile_primary\"\n\n[[modflow.models.gwf.bounds]]\nriver = \"RIV_S\"\ndrain = \"DRN_S\"\nprofile = \"profile_secondary\"\n\n[[modflow.models.gwf.bounds]]\ndrain = \"DRN_T\"\nprofile = \"profile_tertiary\"\nThe section starts by stating the path to the MODFLOW 6 simulation name file (simulation). The next section contains the information regarding the MODFLOW 6 model(s) to couple to Ribasim’s basins. Per model, a path to the coupling parameter dataset is provided (dataset), along with the variable to use as the basin identification number (basins). Next, for every boundary condition that should be coupled to Ribasim, the package names (river, drain) used by MODFLOW 6 must be specified (as we look for these names in the MODFLOW 6 memory), along with the name of the variable in the coupling dataset which provides the volume-level relationship (profile).\nAs can be seen in the example, the coupling mechanism supports coupling of:\n\nA single drainage package (drain entry).\nA single river package (river entry).\nA combination of river and drainage package (when infiltration conductance does not equal drainage conductance) both (river and drain entry)." }, { - "objectID": "python/reference/FractionalFlow.html", - "href": "python/reference/FractionalFlow.html", - "title": "1 FractionalFlow", - "section": "", - "text": "FractionalFlow()\nReceives a fraction of the flow. The fractions must sum to 1.0 for a furcation.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable with the constant flow fractions.\nrequired" + "objectID": "couple/modflow-demo.html#test-case-hupsel", + "href": "couple/modflow-demo.html#test-case-hupsel", + "title": "MODFLOW 6 Demonstration", + "section": "3 Test case: Hupsel", + "text": "3 Test case: Hupsel\nTo test the coupling, the following simulations have been setup and run:\n\nA standalone MODFLOW 6 run of sequential steady-states (i.e. no storage) with differing groundwater recharge values.\nA coupled run where the MODFLOW 6 stages are updated by the Ribasim coupler process, but without Ribasim. This results in volumes of 0.0, so all MODFLOW 6 water levels are set equal to bed elevation.\nA coupled run where the water levels are updated by Ribasim.\n\nFrom these tests, we expect the following behavior:\n\nDrainage terms should remain largely the same when the water level is lowered in a steady-state: the amount of recharge is fixed, and this is locally drained.\nIn case of negative recharge (evapotranspiration), infiltration occurs in the surface waters. Infiltration should be zero when the basin volume is 0.\n\n\n\n\nFigure 5: Water balance of the MODFLOW 6 boundary conditions for the Hupsel basin for a standalone MODFLOW 6 run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 5 shows the water balance of steady-state for submodel of the LHM that has been by selecting the cells belonging to the district containing the Hupsel catch, the Berkel.\nThe Hupsel basin show the expected behavior: for a net groundwater recharge of 1.0 mm/d, most is precipitation with a minor part lateral inflow from higher areas. In terms of outgoing flows, most of the precipitation ends up in the surface water, primarily in the many ditches of the tertiary system. Only a relatively small part of the water leaves the basin via the groundwater. Interbasin flows through the groundwater form such a minor role, as the aquifer is thin and transmissivity is limited.\nReducing groundwater recharge to 0.5 mm/d reduces all flows, with the tertiary system playing a less dominant role, relatively speaking: as its elevation is the highest compared to the primary and secondary system, the head difference is reduced strongest for the tertiary system.\nWith evapotranspiration (ET) excess (-0.05 and 0.1 mm/d; low values are chosen here since most ET would be drawn from storage, which is not available in a steady-state model), the surface waters provide mostly inflow, and recharge is a negative term. In this case, the secondary system provides a small amount of infiltration; most of the water is drawn from the surroundings instead.\n\n\n\nFigure 6: Water balance of the MODFLOW 6 boundary conditions for the Hupsel basin for a zero volume run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 6 shows the same model, with 0-basin volume which causes water levels to be set equal to bed elevation. Consequently, primary and secondary outflow terms are larger for positive groundwater recharge as they drain at a lower level and intercept the water before the tertiary system does. Secondly, with negative groundwater recharge, no infiltration occurs and the water is drawn from the surroundings instead. This shows the coupling mechanism adjusting MODFLOW 6 water levels successfully." }, { - "objectID": "python/reference/FractionalFlow.html#parameters", - "href": "python/reference/FractionalFlow.html#parameters", - "title": "1 FractionalFlow", + "objectID": "couple/modflow-demo.html#test-case-de-tol", + "href": "couple/modflow-demo.html#test-case-de-tol", + "title": "MODFLOW 6 Demonstration", + "section": "4 Test case: de Tol", + "text": "4 Test case: de Tol\nTo test the coupling, the following simulations have been setup and run:\n\nA standalone MODFLOW 6 run of sequential steady-states (i.e. no storage) with differing groundwater recharge values.\nA coupled run where the MODFLOW 6 stages are updated by the Ribasim coupler process, but without Ribasim. This results in volumes of 0.0, so all MODFLOW 6 water levels are set equal to bed elevation.\nA coupled run where the water levels are updated by Ribasim.\n\n\n\n\nFigure 7: Water balance of the MODFLOW 6 boundary conditions for De Tol basin for a standalone MODFLOW 6 run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 7 shows the water balance of steady-state for a submodel of the LHM for the Polder de Tol and its surroundings. While groundwater recharge is the dominant ingoing flow, lateral groundwater flow (over the entire depth of the groundwater model) is a sizable inflow for the area; the larger lateral inflow shows that De Tol is a net receiver of groundwater which is mostly discharged through the secondary system. In contrast to the Hupsel, the tertiary system is almost entirely absent: drainage occurs not through ephemeral tertiary ditches, but by the permanently water-bearing ditches of the primary and secondary system. Unlike the Hupsel, the water balance does not shrink to very small discharges, as there is sizable regional groundwater flow.\n\n\n\nFigure 8: Water balance of the MODFLOW 6 boundary conditions for De Tol basin for a zero volume run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 8 shows the same model, with 0-basin volume which causes water levels to be set equal to bed elevation. The total discharge is larger: the primary and secondary systems are set to lower levels, and so the head difference is larger. While De Tol’s evapotranspiration excess can be fed by the regional groundwater, the primary and secondary ditches also provide some part; as expected, they do not feed when the coupling mechanism adjusts MODFLOW 6’s water levels." + }, + { + "objectID": "index.html", + "href": "index.html", + "title": "Ribasim", "section": "", - "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable with the constant flow fractions.\nrequired" + "text": "Ribasim is a water resources model, designed to be the replacement of the regional surface water modules Mozart and SIMRES in the Netherlands Hydrological Instrument (NHI). Ribasim is a work in progress, it is a prototype that demonstrates all essential functionalities. Further development of the prototype in a software release is planned in 2022 and 2023.\nRibasim is written in the Julia programming language and is built on top of the SciML: Open Source Software for Scientific Machine Learning libraries, notably DifferentialEquations.jl.\nThe latest builds can be downloaded here:\nCurrently only Windows builds for ribasim_cli.zip are available. See Usage for more information." }, { - "objectID": "python/reference/Basin.html", - "href": "python/reference/Basin.html", - "title": "1 Basin", + "objectID": "index.html#water-balance-equations", + "href": "index.html#water-balance-equations", + "title": "Ribasim", + "section": "2.1 Water balance equations", + "text": "2.1 Water balance equations\nThe water balance equation for a drainage basin (Wikipedia contributors 2022) 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.\n\\[\n\\frac{\\mathrm{d}S}{\\mathrm{d}t} = Q_{in} - Q_{out}\n\\]\nWe 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.\n\\[\n\\frac{\\mathrm{d}S}{\\mathrm{d}t} = R + P + ET + Q_{rest}\n\\]" + }, + { + "objectID": "index.html#time", + "href": "index.html#time", + "title": "Ribasim", + "section": "2.2 Time", + "text": "2.2 Time\nThe 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. 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.\nThe 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.\nRibasim 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⁻¹." + }, + { + "objectID": "index.html#sec-space", + "href": "index.html#sec-space", + "title": "Ribasim", + "section": "2.3 Space", + "text": "2.3 Space\nThe 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) (Reggiani, Sivapalan, and Majid Hassanizadeh 1998). 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. (In Mozart the spatial unit was called Local Surface Water (LSW)). 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.\n\n\n\nMozart Local Surface Water polygons and their drainage.\n\n\nThe network is described as graph. Flow can be bi-directional, and the graph does not have to be acyclic.\n\n\n\n\ngraph LR;\n A[\"basin A\"] --- B[\"basin B\"];\n A --- C[\"basin C\"];\n B --- D[\"basin D\"];\n C --- D;\n\n\n\n\n\nInternally 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.\nMultiple basins may exist within the same spatial polygon, representing different aspects of the surface water system (perennial ditches, ephemeral ditches, or even surface ponding). Figure 1, Figure 2, Figure 3 show the 25.0 m rasterized primary, secondary, and tertiary surface waters as identified by BRT TOP10NL (PDOK 2022) in the Hupsel basin (as defined in the Mozart LSW’s). These systems may represented in multiple ways.\n\n\n\nFigure 1: Hupsel: primary surface water.\n\n\n\n\n\nFigure 2: Hupsel: secondary surface water.\n\n\n\n\n\nFigure 3: Hupsel: tertiary surface water.\n\n\nAs a single basin (A) containing all surface water, discharging to its downstream basin to the west (B):\n\n\n\n\ngraph LR;\n A[\"basin A\"] --> B[\"basin B\"];\n\n\n\n\n\nSuch a system may be capable of representing discharge, but it cannot represent residence times or differences in solute concentrations: within a single basin, 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.)\n\n\n\n\ngraph LR;\n T[\"basin T\"] --> S[\"basin S\"];\n S --> P[\"basin P\"];\n P --> B[\"basin B\"];\n\n\n\n\n\nAs 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." + }, + { + "objectID": "core/index.html", + "href": "core/index.html", + "title": "Julia core", "section": "", - "text": "Basin()\nInput for a (sub-)basin: an area of land where all flowing surface water converges to a single point.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nprofile\npandas.DataFrame\nTable describing the geometry.\nrequired\n\n\nstatic\npandas.DataFrame\nTable describing the constant fluxes.\nrequired\n\n\ntime\npandas.DataFrame\nTable describing the time-varying fluxes.\nrequired\n\n\nstate\npandas.DataFrame\nTable describing the initial condition.\nrequired" + "text": "With the term “core”, we mean the computational engine of Ribasim. As detailed in the usage documentation, it is generally used as a command line tool.\nThe theory is described on the equations page.\nThe core is implemented in the Julia programming language, and can be found in the Ribasim repository under the core/ folder. For developers we also advise to read the developer documentation." }, { - "objectID": "python/reference/Basin.html#parameters", - "href": "python/reference/Basin.html#parameters", - "title": "1 Basin", + "objectID": "src/index.html", + "href": "src/index.html", + "title": "1 API Reference", "section": "", - "text": "Name\nType\nDescription\nDefault\n\n\n\n\nprofile\npandas.DataFrame\nTable describing the geometry.\nrequired\n\n\nstatic\npandas.DataFrame\nTable describing the constant fluxes.\nrequired\n\n\ntime\npandas.DataFrame\nTable describing the time-varying fluxes.\nrequired\n\n\nstate\npandas.DataFrame\nTable describing the initial condition.\nrequired" + "text": "This is the private internal documentation of the Ribasim API.\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:module]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:type]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:function]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:constant]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:macro]" }, { - "objectID": "python/reference/index.html", - "href": "python/reference/index.html", + "objectID": "src/index.html#modules", + "href": "src/index.html#modules", "title": "1 API Reference", "section": "", - "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\nA full Ribasim model schematisation with all input.\n\n\n\n\n\n\nThe Node and Edge GeoPackage layers define the network layout.\n\n\n\nNode\nThe Ribasim nodes as Point geometries.\n\n\nEdge\nDefines the connections between nodes.\n\n\n\n\n\n\nAvailable node types to model different situations.\n\n\n\nBasin\nInput for a (sub-)basin: an area of land where all flowing surface water converges to a single point.\n\n\nFractionalFlow\nReceives a fraction of the flow. The fractions must sum to 1.0 for a furcation.\n\n\nLevelBoundary\nStores water at a given level unaffected by flow, like an infinitely large basin.\n\n\nLinearResistance\nFlow through this connection linearly depends on the level difference\n\n\nManningResistance\nFlow through this connection is estimated by conservation of energy and the\n\n\nPump\nPump water from a source node to a destination node.\n\n\nTabulatedRatingCurve\nLinearly interpolates discharge between a tabulation of level and discharge.\n\n\n\n\n\n\nCollection of utility functions.\n\n\n\nutils.geometry_from_connectivity\nCreate edge shapely geometries from connectivities.\n\n\nutils.connectivity_from_geometry\nDerive from_node_id and to_node_id for every edge in lines. LineStrings" + "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:module]" }, { - "objectID": "python/reference/index.html#model", - "href": "python/reference/index.html#model", + "objectID": "src/index.html#types", + "href": "src/index.html#types", "title": "1 API Reference", "section": "", - "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\nA full Ribasim model schematisation with all input." + "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:type]" }, { - "objectID": "python/reference/index.html#network", - "href": "python/reference/index.html#network", + "objectID": "src/index.html#functions", + "href": "src/index.html#functions", "title": "1 API Reference", "section": "", - "text": "The Node and Edge GeoPackage layers define the network layout.\n\n\n\nNode\nThe Ribasim nodes as Point geometries.\n\n\nEdge\nDefines the connections between nodes." + "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:function]" }, { - "objectID": "python/reference/index.html#node-types", - "href": "python/reference/index.html#node-types", + "objectID": "src/index.html#constants", + "href": "src/index.html#constants", "title": "1 API Reference", "section": "", - "text": "Available node types to model different situations.\n\n\n\nBasin\nInput for a (sub-)basin: an area of land where all flowing surface water converges to a single point.\n\n\nFractionalFlow\nReceives a fraction of the flow. The fractions must sum to 1.0 for a furcation.\n\n\nLevelBoundary\nStores water at a given level unaffected by flow, like an infinitely large basin.\n\n\nLinearResistance\nFlow through this connection linearly depends on the level difference\n\n\nManningResistance\nFlow through this connection is estimated by conservation of energy and the\n\n\nPump\nPump water from a source node to a destination node.\n\n\nTabulatedRatingCurve\nLinearly interpolates discharge between a tabulation of level and discharge." + "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:constant]" }, { - "objectID": "python/reference/index.html#utility-functions", - "href": "python/reference/index.html#utility-functions", + "objectID": "src/index.html#macros", + "href": "src/index.html#macros", "title": "1 API Reference", "section": "", - "text": "Collection of utility functions.\n\n\n\nutils.geometry_from_connectivity\nCreate edge shapely geometries from connectivities.\n\n\nutils.connectivity_from_geometry\nDerive from_node_id and to_node_id for every edge in lines. LineStrings" + "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:macro]" }, { - "objectID": "python/index.html", - "href": "python/index.html", - "title": "Python tooling", + "objectID": "contribute/index.html", + "href": "contribute/index.html", + "title": "Contributing", "section": "", - "text": "The Ribasim Python package (named ribasim) aims to make it easy to build, update and analyze Ribasim models programmatically.\nThe Ribasim QGIS plugin allows users to construct a model from scratch without programming. For specific tasks, like adding observed rainfall timeseries, it can be faster to use Python instead.\nOne can also use Ribasim Python to build entire models from base data, such that your model setup is fully reproducible.\nThe package is registered in PyPI and can therefore be installed with pip install ribasim. The most recent (nightly) version can be downloaded here: ribasim-0.4.0-py3-none-any.whl. It can be installed with pip install ribasim-0.4.0-py3-none-any.whl.\nFor documentation please see the examples and API reference." + "text": "Ribasim welcomes contributions.\nThere is developer documentation for the Julia core and Python tooling. A guide on how to add a new node type to both is written in adding node types." }, { - "objectID": "couple/modflow-demo.html", - "href": "couple/modflow-demo.html", - "title": "MODFLOW 6 Demonstration", + "objectID": "contribute/python.html", + "href": "contribute/python.html", + "title": "Python tooling development", "section": "", - "text": "In our test cases, we will work with the Dutch national groundwater model (LHM). For testing, we simplify the top boundary conditions to just three surface water “systems”, from large to small:\n\nPrimary\nSecondary\nTertiary\n\nThese systems are separated in the groundwater model schematization due to the relatively small size of the surface waters (several meters in width) in comparison with relatively coarse cells of the groundwater model (250 m): most cells in the LHM contain more than one surface water, and are included via representative parameters which take the physical scaling into account. In case of sufficiently small cell sizes, no overlap occurs and all surface waters can be represented in a single grid.\nRibasim has no knowledge of these systems unless explicitly separated into different (sub-)basins. In the examples below, these three systems are represented by one basin with a single volume. For the purposes of testing, we have not yet created empirical volume-level relationships for the surface water of every cell extracted from a hydraulic model. Instead, we have chosen an (over)simplified parametrization of the hydraulic properties, which makes it easy to verify the behavior of Ribasim and the coupling procedures:\n\nPrimary, secondary, and tertiary systems have a rectangular profile: the area of the surface water does not change with water level or volume.\nWater depth is constant for a system within a basin.\nWater depth increases linearly with volume. (This follows from 1.)\nWater depth is distributed across the systems in a geometric progression: when the tertiary system contains 0.1 m of water, the secondary system contains 0.2 m, and the primary system contains 0.4 m.\nWater depth is 0.0 m for all systems when basin volume is 0.0. (This follows from 4.)\nThe distribution of water occurs according to the surface water area (width times length) used for the LHM parametrization of river bed conductance.\n\nFor freely draining basins, a simplified storage-discharge relationship has been derived as follows:\n\nThe area of the basin polygon has been multiplied by 1 mm/d (roughly the average precipitation excess in the Netherlands). This yields a “normative discharge”.\nA corresponding normative volume has been chosen, corresponding with water depths of 0.4, 0.2, and 0.1 m for the primary, secondary, and tertiary surface waters respectively.\nA dead storage volume has been chosen, corresponding with water depths of 0.2, 0.1, and 0.05 m for the primary, secondary, and tertiary surface waters respectively. The basin only starts discharging when the storage volume exceeds this value.\n\n\n\n\n\n\n\nNote\n\n\n\nRibasim is not limited to such oversimplified parametrization! Ribasim uses tabulations and therefore supports arbitrary (piecewise linear) volume-depth and volume-discharge relationships.\n\n\nA visual representation of this simplified conceptual schematization is given in Figure 1 and Figure 2.\n\n\n\nFigure 1: Distribution of water depths over the primary, secondary, and tertiary system.\n\n\n\n\n\nFigure 2: Discharge as a function of basin storage volume.\n\n\nAn example of the resulting parameters for a single cell is shown in Table 1. The first row shows the water levels when the basin is empty. The level for primary, secondary, and tertiary are equal to the bottom elevation of the surface waters. The second row shows the volume and levels for water depths of 0.4, 0.2, and 0.1 m. The third row shows the volume and levels for a tenfold larger volume. This results in implausible water levels with depths of 4.0, 2.0 and 1.0 m; the water level in the primary system is over two meters higher than in the tertiary system. In reality, the surface waters would overflow and surface ponding would occur; this mechanism is ignored in the test cases for the sake of simplicity.\n\n\nTable 1: Volume-level table for a single cell in the Hupsel basin.\n\n\nVolume (m3)\nPrimary (m NAP)\nSecondary (m NAP)\nTertiary (m NAP)\n\n\n\n\n0.0\n25.65\n25.83\n26.60\n\n\n6843.1\n26.05\n26.03\n26.70\n\n\n68431.0\n29.65\n27.83\n27.60\n\n\n\n\nFigure 3 shows the volume of the first row of the cell based input for the primary system. Symbology is set to unique values. While water levels differ per cell in this parametrization, the “normative volume” defined above is shared by all cells in a basin.\n\n\n\nFigure 3: Basin normative volume of the primary system.\n\n\nFigure 3 shows the water level corresponding to the normative storage volume based input for the primary system (it corresponds to the value shown in the first row of the primary column in Table 1). We see a clear gradient from west to east: as our simplified parametrization assumes a constant water depth for all cells in a single system, water levels spatially fall and rise with the bottom elevation.\n\n\n\nFigure 4: Water level corresponding to the normative basin volume of the primary system." + "text": "First, set up pixi as described on their getting started page.\nThen set up the environment by running the following commands:\npixi install\npixi run post-install\n\n\n\nIn order to run tests on Ribasim Python execute\npixi run test-ribasim-python\n\n\n\nMake sure to run Clear All Outputs on the notebook before committing.\n\n\n\nBefore running the Julia tests or building binaries, example model input needs to created. This is done by running the following:\npixi run generate-testmodels\nThis places example model input files under ./generated_testmodels/. If the example models change, re-run this script.\n\n\n\n\nInstall the Python, ruff and autoDocstring extensions.\nCopy .vscode/settings_template.json into .vscode/settings.json\n\n\n\n\n\nUpdate __version__ in ribasim/__init__.py\nOpen a terminal and run cd python/ribasim\nActivate the ribasim environment with conda activate ribasim\nIf present remove dist folder\nRe-create the wheels:\n\npython -m build\n\nCheck the package files:\n\ntwine check dist/*\n\nMake a new commit with the updated version number, and push to remote\nRe-upload the new files:\n\ntwine upload dist/*\n\n\n\nTo run our linting suite locally, execute:\npixi run lint" }, { - "objectID": "couple/modflow-demo.html#example-parametrization", - "href": "couple/modflow-demo.html#example-parametrization", - "title": "MODFLOW 6 Demonstration", + "objectID": "contribute/python.html#setting-up-pixi", + "href": "contribute/python.html#setting-up-pixi", + "title": "Python tooling development", "section": "", - "text": "In our test cases, we will work with the Dutch national groundwater model (LHM). For testing, we simplify the top boundary conditions to just three surface water “systems”, from large to small:\n\nPrimary\nSecondary\nTertiary\n\nThese systems are separated in the groundwater model schematization due to the relatively small size of the surface waters (several meters in width) in comparison with relatively coarse cells of the groundwater model (250 m): most cells in the LHM contain more than one surface water, and are included via representative parameters which take the physical scaling into account. In case of sufficiently small cell sizes, no overlap occurs and all surface waters can be represented in a single grid.\nRibasim has no knowledge of these systems unless explicitly separated into different (sub-)basins. In the examples below, these three systems are represented by one basin with a single volume. For the purposes of testing, we have not yet created empirical volume-level relationships for the surface water of every cell extracted from a hydraulic model. Instead, we have chosen an (over)simplified parametrization of the hydraulic properties, which makes it easy to verify the behavior of Ribasim and the coupling procedures:\n\nPrimary, secondary, and tertiary systems have a rectangular profile: the area of the surface water does not change with water level or volume.\nWater depth is constant for a system within a basin.\nWater depth increases linearly with volume. (This follows from 1.)\nWater depth is distributed across the systems in a geometric progression: when the tertiary system contains 0.1 m of water, the secondary system contains 0.2 m, and the primary system contains 0.4 m.\nWater depth is 0.0 m for all systems when basin volume is 0.0. (This follows from 4.)\nThe distribution of water occurs according to the surface water area (width times length) used for the LHM parametrization of river bed conductance.\n\nFor freely draining basins, a simplified storage-discharge relationship has been derived as follows:\n\nThe area of the basin polygon has been multiplied by 1 mm/d (roughly the average precipitation excess in the Netherlands). This yields a “normative discharge”.\nA corresponding normative volume has been chosen, corresponding with water depths of 0.4, 0.2, and 0.1 m for the primary, secondary, and tertiary surface waters respectively.\nA dead storage volume has been chosen, corresponding with water depths of 0.2, 0.1, and 0.05 m for the primary, secondary, and tertiary surface waters respectively. The basin only starts discharging when the storage volume exceeds this value.\n\n\n\n\n\n\n\nNote\n\n\n\nRibasim is not limited to such oversimplified parametrization! Ribasim uses tabulations and therefore supports arbitrary (piecewise linear) volume-depth and volume-discharge relationships.\n\n\nA visual representation of this simplified conceptual schematization is given in Figure 1 and Figure 2.\n\n\n\nFigure 1: Distribution of water depths over the primary, secondary, and tertiary system.\n\n\n\n\n\nFigure 2: Discharge as a function of basin storage volume.\n\n\nAn example of the resulting parameters for a single cell is shown in Table 1. The first row shows the water levels when the basin is empty. The level for primary, secondary, and tertiary are equal to the bottom elevation of the surface waters. The second row shows the volume and levels for water depths of 0.4, 0.2, and 0.1 m. The third row shows the volume and levels for a tenfold larger volume. This results in implausible water levels with depths of 4.0, 2.0 and 1.0 m; the water level in the primary system is over two meters higher than in the tertiary system. In reality, the surface waters would overflow and surface ponding would occur; this mechanism is ignored in the test cases for the sake of simplicity.\n\n\nTable 1: Volume-level table for a single cell in the Hupsel basin.\n\n\nVolume (m3)\nPrimary (m NAP)\nSecondary (m NAP)\nTertiary (m NAP)\n\n\n\n\n0.0\n25.65\n25.83\n26.60\n\n\n6843.1\n26.05\n26.03\n26.70\n\n\n68431.0\n29.65\n27.83\n27.60\n\n\n\n\nFigure 3 shows the volume of the first row of the cell based input for the primary system. Symbology is set to unique values. While water levels differ per cell in this parametrization, the “normative volume” defined above is shared by all cells in a basin.\n\n\n\nFigure 3: Basin normative volume of the primary system.\n\n\nFigure 3 shows the water level corresponding to the normative storage volume based input for the primary system (it corresponds to the value shown in the first row of the primary column in Table 1). We see a clear gradient from west to east: as our simplified parametrization assumes a constant water depth for all cells in a single system, water levels spatially fall and rise with the bottom elevation.\n\n\n\nFigure 4: Water level corresponding to the normative basin volume of the primary system." + "text": "First, set up pixi as described on their getting started page.\nThen set up the environment by running the following commands:\npixi install\npixi run post-install" }, { - "objectID": "couple/modflow-demo.html#example-configuration", - "href": "couple/modflow-demo.html#example-configuration", - "title": "MODFLOW 6 Demonstration", - "section": "2 Example: Configuration", - "text": "2 Example: Configuration\nAn example of the MODFLOW 6 section of TOML configuration required for a coupled run can be seen below:\n[modflow]\nsimulation = \"../data/hupsel/mfsim.nam\"\nmode = \"sequential\"\ntimestep = 86400.0\n\n[modflow.models]\n\n[modflow.models.gwf]\ntype = \"gwf\"\ndataset = \"../data/volume_level_profile-hupsel.nc\"\nbasins = \"basin_id\"\n\n[[modflow.models.gwf.bounds]]\nriver = \"RIV_P\"\ndrain = \"DRN_P\"\nprofile = \"profile_primary\"\n\n[[modflow.models.gwf.bounds]]\nriver = \"RIV_S\"\ndrain = \"DRN_S\"\nprofile = \"profile_secondary\"\n\n[[modflow.models.gwf.bounds]]\ndrain = \"DRN_T\"\nprofile = \"profile_tertiary\"\nThe section starts by stating the path to the MODFLOW 6 simulation name file (simulation). The next section contains the information regarding the MODFLOW 6 model(s) to couple to Ribasim’s basins. Per model, a path to the coupling parameter dataset is provided (dataset), along with the variable to use as the basin identification number (basins). Next, for every boundary condition that should be coupled to Ribasim, the package names (river, drain) used by MODFLOW 6 must be specified (as we look for these names in the MODFLOW 6 memory), along with the name of the variable in the coupling dataset which provides the volume-level relationship (profile).\nAs can be seen in the example, the coupling mechanism supports coupling of:\n\nA single drainage package (drain entry).\nA single river package (river entry).\nA combination of river and drainage package (when infiltration conductance does not equal drainage conductance) both (river and drain entry)." + "objectID": "contribute/python.html#sec-test", + "href": "contribute/python.html#sec-test", + "title": "Python tooling development", + "section": "", + "text": "In order to run tests on Ribasim Python execute\npixi run test-ribasim-python" }, { - "objectID": "couple/modflow-demo.html#test-case-hupsel", - "href": "couple/modflow-demo.html#test-case-hupsel", - "title": "MODFLOW 6 Demonstration", - "section": "3 Test case: Hupsel", - "text": "3 Test case: Hupsel\nTo test the coupling, the following simulations have been setup and run:\n\nA standalone MODFLOW 6 run of sequential steady-states (i.e. no storage) with differing groundwater recharge values.\nA coupled run where the MODFLOW 6 stages are updated by the Ribasim coupler process, but without Ribasim. This results in volumes of 0.0, so all MODFLOW 6 water levels are set equal to bed elevation.\nA coupled run where the water levels are updated by Ribasim.\n\nFrom these tests, we expect the following behavior:\n\nDrainage terms should remain largely the same when the water level is lowered in a steady-state: the amount of recharge is fixed, and this is locally drained.\nIn case of negative recharge (evapotranspiration), infiltration occurs in the surface waters. Infiltration should be zero when the basin volume is 0.\n\n\n\n\nFigure 5: Water balance of the MODFLOW 6 boundary conditions for the Hupsel basin for a standalone MODFLOW 6 run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 5 shows the water balance of steady-state for submodel of the LHM that has been by selecting the cells belonging to the district containing the Hupsel catch, the Berkel.\nThe Hupsel basin show the expected behavior: for a net groundwater recharge of 1.0 mm/d, most is precipitation with a minor part lateral inflow from higher areas. In terms of outgoing flows, most of the precipitation ends up in the surface water, primarily in the many ditches of the tertiary system. Only a relatively small part of the water leaves the basin via the groundwater. Interbasin flows through the groundwater form such a minor role, as the aquifer is thin and transmissivity is limited.\nReducing groundwater recharge to 0.5 mm/d reduces all flows, with the tertiary system playing a less dominant role, relatively speaking: as its elevation is the highest compared to the primary and secondary system, the head difference is reduced strongest for the tertiary system.\nWith evapotranspiration (ET) excess (-0.05 and 0.1 mm/d; low values are chosen here since most ET would be drawn from storage, which is not available in a steady-state model), the surface waters provide mostly inflow, and recharge is a negative term. In this case, the secondary system provides a small amount of infiltration; most of the water is drawn from the surroundings instead.\n\n\n\nFigure 6: Water balance of the MODFLOW 6 boundary conditions for the Hupsel basin for a zero volume run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 6 shows the same model, with 0-basin volume which causes water levels to be set equal to bed elevation. Consequently, primary and secondary outflow terms are larger for positive groundwater recharge as they drain at a lower level and intercept the water before the tertiary system does. Secondly, with negative groundwater recharge, no infiltration occurs and the water is drawn from the surroundings instead. This shows the coupling mechanism adjusting MODFLOW 6 water levels successfully." + "objectID": "contribute/python.html#updating-example-notebooks", + "href": "contribute/python.html#updating-example-notebooks", + "title": "Python tooling development", + "section": "", + "text": "Make sure to run Clear All Outputs on the notebook before committing." }, { - "objectID": "couple/modflow-demo.html#test-case-de-tol", - "href": "couple/modflow-demo.html#test-case-de-tol", - "title": "MODFLOW 6 Demonstration", - "section": "4 Test case: de Tol", - "text": "4 Test case: de Tol\nTo test the coupling, the following simulations have been setup and run:\n\nA standalone MODFLOW 6 run of sequential steady-states (i.e. no storage) with differing groundwater recharge values.\nA coupled run where the MODFLOW 6 stages are updated by the Ribasim coupler process, but without Ribasim. This results in volumes of 0.0, so all MODFLOW 6 water levels are set equal to bed elevation.\nA coupled run where the water levels are updated by Ribasim.\n\n\n\n\nFigure 7: Water balance of the MODFLOW 6 boundary conditions for De Tol basin for a standalone MODFLOW 6 run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 7 shows the water balance of steady-state for a submodel of the LHM for the Polder de Tol and its surroundings. While groundwater recharge is the dominant ingoing flow, lateral groundwater flow (over the entire depth of the groundwater model) is a sizable inflow for the area; the larger lateral inflow shows that De Tol is a net receiver of groundwater which is mostly discharged through the secondary system. In contrast to the Hupsel, the tertiary system is almost entirely absent: drainage occurs not through ephemeral tertiary ditches, but by the permanently water-bearing ditches of the primary and secondary system. Unlike the Hupsel, the water balance does not shrink to very small discharges, as there is sizable regional groundwater flow.\n\n\n\nFigure 8: Water balance of the MODFLOW 6 boundary conditions for De Tol basin for a zero volume run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 8 shows the same model, with 0-basin volume which causes water levels to be set equal to bed elevation. The total discharge is larger: the primary and secondary systems are set to lower levels, and so the head difference is larger. While De Tol’s evapotranspiration excess can be fed by the regional groundwater, the primary and secondary ditches also provide some part; as expected, they do not feed when the coupling mechanism adjusts MODFLOW 6’s water levels." + "objectID": "contribute/python.html#prepare-model-input", + "href": "contribute/python.html#prepare-model-input", + "title": "Python tooling development", + "section": "", + "text": "Before running the Julia tests or building binaries, example model input needs to created. This is done by running the following:\npixi run generate-testmodels\nThis places example model input files under ./generated_testmodels/. If the example models change, re-run this script." }, { - "objectID": "build/index.html#modules", - "href": "build/index.html#modules", - "title": "1 API Reference", - "section": "1.1 Modules", - "text": "1.1 Modules\n# Ribasim.Ribasim — Module.\nmodule Ribasim\nRibasim is a water resources model. The computational core is implemented in Julia in the Ribasim package. It is currently mainly designed to be used as an application. To run a simulation from Julia, use Ribasim.run.\nFor more granular access, see:\n\nConfig\nModel\nsolve!\nBMI.finalize\n\nsource\n# Ribasim.config — Module.\nmodule config\nRibasim.config is a submodule of Ribasim to handle the configuration of a Ribasim model. It is implemented using the Configurations package. A full configuration is represented by Config, which is the main API. Ribasim.config is a submodule mainly to avoid name clashes between the configuration sections and the rest of Ribasim.\nsource" + "objectID": "contribute/python.html#sec-vscode", + "href": "contribute/python.html#sec-vscode", + "title": "Python tooling development", + "section": "", + "text": "Install the Python, ruff and autoDocstring extensions.\nCopy .vscode/settings_template.json into .vscode/settings.json" }, { - "objectID": "build/index.html#types", - "href": "build/index.html#types", - "title": "1 API Reference", - "section": "1.2 Types", - "text": "1.2 Types\n# Ribasim.Basin — Type.\nRequirements:\n\nMust be positive: precipitation, evaporation, infiltration, drainage\nIndex points to a Basin\nvolume, area, level must all be positive and monotonic increasing.\n\nType parameter C indicates the content backing the StructVector, which can be a NamedTuple of vectors or Arrow Tables, and is added to avoid type instabilities. The nodeid are Indices to support fast lookup of e.g. currentlevel using ID.\nif autodiff T = DiffCache{Vector{Float64}} else T = Vector{Float64} end\nsource\n# Ribasim.Connectivity — Type.\nStore the connectivity information\ngraphflow, graphcontrol: directed graph with vertices equal to ids flow: store the flow on every flow edge edgeidsflow, edgeidscontrol: get the external edge id from (src, dst) edgeconnectiontypeflow, edgeconnectiontypescontrol: get (srcnodetype, dstnodetype) from edge id\nif autodiff T = DiffCache{SparseArrays.SparseMatrixCSC{Float64, Int64}, Vector{Float64}} else T = SparseMatrixCSC{Float64, Int} end\nsource\n# Ribasim.DiscreteControl — Type.\nnodeid: node ID of the DiscreteControl node; these are not unique but repeated by the amount of conditions of this DiscreteControl node listenfeatureid: the ID of the node/edge being condition on variable: the name of the variable in the condition greaterthan: The threshold value in the condition conditionvalue: The current value of each condition controlstate: Dictionary: node ID => (control state, control state start) logic_mapping: Dictionary: (control node ID, truth state) => control state record: Namedtuple with discrete control information for output\nsource\n# Ribasim.FlatVector — Type.\nstruct FlatVector{T} <: AbstractVector{T}\nA FlatVector is an AbstractVector that iterates the T of a Vector{Vector{T}}.\nEach inner vector is assumed to be of equal length.\nIt is similar to Iterators.flatten, though that doesn’t work with the Tables.Column interface, which needs length and getindex support.\nsource\n# Ribasim.FlowBoundary — Type.\nnodeid: node ID of the FlowBoundary node active: whether this node is active and thus contributes flow flowrate: target flow rate\nsource\n# Ribasim.FractionalFlow — Type.\nRequirements:\n\nfrom: must be (TabulatedRatingCurve,) node\nto: must be (Basin,) node\nfraction must be positive.\n\nnodeid: node ID of the TabulatedRatingCurve node fraction: The fraction in [0,1] of flow the node lets through controlmapping: dictionary from (nodeid, controlstate) to fraction\nsource\n# Ribasim.LevelBoundary — Type.\nnode_id: node ID of the LevelBoundary node active: whether this node is active level: the fixed level of this ‘infinitely big basin’\nsource\n# Ribasim.LinearResistance — Type.\nRequirements:\n\nfrom: must be (Basin,) node\nto: must be (Basin,) node\n\nnodeid: node ID of the LinearResistance node active: whether this node is active and thus contributes flows resistance: the resistance to flow; Q = Δh/resistance controlmapping: dictionary from (nodeid, controlstate) to resistance and/or active state\nsource\n# Ribasim.ManningResistance — Type.\nThis is a simple Manning-Gauckler reach connection.\n\nLength describes the reach length.\nroughness describes Manning’s n in (SI units).\n\nThe profile is described by a trapezoid:\n \\ / ^\n \\ / |\n \\ / | dz\nbottom \\______/ |\n^ <--->\n| dy\n| <------>\n| width\n|\n|\n+ datum (e.g. MSL)\nWith profile_slope = dy / dz. A rectangular profile requires a slope of 0.0.\nRequirements:\n\nfrom: must be (Basin,) node\nto: must be (Basin,) node\nlength > 0\nroughess > 0\nprofile_width >= 0\nprofile_slope >= 0\n(profilewidth == 0) xor (profileslope == 0)\n\nsource\n# Ribasim.Model — Type.\nModel(config_path::AbstractString)\nModel(config::Config)\nInitialize a Model.\nThe Model struct is an initialized model, combined with the Config used to create it and saved outputs. The Basic Model Interface (BMI) is implemented on the Model. A Model can be created from the path to a TOML configuration file, or a Config object.\nsource\n# Ribasim.Outlet — Type.\nnodeid: node ID of the Outlet node active: whether this node is active and thus contributes flow flowrate: target flow rate minflowrate: The minimal flow rate of the outlet maxflowrate: The maximum flow rate of the outlet controlmapping: dictionary from (nodeid, controlstate) to target flow rate ispid_controlled: whether the flow rate of this outlet is governed by PID control\nsource\n# Ribasim.PidControl — Type.\nPID control currently only supports regulating basin levels.\nnodeid: node ID of the PidControl node active: whether this node is active and thus sets flow rates listennodeid: the id of the basin being controlled pidparams: a vector interpolation for parameters changing over time. The parameters are respectively target, proportional, integral, derivative, where the last three are the coefficients for the PID equation. error: the current error; basintarget - currentlevel\nsource\n# Ribasim.Pump — Type.\nnodeid: node ID of the Pump node active: whether this node is active and thus contributes flow flowrate: target flow rate minflowrate: The minimal flow rate of the pump maxflowrate: The maximum flow rate of the pump controlmapping: dictionary from (nodeid, controlstate) to target flow rate ispid_controlled: whether the flow rate of this pump is governed by PID control\nsource\n# Ribasim.TabulatedRatingCurve — Type.\nstruct TabulatedRatingCurve{C}\nRating curve from level to discharge. The rating curve is a lookup table with linear interpolation in between. Relation can be updated in time, which is done by moving data from the time field into the tables, which is done in the update_tabulated_rating_curve callback.\nType parameter C indicates the content backing the StructVector, which can be a NamedTuple of Vectors or Arrow Primitives, and is added to avoid type instabilities.\nnodeid: node ID of the TabulatedRatingCurve node active: whether this node is active and thus contributes flows tables: The current Q(h) relationships time: The time table used for updating the tables controlmapping: dictionary from (nodeid, controlstate) to Q(h) and/or active state\nsource\n# Ribasim.Terminal — Type.\nnode_id: node ID of the Terminal node\nsource\n# Ribasim.User — Type.\ndemand: water flux demand of user over time active: whether this node is active and thus demands water allocated: water flux currently allocated to user returnfactor: the factor in [0,1] of how much of the abstracted water is given back to the system minlevel: The level of the source basin below which the user does not abstract priority: integer > 0, the lower the number the higher the priority of the users demand\nsource\n# Ribasim.config.Config — Method.\nConfig(config_path::AbstractString; kwargs...)\nParse a TOML file to a Config. Keys can be overruled using keyword arguments. To overrule keys from a subsection, e.g. dt from the solver section, use underscores: solver_dt.\nsource" + "objectID": "contribute/python.html#how-to-publish-to-pypi", + "href": "contribute/python.html#how-to-publish-to-pypi", + "title": "Python tooling development", + "section": "", + "text": "Update __version__ in ribasim/__init__.py\nOpen a terminal and run cd python/ribasim\nActivate the ribasim environment with conda activate ribasim\nIf present remove dist folder\nRe-create the wheels:\n\npython -m build\n\nCheck the package files:\n\ntwine check dist/*\n\nMake a new commit with the updated version number, and push to remote\nRe-upload the new files:\n\ntwine upload dist/*" }, { - "objectID": "build/index.html#functions", - "href": "build/index.html#functions", - "title": "1 API Reference", - "section": "1.3 Functions", - "text": "1.3 Functions\n# BasicModelInterface.finalize — Method.\nBMI.finalize(model::Model)::Model\nWrite all output to the configured output files.\nsource\n# BasicModelInterface.initialize — Method.\nBMI.initialize(T::Type{Model}, config_path::AbstractString)::Model\nInitialize a Model from the path to the TOML configuration file.\nsource\n# BasicModelInterface.initialize — Method.\nBMI.initialize(T::Type{Model}, config::Config)::Model\nInitialize a Model from a Config.\nsource\n# CommonSolve.solve! — Method.\nsolve!(model::Model)::ODESolution\nSolve a Model until the configured endtime.\nsource\n# Ribasim.basin_bottom — Method.\nReturn the bottom elevation of the basin with index i, or nothing if it doesn’t exist\nsource\n# Ribasim.basin_bottoms — Method.\nGet the bottom on both ends of a node. If only one has a bottom, use that for both.\nsource\n# Ribasim.create_callbacks — Method.\nCreate the different callbacks that are used to store output and feed the simulation with new data. The different callbacks are combined to a CallbackSet that goes to the integrator. Returns the CallbackSet and the SavedValues for flow.\nsource\n# Ribasim.create_graph — Method.\nReturn a directed graph, and a mapping from source and target nodes to edge fid.\nsource\n# Ribasim.create_storage_tables — Method.\nRead the Basin / profile table and return all area and level and computed storage values\nsource\n# Ribasim.datetime_since — Method.\ndatetime_since(t::Real, t0::DateTime)::DateTime\nConvert a Real that represents the seconds passed since the simulation start to the nearest DateTime. This is used to convert between the solver’s inner float time, and the calendar.\nsource\n# Ribasim.discrete_control_affect! — Method.\nChange parameters based on the control logic.\nsource\n# Ribasim.discrete_control_affect_downcrossing! — Method.\nAn downcrossing means that a condition (always greater than) becomes false.\nsource\n# Ribasim.discrete_control_affect_upcrossing! — Method.\nAn upcrossing means that a condition (always greater than) becomes true.\nsource\n# Ribasim.discrete_control_condition — Method.\nListens for changes in condition truths.\nsource\n# Ribasim.expand_logic_mapping — Method.\nReplace the truth states in the logic mapping which contain wildcards with all possible explicit truth states.\nsource\n# Ribasim.findlastgroup — Method.\nFor an element id and a vector of elements ids, get the range of indices of the last consecutive block of id. Returns the empty range 1:0 if id is not in ids.\n# 1 2 3 4 5 6 7 8 9\nRibasim.findlastgroup(2, [5,4,2,2,5,2,2,2,1])\n# output\n6:8\nsource\n# Ribasim.findsorted — Method.\nFind the index of element x in a sorted collection a. Returns the index of x if it exists, or nothing if it doesn’t. If x occurs more than once, throw an error.\nsource\n# Ribasim.formulate! — Method.\nSmoothly let the evaporation flux go to 0 when at small water depths Currently at less than 0.1 m.\nsource\n# Ribasim.formulate_flow! — Method.\nDirected graph: outflow is positive!\nsource\n# Ribasim.formulate_flow! — Method.\nConservation of energy for two basins, a and b:\nh_a + v_a^2 / (2 * g) = h_b + v_b^2 / (2 * g) + S_f * L + C / 2 * g * (v_b^2 - v_a^2)\nWhere:\n\nha, hb are the heads at basin a and b.\nva, vb are the velocities at basin a and b.\ng is the gravitational constant.\nS_f is the friction slope.\nC is an expansion or extraction coefficient.\n\nWe assume velocity differences are negligible (va = vb):\nh_a = h_b + S_f * L\nThe friction losses are approximated by the Gauckler-Manning formula:\nQ = A * (1 / n) * R_h^(2/3) * S_f^(1/2)\nWhere:\n\nWhere A is the cross-sectional area.\nV is the cross-sectional average velocity.\nn is the Gauckler-Manning coefficient.\nR_h is the hydraulic radius.\nS_f is the friction slope.\n\nThe hydraulic radius is defined as:\nR_h = A / P\nWhere P is the wetted perimeter.\nThe average of the upstream and downstream water depth is used to compute cross-sectional area and hydraulic radius. This ensures that a basin can receive water after it has gone dry.\nsource\n# Ribasim.formulate_flow! — Method.\nDirected graph: outflow is positive!\nsource\n# Ribasim.get_area_and_level — Method.\nCompute the area and level of a basin given its storage. Also returns darea/dlevel as it is needed for the Jacobian.\nsource\n# Ribasim.get_compressor — Method.\nGet the compressor based on the Output\nsource\n# Ribasim.get_fractional_flow_connected_basins — Method.\nGet the node type specific indices of the fractional flows and basins, that are consecutively connected to a node of given id.\nsource\n# Ribasim.get_jac_prototype — Method.\nGet a sparse matrix whose sparsity matches the sparsity of the Jacobian of the ODE problem. All nodes are taken into consideration, also the ones that are inactive.\nIn Ribasim the Jacobian is typically sparse because each state only depends on a small number of other states.\nNote: the name ‘prototype’ does not mean this code is a prototype, it comes from the naming convention of this sparsity structure in the differentialequations.jl docs.\nsource\n# Ribasim.get_level — Method.\nGet the current water level of a node ID. The ID can belong to either a Basin or a LevelBoundary.\nsource\n# Ribasim.get_scalar_interpolation — Method.\nLinear interpolation of a scalar with constant extrapolation.\nsource\n# Ribasim.get_storage_from_level — Method.\nGet the storage of a basin from its level.\nsource\n# Ribasim.get_storages_and_levels — Method.\nGet the storage and level of all basins as matrices of nbasin × ntime\nsource\n# Ribasim.get_storages_from_levels — Method.\nCompute the storages of the basins based on the water level of the basins.\nsource\n# Ribasim.get_tstops — Method.\nFrom an iterable of DateTimes, find the times the solver needs to stop\nsource\n# Ribasim.get_value — Method.\nGet a value for a condition. Currently supports getting levels from basins and flows from flow boundaries.\nsource\n# Ribasim.id_index — Method.\nGet the index of an ID in a set of indices.\nsource\n# Ribasim.input_path — Method.\nConstruct a path relative to both the TOML directory and the optional input_dir\nsource\n# Ribasim.load_data — Method.\nload_data(db::DB, config::Config, nodetype::Symbol, kind::Symbol)::Union{Table, Query, Nothing}\nLoad data from Arrow files if available, otherwise the GeoPackage. Returns either an Arrow.Table, SQLite.Query or nothing if the data is not present.\nsource\n# Ribasim.load_structvector — Method.\nload_structvector(db::DB, config::Config, ::Type{T})::StructVector{T}\nLoad data from Arrow files if available, otherwise the GeoPackage. Always returns a StructVector of the given struct type T, which is empty if the table is not found. This function validates the schema, and enforces the required sort order.\nsource\n# Ribasim.nodefields — Method.\nGet all node fieldnames of the parameter object.\nsource\n# Ribasim.nodetype — Method.\nFrom a SchemaVersion(“ribasim.flowboundary.static”, 1) return (:FlowBoundary, :static)\nsource\n# Ribasim.output_path — Method.\nConstruct a path relative to both the TOML directory and the optional output_dir\nsource\n# Ribasim.parse_static_and_time — Method.\nProcess the data in the static and time tables for a given node type. The ‘defaults’ named tuple dictates how missing data is filled in. ‘time_interpolatables’ is a vector of Symbols of parameter names for which a time interpolation (linear) object must be constructed. The control mapping for DiscreteControl is also constructed in this function. This function currently does not support node states that are defined by more than one row in a table, as is the case for TabulatedRatingCurve.\nsource\n# Ribasim.profile_storage — Method.\nCalculate a profile storage by integrating the areas over the levels\nsource\n# Ribasim.qh_interpolation — Method.\nFrom a table with columns nodeid, discharge (Q) and level (h), create a LinearInterpolation from level to discharge for a given nodeid.\nsource\n# Ribasim.reduction_factor — Method.\nFunction that goes smoothly from 0 to 1 in the interval [0,threshold], and is constant outside this interval.\nsource\n# Ribasim.run — Method.\nrun(config_file::AbstractString)::Model\nrun(config::Config)::Model\nRun a Model, given a path to a TOML configuration file, or a Config object. Running a model includes initialization, solving to the end with [solve!](@ref) and writing output with BMI.finalize.\nsource\n# Ribasim.save_flow — Method.\nCopy the current flow to the SavedValues\nsource\n# Ribasim.scalar_interpolation_derivative — Method.\nDerivative of scalar interpolation.\nsource\n# Ribasim.seconds_since — Method.\nseconds_since(t::DateTime, t0::DateTime)::Float64\nConvert a DateTime to a float that is the number of seconds since the start of the simulation. This is used to convert between the solver’s inner float time, and the calendar.\nsource\n# Ribasim.set_current_value! — Method.\nFrom a timeseries table time, load the most recent applicable data into table. table must be a NamedTuple of vectors with all variables that must be loaded. The most recent applicable data is non-NaN data for a given ID that is on or before t.\nsource\n# Ribasim.set_static_value! — Method.\nLoad data from a source table static into a destination table. Data is matched based on the node_id, which is sorted.\nsource\n# Ribasim.set_table_row! — Method.\nUpdate table at row index i, with the values of a given row. table must be a NamedTuple of vectors with all variables that must be loaded. The row must contain all the column names that are present in the table. If a value is NaN, it is not set.\nsource\n# Ribasim.sorted_table! — Method.\nDepending on if a table can be sorted, either sort it or assert that it is sorted.\nTables loaded from GeoPackage into memory can be sorted. Tables loaded from Arrow files are memory mapped and can therefore not be sorted.\nsource\n# Ribasim.update_basin — Method.\nLoad updates from ‘Basin / time’ into the parameters\nsource\n# Ribasim.update_jac_prototype! — Method.\nMethod for nodes that do not contribute to the Jacobian\nsource\n# Ribasim.update_jac_prototype! — Method.\nThe controlled basin affects itself and the basins upstream and downstream of the controlled pump affect eachother if there is a basin upstream of the pump. The state for the integral term and the controlled basin affect eachother, and the same for the integral state and the basin upstream of the pump if it is indeed a basin.\nsource\n# Ribasim.update_jac_prototype! — Method.\nIf both the unique node upstream and the unique node downstream of these nodes are basins, then these directly depend on eachother and affect the Jacobian 2x Basins always depend on themselves.\nsource\n# Ribasim.update_jac_prototype! — Method.\nIf both the unique node upstream and the nodes down stream (or one node further if a fractional flow is in between) are basins, then the downstream basin depends on the upstream basin(s) and affect the Jacobian as many times as there are downstream basins Upstream basins always depend on themselves.\nsource\n# Ribasim.update_tabulated_rating_curve! — Method.\nLoad updates from ‘TabulatedRatingCurve / time’ into the parameters\nsource\n# Ribasim.valid_discrete_control — Method.\nCheck:\n\nwhether control states are defined for discrete controlled nodes;\nWhether the supplied truth states have the proper length;\nWhether look_ahead is only supplied for condition variables given by a time-series.\n\nsource\n# Ribasim.valid_edge_types — Method.\nCheck that only supported edge types are declared.\nsource\n# Ribasim.valid_edges — Method.\nTest for each node given its node type whether the nodes that\nare downstream (‘down-edge’) of this node are of an allowed type\nsource\n# Ribasim.valid_flow_rates — Method.\nTest whether static or discrete controlled flow rates are indeed non-negative.\nsource\n# Ribasim.valid_fractional_flow — Method.\nCheck that nodes that have fractional flow outneighbors do not have any other type of outneighbor, that the fractions leaving a node add up to ≈1 and that the fractions are non-negative.\nsource\n# Ribasim.valid_n_neighbors — Method.\nTest for each node given its node type whether it has an allowed number of flow/control inneighbors and outneighbors\nsource\n# Ribasim.valid_profiles — Method.\nCheck whether the profile data has no repeats in the levels and the areas start positive.\nsource\n# Ribasim.water_balance! — Method.\nThe right hand side function of the system of ODEs set up by Ribasim.\nsource\n# Ribasim.config.algorithm — Method.\nCreate an OrdinaryDiffEqAlgorithm from solver config\nsource\n# Ribasim.config.snake_case — Method.\nConvert a string from CamelCase to snake_case.\nsource" + "objectID": "contribute/python.html#linting", + "href": "contribute/python.html#linting", + "title": "Python tooling development", + "section": "", + "text": "To run our linting suite locally, execute:\npixi run lint" }, { - "objectID": "build/index.html#constants", - "href": "build/index.html#constants", + "objectID": "python/index.html", + "href": "python/index.html", + "title": "Python tooling", + "section": "", + "text": "The Ribasim Python package (named ribasim) aims to make it easy to build, update and analyze Ribasim models programmatically.\nThe Ribasim QGIS plugin allows users to construct a model from scratch without programming. For specific tasks, like adding observed rainfall timeseries, it can be faster to use Python instead.\nOne can also use Ribasim Python to build entire models from base data, such that your model setup is fully reproducible.\nThe package is registered in PyPI and can therefore be installed with pip install ribasim. The most recent (nightly) version can be downloaded here: ribasim-0.4.0-py3-none-any.whl. It can be installed with pip install ribasim-0.4.0-py3-none-any.whl.\nFor documentation please see the examples and API reference." + }, + { + "objectID": "python/reference/index.html", + "href": "python/reference/index.html", "title": "1 API Reference", - "section": "1.4 Constants", - "text": "1.4 Constants\n# Ribasim.config.algorithms — Constant.\nconst algorithms::Dict{String, Type}\nMap from config string to a supported algorithm type from OrdinaryDiffEq.\nSupported algorithms:\n\nQNDF\nRosenbrock23\nTRBDF2\nRodas5\nKenCarp4\nTsit5\nRK4\nImplicitEuler\nEuler\n\nsource" + "section": "", + "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\nA full Ribasim model schematisation with all input.\n\n\n\n\n\n\nThe Node and Edge GeoPackage layers define the network layout.\n\n\n\nNode\nThe Ribasim nodes as Point geometries.\n\n\nEdge\nDefines the connections between nodes.\n\n\n\n\n\n\nAvailable node types to model different situations.\n\n\n\nBasin\nInput for a (sub-)basin: an area of land where all flowing surface water converges to a single point.\n\n\nFractionalFlow\nReceives a fraction of the flow. The fractions must sum to 1.0 for a furcation.\n\n\nLevelBoundary\nStores water at a given level unaffected by flow, like an infinitely large basin.\n\n\nLinearResistance\nFlow through this connection linearly depends on the level difference\n\n\nManningResistance\nFlow through this connection is estimated by conservation of energy and the\n\n\nPump\nPump water from a source node to a destination node.\n\n\nTabulatedRatingCurve\nLinearly interpolates discharge between a tabulation of level and discharge.\n\n\n\n\n\n\nCollection of utility functions.\n\n\n\nutils.geometry_from_connectivity\nCreate edge shapely geometries from connectivities.\n\n\nutils.connectivity_from_geometry\nDerive from_node_id and to_node_id for every edge in lines. LineStrings" }, { - "objectID": "build/index.html#macros", - "href": "build/index.html#macros", + "objectID": "python/reference/index.html#model", + "href": "python/reference/index.html#model", "title": "1 API Reference", - "section": "1.5 Macros", - "text": "1.5 Macros\n# Ribasim.config.@addfields — Macro.\nAdd fieldnames with Maybe{String} type to struct expression. Requires (option?) use before it.\nsource\n# Ribasim.config.@addnodetypes — Macro.\nAdd all TableOption subtypes as fields to struct expression. Requires (option?) use before it.\nsource" + "section": "", + "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\nA full Ribasim model schematisation with all input." }, { - "objectID": "build/index.html#index", - "href": "build/index.html#index", + "objectID": "python/reference/index.html#network", + "href": "python/reference/index.html#network", "title": "1 API Reference", - "section": "1.6 Index", - "text": "1.6 Index\n\nRibasim.Ribasim\nRibasim.config\nRibasim.config.algorithms\nRibasim.Basin\nRibasim.Connectivity\nRibasim.DiscreteControl\nRibasim.FlatVector\nRibasim.FlowBoundary\nRibasim.FractionalFlow\nRibasim.LevelBoundary\nRibasim.LinearResistance\nRibasim.ManningResistance\nRibasim.Model\nRibasim.Outlet\nRibasim.PidControl\nRibasim.Pump\nRibasim.TabulatedRatingCurve\nRibasim.Terminal\nRibasim.User\nRibasim.config.Config\nBasicModelInterface.finalize\nBasicModelInterface.initialize\nBasicModelInterface.initialize\nCommonSolve.solve!\nRibasim.basin_bottom\nRibasim.basin_bottoms\nRibasim.config.algorithm\nRibasim.config.snake_case\nRibasim.create_callbacks\nRibasim.create_graph\nRibasim.create_storage_tables\nRibasim.datetime_since\nRibasim.discrete_control_affect!\nRibasim.discrete_control_affect_downcrossing!\nRibasim.discrete_control_affect_upcrossing!\nRibasim.discrete_control_condition\nRibasim.expand_logic_mapping\nRibasim.findlastgroup\nRibasim.findsorted\nRibasim.formulate!\nRibasim.formulate_flow!\nRibasim.formulate_flow!\nRibasim.formulate_flow!\nRibasim.get_area_and_level\nRibasim.get_compressor\nRibasim.get_fractional_flow_connected_basins\nRibasim.get_jac_prototype\nRibasim.get_level\nRibasim.get_scalar_interpolation\nRibasim.get_storage_from_level\nRibasim.get_storages_and_levels\nRibasim.get_storages_from_levels\nRibasim.get_tstops\nRibasim.get_value\nRibasim.id_index\nRibasim.input_path\nRibasim.load_data\nRibasim.load_structvector\nRibasim.nodefields\nRibasim.nodetype\nRibasim.output_path\nRibasim.parse_static_and_time\nRibasim.profile_storage\nRibasim.qh_interpolation\nRibasim.reduction_factor\nRibasim.run\nRibasim.save_flow\nRibasim.scalar_interpolation_derivative\nRibasim.seconds_since\nRibasim.set_current_value!\nRibasim.set_static_value!\nRibasim.set_table_row!\nRibasim.sorted_table!\nRibasim.update_basin\nRibasim.update_jac_prototype!\nRibasim.update_jac_prototype!\nRibasim.update_jac_prototype!\nRibasim.update_jac_prototype!\nRibasim.update_tabulated_rating_curve!\nRibasim.valid_discrete_control\nRibasim.valid_edge_types\nRibasim.valid_edges\nRibasim.valid_flow_rates\nRibasim.valid_fractional_flow\nRibasim.valid_n_neighbors\nRibasim.valid_profiles\nRibasim.water_balance!\nRibasim.config.@addfields\nRibasim.config.@addnodetypes" + "section": "", + "text": "The Node and Edge GeoPackage layers define the network layout.\n\n\n\nNode\nThe Ribasim nodes as Point geometries.\n\n\nEdge\nDefines the connections between nodes." }, { - "objectID": "core/usage.html", - "href": "core/usage.html", - "title": "Usage", + "objectID": "python/reference/index.html#node-types", + "href": "python/reference/index.html#node-types", + "title": "1 API Reference", "section": "", - "text": "Ribasim is typically used as a command-line interface (CLI). It is distributed as a .zip archive, that must be downloaded and unpacked. It can be placed anywhere, however it is important that the contents of the zip file are kept together in a directory. The Ribasim CLI executable is in the bin directory.\nThe latest build can be downloaded here: ribasim_cli.zip. Currently only Windows builds are available.\nTo check whether the installation was performed successfully, run ribasim with no arguments in the command line. This will give the following message:" + "text": "Available node types to model different situations.\n\n\n\nBasin\nInput for a (sub-)basin: an area of land where all flowing surface water converges to a single point.\n\n\nFractionalFlow\nReceives a fraction of the flow. The fractions must sum to 1.0 for a furcation.\n\n\nLevelBoundary\nStores water at a given level unaffected by flow, like an infinitely large basin.\n\n\nLinearResistance\nFlow through this connection linearly depends on the level difference\n\n\nManningResistance\nFlow through this connection is estimated by conservation of energy and the\n\n\nPump\nPump water from a source node to a destination node.\n\n\nTabulatedRatingCurve\nLinearly interpolates discharge between a tabulation of level and discharge." }, { - "objectID": "core/usage.html#solver-settings", - "href": "core/usage.html#solver-settings", - "title": "Usage", - "section": "1.1 Solver settings", - "text": "1.1 Solver settings\nThe solver section in the configuration file is entirely optional, since we aim to use defaults that will generally work well. Common reasons to modify the solver settings are to adjust the calculation or output stepsizes: adaptive, dt, and saveat. If your model does not converge, or your performance is lower than expected, it can help to adjust other solver settings as well.\nThe default solver algorithm = \"QNDF\", which is a multistep method similar to Matlab’s ode15s (Shampine and Reichelt 1997). It is an implicit method that supports the default adaptive = true timestepping. The full list of available solvers is: QNDF, Rosenbrock23, TRBDF2, Rodas5, KenCarp4, Tsit5, RK4, ImplicitEuler, Euler. Information on the solver algorithms can be found on the ODE solvers page.\nThe dt controls the stepsize. When adaptive = true this only applies to the initial stepsize, and the default of 0.0 means that it is automatically determined. When adaptive = false a suitable dt must always be provided. The value is in seconds, so dt = 3600.0 corresponds to an hourly timestep.\nBy default the calculation and output stepsize are the same, with saveat = [], which will save every timestep. saveat can be a number, which is the saving interval in seconds, or it can be a list of numbers, which are the times in seconds since start that are saved. For instance, saveat = 86400.0 will save output after every day that passed.\nThe Jacobian matrix provides information about the local sensitivity of the model with respect to changes in the states. For implicit solvers it must be calculated often, which can be expensive to do. There are several methods to do this. By default Ribasim uses a Jacobian derived automatically using ForwardDiff.jl with memory management provided by PreallocationTools.jl. If this is not used by setting autodiff = false, the Jacobian is calculated with a finite difference method, which can be less accurate and more expensive.\nBy default the Jacobian matrix is a sparse matrix (sparse = true). Since each state typically only depends on a small number of other states, this is generally more efficient, especially for larger models. The sparsity structure is calculated from the network and provided as a Jacobian prototype to the solver. For small or highly connected models it could be faster to use a dense Jacobian matrix instead by setting sparse = false.\nThe total maximum number of iterations maxiters = 1e9, can normally stay as-is unless doing extremely long simulations.\nThe absolute and relative tolerance for adaptive timestepping can be set with abstol and reltol. For more information on these and other solver options, see the DifferentialEquations.jl docs." + "objectID": "python/reference/index.html#utility-functions", + "href": "python/reference/index.html#utility-functions", + "title": "1 API Reference", + "section": "", + "text": "Collection of utility functions.\n\n\n\nutils.geometry_from_connectivity\nCreate edge shapely geometries from connectivities.\n\n\nutils.connectivity_from_geometry\nDerive from_node_id and to_node_id for every edge in lines. LineStrings" }, { - "objectID": "core/usage.html#basin-time", - "href": "core/usage.html#basin-time", - "title": "Usage", - "section": "5.1 Basin / time", - "text": "5.1 Basin / time\nThis table is the transient form of the Basin table. The only difference is that a time column is added. The table must by sorted by time, and per time it must be sorted by node_id. A linear interpolation between the given timesteps is currently done if the solver takes timesteps between the given data points. More options will be available later." + "objectID": "python/reference/Basin.html", + "href": "python/reference/Basin.html", + "title": "1 Basin", + "section": "", + "text": "Basin()\nInput for a (sub-)basin: an area of land where all flowing surface water converges to a single point.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nprofile\npandas.DataFrame\nTable describing the geometry.\nrequired\n\n\nstatic\npandas.DataFrame\nTable describing the constant fluxes.\nrequired\n\n\ntime\npandas.DataFrame\nTable describing the time-varying fluxes.\nrequired\n\n\nstate\npandas.DataFrame\nTable describing the initial condition.\nrequired" }, { - "objectID": "core/usage.html#basin-state", - "href": "core/usage.html#basin-state", - "title": "Usage", - "section": "5.2 Basin / state", - "text": "5.2 Basin / state\nThe state table aims to capture the full state of the Basin, such that it can be used as an initial condition, potentially the outcome of an earlier simulation. Currently only the Basin node types have state.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\nlevel\nFloat64\n\\(m\\)\n\\(\\ge\\) basin bottom\n\n\n\nEach Basin ID needs to be in the table." + "objectID": "python/reference/Basin.html#parameters", + "href": "python/reference/Basin.html#parameters", + "title": "1 Basin", + "section": "", + "text": "Name\nType\nDescription\nDefault\n\n\n\n\nprofile\npandas.DataFrame\nTable describing the geometry.\nrequired\n\n\nstatic\npandas.DataFrame\nTable describing the constant fluxes.\nrequired\n\n\ntime\npandas.DataFrame\nTable describing the time-varying fluxes.\nrequired\n\n\nstate\npandas.DataFrame\nTable describing the initial condition.\nrequired" }, { - "objectID": "core/usage.html#basin-profile", - "href": "core/usage.html#basin-profile", - "title": "Usage", - "section": "5.3 Basin / profile", - "text": "5.3 Basin / profile\nThe profile table defines the physical dimensions of the storage reservoir of each basin.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\narea\nFloat64\n\\(m^2\\)\nnon-negative, per node_id: start positive and increasing\n\n\nlevel\nFloat64\n\\(m\\)\nper node_id: increasing\n\n\n\nThe level is the level at the basin outlet. All levels are defined in meters above a datum that is the same for the entire model. An example of the first 5 rows of such a table is given below. The first 4 rows define the profile of ID 2. The number of rows can vary per ID. Using a very large number of rows may impact performance.\n\n\n\nnode_id\narea\nlevel\n\n\n\n\n2\n1.0\n6.0\n\n\n2\n1000.0\n7.0\n\n\n2\n1000.0\n9.0\n\n\n3\n1.0\n2.2\n\n\n\nWe use the symbol \\(A\\) for area, \\(h\\) for level and \\(S\\) for storage. The profile provides a function \\(A(h)\\) for each basin. Internally this get converted to two functions, \\(A(S)\\) and \\(h(S)\\), by integrating over the function, setting the storage to zero for the bottom of the profile. The minimum area cannot be zero to avoid numerical issues. The maximum area is used to convert the precipitation flux into an inflow." + "objectID": "python/reference/Model.html", + "href": "python/reference/Model.html", + "title": "1 Model", + "section": "", + "text": "Model()\nA full Ribasim model schematisation with all input.\nRibasim model containing the location of the nodes, the edges between the nodes, and the node parametrization.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nmodelname\nstr\nModel name, used in TOML and GeoPackage file name.\nrequired\n\n\nnode\nNode\nThe ID, type and geometry of each node.\nrequired\n\n\nedge\nEdge\nHow the nodes are connected.\nrequired\n\n\nbasin\nBasin\nThe waterbodies.\nrequired\n\n\nfractional_flow\nOptional[FractionalFlow]\nSplit flows into fractions.\nrequired\n\n\nlevel_boundary\nOptional[LevelBoundary]\nBoundary condition specifying the water level.\nrequired\n\n\nflow_boundary\nOptional[FlowBoundary]\nBoundary conditions specifying the flow.\nrequired\n\n\nlinear_resistance\n\nLinear flow resistance.\nrequired\n\n\nmanning_resistance\nOptional[ManningResistance]\nFlow resistance based on the Manning formula.\nrequired\n\n\ntabulated_rating_curve\nOptional[TabulatedRatingCurve]\nTabulated rating curve describing flow based on the upstream water level.\nrequired\n\n\npump\nOptional[Pump]\nPrescribed flow rate from one basin to the other.\nrequired\n\n\noutlet\nOptional[Outlet]\nPrescribed flow rate from one basin to the other.\nrequired\n\n\nterminal\nOptional[Terminal]\nWater sink without state or properties.\nrequired\n\n\ndiscrete_control\nOptional[DiscreteControl]\nDiscrete control logic.\nrequired\n\n\npid_control\nOptional[PidControl]\nPID controller attempting to set the level of a basin to a desired value using a pump/outlet.\nrequired\n\n\nuser\nOptional[User]\nUser node type with demand and priority.\nrequired\n\n\nstarttime\nUnion[str, datetime.datetime]\nStarting time of the simulation.\nrequired\n\n\nendtime\nUnion[str, datetime.datetime]\nEnd time of the simulation.\nrequired\n\n\nsolver\nOptional[Solver]\nSolver settings.\nrequired\n\n\nlogging\nOptional[logging]\nLogging settings.\nrequired\n\n\n\n\n\n\n\n\n\nName\nDescription\n\n\n\n\nfields\nReturn the names of the fields contained in the Model.\n\n\nfrom_toml\nInitialize a model from the TOML configuration file.\n\n\nplot\nPlot the nodes and edges of the model.\n\n\nsort\nSort all input tables as required.\n\n\nvalidate_model\nValidate the model.\n\n\nvalidate_model_node_IDs\nCheck whether the node IDs in the node field correspond to the node IDs on the node type fields.\n\n\nvalidate_model_node_field_IDs\nCheck whether the node IDs of the node_type fields are valid.\n\n\nvalidate_model_node_types\nCheck whether all node types in the node field are valid.\n\n\nwrite\nWrite the contents of the model to a GeoPackage and a TOML configuration file.\n\n\n\n\n\nModel.fields(cls)\nReturn the names of the fields contained in the Model.\n\n\n\nModel.from_toml(path)\nInitialize a model from the TOML configuration file.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\npath\nFilePath\nPath to the configuration TOML file.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nModel\n\n\n\n\n\n\n\n\nModel.plot(self, ax=None)\nPlot the nodes and edges of the model.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nmatplotlib.pyplot.Artist\nAxes on which to draw the plot.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nmatplotlib.pyplot.Artist\n\n\n\n\n\n\n\n\nModel.sort(self)\nSort all input tables as required.\nTables are sorted by “node_id”, unless otherwise specified. Sorting is done automatically before writing the table.\n\n\n\nModel.validate_model(self)\nValidate the model.\nChecks: - Whether all node types in the node field are valid - Whether the node IDs of the node_type fields are valid - Whether the node IDs in the node field correspond to the node IDs on the node type fields\n\n\n\nModel.validate_model_node_IDs(self)\nCheck whether the node IDs in the node field correspond to the node IDs on the node type fields.\n\n\n\nModel.validate_model_node_field_IDs(self)\nCheck whether the node IDs of the node_type fields are valid.\n\n\n\nModel.validate_model_node_types(self)\nCheck whether all node types in the node field are valid.\n\n\n\nModel.write(self, directory)\nWrite the contents of the model to a GeoPackage and a TOML configuration file.\nIf directory does not exist, it is created before writing. The GeoPackage and TOML file will be called {modelname}.gpkg and {modelname}.toml respectively.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\ndirectory\nFilePath\n\nrequired" }, { - "objectID": "core/usage.html#basin-output", - "href": "core/usage.html#basin-output", - "title": "Usage", - "section": "5.4 Basin output", - "text": "5.4 Basin output\nThe basin table contains outputs of the storage and level of each basin at every solver timestep. The initial condition is also written to the file.\n\n\n\ncolumn\ntype\nunit\n\n\n\n\ntime\nDateTime\n-\n\n\nnode_id\nInt\n-\n\n\nstorage\nFloat64\n\\(m^3\\)\n\n\nlevel\nFloat64\n\\(m\\)\n\n\n\nThe table is sorted by time, and per time it is sorted by node_id." + "objectID": "python/reference/Model.html#parameters", + "href": "python/reference/Model.html#parameters", + "title": "1 Model", + "section": "", + "text": "Name\nType\nDescription\nDefault\n\n\n\n\nmodelname\nstr\nModel name, used in TOML and GeoPackage file name.\nrequired\n\n\nnode\nNode\nThe ID, type and geometry of each node.\nrequired\n\n\nedge\nEdge\nHow the nodes are connected.\nrequired\n\n\nbasin\nBasin\nThe waterbodies.\nrequired\n\n\nfractional_flow\nOptional[FractionalFlow]\nSplit flows into fractions.\nrequired\n\n\nlevel_boundary\nOptional[LevelBoundary]\nBoundary condition specifying the water level.\nrequired\n\n\nflow_boundary\nOptional[FlowBoundary]\nBoundary conditions specifying the flow.\nrequired\n\n\nlinear_resistance\n\nLinear flow resistance.\nrequired\n\n\nmanning_resistance\nOptional[ManningResistance]\nFlow resistance based on the Manning formula.\nrequired\n\n\ntabulated_rating_curve\nOptional[TabulatedRatingCurve]\nTabulated rating curve describing flow based on the upstream water level.\nrequired\n\n\npump\nOptional[Pump]\nPrescribed flow rate from one basin to the other.\nrequired\n\n\noutlet\nOptional[Outlet]\nPrescribed flow rate from one basin to the other.\nrequired\n\n\nterminal\nOptional[Terminal]\nWater sink without state or properties.\nrequired\n\n\ndiscrete_control\nOptional[DiscreteControl]\nDiscrete control logic.\nrequired\n\n\npid_control\nOptional[PidControl]\nPID controller attempting to set the level of a basin to a desired value using a pump/outlet.\nrequired\n\n\nuser\nOptional[User]\nUser node type with demand and priority.\nrequired\n\n\nstarttime\nUnion[str, datetime.datetime]\nStarting time of the simulation.\nrequired\n\n\nendtime\nUnion[str, datetime.datetime]\nEnd time of the simulation.\nrequired\n\n\nsolver\nOptional[Solver]\nSolver settings.\nrequired\n\n\nlogging\nOptional[logging]\nLogging settings.\nrequired" }, { - "objectID": "core/usage.html#flow-output", - "href": "core/usage.html#flow-output", - "title": "Usage", - "section": "5.5 Flow output", - "text": "5.5 Flow output\nThe flow table contains outputs of the flow on every edge in the model, for each solver timestep.\n\n\n\ncolumn\ntype\nunit\n\n\n\n\ntime\nDateTime\n-\n\n\nedge_id\nInt\n-\n\n\nfrom_node_id\nInt\n-\n\n\nto_node_id\nInt\n-\n\n\nflow\nFloat64\n\\(m^3 s^{-1}\\)\n\n\n\nThe table is sorted by time, and per time the same edge_id order is used, though not sorted." + "objectID": "python/reference/Model.html#methods", + "href": "python/reference/Model.html#methods", + "title": "1 Model", + "section": "", + "text": "Name\nDescription\n\n\n\n\nfields\nReturn the names of the fields contained in the Model.\n\n\nfrom_toml\nInitialize a model from the TOML configuration file.\n\n\nplot\nPlot the nodes and edges of the model.\n\n\nsort\nSort all input tables as required.\n\n\nvalidate_model\nValidate the model.\n\n\nvalidate_model_node_IDs\nCheck whether the node IDs in the node field correspond to the node IDs on the node type fields.\n\n\nvalidate_model_node_field_IDs\nCheck whether the node IDs of the node_type fields are valid.\n\n\nvalidate_model_node_types\nCheck whether all node types in the node field are valid.\n\n\nwrite\nWrite the contents of the model to a GeoPackage and a TOML configuration file.\n\n\n\n\n\nModel.fields(cls)\nReturn the names of the fields contained in the Model.\n\n\n\nModel.from_toml(path)\nInitialize a model from the TOML configuration file.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\npath\nFilePath\nPath to the configuration TOML file.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nModel\n\n\n\n\n\n\n\n\nModel.plot(self, ax=None)\nPlot the nodes and edges of the model.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nmatplotlib.pyplot.Artist\nAxes on which to draw the plot.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nmatplotlib.pyplot.Artist\n\n\n\n\n\n\n\n\nModel.sort(self)\nSort all input tables as required.\nTables are sorted by “node_id”, unless otherwise specified. Sorting is done automatically before writing the table.\n\n\n\nModel.validate_model(self)\nValidate the model.\nChecks: - Whether all node types in the node field are valid - Whether the node IDs of the node_type fields are valid - Whether the node IDs in the node field correspond to the node IDs on the node type fields\n\n\n\nModel.validate_model_node_IDs(self)\nCheck whether the node IDs in the node field correspond to the node IDs on the node type fields.\n\n\n\nModel.validate_model_node_field_IDs(self)\nCheck whether the node IDs of the node_type fields are valid.\n\n\n\nModel.validate_model_node_types(self)\nCheck whether all node types in the node field are valid.\n\n\n\nModel.write(self, directory)\nWrite the contents of the model to a GeoPackage and a TOML configuration file.\nIf directory does not exist, it is created before writing. The GeoPackage and TOML file will be called {modelname}.gpkg and {modelname}.toml respectively.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\ndirectory\nFilePath\n\nrequired" }, { - "objectID": "core/usage.html#tabulatedratingcurve-time", - "href": "core/usage.html#tabulatedratingcurve-time", - "title": "Usage", - "section": "7.1 TabulatedRatingCurve / time", - "text": "7.1 TabulatedRatingCurve / time\nThis table is the transient form of the TabulatedRatingCurve table. The only difference is that a time column is added. The table must by sorted by time, and per time it must be sorted by node_id. With this the rating curves can be updated over time. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\ntime\nDateTime\n-\nsorted\n\n\nnode_id\nInt\n-\nsorted per time\n\n\nlevel\nFloat64\n\\(m\\)\n-\n\n\ndischarge\nFloat64\n\\(m^3 s^{-1}\\)\nnon-negative" + "objectID": "python/reference/utils.geometry_from_connectivity.html", + "href": "python/reference/utils.geometry_from_connectivity.html", + "title": "1 utils.geometry_from_connectivity", + "section": "", + "text": "utils.geometry_from_connectivity(node, from_id, to_id)\nCreate edge shapely geometries from connectivities.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nnode\nRibasim.Node\n\nrequired\n\n\nfrom_id\nSequence[int]\nFirst node of every edge.\nrequired\n\n\nto_id\nSequence[int]\nSecond node of every edge.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nnp.ndarray\nArray of shapely LineStrings." }, { - "objectID": "core/usage.html#user-time", - "href": "core/usage.html#user-time", - "title": "Usage", - "section": "10.1 User / time", - "text": "10.1 User / time\nThis table is the transient form of the User table. The only difference is that a time column is added and activity is assumed to be true. The table must by sorted by time, and per time it must be sorted by node_id. With this the demand can be updated over time. In between the given times the demand is interpolated linearly, and outside the demand is constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted per time\n\n\ntime\nDateTime\n-\nsorted\n\n\ndemand\nFloat64\n\\(m^3 s^{-1}\\)\n-\n\n\nreturn_factor\nFloat64\n-\nbetween [0 - 1]\n\n\nmin_level\nFloat64\n\\(m\\)\n(optional)\n\n\npriority\nInt\n-\n-" + "objectID": "python/reference/utils.geometry_from_connectivity.html#parameters", + "href": "python/reference/utils.geometry_from_connectivity.html#parameters", + "title": "1 utils.geometry_from_connectivity", + "section": "", + "text": "Name\nType\nDescription\nDefault\n\n\n\n\nnode\nRibasim.Node\n\nrequired\n\n\nfrom_id\nSequence[int]\nFirst node of every edge.\nrequired\n\n\nto_id\nSequence[int]\nSecond node of every edge.\nrequired" }, { - "objectID": "core/usage.html#levelboundary-time", - "href": "core/usage.html#levelboundary-time", - "title": "Usage", - "section": "11.1 LevelBoundary / time", - "text": "11.1 LevelBoundary / time\nThis table is the transient form of the LevelBoundary table. The only difference is that a time column is added and activity is assumed to be true. The table must by sorted by time, and per time it must be sorted by node_id. With this the levels can be updated over time. In between the given times the level is interpolated linearly, and outside the flow rate is constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\ntime\nDateTime\n-\nsorted\n\n\nnode_id\nInt\n-\nsorted per time\n\n\nlevel\nFloat64\n\\(m\\)\n-" + "objectID": "python/reference/utils.geometry_from_connectivity.html#returns", + "href": "python/reference/utils.geometry_from_connectivity.html#returns", + "title": "1 utils.geometry_from_connectivity", + "section": "", + "text": "Type\nDescription\n\n\n\n\nnp.ndarray\nArray of shapely LineStrings." }, { - "objectID": "core/usage.html#flowboundary-time", - "href": "core/usage.html#flowboundary-time", - "title": "Usage", - "section": "12.1 FlowBoundary / time", - "text": "12.1 FlowBoundary / time\nThis table is the transient form of the FlowBoundary table. The only differences are that a time column is added and the nodes are assumed to be active so this column is removed. The table must by sorted by time, and per time it must be sorted by node_id. With this the flow rates can be updated over time. In between the given times the flow rate is interpolated linearly, and outside the flow rate is constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\ntime\nDateTime\n-\nsorted\n\n\nnode_id\nInt\n-\nsorted per time\n\n\nflow_rate\nFloat64\n\\(m^3 s^{-1}\\)\nnon-negative" + "objectID": "python/reference/Edge.html", + "href": "python/reference/Edge.html", + "title": "1 Edge", + "section": "", + "text": "Edge()\nDefines the connections between nodes.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable describing the flow connections.\nrequired\n\n\n\n\n\n\n\n\n\nName\nDescription\n\n\n\n\nwrite_layer\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nEdge.write_layer(self, path)\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\npath\nFilePath\n\nrequired" }, { - "objectID": "core/usage.html#sec-condition", - "href": "core/usage.html#sec-condition", - "title": "Usage", - "section": "16.1 DiscreteControl / condition", - "text": "16.1 DiscreteControl / condition\nThe condition schema defines conditions of the form ‘the discrete_control node with this node id listens to whether the given variable of the node with the given listen feature id is grater than the given value’. If the condition variable comes from a time-series, a look ahead \\(\\Delta t\\) can be supplied.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\nlisten_feature_id\nInt\n-\n-\n\n\nvariable\nString\n-\nmust be “level” or “flow_rate”\n\n\ngreater_than\nFloat64\nvarious\n-\n\n\nlook_ahead\nFloat64\n\\(s\\)\nOnly on transient boundary conditions, non-negative (optional, default 0)" + "objectID": "python/reference/Edge.html#parameters", + "href": "python/reference/Edge.html#parameters", + "title": "1 Edge", + "section": "", + "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable describing the flow connections.\nrequired" }, { - "objectID": "core/usage.html#discretecontrol-logic", - "href": "core/usage.html#discretecontrol-logic", - "title": "Usage", - "section": "16.2 DiscreteControl / logic", - "text": "16.2 DiscreteControl / logic\nThe logic schema defines which control states are triggered based on the truth of the conditions a discrete_control node listens to. DiscreteControl is applied in the Julia core as follows:\n\nDuring the simulation it is checked whether the truth of any of the conditions changes.\nWhen a condition changes, the corresponding discrrete_control node id is retrieved (node_id in the condition schema above).\nThe truth value of all the conditions this discrete_control node lisens to are retrieved, in the order as they are specified in the condition schema. This is then converted into a string of “T” for true and “F” for false. This string we call the truth state.*\nThe table below determines for the given discrete_control node ID and truth state what the corresponding control state is.\nFor all the nodes this discrete_control node affects (as given by the “control” edges in Edges / static), their parameters are set to those parameters in NodeType / static corresponding to the determined control state.\n\n*. There is also a second truth state created in which for the last condition that changed it is specified whether it was an upcrossing (“U”) or downcrossing (“D”) of the threshold (greater than) value. If a control state is specified for a truth state that is crossing-specific, this takes precedence over the control state for the truth state that contains only “T” and “F”.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\ntruth_state\nString\n-\nConsists of the characters “T” (true), “F” (false), “U” (upcrossing), “D” (downcrossing) and “*” (any)\n\n\ncontrol_state\nString\n-" + "objectID": "python/reference/Edge.html#methods", + "href": "python/reference/Edge.html#methods", + "title": "1 Edge", + "section": "", + "text": "Name\nDescription\n\n\n\n\nwrite_layer\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nEdge.write_layer(self, path)\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\npath\nFilePath\n\nrequired" }, { - "objectID": "core/usage.html#discretecontrol-output", - "href": "core/usage.html#discretecontrol-output", - "title": "Usage", - "section": "16.3 DiscreteControl output", - "text": "16.3 DiscreteControl output\nThe control table contains a record of each change of control state: when it happened, which control node was involved, to which control state it changed and based on which truth state.\n\n\n\ncolumn\ntype\n\n\n\n\ntime\nDateTime\n\n\ncontrol_node_id\nInt\n\n\ntruth_state\nString\n\n\ncontrol_state\nString" + "objectID": "python/reference/FractionalFlow.html", + "href": "python/reference/FractionalFlow.html", + "title": "1 FractionalFlow", + "section": "", + "text": "FractionalFlow()\nReceives a fraction of the flow. The fractions must sum to 1.0 for a furcation.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable with the constant flow fractions.\nrequired" }, { - "objectID": "core/usage.html#pidcontrol-time", - "href": "core/usage.html#pidcontrol-time", - "title": "Usage", - "section": "17.1 PidControl / time", - "text": "17.1 PidControl / time\nThis table is the transient form of the PidControl table. The differences are that a time column is added and the nodes are assumed to be active so this column is removed. The table must by sorted by time, and per time it must be sorted by node_id. With this the target level and PID coefficients can be updated over time. In between the given times the these values interpolated linearly, and outside these values area constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted per time\n\n\ntime\nDateTime\n-\nsorted\n\n\nlisten_node_id\nInt\n-\n-\n\n\ntarget\nFloat64\n\\(m\\)\n-\n\n\nproportional\nFloat64\n\\(s^{-1}\\)\n-\n\n\nintegral\nFloat64\n\\(s^{-2}\\)\n-\n\n\nderivative\nFloat64\n-\n-" + "objectID": "python/reference/FractionalFlow.html#parameters", + "href": "python/reference/FractionalFlow.html#parameters", + "title": "1 FractionalFlow", + "section": "", + "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable with the constant flow fractions.\nrequired" }, { "objectID": "qgis/index.html", @@ -357,60 +392,123 @@ "text": "In QGIS select the model group.\n\n\n\n\n\nIn the Ribasim plugin widget, select the Output tab and click “Associate Output”.\n\n\n\n\n\nSelect output/basin.arrow.\n\n\n\n\n\nThis adds metadata to the model that the iMOD plugin can use to find the timeseries data that is associated to the model nodes.\n\n\n\nClick the “Time Series” button of the iMOD plugin.\n\n\n\n\n\nSelect the variables that you want to plot.\n\n\n\n\n\nClick “Select points” and select a node by dragging a rectangle around it on the map. Hold the Ctrl key to select multiple nodes. Currently only the Basin nodes can be plotted.\n\n\n\n\n\nThe associated time series are shown the the graph." }, { - "objectID": "contribute/python.html", - "href": "contribute/python.html", - "title": "Python tooling development", + "objectID": "python/reference/utils.connectivity_from_geometry.html", + "href": "python/reference/utils.connectivity_from_geometry.html", + "title": "1 utils.connectivity_from_geometry", "section": "", - "text": "First, set up pixi as described on their getting started page.\nThen set up the environment by running the following commands:\npixi install\npixi run post-install\n\n\n\nIn order to run tests on Ribasim Python execute\npixi run test-ribasim-python\n\n\n\nMake sure to run Clear All Outputs on the notebook before committing.\n\n\n\nBefore running the Julia tests or building binaries, example model input needs to created. This is done by running the following:\npixi run generate-testmodels\nThis places example model input files under ./generated_testmodels/. If the example models change, re-run this script.\n\n\n\n\nInstall the Python, ruff and autoDocstring extensions.\nCopy .vscode/settings_template.json into .vscode/settings.json\n\n\n\n\n\nUpdate __version__ in ribasim/__init__.py\nOpen a terminal and run cd python/ribasim\nActivate the ribasim environment with conda activate ribasim\nIf present remove dist folder\nRe-create the wheels:\n\npython -m build\n\nCheck the package files:\n\ntwine check dist/*\n\nMake a new commit with the updated version number, and push to remote\nRe-upload the new files:\n\ntwine upload dist/*\n\n\n\nTo run our linting suite locally, execute:\npixi run lint" + "text": "utils.connectivity_from_geometry(node, lines)\nDerive from_node_id and to_node_id for every edge in lines. LineStrings may be used to connect multiple nodes in a sequence, but every linestring vertex must also a node.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nnode\nNode\n\nrequired\n\n\nlines\nnp.ndarray\nArray of shapely linestrings.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nnp.ndarray of int\n\n\n\nnp.ndarray of int" }, { - "objectID": "contribute/python.html#setting-up-pixi", - "href": "contribute/python.html#setting-up-pixi", - "title": "Python tooling development", + "objectID": "python/reference/utils.connectivity_from_geometry.html#parameters", + "href": "python/reference/utils.connectivity_from_geometry.html#parameters", + "title": "1 utils.connectivity_from_geometry", "section": "", - "text": "First, set up pixi as described on their getting started page.\nThen set up the environment by running the following commands:\npixi install\npixi run post-install" + "text": "Name\nType\nDescription\nDefault\n\n\n\n\nnode\nNode\n\nrequired\n\n\nlines\nnp.ndarray\nArray of shapely linestrings.\nrequired" }, { - "objectID": "contribute/python.html#sec-test", - "href": "contribute/python.html#sec-test", - "title": "Python tooling development", + "objectID": "python/reference/utils.connectivity_from_geometry.html#returns", + "href": "python/reference/utils.connectivity_from_geometry.html#returns", + "title": "1 utils.connectivity_from_geometry", "section": "", - "text": "In order to run tests on Ribasim Python execute\npixi run test-ribasim-python" + "text": "Type\nDescription\n\n\n\n\nnp.ndarray of int\n\n\n\nnp.ndarray of int" }, { - "objectID": "contribute/python.html#updating-example-notebooks", - "href": "contribute/python.html#updating-example-notebooks", - "title": "Python tooling development", + "objectID": "python/reference/Node.html", + "href": "python/reference/Node.html", + "title": "1 Node", "section": "", - "text": "Make sure to run Clear All Outputs on the notebook before committing." + "text": "Node()\nThe Ribasim nodes as Point geometries.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\ngeopandas.GeoDataFrame\nTable with node ID, type and geometry.\nrequired\n\n\n\n\n\n\n\n\n\nName\nDescription\n\n\n\n\nplot\nPlot the nodes. Each node type is given a separate marker.\n\n\nwrite_layer\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nNode.plot(self, ax=None, zorder=None)\nPlot the nodes. Each node type is given a separate marker.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nOptional\nThe axis on which the nodes will be plotted.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nNone\n\n\n\n\n\n\n\n\nNode.write_layer(self, path)\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\npath\nFilePath\n\nrequired" }, { - "objectID": "contribute/python.html#prepare-model-input", - "href": "contribute/python.html#prepare-model-input", - "title": "Python tooling development", + "objectID": "python/reference/Node.html#parameters", + "href": "python/reference/Node.html#parameters", + "title": "1 Node", "section": "", - "text": "Before running the Julia tests or building binaries, example model input needs to created. This is done by running the following:\npixi run generate-testmodels\nThis places example model input files under ./generated_testmodels/. If the example models change, re-run this script." + "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\ngeopandas.GeoDataFrame\nTable with node ID, type and geometry.\nrequired" }, { - "objectID": "contribute/python.html#sec-vscode", - "href": "contribute/python.html#sec-vscode", - "title": "Python tooling development", + "objectID": "python/reference/Node.html#methods", + "href": "python/reference/Node.html#methods", + "title": "1 Node", "section": "", - "text": "Install the Python, ruff and autoDocstring extensions.\nCopy .vscode/settings_template.json into .vscode/settings.json" + "text": "Name\nDescription\n\n\n\n\nplot\nPlot the nodes. Each node type is given a separate marker.\n\n\nwrite_layer\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nNode.plot(self, ax=None, zorder=None)\nPlot the nodes. Each node type is given a separate marker.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nOptional\nThe axis on which the nodes will be plotted.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nNone\n\n\n\n\n\n\n\n\nNode.write_layer(self, path)\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\npath\nFilePath\n\nrequired" }, { - "objectID": "contribute/python.html#how-to-publish-to-pypi", - "href": "contribute/python.html#how-to-publish-to-pypi", - "title": "Python tooling development", + "objectID": "python/reference/Pump.html", + "href": "python/reference/Pump.html", + "title": "1 Pump", "section": "", - "text": "Update __version__ in ribasim/__init__.py\nOpen a terminal and run cd python/ribasim\nActivate the ribasim environment with conda activate ribasim\nIf present remove dist folder\nRe-create the wheels:\n\npython -m build\n\nCheck the package files:\n\ntwine check dist/*\n\nMake a new commit with the updated version number, and push to remote\nRe-upload the new files:\n\ntwine upload dist/*" + "text": "Pump()\nPump water from a source node to a destination node. The set flow rate will be pumped unless the intake storage is less than 10m3, in which case the flow rate will be linearly reduced to 0 m3/s. Negative flow rates are not supported. Note that the intake must always be a Basin.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with constant flow rates.\nrequired" }, { - "objectID": "contribute/python.html#linting", - "href": "contribute/python.html#linting", - "title": "Python tooling development", + "objectID": "python/reference/Pump.html#parameters", + "href": "python/reference/Pump.html#parameters", + "title": "1 Pump", "section": "", - "text": "To run our linting suite locally, execute:\npixi run lint" + "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with constant flow rates.\nrequired" + }, + { + "objectID": "python/reference/LevelBoundary.html", + "href": "python/reference/LevelBoundary.html", + "title": "1 LevelBoundary", + "section": "", + "text": "LevelBoundary()\nStores water at a given level unaffected by flow, like an infinitely large basin.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable with the constant water levels.\nrequired" + }, + { + "objectID": "python/reference/LevelBoundary.html#parameters", + "href": "python/reference/LevelBoundary.html#parameters", + "title": "1 LevelBoundary", + "section": "", + "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable with the constant water levels.\nrequired" + }, + { + "objectID": "python/reference/TabulatedRatingCurve.html", + "href": "python/reference/TabulatedRatingCurve.html", + "title": "1 TabulatedRatingCurve", + "section": "", + "text": "TabulatedRatingCurve()\nLinearly interpolates discharge between a tabulation of level and discharge.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with constant rating curves.\nrequired\n\n\ntime\npandas.DataFrame\nTable with time-varying rating curves.\nrequired" + }, + { + "objectID": "python/reference/TabulatedRatingCurve.html#parameters", + "href": "python/reference/TabulatedRatingCurve.html#parameters", + "title": "1 TabulatedRatingCurve", + "section": "", + "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with constant rating curves.\nrequired\n\n\ntime\npandas.DataFrame\nTable with time-varying rating curves.\nrequired" + }, + { + "objectID": "python/reference/ManningResistance.html", + "href": "python/reference/ManningResistance.html", + "title": "1 ManningResistance", + "section": "", + "text": "ManningResistance()\nFlow through this connection is estimated by conservation of energy and the Manning-Gauckler formula to estimate friction losses.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with the constant Manning parameters.\nrequired" + }, + { + "objectID": "python/reference/ManningResistance.html#parameters", + "href": "python/reference/ManningResistance.html#parameters", + "title": "1 ManningResistance", + "section": "", + "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with the constant Manning parameters.\nrequired" + }, + { + "objectID": "python/reference/LinearResistance.html", + "href": "python/reference/LinearResistance.html", + "title": "1 LinearResistance", + "section": "", + "text": "LinearResistance()\nFlow through this connection linearly depends on the level difference between the two connected basins.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with the constant resistances.\nrequired" + }, + { + "objectID": "python/reference/LinearResistance.html#parameters", + "href": "python/reference/LinearResistance.html#parameters", + "title": "1 LinearResistance", + "section": "", + "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with the constant resistances.\nrequired" + }, + { + "objectID": "python/examples.html", + "href": "python/examples.html", + "title": "Examples", + "section": "", + "text": "1 Basic model with static forcing\n\nimport geopandas as gpd\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\n\nimport ribasim\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [1, 1, 3, 3, 6, 6, 9, 9],\n \"area\": [0.01, 1000.0] * 4,\n \"level\": [0.0, 1.0] * 4,\n }\n)\n\n# Convert steady forcing to m/s\n# 2 mm/d precipitation, 1 mm/d evaporation\nseconds_in_day = 24 * 3600\nprecipitation = 0.002 / seconds_in_day\nevaporation = 0.001 / seconds_in_day\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [0],\n \"drainage\": [0.0],\n \"potential_evaporation\": [evaporation],\n \"infiltration\": [0.0],\n \"precipitation\": [precipitation],\n \"urban_runoff\": [0.0],\n }\n)\nstatic = static.iloc[[0, 0, 0, 0]]\nstatic[\"node_id\"] = [1, 3, 6, 9]\n\nbasin = ribasim.Basin(profile=profile, static=static)\n\nSetup linear resistance:\n\nlinear_resistance = ribasim.LinearResistance(\n static=pd.DataFrame(\n data={\"node_id\": [10, 12], \"resistance\": [5e3, (3600.0 * 24) / 100.0]}\n )\n)\n\nSetup Manning resistance:\n\nmanning_resistance = ribasim.ManningResistance(\n static=pd.DataFrame(\n data={\n \"node_id\": [2],\n \"length\": [900.0],\n \"manning_n\": [0.04],\n \"profile_width\": [6.0],\n \"profile_slope\": [3.0],\n }\n )\n)\n\nSet up a rating curve node:\n\n# Discharge: lose 1% of storage volume per day at storage = 1000.0.\nq1000 = 1000.0 * 0.01 / seconds_in_day\n\nrating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\n \"node_id\": [4, 4],\n \"level\": [0.0, 1.0],\n \"discharge\": [0.0, q1000],\n }\n )\n)\n\nSetup fractional flows:\n\nfractional_flow = ribasim.FractionalFlow(\n static=pd.DataFrame(\n data={\n \"node_id\": [5, 8, 13],\n \"fraction\": [0.3, 0.6, 0.1],\n }\n )\n)\n\nSetup pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": [7],\n \"flow_rate\": [0.5 / 3600],\n }\n )\n)\n\nSetup level boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [11, 17],\n \"level\": [0.5, 1.5],\n }\n )\n)\n\nSetup flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [15, 16],\n \"flow_rate\": [1e-4, 1e-4],\n }\n )\n)\n\nSetup terminal:\n\nterminal = ribasim.Terminal(\n static=pd.DataFrame(\n data={\n \"node_id\": [14],\n }\n )\n)\n\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: Basin,\n (1.0, 0.0), # 2: ManningResistance\n (2.0, 0.0), # 3: Basin\n (3.0, 0.0), # 4: TabulatedRatingCurve\n (3.0, 1.0), # 5: FractionalFlow\n (3.0, 2.0), # 6: Basin\n (4.0, 1.0), # 7: Pump\n (4.0, 0.0), # 8: FractionalFlow\n (5.0, 0.0), # 9: Basin\n (6.0, 0.0), # 10: LinearResistance\n (2.0, 2.0), # 11: LevelBoundary\n (2.0, 1.0), # 12: LinearResistance\n (3.0, -1.0), # 13: FractionalFlow\n (3.0, -2.0), # 14: Terminal\n (3.0, 3.0), # 15: FlowBoundary\n (0.0, 1.0), # 16: FlowBoundary\n (6.0, 1.0), # 17: LevelBoundary\n ]\n)\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_id, node_type = ribasim.Node.get_node_ids_and_types(\n basin,\n manning_resistance,\n rating_curve,\n pump,\n fractional_flow,\n linear_resistance,\n level_boundary,\n flow_boundary,\n terminal,\n)\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n static=gpd.GeoDataFrame(\n data={\"type\": node_type},\n index=pd.Index(node_id, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array(\n [1, 2, 3, 4, 4, 5, 6, 8, 7, 9, 11, 12, 4, 13, 15, 16, 10], dtype=np.int64\n)\nto_id = np.array(\n [2, 3, 4, 5, 8, 6, 7, 9, 9, 10, 12, 3, 13, 14, 6, 1, 17], dtype=np.int64\n)\nlines = ribasim.utils.geometry_from_connectivity(node, from_id, to_id)\nedge = ribasim.Edge(\n static=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": len(from_id) * [\"flow\"],\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup a model:\n\nmodel = ribasim.Model(\n modelname=\"basic\",\n node=node,\n edge=edge,\n basin=basin,\n level_boundary=level_boundary,\n flow_boundary=flow_boundary,\n pump=pump,\n linear_resistance=linear_resistance,\n manning_resistance=manning_resistance,\n tabulated_rating_curve=rating_curve,\n fractional_flow=fractional_flow,\n terminal=terminal,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2021-01-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n<Axes: >\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"basic\")\n\n\n\n2 Update the basic model with transient forcing\nThis assumes you have already created the basic model with static forcing.\n\nimport numpy as np\nimport pandas as pd\nimport xarray as xr\n\nimport ribasim\n\n\nmodel = ribasim.Model.from_toml(datadir / \"basic/basic.toml\")\n\n\ntime = pd.date_range(model.starttime, model.endtime)\nday_of_year = time.day_of_year.to_numpy()\nseconds_per_day = 24 * 60 * 60\nevaporation = (\n (-1.0 * np.cos(day_of_year / 365.0 * 2 * np.pi) + 1.0) * 0.0025 / seconds_per_day\n)\nrng = np.random.default_rng(seed=0)\nprecipitation = (\n rng.lognormal(mean=-1.0, sigma=1.7, size=time.size) * 0.001 / seconds_per_day\n)\n\nWe’ll use xarray to easily broadcast the values.\n\ntimeseries = (\n pd.DataFrame(\n data={\n \"node_id\": 1,\n \"time\": time,\n \"drainage\": 0.0,\n \"potential_evaporation\": evaporation,\n \"infiltration\": 0.0,\n \"precipitation\": precipitation,\n \"urban_runoff\": 0.0,\n }\n )\n .set_index(\"time\")\n .to_xarray()\n)\n\nbasin_ids = model.basin.static[\"node_id\"].to_numpy()\nbasin_nodes = xr.DataArray(\n np.ones(len(basin_ids)), coords={\"node_id\": basin_ids}, dims=[\"node_id\"]\n)\nforcing = (timeseries * basin_nodes).to_dataframe().reset_index()\n\n\nstate = pd.DataFrame(\n data={\n \"node_id\": basin_ids,\n \"level\": 1.4,\n \"concentration\": 0.0,\n }\n)\n\n\nmodel.basin.time = forcing\nmodel.basin.state = state\n\n\nmodel.modelname = \"basic_transient\"\nmodel.write(datadir / \"basic_transient\")\n\nNow run the model with ribasim basic-transient/basic.toml. After running the model, read back the output:\n\ndf_basin = pd.read_feather(datadir / \"basic_transient/output/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\ndf_basin_wide[\"level\"].plot()\n\n<Axes: xlabel='time'>\n\n\n\n\n\n\ndf_flow = pd.read_feather(datadir / \"basic_transient/output/flow.arrow\")\ndf_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\ndf_flow[\"flow_m3d\"] = df_flow.flow * 86400\nax = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_m3d\").plot()\nax.legend(bbox_to_anchor=(1.3, 1), title=\"Edge\")\n\n<matplotlib.legend.Legend at 0x7faf64498690>\n\n\n\n\n\n\ntype(df_flow)\n\npandas.core.frame.DataFrame\n\n\n\n\n3 Model with discrete control\nThe model constructed below consists of a single basin which slowly drains trough a TabulatedRatingCurve, but is held within a range around a target level (setpoint) by two connected pumps. These two pumps behave like a reversible pump. When pumping can be done in only one direction, and the other direction is only possible under gravity, use an Outlet for that direction.\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: Basin\n (1.0, 1.0), # 2: Pump\n (1.0, -1.0), # 3: Pump\n (2.0, 0.0), # 4: LevelBoundary\n (-1.0, 0.0), # 5: TabulatedRatingCurve\n (-2.0, 0.0), # 6: Terminal\n (1.0, 0.0), # 7: DiscreteControl\n ]\n)\n\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"Basin\",\n \"Pump\",\n \"Pump\",\n \"LevelBoundary\",\n \"TabulatedRatingCurve\",\n \"Terminal\",\n \"DiscreteControl\",\n]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n static=gpd.GeoDataFrame(\n data={\"type\": node_type},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 3, 4, 2, 1, 5, 7, 7], dtype=np.int64)\nto_id = np.array([3, 4, 2, 1, 5, 6, 2, 3], dtype=np.int64)\n\nedge_type = 6 * [\"flow\"] + 2 * [\"control\"]\n\nlines = ribasim.utils.geometry_from_connectivity(node, from_id, to_id)\nedge = ribasim.Edge(\n static=gpd.GeoDataFrame(\n data={\"from_node_id\": from_id, \"to_node_id\": to_id, \"edge_type\": edge_type},\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [1, 1],\n \"area\": [1000.0, 1000.0],\n \"level\": [0.0, 1.0],\n }\n)\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [1],\n \"drainage\": [0.0],\n \"potential_evaporation\": [0.0],\n \"infiltration\": [0.0],\n \"precipitation\": [0.0],\n \"urban_runoff\": [0.0],\n }\n)\n\nstate = pd.DataFrame(data={\"node_id\": [1], \"level\": [20.0]})\n\nbasin = ribasim.Basin(profile=profile, static=static, state=state)\n\nSetup the discrete control:\n\ncondition = pd.DataFrame(\n data={\n \"node_id\": 3 * [7],\n \"listen_feature_id\": 3 * [1],\n \"variable\": 3 * [\"level\"],\n \"greater_than\": [5.0, 10.0, 15.0], # min, setpoint, max\n }\n)\n\nlogic = pd.DataFrame(\n data={\n \"node_id\": 5 * [7],\n \"truth_state\": [\"FFF\", \"U**\", \"T*F\", \"**D\", \"TTT\"],\n \"control_state\": [\"in\", \"in\", \"none\", \"out\", \"out\"],\n }\n)\n\ndiscrete_control = ribasim.DiscreteControl(condition=condition, logic=logic)\n\nThe above control logic can be summarized as follows: - If the level gets above the maximum, activate the control state “out” until the setpoint is reached; - If the level gets below the minimum, active the control state “in” until the setpoint is reached; - Otherwise activate the control state “none”.\nSetup the pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": 3 * [2] + 3 * [3],\n \"control_state\": 2 * [\"none\", \"in\", \"out\"],\n \"flow_rate\": [0.0, 2e-3, 0.0, 0.0, 0.0, 2e-3],\n }\n )\n)\n\nThe pump data defines the following:\n\n\n\nControl state\nPump #2 flow rate (m/s)\nPump #3 flow rate (m/s)\n\n\n\n\n“none”\n0.0\n0.0\n\n\n“in”\n2e-3\n0.0\n\n\n“out”\n0.0\n2e-3\n\n\n\nSetup the level boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(data={\"node_id\": [4], \"level\": [10.0]})\n)\n\nSetup the rating curve:\n\nrating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\"node_id\": 2 * [5], \"level\": [2.0, 15.0], \"discharge\": [0.0, 1e-3]}\n )\n)\n\nSetup the terminal:\n\nterminal = ribasim.Terminal(static=pd.DataFrame(data={\"node_id\": [6]}))\n\nSetup a model:\n\nmodel = ribasim.Model(\n modelname=\"level_setpoint_with_minmax\",\n node=node,\n edge=edge,\n basin=basin,\n pump=pump,\n level_boundary=level_boundary,\n tabulated_rating_curve=rating_curve,\n terminal=terminal,\n discrete_control=discrete_control,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2021-01-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n<Axes: >\n\n\n\n\n\nListen edges are plotted with a dashed line since they are not present in the “Edge / static” schema but only in the “Control / condition” schema.\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"level_setpoint_with_minmax\")\n\nNow run the model with level_setpoint_with_minmax/level_setpoint_with_minmax.toml. After running the model, read back the output:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"level_setpoint_with_minmax/output/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\n\nax = df_basin_wide[\"level\"].plot()\n\ngreater_than = model.discrete_control.condition.greater_than\n\nax.hlines(\n greater_than,\n df_basin.time[0],\n df_basin.time.max(),\n lw=1,\n ls=\"--\",\n color=\"k\",\n)\n\ndf_control = pd.read_feather(\n datadir / \"level_setpoint_with_minmax/output/control.arrow\"\n)\n\ny_min, y_max = ax.get_ybound()\nax.fill_between(df_control.time[:2], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\nax.fill_between(df_control.time[2:4], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\n\nax.set_xticks(\n date2num(df_control.time).tolist(),\n df_control.control_state.tolist(),\n rotation=50,\n)\n\nax.set_yticks(greater_than, [\"min\", \"setpoint\", \"max\"])\nax.set_ylabel(\"level\")\nplt.show()\n\n\n\n\nThe highlighted regions show where a pump is active.\nLet’s print an overview of what happened with control:\n\nmodel.print_discrete_control_record(\n datadir / \"level_setpoint_with_minmax/output/control.arrow\"\n)\n\n0. At 2020-01-01 00:00:00 the control node with ID 7 reached truth state TTT:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level > 10.0\n For node ID 1 (Basin): level > 15.0\n\n This yielded control state \"out\":\n For node ID 2 (Pump): flow_rate = 0.0\n For node ID 3 (Pump): flow_rate = 0.002\n\n1. At 2020-02-09 01:17:29.324000 the control node with ID 7 reached truth state TFF:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level < 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"none\":\n For node ID 2 (Pump): flow_rate = 0.0\n For node ID 3 (Pump): flow_rate = 0.0\n\n2. At 2020-07-05 13:24:51.165000 the control node with ID 7 reached truth state FFF:\n For node ID 1 (Basin): level < 5.0\n For node ID 1 (Basin): level < 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"in\":\n For node ID 2 (Pump): flow_rate = 0.002\n For node ID 3 (Pump): flow_rate = 0.0\n\n3. At 2020-08-11 11:49:59.015000 the control node with ID 7 reached truth state TTF:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level > 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"none\":\n For node ID 2 (Pump): flow_rate = 0.0\n For node ID 3 (Pump): flow_rate = 0.0\n\n\n\nNote that crossing direction specific truth states (containing “U”, “D”) are not present in this overview even though they are part of the control logic. This is because in the control logic for this model these truth states are only used to sustain control states, while the overview only shows changes in control states.\n\n\n4 Model with PID control\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: FlowBoundary\n (1.0, 0.0), # 2: Basin\n (2.0, 0.5), # 3: Pump\n (3.0, 0.0), # 4: LevelBoundary\n (1.5, 1.0), # 5: PidControl\n (2.0, -0.5), # 6: outlet\n (1.5, -1.0), # 7: PidControl\n ]\n)\n\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"FlowBoundary\",\n \"Basin\",\n \"Pump\",\n \"LevelBoundary\",\n \"PidControl\",\n \"Outlet\",\n \"PidControl\",\n]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n static=gpd.GeoDataFrame(\n data={\"type\": node_type},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 2, 3, 4, 6, 5, 7], dtype=np.int64)\nto_id = np.array([2, 3, 4, 6, 2, 3, 6], dtype=np.int64)\n\nlines = ribasim.utils.geometry_from_connectivity(node, from_id, to_id)\nedge = ribasim.Edge(\n static=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": 5 * [\"flow\"] + 2 * [\"control\"],\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\"node_id\": [2, 2], \"level\": [0.0, 1.0], \"area\": [1000.0, 1000.0]}\n)\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [2],\n \"drainage\": [0.0],\n \"potential_evaporation\": [0.0],\n \"infiltration\": [0.0],\n \"precipitation\": [0.0],\n \"urban_runoff\": [0.0],\n }\n)\n\nstate = pd.DataFrame(\n data={\n \"node_id\": [2],\n \"level\": [6.0],\n }\n)\n\nbasin = ribasim.Basin(profile=profile, static=static, state=state)\n\nSetup the pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": [3],\n \"flow_rate\": [0.0], # Will be overwritten by PID controller\n }\n )\n)\n\nSetup the outlet:\n\noutlet = ribasim.Outlet(\n static=pd.DataFrame(\n data={\n \"node_id\": [6],\n \"flow_rate\": [0.0], # Will be overwritten by PID controller\n }\n )\n)\n\nSetup flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(data={\"node_id\": [1], \"flow_rate\": [1e-3]})\n)\n\nSetup flow boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [4],\n \"level\": [1.0], # Not relevant\n }\n )\n)\n\nSetup PID control:\n\npid_control = ribasim.PidControl(\n time=pd.DataFrame(\n data={\n \"node_id\": 4 * [5, 7],\n \"time\": [\n \"2020-01-01 00:00:00\",\n \"2020-01-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n ],\n \"listen_node_id\": 4 * [2, 2],\n \"target\": [5.0, 5.0, 5.0, 5.0, 7.5, 7.5, 7.5, 7.5],\n \"proportional\": 4 * [-1e-3, 1e-3],\n \"integral\": 4 * [-1e-7, 1e-7],\n \"derivative\": 4 * [0.0, 0.0],\n }\n )\n)\n\nNote that the coefficients for the pump and the outlet are equal in magnitude but opposite in sign. This way the pump and the outlet equally work towards the same goal, while having opposite effects on the controlled basin due to their connectivity to this basin.\nSetup a model:\n\nmodel = ribasim.Model(\n modelname=\"pid_control\",\n node=node,\n edge=edge,\n basin=basin,\n flow_boundary=flow_boundary,\n level_boundary=level_boundary,\n pump=pump,\n outlet=outlet,\n pid_control=pid_control,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-12-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n<Axes: >\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"pid_control\")\n\nNow run the model with ribasim pid_control/pid_control.toml. After running the model, read back the output:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"pid_control/output/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\nax = df_basin_wide[\"level\"].plot()\nax.set_ylabel(\"level [m]\")\n\n# Plot target level\ntarget_levels = model.pid_control.time.target.to_numpy()[::2]\ntimes = date2num(model.pid_control.time.time)[::2]\nax.plot(times, target_levels, color=\"k\", ls=\":\", label=\"target level\");" }, { "objectID": "contribute/core.html", @@ -475,41 +573,6 @@ "section": "2.3 Run Ribasim simulations", "text": "2.3 Run Ribasim simulations\nAssuming your working directory is the root of the repository, you can activate this project by entering the Pkg mode of the REPL with ] and execute:\npkg> activate core\npkg> instantiate\nPress backspace to go back to the Julia REPL. There you can run a model with:\njulia> Ribasim.run(\"path/to/model.toml\")\n\n\n\n\n\n\nTip\n\n\n\nThe Julia VS Code extension allows you to execute code cells in REPL. This is a very convenient way of executing only parts of your source file." }, - { - "objectID": "index.html", - "href": "index.html", - "title": "Ribasim", - "section": "", - "text": "Ribasim is a water resources model, designed to be the replacement of the regional surface water modules Mozart and SIMRES in the Netherlands Hydrological Instrument (NHI). Ribasim is a work in progress, it is a prototype that demonstrates all essential functionalities. Further development of the prototype in a software release is planned in 2022 and 2023.\nRibasim is written in the Julia programming language and is built on top of the SciML: Open Source Software for Scientific Machine Learning libraries, notably DifferentialEquations.jl.\nThe latest builds can be downloaded here:\nCurrently only Windows builds for ribasim_cli.zip are available. See Usage for more information." - }, - { - "objectID": "index.html#water-balance-equations", - "href": "index.html#water-balance-equations", - "title": "Ribasim", - "section": "2.1 Water balance equations", - "text": "2.1 Water balance equations\nThe water balance equation for a drainage basin (Wikipedia contributors 2022) 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.\n\\[\n\\frac{\\mathrm{d}S}{\\mathrm{d}t} = Q_{in} - Q_{out}\n\\]\nWe 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.\n\\[\n\\frac{\\mathrm{d}S}{\\mathrm{d}t} = R + P + ET + Q_{rest}\n\\]" - }, - { - "objectID": "index.html#time", - "href": "index.html#time", - "title": "Ribasim", - "section": "2.2 Time", - "text": "2.2 Time\nThe 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. 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.\nThe 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.\nRibasim 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⁻¹." - }, - { - "objectID": "index.html#sec-space", - "href": "index.html#sec-space", - "title": "Ribasim", - "section": "2.3 Space", - "text": "2.3 Space\nThe 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) (Reggiani, Sivapalan, and Majid Hassanizadeh 1998). 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. (In Mozart the spatial unit was called Local Surface Water (LSW)). 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.\n\n\n\nMozart Local Surface Water polygons and their drainage.\n\n\nThe network is described as graph. Flow can be bi-directional, and the graph does not have to be acyclic.\n\n\n\n\ngraph LR;\n A[\"basin A\"] --- B[\"basin B\"];\n A --- C[\"basin C\"];\n B --- D[\"basin D\"];\n C --- D;\n\n\n\n\n\nInternally 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.\nMultiple basins may exist within the same spatial polygon, representing different aspects of the surface water system (perennial ditches, ephemeral ditches, or even surface ponding). Figure 1, Figure 2, Figure 3 show the 25.0 m rasterized primary, secondary, and tertiary surface waters as identified by BRT TOP10NL (PDOK 2022) in the Hupsel basin (as defined in the Mozart LSW’s). These systems may represented in multiple ways.\n\n\n\nFigure 1: Hupsel: primary surface water.\n\n\n\n\n\nFigure 2: Hupsel: secondary surface water.\n\n\n\n\n\nFigure 3: Hupsel: tertiary surface water.\n\n\nAs a single basin (A) containing all surface water, discharging to its downstream basin to the west (B):\n\n\n\n\ngraph LR;\n A[\"basin A\"] --> B[\"basin B\"];\n\n\n\n\n\nSuch a system may be capable of representing discharge, but it cannot represent residence times or differences in solute concentrations: within a single basin, 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.)\n\n\n\n\ngraph LR;\n T[\"basin T\"] --> S[\"basin S\"];\n S --> P[\"basin P\"];\n P --> B[\"basin B\"];\n\n\n\n\n\nAs 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." - }, - { - "objectID": "contribute/index.html", - "href": "contribute/index.html", - "title": "Contributing", - "section": "", - "text": "Ribasim welcomes contributions.\nThere is developer documentation for the Julia core and Python tooling. A guide on how to add a new node type to both is written in adding node types." - }, { "objectID": "contribute/addnode.html", "href": "contribute/addnode.html", @@ -529,77 +592,28 @@ "href": "contribute/addnode.html#reading-from-configuration", "title": "Adding node types", "section": "1.2 Reading from configuration", - "text": "1.2 Reading from configuration\nThere can be several schemas associated with a single node type. To define a schema for the new node type, add the following to validation.jl:\n@schema \"ribasim.newnodetype.static\" NewNodeTypeStatic\n\n\"\"\"\nnode_id: node ID of the NewNodeType node\n\"\"\"\n@version NewNodeTypeStaticV1 begin\n node_id::Int\n # Other fields\nend\nHere Static refers to data that does not change over time. For naming conventions of these schemas see Node usage.\nvalidation.jl also deals with checking and applying a specific sorting order for the tabular data (default is sorting by node ID only), see sort_by_function and sorted_table!.\nNow we define the function that is called in the second bullet above, in create.jl:\nfunction NewNodeType(db::DB, config::Config)::NewNodeType\n static = load_structvector(db, config, NewNodeTypeStaticV1)\n defaults = (; foo = 1, bar = false)\n # Process potential control states in the static data\n parsed_parameters, valid = parse_static_and_time(db, config, \"Outlet\"; static, defaults)\n\n if !valid\n error(\"Errors occurred when parsing NewNodeType data.\")\n end\n\n # Unpack the fields of static as inputs for the NewNodeType constructor\n return NewNodeType(\n parsed_parameters.node_id,\n parsed_parameters.some_property,\n parsed_parameters.control_mapping)\nend" - }, - { - "objectID": "contribute/addnode.html#node-behavior", - "href": "contribute/addnode.html#node-behavior", - "title": "Adding node types", - "section": "1.3 Node behavior", - "text": "1.3 Node behavior\nIn general if the new node type dictates flow, the behaviour of the new node in the Ribasim core is defined in a method of the formulate_flow! function, which is called within the water_balance! (both in solve.jl) function being the right hand side of the system of differential equations solved by Ribasim. Here the details depend highly on the specifics of the node type. An example structure of a formulate_flow! method is given below.\nfunction formulate_flow!(new_node_type::NewNodeType, p::Parameters)::Nothing\n # Retrieve relevant parameters\n (; connectivity) = p\n (; flow) = connectivity\n (; node_id, param_1, param_2) = new_node_type\n\n # Loop over nodes of NewNodeType\n for (i, id) in enumerate(node_id)\n # compute e.g. flow based on param_1[i], param_2[i]\n end\n\n return nothing\nend" - }, - { - "objectID": "contribute/addnode.html#the-jacobian", - "href": "contribute/addnode.html#the-jacobian", - "title": "Adding node types", - "section": "1.4 The Jacobian", - "text": "1.4 The Jacobian\nSee Equations for a mathematical description of the Jacobian.\nBefore the Julia core runs its simulation, the sparsity structure jac_prototype of \\(J\\) is determined with get_jac_prototype in utils.jl. This function runs trough all node types and looks for nodes that create dependencies between states. It creates a sparse matrix of zeros and ones, where the ones denote locations of possible non-zeros in \\(J\\).\nWe divide the various node types in groups based on what type of state dependencies they yield, and these groups are discussed below. Each group has its own method update_jac_prototype! in utils.jl for the sparsity structure induced by nodes of that group. NewNodeType should be added to the signature of one these methods, or to the list of node types that do not contribute to the Jacobian in the method of update_jac_prototype! whose signature contains node::AbstractParameterNode. Of course it is also possible that a new method of update_jac_prototype! has to be introduced.\nThe current dependency groups are:\n\nOut-neighbor dependencies: examples are TabulatedRatingCurve, Pump (the latter only in the reduction factor regime and not PID controlled). If the in-neighbor of a node of this group is a basin, then the storage of this basin affects itself and the storage of the outneighbor (or the basin one node further if it is connected with a FractionalFlow in between) if that is also a basin;\nEither-neighbor dependencies: examples are LinearResistance, ManningResistance. If either the in-neighbor or out-neighbor of a node of this group is a basin, the storage of this basin depends on itself. If both the in-neighbor and the out-neighbor are basins, their storages also depend on eachother.\nThe PidControl node is a special case which is discussed in equations.\n\nUsing jac_prototype the Jacobian of water_balance! is computed automatically using ForwardDiff.jl with memory management provided by PreallocationTools.jl. These computations make use of DiffCache and dual numbers." - }, - { - "objectID": "contribute/addnode.html#python-class", - "href": "contribute/addnode.html#python-class", - "title": "Adding node types", - "section": "2.1 Python class", - "text": "2.1 Python class\nCreate a new file python/ribasim/ribasim/node_types/new_node_type.py which is structured as follows:\nfrom typing import Optional\n\nimport pandera as pa\nfrom pandera.engines.pandas_engine import PydanticModel\nfrom pandera.typing import DataFrame\n\nfrom ribasim import models\nfrom ribasim.input_base import TableModel\n\n__all__ = (\"NewNodeType\",)\n\nclass StaticSchema(pa.SchemaModel):\n class Config:\n \"\"\"Config with dataframe-level data type.\"\"\"\n\n dtype = PydanticModel(models.NewNodeTypeStatic)\n\n# Possible other schemas\n\n\nclass NewNodeType(TableModel):\n \"\"\"\n Description of this node type.\n\n Parameters\n ----------\n static: pandas.DataFrame\n table with data for this node type.\n\n possible other schemas\n \"\"\"\n\n static: Optional[DataFrame[StaticSchema]] = None\n # possible other schemas\n\n class Config:\n validate_assignment = True\n\n def sort(self):\n self.static.sort_values(\"node_id\", ignore_index=True, inplace=True)\nThe sort method should implement the same sorting as in validation.jl.\nNow in both python/ribasim/ribasim/__init__.py and python/ribasim/ribasim/node_types/__init__.py add\n\nfrom ribasim.node_types.new_node_type import NewNodeType;\n\"NewNodeType\" to __all__.\n\nIn python/ribasim/ribasim/model.py, add\n\nfrom ribasim.new_node_type import NewNodeType;\nnew_node_type as a parameter and in the docstring of the Model class.\n\nIn python/ribasim/ribasim/geometry/node.py add a color and shape description in the MARKERS and COLORS dictionaries." - }, - { - "objectID": "src/index.html", - "href": "src/index.html", - "title": "1 API Reference", - "section": "", - "text": "This is the private internal documentation of the Ribasim API.\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:module]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:type]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:function]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:constant]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:macro]" - }, - { - "objectID": "src/index.html#modules", - "href": "src/index.html#modules", - "title": "1 API Reference", - "section": "", - "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:module]" - }, - { - "objectID": "src/index.html#types", - "href": "src/index.html#types", - "title": "1 API Reference", - "section": "", - "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:type]" - }, - { - "objectID": "src/index.html#functions", - "href": "src/index.html#functions", - "title": "1 API Reference", - "section": "", - "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:function]" + "text": "1.2 Reading from configuration\nThere can be several schemas associated with a single node type. To define a schema for the new node type, add the following to validation.jl:\n@schema \"ribasim.newnodetype.static\" NewNodeTypeStatic\n\n\"\"\"\nnode_id: node ID of the NewNodeType node\n\"\"\"\n@version NewNodeTypeStaticV1 begin\n node_id::Int\n # Other fields\nend\nHere Static refers to data that does not change over time. For naming conventions of these schemas see Node usage.\nvalidation.jl also deals with checking and applying a specific sorting order for the tabular data (default is sorting by node ID only), see sort_by_function and sorted_table!.\nNow we define the function that is called in the second bullet above, in create.jl:\nfunction NewNodeType(db::DB, config::Config)::NewNodeType\n static = load_structvector(db, config, NewNodeTypeStaticV1)\n defaults = (; foo = 1, bar = false)\n # Process potential control states in the static data\n parsed_parameters, valid = parse_static_and_time(db, config, \"Outlet\"; static, defaults)\n\n if !valid\n error(\"Errors occurred when parsing NewNodeType data.\")\n end\n\n # Unpack the fields of static as inputs for the NewNodeType constructor\n return NewNodeType(\n parsed_parameters.node_id,\n parsed_parameters.some_property,\n parsed_parameters.control_mapping)\nend" }, { - "objectID": "src/index.html#constants", - "href": "src/index.html#constants", - "title": "1 API Reference", - "section": "", - "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:constant]" + "objectID": "contribute/addnode.html#node-behavior", + "href": "contribute/addnode.html#node-behavior", + "title": "Adding node types", + "section": "1.3 Node behavior", + "text": "1.3 Node behavior\nIn general if the new node type dictates flow, the behaviour of the new node in the Ribasim core is defined in a method of the formulate_flow! function, which is called within the water_balance! (both in solve.jl) function being the right hand side of the system of differential equations solved by Ribasim. Here the details depend highly on the specifics of the node type. An example structure of a formulate_flow! method is given below.\nfunction formulate_flow!(new_node_type::NewNodeType, p::Parameters)::Nothing\n # Retrieve relevant parameters\n (; connectivity) = p\n (; flow) = connectivity\n (; node_id, param_1, param_2) = new_node_type\n\n # Loop over nodes of NewNodeType\n for (i, id) in enumerate(node_id)\n # compute e.g. flow based on param_1[i], param_2[i]\n end\n\n return nothing\nend" }, { - "objectID": "src/index.html#macros", - "href": "src/index.html#macros", - "title": "1 API Reference", - "section": "", - "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:macro]" + "objectID": "contribute/addnode.html#the-jacobian", + "href": "contribute/addnode.html#the-jacobian", + "title": "Adding node types", + "section": "1.4 The Jacobian", + "text": "1.4 The Jacobian\nSee Equations for a mathematical description of the Jacobian.\nBefore the Julia core runs its simulation, the sparsity structure jac_prototype of \\(J\\) is determined with get_jac_prototype in utils.jl. This function runs trough all node types and looks for nodes that create dependencies between states. It creates a sparse matrix of zeros and ones, where the ones denote locations of possible non-zeros in \\(J\\).\nWe divide the various node types in groups based on what type of state dependencies they yield, and these groups are discussed below. Each group has its own method update_jac_prototype! in utils.jl for the sparsity structure induced by nodes of that group. NewNodeType should be added to the signature of one these methods, or to the list of node types that do not contribute to the Jacobian in the method of update_jac_prototype! whose signature contains node::AbstractParameterNode. Of course it is also possible that a new method of update_jac_prototype! has to be introduced.\nThe current dependency groups are:\n\nOut-neighbor dependencies: examples are TabulatedRatingCurve, Pump (the latter only in the reduction factor regime and not PID controlled). If the in-neighbor of a node of this group is a basin, then the storage of this basin affects itself and the storage of the outneighbor (or the basin one node further if it is connected with a FractionalFlow in between) if that is also a basin;\nEither-neighbor dependencies: examples are LinearResistance, ManningResistance. If either the in-neighbor or out-neighbor of a node of this group is a basin, the storage of this basin depends on itself. If both the in-neighbor and the out-neighbor are basins, their storages also depend on eachother.\nThe PidControl node is a special case which is discussed in equations.\n\nUsing jac_prototype the Jacobian of water_balance! is computed automatically using ForwardDiff.jl with memory management provided by PreallocationTools.jl. These computations make use of DiffCache and dual numbers." }, { - "objectID": "core/index.html", - "href": "core/index.html", - "title": "Julia core", - "section": "", - "text": "With the term “core”, we mean the computational engine of Ribasim. As detailed in the usage documentation, it is generally used as a command line tool.\nThe theory is described on the equations page.\nThe core is implemented in the Julia programming language, and can be found in the Ribasim repository under the core/ folder. For developers we also advise to read the developer documentation." + "objectID": "contribute/addnode.html#python-class", + "href": "contribute/addnode.html#python-class", + "title": "Adding node types", + "section": "2.1 Python class", + "text": "2.1 Python class\nCreate a new file python/ribasim/ribasim/node_types/new_node_type.py which is structured as follows:\nfrom typing import Optional\n\nimport pandera as pa\nfrom pandera.engines.pandas_engine import PydanticModel\nfrom pandera.typing import DataFrame\n\nfrom ribasim import models\nfrom ribasim.input_base import TableModel\n\n__all__ = (\"NewNodeType\",)\n\nclass StaticSchema(pa.SchemaModel):\n class Config:\n \"\"\"Config with dataframe-level data type.\"\"\"\n\n dtype = PydanticModel(models.NewNodeTypeStatic)\n\n# Possible other schemas\n\n\nclass NewNodeType(TableModel):\n \"\"\"\n Description of this node type.\n\n Parameters\n ----------\n static: pandas.DataFrame\n table with data for this node type.\n\n possible other schemas\n \"\"\"\n\n static: Optional[DataFrame[StaticSchema]] = None\n # possible other schemas\n\n class Config:\n validate_assignment = True\n\n def sort(self):\n self.static.sort_values(\"node_id\", ignore_index=True, inplace=True)\nThe sort method should implement the same sorting as in validation.jl.\nNow in both python/ribasim/ribasim/__init__.py and python/ribasim/ribasim/node_types/__init__.py add\n\nfrom ribasim.node_types.new_node_type import NewNodeType;\n\"NewNodeType\" to __all__.\n\nIn python/ribasim/ribasim/model.py, add\n\nfrom ribasim.new_node_type import NewNodeType;\nnew_node_type as a parameter and in the docstring of the Model class.\n\nIn python/ribasim/ribasim/geometry/node.py add a color and shape description in the MARKERS and COLORS dictionaries." }, { "objectID": "core/equations.html", @@ -665,11 +679,109 @@ "text": "3.2 The sign of the parameters\nNote by Equation 6 that the error is positive if the setpoint is larger than the basin level and negative if the setpoint is smaller than the basin level.\nWe enforce the convention that when a pump is controlled, its edge points away from the basin, and when an outlet is controlled, its edge points towards the basin, so that the main flow direction along these edges is positive. Therefore, positive flows of the pump and outlet have opposite effects on the basin, and thus the parameters \\(K_p,K_i,K_d\\) of the pump and outlet must have oppositive signs to achieve the same goal." }, { - "objectID": "couple/index.html", - "href": "couple/index.html", - "title": "Coupled models", + "objectID": "core/usage.html", + "href": "core/usage.html", + "title": "Usage", "section": "", - "text": "Ribasim can be coupled to other software, for instance to model another process or domain, or to control a simulation from another process.\nTo enable this, Ribasim can be compiled as a shared library (libribasim) instead of a command line interface (ribasim). This shared library exposes a C API in the form of the Basic Model Interface (BMI). Other software can then load libribasim and load a Ribasim model, exchange data, and control the time stepping.\nAn initial coupling to MODFLOW 6 was done in 2022 inside the Julia core as a proof of concept. Read about the coupling setup and see the demonstration. Going forward this and other coupling codes will be implemented outside of the core, by making use of iMOD Coupler and libribasim. iMOD Coupler is a generic coupling tool, and can be used to couple to other models as well." + "text": "Ribasim is typically used as a command-line interface (CLI). It is distributed as a .zip archive, that must be downloaded and unpacked. It can be placed anywhere, however it is important that the contents of the zip file are kept together in a directory. The Ribasim CLI executable is in the bin directory.\nThe latest build can be downloaded here: ribasim_cli.zip. Currently only Windows builds are available.\nTo check whether the installation was performed successfully, run ribasim with no arguments in the command line. This will give the following message:" + }, + { + "objectID": "core/usage.html#solver-settings", + "href": "core/usage.html#solver-settings", + "title": "Usage", + "section": "1.1 Solver settings", + "text": "1.1 Solver settings\nThe solver section in the configuration file is entirely optional, since we aim to use defaults that will generally work well. Common reasons to modify the solver settings are to adjust the calculation or output stepsizes: adaptive, dt, and saveat. If your model does not converge, or your performance is lower than expected, it can help to adjust other solver settings as well.\nThe default solver algorithm = \"QNDF\", which is a multistep method similar to Matlab’s ode15s (Shampine and Reichelt 1997). It is an implicit method that supports the default adaptive = true timestepping. The full list of available solvers is: QNDF, Rosenbrock23, TRBDF2, Rodas5, KenCarp4, Tsit5, RK4, ImplicitEuler, Euler. Information on the solver algorithms can be found on the ODE solvers page.\nThe dt controls the stepsize. When adaptive = true this only applies to the initial stepsize, and the default of 0.0 means that it is automatically determined. When adaptive = false a suitable dt must always be provided. The value is in seconds, so dt = 3600.0 corresponds to an hourly timestep.\nBy default the calculation and output stepsize are the same, with saveat = [], which will save every timestep. saveat can be a number, which is the saving interval in seconds, or it can be a list of numbers, which are the times in seconds since start that are saved. For instance, saveat = 86400.0 will save output after every day that passed.\nThe Jacobian matrix provides information about the local sensitivity of the model with respect to changes in the states. For implicit solvers it must be calculated often, which can be expensive to do. There are several methods to do this. By default Ribasim uses a Jacobian derived automatically using ForwardDiff.jl with memory management provided by PreallocationTools.jl. If this is not used by setting autodiff = false, the Jacobian is calculated with a finite difference method, which can be less accurate and more expensive.\nBy default the Jacobian matrix is a sparse matrix (sparse = true). Since each state typically only depends on a small number of other states, this is generally more efficient, especially for larger models. The sparsity structure is calculated from the network and provided as a Jacobian prototype to the solver. For small or highly connected models it could be faster to use a dense Jacobian matrix instead by setting sparse = false.\nThe total maximum number of iterations maxiters = 1e9, can normally stay as-is unless doing extremely long simulations.\nThe absolute and relative tolerance for adaptive timestepping can be set with abstol and reltol. For more information on these and other solver options, see the DifferentialEquations.jl docs." + }, + { + "objectID": "core/usage.html#basin-time", + "href": "core/usage.html#basin-time", + "title": "Usage", + "section": "5.1 Basin / time", + "text": "5.1 Basin / time\nThis table is the transient form of the Basin table. The only difference is that a time column is added. The table must by sorted by time, and per time it must be sorted by node_id. A linear interpolation between the given timesteps is currently done if the solver takes timesteps between the given data points. More options will be available later." + }, + { + "objectID": "core/usage.html#basin-state", + "href": "core/usage.html#basin-state", + "title": "Usage", + "section": "5.2 Basin / state", + "text": "5.2 Basin / state\nThe state table aims to capture the full state of the Basin, such that it can be used as an initial condition, potentially the outcome of an earlier simulation. Currently only the Basin node types have state.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\nlevel\nFloat64\n\\(m\\)\n\\(\\ge\\) basin bottom\n\n\n\nEach Basin ID needs to be in the table." + }, + { + "objectID": "core/usage.html#basin-profile", + "href": "core/usage.html#basin-profile", + "title": "Usage", + "section": "5.3 Basin / profile", + "text": "5.3 Basin / profile\nThe profile table defines the physical dimensions of the storage reservoir of each basin.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\narea\nFloat64\n\\(m^2\\)\nnon-negative, per node_id: start positive and increasing\n\n\nlevel\nFloat64\n\\(m\\)\nper node_id: increasing\n\n\n\nThe level is the level at the basin outlet. All levels are defined in meters above a datum that is the same for the entire model. An example of the first 5 rows of such a table is given below. The first 4 rows define the profile of ID 2. The number of rows can vary per ID. Using a very large number of rows may impact performance.\n\n\n\nnode_id\narea\nlevel\n\n\n\n\n2\n1.0\n6.0\n\n\n2\n1000.0\n7.0\n\n\n2\n1000.0\n9.0\n\n\n3\n1.0\n2.2\n\n\n\nWe use the symbol \\(A\\) for area, \\(h\\) for level and \\(S\\) for storage. The profile provides a function \\(A(h)\\) for each basin. Internally this get converted to two functions, \\(A(S)\\) and \\(h(S)\\), by integrating over the function, setting the storage to zero for the bottom of the profile. The minimum area cannot be zero to avoid numerical issues. The maximum area is used to convert the precipitation flux into an inflow." + }, + { + "objectID": "core/usage.html#basin-output", + "href": "core/usage.html#basin-output", + "title": "Usage", + "section": "5.4 Basin output", + "text": "5.4 Basin output\nThe basin table contains outputs of the storage and level of each basin at every solver timestep. The initial condition is also written to the file.\n\n\n\ncolumn\ntype\nunit\n\n\n\n\ntime\nDateTime\n-\n\n\nnode_id\nInt\n-\n\n\nstorage\nFloat64\n\\(m^3\\)\n\n\nlevel\nFloat64\n\\(m\\)\n\n\n\nThe table is sorted by time, and per time it is sorted by node_id." + }, + { + "objectID": "core/usage.html#flow-output", + "href": "core/usage.html#flow-output", + "title": "Usage", + "section": "5.5 Flow output", + "text": "5.5 Flow output\nThe flow table contains outputs of the flow on every edge in the model, for each solver timestep.\n\n\n\ncolumn\ntype\nunit\n\n\n\n\ntime\nDateTime\n-\n\n\nedge_id\nInt\n-\n\n\nfrom_node_id\nInt\n-\n\n\nto_node_id\nInt\n-\n\n\nflow\nFloat64\n\\(m^3 s^{-1}\\)\n\n\n\nThe table is sorted by time, and per time the same edge_id order is used, though not sorted." + }, + { + "objectID": "core/usage.html#tabulatedratingcurve-time", + "href": "core/usage.html#tabulatedratingcurve-time", + "title": "Usage", + "section": "7.1 TabulatedRatingCurve / time", + "text": "7.1 TabulatedRatingCurve / time\nThis table is the transient form of the TabulatedRatingCurve table. The only difference is that a time column is added. The table must by sorted by time, and per time it must be sorted by node_id. With this the rating curves can be updated over time. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\ntime\nDateTime\n-\nsorted\n\n\nnode_id\nInt\n-\nsorted per time\n\n\nlevel\nFloat64\n\\(m\\)\n-\n\n\ndischarge\nFloat64\n\\(m^3 s^{-1}\\)\nnon-negative" + }, + { + "objectID": "core/usage.html#user-time", + "href": "core/usage.html#user-time", + "title": "Usage", + "section": "10.1 User / time", + "text": "10.1 User / time\nThis table is the transient form of the User table. The only difference is that a time column is added and activity is assumed to be true. The table must by sorted by time, and per time it must be sorted by node_id. With this the demand can be updated over time. In between the given times the demand is interpolated linearly, and outside the demand is constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted per time\n\n\ntime\nDateTime\n-\nsorted\n\n\ndemand\nFloat64\n\\(m^3 s^{-1}\\)\n-\n\n\nreturn_factor\nFloat64\n-\nbetween [0 - 1]\n\n\nmin_level\nFloat64\n\\(m\\)\n(optional)\n\n\npriority\nInt\n-\n-" + }, + { + "objectID": "core/usage.html#levelboundary-time", + "href": "core/usage.html#levelboundary-time", + "title": "Usage", + "section": "11.1 LevelBoundary / time", + "text": "11.1 LevelBoundary / time\nThis table is the transient form of the LevelBoundary table. The only difference is that a time column is added and activity is assumed to be true. The table must by sorted by time, and per time it must be sorted by node_id. With this the levels can be updated over time. In between the given times the level is interpolated linearly, and outside the flow rate is constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\ntime\nDateTime\n-\nsorted\n\n\nnode_id\nInt\n-\nsorted per time\n\n\nlevel\nFloat64\n\\(m\\)\n-" + }, + { + "objectID": "core/usage.html#flowboundary-time", + "href": "core/usage.html#flowboundary-time", + "title": "Usage", + "section": "12.1 FlowBoundary / time", + "text": "12.1 FlowBoundary / time\nThis table is the transient form of the FlowBoundary table. The only differences are that a time column is added and the nodes are assumed to be active so this column is removed. The table must by sorted by time, and per time it must be sorted by node_id. With this the flow rates can be updated over time. In between the given times the flow rate is interpolated linearly, and outside the flow rate is constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\ntime\nDateTime\n-\nsorted\n\n\nnode_id\nInt\n-\nsorted per time\n\n\nflow_rate\nFloat64\n\\(m^3 s^{-1}\\)\nnon-negative" + }, + { + "objectID": "core/usage.html#sec-condition", + "href": "core/usage.html#sec-condition", + "title": "Usage", + "section": "16.1 DiscreteControl / condition", + "text": "16.1 DiscreteControl / condition\nThe condition schema defines conditions of the form ‘the discrete_control node with this node id listens to whether the given variable of the node with the given listen feature id is grater than the given value’. If the condition variable comes from a time-series, a look ahead \\(\\Delta t\\) can be supplied.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\nlisten_feature_id\nInt\n-\n-\n\n\nvariable\nString\n-\nmust be “level” or “flow_rate”\n\n\ngreater_than\nFloat64\nvarious\n-\n\n\nlook_ahead\nFloat64\n\\(s\\)\nOnly on transient boundary conditions, non-negative (optional, default 0)" + }, + { + "objectID": "core/usage.html#discretecontrol-logic", + "href": "core/usage.html#discretecontrol-logic", + "title": "Usage", + "section": "16.2 DiscreteControl / logic", + "text": "16.2 DiscreteControl / logic\nThe logic schema defines which control states are triggered based on the truth of the conditions a discrete_control node listens to. DiscreteControl is applied in the Julia core as follows:\n\nDuring the simulation it is checked whether the truth of any of the conditions changes.\nWhen a condition changes, the corresponding discrrete_control node id is retrieved (node_id in the condition schema above).\nThe truth value of all the conditions this discrete_control node lisens to are retrieved, in the order as they are specified in the condition schema. This is then converted into a string of “T” for true and “F” for false. This string we call the truth state.*\nThe table below determines for the given discrete_control node ID and truth state what the corresponding control state is.\nFor all the nodes this discrete_control node affects (as given by the “control” edges in Edges / static), their parameters are set to those parameters in NodeType / static corresponding to the determined control state.\n\n*. There is also a second truth state created in which for the last condition that changed it is specified whether it was an upcrossing (“U”) or downcrossing (“D”) of the threshold (greater than) value. If a control state is specified for a truth state that is crossing-specific, this takes precedence over the control state for the truth state that contains only “T” and “F”.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\ntruth_state\nString\n-\nConsists of the characters “T” (true), “F” (false), “U” (upcrossing), “D” (downcrossing) and “*” (any)\n\n\ncontrol_state\nString\n-" + }, + { + "objectID": "core/usage.html#discretecontrol-output", + "href": "core/usage.html#discretecontrol-output", + "title": "Usage", + "section": "16.3 DiscreteControl output", + "text": "16.3 DiscreteControl output\nThe control table contains a record of each change of control state: when it happened, which control node was involved, to which control state it changed and based on which truth state.\n\n\n\ncolumn\ntype\n\n\n\n\ntime\nDateTime\n\n\ncontrol_node_id\nInt\n\n\ntruth_state\nString\n\n\ncontrol_state\nString" + }, + { + "objectID": "core/usage.html#pidcontrol-time", + "href": "core/usage.html#pidcontrol-time", + "title": "Usage", + "section": "17.1 PidControl / time", + "text": "17.1 PidControl / time\nThis table is the transient form of the PidControl table. The differences are that a time column is added and the nodes are assumed to be active so this column is removed. The table must by sorted by time, and per time it must be sorted by node_id. With this the target level and PID coefficients can be updated over time. In between the given times the these values interpolated linearly, and outside these values area constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted per time\n\n\ntime\nDateTime\n-\nsorted\n\n\nlisten_node_id\nInt\n-\n-\n\n\ntarget\nFloat64\n\\(m\\)\n-\n\n\nproportional\nFloat64\n\\(s^{-1}\\)\n-\n\n\nintegral\nFloat64\n\\(s^{-2}\\)\n-\n\n\nderivative\nFloat64\n-\n-" }, { "objectID": "couple/modflow.html", @@ -721,122 +833,10 @@ "text": "3.1 Parametrization\nIn coupling Ribasim to MODFLOW 6, relations translating the Ribasim volume must be given for every every cell of every boundary condition. These consist of piecewise linear relationships between the basin volume and its associated water level for the boundary condition in the cell.\nThese values are stored in a netCDF dataset. This dataset must meet the following requirements:\n\nIt must contain a x and y coordinate. The extent and cell size of these coordinates must match the domain of the coupled MODFLOW 6 model exactly.\nIt must contain a variable (x, y) denoting the basin IDs.\nIt must contain a volume-level variable (x, y, row, column) for every coupled MODFLOW 6 boundary condition, describing the volume-level lookup table per cell.\n\n\n\n\n\n\n\nNote\n\n\n\nThe x and y coordinates are valid for structured MODFLOW 6 models (DIS). Discretized-by-vertices (DISV) and fully unstructured discretization (DISU). are not yet supported, but require no fundamental changes: one basin is connected to multiple MODFLOW 6 cells, and the coupling parameters must match the (structured, unstructured) grid of the MODFLOW 6 model exactly.\n\n\nThe MODFLOW 6 coupling example cases show examples of such a parametrization." }, { - "objectID": "python/examples.html", - "href": "python/examples.html", - "title": "Examples", - "section": "", - "text": "1 Basic model with static forcing\n\nimport geopandas as gpd\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\n\nimport ribasim\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [1, 1, 3, 3, 6, 6, 9, 9],\n \"area\": [0.01, 1000.0] * 4,\n \"level\": [0.0, 1.0] * 4,\n }\n)\n\n# Convert steady forcing to m/s\n# 2 mm/d precipitation, 1 mm/d evaporation\nseconds_in_day = 24 * 3600\nprecipitation = 0.002 / seconds_in_day\nevaporation = 0.001 / seconds_in_day\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [0],\n \"drainage\": [0.0],\n \"potential_evaporation\": [evaporation],\n \"infiltration\": [0.0],\n \"precipitation\": [precipitation],\n \"urban_runoff\": [0.0],\n }\n)\nstatic = static.iloc[[0, 0, 0, 0]]\nstatic[\"node_id\"] = [1, 3, 6, 9]\n\nbasin = ribasim.Basin(profile=profile, static=static)\n\nSetup linear resistance:\n\nlinear_resistance = ribasim.LinearResistance(\n static=pd.DataFrame(\n data={\"node_id\": [10, 12], \"resistance\": [5e3, (3600.0 * 24) / 100.0]}\n )\n)\n\nSetup Manning resistance:\n\nmanning_resistance = ribasim.ManningResistance(\n static=pd.DataFrame(\n data={\n \"node_id\": [2],\n \"length\": [900.0],\n \"manning_n\": [0.04],\n \"profile_width\": [6.0],\n \"profile_slope\": [3.0],\n }\n )\n)\n\nSet up a rating curve node:\n\n# Discharge: lose 1% of storage volume per day at storage = 1000.0.\nq1000 = 1000.0 * 0.01 / seconds_in_day\n\nrating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\n \"node_id\": [4, 4],\n \"level\": [0.0, 1.0],\n \"discharge\": [0.0, q1000],\n }\n )\n)\n\nSetup fractional flows:\n\nfractional_flow = ribasim.FractionalFlow(\n static=pd.DataFrame(\n data={\n \"node_id\": [5, 8, 13],\n \"fraction\": [0.3, 0.6, 0.1],\n }\n )\n)\n\nSetup pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": [7],\n \"flow_rate\": [0.5 / 3600],\n }\n )\n)\n\nSetup level boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [11, 17],\n \"level\": [0.5, 1.5],\n }\n )\n)\n\nSetup flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [15, 16],\n \"flow_rate\": [1e-4, 1e-4],\n }\n )\n)\n\nSetup terminal:\n\nterminal = ribasim.Terminal(\n static=pd.DataFrame(\n data={\n \"node_id\": [14],\n }\n )\n)\n\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: Basin,\n (1.0, 0.0), # 2: ManningResistance\n (2.0, 0.0), # 3: Basin\n (3.0, 0.0), # 4: TabulatedRatingCurve\n (3.0, 1.0), # 5: FractionalFlow\n (3.0, 2.0), # 6: Basin\n (4.0, 1.0), # 7: Pump\n (4.0, 0.0), # 8: FractionalFlow\n (5.0, 0.0), # 9: Basin\n (6.0, 0.0), # 10: LinearResistance\n (2.0, 2.0), # 11: LevelBoundary\n (2.0, 1.0), # 12: LinearResistance\n (3.0, -1.0), # 13: FractionalFlow\n (3.0, -2.0), # 14: Terminal\n (3.0, 3.0), # 15: FlowBoundary\n (0.0, 1.0), # 16: FlowBoundary\n (6.0, 1.0), # 17: LevelBoundary\n ]\n)\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_id, node_type = ribasim.Node.get_node_ids_and_types(\n basin,\n manning_resistance,\n rating_curve,\n pump,\n fractional_flow,\n linear_resistance,\n level_boundary,\n flow_boundary,\n terminal,\n)\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n static=gpd.GeoDataFrame(\n data={\"type\": node_type},\n index=pd.Index(node_id, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array(\n [1, 2, 3, 4, 4, 5, 6, 8, 7, 9, 11, 12, 4, 13, 15, 16, 10], dtype=np.int64\n)\nto_id = np.array(\n [2, 3, 4, 5, 8, 6, 7, 9, 9, 10, 12, 3, 13, 14, 6, 1, 17], dtype=np.int64\n)\nlines = ribasim.utils.geometry_from_connectivity(node, from_id, to_id)\nedge = ribasim.Edge(\n static=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": len(from_id) * [\"flow\"],\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup a model:\n\nmodel = ribasim.Model(\n modelname=\"basic\",\n node=node,\n edge=edge,\n basin=basin,\n level_boundary=level_boundary,\n flow_boundary=flow_boundary,\n pump=pump,\n linear_resistance=linear_resistance,\n manning_resistance=manning_resistance,\n tabulated_rating_curve=rating_curve,\n fractional_flow=fractional_flow,\n terminal=terminal,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2021-01-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n<Axes: >\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"basic\")\n\n\n\n2 Update the basic model with transient forcing\nThis assumes you have already created the basic model with static forcing.\n\nimport numpy as np\nimport pandas as pd\nimport xarray as xr\n\nimport ribasim\n\n\nmodel = ribasim.Model.from_toml(datadir / \"basic/basic.toml\")\n\n\ntime = pd.date_range(model.starttime, model.endtime)\nday_of_year = time.day_of_year.to_numpy()\nseconds_per_day = 24 * 60 * 60\nevaporation = (\n (-1.0 * np.cos(day_of_year / 365.0 * 2 * np.pi) + 1.0) * 0.0025 / seconds_per_day\n)\nrng = np.random.default_rng(seed=0)\nprecipitation = (\n rng.lognormal(mean=-1.0, sigma=1.7, size=time.size) * 0.001 / seconds_per_day\n)\n\nWe’ll use xarray to easily broadcast the values.\n\ntimeseries = (\n pd.DataFrame(\n data={\n \"node_id\": 1,\n \"time\": time,\n \"drainage\": 0.0,\n \"potential_evaporation\": evaporation,\n \"infiltration\": 0.0,\n \"precipitation\": precipitation,\n \"urban_runoff\": 0.0,\n }\n )\n .set_index(\"time\")\n .to_xarray()\n)\n\nbasin_ids = model.basin.static[\"node_id\"].to_numpy()\nbasin_nodes = xr.DataArray(\n np.ones(len(basin_ids)), coords={\"node_id\": basin_ids}, dims=[\"node_id\"]\n)\nforcing = (timeseries * basin_nodes).to_dataframe().reset_index()\n\n\nstate = pd.DataFrame(\n data={\n \"node_id\": basin_ids,\n \"level\": 1.4,\n \"concentration\": 0.0,\n }\n)\n\n\nmodel.basin.time = forcing\nmodel.basin.state = state\n\n\nmodel.modelname = \"basic_transient\"\nmodel.write(datadir / \"basic_transient\")\n\nNow run the model with ribasim basic-transient/basic.toml. After running the model, read back the output:\n\ndf_basin = pd.read_feather(datadir / \"basic_transient/output/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\ndf_basin_wide[\"level\"].plot()\n\n<Axes: xlabel='time'>\n\n\n\n\n\n\ndf_flow = pd.read_feather(datadir / \"basic_transient/output/flow.arrow\")\ndf_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\ndf_flow[\"flow_m3d\"] = df_flow.flow * 86400\nax = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_m3d\").plot()\nax.legend(bbox_to_anchor=(1.3, 1), title=\"Edge\")\n\n<matplotlib.legend.Legend at 0x7f9e8848a690>\n\n\n\n\n\n\ntype(df_flow)\n\npandas.core.frame.DataFrame\n\n\n\n\n3 Model with discrete control\nThe model constructed below consists of a single basin which slowly drains trough a TabulatedRatingCurve, but is held within a range around a target level (setpoint) by two connected pumps. These two pumps behave like a reversible pump. When pumping can be done in only one direction, and the other direction is only possible under gravity, use an Outlet for that direction.\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: Basin\n (1.0, 1.0), # 2: Pump\n (1.0, -1.0), # 3: Pump\n (2.0, 0.0), # 4: LevelBoundary\n (-1.0, 0.0), # 5: TabulatedRatingCurve\n (-2.0, 0.0), # 6: Terminal\n (1.0, 0.0), # 7: DiscreteControl\n ]\n)\n\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"Basin\",\n \"Pump\",\n \"Pump\",\n \"LevelBoundary\",\n \"TabulatedRatingCurve\",\n \"Terminal\",\n \"DiscreteControl\",\n]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n static=gpd.GeoDataFrame(\n data={\"type\": node_type},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 3, 4, 2, 1, 5, 7, 7], dtype=np.int64)\nto_id = np.array([3, 4, 2, 1, 5, 6, 2, 3], dtype=np.int64)\n\nedge_type = 6 * [\"flow\"] + 2 * [\"control\"]\n\nlines = ribasim.utils.geometry_from_connectivity(node, from_id, to_id)\nedge = ribasim.Edge(\n static=gpd.GeoDataFrame(\n data={\"from_node_id\": from_id, \"to_node_id\": to_id, \"edge_type\": edge_type},\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [1, 1],\n \"area\": [1000.0, 1000.0],\n \"level\": [0.0, 1.0],\n }\n)\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [1],\n \"drainage\": [0.0],\n \"potential_evaporation\": [0.0],\n \"infiltration\": [0.0],\n \"precipitation\": [0.0],\n \"urban_runoff\": [0.0],\n }\n)\n\nstate = pd.DataFrame(data={\"node_id\": [1], \"level\": [20.0]})\n\nbasin = ribasim.Basin(profile=profile, static=static, state=state)\n\nSetup the discrete control:\n\ncondition = pd.DataFrame(\n data={\n \"node_id\": 3 * [7],\n \"listen_feature_id\": 3 * [1],\n \"variable\": 3 * [\"level\"],\n \"greater_than\": [5.0, 10.0, 15.0], # min, setpoint, max\n }\n)\n\nlogic = pd.DataFrame(\n data={\n \"node_id\": 5 * [7],\n \"truth_state\": [\"FFF\", \"U**\", \"T*F\", \"**D\", \"TTT\"],\n \"control_state\": [\"in\", \"in\", \"none\", \"out\", \"out\"],\n }\n)\n\ndiscrete_control = ribasim.DiscreteControl(condition=condition, logic=logic)\n\nThe above control logic can be summarized as follows: - If the level gets above the maximum, activate the control state “out” until the setpoint is reached; - If the level gets below the minimum, active the control state “in” until the setpoint is reached; - Otherwise activate the control state “none”.\nSetup the pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": 3 * [2] + 3 * [3],\n \"control_state\": 2 * [\"none\", \"in\", \"out\"],\n \"flow_rate\": [0.0, 2e-3, 0.0, 0.0, 0.0, 2e-3],\n }\n )\n)\n\nThe pump data defines the following:\n\n\n\nControl state\nPump #2 flow rate (m/s)\nPump #3 flow rate (m/s)\n\n\n\n\n“none”\n0.0\n0.0\n\n\n“in”\n2e-3\n0.0\n\n\n“out”\n0.0\n2e-3\n\n\n\nSetup the level boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(data={\"node_id\": [4], \"level\": [10.0]})\n)\n\nSetup the rating curve:\n\nrating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\"node_id\": 2 * [5], \"level\": [2.0, 15.0], \"discharge\": [0.0, 1e-3]}\n )\n)\n\nSetup the terminal:\n\nterminal = ribasim.Terminal(static=pd.DataFrame(data={\"node_id\": [6]}))\n\nSetup a model:\n\nmodel = ribasim.Model(\n modelname=\"level_setpoint_with_minmax\",\n node=node,\n edge=edge,\n basin=basin,\n pump=pump,\n level_boundary=level_boundary,\n tabulated_rating_curve=rating_curve,\n terminal=terminal,\n discrete_control=discrete_control,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2021-01-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n<Axes: >\n\n\n\n\n\nListen edges are plotted with a dashed line since they are not present in the “Edge / static” schema but only in the “Control / condition” schema.\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"level_setpoint_with_minmax\")\n\nNow run the model with level_setpoint_with_minmax/level_setpoint_with_minmax.toml. After running the model, read back the output:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"level_setpoint_with_minmax/output/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\n\nax = df_basin_wide[\"level\"].plot()\n\ngreater_than = model.discrete_control.condition.greater_than\n\nax.hlines(\n greater_than,\n df_basin.time[0],\n df_basin.time.max(),\n lw=1,\n ls=\"--\",\n color=\"k\",\n)\n\ndf_control = pd.read_feather(\n datadir / \"level_setpoint_with_minmax/output/control.arrow\"\n)\n\ny_min, y_max = ax.get_ybound()\nax.fill_between(df_control.time[:2], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\nax.fill_between(df_control.time[2:4], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\n\nax.set_xticks(\n date2num(df_control.time).tolist(),\n df_control.control_state.tolist(),\n rotation=50,\n)\n\nax.set_yticks(greater_than, [\"min\", \"setpoint\", \"max\"])\nax.set_ylabel(\"level\")\nplt.show()\n\n\n\n\nThe highlighted regions show where a pump is active.\nLet’s print an overview of what happened with control:\n\nmodel.print_discrete_control_record(\n datadir / \"level_setpoint_with_minmax/output/control.arrow\"\n)\n\n0. At 2020-01-01 00:00:00 the control node with ID 7 reached truth state TTT:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level > 10.0\n For node ID 1 (Basin): level > 15.0\n\n This yielded control state \"out\":\n For node ID 2 (Pump): flow_rate = 0.0\n For node ID 3 (Pump): flow_rate = 0.002\n\n1. At 2020-02-09 01:17:29.324000 the control node with ID 7 reached truth state TFF:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level < 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"none\":\n For node ID 2 (Pump): flow_rate = 0.0\n For node ID 3 (Pump): flow_rate = 0.0\n\n2. At 2020-07-05 13:24:51.165000 the control node with ID 7 reached truth state FFF:\n For node ID 1 (Basin): level < 5.0\n For node ID 1 (Basin): level < 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"in\":\n For node ID 2 (Pump): flow_rate = 0.002\n For node ID 3 (Pump): flow_rate = 0.0\n\n3. At 2020-08-11 11:49:59.015000 the control node with ID 7 reached truth state TTF:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level > 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"none\":\n For node ID 2 (Pump): flow_rate = 0.0\n For node ID 3 (Pump): flow_rate = 0.0\n\n\n\nNote that crossing direction specific truth states (containing “U”, “D”) are not present in this overview even though they are part of the control logic. This is because in the control logic for this model these truth states are only used to sustain control states, while the overview only shows changes in control states.\n\n\n4 Model with PID control\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: FlowBoundary\n (1.0, 0.0), # 2: Basin\n (2.0, 0.5), # 3: Pump\n (3.0, 0.0), # 4: LevelBoundary\n (1.5, 1.0), # 5: PidControl\n (2.0, -0.5), # 6: outlet\n (1.5, -1.0), # 7: PidControl\n ]\n)\n\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"FlowBoundary\",\n \"Basin\",\n \"Pump\",\n \"LevelBoundary\",\n \"PidControl\",\n \"Outlet\",\n \"PidControl\",\n]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n static=gpd.GeoDataFrame(\n data={\"type\": node_type},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 2, 3, 4, 6, 5, 7], dtype=np.int64)\nto_id = np.array([2, 3, 4, 6, 2, 3, 6], dtype=np.int64)\n\nlines = ribasim.utils.geometry_from_connectivity(node, from_id, to_id)\nedge = ribasim.Edge(\n static=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": 5 * [\"flow\"] + 2 * [\"control\"],\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\"node_id\": [2, 2], \"level\": [0.0, 1.0], \"area\": [1000.0, 1000.0]}\n)\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [2],\n \"drainage\": [0.0],\n \"potential_evaporation\": [0.0],\n \"infiltration\": [0.0],\n \"precipitation\": [0.0],\n \"urban_runoff\": [0.0],\n }\n)\n\nstate = pd.DataFrame(\n data={\n \"node_id\": [2],\n \"level\": [6.0],\n }\n)\n\nbasin = ribasim.Basin(profile=profile, static=static, state=state)\n\nSetup the pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": [3],\n \"flow_rate\": [0.0], # Will be overwritten by PID controller\n }\n )\n)\n\nSetup the outlet:\n\noutlet = ribasim.Outlet(\n static=pd.DataFrame(\n data={\n \"node_id\": [6],\n \"flow_rate\": [0.0], # Will be overwritten by PID controller\n }\n )\n)\n\nSetup flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(data={\"node_id\": [1], \"flow_rate\": [1e-3]})\n)\n\nSetup flow boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [4],\n \"level\": [1.0], # Not relevant\n }\n )\n)\n\nSetup PID control:\n\npid_control = ribasim.PidControl(\n time=pd.DataFrame(\n data={\n \"node_id\": 4 * [5, 7],\n \"time\": [\n \"2020-01-01 00:00:00\",\n \"2020-01-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n ],\n \"listen_node_id\": 4 * [2, 2],\n \"target\": [5.0, 5.0, 5.0, 5.0, 7.5, 7.5, 7.5, 7.5],\n \"proportional\": 4 * [-1e-3, 1e-3],\n \"integral\": 4 * [-1e-7, 1e-7],\n \"derivative\": 4 * [0.0, 0.0],\n }\n )\n)\n\nNote that the coefficients for the pump and the outlet are equal in magnitude but opposite in sign. This way the pump and the outlet equally work towards the same goal, while having opposite effects on the controlled basin due to their connectivity to this basin.\nSetup a model:\n\nmodel = ribasim.Model(\n modelname=\"pid_control\",\n node=node,\n edge=edge,\n basin=basin,\n flow_boundary=flow_boundary,\n level_boundary=level_boundary,\n pump=pump,\n outlet=outlet,\n pid_control=pid_control,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-12-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n<Axes: >\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"pid_control\")\n\nNow run the model with ribasim pid_control/pid_control.toml. After running the model, read back the output:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"pid_control/output/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\nax = df_basin_wide[\"level\"].plot()\nax.set_ylabel(\"level [m]\")\n\n# Plot target level\ntarget_levels = model.pid_control.time.target.to_numpy()[::2]\ntimes = date2num(model.pid_control.time.time)[::2]\nax.plot(times, target_levels, color=\"k\", ls=\":\", label=\"target level\");" - }, - { - "objectID": "python/reference/Pump.html", - "href": "python/reference/Pump.html", - "title": "1 Pump", - "section": "", - "text": "Pump()\nPump water from a source node to a destination node. The set flow rate will be pumped unless the intake storage is less than 10m3, in which case the flow rate will be linearly reduced to 0 m3/s. Negative flow rates are not supported. Note that the intake must always be a Basin.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with constant flow rates.\nrequired" - }, - { - "objectID": "python/reference/Pump.html#parameters", - "href": "python/reference/Pump.html#parameters", - "title": "1 Pump", - "section": "", - "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with constant flow rates.\nrequired" - }, - { - "objectID": "python/reference/Node.html", - "href": "python/reference/Node.html", - "title": "1 Node", - "section": "", - "text": "Node()\nThe Ribasim nodes as Point geometries.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\ngeopandas.GeoDataFrame\nTable with node ID, type and geometry.\nrequired\n\n\n\n\n\n\n\n\n\nName\nDescription\n\n\n\n\nplot\nPlot the nodes. Each node type is given a separate marker.\n\n\nwrite_layer\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nNode.plot(self, ax=None, zorder=None)\nPlot the nodes. Each node type is given a separate marker.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nOptional\nThe axis on which the nodes will be plotted.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nNone\n\n\n\n\n\n\n\n\nNode.write_layer(self, path)\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\npath\nFilePath\n\nrequired" - }, - { - "objectID": "python/reference/Node.html#parameters", - "href": "python/reference/Node.html#parameters", - "title": "1 Node", - "section": "", - "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\ngeopandas.GeoDataFrame\nTable with node ID, type and geometry.\nrequired" - }, - { - "objectID": "python/reference/Node.html#methods", - "href": "python/reference/Node.html#methods", - "title": "1 Node", - "section": "", - "text": "Name\nDescription\n\n\n\n\nplot\nPlot the nodes. Each node type is given a separate marker.\n\n\nwrite_layer\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nNode.plot(self, ax=None, zorder=None)\nPlot the nodes. Each node type is given a separate marker.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nOptional\nThe axis on which the nodes will be plotted.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nNone\n\n\n\n\n\n\n\n\nNode.write_layer(self, path)\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\npath\nFilePath\n\nrequired" - }, - { - "objectID": "python/reference/Edge.html", - "href": "python/reference/Edge.html", - "title": "1 Edge", - "section": "", - "text": "Edge()\nDefines the connections between nodes.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable describing the flow connections.\nrequired\n\n\n\n\n\n\n\n\n\nName\nDescription\n\n\n\n\nwrite_layer\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nEdge.write_layer(self, path)\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\npath\nFilePath\n\nrequired" - }, - { - "objectID": "python/reference/Edge.html#parameters", - "href": "python/reference/Edge.html#parameters", - "title": "1 Edge", - "section": "", - "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable describing the flow connections.\nrequired" - }, - { - "objectID": "python/reference/Edge.html#methods", - "href": "python/reference/Edge.html#methods", - "title": "1 Edge", - "section": "", - "text": "Name\nDescription\n\n\n\n\nwrite_layer\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nEdge.write_layer(self, path)\nWrite the contents of the input to a GeoPackage.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\npath\nFilePath\n\nrequired" - }, - { - "objectID": "python/reference/utils.geometry_from_connectivity.html", - "href": "python/reference/utils.geometry_from_connectivity.html", - "title": "1 utils.geometry_from_connectivity", - "section": "", - "text": "utils.geometry_from_connectivity(node, from_id, to_id)\nCreate edge shapely geometries from connectivities.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nnode\nRibasim.Node\n\nrequired\n\n\nfrom_id\nSequence[int]\nFirst node of every edge.\nrequired\n\n\nto_id\nSequence[int]\nSecond node of every edge.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nnp.ndarray\nArray of shapely LineStrings." - }, - { - "objectID": "python/reference/utils.geometry_from_connectivity.html#parameters", - "href": "python/reference/utils.geometry_from_connectivity.html#parameters", - "title": "1 utils.geometry_from_connectivity", - "section": "", - "text": "Name\nType\nDescription\nDefault\n\n\n\n\nnode\nRibasim.Node\n\nrequired\n\n\nfrom_id\nSequence[int]\nFirst node of every edge.\nrequired\n\n\nto_id\nSequence[int]\nSecond node of every edge.\nrequired" - }, - { - "objectID": "python/reference/utils.geometry_from_connectivity.html#returns", - "href": "python/reference/utils.geometry_from_connectivity.html#returns", - "title": "1 utils.geometry_from_connectivity", - "section": "", - "text": "Type\nDescription\n\n\n\n\nnp.ndarray\nArray of shapely LineStrings." - }, - { - "objectID": "python/reference/utils.connectivity_from_geometry.html", - "href": "python/reference/utils.connectivity_from_geometry.html", - "title": "1 utils.connectivity_from_geometry", - "section": "", - "text": "utils.connectivity_from_geometry(node, lines)\nDerive from_node_id and to_node_id for every edge in lines. LineStrings may be used to connect multiple nodes in a sequence, but every linestring vertex must also a node.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nnode\nNode\n\nrequired\n\n\nlines\nnp.ndarray\nArray of shapely linestrings.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nnp.ndarray of int\n\n\n\nnp.ndarray of int" - }, - { - "objectID": "python/reference/utils.connectivity_from_geometry.html#parameters", - "href": "python/reference/utils.connectivity_from_geometry.html#parameters", - "title": "1 utils.connectivity_from_geometry", - "section": "", - "text": "Name\nType\nDescription\nDefault\n\n\n\n\nnode\nNode\n\nrequired\n\n\nlines\nnp.ndarray\nArray of shapely linestrings.\nrequired" - }, - { - "objectID": "python/reference/utils.connectivity_from_geometry.html#returns", - "href": "python/reference/utils.connectivity_from_geometry.html#returns", - "title": "1 utils.connectivity_from_geometry", - "section": "", - "text": "Type\nDescription\n\n\n\n\nnp.ndarray of int\n\n\n\nnp.ndarray of int" - }, - { - "objectID": "python/reference/TabulatedRatingCurve.html", - "href": "python/reference/TabulatedRatingCurve.html", - "title": "1 TabulatedRatingCurve", - "section": "", - "text": "TabulatedRatingCurve()\nLinearly interpolates discharge between a tabulation of level and discharge.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with constant rating curves.\nrequired\n\n\ntime\npandas.DataFrame\nTable with time-varying rating curves.\nrequired" - }, - { - "objectID": "python/reference/TabulatedRatingCurve.html#parameters", - "href": "python/reference/TabulatedRatingCurve.html#parameters", - "title": "1 TabulatedRatingCurve", + "objectID": "couple/index.html", + "href": "couple/index.html", + "title": "Coupled models", "section": "", - "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npd.DataFrame\nTable with constant rating curves.\nrequired\n\n\ntime\npandas.DataFrame\nTable with time-varying rating curves.\nrequired" + "text": "Ribasim can be coupled to other software, for instance to model another process or domain, or to control a simulation from another process.\nTo enable this, Ribasim can be compiled as a shared library (libribasim) instead of a command line interface (ribasim). This shared library exposes a C API in the form of the Basic Model Interface (BMI). Other software can then load libribasim and load a Ribasim model, exchange data, and control the time stepping.\nAn initial coupling to MODFLOW 6 was done in 2022 inside the Julia core as a proof of concept. Read about the coupling setup and see the demonstration. Going forward this and other coupling codes will be implemented outside of the core, by making use of iMOD Coupler and libribasim. iMOD Coupler is a generic coupling tool, and can be used to couple to other models as well." } ] \ No newline at end of file