From 80d1a8775789bc5a5ebe2ea04e34c0da90506c4c Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Wed, 27 Mar 2024 10:02:58 +0000 Subject: [PATCH 1/3] refactor: Move OutputMetric to metrics module. --- pywr-schema/src/lib.rs | 1 + pywr-schema/src/metric.rs | 56 ++++++++++++++++++++++++++++++ pywr-schema/src/metric_sets/mod.rs | 54 +--------------------------- 3 files changed, 58 insertions(+), 53 deletions(-) create mode 100644 pywr-schema/src/metric.rs diff --git a/pywr-schema/src/lib.rs b/pywr-schema/src/lib.rs index 0413d44e..8966f42e 100644 --- a/pywr-schema/src/lib.rs +++ b/pywr-schema/src/lib.rs @@ -7,6 +7,7 @@ pub mod data_tables; pub mod edge; mod error; +pub mod metric; pub mod metric_sets; pub mod model; pub mod nodes; diff --git a/pywr-schema/src/metric.rs b/pywr-schema/src/metric.rs new file mode 100644 index 00000000..a33c199c --- /dev/null +++ b/pywr-schema/src/metric.rs @@ -0,0 +1,56 @@ +use crate::error::SchemaError; +use crate::model::PywrNetwork; +use crate::nodes::NodeAttribute; +use serde::{Deserialize, Serialize}; + +/// Output metrics that can be recorded from a model run. +#[derive(Deserialize, Serialize, Clone)] +#[serde(tag = "type")] +pub enum OutputMetric { + /// Output the default metric for a node. + Default { + node: String, + }, + Deficit { + node: String, + }, + Parameter { + name: String, + }, +} + +impl OutputMetric { + pub fn try_clone_into_metric( + &self, + network: &mut pywr_core::network::Network, + schema: &PywrNetwork, + ) -> Result { + match self { + Self::Default { node } => { + // Get the node from the schema; not the model itself + let node = schema + .get_node_by_name(node) + .ok_or_else(|| SchemaError::NodeNotFound(node.to_string()))?; + // Create and return the node's default metric + node.create_metric(network, None) + } + Self::Deficit { node } => { + // Get the node from the schema; not the model itself + let node = schema + .get_node_by_name(node) + .ok_or_else(|| SchemaError::NodeNotFound(node.to_string()))?; + // Create and return the metric + node.create_metric(network, Some(NodeAttribute::Deficit)) + } + Self::Parameter { name } => { + if let Ok(idx) = network.get_parameter_index_by_name(name) { + Ok(pywr_core::metric::MetricF64::ParameterValue(idx)) + } else if let Ok(idx) = network.get_index_parameter_index_by_name(name) { + Ok(pywr_core::metric::MetricF64::IndexParameterValue(idx)) + } else { + Err(SchemaError::ParameterNotFound(name.to_string())) + } + } + } + } +} diff --git a/pywr-schema/src/metric_sets/mod.rs b/pywr-schema/src/metric_sets/mod.rs index 853ed9ea..9c242e44 100644 --- a/pywr-schema/src/metric_sets/mod.rs +++ b/pywr-schema/src/metric_sets/mod.rs @@ -1,61 +1,9 @@ use crate::error::SchemaError; +use crate::metric::OutputMetric; use crate::model::PywrNetwork; -use crate::nodes::NodeAttribute; use serde::{Deserialize, Serialize}; use std::num::NonZeroUsize; -/// Output metrics that can be recorded from a model run. -#[derive(Deserialize, Serialize, Clone)] -#[serde(tag = "type")] -pub enum OutputMetric { - /// Output the default metric for a node. - Default { - node: String, - }, - Deficit { - node: String, - }, - Parameter { - name: String, - }, -} - -impl OutputMetric { - fn try_clone_into_metric( - &self, - network: &mut pywr_core::network::Network, - schema: &PywrNetwork, - ) -> Result { - match self { - OutputMetric::Default { node } => { - // Get the node from the schema; not the model itself - let node = schema - .get_node_by_name(node) - .ok_or_else(|| SchemaError::NodeNotFound(node.to_string()))?; - // Create and return the node's default metric - node.create_metric(network, None) - } - OutputMetric::Deficit { node } => { - // Get the node from the schema; not the model itself - let node = schema - .get_node_by_name(node) - .ok_or_else(|| SchemaError::NodeNotFound(node.to_string()))?; - // Create and return the metric - node.create_metric(network, Some(NodeAttribute::Deficit)) - } - OutputMetric::Parameter { name } => { - if let Ok(idx) = network.get_parameter_index_by_name(name) { - Ok(pywr_core::metric::MetricF64::ParameterValue(idx)) - } else if let Ok(idx) = network.get_index_parameter_index_by_name(name) { - Ok(pywr_core::metric::MetricF64::IndexParameterValue(idx)) - } else { - Err(SchemaError::ParameterNotFound(name.to_string())) - } - } - } - } -} - /// Aggregation function to apply over metric values. #[derive(serde::Deserialize, serde::Serialize, Debug, Copy, Clone)] #[serde(tag = "type")] From 4c2ee90ad643832c1b2aa1dcee270c46bfde0b4f Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Wed, 27 Mar 2024 10:03:42 +0000 Subject: [PATCH 2/3] refactor: Rename OutputMetric to Metric --- pywr-schema/src/metric.rs | 4 ++-- pywr-schema/src/metric_sets/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pywr-schema/src/metric.rs b/pywr-schema/src/metric.rs index a33c199c..54241667 100644 --- a/pywr-schema/src/metric.rs +++ b/pywr-schema/src/metric.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; /// Output metrics that can be recorded from a model run. #[derive(Deserialize, Serialize, Clone)] #[serde(tag = "type")] -pub enum OutputMetric { +pub enum Metric { /// Output the default metric for a node. Default { node: String, @@ -19,7 +19,7 @@ pub enum OutputMetric { }, } -impl OutputMetric { +impl Metric { pub fn try_clone_into_metric( &self, network: &mut pywr_core::network::Network, diff --git a/pywr-schema/src/metric_sets/mod.rs b/pywr-schema/src/metric_sets/mod.rs index 9c242e44..0ab4713a 100644 --- a/pywr-schema/src/metric_sets/mod.rs +++ b/pywr-schema/src/metric_sets/mod.rs @@ -1,5 +1,5 @@ use crate::error::SchemaError; -use crate::metric::OutputMetric; +use crate::metric::Metric; use crate::model::PywrNetwork; use serde::{Deserialize, Serialize}; use std::num::NonZeroUsize; @@ -82,7 +82,7 @@ impl From for pywr_core::recorders::Aggregator { #[derive(Deserialize, Serialize, Clone)] pub struct MetricSet { pub name: String, - pub metrics: Vec, + pub metrics: Vec, pub aggregator: Option, } From 83440492bf09664ea0e22ceae4d1fcfe648c3603 Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Thu, 28 Mar 2024 13:54:36 +0000 Subject: [PATCH 3/3] refactor: Refactor OutputMetric to a general Metric. --- pywr-core/src/metric.rs | 66 -- pywr-core/src/recorders/csv.rs | 20 +- pywr-core/src/recorders/hdf.rs | 54 +- pywr-core/src/recorders/metric_set.rs | 52 +- pywr-core/src/recorders/mod.rs | 2 +- .../models/aggregated-node1/expected.csv | 2 +- .../tests/models/aggregated-node1/model.json | 40 +- .../tests/models/piecewise-link1/expected.csv | 2 +- .../tests/models/piecewise-link1/model.json | 35 +- .../simple-custom-parameter/expected.csv | 2 +- .../models/simple-custom-parameter/model.json | 17 +- .../simple-storage-timeseries/expected.csv | 2 +- .../simple-storage-timeseries/model.json | 32 +- .../models/simple-timeseries/expected.csv | 2 +- .../tests/models/simple-timeseries/model.json | 17 +- pywr-schema-macros/src/lib.rs | 12 +- pywr-schema/src/error.rs | 2 + pywr-schema/src/metric.rs | 336 +++++++- pywr-schema/src/metric_sets/mod.rs | 12 +- pywr-schema/src/model.rs | 42 +- .../src/nodes/annual_virtual_storage.rs | 13 +- pywr-schema/src/nodes/core.rs | 72 +- pywr-schema/src/nodes/delay.rs | 7 +- pywr-schema/src/nodes/loss_link.rs | 15 +- pywr-schema/src/nodes/mod.rs | 42 +- .../src/nodes/monthly_virtual_storage.rs | 13 +- pywr-schema/src/nodes/piecewise_link.rs | 13 +- pywr-schema/src/nodes/piecewise_storage.rs | 12 +- pywr-schema/src/nodes/river.rs | 6 +- pywr-schema/src/nodes/river_gauge.rs | 65 +- .../src/nodes/river_split_with_gauge.rs | 15 +- .../src/nodes/rolling_virtual_storage.rs | 13 +- pywr-schema/src/nodes/virtual_storage.rs | 13 +- .../src/nodes/water_treatment_works.rs | 59 +- pywr-schema/src/parameters/aggregated.rs | 51 +- pywr-schema/src/parameters/control_curves.rs | 23 +- pywr-schema/src/parameters/core.rs | 14 +- pywr-schema/src/parameters/delay.rs | 5 +- pywr-schema/src/parameters/discount_factor.rs | 7 +- .../parameters/doc_examples/aggregated_1.json | 71 +- pywr-schema/src/parameters/indexed_array.rs | 6 +- pywr-schema/src/parameters/interpolated.rs | 14 +- pywr-schema/src/parameters/mod.rs | 362 ++------- pywr-schema/src/parameters/offset.rs | 5 +- pywr-schema/src/parameters/python.rs | 5 +- pywr-schema/src/parameters/thresholds.rs | 7 +- .../src/test_models/30-day-licence.json | 19 +- .../src/test_models/csv1-outputs-long.csv | 732 +++++++++--------- .../src/test_models/csv1-outputs-wide.csv | 3 +- pywr-schema/src/test_models/csv1.json | 14 +- .../src/test_models/csv2-outputs-long.csv | 26 +- .../src/test_models/csv2-outputs-wide.csv | 3 +- pywr-schema/src/test_models/csv2.json | 14 +- .../src/test_models/csv3-outputs-long.csv | 28 +- pywr-schema/src/test_models/csv3.json | 18 +- pywr-schema/src/test_models/delay1.json | 15 +- pywr-schema/src/test_models/hdf1.json | 14 +- pywr-schema/src/test_models/memory1.json | 14 +- .../src/test_models/multi1/network1.json | 10 +- .../src/test_models/multi1/network2.json | 5 +- .../src/test_models/multi2/network1.json | 5 +- .../src/test_models/multi2/network2.json | 5 +- .../src/test_models/piecewise_link1.json | 40 +- .../src/test_models/piecewise_storage1.json | 45 +- .../src/test_models/piecewise_storage2.json | 40 +- pywr-schema/src/test_models/river_gauge1.json | 66 ++ .../test_models/river_split_with_gauge1.json | 25 +- pywr-schema/src/test_models/simple1.json | 104 +-- pywr-schema/src/test_models/timeseries.json | 27 +- 69 files changed, 1638 insertions(+), 1311 deletions(-) create mode 100644 pywr-schema/src/test_models/river_gauge1.json diff --git a/pywr-core/src/metric.rs b/pywr-core/src/metric.rs index 46bc8545..efb4600e 100644 --- a/pywr-core/src/metric.rs +++ b/pywr-core/src/metric.rs @@ -86,72 +86,6 @@ impl MetricF64 { MetricF64::InterNetworkTransfer(idx) => state.get_inter_network_transfer_value(*idx), } } - pub fn name<'a>(&'a self, network: &'a Network) -> Result<&'a str, PywrError> { - match self { - Self::NodeInFlow(idx) | Self::NodeOutFlow(idx) | Self::NodeVolume(idx) => { - network.get_node(idx).map(|n| n.name()) - } - Self::AggregatedNodeInFlow(idx) | Self::AggregatedNodeOutFlow(idx) => { - network.get_aggregated_node(idx).map(|n| n.name()) - } - Self::AggregatedNodeVolume(idx) => network.get_aggregated_storage_node(idx).map(|n| n.name()), - Self::EdgeFlow(idx) => { - let edge = network.get_edge(idx)?; - network.get_node(&edge.from_node_index).map(|n| n.name()) - } - Self::ParameterValue(idx) => network.get_parameter(idx).map(|p| p.name()), - Self::IndexParameterValue(idx) => network.get_index_parameter(idx).map(|p| p.name()), - Self::MultiParameterValue((idx, _)) => network.get_multi_valued_parameter(idx).map(|p| p.name()), - Self::VirtualStorageVolume(idx) => network.get_virtual_storage_node(idx).map(|v| v.name()), - Self::MultiNodeInFlow { name, .. } | Self::MultiNodeOutFlow { name, .. } => Ok(name), - Self::Constant(_) => Ok(""), - Self::DerivedMetric(idx) => network.get_derived_metric(idx)?.name(network), - Self::InterNetworkTransfer(_) => todo!("InterNetworkTransfer name is not implemented"), - } - } - - pub fn sub_name<'a>(&'a self, network: &'a Network) -> Result, PywrError> { - match self { - Self::NodeInFlow(idx) | Self::NodeOutFlow(idx) | Self::NodeVolume(idx) => { - network.get_node(idx).map(|n| n.sub_name()) - } - Self::AggregatedNodeInFlow(idx) | Self::AggregatedNodeOutFlow(idx) => { - network.get_aggregated_node(idx).map(|n| n.sub_name()) - } - Self::AggregatedNodeVolume(idx) => network.get_aggregated_storage_node(idx).map(|n| n.sub_name()), - Self::EdgeFlow(idx) => { - let edge = network.get_edge(idx)?; - network.get_node(&edge.to_node_index).map(|n| Some(n.name())) - } - Self::ParameterValue(_) | Self::IndexParameterValue(_) | Self::MultiParameterValue(_) => Ok(None), - Self::VirtualStorageVolume(idx) => network.get_virtual_storage_node(idx).map(|v| v.sub_name()), - Self::MultiNodeInFlow { .. } | Self::MultiNodeOutFlow { .. } => Ok(None), - Self::Constant(_) => Ok(None), - Self::DerivedMetric(idx) => network.get_derived_metric(idx)?.sub_name(network), - Self::InterNetworkTransfer(_) => todo!("InterNetworkTransfer sub_name is not implemented"), - } - } - - pub fn attribute(&self) -> &str { - match self { - Self::NodeInFlow(_) => "inflow", - Self::NodeOutFlow(_) => "outflow", - Self::NodeVolume(_) => "volume", - Self::AggregatedNodeInFlow(_) => "inflow", - Self::AggregatedNodeOutFlow(_) => "outflow", - Self::AggregatedNodeVolume(_) => "volume", - Self::EdgeFlow(_) => "edge_flow", - Self::ParameterValue(_) => "value", - Self::IndexParameterValue(_) => "value", - Self::MultiParameterValue(_) => "value", - Self::VirtualStorageVolume(_) => "volume", - Self::MultiNodeInFlow { .. } => "inflow", - Self::MultiNodeOutFlow { .. } => "outflow", - Self::Constant(_) => "value", - Self::DerivedMetric(_) => "value", - Self::InterNetworkTransfer(_) => "value", - } - } } #[derive(Clone, Debug, PartialEq)] diff --git a/pywr-core/src/recorders/csv.rs b/pywr-core/src/recorders/csv.rs index 6858441f..aea1144e 100644 --- a/pywr-core/src/recorders/csv.rs +++ b/pywr-core/src/recorders/csv.rs @@ -80,27 +80,21 @@ impl Recorder for CsvWideFmtOutput { let mut writer = csv::Writer::from_path(&self.filename).map_err(|e| PywrError::CSVError(e.to_string()))?; let mut names = vec![]; - let mut sub_names = vec![]; let mut attributes = vec![]; let metric_set = network.get_metric_set(self.metric_set_idx)?; for metric in metric_set.iter_metrics() { - let name = metric.name(network)?.to_string(); - let sub_name = metric - .sub_name(network)? - .map_or_else(|| "".to_string(), |s| s.to_string()); + let name = metric.name().to_string(); let attribute = metric.attribute().to_string(); // Add entries for each scenario names.push(name); - sub_names.push(sub_name); attributes.push(attribute); } // These are the header rows in the CSV file; we start each let mut header_name = vec!["node".to_string()]; - let mut header_sub_name = vec!["sub-node".to_string()]; let mut header_attribute = vec!["attribute".to_string()]; let mut header_scenario = vec!["global-scenario-index".to_string()]; @@ -113,7 +107,6 @@ impl Recorder for CsvWideFmtOutput { for scenario_index in domain.scenarios().indices().iter() { // Repeat the names, sub-names and attributes for every scenario header_name.extend(names.clone()); - header_sub_name.extend(sub_names.clone()); header_attribute.extend(attributes.clone()); header_scenario.extend(vec![format!("{}", scenario_index.index); names.len()]); @@ -125,9 +118,7 @@ impl Recorder for CsvWideFmtOutput { writer .write_record(header_name) .map_err(|e| PywrError::CSVError(e.to_string()))?; - writer - .write_record(header_sub_name) - .map_err(|e| PywrError::CSVError(e.to_string()))?; + writer .write_record(header_attribute) .map_err(|e| PywrError::CSVError(e.to_string()))?; @@ -200,7 +191,6 @@ pub struct CsvLongFmtRecord { scenario_index: usize, metric_set: String, name: String, - sub_name: String, attribute: String, value: f64, } @@ -243,10 +233,7 @@ impl CsvLongFmtOutput { let metric_set = network.get_metric_set(*metric_set_idx)?; for (metric, value) in metric_set.iter_metrics().zip(current_values.iter()) { - let name = metric.name(network)?.to_string(); - let sub_name = metric - .sub_name(network)? - .map_or_else(|| "".to_string(), |s| s.to_string()); + let name = metric.name().to_string(); let attribute = metric.attribute().to_string(); let record = CsvLongFmtRecord { @@ -255,7 +242,6 @@ impl CsvLongFmtOutput { scenario_index: scenario_idx, metric_set: metric_set.name().to_string(), name, - sub_name, attribute, value: value.value, }; diff --git a/pywr-core/src/recorders/hdf.rs b/pywr-core/src/recorders/hdf.rs index 2be1d02a..50c516a5 100644 --- a/pywr-core/src/recorders/hdf.rs +++ b/pywr-core/src/recorders/hdf.rs @@ -1,4 +1,4 @@ -use super::{MetricSetState, PywrError, Recorder, RecorderMeta, Timestep}; +use super::{MetricSetState, OutputMetric, PywrError, Recorder, RecorderMeta, Timestep}; use crate::models::ModelDomain; use crate::network::Network; use crate::recorders::MetricSetIndex; @@ -95,12 +95,7 @@ impl Recorder for HDF5Recorder { let mut datasets = Vec::new(); for metric in metric_set.iter_metrics() { - let name = metric.name(network)?; - let sub_name = metric.sub_name(network)?; - let attribute = metric.attribute(); - - let ds = require_metric_dataset(root_grp, shape, name, sub_name, attribute)?; - + let ds = require_metric_dataset(root_grp, shape, metric)?; datasets.push(ds); } @@ -176,21 +171,35 @@ fn require_dataset>(parent: &Group, shape: S, name: &str) -> Re fn require_metric_dataset>( parent: &Group, shape: S, - name: &str, - sub_name: Option<&str>, - attribute: &str, + metric: &OutputMetric, ) -> Result { - match sub_name { - None => { - let grp = require_group(parent, name)?; - require_dataset(&grp, shape, attribute) - } - Some(sn) => { - let grp = require_group(parent, name)?; - let grp = require_group(&grp, sn)?; - require_dataset(&grp, shape, attribute) - } + let grp = require_group(parent, metric.name())?; + let ds = require_dataset(&grp, shape, metric.attribute())?; + + // Write the type and subtype as attributes + let ty = hdf5::types::VarLenUnicode::from_str(metric.ty()).map_err(|e| PywrError::HDF5Error(e.to_string()))?; + let attr = ds + .new_attr::() + .shape(()) + .create("pywr-type") + .map_err(|e| PywrError::HDF5Error(e.to_string()))?; + attr.as_writer() + .write_scalar(&ty) + .map_err(|e| PywrError::HDF5Error(e.to_string()))?; + + if let Some(sub_type) = metric.sub_type() { + let sub_type = + hdf5::types::VarLenUnicode::from_str(sub_type).map_err(|e| PywrError::HDF5Error(e.to_string()))?; + let attr = ds + .new_attr::() + .shape(()) + .create("pywr-subtype") + .map_err(|e| PywrError::HDF5Error(e.to_string()))?; + attr.as_writer() + .write_scalar(&sub_type) + .map_err(|e| PywrError::HDF5Error(e.to_string()))?; } + Ok(ds) } fn require_group(parent: &Group, name: &str) -> Result { @@ -208,13 +217,10 @@ fn require_group(parent: &Group, name: &str) -> Result { fn write_pywr_metadata(file: &hdf5::File) -> Result<(), PywrError> { let root = file.deref(); - let grp = require_group(root, "pywr")?; - - // Write the Pywr version as an attribute const VERSION: &str = env!("CARGO_PKG_VERSION"); let version = hdf5::types::VarLenUnicode::from_str(VERSION).map_err(|e| PywrError::HDF5Error(e.to_string()))?; - let attr = grp + let attr = root .new_attr::() .shape(()) .create("pywr-version") diff --git a/pywr-core/src/recorders/metric_set.rs b/pywr-core/src/recorders/metric_set.rs index 39de4afe..350127c2 100644 --- a/pywr-core/src/recorders/metric_set.rs +++ b/pywr-core/src/recorders/metric_set.rs @@ -9,6 +9,52 @@ use std::fmt; use std::fmt::{Display, Formatter}; use std::ops::Deref; +/// A container for a [`MetricF64`] that retains additional information from the schema. +/// +/// This is used to store the name and attribute of the metric so that it can be output in +/// a context that is relevant to the originating schema, and therefore more meaningful to the user. +#[derive(Clone, Debug)] +pub struct OutputMetric { + name: String, + attribute: String, + // The originating type of the metric (e.g. node, parameter, etc.) + ty: String, + // The originating subtype of the metric (e.g. node type, parameter type, etc.) + sub_type: Option, + metric: MetricF64, +} + +impl OutputMetric { + pub fn new(name: &str, attribute: &str, ty: &str, sub_type: Option<&str>, metric: MetricF64) -> Self { + Self { + name: name.to_string(), + attribute: attribute.to_string(), + ty: ty.to_string(), + sub_type: sub_type.map(|s| s.to_string()), + metric, + } + } + + pub fn get_value(&self, model: &Network, state: &State) -> Result { + self.metric.get_value(model, state) + } + + pub fn name(&self) -> &str { + &self.name + } + pub fn attribute(&self) -> &str { + &self.attribute + } + + pub fn ty(&self) -> &str { + &self.ty + } + + pub fn sub_type(&self) -> Option<&str> { + self.sub_type.as_deref() + } +} + #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] pub struct MetricSetIndex(usize); @@ -51,11 +97,11 @@ impl MetricSetState { pub struct MetricSet { name: String, aggregator: Option, - metrics: Vec, + metrics: Vec, } impl MetricSet { - pub fn new(name: &str, aggregator: Option, metrics: Vec) -> Self { + pub fn new(name: &str, aggregator: Option, metrics: Vec) -> Self { Self { name: name.to_string(), aggregator, @@ -67,7 +113,7 @@ impl MetricSet { pub fn name(&self) -> &str { &self.name } - pub fn iter_metrics(&self) -> impl Iterator + '_ { + pub fn iter_metrics(&self) -> impl Iterator + '_ { self.metrics.iter() } diff --git a/pywr-core/src/recorders/mod.rs b/pywr-core/src/recorders/mod.rs index 35920324..862869b7 100644 --- a/pywr-core/src/recorders/mod.rs +++ b/pywr-core/src/recorders/mod.rs @@ -17,7 +17,7 @@ pub use csv::{CsvLongFmtOutput, CsvLongFmtRecord, CsvWideFmtOutput}; use float_cmp::{approx_eq, ApproxEq, F64Margin}; pub use hdf::HDF5Recorder; pub use memory::{Aggregation, AggregationError, MemoryRecorder}; -pub use metric_set::{MetricSet, MetricSetIndex, MetricSetState}; +pub use metric_set::{MetricSet, MetricSetIndex, MetricSetState, OutputMetric}; use ndarray::prelude::*; use ndarray::Array2; use std::any::Any; diff --git a/pywr-python/tests/models/aggregated-node1/expected.csv b/pywr-python/tests/models/aggregated-node1/expected.csv index 29fccc0f..6c821818 100644 --- a/pywr-python/tests/models/aggregated-node1/expected.csv +++ b/pywr-python/tests/models/aggregated-node1/expected.csv @@ -1,5 +1,5 @@ date,input1,link1,link2,agg-node,output1 -,outflow,outflow,outflow,outflow,inflow +,Outflow,Outflow,Outflow,Outflow,Inflow 2021-01-01,1,1,0,1,1 2021-01-02,2,2,0,2,2 2021-01-03,3,2,1,3,3 diff --git a/pywr-python/tests/models/aggregated-node1/model.json b/pywr-python/tests/models/aggregated-node1/model.json index 1ddd2c59..7a034986 100644 --- a/pywr-python/tests/models/aggregated-node1/model.json +++ b/pywr-python/tests/models/aggregated-node1/model.json @@ -24,12 +24,18 @@ { "name": "link1", "type": "Link", - "max_flow": 2.0 + "max_flow": { + "type": "Constant", + "value": 2.0 + } }, { "name": "link2", "type": "Link", - "cost": 1.0 + "cost": { + "type": "Constant", + "value": 1.0 + } }, { "name": "agg-node", @@ -38,12 +44,18 @@ "link1", "link2" ], - "max_flow": 5.0 + "max_flow": { + "type": "Constant", + "value": 5.0 + } }, { "name": "output1", "type": "Output", - "cost": -10.0, + "cost": { + "type": "Constant", + "value": -10.0 + }, "max_flow": { "type": "Parameter", "name": "demand" @@ -89,24 +101,24 @@ "name": "nodes", "metrics": [ { - "type": "Default", - "node": "input1" + "type": "Node", + "name": "input1" }, { - "type": "Default", - "node": "link1" + "type": "Node", + "name": "link1" }, { - "type": "Default", - "node": "link2" + "type": "Node", + "name": "link2" }, { - "type": "Default", - "node": "output1" + "type": "Node", + "name": "output1" }, { - "type": "Default", - "node": "agg-node" + "type": "Node", + "name": "agg-node" } ] } diff --git a/pywr-python/tests/models/piecewise-link1/expected.csv b/pywr-python/tests/models/piecewise-link1/expected.csv index 98257025..f203cf6c 100644 --- a/pywr-python/tests/models/piecewise-link1/expected.csv +++ b/pywr-python/tests/models/piecewise-link1/expected.csv @@ -1,5 +1,5 @@ date,input1,link1,mrf1,term1,demand1 -,outflow,outflow,outflow,inflow,inflow +,Outflow,Outflow,Outflow,Inflow,Inflow 2021-01-01,1,1,1,1,0 2021-01-02,2,2,1,1,1 2021-01-03,3,3,1,1,2 diff --git a/pywr-python/tests/models/piecewise-link1/model.json b/pywr-python/tests/models/piecewise-link1/model.json index 94f0d359..9fd4085e 100644 --- a/pywr-python/tests/models/piecewise-link1/model.json +++ b/pywr-python/tests/models/piecewise-link1/model.json @@ -38,8 +38,14 @@ "type": "PiecewiseLink", "steps": [ { - "cost": -10.0, - "max_flow": 1.0 + "cost": { + "type": "Constant", + "value": -10.0 + }, + "max_flow": { + "type": "Constant", + "value": 1.0 + } }, { } @@ -52,7 +58,10 @@ { "name": "demand1", "type": "Output", - "cost": -5.0, + "cost": { + "type": "Constant", + "value": -5.0 + }, "max_flow": { "type": "Parameter", "name": "demand" @@ -98,24 +107,24 @@ "name": "nodes", "metrics": [ { - "type": "Default", - "node": "input1" + "type": "Node", + "name": "input1" }, { - "type": "Default", - "node": "link1" + "type": "Node", + "name": "link1" }, { - "type": "Default", - "node": "mrf1" + "type": "Node", + "name": "mrf1" }, { - "type": "Default", - "node": "demand1" + "type": "Node", + "name": "demand1" }, { - "type": "Default", - "node": "term1" + "type": "Node", + "name": "term1" } ] } diff --git a/pywr-python/tests/models/simple-custom-parameter/expected.csv b/pywr-python/tests/models/simple-custom-parameter/expected.csv index acf3f0d3..330f4b89 100644 --- a/pywr-python/tests/models/simple-custom-parameter/expected.csv +++ b/pywr-python/tests/models/simple-custom-parameter/expected.csv @@ -1,5 +1,5 @@ date,input1,link1,output1 -,outflow,outflow,inflow +,Outflow,Outflow,Inflow 2021-01-01,1,1,1 2021-01-02,2,2,2 2021-01-03,3,3,3 diff --git a/pywr-python/tests/models/simple-custom-parameter/model.json b/pywr-python/tests/models/simple-custom-parameter/model.json index c2da5aa0..927b80e2 100644 --- a/pywr-python/tests/models/simple-custom-parameter/model.json +++ b/pywr-python/tests/models/simple-custom-parameter/model.json @@ -28,7 +28,10 @@ { "name": "output1", "type": "Output", - "cost": -10.0, + "cost": { + "type": "Constant", + "value": -10.0 + }, "max_flow": { "type": "Parameter", "name": "demand" @@ -73,16 +76,16 @@ "name": "nodes", "metrics": [ { - "type": "Default", - "node": "input1" + "type": "Node", + "name": "input1" }, { - "type": "Default", - "node": "link1" + "type": "Node", + "name": "link1" }, { - "type": "Default", - "node": "output1" + "type": "Node", + "name": "output1" } ] } diff --git a/pywr-python/tests/models/simple-storage-timeseries/expected.csv b/pywr-python/tests/models/simple-storage-timeseries/expected.csv index 03cd818e..670203b8 100644 --- a/pywr-python/tests/models/simple-storage-timeseries/expected.csv +++ b/pywr-python/tests/models/simple-storage-timeseries/expected.csv @@ -1,5 +1,5 @@ date,input1,storage1,output1 -,outflow,volume,inflow +,Outflow,Volume,Inflow 2021-01-01,9,499,10 2021-01-02,9,498,10 2021-01-03,9,497,10 diff --git a/pywr-python/tests/models/simple-storage-timeseries/model.json b/pywr-python/tests/models/simple-storage-timeseries/model.json index ac0f6201..4bc84f3b 100644 --- a/pywr-python/tests/models/simple-storage-timeseries/model.json +++ b/pywr-python/tests/models/simple-storage-timeseries/model.json @@ -12,21 +12,33 @@ { "name": "input1", "type": "Input", - "max_flow": 9.0 + "max_flow": { + "type": "Constant", + "value": 9.0 + } }, { "name": "storage1", "type": "Storage", - "cost": -1.0, + "cost": { + "type": "Constant", + "value": -1.0 + }, "initial_volume": { "Absolute": 500.0 }, - "max_volume": 1000.0 + "max_volume": { + "type": "Constant", + "value": 1000.0 + } }, { "name": "output1", "type": "Output", - "cost": -10.0, + "cost": { + "type": "Constant", + "value": -10.0 + }, "max_flow": { "type": "Parameter", "name": "demand" @@ -55,16 +67,16 @@ "name": "nodes", "metrics": [ { - "type": "Default", - "node": "input1" + "type": "Node", + "name": "input1" }, { - "type": "Default", - "node": "storage1" + "type": "Node", + "name": "storage1" }, { - "type": "Default", - "node": "output1" + "type": "Node", + "name": "output1" } ] } diff --git a/pywr-python/tests/models/simple-timeseries/expected.csv b/pywr-python/tests/models/simple-timeseries/expected.csv index acf3f0d3..330f4b89 100644 --- a/pywr-python/tests/models/simple-timeseries/expected.csv +++ b/pywr-python/tests/models/simple-timeseries/expected.csv @@ -1,5 +1,5 @@ date,input1,link1,output1 -,outflow,outflow,inflow +,Outflow,Outflow,Inflow 2021-01-01,1,1,1 2021-01-02,2,2,2 2021-01-03,3,3,3 diff --git a/pywr-python/tests/models/simple-timeseries/model.json b/pywr-python/tests/models/simple-timeseries/model.json index 90f7e9cc..417457b3 100644 --- a/pywr-python/tests/models/simple-timeseries/model.json +++ b/pywr-python/tests/models/simple-timeseries/model.json @@ -28,7 +28,10 @@ { "name": "output1", "type": "Output", - "cost": -10.0, + "cost": { + "type": "Constant", + "value": -10.0 + }, "max_flow": { "type": "Parameter", "name": "demand" @@ -66,16 +69,16 @@ "name": "nodes", "metrics": [ { - "type": "Default", - "node": "input1" + "type": "Node", + "name": "input1" }, { - "type": "Default", - "node": "link1" + "type": "Node", + "name": "link1" }, { - "type": "Default", - "node": "output1" + "type": "Node", + "name": "output1" } ] } diff --git a/pywr-schema-macros/src/lib.rs b/pywr-schema-macros/src/lib.rs index 827560d8..a2a1372c 100644 --- a/pywr-schema-macros/src/lib.rs +++ b/pywr-schema-macros/src/lib.rs @@ -17,7 +17,7 @@ enum PywrField { /// Generates a [`TokenStream`] containing the implementation of two methods, `parameters` /// and `parameters_mut`, for the given struct. /// -/// Both method returns a [`HashMap`] of parameter names to [`DynamicFloatValue`]. This +/// Both method returns a [`HashMap`] of parameter names to [`Metric`]. This /// is intended to be used for nodes and parameter structs in the Pywr schema. fn impl_parameter_references_derive(ast: &syn::DeriveInput) -> TokenStream { // Name of the node type @@ -33,7 +33,7 @@ fn impl_parameter_references_derive(ast: &syn::DeriveInput) -> TokenStream { } // Iterate through all fields of the struct. Try to find fields that reference - // parameters (e.g. `Option` or `ParameterValue`). + // parameters (e.g. `Option` or `ParameterValue`). let parameter_fields: Vec = data .fields .iter() @@ -105,7 +105,7 @@ fn impl_parameter_references_derive(ast: &syn::DeriveInput) -> TokenStream { // Create the two parameter methods using the insert statements let expanded = quote! { impl #name { - pub fn parameters(&self) -> HashMap<&str, &DynamicFloatValue> { + pub fn parameters(&self) -> HashMap<&str, &Metric> { let mut attributes = HashMap::new(); #( #inserts @@ -113,7 +113,7 @@ fn impl_parameter_references_derive(ast: &syn::DeriveInput) -> TokenStream { attributes } - pub fn parameters_mut(&mut self) -> HashMap<&str, &mut DynamicFloatValue> { + pub fn parameters_mut(&mut self) -> HashMap<&str, &mut Metric> { let mut attributes = HashMap::new(); #( #inserts_mut @@ -191,6 +191,6 @@ fn type_to_ident(ty: &syn::Type) -> Option { fn is_parameter_ident(ident: &syn::Ident) -> bool { // TODO this currenty omits more complex attributes, such as `factors` for AggregatedNode - // and steps for PiecewiseLinks, that can internally contain `DynamicFloatValue` fields - ident == "DynamicFloatValue" + // and steps for PiecewiseLinks, that can internally contain `Metric` fields + ident == "Metric" } diff --git a/pywr-schema/src/error.rs b/pywr-schema/src/error.rs index 916578f9..9d6ec771 100644 --- a/pywr-schema/src/error.rs +++ b/pywr-schema/src/error.rs @@ -57,6 +57,8 @@ pub enum SchemaError { LoadParameter { name: String, error: String }, #[error("Timeseries error: {0}")] Timeseries(#[from] TimeseriesError), + #[error("The output of literal constant values is not supported. This is because they do not have a unique identifier such as a name. If you would like to output a constant value please use a `Constant` parameter.")] + LiteralConstantOutputNotSupported, } impl From for PyErr { diff --git a/pywr-schema/src/metric.rs b/pywr-schema/src/metric.rs index 54241667..3a970b94 100644 --- a/pywr-schema/src/metric.rs +++ b/pywr-schema/src/metric.rs @@ -1,56 +1,326 @@ +use crate::data_tables::TableDataRef; use crate::error::SchemaError; -use crate::model::PywrNetwork; -use crate::nodes::NodeAttribute; +use crate::model::LoadArgs; +use crate::nodes::{NodeAttribute, NodeType}; +use crate::parameters::{Parameter, ParameterOrTimeseries, ParameterType, TryFromV1Parameter, TryIntoV2Parameter}; +use crate::ConversionError; +use pywr_core::metric::MetricF64; +use pywr_core::models::MultiNetworkTransferIndex; +use pywr_core::recorders::OutputMetric; +use pywr_v1_schema::parameters::ParameterValue as ParameterValueV1; use serde::{Deserialize, Serialize}; +use strum_macros::Display; /// Output metrics that can be recorded from a model run. -#[derive(Deserialize, Serialize, Clone)] +#[derive(Deserialize, Serialize, Clone, Debug, Display)] #[serde(tag = "type")] pub enum Metric { - /// Output the default metric for a node. - Default { - node: String, + Constant { + value: f64, }, - Deficit { - node: String, + Table(TableDataRef), + /// An attribute of a node. + Node(NodeReference), + Timeseries(TimeseriesReference), + Parameter(ParameterReference), + InlineParameter { + definition: Box, }, - Parameter { + InterNetworkTransfer { name: String, }, } +impl Default for Metric { + fn default() -> Self { + Self::Constant { value: 0.0 } + } +} + +impl From for Metric { + fn from(value: f64) -> Self { + Self::Constant { value } + } +} + impl Metric { - pub fn try_clone_into_metric( + pub fn load(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result { + 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::Table(table_ref) => { + let value = args.tables.get_scalar_f64(table_ref)?; + Ok(MetricF64::Constant(value)) + } + Self::Timeseries(ts_ref) => { + let param_idx = match &ts_ref.columns { + TimeseriesColumns::Scenario(scenario) => { + args.timeseries + .load_df(network, ts_ref.name.as_ref(), args.domain, scenario.as_str())? + } + TimeseriesColumns::Column(col) => { + args.timeseries + .load_column(network, ts_ref.name.as_ref(), col.as_str())? + } + }; + Ok(MetricF64::ParameterValue(param_idx)) + } + Self::InlineParameter { definition } => { + // This inline parameter could already have been loaded on a previous attempt + // Let's see if exists first. + // TODO this will create strange issues if there are duplicate names in the + // parameter definitions. I.e. we will only ever load the first one and then + // assume it is the correct one for future references to that name. This could be + // improved by checking the parameter returned by name matches the definition here. + + 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)) + } + 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::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(), + definition.name(), + ))), + } + } + } + } + + Self::InterNetworkTransfer { name } => { + // Find the matching inter model transfer + match args.inter_network_transfers.iter().position(|t| &t.name == name) { + Some(idx) => Ok(MetricF64::InterNetworkTransfer(MultiNetworkTransferIndex(idx))), + None => Err(SchemaError::InterNetworkTransferNotFound(name.to_string())), + } + } + } + } + + fn name(&self) -> Result<&str, SchemaError> { + match self { + Self::Node(node_ref) => Ok(&node_ref.name), + Self::Parameter(parameter_ref) => Ok(¶meter_ref.name), + Self::Constant { .. } => Err(SchemaError::LiteralConstantOutputNotSupported), + Self::Table(table_ref) => Ok(&table_ref.table), + Self::Timeseries(ts_ref) => Ok(&ts_ref.name), + Self::InlineParameter { definition } => Ok(definition.name()), + Self::InterNetworkTransfer { name } => Ok(name), + } + } + + fn attribute(&self, args: &LoadArgs) -> Result { + let attribute = match self { + Self::Node(node_ref) => node_ref.attribute(args)?.to_string(), + Self::Parameter(_) => "value".to_string(), + Self::Constant { .. } => "value".to_string(), + Self::Table(_) => "value".to_string(), + Self::Timeseries(_) => "value".to_string(), + Self::InlineParameter { .. } => "value".to_string(), + Self::InterNetworkTransfer { .. } => "value".to_string(), + }; + + Ok(attribute) + } + + /// Return the subtype of the metric. This is the type of the metric that is being + /// referenced. For example, if the metric is a node then the subtype is the type of the + /// node. + fn sub_type(&self, args: &LoadArgs) -> Result, SchemaError> { + let sub_type = match self { + Self::Node(node_ref) => Some(node_ref.node_type(args)?.to_string()), + Self::Parameter(parameter_ref) => Some(parameter_ref.parameter_type(args)?.to_string()), + Self::Constant { .. } => None, + Self::Table(_) => None, + Self::Timeseries(_) => None, + Self::InlineParameter { definition } => Some(definition.parameter_type().to_string()), + Self::InterNetworkTransfer { .. } => None, + }; + + Ok(sub_type) + } + + pub fn load_as_output( &self, network: &mut pywr_core::network::Network, - schema: &PywrNetwork, - ) -> Result { - match self { - Self::Default { node } => { - // Get the node from the schema; not the model itself - let node = schema - .get_node_by_name(node) - .ok_or_else(|| SchemaError::NodeNotFound(node.to_string()))?; - // Create and return the node's default metric - node.create_metric(network, None) + args: &LoadArgs, + ) -> Result { + let metric = self.load(network, args)?; + + let ty = self.to_string(); + let sub_type = self.sub_type(args)?; + + Ok(OutputMetric::new( + self.name()?, + &self.attribute(args)?, + &ty, + sub_type.as_deref(), + metric, + )) + } +} + +impl TryFromV1Parameter for Metric { + type Error = ConversionError; + + fn try_from_v1_parameter( + v1: ParameterValueV1, + parent_node: Option<&str>, + unnamed_count: &mut usize, + ) -> Result { + let p = match v1 { + ParameterValueV1::Constant(value) => Self::Constant { value }, + ParameterValueV1::Reference(p_name) => Self::Parameter(ParameterReference { + name: p_name, + key: None, + }), + ParameterValueV1::Table(tbl) => Self::Table(tbl.try_into()?), + ParameterValueV1::Inline(param) => { + let definition: ParameterOrTimeseries = (*param).try_into_v2_parameter(parent_node, unnamed_count)?; + match definition { + ParameterOrTimeseries::Parameter(p) => Self::InlineParameter { + definition: Box::new(p), + }, + ParameterOrTimeseries::Timeseries(t) => { + let name = match t.name { + Some(n) => n, + None => { + let n = match parent_node { + Some(node_name) => format!("{}-p{}.timeseries", node_name, *unnamed_count), + None => format!("unnamed-timeseries-{}", *unnamed_count), + }; + *unnamed_count += 1; + n + } + }; + + let cols = match (&t.column, &t.scenario) { + (Some(col), None) => TimeseriesColumns::Column(col.clone()), + (None, Some(scenario)) => TimeseriesColumns::Scenario(scenario.clone()), + (Some(_), Some(_)) => { + return Err(ConversionError::AmbiguousColumnAndScenario(name.clone())) + } + (None, None) => return Err(ConversionError::MissingColumnOrScenario(name.clone())), + }; + + Self::Timeseries(TimeseriesReference::new(name, cols)) + } + } } - Self::Deficit { node } => { - // Get the node from the schema; not the model itself - let node = schema - .get_node_by_name(node) - .ok_or_else(|| SchemaError::NodeNotFound(node.to_string()))?; - // Create and return the metric - node.create_metric(network, Some(NodeAttribute::Deficit)) + }; + Ok(p) + } +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +#[serde(tag = "type", content = "name")] +pub enum TimeseriesColumns { + Scenario(String), + Column(String), +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct TimeseriesReference { + name: String, + columns: TimeseriesColumns, +} + +impl TimeseriesReference { + pub fn new(name: String, columns: TimeseriesColumns) -> Self { + Self { name, columns } + } + + pub fn name(&self) -> &str { + self.name.as_str() + } +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct NodeReference { + /// The name of the node + pub name: String, + /// The attribute of the node. If this is `None` then the default attribute is used. + pub attribute: Option, +} + +impl NodeReference { + pub fn load(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result { + // This is the associated node in the schema + let node = args + .schema + .get_node_by_name(&self.name) + .ok_or_else(|| SchemaError::NodeNotFound(self.name.clone()))?; + + node.create_metric(network, self.attribute) + } + + /// Return the attribute of the node. If the attribute is not specified then the default + /// attribute of the node is returned. Note that this does not check if the attribute is + /// valid for the node. + pub fn attribute(&self, args: &LoadArgs) -> Result { + // This is the associated node in the schema + let node = args + .schema + .get_node_by_name(&self.name) + .ok_or_else(|| SchemaError::NodeNotFound(self.name.clone()))?; + + Ok(self.attribute.unwrap_or_else(|| node.default_metric())) + } + + pub fn node_type(&self, args: &LoadArgs) -> Result { + // This is the associated node in the schema + let node = args + .schema + .get_node_by_name(&self.name) + .ok_or_else(|| SchemaError::NodeNotFound(self.name.clone()))?; + + Ok(node.node_type()) + } +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct ParameterReference { + /// The name of the parameter + pub name: String, + /// The key of the parameter. If this is `None` then the default value is used. + pub key: Option, +} + +impl ParameterReference { + pub fn load(&self, network: &mut pywr_core::network::Network) -> Result { + match &self.key { + Some(key) => { + // Key given; this should be a multi-valued parameter + Ok(MetricF64::MultiParameterValue(( + network.get_multi_valued_parameter_index_by_name(&self.name)?, + key.clone(), + ))) } - Self::Parameter { name } => { - if let Ok(idx) = network.get_parameter_index_by_name(name) { - Ok(pywr_core::metric::MetricF64::ParameterValue(idx)) - } else if let Ok(idx) = network.get_index_parameter_index_by_name(name) { - Ok(pywr_core::metric::MetricF64::IndexParameterValue(idx)) + None => { + if let Ok(idx) = network.get_parameter_index_by_name(&self.name) { + Ok(MetricF64::ParameterValue(idx)) + } else if let Ok(idx) = network.get_index_parameter_index_by_name(&self.name) { + Ok(MetricF64::IndexParameterValue(idx)) } else { - Err(SchemaError::ParameterNotFound(name.to_string())) + Err(SchemaError::ParameterNotFound(self.name.to_string())) } } } } + + pub fn parameter_type(&self, args: &LoadArgs) -> Result { + let parameter = args + .schema + .get_parameter_by_name(&self.name) + .ok_or_else(|| SchemaError::ParameterNotFound(self.name.clone()))?; + + Ok(parameter.parameter_type()) + } } diff --git a/pywr-schema/src/metric_sets/mod.rs b/pywr-schema/src/metric_sets/mod.rs index 0ab4713a..5ffaaf8a 100644 --- a/pywr-schema/src/metric_sets/mod.rs +++ b/pywr-schema/src/metric_sets/mod.rs @@ -1,6 +1,6 @@ use crate::error::SchemaError; use crate::metric::Metric; -use crate::model::PywrNetwork; +use crate::model::LoadArgs; use serde::{Deserialize, Serialize}; use std::num::NonZeroUsize; @@ -87,16 +87,12 @@ pub struct MetricSet { } impl MetricSet { - pub fn add_to_model( - &self, - network: &mut pywr_core::network::Network, - schema: &PywrNetwork, - ) -> Result<(), SchemaError> { + pub fn add_to_model(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result<(), SchemaError> { // Convert the schema representation to internal metrics. - let metrics: Vec = self + let metrics: Vec<_> = self .metrics .iter() - .map(|m| m.try_clone_into_metric(network, schema)) + .map(|m| m.load_as_output(network, args)) .collect::>()?; let aggregator = self.aggregator.clone().map(|a| a.into()); diff --git a/pywr-schema/src/model.rs b/pywr-schema/src/model.rs index d4a9262f..705e7190 100644 --- a/pywr-schema/src/model.rs +++ b/pywr-schema/src/model.rs @@ -1,12 +1,12 @@ use super::edge::Edge; use super::nodes::Node; -use super::parameters::Parameter; +use super::parameters::{convert_parameter_v1_to_v2, Parameter}; use crate::data_tables::{DataTable, LoadedTableCollection}; use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::metric_sets::MetricSet; use crate::nodes::NodeAndTimeseries; use crate::outputs::Output; -use crate::parameters::{convert_parameter_v1_to_v2, MetricFloatReference}; use crate::timeseries::{convert_from_v1_data, LoadedTimeseriesCollection, Timeseries}; use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; use pywr_core::models::ModelDomain; @@ -297,6 +297,7 @@ impl PywrNetwork { PywrError::ParameterNotFound(_) => failed_parameters.push(parameter), _ => return Err(SchemaError::PywrCore(core_err)), }, + SchemaError::ParameterNotFound(_) => failed_parameters.push(parameter), _ => return Err(e), } }; @@ -319,7 +320,7 @@ impl PywrNetwork { // Create all of the metric sets if let Some(metric_sets) = &self.metric_sets { for metric_set in metric_sets { - metric_set.add_to_model(&mut network, self)?; + metric_set.add_to_model(&mut network, &args)?; } } @@ -520,7 +521,7 @@ impl PywrModel { #[derive(serde::Deserialize, serde::Serialize, Clone)] pub struct PywrMultiNetworkTransfer { pub from_network: String, - pub metric: MetricFloatReference, + pub metric: Metric, pub name: String, pub initial_value: Option, } @@ -740,11 +741,9 @@ impl PywrMultiNetworkModel { #[cfg(test)] mod tests { use super::{PywrModel, PywrMultiNetworkModel}; + use crate::metric::{Metric, ParameterReference}; use crate::model::Timestepper; - use crate::parameters::{ - AggFunc, AggregatedParameter, ConstantParameter, ConstantValue, DynamicFloatValue, MetricFloatReference, - MetricFloatValue, Parameter, ParameterMeta, - }; + use crate::parameters::{AggFunc, AggregatedParameter, ConstantParameter, ConstantValue, Parameter, ParameterMeta}; use ndarray::{Array1, Array2, Axis}; use pywr_core::metric::MetricF64; use pywr_core::recorders::AssertionRecorder; @@ -809,14 +808,14 @@ mod tests { }, agg_func: AggFunc::Sum, metrics: vec![ - DynamicFloatValue::Dynamic(MetricFloatValue::Reference(MetricFloatReference::Parameter { + Metric::Parameter(ParameterReference { name: "p1".to_string(), key: None, - })), - DynamicFloatValue::Dynamic(MetricFloatValue::Reference(MetricFloatReference::Parameter { + }), + Metric::Parameter(ParameterReference { name: "agg2".to_string(), key: None, - })), + }), ], }), Parameter::Constant(ConstantParameter { @@ -833,14 +832,14 @@ mod tests { }, agg_func: AggFunc::Sum, metrics: vec![ - DynamicFloatValue::Dynamic(MetricFloatValue::Reference(MetricFloatReference::Parameter { + Metric::Parameter(ParameterReference { name: "p1".to_string(), key: None, - })), - DynamicFloatValue::Dynamic(MetricFloatValue::Reference(MetricFloatReference::Parameter { + }), + Metric::Parameter(ParameterReference { name: "agg1".to_string(), key: None, - })), + }), ], }), ]); @@ -865,14 +864,14 @@ mod tests { }, agg_func: AggFunc::Sum, metrics: vec![ - DynamicFloatValue::Dynamic(MetricFloatValue::Reference(MetricFloatReference::Parameter { + Metric::Parameter(ParameterReference { name: "p1".to_string(), key: None, - })), - DynamicFloatValue::Dynamic(MetricFloatValue::Reference(MetricFloatReference::Parameter { + }), + Metric::Parameter(ParameterReference { name: "p2".to_string(), key: None, - })), + }), ], }), Parameter::Constant(ConstantParameter { @@ -892,8 +891,7 @@ mod tests { ]); } // TODO this could assert a specific type of error - let build_result = schema.build_model(None, None); - assert!(build_result.is_ok()); + let _ = schema.build_model(None, None).unwrap(); } /// Test the multi1 model diff --git a/pywr-schema/src/nodes/annual_virtual_storage.rs b/pywr-schema/src/nodes/annual_virtual_storage.rs index bafec164..ffd1d773 100644 --- a/pywr-schema/src/nodes/annual_virtual_storage.rs +++ b/pywr-schema/src/nodes/annual_virtual_storage.rs @@ -1,8 +1,9 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::nodes::core::StorageInitialVolume; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use crate::parameters::TryIntoV2Parameter; use pywr_core::derived_metric::DerivedMetric; use pywr_core::metric::MetricF64; use pywr_core::node::ConstraintValue; @@ -34,9 +35,9 @@ pub struct AnnualVirtualStorageNode { pub meta: NodeMeta, pub nodes: Vec, pub factors: Option>, - pub max_volume: Option, - pub min_volume: Option, - pub cost: Option, + pub max_volume: Option, + pub min_volume: Option, + pub cost: Option, pub initial_volume: StorageInitialVolume, pub reset: AnnualReset, } @@ -94,6 +95,10 @@ impl AnnualVirtualStorageNode { vec![] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &mut pywr_core::network::Network, diff --git a/pywr-schema/src/nodes/core.rs b/pywr-schema/src/nodes/core.rs index 6bdb0370..0ca8c95a 100644 --- a/pywr-schema/src/nodes/core.rs +++ b/pywr-schema/src/nodes/core.rs @@ -1,7 +1,8 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use crate::parameters::TryIntoV2Parameter; use pywr_core::derived_metric::DerivedMetric; use pywr_core::metric::MetricF64; use pywr_core::node::{ConstraintValue, StorageInitialVolume as CoreStorageInitialVolume}; @@ -17,13 +18,13 @@ use std::collections::HashMap; pub struct InputNode { #[serde(flatten)] pub meta: NodeMeta, - pub max_flow: Option, - pub min_flow: Option, - pub cost: Option, + pub max_flow: Option, + pub min_flow: Option, + pub cost: Option, } impl InputNode { - pub const DEFAULT_ATTRIBUTE: NodeAttribute = NodeAttribute::Outflow; + const DEFAULT_ATTRIBUTE: NodeAttribute = NodeAttribute::Outflow; pub fn add_to_model(&self, network: &mut pywr_core::network::Network) -> Result<(), SchemaError> { network.add_input_node(self.meta.name.as_str(), None)?; @@ -60,6 +61,9 @@ impl InputNode { vec![(self.meta.name.as_str(), None)] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } pub fn create_metric( &self, network: &pywr_core::network::Network, @@ -120,9 +124,9 @@ impl TryFrom for InputNode { pub struct LinkNode { #[serde(flatten)] pub meta: NodeMeta, - pub max_flow: Option, - pub min_flow: Option, - pub cost: Option, + pub max_flow: Option, + pub min_flow: Option, + pub cost: Option, } impl LinkNode { @@ -163,6 +167,10 @@ impl LinkNode { vec![(self.meta.name.as_str(), None)] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &pywr_core::network::Network, @@ -223,9 +231,9 @@ impl TryFrom for LinkNode { pub struct OutputNode { #[serde(flatten)] pub meta: NodeMeta, - pub max_flow: Option, - pub min_flow: Option, - pub cost: Option, + pub max_flow: Option, + pub min_flow: Option, + pub cost: Option, } impl OutputNode { @@ -267,6 +275,9 @@ impl OutputNode { vec![(self.meta.name.as_str(), None)] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } pub fn create_metric( &self, network: &mut pywr_core::network::Network, @@ -352,9 +363,9 @@ impl From for CoreStorageInitialVolume { pub struct StorageNode { #[serde(flatten)] pub meta: NodeMeta, - pub max_volume: Option, - pub min_volume: Option, - pub cost: Option, + pub max_volume: Option, + pub min_volume: Option, + pub cost: Option, pub initial_volume: StorageInitialVolume, } @@ -403,6 +414,10 @@ impl StorageNode { vec![(self.meta.name.as_str(), None)] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &mut pywr_core::network::Network, @@ -549,8 +564,8 @@ impl TryFrom for StorageNode { pub struct CatchmentNode { #[serde(flatten)] pub meta: NodeMeta, - pub flow: Option, - pub cost: Option, + pub flow: Option, + pub cost: Option, } impl CatchmentNode { @@ -588,6 +603,10 @@ impl CatchmentNode { vec![(self.meta.name.as_str(), None)] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &pywr_core::network::Network, @@ -637,8 +656,8 @@ impl TryFrom for CatchmentNode { #[derive(serde::Deserialize, serde::Serialize, Clone, Debug)] #[serde(tag = "type")] pub enum Factors { - Proportion { factors: Vec }, - Ratio { factors: Vec }, + Proportion { factors: Vec }, + Ratio { factors: Vec }, } #[derive(serde::Deserialize, serde::Serialize, Clone, Default, Debug, PywrNode)] @@ -646,8 +665,8 @@ pub struct AggregatedNode { #[serde(flatten)] pub meta: NodeMeta, pub nodes: Vec, - pub max_flow: Option, - pub min_flow: Option, + pub max_flow: Option, + pub min_flow: Option, pub factors: Option, } @@ -715,6 +734,10 @@ impl AggregatedNode { vec![] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &pywr_core::network::Network, @@ -811,6 +834,10 @@ impl AggregatedStorageNode { vec![] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &mut pywr_core::network::Network, @@ -865,7 +892,10 @@ mod tests { { "name": "supply1", "type": "Input", - "max_flow": 15.0 + "max_flow": { + "type": "Constant", + "value": 15.0 + } } "#; diff --git a/pywr-schema/src/nodes/delay.rs b/pywr-schema/src/nodes/delay.rs index 6591446d..2aa44ae4 100644 --- a/pywr-schema/src/nodes/delay.rs +++ b/pywr-schema/src/nodes/delay.rs @@ -1,7 +1,8 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::{ConstantValue, DynamicFloatValue}; +use crate::parameters::ConstantValue; use pywr_core::metric::MetricF64; use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::DelayNode as DelayNodeV1; @@ -87,6 +88,10 @@ impl DelayNode { vec![(self.meta.name.as_str(), Self::input_sub_now().map(|s| s.to_string()))] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &pywr_core::network::Network, diff --git a/pywr-schema/src/nodes/loss_link.rs b/pywr-schema/src/nodes/loss_link.rs index a82cebfb..51ae2b87 100644 --- a/pywr-schema/src/nodes/loss_link.rs +++ b/pywr-schema/src/nodes/loss_link.rs @@ -1,7 +1,8 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use crate::parameters::TryIntoV2Parameter; use pywr_core::metric::MetricF64; use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::LossLinkNode as LossLinkNodeV1; @@ -28,10 +29,10 @@ use std::collections::HashMap; pub struct LossLinkNode { #[serde(flatten)] pub meta: NodeMeta, - pub loss_factor: Option, - pub min_net_flow: Option, - pub max_net_flow: Option, - pub net_cost: Option, + pub loss_factor: Option, + pub min_net_flow: Option, + pub max_net_flow: Option, + pub net_cost: Option, } impl LossLinkNode { @@ -91,6 +92,10 @@ impl LossLinkNode { vec![(self.meta.name.as_str(), Self::net_sub_name().map(|s| s.to_string()))] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &pywr_core::network::Network, diff --git a/pywr-schema/src/nodes/mod.rs b/pywr-schema/src/nodes/mod.rs index 6a393dba..58150872 100644 --- a/pywr-schema/src/nodes/mod.rs +++ b/pywr-schema/src/nodes/mod.rs @@ -13,6 +13,7 @@ mod virtual_storage; mod water_treatment_works; use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::{LoadArgs, PywrNetwork}; pub use crate::nodes::core::{ AggregatedNode, AggregatedStorageNode, CatchmentNode, InputNode, LinkNode, OutputNode, StorageNode, @@ -20,7 +21,7 @@ pub use crate::nodes::core::{ pub use crate::nodes::delay::DelayNode; pub use crate::nodes::river::RiverNode; use crate::nodes::rolling_virtual_storage::RollingVirtualStorageNode; -use crate::parameters::{DynamicFloatValue, TimeseriesV1Data}; +use crate::parameters::TimeseriesV1Data; pub use annual_virtual_storage::AnnualVirtualStorageNode; pub use loss_link::LossLinkNode; pub use monthly_virtual_storage::MonthlyVirtualStorageNode; @@ -285,7 +286,7 @@ impl Node { } } - pub fn parameters(&self) -> HashMap<&str, &DynamicFloatValue> { + pub fn parameters(&self) -> HashMap<&str, &Metric> { match self { Node::Input(n) => n.parameters(), Node::Link(n) => n.parameters(), @@ -309,7 +310,7 @@ impl Node { } } - pub fn parameters_mut(&mut self) -> HashMap<&str, &mut DynamicFloatValue> { + pub fn parameters_mut(&mut self) -> HashMap<&str, &mut Metric> { match self { Node::Input(n) => n.parameters_mut(), Node::Link(n) => n.parameters_mut(), @@ -435,6 +436,30 @@ impl Node { } } + pub fn default_metric(&self) -> NodeAttribute { + match self { + Node::Input(n) => n.default_metric(), + Node::Link(n) => n.default_metric(), + Node::Output(n) => n.default_metric(), + Node::Storage(n) => n.default_metric(), + Node::Catchment(n) => n.default_metric(), + Node::RiverGauge(n) => n.default_metric(), + Node::LossLink(n) => n.default_metric(), + Node::River(n) => n.default_metric(), + Node::RiverSplitWithGauge(n) => n.default_metric(), + Node::WaterTreatmentWorks(n) => n.default_metric(), + Node::Aggregated(n) => n.default_metric(), + Node::AggregatedStorage(n) => n.default_metric(), + Node::VirtualStorage(n) => n.default_metric(), + Node::AnnualVirtualStorage(n) => n.default_metric(), + Node::MonthlyVirtualStorage(n) => n.default_metric(), + Node::PiecewiseLink(n) => n.default_metric(), + Node::PiecewiseStorage(n) => n.default_metric(), + Node::Delay(n) => n.default_metric(), + Node::RollingVirtualStorage(n) => n.default_metric(), + } + } + /// Create a metric for the given attribute on this node. pub fn create_metric( &self, @@ -621,9 +646,10 @@ fn extract_timeseries( mod tests { use pywr_v1_schema::nodes::Node as NodeV1; + use crate::metric::Metric; use crate::{ nodes::{Node, NodeAndTimeseries}, - parameters::{DynamicFloatValue, MetricFloatValue, Parameter}, + parameters::Parameter, }; #[test] @@ -655,7 +681,7 @@ mod tests { let expected_name = String::from("catchment1-p0.timeseries"); match input_node.max_flow { - Some(DynamicFloatValue::Dynamic(MetricFloatValue::Timeseries(ts))) => { + Some(Metric::Timeseries(ts)) => { assert_eq!(ts.name(), &expected_name) } _ => panic!("Expected Timeseries"), @@ -722,18 +748,18 @@ mod tests { let expected_name2 = String::from("catchment1-p0-p4.timeseries"); match input_node.max_flow { - Some(DynamicFloatValue::Dynamic(MetricFloatValue::InlineParameter { definition })) => match *definition { + Some(Metric::InlineParameter { definition }) => match *definition { Parameter::Aggregated(param) => { assert_eq!(param.metrics.len(), 4); match ¶m.metrics[1] { - DynamicFloatValue::Dynamic(MetricFloatValue::Timeseries(ts)) => { + Metric::Timeseries(ts) => { assert_eq!(ts.name(), &expected_name1) } _ => panic!("Expected Timeseries"), } match ¶m.metrics[3] { - DynamicFloatValue::Dynamic(MetricFloatValue::Timeseries(ts)) => { + Metric::Timeseries(ts) => { assert_eq!(ts.name(), &expected_name2) } _ => panic!("Expected Timeseries"), diff --git a/pywr-schema/src/nodes/monthly_virtual_storage.rs b/pywr-schema/src/nodes/monthly_virtual_storage.rs index 3e93c9a8..ec5bc6e6 100644 --- a/pywr-schema/src/nodes/monthly_virtual_storage.rs +++ b/pywr-schema/src/nodes/monthly_virtual_storage.rs @@ -1,8 +1,9 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::nodes::core::StorageInitialVolume; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use crate::parameters::TryIntoV2Parameter; use pywr_core::derived_metric::DerivedMetric; use pywr_core::metric::MetricF64; use pywr_core::node::ConstraintValue; @@ -28,9 +29,9 @@ pub struct MonthlyVirtualStorageNode { pub meta: NodeMeta, pub nodes: Vec, pub factors: Option>, - pub max_volume: Option, - pub min_volume: Option, - pub cost: Option, + pub max_volume: Option, + pub min_volume: Option, + pub cost: Option, pub initial_volume: StorageInitialVolume, pub reset: NumberOfMonthsReset, } @@ -88,6 +89,10 @@ impl MonthlyVirtualStorageNode { vec![] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &mut pywr_core::network::Network, diff --git a/pywr-schema/src/nodes/piecewise_link.rs b/pywr-schema/src/nodes/piecewise_link.rs index d08e0775..3449746b 100644 --- a/pywr-schema/src/nodes/piecewise_link.rs +++ b/pywr-schema/src/nodes/piecewise_link.rs @@ -1,7 +1,8 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use crate::parameters::TryIntoV2Parameter; use pywr_core::metric::MetricF64; use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::PiecewiseLinkNode as PiecewiseLinkNodeV1; @@ -9,9 +10,9 @@ use std::collections::HashMap; #[derive(serde::Deserialize, serde::Serialize, Clone, Debug)] pub struct PiecewiseLinkStep { - pub max_flow: Option, - pub min_flow: Option, - pub cost: Option, + pub max_flow: Option, + pub min_flow: Option, + pub cost: Option, } #[doc = svgbobdoc::transform!( @@ -100,6 +101,10 @@ impl PiecewiseLinkNode { .collect() } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &pywr_core::network::Network, diff --git a/pywr-schema/src/nodes/piecewise_storage.rs b/pywr-schema/src/nodes/piecewise_storage.rs index 1fb6765d..2ccf2d1d 100644 --- a/pywr-schema/src/nodes/piecewise_storage.rs +++ b/pywr-schema/src/nodes/piecewise_storage.rs @@ -1,7 +1,7 @@ use crate::error::SchemaError; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::DynamicFloatValue; use pywr_core::derived_metric::DerivedMetric; use pywr_core::metric::MetricF64; use pywr_core::node::{ConstraintValue, StorageInitialVolume}; @@ -11,8 +11,8 @@ use std::collections::HashMap; #[derive(serde::Deserialize, serde::Serialize, Clone, Debug)] pub struct PiecewiseStore { - pub control_curve: DynamicFloatValue, - pub cost: Option, + pub control_curve: Metric, + pub cost: Option, } #[doc = svgbobdoc::transform!( @@ -45,7 +45,7 @@ pub struct PiecewiseStore { pub struct PiecewiseStorageNode { #[serde(flatten)] pub meta: NodeMeta, - pub max_volume: DynamicFloatValue, + pub max_volume: Metric, // TODO implement min volume // pub min_volume: Option, pub steps: Vec, @@ -188,6 +188,10 @@ impl PiecewiseStorageNode { vec![(self.meta.name.as_str(), Self::step_sub_name(self.steps.len()))] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &mut pywr_core::network::Network, diff --git a/pywr-schema/src/nodes/river.rs b/pywr-schema/src/nodes/river.rs index 47fcddf7..74753dcf 100644 --- a/pywr-schema/src/nodes/river.rs +++ b/pywr-schema/src/nodes/river.rs @@ -1,6 +1,6 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::DynamicFloatValue; use pywr_core::metric::MetricF64; use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::LinkNode as LinkNodeV1; @@ -27,6 +27,10 @@ impl RiverNode { vec![(self.meta.name.as_str(), None)] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &pywr_core::network::Network, diff --git a/pywr-schema/src/nodes/river_gauge.rs b/pywr-schema/src/nodes/river_gauge.rs index 16aba56a..6768e2fd 100644 --- a/pywr-schema/src/nodes/river_gauge.rs +++ b/pywr-schema/src/nodes/river_gauge.rs @@ -1,7 +1,8 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use crate::parameters::TryIntoV2Parameter; use pywr_core::metric::MetricF64; use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::RiverGaugeNode as RiverGaugeNodeV1; @@ -26,8 +27,8 @@ use std::collections::HashMap; pub struct RiverGaugeNode { #[serde(flatten)] pub meta: NodeMeta, - pub mrf: Option, - pub mrf_cost: Option, + pub mrf: Option, + pub mrf_cost: Option, } impl RiverGaugeNode { @@ -81,6 +82,10 @@ impl RiverGaugeNode { ] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &pywr_core::network::Network, @@ -144,59 +149,7 @@ mod tests { use pywr_core::test_utils::run_all_solvers; fn model_str() -> &'static str { - r#" - { - "metadata": { - "title": "Simple 1", - "description": "A very simple example.", - "minimum_version": "0.1" - }, - "timestepper": { - "start": "2015-01-01", - "end": "2015-12-31", - "timestep": 1 - }, - "network": { - "nodes": [ - { - "name": "catchment1", - "type": "Catchment", - "flow": 15 - }, - { - "name": "gauge1", - "type": "RiverGauge", - "mrf": 5.0, - "mrf_cost": -20.0 - }, - { - "name": "term1", - "type": "Output" - }, - { - "name": "demand1", - "type": "Output", - "max_flow": 15.0, - "cost": -10 - } - ], - "edges": [ - { - "from_node": "catchment1", - "to_node": "gauge1" - }, - { - "from_node": "gauge1", - "to_node": "term1" - }, - { - "from_node": "gauge1", - "to_node": "demand1" - } - ] - } - } - "# + include_str!("../test_models/river_gauge1.json") } #[test] diff --git a/pywr-schema/src/nodes/river_split_with_gauge.rs b/pywr-schema/src/nodes/river_split_with_gauge.rs index a86aa338..9169160e 100644 --- a/pywr-schema/src/nodes/river_split_with_gauge.rs +++ b/pywr-schema/src/nodes/river_split_with_gauge.rs @@ -1,7 +1,8 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use crate::parameters::TryIntoV2Parameter; use pywr_core::aggregated_node::Factors; use pywr_core::metric::MetricF64; use pywr_core::node::NodeIndex; @@ -35,9 +36,9 @@ use std::collections::HashMap; pub struct RiverSplitWithGaugeNode { #[serde(flatten)] pub meta: NodeMeta, - pub mrf: Option, - pub mrf_cost: Option, - pub splits: Vec<(DynamicFloatValue, String)>, + pub mrf: Option, + pub mrf_cost: Option, + pub splits: Vec<(Metric, String)>, } impl RiverSplitWithGaugeNode { @@ -144,6 +145,10 @@ impl RiverSplitWithGaugeNode { } } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &pywr_core::network::Network, @@ -219,7 +224,7 @@ impl TryFrom for RiverSplitWithGaugeNode { slot_name, )) }) - .collect::, Self::Error>>()?; + .collect::, Self::Error>>()?; let n = Self { meta, diff --git a/pywr-schema/src/nodes/rolling_virtual_storage.rs b/pywr-schema/src/nodes/rolling_virtual_storage.rs index aa6de857..5bbfd3fa 100644 --- a/pywr-schema/src/nodes/rolling_virtual_storage.rs +++ b/pywr-schema/src/nodes/rolling_virtual_storage.rs @@ -1,7 +1,8 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use crate::parameters::TryIntoV2Parameter; use pywr_core::derived_metric::DerivedMetric; use pywr_core::metric::MetricF64; use pywr_core::node::{ConstraintValue, StorageInitialVolume}; @@ -66,9 +67,9 @@ pub struct RollingVirtualStorageNode { pub meta: NodeMeta, pub nodes: Vec, pub factors: Option>, - pub max_volume: Option, - pub min_volume: Option, - pub cost: Option, + pub max_volume: Option, + pub min_volume: Option, + pub cost: Option, pub initial_volume: Option, pub initial_volume_pc: Option, pub window: RollingWindow, @@ -139,6 +140,10 @@ impl RollingVirtualStorageNode { vec![] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &mut pywr_core::network::Network, diff --git a/pywr-schema/src/nodes/virtual_storage.rs b/pywr-schema/src/nodes/virtual_storage.rs index be34ff57..d431f59e 100644 --- a/pywr-schema/src/nodes/virtual_storage.rs +++ b/pywr-schema/src/nodes/virtual_storage.rs @@ -1,8 +1,9 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::nodes::core::StorageInitialVolume; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use crate::parameters::TryIntoV2Parameter; use pywr_core::derived_metric::DerivedMetric; use pywr_core::metric::MetricF64; use pywr_core::node::ConstraintValue; @@ -17,9 +18,9 @@ pub struct VirtualStorageNode { pub meta: NodeMeta, pub nodes: Vec, pub factors: Option>, - pub max_volume: Option, - pub min_volume: Option, - pub cost: Option, + pub max_volume: Option, + pub min_volume: Option, + pub cost: Option, pub initial_volume: StorageInitialVolume, } @@ -74,6 +75,10 @@ impl VirtualStorageNode { vec![] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &mut pywr_core::network::Network, diff --git a/pywr-schema/src/nodes/water_treatment_works.rs b/pywr-schema/src/nodes/water_treatment_works.rs index 49836252..57a290b2 100644 --- a/pywr-schema/src/nodes/water_treatment_works.rs +++ b/pywr-schema/src/nodes/water_treatment_works.rs @@ -1,7 +1,7 @@ use crate::error::SchemaError; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::DynamicFloatValue; use num::Zero; use pywr_core::aggregated_node::Factors; use pywr_core::metric::MetricF64; @@ -41,18 +41,18 @@ pub struct WaterTreatmentWorks { #[serde(flatten)] pub meta: NodeMeta, /// The proportion of net flow that is lost to the loss node. - pub loss_factor: Option, + pub loss_factor: Option, /// The minimum flow through the `net` flow node. - pub min_flow: Option, + pub min_flow: Option, /// The maximum flow through the `net` flow node. - pub max_flow: Option, + pub max_flow: Option, /// The maximum flow applied to the `net_soft_min_flow` node which is typically /// used as a "soft" minimum flow. - pub soft_min_flow: Option, + pub soft_min_flow: Option, /// The cost applied to the `net_soft_min_flow` node. - pub soft_min_flow_cost: Option, + pub soft_min_flow_cost: Option, /// The cost applied to the `net` flow node. - pub cost: Option, + pub cost: Option, } impl WaterTreatmentWorks { @@ -189,6 +189,10 @@ impl WaterTreatmentWorks { ] } + pub fn default_metric(&self) -> NodeAttribute { + Self::DEFAULT_ATTRIBUTE + } + pub fn create_metric( &self, network: &pywr_core::network::Network, @@ -249,11 +253,18 @@ mod tests { "comment": null, "position": null, "loss_factor": { + "type": "Table", "index": "My WTW", "table": "loss_factors" }, - "soft_min_flow": 105, - "cost": 2.29, + "soft_min_flow": { + "type": "Constant", + "value": 105.0 + }, + "cost": { + "type": "Constant", + "value": 2.29 + }, "max_flow": { "type": "InlineParameter", "definition": { @@ -270,7 +281,10 @@ mod tests { "type": "Parameter", "name": "a max flow" }, - 0.0 + { + "type": "Constant", + "value": 0.0 + } ], "storage_node": { "name": "My reservoir", @@ -308,19 +322,34 @@ mod tests { { "name": "input1", "type": "Input", - "flow": 15 + "flow": { + "type": "Constant", + "value": 15.0 + } }, { "name": "wtw1", "type": "WaterTreatmentWorks", - "max_flow": 10.0, - "loss_factor": 0.1 + "max_flow": { + "type": "Constant", + "value": 10.0 + }, + "loss_factor": { + "type": "Constant", + "value": 0.1 + } }, { "name": "demand1", "type": "Output", - "max_flow": 15.0, - "cost": -10 + "max_flow": { + "type": "Constant", + "value": 15.0 + }, + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ diff --git a/pywr-schema/src/parameters/aggregated.rs b/pywr-schema/src/parameters/aggregated.rs index 019fa6fe..2c9b580f 100644 --- a/pywr-schema/src/parameters/aggregated.rs +++ b/pywr-schema/src/parameters/aggregated.rs @@ -1,9 +1,7 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; -use crate::parameters::{ - DynamicFloatValue, DynamicFloatValueType, DynamicIndexValue, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, - TryIntoV2Parameter, -}; +use crate::parameters::{DynamicIndexValue, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter}; use pywr_core::parameters::ParameterIndex; use pywr_v1_schema::parameters::{ AggFunc as AggFuncV1, AggregatedIndexParameter as AggregatedIndexParameterV1, @@ -71,23 +69,10 @@ pub struct AggregatedParameter { #[serde(flatten)] pub meta: ParameterMeta, pub agg_func: AggFunc, - pub metrics: Vec, + pub metrics: Vec, } impl AggregatedParameter { - pub fn node_references(&self) -> HashMap<&str, &str> { - HashMap::new() - } - - pub fn parameters(&self) -> HashMap<&str, DynamicFloatValueType> { - let mut attributes = HashMap::new(); - - let metrics = &self.metrics; - attributes.insert("parameters", metrics.into()); - - attributes - } - pub fn add_to_model( &self, network: &mut pywr_core::network::Network, @@ -236,7 +221,6 @@ impl TryFromV1Parameter for AggregatedIndexParameter #[cfg(test)] mod tests { use crate::parameters::aggregated::AggregatedParameter; - use crate::parameters::{DynamicFloatValue, DynamicFloatValueType, MetricFloatValue, Parameter}; #[test] fn test_aggregated() { @@ -258,7 +242,7 @@ mod tests { }, "control_curves": [ {"type": "Parameter", "name": "reservoir_cc"}, - 0.2 + {"type": "Constant", "value": 0.2} ], "comment": "A witty comment", "values": [ @@ -280,7 +264,7 @@ mod tests { }, "control_curves": [ {"type": "Parameter", "name": "reservoir_cc"}, - 0.2 + {"type": "Constant", "value": 0.2} ], "comment": "A witty comment", "values": [ @@ -295,29 +279,6 @@ mod tests { } "#; - let param: AggregatedParameter = serde_json::from_str(data).unwrap(); - - assert_eq!(param.node_references().len(), 0); - assert_eq!(param.parameters().len(), 1); - match param.parameters().remove("parameters").unwrap() { - DynamicFloatValueType::List(children) => { - assert_eq!(children.len(), 2); - for p in children { - match p { - DynamicFloatValue::Dynamic(p) => match p { - MetricFloatValue::InlineParameter { definition } => match definition.as_ref() { - Parameter::ControlCurvePiecewiseInterpolated(p) => { - assert_eq!(p.storage_node.name, "Reservoir") - } - _ => panic!("Incorrect core parameter deserialized."), - }, - _ => panic!("Non-core parameter was deserialized."), - }, - _ => panic!("Wrong variant for child parameter."), - } - } - } - _ => panic!("Wrong variant for parameters."), - }; + let _: AggregatedParameter = serde_json::from_str(data).unwrap(); } } diff --git a/pywr-schema/src/parameters/control_curves.rs b/pywr-schema/src/parameters/control_curves.rs index d7b7e8da..9a02bc2a 100644 --- a/pywr-schema/src/parameters/control_curves.rs +++ b/pywr-schema/src/parameters/control_curves.rs @@ -1,9 +1,8 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::{Metric, NodeReference}; use crate::model::LoadArgs; use crate::nodes::NodeAttribute; -use crate::parameters::{ - DynamicFloatValue, IntoV2Parameter, NodeReference, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter, -}; +use crate::parameters::{IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter}; use pywr_core::parameters::ParameterIndex; use pywr_v1_schema::parameters::{ ControlCurveIndexParameter as ControlCurveIndexParameterV1, @@ -16,9 +15,9 @@ use pywr_v1_schema::parameters::{ pub struct ControlCurveInterpolatedParameter { #[serde(flatten)] pub meta: ParameterMeta, - pub control_curves: Vec, + pub control_curves: Vec, pub storage_node: NodeReference, - pub values: Vec, + pub values: Vec, } impl ControlCurveInterpolatedParameter { @@ -89,7 +88,7 @@ impl TryFromV1Parameter for ControlCurveInt attrs: vec!["values".to_string(), "parameters".to_string()], }); } - (Some(values), None) => values.into_iter().map(DynamicFloatValue::from_f64).collect(), + (Some(values), None) => values.into_iter().map(Metric::from).collect(), (None, Some(parameters)) => parameters .into_iter() .map(|p| p.try_into_v2_parameter(Some(&meta.name), unnamed_count)) @@ -116,7 +115,7 @@ impl TryFromV1Parameter for ControlCurveInt pub struct ControlCurveIndexParameter { #[serde(flatten)] pub meta: ParameterMeta, - pub control_curves: Vec, + pub control_curves: Vec, pub storage_node: NodeReference, } @@ -222,9 +221,9 @@ impl TryFromV1Parameter for ControlCurveIndexParameter pub struct ControlCurveParameter { #[serde(flatten)] pub meta: ParameterMeta, - pub control_curves: Vec, + pub control_curves: Vec, pub storage_node: NodeReference, - pub values: Vec, + pub values: Vec, } impl ControlCurveParameter { @@ -277,7 +276,7 @@ impl TryFromV1Parameter for ControlCurveParameter { }; let values = if let Some(values) = v1.values { - values.into_iter().map(DynamicFloatValue::from_f64).collect() + values.into_iter().map(Metric::from).collect() } else if let Some(parameters) = v1.parameters { parameters .into_iter() @@ -310,7 +309,7 @@ impl TryFromV1Parameter for ControlCurveParameter { pub struct ControlCurvePiecewiseInterpolatedParameter { #[serde(flatten)] pub meta: ParameterMeta, - pub control_curves: Vec, + pub control_curves: Vec, pub storage_node: NodeReference, pub values: Option>, pub minimum: Option, @@ -406,7 +405,7 @@ mod tests { }, "control_curves": [ {"type": "Parameter", "name": "reservoir_cc"}, - 0.2 + {"type": "Constant", "value": 0.2} ], "comment": "A witty comment", "values": [ diff --git a/pywr-schema/src/parameters/core.rs b/pywr-schema/src/parameters/core.rs index 2a50ed3b..a5dde37e 100644 --- a/pywr-schema/src/parameters/core.rs +++ b/pywr-schema/src/parameters/core.rs @@ -1,8 +1,8 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::parameters::{ - ConstantValue, DynamicFloatValue, DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, - TryIntoV2Parameter, + ConstantValue, DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter, }; use pywr_core::parameters::ParameterIndex; use pywr_v1_schema::parameters::{ @@ -201,7 +201,7 @@ impl TryFromV1Parameter for ConstantParameter { pub struct MaxParameter { #[serde(flatten)] pub meta: ParameterMeta, - pub parameter: DynamicFloatValue, + pub parameter: Metric, pub threshold: Option, } @@ -275,8 +275,8 @@ impl TryFromV1Parameter for MaxParameter { pub struct DivisionParameter { #[serde(flatten)] pub meta: ParameterMeta, - pub numerator: DynamicFloatValue, - pub denominator: DynamicFloatValue, + pub numerator: Metric, + pub denominator: Metric, } impl DivisionParameter { @@ -342,7 +342,7 @@ impl TryFromV1Parameter for DivisionParameter { pub struct MinParameter { #[serde(flatten)] pub meta: ParameterMeta, - pub parameter: DynamicFloatValue, + pub parameter: Metric, pub threshold: Option, } @@ -389,7 +389,7 @@ impl TryFromV1Parameter for MinParameter { pub struct NegativeParameter { #[serde(flatten)] pub meta: ParameterMeta, - pub parameter: DynamicFloatValue, + pub parameter: Metric, } impl NegativeParameter { diff --git a/pywr-schema/src/parameters/delay.rs b/pywr-schema/src/parameters/delay.rs index 1ddb3675..3bee9571 100644 --- a/pywr-schema/src/parameters/delay.rs +++ b/pywr-schema/src/parameters/delay.rs @@ -1,6 +1,7 @@ use crate::error::SchemaError; +use crate::metric::Metric; use crate::model::LoadArgs; -use crate::parameters::{DynamicFloatValue, DynamicFloatValueType, ParameterMeta}; +use crate::parameters::{DynamicFloatValueType, ParameterMeta}; use pywr_core::parameters::ParameterIndex; use std::collections::HashMap; @@ -9,7 +10,7 @@ use std::collections::HashMap; pub struct DelayParameter { #[serde(flatten)] pub meta: ParameterMeta, - pub metric: DynamicFloatValue, + pub metric: Metric, pub delay: usize, pub initial_value: f64, } diff --git a/pywr-schema/src/parameters/discount_factor.rs b/pywr-schema/src/parameters/discount_factor.rs index 2f91389f..6d38fd27 100644 --- a/pywr-schema/src/parameters/discount_factor.rs +++ b/pywr-schema/src/parameters/discount_factor.rs @@ -1,6 +1,7 @@ use crate::error::SchemaError; +use crate::metric::Metric; use crate::model::LoadArgs; -use crate::parameters::{DynamicFloatValue, DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter}; +use crate::parameters::{DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter}; use crate::ConversionError; use pywr_core::parameters::ParameterIndex; use pywr_v1_schema::parameters::DiscountFactorParameter as DiscountFactorParameterV1; @@ -11,7 +12,7 @@ use std::collections::HashMap; pub struct DiscountFactorParameter { #[serde(flatten)] pub meta: ParameterMeta, - pub discount_rate: DynamicFloatValue, + pub discount_rate: Metric, pub base_year: i32, } @@ -49,7 +50,7 @@ impl TryFromV1Parameter for DiscountFactorParameter { unnamed_count: &mut usize, ) -> Result { let meta: ParameterMeta = v1.meta.into_v2_parameter(parent_node, unnamed_count); - let discount_rate = DynamicFloatValue::from_f64(v1.rate); + let discount_rate = Metric::from(v1.rate); Ok(Self { meta, discount_rate, diff --git a/pywr-schema/src/parameters/doc_examples/aggregated_1.json b/pywr-schema/src/parameters/doc_examples/aggregated_1.json index 8536c15c..8d912814 100644 --- a/pywr-schema/src/parameters/doc_examples/aggregated_1.json +++ b/pywr-schema/src/parameters/doc_examples/aggregated_1.json @@ -1,29 +1,46 @@ { - "name": "my-aggregated-value", - "type": "Aggregated", - "agg_func": "sum", - "metrics": [ - 3.1415, - { - "table": "demands", - "index": "my-node" - }, - { - "type": "Parameter", - "name": "my-other-parameter" - }, - { - "type": "Node", - "name": "my-reservoir", - "attribute": "Volume" - }, - { - "type": "InlineParameter", - "definition": { - "name": "my-monthly-profile", - "type": "MonthlyProfile", - "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0] - } - } - ] + "name": "my-aggregated-value", + "type": "Aggregated", + "agg_func": "sum", + "metrics": [ + { + "type": "Constant", + "value": 3.1415 + }, + { + "type": "Table", + "table": "demands", + "index": "my-node" + }, + { + "type": "Parameter", + "name": "my-other-parameter" + }, + { + "type": "Node", + "name": "my-reservoir", + "attribute": "Volume" + }, + { + "type": "InlineParameter", + "definition": { + "name": "my-monthly-profile", + "type": "MonthlyProfile", + "values": [ + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0 + ] + } + } + ] } diff --git a/pywr-schema/src/parameters/indexed_array.rs b/pywr-schema/src/parameters/indexed_array.rs index 6315ccd0..47cef810 100644 --- a/pywr-schema/src/parameters/indexed_array.rs +++ b/pywr-schema/src/parameters/indexed_array.rs @@ -1,8 +1,8 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::parameters::{ - DynamicFloatValue, DynamicFloatValueType, DynamicIndexValue, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, - TryIntoV2Parameter, + DynamicFloatValueType, DynamicIndexValue, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter, }; use pywr_core::parameters::ParameterIndex; use pywr_v1_schema::parameters::IndexedArrayParameter as IndexedArrayParameterV1; @@ -13,7 +13,7 @@ pub struct IndexedArrayParameter { #[serde(flatten)] pub meta: ParameterMeta, #[serde(alias = "params")] - pub metrics: Vec, + pub metrics: Vec, pub index_parameter: DynamicIndexValue, } diff --git a/pywr-schema/src/parameters/interpolated.rs b/pywr-schema/src/parameters/interpolated.rs index 696f0747..f2fbc15f 100644 --- a/pywr-schema/src/parameters/interpolated.rs +++ b/pywr-schema/src/parameters/interpolated.rs @@ -1,8 +1,8 @@ use crate::error::SchemaError; +use crate::metric::{Metric, NodeReference}; use crate::model::LoadArgs; use crate::parameters::{ - DynamicFloatValue, DynamicFloatValueType, IntoV2Parameter, MetricFloatReference, MetricFloatValue, NodeReference, - ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter, + DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter, }; use crate::ConversionError; use pywr_core::parameters::ParameterIndex; @@ -20,9 +20,9 @@ use std::collections::HashMap; pub struct InterpolatedParameter { #[serde(flatten)] pub meta: ParameterMeta, - pub x: DynamicFloatValue, - pub xp: Vec, - pub fp: Vec, + pub x: Metric, + pub xp: Vec, + pub fp: Vec, /// If not given or true, raise an error if the x value is outside the range of the data points. pub error_on_bounds: Option, } @@ -101,7 +101,7 @@ impl TryFromV1Parameter for InterpolatedParameter { attribute: None, }; // This defaults to v2's default metric - let x = DynamicFloatValue::Dynamic(MetricFloatValue::Reference(MetricFloatReference::Node(node_ref))); + let x = Metric::Node(node_ref); let xp = v1 .flows @@ -164,7 +164,7 @@ impl TryFromV1Parameter for InterpolatedParameter attribute: None, }; // This defaults to the node's inflow; not sure if we can do better than that. - let x = DynamicFloatValue::Dynamic(MetricFloatValue::Reference(MetricFloatReference::Node(node_ref))); + let x = Metric::Node(node_ref); let xp = v1 .volumes diff --git a/pywr-schema/src/parameters/mod.rs b/pywr-schema/src/parameters/mod.rs index 1dbb3044..e528b391 100644 --- a/pywr-schema/src/parameters/mod.rs +++ b/pywr-schema/src/parameters/mod.rs @@ -44,20 +44,20 @@ pub use super::parameters::python::{PythonModule, PythonParameter, PythonReturnT pub use super::parameters::tables::TablesArrayParameter; pub use super::parameters::thresholds::ParameterThresholdParameter; use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; -use crate::nodes::NodeAttribute; use crate::parameters::core::DivisionParameter; use crate::parameters::interpolated::InterpolatedParameter; pub use offset::OffsetParameter; -use pywr_core::metric::{MetricF64, MetricUsize}; -use pywr_core::models::MultiNetworkTransferIndex; -use pywr_core::parameters::{ParameterIndex, ParameterType}; +use pywr_core::metric::MetricUsize; +use pywr_core::parameters::ParameterIndex; use pywr_v1_schema::parameters::{ CoreParameter, DataFrameParameter as DataFrameParameterV1, ExternalDataRef as ExternalDataRefV1, Parameter as ParameterV1, ParameterMeta as ParameterMetaV1, ParameterValue as ParameterValueV1, ParameterVec, TableIndex as TableIndexV1, TableIndexEntry as TableIndexEntryV1, }; use std::path::PathBuf; +use strum_macros::{Display, EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] pub struct ParameterMeta { @@ -138,8 +138,11 @@ impl FromV1Parameter> for ParameterMeta { } } -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Debug, EnumDiscriminants, Clone)] #[serde(tag = "type")] +#[strum_discriminants(derive(Display, IntoStaticStr, EnumString, VariantNames))] +// This creates a separate enum called `NodeType` that is available in this module. +#[strum_discriminants(name(ParameterType))] pub enum Parameter { Aggregated(AggregatedParameter), AggregatedIndex(AggregatedIndexParameter), @@ -201,69 +204,51 @@ impl Parameter { } } - pub fn ty(&self) -> &str { - match self { - Self::Constant(_) => "Constant", - Self::ControlCurveInterpolated(_) => "ControlCurveInterpolated", - Self::Aggregated(_) => "Aggregated", - Self::AggregatedIndex(_) => "AggregatedIndex", - Self::AsymmetricSwitchIndex(_) => "AsymmetricSwitchIndex", - Self::ControlCurvePiecewiseInterpolated(_) => "ControlCurvePiecewiseInterpolated", - Self::ControlCurveIndex(_) => "ControlCurveIndex", - Self::ControlCurve(_) => "ControlCurve", - Self::DailyProfile(_) => "DailyProfile", - Self::IndexedArray(_) => "IndexedArray", - Self::MonthlyProfile(_) => "MonthlyProfile", - Self::WeeklyProfile(_) => "WeeklyProfile", - Self::UniformDrawdownProfile(_) => "UniformDrawdownProfile", - Self::Max(_) => "Max", - Self::Min(_) => "Min", - Self::Negative(_) => "Negative", - Self::Polynomial1D(_) => "Polynomial1D", - Self::ParameterThreshold(_) => "ParameterThreshold", - Self::TablesArray(_) => "TablesArray", - Self::Python(_) => "Python", - Self::Delay(_) => "Delay", - Self::Division(_) => "Division", - Self::Offset(_) => "Offset", - Self::DiscountFactor(_) => "DiscountFactor", - Self::Interpolated(_) => "Interpolated", - Self::RbfProfile(_) => "RbfProfile", - } + pub fn parameter_type(&self) -> ParameterType { + // Implementation provided by the `EnumDiscriminants` derive macro. + self.into() } pub fn add_to_model( &self, network: &mut pywr_core::network::Network, args: &LoadArgs, - ) -> Result { + ) -> Result { let ty = match self { - Self::Constant(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::ControlCurveInterpolated(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::Aggregated(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::AggregatedIndex(p) => ParameterType::Index(p.add_to_model(network, args)?), - Self::AsymmetricSwitchIndex(p) => ParameterType::Index(p.add_to_model(network, args)?), - Self::ControlCurvePiecewiseInterpolated(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::ControlCurveIndex(p) => ParameterType::Index(p.add_to_model(network, args)?), - Self::ControlCurve(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::DailyProfile(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::IndexedArray(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::MonthlyProfile(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::WeeklyProfile(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::UniformDrawdownProfile(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::Max(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::Min(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::Negative(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::Polynomial1D(p) => ParameterType::Parameter(p.add_to_model(network)?), - Self::ParameterThreshold(p) => ParameterType::Index(p.add_to_model(network, args)?), - Self::TablesArray(p) => ParameterType::Parameter(p.add_to_model(network, args)?), + Self::Constant(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::ControlCurveInterpolated(p) => { + pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?) + } + Self::Aggregated(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::AggregatedIndex(p) => pywr_core::parameters::ParameterType::Index(p.add_to_model(network, args)?), + Self::AsymmetricSwitchIndex(p) => { + pywr_core::parameters::ParameterType::Index(p.add_to_model(network, args)?) + } + Self::ControlCurvePiecewiseInterpolated(p) => { + pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?) + } + Self::ControlCurveIndex(p) => pywr_core::parameters::ParameterType::Index(p.add_to_model(network, args)?), + Self::ControlCurve(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::DailyProfile(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::IndexedArray(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::MonthlyProfile(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::WeeklyProfile(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::UniformDrawdownProfile(p) => { + pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?) + } + Self::Max(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::Min(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::Negative(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::Polynomial1D(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network)?), + Self::ParameterThreshold(p) => pywr_core::parameters::ParameterType::Index(p.add_to_model(network, args)?), + Self::TablesArray(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), Self::Python(p) => p.add_to_model(network, args)?, - Self::Delay(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::Division(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::Offset(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::DiscountFactor(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::Interpolated(p) => ParameterType::Parameter(p.add_to_model(network, args)?), - Self::RbfProfile(p) => ParameterType::Parameter(p.add_to_model(network)?), + Self::Delay(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::Division(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::Offset(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::DiscountFactor(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::Interpolated(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network, args)?), + Self::RbfProfile(p) => pywr_core::parameters::ParameterType::Parameter(p.add_to_model(network)?), }; Ok(ty) @@ -307,7 +292,7 @@ pub fn convert_parameter_v1_to_v2( } #[derive(Clone)] -enum ParameterOrTimeseries { +pub enum ParameterOrTimeseries { Parameter(Parameter), Timeseries(TimeseriesV1Data), } @@ -553,154 +538,6 @@ impl TryFrom for ConstantValue { } } -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -pub struct NodeReference { - /// The name of the node - pub name: String, - /// The attribute of the node. If this is `None` then the default attribute is used. - pub attribute: Option, -} - -impl NodeReference { - pub fn load(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result { - // This is the associated node in the schema - let node = args - .schema - .get_node_by_name(&self.name) - .ok_or_else(|| SchemaError::NodeNotFound(self.name.clone()))?; - - node.create_metric(network, self.attribute) - } -} - -/// A floating-point(f64) value from a metric in the network. -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -#[serde(tag = "type")] -pub enum MetricFloatReference { - Node(NodeReference), - Parameter { name: String, key: Option }, - InterNetworkTransfer { name: String }, -} - -impl MetricFloatReference { - /// Load the metric definition into a `Metric` containing the appropriate internal references. - pub fn load(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result { - match self { - Self::Node(node_ref) => node_ref.load(network, args), - Self::Parameter { name, key } => { - match key { - Some(key) => { - // Key given; this should be a multi-valued parameter - Ok(MetricF64::MultiParameterValue(( - network.get_multi_valued_parameter_index_by_name(name)?, - key.clone(), - ))) - } - None => { - // This should be an existing parameter - Ok(MetricF64::ParameterValue(network.get_parameter_index_by_name(name)?)) - } - } - } - Self::InterNetworkTransfer { name } => { - // Find the matching inter model transfer - match args.inter_network_transfers.iter().position(|t| &t.name == name) { - Some(idx) => Ok(MetricF64::InterNetworkTransfer(MultiNetworkTransferIndex(idx))), - None => Err(SchemaError::InterNetworkTransferNotFound(name.to_string())), - } - } - } - } -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -#[serde(tag = "type", content = "name")] -pub enum TimeseriesColumns { - Scenario(String), - Column(String), -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -pub struct TimeseriesReference { - #[serde(rename = "type")] - ty: String, - name: String, - columns: TimeseriesColumns, -} - -impl TimeseriesReference { - pub fn new(name: String, columns: TimeseriesColumns) -> Self { - let ty = "Timeseries".to_string(); - Self { ty, name, columns } - } - - pub fn name(&self) -> &str { - self.name.as_str() - } -} - -/// A floating-point(f64) value from a metric in the network. -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -#[serde(untagged)] -pub enum MetricFloatValue { - Reference(MetricFloatReference), - InlineParameter { definition: Box }, - Timeseries(TimeseriesReference), -} - -impl MetricFloatValue { - /// Load the metric definition into a `Metric` containing the appropriate internal references. - pub fn load(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result { - match self { - Self::Reference(reference) => Ok(reference.load(network, args)?), - Self::InlineParameter { definition } => { - // This inline parameter could already have been loaded on a previous attempt - // Let's see if exists first. - // TODO this will create strange issues if there are duplicate names in the - // parameter definitions. I.e. we will only ever load the first one and then - // assume it is the correct one for future references to that name. This could be - // improved by checking the parameter returned by name matches the definition here. - - 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)) - } - Err(_) => { - // An error retrieving a parameter with this name; assume it needs creating. - match definition.add_to_model(network, args)? { - ParameterType::Parameter(idx) => Ok(MetricF64::ParameterValue(idx)), - ParameterType::Index(_) => Err(SchemaError::UnexpectedParameterType(format!( - "Found index parameter of type '{}' with name '{}' where an float parameter was expected.", - definition.ty(), - definition.name(), - ))), - ParameterType::Multi(_) => Err(SchemaError::UnexpectedParameterType(format!( - "Found an inline definition of a multi valued parameter of type '{}' with name '{}' where an float parameter was expected. Multi valued parameters cannot be defined inline.", - definition.ty(), - definition.name(), - ))), - } - } - } - } - Self::Timeseries(ts_ref) => { - let param_idx = match &ts_ref.columns { - TimeseriesColumns::Scenario(scenario) => { - args.timeseries - .load_df(network, ts_ref.name.as_ref(), &args.domain, scenario.as_str())? - } - TimeseriesColumns::Column(col) => { - args.timeseries - .load_column(network, ts_ref.name.as_ref(), col.as_str())? - } - }; - Ok(MetricF64::ParameterValue(param_idx)) - } - } - } -} - /// An integer (i64) value from another parameter #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] #[serde(untagged)] @@ -723,15 +560,15 @@ impl ParameterIndexValue { Self::Inline(parameter) => { // Inline parameter needs to be added match parameter.add_to_model(network, args)? { - ParameterType::Index(idx) => Ok(idx), - ParameterType::Parameter(_) => Err(SchemaError::UnexpectedParameterType(format!( + pywr_core::parameters::ParameterType::Index(idx) => Ok(idx), + pywr_core::parameters::ParameterType::Parameter(_) => Err(SchemaError::UnexpectedParameterType(format!( "Found float parameter of type '{}' with name '{}' where an index parameter was expected.", - parameter.ty(), + parameter.parameter_type(), parameter.name(), ))), - ParameterType::Multi(_) => Err(SchemaError::UnexpectedParameterType(format!( + pywr_core::parameters::ParameterType::Multi(_) => Err(SchemaError::UnexpectedParameterType(format!( "Found an inline definition of a multi valued parameter of type '{}' with name '{}' where an index parameter was expected. Multi valued parameters cannot be defined inline.", - parameter.ty(), + parameter.parameter_type(), parameter.name(), ))), } @@ -740,91 +577,6 @@ impl ParameterIndexValue { } } -/// A potentially dynamic floating-point (f64) value -/// -/// This value can be a constant (literal or otherwise) or a dynamic value provided -/// by another parameter. -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -#[serde(untagged)] -pub enum DynamicFloatValue { - Constant(ConstantValue), - Dynamic(MetricFloatValue), -} - -impl Default for DynamicFloatValue { - fn default() -> Self { - Self::Constant(ConstantValue::default()) - } -} - -impl DynamicFloatValue { - pub fn from_f64(v: f64) -> Self { - Self::Constant(ConstantValue::Literal(v)) - } - - pub fn load(&self, network: &mut pywr_core::network::Network, args: &LoadArgs) -> Result { - let parameter_ref = match self { - DynamicFloatValue::Constant(v) => MetricF64::Constant(v.load(args.tables)?), - DynamicFloatValue::Dynamic(v) => v.load(network, args)?, - }; - Ok(parameter_ref) - } -} - -impl TryFromV1Parameter for DynamicFloatValue { - type Error = ConversionError; - - fn try_from_v1_parameter( - v1: ParameterValueV1, - parent_node: Option<&str>, - unnamed_count: &mut usize, - ) -> Result { - let p = match v1 { - ParameterValueV1::Constant(v) => Self::Constant(ConstantValue::Literal(v)), - ParameterValueV1::Reference(p_name) => { - Self::Dynamic(MetricFloatValue::Reference(MetricFloatReference::Parameter { - name: p_name, - key: None, - })) - } - ParameterValueV1::Table(tbl) => Self::Constant(ConstantValue::Table(tbl.try_into()?)), - ParameterValueV1::Inline(param) => { - let definition: ParameterOrTimeseries = (*param).try_into_v2_parameter(parent_node, unnamed_count)?; - match definition { - ParameterOrTimeseries::Parameter(p) => Self::Dynamic(MetricFloatValue::InlineParameter { - definition: Box::new(p), - }), - ParameterOrTimeseries::Timeseries(t) => { - let name = match t.name { - Some(n) => n, - None => { - let n = match parent_node { - Some(node_name) => format!("{}-p{}.timeseries", node_name, *unnamed_count), - None => format!("unnamed-timeseries-{}", *unnamed_count), - }; - *unnamed_count += 1; - n - } - }; - - let cols = match (&t.column, &t.scenario) { - (Some(col), None) => TimeseriesColumns::Column(col.clone()), - (None, Some(scenario)) => TimeseriesColumns::Scenario(scenario.clone()), - (Some(_), Some(_)) => { - return Err(ConversionError::AmbiguousColumnAndScenario(name.clone())) - } - (None, None) => return Err(ConversionError::MissingColumnOrScenario(name.clone())), - }; - - Self::Dynamic(MetricFloatValue::Timeseries(TimeseriesReference::new(name, cols))) - } - } - } - }; - Ok(p) - } -} - /// A potentially dynamic integer (usize) value /// /// This value can be a constant (literal or otherwise) or a dynamic value provided @@ -959,18 +711,18 @@ impl TryFrom for TableIndex { } pub enum DynamicFloatValueType<'a> { - Single(&'a DynamicFloatValue), - List(&'a Vec), + Single(&'a Metric), + List(&'a Vec), } -impl<'a> From<&'a DynamicFloatValue> for DynamicFloatValueType<'a> { - fn from(v: &'a DynamicFloatValue) -> Self { +impl<'a> From<&'a Metric> for DynamicFloatValueType<'a> { + fn from(v: &'a Metric) -> Self { Self::Single(v) } } -impl<'a> From<&'a Vec> for DynamicFloatValueType<'a> { - fn from(v: &'a Vec) -> Self { +impl<'a> From<&'a Vec> for DynamicFloatValueType<'a> { + fn from(v: &'a Vec) -> Self { Self::List(v) } } diff --git a/pywr-schema/src/parameters/offset.rs b/pywr-schema/src/parameters/offset.rs index c8b3d92c..aa9f52ec 100644 --- a/pywr-schema/src/parameters/offset.rs +++ b/pywr-schema/src/parameters/offset.rs @@ -1,6 +1,7 @@ use crate::error::SchemaError; +use crate::metric::Metric; use crate::model::LoadArgs; -use crate::parameters::{ConstantValue, DynamicFloatValue, DynamicFloatValueType, ParameterMeta}; +use crate::parameters::{ConstantValue, DynamicFloatValueType, ParameterMeta}; use pywr_core::parameters::ParameterIndex; use std::collections::HashMap; @@ -31,7 +32,7 @@ pub struct OffsetParameter { /// function is specified this value will be the `x` value for that activation function. pub offset: ConstantValue, /// The metric from which to apply the offset. - pub metric: DynamicFloatValue, + pub metric: Metric, } impl OffsetParameter { diff --git a/pywr-schema/src/parameters/python.rs b/pywr-schema/src/parameters/python.rs index 1ad5a4af..2200302e 100644 --- a/pywr-schema/src/parameters/python.rs +++ b/pywr-schema/src/parameters/python.rs @@ -1,7 +1,8 @@ use crate::data_tables::make_path; use crate::error::SchemaError; +use crate::metric::Metric; use crate::model::LoadArgs; -use crate::parameters::{DynamicFloatValue, DynamicFloatValueType, DynamicIndexValue, ParameterMeta}; +use crate::parameters::{DynamicFloatValueType, DynamicIndexValue, ParameterMeta}; use pyo3::prelude::PyModule; use pyo3::types::{PyDict, PyTuple}; use pyo3::{IntoPy, PyErr, PyObject, Python, ToPyObject}; @@ -86,7 +87,7 @@ pub struct PythonParameter { pub kwargs: HashMap, /// Metric values to pass to the calculation method of the initialised object (i.e. /// values that the Python calculation is dependent on). - pub metrics: Option>, + pub metrics: Option>, /// Index values to pass to the calculation method of the initialised object (i.e. /// indices that the Python calculation is dependent on). pub indices: Option>, diff --git a/pywr-schema/src/parameters/thresholds.rs b/pywr-schema/src/parameters/thresholds.rs index 7328c4fb..a55875f4 100644 --- a/pywr-schema/src/parameters/thresholds.rs +++ b/pywr-schema/src/parameters/thresholds.rs @@ -1,7 +1,8 @@ use crate::error::{ConversionError, SchemaError}; +use crate::metric::Metric; use crate::model::LoadArgs; use crate::parameters::{ - DynamicFloatValue, DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter, + DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter, }; use pywr_core::parameters::ParameterIndex; use pywr_v1_schema::parameters::{ @@ -51,8 +52,8 @@ impl From for pywr_core::parameters::Predicate { pub struct ParameterThresholdParameter { #[serde(flatten)] pub meta: ParameterMeta, - pub parameter: DynamicFloatValue, - pub threshold: DynamicFloatValue, + pub parameter: Metric, + pub threshold: Metric, pub predicate: Predicate, #[serde(default)] pub ratchet: bool, diff --git a/pywr-schema/src/test_models/30-day-licence.json b/pywr-schema/src/test_models/30-day-licence.json index 0d455b3e..51b03ff5 100644 --- a/pywr-schema/src/test_models/30-day-licence.json +++ b/pywr-schema/src/test_models/30-day-licence.json @@ -14,7 +14,10 @@ { "name": "supply1", "type": "Input", - "max_flow": 15 + "max_flow": { + "type": "Constant", + "value": 15 + } }, { "name": "link1", @@ -27,13 +30,21 @@ "type": "Parameter", "name": "demand" }, - "cost": -10 + "cost": { + "type": "Constant", + "value": -10 + } }, { "name": "licence", "type": "RollingVirtualStorage", - "nodes": ["supply1"], - "max_volume": 300, + "nodes": [ + "supply1" + ], + "max_volume": { + "type": "Constant", + "value": 300 + }, "initial_volume": 0.0, "window": { "Days": 30 diff --git a/pywr-schema/src/test_models/csv1-outputs-long.csv b/pywr-schema/src/test_models/csv1-outputs-long.csv index 53fbccf5..9ea67e21 100644 --- a/pywr-schema/src/test_models/csv1-outputs-long.csv +++ b/pywr-schema/src/test_models/csv1-outputs-long.csv @@ -1,366 +1,366 @@ -time_start,time_end,scenario_index,metric_set,name,sub_name,attribute,value -2015-01-01T00:00:00,2015-01-02T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-02T00:00:00,2015-01-03T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-03T00:00:00,2015-01-04T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-04T00:00:00,2015-01-05T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-05T00:00:00,2015-01-06T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-06T00:00:00,2015-01-07T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-07T00:00:00,2015-01-08T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-08T00:00:00,2015-01-09T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-09T00:00:00,2015-01-10T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-10T00:00:00,2015-01-11T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-11T00:00:00,2015-01-12T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-12T00:00:00,2015-01-13T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-13T00:00:00,2015-01-14T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-14T00:00:00,2015-01-15T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-15T00:00:00,2015-01-16T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-16T00:00:00,2015-01-17T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-17T00:00:00,2015-01-18T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-18T00:00:00,2015-01-19T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-19T00:00:00,2015-01-20T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-20T00:00:00,2015-01-21T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-21T00:00:00,2015-01-22T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-22T00:00:00,2015-01-23T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-23T00:00:00,2015-01-24T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-24T00:00:00,2015-01-25T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-25T00:00:00,2015-01-26T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-26T00:00:00,2015-01-27T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-27T00:00:00,2015-01-28T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-28T00:00:00,2015-01-29T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-29T00:00:00,2015-01-30T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-30T00:00:00,2015-01-31T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-01-31T00:00:00,2015-02-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-01T00:00:00,2015-02-02T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-02T00:00:00,2015-02-03T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-03T00:00:00,2015-02-04T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-04T00:00:00,2015-02-05T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-05T00:00:00,2015-02-06T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-06T00:00:00,2015-02-07T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-07T00:00:00,2015-02-08T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-08T00:00:00,2015-02-09T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-09T00:00:00,2015-02-10T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-10T00:00:00,2015-02-11T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-11T00:00:00,2015-02-12T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-12T00:00:00,2015-02-13T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-13T00:00:00,2015-02-14T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-14T00:00:00,2015-02-15T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-15T00:00:00,2015-02-16T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-16T00:00:00,2015-02-17T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-17T00:00:00,2015-02-18T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-18T00:00:00,2015-02-19T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-19T00:00:00,2015-02-20T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-20T00:00:00,2015-02-21T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-21T00:00:00,2015-02-22T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-22T00:00:00,2015-02-23T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-23T00:00:00,2015-02-24T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-24T00:00:00,2015-02-25T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-25T00:00:00,2015-02-26T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-26T00:00:00,2015-02-27T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-27T00:00:00,2015-02-28T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-28T00:00:00,2015-03-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-01T00:00:00,2015-03-02T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-02T00:00:00,2015-03-03T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-03T00:00:00,2015-03-04T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-04T00:00:00,2015-03-05T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-05T00:00:00,2015-03-06T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-06T00:00:00,2015-03-07T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-07T00:00:00,2015-03-08T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-08T00:00:00,2015-03-09T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-09T00:00:00,2015-03-10T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-10T00:00:00,2015-03-11T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-11T00:00:00,2015-03-12T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-12T00:00:00,2015-03-13T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-13T00:00:00,2015-03-14T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-14T00:00:00,2015-03-15T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-15T00:00:00,2015-03-16T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-16T00:00:00,2015-03-17T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-17T00:00:00,2015-03-18T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-18T00:00:00,2015-03-19T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-19T00:00:00,2015-03-20T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-20T00:00:00,2015-03-21T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-21T00:00:00,2015-03-22T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-22T00:00:00,2015-03-23T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-23T00:00:00,2015-03-24T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-24T00:00:00,2015-03-25T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-25T00:00:00,2015-03-26T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-26T00:00:00,2015-03-27T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-27T00:00:00,2015-03-28T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-28T00:00:00,2015-03-29T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-29T00:00:00,2015-03-30T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-30T00:00:00,2015-03-31T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-31T00:00:00,2015-04-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-01T00:00:00,2015-04-02T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-02T00:00:00,2015-04-03T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-03T00:00:00,2015-04-04T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-04T00:00:00,2015-04-05T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-05T00:00:00,2015-04-06T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-06T00:00:00,2015-04-07T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-07T00:00:00,2015-04-08T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-08T00:00:00,2015-04-09T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-09T00:00:00,2015-04-10T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-10T00:00:00,2015-04-11T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-11T00:00:00,2015-04-12T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-12T00:00:00,2015-04-13T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-13T00:00:00,2015-04-14T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-14T00:00:00,2015-04-15T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-15T00:00:00,2015-04-16T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-16T00:00:00,2015-04-17T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-17T00:00:00,2015-04-18T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-18T00:00:00,2015-04-19T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-19T00:00:00,2015-04-20T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-20T00:00:00,2015-04-21T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-21T00:00:00,2015-04-22T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-22T00:00:00,2015-04-23T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-23T00:00:00,2015-04-24T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-24T00:00:00,2015-04-25T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-25T00:00:00,2015-04-26T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-26T00:00:00,2015-04-27T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-27T00:00:00,2015-04-28T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-28T00:00:00,2015-04-29T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-29T00:00:00,2015-04-30T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-30T00:00:00,2015-05-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-01T00:00:00,2015-05-02T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-02T00:00:00,2015-05-03T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-03T00:00:00,2015-05-04T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-04T00:00:00,2015-05-05T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-05T00:00:00,2015-05-06T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-06T00:00:00,2015-05-07T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-07T00:00:00,2015-05-08T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-08T00:00:00,2015-05-09T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-09T00:00:00,2015-05-10T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-10T00:00:00,2015-05-11T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-11T00:00:00,2015-05-12T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-12T00:00:00,2015-05-13T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-13T00:00:00,2015-05-14T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-14T00:00:00,2015-05-15T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-15T00:00:00,2015-05-16T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-16T00:00:00,2015-05-17T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-17T00:00:00,2015-05-18T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-18T00:00:00,2015-05-19T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-19T00:00:00,2015-05-20T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-20T00:00:00,2015-05-21T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-21T00:00:00,2015-05-22T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-22T00:00:00,2015-05-23T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-23T00:00:00,2015-05-24T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-24T00:00:00,2015-05-25T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-25T00:00:00,2015-05-26T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-26T00:00:00,2015-05-27T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-27T00:00:00,2015-05-28T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-28T00:00:00,2015-05-29T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-29T00:00:00,2015-05-30T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-30T00:00:00,2015-05-31T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-31T00:00:00,2015-06-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-01T00:00:00,2015-06-02T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-02T00:00:00,2015-06-03T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-03T00:00:00,2015-06-04T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-04T00:00:00,2015-06-05T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-05T00:00:00,2015-06-06T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-06T00:00:00,2015-06-07T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-07T00:00:00,2015-06-08T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-08T00:00:00,2015-06-09T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-09T00:00:00,2015-06-10T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-10T00:00:00,2015-06-11T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-11T00:00:00,2015-06-12T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-12T00:00:00,2015-06-13T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-13T00:00:00,2015-06-14T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-14T00:00:00,2015-06-15T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-15T00:00:00,2015-06-16T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-16T00:00:00,2015-06-17T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-17T00:00:00,2015-06-18T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-18T00:00:00,2015-06-19T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-19T00:00:00,2015-06-20T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-20T00:00:00,2015-06-21T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-21T00:00:00,2015-06-22T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-22T00:00:00,2015-06-23T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-23T00:00:00,2015-06-24T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-24T00:00:00,2015-06-25T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-25T00:00:00,2015-06-26T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-26T00:00:00,2015-06-27T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-27T00:00:00,2015-06-28T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-28T00:00:00,2015-06-29T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-29T00:00:00,2015-06-30T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-30T00:00:00,2015-07-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-01T00:00:00,2015-07-02T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-02T00:00:00,2015-07-03T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-03T00:00:00,2015-07-04T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-04T00:00:00,2015-07-05T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-05T00:00:00,2015-07-06T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-06T00:00:00,2015-07-07T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-07T00:00:00,2015-07-08T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-08T00:00:00,2015-07-09T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-09T00:00:00,2015-07-10T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-10T00:00:00,2015-07-11T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-11T00:00:00,2015-07-12T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-12T00:00:00,2015-07-13T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-13T00:00:00,2015-07-14T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-14T00:00:00,2015-07-15T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-15T00:00:00,2015-07-16T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-16T00:00:00,2015-07-17T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-17T00:00:00,2015-07-18T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-18T00:00:00,2015-07-19T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-19T00:00:00,2015-07-20T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-20T00:00:00,2015-07-21T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-21T00:00:00,2015-07-22T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-22T00:00:00,2015-07-23T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-23T00:00:00,2015-07-24T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-24T00:00:00,2015-07-25T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-25T00:00:00,2015-07-26T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-26T00:00:00,2015-07-27T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-27T00:00:00,2015-07-28T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-28T00:00:00,2015-07-29T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-29T00:00:00,2015-07-30T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-30T00:00:00,2015-07-31T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-31T00:00:00,2015-08-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-01T00:00:00,2015-08-02T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-02T00:00:00,2015-08-03T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-03T00:00:00,2015-08-04T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-04T00:00:00,2015-08-05T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-05T00:00:00,2015-08-06T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-06T00:00:00,2015-08-07T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-07T00:00:00,2015-08-08T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-08T00:00:00,2015-08-09T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-09T00:00:00,2015-08-10T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-10T00:00:00,2015-08-11T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-11T00:00:00,2015-08-12T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-12T00:00:00,2015-08-13T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-13T00:00:00,2015-08-14T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-14T00:00:00,2015-08-15T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-15T00:00:00,2015-08-16T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-16T00:00:00,2015-08-17T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-17T00:00:00,2015-08-18T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-18T00:00:00,2015-08-19T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-19T00:00:00,2015-08-20T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-20T00:00:00,2015-08-21T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-21T00:00:00,2015-08-22T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-22T00:00:00,2015-08-23T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-23T00:00:00,2015-08-24T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-24T00:00:00,2015-08-25T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-25T00:00:00,2015-08-26T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-26T00:00:00,2015-08-27T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-27T00:00:00,2015-08-28T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-28T00:00:00,2015-08-29T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-29T00:00:00,2015-08-30T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-30T00:00:00,2015-08-31T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-31T00:00:00,2015-09-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-01T00:00:00,2015-09-02T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-02T00:00:00,2015-09-03T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-03T00:00:00,2015-09-04T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-04T00:00:00,2015-09-05T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-05T00:00:00,2015-09-06T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-06T00:00:00,2015-09-07T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-07T00:00:00,2015-09-08T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-08T00:00:00,2015-09-09T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-09T00:00:00,2015-09-10T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-10T00:00:00,2015-09-11T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-11T00:00:00,2015-09-12T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-12T00:00:00,2015-09-13T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-13T00:00:00,2015-09-14T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-14T00:00:00,2015-09-15T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-15T00:00:00,2015-09-16T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-16T00:00:00,2015-09-17T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-17T00:00:00,2015-09-18T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-18T00:00:00,2015-09-19T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-19T00:00:00,2015-09-20T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-20T00:00:00,2015-09-21T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-21T00:00:00,2015-09-22T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-22T00:00:00,2015-09-23T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-23T00:00:00,2015-09-24T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-24T00:00:00,2015-09-25T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-25T00:00:00,2015-09-26T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-26T00:00:00,2015-09-27T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-27T00:00:00,2015-09-28T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-28T00:00:00,2015-09-29T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-29T00:00:00,2015-09-30T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-30T00:00:00,2015-10-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-01T00:00:00,2015-10-02T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-02T00:00:00,2015-10-03T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-03T00:00:00,2015-10-04T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-04T00:00:00,2015-10-05T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-05T00:00:00,2015-10-06T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-06T00:00:00,2015-10-07T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-07T00:00:00,2015-10-08T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-08T00:00:00,2015-10-09T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-09T00:00:00,2015-10-10T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-10T00:00:00,2015-10-11T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-11T00:00:00,2015-10-12T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-12T00:00:00,2015-10-13T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-13T00:00:00,2015-10-14T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-14T00:00:00,2015-10-15T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-15T00:00:00,2015-10-16T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-16T00:00:00,2015-10-17T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-17T00:00:00,2015-10-18T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-18T00:00:00,2015-10-19T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-19T00:00:00,2015-10-20T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-20T00:00:00,2015-10-21T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-21T00:00:00,2015-10-22T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-22T00:00:00,2015-10-23T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-23T00:00:00,2015-10-24T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-24T00:00:00,2015-10-25T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-25T00:00:00,2015-10-26T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-26T00:00:00,2015-10-27T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-27T00:00:00,2015-10-28T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-28T00:00:00,2015-10-29T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-29T00:00:00,2015-10-30T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-30T00:00:00,2015-10-31T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-31T00:00:00,2015-11-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-01T00:00:00,2015-11-02T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-02T00:00:00,2015-11-03T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-03T00:00:00,2015-11-04T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-04T00:00:00,2015-11-05T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-05T00:00:00,2015-11-06T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-06T00:00:00,2015-11-07T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-07T00:00:00,2015-11-08T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-08T00:00:00,2015-11-09T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-09T00:00:00,2015-11-10T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-10T00:00:00,2015-11-11T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-11T00:00:00,2015-11-12T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-12T00:00:00,2015-11-13T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-13T00:00:00,2015-11-14T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-14T00:00:00,2015-11-15T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-15T00:00:00,2015-11-16T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-16T00:00:00,2015-11-17T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-17T00:00:00,2015-11-18T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-18T00:00:00,2015-11-19T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-19T00:00:00,2015-11-20T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-20T00:00:00,2015-11-21T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-21T00:00:00,2015-11-22T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-22T00:00:00,2015-11-23T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-23T00:00:00,2015-11-24T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-24T00:00:00,2015-11-25T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-25T00:00:00,2015-11-26T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-26T00:00:00,2015-11-27T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-27T00:00:00,2015-11-28T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-28T00:00:00,2015-11-29T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-29T00:00:00,2015-11-30T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-30T00:00:00,2015-12-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-01T00:00:00,2015-12-02T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-02T00:00:00,2015-12-03T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-03T00:00:00,2015-12-04T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-04T00:00:00,2015-12-05T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-05T00:00:00,2015-12-06T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-06T00:00:00,2015-12-07T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-07T00:00:00,2015-12-08T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-08T00:00:00,2015-12-09T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-09T00:00:00,2015-12-10T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-10T00:00:00,2015-12-11T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-11T00:00:00,2015-12-12T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-12T00:00:00,2015-12-13T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-13T00:00:00,2015-12-14T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-14T00:00:00,2015-12-15T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-15T00:00:00,2015-12-16T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-16T00:00:00,2015-12-17T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-17T00:00:00,2015-12-18T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-18T00:00:00,2015-12-19T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-19T00:00:00,2015-12-20T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-20T00:00:00,2015-12-21T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-21T00:00:00,2015-12-22T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-22T00:00:00,2015-12-23T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-23T00:00:00,2015-12-24T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-24T00:00:00,2015-12-25T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-25T00:00:00,2015-12-26T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-26T00:00:00,2015-12-27T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-27T00:00:00,2015-12-28T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-28T00:00:00,2015-12-29T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-29T00:00:00,2015-12-30T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-30T00:00:00,2015-12-31T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-31T00:00:00,2016-01-01T00:00:00,0,nodes,demand1,,inflow,10.0 +time_start,time_end,scenario_index,metric_set,name,attribute,value +2015-01-01T00:00:00,2015-01-02T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-02T00:00:00,2015-01-03T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-03T00:00:00,2015-01-04T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-04T00:00:00,2015-01-05T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-05T00:00:00,2015-01-06T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-06T00:00:00,2015-01-07T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-07T00:00:00,2015-01-08T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-08T00:00:00,2015-01-09T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-09T00:00:00,2015-01-10T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-10T00:00:00,2015-01-11T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-11T00:00:00,2015-01-12T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-12T00:00:00,2015-01-13T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-13T00:00:00,2015-01-14T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-14T00:00:00,2015-01-15T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-15T00:00:00,2015-01-16T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-16T00:00:00,2015-01-17T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-17T00:00:00,2015-01-18T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-18T00:00:00,2015-01-19T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-19T00:00:00,2015-01-20T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-20T00:00:00,2015-01-21T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-21T00:00:00,2015-01-22T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-22T00:00:00,2015-01-23T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-23T00:00:00,2015-01-24T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-24T00:00:00,2015-01-25T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-25T00:00:00,2015-01-26T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-26T00:00:00,2015-01-27T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-27T00:00:00,2015-01-28T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-28T00:00:00,2015-01-29T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-29T00:00:00,2015-01-30T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-30T00:00:00,2015-01-31T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-01-31T00:00:00,2015-02-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-01T00:00:00,2015-02-02T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-02T00:00:00,2015-02-03T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-03T00:00:00,2015-02-04T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-04T00:00:00,2015-02-05T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-05T00:00:00,2015-02-06T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-06T00:00:00,2015-02-07T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-07T00:00:00,2015-02-08T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-08T00:00:00,2015-02-09T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-09T00:00:00,2015-02-10T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-10T00:00:00,2015-02-11T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-11T00:00:00,2015-02-12T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-12T00:00:00,2015-02-13T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-13T00:00:00,2015-02-14T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-14T00:00:00,2015-02-15T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-15T00:00:00,2015-02-16T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-16T00:00:00,2015-02-17T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-17T00:00:00,2015-02-18T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-18T00:00:00,2015-02-19T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-19T00:00:00,2015-02-20T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-20T00:00:00,2015-02-21T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-21T00:00:00,2015-02-22T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-22T00:00:00,2015-02-23T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-23T00:00:00,2015-02-24T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-24T00:00:00,2015-02-25T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-25T00:00:00,2015-02-26T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-26T00:00:00,2015-02-27T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-27T00:00:00,2015-02-28T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-28T00:00:00,2015-03-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-01T00:00:00,2015-03-02T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-02T00:00:00,2015-03-03T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-03T00:00:00,2015-03-04T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-04T00:00:00,2015-03-05T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-05T00:00:00,2015-03-06T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-06T00:00:00,2015-03-07T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-07T00:00:00,2015-03-08T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-08T00:00:00,2015-03-09T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-09T00:00:00,2015-03-10T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-10T00:00:00,2015-03-11T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-11T00:00:00,2015-03-12T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-12T00:00:00,2015-03-13T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-13T00:00:00,2015-03-14T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-14T00:00:00,2015-03-15T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-15T00:00:00,2015-03-16T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-16T00:00:00,2015-03-17T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-17T00:00:00,2015-03-18T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-18T00:00:00,2015-03-19T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-19T00:00:00,2015-03-20T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-20T00:00:00,2015-03-21T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-21T00:00:00,2015-03-22T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-22T00:00:00,2015-03-23T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-23T00:00:00,2015-03-24T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-24T00:00:00,2015-03-25T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-25T00:00:00,2015-03-26T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-26T00:00:00,2015-03-27T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-27T00:00:00,2015-03-28T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-28T00:00:00,2015-03-29T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-29T00:00:00,2015-03-30T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-30T00:00:00,2015-03-31T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-31T00:00:00,2015-04-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-01T00:00:00,2015-04-02T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-02T00:00:00,2015-04-03T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-03T00:00:00,2015-04-04T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-04T00:00:00,2015-04-05T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-05T00:00:00,2015-04-06T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-06T00:00:00,2015-04-07T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-07T00:00:00,2015-04-08T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-08T00:00:00,2015-04-09T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-09T00:00:00,2015-04-10T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-10T00:00:00,2015-04-11T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-11T00:00:00,2015-04-12T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-12T00:00:00,2015-04-13T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-13T00:00:00,2015-04-14T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-14T00:00:00,2015-04-15T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-15T00:00:00,2015-04-16T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-16T00:00:00,2015-04-17T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-17T00:00:00,2015-04-18T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-18T00:00:00,2015-04-19T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-19T00:00:00,2015-04-20T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-20T00:00:00,2015-04-21T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-21T00:00:00,2015-04-22T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-22T00:00:00,2015-04-23T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-23T00:00:00,2015-04-24T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-24T00:00:00,2015-04-25T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-25T00:00:00,2015-04-26T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-26T00:00:00,2015-04-27T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-27T00:00:00,2015-04-28T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-28T00:00:00,2015-04-29T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-29T00:00:00,2015-04-30T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-30T00:00:00,2015-05-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-01T00:00:00,2015-05-02T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-02T00:00:00,2015-05-03T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-03T00:00:00,2015-05-04T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-04T00:00:00,2015-05-05T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-05T00:00:00,2015-05-06T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-06T00:00:00,2015-05-07T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-07T00:00:00,2015-05-08T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-08T00:00:00,2015-05-09T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-09T00:00:00,2015-05-10T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-10T00:00:00,2015-05-11T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-11T00:00:00,2015-05-12T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-12T00:00:00,2015-05-13T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-13T00:00:00,2015-05-14T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-14T00:00:00,2015-05-15T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-15T00:00:00,2015-05-16T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-16T00:00:00,2015-05-17T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-17T00:00:00,2015-05-18T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-18T00:00:00,2015-05-19T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-19T00:00:00,2015-05-20T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-20T00:00:00,2015-05-21T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-21T00:00:00,2015-05-22T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-22T00:00:00,2015-05-23T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-23T00:00:00,2015-05-24T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-24T00:00:00,2015-05-25T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-25T00:00:00,2015-05-26T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-26T00:00:00,2015-05-27T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-27T00:00:00,2015-05-28T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-28T00:00:00,2015-05-29T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-29T00:00:00,2015-05-30T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-30T00:00:00,2015-05-31T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-31T00:00:00,2015-06-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-01T00:00:00,2015-06-02T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-02T00:00:00,2015-06-03T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-03T00:00:00,2015-06-04T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-04T00:00:00,2015-06-05T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-05T00:00:00,2015-06-06T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-06T00:00:00,2015-06-07T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-07T00:00:00,2015-06-08T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-08T00:00:00,2015-06-09T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-09T00:00:00,2015-06-10T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-10T00:00:00,2015-06-11T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-11T00:00:00,2015-06-12T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-12T00:00:00,2015-06-13T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-13T00:00:00,2015-06-14T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-14T00:00:00,2015-06-15T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-15T00:00:00,2015-06-16T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-16T00:00:00,2015-06-17T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-17T00:00:00,2015-06-18T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-18T00:00:00,2015-06-19T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-19T00:00:00,2015-06-20T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-20T00:00:00,2015-06-21T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-21T00:00:00,2015-06-22T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-22T00:00:00,2015-06-23T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-23T00:00:00,2015-06-24T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-24T00:00:00,2015-06-25T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-25T00:00:00,2015-06-26T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-26T00:00:00,2015-06-27T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-27T00:00:00,2015-06-28T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-28T00:00:00,2015-06-29T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-29T00:00:00,2015-06-30T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-30T00:00:00,2015-07-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-01T00:00:00,2015-07-02T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-02T00:00:00,2015-07-03T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-03T00:00:00,2015-07-04T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-04T00:00:00,2015-07-05T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-05T00:00:00,2015-07-06T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-06T00:00:00,2015-07-07T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-07T00:00:00,2015-07-08T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-08T00:00:00,2015-07-09T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-09T00:00:00,2015-07-10T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-10T00:00:00,2015-07-11T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-11T00:00:00,2015-07-12T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-12T00:00:00,2015-07-13T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-13T00:00:00,2015-07-14T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-14T00:00:00,2015-07-15T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-15T00:00:00,2015-07-16T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-16T00:00:00,2015-07-17T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-17T00:00:00,2015-07-18T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-18T00:00:00,2015-07-19T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-19T00:00:00,2015-07-20T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-20T00:00:00,2015-07-21T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-21T00:00:00,2015-07-22T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-22T00:00:00,2015-07-23T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-23T00:00:00,2015-07-24T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-24T00:00:00,2015-07-25T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-25T00:00:00,2015-07-26T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-26T00:00:00,2015-07-27T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-27T00:00:00,2015-07-28T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-28T00:00:00,2015-07-29T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-29T00:00:00,2015-07-30T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-30T00:00:00,2015-07-31T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-31T00:00:00,2015-08-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-01T00:00:00,2015-08-02T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-02T00:00:00,2015-08-03T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-03T00:00:00,2015-08-04T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-04T00:00:00,2015-08-05T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-05T00:00:00,2015-08-06T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-06T00:00:00,2015-08-07T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-07T00:00:00,2015-08-08T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-08T00:00:00,2015-08-09T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-09T00:00:00,2015-08-10T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-10T00:00:00,2015-08-11T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-11T00:00:00,2015-08-12T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-12T00:00:00,2015-08-13T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-13T00:00:00,2015-08-14T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-14T00:00:00,2015-08-15T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-15T00:00:00,2015-08-16T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-16T00:00:00,2015-08-17T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-17T00:00:00,2015-08-18T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-18T00:00:00,2015-08-19T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-19T00:00:00,2015-08-20T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-20T00:00:00,2015-08-21T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-21T00:00:00,2015-08-22T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-22T00:00:00,2015-08-23T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-23T00:00:00,2015-08-24T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-24T00:00:00,2015-08-25T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-25T00:00:00,2015-08-26T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-26T00:00:00,2015-08-27T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-27T00:00:00,2015-08-28T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-28T00:00:00,2015-08-29T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-29T00:00:00,2015-08-30T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-30T00:00:00,2015-08-31T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-31T00:00:00,2015-09-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-01T00:00:00,2015-09-02T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-02T00:00:00,2015-09-03T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-03T00:00:00,2015-09-04T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-04T00:00:00,2015-09-05T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-05T00:00:00,2015-09-06T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-06T00:00:00,2015-09-07T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-07T00:00:00,2015-09-08T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-08T00:00:00,2015-09-09T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-09T00:00:00,2015-09-10T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-10T00:00:00,2015-09-11T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-11T00:00:00,2015-09-12T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-12T00:00:00,2015-09-13T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-13T00:00:00,2015-09-14T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-14T00:00:00,2015-09-15T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-15T00:00:00,2015-09-16T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-16T00:00:00,2015-09-17T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-17T00:00:00,2015-09-18T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-18T00:00:00,2015-09-19T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-19T00:00:00,2015-09-20T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-20T00:00:00,2015-09-21T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-21T00:00:00,2015-09-22T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-22T00:00:00,2015-09-23T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-23T00:00:00,2015-09-24T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-24T00:00:00,2015-09-25T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-25T00:00:00,2015-09-26T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-26T00:00:00,2015-09-27T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-27T00:00:00,2015-09-28T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-28T00:00:00,2015-09-29T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-29T00:00:00,2015-09-30T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-30T00:00:00,2015-10-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-01T00:00:00,2015-10-02T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-02T00:00:00,2015-10-03T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-03T00:00:00,2015-10-04T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-04T00:00:00,2015-10-05T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-05T00:00:00,2015-10-06T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-06T00:00:00,2015-10-07T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-07T00:00:00,2015-10-08T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-08T00:00:00,2015-10-09T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-09T00:00:00,2015-10-10T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-10T00:00:00,2015-10-11T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-11T00:00:00,2015-10-12T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-12T00:00:00,2015-10-13T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-13T00:00:00,2015-10-14T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-14T00:00:00,2015-10-15T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-15T00:00:00,2015-10-16T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-16T00:00:00,2015-10-17T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-17T00:00:00,2015-10-18T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-18T00:00:00,2015-10-19T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-19T00:00:00,2015-10-20T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-20T00:00:00,2015-10-21T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-21T00:00:00,2015-10-22T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-22T00:00:00,2015-10-23T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-23T00:00:00,2015-10-24T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-24T00:00:00,2015-10-25T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-25T00:00:00,2015-10-26T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-26T00:00:00,2015-10-27T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-27T00:00:00,2015-10-28T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-28T00:00:00,2015-10-29T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-29T00:00:00,2015-10-30T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-30T00:00:00,2015-10-31T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-31T00:00:00,2015-11-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-01T00:00:00,2015-11-02T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-02T00:00:00,2015-11-03T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-03T00:00:00,2015-11-04T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-04T00:00:00,2015-11-05T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-05T00:00:00,2015-11-06T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-06T00:00:00,2015-11-07T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-07T00:00:00,2015-11-08T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-08T00:00:00,2015-11-09T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-09T00:00:00,2015-11-10T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-10T00:00:00,2015-11-11T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-11T00:00:00,2015-11-12T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-12T00:00:00,2015-11-13T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-13T00:00:00,2015-11-14T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-14T00:00:00,2015-11-15T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-15T00:00:00,2015-11-16T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-16T00:00:00,2015-11-17T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-17T00:00:00,2015-11-18T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-18T00:00:00,2015-11-19T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-19T00:00:00,2015-11-20T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-20T00:00:00,2015-11-21T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-21T00:00:00,2015-11-22T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-22T00:00:00,2015-11-23T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-23T00:00:00,2015-11-24T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-24T00:00:00,2015-11-25T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-25T00:00:00,2015-11-26T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-26T00:00:00,2015-11-27T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-27T00:00:00,2015-11-28T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-28T00:00:00,2015-11-29T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-29T00:00:00,2015-11-30T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-30T00:00:00,2015-12-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-01T00:00:00,2015-12-02T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-02T00:00:00,2015-12-03T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-03T00:00:00,2015-12-04T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-04T00:00:00,2015-12-05T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-05T00:00:00,2015-12-06T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-06T00:00:00,2015-12-07T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-07T00:00:00,2015-12-08T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-08T00:00:00,2015-12-09T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-09T00:00:00,2015-12-10T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-10T00:00:00,2015-12-11T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-11T00:00:00,2015-12-12T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-12T00:00:00,2015-12-13T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-13T00:00:00,2015-12-14T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-14T00:00:00,2015-12-15T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-15T00:00:00,2015-12-16T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-16T00:00:00,2015-12-17T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-17T00:00:00,2015-12-18T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-18T00:00:00,2015-12-19T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-19T00:00:00,2015-12-20T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-20T00:00:00,2015-12-21T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-21T00:00:00,2015-12-22T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-22T00:00:00,2015-12-23T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-23T00:00:00,2015-12-24T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-24T00:00:00,2015-12-25T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-25T00:00:00,2015-12-26T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-26T00:00:00,2015-12-27T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-27T00:00:00,2015-12-28T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-28T00:00:00,2015-12-29T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-29T00:00:00,2015-12-30T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-30T00:00:00,2015-12-31T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-31T00:00:00,2016-01-01T00:00:00,0,nodes,demand1,Inflow,10.0 diff --git a/pywr-schema/src/test_models/csv1-outputs-wide.csv b/pywr-schema/src/test_models/csv1-outputs-wide.csv index 27dce68a..b8aaff48 100644 --- a/pywr-schema/src/test_models/csv1-outputs-wide.csv +++ b/pywr-schema/src/test_models/csv1-outputs-wide.csv @@ -1,6 +1,5 @@ node,demand1 -sub-node, -attribute,inflow +attribute,Inflow global-scenario-index,0 2015-01-01 00:00:00,10.00 2015-01-02 00:00:00,10.00 diff --git a/pywr-schema/src/test_models/csv1.json b/pywr-schema/src/test_models/csv1.json index e1b0189f..bbb05f74 100644 --- a/pywr-schema/src/test_models/csv1.json +++ b/pywr-schema/src/test_models/csv1.json @@ -14,7 +14,10 @@ { "name": "supply1", "type": "Input", - "max_flow": 15 + "max_flow": { + "type": "Constant", + "value": 15 + } }, { "name": "link1", @@ -27,7 +30,10 @@ "type": "Parameter", "name": "demand" }, - "cost": -10 + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ @@ -52,8 +58,8 @@ "name": "nodes", "metrics": [ { - "type": "Default", - "node": "demand1" + "type": "Node", + "name": "demand1" } ] } diff --git a/pywr-schema/src/test_models/csv2-outputs-long.csv b/pywr-schema/src/test_models/csv2-outputs-long.csv index 16377ed1..d9dd4046 100644 --- a/pywr-schema/src/test_models/csv2-outputs-long.csv +++ b/pywr-schema/src/test_models/csv2-outputs-long.csv @@ -1,13 +1,13 @@ -time_start,time_end,scenario_index,metric_set,name,sub_name,attribute,value -2015-01-01T00:00:00,2015-02-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-02-01T00:00:00,2015-03-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-03-01T00:00:00,2015-04-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-04-01T00:00:00,2015-05-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-05-01T00:00:00,2015-06-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-06-01T00:00:00,2015-07-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-07-01T00:00:00,2015-08-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-08-01T00:00:00,2015-09-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-09-01T00:00:00,2015-10-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-10-01T00:00:00,2015-11-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-11-01T00:00:00,2015-12-01T00:00:00,0,nodes,demand1,,inflow,10.0 -2015-12-01T00:00:00,2015-12-31T00:00:00,0,nodes,demand1,,inflow,10.0 +time_start,time_end,scenario_index,metric_set,name,attribute,value +2015-01-01T00:00:00,2015-02-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-02-01T00:00:00,2015-03-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-03-01T00:00:00,2015-04-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-04-01T00:00:00,2015-05-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-05-01T00:00:00,2015-06-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-06-01T00:00:00,2015-07-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-07-01T00:00:00,2015-08-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-08-01T00:00:00,2015-09-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-09-01T00:00:00,2015-10-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-10-01T00:00:00,2015-11-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-11-01T00:00:00,2015-12-01T00:00:00,0,nodes,demand1,Inflow,10.0 +2015-12-01T00:00:00,2015-12-31T00:00:00,0,nodes,demand1,Inflow,10.0 diff --git a/pywr-schema/src/test_models/csv2-outputs-wide.csv b/pywr-schema/src/test_models/csv2-outputs-wide.csv index 796abe35..6d99fc83 100644 --- a/pywr-schema/src/test_models/csv2-outputs-wide.csv +++ b/pywr-schema/src/test_models/csv2-outputs-wide.csv @@ -1,6 +1,5 @@ node,demand1 -sub-node, -attribute,inflow +attribute,Inflow global-scenario-index,0 2015-01-01 00:00:00,10.00 2015-02-01 00:00:00,10.00 diff --git a/pywr-schema/src/test_models/csv2.json b/pywr-schema/src/test_models/csv2.json index 4f30763d..d1c93fa7 100644 --- a/pywr-schema/src/test_models/csv2.json +++ b/pywr-schema/src/test_models/csv2.json @@ -14,7 +14,10 @@ { "name": "supply1", "type": "Input", - "max_flow": 15 + "max_flow": { + "type": "Constant", + "value": 15 + } }, { "name": "link1", @@ -27,7 +30,10 @@ "type": "Parameter", "name": "demand" }, - "cost": -10 + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ @@ -60,8 +66,8 @@ }, "metrics": [ { - "type": "Default", - "node": "demand1" + "type": "Node", + "name": "demand1" } ] } diff --git a/pywr-schema/src/test_models/csv3-outputs-long.csv b/pywr-schema/src/test_models/csv3-outputs-long.csv index 92d49863..4cafdbd8 100644 --- a/pywr-schema/src/test_models/csv3-outputs-long.csv +++ b/pywr-schema/src/test_models/csv3-outputs-long.csv @@ -1,14 +1,14 @@ -time_start,time_end,scenario_index,metric_set,name,sub_name,attribute,value -2015-01-01T00:00:00,2015-02-01T00:00:00,0,nodes-monthly-mean,demand1,,inflow,10.0 -2015-02-01T00:00:00,2015-03-01T00:00:00,0,nodes-monthly-mean,demand1,,inflow,10.0 -2015-03-01T00:00:00,2015-04-01T00:00:00,0,nodes-monthly-mean,demand1,,inflow,10.0 -2015-04-01T00:00:00,2015-05-01T00:00:00,0,nodes-monthly-mean,demand1,,inflow,10.0 -2015-05-01T00:00:00,2015-06-01T00:00:00,0,nodes-monthly-mean,demand1,,inflow,10.0 -2015-06-01T00:00:00,2015-07-01T00:00:00,0,nodes-monthly-mean,demand1,,inflow,10.0 -2015-07-01T00:00:00,2015-08-01T00:00:00,0,nodes-monthly-mean,demand1,,inflow,10.0 -2015-08-01T00:00:00,2015-09-01T00:00:00,0,nodes-monthly-mean,demand1,,inflow,10.0 -2015-09-01T00:00:00,2015-10-01T00:00:00,0,nodes-monthly-mean,demand1,,inflow,10.0 -2015-10-01T00:00:00,2015-11-01T00:00:00,0,nodes-monthly-mean,demand1,,inflow,10.0 -2015-11-01T00:00:00,2015-12-01T00:00:00,0,nodes-monthly-mean,demand1,,inflow,10.0 -2015-12-01T00:00:00,2015-12-31T00:00:00,0,nodes-monthly-mean,demand1,,inflow,10.0 -2015-01-01T00:00:00,2015-12-31T00:00:00,0,nodes-annual-mean,demand1,,inflow,10.0 +time_start,time_end,scenario_index,metric_set,name,attribute,value +2015-01-01T00:00:00,2015-02-01T00:00:00,0,nodes-monthly-mean,demand1,Inflow,10.0 +2015-02-01T00:00:00,2015-03-01T00:00:00,0,nodes-monthly-mean,demand1,Inflow,10.0 +2015-03-01T00:00:00,2015-04-01T00:00:00,0,nodes-monthly-mean,demand1,Inflow,10.0 +2015-04-01T00:00:00,2015-05-01T00:00:00,0,nodes-monthly-mean,demand1,Inflow,10.0 +2015-05-01T00:00:00,2015-06-01T00:00:00,0,nodes-monthly-mean,demand1,Inflow,10.0 +2015-06-01T00:00:00,2015-07-01T00:00:00,0,nodes-monthly-mean,demand1,Inflow,10.0 +2015-07-01T00:00:00,2015-08-01T00:00:00,0,nodes-monthly-mean,demand1,Inflow,10.0 +2015-08-01T00:00:00,2015-09-01T00:00:00,0,nodes-monthly-mean,demand1,Inflow,10.0 +2015-09-01T00:00:00,2015-10-01T00:00:00,0,nodes-monthly-mean,demand1,Inflow,10.0 +2015-10-01T00:00:00,2015-11-01T00:00:00,0,nodes-monthly-mean,demand1,Inflow,10.0 +2015-11-01T00:00:00,2015-12-01T00:00:00,0,nodes-monthly-mean,demand1,Inflow,10.0 +2015-12-01T00:00:00,2015-12-31T00:00:00,0,nodes-monthly-mean,demand1,Inflow,10.0 +2015-01-01T00:00:00,2015-12-31T00:00:00,0,nodes-annual-mean,demand1,Inflow,10.0 diff --git a/pywr-schema/src/test_models/csv3.json b/pywr-schema/src/test_models/csv3.json index 7a2c36b7..963542bc 100644 --- a/pywr-schema/src/test_models/csv3.json +++ b/pywr-schema/src/test_models/csv3.json @@ -14,7 +14,10 @@ { "name": "supply1", "type": "Input", - "max_flow": 15 + "max_flow": { + "type": "Constant", + "value": 15 + } }, { "name": "link1", @@ -27,7 +30,10 @@ "type": "Parameter", "name": "demand" }, - "cost": -10 + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ @@ -60,8 +66,8 @@ }, "metrics": [ { - "type": "Default", - "node": "demand1" + "type": "Node", + "name": "demand1" } ] }, @@ -77,8 +83,8 @@ }, "metrics": [ { - "type": "Default", - "node": "demand1" + "type": "Node", + "name": "demand1" } ] } diff --git a/pywr-schema/src/test_models/delay1.json b/pywr-schema/src/test_models/delay1.json index fa445c0a..fb156907 100644 --- a/pywr-schema/src/test_models/delay1.json +++ b/pywr-schema/src/test_models/delay1.json @@ -14,7 +14,10 @@ { "name": "input1", "type": "Catchment", - "flow": 15 + "flow": { + "type": "Constant", + "value": 15 + } }, { "name": "link1", @@ -25,8 +28,14 @@ { "name": "demand1", "type": "Output", - "max_flow": 20.0, - "cost": 1.0 + "max_flow": { + "type": "Constant", + "value": 20.0 + }, + "cost": { + "type": "Constant", + "value": 1.0 + } } ], "edges": [ diff --git a/pywr-schema/src/test_models/hdf1.json b/pywr-schema/src/test_models/hdf1.json index 1a906516..5c8bcb53 100644 --- a/pywr-schema/src/test_models/hdf1.json +++ b/pywr-schema/src/test_models/hdf1.json @@ -14,7 +14,10 @@ { "name": "supply1", "type": "Input", - "max_flow": 15 + "max_flow": { + "type": "Constant", + "value": 15 + } }, { "name": "link1", @@ -27,7 +30,10 @@ "type": "Parameter", "name": "demand" }, - "cost": -10 + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ @@ -52,8 +58,8 @@ "name": "nodes", "metrics": [ { - "type": "Default", - "node": "demand1" + "type": "Node", + "name": "demand1" } ] } diff --git a/pywr-schema/src/test_models/memory1.json b/pywr-schema/src/test_models/memory1.json index bc9a6ac3..8343f62a 100644 --- a/pywr-schema/src/test_models/memory1.json +++ b/pywr-schema/src/test_models/memory1.json @@ -14,7 +14,10 @@ { "name": "supply1", "type": "Input", - "max_flow": 15 + "max_flow": { + "type": "Constant", + "value": 15 + } }, { "name": "link1", @@ -27,7 +30,10 @@ "type": "Parameter", "name": "demand" }, - "cost": -10 + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ @@ -69,8 +75,8 @@ }, "metrics": [ { - "type": "Default", - "node": "demand1" + "type": "Node", + "name": "demand1" } ] } diff --git a/pywr-schema/src/test_models/multi1/network1.json b/pywr-schema/src/test_models/multi1/network1.json index b08b2905..abd86ca4 100644 --- a/pywr-schema/src/test_models/multi1/network1.json +++ b/pywr-schema/src/test_models/multi1/network1.json @@ -3,7 +3,10 @@ { "name": "supply1", "type": "Input", - "max_flow": 15 + "max_flow": { + "type": "Constant", + "value": 15.0 + } }, { "name": "link1", @@ -16,7 +19,10 @@ "type": "Parameter", "name": "demand" }, - "cost": -10 + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ diff --git a/pywr-schema/src/test_models/multi1/network2.json b/pywr-schema/src/test_models/multi1/network2.json index ef47e9b1..94d60b3a 100644 --- a/pywr-schema/src/test_models/multi1/network2.json +++ b/pywr-schema/src/test_models/multi1/network2.json @@ -19,7 +19,10 @@ "type": "Parameter", "name": "demand" }, - "cost": -10 + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ diff --git a/pywr-schema/src/test_models/multi2/network1.json b/pywr-schema/src/test_models/multi2/network1.json index c5328072..cf8195a0 100644 --- a/pywr-schema/src/test_models/multi2/network1.json +++ b/pywr-schema/src/test_models/multi2/network1.json @@ -19,7 +19,10 @@ "type": "Parameter", "name": "demand" }, - "cost": -10 + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ diff --git a/pywr-schema/src/test_models/multi2/network2.json b/pywr-schema/src/test_models/multi2/network2.json index ef47e9b1..94d60b3a 100644 --- a/pywr-schema/src/test_models/multi2/network2.json +++ b/pywr-schema/src/test_models/multi2/network2.json @@ -19,7 +19,10 @@ "type": "Parameter", "name": "demand" }, - "cost": -10 + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ diff --git a/pywr-schema/src/test_models/piecewise_link1.json b/pywr-schema/src/test_models/piecewise_link1.json index 1d7174d0..f214898d 100644 --- a/pywr-schema/src/test_models/piecewise_link1.json +++ b/pywr-schema/src/test_models/piecewise_link1.json @@ -14,30 +14,54 @@ { "name": "input1", "type": "Input", - "max_flow": 15 + "max_flow": { + "type": "Constant", + "value": 15 + } }, { "name": "link1", "type": "PiecewiseLink", "steps": [ { - "cost": 1.0, - "max_flow": 1.0 + "cost": { + "type": "Constant", + "value": 1.0 + }, + "max_flow": { + "type": "Constant", + "value": 1.0 + } }, { - "cost": 5.0, - "max_flow": 3.0 + "cost": { + "type": "Constant", + "value": 5.0 + }, + "max_flow": { + "type": "Constant", + "value": 3.0 + } }, { - "cost": 15.0 + "cost": { + "type": "Constant", + "value": 15.0 + } } ] }, { "name": "demand1", "type": "Output", - "max_flow": 15.0, - "cost": -10 + "max_flow": { + "type": "Constant", + "value": 15.0 + }, + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ diff --git a/pywr-schema/src/test_models/piecewise_storage1.json b/pywr-schema/src/test_models/piecewise_storage1.json index 4e326778..00a25c58 100644 --- a/pywr-schema/src/test_models/piecewise_storage1.json +++ b/pywr-schema/src/test_models/piecewise_storage1.json @@ -14,29 +14,56 @@ { "name": "input1", "type": "Input", - "max_flow": 5, - "cost": 2.0 + "max_flow": { + "type": "Constant", + "value": 5 + }, + "cost": { + "type": "Constant", + "value": 2.0 + } }, { "name": "storage1", "type": "PiecewiseStorage", - "max_volume": 1000.0, + "max_volume": { + "type": "Constant", + "value": 1000.0 + }, "steps": [ { - "cost": -15.0, - "control_curve": 0.25 + "cost": { + "type": "Constant", + "value": -15 + }, + "control_curve": { + "type": "Constant", + "value": 0.25 + } }, { - "cost": -5.0, - "control_curve": 0.5 + "cost": { + "type": "Constant", + "value": -5 + }, + "control_curve": { + "type": "Constant", + "value": 0.5 + } } ] }, { "name": "demand1", "type": "Output", - "max_flow": 15.0, - "cost": -10 + "max_flow": { + "type": "Constant", + "value": 15 + }, + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ diff --git a/pywr-schema/src/test_models/piecewise_storage2.json b/pywr-schema/src/test_models/piecewise_storage2.json index af056f11..431b09d0 100644 --- a/pywr-schema/src/test_models/piecewise_storage2.json +++ b/pywr-schema/src/test_models/piecewise_storage2.json @@ -14,20 +14,38 @@ { "name": "input1", "type": "Input", - "max_flow": 3.0, - "cost": 2.0 + "max_flow": { + "type": "Constant", + "value": 3.0 + }, + "cost": { + "type": "Constant", + "value": 2.0 + } }, { "name": "storage1", "type": "PiecewiseStorage", - "max_volume": 1000.0, + "max_volume": { + "type": "Constant", + "value": 1000.0 + }, "steps": [ { - "cost": -15.0, - "control_curve": 0.25 + "cost": { + "type": "Constant", + "value": -15.0 + }, + "control_curve": { + "type": "Constant", + "value": 0.25 + } }, { - "cost": -5.0, + "cost": { + "type": "Constant", + "value": -5.0 + }, "control_curve": { "type": "InlineParameter", "definition": { @@ -55,8 +73,14 @@ { "name": "demand1", "type": "Output", - "max_flow": 5.0, - "cost": -10 + "max_flow": { + "type": "Constant", + "value": 5.0 + }, + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ diff --git a/pywr-schema/src/test_models/river_gauge1.json b/pywr-schema/src/test_models/river_gauge1.json new file mode 100644 index 00000000..ff0c736c --- /dev/null +++ b/pywr-schema/src/test_models/river_gauge1.json @@ -0,0 +1,66 @@ +{ + "metadata": { + "title": "Simple 1", + "description": "A very simple example.", + "minimum_version": "0.1" + }, + "timestepper": { + "start": "2015-01-01", + "end": "2015-12-31", + "timestep": 1 + }, + "network": { + "nodes": [ + { + "name": "catchment1", + "type": "Catchment", + "flow": { + "type": "Constant", + "value": -15 + } + }, + { + "name": "gauge1", + "type": "RiverGauge", + "mrf": { + "type": "Constant", + "value": 5.0 + }, + "mrf_cost": { + "type": "Constant", + "value": -20.0 + } + }, + { + "name": "term1", + "type": "Output" + }, + { + "name": "demand1", + "type": "Output", + "max_flow": { + "type": "Constant", + "value": 15 + }, + "cost": { + "type": "Constant", + "value": -10 + } + } + ], + "edges": [ + { + "from_node": "catchment1", + "to_node": "gauge1" + }, + { + "from_node": "gauge1", + "to_node": "term1" + }, + { + "from_node": "gauge1", + "to_node": "demand1" + } + ] + } +} diff --git a/pywr-schema/src/test_models/river_split_with_gauge1.json b/pywr-schema/src/test_models/river_split_with_gauge1.json index 0184250b..b6fe6be4 100644 --- a/pywr-schema/src/test_models/river_split_with_gauge1.json +++ b/pywr-schema/src/test_models/river_split_with_gauge1.json @@ -14,13 +14,22 @@ { "name": "catchment1", "type": "Catchment", - "flow": 15 + "flow": { + "type": "Constant", + "value": 15.0 + } }, { "name": "gauge1", "type": "RiverGauge", - "mrf": 5.0, - "mrf_cost": -20.0 + "mrf": { + "type": "Constant", + "value": 5.0 + }, + "mrf_cost": { + "type": "Constant", + "value": -20 + } }, { "name": "term1", @@ -29,8 +38,14 @@ { "name": "demand1", "type": "Output", - "max_flow": 15.0, - "cost": -10 + "max_flow": { + "type": "Constant", + "value": 15.0 + }, + "cost": { + "type": "Constant", + "value": -10 + } } ], "edges": [ diff --git a/pywr-schema/src/test_models/simple1.json b/pywr-schema/src/test_models/simple1.json index 646df996..4a6786c9 100644 --- a/pywr-schema/src/test_models/simple1.json +++ b/pywr-schema/src/test_models/simple1.json @@ -1,51 +1,57 @@ { - "metadata": { - "title": "Simple 1", - "description": "A very simple example.", - "minimum_version": "0.1" - }, - "timestepper": { - "start": "2015-01-01", - "end": "2015-12-31", - "timestep": 1 - }, - "network": { - "nodes": [ - { - "name": "supply1", - "type": "Input", - "max_flow": 15 - }, - { - "name": "link1", - "type": "Link" - }, - { - "name": "demand1", - "type": "Output", - "max_flow": { - "type": "Parameter", - "name": "demand" - }, - "cost": -10 - } - ], - "edges": [ - { - "from_node": "supply1", - "to_node": "link1" - }, - { - "from_node": "link1", - "to_node": "demand1" - } - ], - "parameters": [ - { - "name": "demand", - "type": "Constant", - "value": 10.0 - } - ] - } + "metadata": { + "title": "Simple 1", + "description": "A very simple example.", + "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": "link1", + "type": "Link" + }, + { + "name": "demand1", + "type": "Output", + "max_flow": { + "type": "Parameter", + "name": "demand" + }, + "cost": { + "type": "Constant", + "value": -10 + } + } + ], + "edges": [ + { + "from_node": "supply1", + "to_node": "link1" + }, + { + "from_node": "link1", + "to_node": "demand1" + } + ], + "parameters": [ + { + "name": "demand", + "type": "Constant", + "value": 10.0 + } + ] + } } diff --git a/pywr-schema/src/test_models/timeseries.json b/pywr-schema/src/test_models/timeseries.json index 6f876a12..d9113c8a 100644 --- a/pywr-schema/src/test_models/timeseries.json +++ b/pywr-schema/src/test_models/timeseries.json @@ -9,6 +9,14 @@ }, "network": { "nodes": [ + { + "name": "input2", + "type": "Input", + "max_flow": { + "type": "Parameter", + "name": "factored_flow" + } + }, { "name": "input1", "type": "Input", @@ -21,14 +29,6 @@ } } }, - { - "name": "input2", - "type": "Input", - "max_flow": { - "type": "Parameter", - "name": "factored_flow" - } - }, { "name": "link1", "type": "Link" @@ -36,7 +36,10 @@ { "name": "output1", "type": "Output", - "cost": -10.0, + "cost": { + "type": "Constant", + "value": -10 + }, "max_flow": { "type": "Parameter", "name": "demand" @@ -76,10 +79,12 @@ "name": "inflow1" } }, - 0.5 + { + "type": "Constant", + "value": 0.5 + } ] } - ], "timeseries": [ {