From 90bf023aa2f3ee1daeb9b2113ebfcbbbdd395042 Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Sat, 27 Jul 2024 20:59:51 +0100 Subject: [PATCH 1/2] feat(python): Add conversion helper functions to Python extension. New helper functions for converting from Pywr v1.x to v2.x JSON strings added to the Python extension. Includes a new wrapper around `Metric`. --- Cargo.toml | 1 + pywr-python/Cargo.toml | 2 +- pywr-python/pywr/__init__.py | 2 +- pywr-python/src/lib.rs | 52 +++++++++++++++++++++++++++++++++-- pywr-schema/src/model.rs | 9 ++++++ pywr-schema/src/nodes/core.rs | 2 +- 6 files changed, 63 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a7eebef2..7167258e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,5 +47,6 @@ tracing = { version = "0.1", features = ["log"] } csv = "1.1" hdf5 = { git = "https://github.com/aldanor/hdf5-rust.git", package = "hdf5", features = ["static", "zlib"] } pywr-v1-schema = "0.14" +# pywr-v1-schema = { path = "/home/james/dev/pywr/pywr-schema/pywr-v1-schema" } chrono = { version = "0.4.34", features = ["serde"] } schemars = { version = "0.8.16", features = ["chrono"] } diff --git a/pywr-python/Cargo.toml b/pywr-python/Cargo.toml index c11520fa..7551c87f 100644 --- a/pywr-python/Cargo.toml +++ b/pywr-python/Cargo.toml @@ -22,7 +22,7 @@ pyo3-log = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } chrono = { workspace = true } - +pywr-v1-schema = { workspace = true } pywr-core = { path = "../pywr-core" } pywr-schema = { path = "../pywr-schema" } diff --git a/pywr-python/pywr/__init__.py b/pywr-python/pywr/__init__.py index a32b63f7..8e6b8167 100644 --- a/pywr-python/pywr/__init__.py +++ b/pywr-python/pywr/__init__.py @@ -1,7 +1,7 @@ from pathlib import Path from typing import Optional -from .pywr import Schema, Model # type: ignore +from .pywr import Schema, Model, convert_model_from_v1_json_string, convert_metric_from_v1_json_string # type: ignore def run_from_path( diff --git a/pywr-python/src/lib.rs b/pywr-python/src/lib.rs index 29dd3055..23907563 100644 --- a/pywr-python/src/lib.rs +++ b/pywr-python/src/lib.rs @@ -1,7 +1,7 @@ use chrono::NaiveDateTime; use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; -use pyo3::types::{PyDict, PyType}; +use pyo3::types::{PyDict, PyTuple, PyType}; /// Python API /// @@ -16,6 +16,8 @@ use pywr_core::solvers::{ClpSolver, ClpSolverSettings, ClpSolverSettingsBuilder} #[cfg(feature = "highs")] use pywr_core::solvers::{HighsSolver, HighsSolverSettings, HighsSolverSettings, HighsSolverSettingsBuilde}; use pywr_schema::model::DateType; +use pywr_schema::parameters::TryIntoV2Parameter; +use pywr_schema::ConversionError; use std::fmt; use std::path::PathBuf; use std::str::FromStr; @@ -95,13 +97,56 @@ impl Schema { Ok(data) } - /// Convert the schema to a Pywr model. + /// Build the schema in to a Pywr model. fn build(&mut self, data_path: Option, output_path: Option) -> PyResult { let model = self.schema.build_model(data_path.as_deref(), output_path.as_deref())?; Ok(Model { model }) } } +/// Convert a Pywr v1.x JSON string to a Pywr v2.x schema. +#[pyfunction] +fn convert_model_from_v1_json_string(py: Python, data: &str) -> PyResult> { + // Try to convert + let (schema, errors) = + pywr_schema::PywrModel::from_v1_str(data).map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + + // Create a new schema object + let py_schema = Schema { schema }; + let py_errors = errors.into_iter().map(|e| e.to_string()).collect::>(); + + let result = PyTuple::new_bound(py, &[py_schema.into_py(py), py_errors.into_py(py)]).into(); + Ok(result) +} + +#[pyclass] +pub struct Metric { + metric: pywr_schema::metric::Metric, +} + +#[pymethods] +impl Metric { + /// Serialize the metric to a JSON string. + fn to_json_string(&self) -> PyResult { + let data = serde_json::to_string_pretty(&self.metric).map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + Ok(data) + } +} + +/// Convert a Pywr v1.x JSON string to a Pywr v2.x metric. +#[pyfunction] +fn convert_metric_from_v1_json_string(_py: Python, data: &str) -> PyResult { + let v1: pywr_v1_schema::parameters::ParameterValue = + serde_json::from_str(data).map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + + let metric = v1 + .try_into_v2_parameter(None, &mut 0) + .map_err(|e: ConversionError| PyRuntimeError::new_err(e.to_string()))?; + + let py_metric = Metric { metric }; + Ok(py_metric) +} + #[pyclass] pub struct Model { model: pywr_core::models::Model, @@ -199,8 +244,11 @@ fn build_highs_settings(kwargs: Option<&PyDict>) -> PyResult) -> PyResult<()> { pyo3_log::init(); + m.add_function(wrap_pyfunction!(convert_model_from_v1_json_string, m)?)?; + m.add_function(wrap_pyfunction!(convert_metric_from_v1_json_string, m)?)?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/pywr-schema/src/model.rs b/pywr-schema/src/model.rs index b1bfda0e..39f0a60e 100644 --- a/pywr-schema/src/model.rs +++ b/pywr-schema/src/model.rs @@ -667,6 +667,15 @@ impl PywrModel { errors, ) } + + /// Convert a v1 JSON string to a v2 model. + /// + /// See [`PywrModel::from_v1`] for more information. + pub fn from_v1_str(v1: &str) -> Result<(Self, Vec), pywr_v1_schema::model::PywrSchemaError> { + let v1_model: pywr_v1_schema::PywrModel = serde_json::from_str(v1)?; + + Ok(Self::from_v1(v1_model)) + } } #[derive(serde::Deserialize, serde::Serialize, Clone)] diff --git a/pywr-schema/src/nodes/core.rs b/pywr-schema/src/nodes/core.rs index 94719fa5..cb2f8f0c 100644 --- a/pywr-schema/src/nodes/core.rs +++ b/pywr-schema/src/nodes/core.rs @@ -831,7 +831,7 @@ pub struct AggregatedStorageNode { } impl AggregatedStorageNode { - const DEFAULT_ATTRIBUTE: NodeAttribute = NodeAttribute::Outflow; + const DEFAULT_ATTRIBUTE: NodeAttribute = NodeAttribute::Volume; pub fn input_connectors(&self) -> Vec<(&str, Option)> { // Not connectable From b2182728f067a3a0209339972ec5a1bdd2870e5c Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Tue, 6 Aug 2024 10:25:34 +0100 Subject: [PATCH 2/2] fix: Response to review comments. --- Cargo.toml | 1 - pywr-schema/src/nodes/core.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7167258e..a7eebef2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,5 @@ tracing = { version = "0.1", features = ["log"] } csv = "1.1" hdf5 = { git = "https://github.com/aldanor/hdf5-rust.git", package = "hdf5", features = ["static", "zlib"] } pywr-v1-schema = "0.14" -# pywr-v1-schema = { path = "/home/james/dev/pywr/pywr-schema/pywr-v1-schema" } chrono = { version = "0.4.34", features = ["serde"] } schemars = { version = "0.8.16", features = ["chrono"] } diff --git a/pywr-schema/src/nodes/core.rs b/pywr-schema/src/nodes/core.rs index cb2f8f0c..94719fa5 100644 --- a/pywr-schema/src/nodes/core.rs +++ b/pywr-schema/src/nodes/core.rs @@ -831,7 +831,7 @@ pub struct AggregatedStorageNode { } impl AggregatedStorageNode { - const DEFAULT_ATTRIBUTE: NodeAttribute = NodeAttribute::Volume; + const DEFAULT_ATTRIBUTE: NodeAttribute = NodeAttribute::Outflow; pub fn input_connectors(&self) -> Vec<(&str, Option)> { // Not connectable