Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Read some interpolation tables into the Model struct #674

Merged
merged 43 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5096122
Read some interpolation tables into the Model struct
Huite Oct 17, 2023
5373322
Merge branch 'main' into interpolate-level
Huite Nov 15, 2023
7d6e552
format, process comments
Huite Nov 15, 2023
4e3a42b
Update LevelExporter to new Python API
Huite Nov 15, 2023
2c5a384
Make LevelExporter part of Basin
Huite Nov 17, 2023
b3829d1
Merge branch 'main' into interpolate-level
Huite Nov 17, 2023
0cab52a
Move LevelExporter into Parameters
Huite Nov 17, 2023
256c1e0
Get level interpolation running (currently at preset daily times)
Huite Nov 17, 2023
6e98867
Write exported levels to output arrow
Huite Nov 17, 2023
17f2b6e
Allow outputting of multiple systems for exported_levels
Huite Nov 17, 2023
f465a15
lower case basin exporter in model root
Huite Nov 20, 2023
4e348a0
Re-enable logging
Huite Nov 20, 2023
3e30179
Set debug to info for more readable terminal output while running tests
Huite Nov 20, 2023
9456cd1
Add validation for LevelExporter
Huite Nov 20, 2023
db4b6d9
Rename exporter to subgrid; Add validation; Make subgrids optional vi…
Huite Nov 20, 2023
263d5a6
Run codegen
Huite Nov 20, 2023
96a8d25
Update docs: usage.qmd
Huite Nov 20, 2023
973e0e6
Merge branch 'main' into interpolate-level
Huite Nov 20, 2023
b3b376a
Update docs/core/usage.qmd
Huite Nov 21, 2023
3382652
Enable extrapolation on subgrid level interpolation
Huite Nov 21, 2023
75b91fa
Merge branch 'main' into interpolate-level
Huite Nov 21, 2023
332aa57
subgrid levels not optional in expectation of PR 815
Huite Nov 22, 2023
d5d21b4
testset -> testitem for new test
Huite Nov 22, 2023
0dd9670
fixes
visr Nov 23, 2023
cdb0ea8
sentence per line
visr Nov 23, 2023
50e5246
Clarify name
visr Nov 23, 2023
f9a5980
rename to Basin / subgrid_level
visr Nov 23, 2023
be49933
no need for isempty
visr Nov 23, 2023
547cfb0
Expose subgrid_levels via BMI
Huite Nov 24, 2023
36bdd2e
Simplify & make naming consistent
Huite Nov 24, 2023
9313b76
Merge branch 'main' into interpolate-level
Huite Nov 24, 2023
b8d99e4
Add subgrid_levels to RESULTS_FILENAME
Huite Nov 24, 2023
ac58be5
Make subgrid levels computation option via Results config
Huite Nov 24, 2023
f836404
Merge branch 'main' into interpolate-level
visr Nov 24, 2023
359814c
update nodes.py
visr Nov 24, 2023
232d3c0
avoid splatting
visr Nov 24, 2023
6236211
Write out return type
visr Nov 24, 2023
55021b4
log errors directly
visr Nov 24, 2023
1268594
remove redundant sorting key
visr Nov 24, 2023
79c3c4e
pixi run codegen
visr Nov 24, 2023
38c3a5f
update last validation function to log errors directly
visr Nov 24, 2023
7cdcbb8
fix runstats.jl
visr Nov 24, 2023
5ab0091
Fix QGIS plugin in Python 3.9
visr Nov 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/Ribasim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ include("validation.jl")
include("solve.jl")
include("config.jl")
using .config
include("export.jl")
include("utils.jl")
include("lib.jl")
include("io.jl")
Expand Down
6 changes: 4 additions & 2 deletions core/src/bmi.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function BMI.initialize(T::Type{Model}, config::Config)::Model
# All data from the GeoPackage that we need during runtime is copied into memory,
# so we can directly close it again.
db = SQLite.DB(gpkg_path)
local parameters, state, n, tstops
local parameters, state, n, tstops, level_exporters
try
parameters = Parameters(db, config)

