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

Allow missing 'Basin / time' data #1028

Merged
merged 3 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions core/src/create.jl
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@
errors = non_positive_allocation_network_id(graph)

if errors
error("Allocation network initialization failed.")

Check warning on line 205 in core/src/create.jl

View check run for this annotation

Codecov / codecov/patch

core/src/create.jl#L205

Added line #L205 was not covered by tests
end

for allocation_network_id in keys(graph[].node_ids)
Expand Down Expand Up @@ -485,10 +485,10 @@
current_area = DiffCache(current_area, chunk_sizes)
end

precipitation = fill(NaN, length(node_id))
potential_evaporation = fill(NaN, length(node_id))
drainage = fill(NaN, length(node_id))
infiltration = fill(NaN, length(node_id))
precipitation = zeros(length(node_id))
potential_evaporation = zeros(length(node_id))
drainage = zeros(length(node_id))
infiltration = zeros(length(node_id))
table = (; precipitation, potential_evaporation, drainage, infiltration)

area, level, storage = create_storage_tables(db, config)
Expand Down
6 changes: 3 additions & 3 deletions core/src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
end

if incomplete_subnetwork(graph, node_ids)
error("Incomplete connectivity in subnetwork")

Check warning on line 105 in core/src/utils.jl

View check run for this annotation

Codecov / codecov/patch

core/src/utils.jl#L105

Added line #L105 was not covered by tests
end

