diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e2e198a22..0faf24dc9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -29,7 +29,7 @@ Updating other julia files may be required. - update `docs/core/equations.qmd` - update `docs/core/usage.qmd` - update `docs/python/examples.ipynb` # or start a new example model -- update `docs/schema*.json` by running `julia --project=docs docs/gen_schema.jl` and `datamodel-codegen --use-title-as-name --input docs/schema/root.schema.json --output python/ribasim/ribasim/models.py` +- update `docs/schema*.json` by running `julia --project=docs docs/gen_schema.jl` and `datamodel-codegen --use-title-as-name --use-double-quotes --disable-timestamp --use-default --strict-nullable --input docs/schema/root.schema.json --output python/ribasim/ribasim/models.py` and `datamodel-codegen --use-title-as-name --use-double-quotes --disable-timestamp --use-default --strict-nullable --input docs/schema/Config.schema.json --output python/ribasim/ribasim/config.py` - update the instructions in `docs/contribute/*.qmd` if something changes there, e.g. something changes in how a new node type must be defined. ## QGIS diff --git a/core/src/config.jl b/core/src/config.jl index 6073a2187..1a8aa8e8b 100644 --- a/core/src/config.jl +++ b/core/src/config.jl @@ -67,7 +67,7 @@ const nodetypes = collect(keys(nodekinds)) @option struct Solver <: TableOption algorithm::String = "QNDF" - saveat::Union{Float64, Vector{Float64}, Vector{Union{}}} = Float64[] + saveat::Union{Float64, Vector{Float64}} = Float64[] adaptive::Bool = true dt::Float64 = 0.0 abstol::Float64 = 1e-6 @@ -109,7 +109,7 @@ end timing::Bool = false end -@option @addnodetypes struct Config +@option @addnodetypes struct Config <: TableOption starttime::DateTime endtime::DateTime @@ -117,7 +117,7 @@ end update_timestep::Float64 = 60 * 60 * 24.0 # optional, when Config is created from a TOML file, this is its directory - relative_dir::String = pwd() + relative_dir::String = "." # ignored(!) input_dir::String = "." output_dir::String = "." @@ -144,6 +144,11 @@ function Configurations.from_dict(::Type{Logging}, ::Type{LogLevel}, level::Abst ) end +# [] in TOML is parsed as a Vector{Union{}} +function Configurations.from_dict(::Type{Solver}, t::Type, saveat::Vector{Union{}}) + return Float64[] +end + # TODO Use with proper alignment function Base.show(io::IO, c::Config) println(io, "Ribasim Config") diff --git a/docs/contribute/addnode.qmd b/docs/contribute/addnode.qmd index 0ddb5f3a7..913321202 100644 --- a/docs/contribute/addnode.qmd +++ b/docs/contribute/addnode.qmd @@ -247,10 +247,11 @@ If you haven't done so before, you first need to instantiate your docs environme Run `julia --project=docs`, followed by running `instantiate` in the Pkg mode (press `]`). ::: -To generate the Python module `models.py` from the JSON Schemas, run: +To generate the Python module `models.py` and `config.py` from the JSON Schemas, run: ``` -datamodel-codegen --use-title-as-name --input docs/schema/root.schema.json --output python/ribasim/ribasim/models.py +datamodel-codegen --use-title-as-name --use-double-quotes --disable-timestamp --use-default --strict-nullable --input docs/schema/root.schema.json --output python/ribasim/ribasim/models.py +datamodel-codegen --use-title-as-name --use-double-quotes --disable-timestamp --use-default --strict-nullable --input docs/schema/Config.schema.json --output python/ribasim/ribasim/config.py ``` Since adding a node type touches both the Python and Julia code, diff --git a/docs/gen_schema.jl b/docs/gen_schema.jl index 4ad4b2bec..eaa0bbf36 100644 --- a/docs/gen_schema.jl +++ b/docs/gen_schema.jl @@ -11,10 +11,14 @@ using JSON3 using Legolas using InteractiveUtils using Dates +using Configurations +using Logging # set empty to have local file references for development const prefix = "https://deltares.github.io/Ribasim/schema/" +jsondefault(x) = identity(x) +jsondefault(x::LogLevel) = "info" jsontype(x) = jsontype(typeof(x)) jsonformat(x) = jsonformat(typeof(x)) jsontype(::Type{<:AbstractString}) = "string" @@ -23,28 +27,43 @@ jsontype(::Type{<:AbstractFloat}) = "number" jsonformat(::Type{<:Float64}) = "double" jsonformat(::Type{<:Float32}) = "float" jsontype(::Type{<:Number}) = "number" -jsontype(::Type{<:AbstractVector}) = "list" +jsontype(::Type{<:AbstractVector}) = "array" jsontype(::Type{<:Bool}) = "boolean" +jsontype(::Type{LogLevel}) = "string" +jsontype(::Type{<:Enum}) = "string" jsontype(::Type{<:Missing}) = "null" jsontype(::Type{<:DateTime}) = "string" jsonformat(::Type{<:DateTime}) = "date-time" jsontype(::Type{<:Nothing}) = "null" jsontype(::Type{<:Any}) = "object" jsonformat(::Type{<:Any}) = "default" -jsontype(T::Union) = unique(filter(!isequal("null"), jsontype.(Base.uniontypes(T)))) +function jsontype(T::Union) + t = Base.uniontypes(T) + td = Dict(zip(t, jsontype.(t))) + length(td) == 1 && return first(values(td)) + types = Dict[] + for (t, jt) in td + nt = Dict{String, Any}("type" => jt) + if t <: AbstractVector + nt["items"] = Dict("type" => jsontype(eltype(t))) + end + push!(types, nt) + end + return Dict("anyOf" => types) +end function strip_prefix(T::DataType) - (p, v) = rsplit(string(T), 'V'; limit = 2) + n = string(T) + (p, _) = occursin('V', n) ? rsplit(n, 'V'; limit = 2) : (n, "") return string(last(rsplit(p, '.'; limit = 2))) end -function gen_root_schema(TT::Vector, prefix = prefix) - name = "root" +function gen_root_schema(TT::Vector, prefix = prefix, name = "root") schema = Dict( "\$schema" => "https://json-schema.org/draft/2020-12/schema", "properties" => Dict{String, Dict}(), "\$id" => "$(prefix)$name.schema.json", - "title" => "root", + "title" => name, "description" => "All Ribasim Node types", "type" => "object", ) @@ -60,7 +79,7 @@ end os_line_separator() = Sys.iswindows() ? "\r\n" : "\n" -function gen_schema(T::DataType, prefix = prefix) +function gen_schema(T::DataType, prefix = prefix; pandera = true) name = strip_prefix(T) schema = Dict( "\$schema" => "https://json-schema.org/draft/2020-12/schema", @@ -71,24 +90,49 @@ function gen_schema(T::DataType, prefix = prefix) "properties" => Dict{String, Dict}(), "required" => String[], ) - for (fieldname, fieldtype) in zip(fieldnames(T), fieldtypes(T)) - fieldname = string(fieldname) - schema["properties"][fieldname] = Dict( - "description" => "$fieldname", - "type" => jsontype(fieldtype), - "format" => jsonformat(fieldtype), + for (fieldnames, fieldtype) in zip(fieldnames(T), fieldtypes(T)) + fieldname = string(fieldnames) + ref = false + if fieldtype <: Ribasim.config.TableOption + schema["properties"][fieldname] = Dict( + "\$ref" => "$(prefix)$(strip_prefix(fieldtype)).schema.json", + "default" => fieldtype(), + ) + ref = true + else + type = jsontype(fieldtype) + schema["properties"][fieldname] = + Dict{String, Any}("format" => jsonformat(fieldtype)) + if type isa AbstractString + schema["properties"][fieldname]["type"] = type + else + merge!(schema["properties"][fieldname], type) + end + end + if T <: Ribasim.config.TableOption + d = field_default(T, fieldnames) + if !(d isa Configurations.ExproniconLite.NoDefault) + if !ref + schema["properties"][fieldname]["default"] = jsondefault(d) + end + end + end + if !( + (fieldtype isa Union) && + ((fieldtype.a === Missing) || (fieldtype.a === Nothing)) ) - if !((fieldtype isa Union) && (fieldtype.a === Missing)) push!(schema["required"], fieldname) end end - # Temporary hack so pandera will keep the Pydantic record types - schema["properties"]["remarks"] = Dict( - "description" => "a hack for pandera", - "type" => "string", - "format" => "default", - "default" => "", - ) + if pandera + # Temporary hack so pandera will keep the Pydantic record types + schema["properties"]["remarks"] = Dict( + "description" => "a hack for pandera", + "type" => "string", + "format" => "default", + "default" => "", + ) + end open(normpath(@__DIR__, "schema", "$(name).schema.json"), "w") do io JSON3.pretty(io, schema) println(io) @@ -106,4 +150,7 @@ end for T in subtypes(Legolas.AbstractRecord) gen_schema(T) end +for T in subtypes(Ribasim.config.TableOption) + gen_schema(T; pandera = false) +end gen_root_schema(subtypes(Legolas.AbstractRecord)) diff --git a/docs/schema/BasinForcing.schema.json b/docs/schema/BasinForcing.schema.json index 956cc7e99..0f0ad5eb2 100644 --- a/docs/schema/BasinForcing.schema.json +++ b/docs/schema/BasinForcing.schema.json @@ -9,37 +9,30 @@ }, "time": { "format": "date-time", - "description": "time", "type": "string" }, "precipitation": { "format": "double", - "description": "precipitation", "type": "number" }, "infiltration": { "format": "double", - "description": "infiltration", "type": "number" }, "urban_runoff": { "format": "double", - "description": "urban_runoff", "type": "number" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "potential_evaporation": { "format": "double", - "description": "potential_evaporation", "type": "number" }, "drainage": { "format": "double", - "description": "drainage", "type": "number" } }, diff --git a/docs/schema/BasinProfile.schema.json b/docs/schema/BasinProfile.schema.json index 9f771318f..98dc78dd0 100644 --- a/docs/schema/BasinProfile.schema.json +++ b/docs/schema/BasinProfile.schema.json @@ -9,17 +9,14 @@ }, "area": { "format": "double", - "description": "area", "type": "number" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "level": { "format": "double", - "description": "level", "type": "number" } }, diff --git a/docs/schema/BasinState.schema.json b/docs/schema/BasinState.schema.json index a185891c4..906bf5a96 100644 --- a/docs/schema/BasinState.schema.json +++ b/docs/schema/BasinState.schema.json @@ -9,12 +9,10 @@ }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "level": { "format": "double", - "description": "level", "type": "number" } }, diff --git a/docs/schema/BasinStatic.schema.json b/docs/schema/BasinStatic.schema.json index 77e34e79f..a47d2970b 100644 --- a/docs/schema/BasinStatic.schema.json +++ b/docs/schema/BasinStatic.schema.json @@ -9,32 +9,26 @@ }, "precipitation": { "format": "double", - "description": "precipitation", "type": "number" }, "infiltration": { "format": "double", - "description": "infiltration", "type": "number" }, "urban_runoff": { "format": "double", - "description": "urban_runoff", "type": "number" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "potential_evaporation": { "format": "double", - "description": "potential_evaporation", "type": "number" }, "drainage": { "format": "double", - "description": "drainage", "type": "number" } }, diff --git a/docs/schema/Config.schema.json b/docs/schema/Config.schema.json new file mode 100644 index 000000000..f324fc81b --- /dev/null +++ b/docs/schema/Config.schema.json @@ -0,0 +1,180 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "output": { + "default": { + "basin": "output/basin.arrow", + "flow": "output/flow.arrow", + "control": "output/control.arrow", + "outstate": null, + "compression": "zstd", + "compression_level": 6 + }, + "$ref": "https://deltares.github.io/Ribasim/schema/Output.schema.json" + }, + "starttime": { + "format": "date-time", + "type": "string" + }, + "update_timestep": { + "format": "double", + "default": 86400, + "type": "number" + }, + "input_dir": { + "format": "default", + "default": ".", + "type": "string" + }, + "output_dir": { + "format": "default", + "default": ".", + "type": "string" + }, + "level_boundary": { + "default": { + "static": null, + "time": null + }, + "$ref": "https://deltares.github.io/Ribasim/schema/level_boundary.schema.json" + }, + "pump": { + "default": { + "static": null + }, + "$ref": "https://deltares.github.io/Ribasim/schema/pump.schema.json" + }, + "discrete_control": { + "default": { + "condition": null, + "logic": null + }, + "$ref": "https://deltares.github.io/Ribasim/schema/discrete_control.schema.json" + }, + "solver": { + "default": { + "algorithm": "QNDF", + "saveat": [ + ], + "adaptive": true, + "dt": 0, + "abstol": 1.0e-6, + "reltol": 0.001, + "maxiters": 1000000000, + "sparse": true, + "autodiff": true + }, + "$ref": "https://deltares.github.io/Ribasim/schema/Solver.schema.json" + }, + "flow_boundary": { + "default": { + "static": null, + "time": null + }, + "$ref": "https://deltares.github.io/Ribasim/schema/flow_boundary.schema.json" + }, + "pid_control": { + "default": { + "static": null, + "time": null + }, + "$ref": "https://deltares.github.io/Ribasim/schema/pid_control.schema.json" + }, + "fractional_flow": { + "default": { + "static": null + }, + "$ref": "https://deltares.github.io/Ribasim/schema/fractional_flow.schema.json" + }, + "relative_dir": { + "format": "default", + "default": ".", + "type": "string" + }, + "endtime": { + "format": "date-time", + "type": "string" + }, + "manning_resistance": { + "default": { + "static": null + }, + "$ref": "https://deltares.github.io/Ribasim/schema/manning_resistance.schema.json" + }, + "tabulated_rating_curve": { + "default": { + "static": null, + "time": null + }, + "$ref": "https://deltares.github.io/Ribasim/schema/tabulated_rating_curve.schema.json" + }, + "logging": { + "default": { + "verbosity": { + "level": 0 + }, + "timing": false + }, + "$ref": "https://deltares.github.io/Ribasim/schema/Logging.schema.json" + }, + "outlet": { + "default": { + "static": null + }, + "$ref": "https://deltares.github.io/Ribasim/schema/outlet.schema.json" + }, + "geopackage": { + "format": "default", + "type": "string" + }, + "terminal": { + "default": { + "static": null + }, + "$ref": "https://deltares.github.io/Ribasim/schema/terminal.schema.json" + }, + "basin": { + "default": { + "forcing": null, + "profile": null, + "state": null, + "static": null + }, + "$ref": "https://deltares.github.io/Ribasim/schema/basin.schema.json" + }, + "linear_resistance": { + "default": { + "static": null + }, + "$ref": "https://deltares.github.io/Ribasim/schema/linear_resistance.schema.json" + } + }, + "required": [ + "starttime", + "endtime", + "update_timestep", + "relative_dir", + "input_dir", + "output_dir", + "geopackage", + "output", + "solver", + "logging", + "terminal", + "pid_control", + "level_boundary", + "pump", + "tabulated_rating_curve", + "flow_boundary", + "basin", + "manning_resistance", + "discrete_control", + "outlet", + "linear_resistance", + "fractional_flow" + ], + "$id": "https://deltares.github.io/Ribasim/schema/Config.schema.json", + "title": "Config", + "description": "A Config object based on Ribasim.config.Config", + "type": "object" +} diff --git a/docs/schema/DiscreteControlCondition.schema.json b/docs/schema/DiscreteControlCondition.schema.json index 68eda57ca..3ce188fe2 100644 --- a/docs/schema/DiscreteControlCondition.schema.json +++ b/docs/schema/DiscreteControlCondition.schema.json @@ -9,29 +9,29 @@ }, "greater_than": { "format": "double", - "description": "greater_than", "type": "number" }, "listen_feature_id": { "format": "default", - "description": "listen_feature_id", "type": "integer" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "variable": { "format": "default", - "description": "variable", "type": "string" }, "look_ahead": { "format": "default", - "description": "look_ahead", - "type": [ - "number" + "anyOf": [ + { + "type": "null" + }, + { + "type": "number" + } ] } }, diff --git a/docs/schema/DiscreteControlLogic.schema.json b/docs/schema/DiscreteControlLogic.schema.json index a0364d83d..76a549b6b 100644 --- a/docs/schema/DiscreteControlLogic.schema.json +++ b/docs/schema/DiscreteControlLogic.schema.json @@ -9,17 +9,14 @@ }, "truth_state": { "format": "default", - "description": "truth_state", "type": "string" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "control_state": { "format": "default", - "description": "control_state", "type": "string" } }, diff --git a/docs/schema/Edge.schema.json b/docs/schema/Edge.schema.json index 95c8557bd..43626db5f 100644 --- a/docs/schema/Edge.schema.json +++ b/docs/schema/Edge.schema.json @@ -9,22 +9,18 @@ }, "edge_type": { "format": "default", - "description": "edge_type", "type": "string" }, "fid": { "format": "default", - "description": "fid", "type": "integer" }, "to_node_id": { "format": "default", - "description": "to_node_id", "type": "integer" }, "from_node_id": { "format": "default", - "description": "from_node_id", "type": "integer" } }, diff --git a/docs/schema/FlowBoundaryStatic.schema.json b/docs/schema/FlowBoundaryStatic.schema.json index 2bb39e060..8300ea9fd 100644 --- a/docs/schema/FlowBoundaryStatic.schema.json +++ b/docs/schema/FlowBoundaryStatic.schema.json @@ -9,19 +9,21 @@ }, "active": { "format": "default", - "description": "active", - "type": [ - "boolean" + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + } ] }, "flow_rate": { "format": "double", - "description": "flow_rate", "type": "number" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" } }, diff --git a/docs/schema/FlowBoundaryTime.schema.json b/docs/schema/FlowBoundaryTime.schema.json index bc65c4633..dc64986ce 100644 --- a/docs/schema/FlowBoundaryTime.schema.json +++ b/docs/schema/FlowBoundaryTime.schema.json @@ -9,17 +9,14 @@ }, "time": { "format": "date-time", - "description": "time", "type": "string" }, "flow_rate": { "format": "double", - "description": "flow_rate", "type": "number" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" } }, diff --git a/docs/schema/FractionalFlowStatic.schema.json b/docs/schema/FractionalFlowStatic.schema.json index f98567832..64bed064f 100644 --- a/docs/schema/FractionalFlowStatic.schema.json +++ b/docs/schema/FractionalFlowStatic.schema.json @@ -9,19 +9,21 @@ }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "fraction": { "format": "double", - "description": "fraction", "type": "number" }, "control_state": { "format": "default", - "description": "control_state", - "type": [ - "string" + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } ] } }, diff --git a/docs/schema/LevelBoundaryStatic.schema.json b/docs/schema/LevelBoundaryStatic.schema.json index 82de99862..f635afce0 100644 --- a/docs/schema/LevelBoundaryStatic.schema.json +++ b/docs/schema/LevelBoundaryStatic.schema.json @@ -9,19 +9,21 @@ }, "active": { "format": "default", - "description": "active", - "type": [ - "boolean" + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + } ] }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "level": { "format": "double", - "description": "level", "type": "number" } }, diff --git a/docs/schema/LevelBoundaryTime.schema.json b/docs/schema/LevelBoundaryTime.schema.json index fb4ef108f..e50696c32 100644 --- a/docs/schema/LevelBoundaryTime.schema.json +++ b/docs/schema/LevelBoundaryTime.schema.json @@ -9,17 +9,14 @@ }, "time": { "format": "date-time", - "description": "time", "type": "string" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "level": { "format": "double", - "description": "level", "type": "number" } }, diff --git a/docs/schema/LinearResistanceStatic.schema.json b/docs/schema/LinearResistanceStatic.schema.json index a5d6efdb6..5a5146ea9 100644 --- a/docs/schema/LinearResistanceStatic.schema.json +++ b/docs/schema/LinearResistanceStatic.schema.json @@ -9,26 +9,32 @@ }, "active": { "format": "default", - "description": "active", - "type": [ - "boolean" + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + } ] }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "resistance": { "format": "double", - "description": "resistance", "type": "number" }, "control_state": { "format": "default", - "description": "control_state", - "type": [ - "string" + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } ] } }, diff --git a/docs/schema/Logging.schema.json b/docs/schema/Logging.schema.json new file mode 100644 index 000000000..86fb596a7 --- /dev/null +++ b/docs/schema/Logging.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "timing": { + "format": "default", + "default": false, + "type": "boolean" + }, + "verbosity": { + "format": "default", + "default": "info", + "type": "string" + } + }, + "required": [ + "verbosity", + "timing" + ], + "$id": "https://deltares.github.io/Ribasim/schema/Logging.schema.json", + "title": "Logging", + "description": "A Logging object based on Ribasim.config.Logging", + "type": "object" +} diff --git a/docs/schema/ManningResistanceStatic.schema.json b/docs/schema/ManningResistanceStatic.schema.json index 5094f68df..22bad56df 100644 --- a/docs/schema/ManningResistanceStatic.schema.json +++ b/docs/schema/ManningResistanceStatic.schema.json @@ -3,12 +3,10 @@ "properties": { "length": { "format": "double", - "description": "length", "type": "number" }, "manning_n": { "format": "double", - "description": "manning_n", "type": "number" }, "remarks": { @@ -19,31 +17,36 @@ }, "active": { "format": "default", - "description": "active", - "type": [ - "boolean" + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + } ] }, "profile_width": { "format": "double", - "description": "profile_width", "type": "number" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "profile_slope": { "format": "double", - "description": "profile_slope", "type": "number" }, "control_state": { "format": "default", - "description": "control_state", - "type": [ - "string" + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } ] } }, diff --git a/docs/schema/Node.schema.json b/docs/schema/Node.schema.json index b7b93b2fc..dfbf5f509 100644 --- a/docs/schema/Node.schema.json +++ b/docs/schema/Node.schema.json @@ -9,12 +9,10 @@ }, "fid": { "format": "default", - "description": "fid", "type": "integer" }, "type": { "format": "default", - "description": "type", "type": "string" } }, diff --git a/docs/schema/OutletStatic.schema.json b/docs/schema/OutletStatic.schema.json index af03e5539..d8feae988 100644 --- a/docs/schema/OutletStatic.schema.json +++ b/docs/schema/OutletStatic.schema.json @@ -3,9 +3,13 @@ "properties": { "max_flow_rate": { "format": "default", - "description": "max_flow_rate", - "type": [ - "number" + "anyOf": [ + { + "type": "null" + }, + { + "type": "number" + } ] }, "remarks": { @@ -16,40 +20,54 @@ }, "active": { "format": "default", - "description": "active", - "type": [ - "boolean" + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + } ] }, "min_crest_level": { "format": "default", - "description": "min_crest_level", - "type": [ - "number" + "anyOf": [ + { + "type": "null" + }, + { + "type": "number" + } ] }, "flow_rate": { "format": "double", - "description": "flow_rate", "type": "number" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "control_state": { "format": "default", - "description": "control_state", - "type": [ - "string" + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } ] }, "min_flow_rate": { "format": "default", - "description": "min_flow_rate", - "type": [ - "number" + "anyOf": [ + { + "type": "null" + }, + { + "type": "number" + } ] } }, diff --git a/docs/schema/Output.schema.json b/docs/schema/Output.schema.json new file mode 100644 index 000000000..42fffb7e9 --- /dev/null +++ b/docs/schema/Output.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "compression": { + "format": "default", + "default": "zstd", + "type": "string" + }, + "basin": { + "format": "default", + "default": "output/basin.arrow", + "type": "string" + }, + "flow": { + "format": "default", + "default": "output/flow.arrow", + "type": "string" + }, + "control": { + "format": "default", + "default": "output/control.arrow", + "type": "string" + }, + "outstate": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + }, + "compression_level": { + "format": "default", + "default": 6, + "type": "integer" + } + }, + "required": [ + "basin", + "flow", + "control", + "compression", + "compression_level" + ], + "$id": "https://deltares.github.io/Ribasim/schema/Output.schema.json", + "title": "Output", + "description": "A Output object based on Ribasim.config.Output", + "type": "object" +} diff --git a/docs/schema/PIDControlStatic.schema.json b/docs/schema/PIDControlStatic.schema.json index b7725031f..df64576ee 100644 --- a/docs/schema/PIDControlStatic.schema.json +++ b/docs/schema/PIDControlStatic.schema.json @@ -3,7 +3,6 @@ "properties": { "integral": { "format": "double", - "description": "integral", "type": "number" }, "remarks": { @@ -14,41 +13,44 @@ }, "listen_node_id": { "format": "default", - "description": "listen_node_id", "type": "integer" }, "active": { "format": "default", - "description": "active", - "type": [ - "boolean" + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + } ] }, "proportional": { "format": "double", - "description": "proportional", "type": "number" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "target": { "format": "double", - "description": "target", "type": "number" }, "derivative": { "format": "double", - "description": "derivative", "type": "number" }, "control_state": { "format": "default", - "description": "control_state", - "type": [ - "string" + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } ] } }, diff --git a/docs/schema/PidControlTime.schema.json b/docs/schema/PidControlTime.schema.json index c86236d97..de34eaef8 100644 --- a/docs/schema/PidControlTime.schema.json +++ b/docs/schema/PidControlTime.schema.json @@ -3,7 +3,6 @@ "properties": { "integral": { "format": "double", - "description": "integral", "type": "number" }, "remarks": { @@ -14,39 +13,37 @@ }, "listen_node_id": { "format": "default", - "description": "listen_node_id", "type": "integer" }, "time": { "format": "date-time", - "description": "time", "type": "string" }, "proportional": { "format": "double", - "description": "proportional", "type": "number" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "target": { "format": "double", - "description": "target", "type": "number" }, "derivative": { "format": "double", - "description": "derivative", "type": "number" }, "control_state": { "format": "default", - "description": "control_state", - "type": [ - "string" + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } ] } }, diff --git a/docs/schema/PumpStatic.schema.json b/docs/schema/PumpStatic.schema.json index fe4a95864..afe80e42c 100644 --- a/docs/schema/PumpStatic.schema.json +++ b/docs/schema/PumpStatic.schema.json @@ -3,9 +3,13 @@ "properties": { "max_flow_rate": { "format": "default", - "description": "max_flow_rate", - "type": [ - "number" + "anyOf": [ + { + "type": "null" + }, + { + "type": "number" + } ] }, "remarks": { @@ -16,33 +20,43 @@ }, "active": { "format": "default", - "description": "active", - "type": [ - "boolean" + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + } ] }, "flow_rate": { "format": "double", - "description": "flow_rate", "type": "number" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "control_state": { "format": "default", - "description": "control_state", - "type": [ - "string" + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } ] }, "min_flow_rate": { "format": "default", - "description": "min_flow_rate", - "type": [ - "number" + "anyOf": [ + { + "type": "null" + }, + { + "type": "number" + } ] } }, diff --git a/docs/schema/Solver.schema.json b/docs/schema/Solver.schema.json new file mode 100644 index 000000000..ca6c57bd9 --- /dev/null +++ b/docs/schema/Solver.schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "reltol": { + "format": "double", + "default": 0.001, + "type": "number" + }, + "saveat": { + "format": "default", + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "number" + } + ], + "default": [ + ] + }, + "maxiters": { + "format": "default", + "default": 1000000000, + "type": "integer" + }, + "autodiff": { + "format": "default", + "default": true, + "type": "boolean" + }, + "adaptive": { + "format": "default", + "default": true, + "type": "boolean" + }, + "algorithm": { + "format": "default", + "default": "QNDF", + "type": "string" + }, + "abstol": { + "format": "double", + "default": 1.0e-6, + "type": "number" + }, + "dt": { + "format": "double", + "default": 0, + "type": "number" + }, + "sparse": { + "format": "default", + "default": true, + "type": "boolean" + } + }, + "required": [ + "algorithm", + "saveat", + "adaptive", + "dt", + "abstol", + "reltol", + "maxiters", + "sparse", + "autodiff" + ], + "$id": "https://deltares.github.io/Ribasim/schema/Solver.schema.json", + "title": "Solver", + "description": "A Solver object based on Ribasim.config.Solver", + "type": "object" +} diff --git a/docs/schema/TabulatedRatingCurveStatic.schema.json b/docs/schema/TabulatedRatingCurveStatic.schema.json index b89ea9ccf..cb244575b 100644 --- a/docs/schema/TabulatedRatingCurveStatic.schema.json +++ b/docs/schema/TabulatedRatingCurveStatic.schema.json @@ -9,31 +9,36 @@ }, "active": { "format": "default", - "description": "active", - "type": [ - "boolean" + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + } ] }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "discharge": { "format": "double", - "description": "discharge", "type": "number" }, "level": { "format": "double", - "description": "level", "type": "number" }, "control_state": { "format": "default", - "description": "control_state", - "type": [ - "string" + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } ] } }, diff --git a/docs/schema/TabulatedRatingCurveTime.schema.json b/docs/schema/TabulatedRatingCurveTime.schema.json index f65ae219c..47c2ddeb7 100644 --- a/docs/schema/TabulatedRatingCurveTime.schema.json +++ b/docs/schema/TabulatedRatingCurveTime.schema.json @@ -9,22 +9,18 @@ }, "time": { "format": "date-time", - "description": "time", "type": "string" }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" }, "discharge": { "format": "double", - "description": "discharge", "type": "number" }, "level": { "format": "double", - "description": "level", "type": "number" } }, diff --git a/docs/schema/TerminalStatic.schema.json b/docs/schema/TerminalStatic.schema.json index 4d4091a68..257433ed7 100644 --- a/docs/schema/TerminalStatic.schema.json +++ b/docs/schema/TerminalStatic.schema.json @@ -9,7 +9,6 @@ }, "node_id": { "format": "default", - "description": "node_id", "type": "integer" } }, diff --git a/docs/schema/basin.schema.json b/docs/schema/basin.schema.json new file mode 100644 index 000000000..6113db247 --- /dev/null +++ b/docs/schema/basin.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "profile": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + }, + "static": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + }, + "forcing": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + }, + "state": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + } + }, + "required": [ + ], + "$id": "https://deltares.github.io/Ribasim/schema/basin.schema.json", + "title": "basin", + "description": "A basin object based on Ribasim.config.basin", + "type": "object" +} diff --git a/docs/schema/discrete_control.schema.json b/docs/schema/discrete_control.schema.json new file mode 100644 index 000000000..22a244703 --- /dev/null +++ b/docs/schema/discrete_control.schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "logic": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + }, + "condition": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + } + }, + "required": [ + ], + "$id": "https://deltares.github.io/Ribasim/schema/discrete_control.schema.json", + "title": "discrete_control", + "description": "A discrete_control object based on Ribasim.config.discrete_control", + "type": "object" +} diff --git a/docs/schema/flow_boundary.schema.json b/docs/schema/flow_boundary.schema.json new file mode 100644 index 000000000..29f922f9a --- /dev/null +++ b/docs/schema/flow_boundary.schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "time": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + }, + "static": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + } + }, + "required": [ + ], + "$id": "https://deltares.github.io/Ribasim/schema/flow_boundary.schema.json", + "title": "flow_boundary", + "description": "A flow_boundary object based on Ribasim.config.flow_boundary", + "type": "object" +} diff --git a/docs/schema/fractional_flow.schema.json b/docs/schema/fractional_flow.schema.json new file mode 100644 index 000000000..940b2977d --- /dev/null +++ b/docs/schema/fractional_flow.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "static": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + } + }, + "required": [ + ], + "$id": "https://deltares.github.io/Ribasim/schema/fractional_flow.schema.json", + "title": "fractional_flow", + "description": "A fractional_flow object based on Ribasim.config.fractional_flow", + "type": "object" +} diff --git a/docs/schema/level_boundary.schema.json b/docs/schema/level_boundary.schema.json new file mode 100644 index 000000000..f4c2d70c8 --- /dev/null +++ b/docs/schema/level_boundary.schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "time": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + }, + "static": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + } + }, + "required": [ + ], + "$id": "https://deltares.github.io/Ribasim/schema/level_boundary.schema.json", + "title": "level_boundary", + "description": "A level_boundary object based on Ribasim.config.level_boundary", + "type": "object" +} diff --git a/docs/schema/linear_resistance.schema.json b/docs/schema/linear_resistance.schema.json new file mode 100644 index 000000000..96565c7b3 --- /dev/null +++ b/docs/schema/linear_resistance.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "static": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + } + }, + "required": [ + ], + "$id": "https://deltares.github.io/Ribasim/schema/linear_resistance.schema.json", + "title": "linear_resistance", + "description": "A linear_resistance object based on Ribasim.config.linear_resistance", + "type": "object" +} diff --git a/docs/schema/manning_resistance.schema.json b/docs/schema/manning_resistance.schema.json new file mode 100644 index 000000000..aef075138 --- /dev/null +++ b/docs/schema/manning_resistance.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "static": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + } + }, + "required": [ + ], + "$id": "https://deltares.github.io/Ribasim/schema/manning_resistance.schema.json", + "title": "manning_resistance", + "description": "A manning_resistance object based on Ribasim.config.manning_resistance", + "type": "object" +} diff --git a/docs/schema/outlet.schema.json b/docs/schema/outlet.schema.json new file mode 100644 index 000000000..1e6e2efd5 --- /dev/null +++ b/docs/schema/outlet.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "static": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + } + }, + "required": [ + ], + "$id": "https://deltares.github.io/Ribasim/schema/outlet.schema.json", + "title": "outlet", + "description": "A outlet object based on Ribasim.config.outlet", + "type": "object" +} diff --git a/docs/schema/pid_control.schema.json b/docs/schema/pid_control.schema.json new file mode 100644 index 000000000..5e7eb7024 --- /dev/null +++ b/docs/schema/pid_control.schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "time": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + }, + "static": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + } + }, + "required": [ + ], + "$id": "https://deltares.github.io/Ribasim/schema/pid_control.schema.json", + "title": "pid_control", + "description": "A pid_control object based on Ribasim.config.pid_control", + "type": "object" +} diff --git a/docs/schema/pump.schema.json b/docs/schema/pump.schema.json new file mode 100644 index 000000000..2679991b2 --- /dev/null +++ b/docs/schema/pump.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "static": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + } + }, + "required": [ + ], + "$id": "https://deltares.github.io/Ribasim/schema/pump.schema.json", + "title": "pump", + "description": "A pump object based on Ribasim.config.pump", + "type": "object" +} diff --git a/docs/schema/tabulated_rating_curve.schema.json b/docs/schema/tabulated_rating_curve.schema.json new file mode 100644 index 000000000..2d1beff66 --- /dev/null +++ b/docs/schema/tabulated_rating_curve.schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "time": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + }, + "static": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + } + }, + "required": [ + ], + "$id": "https://deltares.github.io/Ribasim/schema/tabulated_rating_curve.schema.json", + "title": "tabulated_rating_curve", + "description": "A tabulated_rating_curve object based on Ribasim.config.tabulated_rating_curve", + "type": "object" +} diff --git a/docs/schema/terminal.schema.json b/docs/schema/terminal.schema.json new file mode 100644 index 000000000..c6ca4c650 --- /dev/null +++ b/docs/schema/terminal.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "static": { + "format": "default", + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "default": null + } + }, + "required": [ + ], + "$id": "https://deltares.github.io/Ribasim/schema/terminal.schema.json", + "title": "terminal", + "description": "A terminal object based on Ribasim.config.terminal", + "type": "object" +} diff --git a/python/ribasim/ribasim/__init__.py b/python/ribasim/ribasim/__init__.py index 0eb79db09..7bf297f14 100644 --- a/python/ribasim/ribasim/__init__.py +++ b/python/ribasim/ribasim/__init__.py @@ -2,9 +2,10 @@ from ribasim import models, utils +from ribasim.config import Config, Logging, Solver from ribasim.geometry.edge import Edge from ribasim.geometry.node import Node -from ribasim.model import Logging, Model, Solver +from ribasim.model import Model from ribasim.node_types.basin import Basin from ribasim.node_types.discrete_control import DiscreteControl from ribasim.node_types.flow_boundary import FlowBoundary @@ -21,6 +22,7 @@ __all__ = [ "models", "utils", + "Config", "Basin", "Edge", "FractionalFlow", diff --git a/python/ribasim/ribasim/config.py b/python/ribasim/ribasim/config.py new file mode 100644 index 000000000..1f5bc7662 --- /dev/null +++ b/python/ribasim/ribasim/config.py @@ -0,0 +1,171 @@ +# generated by datamodel-codegen: +# filename: Config.schema.json + +from __future__ import annotations + +from datetime import datetime +from typing import List, Optional, Union + +from pydantic import BaseModel, Field + + +class Output(BaseModel): + compression: str = "zstd" + basin: str = "output/basin.arrow" + flow: str = "output/flow.arrow" + control: str = "output/control.arrow" + outstate: Optional[str] = None + compression_level: int = 6 + + +class LevelBoundary(BaseModel): + time: Optional[str] = None + static: Optional[str] = None + + +class Pump(BaseModel): + static: Optional[str] = None + + +class DiscreteControl(BaseModel): + logic: Optional[str] = None + condition: Optional[str] = None + + +class Solver(BaseModel): + reltol: float = 0.001 + saveat: Union[List[float], float] = [] + maxiters: int = 1000000000 + autodiff: bool = True + adaptive: bool = True + algorithm: str = "QNDF" + abstol: float = 1e-06 + dt: float = 0 + sparse: bool = True + + +class FlowBoundary(BaseModel): + time: Optional[str] = None + static: Optional[str] = None + + +class PidControl(BaseModel): + time: Optional[str] = None + static: Optional[str] = None + + +class FractionalFlow(BaseModel): + static: Optional[str] = None + + +class ManningResistance(BaseModel): + static: Optional[str] = None + + +class TabulatedRatingCurve(BaseModel): + time: Optional[str] = None + static: Optional[str] = None + + +class Logging(BaseModel): + timing: bool = False + verbosity: str = "info" + + +class Outlet(BaseModel): + static: Optional[str] = None + + +class Terminal(BaseModel): + static: Optional[str] = None + + +class Basin(BaseModel): + profile: Optional[str] = None + static: Optional[str] = None + forcing: Optional[str] = None + state: Optional[str] = None + + +class LinearResistance(BaseModel): + static: Optional[str] = None + + +class Config(BaseModel): + output: Output = Field( + default_factory=lambda: Output.parse_obj( + { + "basin": "output/basin.arrow", + "flow": "output/flow.arrow", + "control": "output/control.arrow", + "outstate": None, + "compression": "zstd", + "compression_level": 6, + } + ) + ) + starttime: datetime + update_timestep: float = 86400 + input_dir: str = "." + output_dir: str = "." + level_boundary: LevelBoundary = Field( + default_factory=lambda: LevelBoundary.parse_obj({"static": None, "time": None}) + ) + pump: Pump = Field(default_factory=lambda: Pump.parse_obj({"static": None})) + discrete_control: DiscreteControl = Field( + default_factory=lambda: DiscreteControl.parse_obj( + {"condition": None, "logic": None} + ) + ) + solver: Solver = Field( + default_factory=lambda: Solver.parse_obj( + { + "algorithm": "QNDF", + "saveat": [], + "adaptive": True, + "dt": 0, + "abstol": 1e-06, + "reltol": 0.001, + "maxiters": 1000000000, + "sparse": True, + "autodiff": True, + } + ) + ) + flow_boundary: FlowBoundary = Field( + default_factory=lambda: FlowBoundary.parse_obj({"static": None, "time": None}) + ) + pid_control: PidControl = Field( + default_factory=lambda: PidControl.parse_obj({"static": None, "time": None}) + ) + fractional_flow: FractionalFlow = Field( + default_factory=lambda: FractionalFlow.parse_obj({"static": None}) + ) + relative_dir: str = "." + endtime: datetime + manning_resistance: ManningResistance = Field( + default_factory=lambda: ManningResistance.parse_obj({"static": None}) + ) + tabulated_rating_curve: TabulatedRatingCurve = Field( + default_factory=lambda: TabulatedRatingCurve.parse_obj( + {"static": None, "time": None} + ) + ) + logging: Logging = Field( + default_factory=lambda: Logging.parse_obj( + {"verbosity": {"level": 0}, "timing": False} + ) + ) + outlet: Outlet = Field(default_factory=lambda: Outlet.parse_obj({"static": None})) + geopackage: str + terminal: Terminal = Field( + default_factory=lambda: Terminal.parse_obj({"static": None}) + ) + basin: Basin = Field( + default_factory=lambda: Basin.parse_obj( + {"forcing": None, "profile": None, "state": None, "static": None} + ) + ) + linear_resistance: LinearResistance = Field( + default_factory=lambda: LinearResistance.parse_obj({"static": None}) + ) diff --git a/python/ribasim/ribasim/model.py b/python/ribasim/ribasim/model.py index 4b271865f..aba0d84af 100644 --- a/python/ribasim/ribasim/model.py +++ b/python/ribasim/ribasim/model.py @@ -2,10 +2,9 @@ import inspect import shutil from contextlib import closing -from enum import Enum from pathlib import Path from sqlite3 import connect -from typing import Any, List, Optional, Type, Union, cast +from typing import Any, Optional, Type, cast import matplotlib.pyplot as plt import numpy as np @@ -15,6 +14,7 @@ from pydantic import BaseModel from ribasim import geometry, node_types +from ribasim.config import Logging, Solver from ribasim.geometry.edge import Edge from ribasim.geometry.node import Node @@ -36,30 +36,6 @@ from ribasim.types import FilePath -class Solver(BaseModel): - algorithm: Optional[str] - saveat: Optional[Union[float, List[float]]] - adaptive: Optional[bool] - dt: Optional[float] - abstol: Optional[float] - reltol: Optional[float] - maxiters: Optional[int] - sparse: Optional[bool] - autodiff: Optional[bool] - - -class Verbosity(str, Enum): - debug = "debug" - info = "info" - warn = "warn" - error = "error" - - -class Logging(BaseModel): - verbosity: Optional[Verbosity] = Verbosity.info - timing: Optional[bool] = False - - class Model(BaseModel): """ A full Ribasim model schematisation with all input. diff --git a/python/ribasim/ribasim/models.py b/python/ribasim/ribasim/models.py index 9fb2ab244..b9aabdfe2 100644 --- a/python/ribasim/ribasim/models.py +++ b/python/ribasim/ribasim/models.py @@ -1,6 +1,5 @@ # generated by datamodel-codegen: # filename: root.schema.json -# timestamp: 2023-09-04T11:45:02+00:00 from __future__ import annotations @@ -11,210 +10,210 @@ class DiscreteControlLogic(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - truth_state: str = Field(..., description="truth_state") - node_id: int = Field(..., description="node_id") - control_state: str = Field(..., description="control_state") + remarks: str = Field("", description="a hack for pandera") + truth_state: str + node_id: int + control_state: str class Edge(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - edge_type: str = Field(..., description="edge_type") - fid: int = Field(..., description="fid") - to_node_id: int = Field(..., description="to_node_id") - from_node_id: int = Field(..., description="from_node_id") + remarks: str = Field("", description="a hack for pandera") + edge_type: str + fid: int + to_node_id: int + from_node_id: int class FlowBoundaryTime(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - time: datetime = Field(..., description="time") - flow_rate: float = Field(..., description="flow_rate") - node_id: int = Field(..., description="node_id") + remarks: str = Field("", description="a hack for pandera") + time: datetime + flow_rate: float + node_id: int class PumpStatic(BaseModel): - max_flow_rate: Optional[float] = Field(None, description="max_flow_rate") - remarks: Optional[str] = Field("", description="a hack for pandera") - active: Optional[bool] = Field(None, description="active") - flow_rate: float = Field(..., description="flow_rate") - node_id: int = Field(..., description="node_id") - control_state: Optional[str] = Field(None, description="control_state") - min_flow_rate: Optional[float] = Field(None, description="min_flow_rate") + max_flow_rate: Optional[float] = None + remarks: str = Field("", description="a hack for pandera") + active: Optional[bool] = None + flow_rate: float + node_id: int + control_state: Optional[str] = None + min_flow_rate: Optional[float] = None class LevelBoundaryStatic(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - active: Optional[bool] = Field(None, description="active") - node_id: int = Field(..., description="node_id") - level: float = Field(..., description="level") + remarks: str = Field("", description="a hack for pandera") + active: Optional[bool] = None + node_id: int + level: float class DiscreteControlCondition(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - greater_than: float = Field(..., description="greater_than") - listen_feature_id: int = Field(..., description="listen_feature_id") - node_id: int = Field(..., description="node_id") - variable: str = Field(..., description="variable") - look_ahead: Optional[float] = Field(None, description="look_ahead") + remarks: str = Field("", description="a hack for pandera") + greater_than: float + listen_feature_id: int + node_id: int + variable: str + look_ahead: Optional[float] = None class BasinForcing(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - time: datetime = Field(..., description="time") - precipitation: float = Field(..., description="precipitation") - infiltration: float = Field(..., description="infiltration") - urban_runoff: float = Field(..., description="urban_runoff") - node_id: int = Field(..., description="node_id") - potential_evaporation: float = Field(..., description="potential_evaporation") - drainage: float = Field(..., description="drainage") + remarks: str = Field("", description="a hack for pandera") + time: datetime + precipitation: float + infiltration: float + urban_runoff: float + node_id: int + potential_evaporation: float + drainage: float class FractionalFlowStatic(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - node_id: int = Field(..., description="node_id") - fraction: float = Field(..., description="fraction") - control_state: Optional[str] = Field(None, description="control_state") + remarks: str = Field("", description="a hack for pandera") + node_id: int + fraction: float + control_state: Optional[str] = None class LinearResistanceStatic(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - active: Optional[bool] = Field(None, description="active") - node_id: int = Field(..., description="node_id") - resistance: float = Field(..., description="resistance") - control_state: Optional[str] = Field(None, description="control_state") + remarks: str = Field("", description="a hack for pandera") + active: Optional[bool] = None + node_id: int + resistance: float + control_state: Optional[str] = None class PidControlStatic(BaseModel): - integral: float = Field(..., description="integral") - remarks: Optional[str] = Field("", description="a hack for pandera") - listen_node_id: int = Field(..., description="listen_node_id") - active: Optional[bool] = Field(None, description="active") - proportional: float = Field(..., description="proportional") - node_id: int = Field(..., description="node_id") - target: float = Field(..., description="target") - derivative: float = Field(..., description="derivative") - control_state: Optional[str] = Field(None, description="control_state") + integral: float + remarks: str = Field("", description="a hack for pandera") + listen_node_id: int + active: Optional[bool] = None + proportional: float + node_id: int + target: float + derivative: float + control_state: Optional[str] = None class PidControlTime(BaseModel): - integral: float = Field(..., description="integral") - remarks: Optional[str] = Field("", description="a hack for pandera") - listen_node_id: int = Field(..., description="listen_node_id") - time: datetime = Field(..., description="time") - proportional: float = Field(..., description="proportional") - node_id: int = Field(..., description="node_id") - target: float = Field(..., description="target") - derivative: float = Field(..., description="derivative") - control_state: Optional[str] = Field(None, description="control_state") + integral: float + remarks: str = Field("", description="a hack for pandera") + listen_node_id: int + time: datetime + proportional: float + node_id: int + target: float + derivative: float + control_state: Optional[str] = None class ManningResistanceStatic(BaseModel): - length: float = Field(..., description="length") - manning_n: float = Field(..., description="manning_n") - remarks: Optional[str] = Field("", description="a hack for pandera") - active: Optional[bool] = Field(None, description="active") - profile_width: float = Field(..., description="profile_width") - node_id: int = Field(..., description="node_id") - profile_slope: float = Field(..., description="profile_slope") - control_state: Optional[str] = Field(None, description="control_state") + length: float + manning_n: float + remarks: str = Field("", description="a hack for pandera") + active: Optional[bool] = None + profile_width: float + node_id: int + profile_slope: float + control_state: Optional[str] = None class FlowBoundaryStatic(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - active: Optional[bool] = Field(None, description="active") - flow_rate: float = Field(..., description="flow_rate") - node_id: int = Field(..., description="node_id") + remarks: str = Field("", description="a hack for pandera") + active: Optional[bool] = None + flow_rate: float + node_id: int class OutletStatic(BaseModel): - max_flow_rate: Optional[float] = Field(None, description="max_flow_rate") - remarks: Optional[str] = Field("", description="a hack for pandera") - active: Optional[bool] = Field(None, description="active") - min_crest_level: Optional[float] = Field(None, description="min_crest_level") - flow_rate: float = Field(..., description="flow_rate") - node_id: int = Field(..., description="node_id") - control_state: Optional[str] = Field(None, description="control_state") - min_flow_rate: Optional[float] = Field(None, description="min_flow_rate") + max_flow_rate: Optional[float] = None + remarks: str = Field("", description="a hack for pandera") + active: Optional[bool] = None + min_crest_level: Optional[float] = None + flow_rate: float + node_id: int + control_state: Optional[str] = None + min_flow_rate: Optional[float] = None class Node(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - fid: int = Field(..., description="fid") - type: str = Field(..., description="type") + remarks: str = Field("", description="a hack for pandera") + fid: int + type: str class TabulatedRatingCurveTime(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - time: datetime = Field(..., description="time") - node_id: int = Field(..., description="node_id") - discharge: float = Field(..., description="discharge") - level: float = Field(..., description="level") + remarks: str = Field("", description="a hack for pandera") + time: datetime + node_id: int + discharge: float + level: float class TabulatedRatingCurveStatic(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - active: Optional[bool] = Field(None, description="active") - node_id: int = Field(..., description="node_id") - discharge: float = Field(..., description="discharge") - level: float = Field(..., description="level") - control_state: Optional[str] = Field(None, description="control_state") + remarks: str = Field("", description="a hack for pandera") + active: Optional[bool] = None + node_id: int + discharge: float + level: float + control_state: Optional[str] = None class LevelBoundaryTime(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - time: datetime = Field(..., description="time") - node_id: int = Field(..., description="node_id") - level: float = Field(..., description="level") + remarks: str = Field("", description="a hack for pandera") + time: datetime + node_id: int + level: float class BasinState(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - node_id: int = Field(..., description="node_id") - level: float = Field(..., description="level") + remarks: str = Field("", description="a hack for pandera") + node_id: int + level: float class BasinProfile(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - area: float = Field(..., description="area") - node_id: int = Field(..., description="node_id") - level: float = Field(..., description="level") + remarks: str = Field("", description="a hack for pandera") + area: float + node_id: int + level: float class TerminalStatic(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - node_id: int = Field(..., description="node_id") + remarks: str = Field("", description="a hack for pandera") + node_id: int class BasinStatic(BaseModel): - remarks: Optional[str] = Field("", description="a hack for pandera") - precipitation: float = Field(..., description="precipitation") - infiltration: float = Field(..., description="infiltration") - urban_runoff: float = Field(..., description="urban_runoff") - node_id: int = Field(..., description="node_id") - potential_evaporation: float = Field(..., description="potential_evaporation") - drainage: float = Field(..., description="drainage") + remarks: str = Field("", description="a hack for pandera") + precipitation: float + infiltration: float + urban_runoff: float + node_id: int + potential_evaporation: float + drainage: float class Root(BaseModel): - BasinForcing: Optional[BasinForcing] = None - BasinProfile: Optional[BasinProfile] = None - BasinState: Optional[BasinState] = None - BasinStatic: Optional[BasinStatic] = None - DiscreteControlCondition: Optional[DiscreteControlCondition] = None DiscreteControlLogic: Optional[DiscreteControlLogic] = None Edge: Optional[Edge] = None - FlowBoundaryStatic: Optional[FlowBoundaryStatic] = None FlowBoundaryTime: Optional[FlowBoundaryTime] = None - FractionalFlowStatic: Optional[FractionalFlowStatic] = None + PumpStatic: Optional[PumpStatic] = None LevelBoundaryStatic: Optional[LevelBoundaryStatic] = None - LevelBoundaryTime: Optional[LevelBoundaryTime] = None + DiscreteControlCondition: Optional[DiscreteControlCondition] = None + BasinForcing: Optional[BasinForcing] = None + FractionalFlowStatic: Optional[FractionalFlowStatic] = None LinearResistanceStatic: Optional[LinearResistanceStatic] = None - ManningResistanceStatic: Optional[ManningResistanceStatic] = None - Node: Optional[Node] = None - OutletStatic: Optional[OutletStatic] = None PidControlStatic: Optional[PidControlStatic] = None PidControlTime: Optional[PidControlTime] = None - PumpStatic: Optional[PumpStatic] = None - TabulatedRatingCurveStatic: Optional[TabulatedRatingCurveStatic] = None + ManningResistanceStatic: Optional[ManningResistanceStatic] = None + FlowBoundaryStatic: Optional[FlowBoundaryStatic] = None + OutletStatic: Optional[OutletStatic] = None + Node: Optional[Node] = None TabulatedRatingCurveTime: Optional[TabulatedRatingCurveTime] = None + TabulatedRatingCurveStatic: Optional[TabulatedRatingCurveStatic] = None + LevelBoundaryTime: Optional[LevelBoundaryTime] = None + BasinState: Optional[BasinState] = None + BasinProfile: Optional[BasinProfile] = None TerminalStatic: Optional[TerminalStatic] = None + BasinStatic: Optional[BasinStatic] = None diff --git a/python/ribasim/ribasim/node_types/basin.py b/python/ribasim/ribasim/node_types/basin.py index 3f7f00537..900b407c3 100644 --- a/python/ribasim/ribasim/node_types/basin.py +++ b/python/ribasim/ribasim/node_types/basin.py @@ -1,43 +1,18 @@ from typing import Optional -import pandera as pa -from pandera.engines.pandas_engine import PydanticModel from pandera.typing import DataFrame -from ribasim import models from ribasim.input_base import TableModel +from ribasim.schemas import ( # type: ignore + BasinForcingSchema, + BasinProfileSchema, + BasinStateSchema, + BasinStaticSchema, +) __all__ = ("Basin",) -class StaticSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.BasinStatic) - - -class ForcingSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.BasinForcing) - - -class ProfileSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.BasinProfile) - - -class StateSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.BasinState) - - class Basin(TableModel): """ Input for a (sub-)basin: an area of land where all flowing surface water converges to a single point. @@ -54,10 +29,10 @@ class Basin(TableModel): Table describing the initial condition. """ - profile: DataFrame[ProfileSchema] - static: Optional[DataFrame[StaticSchema]] = None - forcing: Optional[DataFrame[ForcingSchema]] = None - state: Optional[DataFrame[StateSchema]] = None + profile: DataFrame[BasinProfileSchema] + static: Optional[DataFrame[BasinStaticSchema]] = None + forcing: Optional[DataFrame[BasinForcingSchema]] = None + state: Optional[DataFrame[BasinStateSchema]] = None def sort(self): self.profile = self.profile.sort_values(["node_id", "level"], ignore_index=True) diff --git a/python/ribasim/ribasim/node_types/discrete_control.py b/python/ribasim/ribasim/node_types/discrete_control.py index 07f1b1772..99c3fe4b6 100644 --- a/python/ribasim/ribasim/node_types/discrete_control.py +++ b/python/ribasim/ribasim/node_types/discrete_control.py @@ -1,27 +1,14 @@ -import pandera as pa -from pandera.engines.pandas_engine import PydanticModel from pandera.typing import DataFrame -from ribasim import models from ribasim.input_base import TableModel +from ribasim.schemas import ( # type: ignore + DiscreteControlConditionSchema, + DiscreteControlLogicSchema, +) __all__ = ("DiscreteControl",) -class ConditionSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.DiscreteControlCondition) - - -class LogicSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.DiscreteControlLogic) - - class DiscreteControl(TableModel): """ Defines the control logic. @@ -34,5 +21,5 @@ class DiscreteControl(TableModel): Table with the information of truth state to control state mapping. """ - condition: DataFrame[ConditionSchema] - logic: DataFrame[LogicSchema] + condition: DataFrame[DiscreteControlConditionSchema] + logic: DataFrame[DiscreteControlLogicSchema] diff --git a/python/ribasim/ribasim/node_types/flow_boundary.py b/python/ribasim/ribasim/node_types/flow_boundary.py index 85aad8166..f78f913ca 100644 --- a/python/ribasim/ribasim/node_types/flow_boundary.py +++ b/python/ribasim/ribasim/node_types/flow_boundary.py @@ -1,30 +1,16 @@ from typing import Optional -import pandera as pa -from pandera.engines.pandas_engine import PydanticModel from pandera.typing import DataFrame -from ribasim import models from ribasim.input_base import TableModel +from ribasim.schemas import ( # type: ignore + FlowBoundaryStaticSchema, + FlowBoundaryTimeSchema, +) __all__ = ("FlowBoundary",) -class StaticSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.FlowBoundaryStatic) - - -class TimeSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.FlowBoundaryTime) - coerce = True # this is required, otherwise a SchemaInitError is raised - - class FlowBoundary(TableModel): """ Sets a precribed flow like a one-sided pump. @@ -37,5 +23,5 @@ class FlowBoundary(TableModel): Table with time-varying flow rates. """ - static: Optional[DataFrame[StaticSchema]] = None - time: Optional[DataFrame[TimeSchema]] = None + static: Optional[DataFrame[FlowBoundaryStaticSchema]] = None + time: Optional[DataFrame[FlowBoundaryTimeSchema]] = None diff --git a/python/ribasim/ribasim/node_types/fractional_flow.py b/python/ribasim/ribasim/node_types/fractional_flow.py index d86d5e40e..8e6648701 100644 --- a/python/ribasim/ribasim/node_types/fractional_flow.py +++ b/python/ribasim/ribasim/node_types/fractional_flow.py @@ -1,20 +1,11 @@ -import pandera as pa -from pandera.engines.pandas_engine import PydanticModel from pandera.typing import DataFrame -from ribasim import models from ribasim.input_base import TableModel +from ribasim.schemas import FractionalFlowStaticSchema # type: ignore __all__ = ("FractionalFlow",) -class StaticSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.FractionalFlowStatic) - - class FractionalFlow(TableModel): """ Receives a fraction of the flow. The fractions must sum to 1.0 for a furcation. @@ -25,7 +16,7 @@ class FractionalFlow(TableModel): Table with the constant flow fractions. """ - static: DataFrame[StaticSchema] + static: DataFrame[FractionalFlowStaticSchema] def sort(self): self.static = self.static.sort_values("node_id", ignore_index=True) diff --git a/python/ribasim/ribasim/node_types/level_boundary.py b/python/ribasim/ribasim/node_types/level_boundary.py index b5c3b7da3..c83b9e064 100644 --- a/python/ribasim/ribasim/node_types/level_boundary.py +++ b/python/ribasim/ribasim/node_types/level_boundary.py @@ -1,29 +1,16 @@ from typing import Optional -import pandera as pa -from pandera.engines.pandas_engine import PydanticModel from pandera.typing import DataFrame -from ribasim import models from ribasim.input_base import TableModel +from ribasim.schemas import ( # type: ignore + LevelBoundaryStaticSchema, + LevelBoundaryTimeSchema, +) __all__ = ("LevelBoundary",) -class StaticSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.LevelBoundaryStatic) - - -class TimeSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.LevelBoundaryTime) - - class LevelBoundary(TableModel): """ Stores water at a given level unaffected by flow, like an infinitely large basin. @@ -34,5 +21,5 @@ class LevelBoundary(TableModel): Table with the constant water levels. """ - static: Optional[DataFrame[StaticSchema]] = None - time: Optional[DataFrame[TimeSchema]] = None + static: Optional[DataFrame[LevelBoundaryStaticSchema]] = None + time: Optional[DataFrame[LevelBoundaryTimeSchema]] = None diff --git a/python/ribasim/ribasim/node_types/linear_resistance.py b/python/ribasim/ribasim/node_types/linear_resistance.py index 9f2ad68dd..ec93a53e1 100644 --- a/python/ribasim/ribasim/node_types/linear_resistance.py +++ b/python/ribasim/ribasim/node_types/linear_resistance.py @@ -1,20 +1,11 @@ -import pandera as pa -from pandera.engines.pandas_engine import PydanticModel from pandera.typing import DataFrame -from ribasim import models from ribasim.input_base import TableModel +from ribasim.schemas import LinearResistanceStaticSchema # type: ignore __all__ = ("LinearResistance",) -class StaticSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.LinearResistanceStatic) - - class LinearResistance(TableModel): """ Flow through this connection linearly depends on the level difference @@ -26,4 +17,4 @@ class LinearResistance(TableModel): Table with the constant resistances. """ - static: DataFrame[StaticSchema] + static: DataFrame[LinearResistanceStaticSchema] diff --git a/python/ribasim/ribasim/node_types/manning_resistance.py b/python/ribasim/ribasim/node_types/manning_resistance.py index bf7538314..50dc43fe3 100644 --- a/python/ribasim/ribasim/node_types/manning_resistance.py +++ b/python/ribasim/ribasim/node_types/manning_resistance.py @@ -1,20 +1,11 @@ -import pandera as pa -from pandera.engines.pandas_engine import PydanticModel from pandera.typing import DataFrame -from ribasim import models from ribasim.input_base import TableModel +from ribasim.schemas import ManningResistanceStaticSchema # type: ignore __all__ = ("ManningResistance",) -class StaticSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.ManningResistanceStatic) - - class ManningResistance(TableModel): """ Flow through this connection is estimated by conservation of energy and the @@ -26,4 +17,4 @@ class ManningResistance(TableModel): Table with the constant Manning parameters. """ - static: DataFrame[StaticSchema] + static: DataFrame[ManningResistanceStaticSchema] diff --git a/python/ribasim/ribasim/node_types/outlet.py b/python/ribasim/ribasim/node_types/outlet.py index 45f4afe7f..46fb70c22 100644 --- a/python/ribasim/ribasim/node_types/outlet.py +++ b/python/ribasim/ribasim/node_types/outlet.py @@ -1,20 +1,11 @@ -import pandera as pa -from pandera.engines.pandas_engine import PydanticModel from pandera.typing import DataFrame -from ribasim import models from ribasim.input_base import TableModel +from ribasim.schemas import OutletStaticSchema # type: ignore __all__ = ("Outlet",) -class StaticSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.OutletStatic) - - class Outlet(TableModel): """ Conducts water from a source node to a destination node. @@ -29,4 +20,4 @@ class Outlet(TableModel): Table with constant flow rates. """ - static: DataFrame[StaticSchema] + static: DataFrame[OutletStaticSchema] diff --git a/python/ribasim/ribasim/node_types/pid_control.py b/python/ribasim/ribasim/node_types/pid_control.py index 4de9a77d1..a75eae42d 100644 --- a/python/ribasim/ribasim/node_types/pid_control.py +++ b/python/ribasim/ribasim/node_types/pid_control.py @@ -1,29 +1,13 @@ from typing import Optional -import pandera as pa -from pandera.engines.pandas_engine import PydanticModel from pandera.typing import DataFrame -from ribasim import models from ribasim.input_base import TableModel +from ribasim.schemas import PidControlStaticSchema, PidControlTimeSchema # type: ignore __all__ = ("PidControl",) -class StaticSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.PidControlStatic) - - -class TimeSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.PidControlTime) - - class PidControl(TableModel): """ Controller based on PID (Proportional, integral, derivative) which @@ -37,8 +21,8 @@ class PidControl(TableModel): Table with time-varying data for this node type. """ - static: Optional[DataFrame[StaticSchema]] = None - time: Optional[DataFrame[TimeSchema]] = None + static: Optional[DataFrame[PidControlStaticSchema]] = None + time: Optional[DataFrame[PidControlTimeSchema]] = None class Config: validate_assignment = True diff --git a/python/ribasim/ribasim/node_types/pump.py b/python/ribasim/ribasim/node_types/pump.py index a6ad5ff56..d2a966a25 100644 --- a/python/ribasim/ribasim/node_types/pump.py +++ b/python/ribasim/ribasim/node_types/pump.py @@ -1,20 +1,11 @@ -import pandera as pa -from pandera.engines.pandas_engine import PydanticModel from pandera.typing import DataFrame -from ribasim import models from ribasim.input_base import TableModel +from ribasim.schemas import PumpStaticSchema # type: ignore __all__ = ("Pump",) -class StaticSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.PumpStatic) - - class Pump(TableModel): """ Pump water from a source node to a destination node. @@ -29,4 +20,4 @@ class Pump(TableModel): Table with constant flow rates. """ - static: DataFrame[StaticSchema] + static: DataFrame[PumpStaticSchema] diff --git a/python/ribasim/ribasim/node_types/tabulated_rating_curve.py b/python/ribasim/ribasim/node_types/tabulated_rating_curve.py index cded2f9f1..765a45965 100644 --- a/python/ribasim/ribasim/node_types/tabulated_rating_curve.py +++ b/python/ribasim/ribasim/node_types/tabulated_rating_curve.py @@ -1,31 +1,16 @@ from typing import Optional -import pandera as pa -from pandera.engines.pandas_engine import PydanticModel from pandera.typing import DataFrame -from ribasim import models from ribasim.input_base import TableModel +from ribasim.schemas import ( # type: ignore + TabulatedRatingCurveStaticSchema, + TabulatedRatingCurveTimeSchema, +) __all__ = ("TabulatedRatingCurve",) -class StaticSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.TabulatedRatingCurveStatic) - coerce = True # this is required, otherwise a SchemaInitError is raised - - -class TimeSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.TabulatedRatingCurveTime) - coerce = True # this is required, otherwise a SchemaInitError is raised - - class TabulatedRatingCurve(TableModel): """ Linearly interpolates discharge between a tabulation of level and discharge. @@ -38,8 +23,8 @@ class TabulatedRatingCurve(TableModel): Table with time-varying rating curves. """ - static: Optional[DataFrame[StaticSchema]] = None - time: Optional[DataFrame[TimeSchema]] = None + static: Optional[DataFrame[TabulatedRatingCurveStaticSchema]] = None + time: Optional[DataFrame[TabulatedRatingCurveTimeSchema]] = None def sort(self): self.static = self.static.sort_values(["node_id", "level"], ignore_index=True) diff --git a/python/ribasim/ribasim/node_types/terminal.py b/python/ribasim/ribasim/node_types/terminal.py index a04c85c28..3eb47e2a2 100644 --- a/python/ribasim/ribasim/node_types/terminal.py +++ b/python/ribasim/ribasim/node_types/terminal.py @@ -1,20 +1,11 @@ -import pandera as pa -from pandera.engines.pandas_engine import PydanticModel from pandera.typing import DataFrame -from ribasim import models from ribasim.input_base import TableModel +from ribasim.schemas import TerminalStaticSchema # type: ignore __all__ = ("Terminal",) -class StaticSchema(pa.SchemaModel): - class Config: - """Config with dataframe-level data type.""" - - dtype = PydanticModel(models.TerminalStatic) - - class Terminal(TableModel): """ Water sink without state or properties. @@ -25,4 +16,4 @@ class Terminal(TableModel): Table with only node IDs of this type. """ - static: DataFrame[StaticSchema] + static: DataFrame[TerminalStaticSchema] diff --git a/python/ribasim/ribasim/schemas.py b/python/ribasim/ribasim/schemas.py new file mode 100644 index 000000000..c7c44ab0c --- /dev/null +++ b/python/ribasim/ribasim/schemas.py @@ -0,0 +1,33 @@ +""" +Generate Pandera Schemas from all autogenerated Pydantic Models + +These classes have Schema as a postfix, so Ribasim.models.PumpStatic +becomes Ribasim.schemas.PumpStaticSchema. +""" +import inspect +import sys + +import pandera as pa +from pandera.engines.pandas_engine import PydanticModel + +from ribasim import models + + +def gen_schema(name, cls): + cname = f"{name}Schema" + ctype = type( + cname, + (pa.DataFrameModel,), + { + "Config": type( + f"{cname}.Config", + (), + {"dtype": PydanticModel(cls), "coerce": True}, + ) + }, + ) + setattr(sys.modules[__name__], cname, ctype) + + +for name, cls in inspect.getmembers(models, inspect.isclass): + gen_schema(name, cls) diff --git a/python/ribasim/tests/test_model.py b/python/ribasim/tests/test_model.py index 381cd9eb4..454bbe3df 100644 --- a/python/ribasim/tests/test_model.py +++ b/python/ribasim/tests/test_model.py @@ -14,8 +14,8 @@ def test_repr(basic): def test_solver(): solver = Solver() - assert solver.algorithm is None - assert solver.saveat is None + assert solver.algorithm == "QNDF" # default + assert solver.saveat == [] solver = Solver(saveat=3600.0) assert solver.saveat == 3600.0 @@ -28,10 +28,8 @@ def test_solver(): def test_invalid_node_type(basic): - model = basic - # Add entry with invalid node type - model.node.static = model.node.static._append( + basic.node.static = basic.node.static._append( {"type": "InvalidNodeType", "geometry": Point(0, 0)}, ignore_index=True ) @@ -39,7 +37,7 @@ def test_invalid_node_type(basic): TypeError, match=re.escape("Invalid node types detected: [InvalidNodeType].") + ".+", ): - model.validate_model_node_types() + basic.validate_model_node_types() def test_invalid_node_id(basic): diff --git a/python/ribasim_testmodels/ribasim_testmodels/__init__.py b/python/ribasim_testmodels/ribasim_testmodels/__init__.py index 2f15f4f77..3bcac41c0 100644 --- a/python/ribasim_testmodels/ribasim_testmodels/__init__.py +++ b/python/ribasim_testmodels/ribasim_testmodels/__init__.py @@ -33,9 +33,7 @@ discrete_control_of_pid_control_model, pid_control_model, ) -from ribasim_testmodels.time import ( - flow_boundary_time_model, -) +from ribasim_testmodels.time import flow_boundary_time_model from ribasim_testmodels.trivial import trivial_model __all__ = [