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 c3cff282..ce7b4ec7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,93 +1,27 @@ -[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"] } -nalgebra = "0.32.3" - -# 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 @@ -97,6 +31,16 @@ 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" } +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/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/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-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..6229fd72 --- /dev/null +++ b/pywr-core/Cargo.toml @@ -0,0 +1,65 @@ +[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 } +hdf5-sys = { 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" } +nalgebra = "0.32.3" + +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/rbf.rs b/pywr-core/src/parameters/profiles/rbf.rs similarity index 100% rename from src/parameters/profiles/rbf.rs rename to pywr-core/src/parameters/profiles/rbf.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 97% rename from src/schema/data_tables/mod.rs rename to pywr-schema/src/data_tables/mod.rs index 0220835a..35a69e5c 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; @@ -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 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/doc_examples/rbf_1.json b/pywr-schema/src/parameters/doc_examples/rbf_1.json similarity index 100% rename from src/schema/parameters/doc_examples/rbf_1.json rename to pywr-schema/src/parameters/doc_examples/rbf_1.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 a5bae79b..9ad3d67c 100644 --- a/src/schema/parameters/mod.rs +++ b/pywr-schema/src/parameters/mod.rs @@ -41,14 +41,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, }; @@ -274,10 +274,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)?), @@ -408,7 +408,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)?), @@ -419,7 +419,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)?), @@ -448,8 +448,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())?) } } @@ -469,10 +469,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)?)), @@ -512,12 +512,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(), @@ -541,25 +541,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(), @@ -588,10 +588,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)?, @@ -642,10 +642,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)?), @@ -689,7 +689,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()), @@ -750,7 +750,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; @@ -758,7 +758,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 80% rename from src/schema/parameters/profiles.rs rename to pywr-schema/src/parameters/profiles.rs index 383aa337..160114cd 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))?) } } @@ -230,16 +230,16 @@ pub enum RadialBasisFunction { InverseMultiQuadric { epsilon: f64 }, } -impl Into for RadialBasisFunction { - fn into(self) -> crate::parameters::RadialBasisFunction { +impl Into for RadialBasisFunction { + fn into(self) -> pywr_core::parameters::RadialBasisFunction { match self { - Self::Linear => crate::parameters::RadialBasisFunction::Linear, - Self::Cubic => crate::parameters::RadialBasisFunction::Cubic, - Self::ThinPlateSpline => crate::parameters::RadialBasisFunction::ThinPlateSpline, - Self::Gaussian { epsilon } => crate::parameters::RadialBasisFunction::Gaussian { epsilon }, - Self::MultiQuadric { epsilon } => crate::parameters::RadialBasisFunction::MultiQuadric { epsilon }, + Self::Linear => pywr_core::parameters::RadialBasisFunction::Linear, + Self::Cubic => pywr_core::parameters::RadialBasisFunction::Cubic, + Self::ThinPlateSpline => pywr_core::parameters::RadialBasisFunction::ThinPlateSpline, + Self::Gaussian { epsilon } => pywr_core::parameters::RadialBasisFunction::Gaussian { epsilon }, + Self::MultiQuadric { epsilon } => pywr_core::parameters::RadialBasisFunction::MultiQuadric { epsilon }, Self::InverseMultiQuadric { epsilon } => { - crate::parameters::RadialBasisFunction::InverseMultiQuadric { epsilon } + pywr_core::parameters::RadialBasisFunction::InverseMultiQuadric { epsilon } } } } @@ -274,8 +274,9 @@ impl RbfProfileParameter { HashMap::new() } - pub fn add_to_model(&self, model: &mut crate::model::Model) -> Result { - let p = crate::parameters::RbfProfileParameter::new(&self.meta.name, self.points.clone(), self.function.into()); - model.add_parameter(Box::new(p)) + pub fn add_to_model(&self, model: &mut pywr_core::model::Model) -> Result { + let p = + pywr_core::parameters::RbfProfileParameter::new(&self.meta.name, self.points.clone(), self.function.into()); + 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 }, -}