Skip to content

Commit 95b850f

Browse files
WIP - initial impl - examples not yet updated - FIXME, LCOH calculation broken somewhere
1 parent 2a9af95 commit 95b850f

File tree

2 files changed

+73
-34
lines changed

2 files changed

+73
-34
lines changed

src/geophires_x/Economics.py

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
from __future__ import annotations
2+
13
import math
24
import sys
5+
# noinspection PyPackageRequirements
36
import numpy as np
47
import numpy_financial as npf
58
from pint.facets.plain import PlainQuantity
@@ -349,29 +352,50 @@ def CalculateFinancialPerformance(plantlifetime: int,
349352
return NPV, IRR, VIR, MOIC
350353

351354

352-
def CalculateLCOELCOHLCOC(econ, model: Model) -> tuple:
355+
def CalculateLCOELCOHLCOC(econ, model: Model) -> tuple[float, float, float]:
353356
"""
354357
CalculateLCOELCOH calculates the levelized cost of electricity and heat for the project.
355358
:param econ: Economics object
356359
:type econ: :class:`~geophires_x.Economics.Economics`
357360
:param model: The model object
358361
:type model: :class:`~geophires_x.Model.Model`
359362
:return: LCOE: The levelized cost of electricity and LCOH: The levelized cost of heat and LCOC: The levelized cost of cooling
360-
:rtype: tuple
363+
:rtype: tuple[float, float, float]
361364
"""
362365
LCOE = LCOH = LCOC = 0.0
363366
CCap_elec = (econ.CCap.value * econ.CAPEX_heat_electricity_plant_ratio.value)
364367
Coam_elec = (econ.Coam.value * econ.CAPEX_heat_electricity_plant_ratio.value)
365368
CCap_heat = (econ.CCap.value * (1.0 - econ.CAPEX_heat_electricity_plant_ratio.value))
366369
Coam_heat = (econ.Coam.value * (1.0 - econ.CAPEX_heat_electricity_plant_ratio.value))
370+
371+
def _capex_total_plus_construction_inflation() -> float:
372+
# TODO unit conversions
373+
# TODO should be return value instead of mutating econ
374+
econ.inflation_cost_during_construction.value = econ.CCap.value * econ.inflrateconstruction.value
375+
376+
return econ.CCap.value + econ.inflation_cost_during_construction.value
377+
378+
def _construction_inflation_cost_elec_heat() -> tuple[float, float]:
379+
# TODO unit conversions
380+
construction_inflation_cost_elec = CCap_elec * econ.inflrateconstruction.value
381+
construction_inflation_cost_heat = CCap_heat * econ.inflrateconstruction.value
382+
383+
# TODO should be return value instead of mutating econ
384+
econ.inflation_cost_during_construction.value = (construction_inflation_cost_elec
385+
+ construction_inflation_cost_heat)
386+
387+
return CCap_elec + construction_inflation_cost_elec, CCap_heat + construction_inflation_cost_heat
388+
367389
# Calculate LCOE/LCOH/LCOC
368390
if econ.econmodel.value == EconomicModel.FCR:
391+
capex_total_plus_infl = _capex_total_plus_construction_inflation()
392+
369393
if model.surfaceplant.enduse_option.value == EndUseOptions.ELECTRICITY:
370-
LCOE = (econ.FCR.value * (1 + econ.inflrateconstruction.value) * econ.CCap.value + econ.Coam.value) / \
394+
LCOE = (econ.FCR.value * capex_total_plus_infl + econ.Coam.value) / \
371395
np.average(model.surfaceplant.NetkWhProduced.value) * 1E8 # cents/kWh
372396
elif (model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and
373397
model.surfaceplant.plant_type.value not in [PlantType.ABSORPTION_CHILLER, PlantType.HEAT_PUMP, PlantType.DISTRICT_HEATING]):
374-
LCOH = (econ.FCR.value * (1 + econ.inflrateconstruction.value) * econ.CCap.value + econ.Coam.value +
398+
LCOH = (econ.FCR.value * capex_total_plus_infl + econ.Coam.value +
375399
econ.averageannualpumpingcosts.value) / np.average(
376400
model.surfaceplant.HeatkWhProduced.value) * 1E8 # cents/kWh
377401
LCOH = LCOH * 2.931 # $/Million Btu
@@ -382,38 +406,40 @@ def CalculateLCOELCOHLCOC(econ, model: Model) -> tuple:
382406
EndUseOptions.COGENERATION_BOTTOMING_EXTRA_HEAT,
383407
EndUseOptions.COGENERATION_PARALLEL_EXTRA_HEAT,
384408
EndUseOptions.COGENERATION_PARALLEL_EXTRA_ELECTRICITY]:
385-
LCOE = (econ.FCR.value * (1 + econ.inflrateconstruction.value) * CCap_elec + Coam_elec) / np.average(model.surfaceplant.NetkWhProduced.value) * 1E8 # cents/kWh
386-
LCOH = (econ.FCR.value * (1 + econ.inflrateconstruction.value) * CCap_heat + Coam_heat + econ.averageannualpumpingcosts.value) / np.average(model.surfaceplant.HeatkWhProduced.value) * 1E8 # cents/kWh
409+
capex_elec_plus_infl, capex_heat_plus_infl = _construction_inflation_cost_elec_heat()
410+
LCOE = (econ.FCR.value * capex_elec_plus_infl + Coam_elec) / np.average(model.surfaceplant.NetkWhProduced.value) * 1E8 # cents/kWh
411+
LCOH = (econ.FCR.value * capex_heat_plus_infl + Coam_heat + econ.averageannualpumpingcosts.value) / np.average(model.surfaceplant.HeatkWhProduced.value) * 1E8 # cents/kWh
387412
LCOH = LCOH * 2.931 # $/Million Btu
388413

389414
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.ABSORPTION_CHILLER:
390-
LCOC = (econ.FCR.value * (
391-
1 + econ.inflrateconstruction.value) * econ.CCap.value + econ.Coam.value + econ.averageannualpumpingcosts.value) / np.average(
415+
LCOC = (econ.FCR.value * capex_total_plus_infl + econ.Coam.value + econ.averageannualpumpingcosts.value) / np.average(
392416
model.surfaceplant.cooling_kWh_Produced.value) * 1E8 # cents/kWh
393417
LCOC = LCOC * 2.931 # $/Million Btu
394418
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.HEAT_PUMP:
395-
LCOH = (econ.FCR.value * (
396-
1 + econ.inflrateconstruction.value) * econ.CCap.value + econ.Coam.value + econ.averageannualpumpingcosts.value + econ.averageannualheatpumpelectricitycost.value) / np.average(
419+
LCOH = (econ.FCR.value * capex_total_plus_infl
420+
+ econ.Coam.value + econ.averageannualpumpingcosts.value + econ.averageannualheatpumpelectricitycost.value) / np.average(
397421
model.surfaceplant.HeatkWhProduced.value) * 1E8 # cents/kWh
398422
LCOH = LCOH * 2.931 # $/Million Btu
399423
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING:
400-
LCOH = (econ.FCR.value * (
401-
1 + econ.inflrateconstruction.value) * econ.CCap.value + econ.Coam.value + econ.averageannualpumpingcosts.value + econ.averageannualngcost.value) / model.surfaceplant.annual_heating_demand.value * 1E2 # cents/kWh
424+
LCOH = (econ.FCR.value * capex_total_plus_infl
425+
+ econ.Coam.value + econ.averageannualpumpingcosts.value + econ.averageannualngcost.value) / model.surfaceplant.annual_heating_demand.value * 1E2 # cents/kWh
402426
LCOH = LCOH * 2.931 # $/Million Btu
403427
elif econ.econmodel.value == EconomicModel.STANDARDIZED_LEVELIZED_COST:
404428
discount_vector = 1. / np.power(1 + econ.discountrate.value,
405429
np.linspace(0, model.surfaceplant.plant_lifetime.value - 1,
406430
model.surfaceplant.plant_lifetime.value))
431+
capex_total_plus_infl = _capex_total_plus_construction_inflation()
432+
407433
if model.surfaceplant.enduse_option.value == EndUseOptions.ELECTRICITY:
408-
LCOE = ((1 + econ.inflrateconstruction.value) * econ.CCap.value + np.sum(
434+
LCOE = (capex_total_plus_infl + np.sum(
409435
econ.Coam.value * discount_vector)) / np.sum(
410436
model.surfaceplant.NetkWhProduced.value * discount_vector) * 1E8 # cents/kWh
411437
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and \
412438
model.surfaceplant.plant_type.value not in [PlantType.ABSORPTION_CHILLER, PlantType.HEAT_PUMP, PlantType.DISTRICT_HEATING]:
413439
econ.averageannualpumpingcosts.value = np.average(
414440
model.surfaceplant.PumpingkWh.value) * model.surfaceplant.electricity_cost_to_buy.value / 1E6 # M$/year
415-
LCOH = ((1 + econ.inflrateconstruction.value) * econ.CCap.value + np.sum((
416-
econ.Coam.value + model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6) * discount_vector)) / np.sum(
441+
LCOH = (capex_total_plus_infl + np.sum((
442+
econ.Coam.value + model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6) * discount_vector)) / np.sum(
417443
model.surfaceplant.HeatkWhProduced.value * discount_vector) * 1E8 # cents/kWh
418444
LCOH = LCOH * 2.931 # $/MMBTU
419445

@@ -424,24 +450,30 @@ def CalculateLCOELCOHLCOC(econ, model: Model) -> tuple:
424450
EndUseOptions.COGENERATION_BOTTOMING_EXTRA_HEAT,
425451
EndUseOptions.COGENERATION_PARALLEL_EXTRA_HEAT,
426452
EndUseOptions.COGENERATION_PARALLEL_EXTRA_ELECTRICITY]:
427-
LCOE = ((1 + econ.inflrateconstruction.value) * CCap_elec + np.sum(Coam_elec * discount_vector)) / np.sum(model.surfaceplant.NetkWhProduced.value * discount_vector) * 1E8 # cents/kWh
428-
LCOH = ((1 + econ.inflrateconstruction.value) * CCap_heat +
453+
capex_elec_plus_infl, capex_heat_plus_infl = _construction_inflation_cost_elec_heat()
454+
455+
LCOE = (capex_elec_plus_infl + np.sum(Coam_elec * discount_vector)) / np.sum(model.surfaceplant.NetkWhProduced.value * discount_vector) * 1E8 # cents/kWh
456+
LCOH = (capex_heat_plus_infl * CCap_heat +
429457
np.sum((Coam_heat + model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6) * discount_vector)) / np.sum(model.surfaceplant.HeatkWhProduced.value * discount_vector) * 1E8 # cents/kWh
430458
LCOH = LCOH * 2.931 # $/MMBTU
431459

432460
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.ABSORPTION_CHILLER:
433-
LCOC = ((1 + econ.inflrateconstruction.value) * econ.CCap.value + np.sum((
434-
econ.Coam.value + model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6) * discount_vector)) / np.sum(
461+
capex_total_plus_infl = _capex_total_plus_construction_inflation()
462+
463+
LCOC = (capex_total_plus_infl + np.sum((
464+
econ.Coam.value + model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6) * discount_vector)) / np.sum(
435465
model.surfaceplant.cooling_kWh_Produced.value * discount_vector) * 1E8 # cents/kWh
436466
LCOC = LCOC * 2.931 # $/Million Btu
437467
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.HEAT_PUMP:
438-
LCOH = ((1 + econ.inflrateconstruction.value) * econ.CCap.value + np.sum(
468+
capex_total_plus_infl = _capex_total_plus_construction_inflation()
469+
LCOH = (capex_total_plus_infl + np.sum(
439470
(econ.Coam.value + model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6 +
440471
model.surfaceplant.heat_pump_electricity_kwh_used.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6) * discount_vector)) / np.sum(
441472
model.surfaceplant.HeatkWhProduced.value * discount_vector) * 1E8 # cents/kWh
442473
LCOH = LCOH * 2.931 # $/Million Btu
443474
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING:
444-
LCOH = ((1 + econ.inflrateconstruction.value) * econ.CCap.value + np.sum(
475+
capex_total_plus_infl = _capex_total_plus_construction_inflation()
476+
LCOH = (capex_total_plus_infl + np.sum(
445477
(econ.Coam.value + model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6 +
446478
econ.annualngcost.value) * discount_vector)) / np.sum(
447479
model.surfaceplant.annual_heating_demand.value * discount_vector) * 1E2 # cents/kWh
@@ -457,10 +489,12 @@ def CalculateLCOELCOHLCOC(econ, model: Model) -> tuple:
457489
CRF = i_ave / (1 - np.power(1 + i_ave, -model.surfaceplant.plant_lifetime.value))
458490
inflation_vector = np.power(1 + econ.RINFL.value, np.linspace(1, model.surfaceplant.plant_lifetime.value, model.surfaceplant.plant_lifetime.value))
459491
discount_vector = 1. / np.power(1 + i_ave, np.linspace(1, model.surfaceplant.plant_lifetime.value, model.surfaceplant.plant_lifetime.value))
460-
NPV_cap = np.sum((1 + econ.inflrateconstruction.value) * econ.CCap.value * CRF * discount_vector)
461-
NPV_fc = np.sum((1 + econ.inflrateconstruction.value) * econ.CCap.value * econ.PTR.value * inflation_vector * discount_vector)
462-
NPV_it = np.sum(econ.CTR.value / (1 - econ.CTR.value) * ((1 + econ.inflrateconstruction.value) * econ.CCap.value * CRF - econ.CCap.value / model.surfaceplant.plant_lifetime.value) * discount_vector)
463-
NPV_itc = (1 + econ.inflrateconstruction.value) * econ.CCap.value * econ.RITC.value / (1 - econ.CTR.value)
492+
capex_total_plus_infl = _capex_total_plus_construction_inflation()
493+
494+
NPV_cap = np.sum(capex_total_plus_infl * CRF * discount_vector)
495+
NPV_fc = np.sum(capex_total_plus_infl * econ.PTR.value * inflation_vector * discount_vector)
496+
NPV_it = np.sum(econ.CTR.value / (1 - econ.CTR.value) * (capex_total_plus_infl * CRF - econ.CCap.value / model.surfaceplant.plant_lifetime.value) * discount_vector)
497+
NPV_itc = capex_total_plus_infl * econ.RITC.value / (1 - econ.CTR.value)
464498

465499
if model.surfaceplant.enduse_option.value == EndUseOptions.ELECTRICITY:
466500
NPV_oandm = np.sum(econ.Coam.value * inflation_vector * discount_vector)
@@ -479,21 +513,22 @@ def CalculateLCOELCOHLCOC(econ, model: Model) -> tuple:
479513
EndUseOptions.COGENERATION_BOTTOMING_EXTRA_HEAT,
480514
EndUseOptions.COGENERATION_PARALLEL_EXTRA_HEAT,
481515
EndUseOptions.COGENERATION_PARALLEL_EXTRA_ELECTRICITY]:
516+
capex_elec_plus_infl, capex_heat_plus_infl = _construction_inflation_cost_elec_heat()
482517

483-
NPVcap_elec = np.sum((1 + econ.inflrateconstruction.value) * CCap_elec * CRF * discount_vector)
484-
NPVfc_elec = np.sum((1 + econ.inflrateconstruction.value) * CCap_elec * econ.PTR.value * inflation_vector * discount_vector)
485-
NPVit_elec = np.sum(econ.CTR.value / (1 - econ.CTR.value) * ((1 + econ.inflrateconstruction.value) * CCap_elec * CRF - CCap_elec / model.surfaceplant.plant_lifetime.value) * discount_vector)
486-
NPVitc_elec = (1 + econ.inflrateconstruction.value) * CCap_elec * econ.RITC.value / (1 - econ.CTR.value)
518+
NPVcap_elec = np.sum(capex_elec_plus_infl * CRF * discount_vector)
519+
NPVfc_elec = np.sum(capex_elec_plus_infl * econ.PTR.value * inflation_vector * discount_vector)
520+
NPVit_elec = np.sum(econ.CTR.value / (1 - econ.CTR.value) * (capex_elec_plus_infl * CRF - CCap_elec / model.surfaceplant.plant_lifetime.value) * discount_vector)
521+
NPVitc_elec = capex_elec_plus_infl * econ.RITC.value / (1 - econ.CTR.value)
487522
NPVoandm_elec = np.sum(Coam_elec * inflation_vector * discount_vector)
488523
NPVgrt_elec = econ.GTR.value / (1 - econ.GTR.value) * (NPVcap_elec + NPVoandm_elec + NPVfc_elec + NPVit_elec - NPVitc_elec)
489524

490525
LCOE = ((NPVcap_elec + NPVoandm_elec + NPVfc_elec + NPVit_elec + NPVgrt_elec - NPVitc_elec) /
491526
np.sum(model.surfaceplant.NetkWhProduced.value * inflation_vector * discount_vector) * 1E8)
492527

493-
NPV_cap_heat = np.sum((1 + econ.inflrateconstruction.value) * CCap_heat * CRF * discount_vector)
528+
NPV_cap_heat = np.sum(capex_heat_plus_infl * CRF * discount_vector)
494529
NPV_fc_heat = np.sum((1 + econ.inflrateconstruction.value) * (econ.CCap.value * (1.0 - econ.CAPEX_heat_electricity_plant_ratio.value)) * econ.PTR.value * inflation_vector * discount_vector)
495-
NPV_it_heat = np.sum(econ.CTR.value / (1 - econ.CTR.value) * ((1 + econ.inflrateconstruction.value) * CCap_heat * CRF - CCap_heat / model.surfaceplant.plant_lifetime.value) * discount_vector)
496-
NPV_itc_heat = (1 + econ.inflrateconstruction.value) * CCap_heat * econ.RITC.value / (1 - econ.CTR.value)
530+
NPV_it_heat = np.sum(econ.CTR.value / (1 - econ.CTR.value) * (capex_heat_plus_infl * CRF - CCap_heat / model.surfaceplant.plant_lifetime.value) * discount_vector)
531+
NPV_itc_heat = capex_heat_plus_infl * econ.RITC.value / (1 - econ.CTR.value)
497532
NPV_oandm_heat = np.sum((econ.Coam.value * (1.0 - econ.CAPEX_heat_electricity_plant_ratio.value)) * inflation_vector * discount_vector)
498533
NPV_grt_heat = econ.GTR.value / (1 - econ.GTR.value) * (NPV_cap_heat + NPV_oandm_heat + NPV_fc_heat + NPV_it_heat - NPV_itc_heat)
499534

src/geophires_x/Outputs.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,12 @@ def PrintOutputs(self, model: Model):
497497
# expenditure.
498498
pass
499499

500-
if is_sam_econ_model:
501-
# TODO calculate & display for other economic models
500+
display_inflation_during_construction_in_capital_costs = is_sam_econ_model \
501+
or (econ.econmodel.value in [EconomicModel.BICYCLE, EconomicModel.FCR,
502+
EconomicModel.STANDARDIZED_LEVELIZED_COST]
503+
and
504+
econ.inflation_cost_during_construction.value != 0.)
505+
if display_inflation_during_construction_in_capital_costs:
502506
icc_label = Outputs._field_label(econ.inflation_cost_during_construction.display_name, 47)
503507
f.write(f' {icc_label}{econ.inflation_cost_during_construction.value:10.2f} {econ.inflation_cost_during_construction.CurrentUnits.value}\n')
504508

0 commit comments

Comments
 (0)