From 287cc676fc07c3b6f63c72f9362456b33a12f527 Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Fri, 5 Jul 2024 13:05:18 +0100 Subject: [PATCH] Refactor parameters into General, Simple and Constant (#194) 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 | 19 +- pywr-core/src/derived_metric.rs | 6 +- pywr-core/src/lib.rs | 36 +- pywr-core/src/metric.rs | 229 ++- pywr-core/src/network.rs | 370 +++-- pywr-core/src/node.rs | 202 +-- pywr-core/src/parameters/aggregated.rs | 110 +- pywr-core/src/parameters/aggregated_index.rs | 18 +- pywr-core/src/parameters/array.rs | 27 +- pywr-core/src/parameters/asymmetric.rs | 15 +- pywr-core/src/parameters/constant.rs | 32 +- .../parameters/control_curves/apportion.rs | 15 +- .../src/parameters/control_curves/index.rs | 15 +- .../parameters/control_curves/interpolated.rs | 16 +- .../parameters/control_curves/piecewise.rs | 21 +- .../src/parameters/control_curves/simple.rs | 16 +- .../control_curves/volume_between.rs | 49 +- pywr-core/src/parameters/delay.rs | 100 +- pywr-core/src/parameters/discount_factor.rs | 18 +- pywr-core/src/parameters/division.rs | 18 +- pywr-core/src/parameters/hydropower.rs | 16 +- pywr-core/src/parameters/indexed_array.rs | 16 +- pywr-core/src/parameters/interpolated.rs | 16 +- pywr-core/src/parameters/max.rs | 16 +- pywr-core/src/parameters/min.rs | 15 +- pywr-core/src/parameters/mod.rs | 1297 ++++++++++++++++- pywr-core/src/parameters/negative.rs | 16 +- pywr-core/src/parameters/negativemax.rs | 15 +- pywr-core/src/parameters/negativemin.rs | 15 +- pywr-core/src/parameters/offset.rs | 27 +- pywr-core/src/parameters/polynomial.rs | 16 +- pywr-core/src/parameters/profiles/daily.rs | 20 +- pywr-core/src/parameters/profiles/monthly.rs | 19 +- pywr-core/src/parameters/profiles/rbf.rs | 44 +- .../parameters/profiles/uniform_drawdown.rs | 19 +- pywr-core/src/parameters/profiles/weekly.rs | 20 +- pywr-core/src/parameters/py.rs | 74 +- pywr-core/src/parameters/rhai.rs | 17 +- pywr-core/src/parameters/threshold.rs | 15 +- pywr-core/src/parameters/vector.rs | 16 +- pywr-core/src/solvers/builder.rs | 4 +- pywr-core/src/state.rs | 459 ++++-- pywr-core/src/test_utils.rs | 72 +- pywr-core/src/timestep.rs | 1 - pywr-core/src/virtual_storage.rs | 76 +- pywr-schema/src/metric.rs | 21 +- pywr-schema/src/model.rs | 1 - .../src/nodes/annual_virtual_storage.rs | 11 +- pywr-schema/src/nodes/core.rs | 56 +- 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 | 32 +- .../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 | 27 +- pywr-schema/src/parameters/core.rs | 2 +- pywr-schema/src/parameters/mod.rs | 6 +- pywr-schema/src/parameters/profiles.rs | 10 +- pywr-schema/src/parameters/python.rs | 2 +- .../src/test_models/storage_max_volumes.json | 114 ++ .../src/timeseries/align_and_resample.rs | 6 +- 63 files changed, 3028 insertions(+), 953 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..9d3e79f9 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), @@ -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; @@ -321,17 +320,15 @@ 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); // 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/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 d777485e..2a6bcf01 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::{InterpolationError, ParameterIndex}; +use crate::parameters::{ + ConstParameterIndex, GeneralParameterIndex, InterpolationError, ParameterIndex, SimpleParameterIndex, +}; use crate::recorders::{AggregationError, MetricSetIndex, RecorderIndex}; use crate::state::MultiValue; use crate::virtual_storage::VirtualStorageIndex; @@ -49,9 +51,27 @@ 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("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("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")] InterNetworkParameterStateNotInitialised, @@ -73,10 +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, 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}")] @@ -161,6 +183,8 @@ 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}'")] @@ -169,6 +193,8 @@ pub enum PywrError { TimestepDurationMismatch, #[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 efb4600e..523b6cbc 100644 --- a/pywr-core/src/metric.rs +++ b/pywr-core/src/metric.rs @@ -5,11 +5,48 @@ 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::{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, 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), + } + } +} +#[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(&values.get_constant_values()), + } + } +} + #[derive(Clone, Debug, PartialEq)] pub enum MetricF64 { NodeInFlow(NodeIndex), @@ -19,16 +56,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 +97,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 +121,188 @@ 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()), + } + } +} + +impl TryFrom for SimpleMetricF64 { + type Error = PywrError; + + fn try_from(value: MetricF64) -> Result { + match value { + MetricF64::Simple(s) => Ok(s), + _ => Err(PywrError::CannotSimplifyMetric), + } + } +} + +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) + } +} + +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)), + ParameterIndex::Const(idx) => { + Self::Simple(SimpleMetricF64::Constant(ConstantMetricF64::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)), + ParameterIndex::Const(idx) => { + Self::Simple(SimpleMetricF64::Constant(ConstantMetricF64::IndexParameterValue(idx))) + } + } + } +} + +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 { + 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), } } } #[derive(Clone, Debug, PartialEq)] -pub enum MetricUsize { - IndexParameterValue(ParameterIndex), +pub enum ConstantMetricUsize { + IndexParameterValue(ConstParameterIndex), Constant(usize), } -impl MetricUsize { - pub fn get_value(&self, _network: &Network, state: &State) -> Result { +impl ConstantMetricUsize { + pub fn get_value(&self, values: &ConstParameterValues) -> Result { match self { - Self::IndexParameterValue(idx) => state.get_parameter_index(*idx), - Self::Constant(i) => Ok(*i), + ConstantMetricUsize::IndexParameterValue(idx) => values.get_const_parameter_usize(*idx), + ConstantMetricUsize::Constant(v) => Ok(*v), } } +} - pub fn name<'a>(&'a self, network: &'a Network) -> Result<&'a str, PywrError> { +#[derive(Clone, Debug, PartialEq)] +pub enum SimpleMetricUsize { + IndexParameterValue(SimpleParameterIndex), + Constant(ConstantMetricUsize), +} + +impl SimpleMetricUsize { + pub fn get_value(&self, values: &SimpleParameterValues) -> Result { match self { - Self::IndexParameterValue(idx) => network.get_index_parameter(idx).map(|p| p.name()), - Self::Constant(_) => Ok(""), + SimpleMetricUsize::IndexParameterValue(idx) => values.get_simple_parameter_usize(*idx), + SimpleMetricUsize::Constant(m) => m.get_value(values.get_constant_values()), } } +} - pub fn sub_name<'a>(&'a self, _network: &'a Network) -> Result, PywrError> { +#[derive(Clone, Debug, PartialEq)] +pub enum MetricUsize { + IndexParameterValue(GeneralParameterIndex), + Simple(SimpleMetricUsize), +} + +impl MetricUsize { + pub fn get_value(&self, _network: &Network, state: &State) -> Result { match self { - Self::IndexParameterValue(_) => Ok(None), - Self::Constant(_) => Ok(None), + Self::IndexParameterValue(idx) => state.get_parameter_index(*idx), + Self::Simple(s) => s.get_value(&state.get_simple_parameter_values()), } } +} - pub fn attribute(&self) -> &str { - match self { - Self::IndexParameterValue(_) => "value", - Self::Constant(_) => "value", +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)), + 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 52aa3aa2..f6709afc 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::{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, 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), } @@ -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,44 +244,25 @@ 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 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); - let state = state_builder.build(); - - states.push(state); + let mut state = state_builder.build(); - parameter_internal_states.push(ParameterStates::new( - initial_values_states, - initial_indices_states, - initial_multi_param_states, - )); + 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 { @@ -606,28 +585,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(*idx.deref()) - .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)?; @@ -637,31 +620,31 @@ impl Network { } state.set_parameter_value(*idx, value)?; } - ParameterType::Index(idx) => { + GeneralParameterType::Index(idx) => { let p = self - .index_parameters - .get(*idx.deref()) - .ok_or(PywrError::IndexParameterIndexNotFound(*idx))?; + .parameters + .get_general_usize(*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 - .multi_parameters - .get(*idx.deref()) - .ok_or(PywrError::MultiValueParameterIndexNotFound(*idx))?; + .parameters + .get_general_multi(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); @@ -702,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(_) => { @@ -712,42 +698,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(*idx.deref()) - .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 - .index_parameters - .get(*idx.deref()) - .ok_or(PywrError::IndexParameterIndexNotFound(*idx))?; + .parameters + .get_general_usize(*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 - .multi_parameters - .get(*idx.deref()) - .ok_or(PywrError::MultiValueParameterIndexNotFound(*idx))?; + .parameters + .get_general_multi(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)?; } @@ -828,7 +814,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); @@ -839,7 +825,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) @@ -849,12 +835,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) @@ -911,7 +917,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); @@ -922,7 +928,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); @@ -1093,63 +1099,49 @@ 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)), + 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_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)), - } - } - - /// 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()), + 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())), } } /// 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())), } } /// Get a [`Parameter`] from its index. - pub fn get_index_parameter( - &self, - 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)), + 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::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()), + 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())), } } /// 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())), } } @@ -1158,10 +1150,10 @@ impl Network { pub fn get_multi_valued_parameter( &self, 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)), + ) -> Result<&dyn parameters::Parameter, PywrError> { + match self.parameters.get_multi(index) { + Some(p) => Ok(p), + None => Err(PywrError::MultiValueParameterIndexNotFound(index.clone())), } } @@ -1170,8 +1162,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())), } } @@ -1249,8 +1241,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` @@ -1319,68 +1311,66 @@ 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>, + 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 = self.parameters.add_general_f64(parameter)?; - let parameter_index = ParameterIndex::new(self.parameters.len()); + // 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 the parameter ... - self.parameters.push(parameter); - // .. and add it to the resolve order - self.resolve_order - .push(ComponentType::Parameter(ParameterType::Parameter(parameter_index))); 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::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, - 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 = 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())); } - let parameter_index = ParameterIndex::new(self.index_parameters.len()); - - self.index_parameters.push(index_parameter); - // .. and add it to the resolve order - self.resolve_order - .push(ComponentType::Parameter(ParameterType::Index(parameter_index))); Ok(parameter_index) } /// Add a `parameters::MultiValueParameter` to the network pub fn add_multi_value_parameter( &mut self, - parameter: Box>, + 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 = 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())); } - let parameter_index = ParameterIndex::new(self.multi_parameters.len()); - - // Add the parameter ... - self.multi_parameters.push(parameter); - // .. and add it to the resolve order - self.resolve_order - .push(ComponentType::Parameter(ParameterType::Multi(parameter_index))); Ok(parameter_index) } @@ -1428,7 +1418,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,13 +1458,13 @@ 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 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)?; @@ -1499,12 +1489,12 @@ 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 .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) } @@ -1521,12 +1511,12 @@ 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 .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)) @@ -1542,14 +1532,14 @@ 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 .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)) @@ -1574,13 +1564,13 @@ 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 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)?; @@ -1605,12 +1595,12 @@ 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 .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) } @@ -1627,12 +1617,12 @@ 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 .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)) } @@ -1648,7 +1638,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}; @@ -1722,8 +1711,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())) ); @@ -1736,19 +1725,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_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(); - node.set_constraint( - ConstraintValue::Metric(MetricF64::ParameterValue(parameter)), - 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) ); } @@ -1800,7 +1785,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 @@ -1859,12 +1844,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 @@ -1981,15 +1966,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_const_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(MetricF64::ParameterValue(input_max_flow_idx)), - 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/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 dac7826d..5d4dbaf4 100644 --- a/pywr-core/src/parameters/aggregated.rs +++ b/pywr-core/src/parameters/aggregated.rs @@ -1,12 +1,13 @@ -use super::PywrError; -use crate::metric::MetricF64; +use super::{Parameter, ParameterState, PywrError, SimpleParameter}; +use crate::metric::{MetricF64, SimpleMetricF64}; 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::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,10 +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 { fn compute( &self, _timestep: &Timestep, @@ -58,8 +68,6 @@ impl Parameter 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; @@ -100,4 +108,84 @@ impl Parameter 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 ce5caa53..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::PywrError; +use super::{Parameter, ParameterState, 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::state::State; use crate::timestep::Timestep; use std::str::FromStr; @@ -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, @@ -121,4 +124,11 @@ impl Parameter 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 afde690a..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::{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}; @@ -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, @@ -42,6 +43,13 @@ impl Parameter for Array1Parameter { let value = self.array[[idx]]; Ok(value) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } pub struct Array2Parameter { @@ -62,11 +70,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, @@ -86,4 +96,11 @@ impl Parameter 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 3d7a33f4..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, 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; @@ -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, @@ -63,4 +65,11 @@ impl Parameter 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 74348db7..75f530e3 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, - Parameter, ParameterMeta, VariableConfig, VariableParameter, + ConstParameter, Parameter, ParameterMeta, ParameterState, VariableConfig, VariableParameter, }; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::ConstParameterValues; use crate::timestep::Timestep; use crate::PywrError; @@ -36,7 +35,7 @@ impl ConstantParameter { } } -impl Parameter for ConstantParameter { +impl Parameter for ConstantParameter { fn meta(&self) -> &ParameterMeta { &self.meta } @@ -49,24 +48,31 @@ 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) + } +} +// TODO this should only need to implement `ConstantParameter` when that is implemented. +impl ConstParameter for ConstantParameter { fn compute( &self, - _timestep: &Timestep, _scenario_index: &ScenarioIndex, - _model: &Network, - _state: &State, + _values: &ConstParameterValues, internal_state: &mut Option>, ) -> 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) + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self } } diff --git a/pywr-core/src/parameters/control_curves/apportion.rs b/pywr-core/src/parameters/control_curves/apportion.rs index 25d9d621..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::{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; @@ -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, @@ -57,4 +60,10 @@ impl Parameter 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 36b9ce1b..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::{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; @@ -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, @@ -45,4 +48,10 @@ impl Parameter 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 e6db2d20..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::{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; @@ -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, @@ -62,4 +65,11 @@ impl Parameter 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 8ecbc259..194d535f 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::{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; @@ -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, @@ -63,11 +64,17 @@ impl Parameter 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)] 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}; @@ -84,8 +91,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 9343de02..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::{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; @@ -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, @@ -50,4 +53,11 @@ impl Parameter 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 126362ae..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::{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,29 +24,35 @@ impl VolumeBetweenControlCurvesParameter { } } -impl Parameter for VolumeBetweenControlCurvesParameter { +impl Parameter for VolumeBetweenControlCurvesParameter +where + M: Send + Sync, +{ fn meta(&self) -> &ParameterMeta { &self.meta } +} + +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 4cf963b0..410d684c 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, 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 } @@ -39,7 +57,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, @@ -77,11 +97,71 @@ impl Parameter 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)] 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}; @@ -100,7 +180,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 e3b0d4bf..579a5507 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::{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; @@ -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, @@ -41,11 +43,17 @@ impl Parameter 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)] 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}; @@ -64,7 +72,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 f4e172be..98a369f3 100644 --- a/pywr-core/src/parameters/division.rs +++ b/pywr-core/src/parameters/division.rs @@ -1,9 +1,9 @@ -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, ParameterState}; use crate::scenario::ScenarioIndex; -use crate::state::{ParameterState, State}; +use crate::state::State; use crate::timestep::Timestep; use crate::PywrError::InvalidMetricValue; @@ -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, @@ -48,4 +49,11 @@ impl Parameter 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 314c4fdd..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::{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; @@ -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, @@ -106,4 +109,11 @@ impl Parameter 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 d56a3025..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::{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; @@ -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, @@ -40,4 +43,11 @@ impl Parameter 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 ea3ef5b9..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::{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; @@ -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, @@ -56,4 +57,11 @@ impl Parameter 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 d2b028bd..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::{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; @@ -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, @@ -38,4 +41,11 @@ impl Parameter 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 d3705709..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::{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; @@ -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, @@ -37,4 +39,11 @@ impl Parameter 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 f9bd8acd..9e67e914 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::{ConstParameterValues, 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,135 @@ 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 +/// 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 +208,7 @@ impl ParameterIndex { } } -impl Deref for ParameterIndex { +impl Deref for GeneralParameterIndex { type Target = usize; fn deref(&self) -> &Self::Target { @@ -115,14 +216,61 @@ 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 { + Const(ConstParameterIndex), + Simple(SimpleParameterIndex), + General(GeneralParameterIndex), +} + +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, + } + } +} + +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), + } + } +} +impl From> for ParameterIndex { + fn from(idx: GeneralParameterIndex) -> Self { + Self::General(idx) + } +} + +impl From> for ParameterIndex { + fn from(idx: SimpleParameterIndex) -> Self { + Self::Simple(idx) + } +} + +impl From> for ParameterIndex { + fn from(idx: ConstParameterIndex) -> Self { + Self::Const(idx) + } +} + /// Meta data common to all parameters. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ParameterMeta { pub name: String, pub comment: String, @@ -137,6 +285,145 @@ 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 { + constant: ParameterStatesByType, + 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 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 { + 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()), + } + } + 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_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()), + } + } + + 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_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, + ) -> 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_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, + 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()) + } + + 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 /// message if this fails. pub fn downcast_internal_state_mut(internal_state: &mut Option>) -> &mut T { @@ -192,7 +479,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,6 +493,41 @@ pub trait Parameter: Send + Sync { Ok(None) } + /// Return the parameter as a [`VariableParameter`] if it supports being a variable. + fn as_f64_variable(&self) -> Option<&dyn VariableParameter> { + None + } + + /// Return the parameter as a [`VariableParameter`] if it supports being a variable. + fn as_f64_variable_mut(&mut self) -> Option<&mut dyn VariableParameter> { + None + } + + /// Can this parameter be a variable + fn can_be_f64_variable(&self) -> bool { + self.as_f64_variable().is_some() + } + + /// Return the parameter as a [`VariableParameter`] if it supports being a variable. + fn as_u32_variable(&self) -> Option<&dyn VariableParameter> { + None + } + + /// Return the parameter as a [`VariableParameter`] if it supports being a variable. + fn as_u32_variable_mut(&mut self) -> Option<&mut dyn VariableParameter> { + None + } + + /// Can this parameter be a variable + fn can_be_u32_variable(&self) -> bool { + self.as_u32_variable().is_some() + } +} + +/// 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, @@ -226,34 +548,125 @@ pub trait Parameter: Send + Sync { Ok(()) } - /// Return the parameter as a [`VariableParameter`] if it supports being a variable. - fn as_f64_variable(&self) -> Option<&dyn VariableParameter> { + fn try_into_simple(&self) -> Option>> { None } - /// Return the parameter as a [`VariableParameter`] if it supports being a variable. - fn as_f64_variable_mut(&mut self) -> Option<&mut dyn VariableParameter> { + 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; + + fn try_into_const(&self) -> Option>> { None } +} - /// Can this parameter be a variable - fn can_be_f64_variable(&self) -> bool { - self.as_f64_variable().is_some() +/// 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 { + 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) } +} - /// Return the parameter as a [`VariableParameter`] if it supports being a variable. - fn as_u32_variable(&self) -> Option<&dyn VariableParameter> { - None +impl From> for GeneralParameterType { + fn from(idx: GeneralParameterIndex) -> Self { + Self::Multi(idx) } +} - /// Return the parameter as a [`VariableParameter`] if it supports being a variable. - fn as_u32_variable_mut(&mut self) -> Option<&mut dyn VariableParameter> { - None +pub enum SimpleParameterType { + Parameter(SimpleParameterIndex), + Index(SimpleParameterIndex), + Multi(SimpleParameterIndex), +} + +impl From> for SimpleParameterType { + fn from(idx: SimpleParameterIndex) -> Self { + Self::Parameter(idx) } +} - /// Can this parameter be a variable - fn can_be_u32_variable(&self) -> bool { - self.as_u32_variable().is_some() +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 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) } } @@ -263,6 +676,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 @@ -292,10 +723,660 @@ pub trait VariableParameter { fn get_upper_bounds(&self, variable_config: &dyn VariableConfig) -> Result, PywrError>; } +#[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, + 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 { + 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>>, +} + +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(), + 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 { + // Get the initial internal state + let f64_states = self + .general_f64 + .iter() + .map(|p| p.setup(timesteps, scenario_index)) + .collect::, _>>()?; + + let usize_states = self + .general_usize + .iter() + .map(|p| p.setup(timesteps, scenario_index)) + .collect::, _>>()?; + + let multi_states = self + .general_multi + .iter() + .map(|p| p.setup(timesteps, scenario_index)) + .collect::, _>>()?; + + 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, + }) + } + + 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, + }) + } + + /// 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 + /// 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>, + ) -> Result, PywrError> { + if self.has_name(parameter.meta().name.as_str()) { + return Err(PywrError::ParameterNameAlreadyExists(parameter.meta().name.to_string())); + } + + 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> { + if self.has_name(parameter.meta().name.as_str()) { + return Err(PywrError::ParameterNameAlreadyExists(parameter.meta().name.to_string())); + } + + 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)); + + Ok(index.into()) + } + } + } + + pub fn add_const_f64(&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_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()), + } + } + + 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 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> { + 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| 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 + } + } + + pub fn add_general_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())); + } + + 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()) + } + } + } + + pub fn add_simple_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 = SimpleParameterIndex::new(self.simple_usize.len()); + + self.simple_usize.push(parameter); + self.simple_resolve_order.push(SimpleParameterType::Index(index)); + + Ok(index) + } + + 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 Parameter> { + self.general_usize + .iter() + .find(|p| p.meta().name == name) + .map(|p| p.as_parameter()) + } + + 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 self.has_name(parameter.meta().name.as_str()) { + return Err(PywrError::ParameterNameAlreadyExists(parameter.meta().name.to_string())); + } + + 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> { + 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()); + + self.simple_multi.push(parameter); + self.simple_resolve_order.push(SimpleParameterType::Multi(index)); + + Ok(index) + } + + 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 Parameter> { + self.general_multi + .iter() + .find(|p| p.meta().name == name) + .map(|p| p.as_parameter()) + } + + 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( + &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(()) + } + + /// 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)] 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 @@ -307,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-core/src/parameters/negative.rs b/pywr-core/src/parameters/negative.rs index 4101ff4f..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::{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; @@ -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, @@ -36,4 +39,11 @@ impl Parameter 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 b2e9620d..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::{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; @@ -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, @@ -37,4 +39,11 @@ impl Parameter 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 a92fc614..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::{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; @@ -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, @@ -37,4 +39,11 @@ impl Parameter 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 4bd7c18b..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, - 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; @@ -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,12 +65,12 @@ 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) + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self } } diff --git a/pywr-core/src/parameters/polynomial.rs b/pywr-core/src/parameters/polynomial.rs index 66ebc436..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::{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; @@ -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, @@ -49,4 +52,11 @@ impl Parameter 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 c13d14c8..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::{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; @@ -20,18 +19,27 @@ impl DailyProfileParameter { } } -impl Parameter for DailyProfileParameter { +impl Parameter for DailyProfileParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +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 3bade16b..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::{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}; @@ -70,16 +69,17 @@ 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 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 { @@ -104,4 +104,11 @@ impl Parameter 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 a3d65876..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, Parameter, ParameterMeta, - VariableConfig, VariableParameter, + 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; @@ -100,7 +99,7 @@ impl RbfProfileParameter { } } -impl Parameter for RbfProfileParameter { +impl Parameter for RbfProfileParameter { fn meta(&self) -> &ParameterMeta { &self.meta } @@ -113,21 +112,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 +129,28 @@ impl Parameter for RbfProfileParameter { } } +impl SimpleParameter for RbfProfileParameter { + fn compute( + &self, + timestep: &Timestep, + _scenario_index: &ScenarioIndex, + _values: &SimpleParameterValues, + 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_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } +} + 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..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::{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}; @@ -31,16 +30,17 @@ impl UniformDrawdownProfileParameter { } } -impl Parameter for UniformDrawdownProfileParameter { +impl Parameter for UniformDrawdownProfileParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} +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) @@ -77,4 +77,11 @@ impl Parameter 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 7bb6eb6f..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::{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}; @@ -184,20 +183,29 @@ impl WeeklyProfileParameter { } } -impl Parameter for WeeklyProfileParameter { +impl Parameter for WeeklyProfileParameter { fn meta(&self) -> &ParameterMeta { &self.meta } +} + +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 6e8462a8..11af2eec 100644 --- a/pywr-core/src/parameters/py.rs +++ b/pywr-core/src/parameters/py.rs @@ -1,9 +1,9 @@ -use super::{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; @@ -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, @@ -194,21 +196,16 @@ impl Parameter for PyParameter { ) -> Result<(), PywrError> { self.after(timestep, scenario_index, model, state, internal_state) } -} -impl Parameter for PyParameter { - fn meta(&self) -> &ParameterMeta { - &self.meta - } - - fn setup( - &self, - _timesteps: &[Timestep], - _scenario_index: &ScenarioIndex, - ) -> Result>, PywrError> { - self.setup() + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self } +} +impl GeneralParameter for PyParameter { fn compute( &self, timestep: &Timestep, @@ -230,30 +227,16 @@ impl Parameter for PyParameter { ) -> Result<(), PywrError> { self.after(timestep, scenario_index, model, state, internal_state) } -} - -impl Parameter for PyParameter { - fn meta(&self) -> &ParameterMeta { - &self.meta - } - fn setup( - &self, - _timesteps: &[Timestep], - _scenario_index: &ScenarioIndex, - ) -> Result>, PywrError> { - self.setup() + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self } +} - // 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, @@ -323,6 +306,13 @@ impl Parameter 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)] @@ -379,18 +369,18 @@ 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() - .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); } @@ -448,18 +438,18 @@ 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() - .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..27a20f57 100644 --- a/pywr-core/src/parameters/rhai.rs +++ b/pywr-core/src/parameters/rhai.rs @@ -1,9 +1,9 @@ -use super::{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; @@ -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, @@ -111,6 +113,13 @@ impl Parameter for RhaiParameter { Ok(value) } + + fn as_parameter(&self) -> &dyn Parameter + where + Self: Sized, + { + self + } } #[cfg(test)] @@ -162,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 7655cecc..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, 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; @@ -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, @@ -100,4 +102,11 @@ impl Parameter 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 d1c0e1fc..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::{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; @@ -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, @@ -36,4 +39,11 @@ impl Parameter 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..f3b2eff9 100644 --- a/pywr-core/src/state.rs +++ b/pywr-core/src/state.rs @@ -3,15 +3,16 @@ 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::{ + ConstParameterIndex, 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 +231,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 +251,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 +276,198 @@ 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: 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: ConstParameterValues { + 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, + }, } } - 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_const_parameter_values(&self) -> ConstParameterValues { + ConstParameterValues { + constant: ParameterValuesRef { + values: &self.constant.values, + indices: &self.constant.indices, + multi_values: &self.constant.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: ConstParameterValues<'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() + } + + 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 #[derive(Clone, Debug)] pub struct NetworkState { @@ -626,7 +698,7 @@ impl NetworkState { #[derive(Debug, Clone)] pub struct State { network: NetworkState, - parameters: ParameterValues, + parameters: ParameterValuesCollection, derived_metrics: Vec, inter_network_values: Vec, } @@ -640,36 +712,139 @@ 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 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), + ParameterValuesError::KeyNotFound(key) => PywrError::MultiValueParameterKeyNotFound(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: ParameterIndex, key: &str) -> Result { - self.parameters.get_multi_value(idx, 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, + 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 + .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 set_const_multi_parameter_value( + &mut self, + idx: ConstParameterIndex, value: MultiValue, ) -> Result<(), PywrError> { - self.parameters.set_multi_value(idx, value) + 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: ParameterIndex, key: &str) -> Result { - self.parameters.get_multi_index(idx, 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_simple_parameter_values(&self) -> SimpleParameterValues { + 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> { @@ -749,9 +924,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 +941,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 +954,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 +973,36 @@ impl StateBuilder { /// Build the [`State`] from the builder. pub fn build(self) -> State { + 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), + 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 ae3c85f4..f8d7e2ac 100644 --- a/pywr-core/src/test_utils.rs +++ b/pywr-core/src/test_utils.rs @@ -3,8 +3,8 @@ 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::parameters::{AggFunc, AggregatedParameter, Array2Parameter, ConstantParameter, Parameter}; +use crate::node::StorageInitialVolume; +use crate::parameters::{AggFunc, AggregatedParameter, Array2Parameter, ConstantParameter, GeneralParameter}; use crate::recorders::AssertionRecorder; use crate::scenario::ScenarioGroupCollection; #[cfg(feature = "ipm-ocl")] @@ -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_const_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_const_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_const_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_const_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) } @@ -161,7 +143,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, @@ -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/timestep.rs b/pywr-core/src/timestep.rs index 83403278..929db119 100644 --- a/pywr-core/src/timestep.rs +++ b/pywr-core/src/timestep.rs @@ -13,7 +13,6 @@ const MILLISECS_IN_MINUTE: i64 = 1000 * 60; const MILLISECS_IN_SECOND: i64 = 1000; /// 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 9c096fae..691a550d 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())?; @@ -292,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}; @@ -368,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 @@ -428,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(); @@ -457,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 diff --git a/pywr-schema/src/metric.rs b/pywr-schema/src/metric.rs index 510d1ee8..37f1012d 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 { @@ -73,7 +73,7 @@ impl Metric { } None => args.timeseries.load_single_column(network, ts_ref.name.as_ref())?, }; - Ok(MetricF64::ParameterValue(param_idx)) + Ok(param_idx.into()) } Self::InlineParameter { definition } => { // This inline parameter could already have been loaded on a previous attempt @@ -86,13 +86,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(), @@ -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(MetricF64::ParameterValue(idx)) + 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/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..a823001d 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,12 @@ 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] fn test_input() { @@ -970,4 +969,19 @@ 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") + } + + #[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..fa2b92f9 100644 --- a/pywr-schema/src/nodes/piecewise_storage.rs +++ b/pywr-schema/src/nodes/piecewise_storage.rs @@ -7,8 +7,8 @@ use crate::nodes::{NodeAttribute, NodeMeta}; #[cfg(feature = "core")] use pywr_core::{ derived_metric::DerivedMetric, - metric::MetricF64, - node::{ConstraintValue, StorageInitialVolume}, + metric::{MetricF64, SimpleMetricF64}, + node::StorageInitialVolume, parameters::VolumeBetweenControlCurvesParameter, }; use pywr_schema_macros::PywrVisitAll; @@ -86,7 +86,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 +95,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 +105,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 +135,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 +152,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); @@ -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 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..b6c5064b 100644 --- a/pywr-schema/src/nodes/water_treatment_works.rs +++ b/pywr-schema/src/nodes/water_treatment_works.rs @@ -7,7 +7,10 @@ use crate::nodes::{NodeAttribute, NodeMeta}; #[cfg(feature = "core")] use num::Zero; #[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; @@ -178,20 +181,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..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_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 71cc37ee..1f50a98a 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 @@ -815,8 +815,8 @@ 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::Dynamic(v) => MetricUsize::IndexParameterValue(v.load(network, args)?), + DynamicIndexValue::Constant(v) => v.load(args.tables)?.into(), + 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..7e66f0cf 100644 --- a/pywr-schema/src/parameters/profiles.rs +++ b/pywr-schema/src/parameters/profiles.rs @@ -31,7 +31,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 +104,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 +189,7 @@ impl UniformDrawdownProfileParameter { reset_month, residual_days, ); - Ok(network.add_parameter(Box::new(p))?) + Ok(network.add_simple_parameter(Box::new(p))?) } } @@ -358,7 +358,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 +542,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/align_and_resample.rs b/pywr-schema/src/timeseries/align_and_resample.rs index 6106829c..dde3e2f5 100644 --- a/pywr-schema/src/timeseries/align_and_resample.rs +++ b/pywr-schema/src/timeseries/align_and_resample.rs @@ -70,11 +70,7 @@ pub fn align_and_resample( // TODO: this does not extend the dataframe beyond its original end date. Should it do when using a forward fill strategy? // The df could be extend by the length of the duration it is being resampled to. df.clone() - .upsample::<[String; 0]>( - [], - "time", - Duration::parse(model_duration_string.as_str()), - )? + .upsample::<[String; 0]>([], "time", Duration::parse(model_duration_string.as_str()))? .fill_null(FillNullStrategy::Forward(None))? } Ordering::Equal => df,