Skip to content

Commit 1c90e3a

Browse files
Break out Economics.calculate_operating_and_maintenance_costs
1 parent 349b50d commit 1c90e3a

File tree

1 file changed

+108
-103
lines changed

1 file changed

+108
-103
lines changed

src/geophires_x/Economics.py

Lines changed: 108 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -2416,109 +2416,7 @@ def Calculate(self, model: Model) -> None:
24162416
self.calculate_field_gathering_costs(model)
24172417
self.calculate_plant_costs(model)
24182418
self.calculate_total_capital_costs(model)
2419-
2420-
# O&M costs
2421-
# calculate first O&M costs independent of whether oamtotalfixed is provided or not
2422-
# additional electricity cost for heat pump as end-use
2423-
if model.surfaceplant.plant_type.value == PlantType.HEAT_PUMP: # heat pump:
2424-
self.averageannualheatpumpelectricitycost.value = np.average(
2425-
model.surfaceplant.heat_pump_electricity_kwh_used.value) * model.surfaceplant.electricity_cost_to_buy.value / 1E6 # M$/year
2426-
2427-
# district heating peaking fuel annual cost
2428-
if model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING: # district heating
2429-
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
2430-
self.averageannualngcost.value = np.average(self.annualngcost.value)
2431-
2432-
# calculate average annual pumping costs in case no electricity is provided
2433-
if model.surfaceplant.plant_type.value in [PlantType.INDUSTRIAL, PlantType.ABSORPTION_CHILLER, PlantType.HEAT_PUMP, PlantType.DISTRICT_HEATING]:
2434-
self.averageannualpumpingcosts.value = np.average(model.surfaceplant.PumpingkWh.value) * model.surfaceplant.electricity_cost_to_buy.value / 1E6 # M$/year
2435-
2436-
if not self.oamtotalfixed.Valid:
2437-
# labor cost
2438-
if model.surfaceplant.enduse_option.value == EndUseOptions.ELECTRICITY: # electricity
2439-
if np.max(model.surfaceplant.ElectricityProduced.value) < 2.5:
2440-
self.Claborcorrelation = 236. / 1E3 # M$/year
2441-
else:
2442-
self.Claborcorrelation = (589. * math.log(
2443-
np.max(model.surfaceplant.ElectricityProduced.value)) - 304.) / 1E3 # M$/year
2444-
else:
2445-
if np.max(model.surfaceplant.HeatExtracted.value) < 2.5 * 5.:
2446-
self.Claborcorrelation = 236. / 1E3 # M$/year
2447-
else:
2448-
self.Claborcorrelation = (589. * math.log(
2449-
np.max(model.surfaceplant.HeatExtracted.value) / 5.) - 304.) / 1E3 # M$/year
2450-
# * 1.1 to convert from 2012 to 2016$ with BLS employment cost index (for utilities in March)
2451-
self.Claborcorrelation = self.Claborcorrelation * 1.1
2452-
2453-
# plant O&M cost
2454-
if self.oamplantfixed.Valid:
2455-
self.Coamplant.value = self.oamplantfixed.value
2456-
else:
2457-
self.Coamplant.value = self.oamplantadjfactor.value * (
2458-
1.5 / 100. * self.Cplant.value + 0.75 * self.Claborcorrelation)
2459-
2460-
# wellfield O&M cost
2461-
if self.oamwellfixed.Valid:
2462-
self.Coamwell.value = self.oamwellfixed.value
2463-
else:
2464-
self.Coamwell.value = self.oamwelladjfactor.value * (
2465-
1. / 100. * (self.Cwell.value + self.Cgath.value) + 0.25 * self.Claborcorrelation)
2466-
2467-
# water O&M cost
2468-
if self.oamwaterfixed.Valid:
2469-
self.Coamwater.value = self.oamwaterfixed.value
2470-
else:
2471-
# here is assumed 1 l per kg maybe correct with real temp. (M$/year) 925$/ML = 3.5$/1,000 gallon
2472-
# TODO parameterize
2473-
self.Coamwater.value = self.oamwateradjfactor.value * (model.wellbores.nprod.value *
2474-
model.wellbores.prodwellflowrate.value *
2475-
model.reserv.waterloss.value * model.surfaceplant.utilization_factor.value *
2476-
365. * 24. * 3600. / 1E6 * 925. / 1E6)
2477-
2478-
# additional O&M cost for absorption chiller if used
2479-
if model.surfaceplant.plant_type.value == PlantType.ABSORPTION_CHILLER: # absorption chiller:
2480-
if self.chilleropex.value == -1:
2481-
self.chilleropex.value = self.chillercapex.value * 2 / 100 # assumed annual O&M for chiller is 2% of investment cost
2482-
2483-
# correct plant O&M cost as otherwise chiller opex would be counted double (subtract chiller capex from plant cost when calculating Coandmplant)
2484-
if self.oamplantfixed.Valid == False:
2485-
self.Coamplant.value = self.oamplantadjfactor.value * (
2486-
1.5 / 100. * (self.Cplant.value - self.chillercapex.value) + 0.75 * self.Claborcorrelation)
2487-
2488-
else:
2489-
self.chilleropex.value = 0
2490-
2491-
# district heating O&M cost
2492-
if model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING: # district heating
2493-
self.annualngcost.value = model.surfaceplant.annual_ng_demand.value * self.ngprice.value / 1000 # array with annual O&M cost for peaking fuel
2494-
2495-
if self.dhoandmcost.Provided:
2496-
self.dhdistrictoandmcost.value = self.dhoandmcost.value # M$/yr
2497-
else:
2498-
self.dhdistrictoandmcost.value = 0.01 * self.dhdistrictcost.value + 0.02 * sum(
2499-
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
2500-
2501-
else:
2502-
self.dhdistrictoandmcost.value = 0
2503-
2504-
self.Coam.value = self.Coamwell.value + self.Coamplant.value + self.Coamwater.value + self.chilleropex.value + self.dhdistrictoandmcost.value # total O&M cost (M$/year)
2505-
2506-
else:
2507-
self.Coam.value = self.oamtotalfixed.value # total O&M cost (M$/year)
2508-
2509-
if model.wellbores.redrill.value > 0:
2510-
# account for well redrilling
2511-
redrilling_costs: PlainQuantity = self.calculate_redrilling_costs(model)
2512-
self.redrilling_annual_cost.value = redrilling_costs.to(self.redrilling_annual_cost.CurrentUnits).magnitude
2513-
self.Coam.value += redrilling_costs.to(self.Coam.CurrentUnits).magnitude
2514-
2515-
2516-
# Add in the AnnualLicenseEtc and TaxRelief
2517-
self.Coam.value = self.Coam.value + self.AnnualLicenseEtc.value - self.TaxRelief.value
2518-
2519-
# partition the OPEX for CHP plants based on the CAPEX ratio
2520-
self.OPEX_cost_electricity_plant = self.Coam.value * self.CAPEX_heat_electricity_plant_ratio.value
2521-
self.OPEX_cost_heat_plant = self.Coam.value * (1.0 - self.CAPEX_heat_electricity_plant_ratio.value)
2419+
self.calculate_operating_and_maintenance_costs(model)
25222420

25232421
# The Reservoir depth measure was arbitrarily changed to meters despite being defined in the docs as kilometers.
25242422
# For display consistency sake, we need to convert it back
@@ -2565,6 +2463,7 @@ def Calculate(self, model: Model) -> None:
25652463
# do the additional economic calculations first, if needed, so the summaries below work.
25662464
if self.DoAddOnCalculations.value:
25672465
model.addeconomics.Calculate(model)
2466+
25682467
if self.DoSDACGTCalculations.value:
25692468
model.sdacgteconomics.Calculate(model)
25702469

@@ -3095,6 +2994,111 @@ def calculate_total_capital_costs(self, model):
30952994
# Add in the FlatLicenseEtc, OtherIncentives, & TotalGrant
30962995
self.CCap.value = self.CCap.value + self.FlatLicenseEtc.value - self.OtherIncentives.value - self.TotalGrant.value
30972996

2997+
def calculate_operating_and_maintenance_costs(self, model):
2998+
# O&M costs
2999+
# calculate first O&M costs independent of whether oamtotalfixed is provided or not
3000+
# additional electricity cost for heat pump as end-use
3001+
if model.surfaceplant.plant_type.value == PlantType.HEAT_PUMP: # heat pump:
3002+
self.averageannualheatpumpelectricitycost.value = np.average(
3003+
model.surfaceplant.heat_pump_electricity_kwh_used.value) * model.surfaceplant.electricity_cost_to_buy.value / 1E6 # M$/year
3004+
3005+
# district heating peaking fuel annual cost
3006+
if model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING: # district heating
3007+
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
3008+
self.averageannualngcost.value = np.average(self.annualngcost.value)
3009+
3010+
# calculate average annual pumping costs in case no electricity is provided
3011+
if model.surfaceplant.plant_type.value in [PlantType.INDUSTRIAL, PlantType.ABSORPTION_CHILLER,
3012+
PlantType.HEAT_PUMP, PlantType.DISTRICT_HEATING]:
3013+
self.averageannualpumpingcosts.value = np.average(
3014+
model.surfaceplant.PumpingkWh.value) * model.surfaceplant.electricity_cost_to_buy.value / 1E6 # M$/year
3015+
3016+
if not self.oamtotalfixed.Valid:
3017+
# labor cost
3018+
if model.surfaceplant.enduse_option.value == EndUseOptions.ELECTRICITY: # electricity
3019+
if np.max(model.surfaceplant.ElectricityProduced.value) < 2.5:
3020+
self.Claborcorrelation = 236. / 1E3 # M$/year
3021+
else:
3022+
self.Claborcorrelation = (589. * math.log(
3023+
np.max(model.surfaceplant.ElectricityProduced.value)) - 304.) / 1E3 # M$/year
3024+
else:
3025+
if np.max(model.surfaceplant.HeatExtracted.value) < 2.5 * 5.:
3026+
self.Claborcorrelation = 236. / 1E3 # M$/year
3027+
else:
3028+
self.Claborcorrelation = (589. * math.log(
3029+
np.max(model.surfaceplant.HeatExtracted.value) / 5.) - 304.) / 1E3 # M$/year
3030+
# * 1.1 to convert from 2012 to 2016$ with BLS employment cost index (for utilities in March)
3031+
self.Claborcorrelation = self.Claborcorrelation * 1.1
3032+
3033+
# plant O&M cost
3034+
if self.oamplantfixed.Valid:
3035+
self.Coamplant.value = self.oamplantfixed.value
3036+
else:
3037+
self.Coamplant.value = self.oamplantadjfactor.value * (
3038+
1.5 / 100. * self.Cplant.value + 0.75 * self.Claborcorrelation)
3039+
3040+
# wellfield O&M cost
3041+
if self.oamwellfixed.Valid:
3042+
self.Coamwell.value = self.oamwellfixed.value
3043+
else:
3044+
self.Coamwell.value = self.oamwelladjfactor.value * (
3045+
1. / 100. * (self.Cwell.value + self.Cgath.value) + 0.25 * self.Claborcorrelation)
3046+
3047+
# water O&M cost
3048+
if self.oamwaterfixed.Valid:
3049+
self.Coamwater.value = self.oamwaterfixed.value
3050+
else:
3051+
# here is assumed 1 l per kg maybe correct with real temp. (M$/year) 925$/ML = 3.5$/1,000 gallon
3052+
# TODO parameterize
3053+
self.Coamwater.value = self.oamwateradjfactor.value * (model.wellbores.nprod.value *
3054+
model.wellbores.prodwellflowrate.value *
3055+
model.reserv.waterloss.value * model.surfaceplant.utilization_factor.value *
3056+
365. * 24. * 3600. / 1E6 * 925. / 1E6)
3057+
3058+
# additional O&M cost for absorption chiller if used
3059+
if model.surfaceplant.plant_type.value == PlantType.ABSORPTION_CHILLER: # absorption chiller:
3060+
if self.chilleropex.value == -1:
3061+
self.chilleropex.value = self.chillercapex.value * 2 / 100 # assumed annual O&M for chiller is 2% of investment cost
3062+
3063+
# correct plant O&M cost as otherwise chiller opex would be counted double (subtract chiller capex from plant cost when calculating Coandmplant)
3064+
if self.oamplantfixed.Valid == False:
3065+
self.Coamplant.value = self.oamplantadjfactor.value * (
3066+
1.5 / 100. * (self.Cplant.value - self.chillercapex.value) + 0.75 * self.Claborcorrelation)
3067+
3068+
else:
3069+
self.chilleropex.value = 0
3070+
3071+
# district heating O&M cost
3072+
if model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING: # district heating
3073+
self.annualngcost.value = model.surfaceplant.annual_ng_demand.value * self.ngprice.value / 1000 # array with annual O&M cost for peaking fuel
3074+
3075+
if self.dhoandmcost.Provided:
3076+
self.dhdistrictoandmcost.value = self.dhoandmcost.value # M$/yr
3077+
else:
3078+
self.dhdistrictoandmcost.value = 0.01 * self.dhdistrictcost.value + 0.02 * sum(
3079+
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
3080+
3081+
else:
3082+
self.dhdistrictoandmcost.value = 0
3083+
3084+
self.Coam.value = self.Coamwell.value + self.Coamplant.value + self.Coamwater.value + self.chilleropex.value + self.dhdistrictoandmcost.value # total O&M cost (M$/year)
3085+
3086+
else:
3087+
self.Coam.value = self.oamtotalfixed.value # total O&M cost (M$/year)
3088+
3089+
if model.wellbores.redrill.value > 0:
3090+
# account for well redrilling
3091+
redrilling_costs: PlainQuantity = self.calculate_redrilling_costs(model)
3092+
self.redrilling_annual_cost.value = redrilling_costs.to(self.redrilling_annual_cost.CurrentUnits).magnitude
3093+
self.Coam.value += redrilling_costs.to(self.Coam.CurrentUnits).magnitude
3094+
3095+
# Add in the AnnualLicenseEtc and TaxRelief
3096+
self.Coam.value = self.Coam.value + self.AnnualLicenseEtc.value - self.TaxRelief.value
3097+
3098+
# partition the OPEX for CHP plants based on the CAPEX ratio
3099+
self.OPEX_cost_electricity_plant = self.Coam.value * self.CAPEX_heat_electricity_plant_ratio.value
3100+
self.OPEX_cost_heat_plant = self.Coam.value * (1.0 - self.CAPEX_heat_electricity_plant_ratio.value)
3101+
30983102
def calculate_cashflow(self, model: Model) -> None:
30993103
"""
31003104
Calculate cashflow and cumulative cash flow
@@ -3220,3 +3224,4 @@ def __str__(self):
32203224
return "Economics"
32213225

32223226

3227+

0 commit comments

Comments
 (0)