feat: Allow specifying calculation phase in some parameters. - #745
feat: Allow specifying calculation phase in some parameters.#745jetuk wants to merge 12 commits into
Conversation
This folows on from #726 to do the following: - pywr-core: Internally allocate space only for before and after parameters that are registered to supply values in those phases. Two new index types are added for this. - pywr-core: Validate consumer phasing with parameter return values during network build. This should reject invalid combinations at build time and avoid spurious missing values at runtime. - pywr-schema: Allow specifying the phase for Aggregated and AggregatedIndex parameters in the schema. - pywr-schema: Correctly construct parameter references for appropriate phase when building metric sets from all parameters. - pywr-schema: Add tests models for the issue identified in #725 (i.e. calculating and saving total deficit). What's left to do is implement alternative phase (i.e. after) for some of the other parameter types.
Batch21
left a comment
There was a problem hiding this comment.
I've gone through this in some detail and think the approach is good. A few queries/suggestions below.
| let metrics = resolve_metric_u64_vec!(self, &self.metrics, resolution_maps, self.phase, "metrics"); | ||
|
|
||
| let meta = self.meta; | ||
| let agg_func = self.agg_func; |
There was a problem hiding this comment.
does the builder need to match on phase when processing the metrics like in AggregatedParameterBuilder?
| } | ||
|
|
||
| #[test] | ||
| fn simple_before_parameter_can_depend_on_simple_before_value() { |
There was a problem hiding this comment.
do these removed tests need replacing or updating?
There was a problem hiding this comment.
I have added more unit tests.
| fn resolve_parameter_index_f64_to_metric_f64( | ||
| name: &ParameterName, |
There was a problem hiding this comment.
Should we have some unit tests that check that the correct errors are produced for different combinations of return values and phases.
Also, would it be possible to combined some of the logic from the resolve_parameter_index_* functions into a share function?
| Self::Polynomial1D(_) => ParameterPhase::Before, | ||
| Self::Threshold(_) => ParameterPhase::Before, | ||
| Self::TablesArray(_) => ParameterPhase::Before, | ||
| Self::Python(_) => ParameterPhase::Before, |
There was a problem hiding this comment.
The Python parameter could be After or Both which could cause an error when this is used by by metrics sets. Do these phases need to be determined dynamically from the registered parameters instead?
There was a problem hiding this comment.
Currently the parameter (in core) is hardcoded to be "before". I'll fix this along with other parameters in a follow-up.
| match idx { | ||
| // Constant and simple can always be resolved to a metric, regardless of the consumer phase | ||
| // as long as the parameter return value is "before". | ||
| ParameterIndex::Const(idx) => match parameter_return_value { |
There was a problem hiding this comment.
The other resolve_parameter_index_* function do not match on parameter_return_value from constant paramters like this one does.
| // Determine which calculation phase(s) the parameter will be used in | ||
| // based on whether the target and actual flow are provided. | ||
| // If neither is provided, return an error. | ||
| let phase = match (self.target.is_some(), self.actual_flow.is_some()) { |
There was a problem hiding this comment.
won't the phase here vary across the metrics? some will be access in before and some in after?
There was a problem hiding this comment.
Isn't that what the match statement is doing? Working out which phases this parameter will be in?
| // initial value is acceptable in the "before" phase, so we can resolve it to a metric provided the | ||
| // parameter index contains an "after" index. | ||
| match idx.after { | ||
| Some(after_idx) => Ok(MetricF64::ParameterAfterMulti { |
There was a problem hiding this comment.
This could result in a runtime error as it will try and look up a value in an empty hashmap?
There was a problem hiding this comment.
Yes, that's true. I think a follow-up idea would allow parameters to provide initial values.
| impl AsymmetricSwitchIndexParameterBuilder { | ||
| pub fn new(name: ParameterName, on_parameter: UnresolvedMetricU64, off_parameter: UnresolvedMetricU64) -> Self { | ||
| /// Create a new builder for [`AsymmetricSwitchIndexParameter`] that is evaluated in the "before" phase. | ||
| pub fn before(name: ParameterName, on_parameter: UnresolvedMetricU64, off_parameter: UnresolvedMetricU64) -> Self { |
There was a problem hiding this comment.
Some parameters, like this one, define a new before constructor method while others (like the max parameter) hardcode a before phase variable in their existing build methods. Is there a reasons for this? Should it be made consistent?
There was a problem hiding this comment.
I think this was just a bit of WIP. I was going to do this as the main framework, and then go back through and implement it for other parameters. So, I renamed this one but it doesn't support after at the moment.
| @@ -0,0 +1,133 @@ | |||
| { | |||
There was a problem hiding this comment.
This model isn't tested anywhere?
|
|
||
| impl AggregatedParameterBuilder { | ||
| pub fn new(name: ParameterName, agg_func: AggFuncF64) -> Self { | ||
| /// Create a new builder for [`AggregatedParameter`] that is evaluated in the "after" phase. |
…4 or multi parameters.
A new error type to fail a build if a metric would not be used.
This folows on from #726 to do the following:
What's left to do is implement alternative phase (i.e. after) for some of the other parameter types.