@@ -233,9 +233,7 @@ def create_survival_curve(model: TemoaModel) -> None:
233233
234234 # Collect rptv indices into (r, t, v): p dictionary
235235 for r , p , t , v in model .lifetime_survival_curve .sparse_keys ():
236- if (r , t , v ) not in model .survival_curve_periods :
237- model .survival_curve_periods [r , t , v ] = set ()
238- model .survival_curve_periods [r , t , v ].add (p )
236+ model .survival_curve_periods .setdefault ((r , t , v ), set ()).add (p )
239237 model .is_survival_curve_process [r , t , v ] = True
240238
241239 # Go through all the periods for each (r, t, v) in order
@@ -244,23 +242,21 @@ def create_survival_curve(model: TemoaModel) -> None:
244242
245243 p_first = periods_rtv [0 ]
246244 p_last = periods_rtv [- 1 ]
247-
248- if p_first != v :
245+ eol_year = v + value (model .lifetime_process [r , t , v ])
246+
247+ if (
248+ p_first != v
249+ or p_last != eol_year
250+ or value (model .lifetime_survival_curve [r , v , t , v ]) != 1
251+ or value (model .lifetime_survival_curve [r , eol_year , t , v ]) != 0
252+ ):
249253 msg = (
250- 'lifetime_survival_curve must be defined starting in the vintage period . Must '
251- f'define ({ r } , >{ v } <, { t } , { v } )'
254+ 'lifetime_survival_curve must be defined as 1 at start and 0 at end of life . Must '
255+ f'define ({ r } , >{ v } <, { t } , { v } ) = 1 and ( { r } , > { eol_year } <, { t } , { v } ) = 0. '
252256 )
253257 logger .error (msg )
254258 raise ValueError (msg )
255259
256- if value (model .lifetime_survival_curve [r , v , t , v ]) != 1 :
257- msg_str = (
258- 'lifetime_survival_curve must begin at 1 for calculating annual retirements. '
259- f'Got { value (model .lifetime_survival_curve [r , v , t , v ])} for ({ r } , { v } , { t } , { v } )'
260- )
261- logger .error (msg_str )
262- raise ValueError (msg_str )
263-
264260 # Collect a list of processes that needed to be interpolated, for warning
265261 if periods_rtv != list (range (p_first , p_last + 1 , 1 )):
266262 rtv_interpolated .add ((r , t , v ))
@@ -283,6 +279,7 @@ def create_survival_curve(model: TemoaModel) -> None:
283279 logger .error (msg )
284280 raise ValueError (msg )
285281
282+ # Linearly interpolate to fill in missing years
286283 if p - p_prev > 1 :
287284 _between_periods = [cast ('Period' , _p ) for _p in range (p_prev + 1 , p , 1 )]
288285 for _p in _between_periods :
@@ -294,35 +291,15 @@ def create_survival_curve(model: TemoaModel) -> None:
294291 if lsc < 0.0001 :
295292 if p != p_last :
296293 msg = (
297- 'There is no need to continue a survival curve beyond fraction ~= 0. '
298- f'ignoring periods beyond { p } for ({ r , t , v } )'
299- )
300- logger .info (msg )
301-
302- # Make sure the lifetime for this process aligns with survival curve end
303- if round (value (model .lifetime_process [r , t , v ])) != p - v :
304- msg = (
305- f'The lifetime_process parameter '
306- f'({ round (value (model .lifetime_process [r , t , v ]))} ) for process '
307- f'{ r , t , v } with survival curve does not agree with the end of that '
308- f'survival curve in { p } . To agree with '
309- f'the survival curve, set lifetime_process[{ r , t , v } ] = { p - v } '
294+ 'Survival curve must be strictly positive during the '
295+ 'lifetime of the process. Found non-positive value at '
296+ f'period { p } for ({ r , t , v } ) which could cause a '
297+ 'division by zero error.'
310298 )
311299 logger .error (msg )
312300 raise ValueError (msg )
313-
314301 continue
315302
316- # Flag if the last period is not fraction = 0. This is important for investment costs
317- if p == p_last and lsc > 0.0001 :
318- msg = (
319- 'Any defined survival curve must continue to zero for the purposes of '
320- 'investment cost accounting, even if this period would extend beyond '
321- f'defined future periods. Continue ({ r , t , v } ) to fraction == 0.'
322- )
323- logger .error (msg )
324- raise ValueError (msg )
325-
326303 model .survival_curve_periods [r , t , v ].update (between_periods )
327304
328305 if rtv_interpolated :
0 commit comments