Skip to content

Commit

Permalink
feat(core): Add methods for name, sub_name and attribute to Metrics.
Browse files Browse the repository at this point in the history
Move some replicated code from the CSV and HDF recorders
to the metric definition. This removes a lot of TODOs and
unimplemented metrics for both outputs.
  • Loading branch information
jetuk committed Mar 21, 2024
1 parent 4972a91 commit 4d618df
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 130 deletions.
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",
}
}
}
86 changes: 86 additions & 0 deletions pywr-core/src/metric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::parameters::ParameterIndex;
use crate::state::{MultiValue, State};
use crate::virtual_storage::VirtualStorageIndex;
use crate::PywrError;

#[derive(Clone, Debug, PartialEq)]
pub enum MetricF64 {
NodeInFlow(NodeIndex),
Expand Down Expand Up @@ -83,6 +84,70 @@ 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::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::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::MultiParameterValue(_) => "value",
Self::VirtualStorageVolume(_) => "volume",
Self::MultiNodeInFlow { .. } => "inflow",
Self::MultiNodeOutFlow { .. } => "outflow",
Self::Constant(_) => "value",
Self::DerivedMetric(_) => "value",
Self::InterNetworkTransfer(_) => "value",
}
}
}

#[derive(Clone, Debug, PartialEq)]
Expand All @@ -98,4 +163,25 @@ impl MetricUsize {
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",
}
}
}
29 changes: 28 additions & 1 deletion pywr-core/src/network.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::aggregated_node::{AggregatedNode, AggregatedNodeIndex, AggregatedNodeVec, Factors};
use crate::aggregated_storage_node::{AggregatedStorageNode, AggregatedStorageNodeIndex, AggregatedStorageNodeVec};
use crate::derived_metric::{DerivedMetric, DerivedMetricIndex};
use crate::edge::{EdgeIndex, EdgeVec};
use crate::edge::{Edge, EdgeIndex, EdgeVec};
use crate::metric::MetricF64;
use crate::models::ModelDomain;
use crate::node::{ConstraintValue, Node, NodeVec, StorageInitialVolume};
Expand Down Expand Up @@ -794,6 +794,11 @@ impl Network {
Ok(())
}

/// Get an [`Edge`] from an edge's index
pub fn get_edge(&self, index: &EdgeIndex) -> Result<&Edge, PywrError> {
self.edges.get(index)
}

/// Get a Node from a node's name
pub fn get_node_index_by_name(&self, name: &str, sub_name: Option<&str>) -> Result<NodeIndex, PywrError> {
Ok(self.get_node_by_name(name, sub_name)?.index())
Expand Down Expand Up @@ -1123,6 +1128,17 @@ impl Network {
}
}

/// Get a [`Parameter<usize>`] from its index.
pub fn get_index_parameter(
&self,
index: &ParameterIndex<usize>,
) -> Result<&dyn parameters::Parameter<usize>, PywrError> {
match self.index_parameters.get(*index.deref()) {
Some(p) => Ok(p.as_ref()),
None => Err(PywrError::IndexParameterIndexNotFound(*index)),
}
}

