From 973907699f1aac774965a3d42bd58d4e1283e408 Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Sat, 30 Sep 2023 13:38:22 +0100 Subject: [PATCH 1/2] chore: Split crate into multiple crates. (#56) - pywr-core: Contains the low-level model and interfaces with the LP solvers. - pywr-schema: Contains the Serde declared JSON schema. - pywr-python: Contains the Python extension module and package. - pywr-cli: Contains the Rust CLI. Add brief crate descriptions and updated instructions to the README. --- Cargo.toml | 106 +++++------------- README.md | 21 +++- pywr-cli/Cargo.toml | 20 ++++ {src => pywr-cli/src}/main.rs | 32 +++--- pywr-core/Cargo.toml | 63 +++++++++++ .../benches}/random_models.rs | 12 +- {src => pywr-core/src}/aggregated_node.rs | 0 .../src}/aggregated_storage_node.rs | 0 {src => pywr-core/src}/edge.rs | 0 {src => pywr-core/src}/lib.rs | 41 +++---- {src => pywr-core/src}/metric.rs | 0 {src => pywr-core/src}/model.rs | 1 - {src => pywr-core/src}/node.rs | 0 .../src}/parameters/activation_function.rs | 0 .../src}/parameters/aggregated.rs | 0 .../src}/parameters/aggregated_index.rs | 0 {src => pywr-core/src}/parameters/array.rs | 0 .../src}/parameters/asymmetric.rs | 0 {src => pywr-core/src}/parameters/constant.rs | 0 .../parameters/control_curves/apportion.rs | 0 .../src}/parameters/control_curves/index.rs | 0 .../parameters/control_curves/interpolated.rs | 0 .../src}/parameters/control_curves/mod.rs | 0 .../parameters/control_curves/piecewise.rs | 0 .../src}/parameters/control_curves/simple.rs | 0 {src => pywr-core/src}/parameters/delay.rs | 0 {src => pywr-core/src}/parameters/division.rs | 0 .../src}/parameters/indexed_array.rs | 0 {src => pywr-core/src}/parameters/max.rs | 0 {src => pywr-core/src}/parameters/min.rs | 0 {src => pywr-core/src}/parameters/mod.rs | 0 {src => pywr-core/src}/parameters/negative.rs | 0 {src => pywr-core/src}/parameters/offset.rs | 0 .../src}/parameters/polynomial.rs | 0 .../src}/parameters/profiles/daily.rs | 0 .../src}/parameters/profiles/mod.rs | 0 .../src}/parameters/profiles/monthly.rs | 0 .../parameters/profiles/uniform_drawdown.rs | 0 {src => pywr-core/src}/parameters/py.rs | 0 {src => pywr-core/src}/parameters/rhai.rs | 0 .../src}/parameters/simple_wasm.rs | 0 .../src}/parameters/threshold.rs | 0 {src => pywr-core/src}/parameters/vector.rs | 0 .../src}/recorders/aggregator.rs | 0 {src => pywr-core/src}/recorders/csv.rs | 0 {src => pywr-core/src}/recorders/hdf.rs | 0 .../src}/recorders/metric_set.rs | 0 {src => pywr-core/src}/recorders/mod.rs | 0 {src => pywr-core/src}/recorders/py.rs | 0 {src => pywr-core/src}/scenario.rs | 0 {src => pywr-core/src}/solvers/builder.rs | 0 {src => pywr-core/src}/solvers/clp/mod.rs | 0 .../src}/solvers/clp/settings.rs | 2 +- .../src}/solvers/col_edge_map.rs | 0 {src => pywr-core/src}/solvers/highs/mod.rs | 0 .../src}/solvers/highs/settings.rs | 0 {src => pywr-core/src}/solvers/ipm_ocl/mod.rs | 0 .../src}/solvers/ipm_ocl/settings.rs | 0 .../src}/solvers/ipm_simd/mod.rs | 0 .../src}/solvers/ipm_simd/settings.rs | 0 {src => pywr-core/src}/solvers/mod.rs | 0 {src => pywr-core/src}/state.rs | 0 {src => pywr-core/src}/test_utils.rs | 2 +- {src => pywr-core/src}/timestep.rs | 0 {src => pywr-core/src}/tracing.rs | 0 {src => pywr-core/src}/virtual_storage.rs | 0 pywr-python/Cargo.toml | 22 ++++ pyproject.toml => pywr-python/pyproject.toml | 0 {pywr => pywr-python/pywr}/__init__.py | 0 {pywr => pywr-python/pywr}/__main__.py | 0 {pywr => pywr-python/pywr}/cli.py | 11 +- setup.cfg => pywr-python/setup.cfg | 0 src/python.rs => pywr-python/src/lib.rs | 104 +++++++---------- pywr-schema/Cargo.toml | 36 ++++++ .../src}/data_tables/mod.rs | 4 +- {src/schema => pywr-schema/src}/edge.rs | 4 +- pywr-schema/src/error.rs | 70 ++++++++++++ src/schema/mod.rs => pywr-schema/src/lib.rs | 2 +- .../src}/metric_sets/mod.rs | 22 ++-- {src/schema => pywr-schema/src}/model.rs | 85 +++++++------- .../src}/nodes/annual_virtual_storage.rs | 25 ++--- {src/schema => pywr-schema/src}/nodes/core.rs | 77 +++++++------ .../schema => pywr-schema/src}/nodes/delay.rs | 36 +++--- .../src}/nodes/loss_link.rs | 21 ++-- {src/schema => pywr-schema/src}/nodes/mod.rs | 37 +++--- .../src}/nodes/monthly_virtual_storage.rs | 25 ++--- .../src}/nodes/piecewise_link.rs | 34 +++--- .../src}/nodes/piecewise_storage.rs | 37 +++--- .../schema => pywr-schema/src}/nodes/river.rs | 15 ++- .../src}/nodes/river_gauge.rs | 30 +++-- .../src}/nodes/river_split_with_gauge.rs | 34 +++--- .../src}/nodes/virtual_storage.rs | 25 ++--- .../src}/nodes/water_treatment_works.rs | 31 +++-- .../schema => pywr-schema/src}/outputs/csv.rs | 17 +-- .../schema => pywr-schema/src}/outputs/hdf.rs | 17 +-- .../schema => pywr-schema/src}/outputs/mod.rs | 8 +- .../src}/parameters/aggregated.rs | 54 ++++----- .../src}/parameters/asymmetric_switch.rs | 18 +-- .../src}/parameters/control_curves.rs | 46 ++++---- .../src}/parameters/core.rs | 72 ++++++------ .../src}/parameters/data_frame.rs | 20 ++-- .../src}/parameters/delay.rs | 16 +-- .../parameters/doc_examples/aggregated_1.json | 0 .../doc_examples/constant_simple.json | 0 .../doc_examples/constant_variable.json | 0 .../doc_examples/offset_simple.json | 0 .../doc_examples/offset_variable.json | 0 .../src}/parameters/indexed_array.rs | 18 +-- .../src}/parameters/mod.rs | 58 +++++----- .../src}/parameters/offset.rs | 17 ++- .../src}/parameters/polynomial.rs | 16 +-- .../src}/parameters/profiles.rs | 36 +++--- .../src}/parameters/python.rs | 28 ++--- .../src}/parameters/tables.rs | 30 ++--- .../src}/parameters/thresholds.rs | 32 +++--- .../src}/test_models/csv1.json | 0 .../src}/test_models/delay1.json | 0 .../src}/test_models/hdf1.json | 0 .../src}/test_models/piecewise_link1.json | 0 .../src}/test_models/piecewise_storage1.json | 0 .../src}/test_models/piecewise_storage2.json | 0 .../test_models/river_split_with_gauge1.json | 0 .../src}/test_models/simple1.json | 0 src/schema/error.rs | 31 ----- 124 files changed, 866 insertions(+), 755 deletions(-) create mode 100644 pywr-cli/Cargo.toml rename {src => pywr-cli/src}/main.rs (88%) create mode 100644 pywr-core/Cargo.toml rename {benches => pywr-core/benches}/random_models.rs (98%) rename {src => pywr-core/src}/aggregated_node.rs (100%) rename {src => pywr-core/src}/aggregated_storage_node.rs (100%) rename {src => pywr-core/src}/edge.rs (100%) rename {src => pywr-core/src}/lib.rs (82%) rename {src => pywr-core/src}/metric.rs (100%) rename {src => pywr-core/src}/model.rs (99%) rename {src => pywr-core/src}/node.rs (100%) rename {src => pywr-core/src}/parameters/activation_function.rs (100%) rename {src => pywr-core/src}/parameters/aggregated.rs (100%) rename {src => pywr-core/src}/parameters/aggregated_index.rs (100%) rename {src => pywr-core/src}/parameters/array.rs (100%) rename {src => pywr-core/src}/parameters/asymmetric.rs (100%) rename {src => pywr-core/src}/parameters/constant.rs (100%) rename {src => pywr-core/src}/parameters/control_curves/apportion.rs (100%) rename {src => pywr-core/src}/parameters/control_curves/index.rs (100%) rename {src => pywr-core/src}/parameters/control_curves/interpolated.rs (100%) rename {src => pywr-core/src}/parameters/control_curves/mod.rs (100%) rename {src => pywr-core/src}/parameters/control_curves/piecewise.rs (100%) rename {src => pywr-core/src}/parameters/control_curves/simple.rs (100%) rename {src => pywr-core/src}/parameters/delay.rs (100%) rename {src => pywr-core/src}/parameters/division.rs (100%) rename {src => pywr-core/src}/parameters/indexed_array.rs (100%) rename {src => pywr-core/src}/parameters/max.rs (100%) rename {src => pywr-core/src}/parameters/min.rs (100%) rename {src => pywr-core/src}/parameters/mod.rs (100%) rename {src => pywr-core/src}/parameters/negative.rs (100%) rename {src => pywr-core/src}/parameters/offset.rs (100%) rename {src => pywr-core/src}/parameters/polynomial.rs (100%) rename {src => pywr-core/src}/parameters/profiles/daily.rs (100%) rename {src => pywr-core/src}/parameters/profiles/mod.rs (100%) rename {src => pywr-core/src}/parameters/profiles/monthly.rs (100%) rename {src => pywr-core/src}/parameters/profiles/uniform_drawdown.rs (100%) rename {src => pywr-core/src}/parameters/py.rs (100%) rename {src => pywr-core/src}/parameters/rhai.rs (100%) rename {src => pywr-core/src}/parameters/simple_wasm.rs (100%) rename {src => pywr-core/src}/parameters/threshold.rs (100%) rename {src => pywr-core/src}/parameters/vector.rs (100%) rename {src => pywr-core/src}/recorders/aggregator.rs (100%) rename {src => pywr-core/src}/recorders/csv.rs (100%) rename {src => pywr-core/src}/recorders/hdf.rs (100%) rename {src => pywr-core/src}/recorders/metric_set.rs (100%) rename {src => pywr-core/src}/recorders/mod.rs (100%) rename {src => pywr-core/src}/recorders/py.rs (100%) rename {src => pywr-core/src}/scenario.rs (100%) rename {src => pywr-core/src}/solvers/builder.rs (100%) rename {src => pywr-core/src}/solvers/clp/mod.rs (100%) rename {src => pywr-core/src}/solvers/clp/settings.rs (97%) rename {src => pywr-core/src}/solvers/col_edge_map.rs (100%) rename {src => pywr-core/src}/solvers/highs/mod.rs (100%) rename {src => pywr-core/src}/solvers/highs/settings.rs (100%) rename {src => pywr-core/src}/solvers/ipm_ocl/mod.rs (100%) rename {src => pywr-core/src}/solvers/ipm_ocl/settings.rs (100%) rename {src => pywr-core/src}/solvers/ipm_simd/mod.rs (100%) rename {src => pywr-core/src}/solvers/ipm_simd/settings.rs (100%) rename {src => pywr-core/src}/solvers/mod.rs (100%) rename {src => pywr-core/src}/state.rs (100%) rename {src => pywr-core/src}/test_utils.rs (99%) rename {src => pywr-core/src}/timestep.rs (100%) rename {src => pywr-core/src}/tracing.rs (100%) rename {src => pywr-core/src}/virtual_storage.rs (100%) create mode 100644 pywr-python/Cargo.toml rename pyproject.toml => pywr-python/pyproject.toml (100%) rename {pywr => pywr-python/pywr}/__init__.py (100%) rename {pywr => pywr-python/pywr}/__main__.py (100%) rename {pywr => pywr-python/pywr}/cli.py (87%) rename setup.cfg => pywr-python/setup.cfg (100%) rename src/python.rs => pywr-python/src/lib.rs (93%) create mode 100644 pywr-schema/Cargo.toml rename {src/schema => pywr-schema/src}/data_tables/mod.rs (99%) rename {src/schema => pywr-schema/src}/edge.rs (82%) create mode 100644 pywr-schema/src/error.rs rename src/schema/mod.rs => pywr-schema/src/lib.rs (86%) rename {src/schema => pywr-schema/src}/metric_sets/mod.rs (65%) rename {src/schema => pywr-schema/src}/model.rs (81%) rename {src/schema => pywr-schema/src}/nodes/annual_virtual_storage.rs (83%) rename {src/schema => pywr-schema/src}/nodes/core.rs (89%) rename {src/schema => pywr-schema/src}/nodes/delay.rs (82%) rename {src/schema => pywr-schema/src}/nodes/loss_link.rs (87%) rename {src/schema => pywr-schema/src}/nodes/mod.rs (95%) rename {src/schema => pywr-schema/src}/nodes/monthly_virtual_storage.rs (83%) rename {src/schema => pywr-schema/src}/nodes/piecewise_link.rs (86%) rename {src/schema => pywr-schema/src}/nodes/piecewise_storage.rs (90%) rename {src/schema => pywr-schema/src}/nodes/river.rs (79%) rename {src/schema => pywr-schema/src}/nodes/river_gauge.rs (86%) rename {src/schema => pywr-schema/src}/nodes/river_split_with_gauge.rs (87%) rename {src/schema => pywr-schema/src}/nodes/virtual_storage.rs (82%) rename {src/schema => pywr-schema/src}/nodes/water_treatment_works.rs (93%) rename {src/schema => pywr-schema/src}/outputs/csv.rs (78%) rename {src/schema => pywr-schema/src}/outputs/hdf.rs (78%) rename {src/schema => pywr-schema/src}/outputs/mod.rs (69%) rename {src/schema => pywr-schema/src}/parameters/aggregated.rs (83%) rename {src/schema => pywr-schema/src}/parameters/asymmetric_switch.rs (79%) rename {src/schema => pywr-schema/src}/parameters/control_curves.rs (90%) rename {src/schema => pywr-schema/src}/parameters/core.rs (84%) rename {src/schema => pywr-schema/src}/parameters/data_frame.rs (89%) rename {src/schema => pywr-schema/src}/parameters/delay.rs (66%) rename {src/schema => pywr-schema/src}/parameters/doc_examples/aggregated_1.json (100%) rename {src/schema => pywr-schema/src}/parameters/doc_examples/constant_simple.json (100%) rename {src/schema => pywr-schema/src}/parameters/doc_examples/constant_variable.json (100%) rename {src/schema => pywr-schema/src}/parameters/doc_examples/offset_simple.json (100%) rename {src/schema => pywr-schema/src}/parameters/doc_examples/offset_variable.json (100%) rename {src/schema => pywr-schema/src}/parameters/indexed_array.rs (80%) rename {src/schema => pywr-schema/src}/parameters/mod.rs (95%) rename {src/schema => pywr-schema/src}/parameters/offset.rs (79%) rename {src/schema => pywr-schema/src}/parameters/polynomial.rs (76%) rename {src/schema => pywr-schema/src}/parameters/profiles.rs (86%) rename {src/schema => pywr-schema/src}/parameters/python.rs (91%) rename {src/schema => pywr-schema/src}/parameters/tables.rs (66%) rename {src/schema => pywr-schema/src}/parameters/thresholds.rs (73%) rename {src/schema => pywr-schema/src}/test_models/csv1.json (100%) rename {src/schema => pywr-schema/src}/test_models/delay1.json (100%) rename {src/schema => pywr-schema/src}/test_models/hdf1.json (100%) rename {src/schema => pywr-schema/src}/test_models/piecewise_link1.json (100%) rename {src/schema => pywr-schema/src}/test_models/piecewise_storage1.json (100%) rename {src/schema => pywr-schema/src}/test_models/piecewise_storage2.json (100%) rename {src/schema => pywr-schema/src}/test_models/river_split_with_gauge1.json (100%) rename {src/schema => pywr-schema/src}/test_models/simple1.json (100%) delete mode 100644 src/schema/error.rs diff --git a/Cargo.toml b/Cargo.toml index 23c05c7c..77d8776b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,92 +1,29 @@ -[package] -name = "pywr" -version = "2.0.0-dev" -authors = ["James Tomlinson "] -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" -license-file = "LICENSE" -keywords = ["water", "modelling"] -categories = ["science", "simulation"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -libc = "0.2.97" -thiserror = "1.0.25" -ndarray = "0.15.3" -numpy = "0.19.0" -num = "0.4.0" -float-cmp = "0.9.0" -hdf5 = { version="0.8.1" } -csv = "1.1" -clp-sys = { path = "./clp-sys" } -ipm-ocl = { path = "./ipm-ocl", optional = true } -ipm-simd = { path = "./ipm-simd", optional = true } -wasmer = "4.0.0" -serde = { version = "1", features = ["derive"] } -serde_json = "1.0" -time = { version = "0.3", features = ["serde", "serde-well-known", "serde-human-readable", "macros"] } -svgbobdoc = { version = "0.3.0", features = ["enable"] } -tracing = "0.1" -tracing-subscriber = { version ="0.3.17", features=["env-filter"] } -pyo3-log = "0.8.0" -indicatif = "0.17.2" -highs-sys = { git = "https://github.com/jetuk/highs-sys", branch="fix-build-libz-linking", optional = true } -# highs-sys = { path = "../../highs-sys" } -pyo3 = { version = "0.19.0" } -rayon = "1.6.1" -polars = { version = "0.33.2", features = ["lazy", "rows", "ndarray"] } -pyo3-polars = "0.7.0" -pywr-schema = { git = "https://github.com/pywr/pywr-schema/", tag="v0.7.0" } -rhai = { version="1.12.0", features=["sync"] } - -# OpenCL -ocl = { version = "0.19", optional = true } - -# Binary dependencies -clap = { version="4.0", features=["derive"] } -anyhow = "1.0.69" - -rand = "0.8.5" -rand_distr = "0.4.3" -rand_chacha = "0.3.1" - -[dev-dependencies] -tempfile = "3.3.0" -criterion = "0.5" - -[lib] -name = "pywr" -path = "src/lib.rs" -crate-type = ["cdylib", "rlib"] - - -[features] -extension-module = ["pyo3/extension-module"] -default = ["extension-module"] -highs = ["dep:highs-sys"] -ipm-ocl = ["dep:ipm-ocl", "dep:ocl"] -ipm-simd = ["dep:ipm-simd"] - -[[bin]] -name = "pywr-cli" -path = "src/main.rs" [workspace] +resolver = "2" members = [ "ipm-common", "ipm-ocl", "ipm-simd", "clp-sys", + "pywr-core", + "pywr-schema", + "pywr-cli", + "pywr-python", ] exclude = [ "tests/models/simple-wasm/simple-wasm-parameter" ] +# IPM packages are not default because they require nightly (portable_simd). +default-members = [ + "clp-sys", + "pywr-core", + "pywr-schema", + "pywr-cli", + "pywr-python", +] + [profile.release] opt-level = 3 # fast and small wasm @@ -96,6 +33,15 @@ opt-level = 3 # fast and small wasm # debug = true -[[bench]] -name = "random_models" -harness = false +[workspace.dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0.25" +time = { version = "0.3", features = ["serde", "serde-well-known", "serde-human-readable", "macros"] } +num = "0.4.0" +ndarray = "0.15.3" +pyo3 = { version = "0.19.0" } +tracing = "0.1" +csv = "1.1" +hdf5 = { version="0.8.1" } +pywr-v1-schema = { git = "https://github.com/pywr/pywr-schema/", tag="v0.7.0", package = "pywr-schema" } diff --git a/README.md b/README.md index 4ec44437..4251befe 100644 --- a/README.md +++ b/README.md @@ -117,10 +117,11 @@ git submodule init git submodule update ``` -Rust is required for installation. To create a development installation requires first compiling the -Rust library and then installing the Python package in editable model. +Rust is required for installation of the Python extension. To create a Python development installation +requires first compiling the Rust library and then installing the Python package in editable model. ```bash +cd pywr-python maturin develop pip install -e . ``` @@ -143,19 +144,19 @@ python -m pywr ### Rust CLI -A basic command line interface is included such that you can use this version of Pywr without Python. Running this -currently requires disabling the default features that create the Python extension. +A basic command line interface is included such that you can use this version of Pywr without Python. +This CLI is in the `pywr-cli` crate. To see the CLI commands available run the following: ```bash -cargo run --no-default-features -- --help +cargo run -p pywr-cli -- --help ``` To run a Pywr v2 model use the following: ```bash -cargo run --no-default-features -- run tests/models/simple1.json +cargo run -p pywr-cli -- run tests/models/simple1.json ``` ### Python CLI @@ -191,6 +192,14 @@ Feedback on porting models is very welcome, so please open an issue with any que

(back to top)

+ +## Crates +This repository contains the following crates: + +- `pywr-core`: A low-level Rust library for constructing network models. This crate interfaces with linear program solvers. +- `pywr-schema`: A Rust library for validating Pywr JSON files against a schema, and then building a model from the schema using `pywr-core`. +- `pywr-cli`: A command line interface for running Pywr models. +- `pywr-python`: A Python package (and extension) for constructing and running Pywr models. diff --git a/pywr-cli/Cargo.toml b/pywr-cli/Cargo.toml new file mode 100644 index 00000000..9bf0aaf0 --- /dev/null +++ b/pywr-cli/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "pywr-cli" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version="4.0", features=["derive"] } +anyhow = "1.0.69" + +rand = "0.8.5" +rand_chacha = "0.3.1" +time = { workspace = true, features = ["serde", "serde-well-known", "serde-human-readable", "macros"] } +serde = { workspace = true } +serde_json = { workspace = true } +pywr-v1-schema = { workspace = true } + +pywr-core = { path = "../pywr-core" } +pywr-schema = { path = "../pywr-schema" } diff --git a/src/main.rs b/pywr-cli/src/main.rs similarity index 88% rename from src/main.rs rename to pywr-cli/src/main.rs index 00d834e6..9536e32a 100644 --- a/src/main.rs +++ b/pywr-cli/src/main.rs @@ -1,19 +1,19 @@ use anyhow::{Context, Result}; use clap::{Parser, Subcommand, ValueEnum}; -use pywr::model::Model; -use pywr::schema::model::PywrModel; -use pywr::schema::ConversionError; +use pywr_core::model::Model; #[cfg(feature = "ipm-ocl")] -use pywr::solvers::{ClIpmF32Solver, ClIpmF64Solver, ClIpmSolverSettings}; -use pywr::solvers::{ClpSolver, ClpSolverSettings}; +use pywr_core::solvers::{ClIpmF32Solver, ClIpmF64Solver, ClIpmSolverSettings}; +use pywr_core::solvers::{ClpSolver, ClpSolverSettings}; #[cfg(feature = "highs")] -use pywr::solvers::{HighsSolver, HighsSolverSettings}; +use pywr_core::solvers::{HighsSolver, HighsSolverSettings}; #[cfg(feature = "ipm-simd")] -use pywr::solvers::{SimdIpmF64Solver, SimdIpmSolverSettings}; -use pywr::test_utils::make_random_model; -use pywr::timestep::Timestepper; -use pywr::tracing::setup_tracing; -use pywr::PywrError; +use pywr_core::solvers::{SimdIpmF64Solver, SimdIpmSolverSettings}; +use pywr_core::test_utils::make_random_model; +use pywr_core::timestep::Timestepper; +use pywr_core::tracing::setup_tracing; +use pywr_core::PywrError; +use pywr_schema::model::PywrModel; +use pywr_schema::ConversionError; use rand::SeedableRng; use rand_chacha::ChaCha8Rng; use std::fmt::{Display, Formatter}; @@ -138,15 +138,11 @@ fn convert(path: &Path) -> Result<()> { && (path.extension().unwrap() == "json") && (!path.file_stem().unwrap().to_str().unwrap().contains("_v2")) { - v1_to_v2(&path) - .map_err(PywrError::Conversion) - .with_context(|| format!("Could not convert model: `{:?}`", &path))?; + v1_to_v2(&path).with_context(|| format!("Could not convert model: `{:?}`", &path))?; } } } else { - v1_to_v2(path) - .map_err(PywrError::Conversion) - .with_context(|| format!("Could not convert model: `{:?}`", path))?; + v1_to_v2(path).with_context(|| format!("Could not convert model: `{:?}`", path))?; } Ok(()) @@ -156,7 +152,7 @@ fn v1_to_v2(path: &Path) -> std::result::Result<(), ConversionError> { println!("Model: {}", path.display()); let data = std::fs::read_to_string(path).unwrap(); - let schema: pywr_schema::PywrModel = serde_json::from_str(data.as_str()).unwrap(); + let schema: pywr_v1_schema::PywrModel = serde_json::from_str(data.as_str()).unwrap(); let schema_v2: PywrModel = schema.try_into()?; // There must be a better way to do this!! diff --git a/pywr-core/Cargo.toml b/pywr-core/Cargo.toml new file mode 100644 index 00000000..f9c5725d --- /dev/null +++ b/pywr-core/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "pywr-core" +version = "2.0.0-dev" +authors = ["James Tomlinson "] +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" +license-file = "LICENSE" +keywords = ["water", "modelling"] +categories = ["science", "simulation"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libc = "0.2.97" +thiserror = { workspace = true } +ndarray = { workspace = true } +num = { workspace = true } +float-cmp = "0.9.0" +hdf5 = { workspace = true } +csv = { workspace = true } +clp-sys = { path = "../clp-sys" } +ipm-ocl = { path = "../ipm-ocl", optional = true } +ipm-simd = { path = "../ipm-simd", optional = true } +wasmer = "4.0.0" +time = { workspace = true, features = ["macros"] } +tracing = { workspace = true } +tracing-subscriber = { version ="0.3.17", features=["env-filter"] } +highs-sys = { git = "https://github.com/jetuk/highs-sys", branch="fix-build-libz-linking", optional = true } +# highs-sys = { path = "../../highs-sys" } + +pyo3 = { workspace = true } + + +rayon = "1.6.1" + + +rhai = { version="1.12.0", features=["sync"] } + +# OpenCL +ocl = { version = "0.19", optional = true } + +rand = "0.8.5" +rand_distr = "0.4.3" +rand_chacha = "0.3.1" + +[dev-dependencies] +criterion = "0.5" + +[features] +highs = ["dep:highs-sys"] +ipm-ocl = ["dep:ipm-ocl", "dep:ocl"] +ipm-simd = ["dep:ipm-simd"] +default = [] + + + +[[bench]] +name = "random_models" +harness = false diff --git a/benches/random_models.rs b/pywr-core/benches/random_models.rs similarity index 98% rename from benches/random_models.rs rename to pywr-core/benches/random_models.rs index 11083f80..1062621a 100644 --- a/benches/random_models.rs +++ b/pywr-core/benches/random_models.rs @@ -9,14 +9,14 @@ /// input flows) and number of CPU threads. use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; #[cfg(feature = "ipm-ocl")] -use pywr::solvers::{ClIpmF64Solver, ClIpmSolverSettings, ClIpmSolverSettingsBuilder}; -use pywr::solvers::{ClpSolver, ClpSolverSettings, ClpSolverSettingsBuilder}; +use pywr_core::solvers::{ClIpmF64Solver, ClIpmSolverSettings, ClIpmSolverSettingsBuilder}; +use pywr_core::solvers::{ClpSolver, ClpSolverSettings, ClpSolverSettingsBuilder}; #[cfg(feature = "highs")] -use pywr::solvers::{HighsSolver, HighsSolverSettings}; +use pywr_core::solvers::{HighsSolver, HighsSolverSettings}; #[cfg(feature = "ipm-simd")] -use pywr::solvers::{SimdIpmF64Solver, SimdIpmSolverSettings, SimdIpmSolverSettingsBuilder}; -use pywr::test_utils::make_random_model; -use pywr::timestep::Timestepper; +use pywr_core::solvers::{SimdIpmF64Solver, SimdIpmSolverSettings, SimdIpmSolverSettingsBuilder}; +use pywr_core::test_utils::make_random_model; +use pywr_core::timestep::Timestepper; use rand::SeedableRng; use rand_chacha::ChaCha8Rng; use std::num::NonZeroUsize; diff --git a/src/aggregated_node.rs b/pywr-core/src/aggregated_node.rs similarity index 100% rename from src/aggregated_node.rs rename to pywr-core/src/aggregated_node.rs diff --git a/src/aggregated_storage_node.rs b/pywr-core/src/aggregated_storage_node.rs similarity index 100% rename from src/aggregated_storage_node.rs rename to pywr-core/src/aggregated_storage_node.rs diff --git a/src/edge.rs b/pywr-core/src/edge.rs similarity index 100% rename from src/edge.rs rename to pywr-core/src/edge.rs diff --git a/src/lib.rs b/pywr-core/src/lib.rs similarity index 82% rename from src/lib.rs rename to pywr-core/src/lib.rs index 4ad95868..9c82d52b 100644 --- a/src/lib.rs +++ b/pywr-core/src/lib.rs @@ -4,8 +4,9 @@ extern crate core; use crate::node::NodeIndex; use crate::parameters::{IndexParameterIndex, MultiValueParameterIndex, ParameterIndex}; -use crate::recorders::{MetricSetIndex, RecorderIndex}; -use crate::schema::ConversionError; +use crate::recorders::RecorderIndex; +use pyo3::exceptions::{PyException, PyRuntimeError}; +use pyo3::{create_exception, PyErr}; use thiserror::Error; pub mod aggregated_node; @@ -15,23 +16,17 @@ pub mod metric; pub mod model; pub mod node; pub mod parameters; -pub mod python; -mod recorders; +pub mod recorders; mod scenario; -pub mod schema; pub mod solvers; pub mod state; pub mod test_utils; pub mod timestep; pub mod tracing; -mod virtual_storage; +pub mod virtual_storage; #[derive(Error, Debug, PartialEq, Eq)] pub enum PywrError { - #[error("Schema conversion error")] - Conversion(#[from] ConversionError), - #[error("failed to load schema: {0}")] - SchemaLoad(String), #[error("invalid node connect")] InvalidNodeConnection, #[error("connection to node is already defined")] @@ -42,8 +37,6 @@ pub enum PywrError { NodeNotFound(String), #[error("edge index not found")] EdgeIndexNotFound, - #[error("unexpected parameter type: {0}")] - UnexpectedParameterType(String), #[error("parameter index {0} not found")] ParameterIndexNotFound(ParameterIndex), #[error("index parameter index {0} not found")] @@ -80,14 +73,6 @@ pub enum PywrError { FlowConstraintsUndefined, #[error("storage constraints are undefined for this node")] StorageConstraintsUndefined, - #[error("missing initial volume for node: {0}")] - MissingInitialVolume(String), - #[error("invalid date format description")] - InvalidDateFormatDescription(#[from] time::error::InvalidFormatDescription), - #[error("failed to parse date")] - DateParse(#[from] time::error::Parse), - #[error("invalid date component range")] - InvalidDateComponentRange(#[from] time::error::ComponentRange), #[error("timestep index out of range")] TimestepIndexOutOfRange, #[error("solver not initialised")] @@ -134,10 +119,6 @@ pub enum PywrError { DataOutOfRange, #[error("internal parameter error: {0}")] InternalParameterError(String), - #[error("data table error: {0}")] - DataTable(#[from] schema::data_tables::TableError), - #[error("unsupported file format")] - UnsupportedFileFormat, #[error("parameter type does is not a valid variable")] ParameterTypeNotVariable, #[error("parameter variable is not active")] @@ -147,3 +128,15 @@ pub enum PywrError { #[error("missing solver features")] MissingSolverFeatures, } + +// Python errors +create_exception!(pywr, ParameterNotFoundError, PyException); + +impl From for PyErr { + fn from(err: PywrError) -> PyErr { + match err { + PywrError::ParameterNotFound(name) => ParameterNotFoundError::new_err(name), + _ => PyRuntimeError::new_err(err.to_string()), + } + } +} diff --git a/src/metric.rs b/pywr-core/src/metric.rs similarity index 100% rename from src/metric.rs rename to pywr-core/src/metric.rs diff --git a/src/model.rs b/pywr-core/src/model.rs similarity index 99% rename from src/model.rs rename to pywr-core/src/model.rs index 61a4740d..abf363d1 100644 --- a/src/model.rs +++ b/pywr-core/src/model.rs @@ -11,7 +11,6 @@ use crate::state::{ParameterStates, State}; use crate::timestep::{Timestep, Timestepper}; use crate::virtual_storage::{VirtualStorage, VirtualStorageIndex, VirtualStorageReset, VirtualStorageVec}; use crate::{parameters, recorders, IndexParameterIndex, NodeIndex, ParameterIndex, PywrError, RecorderIndex}; -use indicatif::ProgressIterator; use rayon::prelude::*; use std::any::Any; use std::collections::HashSet; diff --git a/src/node.rs b/pywr-core/src/node.rs similarity index 100% rename from src/node.rs rename to pywr-core/src/node.rs diff --git a/src/parameters/activation_function.rs b/pywr-core/src/parameters/activation_function.rs similarity index 100% rename from src/parameters/activation_function.rs rename to pywr-core/src/parameters/activation_function.rs diff --git a/src/parameters/aggregated.rs b/pywr-core/src/parameters/aggregated.rs similarity index 100% rename from src/parameters/aggregated.rs rename to pywr-core/src/parameters/aggregated.rs diff --git a/src/parameters/aggregated_index.rs b/pywr-core/src/parameters/aggregated_index.rs similarity index 100% rename from src/parameters/aggregated_index.rs rename to pywr-core/src/parameters/aggregated_index.rs diff --git a/src/parameters/array.rs b/pywr-core/src/parameters/array.rs similarity index 100% rename from src/parameters/array.rs rename to pywr-core/src/parameters/array.rs diff --git a/src/parameters/asymmetric.rs b/pywr-core/src/parameters/asymmetric.rs similarity index 100% rename from src/parameters/asymmetric.rs rename to pywr-core/src/parameters/asymmetric.rs diff --git a/src/parameters/constant.rs b/pywr-core/src/parameters/constant.rs similarity index 100% rename from src/parameters/constant.rs rename to pywr-core/src/parameters/constant.rs diff --git a/src/parameters/control_curves/apportion.rs b/pywr-core/src/parameters/control_curves/apportion.rs similarity index 100% rename from src/parameters/control_curves/apportion.rs rename to pywr-core/src/parameters/control_curves/apportion.rs diff --git a/src/parameters/control_curves/index.rs b/pywr-core/src/parameters/control_curves/index.rs similarity index 100% rename from src/parameters/control_curves/index.rs rename to pywr-core/src/parameters/control_curves/index.rs diff --git a/src/parameters/control_curves/interpolated.rs b/pywr-core/src/parameters/control_curves/interpolated.rs similarity index 100% rename from src/parameters/control_curves/interpolated.rs rename to pywr-core/src/parameters/control_curves/interpolated.rs diff --git a/src/parameters/control_curves/mod.rs b/pywr-core/src/parameters/control_curves/mod.rs similarity index 100% rename from src/parameters/control_curves/mod.rs rename to pywr-core/src/parameters/control_curves/mod.rs diff --git a/src/parameters/control_curves/piecewise.rs b/pywr-core/src/parameters/control_curves/piecewise.rs similarity index 100% rename from src/parameters/control_curves/piecewise.rs rename to pywr-core/src/parameters/control_curves/piecewise.rs diff --git a/src/parameters/control_curves/simple.rs b/pywr-core/src/parameters/control_curves/simple.rs similarity index 100% rename from src/parameters/control_curves/simple.rs rename to pywr-core/src/parameters/control_curves/simple.rs diff --git a/src/parameters/delay.rs b/pywr-core/src/parameters/delay.rs similarity index 100% rename from src/parameters/delay.rs rename to pywr-core/src/parameters/delay.rs diff --git a/src/parameters/division.rs b/pywr-core/src/parameters/division.rs similarity index 100% rename from src/parameters/division.rs rename to pywr-core/src/parameters/division.rs diff --git a/src/parameters/indexed_array.rs b/pywr-core/src/parameters/indexed_array.rs similarity index 100% rename from src/parameters/indexed_array.rs rename to pywr-core/src/parameters/indexed_array.rs diff --git a/src/parameters/max.rs b/pywr-core/src/parameters/max.rs similarity index 100% rename from src/parameters/max.rs rename to pywr-core/src/parameters/max.rs diff --git a/src/parameters/min.rs b/pywr-core/src/parameters/min.rs similarity index 100% rename from src/parameters/min.rs rename to pywr-core/src/parameters/min.rs diff --git a/src/parameters/mod.rs b/pywr-core/src/parameters/mod.rs similarity index 100% rename from src/parameters/mod.rs rename to pywr-core/src/parameters/mod.rs diff --git a/src/parameters/negative.rs b/pywr-core/src/parameters/negative.rs similarity index 100% rename from src/parameters/negative.rs rename to pywr-core/src/parameters/negative.rs diff --git a/src/parameters/offset.rs b/pywr-core/src/parameters/offset.rs similarity index 100% rename from src/parameters/offset.rs rename to pywr-core/src/parameters/offset.rs diff --git a/src/parameters/polynomial.rs b/pywr-core/src/parameters/polynomial.rs similarity index 100% rename from src/parameters/polynomial.rs rename to pywr-core/src/parameters/polynomial.rs diff --git a/src/parameters/profiles/daily.rs b/pywr-core/src/parameters/profiles/daily.rs similarity index 100% rename from src/parameters/profiles/daily.rs rename to pywr-core/src/parameters/profiles/daily.rs diff --git a/src/parameters/profiles/mod.rs b/pywr-core/src/parameters/profiles/mod.rs similarity index 100% rename from src/parameters/profiles/mod.rs rename to pywr-core/src/parameters/profiles/mod.rs diff --git a/src/parameters/profiles/monthly.rs b/pywr-core/src/parameters/profiles/monthly.rs similarity index 100% rename from src/parameters/profiles/monthly.rs rename to pywr-core/src/parameters/profiles/monthly.rs diff --git a/src/parameters/profiles/uniform_drawdown.rs b/pywr-core/src/parameters/profiles/uniform_drawdown.rs similarity index 100% rename from src/parameters/profiles/uniform_drawdown.rs rename to pywr-core/src/parameters/profiles/uniform_drawdown.rs diff --git a/src/parameters/py.rs b/pywr-core/src/parameters/py.rs similarity index 100% rename from src/parameters/py.rs rename to pywr-core/src/parameters/py.rs diff --git a/src/parameters/rhai.rs b/pywr-core/src/parameters/rhai.rs similarity index 100% rename from src/parameters/rhai.rs rename to pywr-core/src/parameters/rhai.rs diff --git a/src/parameters/simple_wasm.rs b/pywr-core/src/parameters/simple_wasm.rs similarity index 100% rename from src/parameters/simple_wasm.rs rename to pywr-core/src/parameters/simple_wasm.rs diff --git a/src/parameters/threshold.rs b/pywr-core/src/parameters/threshold.rs similarity index 100% rename from src/parameters/threshold.rs rename to pywr-core/src/parameters/threshold.rs diff --git a/src/parameters/vector.rs b/pywr-core/src/parameters/vector.rs similarity index 100% rename from src/parameters/vector.rs rename to pywr-core/src/parameters/vector.rs diff --git a/src/recorders/aggregator.rs b/pywr-core/src/recorders/aggregator.rs similarity index 100% rename from src/recorders/aggregator.rs rename to pywr-core/src/recorders/aggregator.rs diff --git a/src/recorders/csv.rs b/pywr-core/src/recorders/csv.rs similarity index 100% rename from src/recorders/csv.rs rename to pywr-core/src/recorders/csv.rs diff --git a/src/recorders/hdf.rs b/pywr-core/src/recorders/hdf.rs similarity index 100% rename from src/recorders/hdf.rs rename to pywr-core/src/recorders/hdf.rs diff --git a/src/recorders/metric_set.rs b/pywr-core/src/recorders/metric_set.rs similarity index 100% rename from src/recorders/metric_set.rs rename to pywr-core/src/recorders/metric_set.rs diff --git a/src/recorders/mod.rs b/pywr-core/src/recorders/mod.rs similarity index 100% rename from src/recorders/mod.rs rename to pywr-core/src/recorders/mod.rs diff --git a/src/recorders/py.rs b/pywr-core/src/recorders/py.rs similarity index 100% rename from src/recorders/py.rs rename to pywr-core/src/recorders/py.rs diff --git a/src/scenario.rs b/pywr-core/src/scenario.rs similarity index 100% rename from src/scenario.rs rename to pywr-core/src/scenario.rs diff --git a/src/solvers/builder.rs b/pywr-core/src/solvers/builder.rs similarity index 100% rename from src/solvers/builder.rs rename to pywr-core/src/solvers/builder.rs diff --git a/src/solvers/clp/mod.rs b/pywr-core/src/solvers/clp/mod.rs similarity index 100% rename from src/solvers/clp/mod.rs rename to pywr-core/src/solvers/clp/mod.rs diff --git a/src/solvers/clp/settings.rs b/pywr-core/src/solvers/clp/settings.rs similarity index 97% rename from src/solvers/clp/settings.rs rename to pywr-core/src/solvers/clp/settings.rs index 95b7b431..66ce73df 100644 --- a/src/solvers/clp/settings.rs +++ b/pywr-core/src/solvers/clp/settings.rs @@ -39,7 +39,7 @@ impl ClpSolverSettings { /// /// ``` /// use std::num::NonZeroUsize; -/// use pywr::solvers::ClpSolverSettingsBuilder; +/// use pywr_core::solvers::ClpSolverSettingsBuilder; /// // Settings with parallel enabled and 4 threads. /// let settings = ClpSolverSettingsBuilder::default().parallel().threads(4).build(); /// diff --git a/src/solvers/col_edge_map.rs b/pywr-core/src/solvers/col_edge_map.rs similarity index 100% rename from src/solvers/col_edge_map.rs rename to pywr-core/src/solvers/col_edge_map.rs diff --git a/src/solvers/highs/mod.rs b/pywr-core/src/solvers/highs/mod.rs similarity index 100% rename from src/solvers/highs/mod.rs rename to pywr-core/src/solvers/highs/mod.rs diff --git a/src/solvers/highs/settings.rs b/pywr-core/src/solvers/highs/settings.rs similarity index 100% rename from src/solvers/highs/settings.rs rename to pywr-core/src/solvers/highs/settings.rs diff --git a/src/solvers/ipm_ocl/mod.rs b/pywr-core/src/solvers/ipm_ocl/mod.rs similarity index 100% rename from src/solvers/ipm_ocl/mod.rs rename to pywr-core/src/solvers/ipm_ocl/mod.rs diff --git a/src/solvers/ipm_ocl/settings.rs b/pywr-core/src/solvers/ipm_ocl/settings.rs similarity index 100% rename from src/solvers/ipm_ocl/settings.rs rename to pywr-core/src/solvers/ipm_ocl/settings.rs diff --git a/src/solvers/ipm_simd/mod.rs b/pywr-core/src/solvers/ipm_simd/mod.rs similarity index 100% rename from src/solvers/ipm_simd/mod.rs rename to pywr-core/src/solvers/ipm_simd/mod.rs diff --git a/src/solvers/ipm_simd/settings.rs b/pywr-core/src/solvers/ipm_simd/settings.rs similarity index 100% rename from src/solvers/ipm_simd/settings.rs rename to pywr-core/src/solvers/ipm_simd/settings.rs diff --git a/src/solvers/mod.rs b/pywr-core/src/solvers/mod.rs similarity index 100% rename from src/solvers/mod.rs rename to pywr-core/src/solvers/mod.rs diff --git a/src/state.rs b/pywr-core/src/state.rs similarity index 100% rename from src/state.rs rename to pywr-core/src/state.rs diff --git a/src/test_utils.rs b/pywr-core/src/test_utils.rs similarity index 99% rename from src/test_utils.rs rename to pywr-core/src/test_utils.rs index 5f325c85..08f8ad6a 100644 --- a/src/test_utils.rs +++ b/pywr-core/src/test_utils.rs @@ -3,7 +3,7 @@ use crate::metric::Metric; /// TODO move this to its own local crate ("test-utilities") as part of a workspace. use crate::model::Model; use crate::node::{Constraint, ConstraintValue, StorageInitialVolume}; -use crate::parameters::{AggFunc, AggregatedParameter, Array2Parameter, ConstantParameter, Parameter, VectorParameter}; +use crate::parameters::{AggFunc, AggregatedParameter, Array2Parameter, ConstantParameter, Parameter}; use crate::recorders::AssertionRecorder; #[cfg(feature = "ipm-ocl")] use crate::solvers::ClIpmF64Solver; diff --git a/src/timestep.rs b/pywr-core/src/timestep.rs similarity index 100% rename from src/timestep.rs rename to pywr-core/src/timestep.rs diff --git a/src/tracing.rs b/pywr-core/src/tracing.rs similarity index 100% rename from src/tracing.rs rename to pywr-core/src/tracing.rs diff --git a/src/virtual_storage.rs b/pywr-core/src/virtual_storage.rs similarity index 100% rename from src/virtual_storage.rs rename to pywr-core/src/virtual_storage.rs diff --git a/pywr-python/Cargo.toml b/pywr-python/Cargo.toml new file mode 100644 index 00000000..419a6d99 --- /dev/null +++ b/pywr-python/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "pywr-python" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pyo3-polars = "0.7.0" +pyo3 = { version = "0.19.0" } +serde = { workspace = true } +serde_json = { workspace = true } +pywr-core = { path="../pywr-core" } +pywr-schema = { path="../pywr-schema" } + +[features] +extension-module = ["pyo3/extension-module"] +default = ["extension-module"] + +[lib] +name = "pywr" +crate-type = ["cdylib"] diff --git a/pyproject.toml b/pywr-python/pyproject.toml similarity index 100% rename from pyproject.toml rename to pywr-python/pyproject.toml diff --git a/pywr/__init__.py b/pywr-python/pywr/__init__.py similarity index 100% rename from pywr/__init__.py rename to pywr-python/pywr/__init__.py diff --git a/pywr/__main__.py b/pywr-python/pywr/__main__.py similarity index 100% rename from pywr/__main__.py rename to pywr-python/pywr/__main__.py diff --git a/pywr/cli.py b/pywr-python/pywr/cli.py similarity index 87% rename from pywr/cli.py rename to pywr-python/pywr/cli.py index 142ad9ac..4e749520 100644 --- a/pywr/cli.py +++ b/pywr-python/pywr/cli.py @@ -30,12 +30,17 @@ def cli(): ) @click.option("-t", "--threads", type=int, default=1) @click.option("-d", "--debug", is_flag=True) -def run(path: str, solver: str, data_path: Optional[str], threads: int, - debug: bool): +def run(path: str, solver: str, data_path: Optional[str], threads: int, debug: bool): with open(path) as fh: data = fh.read() - run_model_from_string(data, solver, debug, data_path, threads,) + run_model_from_string( + data, + solver, + debug, + data_path, + threads, + ) def start_cli(): diff --git a/setup.cfg b/pywr-python/setup.cfg similarity index 100% rename from setup.cfg rename to pywr-python/setup.cfg diff --git a/src/python.rs b/pywr-python/src/lib.rs similarity index 93% rename from src/python.rs rename to pywr-python/src/lib.rs index b3b8f80d..fd88f96c 100644 --- a/src/python.rs +++ b/pywr-python/src/lib.rs @@ -1,22 +1,15 @@ -use crate::aggregated_node::AggregatedNodeIndex; -use crate::model::Model; -use crate::recorders::HDF5Recorder; -use crate::schema::model::PywrModel; #[cfg(feature = "ipm-ocl")] use crate::solvers::{ClIpmF32Solver, ClIpmF64Solver, ClIpmSolverSettings}; -use crate::solvers::{ClpSolver, ClpSolverSettings}; #[cfg(feature = "highs")] use crate::solvers::{HighsSolver, HighsSolverSettings}; -use crate::timestep::Timestepper; -use crate::tracing::setup_tracing; -use crate::virtual_storage::VirtualStorageIndex; -use crate::{IndexParameterIndex, ParameterIndex, RecorderIndex}; -use crate::{NodeIndex, PywrError}; -use pyo3::create_exception; -use pyo3::exceptions::{PyException, PyRuntimeError}; +use pyo3::exceptions::PyException; use pyo3::prelude::*; -use pyo3::PyErr; -use std::ops::Deref; +use pywr_core::model::Model; +use pywr_core::solvers::{ClpSolver, ClpSolverSettings}; +use pywr_core::timestep::Timestepper; +use pywr_core::tracing::setup_tracing; +use pywr_core::ParameterNotFoundError; +use pywr_schema::PywrModel; use std::path::PathBuf; /// Python API @@ -26,19 +19,19 @@ use std::path::PathBuf; /// /// -impl IntoPy for ParameterIndex { - fn into_py(self, py: Python<'_>) -> PyObject { - // delegates to i32's IntoPy implementation. - self.deref().into_py(py) - } -} - -impl IntoPy for IndexParameterIndex { - fn into_py(self, py: Python<'_>) -> PyObject { - // delegates to i32's IntoPy implementation. - self.deref().into_py(py) - } -} +// impl IntoPy for ParameterIndex { +// fn into_py(self, py: Python<'_>) -> PyObject { +// // delegates to i32's IntoPy implementation. +// self.deref().into_py(py) +// } +// } +// +// impl IntoPy for IndexParameterIndex { +// fn into_py(self, py: Python<'_>) -> PyObject { +// // delegates to i32's IntoPy implementation. +// self.deref().into_py(py) +// } +// } #[derive(FromPyObject)] enum PyConstraintValue<'a> { @@ -48,17 +41,6 @@ enum PyConstraintValue<'a> { CatchAll(&'a PyAny), // This extraction never fails } -create_exception!(pywr, ParameterNotFoundError, PyException); - -impl std::convert::From for PyErr { - fn from(err: PywrError) -> PyErr { - match err { - PywrError::ParameterNotFound(name) => ParameterNotFoundError::new_err(name), - _ => PyRuntimeError::new_err(err.to_string()), - } - } -} - // #[derive(FromPyObject)] // struct PyMetric { // metric_type: String, @@ -140,29 +122,29 @@ impl std::convert::From for PyErr { // } // } -impl IntoPy for NodeIndex { - fn into_py(self, py: Python) -> PyObject { - self.deref().into_py(py) - } -} - -impl IntoPy for AggregatedNodeIndex { - fn into_py(self, py: Python) -> PyObject { - self.deref().into_py(py) - } -} - -impl IntoPy for VirtualStorageIndex { - fn into_py(self, py: Python) -> PyObject { - self.deref().into_py(py) - } -} - -impl IntoPy for RecorderIndex { - fn into_py(self, py: Python) -> PyObject { - self.deref().into_py(py) - } -} +// impl IntoPy for NodeIndex { +// fn into_py(self, py: Python) -> PyObject { +// self.deref().into_py(py) +// } +// } +// +// impl IntoPy for AggregatedNodeIndex { +// fn into_py(self, py: Python) -> PyObject { +// self.deref().into_py(py) +// } +// } +// +// impl IntoPy for VirtualStorageIndex { +// fn into_py(self, py: Python) -> PyObject { +// self.deref().into_py(py) +// } +// } +// +// impl IntoPy for RecorderIndex { +// fn into_py(self, py: Python) -> PyObject { +// self.deref().into_py(py) +// } +// } // #[pymethods] // impl PyModel { diff --git a/pywr-schema/Cargo.toml b/pywr-schema/Cargo.toml new file mode 100644 index 00000000..f1e28f75 --- /dev/null +++ b/pywr-schema/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "pywr-schema" +version = "0.1.0" +authors = ["James Tomlinson "] +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" +license-file = "LICENSE" +keywords = ["water", "modelling"] +categories = ["science", "simulation"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +svgbobdoc = { version = "0.3.0", features = ["enable"] } +polars = { version = "0.33.2", features = ["lazy", "rows", "ndarray"] } +pyo3-polars = "0.7.0" + +hdf5 = { workspace = true } +csv = { workspace = true } +tracing = { workspace = true } +pyo3 = { workspace = true } +num = { workspace = true } +ndarray = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +pywr-v1-schema = { workspace = true } +time = { workspace = true, features = ["serde", "serde-well-known", "serde-human-readable", "macros"] } +pywr-core = { path="../pywr-core" } + +[dev-dependencies] +tempfile = "3.3.0" diff --git a/src/schema/data_tables/mod.rs b/pywr-schema/src/data_tables/mod.rs similarity index 99% rename from src/schema/data_tables/mod.rs rename to pywr-schema/src/data_tables/mod.rs index 0220835a..501d7165 100644 --- a/src/schema/data_tables/mod.rs +++ b/pywr-schema/src/data_tables/mod.rs @@ -1,5 +1,5 @@ -use crate::schema::parameters::TableIndex; -use pywr_schema::parameters::TableDataRef as TableDataRefV1; +use crate::parameters::TableIndex; +use pywr_v1_schema::parameters::TableDataRef as TableDataRefV1; use std::collections::HashMap; use std::fs::File; use std::io::BufReader; diff --git a/src/schema/edge.rs b/pywr-schema/src/edge.rs similarity index 82% rename from src/schema/edge.rs rename to pywr-schema/src/edge.rs index cd70d1bf..56df9da2 100644 --- a/src/schema/edge.rs +++ b/pywr-schema/src/edge.rs @@ -8,8 +8,8 @@ pub struct Edge { pub to_slot: Option, } -impl From for Edge { - fn from(v1: pywr_schema::edge::Edge) -> Self { +impl From for Edge { + fn from(v1: pywr_v1_schema::edge::Edge) -> Self { Self { from_node: v1.from_node, to_node: v1.to_node, diff --git a/pywr-schema/src/error.rs b/pywr-schema/src/error.rs new file mode 100644 index 00000000..70acd4e3 --- /dev/null +++ b/pywr-schema/src/error.rs @@ -0,0 +1,70 @@ +use crate::data_tables::TableError; +use pyo3::exceptions::PyRuntimeError; +use pyo3::PyErr; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum SchemaError { + #[error("IO error: {0}")] + IO(String), + #[error("JSON error: {0}")] + Json(#[from] serde_json::Error), + #[error("node with name {0} not found")] + NodeNotFound(String), + #[error("parameter {0} not found")] + ParameterNotFound(String), + #[error("missing initial volume for node: {0}")] + MissingInitialVolume(String), + #[error("Pywr core error: {0}")] + PywrCore(#[from] pywr_core::PywrError), + #[error("data table error: {0}")] + DataTable(#[from] TableError), + #[error("Circular node reference(s) found.")] + CircularNodeReference, + #[error("Circular parameters reference(s) found.")] + CircularParameterReference, + #[error("unsupported file format")] + UnsupportedFileFormat, + #[error("Python error: {0}")] + PythonError(String), + #[error("invalid date format description")] + InvalidDateFormatDescription(#[from] time::error::InvalidFormatDescription), + #[error("failed to parse date")] + DateParse(#[from] time::error::Parse), + #[error("invalid date component range")] + InvalidDateComponentRange(#[from] time::error::ComponentRange), + #[error("hdf5 error: {0}")] + HDF5Error(String), + #[error("csv error: {0}")] + CSVError(String), + #[error("unexpected parameter type: {0}")] + UnexpectedParameterType(String), +} + +impl From for PyErr { + fn from(err: SchemaError) -> PyErr { + PyRuntimeError::new_err(err.to_string()) + } +} + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum ConversionError { + #[error("Error converting {attr:?} on node {name:?}")] + NodeAttribute { + attr: String, + name: String, + source: Box, + }, + #[error("Constant float value cannot be a parameter reference.")] + ConstantFloatReferencesParameter, + #[error("Constant float value cannot be an inline parameter.")] + ConstantFloatInlineParameter, + #[error("Missing one of the following attributes {attrs:?} on parameter {name:?}.")] + MissingAttribute { attrs: Vec, name: String }, + #[error("Unexpected the following attributes {attrs:?} on parameter {name:?}.")] + 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:?}.")] + ExtraNodeAttribute { attr: String, name: String }, +} diff --git a/src/schema/mod.rs b/pywr-schema/src/lib.rs similarity index 86% rename from src/schema/mod.rs rename to pywr-schema/src/lib.rs index ddb766f2..dbe4c3ec 100644 --- a/src/schema/mod.rs +++ b/pywr-schema/src/lib.rs @@ -13,5 +13,5 @@ pub mod nodes; pub mod outputs; pub mod parameters; -pub use error::ConversionError; +pub use error::{ConversionError, SchemaError}; pub use model::PywrModel; diff --git a/src/schema/metric_sets/mod.rs b/pywr-schema/src/metric_sets/mod.rs similarity index 65% rename from src/schema/metric_sets/mod.rs rename to pywr-schema/src/metric_sets/mod.rs index 12a9083c..02d2c631 100644 --- a/src/schema/metric_sets/mod.rs +++ b/pywr-schema/src/metric_sets/mod.rs @@ -1,5 +1,5 @@ -use crate::metric::Metric; -use crate::PywrError; +use crate::error::SchemaError; +use crate::PywrModel; use serde::{Deserialize, Serialize}; /// @@ -12,15 +12,15 @@ pub enum OutputMetric { impl OutputMetric { fn try_clone_into_metric( &self, - model: &crate::model::Model, - schema: &crate::schema::PywrModel, - ) -> Result { + model: &pywr_core::model::Model, + schema: &PywrModel, + ) -> Result { match self { OutputMetric::NodeName(node_name) => { // Get the node from the schema; not the model itself let node = schema .get_node_by_name(node_name) - .ok_or_else(|| PywrError::NodeNotFound(node_name.to_string()))?; + .ok_or_else(|| SchemaError::NodeNotFound(node_name.to_string()))?; // Create and return the node's default metric node.default_metric(model) } @@ -36,18 +36,14 @@ pub struct MetricSet { } impl MetricSet { - pub fn add_to_model( - &self, - model: &mut crate::model::Model, - schema: &crate::schema::PywrModel, - ) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model, schema: &PywrModel) -> Result<(), SchemaError> { // Convert the schema representation to internal metrics. - let metrics: Vec = self + let metrics: Vec = self .metrics .iter() .map(|m| m.try_clone_into_metric(model, schema)) .collect::>()?; - let metric_set = crate::recorders::MetricSet::new(&self.name, None, metrics); + let metric_set = pywr_core::recorders::MetricSet::new(&self.name, None, metrics); let _ = model.add_metric_set(metric_set)?; Ok(()) diff --git a/src/schema/model.rs b/pywr-schema/src/model.rs similarity index 81% rename from src/schema/model.rs rename to pywr-schema/src/model.rs index c4b5da15..95f8f1c2 100644 --- a/src/schema/model.rs +++ b/pywr-schema/src/model.rs @@ -1,12 +1,12 @@ use super::edge::Edge; use super::nodes::Node; use super::parameters::Parameter; -use crate::schema::data_tables::{DataTable, LoadedTableCollection}; -use crate::schema::error::{ConversionError, SchemaError}; -use crate::schema::metric_sets::MetricSet; -use crate::schema::outputs::Output; -use crate::schema::parameters::TryIntoV2Parameter; -use crate::PywrError; +use crate::data_tables::{DataTable, LoadedTableCollection}; +use crate::error::{ConversionError, SchemaError}; +use crate::metric_sets::MetricSet; +use crate::outputs::Output; +use crate::parameters::TryIntoV2Parameter; +use pywr_core::PywrError; use std::path::Path; use time::Date; @@ -17,10 +17,10 @@ pub struct Metadata { pub minimum_version: Option, } -impl TryFrom for Metadata { +impl TryFrom for Metadata { type Error = ConversionError; - fn try_from(v1: pywr_schema::model::Metadata) -> Result { + fn try_from(v1: pywr_v1_schema::model::Metadata) -> Result { Ok(Self { title: v1.title, description: v1.description, @@ -36,11 +36,11 @@ pub enum Timestep { Frequency(String), } -impl From for Timestep { - fn from(v1: pywr_schema::model::Timestep) -> Self { +impl From for Timestep { + fn from(v1: pywr_v1_schema::model::Timestep) -> Self { match v1 { - pywr_schema::model::Timestep::Days(d) => Self::Days(d as i64), - pywr_schema::model::Timestep::Frequency(f) => Self::Frequency(f), + pywr_v1_schema::model::Timestep::Days(d) => Self::Days(d as i64), + pywr_v1_schema::model::Timestep::Frequency(f) => Self::Frequency(f), } } } @@ -52,10 +52,10 @@ pub struct Timestepper { pub timestep: Timestep, } -impl TryFrom for Timestepper { +impl TryFrom for Timestepper { type Error = ConversionError; - fn try_from(v1: pywr_schema::model::Timestepper) -> Result { + fn try_from(v1: pywr_v1_schema::model::Timestepper) -> Result { Ok(Self { start: v1.start, end: v1.end, @@ -64,7 +64,7 @@ impl TryFrom for Timestepper { } } -impl From for crate::timestep::Timestepper { +impl From for pywr_core::timestep::Timestepper { fn from(ts: Timestepper) -> Self { let timestep = match ts.timestep { Timestep::Days(d) => d, @@ -131,8 +131,8 @@ impl PywrModel { &self, data_path: Option<&Path>, output_path: Option<&Path>, - ) -> Result<(crate::model::Model, crate::timestep::Timestepper), PywrError> { - let mut model = crate::model::Model::default(); + ) -> Result<(pywr_core::model::Model, pywr_core::timestep::Timestepper), SchemaError> { + let mut model = pywr_core::model::Model::default(); if let Some(scenarios) = &self.scenarios { for scenario in scenarios { @@ -153,7 +153,13 @@ impl PywrModel { if let Err(e) = node.add_to_model(&mut model, &tables, data_path) { // Adding the node failed! match e { - PywrError::NodeNotFound(_) => failed_nodes.push(node), + SchemaError::PywrCore(core_err) => match core_err { + // And it failed because another node was not found. + // Let's try to load more nodes and see if this one can tried + // again later + PywrError::NodeNotFound(_) => failed_nodes.push(node), + _ => return Err(SchemaError::PywrCore(core_err)), + }, _ => return Err(e), } }; @@ -161,9 +167,7 @@ impl PywrModel { if failed_nodes.len() == n { // Could not load any nodes; must be a circular reference - return Err(PywrError::SchemaLoad( - "Circular reference in node definitions.".to_string(), - )); + return Err(SchemaError::CircularNodeReference); } remaining_nodes = failed_nodes; @@ -173,10 +177,10 @@ impl PywrModel { for edge in &self.edges { let from_node = self .get_node_by_name(edge.from_node.as_str()) - .ok_or_else(|| PywrError::NodeNotFound(edge.from_node.clone()))?; + .ok_or_else(|| SchemaError::NodeNotFound(edge.from_node.clone()))?; let to_node = self .get_node_by_name(edge.to_node.as_str()) - .ok_or_else(|| PywrError::NodeNotFound(edge.to_node.clone()))?; + .ok_or_else(|| SchemaError::NodeNotFound(edge.to_node.clone()))?; let from_slot = edge.from_slot.as_deref(); @@ -198,19 +202,23 @@ impl PywrModel { let n = remaining_parameters.len(); for parameter in remaining_parameters.into_iter() { if let Err(e) = parameter.add_to_model(&mut model, &tables, data_path) { - // Adding the node failed! + // Adding the parameter failed! match e { - PywrError::ParameterNotFound(_) => failed_parameters.push(parameter), + SchemaError::PywrCore(core_err) => match core_err { + // And it failed because another parameter was not found. + // Let's try to load more parameters and see if this one can tried + // again later + PywrError::ParameterNotFound(_) => failed_parameters.push(parameter), + _ => return Err(SchemaError::PywrCore(core_err)), + }, _ => return Err(e), } }; } if failed_parameters.len() == n { - // Could not load any nodes; must be a circular reference - return Err(PywrError::SchemaLoad( - "Circular reference in parameter definitions.".to_string(), - )); + // Could not load any parameters; must be a circular reference + return Err(SchemaError::CircularParameterReference); } remaining_parameters = failed_parameters; @@ -242,10 +250,10 @@ impl PywrModel { } } -impl TryFrom for PywrModel { +impl TryFrom for PywrModel { type Error = ConversionError; - fn try_from(v1: pywr_schema::PywrModel) -> Result { + fn try_from(v1: pywr_v1_schema::PywrModel) -> Result { let metadata = v1.metadata.try_into()?; let timestepper = v1.timestepper.try_into()?; @@ -291,15 +299,14 @@ impl TryFrom for PywrModel { #[cfg(test)] mod tests { use super::PywrModel; - use crate::metric::Metric; - use crate::recorders::AssertionRecorder; - use crate::schema::parameters::{ + use crate::parameters::{ AggFunc, AggregatedParameter, ConstantParameter, ConstantValue, DynamicFloatValue, MetricFloatValue, Parameter, ParameterMeta, }; - use crate::solvers::{ClpSolver, ClpSolverSettings}; - use crate::test_utils::run_all_solvers; use ndarray::{Array1, Array2, Axis}; + use pywr_core::metric::Metric; + use pywr_core::recorders::AssertionRecorder; + use pywr_core::test_utils::run_all_solvers; fn model_str() -> &'static str { include_str!("./test_models/simple1.json") @@ -318,8 +325,7 @@ mod tests { fn test_simple1_run() { let data = model_str(); let schema: PywrModel = serde_json::from_str(data).unwrap(); - let (mut model, timestepper): (crate::model::Model, crate::timestep::Timestepper) = - schema.build_model(None, None).unwrap(); + let (mut model, timestepper) = schema.build_model(None, None).unwrap(); assert_eq!(model.nodes.len(), 3); assert_eq!(model.edges.len(), 2); @@ -444,6 +450,7 @@ mod tests { ]); } // TODO this could assert a specific type of error - assert!(schema.build_model(None, None).is_ok()); + let build_result = schema.build_model(None, None); + assert!(build_result.is_ok()); } } diff --git a/src/schema/nodes/annual_virtual_storage.rs b/pywr-schema/src/nodes/annual_virtual_storage.rs similarity index 83% rename from src/schema/nodes/annual_virtual_storage.rs rename to pywr-schema/src/nodes/annual_virtual_storage.rs index 6fb09306..89e2fec6 100644 --- a/src/schema/nodes/annual_virtual_storage.rs +++ b/pywr-schema/src/nodes/annual_virtual_storage.rs @@ -1,12 +1,11 @@ -use crate::metric::Metric; -use crate::node::{ConstraintValue, StorageInitialVolume}; -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::nodes::NodeMeta; -use crate::schema::parameters::{DynamicFloatValue, TryIntoV2Parameter}; -use crate::virtual_storage::VirtualStorageReset; -use crate::PywrError; -use pywr_schema::nodes::AnnualVirtualStorageNode as AnnualVirtualStorageNodeV1; +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::nodes::NodeMeta; +use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use pywr_core::metric::Metric; +use pywr_core::node::{ConstraintValue, StorageInitialVolume}; +use pywr_core::virtual_storage::VirtualStorageReset; +use pywr_v1_schema::nodes::AnnualVirtualStorageNode as AnnualVirtualStorageNodeV1; use std::path::Path; #[derive(serde::Deserialize, serde::Serialize, Clone)] @@ -33,16 +32,16 @@ pub struct AnnualVirtualStorageNode { impl AnnualVirtualStorageNode { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { let initial_volume = if let Some(iv) = self.initial_volume { StorageInitialVolume::Absolute(iv) } else if let Some(pc) = self.initial_volume_pc { StorageInitialVolume::Proportional(pc) } else { - return Err(PywrError::MissingInitialVolume(self.meta.name.to_string())); + return Err(SchemaError::MissingInitialVolume(self.meta.name.to_string())); }; let min_volume = match &self.min_volume { @@ -87,7 +86,7 @@ impl AnnualVirtualStorageNode { vec![] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_virtual_storage_node_index_by_name(self.meta.name.as_str(), None)?; Ok(Metric::VirtualStorageVolume(idx)) } diff --git a/src/schema/nodes/core.rs b/pywr-schema/src/nodes/core.rs similarity index 89% rename from src/schema/nodes/core.rs rename to pywr-schema/src/nodes/core.rs index fc76f2fe..9ce8f97a 100644 --- a/src/schema/nodes/core.rs +++ b/pywr-schema/src/nodes/core.rs @@ -1,11 +1,10 @@ -use crate::metric::Metric; -use crate::node::{ConstraintValue, StorageInitialVolume}; -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::nodes::NodeMeta; -use crate::schema::parameters::{DynamicFloatValue, TryIntoV2Parameter}; -use crate::PywrError; -use pywr_schema::nodes::{ +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::nodes::NodeMeta; +use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use pywr_core::metric::Metric; +use pywr_core::node::{ConstraintValue, StorageInitialVolume}; +use pywr_v1_schema::nodes::{ AggregatedNode as AggregatedNodeV1, AggregatedStorageNode as AggregatedStorageNodeV1, CatchmentNode as CatchmentNodeV1, InputNode as InputNodeV1, LinkNode as LinkNodeV1, OutputNode as OutputNodeV1, ReservoirNode as ReservoirNodeV1, StorageNode as StorageNodeV1, @@ -38,17 +37,17 @@ impl InputNode { attributes } - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result<(), SchemaError> { model.add_input_node(self.meta.name.as_str(), None)?; Ok(()) } pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { if let Some(cost) = &self.cost { let value = cost.load(model, tables, data_path)?; model.set_node_cost(self.meta.name.as_str(), None, value.into())?; @@ -74,7 +73,7 @@ impl InputNode { vec![(self.meta.name.as_str(), None)] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_node_index_by_name(self.meta.name.as_str(), None)?; Ok(Metric::NodeOutFlow(idx)) } @@ -136,17 +135,17 @@ impl LinkNode { attributes } - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result<(), SchemaError> { model.add_link_node(self.meta.name.as_str(), None)?; Ok(()) } pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { if let Some(cost) = &self.cost { let value = cost.load(model, tables, data_path)?; model.set_node_cost(self.meta.name.as_str(), None, value.into())?; @@ -172,7 +171,7 @@ impl LinkNode { vec![(self.meta.name.as_str(), None)] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_node_index_by_name(self.meta.name.as_str(), None)?; Ok(Metric::NodeOutFlow(idx)) } @@ -233,17 +232,17 @@ impl OutputNode { attributes } - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result<(), SchemaError> { model.add_output_node(self.meta.name.as_str(), None)?; Ok(()) } pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { if let Some(cost) = &self.cost { let value = cost.load(model, tables, data_path)?; model.set_node_cost(self.meta.name.as_str(), None, value.into())?; @@ -270,7 +269,7 @@ impl OutputNode { vec![(self.meta.name.as_str(), None)] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_node_index_by_name(self.meta.name.as_str(), None)?; Ok(Metric::NodeInFlow(idx)) } @@ -335,16 +334,16 @@ impl StorageNode { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { let initial_volume = if let Some(iv) = self.initial_volume { StorageInitialVolume::Absolute(iv) } else if let Some(pc) = self.initial_volume_pc { StorageInitialVolume::Proportional(pc) } else { - return Err(PywrError::MissingInitialVolume(self.meta.name.to_string())); + return Err(SchemaError::MissingInitialVolume(self.meta.name.to_string())); }; let min_volume = match &self.min_volume { @@ -363,10 +362,10 @@ impl StorageNode { pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { if let Some(cost) = &self.cost { let value = cost.load(model, tables, data_path)?; model.set_node_cost(self.meta.name.as_str(), None, value.into())?; @@ -383,7 +382,7 @@ impl StorageNode { vec![(self.meta.name.as_str(), None)] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_node_index_by_name(self.meta.name.as_str(), None)?; Ok(Metric::NodeVolume(idx)) } @@ -493,17 +492,17 @@ pub struct CatchmentNode { } impl CatchmentNode { - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result<(), SchemaError> { model.add_input_node(self.meta.name.as_str(), None)?; Ok(()) } pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { if let Some(cost) = &self.cost { let value = cost.load(model, tables, data_path)?; model.set_node_cost(self.meta.name.as_str(), None, value.into())?; @@ -526,7 +525,7 @@ impl CatchmentNode { vec![(self.meta.name.as_str(), None)] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_node_index_by_name(self.meta.name.as_str(), None)?; Ok(Metric::NodeOutFlow(idx)) } @@ -571,7 +570,7 @@ pub struct AggregatedNode { } impl AggregatedNode { - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result<(), SchemaError> { let nodes = self .nodes .iter() @@ -586,10 +585,10 @@ impl AggregatedNode { pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { if let Some(max_flow) = &self.max_flow { let value = max_flow.load(model, tables, data_path)?; model.set_aggregated_node_max_flow(self.meta.name.as_str(), None, value.into())?; @@ -602,13 +601,13 @@ impl AggregatedNode { if let Some(factors) = &self.factors { let f = match factors { - Factors::Proportion { factors } => crate::aggregated_node::Factors::Proportion( + Factors::Proportion { factors } => pywr_core::aggregated_node::Factors::Proportion( factors .iter() .map(|f| f.load(model, tables, data_path)) .collect::, _>>()?, ), - Factors::Ratio { factors } => crate::aggregated_node::Factors::Ratio( + Factors::Ratio { factors } => pywr_core::aggregated_node::Factors::Ratio( factors .iter() .map(|f| f.load(model, tables, data_path)) @@ -633,7 +632,7 @@ impl AggregatedNode { vec![] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_aggregated_node_index_by_name(self.meta.name.as_str(), None)?; Ok(Metric::AggregatedNodeOutFlow(idx)) } @@ -685,7 +684,7 @@ pub struct AggregatedStorageNode { } impl AggregatedStorageNode { - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result<(), SchemaError> { let nodes = self .storage_nodes .iter() @@ -707,7 +706,7 @@ impl AggregatedStorageNode { vec![] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_aggregated_storage_node_index_by_name(self.meta.name.as_str(), None)?; Ok(Metric::AggregatedNodeVolume(idx)) } @@ -727,7 +726,7 @@ impl TryFrom for AggregatedStorageNode { #[cfg(test)] mod tests { - use crate::schema::nodes::InputNode; + use crate::nodes::InputNode; #[test] fn test_input() { diff --git a/src/schema/nodes/delay.rs b/pywr-schema/src/nodes/delay.rs similarity index 82% rename from src/schema/nodes/delay.rs rename to pywr-schema/src/nodes/delay.rs index f711bf4b..71d21ae9 100644 --- a/src/schema/nodes/delay.rs +++ b/pywr-schema/src/nodes/delay.rs @@ -1,11 +1,9 @@ -use crate::metric::Metric; -use crate::parameters::DelayParameter; -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::nodes::NodeMeta; -use crate::schema::parameters::ConstantValue; -use crate::PywrError; -use pywr_schema::nodes::DelayNode as DelayNodeV1; +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::nodes::NodeMeta; +use crate::parameters::ConstantValue; +use pywr_core::metric::Metric; +use pywr_v1_schema::nodes::DelayNode as DelayNodeV1; #[doc = svgbobdoc::transform!( /// This node is used to introduce a delay between flows entering and leaving the node. @@ -43,7 +41,7 @@ impl DelayNode { Some("outflow") } - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result<(), SchemaError> { model.add_output_node(self.meta.name.as_str(), Self::output_sub_name())?; model.add_input_node(self.meta.name.as_str(), Self::input_sub_now())?; @@ -52,14 +50,14 @@ impl DelayNode { pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { // Create the delay parameter let name = format!("{}-delay", self.meta.name.as_str()); let output_idx = model.get_node_index_by_name(self.meta.name.as_str(), Self::output_sub_name())?; let metric = Metric::NodeInFlow(output_idx); - let p = DelayParameter::new(&name, metric, self.delay, self.initial_value.load(tables)?); + let p = pywr_core::parameters::DelayParameter::new(&name, metric, self.delay, self.initial_value.load(tables)?); let delay_idx = model.add_parameter(Box::new(p))?; // Apply it as a constraint on the input node. @@ -80,7 +78,7 @@ impl DelayNode { vec![(self.meta.name.as_str(), Self::input_sub_now().map(|s| s.to_string()))] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_node_index_by_name(self.meta.name.as_str(), Self::input_sub_now().as_deref())?; Ok(Metric::NodeOutFlow(idx)) } @@ -119,12 +117,12 @@ impl TryFrom for DelayNode { #[cfg(test)] mod tests { - use crate::metric::Metric; - use crate::recorders::AssertionRecorder; - use crate::schema::model::PywrModel; - use crate::test_utils::run_all_solvers; - use crate::timestep::Timestepper; + use crate::model::PywrModel; use ndarray::{concatenate, Array2, Axis}; + use pywr_core::metric::Metric; + use pywr_core::recorders::AssertionRecorder; + use pywr_core::test_utils::run_all_solvers; + use pywr_core::timestep::Timestepper; fn model_str() -> &'static str { include_str!("../test_models/delay1.json") @@ -134,7 +132,7 @@ mod tests { fn test_model_run() { let data = model_str(); let schema: PywrModel = serde_json::from_str(data).unwrap(); - let (mut model, timestepper): (crate::model::Model, Timestepper) = schema.build_model(None, None).unwrap(); + let (mut model, timestepper): (pywr_core::model::Model, Timestepper) = schema.build_model(None, None).unwrap(); assert_eq!(model.nodes.len(), 4); assert_eq!(model.edges.len(), 2); diff --git a/src/schema/nodes/loss_link.rs b/pywr-schema/src/nodes/loss_link.rs similarity index 87% rename from src/schema/nodes/loss_link.rs rename to pywr-schema/src/nodes/loss_link.rs index dee77e90..8f9bce53 100644 --- a/src/schema/nodes/loss_link.rs +++ b/pywr-schema/src/nodes/loss_link.rs @@ -1,10 +1,9 @@ -use crate::metric::Metric; -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::nodes::NodeMeta; -use crate::schema::parameters::{DynamicFloatValue, TryIntoV2Parameter}; -use crate::PywrError; -use pywr_schema::nodes::LossLinkNode as LossLinkNodeV1; +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::nodes::NodeMeta; +use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use pywr_core::metric::Metric; +use pywr_v1_schema::nodes::LossLinkNode as LossLinkNodeV1; use std::path::Path; #[doc = svgbobdoc::transform!( @@ -43,7 +42,7 @@ impl LossLinkNode { Some("net") } - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result<(), SchemaError> { model.add_link_node(self.meta.name.as_str(), Self::net_sub_name())?; // TODO make the loss node configurable (i.e. it could be a link if a model wanted to use the loss) // The above would need to support slots in the connections. @@ -55,10 +54,10 @@ impl LossLinkNode { pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { if let Some(cost) = &self.net_cost { let value = cost.load(model, tables, data_path)?; model.set_node_cost(self.meta.name.as_str(), Self::net_sub_name(), value.into())?; @@ -90,7 +89,7 @@ impl LossLinkNode { vec![(self.meta.name.as_str(), Self::net_sub_name().map(|s| s.to_string()))] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_node_index_by_name(self.meta.name.as_str(), Self::net_sub_name().as_deref())?; Ok(Metric::NodeOutFlow(idx)) } diff --git a/src/schema/nodes/mod.rs b/pywr-schema/src/nodes/mod.rs similarity index 95% rename from src/schema/nodes/mod.rs rename to pywr-schema/src/nodes/mod.rs index 7ca05222..b7bcf6e8 100644 --- a/src/schema/nodes/mod.rs +++ b/pywr-schema/src/nodes/mod.rs @@ -11,22 +11,21 @@ mod river_split_with_gauge; mod virtual_storage; mod water_treatment_works; -use crate::metric::Metric; -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -pub use crate::schema::nodes::core::{ +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +pub use crate::nodes::core::{ AggregatedNode, AggregatedStorageNode, CatchmentNode, InputNode, LinkNode, OutputNode, StorageNode, }; -pub use crate::schema::nodes::delay::DelayNode; -pub use crate::schema::nodes::river::RiverNode; -use crate::schema::parameters::DynamicFloatValue; -use crate::PywrError; +pub use crate::nodes::delay::DelayNode; +pub use crate::nodes::river::RiverNode; +use crate::parameters::DynamicFloatValue; pub use annual_virtual_storage::AnnualVirtualStorageNode; pub use loss_link::LossLinkNode; pub use monthly_virtual_storage::MonthlyVirtualStorageNode; pub use piecewise_link::{PiecewiseLinkNode, PiecewiseLinkStep}; pub use piecewise_storage::PiecewiseStorageNode; -use pywr_schema::nodes::{ +use pywr_core::metric::Metric; +use pywr_v1_schema::nodes::{ CoreNode as CoreNodeV1, CustomNode as CustomNodeV1, Node as NodeV1, NodeMeta as NodeMetaV1, NodePosition as NodePositionV1, }; @@ -184,10 +183,10 @@ impl CoreNode { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { match self { CoreNode::Input(n) => n.add_to_model(model), CoreNode::Link(n) => n.add_to_model(model), @@ -212,10 +211,10 @@ impl CoreNode { pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { match self { CoreNode::Input(n) => n.set_constraints(model, tables, data_path), CoreNode::Link(n) => n.set_constraints(model, tables, data_path), @@ -287,7 +286,7 @@ impl CoreNode { } /// Returns the default metric for this node. - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { match self { CoreNode::Input(n) => n.default_metric(model), CoreNode::Link(n) => n.default_metric(model), @@ -349,10 +348,10 @@ impl Node { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { match self { Node::Core(n) => n.add_to_model(model, tables, data_path), Node::Custom(n) => panic!("TODO custom nodes not yet supported: {}", n.meta.name.as_str()), @@ -361,10 +360,10 @@ impl Node { pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { match self { Node::Core(n) => n.set_constraints(model, tables, data_path), Node::Custom(n) => panic!("TODO custom nodes not yet supported: {}", n.meta.name.as_str()), @@ -386,7 +385,7 @@ impl Node { } /// Returns the default metric for this node. - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { match self { Node::Core(n) => n.default_metric(model), Node::Custom(n) => panic!("TODO custom nodes not yet supported: {}", n.meta.name.as_str()), diff --git a/src/schema/nodes/monthly_virtual_storage.rs b/pywr-schema/src/nodes/monthly_virtual_storage.rs similarity index 83% rename from src/schema/nodes/monthly_virtual_storage.rs rename to pywr-schema/src/nodes/monthly_virtual_storage.rs index 27218dd2..a8810268 100644 --- a/src/schema/nodes/monthly_virtual_storage.rs +++ b/pywr-schema/src/nodes/monthly_virtual_storage.rs @@ -1,12 +1,11 @@ -use crate::metric::Metric; -use crate::node::{ConstraintValue, StorageInitialVolume}; -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::nodes::NodeMeta; -use crate::schema::parameters::{DynamicFloatValue, TryIntoV2Parameter}; -use crate::virtual_storage::VirtualStorageReset; -use crate::PywrError; -use pywr_schema::nodes::MonthlyVirtualStorageNode as MonthlyVirtualStorageNodeV1; +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::nodes::NodeMeta; +use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use pywr_core::metric::Metric; +use pywr_core::node::{ConstraintValue, StorageInitialVolume}; +use pywr_core::virtual_storage::VirtualStorageReset; +use pywr_v1_schema::nodes::MonthlyVirtualStorageNode as MonthlyVirtualStorageNodeV1; use std::path::Path; #[derive(serde::Deserialize, serde::Serialize, Clone)] @@ -31,16 +30,16 @@ pub struct MonthlyVirtualStorageNode { impl MonthlyVirtualStorageNode { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { let initial_volume = if let Some(iv) = self.initial_volume { StorageInitialVolume::Absolute(iv) } else if let Some(pc) = self.initial_volume_pc { StorageInitialVolume::Proportional(pc) } else { - return Err(PywrError::MissingInitialVolume(self.meta.name.to_string())); + return Err(SchemaError::MissingInitialVolume(self.meta.name.to_string())); }; let min_volume = match &self.min_volume { @@ -85,7 +84,7 @@ impl MonthlyVirtualStorageNode { vec![] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_virtual_storage_node_index_by_name(self.meta.name.as_str(), None)?; Ok(Metric::VirtualStorageVolume(idx)) } diff --git a/src/schema/nodes/piecewise_link.rs b/pywr-schema/src/nodes/piecewise_link.rs similarity index 86% rename from src/schema/nodes/piecewise_link.rs rename to pywr-schema/src/nodes/piecewise_link.rs index 299ef4ab..328bc5a9 100644 --- a/src/schema/nodes/piecewise_link.rs +++ b/pywr-schema/src/nodes/piecewise_link.rs @@ -1,10 +1,9 @@ -use crate::metric::Metric; -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::nodes::NodeMeta; -use crate::schema::parameters::{DynamicFloatValue, TryIntoV2Parameter}; -use crate::PywrError; -use pywr_schema::nodes::PiecewiseLinkNode as PiecewiseLinkNodeV1; +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::nodes::NodeMeta; +use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use pywr_core::metric::Metric; +use pywr_v1_schema::nodes::PiecewiseLinkNode as PiecewiseLinkNodeV1; use std::path::Path; #[derive(serde::Deserialize, serde::Serialize, Clone)] @@ -48,7 +47,7 @@ impl PiecewiseLinkNode { Some(format!("step-{i:02}")) } - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result<(), SchemaError> { // create a link node for each step for (i, _) in self.steps.iter().enumerate() { model.add_link_node(self.meta.name.as_str(), Self::step_sub_name(i).as_deref())?; @@ -58,10 +57,10 @@ impl PiecewiseLinkNode { pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { for (i, step) in self.steps.iter().enumerate() { let sub_name = Self::step_sub_name(i); @@ -99,7 +98,7 @@ impl PiecewiseLinkNode { .collect() } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let indices = self .steps .iter() @@ -155,13 +154,12 @@ impl TryFrom for PiecewiseLinkNode { #[cfg(test)] mod tests { - use crate::metric::Metric; - use crate::recorders::AssertionRecorder; - use crate::schema::model::PywrModel; - use crate::solvers::{ClpSolver, ClpSolverSettings}; - use crate::test_utils::run_all_solvers; - use crate::timestep::Timestepper; + use crate::model::PywrModel; use ndarray::Array2; + use pywr_core::metric::Metric; + use pywr_core::recorders::AssertionRecorder; + use pywr_core::test_utils::run_all_solvers; + use pywr_core::timestep::Timestepper; fn model_str() -> &'static str { include_str!("../test_models/piecewise_link1.json") @@ -171,7 +169,7 @@ mod tests { fn test_model_run() { let data = model_str(); let schema: PywrModel = serde_json::from_str(data).unwrap(); - let (mut model, timestepper): (crate::model::Model, Timestepper) = schema.build_model(None, None).unwrap(); + let (mut model, timestepper): (pywr_core::model::Model, Timestepper) = schema.build_model(None, None).unwrap(); assert_eq!(model.nodes.len(), 5); assert_eq!(model.edges.len(), 6); diff --git a/src/schema/nodes/piecewise_storage.rs b/pywr-schema/src/nodes/piecewise_storage.rs similarity index 90% rename from src/schema/nodes/piecewise_storage.rs rename to pywr-schema/src/nodes/piecewise_storage.rs index 244f0d15..98433994 100644 --- a/src/schema/nodes/piecewise_storage.rs +++ b/pywr-schema/src/nodes/piecewise_storage.rs @@ -1,9 +1,9 @@ -use crate::metric::{Metric, VolumeBetweenControlCurves}; -use crate::node::{ConstraintValue, StorageInitialVolume}; -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::nodes::NodeMeta; -use crate::schema::parameters::DynamicFloatValue; -use crate::PywrError; +use crate::data_tables::LoadedTableCollection; +use crate::error::SchemaError; +use crate::nodes::NodeMeta; +use crate::parameters::DynamicFloatValue; +use pywr_core::metric::{Metric, VolumeBetweenControlCurves}; +use pywr_core::node::{ConstraintValue, StorageInitialVolume}; use std::path::Path; #[derive(serde::Deserialize, serde::Serialize, Clone)] @@ -58,10 +58,10 @@ impl PiecewiseStorageNode { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { // These are the min and max volume of the overall node let max_volume = self.max_volume.load(model, tables, data_path)?; @@ -149,10 +149,10 @@ impl PiecewiseStorageNode { pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { for (i, step) in self.steps.iter().enumerate() { let sub_name = Self::step_sub_name(i); @@ -172,7 +172,7 @@ impl PiecewiseStorageNode { vec![(self.meta.name.as_str(), Self::step_sub_name(self.steps.len()))] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_aggregated_storage_node_index_by_name(self.meta.name.as_str(), None)?; Ok(Metric::AggregatedNodeVolume(idx)) } @@ -180,13 +180,12 @@ impl PiecewiseStorageNode { #[cfg(test)] mod tests { - use crate::metric::Metric; - use crate::recorders::AssertionRecorder; - use crate::schema::model::PywrModel; - use crate::solvers::{ClpSolver, ClpSolverSettings}; - use crate::test_utils::run_all_solvers; - use crate::timestep::Timestepper; + use crate::model::PywrModel; use ndarray::Array2; + use pywr_core::metric::Metric; + use pywr_core::recorders::AssertionRecorder; + use pywr_core::test_utils::run_all_solvers; + use pywr_core::timestep::Timestepper; fn piecewise_storage1_str() -> &'static str { include_str!("../test_models/piecewise_storage1.json") @@ -201,7 +200,7 @@ mod tests { fn test_piecewise_storage1() { let data = piecewise_storage1_str(); let schema: PywrModel = serde_json::from_str(data).unwrap(); - let (mut model, timestepper): (crate::model::Model, Timestepper) = schema.build_model(None, None).unwrap(); + let (mut model, timestepper): (pywr_core::model::Model, Timestepper) = schema.build_model(None, None).unwrap(); assert_eq!(model.nodes.len(), 5); assert_eq!(model.edges.len(), 6); @@ -242,7 +241,7 @@ mod tests { fn test_piecewise_storage2() { let data = piecewise_storage2_str(); let schema: PywrModel = serde_json::from_str(data).unwrap(); - let (mut model, timestepper): (crate::model::Model, Timestepper) = schema.build_model(None, None).unwrap(); + let (mut model, timestepper): (pywr_core::model::Model, Timestepper) = schema.build_model(None, None).unwrap(); assert_eq!(model.nodes.len(), 5); assert_eq!(model.edges.len(), 6); diff --git a/src/schema/nodes/river.rs b/pywr-schema/src/nodes/river.rs similarity index 79% rename from src/schema/nodes/river.rs rename to pywr-schema/src/nodes/river.rs index 38383089..563e6b40 100644 --- a/src/schema/nodes/river.rs +++ b/pywr-schema/src/nodes/river.rs @@ -1,9 +1,8 @@ -use crate::metric::Metric; -use crate::schema::error::ConversionError; -use crate::schema::nodes::NodeMeta; -use crate::schema::parameters::DynamicFloatValue; -use crate::PywrError; -use pywr_schema::nodes::LinkNode as LinkNodeV1; +use crate::error::{ConversionError, SchemaError}; +use crate::nodes::NodeMeta; +use crate::parameters::DynamicFloatValue; +use pywr_core::metric::Metric; +use pywr_v1_schema::nodes::LinkNode as LinkNodeV1; use std::collections::HashMap; #[derive(serde::Deserialize, serde::Serialize, Clone)] @@ -17,7 +16,7 @@ impl RiverNode { HashMap::new() } - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result<(), SchemaError> { model.add_link_node(self.meta.name.as_str(), None)?; Ok(()) } @@ -29,7 +28,7 @@ impl RiverNode { vec![(self.meta.name.as_str(), None)] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_node_index_by_name(self.meta.name.as_str(), None)?; Ok(Metric::NodeOutFlow(idx)) } diff --git a/src/schema/nodes/river_gauge.rs b/pywr-schema/src/nodes/river_gauge.rs similarity index 86% rename from src/schema/nodes/river_gauge.rs rename to pywr-schema/src/nodes/river_gauge.rs index 300740be..ad9593fe 100644 --- a/src/schema/nodes/river_gauge.rs +++ b/pywr-schema/src/nodes/river_gauge.rs @@ -1,10 +1,9 @@ -use crate::metric::Metric; -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::nodes::NodeMeta; -use crate::schema::parameters::{DynamicFloatValue, TryIntoV2Parameter}; -use crate::PywrError; -use pywr_schema::nodes::RiverGaugeNode as RiverGaugeNodeV1; +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::nodes::NodeMeta; +use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use pywr_core::metric::Metric; +use pywr_v1_schema::nodes::RiverGaugeNode as RiverGaugeNodeV1; use std::path::Path; #[doc = svgbobdoc::transform!( @@ -39,7 +38,7 @@ impl RiverGaugeNode { Some("bypass") } - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result<(), SchemaError> { model.add_link_node(self.meta.name.as_str(), Self::mrf_sub_name())?; model.add_link_node(self.meta.name.as_str(), Self::bypass_sub_name())?; @@ -48,10 +47,10 @@ impl RiverGaugeNode { pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { // MRF applies as a maximum on the MRF node. if let Some(cost) = &self.mrf_cost { let value = cost.load(model, tables, data_path)?; @@ -80,7 +79,7 @@ impl RiverGaugeNode { ] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let indices = vec![ model.get_node_index_by_name(self.meta.name.as_str(), Self::mrf_sub_name())?, model.get_node_index_by_name(self.meta.name.as_str(), Self::bypass_sub_name())?, @@ -118,10 +117,9 @@ impl TryFrom for RiverGaugeNode { #[cfg(test)] mod tests { - use crate::schema::model::PywrModel; - use crate::solvers::{ClpSolver, ClpSolverSettings}; - use crate::test_utils::run_all_solvers; - use crate::timestep::Timestepper; + use crate::model::PywrModel; + use pywr_core::test_utils::run_all_solvers; + use pywr_core::timestep::Timestepper; fn model_str() -> &'static str { r#" @@ -190,7 +188,7 @@ mod tests { fn test_model_run() { let data = model_str(); let schema: PywrModel = serde_json::from_str(data).unwrap(); - let (model, timestepper): (crate::model::Model, Timestepper) = schema.build_model(None, None).unwrap(); + let (model, timestepper): (pywr_core::model::Model, Timestepper) = schema.build_model(None, None).unwrap(); assert_eq!(model.nodes.len(), 5); assert_eq!(model.edges.len(), 6); diff --git a/src/schema/nodes/river_split_with_gauge.rs b/pywr-schema/src/nodes/river_split_with_gauge.rs similarity index 87% rename from src/schema/nodes/river_split_with_gauge.rs rename to pywr-schema/src/nodes/river_split_with_gauge.rs index f09a85dc..d8ad1ec2 100644 --- a/src/schema/nodes/river_split_with_gauge.rs +++ b/pywr-schema/src/nodes/river_split_with_gauge.rs @@ -1,12 +1,11 @@ -use crate::aggregated_node::Factors; -use crate::metric::Metric; -use crate::node::NodeIndex; -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::nodes::NodeMeta; -use crate::schema::parameters::{DynamicFloatValue, TryIntoV2Parameter}; -use crate::PywrError; -use pywr_schema::nodes::RiverSplitWithGaugeNode as RiverSplitWithGaugeNodeV1; +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::nodes::NodeMeta; +use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use pywr_core::aggregated_node::Factors; +use pywr_core::metric::Metric; +use pywr_core::node::NodeIndex; +use pywr_v1_schema::nodes::RiverSplitWithGaugeNode as RiverSplitWithGaugeNodeV1; use std::path::Path; #[doc = svgbobdoc::transform!( @@ -56,7 +55,7 @@ impl RiverSplitWithGaugeNode { Some(format!("split-agg-{i}")) } - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result<(), SchemaError> { // TODO do this properly model.add_link_node(self.meta.name.as_str(), Self::mrf_sub_name())?; let bypass_idx = model.add_link_node(self.meta.name.as_str(), Self::bypass_sub_name())?; @@ -79,10 +78,10 @@ impl RiverSplitWithGaugeNode { pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { // MRF applies as a maximum on the MRF node. if let Some(cost) = &self.mrf_cost { let value = cost.load(model, tables, data_path)?; @@ -143,7 +142,7 @@ impl RiverSplitWithGaugeNode { } } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let mut indices = vec![ model.get_node_index_by_name(self.meta.name.as_str(), Self::mrf_sub_name())?, model.get_node_index_by_name(self.meta.name.as_str(), Self::bypass_sub_name())?, @@ -208,10 +207,9 @@ impl TryFrom for RiverSplitWithGaugeNode { #[cfg(test)] mod tests { - use crate::schema::model::PywrModel; - use crate::solvers::{ClpSolver, ClpSolverSettings}; - use crate::test_utils::run_all_solvers; - use crate::timestep::Timestepper; + use crate::model::PywrModel; + use pywr_core::test_utils::run_all_solvers; + use pywr_core::timestep::Timestepper; fn model_str() -> &'static str { include_str!("../test_models/river_split_with_gauge1.json") @@ -230,7 +228,7 @@ mod tests { fn test_model_run() { let data = model_str(); let schema: PywrModel = serde_json::from_str(data).unwrap(); - let (model, timestepper): (crate::model::Model, Timestepper) = schema.build_model(None, None).unwrap(); + let (model, timestepper): (pywr_core::model::Model, Timestepper) = schema.build_model(None, None).unwrap(); assert_eq!(model.nodes.len(), 5); assert_eq!(model.edges.len(), 6); diff --git a/src/schema/nodes/virtual_storage.rs b/pywr-schema/src/nodes/virtual_storage.rs similarity index 82% rename from src/schema/nodes/virtual_storage.rs rename to pywr-schema/src/nodes/virtual_storage.rs index 00455edd..6ee0696e 100644 --- a/src/schema/nodes/virtual_storage.rs +++ b/pywr-schema/src/nodes/virtual_storage.rs @@ -1,12 +1,11 @@ -use crate::metric::Metric; -use crate::node::{ConstraintValue, StorageInitialVolume}; -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::nodes::NodeMeta; -use crate::schema::parameters::{DynamicFloatValue, TryIntoV2Parameter}; -use crate::virtual_storage::VirtualStorageReset; -use crate::PywrError; -use pywr_schema::nodes::VirtualStorageNode as VirtualStorageNodeV1; +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::nodes::NodeMeta; +use crate::parameters::{DynamicFloatValue, TryIntoV2Parameter}; +use pywr_core::metric::Metric; +use pywr_core::node::{ConstraintValue, StorageInitialVolume}; +use pywr_core::virtual_storage::VirtualStorageReset; +use pywr_v1_schema::nodes::VirtualStorageNode as VirtualStorageNodeV1; use std::path::Path; #[derive(serde::Deserialize, serde::Serialize, Clone)] @@ -25,16 +24,16 @@ pub struct VirtualStorageNode { impl VirtualStorageNode { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { let initial_volume = if let Some(iv) = self.initial_volume { StorageInitialVolume::Absolute(iv) } else if let Some(pc) = self.initial_volume_pc { StorageInitialVolume::Proportional(pc) } else { - return Err(PywrError::MissingInitialVolume(self.meta.name.to_string())); + return Err(SchemaError::MissingInitialVolume(self.meta.name.to_string())); }; let min_volume = match &self.min_volume { @@ -77,7 +76,7 @@ impl VirtualStorageNode { vec![] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_virtual_storage_node_index_by_name(self.meta.name.as_str(), None)?; Ok(Metric::VirtualStorageVolume(idx)) } diff --git a/src/schema/nodes/water_treatment_works.rs b/pywr-schema/src/nodes/water_treatment_works.rs similarity index 93% rename from src/schema/nodes/water_treatment_works.rs rename to pywr-schema/src/nodes/water_treatment_works.rs index 29cac446..f0d67cd2 100644 --- a/src/schema/nodes/water_treatment_works.rs +++ b/pywr-schema/src/nodes/water_treatment_works.rs @@ -1,10 +1,10 @@ -use crate::aggregated_node::Factors; -use crate::metric::Metric; -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::nodes::NodeMeta; -use crate::schema::parameters::DynamicFloatValue; -use crate::PywrError; +use crate::data_tables::LoadedTableCollection; +use crate::error::SchemaError; +use crate::nodes::NodeMeta; +use crate::parameters::DynamicFloatValue; use num::Zero; +use pywr_core::aggregated_node::Factors; +use pywr_core::metric::Metric; use std::path::Path; #[doc = svgbobdoc::transform!( @@ -74,7 +74,7 @@ impl WaterTreatmentWorks { Some("net_above_soft_min_flow") } - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result<(), PywrError> { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result<(), SchemaError> { let idx_net = model.add_link_node(self.meta.name.as_str(), Self::net_sub_name())?; let idx_soft_min_flow = model.add_link_node(self.meta.name.as_str(), Self::net_soft_min_flow_sub_name())?; let idx_above_soft_min_flow = @@ -100,10 +100,10 @@ impl WaterTreatmentWorks { pub fn set_constraints( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result<(), PywrError> { + ) -> Result<(), SchemaError> { if let Some(cost) = &self.cost { let value = cost.load(model, tables, data_path)?; model.set_node_cost(self.meta.name.as_str(), Self::net_sub_name(), value.into())?; @@ -187,7 +187,7 @@ impl WaterTreatmentWorks { ] } - pub fn default_metric(&self, model: &crate::model::Model) -> Result { + pub fn default_metric(&self, model: &pywr_core::model::Model) -> Result { let idx = model.get_node_index_by_name(self.meta.name.as_str(), Self::net_sub_name().as_deref())?; Ok(Metric::NodeOutFlow(idx)) } @@ -195,13 +195,12 @@ impl WaterTreatmentWorks { #[cfg(test)] mod tests { - use crate::metric::Metric; - use crate::recorders::AssertionRecorder; - use crate::schema::model::PywrModel; - use crate::schema::nodes::WaterTreatmentWorks; - use crate::solvers::{ClpSolver, ClpSolverSettings}; - use crate::test_utils::run_all_solvers; + use crate::model::PywrModel; + use crate::nodes::WaterTreatmentWorks; use ndarray::Array2; + use pywr_core::metric::Metric; + use pywr_core::recorders::AssertionRecorder; + use pywr_core::test_utils::run_all_solvers; #[test] fn test_wtw_schema_load() { diff --git a/src/schema/outputs/csv.rs b/pywr-schema/src/outputs/csv.rs similarity index 78% rename from src/schema/outputs/csv.rs rename to pywr-schema/src/outputs/csv.rs index 43b7b800..8853f00e 100644 --- a/src/schema/outputs/csv.rs +++ b/pywr-schema/src/outputs/csv.rs @@ -1,5 +1,5 @@ -use crate::recorders::CSVRecorder; -use crate::PywrError; +use crate::error::SchemaError; +use pywr_core::recorders::CSVRecorder; use std::path::{Path, PathBuf}; #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] @@ -10,7 +10,11 @@ pub struct CsvOutput { } impl CsvOutput { - pub fn add_to_model(&self, model: &mut crate::model::Model, output_path: Option<&Path>) -> Result<(), PywrError> { + pub fn add_to_model( + &self, + model: &mut pywr_core::model::Model, + output_path: Option<&Path>, + ) -> Result<(), SchemaError> { let filename = match (output_path, self.filename.is_relative()) { (Some(odir), true) => odir.join(&self.filename), _ => self.filename.to_path_buf(), @@ -27,8 +31,8 @@ impl CsvOutput { #[cfg(test)] mod tests { - use crate::schema::PywrModel; - use crate::solvers::{ClpSolver, ClpSolverSettings}; + use crate::PywrModel; + use pywr_core::solvers::{ClpSolver, ClpSolverSettings}; use tempfile::TempDir; fn model_str() -> &'static str { @@ -52,8 +56,7 @@ mod tests { let temp_dir = TempDir::new().unwrap(); - let (model, timestepper): (crate::model::Model, crate::timestep::Timestepper) = - schema.build_model(None, Some(temp_dir.path())).unwrap(); + let (model, timestepper) = schema.build_model(None, Some(temp_dir.path())).unwrap(); model .run::(×tepper, &ClpSolverSettings::default()) diff --git a/src/schema/outputs/hdf.rs b/pywr-schema/src/outputs/hdf.rs similarity index 78% rename from src/schema/outputs/hdf.rs rename to pywr-schema/src/outputs/hdf.rs index 07f87399..a48ab6fd 100644 --- a/src/schema/outputs/hdf.rs +++ b/pywr-schema/src/outputs/hdf.rs @@ -1,5 +1,5 @@ -use crate::recorders::HDF5Recorder; -use crate::PywrError; +use crate::error::SchemaError; +use pywr_core::recorders::HDF5Recorder; use std::path::{Path, PathBuf}; #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] @@ -11,7 +11,11 @@ pub struct Hdf5Output { } impl Hdf5Output { - pub fn add_to_model(&self, model: &mut crate::model::Model, output_path: Option<&Path>) -> Result<(), PywrError> { + pub fn add_to_model( + &self, + model: &mut pywr_core::model::Model, + output_path: Option<&Path>, + ) -> Result<(), SchemaError> { let filename = match (output_path, self.filename.is_relative()) { (Some(odir), true) => odir.join(&self.filename), _ => self.filename.to_path_buf(), @@ -29,8 +33,8 @@ impl Hdf5Output { #[cfg(test)] mod tests { - use crate::schema::PywrModel; - use crate::solvers::{ClpSolver, ClpSolverSettings}; + use crate::PywrModel; + use pywr_core::solvers::{ClpSolver, ClpSolverSettings}; use tempfile::TempDir; fn model_str() -> &'static str { @@ -54,8 +58,7 @@ mod tests { let temp_dir = TempDir::new().unwrap(); - let (model, timestepper): (crate::model::Model, crate::timestep::Timestepper) = - schema.build_model(None, Some(temp_dir.path())).unwrap(); + let (model, timestepper) = schema.build_model(None, Some(temp_dir.path())).unwrap(); model .run::(×tepper, &ClpSolverSettings::default()) diff --git a/src/schema/outputs/mod.rs b/pywr-schema/src/outputs/mod.rs similarity index 69% rename from src/schema/outputs/mod.rs rename to pywr-schema/src/outputs/mod.rs index 4a984df9..38b623a3 100644 --- a/src/schema/outputs/mod.rs +++ b/pywr-schema/src/outputs/mod.rs @@ -2,7 +2,7 @@ mod csv; mod hdf; pub use self::csv::CsvOutput; -use crate::PywrError; +use crate::error::SchemaError; pub use hdf::Hdf5Output; use std::path::Path; @@ -14,7 +14,11 @@ pub enum Output { } impl Output { - pub fn add_to_model(&self, model: &mut crate::model::Model, output_path: Option<&Path>) -> Result<(), PywrError> { + pub fn add_to_model( + &self, + model: &mut pywr_core::model::Model, + output_path: Option<&Path>, + ) -> Result<(), SchemaError> { match self { Self::CSV(o) => o.add_to_model(model, output_path), Self::HDF5(o) => o.add_to_model(model, output_path), diff --git a/src/schema/parameters/aggregated.rs b/pywr-schema/src/parameters/aggregated.rs similarity index 83% rename from src/schema/parameters/aggregated.rs rename to pywr-schema/src/parameters/aggregated.rs index 2a9b48ac..0be50de7 100644 --- a/src/schema/parameters/aggregated.rs +++ b/pywr-schema/src/parameters/aggregated.rs @@ -1,11 +1,11 @@ -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::parameters::{ +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::parameters::{ DynamicFloatValue, DynamicFloatValueType, DynamicIndexValue, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter, }; -use crate::{IndexParameterIndex, ParameterIndex, PywrError}; -use pywr_schema::parameters::{ +use pywr_core::parameters::{IndexParameterIndex, ParameterIndex}; +use pywr_v1_schema::parameters::{ AggFunc as AggFuncV1, AggregatedIndexParameter as AggregatedIndexParameterV1, AggregatedParameter as AggregatedParameterV1, IndexAggFunc as IndexAggFuncV1, }; @@ -22,13 +22,13 @@ pub enum AggFunc { Min, } -impl From for crate::parameters::AggFunc { +impl From for pywr_core::parameters::AggFunc { fn from(value: AggFunc) -> Self { match value { - AggFunc::Sum => crate::parameters::AggFunc::Sum, - AggFunc::Product => crate::parameters::AggFunc::Product, - AggFunc::Max => crate::parameters::AggFunc::Max, - AggFunc::Min => crate::parameters::AggFunc::Min, + AggFunc::Sum => pywr_core::parameters::AggFunc::Sum, + AggFunc::Product => pywr_core::parameters::AggFunc::Product, + AggFunc::Max => pywr_core::parameters::AggFunc::Max, + AggFunc::Min => pywr_core::parameters::AggFunc::Min, } } } @@ -91,19 +91,19 @@ impl AggregatedParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let metrics = self .metrics .iter() .map(|v| v.load(model, tables, data_path)) .collect::, _>>()?; - let p = crate::parameters::AggregatedParameter::new(&self.meta.name, &metrics, self.agg_func.into()); + let p = pywr_core::parameters::AggregatedParameter::new(&self.meta.name, &metrics, self.agg_func.into()); - model.add_parameter(Box::new(p)) + Ok(model.add_parameter(Box::new(p))?) } } @@ -144,15 +144,15 @@ pub enum IndexAggFunc { All, } -impl From for crate::parameters::AggIndexFunc { +impl From for pywr_core::parameters::AggIndexFunc { fn from(value: IndexAggFunc) -> Self { match value { - IndexAggFunc::Sum => crate::parameters::AggIndexFunc::Sum, - IndexAggFunc::Product => crate::parameters::AggIndexFunc::Product, - IndexAggFunc::Max => crate::parameters::AggIndexFunc::Max, - IndexAggFunc::Min => crate::parameters::AggIndexFunc::Min, - IndexAggFunc::Any => crate::parameters::AggIndexFunc::Any, - IndexAggFunc::All => crate::parameters::AggIndexFunc::All, + IndexAggFunc::Sum => pywr_core::parameters::AggIndexFunc::Sum, + IndexAggFunc::Product => pywr_core::parameters::AggIndexFunc::Product, + IndexAggFunc::Max => pywr_core::parameters::AggIndexFunc::Max, + IndexAggFunc::Min => pywr_core::parameters::AggIndexFunc::Min, + IndexAggFunc::Any => pywr_core::parameters::AggIndexFunc::Any, + IndexAggFunc::All => pywr_core::parameters::AggIndexFunc::All, } } } @@ -195,19 +195,19 @@ impl AggregatedIndexParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let parameters = self .parameters .iter() .map(|v| v.load(model, tables, data_path)) .collect::, _>>()?; - let p = crate::parameters::AggregatedIndexParameter::new(&self.meta.name, parameters, self.agg_func.into()); + let p = pywr_core::parameters::AggregatedIndexParameter::new(&self.meta.name, parameters, self.agg_func.into()); - model.add_index_parameter(Box::new(p)) + Ok(model.add_index_parameter(Box::new(p))?) } } @@ -238,8 +238,8 @@ impl TryFromV1Parameter for AggregatedIndexParameter #[cfg(test)] mod tests { - use crate::schema::parameters::aggregated::AggregatedParameter; - use crate::schema::parameters::{DynamicFloatValue, DynamicFloatValueType, MetricFloatValue, Parameter}; + use crate::parameters::aggregated::AggregatedParameter; + use crate::parameters::{DynamicFloatValue, DynamicFloatValueType, MetricFloatValue, Parameter}; #[test] fn test_aggregated() { diff --git a/src/schema/parameters/asymmetric_switch.rs b/pywr-schema/src/parameters/asymmetric_switch.rs similarity index 79% rename from src/schema/parameters/asymmetric_switch.rs rename to pywr-schema/src/parameters/asymmetric_switch.rs index c5f2d958..e27aad0b 100644 --- a/src/schema/parameters/asymmetric_switch.rs +++ b/pywr-schema/src/parameters/asymmetric_switch.rs @@ -1,10 +1,10 @@ -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::parameters::{ +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::parameters::{ DynamicFloatValueType, DynamicIndexValue, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter, }; -use crate::{IndexParameterIndex, PywrError}; -use pywr_schema::parameters::AsymmetricSwitchIndexParameter as AsymmetricSwitchIndexParameterV1; +use pywr_core::parameters::IndexParameterIndex; +use pywr_v1_schema::parameters::AsymmetricSwitchIndexParameter as AsymmetricSwitchIndexParameterV1; use std::collections::HashMap; use std::path::Path; @@ -26,20 +26,20 @@ impl AsymmetricSwitchIndexParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let on_index_parameter = self.on_index_parameter.load(model, tables, data_path)?; let off_index_parameter = self.off_index_parameter.load(model, tables, data_path)?; - let p = crate::parameters::AsymmetricSwitchIndexParameter::new( + let p = pywr_core::parameters::AsymmetricSwitchIndexParameter::new( &self.meta.name, on_index_parameter, off_index_parameter, ); - model.add_index_parameter(Box::new(p)) + Ok(model.add_index_parameter(Box::new(p))?) } } diff --git a/src/schema/parameters/control_curves.rs b/pywr-schema/src/parameters/control_curves.rs similarity index 90% rename from src/schema/parameters/control_curves.rs rename to pywr-schema/src/parameters/control_curves.rs index e9f0f8dc..424927d9 100644 --- a/src/schema/parameters/control_curves.rs +++ b/pywr-schema/src/parameters/control_curves.rs @@ -1,10 +1,10 @@ -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::parameters::{ +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::parameters::{ DynamicFloatValue, DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter, }; -use crate::{IndexParameterIndex, ParameterIndex, PywrError}; -use pywr_schema::parameters::{ +use pywr_core::parameters::{IndexParameterIndex, ParameterIndex}; +use pywr_v1_schema::parameters::{ ControlCurveIndexParameter as ControlCurveIndexParameterV1, ControlCurveInterpolatedParameter as ControlCurveInterpolatedParameterV1, ControlCurveParameter as ControlCurveParameterV1, @@ -38,10 +38,10 @@ impl ControlCurveInterpolatedParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let metric = model.get_storage_node_metric(&self.storage_node, None, true)?; let control_curves = self @@ -56,8 +56,8 @@ impl ControlCurveInterpolatedParameter { .map(|val| val.load(model, tables, data_path)) .collect::>()?; - let p = crate::parameters::InterpolatedParameter::new(&self.meta.name, metric, control_curves, values); - model.add_parameter(Box::new(p)) + let p = pywr_core::parameters::InterpolatedParameter::new(&self.meta.name, metric, control_curves, values); + Ok(model.add_parameter(Box::new(p))?) } } @@ -121,10 +121,10 @@ impl ControlCurveIndexParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let metric = model.get_storage_node_metric(&self.storage_node, None, true)?; let control_curves = self @@ -133,8 +133,8 @@ impl ControlCurveIndexParameter { .map(|cc| cc.load(model, tables, data_path)) .collect::>()?; - let p = crate::parameters::ControlCurveIndexParameter::new(&self.meta.name, metric, control_curves); - model.add_index_parameter(Box::new(p)) + let p = pywr_core::parameters::ControlCurveIndexParameter::new(&self.meta.name, metric, control_curves); + Ok(model.add_index_parameter(Box::new(p))?) } } @@ -232,10 +232,10 @@ impl ControlCurveParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let metric = model.get_storage_node_metric(&self.storage_node, None, true)?; let control_curves = self @@ -250,8 +250,8 @@ impl ControlCurveParameter { .map(|val| val.load(model, tables, data_path)) .collect::>()?; - let p = crate::parameters::ControlCurveParameter::new(&self.meta.name, metric, control_curves, values); - model.add_parameter(Box::new(p)) + let p = pywr_core::parameters::ControlCurveParameter::new(&self.meta.name, metric, control_curves, values); + Ok(model.add_parameter(Box::new(p))?) } } @@ -330,10 +330,10 @@ impl ControlCurvePiecewiseInterpolatedParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let metric = model.get_storage_node_metric(&self.storage_node, None, true)?; let control_curves = self @@ -347,7 +347,7 @@ impl ControlCurvePiecewiseInterpolatedParameter { Some(values) => values.clone(), }; - let p = crate::parameters::PiecewiseInterpolatedParameter::new( + let p = pywr_core::parameters::PiecewiseInterpolatedParameter::new( &self.meta.name, metric, control_curves, @@ -355,7 +355,7 @@ impl ControlCurvePiecewiseInterpolatedParameter { self.maximum.unwrap_or(1.0), self.minimum.unwrap_or(0.0), ); - model.add_parameter(Box::new(p)) + Ok(model.add_parameter(Box::new(p))?) } } @@ -397,8 +397,8 @@ impl TryFromV1Parameter for Contro #[cfg(test)] mod tests { - use crate::schema::parameters::control_curves::ControlCurvePiecewiseInterpolatedParameter; - use crate::schema::parameters::DynamicFloatValueType; + use crate::parameters::control_curves::ControlCurvePiecewiseInterpolatedParameter; + use crate::parameters::DynamicFloatValueType; #[test] fn test_control_curve_piecewise_interpolated() { diff --git a/src/schema/parameters/core.rs b/pywr-schema/src/parameters/core.rs similarity index 84% rename from src/schema/parameters/core.rs rename to pywr-schema/src/parameters/core.rs index 61b7650e..834d1b8e 100644 --- a/src/schema/parameters/core.rs +++ b/pywr-schema/src/parameters/core.rs @@ -1,11 +1,11 @@ -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::parameters::{ +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::parameters::{ ConstantValue, DynamicFloatValue, DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter, }; -use crate::{ParameterIndex, PywrError}; -use pywr_schema::parameters::{ +use pywr_core::parameters::ParameterIndex; +use pywr_v1_schema::parameters::{ ConstantParameter as ConstantParameterV1, DivisionParameter as DivisionParameterV1, MaxParameter as MaxParameterV1, MinParameter as MinParameterV1, NegativeParameter as NegativeParameterV1, }; @@ -25,7 +25,7 @@ pub enum ActivationFunction { /// A unit or null transformation. /// /// ```rust - /// # use pywr::schema::parameters::ActivationFunction; + /// # use pywr_schema::parameters::ActivationFunction; /// let data = r#" /// { /// "type": "Unit", @@ -39,7 +39,7 @@ pub enum ActivationFunction { /// A linear rectifier function, or ramp function. /// /// ```rust - /// # use pywr::schema::parameters::ActivationFunction; + /// # use pywr_schema::parameters::ActivationFunction; /// let data = r#" /// { /// "type": "Rectifier", @@ -60,7 +60,7 @@ pub enum ActivationFunction { /// A binary-step function. /// /// ```rust - /// # use pywr::schema::parameters::ActivationFunction; + /// # use pywr_schema::parameters::ActivationFunction; /// let data = r#" /// { /// "type": "BinaryStep", @@ -79,7 +79,7 @@ pub enum ActivationFunction { /// A logistic, or S, function. /// /// ```rust - /// # use pywr::schema::parameters::ActivationFunction; + /// # use pywr_schema::parameters::ActivationFunction; /// let data = r#" /// { /// "type": "Logistic", @@ -92,20 +92,22 @@ pub enum ActivationFunction { Logistic { growth_rate: f64, max: f64 }, } -impl Into for ActivationFunction { - fn into(self) -> crate::parameters::ActivationFunction { +impl Into for ActivationFunction { + fn into(self) -> pywr_core::parameters::ActivationFunction { match self { - Self::Unit { min, max } => crate::parameters::ActivationFunction::Unit { min, max }, - Self::Rectifier { min, max, off_value } => crate::parameters::ActivationFunction::Rectifier { + Self::Unit { min, max } => pywr_core::parameters::ActivationFunction::Unit { min, max }, + Self::Rectifier { min, max, off_value } => pywr_core::parameters::ActivationFunction::Rectifier { min, max, neg_value: off_value.unwrap_or(0.0), }, - Self::BinaryStep { on_value, off_value } => crate::parameters::ActivationFunction::BinaryStep { + Self::BinaryStep { on_value, off_value } => pywr_core::parameters::ActivationFunction::BinaryStep { pos_value: on_value, neg_value: off_value.unwrap_or(0.0), }, - Self::Logistic { growth_rate, max } => crate::parameters::ActivationFunction::Logistic { growth_rate, max }, + Self::Logistic { growth_rate, max } => { + pywr_core::parameters::ActivationFunction::Logistic { growth_rate, max } + } } } } @@ -162,9 +164,9 @@ impl ConstantParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, - ) -> Result { + ) -> Result { let variable = match &self.variable { None => None, Some(v) => { @@ -177,8 +179,8 @@ impl ConstantParameter { } }; - let p = crate::parameters::ConstantParameter::new(&self.meta.name, self.value.load(tables)?, variable); - model.add_parameter(Box::new(p)) + let p = pywr_core::parameters::ConstantParameter::new(&self.meta.name, self.value.load(tables)?, variable); + Ok(model.add_parameter(Box::new(p))?) } } @@ -227,15 +229,15 @@ impl MaxParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let idx = self.parameter.load(model, tables, data_path)?; let threshold = self.threshold.unwrap_or(0.0); - let p = crate::parameters::MaxParameter::new(&self.meta.name, idx, threshold); - model.add_parameter(Box::new(p)) + let p = pywr_core::parameters::MaxParameter::new(&self.meta.name, idx, threshold); + Ok(model.add_parameter(Box::new(p))?) } } @@ -297,15 +299,15 @@ impl DivisionParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let n = self.numerator.load(model, tables, data_path)?; let d = self.denominator.load(model, tables, data_path)?; - let p = crate::parameters::DivisionParameter::new(&self.meta.name, n, d); - model.add_parameter(Box::new(p)) + let p = pywr_core::parameters::DivisionParameter::new(&self.meta.name, n, d); + Ok(model.add_parameter(Box::new(p))?) } } @@ -365,15 +367,15 @@ impl MinParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let idx = self.parameter.load(model, tables, data_path)?; let threshold = self.threshold.unwrap_or(0.0); - let p = crate::parameters::MinParameter::new(&self.meta.name, idx, threshold); - model.add_parameter(Box::new(p)) + let p = pywr_core::parameters::MinParameter::new(&self.meta.name, idx, threshold); + Ok(model.add_parameter(Box::new(p))?) } } @@ -417,14 +419,14 @@ impl NegativeParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let idx = self.parameter.load(model, tables, data_path)?; - let p = crate::parameters::NegativeParameter::new(&self.meta.name, idx); - model.add_parameter(Box::new(p)) + let p = pywr_core::parameters::NegativeParameter::new(&self.meta.name, idx); + Ok(model.add_parameter(Box::new(p))?) } } diff --git a/src/schema/parameters/data_frame.rs b/pywr-schema/src/parameters/data_frame.rs similarity index 89% rename from src/schema/parameters/data_frame.rs rename to pywr-schema/src/parameters/data_frame.rs index 387998de..9a881e72 100644 --- a/src/schema/parameters/data_frame.rs +++ b/pywr-schema/src/parameters/data_frame.rs @@ -1,7 +1,6 @@ -use crate::parameters::{Array1Parameter, Array2Parameter}; -use crate::schema::parameters::python::try_json_value_into_py; -use crate::schema::parameters::{DynamicFloatValueType, ParameterMeta}; -use crate::{ParameterIndex, PywrError}; +use crate::error::SchemaError; +use crate::parameters::python::try_json_value_into_py; +use crate::parameters::{DynamicFloatValueType, ParameterMeta}; use ndarray::Array2; use polars::prelude::DataType::Float64; use polars::prelude::{DataFrame, Float64Type, IndexOrder}; @@ -9,6 +8,7 @@ use pyo3::prelude::PyModule; use pyo3::types::{PyDict, PyTuple}; use pyo3::{IntoPy, PyErr, PyObject, Python, ToPyObject}; use pyo3_polars::PyDataFrame; +use pywr_core::parameters::{Array1Parameter, Array2Parameter, ParameterIndex}; use std::collections::HashMap; use std::path::{Path, PathBuf}; @@ -68,9 +68,9 @@ impl DataFrameParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, data_path: Option<&Path>, - ) -> Result { + ) -> Result { // Handle the case of an optional data path with a relative url. let pth = if let Some(dp) = data_path { if self.url.is_relative() { @@ -82,7 +82,7 @@ impl DataFrameParameter { self.url.clone() }; - let format = FileFormat::from_path(&pth).ok_or(PywrError::UnsupportedFileFormat)?; + let format = FileFormat::from_path(&pth).ok_or(SchemaError::UnsupportedFileFormat)?; // 1. Call Python & Pandas to read the data and return an array let df: DataFrame = Python::with_gil(|py| { @@ -115,7 +115,7 @@ impl DataFrameParameter { Ok(py_polars_df.into()) }) - .map_err(|e: PyErr| PywrError::PythonError(e.to_string()))?; + .map_err(|e: PyErr| SchemaError::PythonError(e.to_string()))?; // 2. TODO Validate the shape of the data array. I.e. check number of columns matches scenario // and number of rows matches time-steps. @@ -126,7 +126,7 @@ impl DataFrameParameter { let scenario_group = model.get_scenario_group_index_by_name(scenario)?; let array: Array2 = df.to_ndarray::(IndexOrder::default()).unwrap(); let p = Array2Parameter::new(&self.meta.name, array, scenario_group); - model.add_parameter(Box::new(p)) + Ok(model.add_parameter(Box::new(p))?) } DataFrameColumns::Column(column) => { let series = df.column(column).unwrap(); @@ -140,7 +140,7 @@ impl DataFrameParameter { .to_owned(); let p = Array1Parameter::new(&self.meta.name, array); - model.add_parameter(Box::new(p)) + Ok(model.add_parameter(Box::new(p))?) } } } diff --git a/src/schema/parameters/delay.rs b/pywr-schema/src/parameters/delay.rs similarity index 66% rename from src/schema/parameters/delay.rs rename to pywr-schema/src/parameters/delay.rs index a90b6faf..706b0a0e 100644 --- a/src/schema/parameters/delay.rs +++ b/pywr-schema/src/parameters/delay.rs @@ -1,7 +1,7 @@ -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::parameters::{DynamicFloatValue, DynamicFloatValueType, ParameterMeta}; -use crate::{ParameterIndex, PywrError}; - +use crate::data_tables::LoadedTableCollection; +use crate::error::SchemaError; +use crate::parameters::{DynamicFloatValue, DynamicFloatValueType, ParameterMeta}; +use pywr_core::parameters::ParameterIndex; use std::collections::HashMap; use std::path::Path; @@ -31,12 +31,12 @@ impl DelayParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let metric = self.metric.load(model, tables, data_path)?; - let p = crate::parameters::DelayParameter::new(&self.meta.name, metric, self.delay, self.initial_value); - model.add_parameter(Box::new(p)) + let p = pywr_core::parameters::DelayParameter::new(&self.meta.name, metric, self.delay, self.initial_value); + Ok(model.add_parameter(Box::new(p))?) } } diff --git a/src/schema/parameters/doc_examples/aggregated_1.json b/pywr-schema/src/parameters/doc_examples/aggregated_1.json similarity index 100% rename from src/schema/parameters/doc_examples/aggregated_1.json rename to pywr-schema/src/parameters/doc_examples/aggregated_1.json diff --git a/src/schema/parameters/doc_examples/constant_simple.json b/pywr-schema/src/parameters/doc_examples/constant_simple.json similarity index 100% rename from src/schema/parameters/doc_examples/constant_simple.json rename to pywr-schema/src/parameters/doc_examples/constant_simple.json diff --git a/src/schema/parameters/doc_examples/constant_variable.json b/pywr-schema/src/parameters/doc_examples/constant_variable.json similarity index 100% rename from src/schema/parameters/doc_examples/constant_variable.json rename to pywr-schema/src/parameters/doc_examples/constant_variable.json diff --git a/src/schema/parameters/doc_examples/offset_simple.json b/pywr-schema/src/parameters/doc_examples/offset_simple.json similarity index 100% rename from src/schema/parameters/doc_examples/offset_simple.json rename to pywr-schema/src/parameters/doc_examples/offset_simple.json diff --git a/src/schema/parameters/doc_examples/offset_variable.json b/pywr-schema/src/parameters/doc_examples/offset_variable.json similarity index 100% rename from src/schema/parameters/doc_examples/offset_variable.json rename to pywr-schema/src/parameters/doc_examples/offset_variable.json diff --git a/src/schema/parameters/indexed_array.rs b/pywr-schema/src/parameters/indexed_array.rs similarity index 80% rename from src/schema/parameters/indexed_array.rs rename to pywr-schema/src/parameters/indexed_array.rs index e7fedeab..ce37a416 100644 --- a/src/schema/parameters/indexed_array.rs +++ b/pywr-schema/src/parameters/indexed_array.rs @@ -1,11 +1,11 @@ -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::parameters::{ +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::parameters::{ DynamicFloatValue, DynamicFloatValueType, DynamicIndexValue, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter, }; -use crate::{ParameterIndex, PywrError}; -use pywr_schema::parameters::IndexedArrayParameter as IndexedArrayParameterV1; +use pywr_core::parameters::ParameterIndex; +use pywr_v1_schema::parameters::IndexedArrayParameter as IndexedArrayParameterV1; use std::collections::HashMap; use std::path::Path; @@ -34,10 +34,10 @@ impl IndexedArrayParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let index_parameter = self.index_parameter.load(model, tables, data_path)?; let metrics = self @@ -46,9 +46,9 @@ impl IndexedArrayParameter { .map(|v| v.load(model, tables, data_path)) .collect::, _>>()?; - let p = crate::parameters::IndexedArrayParameter::new(&self.meta.name, index_parameter, &metrics); + let p = pywr_core::parameters::IndexedArrayParameter::new(&self.meta.name, index_parameter, &metrics); - model.add_parameter(Box::new(p)) + Ok(model.add_parameter(Box::new(p))?) } } diff --git a/src/schema/parameters/mod.rs b/pywr-schema/src/parameters/mod.rs similarity index 95% rename from src/schema/parameters/mod.rs rename to pywr-schema/src/parameters/mod.rs index 12179ee4..b5082f9a 100644 --- a/src/schema/parameters/mod.rs +++ b/pywr-schema/src/parameters/mod.rs @@ -40,14 +40,14 @@ pub use super::parameters::profiles::{ pub use super::parameters::python::PythonParameter; pub use super::parameters::tables::TablesArrayParameter; pub use super::parameters::thresholds::ParameterThresholdParameter; -use crate::metric::Metric; -use crate::parameters::{IndexValue, ParameterType}; -use crate::schema::error::ConversionError; -use crate::schema::parameters::core::DivisionParameter; -pub use crate::schema::parameters::data_frame::DataFrameParameter; -use crate::{IndexParameterIndex, NodeIndex, PywrError}; +use crate::error::{ConversionError, SchemaError}; +use crate::parameters::core::DivisionParameter; +pub use crate::parameters::data_frame::DataFrameParameter; pub use offset::OffsetParameter; -use pywr_schema::parameters::{ +use pywr_core::metric::Metric; +use pywr_core::node::NodeIndex; +use pywr_core::parameters::{IndexParameterIndex, IndexValue, ParameterType}; +use pywr_v1_schema::parameters::{ CoreParameter, ExternalDataRef as ExternalDataRefV1, Parameter as ParameterV1, ParameterMeta as ParameterMetaV1, ParameterValue as ParameterValueV1, TableIndex as TableIndexV1, }; @@ -269,10 +269,10 @@ impl Parameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let ty = match self { Self::Constant(p) => ParameterType::Parameter(p.add_to_model(model, tables)?), Self::ControlCurveInterpolated(p) => ParameterType::Parameter(p.add_to_model(model, tables, data_path)?), @@ -402,7 +402,7 @@ pub enum ConstantValue { impl ConstantValue { /// Return the value loading from a table if required. - pub fn load(&self, tables: &LoadedTableCollection) -> Result { + pub fn load(&self, tables: &LoadedTableCollection) -> Result { match self { Self::Literal(v) => Ok(*v), Self::Table(tbl_ref) => Ok(tables.get_scalar_f64(tbl_ref)?), @@ -413,7 +413,7 @@ impl ConstantValue { impl ConstantValue { /// Return the value loading from a table if required. - pub fn load(&self, tables: &LoadedTableCollection) -> Result { + pub fn load(&self, tables: &LoadedTableCollection) -> Result { match self { Self::Literal(v) => Ok(*v), Self::Table(tbl_ref) => Ok(tables.get_scalar_usize(tbl_ref)?), @@ -442,8 +442,8 @@ pub struct NodeReference { } impl NodeReference { - fn get_node_index(&self, model: &crate::model::Model) -> Result { - model.get_node_index_by_name(&self.name, self.sub_name.as_deref()) + fn get_node_index(&self, model: &pywr_core::model::Model) -> Result { + Ok(model.get_node_index_by_name(&self.name, self.sub_name.as_deref())?) } } @@ -463,10 +463,10 @@ impl MetricFloatValue { /// Load the metric definition into a `Metric` containing the appropriate internal references. pub fn load( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { match self { Self::NodeInFlow(node_ref) => Ok(Metric::NodeInFlow(node_ref.get_node_index(model)?)), Self::NodeOutFlow(node_ref) => Ok(Metric::NodeOutFlow(node_ref.get_node_index(model)?)), @@ -506,12 +506,12 @@ impl MetricFloatValue { // An error retrieving a parameter with this name; assume it needs creating. match definition.add_to_model(model, tables, data_path)? { ParameterType::Parameter(idx) => Ok(Metric::ParameterValue(idx)), - ParameterType::Index(_) => Err(PywrError::UnexpectedParameterType(format!( + ParameterType::Index(_) => Err(SchemaError::UnexpectedParameterType(format!( "Found index parameter of type '{}' with name '{}' where an float parameter was expected.", definition.ty(), definition.name(), ))), - ParameterType::Multi(_) => Err(PywrError::UnexpectedParameterType(format!( + ParameterType::Multi(_) => Err(SchemaError::UnexpectedParameterType(format!( "Found an inline definition of a multi valued parameter of type '{}' with name '{}' where an float parameter was expected. Multi valued parameters cannot be defined inline.", definition.ty(), definition.name(), @@ -535,25 +535,25 @@ pub enum ParameterIndexValue { impl ParameterIndexValue { pub fn load( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { match self { Self::Reference(name) => { // This should be an existing parameter - model.get_index_parameter_index_by_name(name) + Ok(model.get_index_parameter_index_by_name(name)?) } Self::Inline(parameter) => { // Inline parameter needs to be added match parameter.add_to_model(model, tables, data_path)? { ParameterType::Index(idx) => Ok(idx), - ParameterType::Parameter(_) => Err(PywrError::UnexpectedParameterType(format!( + ParameterType::Parameter(_) => Err(SchemaError::UnexpectedParameterType(format!( "Found float parameter of type '{}' with name '{}' where an index parameter was expected.", parameter.ty(), parameter.name(), ))), - ParameterType::Multi(_) => Err(PywrError::UnexpectedParameterType(format!( + ParameterType::Multi(_) => Err(SchemaError::UnexpectedParameterType(format!( "Found an inline definition of a multi valued parameter of type '{}' with name '{}' where an index parameter was expected. Multi valued parameters cannot be defined inline.", parameter.ty(), parameter.name(), @@ -582,10 +582,10 @@ impl DynamicFloatValue { pub fn load( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let parameter_ref = match self { DynamicFloatValue::Constant(v) => Metric::Constant(v.load(tables)?), DynamicFloatValue::Dynamic(v) => v.load(model, tables, data_path)?, @@ -636,10 +636,10 @@ impl DynamicIndexValue { /// pub fn load( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let parameter_ref = match self { DynamicIndexValue::Constant(v) => IndexValue::Constant(v.load(tables)?), DynamicIndexValue::Dynamic(v) => IndexValue::Dynamic(v.load(model, tables, data_path)?), @@ -683,7 +683,7 @@ pub enum ConstantFloatVec { impl ConstantFloatVec { /// Return the value loading from a table if required. - pub fn load(&self, tables: &LoadedTableCollection) -> Result, PywrError> { + pub fn load(&self, tables: &LoadedTableCollection) -> Result, SchemaError> { match self { Self::Literal(v) => Ok(v.clone()), Self::Table(tbl_ref) => Ok(tables.get_vec_f64(tbl_ref)?.clone()), @@ -744,7 +744,7 @@ impl<'a> From<&'a Vec> for DynamicFloatValueType<'a> { #[cfg(test)] mod tests { - use crate::schema::parameters::Parameter; + use crate::parameters::Parameter; use std::fs; use std::path::PathBuf; @@ -752,7 +752,7 @@ mod tests { #[test] fn test_doc_examples() { let mut doc_examples = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - doc_examples.push("src/schema/parameters/doc_examples"); + doc_examples.push("src/parameters/doc_examples"); for entry in fs::read_dir(doc_examples).unwrap() { let p = entry.unwrap().path(); diff --git a/src/schema/parameters/offset.rs b/pywr-schema/src/parameters/offset.rs similarity index 79% rename from src/schema/parameters/offset.rs rename to pywr-schema/src/parameters/offset.rs index 08537261..97951408 100644 --- a/src/schema/parameters/offset.rs +++ b/pywr-schema/src/parameters/offset.rs @@ -1,9 +1,8 @@ -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::parameters::{ - ConstantValue, DynamicFloatValue, DynamicFloatValueType, ParameterMeta, VariableSettings, -}; -use crate::{ParameterIndex, PywrError}; +use crate::data_tables::LoadedTableCollection; +use crate::parameters::{ConstantValue, DynamicFloatValue, DynamicFloatValueType, ParameterMeta, VariableSettings}; +use pywr_core::parameters::ParameterIndex; +use crate::error::SchemaError; use std::collections::HashMap; use std::path::Path; @@ -50,10 +49,10 @@ impl OffsetParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let variable = match &self.variable { None => None, Some(v) => { @@ -68,7 +67,7 @@ impl OffsetParameter { let idx = self.metric.load(model, tables, data_path)?; - let p = crate::parameters::OffsetParameter::new(&self.meta.name, idx, self.offset.load(tables)?, variable); - model.add_parameter(Box::new(p)) + let p = pywr_core::parameters::OffsetParameter::new(&self.meta.name, idx, self.offset.load(tables)?, variable); + Ok(model.add_parameter(Box::new(p))?) } } diff --git a/src/schema/parameters/polynomial.rs b/pywr-schema/src/parameters/polynomial.rs similarity index 76% rename from src/schema/parameters/polynomial.rs rename to pywr-schema/src/parameters/polynomial.rs index 99cf0340..54a3dfcc 100644 --- a/src/schema/parameters/polynomial.rs +++ b/pywr-schema/src/parameters/polynomial.rs @@ -1,8 +1,8 @@ -use crate::metric::Metric; -use crate::schema::error::ConversionError; -use crate::schema::parameters::{DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter}; -use crate::{ParameterIndex, PywrError}; -use pywr_schema::parameters::Polynomial1DParameter as Polynomial1DParameterV1; +use crate::error::{ConversionError, SchemaError}; +use crate::parameters::{DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter}; +use pywr_core::metric::Metric; +use pywr_core::parameters::ParameterIndex; +use pywr_v1_schema::parameters::Polynomial1DParameter as Polynomial1DParameterV1; use std::collections::HashMap; #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] @@ -24,7 +24,7 @@ impl Polynomial1DParameter { HashMap::new() } - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result { + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result { let node_idx = model.get_node_index_by_name(&self.storage_node, None)?; let metric = if self.use_proportional_volume.unwrap_or(true) { Metric::NodeProportionalVolume(node_idx) @@ -32,14 +32,14 @@ impl Polynomial1DParameter { Metric::NodeVolume(node_idx) }; - let p = crate::parameters::Polynomial1DParameter::new( + let p = pywr_core::parameters::Polynomial1DParameter::new( &self.meta.name, metric, self.coefficients.clone(), self.scale.unwrap_or(1.0), self.offset.unwrap_or(0.0), ); - model.add_parameter(Box::new(p)) + Ok(model.add_parameter(Box::new(p))?) } } diff --git a/src/schema/parameters/profiles.rs b/pywr-schema/src/parameters/profiles.rs similarity index 86% rename from src/schema/parameters/profiles.rs rename to pywr-schema/src/parameters/profiles.rs index 20f804f9..fbc78e8b 100644 --- a/src/schema/parameters/profiles.rs +++ b/pywr-schema/src/parameters/profiles.rs @@ -1,10 +1,10 @@ -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::parameters::{ +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::parameters::{ ConstantFloatVec, ConstantValue, DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, }; -use crate::{ParameterIndex, PywrError}; -use pywr_schema::parameters::{ +use pywr_core::parameters::ParameterIndex; +use pywr_v1_schema::parameters::{ DailyProfileParameter as DailyProfileParameterV1, MonthInterpDay as MonthInterpDayV1, MonthlyProfileParameter as MonthlyProfileParameterV1, UniformDrawdownProfileParameter as UniformDrawdownProfileParameterV1, @@ -28,12 +28,12 @@ impl DailyProfileParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, - ) -> Result { + ) -> Result { let values = &self.values.load(tables)?[..366]; - let p = crate::parameters::DailyProfileParameter::new(&self.meta.name, values.try_into().expect("")); - model.add_parameter(Box::new(p)) + let p = pywr_core::parameters::DailyProfileParameter::new(&self.meta.name, values.try_into().expect("")); + Ok(model.add_parameter(Box::new(p))?) } } @@ -71,7 +71,7 @@ pub enum MonthlyInterpDay { Last, } -impl From for crate::parameters::MonthlyInterpDay { +impl From for pywr_core::parameters::MonthlyInterpDay { fn from(value: MonthlyInterpDay) -> Self { match value { MonthlyInterpDay::First => Self::First, @@ -98,16 +98,16 @@ impl MonthlyProfileParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, - ) -> Result { + ) -> Result { let values = &self.values.load(tables)?[..12]; - let p = crate::parameters::MonthlyProfileParameter::new( + let p = pywr_core::parameters::MonthlyProfileParameter::new( &self.meta.name, values.try_into().expect(""), self.interp_day.map(|id| id.into()), ); - model.add_parameter(Box::new(p)) + Ok(model.add_parameter(Box::new(p))?) } } @@ -172,9 +172,9 @@ impl UniformDrawdownProfileParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, - ) -> Result { + ) -> Result { let reset_day = match &self.reset_day { Some(v) => v.load(tables)? as u8, None => 1, @@ -188,13 +188,13 @@ impl UniformDrawdownProfileParameter { None => 0, }; - let p = crate::parameters::UniformDrawdownProfileParameter::new( + let p = pywr_core::parameters::UniformDrawdownProfileParameter::new( &self.meta.name, reset_day, reset_month, residual_days, ); - model.add_parameter(Box::new(p)) + Ok(model.add_parameter(Box::new(p))?) } } diff --git a/src/schema/parameters/python.rs b/pywr-schema/src/parameters/python.rs similarity index 91% rename from src/schema/parameters/python.rs rename to pywr-schema/src/parameters/python.rs index c28a485e..426153c6 100644 --- a/src/schema/parameters/python.rs +++ b/pywr-schema/src/parameters/python.rs @@ -1,10 +1,10 @@ -use crate::parameters::{ParameterType, PyParameter}; -use crate::schema::data_tables::{make_path, LoadedTableCollection}; -use crate::schema::parameters::{DynamicFloatValue, DynamicFloatValueType, DynamicIndexValue, ParameterMeta}; -use crate::PywrError; +use crate::data_tables::{make_path, LoadedTableCollection}; +use crate::error::SchemaError; +use crate::parameters::{DynamicFloatValue, DynamicFloatValueType, DynamicIndexValue, ParameterMeta}; use pyo3::prelude::PyModule; use pyo3::types::{PyDict, PyTuple}; use pyo3::{IntoPy, PyErr, PyObject, Python, ToPyObject}; +use pywr_core::parameters::{ParameterType, PyParameter}; use serde_json::Value; use std::collections::HashMap; use std::path::{Path, PathBuf}; @@ -31,7 +31,7 @@ pub enum PythonModule { /// other parameter value or index). /// /// ``` -/// use pywr::schema::parameters::Parameter; +/// use pywr_schema::parameters::Parameter; /// /// // Parameter JSON definition /// // `my_parameter.py` should contain a Python class. @@ -80,7 +80,7 @@ pub struct PythonParameter { pub indices: Option>, } -pub fn try_json_value_into_py(py: Python, value: &serde_json::Value) -> Result, PywrError> { +pub fn try_json_value_into_py(py: Python, value: &serde_json::Value) -> Result, SchemaError> { let py_value = match value { Value::Null => None, Value::Bool(v) => Some(v.into_py(py)), @@ -122,10 +122,10 @@ impl PythonParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { pyo3::prepare_freethreaded_python(); let object = Python::with_gil(|py| { @@ -143,7 +143,7 @@ impl PythonParameter { Ok(module.getattr(self.object.as_str())?.into()) }) - .map_err(|e: PyErr| PywrError::PythonError(e.to_string()))?; + .map_err(|e: PyErr| SchemaError::PythonError(e.to_string()))?; let args = Python::with_gil(|py| { PyTuple::new(py, self.args.iter().map(|arg| try_json_value_into_py(py, arg).unwrap())).into_py(py) @@ -164,7 +164,7 @@ impl PythonParameter { Some(metrics) => metrics .iter() .map(|(k, v)| Ok((k.to_string(), v.load(model, tables, data_path)?))) - .collect::, PywrError>>()?, + .collect::, SchemaError>>()?, None => HashMap::new(), }; @@ -172,7 +172,7 @@ impl PythonParameter { Some(indices) => indices .iter() .map(|(k, v)| Ok((k.to_string(), v.load(model, tables, data_path)?))) - .collect::, PywrError>>()?, + .collect::, SchemaError>>()?, None => HashMap::new(), }; @@ -189,9 +189,9 @@ impl PythonParameter { #[cfg(test)] mod tests { - use crate::model::Model; - use crate::schema::data_tables::LoadedTableCollection; - use crate::schema::parameters::python::PythonParameter; + use crate::data_tables::LoadedTableCollection; + use crate::parameters::python::PythonParameter; + use pywr_core::model::Model; use serde_json::json; use std::fs::File; use std::io::Write; diff --git a/src/schema/parameters/tables.rs b/pywr-schema/src/parameters/tables.rs similarity index 66% rename from src/schema/parameters/tables.rs rename to pywr-schema/src/parameters/tables.rs index 1e1a3b16..18494e63 100644 --- a/src/schema/parameters/tables.rs +++ b/pywr-schema/src/parameters/tables.rs @@ -1,8 +1,8 @@ -use crate::schema::error::ConversionError; -use crate::schema::parameters::{DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter}; -use crate::{ParameterIndex, PywrError}; +use crate::error::{ConversionError, SchemaError}; +use crate::parameters::{DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter}; use ndarray::s; -use pywr_schema::parameters::TablesArrayParameter as TablesArrayParameterV1; +use pywr_core::parameters::ParameterIndex; +use pywr_v1_schema::parameters::TablesArrayParameter as TablesArrayParameterV1; use std::collections::HashMap; use std::path::{Path, PathBuf}; @@ -28,9 +28,9 @@ impl TablesArrayParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, data_path: Option<&Path>, - ) -> Result { + ) -> Result { // 1. Load the file from the HDF5 file (NB this is not Pandas format). // Handle the case of an optional data path with a relative url. @@ -44,26 +44,28 @@ impl TablesArrayParameter { self.url.clone() }; - let file = hdf5::File::open(pth).map_err(|e| PywrError::HDF5Error(e.to_string()))?; // open for reading + let file = hdf5::File::open(pth).map_err(|e| SchemaError::HDF5Error(e.to_string()))?; // open for reading - let grp = file.group(&self.wh).map_err(|e| PywrError::HDF5Error(e.to_string()))?; // find the group + let grp = file + .group(&self.wh) + .map_err(|e| SchemaError::HDF5Error(e.to_string()))?; // find the group let ds = grp .dataset(&self.node) - .map_err(|e| PywrError::HDF5Error(e.to_string()))?; // find the dataset + .map_err(|e| SchemaError::HDF5Error(e.to_string()))?; // find the dataset - let array = ds.read_2d::().map_err(|e| PywrError::HDF5Error(e.to_string()))?; + let array = ds.read_2d::().map_err(|e| SchemaError::HDF5Error(e.to_string()))?; // 2. TODO Validate the shape of the data array. I.e. check number of columns matches scenario // and number of rows matches time-steps. // 3. Create an ArrayParameter using the loaded array. if let Some(scenario) = &self.scenario { let scenario_group = model.get_scenario_group_index_by_name(scenario)?; - let p = crate::parameters::Array2Parameter::new(&self.meta.name, array, scenario_group); - model.add_parameter(Box::new(p)) + let p = pywr_core::parameters::Array2Parameter::new(&self.meta.name, array, scenario_group); + Ok(model.add_parameter(Box::new(p))?) } else { let array = array.slice_move(s![.., 0]); - let p = crate::parameters::Array1Parameter::new(&self.meta.name, array); - model.add_parameter(Box::new(p)) + let p = pywr_core::parameters::Array1Parameter::new(&self.meta.name, array); + Ok(model.add_parameter(Box::new(p))?) } } } diff --git a/src/schema/parameters/thresholds.rs b/pywr-schema/src/parameters/thresholds.rs similarity index 73% rename from src/schema/parameters/thresholds.rs rename to pywr-schema/src/parameters/thresholds.rs index b8e511be..f1310ab4 100644 --- a/src/schema/parameters/thresholds.rs +++ b/pywr-schema/src/parameters/thresholds.rs @@ -1,10 +1,12 @@ -use crate::schema::data_tables::LoadedTableCollection; -use crate::schema::error::ConversionError; -use crate::schema::parameters::{ +use crate::data_tables::LoadedTableCollection; +use crate::error::{ConversionError, SchemaError}; +use crate::parameters::{ DynamicFloatValue, DynamicFloatValueType, IntoV2Parameter, ParameterMeta, TryFromV1Parameter, TryIntoV2Parameter, }; -use crate::{IndexParameterIndex, PywrError}; -use pywr_schema::parameters::{ParameterThresholdParameter as ParameterThresholdParameterV1, Predicate as PredicateV1}; +use pywr_core::parameters::IndexParameterIndex; +use pywr_v1_schema::parameters::{ + ParameterThresholdParameter as ParameterThresholdParameterV1, Predicate as PredicateV1, +}; use std::collections::HashMap; use std::path::Path; @@ -34,14 +36,14 @@ impl From for Predicate { } } -impl From for crate::parameters::Predicate { +impl From for pywr_core::parameters::Predicate { fn from(p: Predicate) -> Self { match p { - Predicate::LT => crate::parameters::Predicate::LessThan, - Predicate::GT => crate::parameters::Predicate::GreaterThan, - Predicate::EQ => crate::parameters::Predicate::EqualTo, - Predicate::LE => crate::parameters::Predicate::LessThanOrEqualTo, - Predicate::GE => crate::parameters::Predicate::GreaterThanOrEqualTo, + Predicate::LT => pywr_core::parameters::Predicate::LessThan, + Predicate::GT => pywr_core::parameters::Predicate::GreaterThan, + Predicate::EQ => pywr_core::parameters::Predicate::EqualTo, + Predicate::LE => pywr_core::parameters::Predicate::LessThanOrEqualTo, + Predicate::GE => pywr_core::parameters::Predicate::GreaterThanOrEqualTo, } } } @@ -67,21 +69,21 @@ impl ParameterThresholdParameter { pub fn add_to_model( &self, - model: &mut crate::model::Model, + model: &mut pywr_core::model::Model, tables: &LoadedTableCollection, data_path: Option<&Path>, - ) -> Result { + ) -> Result { let metric = self.parameter.load(model, tables, data_path)?; let threshold = self.threshold.load(model, tables, data_path)?; - let p = crate::parameters::ThresholdParameter::new( + let p = pywr_core::parameters::ThresholdParameter::new( &self.meta.name, metric, threshold, self.predicate.into(), self.ratchet, ); - model.add_index_parameter(Box::new(p)) + Ok(model.add_index_parameter(Box::new(p))?) } } diff --git a/src/schema/test_models/csv1.json b/pywr-schema/src/test_models/csv1.json similarity index 100% rename from src/schema/test_models/csv1.json rename to pywr-schema/src/test_models/csv1.json diff --git a/src/schema/test_models/delay1.json b/pywr-schema/src/test_models/delay1.json similarity index 100% rename from src/schema/test_models/delay1.json rename to pywr-schema/src/test_models/delay1.json diff --git a/src/schema/test_models/hdf1.json b/pywr-schema/src/test_models/hdf1.json similarity index 100% rename from src/schema/test_models/hdf1.json rename to pywr-schema/src/test_models/hdf1.json diff --git a/src/schema/test_models/piecewise_link1.json b/pywr-schema/src/test_models/piecewise_link1.json similarity index 100% rename from src/schema/test_models/piecewise_link1.json rename to pywr-schema/src/test_models/piecewise_link1.json diff --git a/src/schema/test_models/piecewise_storage1.json b/pywr-schema/src/test_models/piecewise_storage1.json similarity index 100% rename from src/schema/test_models/piecewise_storage1.json rename to pywr-schema/src/test_models/piecewise_storage1.json diff --git a/src/schema/test_models/piecewise_storage2.json b/pywr-schema/src/test_models/piecewise_storage2.json similarity index 100% rename from src/schema/test_models/piecewise_storage2.json rename to pywr-schema/src/test_models/piecewise_storage2.json diff --git a/src/schema/test_models/river_split_with_gauge1.json b/pywr-schema/src/test_models/river_split_with_gauge1.json similarity index 100% rename from src/schema/test_models/river_split_with_gauge1.json rename to pywr-schema/src/test_models/river_split_with_gauge1.json diff --git a/src/schema/test_models/simple1.json b/pywr-schema/src/test_models/simple1.json similarity index 100% rename from src/schema/test_models/simple1.json rename to pywr-schema/src/test_models/simple1.json diff --git a/src/schema/error.rs b/src/schema/error.rs deleted file mode 100644 index b4f0147f..00000000 --- a/src/schema/error.rs +++ /dev/null @@ -1,31 +0,0 @@ -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum SchemaError { - #[error("IO error: {0}")] - IO(String), - #[error("JSON error: {0}")] - Json(#[from] serde_json::Error), -} - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum ConversionError { - #[error("Error converting {attr:?} on node {name:?}")] - NodeAttribute { - attr: String, - name: String, - source: Box, - }, - #[error("Constant float value cannot be a parameter reference.")] - ConstantFloatReferencesParameter, - #[error("Constant float value cannot be an inline parameter.")] - ConstantFloatInlineParameter, - #[error("Missing one of the following attributes {attrs:?} on parameter {name:?}.")] - MissingAttribute { attrs: Vec, name: String }, - #[error("Unexpected the following attributes {attrs:?} on parameter {name:?}.")] - 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:?}.")] - ExtraNodeAttribute { attr: String, name: String }, -} From 82c0caea53ce5d36b1eb3bfe4b54a7bd60c271a2 Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Sat, 7 Oct 2023 21:26:25 +0100 Subject: [PATCH 2/2] chore: Update to Clp master and add Windows to CI. (#57) This fixes a build issue on Windows. Refactor the clp-sys build.rs to be more generic and add MSVC specific build flags. Use static version of HDF5 for easier install on Windows, but this requires cmake to be installed. Fix path escaping which was preventing the test from working correctly on Windows. --- .github/workflows/{rust.yml => linux.yml} | 6 +-- .github/workflows/python.yml | 2 +- .github/workflows/windows.yml | 23 +++++++++ Cargo.toml | 1 + clp-sys/build.rs | 57 +++++++++++++++++------ clp-sys/vendor/Clp | 2 +- clp-sys/vendor/CoinUtils | 2 +- pywr-core/Cargo.toml | 1 + pywr-schema/src/data_tables/mod.rs | 9 +++- 9 files changed, 79 insertions(+), 24 deletions(-) rename .github/workflows/{rust.yml => linux.yml} (70%) create mode 100644 .github/workflows/windows.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/linux.yml similarity index 70% rename from .github/workflows/rust.yml rename to .github/workflows/linux.yml index c2babf0c..0821f3de 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/linux.yml @@ -1,4 +1,4 @@ -name: Rust +name: Rust (Linux) on: push: @@ -17,10 +17,6 @@ jobs: - uses: actions/checkout@v4 with: submodules: true - - name: Install HDF5 - run: | - sudo apt-get update - sudo apt-get install libhdf5-dev ocl-icd-opencl-dev zlib1g-dev - name: Build run: cargo build --verbose --no-default-features - name: Run tests diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 006c3f73..a884b747 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -1,4 +1,4 @@ -name: Python +name: Python (Linux) on: push: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 00000000..b622cca5 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,23 @@ +name: Rust (Windows) + +on: + push: + branches: [ main ] + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Build + run: cargo build --verbose --no-default-features + - name: Run tests + run: cargo test --no-default-features diff --git a/Cargo.toml b/Cargo.toml index 77d8776b..deb94bfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,4 +44,5 @@ pyo3 = { version = "0.19.0" } tracing = "0.1" csv = "1.1" hdf5 = { version="0.8.1" } +hdf5-sys = { version="0.8.1", features=["static"] } pywr-v1-schema = { git = "https://github.com/pywr/pywr-schema/", tag="v0.7.0", package = "pywr-schema" } diff --git a/clp-sys/build.rs b/clp-sys/build.rs index 02aa7595..3f9a8ab6 100644 --- a/clp-sys/build.rs +++ b/clp-sys/build.rs @@ -1,12 +1,44 @@ +use std::env; + +fn make_builder() -> cc::Build { + let target = env::var("TARGET").expect("Could not find TARGET in environment."); + let mut builder = cc::Build::new() + .cpp(true) + .warnings(false) + .extra_warnings(false) + .define("NDEBUG", None) + .define("HAVE_STDIO_H", None) + .define("HAVE_STDLIB_H", None) + .define("HAVE_STRING_H", None) + .define("HAVE_INTTYPES_H", None) + .define("HAVE_STDINT_H", None) + .define("HAVE_STRINGS_H", None) + .define("HAVE_SYS_TYPES_H", None) + .define("HAVE_SYS_STAT_H", None) + .define("HAVE_UNISTD_H", None) + .define("HAVE_CMATH", None) + .define("HAVE_CFLOAT", None) + // .define("HAVE_DLFCN_H", None) + .define("HAVE_MEMORY_H", None) + .to_owned(); + + if target.contains("msvc") { + builder.flag("-EHsc").flag_if_supported("-std:c++11"); + } else { + builder.flag("-std=c++11").flag("-w"); + } + + builder +} + fn main() { - const COIN_UTILS_SRC: &str = "vendor/CoinUtils/CoinUtils/src"; - const COIN_CLP_SRC: &str = "vendor/Clp/Clp/src"; + const COIN_UTILS_SRC: &str = "vendor/CoinUtils/src"; + const COIN_CLP_SRC: &str = "vendor/Clp/src"; + // Compile CoinUtils - cc::Build::new() - .cpp(true) - .flag("-w") - .flag("-DNDEBUG") - .flag("-DHAVE_CFLOAT") + let mut builder = make_builder(); + + builder .flag(&*format!("-I{}", COIN_UTILS_SRC)) .file(format!("{}/CoinAlloc.cpp", COIN_UTILS_SRC)) .file(format!("{}/CoinBuild.cpp", COIN_UTILS_SRC)) @@ -69,14 +101,11 @@ fn main() { // Compile CoinUtils - cc::Build::new() - .cpp(true) - .flag("-w") + let mut builder = make_builder(); + + builder .flag(&*format!("-I{}", COIN_UTILS_SRC)) .flag(&*format!("-I{}", COIN_CLP_SRC)) - .flag("-DNDEBUG") - .flag("-DHAVE_CFLOAT") - .flag("-DHAVE_CMATH") .file(format!("{}/ClpCholeskyBase.cpp", COIN_CLP_SRC)) .file(format!("{}/ClpCholeskyDense.cpp", COIN_CLP_SRC)) .file(format!("{}/ClpCholeskyPardiso.cpp", COIN_CLP_SRC)) @@ -132,7 +161,7 @@ fn main() { .file(format!("{}/ClpSimplexOther.cpp", COIN_CLP_SRC)) .file(format!("{}/ClpSimplexPrimal.cpp", COIN_CLP_SRC)) .file(format!("{}/ClpSolve.cpp", COIN_CLP_SRC)) - .file(format!("{}/ClpSolver.cpp", COIN_CLP_SRC)) + // .file(format!("{}/ClpSolver.cpp", COIN_CLP_SRC)) // .file(format!("{}/CoinAbcBaseFactorization1.cpp", COIN_CLP_SRC)) // .file(format!("{}/CoinAbcBaseFactorization2.cpp", COIN_CLP_SRC)) // .file(format!("{}/CoinAbcBaseFactorization3.cpp", COIN_CLP_SRC)) diff --git a/clp-sys/vendor/Clp b/clp-sys/vendor/Clp index 1c2586a0..9683fedd 160000 --- a/clp-sys/vendor/Clp +++ b/clp-sys/vendor/Clp @@ -1 +1 @@ -Subproject commit 1c2586a08d33ecc59ed67d319c29044802c0866b +Subproject commit 9683fedda4913cb13bfe323c64a87b970530869d diff --git a/clp-sys/vendor/CoinUtils b/clp-sys/vendor/CoinUtils index 26e9639e..583f1210 160000 --- a/clp-sys/vendor/CoinUtils +++ b/clp-sys/vendor/CoinUtils @@ -1 +1 @@ -Subproject commit 26e9639ed9897e13e89169870dbe910296a9783b +Subproject commit 583f1210b901e030725a88ac508c73f6c5b5fb10 diff --git a/pywr-core/Cargo.toml b/pywr-core/Cargo.toml index f9c5725d..8ecaf1a6 100644 --- a/pywr-core/Cargo.toml +++ b/pywr-core/Cargo.toml @@ -21,6 +21,7 @@ ndarray = { workspace = true } num = { workspace = true } float-cmp = "0.9.0" hdf5 = { workspace = true } +hdf5-sys = { workspace = true } csv = { workspace = true } clp-sys = { path = "../clp-sys" } ipm-ocl = { path = "../ipm-ocl", optional = true } diff --git a/pywr-schema/src/data_tables/mod.rs b/pywr-schema/src/data_tables/mod.rs index 501d7165..35a69e5c 100644 --- a/pywr-schema/src/data_tables/mod.rs +++ b/pywr-schema/src/data_tables/mod.rs @@ -530,6 +530,11 @@ mod tests { fn test_dataframe_row_filter() { let dir = tempdir().unwrap(); + // Temporary file name + let my_data_fn = dir.path().join("my-data.csv"); + // Serialise using serde to do cross-platform character escaping correctly. + let my_data_fn = serde_json::to_string(&my_data_fn).unwrap(); + let table_def = format!( r#" {{ @@ -537,9 +542,9 @@ mod tests { "type": "array", "format": "csv", "lookup": {{"row": 1}}, - "url": "{}/my-data.csv" + "url": {} }}"#, - dir.as_ref().display() + my_data_fn ); // Create the temporary data