Skip to content

Commit ec0e2e5

Browse files
Merge pull request #294 from idelder/fix/survival_curve_retirements
Fix interaction between survival curves and early retirement
2 parents 3dae0d5 + bd791d0 commit ec0e2e5

2 files changed

Lines changed: 18 additions & 41 deletions

File tree

temoa/components/capacity.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,11 +331,11 @@ def annual_retirement_constraint(
331331
)
332332
# next v_capacity also accounts for decision retirement so need to undo that again
333333
p_next_end = p_next + value(model.period_length[p_next])
334-
if t in model.tech_retirement and p_next_end <= eol_year:
334+
if t in model.tech_retirement and v < p_next and p_next_end <= eol_year:
335335
cap_end += model.v_retired_capacity[r, p_next, t, v]
336336

337337
# v_capacity already accounts for decision retirement so need to undo that for beginning cap
338-
if t in model.tech_retirement and p_end <= eol_year:
338+
if t in model.tech_retirement and v < p and p_end <= eol_year:
339339
cap_begin += model.v_retired_capacity[r, p, t, v]
340340

341341
annualised_retirement = (cap_begin - cap_end) / value(model.period_length[p])

temoa/components/technology.py

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)