Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ rustdoc-args = ["--html-in-header", "katex-header.html"]
[profile.dev]
# Disabling debug info speeds up builds a bunch,
# and we don't rely on it for debugging that much.
debug = 0
# debug = 0

[profile.release]
opt-level = 3
Expand Down
85 changes: 63 additions & 22 deletions pywr-book/listings/adding-a-parameter/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,24 @@
#![allow(dead_code)]
use pywr_core::metric::MetricF64;
use pywr_core::network::Network;
use pywr_core::metric::{MetricF64, UnresolvedMetricF64};
use pywr_core::network::{Network, ResolutionMaps};
use pywr_core::parameters::{
GeneralParameter, Parameter, ParameterCalculationError, ParameterMeta, ParameterName, ParameterState,
BuiltParameter, GeneralCalculationError, GeneralParameter, MaybeBuiltParameter, Parameter, ParameterBuildError,
ParameterBuilder, ParameterMeta, ParameterName, ParameterState,
};
use pywr_core::resolve_metric_f64;
use pywr_core::scenario::ScenarioIndex;
use pywr_core::state::State;
use pywr_core::timestep::Timestep;

// ANCHOR: parameter
#[derive(Debug)]
pub struct MaxParameter {
meta: ParameterMeta,
metric: MetricF64,
threshold: f64,
}
// ANCHOR_END: parameter
// ANCHOR: impl-new
impl MaxParameter {
pub fn new(name: ParameterName, metric: MetricF64, threshold: f64) -> Self {
Self {
meta: ParameterMeta::new(name),
metric,
threshold,
}
}
}
// ANCHOR_END: impl-new