Expand Down Expand Up @@ -82,6 +82,8 @@ function BMI.initialize(T::Type{Model}, config::Config)::Model
# use state
state = load_structvector(db, config, BasinStateV1)
n = length(get_ids(db, "Basin"))

level_exporters = create_level_exporters(db, config, basin)
finally
# always close the GeoPackage, also in case of an error
close(db)
Expand Down Expand Up @@ -150,7 +152,7 @@ function BMI.initialize(T::Type{Model}, config::Config)::Model

set_initial_discrete_controlled_parameters!(integrator, storage)

return Model(integrator, config, saved_flow)
return Model(integrator, config, saved_flow, level_exporters)
end

"""
Expand Down
58 changes: 58 additions & 0 deletions core/src/export.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# This module exports a water level:
#
# * the water level of the original hydrodynamic model before lumping.
# * a differently aggregated water level, used for e.g. coupling to MODFLOW.
#
# The second is arguably easier to interpret.

"""
basin_level: a view on Ribasim's basin level.
level: the interpolated water level
tables: the interpolator callables

All members of this struct have length n_elem.
"""
struct LevelExporter
basin_index::Vector{Int}
interpolations::Vector{ScalarInterpolation}
level::Vector{Float64}
end

function LevelExporter(tables, node_to_basin::Dict{Int, Int})::LevelExporter
basin_ids = Int[]
interpolations = ScalarInterpolation[]

for group in IterTools.groupby(row -> row.element_id, tables)
node_id = first(getproperty.(group, :node_id))
basin_level = getproperty.(group, :basin_level)
element_level = getproperty.(group, :level)
# Ensure it doesn't extrapolate before the first value.
new_interp = LinearInterpolation([element_level[1], element_level...], [prevfloat(basin_level[1]), basin_level...])
push!(basin_ids, node_to_basin[node_id])
push!(interpolations, new_interp)
end

return LevelExporter(basin_ids, interpolations, fill(NaN, length(basin_ids)))
end

function create_level_exporters(db::DB, config::Config, basin::Basin)::Dict{String, LevelExporter}
node_to_basin = Dict(node_id => index for (index, node_id) in enumerate(basin.node_id))
tables = load_structvector(db, config, LevelExporterStaticV1)
level_exporters = Dict{String, LevelExporter}()
if length(tables) > 0
visr marked this conversation as resolved.
Show resolved Hide resolved
for group in IterTools.groupby(row -> row.name, tables)
name = first(getproperty.(group, :name))
level_exporters[name] = LevelExporter(group, node_to_basin)
end
end
return level_exporters
end

"""
Compute a new water level for each external element.
"""
function update!(exporter::LevelExporter, basin_level)
visr marked this conversation as resolved.
Show resolved Hide resolved
for (i, (index, interp)) in enumerate(zip(exporter.basin_index, exporter.interpolations))
exporter.level[i] = interp(basin_level[index])
end
end
4 changes: 3 additions & 1 deletion core/src/lib.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ struct Model{T}
integrator::T
config::Config
saved_flow::SavedValues{Float64, Vector{Float64}}
level_exporters::Dict{String, LevelExporter}
function Model(
integrator::T,
config,
saved_flow,
level_exporters,
) where {T <: SciMLBase.AbstractODEIntegrator}
new{T}(integrator, config, saved_flow)
new{T}(integrator, config, saved_flow, level_exporters)
end
end

Expand Down
9 changes: 9 additions & 0 deletions core/src/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
@schema "ribasim.outlet.static" OutletStatic
@schema "ribasim.user.static" UserStatic
@schema "ribasim.user.time" UserTime
@schema "ribasim.levelexporter.static" LevelExporterStatic

const delimiter = " / "
tablename(sv::Type{SchemaVersion{T, N}}) where {T, N} = join(nodetype(sv), delimiter)
Expand Down Expand Up @@ -308,6 +309,14 @@ end
priority::Int
end

@version LevelExporterStaticV1 begin
name::String
element_id::Int
node_id::Int
basin_level::Float64
level::Float64
end

function variable_names(s::Any)
filter(x -> !(x in (:node_id, :control_state)), fieldnames(s))
end
Expand Down
13 changes: 10 additions & 3 deletions docs/schema/Config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@
"timing": false
}
},
"fractional_flow": {
"$ref": "https://deltares.github.io/Ribasim/schema/fractional_flow.schema.json",
"default": {
"static": null
}
},
"terminal": {
"$ref": "https://deltares.github.io/Ribasim/schema/terminal.schema.json",
"default": {
Expand Down Expand Up @@ -156,8 +162,8 @@
"static": null
}
},
"fractional_flow": {
"$ref": "https://deltares.github.io/Ribasim/schema/fractional_flow.schema.json",
"level_exporter": {
"$ref": "https://deltares.github.io/Ribasim/schema/level_exporter.schema.json",
"default": {
"static": null
}
Expand All @@ -174,6 +180,7 @@
"output",
"solver",
"logging",
"fractional_flow",
"terminal",
"pid_control",
"level_boundary",
Expand All @@ -186,6 +193,6 @@
"discrete_control",
"outlet",
"linear_resistance",
"fractional_flow"
"level_exporter"
]
}
42 changes: 42 additions & 0 deletions docs/schema/LevelExporterStatic.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://deltares.github.io/Ribasim/schema/LevelExporterStatic.schema.json",
"title": "LevelExporterStatic",
"description": "A LevelExporterStatic object based on Ribasim.LevelExporterStaticV1",
"type": "object",
"properties": {
"name": {
"format": "default",
"type": "string"
},
"element_id": {
"format": "default",
"type": "integer"
},
"node_id": {
"format": "default",
"type": "integer"
},
"basin_level": {
"format": "double",
"type": "number"
},
"level": {
"format": "double",
"type": "number"
},
"remarks": {
"description": "a hack for pandera",
"type": "string",
"format": "default",
"default": ""
}
},
"required": [
"name",
"element_id",
"node_id",
"basin_level",
"level"
]
}
23 changes: 23 additions & 0 deletions docs/schema/level_exporter.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://deltares.github.io/Ribasim/schema/level_exporter.schema.json",
"title": "level_exporter",
"description": "A level_exporter object based on Ribasim.config.level_exporter",
"type": "object",
"properties": {
"static": {
"format": "default",
"anyOf": [
{
"type": "null"
},
{
"type": "string"
}
],
"default": null
}
},
"required": [
]
}
3 changes: 3 additions & 0 deletions docs/schema/root.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
"LevelBoundaryTime": {
"$ref": "LevelBoundaryTime.schema.json"
},
"LevelExporterStatic": {
"$ref": "LevelExporterStatic.schema.json"
},
"LinearResistanceStatic": {
"$ref": "LinearResistanceStatic.schema.json"
},
Expand Down
2 changes: 2 additions & 0 deletions python/ribasim/ribasim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from ribasim.config import Config, Logging, Solver
from ribasim.geometry.edge import Edge
from ribasim.geometry.node import Node
from ribasim.level_exporter import LevelExporter
from ribasim.model import Model
from ribasim.node_types.basin import Basin
from ribasim.node_types.discrete_control import DiscreteControl
Expand Down Expand Up @@ -42,4 +43,5 @@
"DiscreteControl",
"PidControl",
"User",
"LevelExporter",
]
13 changes: 10 additions & 3 deletions python/ribasim/ribasim/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class Logging(BaseModel):
timing: bool = False


class FractionalFlow(BaseModel):
static: Optional[str] = None


class Terminal(BaseModel):
static: Optional[str] = None

Expand Down Expand Up @@ -95,7 +99,7 @@ class LinearResistance(BaseModel):
static: Optional[str] = None


class FractionalFlow(BaseModel):
class LevelExporter(BaseModel):
static: Optional[str] = None


Expand Down Expand Up @@ -142,6 +146,9 @@ class Config(BaseModel):
{"verbosity": {"level": 0}, "timing": False}
)
)
fractional_flow: FractionalFlow = Field(
default_factory=lambda: FractionalFlow.parse_obj({"static": None})
)
terminal: Terminal = Field(
default_factory=lambda: Terminal.parse_obj({"static": None})
)
Expand Down Expand Up @@ -180,6 +187,6 @@ class Config(BaseModel):
linear_resistance: LinearResistance = Field(
default_factory=lambda: LinearResistance.parse_obj({"static": None})
)
fractional_flow: FractionalFlow = Field(
default_factory=lambda: FractionalFlow.parse_obj({"static": None})
level_exporter: LevelExporter = Field(
default_factory=lambda: LevelExporter.parse_obj({"static": None})
)
17 changes: 17 additions & 0 deletions python/ribasim/ribasim/level_exporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from pandera.typing import DataFrame

from ribasim.input_base import TableModel
from ribasim.schemas import LevelExporterStaticSchema # type: ignore


class LevelExporter(TableModel):
"""The level exporter export Ribasim water levels."""

static: DataFrame[LevelExporterStaticSchema]

def sort(self):
self.static.sort_values(

Check warning on line 13 in python/ribasim/ribasim/level_exporter.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/level_exporter.py#L13

Added line #L13 was not covered by tests
["name", "element_id", "node_id", "basin_level"],
ignore_index=True,
inplace=True,
)
2 changes: 2 additions & 0 deletions python/ribasim/ribasim/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# Do not import from ribasim namespace: will create import errors.
# E.g. not: from ribasim import Basin
from ribasim.input_base import TableModel
from ribasim.level_exporter import LevelExporter
from ribasim.node_types.basin import Basin
from ribasim.node_types.discrete_control import DiscreteControl
from ribasim.node_types.flow_boundary import FlowBoundary
Expand Down Expand Up @@ -104,6 +105,7 @@ class Model(BaseModel):
discrete_control: Optional[DiscreteControl]
pid_control: Optional[PidControl]
user: Optional[User]
level_exporter: Optional[LevelExporter]
starttime: datetime.datetime
endtime: datetime.datetime
solver: Optional[Solver]
Expand Down
10 changes: 10 additions & 0 deletions python/ribasim/ribasim/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ class LevelBoundaryTime(BaseModel):
remarks: str = Field("", description="a hack for pandera")


class LevelExporterStatic(BaseModel):
name: str
element_id: int
node_id: int
basin_level: float
level: float
remarks: str = Field("", description="a hack for pandera")


class LinearResistanceStatic(BaseModel):
node_id: int
active: Optional[bool] = None
Expand Down Expand Up @@ -229,6 +238,7 @@ class Root(BaseModel):
FractionalFlowStatic: Optional[FractionalFlowStatic] = None
LevelBoundaryStatic: Optional[LevelBoundaryStatic] = None
LevelBoundaryTime: Optional[LevelBoundaryTime] = None
LevelExporterStatic: Optional[LevelExporterStatic] = None
LinearResistanceStatic: Optional[LinearResistanceStatic] = None
ManningResistanceStatic: Optional[ManningResistanceStatic] = None
Node: Optional[Node] = None
Expand Down
19 changes: 19 additions & 0 deletions python/ribasim_testmodels/ribasim_testmodels/trivial.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,32 @@ def trivial_model() -> ribasim.Model:
)
)

# Create a level exporter from one basin to three elements. Scale one to one, but:
#
# 1. start at -1.0
# 2. start at 0.0
# 3. start at 1.0
#
level_exporter = ribasim.LevelExporter(
static=pd.DataFrame(
data={
"name": "primary-system",
"element_id": [1, 1, 2, 2, 3, 3],
"node_id": [1, 1, 1, 1, 1, 1],
"basin_level": [0.0, 1.0, 0.0, 1.0, 0.0, 1.0],
"level": [-1.0, 0.0, 0.0, 1.0, 1.0, 2.0],
}
)
)

model = ribasim.Model(
modelname="trivial",
node=node,
edge=edge,
basin=basin,
terminal=terminal,
tabulated_rating_curve=rating_curve,
level_exporter=level_exporter,
starttime="2020-01-01 00:00:00",
endtime="2021-01-01 00:00:00",
)
Expand Down
Loading