diff --git a/LICENSE b/LICENSE deleted file mode 100644 index c08fc322..00000000 --- a/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2021 James Tomlinson Associates Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/pywr-cli/Cargo.toml b/pywr-cli/Cargo.toml index 9bf0aaf0..7aad221e 100644 --- a/pywr-cli/Cargo.toml +++ b/pywr-cli/Cargo.toml @@ -2,6 +2,13 @@ name = "pywr-cli" version = "0.1.0" 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"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/pywr-core/Cargo.toml b/pywr-core/Cargo.toml index 8ecaf1a6..00b15761 100644 --- a/pywr-core/Cargo.toml +++ b/pywr-core/Cargo.toml @@ -5,10 +5,9 @@ authors = ["James Tomlinson "] edition = "2021" rust-version = "1.60" description = "A generalised water resource allocation model." -readme = "README.md" +readme = "../README.md" repository = "https://github.com/pywr/pywr-next/" license = "MIT OR Apache-2.0" -license-file = "LICENSE" keywords = ["water", "modelling"] categories = ["science", "simulation"] diff --git a/pywr-python/Cargo.toml b/pywr-python/Cargo.toml index 419a6d99..47617bc7 100644 --- a/pywr-python/Cargo.toml +++ b/pywr-python/Cargo.toml @@ -2,6 +2,14 @@ name = "pywr-python" version = "0.1.0" 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", "python"] +categories = ["science", "simulation"] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/pywr-schema/Cargo.toml b/pywr-schema/Cargo.toml index f1e28f75..a8857ce3 100644 --- a/pywr-schema/Cargo.toml +++ b/pywr-schema/Cargo.toml @@ -5,10 +5,9 @@ authors = ["James Tomlinson "] edition = "2021" rust-version = "1.60" description = "A generalised water resource allocation model." -readme = "README.md" +readme = "../README.md" repository = "https://github.com/pywr/pywr-next/" license = "MIT OR Apache-2.0" -license-file = "LICENSE" keywords = ["water", "modelling"] categories = ["science", "simulation"] @@ -18,6 +17,8 @@ categories = ["science", "simulation"] svgbobdoc = { version = "0.3.0", features = ["enable"] } polars = { version = "0.33.2", features = ["lazy", "rows", "ndarray"] } pyo3-polars = "0.7.0" +strum = "0.25" +strum_macros = "0.25" hdf5 = { workspace = true } csv = { workspace = true } diff --git a/pywr-schema/src/error.rs b/pywr-schema/src/error.rs index 096b5ab6..6fdbb88c 100644 --- a/pywr-schema/src/error.rs +++ b/pywr-schema/src/error.rs @@ -69,6 +69,8 @@ pub enum ConversionError { UnexpectedAttribute { attrs: Vec, name: String }, #[error("Can not convert a float constant to an index constant.")] FloatToIndex, - #[error("Attribute {attr:?} is not allowed on node {name:?}.")] + #[error("Attribute {attr:?} on node {name:?} is not allowed .")] ExtraNodeAttribute { attr: String, name: String }, + #[error("Custom node of type {ty:?} on node {name:?} is not supported .")] + CustomNodeNotSupported { ty: String, name: String }, } diff --git a/pywr-schema/src/nodes/annual_virtual_storage.rs b/pywr-schema/src/nodes/annual_virtual_storage.rs index 359cf5ac..74440255 100644 --- a/pywr-schema/src/nodes/annual_virtual_storage.rs +++ b/pywr-schema/src/nodes/annual_virtual_storage.rs @@ -16,7 +16,17 @@ pub struct AnnualReset { pub use_initial_volume: bool, } -#[derive(serde::Deserialize, serde::Serialize, Clone)] +impl Default for AnnualReset { + fn default() -> Self { + Self { + day: 1, + month: time::Month::January, + use_initial_volume: false, + } + } +} + +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] 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 49bd5bfd..2ad405d0 100644 --- a/pywr-schema/src/nodes/core.rs +++ b/pywr-schema/src/nodes/core.rs @@ -13,7 +13,7 @@ use pywr_v1_schema::nodes::{ use std::collections::HashMap; use std::path::Path; -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] pub struct InputNode { #[serde(flatten)] pub meta: NodeMeta, @@ -112,7 +112,7 @@ impl TryFrom for InputNode { } } -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] pub struct LinkNode { #[serde(flatten)] pub meta: NodeMeta, @@ -210,7 +210,7 @@ impl TryFrom for LinkNode { } } -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] pub struct OutputNode { #[serde(flatten)] pub meta: NodeMeta, @@ -309,7 +309,7 @@ impl TryFrom for OutputNode { } } -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] pub struct StorageNode { #[serde(flatten)] pub meta: NodeMeta, @@ -489,7 +489,7 @@ impl TryFrom for StorageNode { /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] pub struct CatchmentNode { #[serde(flatten)] pub meta: NodeMeta, @@ -566,7 +566,7 @@ pub enum Factors { Ratio { factors: Vec }, } -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] pub struct AggregatedNode { #[serde(flatten)] pub meta: NodeMeta, @@ -684,7 +684,7 @@ impl TryFrom for AggregatedNode { } } -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] 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 e0151ef8..b49e2433 100644 --- a/pywr-schema/src/nodes/delay.rs +++ b/pywr-schema/src/nodes/delay.rs @@ -24,7 +24,7 @@ use pywr_v1_schema::nodes::DelayNode as DelayNodeV1; /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] 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 da95b83a..1ae7354b 100644 --- a/pywr-schema/src/nodes/loss_link.rs +++ b/pywr-schema/src/nodes/loss_link.rs @@ -24,7 +24,7 @@ use std::path::Path; /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] 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 9d96449f..1706cc19 100644 --- a/pywr-schema/src/nodes/mod.rs +++ b/pywr-schema/src/nodes/mod.rs @@ -13,12 +13,14 @@ mod water_treatment_works; use crate::data_tables::LoadedTableCollection; use crate::error::{ConversionError, SchemaError}; +use crate::model::PywrNetwork; pub use crate::nodes::core::{ AggregatedNode, AggregatedStorageNode, CatchmentNode, InputNode, LinkNode, OutputNode, StorageNode, }; pub use crate::nodes::delay::DelayNode; pub use crate::nodes::river::RiverNode; use crate::parameters::DynamicFloatValue; +use crate::PywrModel; pub use annual_virtual_storage::AnnualVirtualStorageNode; pub use loss_link::LossLinkNode; pub use monthly_virtual_storage::MonthlyVirtualStorageNode; @@ -27,14 +29,13 @@ pub use piecewise_storage::PiecewiseStorageNode; use pywr_core::metric::Metric; use pywr_core::models::ModelDomain; use pywr_v1_schema::nodes::{ - CoreNode as CoreNodeV1, CustomNode as CustomNodeV1, Node as NodeV1, NodeMeta as NodeMetaV1, - NodePosition as NodePositionV1, + CoreNode as CoreNodeV1, Node as NodeV1, NodeMeta as NodeMetaV1, NodePosition as NodePositionV1, }; pub use river_gauge::RiverGaugeNode; pub use river_split_with_gauge::RiverSplitWithGaugeNode; -use serde_json::Value; use std::collections::HashMap; use std::path::Path; +use strum_macros::{Display, EnumString, EnumVariantNames, IntoStaticStr}; pub use virtual_storage::VirtualStorageNode; pub use water_treatment_works::WaterTreatmentWorks; @@ -55,7 +56,7 @@ impl From for NodePosition { } } -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Default)] pub struct NodeMeta { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -74,29 +75,158 @@ impl From for NodeMeta { } } -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -pub struct CustomNode { - #[serde(rename = "type")] - pub ty: String, - #[serde(flatten)] - pub meta: NodeMeta, - #[serde(flatten)] - pub attributes: HashMap, +#[derive(Display, IntoStaticStr, EnumString, EnumVariantNames, Copy, Clone)] +pub enum NodeType { + Input, + Link, + Output, + Storage, + Catchment, + RiverGauge, + LossLink, + Delay, + PiecewiseLink, + PiecewiseStorage, + River, + RiverSplitWithGauge, + WaterTreatmentWorks, + Aggregated, + AggregatedStorage, + VirtualStorage, + AnnualVirtualStorage, + MonthlyVirtualStorage, } -impl From for CustomNode { - fn from(v1: CustomNodeV1) -> Self { +pub struct NodeBuilder { + ty: NodeType, + position: Option, + name: Option, +} + +/// A builder for creating a new node. +impl NodeBuilder { + pub fn new(ty: NodeType) -> Self { Self { - ty: v1.ty, - meta: v1.meta.into(), - attributes: v1.attributes, + ty, + position: None, + name: None, + } + } + + /// Define the position of the node. + pub fn position(mut self, position: NodePosition) -> Self { + self.position = Some(position); + self + } + + /// Define the name of the node. + pub fn name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + /// Create the next default name without duplicating an existing name in the model. + pub fn next_default_name_for_model(mut self, network: &PywrNetwork) -> Self { + let mut num = 1; + loop { + let name = format!("{}-{}", self.ty.to_string(), num); + if network.get_node_by_name(&name).is_none() { + // No node with this name found! + self.name = Some(name); + break; + } else { + num += 1; + } + } + self + } + + /// Build the [`Node`]. + pub fn build(self) -> Node { + let name = self.name.unwrap_or_else(|| self.ty.to_string()); + let meta = NodeMeta { + name, + position: self.position, + ..Default::default() + }; + + match self.ty { + NodeType::Input => Node::Input(InputNode { + meta, + ..Default::default() + }), + NodeType::Link => Node::Link(LinkNode { + meta, + ..Default::default() + }), + NodeType::Output => Node::Output(OutputNode { + meta, + ..Default::default() + }), + NodeType::Storage => Node::Storage(StorageNode { + meta, + ..Default::default() + }), + NodeType::Catchment => Node::Catchment(CatchmentNode { + meta, + ..Default::default() + }), + NodeType::RiverGauge => Node::RiverGauge(RiverGaugeNode { + meta, + ..Default::default() + }), + NodeType::LossLink => Node::LossLink(LossLinkNode { + meta, + ..Default::default() + }), + NodeType::Delay => Node::Delay(DelayNode { + meta, + ..Default::default() + }), + NodeType::PiecewiseLink => Node::PiecewiseLink(PiecewiseLinkNode { + meta, + ..Default::default() + }), + NodeType::PiecewiseStorage => Node::PiecewiseStorage(PiecewiseStorageNode { + meta, + ..Default::default() + }), + NodeType::River => Node::River(RiverNode { meta }), + NodeType::RiverSplitWithGauge => Node::RiverSplitWithGauge(RiverSplitWithGaugeNode { + meta, + ..Default::default() + }), + NodeType::WaterTreatmentWorks => Node::WaterTreatmentWorks(WaterTreatmentWorks { + meta, + ..Default::default() + }), + NodeType::Aggregated => Node::Aggregated(AggregatedNode { + meta, + ..Default::default() + }), + NodeType::AggregatedStorage => Node::AggregatedStorage(AggregatedStorageNode { + meta, + ..Default::default() + }), + NodeType::VirtualStorage => Node::VirtualStorage(VirtualStorageNode { + meta, + ..Default::default() + }), + NodeType::AnnualVirtualStorage => Node::AnnualVirtualStorage(AnnualVirtualStorageNode { + meta, + ..Default::default() + }), + NodeType::MonthlyVirtualStorage => Node::MonthlyVirtualStorage(MonthlyVirtualStorageNode { + meta, + ..Default::default() + }), } } } #[derive(serde::Deserialize, serde::Serialize, Clone)] #[serde(tag = "type")] -pub enum CoreNode { +pub enum Node { Input(InputNode), Link(LinkNode), Output(OutputNode), @@ -117,7 +247,7 @@ pub enum CoreNode { MonthlyVirtualStorage(MonthlyVirtualStorageNode), } -impl CoreNode { +impl Node { pub fn name(&self) -> &str { self.meta().name.as_str() } @@ -126,58 +256,58 @@ impl CoreNode { self.meta().position.as_ref() } - pub fn node_type(&self) -> &str { + pub fn node_type(&self) -> NodeType { match self { - CoreNode::Input(_) => "Input", - CoreNode::Link(_) => "Link", - CoreNode::Output(_) => "Output", - CoreNode::Storage(_) => "Storage", - CoreNode::Catchment(_) => "Catchment", - CoreNode::RiverGauge(_) => "RiverGauge", - CoreNode::LossLink(_) => "LossLink", - CoreNode::River(_) => "River", - CoreNode::RiverSplitWithGauge(_) => "River", - CoreNode::WaterTreatmentWorks(_) => "WaterTreatmentWorks", - CoreNode::Aggregated(_) => "Aggregated", - CoreNode::AggregatedStorage(_) => "AggregatedStorage", - CoreNode::VirtualStorage(_) => "VirtualStorage", - CoreNode::AnnualVirtualStorage(_) => "AnnualVirtualStorage", - CoreNode::PiecewiseLink(_) => "PiecewiseLink", - CoreNode::PiecewiseStorage(_) => "PiecewiseStorage", - CoreNode::Delay(_) => "Delay", - CoreNode::MonthlyVirtualStorage(_) => "MonthlyVirtualStorage", + Node::Input(_) => NodeType::Input, + Node::Link(_) => NodeType::Link, + Node::Output(_) => NodeType::Output, + Node::Storage(_) => NodeType::Storage, + Node::Catchment(_) => NodeType::Catchment, + Node::RiverGauge(_) => NodeType::RiverGauge, + Node::LossLink(_) => NodeType::LossLink, + Node::River(_) => NodeType::River, + Node::RiverSplitWithGauge(_) => NodeType::RiverSplitWithGauge, + Node::WaterTreatmentWorks(_) => NodeType::WaterTreatmentWorks, + Node::Aggregated(_) => NodeType::Aggregated, + Node::AggregatedStorage(_) => NodeType::AggregatedStorage, + Node::VirtualStorage(_) => NodeType::VirtualStorage, + Node::AnnualVirtualStorage(_) => NodeType::AnnualVirtualStorage, + Node::PiecewiseLink(_) => NodeType::PiecewiseLink, + Node::PiecewiseStorage(_) => NodeType::PiecewiseStorage, + Node::Delay(_) => NodeType::Delay, + Node::MonthlyVirtualStorage(_) => NodeType::MonthlyVirtualStorage, } } pub fn meta(&self) -> &NodeMeta { match self { - CoreNode::Input(n) => &n.meta, - CoreNode::Link(n) => &n.meta, - CoreNode::Output(n) => &n.meta, - CoreNode::Storage(n) => &n.meta, - CoreNode::Catchment(n) => &n.meta, - CoreNode::RiverGauge(n) => &n.meta, - CoreNode::LossLink(n) => &n.meta, - CoreNode::River(n) => &n.meta, - CoreNode::RiverSplitWithGauge(n) => &n.meta, - CoreNode::WaterTreatmentWorks(n) => &n.meta, - CoreNode::Aggregated(n) => &n.meta, - CoreNode::AggregatedStorage(n) => &n.meta, - CoreNode::VirtualStorage(n) => &n.meta, - CoreNode::AnnualVirtualStorage(n) => &n.meta, - CoreNode::PiecewiseLink(n) => &n.meta, - CoreNode::PiecewiseStorage(n) => &n.meta, - CoreNode::Delay(n) => &n.meta, - CoreNode::MonthlyVirtualStorage(n) => &n.meta, + Node::Input(n) => &n.meta, + Node::Link(n) => &n.meta, + Node::Output(n) => &n.meta, + Node::Storage(n) => &n.meta, + Node::Catchment(n) => &n.meta, + Node::RiverGauge(n) => &n.meta, + Node::LossLink(n) => &n.meta, + Node::River(n) => &n.meta, + Node::RiverSplitWithGauge(n) => &n.meta, + Node::WaterTreatmentWorks(n) => &n.meta, + Node::Aggregated(n) => &n.meta, + Node::AggregatedStorage(n) => &n.meta, + Node::VirtualStorage(n) => &n.meta, + Node::AnnualVirtualStorage(n) => &n.meta, + Node::PiecewiseLink(n) => &n.meta, + Node::PiecewiseStorage(n) => &n.meta, + Node::Delay(n) => &n.meta, + Node::MonthlyVirtualStorage(n) => &n.meta, } } pub fn parameters(&self) -> HashMap<&str, &DynamicFloatValue> { match self { - CoreNode::Input(n) => n.parameters(), - CoreNode::Link(n) => n.parameters(), - CoreNode::Output(n) => n.parameters(), - CoreNode::Storage(n) => n.parameters(), + Node::Input(n) => n.parameters(), + Node::Link(n) => n.parameters(), + Node::Output(n) => n.parameters(), + Node::Storage(n) => n.parameters(), _ => HashMap::new(), // TODO complete } } @@ -190,24 +320,24 @@ impl CoreNode { data_path: Option<&Path>, ) -> Result<(), SchemaError> { match self { - CoreNode::Input(n) => n.add_to_model(network), - CoreNode::Link(n) => n.add_to_model(network), - CoreNode::Output(n) => n.add_to_model(network), - CoreNode::Storage(n) => n.add_to_model(network, domain, tables, data_path), - CoreNode::Catchment(n) => n.add_to_model(network), - CoreNode::RiverGauge(n) => n.add_to_model(network), - CoreNode::LossLink(n) => n.add_to_model(network), - CoreNode::River(n) => n.add_to_model(network), - CoreNode::RiverSplitWithGauge(n) => n.add_to_model(network), - CoreNode::WaterTreatmentWorks(n) => n.add_to_model(network), - CoreNode::Aggregated(n) => n.add_to_model(network), - CoreNode::AggregatedStorage(n) => n.add_to_model(network), - CoreNode::VirtualStorage(n) => n.add_to_model(network, domain, tables, data_path), - CoreNode::AnnualVirtualStorage(n) => n.add_to_model(network, domain, tables, data_path), - CoreNode::PiecewiseLink(n) => n.add_to_model(network), - CoreNode::PiecewiseStorage(n) => n.add_to_model(network, domain, tables, data_path), - CoreNode::Delay(n) => n.add_to_model(network), - CoreNode::MonthlyVirtualStorage(n) => n.add_to_model(network, domain, tables, data_path), + Node::Input(n) => n.add_to_model(network), + Node::Link(n) => n.add_to_model(network), + Node::Output(n) => n.add_to_model(network), + Node::Storage(n) => n.add_to_model(network, domain, tables, data_path), + Node::Catchment(n) => n.add_to_model(network), + Node::RiverGauge(n) => n.add_to_model(network), + Node::LossLink(n) => n.add_to_model(network), + Node::River(n) => n.add_to_model(network), + Node::RiverSplitWithGauge(n) => n.add_to_model(network), + Node::WaterTreatmentWorks(n) => n.add_to_model(network), + Node::Aggregated(n) => n.add_to_model(network), + Node::AggregatedStorage(n) => n.add_to_model(network), + Node::VirtualStorage(n) => n.add_to_model(network, domain, tables, data_path), + Node::AnnualVirtualStorage(n) => n.add_to_model(network, domain, tables, data_path), + Node::PiecewiseLink(n) => n.add_to_model(network), + Node::PiecewiseStorage(n) => n.add_to_model(network, domain, tables, data_path), + Node::Delay(n) => n.add_to_model(network), + Node::MonthlyVirtualStorage(n) => n.add_to_model(network, domain, tables, data_path), } } @@ -219,181 +349,96 @@ impl CoreNode { data_path: Option<&Path>, ) -> Result<(), SchemaError> { match self { - CoreNode::Input(n) => n.set_constraints(network, domain, tables, data_path), - CoreNode::Link(n) => n.set_constraints(network, domain, tables, data_path), - CoreNode::Output(n) => n.set_constraints(network, domain, tables, data_path), - CoreNode::Storage(n) => n.set_constraints(network, domain, tables, data_path), - CoreNode::Catchment(n) => n.set_constraints(network, domain, tables, data_path), - CoreNode::RiverGauge(n) => n.set_constraints(network, domain, tables, data_path), - CoreNode::LossLink(n) => n.set_constraints(network, domain, tables, data_path), - CoreNode::River(_) => Ok(()), // No constraints on river node - CoreNode::RiverSplitWithGauge(n) => n.set_constraints(network, domain, tables, data_path), - CoreNode::WaterTreatmentWorks(n) => n.set_constraints(network, domain, tables, data_path), - CoreNode::Aggregated(n) => n.set_constraints(network, domain, tables, data_path), - CoreNode::AggregatedStorage(_) => Ok(()), // No constraints on aggregated storage nodes. - CoreNode::VirtualStorage(_) => Ok(()), // TODO - CoreNode::AnnualVirtualStorage(_) => Ok(()), // TODO - CoreNode::PiecewiseLink(n) => n.set_constraints(network, domain, tables, data_path), - CoreNode::PiecewiseStorage(n) => n.set_constraints(network, domain, tables, data_path), - CoreNode::Delay(n) => n.set_constraints(network, tables), - CoreNode::MonthlyVirtualStorage(_) => Ok(()), // TODO + Node::Input(n) => n.set_constraints(network, domain, tables, data_path), + Node::Link(n) => n.set_constraints(network, domain, tables, data_path), + Node::Output(n) => n.set_constraints(network, domain, tables, data_path), + Node::Storage(n) => n.set_constraints(network, domain, tables, data_path), + Node::Catchment(n) => n.set_constraints(network, domain, tables, data_path), + Node::RiverGauge(n) => n.set_constraints(network, domain, tables, data_path), + Node::LossLink(n) => n.set_constraints(network, domain, tables, data_path), + Node::River(_) => Ok(()), // No constraints on river node + Node::RiverSplitWithGauge(n) => n.set_constraints(network, domain, tables, data_path), + Node::WaterTreatmentWorks(n) => n.set_constraints(network, domain, tables, data_path), + Node::Aggregated(n) => n.set_constraints(network, domain, tables, data_path), + Node::AggregatedStorage(_) => Ok(()), // No constraints on aggregated storage nodes. + Node::VirtualStorage(_) => Ok(()), // TODO + Node::AnnualVirtualStorage(_) => Ok(()), // TODO + Node::PiecewiseLink(n) => n.set_constraints(network, domain, tables, data_path), + Node::PiecewiseStorage(n) => n.set_constraints(network, domain, tables, data_path), + Node::Delay(n) => n.set_constraints(network, tables), + Node::MonthlyVirtualStorage(_) => Ok(()), // TODO } } pub fn input_connectors(&self) -> Vec<(&str, Option)> { match self { - CoreNode::Input(n) => n.input_connectors(), - CoreNode::Link(n) => n.input_connectors(), - CoreNode::Output(n) => n.input_connectors(), - CoreNode::Storage(n) => n.input_connectors(), - CoreNode::Catchment(n) => n.input_connectors(), - CoreNode::RiverGauge(n) => n.input_connectors(), - CoreNode::LossLink(n) => n.input_connectors(), - CoreNode::River(n) => n.input_connectors(), - CoreNode::RiverSplitWithGauge(n) => n.input_connectors(), - CoreNode::WaterTreatmentWorks(n) => n.input_connectors(), + Node::Input(n) => n.input_connectors(), + Node::Link(n) => n.input_connectors(), + Node::Output(n) => n.input_connectors(), + Node::Storage(n) => n.input_connectors(), + Node::Catchment(n) => n.input_connectors(), + Node::RiverGauge(n) => n.input_connectors(), + Node::LossLink(n) => n.input_connectors(), + Node::River(n) => n.input_connectors(), + Node::RiverSplitWithGauge(n) => n.input_connectors(), + Node::WaterTreatmentWorks(n) => n.input_connectors(), // TODO input_connectors should not exist for these aggregated & virtual nodes - CoreNode::Aggregated(n) => n.input_connectors(), - CoreNode::AggregatedStorage(n) => n.input_connectors(), - CoreNode::VirtualStorage(n) => n.input_connectors(), - CoreNode::AnnualVirtualStorage(n) => n.input_connectors(), - CoreNode::MonthlyVirtualStorage(n) => n.input_connectors(), - CoreNode::PiecewiseLink(n) => n.input_connectors(), - CoreNode::PiecewiseStorage(n) => n.input_connectors(), - CoreNode::Delay(n) => n.input_connectors(), + Node::Aggregated(n) => n.input_connectors(), + Node::AggregatedStorage(n) => n.input_connectors(), + Node::VirtualStorage(n) => n.input_connectors(), + Node::AnnualVirtualStorage(n) => n.input_connectors(), + Node::MonthlyVirtualStorage(n) => n.input_connectors(), + Node::PiecewiseLink(n) => n.input_connectors(), + Node::PiecewiseStorage(n) => n.input_connectors(), + Node::Delay(n) => n.input_connectors(), } } pub fn output_connectors(&self, slot: Option<&str>) -> Vec<(&str, Option)> { match self { - CoreNode::Input(n) => n.output_connectors(), - CoreNode::Link(n) => n.output_connectors(), - CoreNode::Output(n) => n.output_connectors(), - CoreNode::Storage(n) => n.output_connectors(), - CoreNode::Catchment(n) => n.output_connectors(), - CoreNode::RiverGauge(n) => n.output_connectors(), - CoreNode::LossLink(n) => n.output_connectors(), - CoreNode::River(n) => n.output_connectors(), - CoreNode::RiverSplitWithGauge(n) => n.output_connectors(slot), - CoreNode::WaterTreatmentWorks(n) => n.output_connectors(), + Node::Input(n) => n.output_connectors(), + Node::Link(n) => n.output_connectors(), + Node::Output(n) => n.output_connectors(), + Node::Storage(n) => n.output_connectors(), + Node::Catchment(n) => n.output_connectors(), + Node::RiverGauge(n) => n.output_connectors(), + Node::LossLink(n) => n.output_connectors(), + Node::River(n) => n.output_connectors(), + Node::RiverSplitWithGauge(n) => n.output_connectors(slot), + Node::WaterTreatmentWorks(n) => n.output_connectors(), // TODO output_connectors should not exist for these aggregated & virtual nodes - CoreNode::Aggregated(n) => n.output_connectors(), - CoreNode::AggregatedStorage(n) => n.output_connectors(), - CoreNode::VirtualStorage(n) => n.output_connectors(), - CoreNode::AnnualVirtualStorage(n) => n.output_connectors(), - CoreNode::MonthlyVirtualStorage(n) => n.output_connectors(), - CoreNode::PiecewiseLink(n) => n.output_connectors(), - CoreNode::PiecewiseStorage(n) => n.output_connectors(), - CoreNode::Delay(n) => n.output_connectors(), - } - } - - /// Returns the default metric for this node. - pub fn default_metric(&self, network: &pywr_core::network::Network) -> Result { - match self { - CoreNode::Input(n) => n.default_metric(network), - CoreNode::Link(n) => n.default_metric(network), - CoreNode::Output(n) => n.default_metric(network), - CoreNode::Storage(n) => n.default_metric(network), - CoreNode::Catchment(n) => n.default_metric(network), - CoreNode::RiverGauge(n) => n.default_metric(network), - CoreNode::LossLink(n) => n.default_metric(network), - CoreNode::River(n) => n.default_metric(network), - CoreNode::RiverSplitWithGauge(n) => n.default_metric(network), - CoreNode::WaterTreatmentWorks(n) => n.default_metric(network), - CoreNode::Aggregated(n) => n.default_metric(network), - CoreNode::AggregatedStorage(n) => n.default_metric(network), - CoreNode::VirtualStorage(n) => n.default_metric(network), - CoreNode::AnnualVirtualStorage(n) => n.default_metric(network), - CoreNode::MonthlyVirtualStorage(n) => n.default_metric(network), - CoreNode::PiecewiseLink(n) => n.default_metric(network), - CoreNode::Delay(n) => n.default_metric(network), - CoreNode::PiecewiseStorage(n) => n.default_metric(network), - } - } -} - -#[derive(serde::Deserialize, serde::Serialize, Clone)] -#[serde(untagged)] -pub enum Node { - Core(CoreNode), - Custom(CustomNode), -} - -impl Node { - pub fn name(&self) -> &str { - match self { - Node::Core(n) => n.name(), - Node::Custom(n) => n.meta.name.as_str(), - } - } - - pub fn position(&self) -> Option<&NodePosition> { - match self { - Node::Core(n) => n.position(), - Node::Custom(n) => n.meta.position.as_ref(), - } - } - - pub fn node_type(&self) -> &str { - match self { - Node::Core(n) => n.node_type(), - Node::Custom(n) => n.ty.as_str(), - } - } - - pub fn parameters(&self) -> HashMap<&str, &DynamicFloatValue> { - match self { - Node::Core(n) => n.parameters(), - Node::Custom(_) => HashMap::new(), - } - } - - pub fn add_to_model( - &self, - network: &mut pywr_core::network::Network, - domain: &ModelDomain, - tables: &LoadedTableCollection, - data_path: Option<&Path>, - ) -> Result<(), SchemaError> { - match self { - Node::Core(n) => n.add_to_model(network, domain, tables, data_path), - Node::Custom(n) => panic!("TODO custom nodes not yet supported: {}", n.meta.name.as_str()), - } - } - - pub fn set_constraints( - &self, - network: &mut pywr_core::network::Network, - domain: &ModelDomain, - tables: &LoadedTableCollection, - data_path: Option<&Path>, - ) -> Result<(), SchemaError> { - match self { - Node::Core(n) => n.set_constraints(network, domain, tables, data_path), - Node::Custom(n) => panic!("TODO custom nodes not yet supported: {}", n.meta.name.as_str()), - } - } - - pub fn input_connectors(&self) -> Vec<(&str, Option)> { - match self { - Node::Core(n) => n.input_connectors(), - Node::Custom(n) => panic!("TODO custom nodes not yet supported: {}", n.meta.name.as_str()), - } - } - - pub fn output_connectors(&self, slot: Option<&str>) -> Vec<(&str, Option)> { - match self { - Node::Core(n) => n.output_connectors(slot), - Node::Custom(n) => panic!("TODO custom nodes not yet supported: {}", n.meta.name.as_str()), + Node::Aggregated(n) => n.output_connectors(), + Node::AggregatedStorage(n) => n.output_connectors(), + Node::VirtualStorage(n) => n.output_connectors(), + Node::AnnualVirtualStorage(n) => n.output_connectors(), + Node::MonthlyVirtualStorage(n) => n.output_connectors(), + Node::PiecewiseLink(n) => n.output_connectors(), + Node::PiecewiseStorage(n) => n.output_connectors(), + Node::Delay(n) => n.output_connectors(), } } /// Returns the default metric for this node. pub fn default_metric(&self, network: &pywr_core::network::Network) -> Result { match self { - Node::Core(n) => n.default_metric(network), - Node::Custom(n) => panic!("TODO custom nodes not yet supported: {}", n.meta.name.as_str()), + Node::Input(n) => n.default_metric(network), + Node::Link(n) => n.default_metric(network), + Node::Output(n) => n.default_metric(network), + Node::Storage(n) => n.default_metric(network), + Node::Catchment(n) => n.default_metric(network), + Node::RiverGauge(n) => n.default_metric(network), + Node::LossLink(n) => n.default_metric(network), + Node::River(n) => n.default_metric(network), + Node::RiverSplitWithGauge(n) => n.default_metric(network), + Node::WaterTreatmentWorks(n) => n.default_metric(network), + Node::Aggregated(n) => n.default_metric(network), + Node::AggregatedStorage(n) => n.default_metric(network), + Node::VirtualStorage(n) => n.default_metric(network), + Node::AnnualVirtualStorage(n) => n.default_metric(network), + Node::MonthlyVirtualStorage(n) => n.default_metric(network), + Node::PiecewiseLink(n) => n.default_metric(network), + Node::Delay(n) => n.default_metric(network), + Node::PiecewiseStorage(n) => n.default_metric(network), } } } @@ -404,24 +449,18 @@ impl TryFrom for Node { fn try_from(v1: NodeV1) -> Result { match v1 { NodeV1::Core(n) => { - let nv2: CoreNode = n.try_into()?; - Ok(Self::Core(nv2)) - } - NodeV1::Custom(n) => { - // Custom nodes are left as is (and therefore may not work). - let nv2 = CustomNode { - meta: n.meta.into(), - ty: n.ty, - attributes: n.attributes, - }; - - Ok(Self::Custom(nv2)) + let nv2: Node = n.try_into()?; + Ok(nv2) } + NodeV1::Custom(n) => Err(ConversionError::CustomNodeNotSupported { + name: n.meta.name, + ty: n.ty, + }), } } } -impl TryFrom> for CoreNode { +impl TryFrom> for Node { type Error = ConversionError; fn try_from(v1: Box) -> Result { diff --git a/pywr-schema/src/nodes/monthly_virtual_storage.rs b/pywr-schema/src/nodes/monthly_virtual_storage.rs index 700499bb..d49194c4 100644 --- a/pywr-schema/src/nodes/monthly_virtual_storage.rs +++ b/pywr-schema/src/nodes/monthly_virtual_storage.rs @@ -14,7 +14,13 @@ pub struct NumberOfMonthsReset { pub months: u8, } -#[derive(serde::Deserialize, serde::Serialize, Clone)] +impl Default for NumberOfMonthsReset { + fn default() -> Self { + Self { months: 1 } + } +} + +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] 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 b748b0bc..9db90fbc 100644 --- a/pywr-schema/src/nodes/piecewise_link.rs +++ b/pywr-schema/src/nodes/piecewise_link.rs @@ -36,7 +36,7 @@ pub struct PiecewiseLinkStep { /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] 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 b40bf9dd..93f7c7bb 100644 --- a/pywr-schema/src/nodes/piecewise_storage.rs +++ b/pywr-schema/src/nodes/piecewise_storage.rs @@ -39,7 +39,7 @@ pub struct PiecewiseStore { /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] 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 8026f194..fd78a6c7 100644 --- a/pywr-schema/src/nodes/river.rs +++ b/pywr-schema/src/nodes/river.rs @@ -5,7 +5,7 @@ use pywr_core::metric::Metric; use pywr_v1_schema::nodes::LinkNode as LinkNodeV1; use std::collections::HashMap; -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] pub struct RiverNode { #[serde(flatten)] pub meta: NodeMeta, diff --git a/pywr-schema/src/nodes/river_gauge.rs b/pywr-schema/src/nodes/river_gauge.rs index ad52dbd0..f9cf91db 100644 --- a/pywr-schema/src/nodes/river_gauge.rs +++ b/pywr-schema/src/nodes/river_gauge.rs @@ -22,7 +22,7 @@ use std::path::Path; /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] 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 5500e9d5..7d9732c9 100644 --- a/pywr-schema/src/nodes/river_split_with_gauge.rs +++ b/pywr-schema/src/nodes/river_split_with_gauge.rs @@ -31,7 +31,7 @@ use std::path::Path; /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] pub struct RiverSplitWithGaugeNode { #[serde(flatten)] pub meta: NodeMeta, diff --git a/pywr-schema/src/nodes/virtual_storage.rs b/pywr-schema/src/nodes/virtual_storage.rs index f4cd8fb3..a6fb4bd0 100644 --- a/pywr-schema/src/nodes/virtual_storage.rs +++ b/pywr-schema/src/nodes/virtual_storage.rs @@ -9,7 +9,7 @@ use pywr_core::virtual_storage::VirtualStorageReset; use pywr_v1_schema::nodes::VirtualStorageNode as VirtualStorageNodeV1; use std::path::Path; -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] 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 0ec04724..0057e58c 100644 --- a/pywr-schema/src/nodes/water_treatment_works.rs +++ b/pywr-schema/src/nodes/water_treatment_works.rs @@ -35,7 +35,7 @@ use std::path::Path; /// ``` /// )] -#[derive(serde::Deserialize, serde::Serialize, Clone)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Default)] pub struct WaterTreatmentWorks { /// Node metadata #[serde(flatten)] diff --git a/pywr-schema/src/parameters/mod.rs b/pywr-schema/src/parameters/mod.rs index 1096435b..b8a61bf1 100644 --- a/pywr-schema/src/parameters/mod.rs +++ b/pywr-schema/src/parameters/mod.rs @@ -411,6 +411,12 @@ pub enum ConstantValue { External(ExternalDataRef), } +impl Default for ConstantValue { + fn default() -> Self { + Self::Literal(0.0) + } +} + impl ConstantValue { /// Return the value loading from a table if required. pub fn load(&self, tables: &LoadedTableCollection) -> Result { @@ -604,6 +610,12 @@ pub enum DynamicFloatValue { Dynamic(MetricFloatValue), } +impl Default for DynamicFloatValue { + fn default() -> Self { + Self::Constant(ConstantValue::default()) + } +} + impl DynamicFloatValue { pub fn from_f64(v: f64) -> Self { Self::Constant(ConstantValue::Literal(v))