Skip to content

Commit

Permalink
Merge branch 'main' into doc-allocation
Browse files Browse the repository at this point in the history
  • Loading branch information
SouthEndMusic committed Jul 23, 2024
2 parents ecaca8a + 4110f4e commit 5d7e7e0
Show file tree
Hide file tree
Showing 14 changed files with 187 additions and 73 deletions.
4 changes: 0 additions & 4 deletions .teamcity/Ribasim/buildTypes/Windows_1.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ object Windows_1 : Template({
name = "Ribasim_Windows"
description = "Template for agent that uses Windows OS"

params {
param("env.JULIA_SSL_CA_ROOTS_PATH", "")
}

vcs {
cleanCheckout = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ object Windows_TestRibasimBinaries : BuildType({
id = "RUNNER_1503"
workingDir = "ribasim"
scriptContent = """
pixi run install
pixi run install-ci
pixi run test-ribasim-api
pixi run test-ribasim-cli
""".trimIndent()
Expand Down
17 changes: 0 additions & 17 deletions .teamcity/patches/buildTypes/GenerateTestmodels.kts

This file was deleted.

This file was deleted.

6 changes: 5 additions & 1 deletion core/src/allocation_optim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,11 @@ function get_basin_capacity(
return 0.0
else
level_max = level_demand.max_level[level_demand_idx](t)
storage_max = get_storage_from_level(p.basin, basin_idx, level_max)
if isinf(level_max)
storage_max = Inf
else
storage_max = get_storage_from_level(p.basin, basin_idx, level_max)
end
return max(0.0, (storage_basin - storage_max) / Δt_allocation + influx)
end
end
Expand Down
20 changes: 20 additions & 0 deletions core/test/allocation_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -550,3 +550,23 @@ end
@test all(isapprox.(fractions[1], fractions[3], atol = 1e-4))
@test all(isapprox.(fractions[1], fractions[4], atol = 1e-4))
end

@testitem "level_demand_without_max_level" begin
using Ribasim: NodeID, get_basin_capacity, outflow_id
using JuMP

toml_path = normpath(@__DIR__, "../../generated_testmodels/level_demand/ribasim.toml")
@test ispath(toml_path)
model = Ribasim.Model(toml_path)
(; p, u, t) = model.integrator
(; allocation_models) = p.allocation
(; basin, level_demand, graph) = p

fill!(level_demand.max_level[1].u, Inf)
fill!(level_demand.max_level[2].u, Inf)

# Given a max_level of Inf, the basin capacity is 0.0 because it is not possible for the basin level to be > Inf
@test Ribasim.get_basin_capacity(allocation_models[1], u, p, t, basin.node_id[1]) == 0.0
@test Ribasim.get_basin_capacity(allocation_models[1], u, p, t, basin.node_id[2]) == 0.0
@test Ribasim.get_basin_capacity(allocation_models[1], u, p, t, basin.node_id[3]) == 0.0
end
14 changes: 10 additions & 4 deletions docs/install.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,20 @@ pip install ribasim
Ribasim is typically used as a command-line interface (CLI). It is distributed as a `.zip`
archive, that must be downloaded and unpacked. It can be placed anywhere, however it is
important that the contents of the zip file are kept together in a directory. The Ribasim
CLI executable is in the `bin` directory.
executable is in the main folder.

To check whether the installation was performed successfully, open a terminal and go to the path where the executable is for example 'C:\Ribasim\ribasim_windows'.
If you are using cmd.exe type `ribasim`, or for PowerShell `./ribasim`.

To check whether the installation was performed successfully, run `ribasim` with no
arguments in the command line.
This will give the following message:

```
Usage: ribasim 'path/to/model/ribasim.toml'
error: the following required arguments were not provided:
<TOML_PATH>
Usage: ribasim <TOML_PATH>
For more information, try '--help'.'
```

# Ribasim Python
Expand Down
2 changes: 1 addition & 1 deletion pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ lint = { depends_on = [
build = { "cmd" = "julia --project build.jl", cwd = "build", depends_on = [
"generate-testmodels",
"initialize-julia",
] }
], env = {JULIA_SSL_CA_ROOTS_PATH = ""} }
remove-artifacts = "julia --eval 'rm(joinpath(Base.DEPOT_PATH[1], \"artifacts\"), force=true, recursive=true)'"
# Tests
test-ribasim-cli = "pytest --numprocesses=4 --basetemp=build/tests/temp --junitxml=report.xml build/tests"
Expand Down
82 changes: 82 additions & 0 deletions python/ribasim/ribasim/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@


class Allocation(ChildModel):
"""
Defines the allocation optimization algorithm options.
Attributes
----------
timestep : float
The simulated time in seconds between successive allocation calls (Optional, defaults to 86400)
use_allocation : bool
Whether the allocation algorithm should be active. If not, `UserDemand` nodes attempt to
abstract their full demand (Optional, defaults to False)
"""

timestep: float = 86400.0
use_allocation: bool = False

Expand All @@ -64,6 +76,38 @@ class Results(ChildModel):


class Solver(ChildModel):
"""
Defines the numerical solver options.
For more details see <https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/#solver_options>.
Attributes
----------
algorithm : str
The used numerical time integration algorithm (Optional, defaults to QNDF)
saveat : float
Time interval in seconds between saves of output data.
0 saves every timestep, inf only saves at start- and endtime. (Optional, defaults to 86400)
dt : float
Timestep of the solver. (Optional, defaults to None which implies adaptive timestepping)
dtmin : float
The minimum allowed timestep of the solver (Optional, defaults to 0.0)
dtmax : float
The maximum allowed timestep size (Optional, defaults to 0.0 which implies the total length of the simulation)
force_dtmin : bool
If a smaller dt than dtmin is needed to meet the set error tolerances, the simulation stops, unless force_dtmin = true
(Optional, defaults to False)
abstol : float
The absolute tolerance for adaptive timestepping (Optional, defaults to 1e-6)
reltol : float
The relative tolerance for adaptive timestepping (Optional, defaults to 1e-5)
maxiters : int
The total number of linear iterations over the whole simulation. (Defaults to 1e9, only needs to be increased for extremely long simulations)
sparse : bool
Whether a sparse Jacobian matrix is used, which gives a significant speedup for models with >~10 basins.
autodiff : bool
Whether automatic differentiation instead of fine difference is used to compute the Jacobian. (Optional, defaults to true)
"""

algorithm: str = "QNDF"
saveat: float = 86400.0
dt: float | None = None
Expand All @@ -85,11 +129,37 @@ class Verbosity(str, Enum):


class Logging(ChildModel):
"""
Defines the logging behavior of the core.
Attributes
----------
verbosity : Verbosity
The verbosity of the logging: debug/info/warn/error (Optional, defaults to info)
timing : Bool
Enable timings (Optional, defaults to False)
"""

verbosity: Verbosity = Verbosity.info
timing: bool = False


class Node(pydantic.BaseModel):
"""
Defines a node for the model.
Attributes
----------
node_id : NonNegativeInt
Integer ID of the node. Must be unique within the same node type.
geometry : shapely.geometry.Point
The coordinates of the node.
name : str
An optional name of the node.
subnetwork_id : int
Optionally adds this node to a subnetwork, which is input for the allocation algorithm.
"""

node_id: NonNegativeInt
geometry: Point
name: str = ""
Expand Down Expand Up @@ -124,6 +194,18 @@ def filter(self) -> "MultiNodeModel":
return self

def add(self, node: Node, tables: Sequence[TableModel[Any]] | None = None) -> None:
"""Add a node and the associated data to the model.
Parameters
----------
node : Ribasim.Node
tables : Sequence[TableModel[Any]] | None
Raises
------
ValueError
When the given node ID already exists for this node type
"""
if tables is None:
tables = []

Expand Down
18 changes: 18 additions & 0 deletions python/ribasim/ribasim/geometry/edge.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,24 @@ def add(
subnetwork_id: int | None = None,
**kwargs,
):
"""Add an edge between nodes. The type of the edge (flow or control)
is automatically inferred from the type of the `from_node`.
Parameters
----------
from_node : NodeData
A node indexed by its node ID, e.g. `model.basin[1]`
to_node: NodeData
A node indexed by its node ID, e.g. `model.linear_resistance[1]`
geometry : LineString | MultiLineString | None
The geometry of a line. If not supplied, it creates a straight line between the nodes.
name : str
An optional name for the edge.
subnetwork_id : int | None
An optional subnetwork id for the edge. This edge indicates a source for
the allocation algorithm, and should thus not be set for every edge in a subnetwork.
**kwargs : Dict
"""
geometry_to_append = (
[LineString([from_node.geometry, to_node.geometry])]
if geometry is None
Expand Down
49 changes: 35 additions & 14 deletions python/ribasim/ribasim/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,13 @@ def _save(self, directory: DirectoryPath, input_dir: DirectoryPath):
sub._save(directory, input_dir)

def set_crs(self, crs: str) -> None:
"""Set the coordinate reference system of the data in the model.
Parameters
----------
crs : str
Coordinate reference system, like "EPSG:4326" for WGS84 latitude longitude.
"""
self._apply_crs_function("set_crs", crs)

def to_crs(self, crs: str) -> None:
Expand Down Expand Up @@ -229,18 +236,24 @@ def _children(self):

@classmethod
def read(cls, filepath: str | PathLike[str]) -> "Model":
"""Read model from TOML file."""
"""Read a model from a TOML file.
Parameters
----------
filepath : str | PathLike[str]
The path to the TOML file.
"""
return cls(filepath=filepath) # type: ignore

def write(self, filepath: str | PathLike[str]) -> Path:
"""
Write the contents of the model to disk and save it as a TOML configuration file.
"""Write the contents of the model to disk and save it as a TOML configuration file.
If ``filepath.parent`` does not exist, it is created before writing.
Parameters
----------
filepath: str | PathLike[str] A file path with .toml extension
filepath : str | PathLike[str]
A file path with .toml extension.
"""
# TODO
# self.validate_model()
Expand Down Expand Up @@ -280,6 +293,8 @@ def reset_contextvar(self) -> "Model":
return self

def plot_control_listen(self, ax):
"""Plot the implicit listen edges of the model."""

df_listen_edge = pd.DataFrame(
data={
"control_node_id": pd.Series([], dtype=np.int32),
Expand Down Expand Up @@ -341,17 +356,19 @@ def plot_control_listen(self, ax):
return

def plot(self, ax=None, indicate_subnetworks: bool = True) -> Any:
"""
Plot the nodes, edges and allocation networks of the model.
"""Plot the nodes, edges and allocation networks of the model.
Parameters
----------
ax : matplotlib.pyplot.Artist, optional
ax : matplotlib.pyplot.Artist
Axes on which to draw the plot.
indicate_subnetworks : bool
Whether to indicate subnetworks with a convex hull backdrop.
Returns
-------
ax : matplotlib.pyplot.Artist
Axis on which the plot is drawn.
"""
if ax is None:
_, ax = plt.subplots()
Expand All @@ -377,13 +394,17 @@ def plot(self, ax=None, indicate_subnetworks: bool = True) -> Any:
return ax

def to_xugrid(self, add_flow: bool = False, add_allocation: bool = False):
"""
Convert the network to a `xugrid.UgridDataset`.
To add flow results, set `add_flow=True`.
To add allocation results, set `add_allocation=True`.
Both cannot be added to the same dataset.
This method will throw `ImportError`,
if the optional dependency `xugrid` isn't installed.
"""Convert the network to a `xugrid.UgridDataset`.
Either the flow or the allocation data can be added, but not both simultaneously.
This method will throw `ImportError` if the optional dependency `xugrid` isn't installed.
Parameters
----------
add_flow : bool
add flow results (Optional, defaults to False)
add_allocation : bool
add allocation results (Optional, defaults to False)
"""

if add_flow and add_allocation:
Expand Down
8 changes: 8 additions & 0 deletions python/ribasim/tests/test_io.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime
from pathlib import Path

import numpy as np
import pytest
Expand Down Expand Up @@ -202,3 +203,10 @@ def test_datetime_timezone():
assert isinstance(model.endtime, datetime)
assert model.starttime.tzinfo is None
assert model.endtime.tzinfo is None


def test_minimal_toml():
# Check if the TOML used in QGIS tests is still valid.
toml_path = Path(__file__).parents[3] / "ribasim_qgis/tests/data/simple_valid.toml"
model = ribasim.Model.read(toml_path)
assert model.crs == "EPSG:28992"
Loading

0 comments on commit 5d7e7e0

Please sign in to comment.