Ribasim icon indicating copy to clipboard operation
Ribasim copied to clipboard

Cannot run model without any Basins

Open Fati-Mon opened this issue 1 year ago • 5 comments

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. image 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)

Fati-Mon avatar Jul 08 '24 10:07 Fati-Mon

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

visr avatar Jul 08 '24 10:07 visr

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.

visr avatar Jul 15 '24 14:07 visr

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?

Fati-Mon avatar Jul 19 '24 07:07 Fati-Mon

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.

visr avatar Jul 23 '24 14:07 visr

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.

visr avatar Mar 31 '25 11:03 visr

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)

simulutions avatar Jun 05 '25 13:06 simulutions

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.

visr avatar Jun 06 '25 08:06 visr

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?

simulutions avatar Jun 06 '25 09:06 simulutions

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.

visr avatar Jun 06 '25 09:06 visr