import math
import os
import sys
import numpy as np
import numpy_financial as npf
import geophires_x.Model as Model
from geophires_x.OptionList import Configuration, WellDrillingCostCorrelation, EconomicModel, EndUseOptions, PlantType
from geophires_x.Parameter import intParameter, floatParameter, OutputParameter, ReadParameter, boolParameter, \
coerce_int_params_to_enum_values
from geophires_x.Units import *
from geophires_x.WellBores import calculate_total_drilling_lengths_m
[docs]
def calculate_cost_of_one_vertical_well(model: Model, depth_m: float, well_correlation: int,
vertical_drilling_cost_per_m: float,
fixed_well_cost_name: str, well_cost_adjustment_factor: float) -> float:
"""
CalculateCostOfOneWell calculates the cost of one vertical well based on the depth of the well and the cost correlation.
:param model: The model object
:type model: :class:`~geophires
:param depth_m: The depth of the well in meters
:type depth_m: float
:param well_correlation: The well correlation
:type well_correlation: int
:param vertical_drilling_cost_per_m: The vertical drilling cost per meter in $/m
:type vertical_drilling_cost_per_m: float
:param fixed_well_cost_name: The fixed well cost name
:type fixed_well_cost_name: str
:param well_cost_adjustment_factor: The well cost adjustment factor
:type well_cost_adjustment_factor: float
:return: cost_of_one_well: The cost of one well in MUSD
:rtype: float
"""
# Check if well depth is out of standard bounds for cost correlation
correlations_min_valid_depth_m = 500.
correlations_max_valid_depth_m = 7000.
cost_of_one_well = 0.0
if depth_m < correlations_min_valid_depth_m and not well_correlation is WellDrillingCostCorrelation.SIMPLE:
well_correlation = WellDrillingCostCorrelation.SIMPLE
model.logger.warning(
f'Invalid cost correlation specified ({well_correlation}) for drilling depth '
f'<{correlations_min_valid_depth_m}m ({depth_m}m). '
f'Falling back to simple user-specified cost '
f'({vertical_drilling_cost_per_m} per meter)'
)
if depth_m > correlations_max_valid_depth_m and not well_correlation is WellDrillingCostCorrelation.SIMPLE:
model.logger.warning(
f'{well_correlation} may be invalid for drilling depth '
f'>{correlations_max_valid_depth_m}m ({depth_m}m). '
f'Consider using {WellDrillingCostCorrelation.SIMPLE} (per-meter cost) or '
f'{fixed_well_cost_name} (fixed cost per well) instead.'
)
if well_correlation is WellDrillingCostCorrelation.SIMPLE:
cost_of_one_well = vertical_drilling_cost_per_m * depth_m * 1E-6
else:
cost_of_one_well = well_correlation.calculate_cost_MUSD(depth_m)
# account for adjustment factor
cost_of_one_well = well_cost_adjustment_factor * cost_of_one_well
return cost_of_one_well
[docs]
def calculate_cost_of_non_vertical_section(model: Model, length_m: float, well_correlation: int,
nonvertical_drilling_cost_per_m: float,
num_nonvertical_sections: int,
fixed_well_cost_name: str, NonverticalsCased: bool,
well_cost_adjustment_factor: float) -> float:
"""
calculate_cost_of_non_vertical_section calculates the cost of the non vertical section of the well.
Assume that the cost per meter for drilling of the non-vertical section is the same as the vertical section.
:param model: The model object
:type model: :class:`~geophires
:param length_m: The depth of the well in meters
:type length_m: float
:param well_correlation: The well cost correlation
:type well_correlation: int
:param nonvertical_drilling_cost_per_m: The nonvertical drilling cost per meter in $/m
:type nonvertical_drilling_cost_per_m: float
:param num_nonvertical_sections: The number of non vertical sections
:type num_nonvertical_sections: int
:param fixed_well_cost_name: The fixed well cost name
:type fixed_well_cost_name: str
:param NonverticalsCased: Are the nonverticals cased?
:type NonverticalsCased: bool
:param well_cost_adjustment_factor: The well cost adjustment factor
:type well_cost_adjustment_factor: float
:return: cost_of_one_well: The cost of the nonvertical section in MUSD
:rtype: float
"""
# if we are drilling a vertical well, the nonvertical cost is 0
if model.wellbores.Configuration.value == Configuration.VERTICAL:
return 0.0
# Check if well length is out of standard bounds for cost correlation
length_per_section_m = length_m / num_nonvertical_sections
correlations_min_valid_length_m = 500.
correlations_max_valid_length_m = 7000.
cost_of_non_vertical_section = 0.0
cost_per_section = 0.0
if length_per_section_m < correlations_min_valid_length_m and not well_correlation is WellDrillingCostCorrelation.SIMPLE:
well_correlation = WellDrillingCostCorrelation.SIMPLE
model.logger.warning(
f'Invalid cost correlation specified ({well_correlation}) for drilling length '
f'<{correlations_min_valid_length_m}m ({length_m}m). '
f'Falling back to simple user-specified cost '
f'({nonvertical_drilling_cost_per_m} per meter)'
)
if length_per_section_m > correlations_max_valid_length_m and not well_correlation is WellDrillingCostCorrelation.SIMPLE:
model.logger.warning(
f'{well_correlation} may be invalid for drilling length '
f'>{correlations_max_valid_length_m}m ({length_m}m). '
f'Consider using {WellDrillingCostCorrelation.SIMPLE} (per-meter cost) or '
f'{fixed_well_cost_name} (fixed cost per well) instead.'
)
casing_factor = 1.0
if not NonverticalsCased:
# assume that casing & cementing costs 50% of drilling costs
casing_factor = 0.5
if model.economics.Nonvertical_drilling_cost_per_m.Provided or well_correlation is WellDrillingCostCorrelation.SIMPLE:
cost_of_non_vertical_section = casing_factor * ((num_nonvertical_sections * nonvertical_drilling_cost_per_m * length_per_section_m)) * 1E-6
else:
cost_per_section = well_correlation.calculate_cost_MUSD(length_per_section_m)
cost_of_non_vertical_section = casing_factor * num_nonvertical_sections * cost_per_section
# account for adjustment factor
cost_of_non_vertical_section = well_cost_adjustment_factor * cost_of_non_vertical_section
return cost_of_non_vertical_section
[docs]
def BuildPTCModel(plantlifetime: int, duration: int, ptc_price: float,
ptc_inflation_adjusted: bool, inflation_rate: float) -> list:
"""
BuildPricingModel builds the price model array for the project lifetime. It is used to calculate the revenue
stream for the project.
:param plantlifetime: The lifetime of the project in years
:type plantlifetime: int
:param duration: The duration of the PTC in years
:type duration: int
:param ptc_price: The PTC in $/kWh
:type ptc_price: float
:param ptc_inflation_adjusted: Is the PTC is inflation?
:type ptc_inflation_adjusted: bool
:param inflation_rate: The inflation rate in %
:type inflation_rate: float
:return: Price: The price model array for the PTC in $/kWh
:rtype: list
"""
# Build the PTC price model by setting the price to the PTCPrice for the duration of the PTC
Price = [0.0] * plantlifetime
for year in range(0, duration, 1):
Price[year] = ptc_price
if ptc_inflation_adjusted and year > 0:
Price[year] = Price[year-1] * (1 + inflation_rate)
return Price
[docs]
def BuildPricingModel(plantlifetime: int, StartPrice: float, EndPrice: float,
EscalationStartYear: int, EscalationRate: float, PTCAddition: list) -> list:
"""
BuildPricingModel builds the price model array for the project lifetime. It is used to calculate the revenue
stream for the project.
:param plantlifetime: The lifetime of the project in years
:type plantlifetime: int
:param StartPrice: The price in the first year of the project in $/kWh
:type StartPrice: float
:param EndPrice: The price in the last year of the project in $/kWh
:type EndPrice: float
:param EscalationStartYear: The year the price escalation starts in years (not including construction years) in years
:type EscalationStartYear: int
:param EscalationRate: The rate of price escalation in $/kWh/year
:type EscalationRate: float
:param PTCAddition: The PTC addition array for the project in $/kWh
:type PTCAddition: list
:return: Price: The price model array for the project in $/kWh
:rtype: list
"""
Price = [0.0] * plantlifetime
for i in range(0, plantlifetime, 1):
Price[i] = StartPrice
if i >= EscalationStartYear:
Price[i] = Price[i] + ((i - EscalationStartYear) * EscalationRate)
if Price[i] > EndPrice:
Price[i] = EndPrice
Price[i] = Price[i] + PTCAddition[i]
return Price
[docs]
def CalculateTotalRevenue(plantlifetime: int, ConstructionYears: int, CAPEX: float, OPEX: float, AnnualRev):
"""
CalculateRevenue calculates the revenue stream for the project. It is used to calculate the revenue
stream for the project.
:param plantlifetime: The lifetime of the project in years in years (not including construction years) in years
:type plantlifetime: int
:param ConstructionYears: The number of years of construction for the project in years
:type ConstructionYears: int
:param CAPEX: The total capital cost of the project in MUSD
:type CAPEX: float
:param OPEX: The total annual operating cost of the project in MUSD
:type OPEX: float
:param AnnualRev: The annual revenue array for the project in MUSD
:type AnnualRev: list
:return: CashFlow: The annual cash flow for the project in MUSD and CummCashFlow: The cumulative cash flow for the
project in MUSD
:rtype: list
"""
# Calculate the revenue
ProjectCAPEXPerConstructionYear = CAPEX / ConstructionYears
CashFlow = [0.0] * (plantlifetime + ConstructionYears)
CummCashFlow = [0.0] * (plantlifetime + ConstructionYears)
# Insert the cost of construction into the front of the array that will be used to calculate NPV
# the convention is that the upfront CAPEX is negative
for i in range(0, ConstructionYears, 1):
CashFlow[i] = -1.0 * ProjectCAPEXPerConstructionYear
CummCashFlow[i] = -1.0 * ProjectCAPEXPerConstructionYear
for i in range(ConstructionYears, plantlifetime + ConstructionYears, 1):
CashFlow[i] = (AnnualRev[i]) - OPEX
# Calculate the cumulative revenue, skipping the first year because it is cumulative
for i in range(1, plantlifetime + ConstructionYears, 1):
CummCashFlow[i] = CummCashFlow[i - 1] + CashFlow[i]
return CashFlow, CummCashFlow
[docs]
def CalculateRevenue(plantlifetime: int, ConstructionYears: int, Energy, Price):
"""
CalculateRevenue calculates the revenue stream for the project. It is used to calculate the revenue
stream for the project.
# note this doesn't account for OPEX
:param plantlifetime: The lifetime of the project in years in years (not including construction years) in years
:type plantlifetime: int
:param ConstructionYears: The number of years of construction for the project in years
:type ConstructionYears: int
:param Energy: The energy production array for the project in kWh
:type Energy: list
:param Price: The price model array for the project in $/kWh
:type Price: list
:return: CashFlow: The annual cash flow for the project in MUSD and CummCashFlow: The cumulative cash flow for the
project in MUSD
:rtype: list
"""
# Calculate the revenue
CashFlow = [0.0] * (plantlifetime + ConstructionYears)
CummCashFlow = [0.0] * (plantlifetime + ConstructionYears)
# Revenue/yr in MUSD
for i in range(ConstructionYears, plantlifetime + ConstructionYears, 1):
CashFlow[i] = ((Energy[i - ConstructionYears] * Price[i - ConstructionYears]) / 1_000_000.0)
# Calculate the cumulative revenue, skipping the first year because it is cumulative
for i in range(ConstructionYears, plantlifetime + ConstructionYears, 1):
CummCashFlow[i] = CummCashFlow[i - 1] + CashFlow[i]
return CashFlow, CummCashFlow
def CalculateCarbonRevenue(model, plant_lifetime: int, construction_years: int, price_dollar_lb,
grid_CO2_intensity_lb_kwh: float, natural_gas_CO2_intensity_lb_kwh: float,
NetkWhProduced, HeatkWhProduced):
# Figure out how much carbon is being produced each year, and the amount of carbon that would have been
# produced if that energy had been made using the grid average carbon production.
# That then gives us the revenue, since we have a carbon price model
# We can also get cumulative cash flow from it.
# note this doesn't account for OPEX
cash_flow_musd = [0.0] * (plant_lifetime + construction_years)
cumm_cash_flow_musd = [0.0] * (plant_lifetime + construction_years)
carbon_that_would_have_been_produced_annually_lbs = ([0.0] * (plant_lifetime + construction_years))
carbon_that_would_have_been_produced_total_lbs = 0.0
for i in range(construction_years, plant_lifetime + construction_years, 1):
electrical_energy_kwh = 0.0
heat_energy_kwh = 0.0
elec_CO2_produced_lbs = 0.0
heat_CO2_produced_lbs = 0.0
# Carbon cashflow revenue (from both heat and elec) based net energy produced
if model.surfaceplant.enduse_option.value == EndUseOptions.ELECTRICITY: # This option has no heat component
electrical_energy_kwh = NetkWhProduced[i - construction_years]
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT: # has heat component but no electricity
heat_energy_kwh = HeatkWhProduced[i - construction_years]
else: # everything else has a component of both
electrical_energy_kwh = NetkWhProduced[i - construction_years]
heat_energy_kwh = HeatkWhProduced[i - construction_years]
elec_CO2_produced_lbs = electrical_energy_kwh * grid_CO2_intensity_lb_kwh
heat_CO2_produced_lbs = heat_energy_kwh * natural_gas_CO2_intensity_lb_kwh
# convert lbs/year to tonnes/year
carbon_that_would_have_been_produced_annually_lbs[i] = elec_CO2_produced_lbs + heat_CO2_produced_lbs
carbon_that_would_have_been_produced_total_lbs = carbon_that_would_have_been_produced_total_lbs + \
carbon_that_would_have_been_produced_annually_lbs[i]
cash_flow_musd[i] = (carbon_that_would_have_been_produced_annually_lbs[i] * price_dollar_lb[i - construction_years]) / 1_000_000.0
if i >= construction_years:
cumm_cash_flow_musd[i] = cumm_cash_flow_musd[i - 1] + cash_flow_musd[i]
return cash_flow_musd, cumm_cash_flow_musd, carbon_that_would_have_been_produced_annually_lbs, carbon_that_would_have_been_produced_total_lbs
[docs]
def CalculateLCOELCOHLCOC(self, model: Model) -> tuple:
"""
CalculateLCOELCOH calculates the levelized cost of electricity and heat for the project.
:param model: The model object
:type model: :class:`~geophires_x.Model.Model`
:return: LCOE: The levelized cost of electricity and LCOH: The levelized cost of heat and LCOC: The levelized cost of cooling
:rtype: tuple
"""
LCOE = LCOH = LCOC = 0.0
CCap_elec = (self.CCap.value * self.CAPEX_heat_electricity_plant_ratio.value)
Coam_elec = (self.Coam.value * self.CAPEX_heat_electricity_plant_ratio.value)
CCap_heat = (self.CCap.value * (1.0 - self.CAPEX_heat_electricity_plant_ratio.value))
Coam_heat = (self.Coam.value * (1.0 - self.CAPEX_heat_electricity_plant_ratio.value))
# Calculate LCOE/LCOH/LCOC
if self.econmodel.value == EconomicModel.FCR:
if model.surfaceplant.enduse_option.value == EndUseOptions.ELECTRICITY:
LCOE = (self.FCR.value * (1 + self.inflrateconstruction.value) * self.CCap.value + self.Coam.value) / \
np.average(model.surfaceplant.NetkWhProduced.value) * 1E8 # cents/kWh
elif (model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and
model.surfaceplant.plant_type.value not in [PlantType.ABSORPTION_CHILLER, PlantType.HEAT_PUMP, PlantType.DISTRICT_HEATING]):
LCOH = (self.FCR.value * (1 + self.inflrateconstruction.value) * self.CCap.value + self.Coam.value +
self.averageannualpumpingcosts.value) / np.average(
model.surfaceplant.HeatkWhProduced.value) * 1E8 # cents/kWh
LCOH = LCOH * 2.931 # $/Million Btu
# co-gen
elif model.surfaceplant.enduse_option.value in [EndUseOptions.COGENERATION_TOPPING_EXTRA_HEAT,
EndUseOptions.COGENERATION_TOPPING_EXTRA_ELECTRICITY,
EndUseOptions.COGENERATION_BOTTOMING_EXTRA_ELECTRICITY,
EndUseOptions.COGENERATION_BOTTOMING_EXTRA_HEAT,
EndUseOptions.COGENERATION_PARALLEL_EXTRA_HEAT,
EndUseOptions.COGENERATION_PARALLEL_EXTRA_ELECTRICITY]:
LCOE = (self.FCR.value * (1 + self.inflrateconstruction.value) * CCap_elec + Coam_elec) / np.average(model.surfaceplant.NetkWhProduced.value) * 1E8 # cents/kWh
LCOH = (self.FCR.value * (1 + self.inflrateconstruction.value) * CCap_heat + Coam_heat + self.averageannualpumpingcosts.value) / np.average(model.surfaceplant.HeatkWhProduced.value) * 1E8 # cents/kWh
LCOH = LCOH * 2.931 # $/Million Btu
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.ABSORPTION_CHILLER:
LCOC = (self.FCR.value * (
1 + self.inflrateconstruction.value) * self.CCap.value + self.Coam.value + self.averageannualpumpingcosts.value) / np.average(
model.surfaceplant.cooling_kWh_Produced.value) * 1E8 # cents/kWh
LCOC = LCOC * 2.931 # $/Million Btu
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.HEAT_PUMP:
LCOH = (self.FCR.value * (
1 + self.inflrateconstruction.value) * self.CCap.value + self.Coam.value + self.averageannualpumpingcosts.value + self.averageannualheatpumpelectricitycost.value) / np.average(
model.surfaceplant.HeatkWhProduced.value) * 1E8 # cents/kWh
LCOH = LCOH * 2.931 # $/Million Btu
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING:
LCOH = (self.FCR.value * (
1 + self.inflrateconstruction.value) * self.CCap.value + self.Coam.value + self.averageannualpumpingcosts.value + self.averageannualngcost.value) / model.surfaceplant.annual_heating_demand.value * 1E2 # cents/kWh
LCOH = LCOH * 2.931 # $/Million Btu
elif self.econmodel.value == EconomicModel.STANDARDIZED_LEVELIZED_COST:
discountvector = 1. / np.power(1 + self.discountrate.value,
np.linspace(0, model.surfaceplant.plant_lifetime.value - 1,
model.surfaceplant.plant_lifetime.value))
if model.surfaceplant.enduse_option.value == EndUseOptions.ELECTRICITY:
LCOE = ((1 + self.inflrateconstruction.value) * self.CCap.value + np.sum(
self.Coam.value * discountvector)) / np.sum(
model.surfaceplant.NetkWhProduced.value * discountvector) * 1E8 # cents/kWh
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and \
model.surfaceplant.plant_type.value not in [PlantType.ABSORPTION_CHILLER, PlantType.HEAT_PUMP, PlantType.DISTRICT_HEATING]:
self.averageannualpumpingcosts.value = np.average(
model.surfaceplant.PumpingkWh.value) * model.surfaceplant.electricity_cost_to_buy.value / 1E6 # M$/year
LCOH = ((1 + self.inflrateconstruction.value) * self.CCap.value + np.sum((
self.Coam.value + model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6) * discountvector)) / np.sum(
model.surfaceplant.HeatkWhProduced.value * discountvector) * 1E8 # cents/kWh
LCOH = LCOH * 2.931 # $/MMBTU
# co-gen
elif model.surfaceplant.enduse_option.value in [EndUseOptions.COGENERATION_TOPPING_EXTRA_HEAT,
EndUseOptions.COGENERATION_TOPPING_EXTRA_ELECTRICITY,
EndUseOptions.COGENERATION_BOTTOMING_EXTRA_ELECTRICITY,
EndUseOptions.COGENERATION_BOTTOMING_EXTRA_HEAT,
EndUseOptions.COGENERATION_PARALLEL_EXTRA_HEAT,
EndUseOptions.COGENERATION_PARALLEL_EXTRA_ELECTRICITY]:
LCOE = ((1 + self.inflrateconstruction.value) * CCap_elec + np.sum(Coam_elec * discountvector)) / np.sum(model.surfaceplant.NetkWhProduced.value * discountvector) * 1E8 # cents/kWh
LCOH = ((1 + self.inflrateconstruction.value) * CCap_heat +
np.sum((Coam_heat + model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6) * discountvector)) / np.sum(model.surfaceplant.HeatkWhProduced.value * discountvector) * 1E8 # cents/kWh
LCOH = LCOH * 2.931 # $/MMBTU
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.ABSORPTION_CHILLER:
LCOC = ((1 + self.inflrateconstruction.value) * self.CCap.value + np.sum((
self.Coam.value + model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6) * discountvector)) / np.sum(
model.surfaceplant.cooling_kWh_Produced.value * discountvector) * 1E8 # cents/kWh
LCOC = LCOC * 2.931 # $/Million Btu
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.HEAT_PUMP:
LCOH = ((1 + self.inflrateconstruction.value) * self.CCap.value + np.sum(
(self.Coam.value + model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6 +
model.surfaceplant.heat_pump_electricity_kwh_used.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6) * discountvector)) / np.sum(
model.surfaceplant.HeatkWhProduced.value * discountvector) * 1E8 # cents/kWh
LCOH = LCOH * 2.931 # $/Million Btu
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING:
LCOH = ((1 + self.inflrateconstruction.value) * self.CCap.value + np.sum(
(self.Coam.value + model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6 +
self.annualngcost.value) * discountvector)) / np.sum(
model.surfaceplant.annual_heating_demand.value * discountvector) * 1E2 # cents/kWh
LCOH = LCOH * 2.931 # $/Million Btu
else:
# must be BICYCLE
# average return on investment (tax and inflation adjusted)
iave = self.FIB.value * self.BIR.value * (1 - self.CTR.value) + (1 - self.FIB.value) * self.EIR.value
# capital recovery factor
CRF = iave / (1 - np.power(1 + iave, -model.surfaceplant.plant_lifetime.value))
inflationvector = np.power(1 + self.RINFL.value, np.linspace(1, model.surfaceplant.plant_lifetime.value, model.surfaceplant.plant_lifetime.value))
discountvector = 1. / np.power(1 + iave, np.linspace(1, model.surfaceplant.plant_lifetime.value, model.surfaceplant.plant_lifetime.value))
NPVcap = np.sum((1 + self.inflrateconstruction.value) * self.CCap.value * CRF * discountvector)
NPVfc = np.sum((1 + self.inflrateconstruction.value) * self.CCap.value * self.PTR.value * inflationvector * discountvector)
NPVit = np.sum(self.CTR.value / (1 - self.CTR.value) * ((1 + self.inflrateconstruction.value) * self.CCap.value * CRF - self.CCap.value / model.surfaceplant.plant_lifetime.value) * discountvector)
NPVitc = (1 + self.inflrateconstruction.value) * self.CCap.value * self.RITC.value / (1 - self.CTR.value)
if model.surfaceplant.enduse_option.value == EndUseOptions.ELECTRICITY:
NPVoandm = np.sum(self.Coam.value * inflationvector * discountvector)
NPVgrt = self.GTR.value / (1 - self.GTR.value) * (NPVcap + NPVoandm + NPVfc + NPVit - NPVitc)
LCOE = (NPVcap + NPVoandm + NPVfc + NPVit + NPVgrt - NPVitc) / np.sum(model.surfaceplant.NetkWhProduced.value * inflationvector * discountvector) * 1E8
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value not in [PlantType.ABSORPTION_CHILLER, PlantType.HEAT_PUMP, PlantType.DISTRICT_HEATING]:
PumpingCosts = model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6
NPVoandm = np.sum((self.Coam.value + PumpingCosts) * inflationvector * discountvector)
NPVgrt = self.GTR.value / (1 - self.GTR.value) * (NPVcap + NPVoandm + NPVfc + NPVit - NPVitc)
LCOH = (NPVcap + NPVoandm + NPVfc + NPVit + NPVgrt - NPVitc) / np.sum(model.surfaceplant.HeatkWhProduced.value * inflationvector * discountvector) * 1E8
LCOH = LCOH * 2.931 # $/MMBTU
# co-gen
elif model.surfaceplant.enduse_option.value in [EndUseOptions.COGENERATION_TOPPING_EXTRA_HEAT,
EndUseOptions.COGENERATION_TOPPING_EXTRA_ELECTRICITY,
EndUseOptions.COGENERATION_BOTTOMING_EXTRA_ELECTRICITY,
EndUseOptions.COGENERATION_BOTTOMING_EXTRA_HEAT,
EndUseOptions.COGENERATION_PARALLEL_EXTRA_HEAT,
EndUseOptions.COGENERATION_PARALLEL_EXTRA_ELECTRICITY]:
NPVcap_elec = np.sum((1 + self.inflrateconstruction.value) * CCap_elec * CRF * discountvector)
NPVfc_elec = np.sum((1 + self.inflrateconstruction.value) * CCap_elec * self.PTR.value * inflationvector * discountvector)
NPVit_elec = np.sum(self.CTR.value / (1 - self.CTR.value) * ((1 + self.inflrateconstruction.value) * CCap_elec * CRF - CCap_elec / model.surfaceplant.plant_lifetime.value) * discountvector)
NPVitc_elec = (1 + self.inflrateconstruction.value) * CCap_elec * self.RITC.value / (1 - self.CTR.value)
NPVoandm_elec = np.sum(Coam_elec * inflationvector * discountvector)
NPVgrt_elec = self.GTR.value / (1 - self.GTR.value) * (NPVcap_elec + NPVoandm_elec + NPVfc_elec + NPVit_elec - NPVitc_elec)
LCOE = ((NPVcap_elec + NPVoandm_elec + NPVfc_elec + NPVit_elec + NPVgrt_elec - NPVitc_elec) /
np.sum(model.surfaceplant.NetkWhProduced.value * inflationvector * discountvector) * 1E8)
NPVcap_heat = np.sum((1 + self.inflrateconstruction.value) * CCap_heat * CRF * discountvector)
NPVfc_heat = np.sum((1 + self.inflrateconstruction.value) * (self.CCap.value * (1.0 - self.CAPEX_heat_electricity_plant_ratio.value)) * self.PTR.value * inflationvector * discountvector)
NPVit_heat = np.sum(self.CTR.value / (1 - self.CTR.value) * ((1 + self.inflrateconstruction.value) * CCap_heat * CRF - CCap_heat / model.surfaceplant.plant_lifetime.value) * discountvector)
NPVitc_heat = (1 + self.inflrateconstruction.value) * CCap_heat * self.RITC.value / (1 - self.CTR.value)
NPVoandm_heat = np.sum((self.Coam.value * (1.0 - self.CAPEX_heat_electricity_plant_ratio.value)) * inflationvector * discountvector)
NPVgrt_heat = self.GTR.value / (1 - self.GTR.value) * (NPVcap_heat + NPVoandm_heat + NPVfc_heat + NPVit_heat - NPVitc_heat)
LCOH = ((NPVcap_heat + NPVoandm_heat + NPVfc_heat + NPVit_heat + NPVgrt_heat - NPVitc_heat) /
np.sum(model.surfaceplant.HeatkWhProduced.value * inflationvector * discountvector) * 1E8)
LCOH = LCOH * 2.931 # $/MMBTU
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.ABSORPTION_CHILLER:
PumpingCosts = model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6
NPVoandm = np.sum((self.Coam.value + PumpingCosts) * inflationvector * discountvector)
NPVgrt = self.GTR.value / (1 - self.GTR.value) * (NPVcap + NPVoandm + NPVfc + NPVit - NPVitc)
LCOC = (NPVcap + NPVoandm + NPVfc + NPVit + NPVgrt - NPVitc) / np.sum(
model.surfaceplant.cooling_kWh_Produced.value * inflationvector * discountvector) * 1E8
LCOC = LCOC * 2.931 # $/MMBTU
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.HEAT_PUMP:
PumpingCosts = model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6
HeatPumpElecCosts = model.surfaceplant.heat_pump_electricity_kwh_used.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6
NPVoandm = np.sum((self.Coam.value + PumpingCosts + HeatPumpElecCosts) * inflationvector * discountvector)
NPVgrt = self.GTR.value / (1 - self.GTR.value) * (NPVcap + NPVoandm + NPVfc + NPVit - NPVitc)
LCOH = (NPVcap + NPVoandm + NPVfc + NPVit + NPVgrt - NPVitc) / np.sum(
model.surfaceplant.HeatkWhProduced.value * inflationvector * discountvector) * 1E8
LCOH = self.LCOH.value * 2.931 # $/MMBTU
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING:
PumpingCosts = model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6
NPVoandm = np.sum(
(self.Coam.value + PumpingCosts + self.annualngcost.value) * inflationvector * discountvector)
NPVgrt = self.GTR.value / (1 - self.GTR.value) * (NPVcap + NPVoandm + NPVfc + NPVit - NPVitc)
LCOH = (NPVcap + NPVoandm + NPVfc + NPVit + NPVgrt - NPVitc) / np.sum(
model.surfaceplant.annual_heating_demand.value * inflationvector * discountvector) * 1E2
LCOH = LCOH * 2.931 # $/MMBTU
return LCOE, LCOH, LCOC
[docs]
class Economics:
"""
Class to support the default economic calculations in GEOPHIRES
"""
def __init__(self, model: Model):
"""
The __init__ function is called automatically when a class is instantiated.
It initializes the attributes of an object, and sets default values for certain arguments that can be overridden
by user input.
The __init__ function is used to set up all the parameters in Economics.
Set up all the Parameters that will be predefined by this class using the different types of parameter classes.
Setting up includes giving it a name, a default value, The Unit Type (length, volume, temperature, etc.) and
Unit Name of that value, sets it as required (or not), sets allowable range, the error message if that range
is exceeded, the ToolTip Text, and the name of teh class that created it.
This includes setting up temporary variables that will be available to all the class but noy read in by user,
or used for Output
This also includes all Parameters that are calculated and then published using the Printouts function.
If you choose to subclass this master class, you can do so before or after you create your own parameters.
If you do, you can also choose to call this method from you class, which will effectively add and set all
these parameters to your class.
:param model: The container class of the application, giving access to everything else, including the logger
:type model: :class:`~geophires_x.Model.Model`
:return: None
"""
model.logger.info(f'Init {__class__!s}: {sys._getframe().f_code.co_name}')
# These dictionaries contain a list of all the parameters set in this object, stored as "Parameter" and
# "OutputParameter" Objects. This will allow us later to access them in a user interface and get that list,
# along with unit type, preferred units, etc.
self.ParameterDict = {}
self.OutputParameterDict = {}
# Note: setting Valid to False for any of the cost parameters forces GEOPHIRES to use it's builtin cost engine.
# This is the default.
self.econmodel = self.ParameterDict[self.econmodel.Name] = intParameter(
"Economic Model",
DefaultValue=EconomicModel.STANDARDIZED_LEVELIZED_COST.int_value,
AllowableRange=[1, 2, 3, 4],
ValuesEnum=EconomicModel,
Required=True,
ErrMessage="assume default economic model (2)",
ToolTipText="Specify the economic model to calculate the levelized cost of energy. " +
'; '.join([f'{it.int_value}: {it.value}' for it in EconomicModel])
)
self.ccstimfixed = self.ParameterDict[self.ccstimfixed.Name] = floatParameter(
"Reservoir Stimulation Capital Cost",
DefaultValue=-1.0,
Min=0,
Max=1000,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
Provided=False,
Valid=False,
ToolTipText="Total reservoir stimulation capital cost"
)
self.ccstimadjfactor = self.ParameterDict[self.ccstimadjfactor.Name] = floatParameter(
"Reservoir Stimulation Capital Cost Adjustment Factor",
DefaultValue=1.0,
Min=0,
Max=10,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
Provided=False,
Valid=True,
ToolTipText="Multiplier for built-in reservoir stimulation capital cost correlation"
)
self.ccexplfixed = self.ParameterDict[self.ccexplfixed.Name] = floatParameter(
"Exploration Capital Cost",
DefaultValue=-1.0,
Min=0,
Max=100,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
Provided=False,
Valid=False,
ToolTipText="Total exploration capital cost"
)
self.ccexpladjfactor = self.ParameterDict[self.ccexpladjfactor.Name] = floatParameter(
"Exploration Capital Cost Adjustment Factor",
DefaultValue=1.0,
Min=0,
Max=10,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
Provided=False,
Valid=True,
ToolTipText="Multiplier for built-in exploration capital cost correlation"
)
self.per_production_well_cost = self.ParameterDict[self.per_production_well_cost.Name] = floatParameter(
"Well Drilling and Completion Capital Cost",
DefaultValue=-1.0,
Min=0,
Max=200,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
Provided=False,
Valid=False,
ToolTipText="Well Drilling and Completion Capital Cost"
)
self.per_injection_well_cost = self.ParameterDict[self.per_injection_well_cost.Name] = floatParameter(
"Injection Well Drilling and Completion Capital Cost",
DefaultValue=self.per_production_well_cost.value,
Min=0,
Max=200,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
Provided=False,
Valid=False,
ToolTipText="Injection Well Drilling and Completion Capital Cost"
)
self.production_well_cost_adjustment_factor = self.ParameterDict[self.production_well_cost_adjustment_factor.Name] = floatParameter(
"Well Drilling and Completion Capital Cost Adjustment Factor",
DefaultValue=1.0,
Min=0,
Max=10,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
Provided=False,
Valid=True,
ToolTipText="Well Drilling and Completion Capital Cost Adjustment Factor"
)
self.injection_well_cost_adjustment_factor = self.ParameterDict[self.injection_well_cost_adjustment_factor.Name] = floatParameter(
"Injection Well Drilling and Completion Capital Cost Adjustment Factor",
DefaultValue=self.production_well_cost_adjustment_factor.value,
Min=0,
Max=10,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
Provided=False,
Valid=True,
ToolTipText="Injection Well Drilling and Completion Capital Cost Adjustment Factor"
)
self.oamwellfixed = self.ParameterDict[self.oamwellfixed.Name] = floatParameter(
"Wellfield O&M Cost",
DefaultValue=-1.0,
Min=0,
Max=100,
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
Provided=False,
Valid=False,
ToolTipText="Total annual wellfield O&M cost"
)
self.oamwelladjfactor = self.ParameterDict[self.oamwelladjfactor.Name] = floatParameter(
"Wellfield O&M Cost Adjustment Factor",
DefaultValue=1.0,
Min=0,
Max=10,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
Provided=False,
Valid=True,
ToolTipText="Multiplier for built-in wellfield O&M cost correlation"
)
self.ccplantfixed = self.ParameterDict[self.ccplantfixed.Name] = floatParameter(
"Surface Plant Capital Cost",
DefaultValue=-1.0,
Min=0,
Max=1000,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
Provided=False,
Valid=False,
ToolTipText="Total surface plant capital cost"
)
self.ccplantadjfactor = self.ParameterDict[self.ccplantadjfactor.Name] = floatParameter(
"Surface Plant Capital Cost Adjustment Factor",
DefaultValue=1.0,
Min=0,
Max=10,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
Provided=False,
Valid=True,
ToolTipText="Multiplier for built-in surface plant capital cost correlation"
)
self.ccgathfixed = self.ParameterDict[self.ccgathfixed.Name] = floatParameter(
"Field Gathering System Capital Cost",
DefaultValue=-1.0,
Min=0,
Max=100,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
Provided=False,
Valid=False,
ToolTipText="Total field gathering system capital cost"
)
self.ccgathadjfactor = self.ParameterDict[self.ccgathadjfactor.Name] = floatParameter(
"Field Gathering System Capital Cost Adjustment Factor",
DefaultValue=1.0,
Min=0,
Max=10,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
Provided=False,
Valid=True,
ToolTipText="Multiplier for built-in field gathering system capital cost correlation"
)
self.oamplantfixed = self.ParameterDict[self.oamplantfixed.Name] = floatParameter(
"Surface Plant O&M Cost",
DefaultValue=-1.0,
Min=0,
Max=100,
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
Provided=False,
Valid=False,
ToolTipText="Total annual surface plant O&M cost"
)
self.oamplantadjfactor = self.ParameterDict[self.oamplantadjfactor.Name] = floatParameter(
"Surface Plant O&M Cost Adjustment Factor",
DefaultValue=1.0,
Min=0,
Max=10,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
Provided=False,
Valid=True,
ToolTipText="Multiplier for built-in surface plant O&M cost correlation"
)
self.oamwaterfixed = self.ParameterDict[self.oamwaterfixed.Name] = floatParameter(
"Water Cost",
DefaultValue=-1.0,
Min=0,
Max=100,
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
Provided=False,
Valid=False,
ToolTipText="Total annual make-up water cost"
)
self.oamwateradjfactor = self.ParameterDict[self.oamwateradjfactor.Name] = floatParameter(
"Water Cost Adjustment Factor",
DefaultValue=1.0,
Min=0,
Max=10,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
Provided=False,
Valid=True,
ToolTipText="Multiplier for built-in make-up water cost correlation"
)
self.totalcapcost = self.ParameterDict[self.totalcapcost.Name] = floatParameter(
"Total Capital Cost",
DefaultValue=-1.0,
Min=0,
Max=1000,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
Provided=False,
Valid=False,
ErrMessage="calculate total capital cost using user-provided costs or" +
" built-in correlations for each category.",
ToolTipText="Total initial capital cost."
)
self.oamtotalfixed = self.ParameterDict[self.oamtotalfixed.Name] = floatParameter(
"Total O&M Cost",
DefaultValue=-1.0,
Min=0,
Max=100,
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
Provided=False,
Valid=False,
ErrMessage="calculate total O&M cost using user-provided costs or built-in correlations for each category.",
ToolTipText="Total initial O&M cost."
)
self.timestepsperyear = self.ParameterDict[self.timestepsperyear.Name] = intParameter(
"Time steps per year",
DefaultValue=4,
AllowableRange=list(range(1, 101, 1)),
UnitType=Units.NONE,
Required=True,
ErrMessage="assume default number of time steps per year (4)",
ToolTipText="Number of internal simulation time steps per year"
)
self.FCR = self.ParameterDict[self.FCR.Name] = floatParameter(
"Fixed Charge Rate",
DefaultValue=0.1,
Min=0.0,
Max=1.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
ErrMessage="assume default fixed charge rate (0.1)",
ToolTipText="Fixed charge rate (FCR) used in the Fixed Charge Rate Model"
)
discount_rate_default_val = 0.07
self.discountrate = self.ParameterDict[self.discountrate.Name] = floatParameter(
"Discount Rate",
DefaultValue=discount_rate_default_val,
Min=0.0,
Max=1.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
ErrMessage=f'assume default discount rate ({discount_rate_default_val})',
ToolTipText="Discount rate used in the Standard Levelized Cost Model. "
"Discount Rate is synonymous with Fixed Internal Rate. If one is provided, the other's value "
"will be automatically set to the same value."
)
self.FIB = self.ParameterDict[self.FIB.Name] = floatParameter(
"Fraction of Investment in Bonds",
DefaultValue=0.5,
Min=0.0,
Max=1.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
ErrMessage="assume default fraction of investment in bonds (0.5)",
ToolTipText="Fraction of geothermal project financing through bonds (see docs)"
)
self.BIR = self.ParameterDict[self.BIR.Name] = floatParameter(
"Inflated Bond Interest Rate",
DefaultValue=0.05,
Min=0.0,
Max=1.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
ErrMessage="assume default inflated bond interest rate (0.05)",
ToolTipText="Inflated bond interest rate (see docs)"
)
self.EIR = self.ParameterDict[self.EIR.Name] = floatParameter(
"Inflated Equity Interest Rate",
DefaultValue=0.1,
Min=0.0,
Max=1.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
ErrMessage="assume default inflated equity interest rate (0.1)",
ToolTipText="Inflated equity interest rate (see docs)"
)
self.RINFL = self.ParameterDict[self.RINFL.Name] = floatParameter(
"Inflation Rate",
DefaultValue=0.02,
Min=0.0,
Max=1.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
ErrMessage="assume default inflation rate (0.02)",
ToolTipText="Inflation rate"
)
self.CTR = self.ParameterDict[self.CTR.Name] = floatParameter(
"Combined Income Tax Rate",
DefaultValue=0.02,
Min=0.0,
Max=1.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
ErrMessage="assume default combined income tax rate (0.3)",
ToolTipText="Combined income tax rate (see docs)"
)
self.GTR = self.ParameterDict[self.GTR.Name] = floatParameter(
"Gross Revenue Tax Rate",
DefaultValue=0.02,
Min=0.0,
Max=1.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
ErrMessage="assume default gross revenue tax rate (0)",
ToolTipText="Gross revenue tax rate (see docs)"
)
self.RITC = self.ParameterDict[self.RITC.Name] = floatParameter(
"Investment Tax Credit Rate",
DefaultValue=0.0,
Min=0.0,
Max=1.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
ErrMessage="assume default investment tax credit rate (0)",
ToolTipText="Investment tax credit rate (see docs)"
)
self.PTR = self.ParameterDict[self.PTR.Name] = floatParameter(
"Property Tax Rate",
DefaultValue=0.0,
Min=0.0,
Max=1.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
ErrMessage="assume default property tax rate (0)",
ToolTipText="Property tax rate (see docs)"
)
self.inflrateconstruction = self.ParameterDict[self.inflrateconstruction.Name] = floatParameter(
"Inflation Rate During Construction",
DefaultValue=0.0,
Min=0.0,
Max=1.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
ErrMessage="assume default inflation rate during construction (0)"
)
self.wellcorrelation = self.ParameterDict[self.wellcorrelation.Name] = intParameter(
"Well Drilling Cost Correlation",
DefaultValue=WellDrillingCostCorrelation.VERTICAL_LARGE_INT1.int_value,
AllowableRange=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
ValuesEnum=WellDrillingCostCorrelation,
UnitType=Units.NONE,
ErrMessage="assume default well drilling cost correlation (10)",
ToolTipText="Select the built-in well drilling and completion cost correlation: " +
'; '.join([f'{it.int_value}: {it.value}' for it in WellDrillingCostCorrelation])
)
self.DoAddOnCalculations = self.ParameterDict[self.DoAddOnCalculations.Name] = boolParameter(
"Do AddOn Calculations",
DefaultValue=False,
UnitType=Units.NONE,
Required=False,
ErrMessage="assume default: no economics calculations",
ToolTipText="Set to true if you want the add-on economics calculations to be made"
)
self.DoCarbonCalculations = self.ParameterDict[self.DoCarbonCalculations.Name] = boolParameter(
"Do Carbon Price Calculations",
DefaultValue=False,
UnitType=Units.NONE,
Required=False,
ErrMessage="assume default: no Carbon Credit calculations",
ToolTipText="Set to true if you want the Carbon Credit economics calculations to be made"
)
self.DoSDACGTCalculations = self.ParameterDict[self.DoSDACGTCalculations.Name] = boolParameter(
"Do S-DAC-GT Calculations",
DefaultValue=False,
UnitType=Units.NONE,
Required=False,
ErrMessage="assume default: no S-DAC-GT calculations",
ToolTipText="Set to true if you want the S-DAC-GT economics calculations to be made"
)
self.Vertical_drilling_cost_per_m = self.ParameterDict[self.Vertical_drilling_cost_per_m.Name] = floatParameter(
"All-in Vertical Drilling Costs",
DefaultValue=1000.0,
Min=0.0,
Max=10_000.0,
UnitType=Units.COSTPERDISTANCE,
PreferredUnits=CostPerDistanceUnit.DOLLARSPERM,
CurrentUnits=CostPerDistanceUnit.DOLLARSPERM,
ErrMessage="assume default all-in cost for drill vertical well segment(s) (1000 $/m)",
ToolTipText="Set user specified all-in cost per meter of vertical drilling," +
" including drilling, casing, cement, insulated insert"
)
self.Nonvertical_drilling_cost_per_m = self.ParameterDict[
self.Nonvertical_drilling_cost_per_m.Name] = floatParameter(
"All-in Nonvertical Drilling Costs",
DefaultValue=1300.0,
Min=0.0,
Max=15_000.0,
UnitType=Units.COSTPERDISTANCE,
PreferredUnits=CostPerDistanceUnit.DOLLARSPERM,
CurrentUnits=CostPerDistanceUnit.DOLLARSPERM,
ErrMessage="assume default all-in cost for drill non-vertical well segment(s) (1300 $/m)",
ToolTipText="Set user specified all-in cost per meter of non-vertical drilling, including" +
" drilling, casing, cement, insulated insert"
)
# absorption chiller
self.chillercapex = self.ParameterDict[self.chillercapex.Name] = floatParameter(
"Absorption Chiller Capital Cost",
value=-1.0,
DefaultValue=5,
Min=0,
Max=100,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
Provided=False,
Valid=False,
ToolTipText="Absorption chiller capital cost"
)
self.chilleropex = self.ParameterDict[self.chilleropex.Name] = floatParameter(
"Absorption Chiller O&M Cost",
value=-1.0,
DefaultValue=1,
Min=0,
Max=100,
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
Provided=False,
Valid=False,
ToolTipText="Absorption chiller O&M cost"
)
# heat pump
self.heatpumpcapex = self.ParameterDict[self.heatpumpcapex.Name] = floatParameter(
"Heat Pump Capital Cost",
value=-1.0,
DefaultValue=5,
Min=0,
Max=100,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
Provided=False,
Valid=False,
ToolTipText="Heat pump capital cost"
)
# district heating
self.ngprice = self.ParameterDict[self.ngprice.Name] = floatParameter(
"Peaking Fuel Cost Rate",
DefaultValue=0.034,
Min=0.0,
Max=1.0,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH,
ErrMessage="assume default peaking fuel rate ($0.034/kWh)",
ToolTipText="Price of peaking fuel for peaking boilers"
)
self.peakingboilerefficiency = self.ParameterDict[self.peakingboilerefficiency.Name] = floatParameter(
"Peaking Boiler Efficiency",
DefaultValue=0.85,
Min=0,
Max=1,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
Provided=False,
Valid=False,
ErrMessage="assume default peaking boiler efficiency (85%)",
ToolTipText="Peaking boiler efficiency"
)
self.dhpipingcostrate = self.ParameterDict[self.dhpipingcostrate.Name] = floatParameter(
"District Heating Piping Cost Rate",
DefaultValue=1200,
Min=0,
Max=10000,
UnitType=Units.COSTPERDISTANCE,
PreferredUnits=CostPerDistanceUnit.DOLLARSPERM,
CurrentUnits=CostPerDistanceUnit.DOLLARSPERM,
Provided=False,
Valid=False,
ErrMessage="assume default district heating piping cost rate ($1,200/m)",
ToolTipText="District heating piping cost rate ($/m)"
)
self.dhtotaldistrictnetworkcost = self.ParameterDict[self.dhtotaldistrictnetworkcost.Name] = floatParameter(
"Total District Heating Network Cost",
DefaultValue=10,
Min=0,
Max=1000,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
Provided=False,
Valid=False,
ErrMessage="assume default district heating network cost ($10M)",
ToolTipText="Total district heating network cost ($M)"
)
self.dhoandmcost = self.ParameterDict[self.dhoandmcost.Name] = floatParameter(
"District Heating O&M Cost",
DefaultValue=1,
Min=0,
Max=100,
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
Provided=False, Valid=False,
ToolTipText="Total annual district heating O&M cost ($M/year)"
)
self.dhpipinglength = self.ParameterDict[self.dhpipinglength.Name] = floatParameter(
"District Heating Network Piping Length",
DefaultValue=10.0,
Min=0,
Max=1000,
UnitType=Units.LENGTH,
PreferredUnits=LengthUnit.KILOMETERS,
CurrentUnits=LengthUnit.KILOMETERS,
ErrMessage="assume default district heating network piping length (10 km)",
ToolTipText="District heating network piping length (km)"
)
self.dhroadlength = self.ParameterDict[self.dhroadlength.Name] = floatParameter(
"District Heating Road Length",
DefaultValue=10.0,
Min=0,
Max=1000,
UnitType=Units.LENGTH,
PreferredUnits=LengthUnit.KILOMETERS,
CurrentUnits=LengthUnit.KILOMETERS,
ErrMessage="assume default district heating road length (10 km)",
ToolTipText="District heating road length (km)"
)
self.dhlandarea = self.ParameterDict[self.dhlandarea.Name] = floatParameter(
"District Heating Land Area",
DefaultValue=10.0,
Min=0,
Max=1000,
UnitType=Units.AREA,
PreferredUnits=AreaUnit.KILOMETERS2,
CurrentUnits=AreaUnit.KILOMETERS2,
ErrMessage="assume default district heating land area (10 km2)",
ToolTipText="District heating land area (km2)"
)
self.dhpopulation = self.ParameterDict[self.dhpopulation.Name] = floatParameter(
"District Heating Population",
DefaultValue=200,
Min=0,
Max=1000000,
UnitType=Units.NONE,
ErrMessage="assume default population (200)",
ToolTipText="Specify the population in the district heating network"
)
self.HeatStartPrice = self.ParameterDict[self.HeatStartPrice.Name] = floatParameter(
"Starting Heat Sale Price",
DefaultValue=0.025,
Min=0,
Max=100,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH
)
self.HeatEndPrice = self.ParameterDict[self.HeatEndPrice.Name] = floatParameter(
"Ending Heat Sale Price",
DefaultValue=0.025,
Min=0,
Max=100,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH
)
self.HeatEscalationStart = self.ParameterDict[self.HeatEscalationStart.Name] = intParameter(
"Heat Escalation Start Year",
DefaultValue=5,
AllowableRange=list(range(0, 101, 1)),
UnitType=Units.TIME,
PreferredUnits=TimeUnit.YEAR,
CurrentUnits=TimeUnit.YEAR,
ErrMessage="assume default heat escalation delay time (5 years)",
ToolTipText="Number of years after start of project before start of escalation"
)
self.HeatEscalationRate = self.ParameterDict[self.HeatEscalationRate.Name] = floatParameter(
"Heat Escalation Rate Per Year",
DefaultValue=0.0,
Min=0.0,
Max=100.0,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH,
ErrMessage="assume no heat price escalation (0.0)",
ToolTipText="additional cost per year of price after escalation starts"
)
self.ElecStartPrice = self.ParameterDict[self.ElecStartPrice.Name] = floatParameter(
"Starting Electricity Sale Price",
DefaultValue=0.055,
Min=0,
Max=100,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH
)
self.ElecEndPrice = self.ParameterDict[self.ElecEndPrice.Name] = floatParameter(
"Ending Electricity Sale Price",
DefaultValue=0.055,
Min=0,
Max=100,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH
)
self.ElecEscalationStart = self.ParameterDict[self.ElecEscalationStart.Name] = intParameter(
"Electricity Escalation Start Year",
DefaultValue=5,
AllowableRange=list(range(0, 101, 1)),
UnitType=Units.TIME,
PreferredUnits=TimeUnit.YEAR,
CurrentUnits=TimeUnit.YEAR,
ErrMessage="assume default electricity escalation delay time (5 years)",
ToolTipText="Number of years after start of project before start of escalation"
)
self.ElecEscalationRate = self.ParameterDict[self.ElecEscalationRate.Name] = floatParameter(
"Electricity Escalation Rate Per Year",
DefaultValue=0.0,
Min=0.0,
Max=100.0,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH,
ErrMessage="assume no electricity price escalation (0.0)",
ToolTipText="additional cost per year of price after escalation starts"
)
self.CoolingStartPrice = self.ParameterDict[self.CoolingStartPrice.Name] = floatParameter(
"Starting Cooling Sale Price",
DefaultValue=0.025,
Min=0,
Max=100,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH
)
self.CoolingEndPrice = self.ParameterDict[self.CoolingEndPrice.Name] = floatParameter(
"Ending Cooling Sale Price",
DefaultValue=0.025,
Min=0,
Max=100,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH
)
self.CoolingEscalationStart = self.ParameterDict[self.CoolingEscalationStart.Name] = intParameter(
"Cooling Escalation Start Year",
DefaultValue=5,
AllowableRange=list(range(0, 101, 1)),
UnitType=Units.TIME,
PreferredUnits=TimeUnit.YEAR,
CurrentUnits=TimeUnit.YEAR,
ErrMessage="assume default cooling escalation delay time (5 years)",
ToolTipText="Number of years after start of project before start of escalation"
)
self.CoolingEscalationRate = self.ParameterDict[self.CoolingEscalationRate.Name] = floatParameter(
"Cooling Escalation Rate Per Year",
DefaultValue=0.0,
Min=0.0,
Max=100.0,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH,
ErrMessage="assume no cooling price escalation (0.0)",
ToolTipText="additional cost per year of price after escalation starts"
)
self.CarbonStartPrice = self.ParameterDict[self.CarbonStartPrice.Name] = floatParameter(
"Starting Carbon Credit Value",
DefaultValue=0.0,
Min=0,
Max=1000,
UnitType=Units.COSTPERMASS,
PreferredUnits=CostPerMassUnit.DOLLARSPERLB,
CurrentUnits=CostPerMassUnit.DOLLARSPERLB
)
self.CarbonEndPrice = self.ParameterDict[self.CarbonEndPrice.Name] = floatParameter(
"Ending Carbon Credit Value",
DefaultValue=0.0,
Min=0,
Max=1000,
UnitType=Units.COSTPERMASS,
PreferredUnits=CostPerMassUnit.DOLLARSPERLB,
CurrentUnits=CostPerMassUnit.DOLLARSPERLB
)
self.CarbonEscalationStart = self.ParameterDict[self.CarbonEscalationStart.Name] = intParameter(
"Carbon Escalation Start Year",
DefaultValue=0,
AllowableRange=list(range(0, 101, 1)),
UnitType=Units.TIME,
PreferredUnits=TimeUnit.YEAR,
CurrentUnits=TimeUnit.YEAR,
ErrMessage="assume default Carbon escalation delay time (5 years)",
ToolTipText="Number of years after start of project before start of Carbon incentives"
)
self.CarbonEscalationRate = self.ParameterDict[self.CarbonEscalationRate.Name] = floatParameter(
"Carbon Escalation Rate Per Year",
DefaultValue=0.0,
Min=0.0,
Max=100.0,
UnitType=Units.COSTPERMASS,
PreferredUnits=CostPerMassUnit.DOLLARSPERLB,
CurrentUnits=CostPerMassUnit.DOLLARSPERLB,
ErrMessage="assume no Carbon credit escalation (0.0)",
ToolTipText="additional value per year of price after escalation starts"
)
self.GridCO2Intensity = self.ParameterDict[self.GridCO2Intensity.Name] = floatParameter(
"Current Grid CO2 production",
DefaultValue=0.93916924,
Min=0,
Max=50000,
UnitType=Units.CO2PRODUCTION,
PreferredUnits=CO2ProductionUnit.LBSPERKWH,
CurrentUnits=CO2ProductionUnit.LBSPERKWH,
ErrMessage="assume the grid carbon intensity of Texas ERCOT by grid (0.93916924 lbs/kWh)", #LBSPERKWH https://uh.edu/uh-energy-innovation/uh-energy/energy-research/white-papers/white-paper-files/net-zero-in-texas-electric-grid.pdf
ToolTipText="CO2 intensity of the grid (how much CO2 is produced per kWh of electricity produced (0.93916924 lbs/kWh for Texas ERCOT))"
)
self.NaturalGasCO2Intensity = self.ParameterDict[self.NaturalGasCO2Intensity.Name] = floatParameter(
"CO2 produced by Natural Gas",
DefaultValue=0.070324961,
Min=0,
Max=50000,
UnitType=Units.CO2PRODUCTION,
PreferredUnits=CO2ProductionUnit.LBSPERKWH,
CurrentUnits=CO2ProductionUnit.LBSPERKWH,
ErrMessage="assume the default value CO2 production for burning natural gas (0.070324961 lbs/kWh)",
ToolTipText="CO2 intensity of burning natural gas (how much CO2 is produced per kWh of heat produced "
"(0.070324961 lbs/kWh; https://www.epa.gov/energy/greenhouse-gases-equivalencies-calculator-calculations-and-references))"
)
self.AnnualLicenseEtc = self.ParameterDict[self.AnnualLicenseEtc.Name] = floatParameter(
"Annual License Fees Etc",
DefaultValue=0.0,
Min=-1000.0,
Max=1000.0,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.FlatLicenseEtc = self.ParameterDict[self.FlatLicenseEtc.Name] = floatParameter(
"One-time Flat License Fees Etc",
Min=-1000.0,
Max=1000.0,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.OtherIncentives = self.ParameterDict[self.OtherIncentives.Name] = floatParameter(
"Other Incentives",
Min=-1000.0,
Max=1000.0,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.TaxRelief = self.ParameterDict[self.TaxRelief.Name] = floatParameter(
"Tax Relief Per Year",
DefaultValue=0.0,
Min=0.0,
Max=100.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.PERCENT,
CurrentUnits=PercentUnit.PERCENT,
ErrMessage="assume no tax relief (0.0)",
ToolTipText="Fixed percent reduction in annual tax rate"
)
self.TotalGrant = self.ParameterDict[self.TotalGrant.Name] = floatParameter(
"One-time Grants Etc",
Min=-1000.0,
Max=1000.0,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
fir_default_unit = PercentUnit.PERCENT
fir_default_val = self.discountrate.quantity().to(convertible_unit(fir_default_unit)).magnitude
self.FixedInternalRate = self.ParameterDict[self.FixedInternalRate.Name] = floatParameter(
"Fixed Internal Rate",
DefaultValue=fir_default_val,
Min=0.0,
Max=100.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.PERCENT,
CurrentUnits=fir_default_unit,
ErrMessage=f'assume default for fixed internal rate ({fir_default_val}%)',
ToolTipText="Fixed Internal Rate (used in NPV calculation). "
"Fixed Internal Rate is synonymous with Discount Rate. If one is provided, the other's value "
"will be automatically set to the same value."
)
self.CAPEX_heat_electricity_plant_ratio = self.ParameterDict[self.CAPEX_heat_electricity_plant_ratio.Name] = floatParameter(
"CHP Electrical Plant Cost Allocation Ratio",
DefaultValue=-1.0,
Min=0.0,
Max=1.0,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
Provided=False,
ErrMessage="assume calculation for CHP Electrical Plant Cost Allocation Ratio (cost electrical plant/total CAPEX)",
ToolTipText="CHP Electrical Plant Cost Allocation Ratio (cost electrical plant/total CAPEX)"
)
self.PTCElec = self.ParameterDict[self.PTCElec.Name] = floatParameter(
"Production Tax Credit Electricity",
DefaultValue=0.04,
Min=0.0,
Max=10.0,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH,
ErrMessage="assume default for Production Tax Credit Electricity ($0.04/kWh)",
ToolTipText="Production tax credit for electricity in $/kWh"
)
self.PTCHeat = self.ParameterDict[self.PTCHeat.Name] = floatParameter(
"Production Tax Credit Heat",
DefaultValue=0.0,
Min=0.0,
Max=100.0,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERMMBTU,
CurrentUnits=EnergyCostUnit.DOLLARSPERMMBTU,
ErrMessage="assume default for Production Tax Credit Heat ($0.0/MMBTU)",
ToolTipText="Production tax credit for heat in $/MMBTU"
)
self.PTCCooling = self.ParameterDict[self.PTCCooling.Name] = floatParameter(
"Production Tax Credit Cooling",
DefaultValue=0.0,
Min=0.0,
Max=100.0,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERMMBTU,
CurrentUnits=EnergyCostUnit.DOLLARSPERMMBTU,
ErrMessage="assume default for Production Tax Credit Cooling ($0.0/MMBTU)",
ToolTipText="Production tax credit for cooling in $/MMBTU"
)
self.PTCDuration = self.ParameterDict[self.PTCDuration.Name] = intParameter(
"Production Tax Credit Duration",
DefaultValue=10,
AllowableRange=list(range(0, 100, 1)),
UnitType=Units.TIME,
PreferredUnits=TimeUnit.YEAR,
CurrentUnits=TimeUnit.YEAR,
ErrMessage="assume default for Production Tax Credit Duration (10 years)",
ToolTipText="Production tax credit for duration in years"
)
self.PTCInflationAdjusted = self.ParameterDict[self.PTCInflationAdjusted.Name] = boolParameter(
"Production Tax Credit Inflation Adjusted",
DefaultValue=False,
UnitType=Units.NONE,
Required=False,
ErrMessage="assume default for Production Tax Credit Inflation Adjusted (False)",
ToolTipText="Production tax credit inflation adjusted"
)
self.jobs_created_per_MW_electricity = self.ParameterDict[
self.jobs_created_per_MW_electricity.Name] = floatParameter(
"Estimated Jobs Created per MW of Electricity Produced",
DefaultValue=2.13,
UnitType=Units.NONE,
Required=False,
ToolTipText="Estimated jobs created per MW of electricity produced, per https://geothermal.org/resources/geothermal-basics"
)
# local variable initialization
self.CAPEX_cost_electricity_plant = 0.0
self.CAPEX_cost_heat_plant = 0.0
self.OPEX_cost_electricity_plant = 0.0
self.OPEX_cost_heat_plant = 0.0
self.CAPEX_heat_electricity_plant_ratio.value = 0.0
self.Claborcorrelation = 0.0
self.Cpumps = 0.0
self.annualelectricityincome = 0.0
self.annualheatincome = 0.0
self.InputFile = ""
self.Cplantcorrelation = 0.0
sclass = str(__class__).replace("<class \'", "")
self.MyClass = sclass.replace("\'>", "")
self.MyPath = os.path.abspath(__file__)
# results
self.ElecPrice = self.OutputParameterDict[self.ElecPrice.Name] = OutputParameter(
"Electricity Sale Price Model",
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.CENTSSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH,
)
self.HeatPrice = self.OutputParameterDict[self.HeatPrice.Name] = OutputParameter(
"Heat Sale Price Model",
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.CENTSSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH,
)
self.CoolingPrice = self.OutputParameterDict[self.CoolingPrice.Name] = OutputParameter(
"Cooling Sale Price Model",
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.CENTSSPERKWH,
CurrentUnits=EnergyCostUnit.DOLLARSPERKWH,
)
self.CarbonPrice = self.OutputParameterDict[self.CarbonPrice.Name] = OutputParameter(
"Carbon Price Model",
UnitType=Units.COSTPERMASS,
PreferredUnits=CostPerMassUnit.DOLLARSPERTONNE,
CurrentUnits=CostPerMassUnit.DOLLARSPERTONNE
)
self.LCOC = self.OutputParameterDict[self.LCOC.Name] = OutputParameter(
Name="LCOC",
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERMMBTU,
CurrentUnits=EnergyCostUnit.DOLLARSPERMMBTU
)
self.LCOE = self.OutputParameterDict[self.LCOE.Name] = OutputParameter(
Name="LCOE",
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.CENTSSPERKWH,
CurrentUnits=EnergyCostUnit.CENTSSPERKWH
)
self.LCOH = self.OutputParameterDict[self.LCOH.Name] = OutputParameter(
Name="LCOH",
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERMMBTU,
CurrentUnits=EnergyCostUnit.DOLLARSPERMMBTU
) # $/MMBTU
self.Cstim = self.OutputParameterDict[self.Cstim.Name] = OutputParameter(
Name="O&M Surface Plant costs",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.Cexpl = self.OutputParameterDict[self.Cexpl.Name] = OutputParameter(
Name="Exploration cost",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.Cwell = self.OutputParameterDict[self.Cwell.Name] = OutputParameter(
Name="Wellfield cost",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.Coamwell = self.OutputParameterDict[self.Coamwell.Name] = OutputParameter(
Name="O&M Wellfield cost",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
self.Cplant = self.OutputParameterDict[self.Cplant.Name] = OutputParameter(
Name="Surface Plant cost",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.Coamplant = self.OutputParameterDict[self.Coamplant.Name] = OutputParameter(
Name="O&M Surface Plant costs",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
self.Cgath = self.OutputParameterDict[self.Cgath.Name] = OutputParameter(
Name="Field gathering system cost",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.Cpiping = self.OutputParameterDict[self.Cpiping.Name] = OutputParameter(
Name="Transmission pipeline costs",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.Coamwater = self.OutputParameterDict[self.Coamwater.Name] = OutputParameter(
Name="O&M Make-up Water costs",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
self.CCap = self.OutputParameterDict[self.CCap.Name] = OutputParameter(
Name="Total Capital Cost",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.Coam = self.OutputParameterDict[self.Coam.Name] = OutputParameter(
Name="Total O&M Cost",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
# self.averageannualpumpingcosts = self.OutputParameterDict[
# self.averageannualpumpingcosts.Name] = OutputParameter( #typo here!??!
self.averageannualpumpingcosts = OutputParameter(
Name="Average Annual Pumping Costs",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
# heat pump
self.averageannualheatpumpelectricitycost = self.OutputParameterDict[
self.averageannualheatpumpelectricitycost.Name] = OutputParameter(
Name="Average Annual Heat Pump Electricity Cost",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
# district heating
self.peakingboilercost = self.OutputParameterDict[self.peakingboilercost.Name] = OutputParameter(
Name="Peaking boiler cost",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.dhdistrictcost = self.OutputParameterDict[self.dhdistrictcost.Name] = OutputParameter(
Name="District Heating System Cost",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.populationdensity = self.OutputParameterDict[self.populationdensity.Name] = OutputParameter(
Name="District Heating System Population Density",
UnitType=Units.POPDENSITY,
PreferredUnits=PopDensityUnit.perkm2,
CurrentUnits=PopDensityUnit.perkm2
)
self.annualngcost = self.OutputParameterDict[self.annualngcost.Name] = OutputParameter(
Name="Annual Peaking Fuel Cost",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
self.dhdistrictoandmcost = self.OutputParameterDict[self.dhdistrictoandmcost.Name] = OutputParameter(
Name="Annual District Heating O&M Cost",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
self.averageannualngcost = self.OutputParameterDict[self.averageannualngcost.Name] = OutputParameter(
Name="Average Annual Peaking Fuel Cost",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
self.ElecRevenue = self.OutputParameterDict[self.ElecRevenue.Name] = OutputParameter(
Name="Annual Revenue from Electricity Production",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
self.ElecCummRevenue = self.OutputParameterDict[self.ElecCummRevenue.Name] = OutputParameter(
Name="Cumulative Revenue from Electricity Production",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.HeatRevenue = self.OutputParameterDict[self.HeatRevenue.Name] = OutputParameter(
Name="Annual Revenue from Heat Production",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
self.HeatCummRevenue = self.OutputParameterDict[self.HeatCummRevenue.Name] = OutputParameter(
Name="Cumulative Revenue from Heat Production",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.CoolingRevenue = self.OutputParameterDict[self.CoolingRevenue.Name] = OutputParameter(
Name="Annual Revenue from Cooling Production",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
self.CoolingCummRevenue = self.OutputParameterDict[self.CoolingCummRevenue.Name] = OutputParameter(
Name="Cumulative Revenue from Cooling Production",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.CarbonRevenue = self.OutputParameterDict[self.CarbonRevenue.Name] = OutputParameter(
Name="Annual Revenue from Carbon Pricing",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
self.CarbonCummCashFlow = self.OutputParameterDict[self.CarbonCummCashFlow.Name] = OutputParameter(
Name="Cumulative Revenue from Carbon Pricing",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.CarbonThatWouldHaveBeenProducedAnnually = self.OutputParameterDict[
self.CarbonThatWouldHaveBeenProducedAnnually.Name] = OutputParameter(
"Annual Saved Carbon Production",
UnitType=Units.MASS,
PreferredUnits=MassUnit.LB,
CurrentUnits=MassUnit.LB
)
self.CarbonThatWouldHaveBeenProducedTotal = self.OutputParameterDict[
self.CarbonThatWouldHaveBeenProducedTotal.Name] = OutputParameter(
"Total Saved Carbon Production",
UnitType=Units.MASS,
PreferredUnits=MassUnit.LB,
CurrentUnits=MassUnit.LB
)
self.interest_rate = self.OutputParameterDict[self.interest_rate.Name] = OutputParameter(
Name='Interest Rate',
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.PERCENT,
CurrentUnits=PercentUnit.PERCENT
)
self.TotalRevenue = self.OutputParameterDict[self.TotalRevenue.Name] = OutputParameter(
Name="Annual Revenue from Project",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
)
self.TotalCummRevenue = self.OutputParameterDict[self.TotalCummRevenue.Name] = OutputParameter(
Name="Cumulative Revenue from Project",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.ProjectNPV = self.OutputParameterDict[self.ProjectNPV.Name] = OutputParameter(
"Project Net Present Value",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.ProjectIRR = self.OutputParameterDict[self.ProjectIRR.Name] = OutputParameter(
"Project Internal Rate of Return",
UnitType=Units.PERCENT,
CurrentUnits=PercentUnit.PERCENT,
PreferredUnits=PercentUnit.PERCENT,
)
self.ProjectVIR = self.OutputParameterDict[self.ProjectVIR.Name] = OutputParameter(
"Project Value Investment Ratio",
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH
)
self.ProjectMOIC = self.OutputParameterDict[self.ProjectMOIC.Name] = OutputParameter(
"Project Multiple of Invested Capital",
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH
)
self.ProjectPaybackPeriod = self.OutputParameterDict[self.ProjectPaybackPeriod.Name] = OutputParameter(
"Project Payback Period",
UnitType=Units.TIME,
PreferredUnits=TimeUnit.YEAR,
CurrentUnits=TimeUnit.YEAR
)
self.RITCValue = self.OutputParameterDict[self.RITCValue.Name] = OutputParameter(
Name="Investment Tax Credit Value",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.cost_one_production_well = self.OutputParameterDict[self.cost_one_production_well.Name] = OutputParameter(
Name="Cost of One Production Well",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.cost_one_injection_well = self.OutputParameterDict[self.cost_one_injection_well.Name] = OutputParameter(
Name="Cost of One Injection Well",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.cost_lateral_section = self.OutputParameterDict[self.cost_lateral_section.Name] = OutputParameter(
Name="Cost of the entire (multi-) lateral section of a well",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.cost_to_junction_section = self.OutputParameterDict[self.cost_to_junction_section.Name] = OutputParameter(
Name="Cost of the entire section of a well from bottom of vertical to junction with laterals",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.jobs_created = self.OutputParameterDict[self.jobs_created.Name] = OutputParameter(
Name="Estimated Jobs Created",
UnitType=Units.NONE,
)
model.logger.info(f'Complete {__class__!s}: {sys._getframe().f_code.co_name}')
[docs]
def read_parameters(self, model: Model) -> None:
"""
read_parameters read and update the Economics parameters and handle the special cases
Deal with all the parameter values that the user has provided. They should really only provide values
that they want to change from the default values, but they can provide a value that is already set
because it is a default value set in __init__. It will ignore those.
This also deals with all the special cases that need to be taken care of after a
value has been read in and checked.
If you choose to subclass this master class, you can also choose to override this method (or not),
and if you do, do it before or after you call you own version of this method. If you do, you can also
choose to call this method from you class, which can effectively modify all these superclass parameters
in your class.
:param model: The container class of the application, giving access to everything else, including the logger
:type model: :class:`~geophires_x.Model.Model`
:return: None
"""
model.logger.info(f'Init {__class__!s}: {sys._getframe().f_code.co_name}')
if len(model.InputParameters) > 0:
# loop through all the parameters that the user wishes to set, looking for parameters that match this object
for item in self.ParameterDict.items():
ParameterToModify = item[1]
key = ParameterToModify.Name.strip()
if key in model.InputParameters:
ParameterReadIn = model.InputParameters[key]
# this should handle all the non-special cases
ReadParameter(ParameterReadIn, ParameterToModify, model)
# handle special cases
if ParameterToModify.Name == "Economic Model":
self.econmodel.value = EconomicModel.from_input_string(ParameterReadIn.sValue)
elif ParameterToModify.Name == "Well Drilling Cost Correlation":
ParameterToModify.value = WellDrillingCostCorrelation.from_input_string(ParameterReadIn.sValue)
elif ParameterToModify.Name == "Reservoir Stimulation Capital Cost Adjustment Factor":
if self.ccstimfixed.Valid and ParameterToModify.Valid:
print("Warning: Provided reservoir stimulation cost adjustment factor not considered" +
" because valid total reservoir stimulation cost provided.")
model.logger.warning(
"Provided reservoir stimulation cost adjustment factor not considered" +
" because valid total reservoir stimulation cost provided.")
elif not self.ccstimfixed.Provided and not ParameterToModify.Provided:
ParameterToModify.value = 1.0
print("Warning: No valid reservoir stimulation total cost or adjustment factor provided." +
" GEOPHIRES will assume default built-in reservoir stimulation cost correlation with" +
" adjustment factor = 1.")
model.logger.warning("No valid reservoir stimulation total cost or adjustment factor" +
" provided. GEOPHIRES will assume default built-in reservoir stimulation cost correlation" +
" with adjustment factor = 1.")
elif self.ccstimfixed.Provided and not self.ccstimfixed.Valid:
print("Warning: Provided reservoir stimulation cost outside of range 0-100. GEOPHIRES" +
" will assume default built-in reservoir stimulation cost correlation with" +
" adjustment factor = 1.")
model.logger.warning(
"Provided reservoir stimulation cost outside of range 0-100. GEOPHIRES" +
" will assume default built-in reservoir stimulation cost correlation with" +
" adjustment factor = 1.")
ParameterToModify.value = 1.0
elif not self.ccstimfixed.Provided and ParameterToModify.Provided and not ParameterToModify.Valid:
print("Warning: Provided reservoir stimulation cost adjustment factor outside of" +
" range 0-10. GEOPHIRES will assume default reservoir stimulation cost correlation with" +
" adjustment factor = 1.")
model.logger.warning("Provided reservoir stimulation cost adjustment factor outside of" +
" range 0-10. GEOPHIRES will assume default reservoir stimulation cost correlation with" +
" adjustment factor = 1.")
ParameterToModify.value = 1.0
elif ParameterToModify.Name == "Exploration Capital Cost Adjustment Factor":
if self.totalcapcost.Valid:
if self.ccexplfixed.Provided:
print("Warning: Provided exploration cost not considered because valid" +
" total capital cost provided.")
model.logger.warning("Warning: Provided exploration cost not considered" +
" because valid total capital cost provided.")
if ParameterToModify.Provided:
print("Warning: Provided exploration cost adjustment factor not considered because" +
" valid total capital cost provided.")
model.logger.warning("Warning: Provided exploration cost not considered because valid" +
" total capital cost provided.")
else:
if self.ccexplfixed.Valid and ParameterToModify.Valid:
print("Warning: Provided exploration cost adjustment factor not considered" +
" because valid total exploration cost provided.")
model.logger.warning("Provided exploration cost adjustment factor not" +
" considered because valid total exploration cost provided.")
elif not self.ccexplfixed.Provided and not ParameterToModify.Provided:
ParameterToModify.value = 1.0
print("Warning: No valid exploration total cost or adjustment factor provided." +
" GEOPHIRES will assume default built-in exploration cost correlation with" +
" adjustment factor = 1.")
model.logger.warning("No valid exploration total cost or adjustment factor provided." +
" GEOPHIRES will assume default built-in exploration cost correlation with" +
" adjustment factor = 1.")
elif self.ccexplfixed.Provided and not self.ccexplfixed.Valid:
print("Warning: Provided exploration cost outside of range 0-100. GEOPHIRES" +
" will assume default built-in exploration cost correlation with adjustment factor = 1.")
model.logger.warning("Provided exploration cost outside of range 0-100. GEOPHIRES" +
" will assume default built-in exploration cost correlation with adjustment factor = 1.")
ParameterToModify.value = 1.0
elif not self.ccexplfixed.Provided and ParameterToModify.Provided and not ParameterToModify.Valid:
print("Warning: Provided exploration cost adjustment factor outside of range 0-10." +
" GEOPHIRES will assume default exploration cost correlation with adjustment factor = 1.")
model.logger.warning("Provided exploration cost adjustment factor outside of" +
" range 0-10. GEOPHIRES will assume default exploration cost correlation with" +
" adjustment factor = 1.")
ParameterToModify.value = 1.0
elif ParameterToModify.Name == "Well Drilling and Completion Capital Cost Adjustment Factor":
if self.per_production_well_cost.Valid and ParameterToModify.Valid:
print("Warning: Provided well drilling and completion cost adjustment factor not" +
" considered because valid total well drilling and completion cost provided.")
model.logger.warning("Provided well drilling and completion cost adjustment factor not" +
" considered because valid total well drilling and completion cost provided.")
elif not self.per_production_well_cost.Provided and not self.production_well_cost_adjustment_factor.Provided:
ParameterToModify.value = 1.0
print("Warning: No valid well drilling and completion total cost or adjustment" +
" factor provided. GEOPHIRES will assume default built-in well drilling and" +
" completion cost correlation with adjustment factor = 1.")
model.logger.warning(
"No valid well drilling and completion total cost or adjustment factor" +
" provided. GEOPHIRES will assume default built-in well drilling and completion cost" +
" correlation with adjustment factor = 1.")
elif self.per_production_well_cost.Provided and not self.per_production_well_cost.Valid:
print("Warning: Provided well drilling and completion cost outside of range 0-1000." +
" GEOPHIRES will assume default built-in well drilling and completion cost correlation" +
" with adjustment factor = 1.")
model.logger.warning("Provided well drilling and completion cost outside of range 0-1000." +
" GEOPHIRES will assume default built-in well drilling and completion cost correlation with" +
" adjustment factor = 1.")
self.production_well_cost_adjustment_factor.value = 1.0
elif not self.per_production_well_cost.Provided and self.production_well_cost_adjustment_factor.Provided and not self.production_well_cost_adjustment_factor.Valid:
print("Warning: Provided well drilling and completion cost adjustment factor outside" +
" of range 0-10. GEOPHIRES will assume default built-in well drilling and completion" +
" cost correlation with adjustment factor = 1.")
model.logger.warning(
"Provided well drilling and completion cost adjustment factor outside" +
" of range 0-10. GEOPHIRES will assume default built-in well drilling and completion" +
" cost correlation with adjustment factor = 1.")
self.production_well_cost_adjustment_factor.value = 1.0
elif ParameterToModify.Name == "Wellfield O&M Cost Adjustment Factor":
if self.oamtotalfixed.Valid:
if self.oamwellfixed.Provided:
print("Warning: Provided total wellfield O&M cost not considered because" +
" valid total annual O&M cost provided.")
model.logger.warning("Provided total wellfield O&M cost not considered because" +
" valid total annual O&M cost provided.")
if ParameterToModify.Provided:
print("Warning: Provided wellfield O&M cost adjustment factor not considered because" +
" valid total annual O&M cost provided.")
model.logger.warning("Provided wellfield O&M cost adjustment factor not considered" +
" because valid total annual O&M cost provided.")
else:
if self.oamwellfixed.Valid and ParameterToModify.Valid:
print("Warning: Provided wellfield O&M cost adjustment factor not considered" +
" because valid total wellfield O&M cost provided.")
model.logger.warning("Provided wellfield O&M cost adjustment factor not considered" +
" because valid total wellfield O&M cost provided.")
elif not self.oamwellfixed.Provided and not ParameterToModify.Provided:
ParameterToModify.value = 1.0
print("Warning: No valid total wellfield O&M cost or adjustment factor provided." +
" GEOPHIRES will assume default built-in wellfield O&M cost correlation with" +
" adjustment factor = 1.")
model.logger.warning("No valid total wellfield O&M cost or adjustment factor" +
" provided. GEOPHIRES will assume default built-in wellfield O&M cost correlation" +
" with adjustment factor = 1.")
elif self.oamwellfixed.Provided and not self.oamwellfixed.Valid:
print("Warning: Provided total wellfield O&M cost outside of range 0-100." +
" GEOPHIRES will assume default built-in wellfield O&M cost correlation with" +
" adjustment factor = 1.")
model.logger.warning("Provided total wellfield O&M cost outside of range 0-100." +
" GEOPHIRES will assume default built-in wellfield O&M cost correlation with" +
" adjustment factor = 1.")
ParameterToModify.value = 1.0
elif not self.oamwellfixed.Provided and self.oamwelladjfactor.Provided and not self.oamwelladjfactor.Valid:
print("Warning: Provided wellfield O&M cost adjustment factor outside of range 0-10." +
" GEOPHIRES will assume default wellfield O&M cost correlation with adjustment factor = 1.")
model.logger.warning("Provided wellfield O&M cost adjustment factor outside of" +
" range 0-10. GEOPHIRES will assume default wellfield O&M cost correlation with" +
" adjustment factor = 1.")
ParameterToModify.value = 1.0
elif ParameterToModify.Name == "Surface Plant Capital Cost Adjustment Factor":
if self.totalcapcost.Valid:
if self.ccplantfixed.Provided:
print("Warning: Provided surface plant cost not considered because valid" +
" total capital cost provided.")
model.logger.warning("Provided surface plant cost not considered because valid" +
" total capital cost provided.")
if ParameterToModify.Provided:
print("Warning: Provided surface plant cost adjustment factor not considered" +
" because valid total capital cost provided.")
model.logger.warning("Provided surface plant cost adjustment factor not considered" +
" because valid total capital cost provided.")
else:
if self.ccplantfixed.Valid and ParameterToModify.Valid:
print("Warning: Provided surface plant cost adjustment factor not considered because" +
" valid total surface plant cost provided.")
model.logger.warning("Provided surface plant cost adjustment factor not considered" +
" because valid total surface plant cost provided.")
elif not self.ccplantfixed.Provided and not ParameterToModify.Provided:
ParameterToModify.value = 1.0
print("Warning: No valid surface plant total cost or adjustment factor provided." +
" GEOPHIRES will assume default built-in surface plant cost correlation with" +
" adjustment factor = 1.")
model.logger.warning("No valid surface plant total cost or adjustment factor" +
" provided. GEOPHIRES will assume default built-in surface plant cost correlation" +
" with adjustment factor = 1.")
elif self.ccplantfixed.Provided and not self.ccplantfixed.Valid:
print("Warning: Provided surface plant cost outside of range 0-1000." +
" GEOPHIRES will assume default built-in surface plant cost correlation with" +
" adjustment factor = 1.")
model.logger.warning("Provided surface plant cost outside of range 0-1000." +
" GEOPHIRES will assume default built-in surface plant cost correlation with" +
" adjustment factor = 1.")
ParameterToModify.value = 1.0
elif not self.ccplantfixed.Provided and self.ccplantadjfactor.Provided and not self.ccplantadjfactor.Valid:
print("Warning: Provided surface plant cost adjustment factor outside of range 0-10." +
" GEOPHIRES will assume default surface plant cost correlation with" +
" adjustment factor = 1.")
model.logger.warning("Provided surface plant cost adjustment factor outside of" +
" range 0-10. GEOPHIRES will assume default surface plant cost correlation with" +
" adjustment factor = 1.")
ParameterToModify.value = 1.0
elif ParameterToModify.Name == "Field Gathering System Capital Cost Adjustment Factor":
if self.totalcapcost.Valid:
if self.ccgathfixed.Provided:
print("Warning: Provided field gathering system cost not considered because valid" +
" total capital cost provided.")
model.logger.warning(
"Provided field gathering system cost not considered because valid" +
" total capital cost provided.")
if ParameterToModify.Provided:
print("Warning: Provided field gathering system cost adjustment factor not" +
" considered because valid total capital cost provided.")
model.logger.warning("Provided field gathering system cost adjustment factor not" +
" considered because valid total capital cost provided.")
else:
if self.ccgathfixed.Valid and ParameterToModify.Valid:
print("Warning: Provided field gathering system cost adjustment factor not" +
" considered because valid total field gathering system cost provided.")
model.logger.warning("Provided field gathering system cost adjustment factor not" +
" considered because valid total field gathering system cost provided.")
elif not self.ccgathfixed.Provided and not ParameterToModify.Provided:
ParameterToModify.value = 1.0
print("Warning: No valid field gathering system total cost or adjustment factor" +
" provided. GEOPHIRES will assume default built-in field gathering system cost" +
" correlation with adjustment factor = 1.")
model.logger.warning("No valid field gathering system total cost or adjustment factor" +
" provided. GEOPHIRES will assume default built-in field gathering system cost" +
" correlation with adjustment factor = 1.")
elif self.ccgathfixed.Provided and not self.ccgathfixed.Valid:
print("Warning: Provided field gathering system cost outside of range 0-100." +
" GEOPHIRES will assume default built-in field gathering system cost correlation" +
" with adjustment factor = 1.")
model.logger.warning("Provided field gathering system cost outside of range 0-100." +
" GEOPHIRES will assume default built-in field gathering system cost correlation with" +
" adjustment factor = 1.")
ParameterToModify.value = 1.0
elif not self.ccgathfixed.Provided and ParameterToModify.Provided and not ParameterToModify.Valid:
print("Warning: Provided field gathering system cost adjustment factor" +
" outside of range 0-10. GEOPHIRES will assume default field gathering system" +
" cost correlation with adjustment factor = 1.")
model.logger.warning("Provided field gathering system cost adjustment factor" +
" outside of range 0-10. GEOPHIRES will assume default field gathering system cost" +
" correlation with adjustment factor = 1.")
ParameterToModify.value = 1.0
elif ParameterToModify.Name == "Water Cost Adjustment Factor":
if self.oamtotalfixed.Valid:
if self.oamwaterfixed.Provided:
print("Warning: Provided total water cost not considered because valid" +
" total annual O&M cost provided.")
model.logger.warning("Provided total water cost not considered because valid" +
" total annual O&M cost provided.")
if ParameterToModify.Provided:
print("Warning: Provided water cost adjustment factor not considered because" +
" valid total annual O&M cost provided.")
model.logger.warning("Provided water cost adjustment factor not considered because" +
" valid total annual O&M cost provided.")
else:
if self.oamwaterfixed.Valid and ParameterToModify.Valid:
print("Warning: Provided water cost adjustment factor not considered because" +
" valid total water cost provided.")
model.logger.warning("Provided water cost adjustment factor not considered because" +
" valid total water cost provided.")
elif not self.oamwaterfixed.Provided and not ParameterToModify.Provided:
ParameterToModify.value = 1.0
print("Warning: No valid total water cost or adjustment factor provided." +
" GEOPHIRES will assume default built-in water cost correlation with" +
" adjustment factor = 1.")
model.logger.warning("No valid total water cost or adjustment factor provided." +
" GEOPHIRES will assume default built-in water cost correlation with" +
" adjustment factor = 1.")
elif self.oamwaterfixed.Provided and not self.oamwaterfixed.Valid:
print("Warning: Provided total water cost outside of range 0-100. GEOPHIRES" +
" will assume default built-in water cost correlation with adjustment factor = 1.")
model.logger.warning("Provided total water cost outside of range 0-100. GEOPHIRES" +
" will assume default built-in water cost correlation with adjustment factor = 1.")
ParameterToModify.value = 1.0
elif not self.oamwaterfixed.Provided and ParameterToModify.Provided and not ParameterToModify.Valid:
print("Warning: Provided water cost adjustment factor outside of range 0-10." +
" GEOPHIRES will assume default water cost correlation with adjustment factor = 1.")
model.logger.warning("Provided water cost adjustment factor outside of range 0-10." +
" GEOPHIRES will assume default water cost correlation with adjustment factor = 1.")
ParameterToModify.value = 1.0
elif ParameterToModify.Name == "Surface Plant O&M Cost Adjustment Factor":
if self.oamtotalfixed.Valid:
if self.oamplantfixed.Provided:
print("Warning: Provided total surface plant O&M cost not considered because" +
" valid total annual O&M cost provided.")
model.logger.warning("Provided total surface plant O&M cost not considered because" +
" valid total annual O&M cost provided.")
if ParameterToModify.Provided:
print("Warning: Provided surface plant O&M cost adjustment factor not considered" +
" because valid total annual O&M cost provided.")
model.logger.warning(
"Provided surface plant O&M cost adjustment factor not considered" +
" because valid total annual O&M cost provided.")
else:
if self.oamplantfixed.Valid and ParameterToModify.Valid:
print("Warning: Provided surface plant O&M cost adjustment factor not considered" +
" because valid total surface plant O&M cost provided.")
model.logger.warning(
"Provided surface plant O&M cost adjustment factor not considered" +
" because valid total surface plant O&M cost provided.")
elif not self.oamplantfixed.Provided and not ParameterToModify.Provided:
ParameterToModify.value = 1.0
print("Warning: No valid surface plant O&M cost or adjustment factor provided." +
" GEOPHIRES will assume default built-in surface plant O&M cost correlation with" +
" adjustment factor = 1.")
model.logger.warning("No valid surface plant O&M cost or adjustment factor provided." +
" GEOPHIRES will assume default built-in surface plant O&M cost correlation with" +
" adjustment factor = 1.")
elif self.oamplantfixed.Provided and not self.oamplantfixed.Valid:
print("Warning: Provided surface plant O&M cost outside of range 0-100." +
" GEOPHIRES will assume default built-in surface plant O&M cost correlation with" +
" adjustment factor = 1.")
model.logger.warning("Provided surface plant O&M cost outside of range 0-100." +
" GEOPHIRES will assume default built-in surface plant O&M cost correlation with" +
" adjustment factor = 1.")
ParameterToModify.value = 1.0
elif not self.oamplantfixed.Provided and ParameterToModify.Provided and not ParameterToModify.Valid:
print("Warning: Provided surface plant O&M cost adjustment factor outside of" +
" range 0-10. GEOPHIRES will assume default surface plant O&M cost correlation with" +
" adjustment factor = 1.")
model.logger.warning("Provided surface plant O&M cost adjustment factor outside of" +
" range 0-10. GEOPHIRES will assume default surface plant O&M cost correlation with" +
" adjustment factor = 1.")
ParameterToModify.value = 1.0
else:
model.logger.info("No parameters read because no content provided")
# we can determine on-the-fly if Addons, CCUS, or S-DAC-GT are being used in the user input file
for key in model.InputParameters.keys():
if key.startswith("AddOn"):
self.DoAddOnCalculations.value = True
break
for key in model.InputParameters.keys():
if key.startswith("S-DAC-GT"):
self.DoSDACGTCalculations.value = True
break
coerce_int_params_to_enum_values(self.ParameterDict)
self.sync_interest_rate(model)
model.logger.info(f'complete {__class__!s}: {sys._getframe().f_code.co_name}')
def sync_interest_rate(self, model):
def discount_rate_display() -> str:
return str(self.discountrate.quantity()).replace(' dimensionless', '')
if self.discountrate.Provided ^ self.FixedInternalRate.Provided:
if self.discountrate.Provided:
self.FixedInternalRate.value = self.discountrate.quantity().to(
convertible_unit(self.FixedInternalRate.CurrentUnits)).magnitude
model.logger.info(f'Set {self.FixedInternalRate.Name} to {self.FixedInternalRate.quantity()} '
f'because {self.discountrate.Name} was provided ({discount_rate_display()})')
else:
self.discountrate.value = self.FixedInternalRate.quantity().to(
convertible_unit(self.discountrate.CurrentUnits)).magnitude
model.logger.info(
f'Set {self.discountrate.Name} to {discount_rate_display()} because '
f'{self.FixedInternalRate.Name} was provided ({self.FixedInternalRate.quantity()})')
if self.discountrate.Provided and self.FixedInternalRate.Provided \
and self.discountrate.quantity().to(convertible_unit(self.FixedInternalRate.CurrentUnits)).magnitude \
!= self.FixedInternalRate.value:
model.logger.warning(f'{self.discountrate.Name} and {self.FixedInternalRate.Name} provided with different '
f'values ({discount_rate_display()}; {self.FixedInternalRate.quantity()}). '
f'It is recommended to only provide one of these values.')
self.interest_rate.value = self.discountrate.quantity().to(convertible_unit(self.interest_rate.CurrentUnits)).magnitude
[docs]
def Calculate(self, model: Model) -> None:
"""
The Calculate function is where all the calculations are done.
This function can be called multiple times, and will only recalculate what has changed each time it is called.
This is where all the calculations are made using all the values that have been set.
If you subclass this class, you can choose to run these calculations before (or after) your calculations,
but that assumes you have set all the values that are required for these calculations
If you choose to subclass this master class, you can also choose to override this method (or not),
and if you do, do it before or after you call you own version of this method. If you do,
you can also choose to call this method from you class, which can effectively run the calculations
of the superclass, making all thr values available to your methods. but you had
better have set all the parameters!
:param model: The container class of the application, giving access to everything else, including the logger
:type model: :class:`~geophires_x.Model.Model`
:return: Nothing, but it does make calculations and set values in the model
"""
model.logger.info(f'Init {__class__!s}: {sys._getframe().f_code.co_name}')
# capital costs
# well costs (using GeoVision drilling correlations). These are calculated whether totalcapcostvalid = 1
# start with the cost of one well
# C1well is well drilling and completion cost in M$/well
if self.per_production_well_cost.Valid:
self.cost_one_production_well.value = self.per_production_well_cost.value
if not self.per_injection_well_cost.Provided:
self.cost_one_injection_well.value = self.per_production_well_cost.value
else:
self.cost_one_injection_well.value = self.per_injection_well_cost.value
self.Cwell.value = ((self.cost_one_production_well.value * model.wellbores.nprod.value) +
(self.cost_one_injection_well.value * model.wellbores.ninj.value))
else:
if hasattr(model.wellbores, 'numnonverticalsections') and model.wellbores.numnonverticalsections.Provided:
self.cost_lateral_section.value = 0.0
if not model.wellbores.IsAGS.value:
input_vert_depth_km = model.reserv.depth.quantity().to('km').magnitude
output_vert_depth_km = 0.0
else:
input_vert_depth_km = model.reserv.InputDepth.quantity().to('km').magnitude
output_vert_depth_km = model.reserv.OutputDepth.quantity().to('km').magnitude
model.wellbores.injection_reservoir_depth.value = input_vert_depth_km
tot_m, tot_vert_m, tot_horiz_m, _ = calculate_total_drilling_lengths_m(model.wellbores.Configuration.value,
model.wellbores.numnonverticalsections.value,
model.wellbores.Nonvertical_length.value / 1000.0,
input_vert_depth_km,
output_vert_depth_km,
model.wellbores.nprod.value,
model.wellbores.ninj.value)
else:
tot_m = tot_vert_m = model.reserv.depth.quantity().to('km').magnitude
tot_horiz_m = 0.0
if not model.wellbores.injection_reservoir_depth.Provided:
model.wellbores.injection_reservoir_depth.value = model.reserv.depth.quantity().to('km').magnitude
else:
model.wellbores.injection_reservoir_depth.value = model.wellbores.injection_reservoir_depth.quantity().to('km').magnitude
self.cost_one_production_well.value = calculate_cost_of_one_vertical_well(model, model.reserv.depth.quantity().to('m').magnitude,
self.wellcorrelation.value,
self.Vertical_drilling_cost_per_m.value,
self.per_production_well_cost.Name,
self.production_well_cost_adjustment_factor.value)
if model.wellbores.ninj.value == 0:
self.cost_one_injection_well.value = -1.0
else:
self.cost_one_injection_well.value = calculate_cost_of_one_vertical_well(model,
model.wellbores.injection_reservoir_depth.value * 1000.0,
self.wellcorrelation.value,
self.Vertical_drilling_cost_per_m.value,
self.per_injection_well_cost.Name,
self.injection_well_cost_adjustment_factor.value)
if hasattr(model.wellbores, 'numnonverticalsections') and model.wellbores.numnonverticalsections.Provided:
self.cost_lateral_section.value = calculate_cost_of_non_vertical_section(model, tot_horiz_m,
self.wellcorrelation.value,
self.Nonvertical_drilling_cost_per_m.value,
model.wellbores.numnonverticalsections.value,
self.per_injection_well_cost.Name,
model.wellbores.NonverticalsCased.value,
self.production_well_cost_adjustment_factor.value)
else:
self.cost_lateral_section.value = 0.0
# cost of the well field
# 1.05 for 5% indirect costs
self.Cwell.value = 1.05 * ((self.cost_one_production_well.value * model.wellbores.nprod.value) +
(self.cost_one_injection_well.value * model.wellbores.ninj.value) +
self.cost_lateral_section.value)
# reservoir stimulation costs (M$/injection well). These are calculated whether totalcapcost.Valid = 1
if self.ccstimfixed.Valid:
self.Cstim.value = self.ccstimfixed.value
else:
self.Cstim.value = 1.05 * 1.15 * self.ccstimadjfactor.value * model.wellbores.ninj.value * 1.25 # 1.15 for 15% contingency and 1.05 for 5% indirect costs
# field gathering system costs (M$)
if self.ccgathfixed.Valid:
self.Cgath.value = self.ccgathfixed.value
else:
self.Cgath.value = self.ccgathadjfactor.value * 50 - 6 * np.max(
model.surfaceplant.HeatExtracted.value) * 1000. # (GEOPHIRES v1 correlation)
if model.wellbores.impedancemodelused.value:
pumphp = np.max(model.wellbores.PumpingPower.value) * 1341
numberofpumps = np.ceil(pumphp / 2000) # pump can be maximum 2,000 hp
if numberofpumps == 0:
self.Cpumps = 0.0
else:
pumphpcorrected = pumphp / numberofpumps
self.Cpumps = numberofpumps * 1.5 * (
(1750 * pumphpcorrected ** 0.7) * 3 * pumphpcorrected ** (-0.11))
else:
if model.wellbores.productionwellpumping.value:
prodpumphp = np.max(model.wellbores.PumpingPowerProd.value) / model.wellbores.nprod.value * 1341
Cpumpsprod = model.wellbores.nprod.value * 1.5 * (1750 * prodpumphp ** 0.7 + 5750 *
prodpumphp ** 0.2 + 10000 + np.max(
model.wellbores.pumpdepth.value) * 50 * 3.281) # see page 46 in user's manual assuming rental of rig for 1 day.
else:
Cpumpsprod = 0
injpumphp = np.max(model.wellbores.PumpingPowerInj.value) * 1341
numberofinjpumps = np.ceil(injpumphp / 2000) # pump can be maximum 2,000 hp
if numberofinjpumps == 0:
Cpumpsinj = 0
else:
injpumphpcorrected = injpumphp / numberofinjpumps
Cpumpsinj = numberofinjpumps * 1.5 * (
1750 * injpumphpcorrected ** 0.7) * 3 * injpumphpcorrected ** (-0.11)
self.Cpumps = Cpumpsinj + Cpumpsprod
# Based on GETEM 2016 #1.15 for 15% contingency and 1.12 for 12% indirect costs
self.Cgath.value = 1.15 * self.ccgathadjfactor.value * 1.12 * (
(model.wellbores.nprod.value + model.wellbores.ninj.value) * 750 * 500. + self.Cpumps) / 1E6
# plant costs
if (model.surfaceplant.enduse_option.value == EndUseOptions.HEAT
and model.surfaceplant.plant_type.value not in [PlantType.ABSORPTION_CHILLER, PlantType.HEAT_PUMP, PlantType.DISTRICT_HEATING]): # direct-use
if self.ccplantfixed.Valid:
self.Cplant.value = self.ccplantfixed.value
else:
self.Cplant.value = 1.12 * 1.15 * self.ccplantadjfactor.value * 250E-6 * np.max(
model.surfaceplant.HeatExtracted.value) * 1000. # 1.15 for 15% contingency and 1.12 for 12% indirect costs
# absorption chiller
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.ABSORPTION_CHILLER: # absorption chiller
if self.ccplantfixed.Valid:
self.Cplant.value = self.ccplantfixed.value
else:
# this is for the direct-use part all the way up to the absorption chiller
self.Cplant.value = 1.12 * 1.15 * self.ccplantadjfactor.value * 250E-6 * np.max(
model.surfaceplant.HeatExtracted.value) * 1000. # 1.15 for 15% contingency and 1.12 for 12% indirect costs
if self.chillercapex.value == -1: # no value provided by user, use built-in correlation ($2500/ton)
self.chillercapex.value = 1.12 * 1.15 * np.max(
model.surfaceplant.cooling_produced.value) * 1000 / 3.517 * 2500 / 1e6 # $2,500/ton of cooling. 1.15 for 15% contingency and 1.12 for 12% indirect costs
# now add chiller cost to surface plant cost
self.Cplant.value += self.chillercapex.value
# heat pump
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.HEAT_PUMP:
if self.ccplantfixed.Valid:
self.Cplant.value = self.ccplantfixed.value
else:
# this is for the direct-use part all the way up to the heat pump
self.Cplant.value = 1.12 * 1.15 * self.ccplantadjfactor.value * 250E-6 * np.max(
model.surfaceplant.HeatExtracted.value) * 1000. # 1.15 for 15% contingency and 1.12 for 12% indirect costs
if self.heatpumpcapex.value == -1: # no value provided by user, use built-in correlation ($150/kWth)
self.heatpumpcapex.value = 1.12 * 1.15 * np.max(
model.surfaceplant.HeatProduced.value) * 1000 * 150 / 1e6 # $150/kW. 1.15 for 15% contingency and 1.12 for 12% indirect costs
# now add heat pump cost to surface plant cost
self.Cplant.value += self.heatpumpcapex.value
# district heating
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING:
if self.ccplantfixed.Valid:
self.Cplant.value = self.ccplantfixed.value
else:
self.Cplant.value = 1.12 * 1.15 * self.ccplantadjfactor.value * 250E-6 * np.max(
model.surfaceplant.HeatExtracted.value) * 1000. # 1.15 for 15% contingency and 1.12 for 12% indirect costs
self.peakingboilercost.value = 65 * model.surfaceplant.max_peaking_boiler_demand.value / 1000 # add 65$/KW for peaking boiler
self.Cplant.value += self.peakingboilercost.value # add peaking boiler cost to surface plant cost
else: # all other options have power plant
if model.surfaceplant.plant_type.value == PlantType.SUB_CRITICAL_ORC:
MaxProducedTemperature = np.max(model.surfaceplant.TenteringPP.value)
if MaxProducedTemperature < 150.:
C3 = -1.458333E-3
C2 = 7.6875E-1
C1 = -1.347917E2
C0 = 1.0075E4
CCAPP1 = C3 * MaxProducedTemperature ** 3 + C2 * MaxProducedTemperature ** 2 + C1 * MaxProducedTemperature + C0
else:
CCAPP1 = 2231 - 2 * (MaxProducedTemperature - 150.)
x = np.max(model.surfaceplant.ElectricityProduced.value)
y = np.max(model.surfaceplant.ElectricityProduced.value)
if y == 0.0:
y = 15.0
z = math.pow(y / 15., -0.06)
self.Cplantcorrelation = CCAPP1 * z * x * 1000. / 1E6
elif model.surfaceplant.plant_type.value == PlantType.SUPER_CRITICAL_ORC:
MaxProducedTemperature = np.max(model.surfaceplant.TenteringPP.value)
if MaxProducedTemperature < 150.:
C3 = -1.458333E-3
C2 = 7.6875E-1
C1 = -1.347917E2
C0 = 1.0075E4
CCAPP1 = C3 * MaxProducedTemperature ** 3 + C2 * MaxProducedTemperature ** 2 + C1 * MaxProducedTemperature + C0
else:
CCAPP1 = 2231 - 2 * (MaxProducedTemperature - 150.)
# factor 1.1 to make supercritical 10% more expansive than subcritical
self.Cplantcorrelation = 1.1 * CCAPP1 * math.pow(
np.max(model.surfaceplant.ElectricityProduced.value) / 15., -0.06) * np.max(
model.surfaceplant.ElectricityProduced.value) * 1000. / 1E6
elif model.surfaceplant.plant_type.value == PlantType.SINGLE_FLASH:
if np.max(model.surfaceplant.ElectricityProduced.value) < 10.:
C2 = 4.8472E-2
C1 = -35.2186
C0 = 8.4474E3
D2 = 4.0604E-2
D1 = -29.3817
D0 = 6.9911E3
PLL = 5.
PRL = 10.
elif np.max(model.surfaceplant.ElectricityProduced.value) < 25.:
C2 = 4.0604E-2
C1 = -29.3817
C0 = 6.9911E3
D2 = 3.2773E-2
D1 = -23.5519
D0 = 5.5263E3
PLL = 10.
PRL = 25.
elif np.max(model.surfaceplant.ElectricityProduced.value) < 50.:
C2 = 3.2773E-2
C1 = -23.5519
C0 = 5.5263E3
D2 = 3.4716E-2
D1 = -23.8139
D0 = 5.1787E3
PLL = 25.
PRL = 50.
elif np.max(model.surfaceplant.ElectricityProduced.value) < 75.:
C2 = 3.4716E-2
C1 = -23.8139
C0 = 5.1787E3
D2 = 3.5271E-2
D1 = -24.3962
D0 = 5.1972E3
PLL = 50.
PRL = 75.
else:
C2 = 3.5271E-2
C1 = -24.3962
C0 = 5.1972E3
D2 = 3.3908E-2
D1 = -23.4890
D0 = 5.0238E3
PLL = 75.
PRL = 100.
maxProdTemp = np.max(model.surfaceplant.TenteringPP.value)
CCAPPLL = C2 * maxProdTemp ** 2 + C1 * maxProdTemp + C0
CCAPPRL = D2 * maxProdTemp ** 2 + D1 * maxProdTemp + D0
b = math.log(CCAPPRL / CCAPPLL) / math.log(PRL / PLL)
a = CCAPPRL / PRL ** b
# factor 0.75 to make double flash 25% more expansive than single flash
self.Cplantcorrelation = (0.8 * a * math.pow(np.max(model.surfaceplant.ElectricityProduced.value), b) *
np.max(model.surfaceplant.ElectricityProduced.value) * 1000. / 1E6)
elif model.surfaceplant.plant_type.value == PlantType.DOUBLE_FLASH:
if np.max(model.surfaceplant.ElectricityProduced.value) < 10.:
C2 = 4.8472E-2
C1 = -35.2186
C0 = 8.4474E3
D2 = 4.0604E-2
D1 = -29.3817
D0 = 6.9911E3
PLL = 5.
PRL = 10.
elif np.max(model.surfaceplant.ElectricityProduced.value) < 25.:
C2 = 4.0604E-2
C1 = -29.3817
C0 = 6.9911E3
D2 = 3.2773E-2
D1 = -23.5519
D0 = 5.5263E3
PLL = 10.
PRL = 25.
elif np.max(model.surfaceplant.ElectricityProduced.value) < 50.:
C2 = 3.2773E-2
C1 = -23.5519
C0 = 5.5263E3
D2 = 3.4716E-2
D1 = -23.8139
D0 = 5.1787E3
PLL = 25.
PRL = 50.
elif np.max(model.surfaceplant.ElectricityProduced.value) < 75.:
C2 = 3.4716E-2
C1 = -23.8139
C0 = 5.1787E3
D2 = 3.5271E-2
D1 = -24.3962
D0 = 5.1972E3
PLL = 50.
PRL = 75.
else:
C2 = 3.5271E-2
C1 = -24.3962
C0 = 5.1972E3
D2 = 3.3908E-2
D1 = -23.4890
D0 = 5.0238E3
PLL = 75.
PRL = 100.
maxProdTemp = np.max(model.surfaceplant.TenteringPP.value)
CCAPPLL = C2 * maxProdTemp ** 2 + C1 * maxProdTemp + C0
CCAPPRL = D2 * maxProdTemp ** 2 + D1 * maxProdTemp + D0
b = math.log(CCAPPRL / CCAPPLL) / math.log(PRL / PLL)
a = CCAPPRL / PRL ** b
self.Cplantcorrelation = (a * math.pow(np.max(model.surfaceplant.ElectricityProduced.value), b) *
np.max(model.surfaceplant.ElectricityProduced.value) * 1000. / 1E6)
if self.ccplantfixed.Valid:
self.Cplant.value = self.ccplantfixed.value
self.CAPEX_cost_electricity_plant = self.Cplant.value * self.CAPEX_heat_electricity_plant_ratio.value
self.CAPEX_cost_heat_plant = self.Cplant.value * (1.0 - self.CAPEX_heat_electricity_plant_ratio.value)
else:
# 1.02 to convert cost from 2012 to 2016 #factor 1.15 for 15% contingency and 1.12 for 12% indirect costs. factor 1.10 to convert from 2016 to 2022
self.Cplant.value = 1.12 * 1.15 * self.ccplantadjfactor.value * self.Cplantcorrelation * 1.02 * 1.10
self.CAPEX_cost_electricity_plant = self.Cplant.value
# add direct-use plant cost of co-gen system to Cplant (only of no total Cplant was provided)
if not self.ccplantfixed.Valid: # 1.15 below for contingency and 1.12 for indirect costs
if model.surfaceplant.enduse_option.value in [EndUseOptions.COGENERATION_TOPPING_EXTRA_ELECTRICITY,
EndUseOptions.COGENERATION_TOPPING_EXTRA_HEAT]: # enduse_option = 3: cogen topping cycle
self.CAPEX_cost_heat_plant = 1.12 * 1.15 * self.ccplantadjfactor.value * 250E-6 * np.max(
model.surfaceplant.HeatProduced.value / model.surfaceplant.enduse_efficiency_factor.value) * 1000.
elif model.surfaceplant.enduse_option.value in [EndUseOptions.COGENERATION_BOTTOMING_EXTRA_HEAT,
EndUseOptions.COGENERATION_BOTTOMING_EXTRA_ELECTRICITY]: # enduse_option = 4: cogen bottoming cycle
self.CAPEX_cost_heat_plant = 1.12 * 1.15 * self.ccplantadjfactor.value * 250E-6 * np.max(
model.surfaceplant.HeatProduced.value / model.surfaceplant.enduse_efficiency_factor.value) * 1000.
elif model.surfaceplant.enduse_option.value in [EndUseOptions.COGENERATION_PARALLEL_EXTRA_ELECTRICITY,
EndUseOptions.COGENERATION_PARALLEL_EXTRA_HEAT]: # cogen parallel cycle
self.CAPEX_cost_heat_plant = 1.12 * 1.15 * self.ccplantadjfactor.value * 250E-6 * np.max(
model.surfaceplant.HeatProduced.value / model.surfaceplant.enduse_efficiency_factor.value) * 1000.
self.Cplant.value = self.Cplant.value + self.CAPEX_cost_heat_plant
if not self.CAPEX_heat_electricity_plant_ratio.Provided:
self.CAPEX_heat_electricity_plant_ratio.value = self.CAPEX_cost_electricity_plant/self.Cplant.value
if not self.totalcapcost.Valid:
# exploration costs (same as in Geophires v1.2) (M$)
if self.ccexplfixed.Valid:
self.Cexpl.value = self.ccexplfixed.value
else:
self.Cexpl.value = 1.15 * self.ccexpladjfactor.value * 1.12 * (
1. + self.cost_one_production_well.value * 0.6) # 1.15 for 15% contingency and 1.12 for 12% indirect costs
# Surface Piping Length Costs (M$) #assumed $750k/km
self.Cpiping.value = 750 / 1000 * model.surfaceplant.piping_length.value
# district heating network costs
if model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING: # district heat
if self.dhtotaldistrictnetworkcost.Provided:
self.dhdistrictcost.value = self.dhtotaldistrictnetworkcost.value
elif self.dhpipinglength.Provided:
self.dhdistrictcost.value = self.dhpipinglength.value * self.dhpipingcostrate.value / 1000 # M$
elif self.dhroadlength.Provided: # check if road length is provided to calculate cost
self.dhdistrictcost.value = self.dhroadlength.value * 0.75 * self.dhpipingcostrate.value / 1000 # M$ (assuming 75% of road length is used for district network piping)
else: # calculate district network cost based on population density
if self.dhlandarea.Provided == False:
model.logger.warning("District heating network cost calculated based on default district area")
if self.dhpopulation.Provided:
self.populationdensity.value = self.dhpopulation.value / self.dhlandarea.value
elif model.surfaceplant.dh_number_of_housing_units.Provided:
self.populationdensity.value = model.surfaceplant.dh_number_of_housing_units.value * 2.6 / self.dhlandarea.value # estimate population based on 2.6 number of people per household
else:
model.logger.warning(
"District heating network cost calculated based on default number of people in district")
self.populationdensity.value = self.dhpopulation.value / self.dhlandarea.value
if self.populationdensity.value > 1000:
self.dhpipinglength.value = 7.5 * self.dhlandarea.value # using constant 7.5km of pipe per km^2 when population density is >1500
else:
self.dhpipinglength.value = max(
self.populationdensity.value / 1000 * 7.5 * self.dhlandarea.value,
self.dhlandarea.value) # scale the piping length based on population density, but with a minimum of 1 km of piping per km^2 of area
self.dhdistrictcost.value = self.dhpipingcostrate.value * self.dhpipinglength.value / 1000
else:
self.dhdistrictcost.value = 0
self.CCap.value = self.Cexpl.value + self.Cwell.value + self.Cstim.value + self.Cgath.value + self.Cplant.value + self.Cpiping.value + self.dhdistrictcost.value
else:
self.CCap.value = self.totalcapcost.value
# update the capitol costs, assuming the entire ITC is used to reduce the capitol costs
if self.RITC.Provided:
self.RITCValue.value = self.RITC.value * self.CCap.value
self.CCap.value = self.CCap.value - self.RITCValue.value
# Add in the FlatLicenseEtc, OtherIncentives, & TotalGrant
self.CCap.value = self.CCap.value + self.FlatLicenseEtc.value - self.OtherIncentives.value - self.TotalGrant.value
# O&M costs
# calculate first O&M costs independent of whether oamtotalfixed is provided or not
# additional electricity cost for heat pump as end-use
if model.surfaceplant.plant_type.value == PlantType.HEAT_PUMP: # heat pump:
self.averageannualheatpumpelectricitycost.value = np.average(
model.surfaceplant.heat_pump_electricity_kwh_used.value) * model.surfaceplant.electricity_cost_to_buy.value / 1E6 # M$/year
# district heating peaking fuel annual cost
if model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING: # district heating
self.annualngcost.value = model.surfaceplant.annual_ng_demand.value * self.ngprice.value / 1000 / self.peakingboilerefficiency.value # array with annual O&M cost for peaking fuel
self.averageannualngcost.value = np.average(self.annualngcost.value)
# calculate average annual pumping costs in case no electricity is provided
if model.surfaceplant.plant_type.value in [PlantType.INDUSTRIAL, PlantType.ABSORPTION_CHILLER, PlantType.HEAT_PUMP, PlantType.DISTRICT_HEATING]:
self.averageannualpumpingcosts.value = np.average(model.surfaceplant.PumpingkWh.value) * model.surfaceplant.electricity_cost_to_buy.value / 1E6 # M$/year
if not self.oamtotalfixed.Valid:
# labor cost
if model.surfaceplant.enduse_option.value == EndUseOptions.ELECTRICITY: # electricity
if np.max(model.surfaceplant.ElectricityProduced.value) < 2.5:
self.Claborcorrelation = 236. / 1E3 # M$/year
else:
self.Claborcorrelation = (589. * math.log(
np.max(model.surfaceplant.ElectricityProduced.value)) - 304.) / 1E3 # M$/year
else:
if np.max(model.surfaceplant.HeatExtracted.value) < 2.5 * 5.:
self.Claborcorrelation = 236. / 1E3 # M$/year
else:
self.Claborcorrelation = (589. * math.log(
np.max(model.surfaceplant.HeatExtracted.value) / 5.) - 304.) / 1E3 # M$/year
# * 1.1 to convert from 2012 to 2016$ with BLS employment cost index (for utilities in March)
self.Claborcorrelation = self.Claborcorrelation * 1.1
# plant O&M cost
if self.oamplantfixed.Valid:
self.Coamplant.value = self.oamplantfixed.value
else:
self.Coamplant.value = self.oamplantadjfactor.value * (
1.5 / 100. * self.Cplant.value + 0.75 * self.Claborcorrelation)
# wellfield O&M cost
if self.oamwellfixed.Valid:
self.Coamwell.value = self.oamwellfixed.value
else:
self.Coamwell.value = self.oamwelladjfactor.value * (
1. / 100. * (self.Cwell.value + self.Cgath.value) + 0.25 * self.Claborcorrelation)
# water O&M cost
if self.oamwaterfixed.Valid:
self.Coamwater.value = self.oamwaterfixed.value
else:
# here is assumed 1 l per kg maybe correct with real temp. (M$/year) 925$/ML = 3.5$/1,000 gallon
self.Coamwater.value = self.oamwateradjfactor.value * (model.wellbores.nprod.value *
model.wellbores.prodwellflowrate.value *
model.reserv.waterloss.value * model.surfaceplant.utilization_factor.value *
365. * 24. * 3600. / 1E6 * 925. / 1E6)
# additional O&M cost for absorption chiller if used
if model.surfaceplant.plant_type.value == PlantType.ABSORPTION_CHILLER: # absorption chiller:
if self.chilleropex.value == -1:
self.chilleropex.value = self.chillercapex.value * 2 / 100 # assumed annual O&M for chiller is 2% of investment cost
# correct plant O&M cost as otherwise chiller opex would be counted double (subtract chiller capex from plant cost when calculating Coandmplant)
if self.oamplantfixed.Valid == False:
self.Coamplant.value = self.oamplantadjfactor.value * (
1.5 / 100. * (self.Cplant.value - self.chillercapex.value) + 0.75 * self.Claborcorrelation)
else:
self.chilleropex.value = 0
# district heating O&M cost
if model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING: # district heating
self.annualngcost.value = model.surfaceplant.annual_ng_demand.value * self.ngprice.value / 1000 # array with annual O&M cost for peaking fuel
if self.dhoandmcost.Provided:
self.dhdistrictoandmcost.value = self.dhoandmcost.value # M$/yr
else:
self.dhdistrictoandmcost.value = 0.01 * self.dhdistrictcost.value + 0.02 * sum(
model.surfaceplant.daily_heating_demand.value) * model.surfaceplant.electricity_cost_to_buy.value / 1000 # [M$/year] we assume annual district OPEX equals 1% of district CAPEX and 2% of total heat demand for pumping costs
else:
self.dhdistrictoandmcost.value = 0
self.Coam.value = self.Coamwell.value + self.Coamplant.value + self.Coamwater.value + self.chilleropex.value + self.dhdistrictoandmcost.value # total O&M cost (M$/year)
else:
self.Coam.value = self.oamtotalfixed.value # total O&M cost (M$/year)
if model.wellbores.redrill.value > 0:
# account for well redrilling
self.Coam.value = self.Coam.value + \
(self.Cwell.value + self.Cstim.value) * model.wellbores.redrill.value / model.surfaceplant.plant_lifetime.value
# Add in the AnnualLicenseEtc and TaxRelief
self.Coam.value = self.Coam.value + self.AnnualLicenseEtc.value - self.TaxRelief.value
# partition the OPEX for CHP plants based on the CAPEX ratio
self.OPEX_cost_electricity_plant = self.Coam.value * self.CAPEX_heat_electricity_plant_ratio.value
self.OPEX_cost_heat_plant = self.Coam.value * (1.0 - self.CAPEX_heat_electricity_plant_ratio.value)
# The Reservoir depth measure was arbitrarily changed to meters despite being defined in the docs as kilometers.
# For display consistency sake, we need to convert it back
if model.reserv.depth.value > 500:
model.reserv.depth.value = model.reserv.depth.value / 1000.0
model.reserv.depth.CurrentUnits = LengthUnit.KILOMETERS
# build the PTC price models
self.PTCElecPrice = [0.0] * model.surfaceplant.plant_lifetime.value
self.PTCHeatPrice = [0.0] * model.surfaceplant.plant_lifetime.value
self.PTCCoolingPrice = [0.0] * model.surfaceplant.plant_lifetime.value
self.PTCCarbonPrice = [0.0] * model.surfaceplant.plant_lifetime.value
if self.PTCElec.Provided:
self.PTCElecPrice = BuildPTCModel(model.surfaceplant.plant_lifetime.value,
self.PTCDuration.value, self.PTCElec.value, self.PTCInflationAdjusted.value,
self.RINFL.value)
if self.PTCHeat.Provided:
self.PTCHeatPrice = BuildPTCModel(model.surfaceplant.plant_lifetime.value,
self.PTCDuration.value, self.PTCHeat.value, self.PTCInflationAdjusted.value,
self.RINFL.value)
if self.PTCCooling.Provided:
self.PTCCoolingPrice = BuildPTCModel(model.surfaceplant.plant_lifetime.value,
self.PTCDuration.value,self.PTCCooling.value, self.PTCInflationAdjusted.value,
self.RINFL.value)
# build the price models
self.ElecPrice.value = BuildPricingModel(model.surfaceplant.plant_lifetime.value,
self.ElecStartPrice.value, self.ElecEndPrice.value,
self.ElecEscalationStart.value, self.ElecEscalationRate.value,
self.PTCElecPrice)
self.HeatPrice.value = BuildPricingModel(model.surfaceplant.plant_lifetime.value,
self.HeatStartPrice.value, self.HeatEndPrice.value,
self.HeatEscalationStart.value, self.HeatEscalationRate.value,
self.PTCHeatPrice)
self.CoolingPrice.value = BuildPricingModel(model.surfaceplant.plant_lifetime.value,
self.CoolingStartPrice.value, self.CoolingEndPrice.value,
self.CoolingEscalationStart.value, self.CoolingEscalationRate.value,
self.PTCCoolingPrice)
self.CarbonPrice.value = BuildPricingModel(model.surfaceplant.plant_lifetime.value,
self.CarbonStartPrice.value, self.CarbonEndPrice.value,
self.CarbonEscalationStart.value, self.CarbonEscalationRate.value,
self.PTCCarbonPrice)
# do the additional economic calculations first, if needed, so the summaries below work.
if self.DoAddOnCalculations.value:
model.addeconomics.Calculate(model)
if self.DoSDACGTCalculations.value:
model.sdacgteconomics.Calculate(model)
# Calculate cashflow and cumulative cash flow
total_duration = model.surfaceplant.plant_lifetime.value + model.surfaceplant.construction_years.value
self.ElecRevenue.value = [0.0] * total_duration
self.ElecCummRevenue.value = [0.0] * total_duration
self.HeatRevenue.value = [0.0] * total_duration
self.HeatCummRevenue.value = [0.0] * total_duration
self.CoolingRevenue.value = [0.0] * total_duration
self.CoolingCummRevenue.value = [0.0] * total_duration
self.CarbonRevenue.value = [0.0] * total_duration
self.CarbonCummCashFlow.value = [0.0] * total_duration
self.TotalRevenue.value = [0.0] * total_duration
self.TotalCummRevenue.value = [0.0] * total_duration
self.CarbonThatWouldHaveBeenProducedTotal.value = 0.0
# Based on the style of the project, calculate the revenue & cumulative revenue
if model.surfaceplant.enduse_option.value == EndUseOptions.ELECTRICITY:
self.ElecRevenue.value, self.ElecCummRevenue.value = CalculateRevenue(
model.surfaceplant.plant_lifetime.value, model.surfaceplant.construction_years.value,
model.surfaceplant.NetkWhProduced.value, self.ElecPrice.value)
self.TotalRevenue.value = self.ElecRevenue.value
#self.TotalCummRevenue.value = self.ElecCummRevenue.value
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value not in [PlantType.ABSORPTION_CHILLER]:
self.HeatRevenue.value, self.HeatCummRevenue.value = CalculateRevenue(
model.surfaceplant.plant_lifetime.value, model.surfaceplant.construction_years.value,
model.surfaceplant.HeatkWhProduced.value, self.HeatPrice.value)
self.TotalRevenue.value = self.HeatRevenue.value
#self.TotalCummRevenue.value = self.HeatCummRevenue.value
elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value in [PlantType.ABSORPTION_CHILLER]:
self.CoolingRevenue.value, self.CoolingCummRevenue.value = CalculateRevenue(
model.surfaceplant.plant_lifetime.value, model.surfaceplant.construction_years.value,
model.surfaceplant.cooling_kWh_Produced.value, self.CoolingPrice.value)
self.TotalRevenue.value = self.CoolingRevenue.value
#self.TotalCummRevenue.value = self.CoolingCummRevenue.value
elif model.surfaceplant.enduse_option.value in [EndUseOptions.COGENERATION_TOPPING_EXTRA_HEAT,
EndUseOptions.COGENERATION_TOPPING_EXTRA_ELECTRICITY,
EndUseOptions.COGENERATION_BOTTOMING_EXTRA_ELECTRICITY,
EndUseOptions.COGENERATION_BOTTOMING_EXTRA_HEAT,
EndUseOptions.COGENERATION_PARALLEL_EXTRA_HEAT,
EndUseOptions.COGENERATION_PARALLEL_EXTRA_ELECTRICITY]: # co-gen
# else:
self.ElecRevenue.value, self.ElecCummRevenue.value = CalculateRevenue(
model.surfaceplant.plant_lifetime.value, model.surfaceplant.construction_years.value,
model.surfaceplant.NetkWhProduced.value, self.ElecPrice.value)
self.HeatRevenue.value, self.HeatCummRevenue.value = CalculateRevenue(
model.surfaceplant.plant_lifetime.value, model.surfaceplant.construction_years.value,
model.surfaceplant.HeatkWhProduced.value, self.HeatPrice.value)
for i in range(0, model.surfaceplant.plant_lifetime.value + model.surfaceplant.construction_years.value, 1):
self.TotalRevenue.value[i] = self.ElecRevenue.value[i] + self.HeatRevenue.value[i]
#if i > 0:
# self.TotalCummRevenue.value[i] = self.TotalCummRevenue.value[i - 1] + self.TotalRevenue.value[i]
if self.DoCarbonCalculations.value:
self.CarbonRevenue.value, self.CarbonCummCashFlow.value, self.CarbonThatWouldHaveBeenProducedAnnually.value, \
self.CarbonThatWouldHaveBeenProducedTotal.value = CalculateCarbonRevenue(model,
model.surfaceplant.plant_lifetime.value, model.surfaceplant.construction_years.value,
self.CarbonPrice.value, self.GridCO2Intensity.value, self.NaturalGasCO2Intensity.value,
model.surfaceplant.NetkWhProduced.value, model.surfaceplant.HeatkWhProduced.value)
for i in range(model.surfaceplant.construction_years.value, model.surfaceplant.plant_lifetime.value + model.surfaceplant.construction_years.value, 1):
self.TotalRevenue.value[i] = self.TotalRevenue.value[i] + self.CarbonRevenue.value[i]
#self.TotalCummRevenue.value[i] = self.TotalCummRevenue.value[i] + self.CarbonCummCashFlow.value[i]
# for the sake of display, insert zeros at the beginning of the pricing arrays
for i in range(0, model.surfaceplant.construction_years.value, 1):
self.ElecPrice.value.insert(0, 0.0)
self.HeatPrice.value.insert(0, 0.0)
self.CoolingPrice.value.insert(0, 0.0)
self.CarbonPrice.value.insert(0, 0.0)
# Insert the cost of construction into the front of the array that will be used to calculate NPV
# the convention is that the upfront CAPEX is negative
# This is the same for all projects
ProjectCAPEXPerConstructionYear = self.CCap.value / model.surfaceplant.construction_years.value
for i in range(0, model.surfaceplant.construction_years.value, 1):
self.TotalRevenue.value[i] = -1.0 * ProjectCAPEXPerConstructionYear
self.TotalCummRevenue.value[i] = -1.0 * ProjectCAPEXPerConstructionYear
# self.TotalRevenue.value, self.TotalCummRevenue.value = CalculateTotalRevenue(
# model.surfaceplant.plant_lifetime.value, model.surfaceplant.construction_years.value, self.CCap.value,
# self.Coam.value, self.TotalRevenue.value, self.TotalCummRevenue.value)
# Do a one-time calculation that accounts for OPEX - no OPEX in the first year.
for i in range(model.surfaceplant.construction_years.value,
model.surfaceplant.plant_lifetime.value + model.surfaceplant.construction_years.value, 1):
self.TotalRevenue.value[i] = self.TotalRevenue.value[i] - self.Coam.value
# Now do a one-time calculation that calculates the cumulative cash flow after everything else has been accounted for
for i in range(1, model.surfaceplant.plant_lifetime.value + model.surfaceplant.construction_years.value, 1):
self.TotalCummRevenue.value[i] = self.TotalCummRevenue.value[i-1] + self.TotalRevenue.value[i]
# Calculate more financial values using numpy financials
self.ProjectNPV.value, self.ProjectIRR.value, self.ProjectVIR.value, self.ProjectMOIC.value = \
CalculateFinancialPerformance(model.surfaceplant.plant_lifetime.value, self.FixedInternalRate.value,
self.TotalRevenue.value, self.TotalCummRevenue.value, self.CCap.value,
self.Coam.value)
# Calculate the project payback period
self.ProjectPaybackPeriod.value = 0.0 # start by assuming the project never pays back
for i in range(0, len(self.TotalCummRevenue.value), 1):
# find out when the cumm cashflow goes from negative to positive
if self.TotalCummRevenue.value[i] > 0 >= self.TotalCummRevenue.value[i - 1]:
# we just crossed the threshold into positive project cummcashflow, so we can calculate payback period
dFullDiff = self.TotalCummRevenue.value[i] + math.fabs(self.TotalCummRevenue.value[(i - 1)])
dPerc = math.fabs(self.TotalCummRevenue.value[(i - 1)]) / dFullDiff
self.ProjectPaybackPeriod.value = i + dPerc
# Calculate LCOE/LCOH
self.LCOE.value, self.LCOH.value, self.LCOC.value = CalculateLCOELCOHLCOC(self, model)
# https://github.com/NREL/GEOPHIRES-X/issues/232
self.jobs_created.value = round(
np.average(model.surfaceplant.ElectricityProduced.quantity().to(
'MW').magnitude * self.jobs_created_per_MW_electricity.value))
model.logger.info(f'complete {__class__!s}: {sys._getframe().f_code.co_name}')
def __str__(self):
return "Economics"