Skip to content

Commit

Permalink
chore: Use an enum for initial_volume attributes in the schema (#87)
Browse files Browse the repository at this point in the history
Previously the node had two attributes, initial_volume and initial_volume_pc.
These are now variants of the enum.
  • Loading branch information
Batch21 authored Jan 31, 2024
1 parent d3a6517 commit 5ccc949
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
"name": "storage1",
"type": "Storage",
"cost": -1.0,
"initial_volume": 500.0,
"initial_volume": {
"Absolute": 500.0
},
"max_volume": 1000.0
},
{
Expand Down
30 changes: 16 additions & 14 deletions pywr-schema/src/nodes/annual_virtual_storage.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::data_tables::LoadedTableCollection;
use crate::error::{ConversionError, SchemaError};
use crate::model::PywrMultiNetworkTransfer;
use crate::nodes::core::StorageInitialVolume;
use crate::nodes::{NodeAttribute, NodeMeta};
use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter};
use pywr_core::derived_metric::DerivedMetric;
use pywr_core::metric::Metric;
use pywr_core::models::ModelDomain;
use pywr_core::node::{ConstraintValue, StorageInitialVolume};
use pywr_core::node::ConstraintValue;
use pywr_core::virtual_storage::VirtualStorageReset;
use pywr_v1_schema::nodes::AnnualVirtualStorageNode as AnnualVirtualStorageNodeV1;
use std::path::Path;
Expand Down Expand Up @@ -37,8 +38,7 @@ pub struct AnnualVirtualStorageNode {
pub max_volume: Option<DynamicFloatValue>,
pub min_volume: Option<DynamicFloatValue>,
pub cost: Option<DynamicFloatValue>,
pub initial_volume: Option<f64>,
pub initial_volume_pc: Option<f64>,
pub initial_volume: StorageInitialVolume,
pub reset: AnnualReset,
}

