Skip to content

Redundant class cleanup #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
329 changes: 2 additions & 327 deletions src/geophires_x/SBTEconomics.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,67 +101,7 @@ def __init__(self, model: Model):
sclass = str(__class__).replace("<class \'", "")
self.MyClass = sclass.replace("\'>", "")
self.MyPath = os.path.abspath(__file__)
#self.Electricity_rate = None
#self.Discount_rate = None
#self.error = 0
#self.AverageOPEX_Plant = 0
#self.OPEX_Plant = 0
#self.TotalCAPEX = 0
#self.CAPEX_Surface_Plant = 0
#self.CAPEX_Drilling = 0

# 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 the 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.
# NB: inputs we already have ("already have it") need to be set at ReadParameter time so values
# are set at the last possible time

"""
self.O_and_M_cost_plant = self.ParameterDict[self.O_and_M_cost_plant.Name] = floatParameter(
"Operation & Maintenance Cost of Surface Plant",
DefaultValue=0.015,
Min=0.0,
Max=0.2,
UnitType=Units.PERCENT,
PreferredUnits=PercentUnit.TENTH,
CurrentUnits=PercentUnit.TENTH,
Required=True,
ErrMessage="assume default Operation & Maintenance cost of surface plant expressed as fraction of total surface plant capital cost (0.015)"
)
self.Direct_use_heat_cost_per_kWth = self.ParameterDict[
self.Direct_use_heat_cost_per_kWth.Name] = floatParameter(
"Capital Cost for Surface Plant for Direct-use System",
DefaultValue=100.0,
Min=0.0,
Max=10000.0,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKW,
CurrentUnits=EnergyCostUnit.DOLLARSPERKW,
Required=False,
ErrMessage="assume default Capital cost for surface plant for direct-use system (100 $/kWth)"
)
self.Power_plant_cost_per_kWe = self.ParameterDict[self.Power_plant_cost_per_kWe.Name] = floatParameter(
"Capital Cost for Power Plant for Electricity Generation",
DefaultValue=3000.0,
Min=0.0,
Max=10000.0,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKW,
CurrentUnits=EnergyCostUnit.DOLLARSPERKW,
Required=True,
ErrMessage="assume default Power plant capital cost per kWe (3000 USD/kWe)"
)

# results are stored here and in the parent ProducedTemperature array

"""
model.logger.info(f'complete {__class__!s}: {sys._getframe().f_code.co_name}')

def __str__(self):
Expand All @@ -183,10 +123,6 @@ def read_parameters(self, model: Model) -> None:
# because the call to the super.readparameters will set all the variables,
# including the ones that are specific to this class

# inputs we already have - needs to be set at ReadParameter time so values set at the latest possible time
# self.Discount_rate = model.economics.discountrate.value # same units are GEOPHIRES
# self.Electricity_rate = model.surfaceplant.electricity_cost_to_buy.value # same units are GEOPHIRES

model.logger.info(f'complete {__class__!s}: {sys._getframe().f_code.co_name}')

def Calculate(self, model: Model) -> None:
Expand Down Expand Up @@ -280,269 +216,8 @@ def Calculate(self, model: Model) -> None:
self.cost_lateral_section.value + self.cost_to_junction_section.value)

self.Cstim.value = self.calculate_stimulation_costs(model).to(self.Cstim.CurrentUnits).magnitude

# 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
self.Cgath.value = 1.15 * self.ccgathadjfactor.value * self._indirect_cost_factor * (
(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 = self._indirect_cost_factor * 1.15 * self.ccplantadjfactor.value * 250E-6 * np.max(
model.surfaceplant.HeatExtracted.value) * 1000. # 1.15 for 15% contingency

# 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 = self._indirect_cost_factor * 1.15 * self.ccplantadjfactor.value * 250E-6 * np.max(
model.surfaceplant.HeatExtracted.value) * 1000. # 1.15 for 15% contingency
if self.chillercapex.value == -1: # no value provided by user, use built-in correlation ($2500/ton)
self.chillercapex.value = self._indirect_cost_factor * 1.15 * np.max(
model.surfaceplant.cooling_produced.value) * 1000 / 3.517 * 2500 / 1e6 # $2,500/ton of cooling. 1.15 for 15% contingency

# 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 = self._indirect_cost_factor * 1.15 * self.ccplantadjfactor.value * 250E-6 * np.max(
model.surfaceplant.HeatExtracted.value) * 1000. # 1.15 for 15% contingency
if self.heatpumpcapex.value == -1: # no value provided by user, use built-in correlation ($150/kWth)
self.heatpumpcapex.value = self._indirect_cost_factor * 1.15 * np.max(
model.surfaceplant.HeatProduced.value) * 1000 * 150 / 1e6 # $150/kW. 1.15 for 15% contingency

# 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 = self._indirect_cost_factor * 1.15 * self.ccplantadjfactor.value * 250E-6 * np.max(
model.surfaceplant.HeatExtracted.value) * 1000. # 1.15 for 15% contingency

self.peakingboilercost.value = (self.peaking_boiler_cost_per_kW.quantity()
.to('USD / kilowatt').magnitude
* model.surfaceplant.max_peaking_boiler_demand.value
/ 1000)

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 factor 1.10 to convert from 2016 to 2022
self.Cplant.value = self._indirect_cost_factor * 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
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 = self._indirect_cost_factor * 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 = self._indirect_cost_factor * 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 = self._indirect_cost_factor * 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
self.calculate_field_gathering_costs(model)
self.calculate_plant_costs(model)

if not self.totalcapcost.Valid:
# exploration costs (same as in Geophires v1.2) (M$)
Expand Down
Loading
Loading