Skip to content
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

Several metric improvements #147

Merged
merged 4 commits into from
Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions pywr-core/src/aggregated_node.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::metric::Metric;
use crate::metric::MetricF64;
use crate::network::Network;
use crate::node::{Constraint, ConstraintValue, FlowConstraints, NodeMeta};
use crate::state::State;
Expand Down Expand Up @@ -60,8 +60,8 @@ impl AggregatedNodeVec {

#[derive(Debug, PartialEq)]
pub enum Factors {
Proportion(Vec<Metric>),
Ratio(Vec<Metric>),
Proportion(Vec<MetricF64>),
Ratio(Vec<MetricF64>),
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -220,14 +220,14 @@ impl AggregatedNode {
}
}

pub fn default_metric(&self) -> Metric {
Metric::AggregatedNodeInFlow(self.index())
pub fn default_metric(&self) -> MetricF64 {
MetricF64::AggregatedNodeInFlow(self.index())
}
}

/// Proportional factors
fn get_norm_proportional_factor_pairs(
factors: &[Metric],
factors: &[MetricF64],
nodes: &[NodeIndex],
model: &Network,
state: &State,
Expand Down Expand Up @@ -265,7 +265,7 @@ fn get_norm_proportional_factor_pairs(

/// Ratio factors
fn get_norm_ratio_factor_pairs(
factors: &[Metric],
factors: &[MetricF64],
nodes: &[NodeIndex],
model: &Network,
state: &State,
Expand Down Expand Up @@ -293,7 +293,7 @@ fn get_norm_ratio_factor_pairs(
#[cfg(test)]
mod tests {
use crate::aggregated_node::Factors;
use crate::metric::Metric;
use crate::metric::MetricF64;
use crate::models::Model;
use crate::network::Network;
use crate::node::ConstraintValue;
Expand Down Expand Up @@ -321,7 +321,7 @@ mod tests {
network.connect_nodes(input_node, link_node1).unwrap();
network.connect_nodes(link_node1, output_node1).unwrap();

let factors = Some(Factors::Ratio(vec![Metric::Constant(2.0), Metric::Constant(1.0)]));
let factors = Some(Factors::Ratio(vec![MetricF64::Constant(2.0), MetricF64::Constant(1.0)]));

let _agg_node = network.add_aggregated_node("agg-node", None, &[link_node0, link_node1], factors);

Expand All @@ -336,13 +336,13 @@ mod tests {
// Set-up assertion for "input" node
let idx = network.get_node_by_name("link", Some("0")).unwrap().index();
let expected = Array2::from_elem((366, 10), 100.0);
let recorder = AssertionRecorder::new("link-0-flow", Metric::NodeOutFlow(idx), expected, None, None);
let recorder = AssertionRecorder::new("link-0-flow", MetricF64::NodeOutFlow(idx), expected, None, None);
network.add_recorder(Box::new(recorder)).unwrap();

// Set-up assertion for "input" node
let idx = network.get_node_by_name("link", Some("1")).unwrap().index();
let expected = Array2::from_elem((366, 10), 50.0);
let recorder = AssertionRecorder::new("link-0-flow", Metric::NodeOutFlow(idx), expected, None, None);
let recorder = AssertionRecorder::new("link-0-flow", MetricF64::NodeOutFlow(idx), expected, None, None);
network.add_recorder(Box::new(recorder)).unwrap();

let model = Model::new(default_time_domain().into(), network);
Expand Down
6 changes: 3 additions & 3 deletions pywr-core/src/aggregated_storage_node.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::metric::Metric;
use crate::metric::MetricF64;
use crate::node::NodeMeta;
use crate::{NodeIndex, PywrError};
use std::ops::{Deref, DerefMut};
Expand Down Expand Up @@ -91,7 +91,7 @@ impl AggregatedStorageNode {
self.nodes.to_vec()
}

pub fn default_metric(&self) -> Vec<Metric> {
self.nodes.iter().map(|n| Metric::NodeOutFlow(*n)).collect()
pub fn default_metric(&self) -> Vec<MetricF64> {
self.nodes.iter().map(|n| MetricF64::NodeOutFlow(*n)).collect()
}
}
29 changes: 29 additions & 0 deletions pywr-core/src/derived_metric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,33 @@ 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::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) => {
network.get_node(idx).map(|n| n.sub_name())
}
Self::AggregatedNodeProportionalVolume(idx) => {
network.get_aggregated_storage_node(idx).map(|n| n.sub_name())
}
Self::VirtualStorageProportionalVolume(idx) => network.get_virtual_storage_node(idx).map(|v| v.sub_name()),
}
}

pub fn attribute(&self) -> &str {
match self {
Self::NodeInFlowDeficit(_) => "in_flow_deficit",
Self::NodeProportionalVolume(_) => "proportional_volume",
Self::AggregatedNodeProportionalVolume(_) => "proportional_volume",
Self::VirtualStorageProportionalVolume(_) => "proportional_volume",
}
}
}
128 changes: 109 additions & 19 deletions pywr-core/src/metric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use crate::parameters::ParameterIndex;
use crate::state::{MultiValue, State};
use crate::virtual_storage::VirtualStorageIndex;
use crate::PywrError;

#[derive(Clone, Debug, PartialEq)]
pub enum Metric {
pub enum MetricF64 {
NodeInFlow(NodeIndex),
NodeOutFlow(NodeIndex),
NodeVolume(NodeIndex),
Expand All @@ -19,6 +20,7 @@ pub enum Metric {
AggregatedNodeVolume(AggregatedStorageNodeIndex),
EdgeFlow(EdgeIndex),
ParameterValue(ParameterIndex<f64>),
IndexParameterValue(ParameterIndex<usize>),
MultiParameterValue((ParameterIndex<MultiValue>, String)),
VirtualStorageVolume(VirtualStorageIndex),
MultiNodeInFlow { indices: Vec<NodeIndex>, name: String },
Expand All @@ -29,21 +31,21 @@ pub enum Metric {
InterNetworkTransfer(MultiNetworkTransferIndex),
}

impl Metric {
impl MetricF64 {
pub fn get_value(&self, model: &Network, state: &State) -> Result<f64, PywrError> {
match self {
Metric::NodeInFlow(idx) => Ok(state.get_network_state().get_node_in_flow(idx)?),
Metric::NodeOutFlow(idx) => Ok(state.get_network_state().get_node_out_flow(idx)?),
Metric::NodeVolume(idx) => Ok(state.get_network_state().get_node_volume(idx)?),
Metric::AggregatedNodeInFlow(idx) => {
MetricF64::NodeInFlow(idx) => Ok(state.get_network_state().get_node_in_flow(idx)?),
MetricF64::NodeOutFlow(idx) => Ok(state.get_network_state().get_node_out_flow(idx)?),
MetricF64::NodeVolume(idx) => Ok(state.get_network_state().get_node_volume(idx)?),
MetricF64::AggregatedNodeInFlow(idx) => {
let node = model.get_aggregated_node(idx)?;
// TODO this could be more efficient with an iterator method? I.e. avoid the `Vec<_>` allocation
node.get_nodes()
.iter()
.map(|idx| state.get_network_state().get_node_in_flow(idx))
.sum::<Result<_, _>>()
}
Metric::AggregatedNodeOutFlow(idx) => {
MetricF64::AggregatedNodeOutFlow(idx) => {
let node = model.get_aggregated_node(idx)?;
// TODO this could be more efficient with an iterator method? I.e. avoid the `Vec<_>` allocation
node.get_nodes()
Expand All @@ -52,50 +54,138 @@ impl Metric {
.sum::<Result<_, _>>()
}

Metric::EdgeFlow(idx) => Ok(state.get_network_state().get_edge_flow(idx)?),
Metric::ParameterValue(idx) => Ok(state.get_parameter_value(*idx)?),
Metric::MultiParameterValue((idx, key)) => Ok(state.get_multi_parameter_value(*idx, key)?),
Metric::VirtualStorageVolume(idx) => Ok(state.get_network_state().get_virtual_storage_volume(idx)?),
Metric::DerivedMetric(idx) => state.get_derived_metric_value(*idx),
Metric::Constant(v) => Ok(*v),
Metric::AggregatedNodeVolume(idx) => {
MetricF64::EdgeFlow(idx) => Ok(state.get_network_state().get_edge_flow(idx)?),
MetricF64::ParameterValue(idx) => Ok(state.get_parameter_value(*idx)?),
MetricF64::IndexParameterValue(idx) => Ok(state.get_parameter_index(*idx)? as f64),
MetricF64::MultiParameterValue((idx, key)) => Ok(state.get_multi_parameter_value(*idx, key)?),
MetricF64::VirtualStorageVolume(idx) => Ok(state.get_network_state().get_virtual_storage_volume(idx)?),
MetricF64::DerivedMetric(idx) => state.get_derived_metric_value(*idx),
MetricF64::Constant(v) => Ok(*v),
MetricF64::AggregatedNodeVolume(idx) => {
let node = model.get_aggregated_storage_node(idx)?;
node.nodes
.iter()
.map(|idx| state.get_network_state().get_node_volume(idx))
.sum::<Result<_, _>>()
}

Metric::MultiNodeInFlow { indices, .. } => {
MetricF64::MultiNodeInFlow { indices, .. } => {
let flow = indices
.iter()
.map(|idx| state.get_network_state().get_node_in_flow(idx))
.sum::<Result<_, _>>()?;
Ok(flow)
}
Metric::MultiNodeOutFlow { indices, .. } => {
MetricF64::MultiNodeOutFlow { indices, .. } => {
let flow = indices
.iter()
.map(|idx| state.get_network_state().get_node_out_flow(idx))
.sum::<Result<_, _>>()?;
Ok(flow)
}
Metric::InterNetworkTransfer(idx) => state.get_inter_network_transfer_value(*idx),
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<Option<&'a str>, 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)]
pub enum IndexMetric {
pub enum MetricUsize {
IndexParameterValue(ParameterIndex<usize>),
Constant(usize),
}

impl IndexMetric {
impl MetricUsize {
pub fn get_value(&self, _network: &Network, state: &State) -> Result<usize, PywrError> {
match self {
Self::IndexParameterValue(idx) => state.get_parameter_index(*idx),
Self::Constant(i) => Ok(*i),
}
}

pub fn name<'a>(&'a self, network: &'a Network) -> Result<&'a str, PywrError> {
match self {
Self::IndexParameterValue(idx) => network.get_index_parameter(idx).map(|p| p.name()),
Self::Constant(_) => Ok(""),
}
}

pub fn sub_name<'a>(&'a self, _network: &'a Network) -> Result<Option<&'a str>, PywrError> {
match self {
Self::IndexParameterValue(_) => Ok(None),
Self::Constant(_) => Ok(None),
}
}

pub fn attribute(&self) -> &str {
match self {
Self::IndexParameterValue(_) => "value",
Self::Constant(_) => "value",
}
}
}
6 changes: 3 additions & 3 deletions pywr-core/src/models/multi.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::metric::Metric;
use crate::metric::MetricF64;
use crate::models::ModelDomain;
use crate::network::{Network, NetworkState, RunTimings};
use crate::scenario::ScenarioIndex;
Expand Down Expand Up @@ -53,7 +53,7 @@ struct MultiNetworkTransfer {
/// The model to get the value from.
from_model_idx: OtherNetworkIndex,
/// The metric to get the value from.
from_metric: Metric,
from_metric: MetricF64,
/// Optional initial value to use on the first time-step
initial_value: Option<f64>,
}
Expand Down Expand Up @@ -130,7 +130,7 @@ impl MultiNetworkModel {
pub fn add_inter_network_transfer(
&mut self,
from_network_idx: usize,
from_metric: Metric,
from_metric: MetricF64,
to_network_idx: usize,
initial_value: Option<f64>,
) {
Expand Down
Loading