Expand All @@ -54,14 +54,6 @@ impl AnnualVirtualStorageNode {
data_path: Option<&Path>,
inter_network_transfers: &[PywrMultiNetworkTransfer],
) -> Result<(), SchemaError> {
let initial_volume = if let Some(iv) = self.initial_volume {
StorageInitialVolume::Absolute(iv)
} else if let Some(pc) = self.initial_volume_pc {
StorageInitialVolume::Proportional(pc)
} else {
return Err(SchemaError::MissingInitialVolume(self.meta.name.to_string()));
};

let cost = match &self.cost {
Some(v) => v
.load(network, schema, domain, tables, data_path, inter_network_transfers)?
Expand Down Expand Up @@ -99,7 +91,7 @@ impl AnnualVirtualStorageNode {
None,
node_idxs.as_ref(),
self.factors.as_deref(),
initial_volume,
self.initial_volume.into(),
min_volume,
max_volume,
reset,
Expand Down Expand Up @@ -167,15 +159,25 @@ impl TryFrom<AnnualVirtualStorageNodeV1> for AnnualVirtualStorageNode {
.map(|v| v.try_into_v2_parameter(Some(&meta.name), &mut unnamed_count))
.transpose()?;

let initial_volume = if let Some(v) = v1.initial_volume {
StorageInitialVolume::Absolute(v)
} else if let Some(v) = v1.initial_volume_pc {
StorageInitialVolume::Proportional(v)
} else {
return Err(ConversionError::MissingAttribute {
name: meta.name,
attrs: vec!["initial_volume".to_string(), "initial_volume_pc".to_string()],
});
};

let n = Self {
meta,
nodes: v1.nodes,
factors: v1.factors,
max_volume,
min_volume,
cost,
initial_volume: v1.initial_volume,
initial_volume_pc: v1.initial_volume_pc,
initial_volume,
reset: AnnualReset {
day: v1.reset_day,
month: v1.reset_month,
Expand Down
107 changes: 90 additions & 17 deletions pywr-schema/src/nodes/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter};
use pywr_core::derived_metric::DerivedMetric;
use pywr_core::metric::Metric;
use pywr_core::models::ModelDomain;
use pywr_core::node::{ConstraintValue, StorageInitialVolume};
use pywr_core::node::{ConstraintValue, StorageInitialVolume as CoreStorageInitialVolume};
use pywr_v1_schema::nodes::{
AggregatedNode as AggregatedNodeV1, AggregatedStorageNode as AggregatedStorageNodeV1,
CatchmentNode as CatchmentNodeV1, InputNode as InputNodeV1, LinkNode as LinkNodeV1, OutputNode as OutputNodeV1,
Expand Down Expand Up @@ -381,15 +381,35 @@ impl TryFrom<OutputNodeV1> for OutputNode {
}
}

#[derive(serde::Deserialize, serde::Serialize, Clone, Default)]
#[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq, Copy, Debug)]
pub enum StorageInitialVolume {
Absolute(f64),
Proportional(f64),
}

impl Default for StorageInitialVolume {
fn default() -> Self {
StorageInitialVolume::Proportional(1.0)
}
}

impl Into<CoreStorageInitialVolume> for StorageInitialVolume {
fn into(self) -> CoreStorageInitialVolume {
match self {
StorageInitialVolume::Absolute(v) => CoreStorageInitialVolume::Absolute(v),
StorageInitialVolume::Proportional(v) => CoreStorageInitialVolume::Proportional(v),
}
}
}

#[derive(serde::Deserialize, serde::Serialize, Clone, Default, Debug)]
pub struct StorageNode {
#[serde(flatten)]
pub meta: NodeMeta,
pub max_volume: Option<DynamicFloatValue>,
pub min_volume: Option<DynamicFloatValue>,
pub cost: Option<DynamicFloatValue>,
pub initial_volume: Option<f64>,
pub initial_volume_pc: Option<f64>,
pub initial_volume: StorageInitialVolume,
}

impl StorageNode {
Expand Down Expand Up @@ -419,14 +439,6 @@ impl StorageNode {
data_path: Option<&Path>,
inter_network_transfers: &[PywrMultiNetworkTransfer],
) -> Result<(), SchemaError> {
let initial_volume = if let Some(iv) = self.initial_volume {
StorageInitialVolume::Absolute(iv)
} else if let Some(pc) = self.initial_volume_pc {
StorageInitialVolume::Proportional(pc)
} else {
return Err(SchemaError::MissingInitialVolume(self.meta.name.to_string()));
};

let min_volume = match &self.min_volume {
Some(v) => v
.load(network, schema, domain, tables, data_path, inter_network_transfers)?
Expand All @@ -441,7 +453,13 @@ impl StorageNode {
None => ConstraintValue::None,
};

network.add_storage_node(self.meta.name.as_str(), None, initial_volume, min_volume, max_volume)?;
network.add_storage_node(
self.meta.name.as_str(),
None,
self.initial_volume.into(),
min_volume,
max_volume,
)?;
Ok(())
}

Expand Down Expand Up @@ -537,13 +555,23 @@ impl TryFrom<StorageNodeV1> for StorageNode {
source: Box::new(source),
})?;

let initial_volume = if let Some(v) = v1.initial_volume {
StorageInitialVolume::Absolute(v)
} else if let Some(v) = v1.initial_volume_pc {
StorageInitialVolume::Proportional(v)
} else {
return Err(ConversionError::MissingAttribute {
name: meta.name,
attrs: vec!["initial_volume".to_string(), "initial_volume_pc".to_string()],
});
};

let n = Self {
meta,
max_volume,
min_volume,
cost,
initial_volume: v1.initial_volume,
initial_volume_pc: v1.initial_volume_pc,
initial_volume,
};
Ok(n)
}
Expand Down Expand Up @@ -571,13 +599,20 @@ impl TryFrom<ReservoirNodeV1> for StorageNode {
.map(|v| v.try_into_v2_parameter(Some(&meta.name), &mut unnamed_count))
.transpose()?;

let initial_volume = if let Some(v) = v1.initial_volume {
StorageInitialVolume::Absolute(v)
} else if let Some(v) = v1.initial_volume_pc {
StorageInitialVolume::Proportional(v)
} else {
StorageInitialVolume::default()
};

let n = Self {
meta,
max_volume,
min_volume,
cost,
initial_volume: v1.initial_volume,
initial_volume_pc: v1.initial_volume_pc,
initial_volume,
};
Ok(n)
}
Expand Down Expand Up @@ -913,7 +948,9 @@ impl TryFrom<AggregatedStorageNodeV1> for AggregatedStorageNode {

#[cfg(test)]
mod tests {
use crate::nodes::core::StorageInitialVolume;
use crate::nodes::InputNode;
use crate::nodes::StorageNode;

#[test]
fn test_input() {
Expand All @@ -929,4 +966,40 @@ mod tests {

assert_eq!(node.meta.name, "supply1");
}

#[test]
fn test_storage_initial_volume_absolute() {
let data = r#"
{
"name": "storage1",
"type": "Storage",
"volume": 15.0,
"initial_volume": {
"Absolute": 12.0
}
}
"#;

let storage: StorageNode = serde_json::from_str(data).unwrap();

assert_eq!(storage.initial_volume, StorageInitialVolume::Absolute(12.0));
}

#[test]
fn test_storage_initial_volume_proportional() {
let data = r#"
{
"name": "storage1",
"type": "Storage",
"volume": 15.0,
"initial_volume": {
"Proportional": 0.5
}
}
"#;

let storage: StorageNode = serde_json::from_str(data).unwrap();

assert_eq!(storage.initial_volume, StorageInitialVolume::Proportional(0.5));
}
}
30 changes: 16 additions & 14 deletions pywr-schema/src/nodes/monthly_virtual_storage.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::data_tables::LoadedTableCollection;
use crate::error::{ConversionError, SchemaError};
use crate::model::PywrMultiNetworkTransfer;
use crate::nodes::core::StorageInitialVolume;
use crate::nodes::{NodeAttribute, NodeMeta};
use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter};
use pywr_core::derived_metric::DerivedMetric;
use pywr_core::metric::Metric;
use pywr_core::models::ModelDomain;
use pywr_core::node::{ConstraintValue, StorageInitialVolume};
use pywr_core::node::ConstraintValue;
use pywr_core::virtual_storage::VirtualStorageReset;
use pywr_v1_schema::nodes::MonthlyVirtualStorageNode as MonthlyVirtualStorageNodeV1;
use std::path::Path;
Expand All @@ -31,8 +32,7 @@ pub struct MonthlyVirtualStorageNode {
pub max_volume: Option<DynamicFloatValue>,
pub min_volume: Option<DynamicFloatValue>,
pub cost: Option<DynamicFloatValue>,
pub initial_volume: Option<f64>,
pub initial_volume_pc: Option<f64>,
pub initial_volume: StorageInitialVolume,
pub reset: NumberOfMonthsReset,
}

Expand All @@ -48,14 +48,6 @@ impl MonthlyVirtualStorageNode {
data_path: Option<&Path>,
inter_network_transfers: &[PywrMultiNetworkTransfer],
) -> Result<(), SchemaError> {
let initial_volume = if let Some(iv) = self.initial_volume {
StorageInitialVolume::Absolute(iv)
} else if let Some(pc) = self.initial_volume_pc {
StorageInitialVolume::Proportional(pc)
} else {
return Err(SchemaError::MissingInitialVolume(self.meta.name.to_string()));
};

let cost = match &self.cost {
Some(v) => v
.load(network, schema, domain, tables, data_path, inter_network_transfers)?
Expand Down Expand Up @@ -93,7 +85,7 @@ impl MonthlyVirtualStorageNode {
None,
node_idxs.as_ref(),
self.factors.as_deref(),
initial_volume,
self.initial_volume.into(),
min_volume,
max_volume,
reset,
Expand Down Expand Up @@ -161,15 +153,25 @@ impl TryFrom<MonthlyVirtualStorageNodeV1> for MonthlyVirtualStorageNode {
.map(|v| v.try_into_v2_parameter(Some(&meta.name), &mut unnamed_count))
.transpose()?;

let initial_volume = if let Some(v) = v1.initial_volume {
StorageInitialVolume::Absolute(v)
} else if let Some(v) = v1.initial_volume_pc {
StorageInitialVolume::Proportional(v)
} else {
return Err(ConversionError::MissingAttribute {
name: meta.name,
attrs: vec!["initial_volume".to_string(), "initial_volume_pc".to_string()],
});
};

let n = Self {
meta,
nodes: v1.nodes,
factors: v1.factors,
max_volume,
min_volume,
cost,
initial_volume: v1.initial_volume,
initial_volume_pc: v1.initial_volume_pc,
initial_volume,
reset: NumberOfMonthsReset { months: v1.months },
};
Ok(n)
Expand Down
Loading

0 comments on commit 5ccc949

Please sign in to comment.