Skip to content

Commit

Permalink
feat: Add pywr-book listings to workspace.
Browse files Browse the repository at this point in the history
  • Loading branch information
jetuk committed Jul 5, 2024
1 parent 0db363d commit ed4187b
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 65 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
echo "$(pwd)/bin" >> $GITHUB_PATH
- name: Build
run: cargo build --verbose --features highs
run: cargo build --verbose --features highs --workspace --exclude ipm-simd --exclude pywr-python
- name: Run tests
run: cargo test --features highs
- name: Run mdbook tests
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ jobs:
with:
submodules: true
- name: Build
run: cargo build --verbose
run: cargo build --verbose --workspace --exclude ipm-simd --exclude pywr-python
- name: Run tests
run: cargo test
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ members = [
"pywr-cli",
"pywr-python",
"pywr-schema-macros",
# These are the listings for the book
"pywr-book/listings/*",
]
exclude = [
"tests/models/simple-wasm/simple-wasm-parameter"
Expand Down
11 changes: 11 additions & 0 deletions pywr-book/listings/adding-a-parameter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "adding-a-parameter"
version = "0.1.0"
edition = "2021"

[dependencies]
pywr-core = { path = "../../../pywr-core" }
pywr-schema = { path = "../../../pywr-schema" }
pywr-schema-macros = { path = "../../../pywr-schema-macros" }
serde = { workspace = true }
schemars = { workspace = true }
81 changes: 81 additions & 0 deletions pywr-book/listings/adding-a-parameter/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use pywr_core::metric::MetricF64;
use pywr_core::network::Network;
use pywr_core::parameters::{Parameter, ParameterMeta};
use pywr_core::scenario::ScenarioIndex;
use pywr_core::state::{ParameterState, State};
use pywr_core::timestep::Timestep;
use pywr_core::PywrError;

// ANCHOR: parameter
pub struct MaxParameter {
meta: ParameterMeta,
metric: MetricF64,
threshold: f64,
}
// ANCHOR_END: parameter
// ANCHOR: impl-new
impl MaxParameter {
pub fn new(name: &str, metric: MetricF64, threshold: f64) -> Self {
Self {
meta: ParameterMeta::new(name),
metric,
threshold,
}
}
}
// ANCHOR_END: impl-new
// ANCHOR: impl-parameter
impl Parameter<f64> for MaxParameter {
fn meta(&self) -> &ParameterMeta {
&self.meta
}
fn compute(
&self,
_timestep: &Timestep,
_scenario_index: &ScenarioIndex,
model: &Network,
state: &State,
_internal_state: &mut Option<Box<dyn ParameterState>>,
) -> Result<f64, PywrError> {
// Current value
let x = self.metric.get_value(model, state)?;
Ok(x.max(self.threshold))
}
}
// ANCHOR_END: impl-parameter
mod schema {
use pywr_schema::metric::Metric;
use pywr_schema::parameters::ParameterMeta;
use schemars::JsonSchema;

// ANCHOR: schema
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, JsonSchema)]
pub struct MaxParameter {
#[serde(flatten)]
pub meta: ParameterMeta,
pub parameter: Metric,
pub threshold: Option<f64>,
}

// ANCHOR_END: schema
// ANCHOR: schema-impl
#[cfg(feature = "core")]
impl MaxParameter {
pub fn add_to_model(
&self,
network: &mut pywr_core::network::Network,
args: &LoadArgs,
) -> Result<ParameterIndex<f64>, SchemaError> {
let idx = self.parameter.load(network, args)?;
let threshold = self.threshold.unwrap_or(0.0);

let p = pywr_core::parameters::MaxParameter::new(&self.meta.name, idx, threshold);
Ok(network.add_parameter(Box::new(p))?)
}
}
// ANCHOR_END: schema-impl
}

fn main() {
println!("Hello, world!");
}
71 changes: 14 additions & 57 deletions pywr-book/src/developers-guide/adding-a-parameter.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,15 @@ The threshold is a constant value that is set when the parameter is created.
Finally, the `meta` field stores the metadata for the parameter.
The `ParameterMeta` struct is used to store the metadata for all parameters and can be reused.

