From a865900bfcef642dc364b9097be1de26782f447a Mon Sep 17 00:00:00 2001 From: James Batchelor Date: Mon, 11 Mar 2024 12:18:28 +0000 Subject: [PATCH] feat: add pywr-schema-macro crate (#136) This contains a derive macro that implements `parameters` and `parameters_mut` methods for nodes. --- Cargo.toml | 1 + pywr-schema-macros/Cargo.toml | 22 ++ pywr-schema-macros/src/lib.rs | 198 ++++++++++++++++++ pywr-schema/Cargo.toml | 1 + .../src/nodes/annual_virtual_storage.rs | 4 +- pywr-schema/src/nodes/core.rs | 75 +------ pywr-schema/src/nodes/delay.rs | 6 +- pywr-schema/src/nodes/loss_link.rs | 4 +- pywr-schema/src/nodes/mod.rs | 40 +++- .../src/nodes/monthly_virtual_storage.rs | 4 +- pywr-schema/src/nodes/piecewise_link.rs | 4 +- pywr-schema/src/nodes/piecewise_storage.rs | 4 +- pywr-schema/src/nodes/river.rs | 7 +- pywr-schema/src/nodes/river_gauge.rs | 4 +- .../src/nodes/river_split_with_gauge.rs | 4 +- .../src/nodes/rolling_virtual_storage.rs | 4 +- pywr-schema/src/nodes/virtual_storage.rs | 4 +- .../src/nodes/water_treatment_works.rs | 4 +- 18 files changed, 305 insertions(+), 85 deletions(-) create mode 100644 pywr-schema-macros/Cargo.toml create mode 100644 pywr-schema-macros/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index ab234203..da6b96ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "pywr-schema", "pywr-cli", "pywr-python", + "pywr-schema-macros", ] exclude = [ "tests/models/simple-wasm/simple-wasm-parameter" diff --git a/pywr-schema-macros/Cargo.toml b/pywr-schema-macros/Cargo.toml new file mode 100644 index 00000000..f9046ed6 --- /dev/null +++ b/pywr-schema-macros/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "pywr-schema-macros" +version = "2.0.0-dev" +edition = "2021" +rust-version = "1.60" +description = "A generalised water resource allocation model." +readme = "../README.md" +repository = "https://github.com/pywr/pywr-next/" +license = "MIT OR Apache-2.0" +keywords = ["water", "modelling"] +categories = ["science", "simulation"] + +[lib] +name = "pywr_schema_macros" +path = "src/lib.rs" +proc-macro = true + +[dependencies] +syn = "2.0.52" +quote = "1.0.35" + + diff --git a/pywr-schema-macros/src/lib.rs b/pywr-schema-macros/src/lib.rs new file mode 100644 index 00000000..e5de8f38 --- /dev/null +++ b/pywr-schema-macros/src/lib.rs @@ -0,0 +1,198 @@ +use proc_macro::TokenStream; +use quote::quote; + +/// A derive macro for Pywr nodes that implements `parameters` and `parameters_mut` methods. +#[proc_macro_derive(PywrNode)] +pub fn pywr_node_macro(input: TokenStream) -> TokenStream { + // Parse the input tokens into a syntax tree + let input = syn::parse_macro_input!(input as syn::DeriveInput); + impl_parameter_references_derive(&input) +} + +enum PywrField { + Optional(syn::Ident), + Required(syn::Ident), +} + +/// Generates a [`TokenStream`] containing the implementation of two methods, `parameters` +/// and `parameters_mut`, for the given struct. +/// +/// Both method returns a [`HashMap`] of parameter names to [`DynamicFloatValue`]. This +/// is intended to be used for nodes and parameter structs in the Pywr schema. +fn impl_parameter_references_derive(ast: &syn::DeriveInput) -> TokenStream { + // Name of the node type + let name = &ast.ident; + + if let syn::Data::Struct(data) = &ast.data { + // Only apply this to structs + + // Help struct for capturing parameter fields and whether they are optional. + struct ParamField { + field_name: syn::Ident, + optional: bool, + } + + // Iterate through all fields of the struct. Try to find fields that reference + // parameters (e.g. `Option` or `ParameterValue`). + let parameter_fields: Vec = data + .fields + .iter() + .filter_map(|field| { + let field_ident = field.ident.as_ref()?; + // Identify optional fields + match type_to_ident(&field.ty) { + Some(PywrField::Optional(ident)) => { + // If optional and a parameter identifier then add to the list + is_parameter_ident(&ident).then_some(ParamField { + field_name: field_ident.clone(), + optional: true, + }) + } + Some(PywrField::Required(ident)) => { + // Otherwise, if a parameter identifier then add to the list + is_parameter_ident(&ident).then_some(ParamField { + field_name: field_ident.clone(), + optional: false, + }) + } + None => None, // All other fields are ignored. + } + }) + .collect(); + + // Insert statements for non-mutable version + let inserts = parameter_fields + .iter() + .map(|param_field| { + let ident = ¶m_field.field_name; + let key = ident.to_string(); + if param_field.optional { + quote! { + if let Some(p) = &self.#ident { + attributes.insert(#key, p.into()); + } + } + } else { + quote! { + let #ident = &self.#ident; + attributes.insert(#key, #ident.into()); + } + } + }) + .collect::>(); + + // Insert statements for mutable version + let inserts_mut = parameter_fields + .iter() + .map(|param_field| { + let ident = ¶m_field.field_name; + let key = ident.to_string(); + if param_field.optional { + quote! { + if let Some(p) = &mut self.#ident { + attributes.insert(#key, p.into()); + } + } + } else { + quote! { + let #ident = &mut self.#ident; + attributes.insert(#key, #ident.into()); + } + } + }) + .collect::>(); + + // Create the two parameter methods using the insert statements + let expanded = quote! { + impl #name { + pub fn parameters(&self) -> HashMap<&str, &DynamicFloatValue> { + let mut attributes = HashMap::new(); + #( + #inserts + )* + attributes + } + + pub fn parameters_mut(&mut self) -> HashMap<&str, &mut DynamicFloatValue> { + let mut attributes = HashMap::new(); + #( + #inserts_mut + )* + attributes + } + } + }; + + // Hand the output tokens back to the compiler. + TokenStream::from(expanded) + } else { + panic!("Only structs are supported for #[derive(PywrNode)]") + } +} + +/// Returns the last segment of a type path as an identifier +fn type_to_ident(ty: &syn::Type) -> Option { + match ty { + // Match type's that are a path and not a self type. + syn::Type::Path(type_path) if type_path.qself.is_none() => { + // Match on the last segment + match type_path.path.segments.last() { + Some(last_segment) => { + let ident = &last_segment.ident; + + if ident == "Option" { + // The last segment is an Option, now we need to parse the argument + // I.e. the bit in inside the angle brackets. + let first_arg = match &last_segment.arguments { + syn::PathArguments::AngleBracketed(params) => params.args.first(), + _ => None, + }; + + // Find type arguments; ignore others + let arg_ty = match first_arg { + Some(syn::GenericArgument::Type(ty)) => Some(ty), + _ => None, + }; + + // Match on path types that are no self types. + let arg_type_path = match arg_ty { + Some(ty) => match ty { + syn::Type::Path(type_path) if type_path.qself.is_none() => { + Some(type_path) + } + _ => None, + }, + None => None, + }; + + // Get the last segment of the path + let last_segment = match arg_type_path { + Some(type_path) => type_path.path.segments.last(), + None => None, + }; + + // Finally, if there's a last segment return this as an optional `PywrField` + match last_segment { + Some(last_segment) => { + let ident = &last_segment.ident; + Some(PywrField::Optional(ident.clone())) + } + None => None, + } + } else { + // Otherwise, assume this a simple required field + Some(PywrField::Required(ident.clone())) + } + } + None => None, + } + } + _ => None, + } +} + +fn is_parameter_ident(ident: &syn::Ident) -> bool { + // TODO this currenty omits more complex attributes, such as `factors` for AggregatedNode + // and steps for PiecewiseLinks, that can internally contain `DynamicFloatValue` fields + ident == "DynamicFloatValue" +} \ No newline at end of file diff --git a/pywr-schema/Cargo.toml b/pywr-schema/Cargo.toml index 40428a82..aa6a529b 100644 --- a/pywr-schema/Cargo.toml +++ b/pywr-schema/Cargo.toml @@ -33,6 +33,7 @@ thiserror = { workspace = true } pywr-v1-schema = { workspace = true } pywr-core = { path="../pywr-core" } chrono = { workspace = true, features = ["serde"] } +pywr-schema-macros = { path = "../pywr-schema-macros" } [dev-dependencies] tempfile = "3.3.0" diff --git a/pywr-schema/src/nodes/annual_virtual_storage.rs b/pywr-schema/src/nodes/annual_virtual_storage.rs index 00205506..7965af51 100644 --- a/pywr-schema/src/nodes/annual_virtual_storage.rs +++ b/pywr-schema/src/nodes/annual_virtual_storage.rs @@ -9,7 +9,9 @@ use pywr_core::metric::Metric; use pywr_core::models::ModelDomain; use pywr_core::node::ConstraintValue; use pywr_core::virtual_storage::VirtualStorageReset; +use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::AnnualVirtualStorageNode as AnnualVirtualStorageNodeV1; +use std::collections::HashMap; use std::path::Path; #[derive(serde::Deserialize, serde::Serialize, Clone)] @@ -29,7 +31,7 @@ impl Default for AnnualReset { } } -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct AnnualVirtualStorageNode { #[serde(flatten)] pub meta: NodeMeta, diff --git a/pywr-schema/src/nodes/core.rs b/pywr-schema/src/nodes/core.rs index 3f2d3a3f..d58ecf73 100644 --- a/pywr-schema/src/nodes/core.rs +++ b/pywr-schema/src/nodes/core.rs @@ -7,6 +7,7 @@ use pywr_core::derived_metric::DerivedMetric; use pywr_core::metric::Metric; use pywr_core::models::ModelDomain; use pywr_core::node::{ConstraintValue, StorageInitialVolume as CoreStorageInitialVolume}; +use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::{ AggregatedNode as AggregatedNodeV1, AggregatedStorageNode as AggregatedStorageNodeV1, CatchmentNode as CatchmentNodeV1, InputNode as InputNodeV1, LinkNode as LinkNodeV1, OutputNode as OutputNodeV1, @@ -15,7 +16,7 @@ use pywr_v1_schema::nodes::{ use std::collections::HashMap; use std::path::Path; -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct InputNode { #[serde(flatten)] pub meta: NodeMeta, @@ -27,21 +28,6 @@ pub struct InputNode { impl InputNode { pub const DEFAULT_ATTRIBUTE: NodeAttribute = NodeAttribute::Outflow; - pub fn parameters(&self) -> HashMap<&str, &DynamicFloatValue> { - let mut attributes = HashMap::new(); - if let Some(p) = &self.max_flow { - attributes.insert("max_flow", p); - } - if let Some(p) = &self.min_flow { - attributes.insert("min_flow", p); - } - if let Some(p) = &self.cost { - attributes.insert("cost", p); - } - - attributes - } - pub fn add_to_model(&self, network: &mut pywr_core::network::Network) -> Result<(), SchemaError> { network.add_input_node(self.meta.name.as_str(), None)?; Ok(()) @@ -137,7 +123,7 @@ impl TryFrom for InputNode { } } -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct LinkNode { #[serde(flatten)] pub meta: NodeMeta, @@ -149,21 +135,6 @@ pub struct LinkNode { impl LinkNode { const DEFAULT_ATTRIBUTE: NodeAttribute = NodeAttribute::Outflow; - pub fn parameters(&self) -> HashMap<&str, &DynamicFloatValue> { - let mut attributes = HashMap::new(); - if let Some(p) = &self.max_flow { - attributes.insert("max_flow", p); - } - if let Some(p) = &self.min_flow { - attributes.insert("min_flow", p); - } - if let Some(p) = &self.cost { - attributes.insert("cost", p); - } - - attributes - } - pub fn add_to_model(&self, network: &mut pywr_core::network::Network) -> Result<(), SchemaError> { network.add_link_node(self.meta.name.as_str(), None)?; Ok(()) @@ -259,7 +230,7 @@ impl TryFrom for LinkNode { } } -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct OutputNode { #[serde(flatten)] pub meta: NodeMeta, @@ -271,21 +242,6 @@ pub struct OutputNode { impl OutputNode { const DEFAULT_ATTRIBUTE: NodeAttribute = NodeAttribute::Inflow; - pub fn parameters(&self) -> HashMap<&str, &DynamicFloatValue> { - let mut attributes = HashMap::new(); - if let Some(p) = &self.max_flow { - attributes.insert("max_flow", p); - } - if let Some(p) = &self.min_flow { - attributes.insert("min_flow", p); - } - if let Some(p) = &self.cost { - attributes.insert("cost", p); - } - - attributes - } - pub fn add_to_model(&self, network: &mut pywr_core::network::Network) -> Result<(), SchemaError> { network.add_output_node(self.meta.name.as_str(), None)?; Ok(()) @@ -407,7 +363,7 @@ impl From for CoreStorageInitialVolume { } } -#[derive(serde::Deserialize, serde::Serialize, Clone, Default, Debug)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, Debug, PywrNode)] pub struct StorageNode { #[serde(flatten)] pub meta: NodeMeta, @@ -420,21 +376,6 @@ pub struct StorageNode { impl StorageNode { const DEFAULT_ATTRIBUTE: NodeAttribute = NodeAttribute::Volume; - pub fn parameters(&self) -> HashMap<&str, &DynamicFloatValue> { - let mut attributes = HashMap::new(); - // if let Some(p) = &self.max_volume { - // attributes.insert("max_volume", p); - // } - // if let Some(p) = &self.min_volume { - // attributes.insert("min_volume", p); - // } - if let Some(p) = &self.cost { - attributes.insert("cost", p); - } - - attributes - } - pub fn add_to_model( &self, network: &mut pywr_core::network::Network, @@ -635,7 +576,7 @@ impl TryFrom for StorageNode { /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct CatchmentNode { #[serde(flatten)] pub meta: NodeMeta, @@ -735,7 +676,7 @@ pub enum Factors { Ratio { factors: Vec }, } -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct AggregatedNode { #[serde(flatten)] pub meta: NodeMeta, @@ -877,7 +818,7 @@ impl TryFrom for AggregatedNode { } } -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct AggregatedStorageNode { #[serde(flatten)] pub meta: NodeMeta, diff --git a/pywr-schema/src/nodes/delay.rs b/pywr-schema/src/nodes/delay.rs index 78335ed3..e2d578e7 100644 --- a/pywr-schema/src/nodes/delay.rs +++ b/pywr-schema/src/nodes/delay.rs @@ -1,9 +1,11 @@ use crate::data_tables::LoadedTableCollection; use crate::error::{ConversionError, SchemaError}; use crate::nodes::{NodeAttribute, NodeMeta}; -use crate::parameters::ConstantValue; +use crate::parameters::{ConstantValue, DynamicFloatValue}; use pywr_core::metric::Metric; +use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::DelayNode as DelayNodeV1; +use std::collections::HashMap; #[doc = svgbobdoc::transform!( /// This node is used to introduce a delay between flows entering and leaving the node. @@ -24,7 +26,7 @@ use pywr_v1_schema::nodes::DelayNode as DelayNodeV1; /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct DelayNode { #[serde(flatten)] pub meta: NodeMeta, diff --git a/pywr-schema/src/nodes/loss_link.rs b/pywr-schema/src/nodes/loss_link.rs index f9280f87..095e9920 100644 --- a/pywr-schema/src/nodes/loss_link.rs +++ b/pywr-schema/src/nodes/loss_link.rs @@ -5,7 +5,9 @@ use crate::nodes::{NodeAttribute, NodeMeta}; use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; use pywr_core::metric::Metric; use pywr_core::models::ModelDomain; +use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::LossLinkNode as LossLinkNodeV1; +use std::collections::HashMap; use std::path::Path; #[doc = svgbobdoc::transform!( @@ -25,7 +27,7 @@ use std::path::Path; /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct LossLinkNode { #[serde(flatten)] pub meta: NodeMeta, diff --git a/pywr-schema/src/nodes/mod.rs b/pywr-schema/src/nodes/mod.rs index f55a712b..6776cdd1 100644 --- a/pywr-schema/src/nodes/mod.rs +++ b/pywr-schema/src/nodes/mod.rs @@ -291,7 +291,45 @@ impl Node { Node::Link(n) => n.parameters(), Node::Output(n) => n.parameters(), Node::Storage(n) => n.parameters(), - _ => HashMap::new(), // TODO complete + Node::Catchment(n) => n.parameters(), + Node::RiverGauge(n) => n.parameters(), + Node::LossLink(n) => n.parameters(), + Node::River(n) => n.parameters(), + Node::RiverSplitWithGauge(n) => n.parameters(), + Node::WaterTreatmentWorks(n) => n.parameters(), + Node::Aggregated(n) => n.parameters(), + Node::AggregatedStorage(n) => n.parameters(), + Node::VirtualStorage(n) => n.parameters(), + Node::AnnualVirtualStorage(n) => n.parameters(), + Node::PiecewiseLink(n) => n.parameters(), + Node::PiecewiseStorage(n) => n.parameters(), + Node::Delay(n) => n.parameters(), + Node::MonthlyVirtualStorage(n) => n.parameters(), + Node::RollingVirtualStorage(n) => n.parameters(), + } + } + + pub fn parameters_mut(&mut self) -> HashMap<&str, &mut DynamicFloatValue> { + match self { + Node::Input(n) => n.parameters_mut(), + Node::Link(n) => n.parameters_mut(), + Node::Output(n) => n.parameters_mut(), + Node::Storage(n) => n.parameters_mut(), + Node::Catchment(n) => n.parameters_mut(), + Node::RiverGauge(n) => n.parameters_mut(), + Node::LossLink(n) => n.parameters_mut(), + Node::River(n) => n.parameters_mut(), + Node::RiverSplitWithGauge(n) => n.parameters_mut(), + Node::WaterTreatmentWorks(n) => n.parameters_mut(), + Node::Aggregated(n) => n.parameters_mut(), + Node::AggregatedStorage(n) => n.parameters_mut(), + Node::VirtualStorage(n) => n.parameters_mut(), + Node::AnnualVirtualStorage(n) => n.parameters_mut(), + Node::PiecewiseLink(n) => n.parameters_mut(), + Node::PiecewiseStorage(n) => n.parameters_mut(), + Node::Delay(n) => n.parameters_mut(), + Node::MonthlyVirtualStorage(n) => n.parameters_mut(), + Node::RollingVirtualStorage(n) => n.parameters_mut(), } } diff --git a/pywr-schema/src/nodes/monthly_virtual_storage.rs b/pywr-schema/src/nodes/monthly_virtual_storage.rs index 8673c003..a58a34df 100644 --- a/pywr-schema/src/nodes/monthly_virtual_storage.rs +++ b/pywr-schema/src/nodes/monthly_virtual_storage.rs @@ -9,7 +9,9 @@ use pywr_core::metric::Metric; use pywr_core::models::ModelDomain; use pywr_core::node::ConstraintValue; use pywr_core::virtual_storage::VirtualStorageReset; +use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::MonthlyVirtualStorageNode as MonthlyVirtualStorageNodeV1; +use std::collections::HashMap; use std::path::Path; #[derive(serde::Deserialize, serde::Serialize, Clone)] @@ -23,7 +25,7 @@ impl Default for NumberOfMonthsReset { } } -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct MonthlyVirtualStorageNode { #[serde(flatten)] pub meta: NodeMeta, diff --git a/pywr-schema/src/nodes/piecewise_link.rs b/pywr-schema/src/nodes/piecewise_link.rs index 02587224..13e42b89 100644 --- a/pywr-schema/src/nodes/piecewise_link.rs +++ b/pywr-schema/src/nodes/piecewise_link.rs @@ -5,7 +5,9 @@ use crate::nodes::{NodeAttribute, NodeMeta}; use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; use pywr_core::metric::Metric; use pywr_core::models::ModelDomain; +use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::PiecewiseLinkNode as PiecewiseLinkNodeV1; +use std::collections::HashMap; use std::path::Path; #[derive(serde::Deserialize, serde::Serialize, Clone)] @@ -37,7 +39,7 @@ pub struct PiecewiseLinkStep { /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct PiecewiseLinkNode { #[serde(flatten)] pub meta: NodeMeta, diff --git a/pywr-schema/src/nodes/piecewise_storage.rs b/pywr-schema/src/nodes/piecewise_storage.rs index f57b3d8c..2b059d98 100644 --- a/pywr-schema/src/nodes/piecewise_storage.rs +++ b/pywr-schema/src/nodes/piecewise_storage.rs @@ -8,6 +8,8 @@ use pywr_core::metric::Metric; use pywr_core::models::ModelDomain; use pywr_core::node::{ConstraintValue, StorageInitialVolume}; use pywr_core::parameters::VolumeBetweenControlCurvesParameter; +use pywr_schema_macros::PywrNode; +use std::collections::HashMap; use std::path::Path; #[derive(serde::Deserialize, serde::Serialize, Clone)] @@ -42,7 +44,7 @@ pub struct PiecewiseStore { /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct PiecewiseStorageNode { #[serde(flatten)] pub meta: NodeMeta, diff --git a/pywr-schema/src/nodes/river.rs b/pywr-schema/src/nodes/river.rs index 55587875..db6f4a28 100644 --- a/pywr-schema/src/nodes/river.rs +++ b/pywr-schema/src/nodes/river.rs @@ -2,10 +2,11 @@ use crate::error::{ConversionError, SchemaError}; use crate::nodes::{NodeAttribute, NodeMeta}; use crate::parameters::DynamicFloatValue; use pywr_core::metric::Metric; +use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::LinkNode as LinkNodeV1; use std::collections::HashMap; -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct RiverNode { #[serde(flatten)] pub meta: NodeMeta, @@ -14,10 +15,6 @@ pub struct RiverNode { impl RiverNode { const DEFAULT_ATTRIBUTE: NodeAttribute = NodeAttribute::Outflow; - pub fn parameters(&self) -> HashMap<&str, &DynamicFloatValue> { - HashMap::new() - } - pub fn add_to_model(&self, network: &mut pywr_core::network::Network) -> Result<(), SchemaError> { network.add_link_node(self.meta.name.as_str(), None)?; Ok(()) diff --git a/pywr-schema/src/nodes/river_gauge.rs b/pywr-schema/src/nodes/river_gauge.rs index 58921b52..d820aa39 100644 --- a/pywr-schema/src/nodes/river_gauge.rs +++ b/pywr-schema/src/nodes/river_gauge.rs @@ -5,7 +5,9 @@ use crate::nodes::{NodeAttribute, NodeMeta}; use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; use pywr_core::metric::Metric; use pywr_core::models::ModelDomain; +use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::RiverGaugeNode as RiverGaugeNodeV1; +use std::collections::HashMap; use std::path::Path; #[doc = svgbobdoc::transform!( @@ -23,7 +25,7 @@ use std::path::Path; /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct RiverGaugeNode { #[serde(flatten)] pub meta: NodeMeta, diff --git a/pywr-schema/src/nodes/river_split_with_gauge.rs b/pywr-schema/src/nodes/river_split_with_gauge.rs index 8195f8a6..22656123 100644 --- a/pywr-schema/src/nodes/river_split_with_gauge.rs +++ b/pywr-schema/src/nodes/river_split_with_gauge.rs @@ -7,7 +7,9 @@ use pywr_core::aggregated_node::Factors; use pywr_core::metric::Metric; use pywr_core::models::ModelDomain; use pywr_core::node::NodeIndex; +use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::RiverSplitWithGaugeNode as RiverSplitWithGaugeNodeV1; +use std::collections::HashMap; use std::path::Path; #[doc = svgbobdoc::transform!( @@ -32,7 +34,7 @@ use std::path::Path; /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct RiverSplitWithGaugeNode { #[serde(flatten)] pub meta: NodeMeta, diff --git a/pywr-schema/src/nodes/rolling_virtual_storage.rs b/pywr-schema/src/nodes/rolling_virtual_storage.rs index 7665f847..6a17b8dd 100644 --- a/pywr-schema/src/nodes/rolling_virtual_storage.rs +++ b/pywr-schema/src/nodes/rolling_virtual_storage.rs @@ -9,7 +9,9 @@ use pywr_core::models::ModelDomain; use pywr_core::node::{ConstraintValue, StorageInitialVolume}; use pywr_core::timestep::TimeDomain; use pywr_core::virtual_storage::VirtualStorageReset; +use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::RollingVirtualStorageNode as RollingVirtualStorageNodeV1; +use std::collections::HashMap; use std::num::NonZeroUsize; use std::path::Path; @@ -61,7 +63,7 @@ impl RollingWindow { /// The rolling virtual storage node is useful for representing rolling licences. For example, a 30-day or 90-day /// licence on a water abstraction. /// -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct RollingVirtualStorageNode { #[serde(flatten)] pub meta: NodeMeta, diff --git a/pywr-schema/src/nodes/virtual_storage.rs b/pywr-schema/src/nodes/virtual_storage.rs index 2c247117..75f7dbb3 100644 --- a/pywr-schema/src/nodes/virtual_storage.rs +++ b/pywr-schema/src/nodes/virtual_storage.rs @@ -9,10 +9,12 @@ use pywr_core::metric::Metric; use pywr_core::models::ModelDomain; use pywr_core::node::ConstraintValue; use pywr_core::virtual_storage::VirtualStorageReset; +use pywr_schema_macros::PywrNode; use pywr_v1_schema::nodes::VirtualStorageNode as VirtualStorageNodeV1; +use std::collections::HashMap; use std::path::Path; -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct VirtualStorageNode { #[serde(flatten)] pub meta: NodeMeta, diff --git a/pywr-schema/src/nodes/water_treatment_works.rs b/pywr-schema/src/nodes/water_treatment_works.rs index 3d2b65eb..bc9920fc 100644 --- a/pywr-schema/src/nodes/water_treatment_works.rs +++ b/pywr-schema/src/nodes/water_treatment_works.rs @@ -7,6 +7,8 @@ use num::Zero; use pywr_core::aggregated_node::Factors; use pywr_core::metric::Metric; use pywr_core::models::ModelDomain; +use pywr_schema_macros::PywrNode; +use std::collections::HashMap; use std::path::Path; #[doc = svgbobdoc::transform!( @@ -36,7 +38,7 @@ use std::path::Path; /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default, PywrNode)] pub struct WaterTreatmentWorks { /// Node metadata #[serde(flatten)]