flow = zeros(flow_counter)
Expand Down Expand Up @@ -625,12 +625,12 @@
Update `table` at row index `i`, with the values of a given row.
`table` must be a NamedTuple of vectors with all variables that must be loaded.
The row must contain all the column names that are present in the table.
If a value is NaN, it is not set.
If a value is missing, it is not set.
"""
function set_table_row!(table::NamedTuple, row, i::Int)::NamedTuple
for (symbol, vector) in pairs(table)
val = getproperty(row, symbol)
if !isnan(val)
if !ismissing(val)
vector[i] = val
end
end
Expand Down Expand Up @@ -672,7 +672,7 @@
for (i, id) in enumerate(node_id)
for (symbol, vector) in pairs(table)
idx = findlast(
row -> row.node_id == id && !isnan(getproperty(row, symbol)),
row -> row.node_id == id && !ismissing(getproperty(row, symbol)),
pre_table,
)
if idx !== nothing
Expand Down
10 changes: 5 additions & 5 deletions core/src/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,11 @@ end
@version BasinTimeV1 begin
node_id::Int
time::DateTime
drainage::Float64
potential_evaporation::Float64
infiltration::Float64
precipitation::Float64
urban_runoff::Float64
drainage::Union{Missing, Float64}
potential_evaporation::Union{Missing, Float64}
infiltration::Union{Missing, Float64}
precipitation::Union{Missing, Float64}
urban_runoff::Union{Missing, Float64}
end

@version BasinProfileV1 begin
Expand Down
36 changes: 36 additions & 0 deletions core/test/run_models_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,42 @@ end
@test successful_retcode(model)
end

@testitem "leaky bucket model" begin
using SciMLBase: successful_retcode
import BasicModelInterface as BMI

toml_path = normpath(@__DIR__, "../../generated_testmodels/leaky_bucket/ribasim.toml")
@test ispath(toml_path)
model = Ribasim.Model(toml_path)
@test model isa Ribasim.Model

stor = model.integrator.u.storage
prec = model.integrator.p.basin.precipitation
evap = model.integrator.p.basin.potential_evaporation
drng = model.integrator.p.basin.drainage
infl = model.integrator.p.basin.infiltration
# The dynamic data has missings, but these are not set.
@test prec == [0.0]
@test evap == [0.0]
@test drng == [0.003]
@test infl == [0.0]
init_stor = 1000.0
@test stor == [init_stor]
BMI.update_until(model, 1.5 * 86400)
@test prec == [0.0]
@test evap == [0.0]
@test drng == [0.003]
@test infl == [0.001]
stor ≈ Float32[init_stor + 86400 * (0.003 * 1.5 - 0.001 * 0.5)]
BMI.update_until(model, 2.5 * 86400)
@test prec == [0.00]
@test evap == [0.0]
@test drng == [0.001]
@test infl == [0.002]
stor ≈ Float32[init_stor + 86400 * (0.003 * 2.0 + 0.001 * 0.5 - 0.001 - 0.002 * 0.5)]
@test successful_retcode(Ribasim.solve!(model))
end

@testitem "basic model" begin
using Logging: Debug, with_logger
using LoggingExtras
Expand Down
62 changes: 46 additions & 16 deletions docs/schema/BasinTime.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,59 @@
"type": "string"
},
"drainage": {
"format": "double",
"type": "number"
"format": "default",
"anyOf": [
{
"type": "null"
},
{
"type": "number"
}
]
},
"potential_evaporation": {
"format": "double",
"type": "number"
"format": "default",
"anyOf": [
{
"type": "null"
},
{
"type": "number"
}
]
},
"infiltration": {
"format": "double",
"type": "number"
"format": "default",
"anyOf": [
{
"type": "null"
},
{
"type": "number"
}
]
},
"precipitation": {
"format": "double",
"type": "number"
"format": "default",
"anyOf": [
{
"type": "null"
},
{
"type": "number"
}
]
},
"urban_runoff": {
"format": "double",
"type": "number"
"format": "default",
"anyOf": [
{
"type": "null"
},
{
"type": "number"
}
]
},
"remarks": {
"description": "a hack for pandera",
Expand All @@ -42,11 +77,6 @@
},
"required": [
"node_id",
"time",
"drainage",
"potential_evaporation",
"infiltration",
"precipitation",
"urban_runoff"
"time"
]
}
10 changes: 5 additions & 5 deletions python/ribasim/ribasim/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ class BasinSubgrid(BaseModel):
class BasinTime(BaseModel):
node_id: int
time: datetime
drainage: float
potential_evaporation: float
infiltration: float
precipitation: float
urban_runoff: float
drainage: float | None = None
potential_evaporation: float | None = None
infiltration: float | None = None
precipitation: float | None = None
urban_runoff: float | None = None
remarks: str = Field("", description="a hack for pandera")


Expand Down
43 changes: 22 additions & 21 deletions python/ribasim_testmodels/ribasim_testmodels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
outlet_model,
tabulated_rating_curve_model,
)
from ribasim_testmodels.bucket import bucket_model
from ribasim_testmodels.bucket import bucket_model, leaky_bucket_model
from ribasim_testmodels.discrete_control import (
flow_condition_model,
level_boundary_condition_model,
Expand Down Expand Up @@ -54,37 +54,38 @@
__all__ = [
"allocation_example_model",
"backwater_model",
"basic_model",
"basic_arrow_model",
"basic_model",
"basic_transient_model",
"bucket_model",
"pump_discrete_control_model",
"flow_condition_model",
"tabulated_rating_curve_model",
"trivial_model",
"linear_resistance_model",
"rating_curve_model",
"manning_resistance_model",
"pid_control_model",
"misc_nodes_model",
"tabulated_rating_curve_control_model",
"discrete_control_of_pid_control_model",
"dutch_waterways_model",
"invalid_qh_model",
"flow_boundary_time_model",
"pid_control_equation_model",
"invalid_fractional_flow_model",
"flow_condition_model",
"fractional_flow_subnetwork_model",
"invalid_discrete_control_model",
"level_setpoint_with_minmax_model",
"invalid_edge_types_model",
"discrete_control_of_pid_control_model",
"invalid_fractional_flow_model",
"invalid_qh_model",
"leaky_bucket_model",
"level_boundary_condition_model",
"level_setpoint_with_minmax_model",
"linear_resistance_model",
"looped_subnetwork_model",
"manning_resistance_model",
"minimal_subnetwork_model",
"misc_nodes_model",
"outlet_model",
"user_model",
"pid_control_equation_model",
"pid_control_model",
"pump_discrete_control_model",
"rating_curve_model",
"subnetwork_model",
"minimal_subnetwork_model",
"fractional_flow_subnetwork_model",
"looped_subnetwork_model",
"tabulated_rating_curve_control_model",
"tabulated_rating_curve_model",
"trivial_model",
"two_basin_model",
"user_model",
]

# provide a mapping from model name to its constructor, so we can iterate over all models
Expand Down
75 changes: 75 additions & 0 deletions python/ribasim_testmodels/ribasim_testmodels/bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,78 @@ def bucket_model() -> ribasim.Model:
endtime="2021-01-01 00:00:00",
)
return model


def leaky_bucket_model() -> ribasim.Model:
"""Bucket model with dynamic forcing with missings."""

# Set up the nodes:
xy = np.array(
[
(400.0, 200.0), # Basin
]
)
node_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])
node_type = ["Basin"]
# Make sure the feature id starts at 1: explicitly give an index.
node = ribasim.Node(
df=gpd.GeoDataFrame(
data={"type": node_type},
index=pd.Index(np.arange(len(xy)) + 1, name="fid"),
geometry=node_xy,
crs="EPSG:28992",
)
)

# Setup the dummy edges:
from_id = np.array([], dtype=np.int64)
to_id = np.array([], dtype=np.int64)
lines = node.geometry_from_connectivity(from_id.tolist(), to_id.tolist())
edge = ribasim.Edge(
df=gpd.GeoDataFrame(
data={
"from_node_id": from_id,
"to_node_id": to_id,
"edge_type": len(from_id) * ["flow"],
},
geometry=lines,
crs="EPSG:28992",
)
)

# Setup the basins:
profile = pd.DataFrame(
data={
"node_id": [1, 1],
"area": [1000.0, 1000.0],
"level": [0.0, 1.0],
}
)

state = pd.DataFrame(
data={
"node_id": [1],
"level": [1.0],
}
)

time = pd.DataFrame(
data={
"time": pd.date_range("2020-01-01", "2020-01-05"),
"node_id": 1,
"drainage": [0.003, np.nan, 0.001, 0.002, 0.0],
"potential_evaporation": np.nan,
"infiltration": [np.nan, 0.001, 0.002, 0.0, 0.0],
"precipitation": np.nan,
"urban_runoff": 0.0,
}
)
basin = ribasim.Basin(profile=profile, time=time, state=state)

model = ribasim.Model(
network=ribasim.Network(node=node, edge=edge),
basin=basin,
starttime="2020-01-01 00:00:00",
endtime="2020-01-05 00:00:00",
)
return model
Loading