Skip to content

Commit

Permalink
Use new MetricF64 in turbine components
Browse files Browse the repository at this point in the history
  • Loading branch information
s-simoncelli committed Apr 11, 2024
1 parent 9552b86 commit 525b00e
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 74 deletions.
11 changes: 7 additions & 4 deletions pywr-core/src/derived_metric.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::aggregated_storage_node::AggregatedStorageNodeIndex;
use crate::metric::Metric;
use crate::metric::MetricF64;
use crate::network::Network;
use crate::node::NodeIndex;
use crate::state::State;
Expand Down Expand Up @@ -42,7 +42,7 @@ pub struct TurbineData {
// The turbine relative efficiency (0-1)
pub efficiency: f64,
// The water elevation above the turbine
pub water_elevation: Option<Metric>,
pub water_elevation: Option<MetricF64>,
// The water density
pub water_density: f64,
/// A factor used to transform the units of flow to be compatible with the hydropower equation
Expand Down Expand Up @@ -137,15 +137,17 @@ impl DerivedMetric {

pub fn name<'a>(&self, network: &'a Network) -> Result<&'a str, PywrError> {
match self {
Self::NodeInFlowDeficit(idx) | Self::NodeProportionalVolume(idx) => network.get_node(idx).map(|n| n.name()),
Self::NodeInFlowDeficit(idx) | Self::NodeProportionalVolume(idx) | Self::PowerFromNodeFlow(idx, _) => {
network.get_node(idx).map(|n| n.name())
}
Self::AggregatedNodeProportionalVolume(idx) => network.get_aggregated_storage_node(idx).map(|n| n.name()),
Self::VirtualStorageProportionalVolume(idx) => network.get_virtual_storage_node(idx).map(|v| v.name()),
}
}

