@@ -2416,109 +2416,7 @@ def Calculate(self, model: Model) -> None:
2416
2416
self .calculate_field_gathering_costs (model )
2417
2417
self .calculate_plant_costs (model )
2418
2418
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 )
2522
2420
2523
2421
# The Reservoir depth measure was arbitrarily changed to meters despite being defined in the docs as kilometers.
2524
2422
# For display consistency sake, we need to convert it back
@@ -2565,6 +2463,7 @@ def Calculate(self, model: Model) -> None:
2565
2463
# do the additional economic calculations first, if needed, so the summaries below work.
2566
2464
if self .DoAddOnCalculations .value :
2567
2465
model .addeconomics .Calculate (model )
2466
+
2568
2467
if self .DoSDACGTCalculations .value :
2569
2468
model .sdacgteconomics .Calculate (model )
2570
2469
@@ -3095,6 +2994,111 @@ def calculate_total_capital_costs(self, model):
3095
2994
# Add in the FlatLicenseEtc, OtherIncentives, & TotalGrant
3096
2995
self .CCap .value = self .CCap .value + self .FlatLicenseEtc .value - self .OtherIncentives .value - self .TotalGrant .value
3097
2996
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
+
3098
3102
def calculate_cashflow (self , model : Model ) -> None :
3099
3103
"""
3100
3104
Calculate cashflow and cumulative cash flow
@@ -3220,3 +3224,4 @@ def __str__(self):
3220
3224
return "Economics"
3221
3225
3222
3226
3227
+
0 commit comments