```rust
pub struct MaxParameter {
meta: ParameterMeta,
metric: MetricF64,
threshold: f64,
}
```rust,ignore
{{#rustdoc_include ../../listings/adding-a-parameter/src/main.rs:parameter}}
```

To allow the parameter to be used in the model it is helpful to add a `new` function that creates a new instance of the
parameter. This will be used by the schema to create the parameter when it is loaded from a model file.

```rust
impl MaxParameter {
pub fn new(name: &str, metric: MetricF64, threshold: f64) -> Self {
Self {
meta: ParameterMeta::new(name),
metric,
threshold,
}
}
}
```rust,ignore
{{#rustdoc_include ../../listings/adding-a-parameter/src/main.rs:impl-new}}
```

Finally, the minimum implementation of the `Parameter` trait should be added for `MaxParameter`.
Expand All @@ -66,24 +54,8 @@ calculate the value of the parameter at a given timestep and scenario.
In this case the `compute` function calculates the maximum value of the metric and the threshold.
The value of the metric is obtained from the model using the `get_value` function.

```rust
impl Parameter<f64> for MaxParameter {
fn meta(&self) -> &ParameterMeta {
&self.meta
}
fn compute(
&self,
_timestep: &Timestep,
_scenario_index: &ScenarioIndex,
model: &Network,
state: &State,
_internal_state: &mut Option<Box<dyn ParameterState>>,
) -> Result<f64, PywrError> {
// Current value
let x = self.metric.get_value(model, state)?;
Ok(x.max(self.threshold))
}
}
```rust,ignore
{{#rustdoc_include ../../listings/adding-a-parameter/src/main.rs:impl-parameter}}
```

### Adding the schema definition to `pywr-schema`
Expand All @@ -98,14 +70,12 @@ types for the fields.
The struct should also derive `serde::Deserialize`, `serde::Serialize`, `Debug`, `Clone`, `JsonSchema`,
and `PywrVisitAll` to be compatible with the rest of Pywr.

```rust
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, JsonSchema, PywrVisitAll)]
pub struct MaxParameter {
#[serde(flatten)]
pub meta: ParameterMeta,
pub parameter: Metric,
pub threshold: Option<f64>,
}
> Note: The `PywrVisitAll` derive is not shown in the listing as it can not currently be used outside
> the `pywr-schema` crate.
```rust,ignore
{{#rustdoc_include ../../listings/adding-a-parameter/src/main.rs:schema}}
```

Next, the parameter needs a method to add itself to a network.
Expand All @@ -117,21 +87,8 @@ The method should load the metric from the model using the `load` method, and th
the `new` method implemented above.
Finally, the method should add the parameter to the network using the `add_parameter` method.

```rust
#[cfg(feature = "core")]
impl MaxParameter {
pub fn add_to_model(
&self,
network: &mut pywr_core::network::Network,
args: &LoadArgs,
) -> Result<ParameterIndex<f64>, SchemaError> {
let idx = self.parameter.load(network, args)?;
let threshold = self.threshold.unwrap_or(0.0);

let p = pywr_core::parameters::MaxParameter::new(&self.meta.name, idx, threshold);
Ok(network.add_parameter(Box::new(p))?)
}
}
```rust,ignore
{{#rustdoc_include ../../listings/adding-a-parameter/src/main.rs:schema-impl}}
```

Finally, the schema definition should be added to the `Parameter` enum in the `parameters` module.
Expand Down
2 changes: 1 addition & 1 deletion pywr-core/src/parameters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ pub struct ParameterMeta {
}

impl ParameterMeta {
fn new(name: &str) -> Self {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
comment: "".to_string(),
Expand Down
6 changes: 1 addition & 5 deletions pywr-schema/src/timeseries/align_and_resample.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,7 @@ pub fn align_and_resample(
// TODO: this does not extend the dataframe beyond its original end date. Should it do when using a forward fill strategy?
// The df could be extend by the length of the duration it is being resampled to.
df.clone()
.upsample::<[String; 0]>(
[],
"time",
Duration::parse(model_duration_string.as_str()),
)?
.upsample::<[String; 0]>([], "time", Duration::parse(model_duration_string.as_str()))?
.fill_null(FillNullStrategy::Forward(None))?
}
Ordering::Equal => df,
Expand Down

0 comments on commit ed4187b

Please sign in to comment.