/// Get a `IndexParameter` from a parameter's name
pub fn get_index_parameter_by_name(&self, name: &str) -> Result<&dyn parameters::Parameter<usize>, PywrError> {
match self.index_parameters.iter().find(|p| p.name() == name) {
Expand All @@ -1139,6 +1155,17 @@ impl Network {
}
}

/// Get a `MultiValueParameterIndex` from a parameter's name
pub fn get_multi_valued_parameter(
&self,
index: &ParameterIndex<MultiValue>,
) -> Result<&dyn parameters::Parameter<MultiValue>, PywrError> {
match self.multi_parameters.get(*index.deref()) {
Some(p) => Ok(p.as_ref()),
None => Err(PywrError::MultiValueParameterIndexNotFound(*index)),
}
}

/// Get a `MultiValueParameterIndex` from a parameter's name
pub fn get_multi_valued_parameter_index_by_name(
&self,
Expand Down
75 changes: 5 additions & 70 deletions pywr-core/src/recorders/csv.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use super::{MetricSetState, PywrError, Recorder, RecorderMeta, Timestep};
use crate::metric::MetricF64;
use crate::models::ModelDomain;
use crate::network::Network;
use crate::recorders::metric_set::MetricSetIndex;
Expand Down Expand Up @@ -46,75 +45,11 @@ impl Recorder for CSVRecorder {
let metric_set = network.get_metric_set(self.metric_set_idx)?;

for metric in metric_set.iter_metrics() {
let (name, sub_name, attribute) = match metric {
MetricF64::NodeInFlow(idx) => {
let node = network.get_node(idx)?;
let (name, sub_name) = node.full_name();
let sub_name = sub_name.map_or("".to_string(), |sn| sn.to_string());

(name.to_string(), sub_name, "inflow".to_string())
}
MetricF64::NodeOutFlow(idx) => {
let node = network.get_node(idx)?;
let (name, sub_name) = node.full_name();
let sub_name = sub_name.map_or("".to_string(), |sn| sn.to_string());

(name.to_string(), sub_name, "outflow".to_string())
}
MetricF64::NodeVolume(idx) => {
let node = network.get_node(idx)?;
let (name, sub_name) = node.full_name();
let sub_name = sub_name.map_or("".to_string(), |sn| sn.to_string());

(name.to_string(), sub_name, "volume".to_string())
}
MetricF64::DerivedMetric(_idx) => {
todo!("Derived metrics are not yet supported in CSV recorders");
}
MetricF64::AggregatedNodeVolume(idx) => {
let node = network.get_aggregated_storage_node(idx)?;
let (name, sub_name) = node.full_name();
let sub_name = sub_name.map_or("".to_string(), |sn| sn.to_string());

(name.to_string(), sub_name, "volume".to_string())
}
MetricF64::EdgeFlow(_) => {
continue; // TODO
}
MetricF64::ParameterValue(idx) => {
let parameter = network.get_parameter(idx)?;
let name = parameter.name();
(name.to_string(), "".to_string(), "parameter".to_string())
}
MetricF64::VirtualStorageVolume(_) => {
continue; // TODO
}
MetricF64::Constant(_) => {
continue; // TODO
}
MetricF64::MultiParameterValue(_) => {
continue; // TODO
}
MetricF64::AggregatedNodeInFlow(idx) => {
let node = network.get_aggregated_node(idx)?;
let (name, sub_name) = node.full_name();
let sub_name = sub_name.map_or("".to_string(), |sn| sn.to_string());

(name.to_string(), sub_name, "inflow".to_string())
}
MetricF64::AggregatedNodeOutFlow(idx) => {
let node = network.get_aggregated_node(idx)?;
let (name, sub_name) = node.full_name();
let sub_name = sub_name.map_or("".to_string(), |sn| sn.to_string());

(name.to_string(), sub_name, "outflow".to_string())
}
MetricF64::MultiNodeInFlow { name, .. } => (name.to_string(), "".to_string(), "inflow".to_string()),
MetricF64::MultiNodeOutFlow { name, .. } => (name.to_string(), "".to_string(), "outflow".to_string()),
MetricF64::InterNetworkTransfer(_) => {
continue; // TODO
}
};
let name = metric.name(network)?.to_string();
let sub_name = metric
.sub_name(network)?
.map_or_else(|| "".to_string(), |s| s.to_string());
let attribute = metric.attribute().to_string();

// Add entries for each scenario
names.push(name);
Expand Down
63 changes: 7 additions & 56 deletions pywr-core/src/recorders/hdf.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use super::{MetricSetState, PywrError, Recorder, RecorderMeta, Timestep};
use crate::metric::MetricF64;
use crate::models::ModelDomain;
use crate::network::Network;
use crate::recorders::MetricSetIndex;
Expand Down Expand Up @@ -65,7 +64,7 @@ impl Recorder for HDF5Recorder {
fn meta(&self) -> &RecorderMeta {
&self.meta
}
fn setup(&self, domain: &ModelDomain, model: &Network) -> Result<Option<Box<(dyn Any)>>, PywrError> {
fn setup(&self, domain: &ModelDomain, network: &Network) -> Result<Option<Box<(dyn Any)>>, PywrError> {
let file = match hdf5::File::create(&self.filename) {
Ok(f) => f,
Err(e) => return Err(PywrError::HDF5Error(e.to_string())),
Expand All @@ -82,62 +81,14 @@ impl Recorder for HDF5Recorder {

let root_grp = file.deref();

let metric_set = model.get_metric_set(self.metric_set_idx)?;
let metric_set = network.get_metric_set(self.metric_set_idx)?;

for metric in metric_set.iter_metrics() {
let ds = match metric {
MetricF64::NodeInFlow(idx) => {
let node = model.get_node(idx)?;
require_node_dataset(root_grp, shape, node.name(), node.sub_name(), "inflow")?
}
MetricF64::NodeOutFlow(idx) => {
let node = model.get_node(idx)?;
require_node_dataset(root_grp, shape, node.name(), node.sub_name(), "outflow")?
}
MetricF64::NodeVolume(idx) => {
let node = model.get_node(idx)?;
require_node_dataset(root_grp, shape, node.name(), node.sub_name(), "volume")?
}
MetricF64::DerivedMetric(_idx) => {
todo!("Derived metrics are not yet supported in HDF recorders");
}
MetricF64::AggregatedNodeVolume(idx) => {
let node = model.get_aggregated_storage_node(idx)?;
require_node_dataset(root_grp, shape, node.name(), node.sub_name(), "volume")?
}
MetricF64::EdgeFlow(_) => {
continue; // TODO
}
MetricF64::ParameterValue(idx) => {
let parameter = model.get_parameter(idx)?;
let parameter_group = require_group(root_grp, "parameters")?;
require_dataset(&parameter_group, shape, parameter.name())?
}
MetricF64::VirtualStorageVolume(_) => {
continue; // TODO
}
MetricF64::Constant(_) => {
continue; // TODO
}
MetricF64::MultiParameterValue(_) => {
continue; // TODO
}
MetricF64::AggregatedNodeInFlow(idx) => {
let node = model.get_aggregated_node(idx)?;
require_node_dataset(root_grp, shape, node.name(), node.sub_name(), "inflow")?
}
MetricF64::AggregatedNodeOutFlow(idx) => {
let node = model.get_aggregated_node(idx)?;
require_node_dataset(root_grp, shape, node.name(), node.sub_name(), "outflow")?
}
MetricF64::MultiNodeInFlow { name, .. } => require_node_dataset(root_grp, shape, name, None, "inflow")?,
MetricF64::MultiNodeOutFlow { name, .. } => {
require_node_dataset(root_grp, shape, name, None, "outflow")?
}
MetricF64::InterNetworkTransfer(_) => {
continue; // TODO
}
};
let name = metric.name(network)?;
let sub_name = metric.sub_name(network)?;
let attribute = metric.attribute();

let ds = require_node_dataset(root_grp, shape, name, sub_name, attribute)?;

datasets.push(ds);
}
Expand Down
4 changes: 1 addition & 3 deletions pywr-core/src/recorders/metric_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ use crate::scenario::ScenarioIndex;
use crate::state::State;
use crate::timestep::Timestep;
use crate::PywrError;
use core::f64;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use std::slice::Iter;

#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
pub struct MetricSetIndex(usize);
Expand Down Expand Up @@ -69,7 +67,7 @@ impl MetricSet {
pub fn name(&self) -> &str {
&self.name
}
pub fn iter_metrics(&self) -> Iter<'_, MetricF64> {
pub fn iter_metrics(&self) -> impl Iterator<Item = &MetricF64> + '_ {
self.metrics.iter()
}

Expand Down

0 comments on commit 4d618df

Please sign in to comment.