pub fn sub_name<'a>(&self, network: &'a Network) -> Result<Option<&'a str>, PywrError> {
match self {
Self::NodeInFlowDeficit(idx) | Self::NodeProportionalVolume(idx) => {
Self::NodeInFlowDeficit(idx) | Self::NodeProportionalVolume(idx) | Self::PowerFromNodeFlow(idx, _) => {
network.get_node(idx).map(|n| n.sub_name())
}
Self::AggregatedNodeProportionalVolume(idx) => {
Expand All @@ -161,6 +163,7 @@ impl DerivedMetric {
Self::NodeProportionalVolume(_) => "proportional_volume",
Self::AggregatedNodeProportionalVolume(_) => "proportional_volume",
Self::VirtualStorageProportionalVolume(_) => "proportional_volume",
Self::PowerFromNodeFlow(_, _) => "power_from_flow",
}
}
}
24 changes: 10 additions & 14 deletions pywr-core/src/parameters/hydropower.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
use crate::metric::Metric;
use crate::metric::MetricF64;
use crate::network::Network;
use crate::parameters::{Parameter, ParameterMeta};
use crate::scenario::ScenarioIndex;
use crate::state::{ParameterState, State};
use crate::timestep::Timestep;
use crate::utils::inverse_hydropower_calculation;
use crate::PywrError;
use std::any::Any;

pub struct HydropowerTargetData {
pub target: Metric,
pub target: MetricF64,
pub elevation: Option<f64>,
pub min_head: Option<f64>,
pub max_flow: Option<Metric>,
pub min_flow: Option<Metric>,
pub max_flow: Option<MetricF64>,
pub min_flow: Option<MetricF64>,
pub efficiency: Option<f64>,
pub water_elevation: Option<Metric>,
pub water_elevation: Option<MetricF64>,
pub water_density: Option<f64>,
pub flow_unit_conversion: Option<f64>,
pub energy_unit_conversion: Option<f64>,
}

pub struct HydropowerTargetParameter {
pub meta: ParameterMeta,
pub target: Metric,
pub max_flow: Option<Metric>,
pub min_flow: Option<Metric>,
pub target: MetricF64,
pub max_flow: Option<MetricF64>,
pub min_flow: Option<MetricF64>,
pub turbine_min_head: f64,
pub turbine_elevation: f64,
pub turbine_efficiency: f64,
pub water_elevation: Option<Metric>,
pub water_elevation: Option<MetricF64>,
pub water_density: f64,
pub flow_unit_conversion: f64,
pub energy_unit_conversion: f64,
Expand All @@ -53,10 +52,7 @@ impl HydropowerTargetParameter {
}
}

impl Parameter for HydropowerTargetParameter {
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
impl Parameter<f64> for HydropowerTargetParameter {
fn meta(&self) -> &ParameterMeta {
&self.meta
}
Expand Down
66 changes: 32 additions & 34 deletions pywr-schema/src/nodes/turbine.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
use crate::data_tables::LoadedTableCollection;
use crate::model::PywrMultiNetworkTransfer;
use crate::model::LoadArgs;
use crate::nodes::{NodeAttribute, NodeMeta};
use crate::parameters::DynamicFloatValue;
use crate::SchemaError;
use pywr_core::derived_metric::{DerivedMetric, TurbineData};
use pywr_core::metric::Metric;
use pywr_core::models::ModelDomain;
use pywr_core::metric::MetricF64;
use pywr_core::parameters::HydropowerTargetData;
use pywr_schema_macros::PywrNode;
use std::collections::HashMap;
use std::path::Path;

enum TargetType {
#[derive(serde::Deserialize, serde::Serialize, Clone, Debug)]
pub enum TargetType {
// set flow derived from the hydropower target as a max_flow
MaxFlow,
// set flow derived from the hydropower target as a min_flow
Expand All @@ -21,7 +20,7 @@ enum TargetType {

/// This turbine node can be used to set a flow constraint based on a hydropower production target.
/// The turbine elevation, minimum head and efficiency can also be configured.
#[derive(serde::Deserialize, serde::Serialize, Clone)]
#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PywrNode)]
pub struct TurbineNode {
#[serde(flatten)]
pub meta: NodeMeta,
Expand All @@ -30,8 +29,8 @@ pub struct TurbineNode {
/// calculated using the hydropower equation. If omitted no flow restriction is set.
/// Units should be in units of energy per day.
pub target: Option<DynamicFloatValue>,
// This can be used to define where to apply the flow calculated from the hydropower production
// target using the inverse hydropower equation. Default to [`TargetType::MaxFlow`])
/// This can be used to define where to apply the flow calculated from the hydropower production
/// target using the inverse hydropower equation. Default to [`TargetType::MaxFlow`])
pub target_type: TargetType,
/// The elevation of water entering the turbine. The difference of this value with the
/// `turbine_elevation` gives the working head of the turbine. This is optional
Expand Down Expand Up @@ -59,6 +58,8 @@ pub struct TurbineNode {
impl Default for TurbineNode {
fn default() -> Self {
Self {
meta: Default::default(),
cost: None,
target: None,
target_type: TargetType::MaxFlow,
water_elevation: None,
Expand All @@ -68,54 +69,50 @@ impl Default for TurbineNode {
water_density: 1000.0,
flow_unit_conversion: 1.0,
energy_unit_conversion: 1e-6,
..Default::default()
}
}
}

impl TurbineNode {
const DEFAULT_ATTRIBUTE: NodeAttribute = NodeAttribute::Outflow;

pub fn parameters(&self) -> HashMap<&str, &DynamicFloatValue> {
let mut attributes = HashMap::new();
if let Some(p) = &self.cost {
attributes.insert("cost", p);
}

attributes
}
// pub fn parameters(&self) -> HashMap<&str, &DynamicFloatValue> {
// let mut attributes = HashMap::new();
// if let Some(p) = &self.cost {
// attributes.insert("cost", p);
// }
//
// attributes
// }

fn sub_name() -> Option<&'static str> {
Some("turbine")
}

pub fn add_to_model(&self, network: &mut pywr_core::network::Network) -> Result<(), SchemaError> {
pub fn add_to_model(&self, network: &mut pywr_core::network::Network, _args: &LoadArgs) -> Result<(), SchemaError> {
network.add_link_node(self.meta.name.as_str(), None)?;
Ok(())
}

pub fn set_constraints(
&self,
network: &mut pywr_core::network::Network,
schema: &crate::model::PywrNetwork,
domain: &ModelDomain,
tables: &LoadedTableCollection,
data_path: Option<&Path>,
inter_network_transfers: &[PywrMultiNetworkTransfer],
args: &LoadArgs,
) -> Result<(), SchemaError> {
if let Some(cost) = &self.cost {
let value = cost.load(network, schema, domain, tables, data_path, inter_network_transfers)?;
let value = cost.load(network, args)?;
network.set_node_cost(self.meta.name.as_str(), None, value.into())?;
}

if let Some(target) = &self.target {
// TODO: address parameter name. See https://github.com/pywr/pywr-next/issues/107#issuecomment-1980957962
let name = format!("{}-power", self.meta.name.as_str());
let target_value = target.load(network, schema, domain, tables, data_path, inter_network_transfers)?;
let target_value = target.load(network, args)?;

let water_elevation = self
.water_elevation
.map(|t| t.load(network, schema, domain, tables, data_path, inter_network_transfers))
.as_ref()
.map(|t| t.load(network, args))
.transpose()?;
let turbine_data = HydropowerTargetData {
target: target_value,
Expand All @@ -131,7 +128,7 @@ impl TurbineNode {
};
let p = pywr_core::parameters::HydropowerTargetParameter::new(&name, turbine_data);
let power_idx = network.add_parameter(Box::new(p))?;
let metric = Metric::ParameterValue(power_idx);
let metric = MetricF64::ParameterValue(power_idx);

match self.target_type {
TargetType::MaxFlow => {
Expand Down Expand Up @@ -161,20 +158,21 @@ impl TurbineNode {
&self,
network: &mut pywr_core::network::Network,
attribute: Option<NodeAttribute>,
) -> Result<Metric, SchemaError> {
args: &LoadArgs,
) -> Result<MetricF64, SchemaError> {
// Use the default attribute if none is specified
let attr = attribute.unwrap_or(Self::DEFAULT_ATTRIBUTE);

let idx = network.get_node_index_by_name(self.meta.name.as_str(), None)?;

let metric = match attr {
NodeAttribute::Outflow => Metric::NodeOutFlow(idx),
NodeAttribute::Inflow => Metric::NodeInFlow(idx),
NodeAttribute::Outflow => MetricF64::NodeOutFlow(idx),
NodeAttribute::Inflow => MetricF64::NodeInFlow(idx),
NodeAttribute::Power => {
// TODO
let water_elevation = self
.water_elevation
.map(|t| t.load(network, schema, domain, tables, data_path, inter_network_transfers))
.as_ref()
.map(|t| t.load(network, args))
.transpose()?;

let turbine_data = TurbineData {
Expand All @@ -187,7 +185,7 @@ impl TurbineNode {
};
let dm = DerivedMetric::PowerFromNodeFlow(idx, turbine_data);
let dm_idx = network.add_derived_metric(dm);
Metric::DerivedMetric(dm_idx)
MetricF64::DerivedMetric(dm_idx)
}
_ => {
return Err(SchemaError::NodeAttributeNotSupported {
Expand Down
30 changes: 8 additions & 22 deletions pywr-schema/src/parameters/hydropower.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
use crate::data_tables::LoadedTableCollection;
use crate::model::PywrMultiNetworkTransfer;
use crate::model::LoadArgs;
use crate::parameters::{
DynamicFloatValue, DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter,
};
use crate::{ConversionError, SchemaError};
use pywr_core::models::ModelDomain;
use pywr_core::parameters::{HydropowerTargetData, ParameterIndex};
use pywr_v1_schema::parameters::HydropowerTargetParameter as HydropowerTargetParameterV1;
use std::collections::HashMap;
use std::path::Path;

/// A parameter that returns flow from a hydropower generation target.
///
Expand Down Expand Up @@ -87,27 +84,16 @@ impl HydropowerTargetParameter {
pub fn add_to_model(
&self,
network: &mut pywr_core::network::Network,
schema: &crate::model::PywrNetwork,
domain: &ModelDomain,
tables: &LoadedTableCollection,
data_path: Option<&Path>,
inter_network_transfers: &[PywrMultiNetworkTransfer],
) -> Result<ParameterIndex, SchemaError> {
let target = self
.target
.load(network, schema, domain, tables, data_path, inter_network_transfers)?;
args: &LoadArgs,
) -> Result<ParameterIndex<f64>, SchemaError> {
let target = self.target.load(network, args)?;
let water_elevation = self
.water_elevation
.map(|t| t.load(network, schema, domain, tables, data_path, inter_network_transfers))
.transpose()?;
let max_flow = self
.max_flow
.map(|t| t.load(network, schema, domain, tables, data_path, inter_network_transfers))
.transpose()?;
let min_flow = self
.min_flow
.map(|t| t.load(network, schema, domain, tables, data_path, inter_network_transfers))
.as_ref()
.map(|t| t.load(network, args))
.transpose()?;
let max_flow = self.max_flow.as_ref().map(|t| t.load(network, args)).transpose()?;
let min_flow = self.min_flow.as_ref().map(|t| t.load(network, args)).transpose()?;

let turbine_data = HydropowerTargetData {
target,
Expand Down

0 comments on commit 525b00e

Please sign in to comment.