From 7030a51b72c583d64fb62401db40491f0cdeaff2 Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Tue, 21 May 2024 14:37:25 +0100 Subject: [PATCH 1/7] refactor: Add ParameterCollection to hold all 3 parameter types. --- pywr-core/src/lib.rs | 6 +- pywr-core/src/metric.rs | 2 +- pywr-core/src/network.rs | 159 ++++++++--------------- pywr-core/src/parameters/mod.rs | 135 +++++++++++++++++++ pywr-schema/src/metric.rs | 6 +- pywr-schema/src/nodes/virtual_storage.rs | 2 +- 6 files changed, 195 insertions(+), 115 deletions(-) diff --git a/pywr-core/src/lib.rs b/pywr-core/src/lib.rs index 5cb2e140..0ec21001 100644 --- a/pywr-core/src/lib.rs +++ b/pywr-core/src/lib.rs @@ -49,9 +49,9 @@ pub enum PywrError { ParameterIndexNotFound(ParameterIndex), #[error("index parameter index {0} not found")] IndexParameterIndexNotFound(ParameterIndex), - #[error("multi1 value parameter index {0} not found")] + #[error("multi-value parameter index {0} not found")] MultiValueParameterIndexNotFound(ParameterIndex), - #[error("multi1 value parameter key {0} not found")] + #[error("multi-value parameter key {0} not found")] MultiValueParameterKeyNotFound(String), #[error("inter-network parameter state not initialised")] InterNetworkParameterStateNotInitialised, @@ -77,6 +77,8 @@ pub enum PywrError { ParameterNameAlreadyExists(String, ParameterIndex), #[error("index parameter name `{0}` already exists at index {1}")] IndexParameterNameAlreadyExists(String, ParameterIndex), + #[error("multi-value parameter name `{0}` already exists at index {1}")] + MultiValueParameterNameAlreadyExists(String, ParameterIndex), #[error("metric set name `{0}` already exists")] MetricSetNameAlreadyExists(String), #[error("recorder name `{0}` already exists at index {1}")] diff --git a/pywr-core/src/metric.rs b/pywr-core/src/metric.rs index efb4600e..4c95cc34 100644 --- a/pywr-core/src/metric.rs +++ b/pywr-core/src/metric.rs @@ -104,7 +104,7 @@ impl MetricUsize { pub fn name<'a>(&'a self, network: &'a Network) -> Result<&'a str, PywrError> { match self { - Self::IndexParameterValue(idx) => network.get_index_parameter(idx).map(|p| p.name()), + Self::IndexParameterValue(idx) => network.get_index_parameter(*idx).map(|p| p.name()), Self::Constant(_) => Ok(""), } } diff --git a/pywr-core/src/network.rs b/pywr-core/src/network.rs index 52aa3aa2..4b172e79 100644 --- a/pywr-core/src/network.rs +++ b/pywr-core/src/network.rs @@ -5,7 +5,7 @@ use crate::edge::{Edge, EdgeIndex, EdgeVec}; use crate::metric::MetricF64; use crate::models::ModelDomain; use crate::node::{ConstraintValue, Node, NodeVec, StorageInitialVolume}; -use crate::parameters::{ParameterType, VariableConfig}; +use crate::parameters::{ParameterCollection, ParameterType, VariableConfig}; use crate::recorders::{MetricSet, MetricSetIndex, MetricSetState}; use crate::scenario::ScenarioIndex; use crate::solvers::{MultiStateSolver, Solver, SolverFeatures, SolverTimings}; @@ -200,9 +200,7 @@ pub struct Network { aggregated_nodes: AggregatedNodeVec, aggregated_storage_nodes: AggregatedStorageNodeVec, virtual_storage_nodes: VirtualStorageVec, - parameters: Vec>>, - index_parameters: Vec>>, - multi_parameters: Vec>>, + parameters: ParameterCollection, derived_metrics: Vec, metric_sets: Vec, resolve_order: Vec, @@ -246,24 +244,8 @@ impl Network { let initial_virtual_storage_states = self.virtual_storage_nodes.iter().map(|n| n.default_state()).collect(); - // Get the initial internal state - let initial_values_states = self - .parameters - .iter() - .map(|p| p.setup(timesteps, scenario_index)) - .collect::, _>>()?; - - let initial_indices_states = self - .index_parameters - .iter() - .map(|p| p.setup(timesteps, scenario_index)) - .collect::, _>>()?; - - let initial_multi_param_states = self - .multi_parameters - .iter() - .map(|p| p.setup(timesteps, scenario_index)) - .collect::, _>>()?; + let (initial_values_states, initial_indices_states, initial_multi_param_states) = + self.parameters.initial_states(timesteps, scenario_index)?; let state_builder = StateBuilder::new(initial_node_states, self.edges.len()) .with_virtual_storage_states(initial_virtual_storage_states) @@ -622,7 +604,7 @@ impl Network { // Find the parameter itself let p = self .parameters - .get(*idx.deref()) + .get_f64(*idx) .ok_or(PywrError::ParameterIndexNotFound(*idx))?; // .. and its internal state let internal_state = internal_states @@ -639,8 +621,8 @@ impl Network { } ParameterType::Index(idx) => { let p = self - .index_parameters - .get(*idx.deref()) + .parameters + .get_usize(*idx) .ok_or(PywrError::IndexParameterIndexNotFound(*idx))?; // .. and its internal state @@ -654,8 +636,8 @@ impl Network { } ParameterType::Multi(idx) => { let p = self - .multi_parameters - .get(*idx.deref()) + .parameters + .get_multi(*idx) .ok_or(PywrError::MultiValueParameterIndexNotFound(*idx))?; // .. and its internal state @@ -716,7 +698,7 @@ impl Network { // Find the parameter itself let p = self .parameters - .get(*idx.deref()) + .get_f64(*idx) .ok_or(PywrError::ParameterIndexNotFound(*idx))?; // .. and its internal state let internal_state = internal_states @@ -727,8 +709,8 @@ impl Network { } ParameterType::Index(idx) => { let p = self - .index_parameters - .get(*idx.deref()) + .parameters + .get_usize(*idx) .ok_or(PywrError::IndexParameterIndexNotFound(*idx))?; // .. and its internal state @@ -740,8 +722,8 @@ impl Network { } ParameterType::Multi(idx) => { let p = self - .multi_parameters - .get(*idx.deref()) + .parameters + .get_multi(*idx) .ok_or(PywrError::MultiValueParameterIndexNotFound(*idx))?; // .. and its internal state @@ -1093,36 +1075,25 @@ impl Network { } /// Get a `Parameter` from a parameter's name - pub fn get_parameter(&self, index: &ParameterIndex) -> Result<&dyn parameters::Parameter, PywrError> { - match self.parameters.get(*index.deref()) { - Some(p) => Ok(p.as_ref()), - None => Err(PywrError::ParameterIndexNotFound(*index)), - } - } - - /// Get a `Parameter` from a parameter's name - pub fn get_mut_parameter( - &mut self, - index: &ParameterIndex, - ) -> Result<&mut dyn parameters::Parameter, PywrError> { - match self.parameters.get_mut(*index.deref()) { - Some(p) => Ok(p.as_mut()), - None => Err(PywrError::ParameterIndexNotFound(*index)), + pub fn get_parameter(&self, index: ParameterIndex) -> Result<&dyn parameters::Parameter, PywrError> { + match self.parameters.get_f64(index) { + Some(p) => Ok(p), + None => Err(PywrError::ParameterIndexNotFound(index)), } } /// Get a `Parameter` from a parameter's name pub fn get_parameter_by_name(&self, name: &str) -> Result<&dyn parameters::Parameter, PywrError> { - match self.parameters.iter().find(|p| p.name() == name) { - Some(parameter) => Ok(parameter.as_ref()), + match self.parameters.get_f64_by_name(name) { + Some(parameter) => Ok(parameter), None => Err(PywrError::ParameterNotFound(name.to_string())), } } /// Get a `ParameterIndex` from a parameter's name pub fn get_parameter_index_by_name(&self, name: &str) -> Result, PywrError> { - match self.parameters.iter().position(|p| p.name() == name) { - Some(idx) => Ok(ParameterIndex::new(idx)), + match self.parameters.get_f64_index_by_name(name) { + Some(idx) => Ok(idx), None => Err(PywrError::ParameterNotFound(name.to_string())), } } @@ -1130,26 +1101,26 @@ impl Network { /// Get a [`Parameter`] from its index. pub fn get_index_parameter( &self, - index: &ParameterIndex, + index: ParameterIndex, ) -> Result<&dyn parameters::Parameter, PywrError> { - match self.index_parameters.get(*index.deref()) { - Some(p) => Ok(p.as_ref()), - None => Err(PywrError::IndexParameterIndexNotFound(*index)), + match self.parameters.get_usize(index) { + Some(p) => Ok(p), + None => Err(PywrError::IndexParameterIndexNotFound(index)), } } /// Get a `IndexParameter` from a parameter's name pub fn get_index_parameter_by_name(&self, name: &str) -> Result<&dyn parameters::Parameter, PywrError> { - match self.index_parameters.iter().find(|p| p.name() == name) { - Some(parameter) => Ok(parameter.as_ref()), + match self.parameters.get_usize_by_name(name) { + Some(parameter) => Ok(parameter), None => Err(PywrError::ParameterNotFound(name.to_string())), } } /// Get a `IndexParameterIndex` from a parameter's name pub fn get_index_parameter_index_by_name(&self, name: &str) -> Result, PywrError> { - match self.index_parameters.iter().position(|p| p.name() == name) { - Some(idx) => Ok(ParameterIndex::new(idx)), + match self.parameters.get_usize_index_by_name(name) { + Some(idx) => Ok(idx), None => Err(PywrError::ParameterNotFound(name.to_string())), } } @@ -1157,11 +1128,11 @@ impl Network { /// Get a `MultiValueParameterIndex` from a parameter's name pub fn get_multi_valued_parameter( &self, - index: &ParameterIndex, + index: ParameterIndex, ) -> Result<&dyn parameters::Parameter, PywrError> { - match self.multi_parameters.get(*index.deref()) { - Some(p) => Ok(p.as_ref()), - None => Err(PywrError::MultiValueParameterIndexNotFound(*index)), + match self.parameters.get_multi(index) { + Some(p) => Ok(p), + None => Err(PywrError::MultiValueParameterIndexNotFound(index)), } } @@ -1170,8 +1141,8 @@ impl Network { &self, name: &str, ) -> Result, PywrError> { - match self.multi_parameters.iter().position(|p| p.name() == name) { - Some(idx) => Ok(ParameterIndex::new(idx)), + match self.parameters.get_multi_index_by_name(name) { + Some(idx) => Ok(idx), None => Err(PywrError::ParameterNotFound(name.to_string())), } } @@ -1324,18 +1295,8 @@ impl Network { &mut self, parameter: Box>, ) -> Result, PywrError> { - if let Ok(idx) = self.get_parameter_index_by_name(¶meter.meta().name) { - return Err(PywrError::ParameterNameAlreadyExists( - parameter.meta().name.to_string(), - idx, - )); - } - - let parameter_index = ParameterIndex::new(self.parameters.len()); - - // Add the parameter ... - self.parameters.push(parameter); - // .. and add it to the resolve order + let parameter_index = self.parameters.add_f64(parameter)?; + // add it to the resolve order self.resolve_order .push(ComponentType::Parameter(ParameterType::Parameter(parameter_index))); Ok(parameter_index) @@ -1344,19 +1305,10 @@ impl Network { /// Add a `parameters::IndexParameter` to the network pub fn add_index_parameter( &mut self, - index_parameter: Box>, + parameter: Box>, ) -> Result, PywrError> { - if let Ok(idx) = self.get_index_parameter_index_by_name(&index_parameter.meta().name) { - return Err(PywrError::IndexParameterNameAlreadyExists( - index_parameter.meta().name.to_string(), - idx, - )); - } - - let parameter_index = ParameterIndex::new(self.index_parameters.len()); - - self.index_parameters.push(index_parameter); - // .. and add it to the resolve order + let parameter_index = self.parameters.add_usize(parameter)?; + // add it to the resolve order self.resolve_order .push(ComponentType::Parameter(ParameterType::Index(parameter_index))); Ok(parameter_index) @@ -1367,18 +1319,9 @@ impl Network { &mut self, parameter: Box>, ) -> Result, PywrError> { - if let Ok(idx) = self.get_parameter_index_by_name(¶meter.meta().name) { - return Err(PywrError::ParameterNameAlreadyExists( - parameter.meta().name.to_string(), - idx, - )); - } - - let parameter_index = ParameterIndex::new(self.multi_parameters.len()); + let parameter_index = self.parameters.add_multi(parameter)?; - // Add the parameter ... - self.multi_parameters.push(parameter); - // .. and add it to the resolve order + // add it to the resolve order self.resolve_order .push(ComponentType::Parameter(ParameterType::Multi(parameter_index))); Ok(parameter_index) @@ -1428,7 +1371,7 @@ impl Network { // )); // } - let recorder_index = RecorderIndex::new(self.index_parameters.len()); + let recorder_index = RecorderIndex::new(self.recorders.len()); self.recorders.push(recorder); Ok(recorder_index) } @@ -1468,7 +1411,7 @@ impl Network { variable_config: &dyn VariableConfig, state: &mut NetworkState, ) -> Result<(), PywrError> { - match self.parameters.get(*parameter_index.deref()) { + match self.parameters.get_f64(parameter_index) { Some(parameter) => match parameter.as_f64_variable() { Some(variable) => { // Iterate over all scenarios and set the variable values @@ -1499,7 +1442,7 @@ impl Network { variable_config: &dyn VariableConfig, state: &mut NetworkState, ) -> Result<(), PywrError> { - match self.parameters.get(*parameter_index.deref()) { + match self.parameters.get_f64(parameter_index) { Some(parameter) => match parameter.as_f64_variable() { Some(variable) => { let internal_state = state @@ -1521,7 +1464,7 @@ impl Network { scenario_index: ScenarioIndex, state: &NetworkState, ) -> Result>, PywrError> { - match self.parameters.get(*parameter_index.deref()) { + match self.parameters.get_f64(parameter_index) { Some(parameter) => match parameter.as_f64_variable() { Some(variable) => { let internal_state = state @@ -1542,7 +1485,7 @@ impl Network { parameter_index: ParameterIndex, state: &NetworkState, ) -> Result>>, PywrError> { - match self.parameters.get(*parameter_index.deref()) { + match self.parameters.get_f64(parameter_index) { Some(parameter) => match parameter.as_f64_variable() { Some(variable) => { let values = state @@ -1574,7 +1517,7 @@ impl Network { variable_config: &dyn VariableConfig, state: &mut NetworkState, ) -> Result<(), PywrError> { - match self.parameters.get(*parameter_index.deref()) { + match self.parameters.get_f64(parameter_index) { Some(parameter) => match parameter.as_u32_variable() { Some(variable) => { // Iterate over all scenarios and set the variable values @@ -1605,7 +1548,7 @@ impl Network { variable_config: &dyn VariableConfig, state: &mut NetworkState, ) -> Result<(), PywrError> { - match self.parameters.get(*parameter_index.deref()) { + match self.parameters.get_f64(parameter_index) { Some(parameter) => match parameter.as_u32_variable() { Some(variable) => { let internal_state = state @@ -1627,7 +1570,7 @@ impl Network { scenario_index: ScenarioIndex, state: &NetworkState, ) -> Result>, PywrError> { - match self.parameters.get(*parameter_index.deref()) { + match self.parameters.get_f64(parameter_index) { Some(parameter) => match parameter.as_u32_variable() { Some(variable) => { let internal_state = state diff --git a/pywr-core/src/parameters/mod.rs b/pywr-core/src/parameters/mod.rs index f9bd8acd..a2260df1 100644 --- a/pywr-core/src/parameters/mod.rs +++ b/pywr-core/src/parameters/mod.rs @@ -292,6 +292,141 @@ pub trait VariableParameter { fn get_upper_bounds(&self, variable_config: &dyn VariableConfig) -> Result, PywrError>; } +/// A collection of parameters that return different types. +#[derive(Default)] +pub struct ParameterCollection { + f64: Vec>>, + usize: Vec>>, + multi: Vec>>, +} + +impl ParameterCollection { + pub fn initial_states( + &self, + timesteps: &[Timestep], + scenario_index: &ScenarioIndex, + ) -> Result< + ( + Vec>>, + Vec>>, + Vec>>, + ), + PywrError, + > { + // Get the initial internal state + let values_states = self + .f64 + .iter() + .map(|p| p.setup(timesteps, scenario_index)) + .collect::, _>>()?; + + let initial_indices_states = self + .usize + .iter() + .map(|p| p.setup(timesteps, scenario_index)) + .collect::, _>>()?; + + let initial_multi_param_states = self + .multi + .iter() + .map(|p| p.setup(timesteps, scenario_index)) + .collect::, _>>()?; + + Ok((values_states, initial_indices_states, initial_multi_param_states)) + } + + /// Add a new parameter to the collection. + pub fn add_f64(&mut self, parameter: Box>) -> Result, PywrError> { + if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { + return Err(PywrError::ParameterNameAlreadyExists( + parameter.meta().name.to_string(), + index, + )); + } + + let index = ParameterIndex::new(self.f64.len()); + + self.f64.push(parameter); + + Ok(index) + } + pub fn get_f64(&self, index: ParameterIndex) -> Option<&dyn Parameter> { + self.f64.get(*index.deref()).map(|p| p.as_ref()) + } + + pub fn get_f64_by_name(&self, name: &str) -> Option<&dyn Parameter> { + self.f64.iter().find(|p| p.meta().name == name).map(|p| p.as_ref()) + } + + pub fn get_f64_index_by_name(&self, name: &str) -> Option> { + self.f64 + .iter() + .position(|p| p.meta().name == name) + .map(|idx| ParameterIndex::new(idx)) + } + + pub fn add_usize(&mut self, parameter: Box>) -> Result, PywrError> { + if let Some(index) = self.get_usize_index_by_name(¶meter.meta().name) { + return Err(PywrError::IndexParameterNameAlreadyExists( + parameter.meta().name.to_string(), + index, + )); + } + + let index = ParameterIndex::new(self.usize.len()); + + self.usize.push(parameter); + + Ok(index) + } + pub fn get_usize(&self, index: ParameterIndex) -> Option<&dyn Parameter> { + self.usize.get(*index.deref()).map(|p| p.as_ref()) + } + + pub fn get_usize_by_name(&self, name: &str) -> Option<&dyn Parameter> { + self.usize.iter().find(|p| p.meta().name == name).map(|p| p.as_ref()) + } + + pub fn get_usize_index_by_name(&self, name: &str) -> Option> { + self.usize + .iter() + .position(|p| p.meta().name == name) + .map(|idx| ParameterIndex::new(idx)) + } + + pub fn add_multi( + &mut self, + parameter: Box>, + ) -> Result, PywrError> { + if let Some(index) = self.get_multi_index_by_name(¶meter.meta().name) { + return Err(PywrError::MultiValueParameterNameAlreadyExists( + parameter.meta().name.to_string(), + index, + )); + } + + let index = ParameterIndex::new(self.usize.len()); + + self.multi.push(parameter); + + Ok(index) + } + pub fn get_multi(&self, index: ParameterIndex) -> Option<&dyn Parameter> { + self.multi.get(*index.deref()).map(|p| p.as_ref()) + } + + pub fn get_multi_by_name(&self, name: &str) -> Option<&dyn Parameter> { + self.multi.iter().find(|p| p.meta().name == name).map(|p| p.as_ref()) + } + + pub fn get_multi_index_by_name(&self, name: &str) -> Option> { + self.multi + .iter() + .position(|p| p.meta().name == name) + .map(|idx| ParameterIndex::new(idx)) + } +} + #[cfg(test)] mod tests { diff --git a/pywr-schema/src/metric.rs b/pywr-schema/src/metric.rs index d2fc8ecc..18f64665 100644 --- a/pywr-schema/src/metric.rs +++ b/pywr-schema/src/metric.rs @@ -297,11 +297,11 @@ impl NodeReference { } } -impl From for NodeReference{ - fn from (v: String) -> Self { +impl From for NodeReference { + fn from(v: String) -> Self { NodeReference { name: v, - attribute: None + attribute: None, } } } diff --git a/pywr-schema/src/nodes/virtual_storage.rs b/pywr-schema/src/nodes/virtual_storage.rs index 2b563b8e..50f7e591 100644 --- a/pywr-schema/src/nodes/virtual_storage.rs +++ b/pywr-schema/src/nodes/virtual_storage.rs @@ -2,11 +2,11 @@ use crate::error::ConversionError; #[cfg(feature = "core")] use crate::error::SchemaError; use crate::metric::Metric; +use crate::metric::NodeReference; #[cfg(feature = "core")] use crate::model::LoadArgs; use crate::nodes::core::StorageInitialVolume; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::metric::NodeReference; use crate::parameters::TryIntoV2Parameter; #[cfg(feature = "core")] use pywr_core::{ From da01c7eefeb01bcdad95856c878fd749b527c6e0 Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Tue, 21 May 2024 15:24:16 +0100 Subject: [PATCH 2/7] refactor: Separate Parameter into two traits. GeneralParameter contains compute and after methods, Parameter contains the others. --- pywr-core/src/network.rs | 22 ++++-- pywr-core/src/parameters/aggregated.rs | 9 ++- pywr-core/src/parameters/aggregated_index.rs | 9 ++- pywr-core/src/parameters/array.rs | 11 ++- pywr-core/src/parameters/asymmetric.rs | 6 +- pywr-core/src/parameters/constant.rs | 21 +++--- .../parameters/control_curves/apportion.rs | 7 +- .../src/parameters/control_curves/index.rs | 7 +- .../parameters/control_curves/interpolated.rs | 7 +- .../parameters/control_curves/piecewise.rs | 7 +- .../src/parameters/control_curves/simple.rs | 7 +- .../control_curves/volume_between.rs | 7 +- pywr-core/src/parameters/delay.rs | 6 +- pywr-core/src/parameters/discount_factor.rs | 6 +- pywr-core/src/parameters/division.rs | 9 ++- pywr-core/src/parameters/hydropower.rs | 7 +- pywr-core/src/parameters/indexed_array.rs | 7 +- pywr-core/src/parameters/interpolated.rs | 7 +- pywr-core/src/parameters/max.rs | 7 +- pywr-core/src/parameters/min.rs | 6 +- pywr-core/src/parameters/mod.rs | 74 ++++++++++--------- pywr-core/src/parameters/negative.rs | 7 +- pywr-core/src/parameters/negativemax.rs | 6 +- pywr-core/src/parameters/negativemin.rs | 6 +- pywr-core/src/parameters/offset.rs | 22 +++--- pywr-core/src/parameters/polynomial.rs | 7 +- pywr-core/src/parameters/profiles/daily.rs | 7 +- pywr-core/src/parameters/profiles/monthly.rs | 6 +- pywr-core/src/parameters/profiles/rbf.rs | 37 +++++----- .../parameters/profiles/uniform_drawdown.rs | 6 +- pywr-core/src/parameters/profiles/weekly.rs | 7 +- pywr-core/src/parameters/py.rs | 51 +++---------- pywr-core/src/parameters/rhai.rs | 6 +- pywr-core/src/parameters/threshold.rs | 6 +- pywr-core/src/parameters/vector.rs | 7 +- pywr-core/src/test_utils.rs | 4 +- 36 files changed, 246 insertions(+), 188 deletions(-) diff --git a/pywr-core/src/network.rs b/pywr-core/src/network.rs index 4b172e79..862cf22c 100644 --- a/pywr-core/src/network.rs +++ b/pywr-core/src/network.rs @@ -1075,7 +1075,10 @@ impl Network { } /// Get a `Parameter` from a parameter's name - pub fn get_parameter(&self, index: ParameterIndex) -> Result<&dyn parameters::Parameter, PywrError> { + pub fn get_parameter( + &self, + index: ParameterIndex, + ) -> Result<&dyn parameters::GeneralParameter, PywrError> { match self.parameters.get_f64(index) { Some(p) => Ok(p), None => Err(PywrError::ParameterIndexNotFound(index)), @@ -1083,7 +1086,7 @@ impl Network { } /// Get a `Parameter` from a parameter's name - pub fn get_parameter_by_name(&self, name: &str) -> Result<&dyn parameters::Parameter, PywrError> { + pub fn get_parameter_by_name(&self, name: &str) -> Result<&dyn parameters::GeneralParameter, PywrError> { match self.parameters.get_f64_by_name(name) { Some(parameter) => Ok(parameter), None => Err(PywrError::ParameterNotFound(name.to_string())), @@ -1102,7 +1105,7 @@ impl Network { pub fn get_index_parameter( &self, index: ParameterIndex, - ) -> Result<&dyn parameters::Parameter, PywrError> { + ) -> Result<&dyn parameters::GeneralParameter, PywrError> { match self.parameters.get_usize(index) { Some(p) => Ok(p), None => Err(PywrError::IndexParameterIndexNotFound(index)), @@ -1110,7 +1113,10 @@ impl Network { } /// Get a `IndexParameter` from a parameter's name - pub fn get_index_parameter_by_name(&self, name: &str) -> Result<&dyn parameters::Parameter, PywrError> { + pub fn get_index_parameter_by_name( + &self, + name: &str, + ) -> Result<&dyn parameters::GeneralParameter, PywrError> { match self.parameters.get_usize_by_name(name) { Some(parameter) => Ok(parameter), None => Err(PywrError::ParameterNotFound(name.to_string())), @@ -1129,7 +1135,7 @@ impl Network { pub fn get_multi_valued_parameter( &self, index: ParameterIndex, - ) -> Result<&dyn parameters::Parameter, PywrError> { + ) -> Result<&dyn parameters::GeneralParameter, PywrError> { match self.parameters.get_multi(index) { Some(p) => Ok(p), None => Err(PywrError::MultiValueParameterIndexNotFound(index)), @@ -1293,7 +1299,7 @@ impl Network { /// Add a `parameters::Parameter` to the network pub fn add_parameter( &mut self, - parameter: Box>, + parameter: Box>, ) -> Result, PywrError> { let parameter_index = self.parameters.add_f64(parameter)?; // add it to the resolve order @@ -1305,7 +1311,7 @@ impl Network { /// Add a `parameters::IndexParameter` to the network pub fn add_index_parameter( &mut self, - parameter: Box>, + parameter: Box>, ) -> Result, PywrError> { let parameter_index = self.parameters.add_usize(parameter)?; // add it to the resolve order @@ -1317,7 +1323,7 @@ impl Network { /// Add a `parameters::MultiValueParameter` to the network pub fn add_multi_value_parameter( &mut self, - parameter: Box>, + parameter: Box>, ) -> Result, PywrError> { let parameter_index = self.parameters.add_multi(parameter)?; diff --git a/pywr-core/src/parameters/aggregated.rs b/pywr-core/src/parameters/aggregated.rs index dac7826d..fb659bbc 100644 --- a/pywr-core/src/parameters/aggregated.rs +++ b/pywr-core/src/parameters/aggregated.rs @@ -1,7 +1,7 @@ -use super::PywrError; +use super::{Parameter, PywrError}; use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -46,10 +46,13 @@ impl AggregatedParameter { } } -impl Parameter for AggregatedParameter { +impl Parameter for AggregatedParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for AggregatedParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/aggregated_index.rs b/pywr-core/src/parameters/aggregated_index.rs index ce5caa53..21578185 100644 --- a/pywr-core/src/parameters/aggregated_index.rs +++ b/pywr-core/src/parameters/aggregated_index.rs @@ -1,9 +1,9 @@ /// AggregatedIndexParameter /// -use super::PywrError; +use super::{Parameter, PywrError}; use crate::metric::MetricUsize; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -50,10 +50,13 @@ impl AggregatedIndexParameter { } } -impl Parameter for AggregatedIndexParameter { +impl Parameter for AggregatedIndexParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for AggregatedIndexParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/array.rs b/pywr-core/src/parameters/array.rs index afde690a..f17b25b8 100644 --- a/pywr-core/src/parameters/array.rs +++ b/pywr-core/src/parameters/array.rs @@ -1,5 +1,5 @@ use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -21,11 +21,12 @@ impl Array1Parameter { } } } - -impl Parameter for Array1Parameter { +impl Parameter for Array1Parameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} +impl GeneralParameter for Array1Parameter { fn compute( &self, timestep: &Timestep, @@ -62,11 +63,13 @@ impl Array2Parameter { } } -impl Parameter for Array2Parameter { +impl Parameter for Array2Parameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} +impl GeneralParameter for Array2Parameter { fn compute( &self, timestep: &Timestep, diff --git a/pywr-core/src/parameters/asymmetric.rs b/pywr-core/src/parameters/asymmetric.rs index 3d7a33f4..edafe3ef 100644 --- a/pywr-core/src/parameters/asymmetric.rs +++ b/pywr-core/src/parameters/asymmetric.rs @@ -1,6 +1,6 @@ use crate::metric::MetricUsize; use crate::network::Network; -use crate::parameters::{downcast_internal_state_mut, Parameter, ParameterMeta}; +use crate::parameters::{downcast_internal_state_mut, GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -22,7 +22,7 @@ impl AsymmetricSwitchIndexParameter { } } -impl Parameter for AsymmetricSwitchIndexParameter { +impl Parameter for AsymmetricSwitchIndexParameter { fn meta(&self) -> &ParameterMeta { &self.meta } @@ -33,7 +33,9 @@ impl Parameter for AsymmetricSwitchIndexParameter { ) -> Result>, PywrError> { Ok(Some(Box::new(0_usize))) } +} +impl GeneralParameter for AsymmetricSwitchIndexParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/constant.rs b/pywr-core/src/parameters/constant.rs index 74348db7..8aeaf6f8 100644 --- a/pywr-core/src/parameters/constant.rs +++ b/pywr-core/src/parameters/constant.rs @@ -1,7 +1,7 @@ use crate::network::Network; use crate::parameters::{ downcast_internal_state_mut, downcast_internal_state_ref, downcast_variable_config_ref, ActivationFunction, - Parameter, ParameterMeta, VariableConfig, VariableParameter, + GeneralParameter, Parameter, ParameterMeta, VariableConfig, VariableParameter, }; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; @@ -36,7 +36,7 @@ impl ConstantParameter { } } -impl Parameter for ConstantParameter { +impl Parameter for ConstantParameter { fn meta(&self) -> &ParameterMeta { &self.meta } @@ -49,7 +49,16 @@ impl Parameter for ConstantParameter { let value: Option = None; Ok(Some(Box::new(value))) } + fn as_f64_variable(&self) -> Option<&dyn VariableParameter> { + Some(self) + } + + fn as_f64_variable_mut(&mut self) -> Option<&mut dyn VariableParameter> { + Some(self) + } +} +impl GeneralParameter for ConstantParameter { fn compute( &self, _timestep: &Timestep, @@ -60,14 +69,6 @@ impl Parameter for ConstantParameter { ) -> Result { Ok(self.value(internal_state)) } - - fn as_f64_variable(&self) -> Option<&dyn VariableParameter> { - Some(self) - } - - fn as_f64_variable_mut(&mut self) -> Option<&mut dyn VariableParameter> { - Some(self) - } } impl VariableParameter for ConstantParameter { diff --git a/pywr-core/src/parameters/control_curves/apportion.rs b/pywr-core/src/parameters/control_curves/apportion.rs index 25d9d621..4dea2450 100644 --- a/pywr-core/src/parameters/control_curves/apportion.rs +++ b/pywr-core/src/parameters/control_curves/apportion.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{MultiValue, ParameterState, State}; use crate::timestep::Timestep; @@ -31,10 +31,13 @@ impl ApportionParameter { } } -impl Parameter for ApportionParameter { +impl Parameter for ApportionParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for ApportionParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/control_curves/index.rs b/pywr-core/src/parameters/control_curves/index.rs index 36b9ce1b..60fe9701 100644 --- a/pywr-core/src/parameters/control_curves/index.rs +++ b/pywr-core/src/parameters/control_curves/index.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -22,10 +22,13 @@ impl ControlCurveIndexParameter { } } -impl Parameter for ControlCurveIndexParameter { +impl Parameter for ControlCurveIndexParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for ControlCurveIndexParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/control_curves/interpolated.rs b/pywr-core/src/parameters/control_curves/interpolated.rs index e6db2d20..f0f2b3ca 100644 --- a/pywr-core/src/parameters/control_curves/interpolated.rs +++ b/pywr-core/src/parameters/control_curves/interpolated.rs @@ -1,7 +1,7 @@ use crate::metric::MetricF64; use crate::network::Network; use crate::parameters::interpolate::interpolate; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -25,10 +25,13 @@ impl ControlCurveInterpolatedParameter { } } -impl Parameter for ControlCurveInterpolatedParameter { +impl Parameter for ControlCurveInterpolatedParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for ControlCurveInterpolatedParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/control_curves/piecewise.rs b/pywr-core/src/parameters/control_curves/piecewise.rs index 8ecbc259..97f24c7b 100644 --- a/pywr-core/src/parameters/control_curves/piecewise.rs +++ b/pywr-core/src/parameters/control_curves/piecewise.rs @@ -1,7 +1,7 @@ use crate::metric::MetricF64; use crate::network::Network; use crate::parameters::interpolate::interpolate; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -35,11 +35,12 @@ impl PiecewiseInterpolatedParameter { } } } - -impl Parameter for PiecewiseInterpolatedParameter { +impl Parameter for PiecewiseInterpolatedParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} +impl GeneralParameter for PiecewiseInterpolatedParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/control_curves/simple.rs b/pywr-core/src/parameters/control_curves/simple.rs index 9343de02..e2a4da92 100644 --- a/pywr-core/src/parameters/control_curves/simple.rs +++ b/pywr-core/src/parameters/control_curves/simple.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -24,10 +24,13 @@ impl ControlCurveParameter { } } -impl Parameter for ControlCurveParameter { +impl Parameter for ControlCurveParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for ControlCurveParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/control_curves/volume_between.rs b/pywr-core/src/parameters/control_curves/volume_between.rs index 126362ae..f4deef34 100644 --- a/pywr-core/src/parameters/control_curves/volume_between.rs +++ b/pywr-core/src/parameters/control_curves/volume_between.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -25,10 +25,13 @@ impl VolumeBetweenControlCurvesParameter { } } -impl Parameter for VolumeBetweenControlCurvesParameter { +impl Parameter for VolumeBetweenControlCurvesParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for VolumeBetweenControlCurvesParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/delay.rs b/pywr-core/src/parameters/delay.rs index 4cf963b0..47c432e4 100644 --- a/pywr-core/src/parameters/delay.rs +++ b/pywr-core/src/parameters/delay.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{downcast_internal_state_mut, Parameter, ParameterMeta}; +use crate::parameters::{downcast_internal_state_mut, GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -25,7 +25,7 @@ impl DelayParameter { } } -impl Parameter for DelayParameter { +impl Parameter for DelayParameter { fn meta(&self) -> &ParameterMeta { &self.meta } @@ -39,7 +39,9 @@ impl Parameter for DelayParameter { let memory: VecDeque = (0..self.delay).map(|_| self.initial_value).collect(); Ok(Some(Box::new(memory))) } +} +impl GeneralParameter for DelayParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/discount_factor.rs b/pywr-core/src/parameters/discount_factor.rs index e3b0d4bf..4bab7938 100644 --- a/pywr-core/src/parameters/discount_factor.rs +++ b/pywr-core/src/parameters/discount_factor.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -23,10 +23,12 @@ impl DiscountFactorParameter { } } -impl Parameter for DiscountFactorParameter { +impl Parameter for DiscountFactorParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} +impl GeneralParameter for DiscountFactorParameter { fn compute( &self, timestep: &Timestep, diff --git a/pywr-core/src/parameters/division.rs b/pywr-core/src/parameters/division.rs index f4e172be..65490eb0 100644 --- a/pywr-core/src/parameters/division.rs +++ b/pywr-core/src/parameters/division.rs @@ -1,7 +1,7 @@ -use super::PywrError; +use super::{Parameter, PywrError}; use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -22,11 +22,12 @@ impl DivisionParameter { } } } - -impl Parameter for DivisionParameter { +impl Parameter for DivisionParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} +impl GeneralParameter for DivisionParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/hydropower.rs b/pywr-core/src/parameters/hydropower.rs index 314c4fdd..8a3d6950 100644 --- a/pywr-core/src/parameters/hydropower.rs +++ b/pywr-core/src/parameters/hydropower.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -52,10 +52,13 @@ impl HydropowerTargetParameter { } } -impl Parameter for HydropowerTargetParameter { +impl Parameter for HydropowerTargetParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for HydropowerTargetParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/indexed_array.rs b/pywr-core/src/parameters/indexed_array.rs index d56a3025..ce3c7e21 100644 --- a/pywr-core/src/parameters/indexed_array.rs +++ b/pywr-core/src/parameters/indexed_array.rs @@ -1,6 +1,6 @@ use crate::metric::{MetricF64, MetricUsize}; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -22,10 +22,13 @@ impl IndexedArrayParameter { } } -impl Parameter for IndexedArrayParameter { +impl Parameter for IndexedArrayParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for IndexedArrayParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/interpolated.rs b/pywr-core/src/parameters/interpolated.rs index ea3ef5b9..bac314d6 100644 --- a/pywr-core/src/parameters/interpolated.rs +++ b/pywr-core/src/parameters/interpolated.rs @@ -1,7 +1,7 @@ use crate::metric::MetricF64; use crate::network::Network; use crate::parameters::interpolate::linear_interpolation; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -25,11 +25,12 @@ impl InterpolatedParameter { } } } - -impl Parameter for InterpolatedParameter { +impl Parameter for InterpolatedParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} +impl GeneralParameter for InterpolatedParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/max.rs b/pywr-core/src/parameters/max.rs index d2b028bd..f75841e4 100644 --- a/pywr-core/src/parameters/max.rs +++ b/pywr-core/src/parameters/max.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -22,10 +22,13 @@ impl MaxParameter { } } -impl Parameter for MaxParameter { +impl Parameter for MaxParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for MaxParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/min.rs b/pywr-core/src/parameters/min.rs index d3705709..ae9e1e64 100644 --- a/pywr-core/src/parameters/min.rs +++ b/pywr-core/src/parameters/min.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -22,10 +22,12 @@ impl MinParameter { } } -impl Parameter for MinParameter { +impl Parameter for MinParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} +impl GeneralParameter for MinParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/mod.rs b/pywr-core/src/parameters/mod.rs index a2260df1..1b66289b 100644 --- a/pywr-core/src/parameters/mod.rs +++ b/pywr-core/src/parameters/mod.rs @@ -192,7 +192,7 @@ pub fn downcast_variable_config_ref(variable_config: &dyn VariableCo /// A trait that defines a component that produces a value each time-step. /// /// The trait is generic over the type of the value produced. -pub trait Parameter: Send + Sync { +pub trait Parameter: Send + Sync { fn meta(&self) -> &ParameterMeta; fn name(&self) -> &str { self.meta().name.as_str() @@ -206,26 +206,6 @@ pub trait Parameter: Send + Sync { Ok(None) } - fn compute( - &self, - timestep: &Timestep, - scenario_index: &ScenarioIndex, - model: &Network, - state: &State, - internal_state: &mut Option>, - ) -> Result; - - fn after( - &self, - #[allow(unused_variables)] timestep: &Timestep, - #[allow(unused_variables)] scenario_index: &ScenarioIndex, - #[allow(unused_variables)] model: &Network, - #[allow(unused_variables)] state: &State, - #[allow(unused_variables)] internal_state: &mut Option>, - ) -> Result<(), PywrError> { - Ok(()) - } - /// Return the parameter as a [`VariableParameter`] if it supports being a variable. fn as_f64_variable(&self) -> Option<&dyn VariableParameter> { None @@ -257,6 +237,31 @@ pub trait Parameter: Send + Sync { } } +/// A trait that defines a component that produces a value each time-step. +/// +/// The trait is generic over the type of the value produced. +pub trait GeneralParameter: Parameter { + fn compute( + &self, + timestep: &Timestep, + scenario_index: &ScenarioIndex, + model: &Network, + state: &State, + internal_state: &mut Option>, + ) -> Result; + + fn after( + &self, + #[allow(unused_variables)] timestep: &Timestep, + #[allow(unused_variables)] scenario_index: &ScenarioIndex, + #[allow(unused_variables)] model: &Network, + #[allow(unused_variables)] state: &State, + #[allow(unused_variables)] internal_state: &mut Option>, + ) -> Result<(), PywrError> { + Ok(()) + } +} + pub enum ParameterType { Parameter(ParameterIndex), Index(ParameterIndex), @@ -295,9 +300,9 @@ pub trait VariableParameter { /// A collection of parameters that return different types. #[derive(Default)] pub struct ParameterCollection { - f64: Vec>>, - usize: Vec>>, - multi: Vec>>, + f64: Vec>>, + usize: Vec>>, + multi: Vec>>, } impl ParameterCollection { @@ -336,7 +341,7 @@ impl ParameterCollection { } /// Add a new parameter to the collection. - pub fn add_f64(&mut self, parameter: Box>) -> Result, PywrError> { + pub fn add_f64(&mut self, parameter: Box>) -> Result, PywrError> { if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { return Err(PywrError::ParameterNameAlreadyExists( parameter.meta().name.to_string(), @@ -350,11 +355,11 @@ impl ParameterCollection { Ok(index) } - pub fn get_f64(&self, index: ParameterIndex) -> Option<&dyn Parameter> { + pub fn get_f64(&self, index: ParameterIndex) -> Option<&dyn GeneralParameter> { self.f64.get(*index.deref()).map(|p| p.as_ref()) } - pub fn get_f64_by_name(&self, name: &str) -> Option<&dyn Parameter> { + pub fn get_f64_by_name(&self, name: &str) -> Option<&dyn GeneralParameter> { self.f64.iter().find(|p| p.meta().name == name).map(|p| p.as_ref()) } @@ -365,7 +370,10 @@ impl ParameterCollection { .map(|idx| ParameterIndex::new(idx)) } - pub fn add_usize(&mut self, parameter: Box>) -> Result, PywrError> { + pub fn add_usize( + &mut self, + parameter: Box>, + ) -> Result, PywrError> { if let Some(index) = self.get_usize_index_by_name(¶meter.meta().name) { return Err(PywrError::IndexParameterNameAlreadyExists( parameter.meta().name.to_string(), @@ -379,11 +387,11 @@ impl ParameterCollection { Ok(index) } - pub fn get_usize(&self, index: ParameterIndex) -> Option<&dyn Parameter> { + pub fn get_usize(&self, index: ParameterIndex) -> Option<&dyn GeneralParameter> { self.usize.get(*index.deref()).map(|p| p.as_ref()) } - pub fn get_usize_by_name(&self, name: &str) -> Option<&dyn Parameter> { + pub fn get_usize_by_name(&self, name: &str) -> Option<&dyn GeneralParameter> { self.usize.iter().find(|p| p.meta().name == name).map(|p| p.as_ref()) } @@ -396,7 +404,7 @@ impl ParameterCollection { pub fn add_multi( &mut self, - parameter: Box>, + parameter: Box>, ) -> Result, PywrError> { if let Some(index) = self.get_multi_index_by_name(¶meter.meta().name) { return Err(PywrError::MultiValueParameterNameAlreadyExists( @@ -411,11 +419,11 @@ impl ParameterCollection { Ok(index) } - pub fn get_multi(&self, index: ParameterIndex) -> Option<&dyn Parameter> { + pub fn get_multi(&self, index: ParameterIndex) -> Option<&dyn GeneralParameter> { self.multi.get(*index.deref()).map(|p| p.as_ref()) } - pub fn get_multi_by_name(&self, name: &str) -> Option<&dyn Parameter> { + pub fn get_multi_by_name(&self, name: &str) -> Option<&dyn GeneralParameter> { self.multi.iter().find(|p| p.meta().name == name).map(|p| p.as_ref()) } diff --git a/pywr-core/src/parameters/negative.rs b/pywr-core/src/parameters/negative.rs index 4101ff4f..8e3a2f49 100644 --- a/pywr-core/src/parameters/negative.rs +++ b/pywr-core/src/parameters/negative.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -20,10 +20,13 @@ impl NegativeParameter { } } -impl Parameter for NegativeParameter { +impl Parameter for NegativeParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for NegativeParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/negativemax.rs b/pywr-core/src/parameters/negativemax.rs index b2e9620d..f0c62ebd 100644 --- a/pywr-core/src/parameters/negativemax.rs +++ b/pywr-core/src/parameters/negativemax.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -22,10 +22,12 @@ impl NegativeMaxParameter { } } -impl Parameter for NegativeMaxParameter { +impl Parameter for NegativeMaxParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} +impl GeneralParameter for NegativeMaxParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/negativemin.rs b/pywr-core/src/parameters/negativemin.rs index a92fc614..a5cea191 100644 --- a/pywr-core/src/parameters/negativemin.rs +++ b/pywr-core/src/parameters/negativemin.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -22,10 +22,12 @@ impl NegativeMinParameter { } } -impl Parameter for NegativeMinParameter { +impl Parameter for NegativeMinParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} +impl GeneralParameter for NegativeMinParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/offset.rs b/pywr-core/src/parameters/offset.rs index 4bd7c18b..f7bd2b01 100644 --- a/pywr-core/src/parameters/offset.rs +++ b/pywr-core/src/parameters/offset.rs @@ -2,7 +2,7 @@ use crate::metric::MetricF64; use crate::network::Network; use crate::parameters::{ downcast_internal_state_mut, downcast_internal_state_ref, downcast_variable_config_ref, ActivationFunction, - Parameter, ParameterMeta, VariableConfig, VariableParameter, + GeneralParameter, Parameter, ParameterMeta, VariableConfig, VariableParameter, }; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; @@ -38,11 +38,20 @@ impl OffsetParameter { } } } - -impl Parameter for OffsetParameter { +impl Parameter for OffsetParameter { fn meta(&self) -> &ParameterMeta { &self.meta } + + fn as_f64_variable(&self) -> Option<&dyn VariableParameter> { + Some(self) + } + + fn as_f64_variable_mut(&mut self) -> Option<&mut dyn VariableParameter> { + Some(self) + } +} +impl GeneralParameter for OffsetParameter { fn compute( &self, _timestep: &Timestep, @@ -56,13 +65,6 @@ impl Parameter for OffsetParameter { let x = self.metric.get_value(model, state)?; Ok(x + offset) } - fn as_f64_variable(&self) -> Option<&dyn VariableParameter> { - Some(self) - } - - fn as_f64_variable_mut(&mut self) -> Option<&mut dyn VariableParameter> { - Some(self) - } } impl VariableParameter for OffsetParameter { diff --git a/pywr-core/src/parameters/polynomial.rs b/pywr-core/src/parameters/polynomial.rs index 66ebc436..e521fd5b 100644 --- a/pywr-core/src/parameters/polynomial.rs +++ b/pywr-core/src/parameters/polynomial.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -26,10 +26,13 @@ impl Polynomial1DParameter { } } -impl Parameter for Polynomial1DParameter { +impl Parameter for Polynomial1DParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for Polynomial1DParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/profiles/daily.rs b/pywr-core/src/parameters/profiles/daily.rs index c13d14c8..66d124f5 100644 --- a/pywr-core/src/parameters/profiles/daily.rs +++ b/pywr-core/src/parameters/profiles/daily.rs @@ -1,5 +1,5 @@ use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -20,10 +20,13 @@ impl DailyProfileParameter { } } -impl Parameter for DailyProfileParameter { +impl Parameter for DailyProfileParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for DailyProfileParameter { fn compute( &self, timestep: &Timestep, diff --git a/pywr-core/src/parameters/profiles/monthly.rs b/pywr-core/src/parameters/profiles/monthly.rs index 3bade16b..6e1970fc 100644 --- a/pywr-core/src/parameters/profiles/monthly.rs +++ b/pywr-core/src/parameters/profiles/monthly.rs @@ -1,5 +1,5 @@ use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -70,10 +70,12 @@ fn interpolate_last(date: &NaiveDateTime, first_value: f64, last_value: f64) -> } } -impl Parameter for MonthlyProfileParameter { +impl Parameter for MonthlyProfileParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} +impl GeneralParameter for MonthlyProfileParameter { fn compute( &self, timestep: &Timestep, diff --git a/pywr-core/src/parameters/profiles/rbf.rs b/pywr-core/src/parameters/profiles/rbf.rs index a3d65876..26d13128 100644 --- a/pywr-core/src/parameters/profiles/rbf.rs +++ b/pywr-core/src/parameters/profiles/rbf.rs @@ -1,7 +1,7 @@ use crate::network::Network; use crate::parameters::{ - downcast_internal_state_mut, downcast_internal_state_ref, downcast_variable_config_ref, Parameter, ParameterMeta, - VariableConfig, VariableParameter, + downcast_internal_state_mut, downcast_internal_state_ref, downcast_variable_config_ref, GeneralParameter, + Parameter, ParameterMeta, VariableConfig, VariableParameter, }; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; @@ -100,7 +100,7 @@ impl RbfProfileParameter { } } -impl Parameter for RbfProfileParameter { +impl Parameter for RbfProfileParameter { fn meta(&self) -> &ParameterMeta { &self.meta } @@ -113,21 +113,6 @@ impl Parameter for RbfProfileParameter { let internal_state = RbfProfileInternalState::new(&self.points, &self.function); Ok(Some(Box::new(internal_state))) } - - fn compute( - &self, - timestep: &Timestep, - _scenario_index: &ScenarioIndex, - _network: &Network, - _state: &State, - internal_state: &mut Option>, - ) -> Result { - // Get the profile from the internal state - let internal_state = downcast_internal_state_ref::(internal_state); - // Return today's value from the profile - Ok(internal_state.profile[timestep.date.ordinal() as usize - 1]) - } - fn as_f64_variable(&self) -> Option<&dyn VariableParameter> { Some(self) } @@ -145,6 +130,22 @@ impl Parameter for RbfProfileParameter { } } +impl GeneralParameter for RbfProfileParameter { + fn compute( + &self, + timestep: &Timestep, + _scenario_index: &ScenarioIndex, + _network: &Network, + _state: &State, + internal_state: &mut Option>, + ) -> Result { + // Get the profile from the internal state + let internal_state = downcast_internal_state_ref::(internal_state); + // Return today's value from the profile + Ok(internal_state.profile[timestep.date.ordinal() as usize - 1]) + } +} + impl VariableParameter for RbfProfileParameter { fn meta(&self) -> &ParameterMeta { &self.meta diff --git a/pywr-core/src/parameters/profiles/uniform_drawdown.rs b/pywr-core/src/parameters/profiles/uniform_drawdown.rs index 12f619b8..b965218c 100644 --- a/pywr-core/src/parameters/profiles/uniform_drawdown.rs +++ b/pywr-core/src/parameters/profiles/uniform_drawdown.rs @@ -1,5 +1,5 @@ use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -31,10 +31,12 @@ impl UniformDrawdownProfileParameter { } } -impl Parameter for UniformDrawdownProfileParameter { +impl Parameter for UniformDrawdownProfileParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} +impl GeneralParameter for UniformDrawdownProfileParameter { fn compute( &self, timestep: &Timestep, diff --git a/pywr-core/src/parameters/profiles/weekly.rs b/pywr-core/src/parameters/profiles/weekly.rs index 7bb6eb6f..b259d193 100644 --- a/pywr-core/src/parameters/profiles/weekly.rs +++ b/pywr-core/src/parameters/profiles/weekly.rs @@ -1,5 +1,5 @@ use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -184,10 +184,13 @@ impl WeeklyProfileParameter { } } -impl Parameter for WeeklyProfileParameter { +impl Parameter for WeeklyProfileParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for WeeklyProfileParameter { fn compute( &self, timestep: &Timestep, diff --git a/pywr-core/src/parameters/py.rs b/pywr-core/src/parameters/py.rs index 6e8462a8..ee19e137 100644 --- a/pywr-core/src/parameters/py.rs +++ b/pywr-core/src/parameters/py.rs @@ -1,4 +1,4 @@ -use super::{Parameter, ParameterMeta, PywrError, Timestep}; +use super::{GeneralParameter, Parameter, ParameterMeta, PywrError, Timestep}; use crate::metric::{MetricF64, MetricUsize}; use crate::network::Network; use crate::parameters::downcast_internal_state_mut; @@ -160,7 +160,7 @@ impl PyParameter { } } -impl Parameter for PyParameter { +impl Parameter for PyParameter { fn meta(&self) -> &ParameterMeta { &self.meta } @@ -172,7 +172,9 @@ impl Parameter for PyParameter { ) -> Result>, PywrError> { self.setup() } +} +impl GeneralParameter for PyParameter { fn compute( &self, timestep: &Timestep, @@ -196,19 +198,7 @@ impl Parameter for PyParameter { } } -impl Parameter for PyParameter { - fn meta(&self) -> &ParameterMeta { - &self.meta - } - - fn setup( - &self, - _timesteps: &[Timestep], - _scenario_index: &ScenarioIndex, - ) -> Result>, PywrError> { - self.setup() - } - +impl GeneralParameter for PyParameter { fn compute( &self, timestep: &Timestep, @@ -232,28 +222,7 @@ impl Parameter for PyParameter { } } -impl Parameter for PyParameter { - fn meta(&self) -> &ParameterMeta { - &self.meta - } - - fn setup( - &self, - _timesteps: &[Timestep], - _scenario_index: &ScenarioIndex, - ) -> Result>, PywrError> { - self.setup() - } - - // fn before(&self, internal_state: &mut Option>) -> Result<(), PywrError> { - // let internal = downcast_internal_state::(internal_state); - // - // Python::with_gil(|py| internal.user_obj.call_method0(py, "before")) - // .map_err(|e| PywrError::PythonError(e.to_string()))?; - // - // Ok(()) - // } - +impl GeneralParameter for PyParameter { fn compute( &self, timestep: &Timestep, @@ -383,14 +352,14 @@ class MyParameter: let mut internal_p_states: Vec<_> = scenario_indices .iter() - .map(|si| Parameter::::setup(¶m, ×teps, si).expect("Could not setup the PyParameter")) + .map(|si| Parameter::setup(¶m, ×teps, si).expect("Could not setup the PyParameter")) .collect(); let model = Network::default(); for ts in timesteps { for (si, internal) in scenario_indices.iter().zip(internal_p_states.iter_mut()) { - let value = Parameter::compute(¶m, ts, si, &model, &state, internal).unwrap(); + let value = GeneralParameter::compute(¶m, ts, si, &model, &state, internal).unwrap(); assert_approx_eq!(f64, value, ((ts.index + 1) * si.index + ts.date.day() as usize) as f64); } @@ -452,14 +421,14 @@ class MyParameter: let mut internal_p_states: Vec<_> = scenario_indices .iter() - .map(|si| Parameter::::setup(¶m, ×teps, si).expect("Could not setup the PyParameter")) + .map(|si| Parameter::setup(¶m, ×teps, si).expect("Could not setup the PyParameter")) .collect(); let model = Network::default(); for ts in timesteps { for (si, internal) in scenario_indices.iter().zip(internal_p_states.iter_mut()) { - let value = Parameter::::compute(¶m, ts, si, &model, &state, internal).unwrap(); + let value = GeneralParameter::::compute(¶m, ts, si, &model, &state, internal).unwrap(); assert_approx_eq!(f64, *value.get_value("a-float").unwrap(), std::f64::consts::PI); diff --git a/pywr-core/src/parameters/rhai.rs b/pywr-core/src/parameters/rhai.rs index 80925061..db00543e 100644 --- a/pywr-core/src/parameters/rhai.rs +++ b/pywr-core/src/parameters/rhai.rs @@ -1,4 +1,4 @@ -use super::{Parameter, ParameterMeta, PywrError, Timestep}; +use super::{GeneralParameter, Parameter, ParameterMeta, PywrError, Timestep}; use crate::metric::{MetricF64, MetricUsize}; use crate::network::Network; use crate::parameters::downcast_internal_state_mut; @@ -54,7 +54,7 @@ impl RhaiParameter { } } -impl Parameter for RhaiParameter { +impl Parameter for RhaiParameter { fn meta(&self) -> &ParameterMeta { &self.meta } @@ -77,7 +77,9 @@ impl Parameter for RhaiParameter { Ok(Some(Box::new(internal))) } +} +impl GeneralParameter for RhaiParameter { fn compute( &self, timestep: &Timestep, diff --git a/pywr-core/src/parameters/threshold.rs b/pywr-core/src/parameters/threshold.rs index 7655cecc..835f7e0d 100644 --- a/pywr-core/src/parameters/threshold.rs +++ b/pywr-core/src/parameters/threshold.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{downcast_internal_state_mut, Parameter, ParameterMeta}; +use crate::parameters::{downcast_internal_state_mut, GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -50,7 +50,7 @@ impl ThresholdParameter { } } -impl Parameter for ThresholdParameter { +impl Parameter for ThresholdParameter { fn meta(&self) -> &ParameterMeta { &self.meta } @@ -64,7 +64,9 @@ impl Parameter for ThresholdParameter { // Initially this is false. Ok(Some(Box::new(false))) } +} +impl GeneralParameter for ThresholdParameter { fn compute( &self, _timestep: &Timestep, diff --git a/pywr-core/src/parameters/vector.rs b/pywr-core/src/parameters/vector.rs index d1c0e1fc..a6be9326 100644 --- a/pywr-core/src/parameters/vector.rs +++ b/pywr-core/src/parameters/vector.rs @@ -1,5 +1,5 @@ use crate::network::Network; -use crate::parameters::{Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; use crate::scenario::ScenarioIndex; use crate::state::{ParameterState, State}; use crate::timestep::Timestep; @@ -19,10 +19,13 @@ impl VectorParameter { } } -impl Parameter for VectorParameter { +impl Parameter for VectorParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +impl GeneralParameter for VectorParameter { fn compute( &self, timestep: &Timestep, diff --git a/pywr-core/src/test_utils.rs b/pywr-core/src/test_utils.rs index ae3c85f4..e4f99ee7 100644 --- a/pywr-core/src/test_utils.rs +++ b/pywr-core/src/test_utils.rs @@ -4,7 +4,7 @@ use crate::models::{Model, ModelDomain}; /// TODO move this to its own local crate ("test-utilities") as part of a workspace. use crate::network::Network; use crate::node::{Constraint, ConstraintValue, StorageInitialVolume}; -use crate::parameters::{AggFunc, AggregatedParameter, Array2Parameter, ConstantParameter, Parameter}; +use crate::parameters::{AggFunc, AggregatedParameter, Array2Parameter, ConstantParameter, GeneralParameter}; use crate::recorders::AssertionRecorder; use crate::scenario::ScenarioGroupCollection; #[cfg(feature = "ipm-ocl")] @@ -161,7 +161,7 @@ pub fn simple_storage_model() -> Model { /// See [`AssertionRecorder`] for more information. pub fn run_and_assert_parameter( model: &mut Model, - parameter: Box>, + parameter: Box>, expected_values: Array2, ulps: Option, epsilon: Option, From a1f80a02f4cf3b4ec66e51a449167728450c886a Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Tue, 4 Jun 2024 22:46:38 +0100 Subject: [PATCH 3/7] feat: Add General, Constant and Simple parameters. This is designed as an alternative to #192. The high level idea is the solve the circular parameter issue with initial volumes by recognising that some parameters do not vary in time and/or depend no node state. We introduce the General, Simple and Constant parameters as new traits: - `GeneralParameter` - rename of the current implementation. - `SimpleParameter` - Parameters that only depend on other Simple or Constant parameters, but do vary in time. - `ConstantParameter` - Parameters that only depend on other Constant parameters, and do *not* vary in time. Simple parameters are evaluated before general and `.before` methods each time-step. This allows them to vary in time, but means that can't depend on network or general parameter values. This is most relevant for profiles which have no dependencies but do vary in time. Constant parameters would be evaluated at the beginning of the simulation only. NB, this is not yet implemented. Some parameters can potentially implement one or more of these traits. For example, the AggregatedParameter is implemented for General and Simple depending on whether it is aggregating over MetricF64 or SimpleMetricF64. This allows the user to use an aggregated parameter as a simple parameter provided it is only aggregating over simple parameters. From the schema point of view there is no difference. When added to the core model it attempts to add it as a simplified version if possible. --- pywr-core/src/aggregated_node.rs | 12 +- pywr-core/src/derived_metric.rs | 6 +- pywr-core/src/lib.rs | 22 +- pywr-core/src/metric.rs | 170 ++++- pywr-core/src/network.rs | 218 +++--- pywr-core/src/node.rs | 202 ++---- pywr-core/src/parameters/aggregated.rs | 107 ++- pywr-core/src/parameters/aggregated_index.rs | 11 +- pywr-core/src/parameters/array.rs | 18 +- pywr-core/src/parameters/asymmetric.rs | 11 +- pywr-core/src/parameters/constant.rs | 18 +- .../parameters/control_curves/apportion.rs | 10 +- .../src/parameters/control_curves/index.rs | 10 +- .../parameters/control_curves/interpolated.rs | 11 +- .../parameters/control_curves/piecewise.rs | 15 +- .../src/parameters/control_curves/simple.rs | 11 +- .../control_curves/volume_between.rs | 48 +- pywr-core/src/parameters/delay.rs | 99 ++- pywr-core/src/parameters/discount_factor.rs | 13 +- pywr-core/src/parameters/division.rs | 11 +- pywr-core/src/parameters/hydropower.rs | 11 +- pywr-core/src/parameters/indexed_array.rs | 11 +- pywr-core/src/parameters/interpolated.rs | 11 +- pywr-core/src/parameters/max.rs | 11 +- pywr-core/src/parameters/min.rs | 11 +- pywr-core/src/parameters/mod.rs | 633 ++++++++++++++++-- pywr-core/src/parameters/negative.rs | 11 +- pywr-core/src/parameters/negativemax.rs | 11 +- pywr-core/src/parameters/negativemin.rs | 11 +- pywr-core/src/parameters/offset.rs | 11 +- pywr-core/src/parameters/polynomial.rs | 11 +- pywr-core/src/parameters/profiles/daily.rs | 17 +- pywr-core/src/parameters/profiles/monthly.rs | 17 +- pywr-core/src/parameters/profiles/rbf.rs | 19 +- .../parameters/profiles/uniform_drawdown.rs | 17 +- pywr-core/src/parameters/profiles/weekly.rs | 17 +- pywr-core/src/parameters/py.rs | 29 +- pywr-core/src/parameters/rhai.rs | 13 +- pywr-core/src/parameters/threshold.rs | 11 +- pywr-core/src/parameters/vector.rs | 11 +- pywr-core/src/solvers/builder.rs | 4 +- pywr-core/src/state.rs | 369 ++++++---- pywr-core/src/test_utils.rs | 68 +- pywr-core/src/virtual_storage.rs | 50 +- pywr-schema/src/metric.rs | 14 +- .../src/nodes/annual_virtual_storage.rs | 11 +- pywr-schema/src/nodes/core.rs | 52 +- pywr-schema/src/nodes/delay.rs | 2 +- pywr-schema/src/nodes/mod.rs | 2 +- .../src/nodes/monthly_virtual_storage.rs | 11 +- pywr-schema/src/nodes/piecewise_storage.rs | 25 +- .../src/nodes/rolling_virtual_storage.rs | 12 +- pywr-schema/src/nodes/turbine.rs | 2 +- pywr-schema/src/nodes/virtual_storage.rs | 11 +- .../src/nodes/water_treatment_works.rs | 23 +- pywr-schema/src/parameters/core.rs | 2 +- pywr-schema/src/parameters/mod.rs | 4 +- pywr-schema/src/parameters/profiles.rs | 13 +- pywr-schema/src/parameters/python.rs | 2 +- .../src/test_models/storage_max_volumes.json | 114 ++++ pywr-schema/src/timeseries/mod.rs | 3 +- 61 files changed, 1956 insertions(+), 755 deletions(-) create mode 100644 pywr-schema/src/test_models/storage_max_volumes.json diff --git a/pywr-core/src/aggregated_node.rs b/pywr-core/src/aggregated_node.rs index 1924dbc5..ae879973 100644 --- a/pywr-core/src/aggregated_node.rs +++ b/pywr-core/src/aggregated_node.rs @@ -1,6 +1,6 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::node::{Constraint, ConstraintValue, FlowConstraints, NodeMeta}; +use crate::node::{Constraint, FlowConstraints, NodeMeta}; use crate::state::State; use crate::{NodeIndex, PywrError}; use std::ops::{Deref, DerefMut}; @@ -112,7 +112,7 @@ impl AggregatedNode { ) -> Self { Self { meta: NodeMeta::new(index, name, sub_name), - flow_constraints: FlowConstraints::new(), + flow_constraints: FlowConstraints::default(), nodes: nodes.to_vec(), factors, } @@ -174,13 +174,13 @@ impl AggregatedNode { } } - pub fn set_min_flow_constraint(&mut self, value: ConstraintValue) { + pub fn set_min_flow_constraint(&mut self, value: Option) { self.flow_constraints.min_flow = value; } pub fn get_min_flow_constraint(&self, model: &Network, state: &State) -> Result { self.flow_constraints.get_min_flow(model, state) } - pub fn set_max_flow_constraint(&mut self, value: ConstraintValue) { + pub fn set_max_flow_constraint(&mut self, value: Option) { self.flow_constraints.max_flow = value; } pub fn get_max_flow_constraint(&self, model: &Network, state: &State) -> Result { @@ -188,7 +188,7 @@ impl AggregatedNode { } /// Set a constraint on a node. - pub fn set_constraint(&mut self, value: ConstraintValue, constraint: Constraint) -> Result<(), PywrError> { + pub fn set_constraint(&mut self, value: Option, constraint: Constraint) -> Result<(), PywrError> { match constraint { Constraint::MinFlow => self.set_min_flow_constraint(value), Constraint::MaxFlow => self.set_max_flow_constraint(value), @@ -321,7 +321,7 @@ mod tests { network.connect_nodes(input_node, link_node1).unwrap(); network.connect_nodes(link_node1, output_node1).unwrap(); - let factors = Some(Factors::Ratio(vec![MetricF64::Constant(2.0), MetricF64::Constant(1.0)])); + let factors = Some(Factors::Ratio(vec![2.0.into(), 1.0.into()])); let _agg_node = network.add_aggregated_node("agg-node", None, &[link_node0, link_node1], factors); diff --git a/pywr-core/src/derived_metric.rs b/pywr-core/src/derived_metric.rs index 0930dc34..61f90980 100644 --- a/pywr-core/src/derived_metric.rs +++ b/pywr-core/src/derived_metric.rs @@ -78,13 +78,13 @@ impl DerivedMetric { pub fn compute(&self, network: &Network, state: &State) -> Result { match self { Self::NodeProportionalVolume(idx) => { - let max_volume = network.get_node(idx)?.get_current_max_volume(network, state)?; + let max_volume = network.get_node(idx)?.get_current_max_volume(state)?; Ok(state .get_network_state() .get_node_proportional_volume(idx, max_volume)?) } Self::VirtualStorageProportionalVolume(idx) => { - let max_volume = network.get_virtual_storage_node(idx)?.get_max_volume(network, state)?; + let max_volume = network.get_virtual_storage_node(idx)?.get_max_volume(state)?; Ok(state .get_network_state() .get_virtual_storage_proportional_volume(*idx, max_volume)?) @@ -100,7 +100,7 @@ impl DerivedMetric { let max_volume: f64 = node .nodes .iter() - .map(|idx| network.get_node(idx)?.get_current_max_volume(network, state)) + .map(|idx| network.get_node(idx)?.get_current_max_volume(state)) .sum::>()?; // TODO handle divide by zero Ok(volume / max_volume) diff --git a/pywr-core/src/lib.rs b/pywr-core/src/lib.rs index 0ec21001..bad59d2b 100644 --- a/pywr-core/src/lib.rs +++ b/pywr-core/src/lib.rs @@ -5,7 +5,7 @@ extern crate core; use crate::derived_metric::DerivedMetricIndex; use crate::models::MultiNetworkTransferIndex; use crate::node::NodeIndex; -use crate::parameters::{InterpolationError, ParameterIndex}; +use crate::parameters::{GeneralParameterIndex, InterpolationError, ParameterIndex, SimpleParameterIndex}; use crate::recorders::{AggregationError, MetricSetIndex, RecorderIndex}; use crate::state::MultiValue; use crate::virtual_storage::VirtualStorageIndex; @@ -51,6 +51,18 @@ pub enum PywrError { IndexParameterIndexNotFound(ParameterIndex), #[error("multi-value parameter index {0} not found")] MultiValueParameterIndexNotFound(ParameterIndex), + #[error("parameter index {0} not found")] + GeneralParameterIndexNotFound(GeneralParameterIndex), + #[error("index parameter index {0} not found")] + GeneralIndexParameterIndexNotFound(GeneralParameterIndex), + #[error("multi-value parameter index {0} not found")] + GeneralMultiValueParameterIndexNotFound(GeneralParameterIndex), + #[error("parameter index {0} not found")] + SimpleParameterIndexNotFound(SimpleParameterIndex), + #[error("index parameter index {0} not found")] + SimpleIndexParameterIndexNotFound(SimpleParameterIndex), + #[error("multi-value parameter index {0} not found")] + SimpleMultiValueParameterIndexNotFound(SimpleParameterIndex), #[error("multi-value parameter key {0} not found")] MultiValueParameterKeyNotFound(String), #[error("inter-network parameter state not initialised")] @@ -76,9 +88,9 @@ pub enum PywrError { #[error("parameter name `{0}` already exists at index {1}")] ParameterNameAlreadyExists(String, ParameterIndex), #[error("index parameter name `{0}` already exists at index {1}")] - IndexParameterNameAlreadyExists(String, ParameterIndex), + IndexParameterNameAlreadyExists(String, GeneralParameterIndex), #[error("multi-value parameter name `{0}` already exists at index {1}")] - MultiValueParameterNameAlreadyExists(String, ParameterIndex), + MultiValueParameterNameAlreadyExists(String, GeneralParameterIndex), #[error("metric set name `{0}` already exists")] MetricSetNameAlreadyExists(String), #[error("recorder name `{0}` already exists at index {1}")] @@ -163,12 +175,16 @@ pub enum PywrError { ParameterNoInitialValue, #[error("parameter state not found for parameter index {0}")] ParameterStateNotFound(ParameterIndex), + #[error("parameter state not found for parameter index {0}")] + GeneralParameterStateNotFound(GeneralParameterIndex), #[error("Could not create timestep range due to following error: {0}")] TimestepRangeGenerationError(String), #[error("Could not create timesteps for frequency '{0}'")] TimestepGenerationError(String), #[error("aggregation error: {0}")] Aggregation(#[from] AggregationError), + #[error("cannot simplify metric")] + CannotSimplifyMetric, } // Python errors diff --git a/pywr-core/src/metric.rs b/pywr-core/src/metric.rs index 4c95cc34..f01f7c5a 100644 --- a/pywr-core/src/metric.rs +++ b/pywr-core/src/metric.rs @@ -5,11 +5,42 @@ use crate::edge::EdgeIndex; use crate::models::MultiNetworkTransferIndex; use crate::network::Network; use crate::node::NodeIndex; -use crate::parameters::ParameterIndex; -use crate::state::{MultiValue, State}; +use crate::parameters::{GeneralParameterIndex, ParameterIndex, SimpleParameterIndex}; +use crate::state::{MultiValue, SimpleParameterValues, State}; use crate::virtual_storage::VirtualStorageIndex; use crate::PywrError; +#[derive(Clone, Debug, PartialEq)] +pub enum ConstantMetricF64 { + Constant(f64), +} + +impl ConstantMetricF64 { + pub fn get_value(&self) -> Result { + match self { + ConstantMetricF64::Constant(v) => Ok(*v), + } + } +} +#[derive(Clone, Debug, PartialEq)] +pub enum SimpleMetricF64 { + ParameterValue(SimpleParameterIndex), + IndexParameterValue(SimpleParameterIndex), + MultiParameterValue((SimpleParameterIndex, String)), + Constant(ConstantMetricF64), +} + +impl SimpleMetricF64 { + pub fn get_value(&self, values: &SimpleParameterValues) -> Result { + match self { + SimpleMetricF64::ParameterValue(idx) => Ok(values.get_simple_parameter_f64(*idx)?), + SimpleMetricF64::IndexParameterValue(idx) => Ok(values.get_simple_parameter_usize(*idx)? as f64), + SimpleMetricF64::MultiParameterValue((idx, key)) => Ok(values.get_simple_multi_parameter_f64(*idx, key)?), + SimpleMetricF64::Constant(m) => m.get_value(), + } + } +} + #[derive(Clone, Debug, PartialEq)] pub enum MetricF64 { NodeInFlow(NodeIndex), @@ -19,16 +50,16 @@ pub enum MetricF64 { AggregatedNodeOutFlow(AggregatedNodeIndex), AggregatedNodeVolume(AggregatedStorageNodeIndex), EdgeFlow(EdgeIndex), - ParameterValue(ParameterIndex), - IndexParameterValue(ParameterIndex), - MultiParameterValue((ParameterIndex, String)), + ParameterValue(GeneralParameterIndex), + IndexParameterValue(GeneralParameterIndex), + MultiParameterValue((GeneralParameterIndex, String)), VirtualStorageVolume(VirtualStorageIndex), MultiNodeInFlow { indices: Vec, name: String }, MultiNodeOutFlow { indices: Vec, name: String }, // TODO implement other MultiNodeXXX variants - Constant(f64), DerivedMetric(DerivedMetricIndex), InterNetworkTransfer(MultiNetworkTransferIndex), + Simple(SimpleMetricF64), } impl MetricF64 { @@ -60,7 +91,7 @@ impl MetricF64 { MetricF64::MultiParameterValue((idx, key)) => Ok(state.get_multi_parameter_value(*idx, key)?), MetricF64::VirtualStorageVolume(idx) => Ok(state.get_network_state().get_virtual_storage_volume(idx)?), MetricF64::DerivedMetric(idx) => state.get_derived_metric_value(*idx), - MetricF64::Constant(v) => Ok(*v), + MetricF64::AggregatedNodeVolume(idx) => { let node = model.get_aggregated_storage_node(idx)?; node.nodes @@ -84,42 +115,129 @@ impl MetricF64 { Ok(flow) } MetricF64::InterNetworkTransfer(idx) => state.get_inter_network_transfer_value(*idx), + MetricF64::Simple(s) => s.get_value(&state.get_simple_parameter_values()), } } } -#[derive(Clone, Debug, PartialEq)] -pub enum MetricUsize { - IndexParameterValue(ParameterIndex), - Constant(usize), +impl TryFrom for SimpleMetricF64 { + type Error = PywrError; + + fn try_from(value: MetricF64) -> Result { + match value { + MetricF64::Simple(s) => Ok(s), + _ => Err(PywrError::CannotSimplifyMetric), + } + } } -impl MetricUsize { - pub fn get_value(&self, _network: &Network, state: &State) -> Result { - match self { - Self::IndexParameterValue(idx) => state.get_parameter_index(*idx), - Self::Constant(i) => Ok(*i), +impl TryFrom for ConstantMetricF64 { + type Error = PywrError; + + fn try_from(value: SimpleMetricF64) -> Result { + match value { + SimpleMetricF64::Constant(c) => Ok(c), + _ => Err(PywrError::CannotSimplifyMetric), } } +} + +impl From for ConstantMetricF64 { + fn from(v: f64) -> Self { + ConstantMetricF64::Constant(v) + } +} - pub fn name<'a>(&'a self, network: &'a Network) -> Result<&'a str, PywrError> { - match self { - Self::IndexParameterValue(idx) => network.get_index_parameter(*idx).map(|p| p.name()), - Self::Constant(_) => Ok(""), +impl From for SimpleMetricF64 +where + T: Into, +{ + fn from(v: T) -> Self { + SimpleMetricF64::Constant(v.into()) + } +} +impl From for MetricF64 +where + T: Into, +{ + fn from(v: T) -> Self { + MetricF64::Simple(v.into()) + } +} + +impl From> for MetricF64 { + fn from(idx: ParameterIndex) -> Self { + match idx { + ParameterIndex::General(idx) => Self::ParameterValue(idx), + ParameterIndex::Simple(idx) => Self::Simple(SimpleMetricF64::ParameterValue(idx)), + } + } +} + +impl From> for MetricF64 { + fn from(idx: ParameterIndex) -> Self { + match idx { + ParameterIndex::General(idx) => Self::IndexParameterValue(idx), + ParameterIndex::Simple(idx) => Self::Simple(SimpleMetricF64::IndexParameterValue(idx)), } } +} + +impl TryFrom> for SimpleMetricF64 { + type Error = PywrError; + fn try_from(idx: ParameterIndex) -> Result { + match idx { + ParameterIndex::Simple(idx) => Ok(Self::ParameterValue(idx)), + _ => Err(PywrError::CannotSimplifyMetric), + } + } +} + +impl TryFrom> for SimpleMetricUsize { + type Error = PywrError; + fn try_from(idx: ParameterIndex) -> Result { + match idx { + ParameterIndex::Simple(idx) => Ok(Self::IndexParameterValue(idx)), + _ => Err(PywrError::CannotSimplifyMetric), + } + } +} - pub fn sub_name<'a>(&'a self, _network: &'a Network) -> Result, PywrError> { +#[derive(Clone, Debug, PartialEq)] +pub enum SimpleMetricUsize { + IndexParameterValue(SimpleParameterIndex), +} + +impl SimpleMetricUsize { + pub fn get_value(&self, values: &SimpleParameterValues) -> Result { match self { - Self::IndexParameterValue(_) => Ok(None), - Self::Constant(_) => Ok(None), + SimpleMetricUsize::IndexParameterValue(idx) => values.get_simple_parameter_usize(*idx), } } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum MetricUsize { + IndexParameterValue(GeneralParameterIndex), + Simple(SimpleMetricUsize), + Constant(usize), +} - pub fn attribute(&self) -> &str { +impl MetricUsize { + pub fn get_value(&self, _network: &Network, state: &State) -> Result { match self { - Self::IndexParameterValue(_) => "value", - Self::Constant(_) => "value", + Self::IndexParameterValue(idx) => state.get_parameter_index(*idx), + Self::Simple(s) => s.get_value(&state.get_simple_parameter_values()), + Self::Constant(i) => Ok(*i), + } + } +} + +impl From> for MetricUsize { + fn from(idx: ParameterIndex) -> Self { + match idx { + ParameterIndex::General(idx) => Self::IndexParameterValue(idx), + ParameterIndex::Simple(idx) => Self::Simple(SimpleMetricUsize::IndexParameterValue(idx)), } } } diff --git a/pywr-core/src/network.rs b/pywr-core/src/network.rs index 862cf22c..94f4caac 100644 --- a/pywr-core/src/network.rs +++ b/pywr-core/src/network.rs @@ -2,17 +2,17 @@ use crate::aggregated_node::{AggregatedNode, AggregatedNodeIndex, AggregatedNode use crate::aggregated_storage_node::{AggregatedStorageNode, AggregatedStorageNodeIndex, AggregatedStorageNodeVec}; use crate::derived_metric::{DerivedMetric, DerivedMetricIndex}; use crate::edge::{Edge, EdgeIndex, EdgeVec}; -use crate::metric::MetricF64; +use crate::metric::{MetricF64, SimpleMetricF64}; use crate::models::ModelDomain; -use crate::node::{ConstraintValue, Node, NodeVec, StorageInitialVolume}; -use crate::parameters::{ParameterCollection, ParameterType, VariableConfig}; +use crate::node::{Node, NodeVec, StorageInitialVolume}; +use crate::parameters::{GeneralParameterType, ParameterCollection, ParameterIndex, ParameterStates, VariableConfig}; use crate::recorders::{MetricSet, MetricSetIndex, MetricSetState}; use crate::scenario::ScenarioIndex; use crate::solvers::{MultiStateSolver, Solver, SolverFeatures, SolverTimings}; -use crate::state::{MultiValue, ParameterStates, State, StateBuilder}; +use crate::state::{MultiValue, State, StateBuilder}; use crate::timestep::Timestep; use crate::virtual_storage::{VirtualStorage, VirtualStorageBuilder, VirtualStorageIndex, VirtualStorageVec}; -use crate::{parameters, recorders, NodeIndex, ParameterIndex, PywrError, RecorderIndex}; +use crate::{parameters, recorders, GeneralParameterIndex, NodeIndex, PywrError, RecorderIndex}; use rayon::prelude::*; use std::any::Any; use std::collections::HashSet; @@ -140,7 +140,7 @@ impl RunTimings { enum ComponentType { Node(NodeIndex), VirtualStorageNode(VirtualStorageIndex), - Parameter(ParameterType), + Parameter(GeneralParameterType), DerivedMetric(DerivedMetricIndex), } @@ -244,14 +244,9 @@ impl Network { let initial_virtual_storage_states = self.virtual_storage_nodes.iter().map(|n| n.default_state()).collect(); - let (initial_values_states, initial_indices_states, initial_multi_param_states) = - self.parameters.initial_states(timesteps, scenario_index)?; - let state_builder = StateBuilder::new(initial_node_states, self.edges.len()) .with_virtual_storage_states(initial_virtual_storage_states) - .with_value_parameters(initial_values_states.len()) - .with_index_parameters(initial_indices_states.len()) - .with_multi_parameters(initial_multi_param_states.len()) + .with_parameters(&self.parameters) .with_derived_metrics(self.derived_metrics.len()) .with_inter_network_transfers(num_inter_network_transfers); @@ -259,11 +254,11 @@ impl Network { states.push(state); - parameter_internal_states.push(ParameterStates::new( - initial_values_states, - initial_indices_states, - initial_multi_param_states, - )); + parameter_internal_states.push(ParameterStates::from_collection( + &self.parameters, + timesteps, + scenario_index, + )?); metric_set_internal_states.push(self.metric_sets.iter().map(|p| p.setup()).collect::>()); } @@ -588,28 +583,32 @@ impl Network { ) -> Result<(), PywrError> { // TODO reset parameter state to zero + // First we update the simple parameters + self.parameters + .compute_simple(timestep, scenario_index, state, internal_states)?; + for c_type in &self.resolve_order { match c_type { ComponentType::Node(idx) => { let n = self.nodes.get(idx)?; - n.before(timestep, self, state)?; + n.before(timestep, state)?; } ComponentType::VirtualStorageNode(idx) => { let n = self.virtual_storage_nodes.get(idx)?; - n.before(timestep, self, state)?; + n.before(timestep, state)?; } ComponentType::Parameter(p_type) => { match p_type { - ParameterType::Parameter(idx) => { + GeneralParameterType::Parameter(idx) => { // Find the parameter itself let p = self .parameters - .get_f64(*idx) - .ok_or(PywrError::ParameterIndexNotFound(*idx))?; + .get_general_f64(*idx) + .ok_or(PywrError::GeneralParameterIndexNotFound(*idx))?; // .. and its internal state let internal_state = internal_states - .get_mut_value_state(*idx) - .ok_or(PywrError::ParameterIndexNotFound(*idx))?; + .get_general_mut_f64_state(*idx) + .ok_or(PywrError::GeneralParameterIndexNotFound(*idx))?; let value = p.compute(timestep, scenario_index, self, state, internal_state)?; @@ -619,31 +618,31 @@ impl Network { } state.set_parameter_value(*idx, value)?; } - ParameterType::Index(idx) => { + GeneralParameterType::Index(idx) => { let p = self .parameters .get_usize(*idx) - .ok_or(PywrError::IndexParameterIndexNotFound(*idx))?; + .ok_or(PywrError::GeneralIndexParameterIndexNotFound(*idx))?; // .. and its internal state let internal_state = internal_states - .get_mut_index_state(*idx) - .ok_or(PywrError::IndexParameterIndexNotFound(*idx))?; + .get_general_mut_usize_state(*idx) + .ok_or(PywrError::GeneralIndexParameterIndexNotFound(*idx))?; let value = p.compute(timestep, scenario_index, self, state, internal_state)?; // debug!("Current value of index parameter {}: {}", p.name(), value); state.set_parameter_index(*idx, value)?; } - ParameterType::Multi(idx) => { + GeneralParameterType::Multi(idx) => { let p = self .parameters .get_multi(*idx) - .ok_or(PywrError::MultiValueParameterIndexNotFound(*idx))?; + .ok_or(PywrError::GeneralMultiValueParameterIndexNotFound(*idx))?; // .. and its internal state let internal_state = internal_states - .get_mut_multi_state(*idx) - .ok_or(PywrError::MultiValueParameterIndexNotFound(*idx))?; + .get_general_mut_multi_state(*idx) + .ok_or(PywrError::GeneralMultiValueParameterIndexNotFound(*idx))?; let value = p.compute(timestep, scenario_index, self, state, internal_state)?; // debug!("Current value of index parameter {}: {}", p.name(), value); @@ -694,42 +693,42 @@ impl Network { } ComponentType::Parameter(p_type) => { match p_type { - ParameterType::Parameter(idx) => { + GeneralParameterType::Parameter(idx) => { // Find the parameter itself let p = self .parameters - .get_f64(*idx) - .ok_or(PywrError::ParameterIndexNotFound(*idx))?; + .get_general_f64(*idx) + .ok_or(PywrError::GeneralParameterIndexNotFound(*idx))?; // .. and its internal state let internal_state = internal_states - .get_mut_value_state(*idx) - .ok_or(PywrError::ParameterIndexNotFound(*idx))?; + .get_general_mut_f64_state(*idx) + .ok_or(PywrError::GeneralParameterIndexNotFound(*idx))?; p.after(timestep, scenario_index, self, state, internal_state)?; } - ParameterType::Index(idx) => { + GeneralParameterType::Index(idx) => { let p = self .parameters .get_usize(*idx) - .ok_or(PywrError::IndexParameterIndexNotFound(*idx))?; + .ok_or(PywrError::GeneralIndexParameterIndexNotFound(*idx))?; // .. and its internal state let internal_state = internal_states - .get_mut_index_state(*idx) - .ok_or(PywrError::IndexParameterIndexNotFound(*idx))?; + .get_general_mut_usize_state(*idx) + .ok_or(PywrError::GeneralIndexParameterIndexNotFound(*idx))?; p.after(timestep, scenario_index, self, state, internal_state)?; } - ParameterType::Multi(idx) => { + GeneralParameterType::Multi(idx) => { let p = self .parameters .get_multi(*idx) - .ok_or(PywrError::MultiValueParameterIndexNotFound(*idx))?; + .ok_or(PywrError::GeneralMultiValueParameterIndexNotFound(*idx))?; // .. and its internal state let internal_state = internal_states - .get_mut_multi_state(*idx) - .ok_or(PywrError::MultiValueParameterIndexNotFound(*idx))?; + .get_general_mut_multi_state(*idx) + .ok_or(PywrError::GeneralMultiValueParameterIndexNotFound(*idx))?; p.after(timestep, scenario_index, self, state, internal_state)?; } @@ -810,7 +809,7 @@ impl Network { &mut self, name: &str, sub_name: Option<&str>, - value: ConstraintValue, + value: Option, ) -> Result<(), PywrError> { let node = self.get_mut_node_by_name(name, sub_name)?; node.set_cost(value); @@ -821,7 +820,7 @@ impl Network { &mut self, name: &str, sub_name: Option<&str>, - value: ConstraintValue, + value: Option, ) -> Result<(), PywrError> { let node = self.get_mut_node_by_name(name, sub_name)?; node.set_max_flow_constraint(value) @@ -831,12 +830,32 @@ impl Network { &mut self, name: &str, sub_name: Option<&str>, - value: ConstraintValue, + value: Option, ) -> Result<(), PywrError> { let node = self.get_mut_node_by_name(name, sub_name)?; node.set_min_flow_constraint(value) } + pub fn set_node_max_volume( + &mut self, + name: &str, + sub_name: Option<&str>, + value: Option, + ) -> Result<(), PywrError> { + let node = self.get_mut_node_by_name(name, sub_name)?; + node.set_max_volume_constraint(value) + } + + pub fn set_node_min_volume( + &mut self, + name: &str, + sub_name: Option<&str>, + value: Option, + ) -> Result<(), PywrError> { + let node = self.get_mut_node_by_name(name, sub_name)?; + node.set_min_volume_constraint(value) + } + /// Get a `AggregatedNodeIndex` from a node's name pub fn get_aggregated_node(&self, index: &AggregatedNodeIndex) -> Result<&AggregatedNode, PywrError> { self.aggregated_nodes.get(index) @@ -893,7 +912,7 @@ impl Network { &mut self, name: &str, sub_name: Option<&str>, - value: ConstraintValue, + value: Option, ) -> Result<(), PywrError> { let node = self.get_mut_aggregated_node_by_name(name, sub_name)?; node.set_max_flow_constraint(value); @@ -904,7 +923,7 @@ impl Network { &mut self, name: &str, sub_name: Option<&str>, - value: ConstraintValue, + value: Option, ) -> Result<(), PywrError> { let node = self.get_mut_aggregated_node_by_name(name, sub_name)?; node.set_min_flow_constraint(value); @@ -1075,10 +1094,7 @@ impl Network { } /// Get a `Parameter` from a parameter's name - pub fn get_parameter( - &self, - index: ParameterIndex, - ) -> Result<&dyn parameters::GeneralParameter, PywrError> { + pub fn get_parameter(&self, index: ParameterIndex) -> Result<&dyn parameters::Parameter, PywrError> { match self.parameters.get_f64(index) { Some(p) => Ok(p), None => Err(PywrError::ParameterIndexNotFound(index)), @@ -1086,7 +1102,7 @@ impl Network { } /// Get a `Parameter` from a parameter's name - pub fn get_parameter_by_name(&self, name: &str) -> Result<&dyn parameters::GeneralParameter, PywrError> { + pub fn get_parameter_by_name(&self, name: &str) -> Result<&dyn parameters::Parameter, PywrError> { match self.parameters.get_f64_by_name(name) { Some(parameter) => Ok(parameter), None => Err(PywrError::ParameterNotFound(name.to_string())), @@ -1104,11 +1120,11 @@ impl Network { /// Get a [`Parameter`] from its index. pub fn get_index_parameter( &self, - index: ParameterIndex, + index: GeneralParameterIndex, ) -> Result<&dyn parameters::GeneralParameter, PywrError> { match self.parameters.get_usize(index) { Some(p) => Ok(p), - None => Err(PywrError::IndexParameterIndexNotFound(index)), + None => Err(PywrError::GeneralIndexParameterIndexNotFound(index)), } } @@ -1124,7 +1140,7 @@ impl Network { } /// Get a `IndexParameterIndex` from a parameter's name - pub fn get_index_parameter_index_by_name(&self, name: &str) -> Result, PywrError> { + pub fn get_index_parameter_index_by_name(&self, name: &str) -> Result, PywrError> { match self.parameters.get_usize_index_by_name(name) { Some(idx) => Ok(idx), None => Err(PywrError::ParameterNotFound(name.to_string())), @@ -1134,11 +1150,11 @@ impl Network { /// Get a `MultiValueParameterIndex` from a parameter's name pub fn get_multi_valued_parameter( &self, - index: ParameterIndex, + index: GeneralParameterIndex, ) -> Result<&dyn parameters::GeneralParameter, PywrError> { match self.parameters.get_multi(index) { Some(p) => Ok(p), - None => Err(PywrError::MultiValueParameterIndexNotFound(index)), + None => Err(PywrError::GeneralMultiValueParameterIndexNotFound(index)), } } @@ -1146,7 +1162,7 @@ impl Network { pub fn get_multi_valued_parameter_index_by_name( &self, name: &str, - ) -> Result, PywrError> { + ) -> Result, PywrError> { match self.parameters.get_multi_index_by_name(name) { Some(idx) => Ok(idx), None => Err(PywrError::ParameterNotFound(name.to_string())), @@ -1226,8 +1242,8 @@ impl Network { name: &str, sub_name: Option<&str>, initial_volume: StorageInitialVolume, - min_volume: ConstraintValue, - max_volume: ConstraintValue, + min_volume: Option, + max_volume: Option, ) -> Result { // Check for name. // TODO move this check to `NodeVec` @@ -1296,27 +1312,42 @@ impl Network { Ok(vs_node_index) } - /// Add a `parameters::Parameter` to the network + /// Add a [`parameters::GeneralParameter`] to the network pub fn add_parameter( &mut self, parameter: Box>, ) -> Result, PywrError> { - let parameter_index = self.parameters.add_f64(parameter)?; - // add it to the resolve order - self.resolve_order - .push(ComponentType::Parameter(ParameterType::Parameter(parameter_index))); + let parameter_index = self.parameters.add_general_f64(parameter)?; + + // add it to the general resolve order (simple and constant parameters are resolved separately) + if let ParameterIndex::General(idx) = parameter_index { + self.resolve_order.push(ComponentType::Parameter(idx.into())); + } + Ok(parameter_index) } + /// Add a [`parameters::SimpleParameter`] to the network + pub fn add_simple_parameter( + &mut self, + parameter: Box>, + ) -> Result, PywrError> { + let parameter_index = self.parameters.add_simple_f64(parameter)?; + + Ok(parameter_index.into()) + } + /// Add a `parameters::IndexParameter` to the network pub fn add_index_parameter( &mut self, parameter: Box>, ) -> Result, PywrError> { - let parameter_index = self.parameters.add_usize(parameter)?; - // add it to the resolve order - self.resolve_order - .push(ComponentType::Parameter(ParameterType::Index(parameter_index))); + let parameter_index = self.parameters.add_general_usize(parameter)?; + // add it to the general resolve order (simple and constant parameters are resolved separately) + if let ParameterIndex::General(idx) = parameter_index { + self.resolve_order.push(ComponentType::Parameter(idx.into())); + } + Ok(parameter_index) } @@ -1325,11 +1356,12 @@ impl Network { &mut self, parameter: Box>, ) -> Result, PywrError> { - let parameter_index = self.parameters.add_multi(parameter)?; + let parameter_index = self.parameters.add_general_multi(parameter)?; + // add it to the general resolve order (simple and constant parameters are resolved separately) + if let ParameterIndex::General(idx) = parameter_index { + self.resolve_order.push(ComponentType::Parameter(idx.into())); + } - // add it to the resolve order - self.resolve_order - .push(ComponentType::Parameter(ParameterType::Multi(parameter_index))); Ok(parameter_index) } @@ -1423,7 +1455,7 @@ impl Network { // Iterate over all scenarios and set the variable values for parameter_states in state.iter_parameter_states_mut() { let internal_state = parameter_states - .get_mut_value_state(parameter_index) + .get_mut_f64_state(parameter_index) .ok_or(PywrError::ParameterStateNotFound(parameter_index))?; variable.set_variables(values, variable_config, internal_state)?; @@ -1453,7 +1485,7 @@ impl Network { Some(variable) => { let internal_state = state .parameter_states_mut(&scenario_index) - .get_mut_value_state(parameter_index) + .get_mut_f64_state(parameter_index) .ok_or(PywrError::ParameterStateNotFound(parameter_index))?; variable.set_variables(values, variable_config, internal_state) } @@ -1475,7 +1507,7 @@ impl Network { Some(variable) => { let internal_state = state .parameter_states(&scenario_index) - .get_value_state(parameter_index) + .get_f64_state(parameter_index) .ok_or(PywrError::ParameterStateNotFound(parameter_index))?; Ok(variable.get_variables(internal_state)) @@ -1498,7 +1530,7 @@ impl Network { .iter_parameter_states() .map(|parameter_states| { let internal_state = parameter_states - .get_value_state(parameter_index) + .get_f64_state(parameter_index) .ok_or(PywrError::ParameterStateNotFound(parameter_index))?; Ok(variable.get_variables(internal_state)) @@ -1529,7 +1561,7 @@ impl Network { // Iterate over all scenarios and set the variable values for parameter_states in state.iter_parameter_states_mut() { let internal_state = parameter_states - .get_mut_value_state(parameter_index) + .get_mut_f64_state(parameter_index) .ok_or(PywrError::ParameterStateNotFound(parameter_index))?; variable.set_variables(values, variable_config, internal_state)?; @@ -1559,7 +1591,7 @@ impl Network { Some(variable) => { let internal_state = state .parameter_states_mut(&scenario_index) - .get_mut_value_state(parameter_index) + .get_mut_f64_state(parameter_index) .ok_or(PywrError::ParameterIndexNotFound(parameter_index))?; variable.set_variables(values, variable_config, internal_state) } @@ -1581,7 +1613,7 @@ impl Network { Some(variable) => { let internal_state = state .parameter_states(&scenario_index) - .get_value_state(parameter_index) + .get_f64_state(parameter_index) .ok_or(PywrError::ParameterStateNotFound(parameter_index))?; Ok(variable.get_variables(internal_state)) } @@ -1689,11 +1721,8 @@ mod tests { // assign the new parameter to one of the nodes. let node = network.get_mut_node_by_name("input", None).unwrap(); - node.set_constraint( - ConstraintValue::Metric(MetricF64::ParameterValue(parameter)), - Constraint::MaxFlow, - ) - .unwrap(); + node.set_constraint(ConstraintValue::Metric(parameter.into()), Constraint::MaxFlow) + .unwrap(); // Try to assign a constraint not defined for particular node type assert_eq!( @@ -1749,7 +1778,7 @@ mod tests { let idx = model.network().get_parameter_index_by_name("total-demand").unwrap(); let expected = Array2::from_elem((366, 10), 12.0); - let recorder = AssertionRecorder::new("total-demand", MetricF64::ParameterValue(idx), expected, None, None); + let recorder = AssertionRecorder::new("total-demand", idx.into(), expected, None, None); model.network_mut().add_recorder(Box::new(recorder)).unwrap(); // Test all solvers @@ -1808,12 +1837,12 @@ mod tests { "interp", MetricF64::DerivedMetric(dm_idx), vec![], - vec![MetricF64::Constant(100.0), MetricF64::Constant(0.0)], + vec![100.0.into(), 0.0.into()], ); let p_idx = network.add_parameter(Box::new(cc)).unwrap(); let expected = Array2::from_shape_fn((15, 10), |(i, _j)| (100.0 - 10.0 * i as f64).max(0.0)); - let recorder = AssertionRecorder::new("reservoir-cc", MetricF64::ParameterValue(p_idx), expected, None, None); + let recorder = AssertionRecorder::new("reservoir-cc", p_idx.into(), expected, None, None); network.add_recorder(Box::new(recorder)).unwrap(); // Test all solvers @@ -1934,11 +1963,8 @@ mod tests { // assign the new parameter to one of the nodes. let node = model.network_mut().get_mut_node_by_name("input", None).unwrap(); - node.set_constraint( - ConstraintValue::Metric(MetricF64::ParameterValue(input_max_flow_idx)), - Constraint::MaxFlow, - ) - .unwrap(); + node.set_constraint(ConstraintValue::Metric(input_max_flow_idx.into()), Constraint::MaxFlow) + .unwrap(); let mut state = model.setup::(&ClpSolverSettings::default()).unwrap(); diff --git a/pywr-core/src/node.rs b/pywr-core/src/node.rs index a5b9bcc4..b5631295 100644 --- a/pywr-core/src/node.rs +++ b/pywr-core/src/node.rs @@ -1,7 +1,7 @@ use crate::edge::EdgeIndex; -use crate::metric::MetricF64; +use crate::metric::{MetricF64, SimpleMetricF64}; use crate::network::Network; -use crate::state::{NodeState, State}; +use crate::state::{NodeState, SimpleParameterValues, State}; use crate::timestep::Timestep; use crate::virtual_storage::VirtualStorageIndex; use crate::PywrError; @@ -86,8 +86,8 @@ impl NodeVec { name: &str, sub_name: Option<&str>, initial_volume: StorageInitialVolume, - min_volume: ConstraintValue, - max_volume: ConstraintValue, + min_volume: Option, + max_volume: Option, ) -> NodeIndex { let node_index = NodeIndex(self.nodes.len()); let node = Node::new_storage(&node_index, name, sub_name, initial_volume, min_volume, max_volume); @@ -105,25 +105,6 @@ pub enum Constraint { MaxVolume, } -#[derive(Debug, Clone, PartialEq)] -pub enum ConstraintValue { - None, - Scalar(f64), - Metric(MetricF64), -} - -impl From for ConstraintValue { - fn from(v: f64) -> Self { - ConstraintValue::Scalar(v) - } -} - -impl From for ConstraintValue { - fn from(metric: MetricF64) -> Self { - Self::Metric(metric) - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CostAggFunc { Sum, @@ -153,8 +134,8 @@ impl Node { name: &str, sub_name: Option<&str>, initial_volume: StorageInitialVolume, - min_volume: ConstraintValue, - max_volume: ConstraintValue, + min_volume: Option, + max_volume: Option, ) -> Self { Self::Storage(StorageNode::new( node_index, @@ -352,32 +333,17 @@ impl Node { // } // } - pub fn before(&self, timestep: &Timestep, network: &Network, state: &mut State) -> Result<(), PywrError> { + pub fn before(&self, timestep: &Timestep, state: &mut State) -> Result<(), PywrError> { // Currently only storage nodes do something during before match self { Node::Input(_) => Ok(()), Node::Output(_) => Ok(()), Node::Link(_) => Ok(()), - Node::Storage(n) => n.before(timestep, network, state), + Node::Storage(n) => n.before(timestep, state), } } - /// Set a constraint on a node. - pub fn set_constraint(&mut self, value: ConstraintValue, constraint: Constraint) -> Result<(), PywrError> { - match constraint { - Constraint::MinFlow => self.set_min_flow_constraint(value)?, - Constraint::MaxFlow => self.set_max_flow_constraint(value)?, - Constraint::MinAndMaxFlow => { - self.set_min_flow_constraint(value.clone())?; - self.set_max_flow_constraint(value)?; - } - Constraint::MinVolume => self.set_min_volume_constraint(value)?, - Constraint::MaxVolume => self.set_max_volume_constraint(value)?, - } - Ok(()) - } - - pub fn set_min_flow_constraint(&mut self, value: ConstraintValue) -> Result<(), PywrError> { + pub fn set_min_flow_constraint(&mut self, value: Option) -> Result<(), PywrError> { match self { Self::Input(n) => { n.set_min_flow(value); @@ -404,7 +370,7 @@ impl Node { } } - pub fn set_max_flow_constraint(&mut self, value: ConstraintValue) -> Result<(), PywrError> { + pub fn set_max_flow_constraint(&mut self, value: Option) -> Result<(), PywrError> { match self { Self::Input(n) => { n.set_max_flow(value); @@ -450,7 +416,7 @@ impl Node { } } - pub fn set_min_volume_constraint(&mut self, value: ConstraintValue) -> Result<(), PywrError> { + pub fn set_min_volume_constraint(&mut self, value: Option) -> Result<(), PywrError> { match self { Self::Input(_) => Err(PywrError::StorageConstraintsUndefined), Self::Link(_) => Err(PywrError::StorageConstraintsUndefined), @@ -462,16 +428,16 @@ impl Node { } } - pub fn get_current_min_volume(&self, network: &Network, state: &State) -> Result { + pub fn get_current_min_volume(&self, state: &State) -> Result { match self { Self::Input(_) => Err(PywrError::StorageConstraintsUndefined), Self::Link(_) => Err(PywrError::StorageConstraintsUndefined), Self::Output(_) => Err(PywrError::StorageConstraintsUndefined), - Self::Storage(n) => n.get_min_volume(network, state), + Self::Storage(n) => n.get_min_volume(state), } } - pub fn set_max_volume_constraint(&mut self, value: ConstraintValue) -> Result<(), PywrError> { + pub fn set_max_volume_constraint(&mut self, value: Option) -> Result<(), PywrError> { match self { Self::Input(_) => Err(PywrError::StorageConstraintsUndefined), Self::Link(_) => Err(PywrError::StorageConstraintsUndefined), @@ -483,34 +449,24 @@ impl Node { } } - pub fn get_current_max_volume(&self, network: &Network, state: &State) -> Result { + pub fn get_current_max_volume(&self, state: &State) -> Result { match self { Self::Input(_) => Err(PywrError::StorageConstraintsUndefined), Self::Link(_) => Err(PywrError::StorageConstraintsUndefined), Self::Output(_) => Err(PywrError::StorageConstraintsUndefined), - Self::Storage(n) => n.get_max_volume(network, state), + Self::Storage(n) => n.get_max_volume(state), } } - pub fn get_current_volume_bounds(&self, network: &Network, state: &State) -> Result<(f64, f64), PywrError> { - match ( - self.get_current_min_volume(network, state), - self.get_current_max_volume(network, state), - ) { + pub fn get_current_volume_bounds(&self, state: &State) -> Result<(f64, f64), PywrError> { + match (self.get_current_min_volume(state), self.get_current_max_volume(state)) { (Ok(min_vol), Ok(max_vol)) => Ok((min_vol, max_vol)), _ => Err(PywrError::FlowConstraintsUndefined), } } - pub fn get_current_available_volume_bounds( - &self, - network: &Network, - state: &State, - ) -> Result<(f64, f64), PywrError> { - match ( - self.get_current_min_volume(network, state), - self.get_current_max_volume(network, state), - ) { + pub fn get_current_available_volume_bounds(&self, state: &State) -> Result<(f64, f64), PywrError> { + match (self.get_current_min_volume(state), self.get_current_max_volume(state)) { (Ok(min_vol), Ok(max_vol)) => { let current_volume = state.get_network_state().get_node_volume(&self.index())?; @@ -523,7 +479,7 @@ impl Node { } } - pub fn set_cost(&mut self, value: ConstraintValue) { + pub fn set_cost(&mut self, value: Option) { match self { Self::Input(n) => n.set_cost(value), Self::Link(n) => n.set_cost(value), @@ -598,73 +554,63 @@ where } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Default)] pub struct FlowConstraints { - pub(crate) min_flow: ConstraintValue, - pub(crate) max_flow: ConstraintValue, + pub min_flow: Option, + pub max_flow: Option, } impl FlowConstraints { - pub(crate) fn new() -> Self { - Self { - min_flow: ConstraintValue::None, - max_flow: ConstraintValue::None, - } - } /// Return the current minimum flow from the parameter state /// /// Defaults to zero if no parameter is defined. - pub(crate) fn get_min_flow(&self, network: &Network, state: &State) -> Result { + pub fn get_min_flow(&self, network: &Network, state: &State) -> Result { match &self.min_flow { - ConstraintValue::None => Ok(0.0), - ConstraintValue::Scalar(v) => Ok(*v), - ConstraintValue::Metric(m) => m.get_value(network, state), + None => Ok(0.0), + Some(m) => m.get_value(network, state), } } /// Return the current maximum flow from the parameter state /// /// Defaults to f64::MAX if no parameter is defined. - pub(crate) fn get_max_flow(&self, network: &Network, state: &State) -> Result { + pub fn get_max_flow(&self, network: &Network, state: &State) -> Result { match &self.max_flow { - ConstraintValue::None => Ok(f64::MAX), // TODO should this return infinity? - ConstraintValue::Scalar(v) => Ok(*v), - ConstraintValue::Metric(m) => m.get_value(network, state), + None => Ok(f64::MAX), // TODO should this return infinity? + Some(m) => m.get_value(network, state), } } - pub(crate) fn is_max_flow_unconstrained(&self) -> bool { - self.max_flow == ConstraintValue::None + pub fn is_max_flow_unconstrained(&self) -> bool { + self.max_flow.is_none() } } #[derive(Debug, PartialEq)] pub struct StorageConstraints { - pub(crate) min_volume: ConstraintValue, - pub(crate) max_volume: ConstraintValue, + pub(crate) min_volume: Option, + pub(crate) max_volume: Option, } impl StorageConstraints { - pub fn new(min_volume: ConstraintValue, max_volume: ConstraintValue) -> Self { + pub fn new(min_volume: Option, max_volume: Option) -> Self { Self { min_volume, max_volume } } /// Return the current minimum volume from the parameter state /// /// Defaults to zero if no parameter is defined. - pub fn get_min_volume(&self, network: &Network, state: &State) -> Result { + pub fn get_min_volume(&self, values: &SimpleParameterValues) -> Result { match &self.min_volume { - ConstraintValue::None => Ok(f64::MAX), - ConstraintValue::Scalar(v) => Ok(*v), - ConstraintValue::Metric(m) => m.get_value(network, state), + None => Ok(0.0), + Some(m) => m.get_value(values), } } /// Return the current maximum volume from the metric state /// /// Defaults to f64::MAX if no parameter is defined. - pub fn get_max_volume(&self, network: &Network, state: &State) -> Result { + pub fn get_max_volume(&self, values: &SimpleParameterValues) -> Result { match &self.max_volume { - ConstraintValue::None => Ok(f64::MAX), - ConstraintValue::Scalar(v) => Ok(*v), - ConstraintValue::Metric(m) => m.get_value(network, state), + None => Ok(f64::MAX), + Some(m) => m.get_value(values), } } } @@ -672,7 +618,7 @@ impl StorageConstraints { /// Generic cost data for a node. #[derive(Debug, PartialEq)] struct NodeCost { - local: ConstraintValue, + local: Option, virtual_storage_nodes: Vec, agg_func: CostAggFunc, } @@ -680,7 +626,7 @@ struct NodeCost { impl Default for NodeCost { fn default() -> Self { Self { - local: ConstraintValue::None, + local: None, virtual_storage_nodes: Vec::new(), agg_func: CostAggFunc::Max, } @@ -690,9 +636,8 @@ impl Default for NodeCost { impl NodeCost { fn get_cost(&self, network: &Network, state: &State) -> Result { let local_cost = match &self.local { - ConstraintValue::None => Ok(0.0), - ConstraintValue::Scalar(v) => Ok(*v), - ConstraintValue::Metric(m) => m.get_value(network, state), + None => Ok(0.0), + Some(m) => m.get_value(network, state), }?; let vs_costs: Vec = self @@ -737,11 +682,11 @@ impl InputNode { Self { meta: NodeMeta::new(index, name, sub_name), cost: NodeCost::default(), - flow_constraints: FlowConstraints::new(), + flow_constraints: FlowConstraints::default(), outgoing_edges: Vec::new(), } } - fn set_cost(&mut self, value: ConstraintValue) { + fn set_cost(&mut self, value: Option) { self.cost.local = value } fn set_cost_agg_func(&mut self, agg_func: CostAggFunc) { @@ -750,13 +695,13 @@ impl InputNode { fn get_cost(&self, network: &Network, state: &State) -> Result { self.cost.get_cost(network, state) } - fn set_min_flow(&mut self, value: ConstraintValue) { + fn set_min_flow(&mut self, value: Option) { self.flow_constraints.min_flow = value; } fn get_min_flow(&self, network: &Network, state: &State) -> Result { self.flow_constraints.get_min_flow(network, state) } - fn set_max_flow(&mut self, value: ConstraintValue) { + fn set_max_flow(&mut self, value: Option) { self.flow_constraints.max_flow = value; } fn get_max_flow(&self, network: &Network, state: &State) -> Result { @@ -783,11 +728,11 @@ impl OutputNode { Self { meta: NodeMeta::new(index, name, sub_name), cost: NodeCost::default(), - flow_constraints: FlowConstraints::new(), + flow_constraints: FlowConstraints::default(), incoming_edges: Vec::new(), } } - fn set_cost(&mut self, value: ConstraintValue) { + fn set_cost(&mut self, value: Option) { self.cost.local = value } fn get_cost(&self, network: &Network, state: &State) -> Result { @@ -796,13 +741,13 @@ impl OutputNode { fn set_cost_agg_func(&mut self, agg_func: CostAggFunc) { self.cost.agg_func = agg_func } - fn set_min_flow(&mut self, value: ConstraintValue) { + fn set_min_flow(&mut self, value: Option) { self.flow_constraints.min_flow = value; } fn get_min_flow(&self, network: &Network, state: &State) -> Result { self.flow_constraints.get_min_flow(network, state) } - fn set_max_flow(&mut self, value: ConstraintValue) { + fn set_max_flow(&mut self, value: Option) { self.flow_constraints.max_flow = value; } fn get_max_flow(&self, network: &Network, state: &State) -> Result { @@ -830,12 +775,12 @@ impl LinkNode { Self { meta: NodeMeta::new(index, name, sub_name), cost: NodeCost::default(), - flow_constraints: FlowConstraints::new(), + flow_constraints: FlowConstraints::default(), incoming_edges: Vec::new(), outgoing_edges: Vec::new(), } } - fn set_cost(&mut self, value: ConstraintValue) { + fn set_cost(&mut self, value: Option) { self.cost.local = value } fn set_cost_agg_func(&mut self, agg_func: CostAggFunc) { @@ -844,13 +789,13 @@ impl LinkNode { fn get_cost(&self, network: &Network, state: &State) -> Result { self.cost.get_cost(network, state) } - fn set_min_flow(&mut self, value: ConstraintValue) { + fn set_min_flow(&mut self, value: Option) { self.flow_constraints.min_flow = value; } fn get_min_flow(&self, network: &Network, state: &State) -> Result { self.flow_constraints.get_min_flow(network, state) } - fn set_max_flow(&mut self, value: ConstraintValue) { + fn set_max_flow(&mut self, value: Option) { self.flow_constraints.max_flow = value; } fn get_max_flow(&self, network: &Network, state: &State) -> Result { @@ -876,7 +821,7 @@ pub enum StorageInitialVolume { #[derive(Debug, PartialEq)] pub struct StorageNode { pub meta: NodeMeta, - pub cost: ConstraintValue, + pub cost: Option, pub initial_volume: StorageInitialVolume, pub storage_constraints: StorageConstraints, pub incoming_edges: Vec, @@ -889,12 +834,12 @@ impl StorageNode { name: &str, sub_name: Option<&str>, initial_volume: StorageInitialVolume, - min_volume: ConstraintValue, - max_volume: ConstraintValue, + min_volume: Option, + max_volume: Option, ) -> Self { Self { meta: NodeMeta::new(index, name, sub_name), - cost: ConstraintValue::None, + cost: None, initial_volume, storage_constraints: StorageConstraints::new(min_volume, max_volume), incoming_edges: Vec::new(), @@ -902,13 +847,13 @@ impl StorageNode { } } - pub fn before(&self, timestep: &Timestep, network: &Network, state: &mut State) -> Result<(), PywrError> { + pub fn before(&self, timestep: &Timestep, state: &mut State) -> Result<(), PywrError> { // Set the initial volume if it is the first timestep. if timestep.is_first() { let volume = match &self.initial_volume { StorageInitialVolume::Absolute(iv) => *iv, StorageInitialVolume::Proportional(ipc) => { - let max_volume = self.get_max_volume(network, state)?; + let max_volume = self.get_max_volume(state)?; max_volume * ipc } }; @@ -918,29 +863,30 @@ impl StorageNode { Ok(()) } - fn set_cost(&mut self, value: ConstraintValue) { + fn set_cost(&mut self, value: Option) { self.cost = value } fn get_cost(&self, network: &Network, state: &State) -> Result { match &self.cost { - ConstraintValue::None => Ok(0.0), - ConstraintValue::Scalar(v) => Ok(*v), - ConstraintValue::Metric(m) => m.get_value(network, state), + None => Ok(0.0), + Some(m) => m.get_value(network, state), } } - fn set_min_volume(&mut self, value: ConstraintValue) { + fn set_min_volume(&mut self, value: Option) { // TODO use a set_min_volume method self.storage_constraints.min_volume = value; } - fn get_min_volume(&self, network: &Network, state: &State) -> Result { - self.storage_constraints.get_min_volume(network, state) + fn get_min_volume(&self, state: &State) -> Result { + self.storage_constraints + .get_min_volume(&state.get_simple_parameter_values()) } - fn set_max_volume(&mut self, value: ConstraintValue) { + fn set_max_volume(&mut self, value: Option) { // TODO use a set_min_volume method self.storage_constraints.max_volume = value; } - fn get_max_volume(&self, network: &Network, state: &State) -> Result { - self.storage_constraints.get_max_volume(network, state) + fn get_max_volume(&self, state: &State) -> Result { + self.storage_constraints + .get_max_volume(&state.get_simple_parameter_values()) } fn add_incoming_edge(&mut self, edge: EdgeIndex) { self.incoming_edges.push(edge); diff --git a/pywr-core/src/parameters/aggregated.rs b/pywr-core/src/parameters/aggregated.rs index fb659bbc..5d4dbaf4 100644 --- a/pywr-core/src/parameters/aggregated.rs +++ b/pywr-core/src/parameters/aggregated.rs @@ -1,12 +1,13 @@ -use super::{Parameter, PywrError}; -use crate::metric::MetricF64; +use super::{Parameter, ParameterState, PywrError, SimpleParameter}; +use crate::metric::{MetricF64, SimpleMetricF64}; use crate::network::Network; use crate::parameters::{GeneralParameter, ParameterMeta}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::{SimpleParameterValues, State}; use crate::timestep::Timestep; use std::str::FromStr; +#[derive(Debug, Clone, Copy)] pub enum AggFunc { Sum, Product, @@ -30,14 +31,17 @@ impl FromStr for AggFunc { } } -pub struct AggregatedParameter { +pub struct AggregatedParameter { meta: ParameterMeta, - metrics: Vec, + metrics: Vec, agg_func: AggFunc, } -impl AggregatedParameter { - pub fn new(name: &str, metrics: &[MetricF64], agg_func: AggFunc) -> Self { +impl AggregatedParameter +where + M: Send + Sync + Clone, +{ + pub fn new(name: &str, metrics: &[M], agg_func: AggFunc) -> Self { Self { meta: ParameterMeta::new(name), metrics: metrics.to_vec(), @@ -46,13 +50,16 @@ impl AggregatedParameter { } } -impl Parameter for AggregatedParameter { +impl Parameter for AggregatedParameter +where + M: Send + Sync, +{ fn meta(&self) -> &ParameterMeta { &self.meta } } -impl GeneralParameter for AggregatedParameter { +impl GeneralParameter for AggregatedParameter { fn compute( &self, _timestep: &Timestep, @@ -61,8 +68,6 @@ impl GeneralParameter for AggregatedParameter { state: &State, _internal_state: &mut Option>, ) -> Result { - // TODO scenarios! - let value: f64 = match self.agg_func { AggFunc::Sum => { let mut total = 0.0_f64; @@ -103,4 +108,84 @@ impl GeneralParameter for AggregatedParameter { Ok(value) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } + + fn try_into_simple(&self) -> Option>> { + // We can make a simple version if all metrics can be simplified + let metrics: Vec = self + .metrics + .clone() + .into_iter() + .map(|m| m.try_into().ok()) + .collect::>>()?; + + Some(Box::new(AggregatedParameter:: { + meta: self.meta.clone(), + metrics, + agg_func: self.agg_func, + })) + } +} + +impl SimpleParameter for AggregatedParameter { + fn compute( + &self, + _timestep: &Timestep, + _scenario_index: &ScenarioIndex, + values: &SimpleParameterValues, + _internal_state: &mut Option>, + ) -> Result { + let value: f64 = match self.agg_func { + AggFunc::Sum => { + let mut total = 0.0_f64; + for p in &self.metrics { + total += p.get_value(values)?; + } + total + } + AggFunc::Mean => { + let mut total = 0.0_f64; + for p in &self.metrics { + total += p.get_value(values)?; + } + total / self.metrics.len() as f64 + } + AggFunc::Max => { + let mut total = f64::MIN; + for p in &self.metrics { + total = total.max(p.get_value(values)?); + } + total + } + AggFunc::Min => { + let mut total = f64::MAX; + for p in &self.metrics { + total = total.min(p.get_value(values)?); + } + total + } + AggFunc::Product => { + let mut total = 1.0_f64; + for p in &self.metrics { + total *= p.get_value(values)?; + } + total + } + }; + + Ok(value) + } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/aggregated_index.rs b/pywr-core/src/parameters/aggregated_index.rs index 21578185..42cd1cbd 100644 --- a/pywr-core/src/parameters/aggregated_index.rs +++ b/pywr-core/src/parameters/aggregated_index.rs @@ -1,11 +1,11 @@ /// AggregatedIndexParameter /// -use super::{Parameter, PywrError}; +use super::{Parameter, ParameterState, PywrError}; use crate::metric::MetricUsize; use crate::network::Network; use crate::parameters::{GeneralParameter, ParameterMeta}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use std::str::FromStr; @@ -124,4 +124,11 @@ impl GeneralParameter for AggregatedIndexParameter { Ok(value) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/array.rs b/pywr-core/src/parameters/array.rs index f17b25b8..2e83288b 100644 --- a/pywr-core/src/parameters/array.rs +++ b/pywr-core/src/parameters/array.rs @@ -1,7 +1,7 @@ use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; use ndarray::{Array1, Array2, Axis}; @@ -43,6 +43,13 @@ impl GeneralParameter for Array1Parameter { let value = self.array[[idx]]; Ok(value) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } pub struct Array2Parameter { @@ -89,4 +96,11 @@ impl GeneralParameter for Array2Parameter { Ok(self.array[[t_idx, s_idx]]) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/asymmetric.rs b/pywr-core/src/parameters/asymmetric.rs index edafe3ef..1d36781f 100644 --- a/pywr-core/src/parameters/asymmetric.rs +++ b/pywr-core/src/parameters/asymmetric.rs @@ -1,8 +1,8 @@ use crate::metric::MetricUsize; use crate::network::Network; -use crate::parameters::{downcast_internal_state_mut, GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{downcast_internal_state_mut, GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -65,4 +65,11 @@ impl GeneralParameter for AsymmetricSwitchIndexParameter { Ok(*current_state) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/constant.rs b/pywr-core/src/parameters/constant.rs index 8aeaf6f8..8ff1420f 100644 --- a/pywr-core/src/parameters/constant.rs +++ b/pywr-core/src/parameters/constant.rs @@ -1,10 +1,9 @@ -use crate::network::Network; use crate::parameters::{ downcast_internal_state_mut, downcast_internal_state_ref, downcast_variable_config_ref, ActivationFunction, - GeneralParameter, Parameter, ParameterMeta, VariableConfig, VariableParameter, + Parameter, ParameterMeta, ParameterState, SimpleParameter, VariableConfig, VariableParameter, }; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::SimpleParameterValues; use crate::timestep::Timestep; use crate::PywrError; @@ -58,17 +57,24 @@ impl Parameter for ConstantParameter { } } -impl GeneralParameter for ConstantParameter { +// TODO this should only need to implement `ConstantParameter` when that is implemented. +impl SimpleParameter for ConstantParameter { fn compute( &self, _timestep: &Timestep, _scenario_index: &ScenarioIndex, - _model: &Network, - _state: &State, + _values: &SimpleParameterValues, internal_state: &mut Option>, ) -> Result { Ok(self.value(internal_state)) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } impl VariableParameter for ConstantParameter { diff --git a/pywr-core/src/parameters/control_curves/apportion.rs b/pywr-core/src/parameters/control_curves/apportion.rs index 4dea2450..c897d303 100644 --- a/pywr-core/src/parameters/control_curves/apportion.rs +++ b/pywr-core/src/parameters/control_curves/apportion.rs @@ -1,8 +1,8 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{MultiValue, ParameterState, State}; +use crate::state::{MultiValue, State}; use crate::timestep::Timestep; use crate::PywrError; use std::collections::HashMap; @@ -60,4 +60,10 @@ impl GeneralParameter for ApportionParameter { let value = MultiValue::new(values, HashMap::new()); Ok(value) } + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/control_curves/index.rs b/pywr-core/src/parameters/control_curves/index.rs index 60fe9701..0333b180 100644 --- a/pywr-core/src/parameters/control_curves/index.rs +++ b/pywr-core/src/parameters/control_curves/index.rs @@ -1,8 +1,8 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -48,4 +48,10 @@ impl GeneralParameter for ControlCurveIndexParameter { } Ok(self.control_curves.len()) } + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/control_curves/interpolated.rs b/pywr-core/src/parameters/control_curves/interpolated.rs index f0f2b3ca..9ec8da30 100644 --- a/pywr-core/src/parameters/control_curves/interpolated.rs +++ b/pywr-core/src/parameters/control_curves/interpolated.rs @@ -1,9 +1,9 @@ use crate::metric::MetricF64; use crate::network::Network; use crate::parameters::interpolate::interpolate; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -65,4 +65,11 @@ impl GeneralParameter for ControlCurveInterpolatedParameter { Ok(interpolate(x, cc_value, cc_prev, lower_value, upper_value)) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/control_curves/piecewise.rs b/pywr-core/src/parameters/control_curves/piecewise.rs index 97f24c7b..5dd79e46 100644 --- a/pywr-core/src/parameters/control_curves/piecewise.rs +++ b/pywr-core/src/parameters/control_curves/piecewise.rs @@ -1,9 +1,9 @@ use crate::metric::MetricF64; use crate::network::Network; use crate::parameters::interpolate::interpolate; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -64,6 +64,13 @@ impl GeneralParameter for PiecewiseInterpolatedParameter { let v = self.values.last().ok_or(PywrError::DataOutOfRange)?; Ok(interpolate(x, self.minimum, cc_previous_value, v[1], v[0])) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } #[cfg(test)] @@ -85,8 +92,8 @@ mod test { let parameter = PiecewiseInterpolatedParameter::new( "test-parameter", - MetricF64::ParameterValue(volume_idx), // Interpolate with the parameter based values - vec![MetricF64::Constant(0.8), MetricF64::Constant(0.5)], + volume_idx.into(), // Interpolate with the parameter based values + vec![0.8.into(), 0.5.into()], vec![[10.0, 1.0], [0.0, 0.0], [-1.0, -10.0]], 1.0, 0.0, diff --git a/pywr-core/src/parameters/control_curves/simple.rs b/pywr-core/src/parameters/control_curves/simple.rs index e2a4da92..378fa5c8 100644 --- a/pywr-core/src/parameters/control_curves/simple.rs +++ b/pywr-core/src/parameters/control_curves/simple.rs @@ -1,8 +1,8 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -53,4 +53,11 @@ impl GeneralParameter for ControlCurveParameter { let value = self.values.last().ok_or(PywrError::DataOutOfRange)?; value.get_value(model, state) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/control_curves/volume_between.rs b/pywr-core/src/parameters/control_curves/volume_between.rs index f4deef34..45eedb12 100644 --- a/pywr-core/src/parameters/control_curves/volume_between.rs +++ b/pywr-core/src/parameters/control_curves/volume_between.rs @@ -1,21 +1,20 @@ -use crate::metric::MetricF64; -use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::metric::SimpleMetricF64; +use crate::parameters::{Parameter, ParameterMeta, ParameterState, SimpleParameter}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::SimpleParameterValues; use crate::timestep::Timestep; use crate::PywrError; /// A parameter that returns the volume that is the proportion between two control curves -pub struct VolumeBetweenControlCurvesParameter { +pub struct VolumeBetweenControlCurvesParameter { meta: ParameterMeta, - total: MetricF64, - upper: Option, - lower: Option, + total: M, + upper: Option, + lower: Option, } -impl VolumeBetweenControlCurvesParameter { - pub fn new(name: &str, total: MetricF64, upper: Option, lower: Option) -> Self { +impl VolumeBetweenControlCurvesParameter { + pub fn new(name: &str, total: M, upper: Option, lower: Option) -> Self { Self { meta: ParameterMeta::new(name), total, @@ -25,32 +24,35 @@ impl VolumeBetweenControlCurvesParameter { } } -impl Parameter for VolumeBetweenControlCurvesParameter { +impl Parameter for VolumeBetweenControlCurvesParameter +where + M: Send + Sync, +{ fn meta(&self) -> &ParameterMeta { &self.meta } } -impl GeneralParameter for VolumeBetweenControlCurvesParameter { +impl SimpleParameter for VolumeBetweenControlCurvesParameter { fn compute( &self, _timestep: &Timestep, _scenario_index: &ScenarioIndex, - network: &Network, - state: &State, + values: &SimpleParameterValues, _internal_state: &mut Option>, ) -> Result { - let total = self.total.get_value(network, state)?; + let total = self.total.get_value(values)?; - let lower = self - .lower - .as_ref() - .map_or(Ok(0.0), |metric| metric.get_value(network, state))?; - let upper = self - .upper - .as_ref() - .map_or(Ok(1.0), |metric| metric.get_value(network, state))?; + let lower = self.lower.as_ref().map_or(Ok(0.0), |metric| metric.get_value(values))?; + let upper = self.upper.as_ref().map_or(Ok(1.0), |metric| metric.get_value(values))?; Ok(total * (upper - lower)) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/delay.rs b/pywr-core/src/parameters/delay.rs index 47c432e4..dd92cf4b 100644 --- a/pywr-core/src/parameters/delay.rs +++ b/pywr-core/src/parameters/delay.rs @@ -1,21 +1,23 @@ -use crate::metric::MetricF64; +use crate::metric::{MetricF64, SimpleMetricF64}; use crate::network::Network; -use crate::parameters::{downcast_internal_state_mut, GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{ + downcast_internal_state_mut, GeneralParameter, Parameter, ParameterMeta, ParameterState, SimpleParameter, +}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::{SimpleParameterValues, State}; use crate::timestep::Timestep; use crate::PywrError; use std::collections::VecDeque; -pub struct DelayParameter { +pub struct DelayParameter { meta: ParameterMeta, - metric: MetricF64, + metric: M, delay: usize, initial_value: f64, } -impl DelayParameter { - pub fn new(name: &str, metric: MetricF64, delay: usize, initial_value: f64) -> Self { +impl DelayParameter { + pub fn new(name: &str, metric: M, delay: usize, initial_value: f64) -> Self { Self { meta: ParameterMeta::new(name), metric, @@ -25,7 +27,23 @@ impl DelayParameter { } } -impl Parameter for DelayParameter { +impl TryInto> for &DelayParameter { + type Error = PywrError; + + fn try_into(self) -> Result, Self::Error> { + Ok(DelayParameter { + meta: self.meta.clone(), + metric: self.metric.clone().try_into()?, + delay: self.delay, + initial_value: self.initial_value, + }) + } +} + +impl Parameter for DelayParameter +where + M: Send + Sync, +{ fn meta(&self) -> &ParameterMeta { &self.meta } @@ -41,7 +59,7 @@ impl Parameter for DelayParameter { } } -impl GeneralParameter for DelayParameter { +impl GeneralParameter for DelayParameter { fn compute( &self, _timestep: &Timestep, @@ -79,6 +97,67 @@ impl GeneralParameter for DelayParameter { Ok(()) } + + fn try_into_simple(&self) -> Option>> + where + Self: Sized, + { + self.try_into() + .ok() + .map(|p: DelayParameter| Box::new(p) as Box>) + } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } +} + +impl SimpleParameter for DelayParameter { + fn compute( + &self, + _timestep: &Timestep, + _scenario_index: &ScenarioIndex, + _values: &SimpleParameterValues, + internal_state: &mut Option>, + ) -> Result { + // Downcast the internal state to the correct type + let memory = downcast_internal_state_mut::>(internal_state); + + // Take the oldest value from the queue + // It should be guaranteed that the internal memory/queue has self.delay number of values + let value = memory + .pop_front() + .expect("Delay parameter queue did not contain any values. This internal error should not be possible!"); + + Ok(value) + } + + fn after( + &self, + _timestep: &Timestep, + _scenario_index: &ScenarioIndex, + values: &SimpleParameterValues, + internal_state: &mut Option>, + ) -> Result<(), PywrError> { + // Downcast the internal state to the correct type + let memory = downcast_internal_state_mut::>(internal_state); + + // Get today's value from the metric + let value = self.metric.get_value(values)?; + memory.push_back(value); + + Ok(()) + } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } #[cfg(test)] @@ -102,7 +181,7 @@ mod test { const DELAY: usize = 3; // 3 time-step delay let parameter = DelayParameter::new( "test-parameter", - MetricF64::ParameterValue(volume_idx), // Interpolate with the parameter based values + volume_idx.into(), // Interpolate with the parameter based values DELAY, 0.0, ); diff --git a/pywr-core/src/parameters/discount_factor.rs b/pywr-core/src/parameters/discount_factor.rs index 4bab7938..a9fc3290 100644 --- a/pywr-core/src/parameters/discount_factor.rs +++ b/pywr-core/src/parameters/discount_factor.rs @@ -1,8 +1,8 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; use chrono::Datelike; @@ -43,6 +43,13 @@ impl GeneralParameter for DiscountFactorParameter { let factor = 1.0 / (1.0 + rate).powi(year); Ok(factor) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } #[cfg(test)] @@ -66,7 +73,7 @@ mod test { let parameter = DiscountFactorParameter::new( "test-parameter", - MetricF64::Constant(0.03), // Interpolate with the parameter based values + 0.03.into(), // Interpolate with the parameter based values 2020, ); diff --git a/pywr-core/src/parameters/division.rs b/pywr-core/src/parameters/division.rs index 65490eb0..98a369f3 100644 --- a/pywr-core/src/parameters/division.rs +++ b/pywr-core/src/parameters/division.rs @@ -1,9 +1,9 @@ use super::{Parameter, PywrError}; use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{GeneralParameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError::InvalidMetricValue; @@ -49,4 +49,11 @@ impl GeneralParameter for DivisionParameter { let numerator = self.numerator.get_value(model, state)?; Ok(numerator / denominator) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/hydropower.rs b/pywr-core/src/parameters/hydropower.rs index 8a3d6950..0d90d219 100644 --- a/pywr-core/src/parameters/hydropower.rs +++ b/pywr-core/src/parameters/hydropower.rs @@ -1,8 +1,8 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::utils::inverse_hydropower_calculation; use crate::PywrError; @@ -109,4 +109,11 @@ impl GeneralParameter for HydropowerTargetParameter { } Ok(q) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/indexed_array.rs b/pywr-core/src/parameters/indexed_array.rs index ce3c7e21..721b1c91 100644 --- a/pywr-core/src/parameters/indexed_array.rs +++ b/pywr-core/src/parameters/indexed_array.rs @@ -1,8 +1,8 @@ use crate::metric::{MetricF64, MetricUsize}; use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -43,4 +43,11 @@ impl GeneralParameter for IndexedArrayParameter { metric.get_value(network, state) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/interpolated.rs b/pywr-core/src/parameters/interpolated.rs index bac314d6..17b823cc 100644 --- a/pywr-core/src/parameters/interpolated.rs +++ b/pywr-core/src/parameters/interpolated.rs @@ -1,9 +1,9 @@ use crate::metric::MetricF64; use crate::network::Network; use crate::parameters::interpolate::linear_interpolation; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -57,4 +57,11 @@ impl GeneralParameter for InterpolatedParameter { Ok(f) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/max.rs b/pywr-core/src/parameters/max.rs index f75841e4..004a9a02 100644 --- a/pywr-core/src/parameters/max.rs +++ b/pywr-core/src/parameters/max.rs @@ -1,8 +1,8 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -41,4 +41,11 @@ impl GeneralParameter for MaxParameter { let x = self.metric.get_value(model, state)?; Ok(x.max(self.threshold)) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/min.rs b/pywr-core/src/parameters/min.rs index ae9e1e64..e26cd54d 100644 --- a/pywr-core/src/parameters/min.rs +++ b/pywr-core/src/parameters/min.rs @@ -1,8 +1,8 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -39,4 +39,11 @@ impl GeneralParameter for MinParameter { let x = self.metric.get_value(model, state)?; Ok(x.min(self.threshold)) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/mod.rs b/pywr-core/src/parameters/mod.rs index 1b66289b..19a0e0b1 100644 --- a/pywr-core/src/parameters/mod.rs +++ b/pywr-core/src/parameters/mod.rs @@ -31,7 +31,7 @@ pub use self::rhai::RhaiParameter; use super::PywrError; use crate::network::Network; use crate::scenario::ScenarioIndex; -use crate::state::{MultiValue, ParameterState, State}; +use crate::state::{MultiValue, SimpleParameterValues, State}; use crate::timestep::Timestep; pub use activation_function::ActivationFunction; pub use aggregated::{AggFunc, AggregatedParameter}; @@ -46,6 +46,7 @@ pub use control_curves::{ pub use delay::DelayParameter; pub use discount_factor::DiscountFactorParameter; pub use division::DivisionParameter; +use dyn_clone::DynClone; pub use hydropower::{HydropowerTargetData, HydropowerTargetParameter}; pub use indexed_array::IndexedArrayParameter; pub use interpolate::{interpolate, linear_interpolation, InterpolationError}; @@ -70,35 +71,85 @@ use std::ops::Deref; pub use threshold::{Predicate, ThresholdParameter}; pub use vector::VectorParameter; +/// Simple parameter index. +/// +/// This is a wrapper around usize that is used to index parameters in the state. It is +/// generic over the type of the value that the parameter returns. +#[derive(Debug)] +pub struct SimpleParameterIndex { + idx: usize, + phantom: PhantomData, +} + +// These implementations are required because the derive macro does not work well with PhantomData. +// See issue: https://github.com/rust-lang/rust/issues/26925 +impl Clone for SimpleParameterIndex { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for SimpleParameterIndex {} +impl PartialEq for SimpleParameterIndex { + fn eq(&self, other: &Self) -> bool { + self.idx == other.idx + } +} + +impl Eq for SimpleParameterIndex {} + +impl SimpleParameterIndex { + pub fn new(idx: usize) -> Self { + Self { + idx, + phantom: PhantomData, + } + } +} + +impl Deref for SimpleParameterIndex { + type Target = usize; + + fn deref(&self) -> &Self::Target { + &self.idx + } +} + +impl Display for SimpleParameterIndex { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.idx) + } +} + /// Generic parameter index. /// /// This is a wrapper around usize that is used to index parameters in the state. It is /// generic over the type of the value that the parameter returns. #[derive(Debug)] -pub struct ParameterIndex { +pub struct GeneralParameterIndex { idx: usize, phantom: PhantomData, } // These implementations are required because the derive macro does not work well with PhantomData. // See issue: https://github.com/rust-lang/rust/issues/26925 -impl Clone for ParameterIndex { +impl Clone for GeneralParameterIndex { fn clone(&self) -> Self { *self } } -impl Copy for ParameterIndex {} +impl Copy for GeneralParameterIndex {} -impl PartialEq for ParameterIndex { +impl PartialEq for GeneralParameterIndex { fn eq(&self, other: &Self) -> bool { self.idx == other.idx } } -impl Eq for ParameterIndex {} +impl Eq for GeneralParameterIndex {} -impl ParameterIndex { +impl GeneralParameterIndex { pub fn new(idx: usize) -> Self { Self { idx, @@ -107,7 +158,7 @@ impl ParameterIndex { } } -impl Deref for ParameterIndex { +impl Deref for GeneralParameterIndex { type Target = usize; fn deref(&self) -> &Self::Target { @@ -115,14 +166,52 @@ impl Deref for ParameterIndex { } } -impl Display for ParameterIndex { +impl Display for GeneralParameterIndex { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}", self.idx) } } +#[derive(Debug, Copy, Clone)] +pub enum ParameterIndex { + Simple(SimpleParameterIndex), + General(GeneralParameterIndex), +} + +impl PartialEq for ParameterIndex { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Simple(idx1), Self::Simple(idx2)) => idx1 == idx2, + (Self::General(idx1), Self::General(idx2)) => idx1 == idx2, + _ => false, + } + } +} + +impl Eq for ParameterIndex {} + +impl Display for ParameterIndex { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Simple(idx) => write!(f, "{}", idx), + Self::General(idx) => write!(f, "{}", idx), + } + } +} +impl From> for ParameterIndex { + fn from(idx: GeneralParameterIndex) -> Self { + Self::General(idx) + } +} + +impl From> for ParameterIndex { + fn from(idx: SimpleParameterIndex) -> Self { + Self::Simple(idx) + } +} + /// Meta data common to all parameters. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ParameterMeta { pub name: String, pub comment: String, @@ -137,6 +226,114 @@ impl ParameterMeta { } } +pub trait ParameterState: Any + Send + DynClone { + fn as_any(&self) -> &dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; +} + +impl ParameterState for T +where + T: Any + Send + Clone, +{ + fn as_any(&self) -> &dyn Any { + self + } + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} +// impl ParameterState for f64 {} + +dyn_clone::clone_trait_object!(ParameterState); + +#[derive(Clone)] +struct ParameterStatesByType { + f64: Vec>>, + usize: Vec>>, + multi: Vec>>, +} + +#[derive(Clone)] +pub struct ParameterStates { + simple: ParameterStatesByType, + general: ParameterStatesByType, +} + +impl ParameterStates { + /// Create new default states for the desired number of parameters. + pub fn from_collection( + collection: &ParameterCollection, + timesteps: &[Timestep], + scenario_index: &ScenarioIndex, + ) -> Result { + let simple = collection.simple_initial_states(timesteps, scenario_index)?; + let general = collection.general_initial_states(timesteps, scenario_index)?; + + Ok(Self { simple, general }) + } + + pub fn get_f64_state(&self, index: ParameterIndex) -> Option<&Option>> { + match index { + ParameterIndex::Simple(idx) => self.simple.f64.get(*idx.deref()), + ParameterIndex::General(idx) => self.general.f64.get(*idx.deref()), + } + } + pub fn get_general_f64_state(&self, index: GeneralParameterIndex) -> Option<&Option>> { + self.general.f64.get(*index.deref()) + } + + pub fn get_simple_f64_state(&self, index: SimpleParameterIndex) -> Option<&Option>> { + self.simple.f64.get(*index.deref()) + } + + pub fn get_mut_f64_state(&mut self, index: ParameterIndex) -> Option<&mut Option>> { + match index { + ParameterIndex::Simple(idx) => self.simple.f64.get_mut(*idx.deref()), + ParameterIndex::General(idx) => self.general.f64.get_mut(*idx.deref()), + } + } + + pub fn get_general_mut_f64_state( + &mut self, + index: GeneralParameterIndex, + ) -> Option<&mut Option>> { + self.general.f64.get_mut(*index.deref()) + } + pub fn get_simple_mut_f64_state( + &mut self, + index: SimpleParameterIndex, + ) -> Option<&mut Option>> { + self.simple.f64.get_mut(*index.deref()) + } + pub fn get_general_mut_usize_state( + &mut self, + index: GeneralParameterIndex, + ) -> Option<&mut Option>> { + self.general.usize.get_mut(*index.deref()) + } + + pub fn get_simple_mut_usize_state( + &mut self, + index: SimpleParameterIndex, + ) -> Option<&mut Option>> { + self.simple.usize.get_mut(*index.deref()) + } + + pub fn get_general_mut_multi_state( + &mut self, + index: GeneralParameterIndex, + ) -> Option<&mut Option>> { + self.general.multi.get_mut(*index.deref()) + } + + pub fn get_simple_mut_multi_state( + &mut self, + index: SimpleParameterIndex, + ) -> Option<&mut Option>> { + self.simple.multi.get_mut(*index.deref()) + } +} + /// Helper function to downcast to internal parameter state and print a helpful panic /// message if this fails. pub fn downcast_internal_state_mut(internal_state: &mut Option>) -> &mut T { @@ -260,6 +457,85 @@ pub trait GeneralParameter: Parameter { ) -> Result<(), PywrError> { Ok(()) } + + fn try_into_simple(&self) -> Option>> { + None + } + + fn as_parameter(&self) -> &dyn Parameter; +} + +/// A trait that defines a component that produces a value each time-step. +/// +/// The trait is generic over the type of the value produced. +pub trait SimpleParameter: Parameter { + fn compute( + &self, + timestep: &Timestep, + scenario_index: &ScenarioIndex, + values: &SimpleParameterValues, + internal_state: &mut Option>, + ) -> Result; + + fn after( + &self, + #[allow(unused_variables)] timestep: &Timestep, + #[allow(unused_variables)] scenario_index: &ScenarioIndex, + #[allow(unused_variables)] values: &SimpleParameterValues, + #[allow(unused_variables)] internal_state: &mut Option>, + ) -> Result<(), PywrError> { + Ok(()) + } + + fn as_parameter(&self) -> &dyn Parameter; +} + +pub enum GeneralParameterType { + Parameter(GeneralParameterIndex), + Index(GeneralParameterIndex), + Multi(GeneralParameterIndex), +} + +impl From> for GeneralParameterType { + fn from(idx: GeneralParameterIndex) -> Self { + Self::Parameter(idx) + } +} + +impl From> for GeneralParameterType { + fn from(idx: GeneralParameterIndex) -> Self { + Self::Index(idx) + } +} + +impl From> for GeneralParameterType { + fn from(idx: GeneralParameterIndex) -> Self { + Self::Multi(idx) + } +} + +pub enum SimpleParameterType { + Parameter(SimpleParameterIndex), + Index(SimpleParameterIndex), + Multi(SimpleParameterIndex), +} + +impl From> for SimpleParameterType { + fn from(idx: SimpleParameterIndex) -> Self { + Self::Parameter(idx) + } +} + +impl From> for SimpleParameterType { + fn from(idx: SimpleParameterIndex) -> Self { + Self::Index(idx) + } +} + +impl From> for SimpleParameterType { + fn from(idx: SimpleParameterIndex) -> Self { + Self::Multi(idx) + } } pub enum ParameterType { @@ -268,6 +544,24 @@ pub enum ParameterType { Multi(ParameterIndex), } +impl From> for ParameterType { + fn from(idx: ParameterIndex) -> Self { + Self::Parameter(idx) + } +} + +impl From> for ParameterType { + fn from(idx: ParameterIndex) -> Self { + Self::Index(idx) + } +} + +impl From> for ParameterType { + fn from(idx: ParameterIndex) -> Self { + Self::Multi(idx) + } +} + /// A parameter that can be optimised. /// /// This trait is used to allow parameter's internal values to be accessed and altered by @@ -297,51 +591,107 @@ pub trait VariableParameter { fn get_upper_bounds(&self, variable_config: &dyn VariableConfig) -> Result, PywrError>; } +#[derive(Debug, Clone, Copy)] +pub struct ParameterCollectionSize { + pub simple_f64: usize, + pub simple_usize: usize, + pub simple_multi: usize, + pub general_f64: usize, + pub general_usize: usize, + pub general_multi: usize, +} + /// A collection of parameters that return different types. #[derive(Default)] pub struct ParameterCollection { - f64: Vec>>, - usize: Vec>>, - multi: Vec>>, + simple_f64: Vec>>, + simple_usize: Vec>>, + simple_multi: Vec>>, + simple_resolve_order: Vec, + + general_f64: Vec>>, + general_usize: Vec>>, + general_multi: Vec>>, } impl ParameterCollection { - pub fn initial_states( + pub fn size(&self) -> ParameterCollectionSize { + ParameterCollectionSize { + simple_f64: self.simple_f64.len(), + simple_usize: self.simple_usize.len(), + simple_multi: self.simple_multi.len(), + general_f64: self.general_f64.len(), + general_usize: self.general_usize.len(), + general_multi: self.general_multi.len(), + } + } + fn general_initial_states( &self, timesteps: &[Timestep], scenario_index: &ScenarioIndex, - ) -> Result< - ( - Vec>>, - Vec>>, - Vec>>, - ), - PywrError, - > { + ) -> Result { // Get the initial internal state - let values_states = self - .f64 + let f64_states = self + .general_f64 .iter() .map(|p| p.setup(timesteps, scenario_index)) .collect::, _>>()?; - let initial_indices_states = self - .usize + let usize_states = self + .general_usize .iter() .map(|p| p.setup(timesteps, scenario_index)) .collect::, _>>()?; - let initial_multi_param_states = self - .multi + let multi_states = self + .general_multi .iter() .map(|p| p.setup(timesteps, scenario_index)) .collect::, _>>()?; - Ok((values_states, initial_indices_states, initial_multi_param_states)) + Ok(ParameterStatesByType { + f64: f64_states, + usize: usize_states, + multi: multi_states, + }) + } + + fn simple_initial_states( + &self, + timesteps: &[Timestep], + scenario_index: &ScenarioIndex, + ) -> Result { + // Get the initial internal state + let f64_states = self + .simple_f64 + .iter() + .map(|p| p.setup(timesteps, scenario_index)) + .collect::, _>>()?; + + let usize_states = self + .simple_usize + .iter() + .map(|p| p.setup(timesteps, scenario_index)) + .collect::, _>>()?; + + let multi_states = self + .simple_multi + .iter() + .map(|p| p.setup(timesteps, scenario_index)) + .collect::, _>>()?; + + Ok(ParameterStatesByType { + f64: f64_states, + usize: usize_states, + multi: multi_states, + }) } /// Add a new parameter to the collection. - pub fn add_f64(&mut self, parameter: Box>) -> Result, PywrError> { + pub fn add_general_f64( + &mut self, + parameter: Box>, + ) -> Result, PywrError> { if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { return Err(PywrError::ParameterNameAlreadyExists( parameter.meta().name.to_string(), @@ -349,28 +699,75 @@ impl ParameterCollection { )); } - let index = ParameterIndex::new(self.f64.len()); + match parameter.try_into_simple() { + Some(simple) => self.add_simple_f64(simple).map(|idx| idx.into()), + None => { + let index = GeneralParameterIndex::new(self.general_f64.len()); + self.general_f64.push(parameter); + Ok(index.into()) + } + } + } + + pub fn add_simple_f64( + &mut self, + parameter: Box>, + ) -> Result, PywrError> { + // TODO Fix this check + // if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { + // return Err(PywrError::SimpleParameterNameAlreadyExists( + // parameter.meta().name.to_string(), + // index, + // )); + // } + + let index = SimpleParameterIndex::new(self.simple_f64.len()); - self.f64.push(parameter); + self.simple_f64.push(parameter); + self.simple_resolve_order.push(SimpleParameterType::Parameter(index)); Ok(index) } - pub fn get_f64(&self, index: ParameterIndex) -> Option<&dyn GeneralParameter> { - self.f64.get(*index.deref()).map(|p| p.as_ref()) + + pub fn get_f64(&self, index: ParameterIndex) -> Option<&dyn Parameter> { + match index { + ParameterIndex::Simple(idx) => self.simple_f64.get(*idx.deref()).map(|p| p.as_parameter()), + ParameterIndex::General(idx) => self.general_f64.get(*idx.deref()).map(|p| p.as_parameter()), + } + } + + pub fn get_general_f64(&self, index: GeneralParameterIndex) -> Option<&dyn GeneralParameter> { + self.general_f64.get(*index.deref()).map(|p| p.as_ref()) } - pub fn get_f64_by_name(&self, name: &str) -> Option<&dyn GeneralParameter> { - self.f64.iter().find(|p| p.meta().name == name).map(|p| p.as_ref()) + pub fn get_f64_by_name(&self, name: &str) -> Option<&dyn Parameter> { + self.general_f64 + .iter() + .find(|p| p.meta().name == name) + .map(|p| p.as_parameter()) } pub fn get_f64_index_by_name(&self, name: &str) -> Option> { - self.f64 + if let Some(idx) = self + .general_f64 + .iter() + .position(|p| p.meta().name == name) + .map(|idx| GeneralParameterIndex::new(idx)) + { + Some(idx.into()) + } else if let Some(idx) = self + .simple_f64 .iter() .position(|p| p.meta().name == name) - .map(|idx| ParameterIndex::new(idx)) + .map(|idx| SimpleParameterIndex::new(idx)) + { + Some(idx.into()) + } else { + None + } } - pub fn add_usize( + pub fn add_general_usize( &mut self, parameter: Box>, ) -> Result, PywrError> { @@ -381,28 +778,54 @@ impl ParameterCollection { )); } - let index = ParameterIndex::new(self.usize.len()); + match parameter.try_into_simple() { + Some(simple) => self.add_simple_usize(simple).map(|idx| idx.into()), + None => { + let index = GeneralParameterIndex::new(self.general_usize.len()); + self.general_usize.push(parameter); + Ok(index.into()) + } + } + } - self.usize.push(parameter); + pub fn add_simple_usize( + &mut self, + parameter: Box>, + ) -> Result, PywrError> { + // TODO Fix this check + // if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { + // return Err(PywrError::SimpleParameterNameAlreadyExists( + // parameter.meta().name.to_string(), + // index, + // )); + // } + + let index = SimpleParameterIndex::new(self.simple_usize.len()); + + self.simple_usize.push(parameter); + self.simple_resolve_order.push(SimpleParameterType::Index(index)); Ok(index) } - pub fn get_usize(&self, index: ParameterIndex) -> Option<&dyn GeneralParameter> { - self.usize.get(*index.deref()).map(|p| p.as_ref()) + pub fn get_usize(&self, index: GeneralParameterIndex) -> Option<&dyn GeneralParameter> { + self.general_usize.get(*index.deref()).map(|p| p.as_ref()) } pub fn get_usize_by_name(&self, name: &str) -> Option<&dyn GeneralParameter> { - self.usize.iter().find(|p| p.meta().name == name).map(|p| p.as_ref()) + self.general_usize + .iter() + .find(|p| p.meta().name == name) + .map(|p| p.as_ref()) } - pub fn get_usize_index_by_name(&self, name: &str) -> Option> { - self.usize + pub fn get_usize_index_by_name(&self, name: &str) -> Option> { + self.general_usize .iter() .position(|p| p.meta().name == name) - .map(|idx| ParameterIndex::new(idx)) + .map(|idx| GeneralParameterIndex::new(idx)) } - pub fn add_multi( + pub fn add_general_multi( &mut self, parameter: Box>, ) -> Result, PywrError> { @@ -413,25 +836,123 @@ impl ParameterCollection { )); } - let index = ParameterIndex::new(self.usize.len()); + match parameter.try_into_simple() { + Some(simple) => self.add_simple_multi(simple).map(|idx| idx.into()), + None => { + let index = GeneralParameterIndex::new(self.general_multi.len()); + self.general_multi.push(parameter); + Ok(index.into()) + } + } + } + + pub fn add_simple_multi( + &mut self, + parameter: Box>, + ) -> Result, PywrError> { + // TODO Fix this check + // if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { + // return Err(PywrError::SimpleParameterNameAlreadyExists( + // parameter.meta().name.to_string(), + // index, + // )); + // } - self.multi.push(parameter); + let index = SimpleParameterIndex::new(self.simple_multi.len()); + + self.simple_multi.push(parameter); + self.simple_resolve_order.push(SimpleParameterType::Multi(index)); Ok(index) } - pub fn get_multi(&self, index: ParameterIndex) -> Option<&dyn GeneralParameter> { - self.multi.get(*index.deref()).map(|p| p.as_ref()) + pub fn get_multi(&self, index: GeneralParameterIndex) -> Option<&dyn GeneralParameter> { + self.general_multi.get(*index.deref()).map(|p| p.as_ref()) } pub fn get_multi_by_name(&self, name: &str) -> Option<&dyn GeneralParameter> { - self.multi.iter().find(|p| p.meta().name == name).map(|p| p.as_ref()) + self.general_multi + .iter() + .find(|p| p.meta().name == name) + .map(|p| p.as_ref()) } - pub fn get_multi_index_by_name(&self, name: &str) -> Option> { - self.multi + pub fn get_multi_index_by_name(&self, name: &str) -> Option> { + self.general_multi .iter() .position(|p| p.meta().name == name) - .map(|idx| ParameterIndex::new(idx)) + .map(|idx| GeneralParameterIndex::new(idx)) + } + + pub fn compute_simple( + &self, + timestep: &Timestep, + scenario_index: &ScenarioIndex, + state: &mut State, + internal_states: &mut ParameterStates, + ) -> Result<(), PywrError> { + for p in &self.simple_resolve_order { + match p { + SimpleParameterType::Parameter(idx) => { + // Find the parameter itself + let p = self + .simple_f64 + .get(*idx.deref()) + .ok_or(PywrError::SimpleParameterIndexNotFound(*idx))?; + // .. and its internal state + let internal_state = internal_states + .get_simple_mut_f64_state(*idx) + .ok_or(PywrError::SimpleParameterIndexNotFound(*idx))?; + + let value = p.compute( + timestep, + scenario_index, + &state.get_simple_parameter_values(), + internal_state, + )?; + state.set_simple_parameter_value(*idx, value)?; + } + SimpleParameterType::Index(idx) => { + // Find the parameter itself + let p = self + .simple_usize + .get(*idx.deref()) + .ok_or(PywrError::SimpleIndexParameterIndexNotFound(*idx))?; + // .. and its internal state + let internal_state = internal_states + .get_simple_mut_usize_state(*idx) + .ok_or(PywrError::SimpleIndexParameterIndexNotFound(*idx))?; + + let value = p.compute( + timestep, + scenario_index, + &state.get_simple_parameter_values(), + internal_state, + )?; + state.set_simple_parameter_index(*idx, value)?; + } + SimpleParameterType::Multi(idx) => { + // Find the parameter itself + let p = self + .simple_multi + .get(*idx.deref()) + .ok_or(PywrError::SimpleMultiValueParameterIndexNotFound(*idx))?; + // .. and its internal state + let internal_state = internal_states + .get_simple_mut_multi_state(*idx) + .ok_or(PywrError::SimpleMultiValueParameterIndexNotFound(*idx))?; + + let value = p.compute( + timestep, + scenario_index, + &state.get_simple_parameter_values(), + internal_state, + )?; + state.set_simple_multi_parameter_value(*idx, value)?; + } + } + } + + Ok(()) } } diff --git a/pywr-core/src/parameters/negative.rs b/pywr-core/src/parameters/negative.rs index 8e3a2f49..540b5e0e 100644 --- a/pywr-core/src/parameters/negative.rs +++ b/pywr-core/src/parameters/negative.rs @@ -1,8 +1,8 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -39,4 +39,11 @@ impl GeneralParameter for NegativeParameter { let x = self.metric.get_value(model, state)?; Ok(-x) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/negativemax.rs b/pywr-core/src/parameters/negativemax.rs index f0c62ebd..8a56c8b0 100644 --- a/pywr-core/src/parameters/negativemax.rs +++ b/pywr-core/src/parameters/negativemax.rs @@ -1,8 +1,8 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -39,4 +39,11 @@ impl GeneralParameter for NegativeMaxParameter { let x = -self.metric.get_value(network, state)?; Ok(x.max(self.threshold)) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/negativemin.rs b/pywr-core/src/parameters/negativemin.rs index a5cea191..5c0c75ad 100644 --- a/pywr-core/src/parameters/negativemin.rs +++ b/pywr-core/src/parameters/negativemin.rs @@ -1,8 +1,8 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -39,4 +39,11 @@ impl GeneralParameter for NegativeMinParameter { let x = -self.metric.get_value(network, state)?; Ok(x.min(self.threshold)) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/offset.rs b/pywr-core/src/parameters/offset.rs index f7bd2b01..51c0dd37 100644 --- a/pywr-core/src/parameters/offset.rs +++ b/pywr-core/src/parameters/offset.rs @@ -2,10 +2,10 @@ use crate::metric::MetricF64; use crate::network::Network; use crate::parameters::{ downcast_internal_state_mut, downcast_internal_state_ref, downcast_variable_config_ref, ActivationFunction, - GeneralParameter, Parameter, ParameterMeta, VariableConfig, VariableParameter, + GeneralParameter, Parameter, ParameterMeta, ParameterState, VariableConfig, VariableParameter, }; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -65,6 +65,13 @@ impl GeneralParameter for OffsetParameter { let x = self.metric.get_value(model, state)?; Ok(x + offset) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } impl VariableParameter for OffsetParameter { diff --git a/pywr-core/src/parameters/polynomial.rs b/pywr-core/src/parameters/polynomial.rs index e521fd5b..97007afc 100644 --- a/pywr-core/src/parameters/polynomial.rs +++ b/pywr-core/src/parameters/polynomial.rs @@ -1,8 +1,8 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -52,4 +52,11 @@ impl GeneralParameter for Polynomial1DParameter { .fold(0.0, |y, (i, c)| y + c * x.powi(i as i32)); Ok(y) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/profiles/daily.rs b/pywr-core/src/parameters/profiles/daily.rs index 66d124f5..2f1db15b 100644 --- a/pywr-core/src/parameters/profiles/daily.rs +++ b/pywr-core/src/parameters/profiles/daily.rs @@ -1,7 +1,6 @@ -use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{Parameter, ParameterMeta, ParameterState, SimpleParameter}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::SimpleParameterValues; use crate::timestep::Timestep; use crate::PywrError; use chrono::Datelike; @@ -26,15 +25,21 @@ impl Parameter for DailyProfileParameter { } } -impl GeneralParameter for DailyProfileParameter { +impl SimpleParameter for DailyProfileParameter { fn compute( &self, timestep: &Timestep, _scenario_index: &ScenarioIndex, - _model: &Network, - _state: &State, + _values: &SimpleParameterValues, _internal_state: &mut Option>, ) -> Result { Ok(self.values[timestep.date.ordinal() as usize - 1]) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/profiles/monthly.rs b/pywr-core/src/parameters/profiles/monthly.rs index 6e1970fc..1e1dd709 100644 --- a/pywr-core/src/parameters/profiles/monthly.rs +++ b/pywr-core/src/parameters/profiles/monthly.rs @@ -1,7 +1,6 @@ -use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{Parameter, ParameterMeta, ParameterState, SimpleParameter}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::SimpleParameterValues; use crate::timestep::Timestep; use crate::PywrError; use chrono::{Datelike, NaiveDateTime, Timelike}; @@ -75,13 +74,12 @@ impl Parameter for MonthlyProfileParameter { &self.meta } } -impl GeneralParameter for MonthlyProfileParameter { +impl SimpleParameter for MonthlyProfileParameter { fn compute( &self, timestep: &Timestep, _scenario_index: &ScenarioIndex, - _model: &Network, - _state: &State, + _values: &SimpleParameterValues, _internal_state: &mut Option>, ) -> Result { let v = match &self.interp_day { @@ -106,4 +104,11 @@ impl GeneralParameter for MonthlyProfileParameter { }; Ok(v) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/profiles/rbf.rs b/pywr-core/src/parameters/profiles/rbf.rs index 26d13128..6b0318f6 100644 --- a/pywr-core/src/parameters/profiles/rbf.rs +++ b/pywr-core/src/parameters/profiles/rbf.rs @@ -1,10 +1,9 @@ -use crate::network::Network; use crate::parameters::{ - downcast_internal_state_mut, downcast_internal_state_ref, downcast_variable_config_ref, GeneralParameter, - Parameter, ParameterMeta, VariableConfig, VariableParameter, + downcast_internal_state_mut, downcast_internal_state_ref, downcast_variable_config_ref, Parameter, ParameterMeta, + ParameterState, SimpleParameter, VariableConfig, VariableParameter, }; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::SimpleParameterValues; use crate::timestep::Timestep; use crate::PywrError; use chrono::Datelike; @@ -130,13 +129,12 @@ impl Parameter for RbfProfileParameter { } } -impl GeneralParameter for RbfProfileParameter { +impl SimpleParameter for RbfProfileParameter { fn compute( &self, timestep: &Timestep, _scenario_index: &ScenarioIndex, - _network: &Network, - _state: &State, + _values: &SimpleParameterValues, internal_state: &mut Option>, ) -> Result { // Get the profile from the internal state @@ -144,6 +142,13 @@ impl GeneralParameter for RbfProfileParameter { // Return today's value from the profile Ok(internal_state.profile[timestep.date.ordinal() as usize - 1]) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } impl VariableParameter for RbfProfileParameter { diff --git a/pywr-core/src/parameters/profiles/uniform_drawdown.rs b/pywr-core/src/parameters/profiles/uniform_drawdown.rs index b965218c..443a3b57 100644 --- a/pywr-core/src/parameters/profiles/uniform_drawdown.rs +++ b/pywr-core/src/parameters/profiles/uniform_drawdown.rs @@ -1,7 +1,6 @@ -use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{Parameter, ParameterMeta, ParameterState, SimpleParameter}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::SimpleParameterValues; use crate::timestep::Timestep; use crate::PywrError; use chrono::{Datelike, NaiveDate}; @@ -36,13 +35,12 @@ impl Parameter for UniformDrawdownProfileParameter { &self.meta } } -impl GeneralParameter for UniformDrawdownProfileParameter { +impl SimpleParameter for UniformDrawdownProfileParameter { fn compute( &self, timestep: &Timestep, _scenario_index: &ScenarioIndex, - _model: &Network, - _state: &State, + _values: &SimpleParameterValues, _internal_state: &mut Option>, ) -> Result { // Current calendar year (might be adjusted depending on position of reset day) @@ -79,4 +77,11 @@ impl GeneralParameter for UniformDrawdownProfileParameter { Ok(1.0 + (slope * days_into_period as f64)) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/profiles/weekly.rs b/pywr-core/src/parameters/profiles/weekly.rs index b259d193..d3daf1bb 100644 --- a/pywr-core/src/parameters/profiles/weekly.rs +++ b/pywr-core/src/parameters/profiles/weekly.rs @@ -1,7 +1,6 @@ -use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{Parameter, ParameterMeta, ParameterState, SimpleParameter}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::SimpleParameterValues; use crate::timestep::Timestep; use crate::PywrError; use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike}; @@ -190,17 +189,23 @@ impl Parameter for WeeklyProfileParameter { } } -impl GeneralParameter for WeeklyProfileParameter { +impl SimpleParameter for WeeklyProfileParameter { fn compute( &self, timestep: &Timestep, _scenario_index: &ScenarioIndex, - _model: &Network, - _state: &State, + _values: &SimpleParameterValues, _internal_state: &mut Option>, ) -> Result { Ok(self.values.value(×tep.date, &self.interp_day)) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } #[cfg(test)] diff --git a/pywr-core/src/parameters/py.rs b/pywr-core/src/parameters/py.rs index ee19e137..11af2eec 100644 --- a/pywr-core/src/parameters/py.rs +++ b/pywr-core/src/parameters/py.rs @@ -1,9 +1,9 @@ -use super::{GeneralParameter, Parameter, ParameterMeta, PywrError, Timestep}; +use super::{GeneralParameter, Parameter, ParameterMeta, ParameterState, PywrError, Timestep}; use crate::metric::{MetricF64, MetricUsize}; use crate::network::Network; use crate::parameters::downcast_internal_state_mut; use crate::scenario::ScenarioIndex; -use crate::state::{MultiValue, ParameterState, State}; +use crate::state::{MultiValue, State}; use pyo3::prelude::*; use pyo3::types::{IntoPyDict, PyDict, PyFloat, PyLong, PyTuple}; use std::collections::HashMap; @@ -196,6 +196,13 @@ impl GeneralParameter for PyParameter { ) -> Result<(), PywrError> { self.after(timestep, scenario_index, model, state, internal_state) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } impl GeneralParameter for PyParameter { @@ -220,6 +227,13 @@ impl GeneralParameter for PyParameter { ) -> Result<(), PywrError> { self.after(timestep, scenario_index, model, state, internal_state) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } impl GeneralParameter for PyParameter { @@ -292,6 +306,13 @@ impl GeneralParameter for PyParameter { ) -> Result<(), PywrError> { self.after(timestep, scenario_index, model, state, internal_state) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } #[cfg(test)] @@ -348,7 +369,7 @@ class MyParameter: }, ]; - let state = StateBuilder::new(vec![], 0).with_value_parameters(1).build(); + let state = StateBuilder::new(vec![], 0).build(); let mut internal_p_states: Vec<_> = scenario_indices .iter() @@ -417,7 +438,7 @@ class MyParameter: }, ]; - let state = StateBuilder::new(vec![], 0).with_value_parameters(1).build(); + let state = StateBuilder::new(vec![], 0).build(); let mut internal_p_states: Vec<_> = scenario_indices .iter() diff --git a/pywr-core/src/parameters/rhai.rs b/pywr-core/src/parameters/rhai.rs index db00543e..27a20f57 100644 --- a/pywr-core/src/parameters/rhai.rs +++ b/pywr-core/src/parameters/rhai.rs @@ -1,9 +1,9 @@ -use super::{GeneralParameter, Parameter, ParameterMeta, PywrError, Timestep}; +use super::{GeneralParameter, Parameter, ParameterMeta, ParameterState, PywrError, Timestep}; use crate::metric::{MetricF64, MetricUsize}; use crate::network::Network; use crate::parameters::downcast_internal_state_mut; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use chrono::Datelike; use rhai::{Dynamic, Engine, Map, Scope, AST}; use std::collections::HashMap; @@ -113,6 +113,13 @@ impl GeneralParameter for RhaiParameter { Ok(value) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } #[cfg(test)] @@ -164,7 +171,7 @@ mod tests { }, ]; - let state = StateBuilder::new(vec![], 0).with_value_parameters(1).build(); + let state = StateBuilder::new(vec![], 0).build(); let mut internal_p_states: Vec<_> = scenario_indices .iter() diff --git a/pywr-core/src/parameters/threshold.rs b/pywr-core/src/parameters/threshold.rs index 835f7e0d..30d9c817 100644 --- a/pywr-core/src/parameters/threshold.rs +++ b/pywr-core/src/parameters/threshold.rs @@ -1,8 +1,8 @@ use crate::metric::MetricF64; use crate::network::Network; -use crate::parameters::{downcast_internal_state_mut, GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{downcast_internal_state_mut, GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; use std::str::FromStr; @@ -102,4 +102,11 @@ impl GeneralParameter for ThresholdParameter { Ok(0) } } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/parameters/vector.rs b/pywr-core/src/parameters/vector.rs index a6be9326..6f76eb98 100644 --- a/pywr-core/src/parameters/vector.rs +++ b/pywr-core/src/parameters/vector.rs @@ -1,7 +1,7 @@ use crate::network::Network; -use crate::parameters::{GeneralParameter, Parameter, ParameterMeta}; +use crate::parameters::{GeneralParameter, Parameter, ParameterMeta, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError; @@ -39,4 +39,11 @@ impl GeneralParameter for VectorParameter { None => Err(PywrError::TimestepIndexOutOfRange), } } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } diff --git a/pywr-core/src/solvers/builder.rs b/pywr-core/src/solvers/builder.rs index 2bdc2715..65228728 100644 --- a/pywr-core/src/solvers/builder.rs +++ b/pywr-core/src/solvers/builder.rs @@ -396,7 +396,7 @@ where Ok(bnds) => bnds, Err(PywrError::FlowConstraintsUndefined) => { // Must be a storage node - let (avail, missing) = match node.get_current_available_volume_bounds(network, state) { + let (avail, missing) = match node.get_current_available_volume_bounds(state) { Ok(bnds) => bnds, Err(e) => return Err(e), }; @@ -466,7 +466,7 @@ where .iter() .zip(network.virtual_storage_nodes().deref()) { - let (avail, missing) = match node.get_current_available_volume_bounds(network, state) { + let (avail, missing) = match node.get_current_available_volume_bounds(state) { Ok(bnds) => bnds, Err(e) => return Err(e), }; diff --git a/pywr-core/src/state.rs b/pywr-core/src/state.rs index 194beeae..7d5a0498 100644 --- a/pywr-core/src/state.rs +++ b/pywr-core/src/state.rs @@ -3,15 +3,14 @@ use crate::edge::{Edge, EdgeIndex}; use crate::models::MultiNetworkTransferIndex; use crate::network::Network; use crate::node::{Node, NodeIndex}; -use crate::parameters::ParameterIndex; +use crate::parameters::{GeneralParameterIndex, ParameterCollection, ParameterCollectionSize, SimpleParameterIndex}; use crate::timestep::Timestep; use crate::virtual_storage::VirtualStorageIndex; use crate::PywrError; -use dyn_clone::DynClone; -use std::any::Any; use std::collections::{HashMap, VecDeque}; use std::num::NonZeroUsize; use std::ops::Deref; +use thiserror::Error; #[derive(Clone, Copy, Debug)] pub enum NodeState { @@ -230,70 +229,6 @@ impl EdgeState { } } -pub trait ParameterState: Any + Send + DynClone { - fn as_any(&self) -> &dyn Any; - fn as_any_mut(&mut self) -> &mut dyn Any; -} - -impl ParameterState for T -where - T: Any + Send + Clone, -{ - fn as_any(&self) -> &dyn Any { - self - } - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} -// impl ParameterState for f64 {} - -dyn_clone::clone_trait_object!(ParameterState); - -#[derive(Clone)] -pub struct ParameterStates { - values: Vec>>, - indices: Vec>>, - multi: Vec>>, -} - -impl ParameterStates { - /// Create new default states for the desired number of parameters. - pub fn new( - initial_values_states: Vec>>, - initial_indices_states: Vec>>, - initial_multi_states: Vec>>, - ) -> Self { - Self { - values: initial_values_states, - indices: initial_indices_states, - multi: initial_multi_states, - } - } - - pub fn get_value_state(&self, index: ParameterIndex) -> Option<&Option>> { - self.values.get(*index.deref()) - } - - pub fn get_mut_value_state(&mut self, index: ParameterIndex) -> Option<&mut Option>> { - self.values.get_mut(*index.deref()) - } - - pub fn get_mut_index_state( - &mut self, - index: ParameterIndex, - ) -> Option<&mut Option>> { - self.indices.get_mut(*index.deref()) - } - - pub fn get_mut_multi_state( - &mut self, - index: ParameterIndex, - ) -> Option<&mut Option>> { - self.multi.get_mut(*index.deref()) - } -} - #[derive(Debug, Default, Clone, PartialEq)] pub struct MultiValue { values: HashMap, @@ -314,6 +249,14 @@ impl MultiValue { } } +#[derive(Error, Debug)] +pub enum ParameterValuesError { + #[error("index not found: {0}")] + IndexNotFound(usize), + #[error("key not found: {0}")] + KeyNotFound(String), +} + // State of the parameters #[derive(Debug, Clone)] struct ParameterValues { @@ -331,71 +274,151 @@ impl ParameterValues { } } - fn get_value(&self, idx: ParameterIndex) -> Result { - match self.values.get(*idx.deref()) { - Some(s) => Ok(*s), - None => Err(PywrError::ParameterIndexNotFound(idx)), - } + fn get_value(&self, idx: usize) -> Result { + self.values + .get(idx) + .ok_or(ParameterValuesError::IndexNotFound(idx)) + .copied() } - fn set_value(&mut self, idx: ParameterIndex, value: f64) -> Result<(), PywrError> { - match self.values.get_mut(*idx.deref()) { + fn set_value(&mut self, idx: usize, value: f64) -> Result<(), ParameterValuesError> { + match self.values.get_mut(idx) { Some(s) => { *s = value; Ok(()) } - None => Err(PywrError::ParameterIndexNotFound(idx)), + None => Err(ParameterValuesError::IndexNotFound(idx)), } } - fn get_index(&self, idx: ParameterIndex) -> Result { - match self.indices.get(*idx.deref()) { - Some(s) => Ok(*s), - None => Err(PywrError::IndexParameterIndexNotFound(idx)), - } + fn get_index(&self, idx: usize) -> Result { + self.indices + .get(idx) + .ok_or(ParameterValuesError::IndexNotFound(idx)) + .copied() } - fn set_index(&mut self, idx: ParameterIndex, value: usize) -> Result<(), PywrError> { - match self.indices.get_mut(*idx.deref()) { + fn set_index(&mut self, idx: usize, value: usize) -> Result<(), ParameterValuesError> { + match self.indices.get_mut(idx) { Some(s) => { *s = value; Ok(()) } - None => Err(PywrError::IndexParameterIndexNotFound(idx)), + None => Err(ParameterValuesError::IndexNotFound(idx)), } } - fn get_multi_value(&self, idx: ParameterIndex, key: &str) -> Result { - match self.multi_values.get(*idx.deref()) { - Some(s) => match s.get_value(key) { - Some(v) => Ok(*v), - None => Err(PywrError::MultiValueParameterKeyNotFound(key.to_string())), - }, - None => Err(PywrError::MultiValueParameterIndexNotFound(idx)), - } + fn get_multi_value(&self, idx: usize, key: &str) -> Result { + let value = self + .multi_values + .get(idx) + .ok_or(ParameterValuesError::IndexNotFound(idx))?; + + value + .get_value(key) + .ok_or(ParameterValuesError::KeyNotFound(key.to_string())) + .copied() } - fn set_multi_value(&mut self, idx: ParameterIndex, value: MultiValue) -> Result<(), PywrError> { - match self.multi_values.get_mut(*idx.deref()) { + fn set_multi_value(&mut self, idx: usize, value: MultiValue) -> Result<(), ParameterValuesError> { + match self.multi_values.get_mut(idx) { Some(s) => { *s = value; Ok(()) } - None => Err(PywrError::MultiValueParameterIndexNotFound(idx)), + None => Err(ParameterValuesError::IndexNotFound(idx)), } } - fn get_multi_index(&self, idx: ParameterIndex, key: &str) -> Result { - match self.multi_values.get(*idx.deref()) { - Some(s) => match s.get_index(key) { - Some(v) => Ok(*v), - None => Err(PywrError::MultiValueParameterKeyNotFound(key.to_string())), + fn get_multi_index(&self, idx: usize, key: &str) -> Result { + let value = self + .multi_values + .get(idx) + .ok_or(ParameterValuesError::IndexNotFound(idx))?; + + value + .get_index(key) + .ok_or(ParameterValuesError::KeyNotFound(key.to_string())) + .copied() + } +} + +#[derive(Debug, Clone)] +pub struct ParameterValuesCollection { + constant: ParameterValues, + simple: ParameterValues, + general: ParameterValues, +} + +impl ParameterValuesCollection { + fn get_simple_parameter_values(&self) -> SimpleParameterValues { + SimpleParameterValues { + constant: ParameterValuesRef { + values: &self.constant.values, + indices: &self.constant.indices, + multi_values: &self.constant.multi_values, + }, + simple: ParameterValuesRef { + values: &self.simple.values, + indices: &self.simple.indices, + multi_values: &self.simple.multi_values, }, - None => Err(PywrError::MultiValueParameterIndexNotFound(idx)), } } } +pub struct ParameterValuesRef<'a> { + values: &'a [f64], + indices: &'a [usize], + multi_values: &'a [MultiValue], +} + +impl<'a> ParameterValuesRef<'a> { + fn get_value(&self, idx: usize) -> Option<&f64> { + self.values.get(idx) + } + + fn get_index(&self, idx: usize) -> Option<&usize> { + self.indices.get(idx) + } + + fn get_multi_value(&self, idx: usize, key: &str) -> Option<&f64> { + self.multi_values.get(idx).map(|s| s.get_value(key)).flatten() + } +} + +pub struct SimpleParameterValues<'a> { + constant: ParameterValuesRef<'a>, + simple: ParameterValuesRef<'a>, +} + +impl<'a> SimpleParameterValues<'a> { + pub fn get_simple_parameter_f64(&self, idx: SimpleParameterIndex) -> Result { + self.simple + .get_value(*idx.deref()) + .ok_or(PywrError::SimpleParameterIndexNotFound(idx)) + .copied() + } + + pub fn get_simple_parameter_usize(&self, idx: SimpleParameterIndex) -> Result { + self.simple + .get_index(*idx.deref()) + .ok_or(PywrError::SimpleIndexParameterIndexNotFound(idx)) + .copied() + } + + pub fn get_simple_multi_parameter_f64( + &self, + idx: SimpleParameterIndex, + key: &str, + ) -> Result { + self.simple + .get_multi_value(*idx.deref(), key) + .ok_or(PywrError::SimpleMultiValueParameterIndexNotFound(idx)) + .copied() + } +} + // State of the nodes and edges #[derive(Clone, Debug)] pub struct NetworkState { @@ -626,7 +649,7 @@ impl NetworkState { #[derive(Debug, Clone)] pub struct State { network: NetworkState, - parameters: ParameterValues, + parameters: ParameterValuesCollection, derived_metrics: Vec, inter_network_values: Vec, } @@ -640,36 +663,103 @@ impl State { &mut self.network } - pub fn get_parameter_value(&self, idx: ParameterIndex) -> Result { - self.parameters.get_value(idx) + pub fn get_parameter_value(&self, idx: GeneralParameterIndex) -> Result { + self.parameters.general.get_value(*idx).map_err(|e| match e { + ParameterValuesError::IndexNotFound(_) => PywrError::GeneralParameterIndexNotFound(idx), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), + }) } - pub fn set_parameter_value(&mut self, idx: ParameterIndex, value: f64) -> Result<(), PywrError> { - self.parameters.set_value(idx, value) + pub fn set_parameter_value(&mut self, idx: GeneralParameterIndex, value: f64) -> Result<(), PywrError> { + self.parameters.general.set_value(*idx, value).map_err(|e| match e { + ParameterValuesError::IndexNotFound(_) => PywrError::GeneralParameterIndexNotFound(idx), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), + }) } - pub fn get_parameter_index(&self, idx: ParameterIndex) -> Result { - self.parameters.get_index(idx) + pub fn set_simple_parameter_value(&mut self, idx: SimpleParameterIndex, value: f64) -> Result<(), PywrError> { + self.parameters.simple.set_value(*idx, value).map_err(|e| match e { + ParameterValuesError::IndexNotFound(_) => PywrError::SimpleParameterIndexNotFound(idx), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), + }) } - pub fn set_parameter_index(&mut self, idx: ParameterIndex, value: usize) -> Result<(), PywrError> { - self.parameters.set_index(idx, value) + pub fn get_parameter_index(&self, idx: GeneralParameterIndex) -> Result { + self.parameters.general.get_index(*idx).map_err(|e| match e { + ParameterValuesError::IndexNotFound(_) => PywrError::GeneralIndexParameterIndexNotFound(idx), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), + }) } - pub fn get_multi_parameter_value(&self, idx: ParameterIndex, key: &str) -> Result { - self.parameters.get_multi_value(idx, key) + pub fn set_parameter_index(&mut self, idx: GeneralParameterIndex, value: usize) -> Result<(), PywrError> { + self.parameters.general.set_index(*idx, value).map_err(|e| match e { + ParameterValuesError::IndexNotFound(_) => PywrError::GeneralIndexParameterIndexNotFound(idx), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), + }) + } + + pub fn set_simple_parameter_index( + &mut self, + idx: SimpleParameterIndex, + value: usize, + ) -> Result<(), PywrError> { + self.parameters.simple.set_index(*idx, value).map_err(|e| match e { + ParameterValuesError::IndexNotFound(_) => PywrError::SimpleIndexParameterIndexNotFound(idx), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), + }) + } + pub fn get_multi_parameter_value( + &self, + idx: GeneralParameterIndex, + key: &str, + ) -> Result { + self.parameters.general.get_multi_value(*idx, key).map_err(|e| match e { + ParameterValuesError::IndexNotFound(_) => PywrError::GeneralMultiValueParameterIndexNotFound(idx), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), + }) } pub fn set_multi_parameter_value( &mut self, - idx: ParameterIndex, + idx: GeneralParameterIndex, value: MultiValue, ) -> Result<(), PywrError> { - self.parameters.set_multi_value(idx, value) + self.parameters + .general + .set_multi_value(*idx, value) + .map_err(|e| match e { + ParameterValuesError::IndexNotFound(_) => PywrError::GeneralMultiValueParameterIndexNotFound(idx), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), + }) + } + + pub fn set_simple_multi_parameter_value( + &mut self, + idx: SimpleParameterIndex, + value: MultiValue, + ) -> Result<(), PywrError> { + self.parameters + .simple + .set_multi_value(*idx, value) + .map_err(|e| match e { + ParameterValuesError::IndexNotFound(_) => PywrError::SimpleMultiValueParameterIndexNotFound(idx), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), + }) + } + + pub fn get_multi_parameter_index( + &self, + idx: GeneralParameterIndex, + key: &str, + ) -> Result { + self.parameters.general.get_multi_index(*idx, key).map_err(|e| match e { + ParameterValuesError::IndexNotFound(_) => PywrError::GeneralMultiValueParameterIndexNotFound(idx), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), + }) } - pub fn get_multi_parameter_index(&self, idx: ParameterIndex, key: &str) -> Result { - self.parameters.get_multi_index(idx, key) + pub fn get_simple_parameter_values(&self) -> SimpleParameterValues { + self.parameters.get_simple_parameter_values() } pub fn set_node_volume(&mut self, idx: NodeIndex, volume: f64) -> Result<(), PywrError> { @@ -749,9 +839,7 @@ pub struct StateBuilder { initial_node_states: Vec, num_edges: usize, initial_virtual_storage_states: Option>, - num_value_parameters: Option, - num_index_parameters: Option, - num_multi_parameters: Option, + num_parameters: Option, num_derived_metrics: Option, num_inter_network_values: Option, } @@ -768,9 +856,7 @@ impl StateBuilder { initial_node_states, num_edges, initial_virtual_storage_states: None, - num_value_parameters: None, - num_index_parameters: None, - num_multi_parameters: None, + num_parameters: None, num_derived_metrics: None, num_inter_network_values: None, } @@ -783,21 +869,8 @@ impl StateBuilder { } /// Add the number of value parameters to the builder. - pub fn with_value_parameters(mut self, num_value_parameters: usize) -> Self { - self.num_value_parameters = Some(num_value_parameters); - self - } - - /// Add the number of index parameters to the builder. - pub fn with_index_parameters(mut self, num_index_parameters: usize) -> Self { - self.num_index_parameters = Some(num_index_parameters); - - self - } - - /// Add the number of multivalued parameters to the builder. - pub fn with_multi_parameters(mut self, num_multi_parameters: usize) -> Self { - self.num_multi_parameters = Some(num_multi_parameters); + pub fn with_parameters(mut self, collection: &ParameterCollection) -> Self { + self.num_parameters = Some(collection.size()); self } @@ -815,17 +888,31 @@ impl StateBuilder { /// Build the [`State`] from the builder. pub fn build(self) -> State { + let constant = ParameterValues::new(0, 0, 0); + let simple = ParameterValues::new( + self.num_parameters.map(|s| s.simple_f64).unwrap_or(0), + self.num_parameters.map(|s| s.simple_usize).unwrap_or(0), + self.num_parameters.map(|s| s.simple_multi).unwrap_or(0), + ); + let general = ParameterValues::new( + self.num_parameters.map(|s| s.general_f64).unwrap_or(0), + self.num_parameters.map(|s| s.general_usize).unwrap_or(0), + self.num_parameters.map(|s| s.general_multi).unwrap_or(0), + ); + + let parameters = ParameterValuesCollection { + constant, + simple, + general, + }; + State { network: NetworkState::new( self.initial_node_states, self.num_edges, self.initial_virtual_storage_states.unwrap_or_default(), ), - parameters: ParameterValues::new( - self.num_value_parameters.unwrap_or(0), - self.num_index_parameters.unwrap_or(0), - self.num_multi_parameters.unwrap_or(0), - ), + parameters, derived_metrics: vec![0.0; self.num_derived_metrics.unwrap_or(0)], inter_network_values: vec![0.0; self.num_inter_network_values.unwrap_or(0)], } diff --git a/pywr-core/src/test_utils.rs b/pywr-core/src/test_utils.rs index e4f99ee7..a393cc22 100644 --- a/pywr-core/src/test_utils.rs +++ b/pywr-core/src/test_utils.rs @@ -3,7 +3,7 @@ use crate::models::{Model, ModelDomain}; /// Utilities for unit tests. /// TODO move this to its own local crate ("test-utilities") as part of a workspace. use crate::network::Network; -use crate::node::{Constraint, ConstraintValue, StorageInitialVolume}; +use crate::node::StorageInitialVolume; use crate::parameters::{AggFunc, AggregatedParameter, Array2Parameter, ConstantParameter, GeneralParameter}; use crate::recorders::AssertionRecorder; use crate::scenario::ScenarioGroupCollection; @@ -64,39 +64,26 @@ pub fn simple_network(network: &mut Network, inflow_scenario_index: usize, num_i let inflow = network.add_parameter(Box::new(inflow)).unwrap(); let input_node = network.get_mut_node_by_name("input", None).unwrap(); - input_node - .set_constraint( - ConstraintValue::Metric(MetricF64::ParameterValue(inflow)), - Constraint::MaxFlow, - ) - .unwrap(); + input_node.set_max_flow_constraint(Some(inflow.into())).unwrap(); let base_demand = 10.0; let demand_factor = ConstantParameter::new("demand-factor", 1.2); - let demand_factor = network.add_parameter(Box::new(demand_factor)).unwrap(); + let demand_factor = network.add_simple_parameter(Box::new(demand_factor)).unwrap(); - let total_demand = AggregatedParameter::new( + let total_demand: AggregatedParameter = AggregatedParameter::new( "total-demand", - &[ - MetricF64::Constant(base_demand), - MetricF64::ParameterValue(demand_factor), - ], + &[base_demand.into(), demand_factor.into()], AggFunc::Product, ); let total_demand = network.add_parameter(Box::new(total_demand)).unwrap(); let demand_cost = ConstantParameter::new("demand-cost", -10.0); - let demand_cost = network.add_parameter(Box::new(demand_cost)).unwrap(); + let demand_cost = network.add_simple_parameter(Box::new(demand_cost)).unwrap(); let output_node = network.get_mut_node_by_name("output", None).unwrap(); - output_node - .set_constraint( - ConstraintValue::Metric(MetricF64::ParameterValue(total_demand)), - Constraint::MaxFlow, - ) - .unwrap(); - output_node.set_cost(ConstraintValue::Metric(MetricF64::ParameterValue(demand_cost))); + output_node.set_max_flow_constraint(Some(total_demand.into())).unwrap(); + output_node.set_cost(Some(demand_cost.into())); } /// Create a simple test model with three nodes. pub fn simple_model(num_scenarios: usize) -> Model { @@ -124,8 +111,8 @@ pub fn simple_storage_model() -> Model { "reservoir", None, StorageInitialVolume::Absolute(100.0), - ConstraintValue::Scalar(0.0), - ConstraintValue::Scalar(100.0), + None, + Some(100.0.into()), ) .unwrap(); let output_node = network.add_output_node("output", None).unwrap(); @@ -135,19 +122,14 @@ pub fn simple_storage_model() -> Model { // Apply demand to the model // TODO convenience function for adding a constant constraint. let demand = ConstantParameter::new("demand", 10.0); - let demand = network.add_parameter(Box::new(demand)).unwrap(); + let demand = network.add_simple_parameter(Box::new(demand)).unwrap(); let demand_cost = ConstantParameter::new("demand-cost", -10.0); - let demand_cost = network.add_parameter(Box::new(demand_cost)).unwrap(); + let demand_cost = network.add_simple_parameter(Box::new(demand_cost)).unwrap(); let output_node = network.get_mut_node_by_name("output", None).unwrap(); - output_node - .set_constraint( - ConstraintValue::Metric(MetricF64::ParameterValue(demand)), - Constraint::MaxFlow, - ) - .unwrap(); - output_node.set_cost(ConstraintValue::Metric(MetricF64::ParameterValue(demand_cost))); + output_node.set_max_flow_constraint(Some(demand.into())).unwrap(); + output_node.set_cost(Some(demand_cost.into())); Model::new(default_time_domain().into(), network) } @@ -176,13 +158,7 @@ pub fn run_and_assert_parameter( .checked_add_days(Days::new(expected_values.nrows() as u64 - 1)) .unwrap(); - let rec = AssertionRecorder::new( - "assert", - MetricF64::ParameterValue(p_idx), - expected_values, - ulps, - epsilon, - ); + let rec = AssertionRecorder::new("assert", p_idx.into(), expected_values, ulps, epsilon); model.network_mut().add_recorder(Box::new(rec)).unwrap(); run_all_solvers(model) @@ -251,22 +227,18 @@ fn make_simple_system( let inflow = Array2Parameter::new(&format!("inflow-{suffix}"), inflow, inflow_scenario_group_index, None); let idx = network.add_parameter(Box::new(inflow))?; - network.set_node_max_flow( - "input", - Some(suffix), - ConstraintValue::Metric(MetricF64::ParameterValue(idx)), - )?; + network.set_node_max_flow("input", Some(suffix), Some(idx.into()))?; let input_cost = rng.gen_range(-20.0..-5.00); - network.set_node_cost("input", Some(suffix), ConstraintValue::Scalar(input_cost))?; + network.set_node_cost("input", Some(suffix), Some(input_cost.into()))?; let outflow_distr = Normal::new(8.0, 3.0).unwrap(); let mut outflow: f64 = outflow_distr.sample(rng); outflow = outflow.max(0.0); - network.set_node_max_flow("output", Some(suffix), ConstraintValue::Scalar(outflow))?; + network.set_node_max_flow("output", Some(suffix), Some(outflow.into()))?; - network.set_node_cost("output", Some(suffix), ConstraintValue::Scalar(-500.0))?; + network.set_node_cost("output", Some(suffix), Some((-500.0).into()))?; Ok(()) } @@ -296,7 +268,7 @@ fn make_simple_connections( if let Ok(idx) = model.add_link_node("transfer", Some(&name)) { let transfer_cost = rng.gen_range(0.0..1.0); - model.set_node_cost("transfer", Some(&name), ConstraintValue::Scalar(transfer_cost))?; + model.set_node_cost("transfer", Some(&name), Some(transfer_cost.into()))?; let from_suffix = format!("sys-{i:04}"); let from_idx = model.get_node_index_by_name("link", Some(&from_suffix))?; diff --git a/pywr-core/src/virtual_storage.rs b/pywr-core/src/virtual_storage.rs index 9c096fae..a5bc62a7 100644 --- a/pywr-core/src/virtual_storage.rs +++ b/pywr-core/src/virtual_storage.rs @@ -1,5 +1,6 @@ +use crate::metric::{MetricF64, SimpleMetricF64}; use crate::network::Network; -use crate::node::{ConstraintValue, FlowConstraints, NodeMeta, StorageConstraints, StorageInitialVolume}; +use crate::node::{FlowConstraints, NodeMeta, StorageConstraints, StorageInitialVolume}; use crate::state::{State, VirtualStorageState}; use crate::timestep::Timestep; use crate::{NodeIndex, PywrError}; @@ -73,11 +74,11 @@ pub struct VirtualStorageBuilder { nodes: Vec, factors: Option>, initial_volume: StorageInitialVolume, - min_volume: ConstraintValue, - max_volume: ConstraintValue, + min_volume: Option, + max_volume: Option, reset: VirtualStorageReset, rolling_window: Option, - cost: ConstraintValue, + cost: Option, } impl VirtualStorageBuilder { @@ -88,11 +89,11 @@ impl VirtualStorageBuilder { nodes: nodes.to_vec(), factors: None, initial_volume: StorageInitialVolume::Absolute(0.0), - min_volume: ConstraintValue::Scalar(0.0), - max_volume: ConstraintValue::Scalar(f64::INFINITY), + min_volume: None, + max_volume: None, reset: VirtualStorageReset::Never, rolling_window: None, - cost: ConstraintValue::None, + cost: None, } } @@ -111,12 +112,12 @@ impl VirtualStorageBuilder { self } - pub fn min_volume(mut self, min_volume: ConstraintValue) -> Self { + pub fn min_volume(mut self, min_volume: Option) -> Self { self.min_volume = min_volume; self } - pub fn max_volume(mut self, max_volume: ConstraintValue) -> Self { + pub fn max_volume(mut self, max_volume: Option) -> Self { self.max_volume = max_volume; self } @@ -131,7 +132,7 @@ impl VirtualStorageBuilder { self } - pub fn cost(mut self, cost: ConstraintValue) -> Self { + pub fn cost(mut self, cost: Option) -> Self { self.cost = cost; self } @@ -139,7 +140,7 @@ impl VirtualStorageBuilder { pub fn build(self, index: VirtualStorageIndex) -> VirtualStorage { VirtualStorage { meta: NodeMeta::new(&index, &self.name, self.sub_name.as_deref()), - flow_constraints: FlowConstraints::new(), + flow_constraints: FlowConstraints::default(), nodes: self.nodes, factors: self.factors, initial_volume: self.initial_volume, @@ -167,7 +168,7 @@ pub struct VirtualStorage { pub storage_constraints: StorageConstraints, pub reset: VirtualStorageReset, pub rolling_window: Option, - pub cost: ConstraintValue, + pub cost: Option, } impl VirtualStorage { @@ -199,13 +200,12 @@ impl VirtualStorage { pub fn get_cost(&self, network: &Network, state: &State) -> Result { match &self.cost { - ConstraintValue::None => Ok(0.0), - ConstraintValue::Scalar(v) => Ok(*v), - ConstraintValue::Metric(m) => m.get_value(network, state), + None => Ok(0.0), + Some(m) => m.get_value(network, state), } } - pub fn before(&self, timestep: &Timestep, network: &Network, state: &mut State) -> Result<(), PywrError> { + pub fn before(&self, timestep: &Timestep, state: &mut State) -> Result<(), PywrError> { let do_reset = if timestep.is_first() { // Set the initial volume if it is the first timestep. true @@ -228,7 +228,7 @@ impl VirtualStorage { }; if do_reset { - let max_volume = self.get_max_volume(network, state)?; + let max_volume = self.get_max_volume(state)?; // Determine the initial volume let volume = match &self.initial_volume { StorageInitialVolume::Absolute(iv) => *iv, @@ -262,17 +262,19 @@ impl VirtualStorage { .map(|factors| self.nodes.iter().zip(factors.iter()).map(|(n, f)| (*n, *f)).collect()) } - pub fn get_min_volume(&self, model: &Network, state: &State) -> Result { - self.storage_constraints.get_min_volume(model, state) + pub fn get_min_volume(&self, state: &State) -> Result { + self.storage_constraints + .get_min_volume(&state.get_simple_parameter_values()) } - pub fn get_max_volume(&self, model: &Network, state: &State) -> Result { - self.storage_constraints.get_max_volume(model, state) + pub fn get_max_volume(&self, state: &State) -> Result { + self.storage_constraints + .get_max_volume(&state.get_simple_parameter_values()) } - pub fn get_current_available_volume_bounds(&self, model: &Network, state: &State) -> Result<(f64, f64), PywrError> { - let min_vol = self.get_min_volume(model, state)?; - let max_vol = self.get_max_volume(model, state)?; + pub fn get_current_available_volume_bounds(&self, state: &State) -> Result<(f64, f64), PywrError> { + let min_vol = self.get_min_volume(state)?; + let max_vol = self.get_max_volume(state)?; let current_volume = state.get_network_state().get_virtual_storage_volume(&self.index())?; diff --git a/pywr-schema/src/metric.rs b/pywr-schema/src/metric.rs index 18f64665..02f2a3d9 100644 --- a/pywr-schema/src/metric.rs +++ b/pywr-schema/src/metric.rs @@ -56,10 +56,10 @@ impl Metric { match self { Self::Node(node_ref) => node_ref.load(network, args), Self::Parameter(parameter_ref) => parameter_ref.load(network), - Self::Constant { value } => Ok(MetricF64::Constant(*value)), + Self::Constant { value } => Ok((*value).into()), Self::Table(table_ref) => { let value = args.tables.get_scalar_f64(table_ref)?; - Ok(MetricF64::Constant(value)) + Ok(value.into()) } Self::Timeseries(ts_ref) => { let param_idx = match &ts_ref.columns { @@ -72,7 +72,7 @@ impl Metric { .load_column(network, ts_ref.name.as_ref(), col.as_str())? } }; - Ok(MetricF64::ParameterValue(param_idx)) + Ok(param_idx.into()) } Self::InlineParameter { definition } => { // This inline parameter could already have been loaded on a previous attempt @@ -85,13 +85,13 @@ impl Metric { match network.get_parameter_index_by_name(definition.name()) { Ok(p) => { // Found a parameter with the name; assume it is the right one! - Ok(MetricF64::ParameterValue(p)) + Ok(p.into()) } Err(_) => { // An error retrieving a parameter with this name; assume it needs creating. match definition.add_to_model(network, args)? { - pywr_core::parameters::ParameterType::Parameter(idx) => Ok(MetricF64::ParameterValue(idx)), - pywr_core::parameters::ParameterType::Index(idx) => Ok(MetricF64::IndexParameterValue(idx)), + pywr_core::parameters::ParameterType::Parameter(idx) => Ok(idx.into()), + pywr_core::parameters::ParameterType::Index(idx) => Ok(idx.into()), pywr_core::parameters::ParameterType::Multi(_) => Err(SchemaError::UnexpectedParameterType(format!( "Found an inline definition of a multi valued parameter of type '{}' with name '{}' where a float parameter was expected. Multi valued parameters cannot be defined inline.", definition.parameter_type(), @@ -327,7 +327,7 @@ impl ParameterReference { } None => { if let Ok(idx) = network.get_parameter_index_by_name(&self.name) { - Ok(MetricF64::ParameterValue(idx)) + Ok(idx.into()) } else if let Ok(idx) = network.get_index_parameter_index_by_name(&self.name) { Ok(MetricF64::IndexParameterValue(idx)) } else { diff --git a/pywr-schema/src/nodes/annual_virtual_storage.rs b/pywr-schema/src/nodes/annual_virtual_storage.rs index a2645ecd..51a313b4 100644 --- a/pywr-schema/src/nodes/annual_virtual_storage.rs +++ b/pywr-schema/src/nodes/annual_virtual_storage.rs @@ -11,7 +11,6 @@ use crate::parameters::TryIntoV2Parameter; use pywr_core::{ derived_metric::DerivedMetric, metric::MetricF64, - node::ConstraintValue, virtual_storage::{VirtualStorageBuilder, VirtualStorageReset}, }; use pywr_schema_macros::PywrVisitAll; @@ -69,17 +68,17 @@ impl AnnualVirtualStorageNode { pub fn add_to_model(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result<(), SchemaError> { let cost = match &self.cost { Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::Scalar(0.0), + None => None, }; let min_volume = match &self.min_volume { - Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::Scalar(0.0), + Some(v) => Some(v.load(network, args)?.try_into()?), + None => None, }; let max_volume = match &self.max_volume { - Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::None, + Some(v) => Some(v.load(network, args)?.try_into()?), + None => None, }; let node_idxs = self diff --git a/pywr-schema/src/nodes/core.rs b/pywr-schema/src/nodes/core.rs index 6a5fd6c9..6ea1bc9c 100644 --- a/pywr-schema/src/nodes/core.rs +++ b/pywr-schema/src/nodes/core.rs @@ -9,9 +9,7 @@ use crate::nodes::{NodeAttribute, NodeMeta}; use crate::parameters::TryIntoV2Parameter; #[cfg(feature = "core")] use pywr_core::{ - derived_metric::DerivedMetric, - metric::MetricF64, - node::{ConstraintValue, StorageInitialVolume as CoreStorageInitialVolume}, + derived_metric::DerivedMetric, metric::MetricF64, node::StorageInitialVolume as CoreStorageInitialVolume, }; use pywr_schema_macros::PywrVisitAll; use pywr_v1_schema::nodes::{ @@ -406,24 +404,9 @@ impl StorageNode { #[cfg(feature = "core")] impl StorageNode { - pub fn add_to_model(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result<(), SchemaError> { - let min_volume = match &self.min_volume { - Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::Scalar(0.0), - }; - - let max_volume = match &self.max_volume { - Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::None, - }; - - network.add_storage_node( - self.meta.name.as_str(), - None, - self.initial_volume.into(), - min_volume, - max_volume, - )?; + pub fn add_to_model(&self, network: &mut pywr_core::network::Network) -> Result<(), SchemaError> { + // Add the node with no constraints + network.add_storage_node(self.meta.name.as_str(), None, self.initial_volume.into(), None, None)?; Ok(()) } @@ -437,6 +420,16 @@ impl StorageNode { network.set_node_cost(self.meta.name.as_str(), None, value.into())?; } + if let Some(min_volume) = &self.min_volume { + let value = min_volume.load(network, args)?; + network.set_node_min_volume(self.meta.name.as_str(), None, Some(value.try_into()?))?; + } + + if let Some(max_volume) = &self.max_volume { + let value = max_volume.load(network, args)?; + network.set_node_max_volume(self.meta.name.as_str(), None, Some(value.try_into()?))?; + } + Ok(()) } @@ -916,6 +909,9 @@ mod tests { use crate::nodes::core::StorageInitialVolume; use crate::nodes::InputNode; use crate::nodes::StorageNode; + use crate::PywrModel; + use pywr_core::test_utils::run_all_solvers; + use std::str::FromStr; #[test] fn test_input() { @@ -970,4 +966,18 @@ mod tests { assert_eq!(storage.initial_volume, StorageInitialVolume::Proportional(0.5)); } + + fn storage_max_volumes_str() -> &'static str { + include_str!("../test_models/storage_max_volumes.json") + } + + #[test] + #[cfg(feature = "core")] + fn test_storage_max_volumes_run() { + let data = storage_max_volumes_str(); + let schema = PywrModel::from_str(data).unwrap(); + let model: pywr_core::models::Model = schema.build_model(None, None).unwrap(); + // Test all solvers + run_all_solvers(&model); + } } diff --git a/pywr-schema/src/nodes/delay.rs b/pywr-schema/src/nodes/delay.rs index 45cbea2d..8f1f6453 100644 --- a/pywr-schema/src/nodes/delay.rs +++ b/pywr-schema/src/nodes/delay.rs @@ -91,7 +91,7 @@ impl DelayNode { let delay_idx = network.add_parameter(Box::new(p))?; // Apply it as a constraint on the input node. - let metric = MetricF64::ParameterValue(delay_idx); + let metric: MetricF64 = delay_idx.into(); network.set_node_max_flow(self.meta.name.as_str(), Self::input_sub_now(), metric.clone().into())?; network.set_node_min_flow(self.meta.name.as_str(), Self::input_sub_now(), metric.into())?; diff --git a/pywr-schema/src/nodes/mod.rs b/pywr-schema/src/nodes/mod.rs index e8f05d0c..25ec16bf 100644 --- a/pywr-schema/src/nodes/mod.rs +++ b/pywr-schema/src/nodes/mod.rs @@ -388,7 +388,7 @@ impl Node { Node::Input(n) => n.add_to_model(network), Node::Link(n) => n.add_to_model(network), Node::Output(n) => n.add_to_model(network), - Node::Storage(n) => n.add_to_model(network, args), + Node::Storage(n) => n.add_to_model(network), Node::Catchment(n) => n.add_to_model(network), Node::RiverGauge(n) => n.add_to_model(network), Node::LossLink(n) => n.add_to_model(network), diff --git a/pywr-schema/src/nodes/monthly_virtual_storage.rs b/pywr-schema/src/nodes/monthly_virtual_storage.rs index fa656e4a..5bbb8e81 100644 --- a/pywr-schema/src/nodes/monthly_virtual_storage.rs +++ b/pywr-schema/src/nodes/monthly_virtual_storage.rs @@ -11,7 +11,6 @@ use crate::parameters::TryIntoV2Parameter; use pywr_core::{ derived_metric::DerivedMetric, metric::MetricF64, - node::ConstraintValue, virtual_storage::{VirtualStorageBuilder, VirtualStorageReset}, }; use pywr_schema_macros::PywrVisitAll; @@ -63,17 +62,17 @@ impl MonthlyVirtualStorageNode { pub fn add_to_model(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result<(), SchemaError> { let cost = match &self.cost { Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::Scalar(0.0), + None => None, }; let min_volume = match &self.min_volume { - Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::Scalar(0.0), + Some(v) => Some(v.load(network, args)?.try_into()?), + None => None, }; let max_volume = match &self.max_volume { - Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::None, + Some(v) => Some(v.load(network, args)?.try_into()?), + None => None, }; let node_idxs = self diff --git a/pywr-schema/src/nodes/piecewise_storage.rs b/pywr-schema/src/nodes/piecewise_storage.rs index 189f0e48..d81815d7 100644 --- a/pywr-schema/src/nodes/piecewise_storage.rs +++ b/pywr-schema/src/nodes/piecewise_storage.rs @@ -4,11 +4,10 @@ use crate::metric::Metric; #[cfg(feature = "core")] use crate::model::LoadArgs; use crate::nodes::{NodeAttribute, NodeMeta}; +use pywr_core::metric::SimpleMetricF64; #[cfg(feature = "core")] use pywr_core::{ - derived_metric::DerivedMetric, - metric::MetricF64, - node::{ConstraintValue, StorageInitialVolume}, + derived_metric::DerivedMetric, metric::MetricF64, node::StorageInitialVolume, parameters::VolumeBetweenControlCurvesParameter, }; use pywr_schema_macros::PywrVisitAll; @@ -86,7 +85,7 @@ impl PiecewiseStorageNode { pub fn add_to_model(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result<(), SchemaError> { // These are the min and max volume of the overall node - let max_volume = self.max_volume.load(network, args)?; + let max_volume: SimpleMetricF64 = self.max_volume.load(network, args)?.try_into()?; let mut store_node_indices = Vec::new(); @@ -95,7 +94,7 @@ impl PiecewiseStorageNode { // The volume of this step is the proportion between the last control curve // (or zero if first) and this control curve. let lower = if i > 0 { - Some(self.steps[i - 1].control_curve.load(network, args)?) + Some(self.steps[i - 1].control_curve.load(network, args)?.try_into()?) } else { None }; @@ -105,14 +104,14 @@ impl PiecewiseStorageNode { let max_volume_parameter = VolumeBetweenControlCurvesParameter::new( format!("{}-{}-max-volume", self.meta.name, Self::step_sub_name(i).unwrap()).as_str(), max_volume.clone(), - Some(upper), + Some(upper.try_into()?), lower, ); - let max_volume_parameter_idx = network.add_parameter(Box::new(max_volume_parameter))?; - let max_volume = ConstraintValue::Metric(MetricF64::ParameterValue(max_volume_parameter_idx)); + let max_volume_parameter_idx = network.add_simple_parameter(Box::new(max_volume_parameter))?; + let max_volume = Some(max_volume_parameter_idx.try_into()?); // Each store has min volume of zero - let min_volume = ConstraintValue::Scalar(0.0); + let min_volume = None; // Assume each store is full to start with let initial_volume = StorageInitialVolume::Proportional(1.0); @@ -135,7 +134,7 @@ impl PiecewiseStorageNode { // The volume of this store the remain proportion above the last control curve let lower = match self.steps.last() { - Some(step) => Some(step.control_curve.load(network, args)?), + Some(step) => Some(step.control_curve.load(network, args)?.try_into()?), None => None, }; @@ -152,11 +151,11 @@ impl PiecewiseStorageNode { upper, lower, ); - let max_volume_parameter_idx = network.add_parameter(Box::new(max_volume_parameter))?; - let max_volume = ConstraintValue::Metric(MetricF64::ParameterValue(max_volume_parameter_idx)); + let max_volume_parameter_idx = network.add_simple_parameter(Box::new(max_volume_parameter))?; + let max_volume = Some(max_volume_parameter_idx.try_into()?); // Each store has min volume of zero - let min_volume = ConstraintValue::Scalar(0.0); + let min_volume = None; // Assume each store is full to start with let initial_volume = StorageInitialVolume::Proportional(1.0); diff --git a/pywr-schema/src/nodes/rolling_virtual_storage.rs b/pywr-schema/src/nodes/rolling_virtual_storage.rs index 3d192e2f..506eed62 100644 --- a/pywr-schema/src/nodes/rolling_virtual_storage.rs +++ b/pywr-schema/src/nodes/rolling_virtual_storage.rs @@ -10,7 +10,7 @@ use crate::parameters::TryIntoV2Parameter; use pywr_core::{ derived_metric::DerivedMetric, metric::MetricF64, - node::{ConstraintValue, StorageInitialVolume}, + node::StorageInitialVolume, timestep::TimeDomain, virtual_storage::{VirtualStorageBuilder, VirtualStorageReset}, }; @@ -111,17 +111,17 @@ impl RollingVirtualStorageNode { let cost = match &self.cost { Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::Scalar(0.0), + None => None, }; let min_volume = match &self.min_volume { - Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::Scalar(0.0), + Some(v) => Some(v.load(network, args)?.try_into()?), + None => None, }; let max_volume = match &self.max_volume { - Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::None, + Some(v) => Some(v.load(network, args)?.try_into()?), + None => None, }; let node_idxs = self diff --git a/pywr-schema/src/nodes/turbine.rs b/pywr-schema/src/nodes/turbine.rs index 493418b5..8f517d7b 100644 --- a/pywr-schema/src/nodes/turbine.rs +++ b/pywr-schema/src/nodes/turbine.rs @@ -137,7 +137,7 @@ impl TurbineNode { }; let p = pywr_core::parameters::HydropowerTargetParameter::new(&name, turbine_data); let power_idx = network.add_parameter(Box::new(p))?; - let metric = MetricF64::ParameterValue(power_idx); + let metric: MetricF64 = power_idx.into(); match self.target_type { TargetType::MaxFlow => { diff --git a/pywr-schema/src/nodes/virtual_storage.rs b/pywr-schema/src/nodes/virtual_storage.rs index 50f7e591..18d499b8 100644 --- a/pywr-schema/src/nodes/virtual_storage.rs +++ b/pywr-schema/src/nodes/virtual_storage.rs @@ -12,7 +12,6 @@ use crate::parameters::TryIntoV2Parameter; use pywr_core::{ derived_metric::DerivedMetric, metric::MetricF64, - node::ConstraintValue, virtual_storage::{VirtualStorageBuilder, VirtualStorageReset}, }; use pywr_schema_macros::PywrVisitAll; @@ -52,17 +51,17 @@ impl VirtualStorageNode { pub fn add_to_model(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result<(), SchemaError> { let cost = match &self.cost { Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::Scalar(0.0), + None => None, }; let min_volume = match &self.min_volume { - Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::Scalar(0.0), + Some(v) => Some(v.load(network, args)?.try_into()?), + None => None, }; let max_volume = match &self.max_volume { - Some(v) => v.load(network, args)?.into(), - None => ConstraintValue::None, + Some(v) => Some(v.load(network, args)?.try_into()?), + None => None, }; let node_idxs = self diff --git a/pywr-schema/src/nodes/water_treatment_works.rs b/pywr-schema/src/nodes/water_treatment_works.rs index d2815ec6..15fd65df 100644 --- a/pywr-schema/src/nodes/water_treatment_works.rs +++ b/pywr-schema/src/nodes/water_treatment_works.rs @@ -6,6 +6,7 @@ use crate::model::LoadArgs; use crate::nodes::{NodeAttribute, NodeMeta}; #[cfg(feature = "core")] use num::Zero; +use pywr_core::metric::{ConstantMetricF64, SimpleMetricF64}; #[cfg(feature = "core")] use pywr_core::{aggregated_node::Factors, metric::MetricF64}; use pywr_schema_macros::PywrVisitAll; @@ -178,20 +179,26 @@ impl WaterTreatmentWorks { // Handle the case where we a given a zero loss factor // The aggregated node does not support zero loss factors so filter them here. let lf = match loss_factor.load(network, args)? { - MetricF64::Constant(f) => { - if f.is_zero() { - None - } else { - Some(MetricF64::Constant(f)) - } - } + MetricF64::Simple(s) => match s { + SimpleMetricF64::Constant(c) => match c { + ConstantMetricF64::Constant(f) => { + if f.is_zero() { + None + } else { + Some(f.into()) + } + } + _ => None, + }, + _ => None, + }, m => Some(m), }; if let Some(lf) = lf { // Set the factors for the loss // TODO allow for configuring as proportion of gross. - let factors = Factors::Ratio(vec![MetricF64::Constant(1.0), lf]); + let factors = Factors::Ratio(vec![1.0.into(), lf]); network.set_aggregated_node_factors(self.meta.name.as_str(), Self::agg_sub_name(), Some(factors))?; } } diff --git a/pywr-schema/src/parameters/core.rs b/pywr-schema/src/parameters/core.rs index 23b490a5..3c1161a2 100644 --- a/pywr-schema/src/parameters/core.rs +++ b/pywr-schema/src/parameters/core.rs @@ -167,7 +167,7 @@ impl ConstantParameter { args: &LoadArgs, ) -> Result, SchemaError> { let p = pywr_core::parameters::ConstantParameter::new(&self.meta.name, self.value.load(args.tables)?); - Ok(network.add_parameter(Box::new(p))?) + Ok(network.add_simple_parameter(Box::new(p))?) } } diff --git a/pywr-schema/src/parameters/mod.rs b/pywr-schema/src/parameters/mod.rs index 71cc37ee..a4cb7bfe 100644 --- a/pywr-schema/src/parameters/mod.rs +++ b/pywr-schema/src/parameters/mod.rs @@ -772,7 +772,7 @@ impl ParameterIndexValue { match self { Self::Reference(name) => { // This should be an existing parameter - Ok(network.get_index_parameter_index_by_name(name)?) + Ok(network.get_index_parameter_index_by_name(name)?.into()) } Self::Inline(parameter) => { // Inline parameter needs to be added @@ -816,7 +816,7 @@ impl DynamicIndexValue { pub fn load(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result { let parameter_ref = match self { DynamicIndexValue::Constant(v) => MetricUsize::Constant(v.load(args.tables)?), - DynamicIndexValue::Dynamic(v) => MetricUsize::IndexParameterValue(v.load(network, args)?), + DynamicIndexValue::Dynamic(v) => v.load(network, args)?.into(), }; Ok(parameter_ref) } diff --git a/pywr-schema/src/parameters/profiles.rs b/pywr-schema/src/parameters/profiles.rs index 36ba941d..900e9e5b 100644 --- a/pywr-schema/src/parameters/profiles.rs +++ b/pywr-schema/src/parameters/profiles.rs @@ -4,8 +4,9 @@ use crate::error::SchemaError; #[cfg(feature = "core")] use crate::model::LoadArgs; use crate::parameters::{ConstantFloatVec, ConstantValue, IntoV2Parameter, ParameterMeta, TryFromV1Parameter}; +use pywr_core::parameters::ParameterIndex; #[cfg(feature = "core")] -use pywr_core::parameters::{ParameterIndex, WeeklyProfileError, WeeklyProfileValues}; +use pywr_core::parameters::{WeeklyProfileError, WeeklyProfileValues}; use pywr_schema_macros::PywrVisitAll; use pywr_v1_schema::parameters::{ DailyProfileParameter as DailyProfileParameterV1, MonthInterpDay as MonthInterpDayV1, @@ -31,7 +32,7 @@ impl DailyProfileParameter { ) -> Result, SchemaError> { let values = &self.values.load(args.tables)?[..366]; let p = pywr_core::parameters::DailyProfileParameter::new(&self.meta.name, values.try_into().expect("")); - Ok(network.add_parameter(Box::new(p))?) + Ok(network.add_simple_parameter(Box::new(p))?) } } @@ -104,7 +105,7 @@ impl MonthlyProfileParameter { values.try_into().expect(""), self.interp_day.map(|id| id.into()), ); - Ok(network.add_parameter(Box::new(p))?) + Ok(network.add_simple_parameter(Box::new(p))?) } } @@ -189,7 +190,7 @@ impl UniformDrawdownProfileParameter { reset_month, residual_days, ); - Ok(network.add_parameter(Box::new(p))?) + Ok(network.add_simple_parameter(Box::new(p))?) } } @@ -358,7 +359,7 @@ impl RbfProfileParameter { let function = self.function.into_core_rbf(&self.points)?; let p = pywr_core::parameters::RbfProfileParameter::new(&self.meta.name, self.points.clone(), function); - Ok(network.add_parameter(Box::new(p))?) + Ok(network.add_simple_parameter(Box::new(p))?) } } @@ -542,7 +543,7 @@ impl WeeklyProfileParameter { )?, self.interp_day.map(|id| id.into()), ); - Ok(network.add_parameter(Box::new(p))?) + Ok(network.add_simple_parameter(Box::new(p))?) } } diff --git a/pywr-schema/src/parameters/python.rs b/pywr-schema/src/parameters/python.rs index cf222319..de68a5ec 100644 --- a/pywr-schema/src/parameters/python.rs +++ b/pywr-schema/src/parameters/python.rs @@ -247,7 +247,7 @@ impl PythonParameter { let p = PyParameter::new(&self.meta.name, object, py_args, kwargs, &metrics, &indices); let pt = match self.return_type { - PythonReturnType::Float => ParameterType::Parameter(network.add_parameter(Box::new(p))?), + PythonReturnType::Float => network.add_parameter(Box::new(p))?.into(), PythonReturnType::Int => ParameterType::Index(network.add_index_parameter(Box::new(p))?), PythonReturnType::Dict => ParameterType::Multi(network.add_multi_value_parameter(Box::new(p))?), }; diff --git a/pywr-schema/src/test_models/storage_max_volumes.json b/pywr-schema/src/test_models/storage_max_volumes.json new file mode 100644 index 00000000..149f5d04 --- /dev/null +++ b/pywr-schema/src/test_models/storage_max_volumes.json @@ -0,0 +1,114 @@ +{ + "metadata": { + "title": "Storage max volumes", + "description": "Several examples of storage nodes with different max volumes.", + "minimum_version": "0.1" + }, + "timestepper": { + "start": "2015-01-01", + "end": "2015-12-31", + "timestep": 1 + }, + "network": { + "nodes": [ + { + "name": "supply1", + "type": "Input", + "max_flow": { + "type": "Constant", + "value": 15.0 + } + }, + { + "name": "storage1", + "type": "Storage", + "initial_volume": { + "Proportional": 0.5 + }, + "max_volume": { + "type": "Constant", + "value": 10.0 + } + }, + { + "name": "storage2", + "type": "Storage", + "initial_volume": { + "Proportional": 0.5 + }, + "max_volume": { + "type": "Parameter", + "name": "ten" + } + }, + { + "name": "storage3", + "type": "Storage", + "initial_volume": { + "Proportional": 0.5 + }, + "max_volume": { + "type": "Parameter", + "name": "fifteen" + } + }, + { + "name": "output1", + "type": "Output" + } + ], + "edges": [ + { + "from_node": "supply1", + "to_node": "storage1" + }, + { + "from_node": "supply1", + "to_node": "storage2" + }, + { + "from_node": "supply1", + "to_node": "storage3" + }, + { + "from_node": "storage1", + "to_node": "output1" + }, + { + "from_node": "storage2", + "to_node": "output1" + }, + { + "from_node": "storage3", + "to_node": "output1" + } + ], + "parameters": [ + { + "name": "ten", + "type": "Constant", + "value": 10.0 + }, + { + "name": "five", + "type": "Constant", + "value": 5.0 + }, + { + "name": "fifteen", + "type": "Aggregated", + "agg_func": "sum", + "metrics": [ + { + "type": "Parameter", + "name": "five" + }, + { + "type": "Parameter", + "name": "ten" + } + ] + } + ] + } +} diff --git a/pywr-schema/src/timeseries/mod.rs b/pywr-schema/src/timeseries/mod.rs index bcbd275f..0832d226 100644 --- a/pywr-schema/src/timeseries/mod.rs +++ b/pywr-schema/src/timeseries/mod.rs @@ -12,10 +12,11 @@ use ndarray::Array2; use polars::error::PolarsError; #[cfg(feature = "core")] use polars::prelude::{DataFrame, DataType::Float64, Float64Type, IndexOrder}; +use pywr_core::parameters::ParameterIndex; #[cfg(feature = "core")] use pywr_core::{ models::ModelDomain, - parameters::{Array1Parameter, Array2Parameter, ParameterIndex}, + parameters::{Array1Parameter, Array2Parameter}, PywrError, }; use pywr_v1_schema::tables::TableVec; From ed35451c0a971b37dea0e2e77a40cb6986354123 Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Wed, 5 Jun 2024 12:12:38 +0100 Subject: [PATCH 4/7] fix: Fix pywr-core tests. --- Cargo.toml | 2 +- pywr-core/Cargo.toml | 2 +- pywr-core/src/aggregated_node.rs | 7 ++--- pywr-core/src/network.rs | 20 +++++++------- .../parameters/control_curves/piecewise.rs | 1 - pywr-core/src/parameters/delay.rs | 1 - pywr-core/src/parameters/discount_factor.rs | 1 - pywr-core/src/timestep.rs | 1 - pywr-core/src/virtual_storage.rs | 26 +++++++++---------- 9 files changed, 26 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4406cd4e..1a099d9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,5 +47,5 @@ tracing = { version = "0.1", features = ["log"] } csv = "1.1" hdf5 = { git = "https://github.com/aldanor/hdf5-rust.git", package = "hdf5", features = ["static", "zlib"] } pywr-v1-schema = { git = "https://github.com/pywr/pywr-schema/", tag = "v0.13.0", package = "pywr-schema" } -chrono = { version = "0.4.34" } +chrono = { version = "0.4.34", features = ["serde"] } schemars = { version = "0.8.16", features = ["chrono"] } diff --git a/pywr-core/Cargo.toml b/pywr-core/Cargo.toml index 04097ff3..8d5da74d 100644 --- a/pywr-core/Cargo.toml +++ b/pywr-core/Cargo.toml @@ -30,7 +30,7 @@ nalgebra = "0.32.3" chrono = { workspace = true } polars = { workspace = true } -pyo3 = { workspace = true, features = ["chrono"] } +pyo3 = { workspace = true, features = ["chrono", "macros"] } rayon = "1.6.1" diff --git a/pywr-core/src/aggregated_node.rs b/pywr-core/src/aggregated_node.rs index ae879973..9d3e79f9 100644 --- a/pywr-core/src/aggregated_node.rs +++ b/pywr-core/src/aggregated_node.rs @@ -296,7 +296,6 @@ mod tests { use crate::metric::MetricF64; use crate::models::Model; use crate::network::Network; - use crate::node::ConstraintValue; use crate::recorders::AssertionRecorder; use crate::test_utils::{default_time_domain, run_all_solvers}; use ndarray::Array2; @@ -327,11 +326,9 @@ mod tests { // Setup a demand on output-0 let output_node = network.get_mut_node_by_name("output", Some("0")).unwrap(); - output_node - .set_max_flow_constraint(ConstraintValue::Scalar(100.0)) - .unwrap(); + output_node.set_max_flow_constraint(Some(100.0.into())).unwrap(); - output_node.set_cost(ConstraintValue::Scalar(-10.0)); + output_node.set_cost(Some((-10.0).into())); // Set-up assertion for "input" node let idx = network.get_node_by_name("link", Some("0")).unwrap().index(); diff --git a/pywr-core/src/network.rs b/pywr-core/src/network.rs index 94f4caac..e2a42b9d 100644 --- a/pywr-core/src/network.rs +++ b/pywr-core/src/network.rs @@ -1629,7 +1629,6 @@ mod tests { use super::*; use crate::metric::MetricF64; use crate::network::Network; - use crate::node::{Constraint, ConstraintValue}; use crate::parameters::{ActivationFunction, ControlCurveInterpolatedParameter, Parameter}; use crate::recorders::AssertionRecorder; use crate::scenario::{ScenarioDomain, ScenarioGroupCollection, ScenarioIndex}; @@ -1703,8 +1702,8 @@ mod tests { "my-node", None, StorageInitialVolume::Absolute(10.0), - ConstraintValue::Scalar(0.0), - ConstraintValue::Scalar(10.0) + None, + Some(10.0.into()) ), Err(PywrError::NodeNameAlreadyExists("my-node".to_string())) ); @@ -1717,16 +1716,15 @@ mod tests { let _node_index = network.add_input_node("input", None).unwrap(); let input_max_flow = parameters::ConstantParameter::new("my-constant", 10.0); - let parameter = network.add_parameter(Box::new(input_max_flow)).unwrap(); + let parameter = network.add_simple_parameter(Box::new(input_max_flow)).unwrap(); // assign the new parameter to one of the nodes. let node = network.get_mut_node_by_name("input", None).unwrap(); - node.set_constraint(ConstraintValue::Metric(parameter.into()), Constraint::MaxFlow) - .unwrap(); + node.set_max_flow_constraint(Some(parameter.into())).unwrap(); // Try to assign a constraint not defined for particular node type assert_eq!( - node.set_constraint(ConstraintValue::Scalar(10.0), Constraint::MaxVolume), + node.set_max_volume_constraint(Some(10.0.into())), Err(PywrError::StorageConstraintsUndefined) ); } @@ -1959,12 +1957,14 @@ mod tests { assert!(input_max_flow.can_be_f64_variable()); - let input_max_flow_idx = model.network_mut().add_parameter(Box::new(input_max_flow)).unwrap(); + let input_max_flow_idx = model + .network_mut() + .add_simple_parameter(Box::new(input_max_flow)) + .unwrap(); // assign the new parameter to one of the nodes. let node = model.network_mut().get_mut_node_by_name("input", None).unwrap(); - node.set_constraint(ConstraintValue::Metric(input_max_flow_idx.into()), Constraint::MaxFlow) - .unwrap(); + node.set_max_flow_constraint(Some(input_max_flow_idx.into())).unwrap(); let mut state = model.setup::(&ClpSolverSettings::default()).unwrap(); diff --git a/pywr-core/src/parameters/control_curves/piecewise.rs b/pywr-core/src/parameters/control_curves/piecewise.rs index 5dd79e46..194d535f 100644 --- a/pywr-core/src/parameters/control_curves/piecewise.rs +++ b/pywr-core/src/parameters/control_curves/piecewise.rs @@ -75,7 +75,6 @@ impl GeneralParameter for PiecewiseInterpolatedParameter { #[cfg(test)] mod test { - use crate::metric::MetricF64; use crate::parameters::{Array1Parameter, PiecewiseInterpolatedParameter}; use crate::test_utils::{run_and_assert_parameter, simple_model}; use ndarray::{Array1, Array2, Axis}; diff --git a/pywr-core/src/parameters/delay.rs b/pywr-core/src/parameters/delay.rs index dd92cf4b..410d684c 100644 --- a/pywr-core/src/parameters/delay.rs +++ b/pywr-core/src/parameters/delay.rs @@ -162,7 +162,6 @@ impl SimpleParameter for DelayParameter { #[cfg(test)] mod test { - use crate::metric::MetricF64; use crate::parameters::{Array1Parameter, DelayParameter}; use crate::test_utils::{run_and_assert_parameter, simple_model}; use ndarray::{concatenate, s, Array1, Array2, Axis}; diff --git a/pywr-core/src/parameters/discount_factor.rs b/pywr-core/src/parameters/discount_factor.rs index a9fc3290..579a5507 100644 --- a/pywr-core/src/parameters/discount_factor.rs +++ b/pywr-core/src/parameters/discount_factor.rs @@ -54,7 +54,6 @@ impl GeneralParameter for DiscountFactorParameter { #[cfg(test)] mod test { - use crate::metric::MetricF64; use crate::parameters::{Array1Parameter, DiscountFactorParameter}; use crate::test_utils::{run_and_assert_parameter, simple_model}; use ndarray::{Array1, Array2, Axis}; diff --git a/pywr-core/src/timestep.rs b/pywr-core/src/timestep.rs index 55adf34d..e83b5f93 100644 --- a/pywr-core/src/timestep.rs +++ b/pywr-core/src/timestep.rs @@ -9,7 +9,6 @@ use crate::PywrError; const SECS_IN_DAY: i64 = 60 * 60 * 24; /// A newtype for `chrono::TimeDelta` that provides a couple of useful convenience methods. -#[pyclass] #[derive(Debug, Copy, Clone)] pub struct PywrDuration(TimeDelta); diff --git a/pywr-core/src/virtual_storage.rs b/pywr-core/src/virtual_storage.rs index a5bc62a7..691a550d 100644 --- a/pywr-core/src/virtual_storage.rs +++ b/pywr-core/src/virtual_storage.rs @@ -294,7 +294,7 @@ mod tests { use crate::metric::MetricF64; use crate::models::Model; use crate::network::Network; - use crate::node::{ConstraintValue, StorageInitialVolume}; + use crate::node::StorageInitialVolume; use crate::recorders::{AssertionFnRecorder, AssertionRecorder}; use crate::scenario::ScenarioIndex; use crate::test_utils::{default_timestepper, run_all_solvers, simple_model}; @@ -370,20 +370,18 @@ mod tests { let vs_builder = VirtualStorageBuilder::new("virtual-storage", &[link_node0, link_node1]) .factors(&[2.0, 1.0]) .initial_volume(StorageInitialVolume::Absolute(100.0)) - .min_volume(ConstraintValue::Scalar(0.0)) - .max_volume(ConstraintValue::Scalar(100.0)) + .min_volume(Some(0.0.into())) + .max_volume(Some(100.0.into())) .reset(VirtualStorageReset::Never) - .cost(ConstraintValue::Scalar(0.0)); + .cost(None); let _vs = network.add_virtual_storage_node(vs_builder); // Setup a demand on output-0 and output-1 for sub_name in &["0", "1"] { let output_node = network.get_mut_node_by_name("output", Some(sub_name)).unwrap(); - output_node - .set_max_flow_constraint(ConstraintValue::Scalar(10.0)) - .unwrap(); - output_node.set_cost(ConstraintValue::Scalar(-10.0)); + output_node.set_max_flow_constraint(Some(10.0.into())).unwrap(); + output_node.set_cost(Some((-10.0).into())); } // With a demand of 10 on each link node. The virtual storage will depleted at a rate of @@ -430,10 +428,10 @@ mod tests { let vs_builder = VirtualStorageBuilder::new("vs", &nodes) .initial_volume(StorageInitialVolume::Proportional(1.0)) - .min_volume(ConstraintValue::Scalar(0.0)) - .max_volume(ConstraintValue::Scalar(100.0)) + .min_volume(Some(0.0.into())) + .max_volume(Some(100.0.into())) .reset(VirtualStorageReset::Never) - .cost(ConstraintValue::Scalar(20.0)); + .cost(Some(20.0.into())); network.add_virtual_storage_node(vs_builder).unwrap(); @@ -459,11 +457,11 @@ mod tests { let vs_builder = VirtualStorageBuilder::new("virtual-storage", &nodes) .factors(&[1.0]) .initial_volume(StorageInitialVolume::Absolute(2.5)) - .min_volume(ConstraintValue::Scalar(0.0)) - .max_volume(ConstraintValue::Scalar(2.5)) + .min_volume(Some(0.0.into())) + .max_volume(Some(2.5.into())) .reset(VirtualStorageReset::Never) .rolling_window(NonZeroUsize::new(5).unwrap()) - .cost(ConstraintValue::Scalar(0.0)); + .cost(None); let _vs = network.add_virtual_storage_node(vs_builder); // Expected values will follow a pattern set by the first few time-steps From 173ae4c6c4c3ed236d1ee43597ba47c4bf8ae583 Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Wed, 5 Jun 2024 12:52:13 +0100 Subject: [PATCH 5/7] fix: Fix schema-only builds. --- pywr-schema/src/nodes/core.rs | 4 ++++ pywr-schema/src/nodes/piecewise_storage.rs | 5 +++-- pywr-schema/src/nodes/water_treatment_works.rs | 6 ++++-- pywr-schema/src/parameters/profiles.rs | 3 +-- pywr-schema/src/timeseries/mod.rs | 3 +-- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pywr-schema/src/nodes/core.rs b/pywr-schema/src/nodes/core.rs index 6ea1bc9c..a823001d 100644 --- a/pywr-schema/src/nodes/core.rs +++ b/pywr-schema/src/nodes/core.rs @@ -909,8 +909,11 @@ mod tests { use crate::nodes::core::StorageInitialVolume; use crate::nodes::InputNode; use crate::nodes::StorageNode; + #[cfg(feature = "core")] use crate::PywrModel; + #[cfg(feature = "core")] use pywr_core::test_utils::run_all_solvers; + #[cfg(feature = "core")] use std::str::FromStr; #[test] @@ -967,6 +970,7 @@ mod tests { assert_eq!(storage.initial_volume, StorageInitialVolume::Proportional(0.5)); } + #[cfg(feature = "core")] fn storage_max_volumes_str() -> &'static str { include_str!("../test_models/storage_max_volumes.json") } diff --git a/pywr-schema/src/nodes/piecewise_storage.rs b/pywr-schema/src/nodes/piecewise_storage.rs index d81815d7..84796298 100644 --- a/pywr-schema/src/nodes/piecewise_storage.rs +++ b/pywr-schema/src/nodes/piecewise_storage.rs @@ -4,10 +4,11 @@ use crate::metric::Metric; #[cfg(feature = "core")] use crate::model::LoadArgs; use crate::nodes::{NodeAttribute, NodeMeta}; -use pywr_core::metric::SimpleMetricF64; #[cfg(feature = "core")] use pywr_core::{ - derived_metric::DerivedMetric, metric::MetricF64, node::StorageInitialVolume, + derived_metric::DerivedMetric, + metric::{MetricF64, SimpleMetricF64}, + node::StorageInitialVolume, parameters::VolumeBetweenControlCurvesParameter, }; use pywr_schema_macros::PywrVisitAll; diff --git a/pywr-schema/src/nodes/water_treatment_works.rs b/pywr-schema/src/nodes/water_treatment_works.rs index 15fd65df..b6c5064b 100644 --- a/pywr-schema/src/nodes/water_treatment_works.rs +++ b/pywr-schema/src/nodes/water_treatment_works.rs @@ -6,9 +6,11 @@ use crate::model::LoadArgs; use crate::nodes::{NodeAttribute, NodeMeta}; #[cfg(feature = "core")] use num::Zero; -use pywr_core::metric::{ConstantMetricF64, SimpleMetricF64}; #[cfg(feature = "core")] -use pywr_core::{aggregated_node::Factors, metric::MetricF64}; +use pywr_core::{ + aggregated_node::Factors, + metric::{ConstantMetricF64, MetricF64, SimpleMetricF64}, +}; use pywr_schema_macros::PywrVisitAll; use schemars::JsonSchema; diff --git a/pywr-schema/src/parameters/profiles.rs b/pywr-schema/src/parameters/profiles.rs index 900e9e5b..7e66f0cf 100644 --- a/pywr-schema/src/parameters/profiles.rs +++ b/pywr-schema/src/parameters/profiles.rs @@ -4,9 +4,8 @@ use crate::error::SchemaError; #[cfg(feature = "core")] use crate::model::LoadArgs; use crate::parameters::{ConstantFloatVec, ConstantValue, IntoV2Parameter, ParameterMeta, TryFromV1Parameter}; -use pywr_core::parameters::ParameterIndex; #[cfg(feature = "core")] -use pywr_core::parameters::{WeeklyProfileError, WeeklyProfileValues}; +use pywr_core::parameters::{ParameterIndex, WeeklyProfileError, WeeklyProfileValues}; use pywr_schema_macros::PywrVisitAll; use pywr_v1_schema::parameters::{ DailyProfileParameter as DailyProfileParameterV1, MonthInterpDay as MonthInterpDayV1, diff --git a/pywr-schema/src/timeseries/mod.rs b/pywr-schema/src/timeseries/mod.rs index 0832d226..bcbd275f 100644 --- a/pywr-schema/src/timeseries/mod.rs +++ b/pywr-schema/src/timeseries/mod.rs @@ -12,11 +12,10 @@ use ndarray::Array2; use polars::error::PolarsError; #[cfg(feature = "core")] use polars::prelude::{DataFrame, DataType::Float64, Float64Type, IndexOrder}; -use pywr_core::parameters::ParameterIndex; #[cfg(feature = "core")] use pywr_core::{ models::ModelDomain, - parameters::{Array1Parameter, Array2Parameter}, + parameters::{Array1Parameter, Array2Parameter, ParameterIndex}, PywrError, }; use pywr_v1_schema::tables::TableVec; From 17e008f10b7796d509ebbd4fb098afb997634f35 Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Mon, 17 Jun 2024 12:41:54 +0100 Subject: [PATCH 6/7] feat: Implement ConstParameter. Final parameter variant that is computed only at the beginning of a model. --- pywr-core/src/lib.rs | 10 +- pywr-core/src/metric.rs | 65 ++++- pywr-core/src/network.rs | 35 ++- pywr-core/src/parameters/constant.rs | 9 +- pywr-core/src/parameters/mod.rs | 365 +++++++++++++++++++++++++-- pywr-core/src/state.rs | 104 +++++++- pywr-core/src/test_utils.rs | 8 +- pywr-schema/src/parameters/core.rs | 2 +- pywr-schema/src/parameters/mod.rs | 2 +- 9 files changed, 550 insertions(+), 50 deletions(-) diff --git a/pywr-core/src/lib.rs b/pywr-core/src/lib.rs index bad59d2b..67046719 100644 --- a/pywr-core/src/lib.rs +++ b/pywr-core/src/lib.rs @@ -5,7 +5,9 @@ extern crate core; use crate::derived_metric::DerivedMetricIndex; use crate::models::MultiNetworkTransferIndex; use crate::node::NodeIndex; -use crate::parameters::{GeneralParameterIndex, InterpolationError, ParameterIndex, SimpleParameterIndex}; +use crate::parameters::{ + ConstParameterIndex, GeneralParameterIndex, InterpolationError, ParameterIndex, SimpleParameterIndex, +}; use crate::recorders::{AggregationError, MetricSetIndex, RecorderIndex}; use crate::state::MultiValue; use crate::virtual_storage::VirtualStorageIndex; @@ -63,6 +65,12 @@ pub enum PywrError { SimpleIndexParameterIndexNotFound(SimpleParameterIndex), #[error("multi-value parameter index {0} not found")] SimpleMultiValueParameterIndexNotFound(SimpleParameterIndex), + #[error("parameter index {0} not found")] + ConstParameterIndexNotFound(ConstParameterIndex), + #[error("index parameter index {0} not found")] + ConstIndexParameterIndexNotFound(ConstParameterIndex), + #[error("multi-value parameter index {0} not found")] + ConstMultiValueParameterIndexNotFound(ConstParameterIndex), #[error("multi-value parameter key {0} not found")] MultiValueParameterKeyNotFound(String), #[error("inter-network parameter state not initialised")] diff --git a/pywr-core/src/metric.rs b/pywr-core/src/metric.rs index f01f7c5a..620cbc63 100644 --- a/pywr-core/src/metric.rs +++ b/pywr-core/src/metric.rs @@ -5,19 +5,25 @@ use crate::edge::EdgeIndex; use crate::models::MultiNetworkTransferIndex; use crate::network::Network; use crate::node::NodeIndex; -use crate::parameters::{GeneralParameterIndex, ParameterIndex, SimpleParameterIndex}; -use crate::state::{MultiValue, SimpleParameterValues, State}; +use crate::parameters::{ConstParameterIndex, GeneralParameterIndex, ParameterIndex, SimpleParameterIndex}; +use crate::state::{ConstParameterValues, MultiValue, SimpleParameterValues, State}; use crate::virtual_storage::VirtualStorageIndex; use crate::PywrError; #[derive(Clone, Debug, PartialEq)] pub enum ConstantMetricF64 { + ParameterValue(ConstParameterIndex), + IndexParameterValue(ConstParameterIndex), + MultiParameterValue((ConstParameterIndex, String)), Constant(f64), } impl ConstantMetricF64 { - pub fn get_value(&self) -> Result { + pub fn get_value(&self, values: &ConstParameterValues) -> Result { match self { + ConstantMetricF64::ParameterValue(idx) => Ok(values.get_const_parameter_f64(*idx)?), + ConstantMetricF64::IndexParameterValue(idx) => Ok(values.get_const_parameter_usize(*idx)? as f64), + ConstantMetricF64::MultiParameterValue((idx, key)) => Ok(values.get_const_multi_parameter_f64(*idx, key)?), ConstantMetricF64::Constant(v) => Ok(*v), } } @@ -36,7 +42,7 @@ impl SimpleMetricF64 { SimpleMetricF64::ParameterValue(idx) => Ok(values.get_simple_parameter_f64(*idx)?), SimpleMetricF64::IndexParameterValue(idx) => Ok(values.get_simple_parameter_usize(*idx)? as f64), SimpleMetricF64::MultiParameterValue((idx, key)) => Ok(values.get_simple_multi_parameter_f64(*idx, key)?), - SimpleMetricF64::Constant(m) => m.get_value(), + SimpleMetricF64::Constant(m) => m.get_value(&values.get_constant_values()), } } } @@ -170,6 +176,9 @@ impl From> for MetricF64 { match idx { ParameterIndex::General(idx) => Self::ParameterValue(idx), ParameterIndex::Simple(idx) => Self::Simple(SimpleMetricF64::ParameterValue(idx)), + ParameterIndex::Const(idx) => { + Self::Simple(SimpleMetricF64::Constant(ConstantMetricF64::ParameterValue(idx))) + } } } } @@ -179,6 +188,9 @@ impl From> for MetricF64 { match idx { ParameterIndex::General(idx) => Self::IndexParameterValue(idx), ParameterIndex::Simple(idx) => Self::Simple(SimpleMetricF64::IndexParameterValue(idx)), + ParameterIndex::Const(idx) => { + Self::Simple(SimpleMetricF64::Constant(ConstantMetricF64::IndexParameterValue(idx))) + } } } } @@ -203,15 +215,32 @@ impl TryFrom> for SimpleMetricUsize { } } +#[derive(Clone, Debug, PartialEq)] +pub enum ConstantMetricUsize { + IndexParameterValue(ConstParameterIndex), + Constant(usize), +} + +impl ConstantMetricUsize { + pub fn get_value(&self, values: &ConstParameterValues) -> Result { + match self { + ConstantMetricUsize::IndexParameterValue(idx) => values.get_const_parameter_usize(*idx), + ConstantMetricUsize::Constant(v) => Ok(*v), + } + } +} + #[derive(Clone, Debug, PartialEq)] pub enum SimpleMetricUsize { IndexParameterValue(SimpleParameterIndex), + Constant(ConstantMetricUsize), } impl SimpleMetricUsize { pub fn get_value(&self, values: &SimpleParameterValues) -> Result { match self { SimpleMetricUsize::IndexParameterValue(idx) => values.get_simple_parameter_usize(*idx), + SimpleMetricUsize::Constant(m) => m.get_value(values.get_constant_values()), } } } @@ -220,7 +249,6 @@ impl SimpleMetricUsize { pub enum MetricUsize { IndexParameterValue(GeneralParameterIndex), Simple(SimpleMetricUsize), - Constant(usize), } impl MetricUsize { @@ -228,7 +256,6 @@ impl MetricUsize { match self { Self::IndexParameterValue(idx) => state.get_parameter_index(*idx), Self::Simple(s) => s.get_value(&state.get_simple_parameter_values()), - Self::Constant(i) => Ok(*i), } } } @@ -238,6 +265,32 @@ impl From> for MetricUsize { match idx { ParameterIndex::General(idx) => Self::IndexParameterValue(idx), ParameterIndex::Simple(idx) => Self::Simple(SimpleMetricUsize::IndexParameterValue(idx)), + ParameterIndex::Const(idx) => Self::Simple(SimpleMetricUsize::Constant( + ConstantMetricUsize::IndexParameterValue(idx), + )), } } } +impl From for ConstantMetricUsize { + fn from(v: usize) -> Self { + ConstantMetricUsize::Constant(v) + } +} + +impl From for SimpleMetricUsize +where + T: Into, +{ + fn from(v: T) -> Self { + SimpleMetricUsize::Constant(v.into()) + } +} + +impl From for MetricUsize +where + T: Into, +{ + fn from(v: T) -> Self { + MetricUsize::Simple(v.into()) + } +} diff --git a/pywr-core/src/network.rs b/pywr-core/src/network.rs index e2a42b9d..88cfb5e6 100644 --- a/pywr-core/src/network.rs +++ b/pywr-core/src/network.rs @@ -250,17 +250,19 @@ impl Network { .with_derived_metrics(self.derived_metrics.len()) .with_inter_network_transfers(num_inter_network_transfers); - let state = state_builder.build(); + let mut state = state_builder.build(); - states.push(state); - - parameter_internal_states.push(ParameterStates::from_collection( - &self.parameters, - timesteps, - scenario_index, - )?); + let mut internal_states = ParameterStates::from_collection(&self.parameters, timesteps, scenario_index)?; metric_set_internal_states.push(self.metric_sets.iter().map(|p| p.setup()).collect::>()); + + // Calculate parameters that implement `ConstParameter` + // First we update the simple parameters + self.parameters + .compute_const(scenario_index, &mut state, &mut internal_states)?; + + states.push(state); + parameter_internal_states.push(internal_states); } Ok(NetworkState { @@ -683,6 +685,9 @@ impl Network { ) -> Result<(), PywrError> { // TODO reset parameter state to zero + self.parameters + .after_simple(timestep, scenario_index, state, internal_states)?; + for c_type in &self.resolve_order { match c_type { ComponentType::Node(_) => { @@ -1337,6 +1342,16 @@ impl Network { Ok(parameter_index.into()) } + /// Add a [`parameters::ConstParameter`] to the network + pub fn add_const_parameter( + &mut self, + parameter: Box>, + ) -> Result, PywrError> { + let parameter_index = self.parameters.add_const_f64(parameter)?; + + Ok(parameter_index.into()) + } + /// Add a `parameters::IndexParameter` to the network pub fn add_index_parameter( &mut self, @@ -1716,7 +1731,7 @@ mod tests { let _node_index = network.add_input_node("input", None).unwrap(); let input_max_flow = parameters::ConstantParameter::new("my-constant", 10.0); - let parameter = network.add_simple_parameter(Box::new(input_max_flow)).unwrap(); + let parameter = network.add_const_parameter(Box::new(input_max_flow)).unwrap(); // assign the new parameter to one of the nodes. let node = network.get_mut_node_by_name("input", None).unwrap(); @@ -1959,7 +1974,7 @@ mod tests { let input_max_flow_idx = model .network_mut() - .add_simple_parameter(Box::new(input_max_flow)) + .add_const_parameter(Box::new(input_max_flow)) .unwrap(); // assign the new parameter to one of the nodes. diff --git a/pywr-core/src/parameters/constant.rs b/pywr-core/src/parameters/constant.rs index 8ff1420f..75f530e3 100644 --- a/pywr-core/src/parameters/constant.rs +++ b/pywr-core/src/parameters/constant.rs @@ -1,9 +1,9 @@ use crate::parameters::{ downcast_internal_state_mut, downcast_internal_state_ref, downcast_variable_config_ref, ActivationFunction, - Parameter, ParameterMeta, ParameterState, SimpleParameter, VariableConfig, VariableParameter, + ConstParameter, Parameter, ParameterMeta, ParameterState, VariableConfig, VariableParameter, }; use crate::scenario::ScenarioIndex; -use crate::state::SimpleParameterValues; +use crate::state::ConstParameterValues; use crate::timestep::Timestep; use crate::PywrError; @@ -58,12 +58,11 @@ impl Parameter for ConstantParameter { } // TODO this should only need to implement `ConstantParameter` when that is implemented. -impl SimpleParameter for ConstantParameter { +impl ConstParameter for ConstantParameter { fn compute( &self, - _timestep: &Timestep, _scenario_index: &ScenarioIndex, - _values: &SimpleParameterValues, + _values: &ConstParameterValues, internal_state: &mut Option>, ) -> Result { Ok(self.value(internal_state)) diff --git a/pywr-core/src/parameters/mod.rs b/pywr-core/src/parameters/mod.rs index 19a0e0b1..53470e56 100644 --- a/pywr-core/src/parameters/mod.rs +++ b/pywr-core/src/parameters/mod.rs @@ -31,7 +31,7 @@ pub use self::rhai::RhaiParameter; use super::PywrError; use crate::network::Network; use crate::scenario::ScenarioIndex; -use crate::state::{MultiValue, SimpleParameterValues, State}; +use crate::state::{ConstParameterValues, MultiValue, SimpleParameterValues, State}; use crate::timestep::Timestep; pub use activation_function::ActivationFunction; pub use aggregated::{AggFunc, AggregatedParameter}; @@ -71,6 +71,56 @@ use std::ops::Deref; pub use threshold::{Predicate, ThresholdParameter}; pub use vector::VectorParameter; +/// Simple parameter index. +/// +/// This is a wrapper around usize that is used to index parameters in the state. It is +/// generic over the type of the value that the parameter returns. +#[derive(Debug)] +pub struct ConstParameterIndex { + idx: usize, + phantom: PhantomData, +} + +// These implementations are required because the derive macro does not work well with PhantomData. +// See issue: https://github.com/rust-lang/rust/issues/26925 +impl Clone for ConstParameterIndex { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for ConstParameterIndex {} +impl PartialEq for ConstParameterIndex { + fn eq(&self, other: &Self) -> bool { + self.idx == other.idx + } +} + +impl Eq for ConstParameterIndex {} + +impl ConstParameterIndex { + pub fn new(idx: usize) -> Self { + Self { + idx, + phantom: PhantomData, + } + } +} + +impl Deref for ConstParameterIndex { + type Target = usize; + + fn deref(&self) -> &Self::Target { + &self.idx + } +} + +impl Display for ConstParameterIndex { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.idx) + } +} + /// Simple parameter index. /// /// This is a wrapper around usize that is used to index parameters in the state. It is @@ -174,6 +224,7 @@ impl Display for GeneralParameterIndex { #[derive(Debug, Copy, Clone)] pub enum ParameterIndex { + Const(ConstParameterIndex), Simple(SimpleParameterIndex), General(GeneralParameterIndex), } @@ -181,6 +232,7 @@ pub enum ParameterIndex { impl PartialEq for ParameterIndex { fn eq(&self, other: &Self) -> bool { match (self, other) { + (Self::Const(idx1), Self::Const(idx2)) => idx1 == idx2, (Self::Simple(idx1), Self::Simple(idx2)) => idx1 == idx2, (Self::General(idx1), Self::General(idx2)) => idx1 == idx2, _ => false, @@ -193,6 +245,7 @@ impl Eq for ParameterIndex {} impl Display for ParameterIndex { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { + Self::Const(idx) => write!(f, "{}", idx), Self::Simple(idx) => write!(f, "{}", idx), Self::General(idx) => write!(f, "{}", idx), } @@ -210,6 +263,12 @@ impl From> for ParameterIndex { } } +impl From> for ParameterIndex { + fn from(idx: ConstParameterIndex) -> Self { + Self::Const(idx) + } +} + /// Meta data common to all parameters. #[derive(Debug, Clone)] pub struct ParameterMeta { @@ -255,6 +314,7 @@ struct ParameterStatesByType { #[derive(Clone)] pub struct ParameterStates { + constant: ParameterStatesByType, simple: ParameterStatesByType, general: ParameterStatesByType, } @@ -266,14 +326,20 @@ impl ParameterStates { timesteps: &[Timestep], scenario_index: &ScenarioIndex, ) -> Result { + let constant = collection.const_initial_states(timesteps, scenario_index)?; let simple = collection.simple_initial_states(timesteps, scenario_index)?; let general = collection.general_initial_states(timesteps, scenario_index)?; - Ok(Self { simple, general }) + Ok(Self { + constant, + simple, + general, + }) } pub fn get_f64_state(&self, index: ParameterIndex) -> Option<&Option>> { match index { + ParameterIndex::Const(idx) => self.constant.f64.get(*idx.deref()), ParameterIndex::Simple(idx) => self.simple.f64.get(*idx.deref()), ParameterIndex::General(idx) => self.general.f64.get(*idx.deref()), } @@ -286,8 +352,13 @@ impl ParameterStates { self.simple.f64.get(*index.deref()) } + pub fn get_const_f64_state(&self, index: SimpleParameterIndex) -> Option<&Option>> { + self.constant.f64.get(*index.deref()) + } + pub fn get_mut_f64_state(&mut self, index: ParameterIndex) -> Option<&mut Option>> { match index { + ParameterIndex::Const(idx) => self.constant.f64.get_mut(*idx.deref()), ParameterIndex::Simple(idx) => self.simple.f64.get_mut(*idx.deref()), ParameterIndex::General(idx) => self.general.f64.get_mut(*idx.deref()), } @@ -305,6 +376,12 @@ impl ParameterStates { ) -> Option<&mut Option>> { self.simple.f64.get_mut(*index.deref()) } + pub fn get_const_mut_f64_state( + &mut self, + index: ConstParameterIndex, + ) -> Option<&mut Option>> { + self.constant.f64.get_mut(*index.deref()) + } pub fn get_general_mut_usize_state( &mut self, index: GeneralParameterIndex, @@ -318,6 +395,12 @@ impl ParameterStates { ) -> Option<&mut Option>> { self.simple.usize.get_mut(*index.deref()) } + pub fn get_const_mut_usize_state( + &mut self, + index: ConstParameterIndex, + ) -> Option<&mut Option>> { + self.constant.usize.get_mut(*index.deref()) + } pub fn get_general_mut_multi_state( &mut self, @@ -332,6 +415,13 @@ impl ParameterStates { ) -> Option<&mut Option>> { self.simple.multi.get_mut(*index.deref()) } + + pub fn get_const_mut_multi_state( + &mut self, + index: ConstParameterIndex, + ) -> Option<&mut Option>> { + self.constant.multi.get_mut(*index.deref()) + } } /// Helper function to downcast to internal parameter state and print a helpful panic @@ -488,6 +578,24 @@ pub trait SimpleParameter: Parameter { } fn as_parameter(&self) -> &dyn Parameter; + + fn try_into_const(&self) -> Option>> { + None + } +} + +/// A trait that defines a component that produces a value each time-step. +/// +/// The trait is generic over the type of the value produced. +pub trait ConstParameter: Parameter { + fn compute( + &self, + scenario_index: &ScenarioIndex, + values: &ConstParameterValues, + internal_state: &mut Option>, + ) -> Result; + + fn as_parameter(&self) -> &dyn Parameter; } pub enum GeneralParameterType { @@ -538,6 +646,30 @@ impl From> for SimpleParameterType { } } +pub enum ConstParameterType { + Parameter(ConstParameterIndex), + Index(ConstParameterIndex), + Multi(ConstParameterIndex), +} + +impl From> for ConstParameterType { + fn from(idx: ConstParameterIndex) -> Self { + Self::Parameter(idx) + } +} + +impl From> for ConstParameterType { + fn from(idx: ConstParameterIndex) -> Self { + Self::Index(idx) + } +} + +impl From> for ConstParameterType { + fn from(idx: ConstParameterIndex) -> Self { + Self::Multi(idx) + } +} + pub enum ParameterType { Parameter(ParameterIndex), Index(ParameterIndex), @@ -593,6 +725,9 @@ pub trait VariableParameter { #[derive(Debug, Clone, Copy)] pub struct ParameterCollectionSize { + pub const_f64: usize, + pub const_usize: usize, + pub const_multi: usize, pub simple_f64: usize, pub simple_usize: usize, pub simple_multi: usize, @@ -604,11 +739,18 @@ pub struct ParameterCollectionSize { /// A collection of parameters that return different types. #[derive(Default)] pub struct ParameterCollection { + constant_f64: Vec>>, + constant_usize: Vec>>, + constant_multi: Vec>>, + constant_resolve_order: Vec, + simple_f64: Vec>>, simple_usize: Vec>>, simple_multi: Vec>>, simple_resolve_order: Vec, + // There is no resolve order for general parameters as they are resolved at a model + // level with other component types (e.g. nodes). general_f64: Vec>>, general_usize: Vec>>, general_multi: Vec>>, @@ -617,6 +759,9 @@ pub struct ParameterCollection { impl ParameterCollection { pub fn size(&self) -> ParameterCollectionSize { ParameterCollectionSize { + const_f64: self.constant_f64.len(), + const_usize: self.constant_usize.len(), + const_multi: self.constant_multi.len(), simple_f64: self.simple_f64.len(), simple_usize: self.simple_usize.len(), simple_multi: self.simple_multi.len(), @@ -687,7 +832,42 @@ impl ParameterCollection { }) } - /// Add a new parameter to the collection. + fn const_initial_states( + &self, + timesteps: &[Timestep], + scenario_index: &ScenarioIndex, + ) -> Result { + // Get the initial internal state + let f64_states = self + .constant_f64 + .iter() + .map(|p| p.setup(timesteps, scenario_index)) + .collect::, _>>()?; + + let usize_states = self + .constant_usize + .iter() + .map(|p| p.setup(timesteps, scenario_index)) + .collect::, _>>()?; + + let multi_states = self + .constant_multi + .iter() + .map(|p| p.setup(timesteps, scenario_index)) + .collect::, _>>()?; + + Ok(ParameterStatesByType { + f64: f64_states, + usize: usize_states, + multi: multi_states, + }) + } + + /// Add a [`GeneralParameter`] parameter to the collection. + /// + /// This function will add attempt to simplify the parameter and add it to the simple or + /// constant parameter list. If the parameter cannot be simplified it will be added to the + /// general parameter list. pub fn add_general_f64( &mut self, parameter: Box>, @@ -712,25 +892,46 @@ impl ParameterCollection { pub fn add_simple_f64( &mut self, parameter: Box>, - ) -> Result, PywrError> { - // TODO Fix this check - // if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { - // return Err(PywrError::SimpleParameterNameAlreadyExists( - // parameter.meta().name.to_string(), - // index, - // )); - // } + ) -> Result, PywrError> { + if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { + return Err(PywrError::ParameterNameAlreadyExists( + parameter.meta().name.to_string(), + index, + )); + } - let index = SimpleParameterIndex::new(self.simple_f64.len()); + match parameter.try_into_const() { + Some(constant) => self.add_const_f64(constant).map(|idx| idx.into()), + None => { + let index = SimpleParameterIndex::new(self.simple_f64.len()); - self.simple_f64.push(parameter); - self.simple_resolve_order.push(SimpleParameterType::Parameter(index)); + self.simple_f64.push(parameter); + self.simple_resolve_order.push(SimpleParameterType::Parameter(index)); - Ok(index) + Ok(index.into()) + } + } + } + + pub fn add_const_f64(&mut self, parameter: Box>) -> Result, PywrError> { + if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { + return Err(PywrError::ParameterNameAlreadyExists( + parameter.meta().name.to_string(), + index, + )); + } + + let index = ConstParameterIndex::new(self.constant_f64.len()); + + self.constant_f64.push(parameter); + self.constant_resolve_order.push(ConstParameterType::Parameter(index)); + + Ok(index.into()) } pub fn get_f64(&self, index: ParameterIndex) -> Option<&dyn Parameter> { match index { + ParameterIndex::Const(idx) => self.constant_f64.get(*idx.deref()).map(|p| p.as_parameter()), ParameterIndex::Simple(idx) => self.simple_f64.get(*idx.deref()).map(|p| p.as_parameter()), ParameterIndex::General(idx) => self.general_f64.get(*idx.deref()).map(|p| p.as_parameter()), } @@ -762,6 +963,13 @@ impl ParameterCollection { .map(|idx| SimpleParameterIndex::new(idx)) { Some(idx.into()) + } else if let Some(idx) = self + .constant_f64 + .iter() + .position(|p| p.meta().name == name) + .map(|idx| ConstParameterIndex::new(idx)) + { + Some(idx.into()) } else { None } @@ -954,6 +1162,133 @@ impl ParameterCollection { Ok(()) } + + /// Perform the after step for simple parameters. + pub fn after_simple( + &self, + timestep: &Timestep, + scenario_index: &ScenarioIndex, + state: &mut State, + internal_states: &mut ParameterStates, + ) -> Result<(), PywrError> { + for p in &self.simple_resolve_order { + match p { + SimpleParameterType::Parameter(idx) => { + // Find the parameter itself + let p = self + .simple_f64 + .get(*idx.deref()) + .ok_or(PywrError::SimpleParameterIndexNotFound(*idx))?; + // .. and its internal state + let internal_state = internal_states + .get_simple_mut_f64_state(*idx) + .ok_or(PywrError::SimpleParameterIndexNotFound(*idx))?; + + p.after( + timestep, + scenario_index, + &state.get_simple_parameter_values(), + internal_state, + )?; + } + SimpleParameterType::Index(idx) => { + // Find the parameter itself + let p = self + .simple_usize + .get(*idx.deref()) + .ok_or(PywrError::SimpleIndexParameterIndexNotFound(*idx))?; + // .. and its internal state + let internal_state = internal_states + .get_simple_mut_usize_state(*idx) + .ok_or(PywrError::SimpleIndexParameterIndexNotFound(*idx))?; + + p.after( + timestep, + scenario_index, + &state.get_simple_parameter_values(), + internal_state, + )?; + } + SimpleParameterType::Multi(idx) => { + // Find the parameter itself + let p = self + .simple_multi + .get(*idx.deref()) + .ok_or(PywrError::SimpleMultiValueParameterIndexNotFound(*idx))?; + // .. and its internal state + let internal_state = internal_states + .get_simple_mut_multi_state(*idx) + .ok_or(PywrError::SimpleMultiValueParameterIndexNotFound(*idx))?; + + p.compute( + timestep, + scenario_index, + &state.get_simple_parameter_values(), + internal_state, + )?; + } + } + } + + Ok(()) + } + + /// Compute the constant parameters. + pub fn compute_const( + &self, + scenario_index: &ScenarioIndex, + state: &mut State, + internal_states: &mut ParameterStates, + ) -> Result<(), PywrError> { + for p in &self.constant_resolve_order { + match p { + ConstParameterType::Parameter(idx) => { + // Find the parameter itself + let p = self + .constant_f64 + .get(*idx.deref()) + .ok_or(PywrError::ConstParameterIndexNotFound(*idx))?; + // .. and its internal state + let internal_state = internal_states + .get_const_mut_f64_state(*idx) + .ok_or(PywrError::ConstParameterIndexNotFound(*idx))?; + + let value = p.compute(scenario_index, &state.get_const_parameter_values(), internal_state)?; + state.set_const_parameter_value(*idx, value)?; + } + ConstParameterType::Index(idx) => { + // Find the parameter itself + let p = self + .constant_usize + .get(*idx.deref()) + .ok_or(PywrError::ConstIndexParameterIndexNotFound(*idx))?; + // .. and its internal state + let internal_state = internal_states + .get_const_mut_usize_state(*idx) + .ok_or(PywrError::ConstIndexParameterIndexNotFound(*idx))?; + + let value = p.compute(scenario_index, &state.get_const_parameter_values(), internal_state)?; + state.set_const_parameter_index(*idx, value)?; + } + ConstParameterType::Multi(idx) => { + // Find the parameter itself + let p = self + .constant_multi + .get(*idx.deref()) + .ok_or(PywrError::ConstMultiValueParameterIndexNotFound(*idx))?; + // .. and its internal state + let internal_state = internal_states + .get_const_mut_multi_state(*idx) + .ok_or(PywrError::ConstMultiValueParameterIndexNotFound(*idx))?; + + let value = p.compute(scenario_index, &state.get_const_parameter_values(), internal_state)?; + state.set_const_multi_parameter_value(*idx, value)?; + } + } + } + + Ok(()) + } } #[cfg(test)] diff --git a/pywr-core/src/state.rs b/pywr-core/src/state.rs index 7d5a0498..f3b2eff9 100644 --- a/pywr-core/src/state.rs +++ b/pywr-core/src/state.rs @@ -3,7 +3,9 @@ use crate::edge::{Edge, EdgeIndex}; use crate::models::MultiNetworkTransferIndex; use crate::network::Network; use crate::node::{Node, NodeIndex}; -use crate::parameters::{GeneralParameterIndex, ParameterCollection, ParameterCollectionSize, SimpleParameterIndex}; +use crate::parameters::{ + ConstParameterIndex, GeneralParameterIndex, ParameterCollection, ParameterCollectionSize, SimpleParameterIndex, +}; use crate::timestep::Timestep; use crate::virtual_storage::VirtualStorageIndex; use crate::PywrError; @@ -353,10 +355,12 @@ pub struct ParameterValuesCollection { impl ParameterValuesCollection { fn get_simple_parameter_values(&self) -> SimpleParameterValues { SimpleParameterValues { - constant: ParameterValuesRef { - values: &self.constant.values, - indices: &self.constant.indices, - multi_values: &self.constant.multi_values, + constant: ConstParameterValues { + constant: ParameterValuesRef { + values: &self.constant.values, + indices: &self.constant.indices, + multi_values: &self.constant.multi_values, + }, }, simple: ParameterValuesRef { values: &self.simple.values, @@ -365,6 +369,16 @@ impl ParameterValuesCollection { }, } } + + fn get_const_parameter_values(&self) -> ConstParameterValues { + ConstParameterValues { + constant: ParameterValuesRef { + values: &self.constant.values, + indices: &self.constant.indices, + multi_values: &self.constant.multi_values, + }, + } + } } pub struct ParameterValuesRef<'a> { @@ -388,7 +402,7 @@ impl<'a> ParameterValuesRef<'a> { } pub struct SimpleParameterValues<'a> { - constant: ParameterValuesRef<'a>, + constant: ConstParameterValues<'a>, simple: ParameterValuesRef<'a>, } @@ -417,6 +431,41 @@ impl<'a> SimpleParameterValues<'a> { .ok_or(PywrError::SimpleMultiValueParameterIndexNotFound(idx)) .copied() } + + pub fn get_constant_values(&self) -> &ConstParameterValues { + &self.constant + } +} + +pub struct ConstParameterValues<'a> { + constant: ParameterValuesRef<'a>, +} + +impl<'a> ConstParameterValues<'a> { + pub fn get_const_parameter_f64(&self, idx: ConstParameterIndex) -> Result { + self.constant + .get_value(*idx.deref()) + .ok_or(PywrError::ConstParameterIndexNotFound(idx)) + .copied() + } + + pub fn get_const_parameter_usize(&self, idx: ConstParameterIndex) -> Result { + self.constant + .get_index(*idx.deref()) + .ok_or(PywrError::ConstIndexParameterIndexNotFound(idx)) + .copied() + } + + pub fn get_const_multi_parameter_f64( + &self, + idx: ConstParameterIndex, + key: &str, + ) -> Result { + self.constant + .get_multi_value(*idx.deref(), key) + .ok_or(PywrError::ConstMultiValueParameterIndexNotFound(idx)) + .copied() + } } // State of the nodes and edges @@ -684,6 +733,13 @@ impl State { }) } + pub fn set_const_parameter_value(&mut self, idx: ConstParameterIndex, value: f64) -> Result<(), PywrError> { + self.parameters.constant.set_value(*idx, value).map_err(|e| match e { + ParameterValuesError::IndexNotFound(_) => PywrError::ConstParameterIndexNotFound(idx), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), + }) + } + pub fn get_parameter_index(&self, idx: GeneralParameterIndex) -> Result { self.parameters.general.get_index(*idx).map_err(|e| match e { ParameterValuesError::IndexNotFound(_) => PywrError::GeneralIndexParameterIndexNotFound(idx), @@ -708,6 +764,17 @@ impl State { ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), }) } + + pub fn set_const_parameter_index( + &mut self, + idx: ConstParameterIndex, + value: usize, + ) -> Result<(), PywrError> { + self.parameters.constant.set_index(*idx, value).map_err(|e| match e { + ParameterValuesError::IndexNotFound(_) => PywrError::ConstIndexParameterIndexNotFound(idx), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), + }) + } pub fn get_multi_parameter_value( &self, idx: GeneralParameterIndex, @@ -747,6 +814,20 @@ impl State { }) } + pub fn set_const_multi_parameter_value( + &mut self, + idx: ConstParameterIndex, + value: MultiValue, + ) -> Result<(), PywrError> { + self.parameters + .constant + .set_multi_value(*idx, value) + .map_err(|e| match e { + ParameterValuesError::IndexNotFound(_) => PywrError::ConstMultiValueParameterIndexNotFound(idx), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(key), + }) + } + pub fn get_multi_parameter_index( &self, idx: GeneralParameterIndex, @@ -762,6 +843,10 @@ impl State { self.parameters.get_simple_parameter_values() } + pub fn get_const_parameter_values(&self) -> ConstParameterValues { + self.parameters.get_const_parameter_values() + } + pub fn set_node_volume(&mut self, idx: NodeIndex, volume: f64) -> Result<(), PywrError> { self.network.set_volume(idx, volume) } @@ -888,7 +973,12 @@ impl StateBuilder { /// Build the [`State`] from the builder. pub fn build(self) -> State { - let constant = ParameterValues::new(0, 0, 0); + let constant = ParameterValues::new( + self.num_parameters.map(|s| s.const_f64).unwrap_or(0), + self.num_parameters.map(|s| s.const_usize).unwrap_or(0), + self.num_parameters.map(|s| s.const_multi).unwrap_or(0), + ); + let simple = ParameterValues::new( self.num_parameters.map(|s| s.simple_f64).unwrap_or(0), self.num_parameters.map(|s| s.simple_usize).unwrap_or(0), diff --git a/pywr-core/src/test_utils.rs b/pywr-core/src/test_utils.rs index a393cc22..f8d7e2ac 100644 --- a/pywr-core/src/test_utils.rs +++ b/pywr-core/src/test_utils.rs @@ -69,7 +69,7 @@ pub fn simple_network(network: &mut Network, inflow_scenario_index: usize, num_i let base_demand = 10.0; let demand_factor = ConstantParameter::new("demand-factor", 1.2); - let demand_factor = network.add_simple_parameter(Box::new(demand_factor)).unwrap(); + let demand_factor = network.add_const_parameter(Box::new(demand_factor)).unwrap(); let total_demand: AggregatedParameter = AggregatedParameter::new( "total-demand", @@ -79,7 +79,7 @@ pub fn simple_network(network: &mut Network, inflow_scenario_index: usize, num_i let total_demand = network.add_parameter(Box::new(total_demand)).unwrap(); let demand_cost = ConstantParameter::new("demand-cost", -10.0); - let demand_cost = network.add_simple_parameter(Box::new(demand_cost)).unwrap(); + let demand_cost = network.add_const_parameter(Box::new(demand_cost)).unwrap(); let output_node = network.get_mut_node_by_name("output", None).unwrap(); output_node.set_max_flow_constraint(Some(total_demand.into())).unwrap(); @@ -122,10 +122,10 @@ pub fn simple_storage_model() -> Model { // Apply demand to the model // TODO convenience function for adding a constant constraint. let demand = ConstantParameter::new("demand", 10.0); - let demand = network.add_simple_parameter(Box::new(demand)).unwrap(); + let demand = network.add_const_parameter(Box::new(demand)).unwrap(); let demand_cost = ConstantParameter::new("demand-cost", -10.0); - let demand_cost = network.add_simple_parameter(Box::new(demand_cost)).unwrap(); + let demand_cost = network.add_const_parameter(Box::new(demand_cost)).unwrap(); let output_node = network.get_mut_node_by_name("output", None).unwrap(); output_node.set_max_flow_constraint(Some(demand.into())).unwrap(); diff --git a/pywr-schema/src/parameters/core.rs b/pywr-schema/src/parameters/core.rs index 3c1161a2..826808b1 100644 --- a/pywr-schema/src/parameters/core.rs +++ b/pywr-schema/src/parameters/core.rs @@ -167,7 +167,7 @@ impl ConstantParameter { args: &LoadArgs, ) -> Result, SchemaError> { let p = pywr_core::parameters::ConstantParameter::new(&self.meta.name, self.value.load(args.tables)?); - Ok(network.add_simple_parameter(Box::new(p))?) + Ok(network.add_const_parameter(Box::new(p))?) } } diff --git a/pywr-schema/src/parameters/mod.rs b/pywr-schema/src/parameters/mod.rs index a4cb7bfe..1f50a98a 100644 --- a/pywr-schema/src/parameters/mod.rs +++ b/pywr-schema/src/parameters/mod.rs @@ -815,7 +815,7 @@ impl DynamicIndexValue { impl DynamicIndexValue { pub fn load(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result { let parameter_ref = match self { - DynamicIndexValue::Constant(v) => MetricUsize::Constant(v.load(args.tables)?), + DynamicIndexValue::Constant(v) => v.load(args.tables)?.into(), DynamicIndexValue::Dynamic(v) => v.load(network, args)?.into(), }; Ok(parameter_ref) From 07ad27a2f3c9afd499703a144a76363d876294d5 Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Thu, 4 Jul 2024 14:49:02 +0100 Subject: [PATCH 7/7] feat: Finalise ParameterCollection API for usize and multi. Implements the same API for all 3 types. Adds checks to ensure names are unique across all parameter variants. --- pywr-core/src/lib.rs | 8 +- pywr-core/src/metric.rs | 12 + pywr-core/src/network.rs | 32 +- pywr-core/src/parameters/mod.rs | 342 ++++++++++++++++++--- pywr-schema/src/metric.rs | 7 +- pywr-schema/src/model.rs | 1 - pywr-schema/src/nodes/piecewise_storage.rs | 8 +- 7 files changed, 327 insertions(+), 83 deletions(-) diff --git a/pywr-core/src/lib.rs b/pywr-core/src/lib.rs index 25df5d7d..2a6bcf01 100644 --- a/pywr-core/src/lib.rs +++ b/pywr-core/src/lib.rs @@ -93,12 +93,12 @@ pub enum PywrError { DerivedMetricIndexNotFound(DerivedMetricIndex), #[error("node name `{0}` already exists")] NodeNameAlreadyExists(String), - #[error("parameter name `{0}` already exists at index {1}")] - ParameterNameAlreadyExists(String, ParameterIndex), + #[error("parameter name `{0}` already exists")] + ParameterNameAlreadyExists(String), #[error("index parameter name `{0}` already exists at index {1}")] - IndexParameterNameAlreadyExists(String, GeneralParameterIndex), + IndexParameterNameAlreadyExists(String, ParameterIndex), #[error("multi-value parameter name `{0}` already exists at index {1}")] - MultiValueParameterNameAlreadyExists(String, GeneralParameterIndex), + MultiValueParameterNameAlreadyExists(String, ParameterIndex), #[error("metric set name `{0}` already exists")] MetricSetNameAlreadyExists(String), #[error("recorder name `{0}` already exists at index {1}")] diff --git a/pywr-core/src/metric.rs b/pywr-core/src/metric.rs index 620cbc63..523b6cbc 100644 --- a/pywr-core/src/metric.rs +++ b/pywr-core/src/metric.rs @@ -195,6 +195,18 @@ impl From> for MetricF64 { } } +impl From<(ParameterIndex, String)> for MetricF64 { + fn from((idx, key): (ParameterIndex, String)) -> Self { + match idx { + ParameterIndex::General(idx) => Self::MultiParameterValue((idx, key)), + ParameterIndex::Simple(idx) => Self::Simple(SimpleMetricF64::MultiParameterValue((idx, key))), + ParameterIndex::Const(idx) => Self::Simple(SimpleMetricF64::Constant( + ConstantMetricF64::MultiParameterValue((idx, key)), + )), + } + } +} + impl TryFrom> for SimpleMetricF64 { type Error = PywrError; fn try_from(idx: ParameterIndex) -> Result { diff --git a/pywr-core/src/network.rs b/pywr-core/src/network.rs index 88cfb5e6..f6709afc 100644 --- a/pywr-core/src/network.rs +++ b/pywr-core/src/network.rs @@ -12,7 +12,7 @@ use crate::solvers::{MultiStateSolver, Solver, SolverFeatures, SolverTimings}; use crate::state::{MultiValue, State, StateBuilder}; use crate::timestep::Timestep; use crate::virtual_storage::{VirtualStorage, VirtualStorageBuilder, VirtualStorageIndex, VirtualStorageVec}; -use crate::{parameters, recorders, GeneralParameterIndex, NodeIndex, PywrError, RecorderIndex}; +use crate::{parameters, recorders, NodeIndex, PywrError, RecorderIndex}; use rayon::prelude::*; use std::any::Any; use std::collections::HashSet; @@ -623,7 +623,7 @@ impl Network { GeneralParameterType::Index(idx) => { let p = self .parameters - .get_usize(*idx) + .get_general_usize(*idx) .ok_or(PywrError::GeneralIndexParameterIndexNotFound(*idx))?; // .. and its internal state @@ -638,7 +638,7 @@ impl Network { GeneralParameterType::Multi(idx) => { let p = self .parameters - .get_multi(*idx) + .get_general_multi(idx) .ok_or(PywrError::GeneralMultiValueParameterIndexNotFound(*idx))?; // .. and its internal state @@ -714,7 +714,7 @@ impl Network { GeneralParameterType::Index(idx) => { let p = self .parameters - .get_usize(*idx) + .get_general_usize(*idx) .ok_or(PywrError::GeneralIndexParameterIndexNotFound(*idx))?; // .. and its internal state @@ -727,7 +727,7 @@ impl Network { GeneralParameterType::Multi(idx) => { let p = self .parameters - .get_multi(*idx) + .get_general_multi(idx) .ok_or(PywrError::GeneralMultiValueParameterIndexNotFound(*idx))?; // .. and its internal state @@ -1123,21 +1123,15 @@ impl Network { } /// Get a [`Parameter`] from its index. - pub fn get_index_parameter( - &self, - index: GeneralParameterIndex, - ) -> Result<&dyn parameters::GeneralParameter, PywrError> { + pub fn get_index_parameter(&self, index: ParameterIndex) -> Result<&dyn parameters::Parameter, PywrError> { match self.parameters.get_usize(index) { Some(p) => Ok(p), - None => Err(PywrError::GeneralIndexParameterIndexNotFound(index)), + None => Err(PywrError::IndexParameterIndexNotFound(index)), } } /// Get a `IndexParameter` from a parameter's name - pub fn get_index_parameter_by_name( - &self, - name: &str, - ) -> Result<&dyn parameters::GeneralParameter, PywrError> { + pub fn get_index_parameter_by_name(&self, name: &str) -> Result<&dyn parameters::Parameter, PywrError> { match self.parameters.get_usize_by_name(name) { Some(parameter) => Ok(parameter), None => Err(PywrError::ParameterNotFound(name.to_string())), @@ -1145,7 +1139,7 @@ impl Network { } /// Get a `IndexParameterIndex` from a parameter's name - pub fn get_index_parameter_index_by_name(&self, name: &str) -> Result, PywrError> { + pub fn get_index_parameter_index_by_name(&self, name: &str) -> Result, PywrError> { match self.parameters.get_usize_index_by_name(name) { Some(idx) => Ok(idx), None => Err(PywrError::ParameterNotFound(name.to_string())), @@ -1155,11 +1149,11 @@ impl Network { /// Get a `MultiValueParameterIndex` from a parameter's name pub fn get_multi_valued_parameter( &self, - index: GeneralParameterIndex, - ) -> Result<&dyn parameters::GeneralParameter, PywrError> { + index: &ParameterIndex, + ) -> Result<&dyn parameters::Parameter, PywrError> { match self.parameters.get_multi(index) { Some(p) => Ok(p), - None => Err(PywrError::GeneralMultiValueParameterIndexNotFound(index)), + None => Err(PywrError::MultiValueParameterIndexNotFound(index.clone())), } } @@ -1167,7 +1161,7 @@ impl Network { pub fn get_multi_valued_parameter_index_by_name( &self, name: &str, - ) -> Result, PywrError> { + ) -> Result, PywrError> { match self.parameters.get_multi_index_by_name(name) { Some(idx) => Ok(idx), None => Err(PywrError::ParameterNotFound(name.to_string())), diff --git a/pywr-core/src/parameters/mod.rs b/pywr-core/src/parameters/mod.rs index 53470e56..9e67e914 100644 --- a/pywr-core/src/parameters/mod.rs +++ b/pywr-core/src/parameters/mod.rs @@ -863,6 +863,13 @@ impl ParameterCollection { }) } + /// Does a parameter with the given name exist in the collection. + pub fn has_name(&self, name: &str) -> bool { + self.get_f64_index_by_name(name).is_some() + || self.get_usize_index_by_name(name).is_some() + || self.get_multi_index_by_name(name).is_some() + } + /// Add a [`GeneralParameter`] parameter to the collection. /// /// This function will add attempt to simplify the parameter and add it to the simple or @@ -872,11 +879,8 @@ impl ParameterCollection { &mut self, parameter: Box>, ) -> Result, PywrError> { - if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { - return Err(PywrError::ParameterNameAlreadyExists( - parameter.meta().name.to_string(), - index, - )); + if self.has_name(parameter.meta().name.as_str()) { + return Err(PywrError::ParameterNameAlreadyExists(parameter.meta().name.to_string())); } match parameter.try_into_simple() { @@ -893,11 +897,8 @@ impl ParameterCollection { &mut self, parameter: Box>, ) -> Result, PywrError> { - if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { - return Err(PywrError::ParameterNameAlreadyExists( - parameter.meta().name.to_string(), - index, - )); + if self.has_name(parameter.meta().name.as_str()) { + return Err(PywrError::ParameterNameAlreadyExists(parameter.meta().name.to_string())); } match parameter.try_into_const() { @@ -914,11 +915,8 @@ impl ParameterCollection { } pub fn add_const_f64(&mut self, parameter: Box>) -> Result, PywrError> { - if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { - return Err(PywrError::ParameterNameAlreadyExists( - parameter.meta().name.to_string(), - index, - )); + if self.has_name(parameter.meta().name.as_str()) { + return Err(PywrError::ParameterNameAlreadyExists(parameter.meta().name.to_string())); } let index = ConstParameterIndex::new(self.constant_f64.len()); @@ -979,11 +977,8 @@ impl ParameterCollection { &mut self, parameter: Box>, ) -> Result, PywrError> { - if let Some(index) = self.get_usize_index_by_name(¶meter.meta().name) { - return Err(PywrError::IndexParameterNameAlreadyExists( - parameter.meta().name.to_string(), - index, - )); + if self.has_name(parameter.meta().name.as_str()) { + return Err(PywrError::ParameterNameAlreadyExists(parameter.meta().name.to_string())); } match parameter.try_into_simple() { @@ -1000,13 +995,9 @@ impl ParameterCollection { &mut self, parameter: Box>, ) -> Result, PywrError> { - // TODO Fix this check - // if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { - // return Err(PywrError::SimpleParameterNameAlreadyExists( - // parameter.meta().name.to_string(), - // index, - // )); - // } + if self.has_name(parameter.meta().name.as_str()) { + return Err(PywrError::ParameterNameAlreadyExists(parameter.meta().name.to_string())); + } let index = SimpleParameterIndex::new(self.simple_usize.len()); @@ -1015,33 +1006,75 @@ impl ParameterCollection { Ok(index) } - pub fn get_usize(&self, index: GeneralParameterIndex) -> Option<&dyn GeneralParameter> { + + pub fn add_const_usize( + &mut self, + parameter: Box>, + ) -> Result, PywrError> { + if self.has_name(parameter.meta().name.as_str()) { + return Err(PywrError::ParameterNameAlreadyExists(parameter.meta().name.to_string())); + } + + let index = ConstParameterIndex::new(self.constant_usize.len()); + + self.constant_usize.push(parameter); + self.constant_resolve_order.push(ConstParameterType::Index(index)); + + Ok(index.into()) + } + + pub fn get_usize(&self, index: ParameterIndex) -> Option<&dyn Parameter> { + match index { + ParameterIndex::Const(idx) => self.constant_usize.get(*idx.deref()).map(|p| p.as_parameter()), + ParameterIndex::Simple(idx) => self.simple_usize.get(*idx.deref()).map(|p| p.as_parameter()), + ParameterIndex::General(idx) => self.general_usize.get(*idx.deref()).map(|p| p.as_parameter()), + } + } + + pub fn get_general_usize(&self, index: GeneralParameterIndex) -> Option<&dyn GeneralParameter> { self.general_usize.get(*index.deref()).map(|p| p.as_ref()) } - pub fn get_usize_by_name(&self, name: &str) -> Option<&dyn GeneralParameter> { + pub fn get_usize_by_name(&self, name: &str) -> Option<&dyn Parameter> { self.general_usize .iter() .find(|p| p.meta().name == name) - .map(|p| p.as_ref()) + .map(|p| p.as_parameter()) } - pub fn get_usize_index_by_name(&self, name: &str) -> Option> { - self.general_usize + pub fn get_usize_index_by_name(&self, name: &str) -> Option> { + if let Some(idx) = self + .general_usize .iter() .position(|p| p.meta().name == name) .map(|idx| GeneralParameterIndex::new(idx)) + { + Some(idx.into()) + } else if let Some(idx) = self + .simple_usize + .iter() + .position(|p| p.meta().name == name) + .map(|idx| SimpleParameterIndex::new(idx)) + { + Some(idx.into()) + } else if let Some(idx) = self + .constant_usize + .iter() + .position(|p| p.meta().name == name) + .map(|idx| ConstParameterIndex::new(idx)) + { + Some(idx.into()) + } else { + None + } } pub fn add_general_multi( &mut self, parameter: Box>, ) -> Result, PywrError> { - if let Some(index) = self.get_multi_index_by_name(¶meter.meta().name) { - return Err(PywrError::MultiValueParameterNameAlreadyExists( - parameter.meta().name.to_string(), - index, - )); + if self.has_name(parameter.meta().name.as_str()) { + return Err(PywrError::ParameterNameAlreadyExists(parameter.meta().name.to_string())); } match parameter.try_into_simple() { @@ -1058,13 +1091,9 @@ impl ParameterCollection { &mut self, parameter: Box>, ) -> Result, PywrError> { - // TODO Fix this check - // if let Some(index) = self.get_f64_index_by_name(¶meter.meta().name) { - // return Err(PywrError::SimpleParameterNameAlreadyExists( - // parameter.meta().name.to_string(), - // index, - // )); - // } + if self.has_name(parameter.meta().name.as_str()) { + return Err(PywrError::ParameterNameAlreadyExists(parameter.meta().name.to_string())); + } let index = SimpleParameterIndex::new(self.simple_multi.len()); @@ -1073,22 +1102,69 @@ impl ParameterCollection { Ok(index) } - pub fn get_multi(&self, index: GeneralParameterIndex) -> Option<&dyn GeneralParameter> { + + pub fn add_const_multi( + &mut self, + parameter: Box>, + ) -> Result, PywrError> { + if self.has_name(parameter.meta().name.as_str()) { + return Err(PywrError::ParameterNameAlreadyExists(parameter.meta().name.to_string())); + } + + let index = ConstParameterIndex::new(self.constant_multi.len()); + + self.constant_multi.push(parameter); + self.constant_resolve_order.push(ConstParameterType::Multi(index)); + + Ok(index) + } + pub fn get_multi(&self, index: &ParameterIndex) -> Option<&dyn Parameter> { + match index { + ParameterIndex::Const(idx) => self.constant_multi.get(*idx.deref()).map(|p| p.as_parameter()), + ParameterIndex::Simple(idx) => self.simple_multi.get(*idx.deref()).map(|p| p.as_parameter()), + ParameterIndex::General(idx) => self.general_multi.get(*idx.deref()).map(|p| p.as_parameter()), + } + } + + pub fn get_general_multi( + &self, + index: &GeneralParameterIndex, + ) -> Option<&dyn GeneralParameter> { self.general_multi.get(*index.deref()).map(|p| p.as_ref()) } - pub fn get_multi_by_name(&self, name: &str) -> Option<&dyn GeneralParameter> { + pub fn get_multi_by_name(&self, name: &str) -> Option<&dyn Parameter> { self.general_multi .iter() .find(|p| p.meta().name == name) - .map(|p| p.as_ref()) + .map(|p| p.as_parameter()) } - pub fn get_multi_index_by_name(&self, name: &str) -> Option> { - self.general_multi + pub fn get_multi_index_by_name(&self, name: &str) -> Option> { + if let Some(idx) = self + .general_multi .iter() .position(|p| p.meta().name == name) .map(|idx| GeneralParameterIndex::new(idx)) + { + Some(idx.into()) + } else if let Some(idx) = self + .simple_multi + .iter() + .position(|p| p.meta().name == name) + .map(|idx| SimpleParameterIndex::new(idx)) + { + Some(idx.into()) + } else if let Some(idx) = self + .constant_multi + .iter() + .position(|p| p.meta().name == name) + .map(|idx| ConstParameterIndex::new(idx)) + { + Some(idx.into()) + } else { + None + } } pub fn compute_simple( @@ -1293,8 +1369,14 @@ impl ParameterCollection { #[cfg(test)] mod tests { - + use super::{ + ConstParameter, GeneralParameter, Parameter, ParameterCollection, ParameterMeta, ParameterState, + SimpleParameter, + }; + use crate::scenario::ScenarioIndex; + use crate::state::{ConstParameterValues, MultiValue}; use crate::timestep::{TimestepDuration, Timestepper}; + use crate::PywrError; use chrono::NaiveDateTime; // TODO tests need re-enabling @@ -1306,6 +1388,166 @@ mod tests { Timestepper::new(start, end, duration) } + /// Parameter for testing purposes + struct TestParameter { + meta: ParameterMeta, + } + + impl Default for TestParameter { + fn default() -> Self { + Self { + meta: ParameterMeta::new("test-parameter"), + } + } + } + impl Parameter for TestParameter { + fn meta(&self) -> &ParameterMeta { + &self.meta + } + } + + impl ConstParameter for TestParameter + where + T: From, + { + fn compute( + &self, + _scenario_index: &ScenarioIndex, + _values: &ConstParameterValues, + _internal_state: &mut Option>, + ) -> Result { + Ok(T::from(1)) + } + + fn as_parameter(&self) -> &dyn Parameter { + self + } + } + + impl ConstParameter for TestParameter { + fn compute( + &self, + _scenario_index: &ScenarioIndex, + _values: &ConstParameterValues, + _internal_state: &mut Option>, + ) -> Result { + Ok(MultiValue::default()) + } + + fn as_parameter(&self) -> &dyn Parameter { + self + } + } + impl SimpleParameter for TestParameter + where + T: From, + { + fn compute( + &self, + _timestep: &crate::timestep::Timestep, + _scenario_index: &ScenarioIndex, + _values: &crate::state::SimpleParameterValues, + _internal_state: &mut Option>, + ) -> Result { + Ok(T::from(1)) + } + + fn as_parameter(&self) -> &dyn Parameter { + self + } + } + + impl SimpleParameter for TestParameter { + fn compute( + &self, + _timestep: &crate::timestep::Timestep, + _scenario_index: &ScenarioIndex, + _values: &crate::state::SimpleParameterValues, + _internal_state: &mut Option>, + ) -> Result { + Ok(MultiValue::default()) + } + + fn as_parameter(&self) -> &dyn Parameter { + self + } + } + impl GeneralParameter for TestParameter + where + T: From, + { + fn compute( + &self, + _timestep: &crate::timestep::Timestep, + _scenario_index: &ScenarioIndex, + _model: &crate::network::Network, + _state: &crate::state::State, + _internal_state: &mut Option>, + ) -> Result { + Ok(T::from(1)) + } + + fn as_parameter(&self) -> &dyn Parameter { + self + } + } + + impl GeneralParameter for TestParameter { + fn compute( + &self, + _timestep: &crate::timestep::Timestep, + _scenario_index: &ScenarioIndex, + _model: &crate::network::Network, + _state: &crate::state::State, + _internal_state: &mut Option>, + ) -> Result { + Ok(MultiValue::default()) + } + + fn as_parameter(&self) -> &dyn Parameter { + self + } + } + + /// Test naming constraints on parameter collection. + #[test] + fn test_parameter_collection_name_constraints() { + let mut collection = ParameterCollection::default(); + + let ret = collection.add_const_f64(Box::new(TestParameter::default())); + assert!(ret.is_ok()); + + assert!(collection.has_name("test-parameter")); + + // Try to add a parameter with the same name + let ret = collection.add_const_f64(Box::new(TestParameter::default())); + assert!(ret.is_err()); + + let ret = collection.add_simple_f64(Box::new(TestParameter::default())); + assert!(ret.is_err()); + + let ret = collection.add_general_f64(Box::new(TestParameter::default())); + assert!(ret.is_err()); + + let ret = collection.add_const_usize(Box::new(TestParameter::default())); + assert!(ret.is_err()); + + let ret = collection.add_simple_usize(Box::new(TestParameter::default())); + assert!(ret.is_err()); + + let ret = collection.add_general_usize(Box::new(TestParameter::default())); + assert!(ret.is_err()); + + let ret = collection.add_const_multi(Box::new(TestParameter::default())); + assert!(ret.is_err()); + + let ret = collection.add_simple_multi(Box::new(TestParameter::default())); + assert!(ret.is_err()); + + let ret = collection.add_general_multi(Box::new(TestParameter::default())); + assert!(ret.is_err()); + } + // #[test] // /// Test `ConstantParameter` returns the correct value. // fn test_constant_parameter() { diff --git a/pywr-schema/src/metric.rs b/pywr-schema/src/metric.rs index 1493a3ec..37f1012d 100644 --- a/pywr-schema/src/metric.rs +++ b/pywr-schema/src/metric.rs @@ -321,16 +321,17 @@ impl ParameterReference { match &self.key { Some(key) => { // Key given; this should be a multi-valued parameter - Ok(MetricF64::MultiParameterValue(( + Ok(( network.get_multi_valued_parameter_index_by_name(&self.name)?, key.clone(), - ))) + ) + .into()) } None => { if let Ok(idx) = network.get_parameter_index_by_name(&self.name) { Ok(idx.into()) } else if let Ok(idx) = network.get_index_parameter_index_by_name(&self.name) { - Ok(MetricF64::IndexParameterValue(idx)) + Ok(idx.into()) } else { Err(SchemaError::ParameterNotFound(self.name.to_string())) } diff --git a/pywr-schema/src/model.rs b/pywr-schema/src/model.rs index 344f8841..ac30f3c3 100644 --- a/pywr-schema/src/model.rs +++ b/pywr-schema/src/model.rs @@ -1036,7 +1036,6 @@ mod core_tests { use ndarray::{Array1, Array2, Axis}; use pywr_core::{metric::MetricF64, recorders::AssertionRecorder, solvers::ClpSolver, test_utils::run_all_solvers}; use std::path::PathBuf; - use std::str::FromStr; fn model_str() -> &'static str { include_str!("./test_models/simple1.json") diff --git a/pywr-schema/src/nodes/piecewise_storage.rs b/pywr-schema/src/nodes/piecewise_storage.rs index 84796298..fa2b92f9 100644 --- a/pywr-schema/src/nodes/piecewise_storage.rs +++ b/pywr-schema/src/nodes/piecewise_storage.rs @@ -235,7 +235,7 @@ mod tests { use crate::model::PywrModel; use crate::nodes::PiecewiseStorageNode; use ndarray::{concatenate, Array, Array2, Axis}; - use pywr_core::metric::{MetricF64, MetricUsize}; + use pywr_core::metric::MetricF64; use pywr_core::recorders::{AssertionRecorder, IndexAssertionRecorder}; use pywr_core::test_utils::run_all_solvers; @@ -353,11 +353,7 @@ mod tests { .get_index_parameter_index_by_name("storage1-drought-index") .unwrap(); - let recorder = IndexAssertionRecorder::new( - "storage1-drought-index", - MetricUsize::IndexParameterValue(idx), - expected_drought_index, - ); + let recorder = IndexAssertionRecorder::new("storage1-drought-index", idx.into(), expected_drought_index); network.add_recorder(Box::new(recorder)).unwrap(); // Test all solvers