Skip to content

Commit

Permalink
feat: Add division parameter (#40)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: James Tomlinson <[email protected]>
  • Loading branch information
s-simoncelli and jetuk authored Sep 18, 2023
1 parent 5f9702c commit 100a9fd
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ pub enum PywrError {
MetricNotDefinedForNode,
#[error("invalid metric type: {0}")]
InvalidMetricType(String),
#[error("invalid metric value: {0}")]
InvalidMetricValue(String),
#[error("recorder not initialised")]
RecorderNotInitialised,
#[error("hdf5 error: {0}")]
Expand Down
55 changes: 55 additions & 0 deletions src/parameters/division.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use super::PywrError;
use crate::metric::Metric;
use crate::model::Model;
use crate::parameters::{Parameter, ParameterMeta};
use crate::scenario::ScenarioIndex;
use crate::state::State;
use crate::timestep::Timestep;
use crate::PywrError::InvalidMetricValue;
use std::any::Any;

pub struct DivisionParameter {
meta: ParameterMeta,
numerator: Metric,
denominator: Metric,
}

impl DivisionParameter {
pub fn new(name: &str, numerator: Metric, denominator: Metric) -> Self {
Self {
meta: ParameterMeta::new(name),
numerator,
denominator,
}
}
}

impl Parameter for DivisionParameter {
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn meta(&self) -> &ParameterMeta {
&self.meta
}
fn compute(
&self,
_timestep: &Timestep,
_scenario_index: &ScenarioIndex,
model: &Model,
state: &State,
_internal_state: &mut Option<Box<dyn Any + Send>>,
) -> Result<f64, PywrError> {
// TODO handle scenarios
let denominator = self.denominator.get_value(model, state)?;

if denominator == 0.0 {
return Err(InvalidMetricValue(format!(
"Division by zero creates a NaN in {}.",
self.name()
)));
}

let numerator = self.numerator.get_value(model, state)?;
Ok(numerator / denominator)
}
}
2 changes: 2 additions & 0 deletions src/parameters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod array;
mod asymmetric;
mod control_curves;
mod delay;
mod division;
mod indexed_array;
mod max;
mod min;
Expand Down Expand Up @@ -32,6 +33,7 @@ pub use control_curves::{
PiecewiseInterpolatedParameter,
};
pub use delay::DelayParameter;
pub use division::DivisionParameter;
pub use indexed_array::IndexedArrayParameter;
pub use max::MaxParameter;
pub use min::MinParameter;
Expand Down
75 changes: 73 additions & 2 deletions src/schema/parameters/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use crate::schema::parameters::{
};
use crate::{ParameterIndex, PywrError};
use pywr_schema::parameters::{
ConstantParameter as ConstantParameterV1, MaxParameter as MaxParameterV1, MinParameter as MinParameterV1,
NegativeParameter as NegativeParameterV1,
ConstantParameter as ConstantParameterV1, DivisionParameter as DivisionParameterV1, MaxParameter as MaxParameterV1,
MinParameter as MinParameterV1, NegativeParameter as NegativeParameterV1,
};
use std::collections::HashMap;
use std::path::Path;
Expand Down Expand Up @@ -115,6 +115,77 @@ impl TryFromV1Parameter<MaxParameterV1> for MaxParameter {
}
}

/// This parameter divides one Parameter by another.
///
/// # Arguments
///
/// * `numerator` - The parameter to use as the numerator (or dividend).
/// * `denominator` - The parameter to use as the denominator (or divisor).
///
/// # Examples
///
/// ```json
/// {
/// "type": "Division",
/// "numerator": {
/// "type": "MonthlyProfile",
/// "values": [1, 4, 5, 9, 1, 5, 10, 8, 11, 9, 11 ,12]
/// },
/// "denominator": {
/// "type": "Constant",
/// "value": 0.3
/// }
/// }
/// ```
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
pub struct DivisionParameter {
#[serde(flatten)]
pub meta: ParameterMeta,
pub numerator: DynamicFloatValue,
pub denominator: DynamicFloatValue,
}

impl DivisionParameter {
pub fn node_references(&self) -> HashMap<&str, &str> {
HashMap::new()
}

pub fn add_to_model(
&self,
model: &mut crate::model::Model,
tables: &LoadedTableCollection,
data_path: Option<&Path>,
) -> Result<ParameterIndex, PywrError> {
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))
}
}

impl TryFromV1Parameter<DivisionParameterV1> for DivisionParameter {
type Error = ConversionError;

fn try_from_v1_parameter(
v1: DivisionParameterV1,
parent_node: Option<&str>,
unnamed_count: &mut usize,
) -> Result<Self, Self::Error> {
let meta: ParameterMeta = v1.meta.into_v2_parameter(parent_node, unnamed_count);

let numerator = v1.numerator.try_into_v2_parameter(Some(&meta.name), unnamed_count)?;
let denominator = v1.denominator.try_into_v2_parameter(Some(&meta.name), unnamed_count)?;

let p = Self {
meta,
numerator,
denominator,
};
Ok(p)
}
}

/// This parameter takes the minimum of another Parameter and a constant value (threshold).
///
/// # Arguments
Expand Down
8 changes: 7 additions & 1 deletion src/schema/parameters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ 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 pywr_schema::parameters::{
Expand Down Expand Up @@ -143,6 +144,7 @@ pub enum Parameter {
Python(PythonParameter),
DataFrame(DataFrameParameter),
Delay(DelayParameter),
Division(DivisionParameter),
}

impl Parameter {
Expand All @@ -168,6 +170,7 @@ impl Parameter {
Self::TablesArray(p) => p.meta.name.as_str(),
Self::Python(p) => p.meta.name.as_str(),
Self::DataFrame(p) => p.meta.name.as_str(),
Self::Division(p) => p.meta.name.as_str(),
Parameter::Delay(p) => p.meta.name.as_str(),
}
}
Expand Down Expand Up @@ -197,6 +200,7 @@ impl Parameter {
Self::Python(p) => p.node_references(),
Self::DataFrame(p) => p.node_references(),
Self::Delay(p) => p.node_references(),
Self::Division(p) => p.node_references(),
}
}

Expand Down Expand Up @@ -242,6 +246,7 @@ impl Parameter {
Self::Python(_) => "Python",
Self::DataFrame(_) => "DataFrame",
Self::Delay(_) => "Delay",
Self::Division(_) => "Division",
}
}

Expand Down Expand Up @@ -275,6 +280,7 @@ impl Parameter {
Self::Python(p) => p.add_to_model(model, tables, data_path)?,
Self::DataFrame(p) => ParameterType::Parameter(p.add_to_model(model, data_path)?),
Self::Delay(p) => ParameterType::Parameter(p.add_to_model(model, tables, data_path)?),
Self::Division(p) => ParameterType::Parameter(p.add_to_model(model, tables, data_path)?),
};

Ok(ty)
Expand Down Expand Up @@ -338,7 +344,7 @@ impl TryFromV1Parameter<ParameterV1> for Parameter {
Parameter::TablesArray(p.try_into_v2_parameter(parent_node, unnamed_count)?)
}
CoreParameter::Min(p) => Parameter::Min(p.try_into_v2_parameter(parent_node, unnamed_count)?),
CoreParameter::Division(_) => todo!(),
CoreParameter::Division(p) => Parameter::Division(p.try_into_v2_parameter(parent_node, unnamed_count)?),
},
ParameterV1::Custom(p) => {
println!("Custom parameter: {:?} ({})", p.meta.name, p.ty);
Expand Down

0 comments on commit 100a9fd

Please sign in to comment.