Loading examples/dev/reservoirs/reservoir_config.yaml 0 → 100644 +10 −0 Original line number Diff line number Diff line Wolf_Creek: capacity: 100.0 initial_storage: 50.0 max_release: 15.0 min_release: 3.0 Cordell_Hull: capacity: 80.0 initial_storage: 40.0 max_release: 12.0 min_release: 2.0 No newline at end of file examples/dev/script.qmd +56 −8 Original line number Diff line number Diff line Loading @@ -10,29 +10,77 @@ library(dplyr) library(lubridate) library(readr) library(ggplot2) library(patchwork) setwd("examples/dev/") use_condaenv("powersheds") ``` ```{python} import pandas as pd import powersheds import yaml from dataclasses import dataclass ``` ```{python} @dataclass class ReservoirSpecs: capacity: float initial_storage: float max_release: float min_release: float # Read the reservoir configuration with open('reservoirs/reservoir_config.yaml', 'r') as file: config_dict = yaml.safe_load(file) df = pd.read_csv("examples/dev/inflow_release_ex1.csv") inflow = df['inflow'] release = df['release'] # Convert dictionary to class instances wolf_creek = ReservoirSpecs(**config_dict['Wolf_Creek']) cordell_hull = ReservoirSpecs(**config_dict['Cordell_Hull']) storage, actual_release, spill = powersheds.simulate_cascade(inflow, release) df = pd.read_csv("inflow_release_ex1.csv") inflow = df['inflow'].tolist() release = df['release'].tolist() storage, actual_release, spill = powersheds.simulate_cascade(inflow, release, cordell_hull) ``` ```{r} ```{r} tibble( inflow = py$inflow, release_target = py$release, storage = py$storage, release = py$actual_release, release_implemented = py$actual_release, spill = py$spill ) |> bind_cols(read_csv("examples/dev/inflow_release_ex1.csv")) |> ggplot(aes(datetime, storage)) + geom_line() mutate(hr_index = 1:n()) -> ts_data_for_plot powersheds_plot_theme <- function(){ theme_classic() + theme( axis.text = element_text(size = 18), axis.title = element_text(size = 18), legend.text = element_text(size = 18), legend.position = "top") } ts_data_for_plot |> select(hr_index, inflow, release_target, release_implemented, spill) |> tidyr::pivot_longer(-hr_index, names_to = "variable") |> ggplot(aes(hr_index, value, col = variable)) + geom_line() + powersheds_plot_theme() -> flows_plot ts_data_for_plot |> select(hr_index, storage) |> ggplot(aes(hr_index, storage)) + geom_line() + powersheds_plot_theme() -> storage_plot flows_plot + storage_plot + plot_layout(ncol = 1) ``` No newline at end of file src/lib.rs +18 −13 Original line number Diff line number Diff line use pyo3::prelude::*; use pyo3::exceptions::PyValueError; #[derive(FromPyObject)] struct ReservoirSpecs { capacity: f64, initial_storage: f64, max_release: f64, min_release: f64, } /// Simulates a cascade of reservoirs #[pyfunction] fn simulate_cascade(inflow: Vec<f64>, release: Vec<f64>) -> PyResult<(Vec<f64>, Vec<f64>, Vec<f64>)> { fn simulate_cascade(inflow: Vec<f64>, release: Vec<f64>, specs: ReservoirSpecs) -> PyResult<(Vec<f64>, Vec<f64>, Vec<f64>)> { // Reservoir specifications const CAPACITY: f64 = 100.0; // Maximum storage in MCM (Million cubic meters) const INITIAL_STORAGE: f64 = 50.0; // Initial storage in MCM const MAX_RELEASE: f64 = 15.0; // Maximum release per timestep in MCM const MIN_RELEASE: f64 = 3.0; // Minimum release per timestep in MCM if inflow.len() != release.len() { return Err(PyValueError::new_err("Input vectors must have the same length")); Loading @@ -21,28 +26,28 @@ fn simulate_cascade(inflow: Vec<f64>, release: Vec<f64>) -> PyResult<(Vec<f64>, let mut spill = vec![0.0; n]; // Spill for timestep // Initialize first timestep storage storage[0] = INITIAL_STORAGE; storage[0] = specs.initial_storage; for t in 0..n { let available_water = if t == 0 { INITIAL_STORAGE + inflow[t] specs.initial_storage + inflow[t] } else { storage[t-1] + inflow[t] }; // Calculate actual release (constrained by available water and max/min release) actual_release[t] = release[t] .max(MIN_RELEASE) // Ensure we meet minimum release .min(MAX_RELEASE) // Cannot exceed maximum release .max(specs.min_release) // Ensure we meet minimum release .min(specs.max_release) // Cannot exceed maximum release .min(available_water); // Cannot release more than available // Calculate new storage let potential_storage = available_water - actual_release[t]; // Calculate spill if storage would exceed capacity if potential_storage > CAPACITY { storage[t] = CAPACITY; spill[t] = potential_storage - CAPACITY; if potential_storage > specs.capacity { storage[t] = specs.capacity; spill[t] = potential_storage - specs.capacity; } else { storage[t] = potential_storage; spill[t] = 0.0; Loading Loading
examples/dev/reservoirs/reservoir_config.yaml 0 → 100644 +10 −0 Original line number Diff line number Diff line Wolf_Creek: capacity: 100.0 initial_storage: 50.0 max_release: 15.0 min_release: 3.0 Cordell_Hull: capacity: 80.0 initial_storage: 40.0 max_release: 12.0 min_release: 2.0 No newline at end of file
examples/dev/script.qmd +56 −8 Original line number Diff line number Diff line Loading @@ -10,29 +10,77 @@ library(dplyr) library(lubridate) library(readr) library(ggplot2) library(patchwork) setwd("examples/dev/") use_condaenv("powersheds") ``` ```{python} import pandas as pd import powersheds import yaml from dataclasses import dataclass ``` ```{python} @dataclass class ReservoirSpecs: capacity: float initial_storage: float max_release: float min_release: float # Read the reservoir configuration with open('reservoirs/reservoir_config.yaml', 'r') as file: config_dict = yaml.safe_load(file) df = pd.read_csv("examples/dev/inflow_release_ex1.csv") inflow = df['inflow'] release = df['release'] # Convert dictionary to class instances wolf_creek = ReservoirSpecs(**config_dict['Wolf_Creek']) cordell_hull = ReservoirSpecs(**config_dict['Cordell_Hull']) storage, actual_release, spill = powersheds.simulate_cascade(inflow, release) df = pd.read_csv("inflow_release_ex1.csv") inflow = df['inflow'].tolist() release = df['release'].tolist() storage, actual_release, spill = powersheds.simulate_cascade(inflow, release, cordell_hull) ``` ```{r} ```{r} tibble( inflow = py$inflow, release_target = py$release, storage = py$storage, release = py$actual_release, release_implemented = py$actual_release, spill = py$spill ) |> bind_cols(read_csv("examples/dev/inflow_release_ex1.csv")) |> ggplot(aes(datetime, storage)) + geom_line() mutate(hr_index = 1:n()) -> ts_data_for_plot powersheds_plot_theme <- function(){ theme_classic() + theme( axis.text = element_text(size = 18), axis.title = element_text(size = 18), legend.text = element_text(size = 18), legend.position = "top") } ts_data_for_plot |> select(hr_index, inflow, release_target, release_implemented, spill) |> tidyr::pivot_longer(-hr_index, names_to = "variable") |> ggplot(aes(hr_index, value, col = variable)) + geom_line() + powersheds_plot_theme() -> flows_plot ts_data_for_plot |> select(hr_index, storage) |> ggplot(aes(hr_index, storage)) + geom_line() + powersheds_plot_theme() -> storage_plot flows_plot + storage_plot + plot_layout(ncol = 1) ``` No newline at end of file
src/lib.rs +18 −13 Original line number Diff line number Diff line use pyo3::prelude::*; use pyo3::exceptions::PyValueError; #[derive(FromPyObject)] struct ReservoirSpecs { capacity: f64, initial_storage: f64, max_release: f64, min_release: f64, } /// Simulates a cascade of reservoirs #[pyfunction] fn simulate_cascade(inflow: Vec<f64>, release: Vec<f64>) -> PyResult<(Vec<f64>, Vec<f64>, Vec<f64>)> { fn simulate_cascade(inflow: Vec<f64>, release: Vec<f64>, specs: ReservoirSpecs) -> PyResult<(Vec<f64>, Vec<f64>, Vec<f64>)> { // Reservoir specifications const CAPACITY: f64 = 100.0; // Maximum storage in MCM (Million cubic meters) const INITIAL_STORAGE: f64 = 50.0; // Initial storage in MCM const MAX_RELEASE: f64 = 15.0; // Maximum release per timestep in MCM const MIN_RELEASE: f64 = 3.0; // Minimum release per timestep in MCM if inflow.len() != release.len() { return Err(PyValueError::new_err("Input vectors must have the same length")); Loading @@ -21,28 +26,28 @@ fn simulate_cascade(inflow: Vec<f64>, release: Vec<f64>) -> PyResult<(Vec<f64>, let mut spill = vec![0.0; n]; // Spill for timestep // Initialize first timestep storage storage[0] = INITIAL_STORAGE; storage[0] = specs.initial_storage; for t in 0..n { let available_water = if t == 0 { INITIAL_STORAGE + inflow[t] specs.initial_storage + inflow[t] } else { storage[t-1] + inflow[t] }; // Calculate actual release (constrained by available water and max/min release) actual_release[t] = release[t] .max(MIN_RELEASE) // Ensure we meet minimum release .min(MAX_RELEASE) // Cannot exceed maximum release .max(specs.min_release) // Ensure we meet minimum release .min(specs.max_release) // Cannot exceed maximum release .min(available_water); // Cannot release more than available // Calculate new storage let potential_storage = available_water - actual_release[t]; // Calculate spill if storage would exceed capacity if potential_storage > CAPACITY { storage[t] = CAPACITY; spill[t] = potential_storage - CAPACITY; if potential_storage > specs.capacity { storage[t] = specs.capacity; spill[t] = potential_storage - specs.capacity; } else { storage[t] = potential_storage; spill[t] = 0.0; Loading