-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add timeseries section to model schema #99
Changes from 15 commits
32b5e5e
2b308ec
e5c6822
1b5c5b1
72fe90e
5320983
1300e77
6b29780
da16f71
d7d011c
483b17e
413a6be
e9805f4
4040da2
bbe8187
38f16d4
a021b2b
7de6996
4150d54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
use crate::data_tables::TableError; | ||
use crate::nodes::NodeAttribute; | ||
use crate::timeseries::TimeseriesError; | ||
use polars::error::PolarsError; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused import |
||
use pyo3::exceptions::PyRuntimeError; | ||
use pyo3::PyErr; | ||
use thiserror::Error; | ||
|
@@ -54,6 +56,8 @@ pub enum SchemaError { | |
InvalidRollingWindow { name: String }, | ||
#[error("Failed to load parameter {name}: {error}")] | ||
LoadParameter { name: String, error: String }, | ||
#[error("Timeseries error: {0}")] | ||
Timeseries(#[from] TimeseriesError), | ||
} | ||
|
||
impl From<SchemaError> for PyErr { | ||
|
@@ -103,4 +107,8 @@ pub enum ConversionError { | |
UnparseableDate(String), | ||
#[error("Chrono out of range error: {0}")] | ||
OutOfRange(#[from] chrono::OutOfRange), | ||
#[error("The dataframe parameters '{0}' defines both a column and a scenario attribute. Only 1 is allowed.")] | ||
AmbiguousColumnAndScenario(String), | ||
#[error("The dataframe parameters '{0}' defines both a column and a scenario. Only 1 is allowed.")] | ||
MissingColumnOrScenario(String), | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,12 +4,21 @@ use super::parameters::Parameter; | |
use crate::data_tables::{DataTable, LoadedTableCollection}; | ||
use crate::error::{ConversionError, SchemaError}; | ||
use crate::metric_sets::MetricSet; | ||
use crate::nodes::NodeAndTimeseries; | ||
use crate::outputs::Output; | ||
use crate::parameters::{MetricFloatReference, TryIntoV2Parameter}; | ||
use crate::parameters::{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some unused imports to resolve here. |
||
convert_parameter_v1_to_v2, DataFrameColumns, DynamicFloatValue, MetricFloatReference, MetricFloatValue, | ||
TimeseriesReference, TryIntoV2Parameter, | ||
}; | ||
use crate::timeseries::{convert_from_v1_data, LoadedTimeseriesCollection, Timeseries}; | ||
use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; | ||
use polars::frame::DataFrame; | ||
use pywr_core::models::ModelDomain; | ||
use pywr_core::timestep::TimestepDuration; | ||
use pywr_core::PywrError; | ||
use serde::de; | ||
use std::collections::HashSet; | ||
use std::hash::Hash; | ||
use std::path::{Path, PathBuf}; | ||
use std::str::FromStr; | ||
|
||
|
@@ -140,6 +149,7 @@ pub struct PywrNetwork { | |
pub edges: Vec<Edge>, | ||
pub parameters: Option<Vec<Parameter>>, | ||
pub tables: Option<Vec<DataTable>>, | ||
pub timeseries: Option<Vec<Timeseries>>, | ||
pub metric_sets: Option<Vec<MetricSet>>, | ||
pub outputs: Option<Vec<Output>>, | ||
} | ||
|
@@ -192,16 +202,25 @@ impl PywrNetwork { | |
// Load all the data tables | ||
let tables = LoadedTableCollection::from_schema(self.tables.as_deref(), data_path)?; | ||
|
||
// Load all timeseries data | ||
let timeseries = LoadedTimeseriesCollection::from_schema(self.timeseries.as_deref(), domain, data_path)?; | ||
|
||
// Create all the nodes | ||
let mut remaining_nodes = self.nodes.clone(); | ||
|
||
while !remaining_nodes.is_empty() { | ||
let mut failed_nodes: Vec<Node> = Vec::new(); | ||
let n = remaining_nodes.len(); | ||
for node in remaining_nodes.into_iter() { | ||
if let Err(e) = | ||
node.add_to_model(&mut network, self, domain, &tables, data_path, inter_network_transfers) | ||
{ | ||
if let Err(e) = node.add_to_model( | ||
&mut network, | ||
&self, | ||
domain, | ||
&tables, | ||
data_path, | ||
inter_network_transfers, | ||
×eries, | ||
) { | ||
// Adding the node failed! | ||
match e { | ||
SchemaError::PywrCore(core_err) => match core_err { | ||
|
@@ -252,9 +271,15 @@ impl PywrNetwork { | |
let mut failed_parameters: Vec<Parameter> = Vec::new(); | ||
let n = remaining_parameters.len(); | ||
for parameter in remaining_parameters.into_iter() { | ||
if let Err(e) = | ||
parameter.add_to_model(&mut network, self, domain, &tables, data_path, inter_network_transfers) | ||
{ | ||
if let Err(e) = parameter.add_to_model( | ||
&mut network, | ||
self, | ||
domain, | ||
&tables, | ||
data_path, | ||
inter_network_transfers, | ||
×eries, | ||
) { | ||
// Adding the parameter failed! | ||
match e { | ||
SchemaError::PywrCore(core_err) => match core_err { | ||
|
@@ -280,7 +305,15 @@ impl PywrNetwork { | |
|
||
// Apply the inline parameters & constraints to the nodes | ||
for node in &self.nodes { | ||
node.set_constraints(&mut network, self, domain, &tables, data_path, inter_network_transfers)?; | ||
node.set_constraints( | ||
&mut network, | ||
self, | ||
domain, | ||
&tables, | ||
data_path, | ||
inter_network_transfers, | ||
×eries, | ||
)?; | ||
} | ||
|
||
// Create all of the metric sets | ||
|
@@ -412,8 +445,9 @@ impl PywrModel { | |
Timestepper::default() | ||
}); | ||
|
||
let nodes = v1 | ||
let nodes_and_ts: Vec<NodeAndTimeseries> = v1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be worthwhile adding some comments here about this process. |
||
.nodes | ||
.clone() | ||
.into_iter() | ||
.filter_map(|n| match n.try_into() { | ||
Ok(n) => Some(n), | ||
|
@@ -424,22 +458,29 @@ impl PywrModel { | |
}) | ||
.collect::<Vec<_>>(); | ||
|
||
let mut ts_data = nodes_and_ts | ||
.iter() | ||
.filter_map(|n| n.timeseries.clone()) | ||
.flatten() | ||
.collect::<Vec<_>>(); | ||
|
||
let nodes = nodes_and_ts.into_iter().map(|n| n.node).collect::<Vec<_>>(); | ||
|
||
let edges = v1.edges.into_iter().map(|e| e.into()).collect(); | ||
|
||
let parameters = if let Some(v1_parameters) = v1.parameters { | ||
let (parameters, param_ts_data) = if let Some(v1_parameters) = v1.parameters { | ||
let mut unnamed_count: usize = 0; | ||
Some( | ||
v1_parameters | ||
.into_iter() | ||
.filter_map(|p| match p.try_into_v2_parameter(None, &mut unnamed_count) { | ||
Ok(p) => Some(p), | ||
Err(e) => { | ||
errors.push(e); | ||
None | ||
} | ||
}) | ||
.collect::<Vec<_>>(), | ||
) | ||
let (parameters, param_ts_data) = convert_parameter_v1_to_v2(v1_parameters, &mut unnamed_count, &mut errors); | ||
(Some(parameters), Some(param_ts_data)) | ||
} else { | ||
(None, None) | ||
}; | ||
|
||
ts_data.extend(param_ts_data.into_iter().flatten()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can move this up into the above |
||
|
||
let timeseries = if !ts_data.is_empty() { | ||
let ts = convert_from_v1_data(&ts_data, &v1.tables); | ||
Some(ts) | ||
} else { | ||
None | ||
}; | ||
|
@@ -453,6 +494,7 @@ impl PywrModel { | |
edges, | ||
parameters, | ||
tables, | ||
timeseries, | ||
metric_sets, | ||
outputs, | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add docstring. Would be good to make the one above a proper docstring as well.