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

Feature/add reservoir node #256

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions pywr-core/src/metric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pub enum MetricF64 {
NodeInFlow(NodeIndex),
NodeOutFlow(NodeIndex),
NodeVolume(NodeIndex),
NodeMaxVolume(NodeIndex),
AggregatedNodeInFlow(AggregatedNodeIndex),
AggregatedNodeOutFlow(AggregatedNodeIndex),
AggregatedNodeVolume(AggregatedStorageNodeIndex),
Expand All @@ -103,6 +104,7 @@ impl MetricF64 {
MetricF64::NodeInFlow(idx) => Ok(state.get_network_state().get_node_in_flow(idx)?),
MetricF64::NodeOutFlow(idx) => Ok(state.get_network_state().get_node_out_flow(idx)?),
MetricF64::NodeVolume(idx) => Ok(state.get_network_state().get_node_volume(idx)?),
MetricF64::NodeMaxVolume(idx) => Ok(model.get_node(idx)?.get_current_max_volume(state)?),
MetricF64::AggregatedNodeInFlow(idx) => {
let node = model.get_aggregated_node(idx)?;
node.iter_nodes()
Expand Down
4 changes: 2 additions & 2 deletions pywr-core/src/models/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ impl Model {
&mut self.network
}

/// Check whether a solver [`S`] has the required features to run this model.
/// Check whether a solver `S` has the required features to run this model.
pub fn check_solver_features<S>(&self) -> bool
where
S: Solver,
{
self.network.check_solver_features::<S>()
}

/// Check whether a solver [`S`] has the required features to run this model.
/// Check whether a solver `S` has the required features to run this model.
pub fn check_multi_scenario_solver_features<S>(&self) -> bool
where
S: MultiStateSolver,
Expand Down
15 changes: 8 additions & 7 deletions pywr-core/src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ impl Network {
pub fn nodes(&self) -> &NodeVec {
&self.nodes
}

pub fn edges(&self) -> &EdgeVec {
&self.edges
}
Expand Down Expand Up @@ -285,7 +286,7 @@ impl Network {
Ok(recorder_internal_states)
}

/// Check whether a solver [`S`] has the required features to run this network.
/// Check whether a solver `S` has the required features to run this network.
pub fn check_solver_features<S>(&self) -> bool
where
S: Solver,
Expand All @@ -295,7 +296,7 @@ impl Network {
required_features.iter().all(|f| S::features().contains(f))
}

/// Check whether a solver [`S`] has the required features to run this network.
/// Check whether a solver `S` has the required features to run this network.
pub fn check_multi_scenario_solver_features<S>(&self) -> bool
where
S: MultiStateSolver,
Expand Down Expand Up @@ -1140,7 +1141,7 @@ impl Network {
}
}

/// Get a [`Parameter<usize>`] from its index.
/// Get a `Parameter<usize>` from its index.
pub fn get_index_parameter(&self, index: ParameterIndex<usize>) -> Result<&dyn parameters::Parameter, PywrError> {
match self.parameters.get_usize(index) {
Some(p) => Ok(p),
Expand Down Expand Up @@ -1462,7 +1463,7 @@ impl Network {
Ok(edge_index)
}

/// Set the variable values on the parameter [`parameter_index`].
/// Set the variable values on the parameter `parameter_index`.
///
/// This will update the internal state of the parameter with the new values for all scenarios.
pub fn set_f64_parameter_variable_values(
Expand Down Expand Up @@ -1492,7 +1493,7 @@ impl Network {
}
}

/// Set the variable values on the parameter [`parameter_index`] and scenario [`scenario_index`].
/// Set the variable values on the parameter `parameter_index` and scenario `scenario_index`.
///
/// Only the internal state of the parameter for the given scenario will be updated.
pub fn set_f64_parameter_variable_values_for_scenario(
Expand Down Expand Up @@ -1568,7 +1569,7 @@ impl Network {
}
}

/// Set the variable values on the parameter [`parameter_index`].
/// Set the variable values on the parameter `parameter_index`.
///
/// This will update the internal state of the parameter with the new values for scenarios.
pub fn set_u32_parameter_variable_values(
Expand Down Expand Up @@ -1598,7 +1599,7 @@ impl Network {
}
}

/// Set the variable values on the parameter [`parameter_index`] and scenario [`scenario_index`].
/// Set the variable values on the parameter `parameter_index` and scenario `scenario_index`.
///
/// Only the internal state of the parameter for the given scenario will be updated.
pub fn set_u32_parameter_variable_values_for_scenario(
Expand Down
4 changes: 2 additions & 2 deletions pywr-core/src/recorders/csv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::num::NonZeroU32;
use std::ops::Deref;
use std::path::PathBuf;

/// Output the values from a [`MetricSet`] to a CSV file.
/// Output the values from a [`crate::recorders::MetricSet`] to a CSV file.
#[derive(Clone, Debug)]
pub struct CsvWideFmtOutput {
meta: RecorderMeta,
Expand Down Expand Up @@ -196,7 +196,7 @@ pub struct CsvLongFmtRecord {
value: f64,
}

/// Output the values from a several [`MetricSet`]s to a CSV file in long format.
/// Output the values from a several [`crate::recorders::MetricSet`]s to a CSV file in long format.
///
/// The long format contains a row for each value produced by the metric set. This is useful
/// for analysis in tools like R or Python which can easily read long format data.
Expand Down
2 changes: 1 addition & 1 deletion pywr-core/src/timestep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl PywrDuration {
}

/// Convert the duration to a string representation that can be parsed by polars
/// see: https://docs.rs/polars/latest/polars/prelude/struct.Duration.html#method.parse
/// see: <https://docs.rs/polars/latest/polars/prelude/struct.Duration.html#method.parse>
pub fn duration_string(&self) -> String {
let milliseconds = self.milliseconds();
let mut duration = String::new();
Expand Down
2 changes: 2 additions & 0 deletions pywr-schema/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ pub enum SchemaError {
OutOfRange(#[from] chrono::OutOfRange),
#[error("The metric set with name '{0}' contains no metrics")]
EmptyMetricSet(String),
#[error("Missing the following attribute {attr:?} on node {name:?}.")]
MissingNodeAttribute { attr: String, name: String },
}

#[cfg(feature = "core")]
Expand Down
3 changes: 2 additions & 1 deletion pywr-schema/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,10 +459,11 @@ impl PywrNetwork {
.ok_or_else(|| SchemaError::NodeNotFound(edge.to_node.clone()))?;

let from_slot = edge.from_slot.as_deref();
let to_slot = edge.to_slot.as_deref();

// Connect each "from" connector to each "to" connector
for from_connector in from_node.output_connectors(from_slot) {
for to_connector in to_node.input_connectors() {
for to_connector in to_node.input_connectors(to_slot) {
let from_node_index =
network.get_node_index_by_name(from_connector.0, from_connector.1.as_deref())?;
let to_node_index = network.get_node_index_by_name(to_connector.0, to_connector.1.as_deref())?;
Expand Down
3 changes: 2 additions & 1 deletion pywr-schema/src/nodes/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,7 @@ impl StorageNode {

let metric = match attr {
NodeAttribute::Volume => MetricF64::NodeVolume(idx),
NodeAttribute::MaxVolume => MetricF64::NodeMaxVolume(idx),
NodeAttribute::ProportionalVolume => {
let dm = DerivedMetric::NodeProportionalVolume(idx);
let derived_metric_idx = network.add_derived_metric(dm);
Expand Down Expand Up @@ -931,7 +932,7 @@ impl TryFrom<ReservoirNodeV1> for StorageNode {
#[doc = svgbobdoc::transform!(
/// This is used to represent a catchment inflow.
///
/// Catchment nodes create a single [`crate::node::InputNode`] node in the network, but
/// Catchment nodes create a single [`InputNode`] node in the network, but
/// ensure that the maximum and minimum flow are equal to [`Self::flow`].
///
/// ```svgbob
Expand Down
4 changes: 2 additions & 2 deletions pywr-schema/src/nodes/delay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ use schemars::JsonSchema;
///
/// This is often useful in long river reaches as a simply way to model time-of-travel. Internally
/// an `Output` node is used to terminate flows entering the node and an `Input` node is created
/// with flow constraints set by a [DelayParameter]. These constraints set the minimum and
/// with flow constraints set by a [`pywr_core::parameters::DelayParameter`]. These constraints set the minimum and
/// maximum flow on the `Input` node equal to the flow reaching the `Output` node N time-steps
/// ago. The internally created [DelayParameter] is created with this node's name and the suffix
/// ago. The internally created [`pywr_core::parameters::DelayParameter`] is created with this node's name and the suffix
/// "-delay".
///
///
Expand Down
28 changes: 27 additions & 1 deletion pywr-schema/src/nodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod loss_link;
mod monthly_virtual_storage;
mod piecewise_link;
mod piecewise_storage;
mod reservoir;
mod river;
mod river_gauge;
mod river_split_with_gauge;
Expand All @@ -20,6 +21,10 @@ use crate::metric::Metric;
#[cfg(feature = "core")]
use crate::model::LoadArgs;
use crate::model::PywrNetwork;
pub use crate::nodes::reservoir::{
Bathymetry, BathymetryType, Evaporation, Leakage, Rainfall, ReservoirNode, SpillNodeType,
};

use crate::parameters::TimeseriesV1Data;
use crate::visit::{VisitMetrics, VisitPaths};
pub use annual_virtual_storage::{AnnualReset, AnnualVirtualStorageNode};
Expand Down Expand Up @@ -96,10 +101,17 @@ pub enum NodeAttribute {
Inflow,
Outflow,
Volume,
MaxVolume,
ProportionalVolume,
Loss,
Deficit,
Power,
/// The compensation flow out of a [`ReservoirNode`]
Compensation,
/// The rainfall volume into a [`ReservoirNode`]
Rainfall,
/// The evaporation volume out of a [`ReservoirNode`]
Evaporation,
}

pub struct NodeBuilder {
Expand Down Expand Up @@ -233,6 +245,7 @@ impl NodeBuilder {
meta,
..Default::default()
}),
NodeType::Reservoir => Node::Reservoir(ReservoirNode { ..Default::default() }),
}
}
}
Expand Down Expand Up @@ -263,6 +276,7 @@ pub enum Node {
MonthlyVirtualStorage(MonthlyVirtualStorageNode),
RollingVirtualStorage(RollingVirtualStorageNode),
Turbine(TurbineNode),
Reservoir(ReservoirNode),
}

impl Node {
Expand Down Expand Up @@ -301,10 +315,11 @@ impl Node {
Node::MonthlyVirtualStorage(n) => &n.meta,
Node::RollingVirtualStorage(n) => &n.meta,
Node::Turbine(n) => &n.meta,
Node::Reservoir(n) => n.meta(),
}
}

pub fn input_connectors(&self) -> Vec<(&str, Option<String>)> {
pub fn input_connectors(&self, slot: Option<&str>) -> Vec<(&str, Option<String>)> {
match self {
Node::Input(n) => n.input_connectors(),
Node::Link(n) => n.input_connectors(),
Expand All @@ -327,6 +342,7 @@ impl Node {
Node::Delay(n) => n.input_connectors(),
Node::RollingVirtualStorage(n) => n.input_connectors(),
Node::Turbine(n) => n.input_connectors(),
Node::Reservoir(n) => n.input_connectors(slot),
}
}

Expand All @@ -353,6 +369,7 @@ impl Node {
Node::Delay(n) => n.output_connectors(),
Node::RollingVirtualStorage(n) => n.output_connectors(),
Node::Turbine(n) => n.output_connectors(),
Node::Reservoir(n) => n.output_connectors(slot),
}
}

Expand All @@ -378,6 +395,7 @@ impl Node {
Node::Delay(n) => n.default_metric(),
Node::RollingVirtualStorage(n) => n.default_metric(),
Node::Turbine(n) => n.default_metric(),
Node::Reservoir(n) => n.default_metric(),
}
}
}
Expand Down Expand Up @@ -406,6 +424,7 @@ impl Node {
Node::Turbine(n) => n.add_to_model(network, args),
Node::MonthlyVirtualStorage(n) => n.add_to_model(network, args),
Node::RollingVirtualStorage(n) => n.add_to_model(network, args),
Node::Reservoir(n) => n.add_to_model(network),
}
}

Expand Down Expand Up @@ -436,6 +455,7 @@ impl Node {
Node::Turbine(n) => n.node_indices_for_constraints(network),
Node::MonthlyVirtualStorage(n) => n.node_indices_for_constraints(network, args),
Node::RollingVirtualStorage(n) => n.node_indices_for_constraints(network, args),
Node::Reservoir(n) => n.node_indices_for_constraints(network),
}
}

Expand Down Expand Up @@ -463,6 +483,7 @@ impl Node {
Node::PiecewiseStorage(n) => n.set_constraints(network, args),
Node::Delay(n) => n.set_constraints(network, args),
Node::Turbine(n) => n.set_constraints(network, args),
Node::Reservoir(n) => n.set_constraints(network, args),
Node::MonthlyVirtualStorage(_) => Ok(()), // TODO
Node::RollingVirtualStorage(_) => Ok(()), // TODO
}
Expand Down Expand Up @@ -496,6 +517,7 @@ impl Node {
Node::Delay(n) => n.create_metric(network, attribute),
Node::RollingVirtualStorage(n) => n.create_metric(network, attribute),
Node::Turbine(n) => n.create_metric(network, attribute, args),
Node::Reservoir(n) => n.create_metric(network, attribute),
}
}
}
Expand Down Expand Up @@ -573,6 +595,7 @@ impl VisitMetrics for Node {
Node::MonthlyVirtualStorage(n) => n.visit_metrics(visitor),
Node::RollingVirtualStorage(n) => n.visit_metrics(visitor),
Node::Turbine(n) => n.visit_metrics(visitor),
Node::Reservoir(n) => n.visit_metrics(visitor),
}
}

Expand All @@ -598,6 +621,7 @@ impl VisitMetrics for Node {
Node::MonthlyVirtualStorage(n) => n.visit_metrics_mut(visitor),
Node::RollingVirtualStorage(n) => n.visit_metrics_mut(visitor),
Node::Turbine(n) => n.visit_metrics_mut(visitor),
Node::Reservoir(n) => n.visit_metrics_mut(visitor),
}
}
}
Expand Down Expand Up @@ -625,6 +649,7 @@ impl VisitPaths for Node {
Node::MonthlyVirtualStorage(n) => n.visit_paths(visitor),
Node::RollingVirtualStorage(n) => n.visit_paths(visitor),
Node::Turbine(n) => n.visit_paths(visitor),
Node::Reservoir(n) => n.visit_paths(visitor),
}
}

Expand All @@ -650,6 +675,7 @@ impl VisitPaths for Node {
Node::MonthlyVirtualStorage(n) => n.visit_paths_mut(visitor),
Node::RollingVirtualStorage(n) => n.visit_paths_mut(visitor),
Node::Turbine(n) => n.visit_paths_mut(visitor),
Node::Reservoir(n) => n.visit_paths_mut(visitor),
}
}
}
Expand Down
Loading