Source code for geophires_x.SUTRAEconomics

import sys
import os
import numpy as np
import geophires_x.Model as Model
import geophires_x.Economics as Economics
from geophires_x.OptionList import WellDrillingCostCorrelation, EconomicModel
from geophires_x.Parameter import intParameter, floatParameter, OutputParameter, ReadParameter, boolParameter
from geophires_x.Units import *


[docs] class SUTRAEconomics(Economics.Economics): """ Class to support the default economic calculations in GEOPHIRES """ def __init__(self, model: Model): model.logger.info(f'Init {str(__class__)}: {sys._getframe().f_code.co_name}') super().__init__(model) self.LCOH = self.OutputParameterDict[self.LCOH.Name] = OutputParameter( "Heat Sale Price Model", display_name='Direct-Use heat breakeven price (LCOH)', value=[0.025], UnitType=Units.ENERGYCOST, PreferredUnits=EnergyCostUnit.CENTSSPERKWH, CurrentUnits=EnergyCostUnit.CENTSSPERKWH, ) # local variable initialization self.Cpumps = 0.0 self.InputFile = "" self.C1well = 0.0 sclass = str(__class__).replace("<class \'", "") self.MyClass = sclass.replace("\'>", "") self.MyPath = os.path.abspath(__file__) self.Coam = self.OutputParameterDict[self.Coam.Name] = OutputParameter( Name="Total O&M Cost", value=-999.9, UnitType=Units.CURRENCYFREQUENCY, PreferredUnits=CurrencyFrequencyUnit.KDOLLARSPERYEAR, CurrentUnits=CurrencyFrequencyUnit.KDOLLARSPERYEAR, ) self.annualpumpingcosts = self.OutputParameterDict[self.annualpumpingcosts.Name] = OutputParameter( Name="Annual Pumping Costs", value=-0.0, UnitType=Units.CURRENCYFREQUENCY, PreferredUnits=CurrencyFrequencyUnit.KDOLLARSPERYEAR, CurrentUnits=CurrencyFrequencyUnit.KDOLLARSPERYEAR, ) self.annualngcost = self.OutputParameterDict[self.annualngcost.Name] = OutputParameter( Name="Annual Peaking Fuel Cost", value=0, UnitType=Units.CURRENCYFREQUENCY, PreferredUnits=CurrencyFrequencyUnit.KDOLLARSPERYEAR, CurrentUnits=CurrencyFrequencyUnit.KDOLLARSPERYEAR, ) 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 that need to be taken care of after a value has been read in and checked. This is called from the main Model class. It is not called from the __init__ function because the user may not want to read in the parameters from the input file, but may want to set them in the user interface. :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 {str(__class__)}: {sys._getframe().f_code.co_name}') # 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. 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) else: model.logger.info('No parameters read because no content provided') self.sync_interest_rate(model) model.logger.info(f'Complete {__class__!s}: {sys._getframe().f_code.co_name}')
[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. :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 {str(__class__)}: {sys._getframe().f_code.co_name}') # 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! # CAPEX # Drilling self.C1well = 0 if self.per_production_well_cost.Valid: self.C1well = self.ccwellfixed.value self.Cwell.value = self.C1well * (model.wellbores.nprod.value + model.wellbores.ninj.value) else: if model.reserv.depth.value > 7000.0 or model.reserv.depth.value < 500: print('Warning: simple user-specified cost per meter used for drilling depth < 500 or > 7000 m') model.logger.warning( 'Warning: simple user-specified cost per meter used for drilling depth < 500 or > 7000 m' ) self.C1well = self.wellcorrelation.value.calculate_cost_MUSD(model.reserv.depth.value) self.C1well = self.C1well * self.production_well_cost_adjustment_factor.value self.Cwell.value = self.C1well * (model.wellbores.nprod.value + model.wellbores.ninj.value) # Boiler self.peakingboilercost.value = ( self.peaking_boiler_cost_per_kW.quantity().to('USD/kW').magnitude * model.surfaceplant.max_peaking_boiler_demand.value / self.peakingboilerefficiency.value / 1000 ) # Circulation Pump pumphp = np.max(model.wellbores.PumpingPower.value) * 1.341 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)) / 1e6 # Total CAPEX ($M) self.CCap.value = self.Cwell.value + self.peakingboilercost.value + self.Cpumps # OPEX # Pumping self.annualpumpingcosts.value = ( model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1e3 ) # Natural Gas self.annualngcost.value = ( model.surfaceplant.AnnualAuxiliaryHeatProduced.value * self.ngprice.value / self.peakingboilerefficiency.value * 1e3 ) # Price for the heat injected currently not considered # Total O&M cost ($K/year) self.Coam.value = self.annualpumpingcosts.value + self.annualngcost.value # LCOH discountvector = 1.0 / np.power( 1 + self.discountrate.value, np.linspace(0, model.surfaceplant.plant_lifetime.value - 1, model.surfaceplant.plant_lifetime.value), ) self.LCOH.value = ( ((1 + self.inflrateconstruction.value) * self.CCap.value + np.sum(self.Coam.value * discountvector)) / np.sum(model.surfaceplant.AnnualTotalHeatProduced.value * 1e6 * discountvector) * 1e8 ) # cents/kWh self._calculate_derived_outputs(model) model.logger.info(f'complete {str(__class__)}: {sys._getframe().f_code.co_name}')
def __str__(self): return "Economics"