// ANCHOR: impl-parameter
impl Parameter for MaxParameter {
fn meta(&self) -> &ParameterMeta {
Expand All @@ -41,7 +34,7 @@ impl GeneralParameter<f64> for MaxParameter {
model: &Network,
state: &State,
_internal_state: &mut Option<Box<dyn ParameterState>>,
) -> Result<Option<f64>, ParameterCalculationError> {
) -> Result<Option<f64>, GeneralCalculationError> {
// Current value
let x = self.metric.get_value(model, state)?;
Ok(Some(x.max(self.threshold)))
Expand All @@ -55,10 +48,50 @@ impl GeneralParameter<f64> for MaxParameter {
}
}

// ANCHOR: impl-builder
#[derive(Debug)]
pub struct MaxParameterBuilder {
meta: ParameterMeta,
metric: UnresolvedMetricF64,
threshold: f64,
}

impl MaxParameterBuilder {
pub fn new(name: ParameterName, metric: UnresolvedMetricF64, threshold: f64) -> Self {
Self {
meta: ParameterMeta::new(name),
metric,
threshold,
}
}
}

impl ParameterBuilder<f64> for MaxParameterBuilder {
fn name(&self) -> &ParameterName {
&self.meta.name
}

fn build(
self: Box<Self>,
resolution_maps: &ResolutionMaps,
) -> Result<MaybeBuiltParameter<f64>, ParameterBuildError> {
let metric = resolve_metric_f64!(self, self.metric, resolution_maps, "metric");

let p = MaxParameter {
meta: self.meta,
metric,
threshold: self.threshold,
};

Ok(MaybeBuiltParameter::Built(BuiltParameter::General(Box::new(p))))
}
}
// ANCHOR_END: impl-builder

// ANCHOR_END: impl-parameter
mod schema {
#[cfg(feature = "core")]
use pywr_core::parameters::ParameterIndex;
use pywr_core::parameters::ParameterName;
use pywr_schema::metric::Metric;
use pywr_schema::parameters::ParameterMeta;
#[cfg(feature = "core")]
Expand All @@ -78,16 +111,24 @@ mod schema {
// ANCHOR: schema-impl
#[cfg(feature = "core")]
impl MaxParameter {
pub fn add_to_model(
pub fn add_to_network(
&self,
network: &mut pywr_core::network::Network,
network: &mut pywr_core::network::NetworkBuilder,
args: &LoadArgs,
) -> Result<ParameterIndex<f64>, SchemaError> {
let idx = self.parameter.load(network, args, Some(&self.meta.name))?;
parent: Option<&str>,
) -> Result<(), SchemaError> {
let idx = self.parameter.load(network, args, None)?;
let threshold = self.threshold.unwrap_or(0.0);

let p = pywr_core::parameters::MaxParameter::new(self.meta.name.as_str().into(), idx, threshold);
Ok(network.add_parameter(Box::new(p))?)
let p = pywr_core::parameters::MaxParameterBuilder::new(
ParameterName::new(&self.meta.name, parent),
idx,
threshold,
);

network.parameters().f64(Box::new(p));

Ok(())
}
}
// ANCHOR_END: schema-impl
Expand Down
10 changes: 7 additions & 3 deletions pywr-book/src/developers-guide/adding-a-parameter.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,15 @@ The `ParameterMeta` struct is used to store the metadata for all parameters and
{{#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.
To allow the parameter to be used in the model a "builder" is required. This struct must implement the
`ParameterBuilder<T>` trait. This builder be used by the schema to create the parameter when it is loaded from a model
file. Builders will typically have the same fields as the parameter itself, but will use "unresolved" types
(e.g. `UnresolvedMetricF64` or `UnresolvedNode`). This allows the builder to be created without first resolving the
dependencies of the parameter. The `build` function is then used to resolve the dependencies and create the parameter
used in the model.

```rust,ignore
{{#rustdoc_include ../../listings/adding-a-parameter/src/main.rs:impl-new}}
{{#rustdoc_include ../../listings/adding-a-parameter/src/main.rs:impl-builder}}
```

Finally, the minimum implementation of the `Parameter` and one of the three types of parameter compute traits should be
Expand Down
11 changes: 7 additions & 4 deletions pywr-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use pywr_core::solvers::{HighsSolver, HighsSolverSettings, HighsSolverSettingsBu
use pywr_core::solvers::{MicroLpSolver, MicroLpSolverSettings, MicroLpSolverSettingsBuilder};
#[cfg(feature = "ipm-simd")]
use pywr_core::solvers::{SimdIpmF64Solver, SimdIpmSolverSettings, SimdIpmSolverSettingsBuilder};
use pywr_core::test_utils::make_random_model;
use pywr_core::test_utils::make_random_model_builder;
use pywr_schema::{ComponentConversionError, ModelSchema, MultiNetworkModelSchema, NetworkSchema};
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
Expand Down Expand Up @@ -278,7 +278,8 @@ fn run(
let data_path = data_path.or_else(|| path.parent());
let schema_v2: ModelSchema = serde_json::from_str(data.as_str()).unwrap();

let model = schema_v2.build_model(data_path, output_path).unwrap();
let builder = schema_v2.create_model_builder(data_path, output_path).unwrap();
let model = builder.build().unwrap();

match *solver {
Solver::Clp => {
Expand Down Expand Up @@ -384,7 +385,8 @@ fn run_multi(path: &Path, solver: &Solver, data_path: Option<&Path>, output_path

let schema_v2: MultiNetworkModelSchema = serde_json::from_str(data.as_str()).unwrap();

let model = schema_v2.build_model(data_path, output_path).unwrap();
let builder = schema_v2.create_model_builder(data_path, output_path).unwrap();
let model = builder.build().unwrap();

match *solver {
Solver::Clp => model.run::<ClpSolver>(&ClpSolverSettings::default()),
Expand All @@ -406,7 +408,8 @@ fn run_multi(path: &Path, solver: &Solver, data_path: Option<&Path>, output_path

fn run_random(num_systems: usize, density: usize, num_scenarios: usize, solver: &Solver) {
let mut rng = ChaCha8Rng::seed_from_u64(0);
let model = make_random_model(num_systems, density, num_scenarios, &mut rng).unwrap();
let builder = make_random_model_builder(num_systems, density, num_scenarios, &mut rng);
let model = builder.build().unwrap();

match *solver {
Solver::Clp => model.run::<ClpSolver>(&ClpSolverSettings::default()),
Expand Down
6 changes: 4 additions & 2 deletions pywr-core/benches/random_models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use pywr_core::solvers::{ClpSolver, ClpSolverSettings, ClpSolverSettingsBuilder}
use pywr_core::solvers::{HighsSolver, HighsSolverSettings};
#[cfg(feature = "ipm-simd")]
use pywr_core::solvers::{SimdIpmF64Solver, SimdIpmSolverSettings, SimdIpmSolverSettingsBuilder};
use pywr_core::test_utils::make_random_model;
use pywr_core::test_utils::make_random_model_builder;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
#[cfg(feature = "ipm-ocl")]
Expand Down Expand Up @@ -44,7 +44,9 @@ fn random_benchmark(
// Make a consistent random number generator
// ChaCha8 should be consistent across builds and platforms
let mut rng = ChaCha8Rng::seed_from_u64(0);
let model = make_random_model(n_sys, density, n_sc, &mut rng).unwrap();
let model = make_random_model_builder(n_sys, density, n_sc, &mut rng)
.build()
.expect("Failed to build random model!");
let num_timesteps = model.domain().time().timesteps().len();

// This is the number of time-steps
Expand Down
Loading