@@ -7,7 +7,7 @@ use crate::asset::check_capacity_valid_for_asset;
77use crate :: input:: {
88 deserialise_proportion_nonzero, input_err_msg, is_sorted_and_unique, read_toml,
99} ;
10- use crate :: units:: { Capacity , Dimensionless , MoneyPerFlow } ;
10+ use crate :: units:: { Capacity , Dimensionless , Flow , MoneyPerFlow } ;
1111use anyhow:: { Context , Result , ensure} ;
1212use log:: warn;
1313use serde:: Deserialize ;
@@ -76,6 +76,7 @@ define_unit_param_default!(default_candidate_asset_capacity, Capacity, 0.0001);
7676define_unit_param_default ! ( default_capacity_limit_factor, Dimensionless , 0.1 ) ;
7777define_unit_param_default ! ( default_value_of_lost_load, MoneyPerFlow , 1e9 ) ;
7878define_unit_param_default ! ( default_price_tolerance, Dimensionless , 1e-6 ) ;
79+ define_unit_param_default ! ( default_remaining_demand_absolute_tolerance, Flow , 1e-12 ) ;
7980define_param_default ! ( default_max_ironing_out_iterations, u32 , 10 ) ;
8081define_param_default ! ( default_capacity_margin, f64 , 0.2 ) ;
8182define_param_default ! ( default_mothball_years, u32 , 0 ) ;
@@ -123,6 +124,9 @@ pub struct ModelParameters {
123124 /// Number of years an asset can remain unused before being decommissioned
124125 #[ serde( default = "default_mothball_years" ) ]
125126 pub mothball_years : u32 ,
127+ /// Absolute tolerance when checking if remaining demand is close enough to zero
128+ #[ serde( default = "default_remaining_demand_absolute_tolerance" ) ]
129+ pub remaining_demand_absolute_tolerance : Flow ,
126130}
127131
128132/// Check that the `milestone_years` parameter is valid
@@ -164,6 +168,29 @@ fn check_price_tolerance(value: Dimensionless) -> Result<()> {
164168 Ok ( ( ) )
165169}
166170
171+ fn check_remaining_demand_absolute_tolerance (
172+ allow_broken_options : bool ,
173+ value : Flow ,
174+ ) -> Result < ( ) > {
175+ ensure ! (
176+ value. is_finite( ) && value >= Flow ( 0.0 ) ,
177+ "remaining_demand_absolute_tolerance must be a finite number greater than or equal to zero"
178+ ) ;
179+
180+ let default_value = default_remaining_demand_absolute_tolerance ( ) ;
181+ if !allow_broken_options {
182+ ensure ! (
183+ value == default_value,
184+ "Setting a remaining_demand_absolute_tolerance different from the default value of {:e} \
185+ is potentially dangerous, set please_give_me_broken_results to true \
186+ if you want to allow this.",
187+ default_value. 0
188+ ) ;
189+ }
190+
191+ Ok ( ( ) )
192+ }
193+
167194/// Check that the `capacity_margin` parameter is valid
168195fn check_capacity_margin ( value : f64 ) -> Result < ( ) > {
169196 ensure ! (
@@ -229,6 +256,12 @@ impl ModelParameters {
229256 // capacity_margin
230257 check_capacity_margin ( self . capacity_margin ) ?;
231258
259+ // remaining_demand_absolute_tolerance
260+ check_remaining_demand_absolute_tolerance (
261+ self . allow_broken_options ,
262+ self . remaining_demand_absolute_tolerance ,
263+ ) ?;
264+
232265 Ok ( ( ) )
233266 }
234267}
@@ -356,6 +389,57 @@ mod tests {
356389 ) ;
357390 }
358391
392+ #[ rstest]
393+ #[ case( true , 0.0 , true ) ] // Valid minimum value broken options allowed
394+ #[ case( true , 1e-10 , true ) ] // Valid value with broken options allowed
395+ #[ case( true , 1e-15 , true ) ] // Valid value with broken options allowed
396+ #[ case( false , 1e-12 , true ) ] // Valid value same as default, no broken options needed
397+ #[ case( true , 1.0 , true ) ] // Valid larger value with broken options allowed
398+ #[ case( true , f64 :: MAX , true ) ] // Valid maximum finite value with broken options allowed
399+ #[ case( true , -1e-10 , false ) ] // Invalid: negative value
400+ #[ case( true , f64 :: INFINITY , false ) ] // Invalid: positive infinity
401+ #[ case( true , f64 :: NEG_INFINITY , false ) ] // Invalid: negative infinity
402+ #[ case( true , f64 :: NAN , false ) ] // Invalid: NaN
403+ #[ case( false , -1e-10 , false ) ] // Invalid: negative value
404+ #[ case( false , f64 :: INFINITY , false ) ] // Invalid: positive infinity
405+ #[ case( false , f64 :: NEG_INFINITY , false ) ] // Invalid: negative infinity
406+ #[ case( false , f64 :: NAN , false ) ] // Invalid: NaN
407+ fn check_remaining_demand_absolute_tolerance_works (
408+ #[ case] allow_broken_options : bool ,
409+ #[ case] value : f64 ,
410+ #[ case] expected_valid : bool ,
411+ ) {
412+ let flow = Flow :: new ( value) ;
413+ let result = check_remaining_demand_absolute_tolerance ( allow_broken_options, flow) ;
414+
415+ assert_validation_result (
416+ result,
417+ expected_valid,
418+ value,
419+ "remaining_demand_absolute_tolerance must be a finite number greater than or equal to zero" ,
420+ ) ;
421+ }
422+
423+ #[ rstest]
424+ #[ case( 0.0 ) ] // smaller than default
425+ #[ case( 1e-10 ) ] // Larger than default (1e-12)
426+ #[ case( 1.0 ) ] // Well above default
427+ #[ case( f64 :: MAX ) ] // Maximum finite value
428+ fn check_remaining_demand_absolute_tolerance_requires_broken_options_if_non_default (
429+ #[ case] value : f64 ,
430+ ) {
431+ let flow = Flow :: new ( value) ;
432+ let result = check_remaining_demand_absolute_tolerance ( false , flow) ;
433+ assert_validation_result (
434+ result,
435+ false ,
436+ value,
437+ "Setting a remaining_demand_absolute_tolerance different from the default value \
438+ of 1e-12 is potentially dangerous, set \
439+ please_give_me_broken_results to true if you want to allow this.",
440+ ) ;
441+ }
442+
359443 #[ rstest]
360444 #[ case( 0.0 , true ) ] // Valid minimum value
361445 #[ case( 0.2 , true ) ] // Valid default value
0 commit comments