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,