Cannot run model without any Basins
What If a user wants to model a simple system with no storage, the following error is shown:
Cell In[2], line 6
subprocess.run([rib_path, toml_path], check=True)
File C:\Ribasim9\.pixi\envs\default\Lib\subprocess.py:571 in run
raise CalledProcessError(retcode, process.args,
CalledProcessError: Command '[WindowsPath('c:/Ribasim9/ribasim_windows/ribasim.exe'), WindowsPath('c:/Ribasim9/data/basic/ribasim.toml')]' returned non-zero exit status 1.
Why
Not clear that a basin node is needed to be able to run the model.
How
Mention in the error that the user needs to add a basin for the model to run or describe that certain nodes cannot be directly linked (in this case flow boundaries directly linked with a terminal)
Thanks for the report. Could you add the Python script you have for writing the model, and the error message that Ribasim gives?
EDIT: The Python script is here: https://github.com/Deltares/Ribasim/issues/1263#issuecomment-2213567653
The error itself comes from here, where area and level are both typed empty arrays Float64[] and the resulting level_to_area is an Any[] which causes dispatch issues.
https://github.com/Deltares/Ribasim/blob/eadc057aec2d1513fe702c122d222755ef64a23d/core/src/read.jl#L603
Not sure how many issues hide behind this one though. If it isn't easy to support, with correct results, we should error early with a clear message.
Even after adding a Basin I get the same problem:
import subprocess # for running the model
import shutil
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from ribasim import Allocation, Model, Node
from ribasim.nodes import (
flow_boundary,
basin,
terminal
)
from shapely.geometry import Point
base_dir = Path("c:/Ribasim9")
model_dir = base_dir / "Virgin"
flows_path = model_dir / "input/ACTINFLW.csv"
starttime = "2023-01-01"
endtime = "2024-01-01"
model = Model(
starttime=starttime,
endtime=endtime,
crs="EPSG:4326",
)
#%% FLOW BOUNDARY
# FLOW BOUNDARY
flows = pd.read_csv(flows_path, sep=";")
model.flow_boundary.add(
Node(1, Point(0.0, 0.0), name='Main'),
[flow_boundary.Time(time=flows.time, flow_rate=flows.main, #name="Main"
)]
)
model.flow_boundary.add(
Node(2, Point(-3.0, 0.0), name='Minor'),
[flow_boundary.Time(time=flows.time, flow_rate=flows.minor, #name="Main"
)]
)
# BASIN (confluence)
model.basin.add(
Node(3, Point(-1.5, -1), name='Conf'),
[
basin.Profile(area=[1000.0, 1000.0], level=[0.0, 1.0]),
basin.State(level=[20.0]),
basin.Time(time=[starttime, endtime], precipitation=[0.0, 3e-6]),
],
)
# TERMINAL
model.terminal.add(Node(4, Point(-1.5, -3.0), name="Terminal"))
# EDGES
model.edge.add(model.flow_boundary[1], model.basin[3])
model.edge.add(model.flow_boundary[2], model.basin[3])
model.edge.add(model.basin[3], model.terminal[4])
# SCHEMATIZATION
model.plot()
datadir = Path("data")
toml_path = datadir / "basic/ribasim.toml"
model.write(toml_path)
rib_path = base_dir / "ribasim_windows/ribasim.exe"
subprocess.run([rib_path, toml_path], check=True)
Is it the way the subprocess run is called?
Is it the way the subprocess run is called?
Indeed, this is improved in #1650.
For the error of https://github.com/Deltares/Ribasim/issues/1615#issuecomment-2228671453, I suggest we look again after #1649 is merged.
Current reproducer and error message:
Python script
# using ribasim v2025.2
from pathlib import Path
from ribasim import Model, Node
from ribasim.nodes import (
flow_boundary,
)
from shapely.geometry import Point
base_dir = Path("c:/Ribasim9")
model_dir = base_dir / "Virgin"
starttime = "2023-01-01"
endtime = "2024-01-01"
model = Model(
starttime=starttime,
endtime=endtime,
crs="EPSG:4326",
)
model.flow_boundary.add(
Node(1, Point(0.0, 0.0), name="Main"),
[
flow_boundary.Static(
flow_rate=[2.0], # name="Main"
)
],
)
model.flow_boundary.add(
Node(2, Point(-3.0, 0.0), name="Minor"),
[
flow_boundary.Static(
flow_rate=[1.0], # name="Main"
)
],
)
model.terminal.add(Node(3, Point(-1.5, -3.0), name="Terminal"))
model.link.add(model.flow_boundary[1], model.terminal[3])
model.link.add(model.flow_boundary[2], model.terminal[3])
model.plot()
datadir = base_dir / "data"
toml_path = datadir / "basic/ribasim.toml"
model.write(toml_path)
Stacktrace
ERROR: MethodError: no method matching Ribasim.Basin(::Vector{Ribasim.NodeID}, ::Vector{Vector{Ribasim.NodeID}}, ::Vector{Vector{Ribasim.NodeID}}, ::Ribasim.VerticalFlux, ::Vector{Float64}, ::Vector{Float64}, ::Vector{Float64}, ::Vector{Float64}, ::Vector{Float64}, ::Vector{Float64}, ::Ribasim.CurrentBasinProperties, ::Vector{Any}, ::Vector{Any}, ::Vector{Float64}, ::Vector{Float64}, ::Ribasim.BasinForcing, ::Vector{Float64}, ::Vector{Float64}, ::Ribasim.ConcentrationData, ::StructArrays.StructVector{Ribasim.BasinConcentrationV1, @NamedTuple{node_id::Vector{Int32}, time::Vector{Dates.DateTime}, substance::Vector{String}, drainage::Vector{Union{Missing, Float64}}, precipitation::Vector{Union{Missing, Float64}}}, Int64})
The type `Ribasim.Basin` exists, but no method is defined for this combination of argument types when trying to construct it.
Closest candidates are:
Ribasim.Basin(::Vector{Ribasim.NodeID}, ::Vector{Vector{Ribasim.NodeID}}, ::Vector{Vector{Ribasim.NodeID}}, ::Ribasim.VerticalFlux, ::Vector{Float64}, ::Vector{Float64}, ::Vector{Float64}, ::Vector{Float64}, ::Vector{Float64}, ::Vector{Float64}, ::Ribasim.CurrentBasinProperties, ::Vector{DataInterpolations.LinearInterpolationIntInv{Vector{Float64}, Vector{Float64}, DataInterpolations.LinearInterpolation{Vector{Float64}, Vector{Float64}, Vector{Float64}, Vector{Float64}, Float64, (1,)}, Float64, (1,)}}, ::Vector{DataInterpolations.LinearInterpolation{Vector{Float64}, Vector{Float64}, Vector{Float64}, Vector{Float64}, Float64, (1,)}}, ::Vector{Float64}, ::Vector{Float64}, ::Ribasim.BasinForcing, ::Vector{Float64}, ::Vector{Float64}, ::CD, ::StructArrays.StructArray{Ribasim.BasinConcentrationV1, 1, D, Int64}) where {CD, D}
@ Ribasim D:\buildAgent\work\ecd2b8f9b25b1609\ribasim\core\src\parameter.jl:461
Ribasim.Basin(; node_id, inflow_ids, outflow_ids, vertical_flux, storage0, Δstorage_prev_saveat, cumulative_precipitation, cumulative_drainage, cumulative_precipitation_saveat, cumulative_drainage_saveat, current_properties, storage_to_level, level_to_area, demand, allocated, forcing, storage_prev, level_prev, concentration_data, concentration_time)
@ Ribasim D:\buildAgent\work\ecd2b8f9b25b1609\ribasim\core\src\parameter.jl:460
Ribasim.Basin(::SQLite.DB, ::Ribasim.config.Config, ::MetaGraphsNext.MetaGraph)
@ Ribasim D:\buildAgent\work\ecd2b8f9b25b1609\ribasim\core\src\read.jl:908
Stacktrace:
[1] Ribasim.Basin(; node_id::Vector{Ribasim.NodeID}, inflow_ids::Vector{Vector{Ribasim.NodeID}}, outflow_ids::Vector{Vector{Ribasim.NodeID}}, vertical_flux::Ribasim.VerticalFlux, storage0::Vector{Float64}, Δstorage_prev_saveat::Vector{Float64}, cumulative_precipitation::Vector{Float64}, cumulative_drainage::Vector{Float64}, cumulative_precipitation_saveat::Vector{Float64}, cumulative_drainage_saveat::Vector{Float64}, current_properties::Ribasim.CurrentBasinProperties, storage_to_level::Vector{Any}, level_to_area::Vector{Any}, demand::Vector{Float64}, allocated::Vector{Float64}, forcing::Ribasim.BasinForcing, storage_prev::Vector{Float64}, level_prev::Vector{Float64}, concentration_data::Ribasim.ConcentrationData, concentration_time::StructArrays.StructVector{Ribasim.BasinConcentrationV1, @NamedTuple{node_id::Vector{Int32}, time::Vector{Dates.DateTime}, substance::Vector{String}, drainage::Vector{Union{Missing, Float64}}, precipitation::Vector{Union{Missing, Float64}}}, Int64})
@ Ribasim D:\buildAgent\work\ecd2b8f9b25b1609\ribasim\core\src\parameter.jl:460
[2] Ribasim.Basin(db::SQLite.DB, config::Ribasim.config.Config, graph::MetaGraphsNext.MetaGraph{Int64, Graphs.SimpleGraphs.SimpleDiGraph{Int64}, Ribasim.NodeID, Ribasim.NodeMetadata, Ribasim.LinkMetadata, @NamedTuple{node_ids::Dict{Int32, Set{Ribasim.NodeID}}, flow_links::Vector{Ribasim.LinkMetadata}, saveat::Float64}, MetaGraphsNext.var"#11#13", Float64})
@ Ribasim D:\buildAgent\work\ecd2b8f9b25b1609\ribasim\core\src\read.jl:969
[3] Ribasim.Parameters(db::SQLite.DB, config::Ribasim.config.Config)
@ Ribasim D:\buildAgent\work\ecd2b8f9b25b1609\ribasim\core\src\read.jl:1806
[4] Ribasim.Model(config::Ribasim.config.Config)
@ Ribasim D:\buildAgent\work\ecd2b8f9b25b1609\ribasim\core\src\model.jl:66
[5] run(config::Ribasim.config.Config)
@ Ribasim D:\buildAgent\work\ecd2b8f9b25b1609\ribasim\core\src\main.jl:11
[6] (::Ribasim.var"#370#372"{IOStream, String, Ribasim.config.Config})()
@ Ribasim D:\buildAgent\work\ecd2b8f9b25b1609\ribasim\core\src\main.jl:46
[7] with_logstate(f::Ribasim.var"#370#372"{IOStream, String, Ribasim.config.Config}, logstate::Base.CoreLogging.LogState)
@ Base.CoreLogging .\logging\logging.jl:522
[8] with_logger
@ .\logging\logging.jl:632 [inlined]
[9] #369
@ D:\buildAgent\work\ecd2b8f9b25b1609\ribasim\core\src\main.jl:34 [inlined]
[10] open(::Ribasim.var"#369#371"{String, Ribasim.config.Config}, ::String, ::Vararg{String}; kwargs::@Kwargs{})
@ Base .\io.jl:410
[11] open
@ .\io.jl:407 [inlined]
[12] main(toml_path::String)
@ Ribasim D:\buildAgent\work\ecd2b8f9b25b1609\ribasim\core\src\main.jl:32
[13] execute(toml_path::Cstring)
@ Ribasim.libribasim D:\buildAgent\work\ecd2b8f9b25b1609\ribasim\core\src\libribasim.jl:190
Also worth noting that Junction (#2175) can help avoid Basins in some scenarios.
Right now, such model crashes in preparing the solver, due to not having any variables to solve:
ERROR: LoadError: ArgumentError: reducing over an empty collection is not allowed; consider supplying init to the reducer
Stacktrace:
[1] _empty_reduce_error()
@ Base ~/.julia/juliaup/julia-1.11.5+0.aarch64.apple.darwin14/share/julia/base/reduce.jl:319
[2] mapreduce_empty(f::Function, op::Function, T::Type)
@ Base ~/.julia/juliaup/julia-1.11.5+0.aarch64.apple.darwin14/share/julia/base/reduce.jl:321
[3] reduce_empty(op::Base.MappingRF{Base.ExtremaMap{typeof(identity)}, typeof(Base._extrema_rf)}, ::Type{Int64})
@ Base ~/.julia/juliaup/julia-1.11.5+0.aarch64.apple.darwin14/share/julia/base/reduce.jl:358
[4] reduce_empty_iter
@ ./reduce.jl:381 [inlined]
[5] mapreduce_empty_iter(f::Function, op::Function, itr::Vector{Int64}, ItrEltype::Base.HasEltype)
@ Base ~/.julia/juliaup/julia-1.11.5+0.aarch64.apple.darwin14/share/julia/base/reduce.jl:377
[6] _mapreduce
@ ./reduce.jl:429 [inlined]
[7] _mapreduce_dim
@ ./reducedim.jl:337 [inlined]
[8] mapreduce
@ ./reducedim.jl:329 [inlined]
[9] _extrema
@ ./reducedim.jl:987 [inlined]
[10] _extrema
@ ./reducedim.jl:986 [inlined]
[11] extrema
@ ./reducedim.jl:982 [inlined]
[12] group_by_color(::Type{Int64}, color::Vector{Int64})
@ SparseMatrixColorings ~/.julia/packages/SparseMatrixColorings/r6kzQ/src/result.jl:85
[13] SparseMatrixColorings.ColumnColoringResult(A::SparseArrays.SparseMatrixCSC{Bool, Int64}, bg::SparseMatrixColorings.BipartiteGraph{Int64}, color::Vector{Int64})
@ SparseMatrixColorings ~/.julia/packages/SparseMatrixColorings/r6kzQ/src/result.jl:167
[14] _coloring(speed_setting::SparseMatrixColorings.WithResult, A::SparseArrays.SparseMatrixCSC{Bool, Int64}, ::SparseMatrixColorings.ColoringProblem{:nonsymmetric, :column}, algo::SparseMatrixColorings.GreedyColoringAlgorithm{:direct, SparseMatrixColorings.NaturalOrder}, decompression_eltype::Type, symmetric_pattern::Bool)
@ SparseMatrixColorings ~/.julia/packages/SparseMatrixColorings/r6kzQ/src/interface.jl:235
[15] #coloring#29
@ ~/.julia/packages/SparseMatrixColorings/r6kzQ/src/interface.jl:191 [inlined]
[16] coloring
@ ~/.julia/packages/SparseMatrixColorings/r6kzQ/src/interface.jl:184 [inlined]
[17] _prepare_sparse_jacobian_aux(::Val{true}, ::DifferentiationInterface.PushforwardFast, ::Ribasim.CArrays.CArray{Float64, 1, Vector{Float64}, @NamedTuple{tabulated_rating_curve::UnitRange{Int64}, pump::UnitRange{Int64}, outlet::UnitRange{Int64}, user_demand_inflow::UnitRange{Int64}, user_demand_outflow::UnitRange{Int64}, linear_resistance::UnitRange{Int64}, manning_resistance::UnitRange{Int64}, evaporation::UnitRange{Int64}, infiltration::UnitRange{Int64}, integral::UnitRange{Int64}}}, ::Tuple{typeof(Ribasim.water_balance!), Ribasim.CArrays.CArray{Float64, 1, Vector{Float64}, @NamedTuple{tabulated_rating_curve::UnitRange{Int64}, pump::UnitRange{Int64}, outlet::UnitRange{Int64}, user_demand_inflow::UnitRange{Int64}, user_demand_outflow::UnitRange{Int64}, linear_resistance::UnitRange{Int64}, manning_resistance::UnitRange{Int64}, evaporation::UnitRange{Int64}, infiltration::UnitRange{Int64}, integral::UnitRange{Int64}}}}, ::ADTypes.AutoSparse{ADTypes.AutoForwardDiff{nothing, Symbol}, SparseConnectivityTracer.TracerSparsityDetector{SparseConnectivityTracer.GradientTracer{SparseConnectivityTracer.IndexSetGradientPattern{Int64, BitSet}}, SparseConnectivityTracer.HessianTracer{SparseConnectivityTracer.DictHessianPattern{Int64, BitSet, Dict{Int64, BitSet}, SparseConnectivityTracer.NotShared}}}, SparseMatrixColorings.GreedyColoringAlgorithm{:direct, SparseMatrixColorings.NaturalOrder}}, ::Ribasim.CArrays.CArray{Float64, 1, Vector{Float64}, @NamedTuple{tabulated_rating_curve::UnitRange{Int64}, pump::UnitRange{Int64}, outlet::UnitRange{Int64}, user_demand_inflow::UnitRange{Int64}, user_demand_outflow::UnitRange{Int64}, linear_resistance::UnitRange{Int64}, manning_resistance::UnitRange{Int64}, evaporation::UnitRange{Int64}, infiltration::UnitRange{Int64}, integral::UnitRange{Int64}}}, ::DifferentiationInterface.Constant{Ribasim.ParametersNonDiff{Ribasim.ConcentrationData, @NamedTuple{node_id::Vector{Int32}, time::Vector{Dates.DateTime}, substance::Vector{String}, drainage::Vector{Union{Missing, Float64}}, precipitation::Vector{Union{Missing, Float64}}}, @NamedTuple{node_id::Vector{Int32}, time::Vector{Dates.DateTime}, substance::Vector{String}, concentration::Vector{Float64}}, @NamedTuple{node_id::Vector{Int32}, time::Vector{Dates.DateTime}, substance::Vector{String}, concentration::Vector{Float64}}, DataInterpolations.SmoothedConstantInterpolation{Vector{Float64}, Vector{Float64}, Vector{Float64}, Vector{Float64}, Vector{Float64}, Float64, Float64}, @NamedTuple{node_id::Vector{Int32}, time::Vector{Dates.DateTime}, substance::Vector{String}, concentration::Vector{Float64}}}}, ::DifferentiationInterface.Cache{@NamedTuple{current_storage::Vector{Float64}, current_low_storage_factor::Vector{Float64}, current_level::Vector{Float64}, current_area::Vector{Float64}, current_cumulative_precipitation::Vector{Float64}, current_cumulative_drainage::Vector{Float64}, current_cumulative_boundary_flow::Vector{Float64}, flow_rate_pump::Vector{Float64}, flow_rate_outlet::Vector{Float64}, error_pid_control::Vector{Float64}}}, ::DifferentiationInterface.Constant{Ribasim.ParametersMutable}, ::DifferentiationInterface.Constant{Float64})
@ DifferentiationInterfaceSparseMatrixColoringsExt ~/.julia/packages/DifferentiationInterface/zJHX8/ext/DifferentiationInterfaceSparseMatrixColoringsExt/jacobian.jl:78
[18] prepare_jacobian_nokwarg
@ ~/.julia/packages/DifferentiationInterface/zJHX8/ext/DifferentiationInterfaceSparseMatrixColoringsExt/jacobian.jl:57 [inlined]
[19] #prepare_jacobian#49
@ ~/.julia/packages/DifferentiationInterface/zJHX8/src/first_order/jacobian.jl:23 [inlined]
[20] get_diff_eval(du::Ribasim.CArrays.CArray{Float64, 1, Vector{Float64}, @NamedTuple{tabulated_rating_curve::UnitRange{Int64}, pump::UnitRange{Int64}, outlet::UnitRange{Int64}, user_demand_inflow::UnitRange{Int64}, user_demand_outflow::UnitRange{Int64}, linear_resistance::UnitRange{Int64}, manning_resistance::UnitRange{Int64}, evaporation::UnitRange{Int64}, infiltration::UnitRange{Int64}, integral::UnitRange{Int64}}}, u::Ribasim.CArrays.CArray{Float64, 1, Vector{Float64}, @NamedTuple{tabulated_rating_curve::UnitRange{Int64}, pump::UnitRange{Int64}, outlet::UnitRange{Int64}, user_demand_inflow::UnitRange{Int64}, user_demand_outflow::UnitRange{Int64}, linear_resistance::UnitRange{Int64}, manning_resistance::UnitRange{Int64}, evaporation::UnitRange{Int64}, infiltration::UnitRange{Int64}, integral::UnitRange{Int64}}}, p::Ribasim.Parameters{Ribasim.ConcentrationData, @NamedTuple{node_id::Vector{Int32}, time::Vector{Dates.DateTime}, substance::Vector{String}, drainage::Vector{Union{Missing, Float64}}, precipitation::Vector{Union{Missing, Float64}}}, @NamedTuple{node_id::Vector{Int32}, time::Vector{Dates.DateTime}, substance::Vector{String}, concentration::Vector{Float64}}, @NamedTuple{node_id::Vector{Int32}, time::Vector{Dates.DateTime}, substance::Vector{String}, concentration::Vector{Float64}}, DataInterpolations.SmoothedConstantInterpolation{Vector{Float64}, Vector{Float64}, Vector{Float64}, Vector{Float64}, Vector{Float64}, Float64, Float64}, @NamedTuple{node_id::Vector{Int32}, time::Vector{Dates.DateTime}, substance::Vector{String}, concentration::Vector{Float64}}, Float64}, solver::Ribasim.config.Solver)
@ Ribasim ~/Code/Ribasim/core/src/model.jl:50
[21] Ribasim.Model(config::Ribasim.config.Config)
@ Ribasim ~/Code/Ribasim/core/src/model.jl:194
[22] run(config::Ribasim.config.Config)
@ Ribasim ~/Code/Ribasim/core/src/main.jl:11
[23] run(config_path::String)
If we cannot get this to work easily it is probably best just log a clear error message to the user and close this issue.
Something like "State vector u0 has size 0, nothing to solve for. Please check that your model has nodes defined.".
However the model does have "nodes". Wat would be the right term here?
How about "Models without states are unsupported, please add a Basin node."
In practice the models without states won't have Basins. I cannot easily think of a model without Basins but with states that is valid.