Skip to content

Commit a1706b3

Browse files
WIP - SurfacePlantFgemORC.py
1 parent bb8bf96 commit a1706b3

File tree

7 files changed

+210
-4
lines changed

7 files changed

+210
-4
lines changed

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ def read(*names, **kwargs):
8686
'nrel-pysam',
8787
'tabulate',
8888
'tqdm',
89+
#'sklearn',
90+
'scikit-learn',
8991
],
9092
extras_require={
9193
# eg:

src/fgem/powerplant.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ def compute_injection_temp(self, T, T_amb, m_turbine):
543543
class ORCPowerPlant(BasePowerPlant):
544544
"""On/Off-Design Air-cooled ORC Binary power plant developed in Python by Aljubran et al. (2024)."""
545545

546-
def __init__(self, Tres, Tamb, m_prd, num_prd=None, ppc=None, cf=1.0, k=2):
546+
def __init__(self, Tres, Tamb, m_prd, num_prd=None, ppc=None, cf=1.0, k=2, timestep=timedelta(hours=1)):
547547
"""Define attributes for the BasePowerPlant class.
548548
549549
Args:
@@ -583,7 +583,7 @@ def __init__(self, Tres, Tamb, m_prd, num_prd=None, ppc=None, cf=1.0, k=2):
583583

584584
if ppc is None:
585585
ppc = max(model_output[0] * num_prd, 1) # MWe power plant capacity for all wells
586-
super(ORCPowerPlant, self).__init__(ppc, Tres, cf)
586+
super(ORCPowerPlant, self).__init__(ppc, Tres, cf, timestep=timestep)
587587

588588
self.power_output_MWh_kg = np.clip(
589589
model_output[0] / self.m_prd_design / 3600, a_min=0.0, a_max=2.5e-4

src/geophires_x/Model.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from geophires_x.GeoPHIRESUtils import read_input_file
1010
from geophires_x.OutputsAddOns import OutputsAddOns
1111
from geophires_x.OutputsS_DAC_GT import OutputsS_DAC_GT
12+
from geophires_x.SurfacePlantFgemORC import SurfacePlantFgemOrc
1213
from geophires_x.TDPReservoir import TDPReservoir
1314
from geophires_x.WellBores import WellBores
1415
from geophires_x.SurfacePlant import SurfacePlant
@@ -177,6 +178,8 @@ def __init__(self, enable_geophires_logging_config=True, input_file=None):
177178
self.surfaceplant = SurfacePlantSUTRA(self)
178179
elif self.InputParameters['Power Plant Type'].sValue in ['9', 'Industrial']:
179180
self.surfaceplant = SurfacePlantIndustrialHeat(self)
181+
elif self.InputParameters['Power Plant Type'].sValue in ['10', 'FGEM ORC']:
182+
self.surfaceplant = SurfacePlantFgemOrc(self)
180183

181184
# if we find out we have an add-ons, we need to instantiate it, then read for the parameters
182185
if 'AddOn Nickname 1' in self.InputParameters:
@@ -238,6 +241,8 @@ def read_parameters(self, default_output_path: Path = None) -> None:
238241
self.surfaceplant = SurfacePlantSupercriticalOrc(self)
239242
elif self.surfaceplant.plant_type.value == PlantType.SINGLE_FLASH:
240243
self.surfaceplant = SurfacePlantSingleFlash(self)
244+
elif self.surfaceplant.plant_type.value == PlantType.FGEM_ORC:
245+
self.surfaceplant = SurfacePlantFgemOrc(self)
241246
else: # default is double flash
242247
self.surfaceplant = SurfacePlantDoubleFlash(self)
243248

src/geophires_x/OptionList.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class PlantType(GeophiresInputEnum):
6464
DISTRICT_HEATING = 7, "District Heating"
6565
RTES = 8, "Reservoir Thermal Energy Storage"
6666
INDUSTRIAL = 9, "Industrial"
67+
FGEM_ORC = 10, "FGEM ORC"
6768

6869
@staticmethod
6970
def from_input_string(input_string: str):

src/geophires_x/SurfacePlant.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ def __init__(self, model: Model):
276276
self.plant_type = self.ParameterDict[self.plant_type.Name] = intParameter(
277277
"Power Plant Type",
278278
DefaultValue=PlantType.SUB_CRITICAL_ORC.int_value,
279-
AllowableRange=[1, 2, 3, 4, 5, 6, 7, 8, 9],
279+
AllowableRange=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
280280
ValuesEnum=PlantType,
281281
UnitType=Units.NONE,
282282
ErrMessage="assume default power plant type (1: subcritical ORC)",
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
from datetime import timedelta
2+
from pathlib import Path
3+
4+
import numpy as np
5+
6+
from fgem.powerplant import GEOPHIRESORCPowerPlant, ORCPowerPlant
7+
from geophires_x.Parameter import HasQuantity
8+
from geophires_x.SurfacePlant import SurfacePlant
9+
import geophires_x.Model as Model
10+
11+
12+
class SurfacePlantFgemOrc(SurfacePlant):
13+
def __init__(self, model: Model):
14+
"""
15+
The __init__ function is called automatically when a class is instantiated.
16+
It initializes the attributes of an object, and sets default values for certain arguments that can be overridden
17+
by user input.
18+
The __init__ function is used to set up all the parameters in the Surfaceplant.
19+
:param model: The container class of the application, giving access to everything else, including the logger
20+
:type model: :class:`~geophires_x.Model.Model`
21+
:return: None
22+
"""
23+
24+
model.logger.info("Init " + self.__class__.__name__ + ": " + __name__)
25+
super().__init__(model) # Initialize all the parameters in the superclass
26+
27+
# Set up all the Parameters that will be predefined by this class using the different types of parameter classes.
28+
# Setting up includes giving it a name, a default value, The Unit Type (length, volume, temperature, etc.) and
29+
# Unit Name of that value, sets it as required (or not), sets allowable range, the error message if that range
30+
# is exceeded, the ToolTip Text, and the name of teh class that created it.
31+
# This includes setting up temporary variables that will be available to all the class but noy read in by user,
32+
# or used for Output
33+
# This also includes all Parameters that are calculated and then published using the Printouts function.
34+
35+
# These dictionaries contain a list of all the parameters set in this object, stored as "Parameter" and
36+
# "OutputParameter" Objects. This will allow us later to access them in a user interface and get that list,
37+
# along with unit type, preferred units, etc.
38+
39+
# local variable initialization
40+
sclass = self.__class__.__name__
41+
self.MyClass = sclass
42+
self.MyPath = Path(__file__).resolve()
43+
44+
model.logger.info("Complete " + self.__class__.__name__ + ": " + __name__)
45+
46+
def __str__(self):
47+
return "SurfacePlantFgemORC"
48+
49+
def read_parameters(self, model: Model) -> None:
50+
"""
51+
The read_parameters function reads in the parameters from a dictionary and stores them in the parameters.
52+
It also handles special cases that need to be handled after a value has been read in and checked.
53+
If you choose to subclass this master class, you can also choose to override this method (or not), and if you do
54+
:param model: The container class of the application, giving access to everything else, including the logger
55+
:return: None
56+
"""
57+
model.logger.info("Init " + self.__class__.__name__ + ": " + __name__)
58+
super().read_parameters(model) # Initialize all the parameters in the superclass
59+
model.logger.info("complete " + self.__class__.__name__ + ": " + __name__)
60+
61+
def Calculate(self, model: Model) -> None:
62+
"""
63+
:param model: The container class of the application
64+
:type model: :class:`~geophires_x.Model.Model`
65+
"""
66+
67+
model.logger.info("Init " + self.__class__.__name__ + ": " + __name__)
68+
69+
def degC(p: HasQuantity) -> float:
70+
return p.quantity().to('degC').magnitude
71+
72+
# fgem_TenteringPP = np.zeros(len(model.reserv.timevector.value))
73+
74+
# calculate power plant entering temperature
75+
self.TenteringPP.value = SurfacePlant.power_plant_entering_temperature(self, self.enduse_option.value,
76+
model.reserv.timevector.value,
77+
self.T_chp_bottom.value,
78+
model.wellbores.ProducedTemperature.value)
79+
80+
81+
82+
# Availability water
83+
self.Availability.value = SurfacePlant.availability_water(self, self.ambient_temperature.value,
84+
self.TenteringPP.value,
85+
self.ambient_temperature.value)
86+
87+
# Subcritical ORC-specific values.
88+
if self.ambient_temperature.value < 15.:
89+
C21 = 0.0
90+
C11 = 2.746E-3
91+
C01 = -8.3806E-2
92+
D21 = 0.0
93+
D11 = 2.713E-3
94+
D01 = -9.1841E-2
95+
C22 = 0.0
96+
C12 = 0.0894
97+
C02 = 55.6
98+
D22 = 0.0
99+
D12 = 0.0894
100+
D02 = 62.6
101+
else:
102+
C21 = 0.0
103+
C11 = 2.713E-3
104+
C01 = -9.1841E-2
105+
D21 = 0.0
106+
D11 = 2.676E-3
107+
D01 = -1.012E-1
108+
C22 = 0.0
109+
C12 = 0.0894
110+
C02 = 62.6
111+
D22 = 0.0
112+
D12 = 0.0894
113+
D02 = 69.6
114+
115+
model.wellbores.Tinj.value, ReinjTemp, etau = SurfacePlant.reinjection_temperature(self, model,
116+
self.ambient_temperature.value, self.TenteringPP.value, model.wellbores.Tinj.value,
117+
C01, C11, C21, D01, D11, D21, C02, C12, C22, D02, D12, D22)
118+
119+
# calculate electricity & heat production
120+
self.ElectricityProduced.value, self.HeatExtracted.value, self.HeatProduced.value, HeatExtractedTowardsElectricity = \
121+
SurfacePlant.electricity_heat_production(self, self.enduse_option.value, self.Availability.value, etau,
122+
model.wellbores.nprod.value,
123+
model.wellbores.prodwellflowrate.value,
124+
model.reserv.cpwater.value,
125+
model.wellbores.ProducedTemperature.value,
126+
model.wellbores.Tinj.value, ReinjTemp, self.T_chp_bottom.value,
127+
self.enduse_efficiency_factor.value, self.chp_fraction.value)
128+
129+
# subtract pumping power for net electricity and calculate first law efficiency
130+
self.NetElectricityProduced.value = self.ElectricityProduced.value - model.wellbores.PumpingPower.value
131+
self.FirstLawEfficiency.value = self.NetElectricityProduced.value / HeatExtractedTowardsElectricity
132+
133+
# Calculate annual electricity, pum;ping, and heat production
134+
self.HeatkWhExtracted.value, self.PumpingkWh.value, self.TotalkWhProduced.value, self.NetkWhProduced.value, self.HeatkWhProduced.value = \
135+
SurfacePlant.annual_electricity_pumping_power(self, self.plant_lifetime.value, self.enduse_option.value,
136+
self.HeatExtracted.value,
137+
model.economics.timestepsperyear.value,
138+
self.utilization_factor.value,
139+
model.wellbores.PumpingPower.value,
140+
self.ElectricityProduced.value,
141+
self.NetElectricityProduced.value, self.HeatProduced.value)
142+
143+
# calculate reservoir heat content
144+
self.RemainingReservoirHeatContent.value = SurfacePlant.remaining_reservoir_heat_content(
145+
self, model.reserv.InitialReservoirHeatContent.value, self.HeatkWhExtracted.value)
146+
147+
148+
nameplate_capacity_MW = 10 # FIXME WIP
149+
# self._fgem_geophires_orc_power_plant: GEOPHIRESORCPowerPlant = GEOPHIRESORCPowerPlant(
150+
# nameplate_capacity_MW,
151+
# model.reserv.Tresoutput.value
152+
# ) # FIXME TEMP WIP
153+
154+
fgem_orc_power_plant = ORCPowerPlant(
155+
#degC(model.reserv.Tresoutput)[0],
156+
degC(model.wellbores.ProducedTemperature)[0],
157+
degC(self.ambient_temperature),
158+
model.wellbores.prodwellflowrate.value,
159+
num_prd=model.wellbores.nprod.value,
160+
# TODO capacity factor
161+
cf =self.utilization_factor.value,
162+
163+
timestep=timedelta(days=365*(1 / model.economics.timestepsperyear.value))
164+
)
165+
166+
167+
fgem_Tinj = np.zeros(len(model.reserv.timevector.value))
168+
fgem_ElectricityProduced = np.zeros(len(model.reserv.timevector.value))
169+
for t in range(len(model.reserv.timevector.value)):
170+
m_turbine = model.wellbores.prodwellflowrate.value * model.wellbores.nprod.value
171+
fgem_orc_power_plant.step(
172+
m_turbine, # total mass flowing into turbine
173+
model.wellbores.ProducedTemperature.value[t], # T_prd_wh = producer wellhead temperature degC
174+
self.ambient_temperature.value
175+
)
176+
fgem_Tinj[t] = fgem_orc_power_plant.T_inj
177+
fgem_ElectricityProduced[t] = fgem_orc_power_plant.power_output_MWe
178+
179+
self._calculate_derived_outputs(model)
180+
model.logger.info(f"complete {self.__class__.__name__}: {__name__}")

tests/fgem_tests/test_fgem.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33

44
from base_test_case import BaseTestCase
5+
from geophires_x_client import GeophiresXResult, GeophiresXClient, GeophiresInputParameters
6+
57

68
# ruff: noqa: I001 # Successful module initialization is dependent on this specific import order.
79

@@ -14,4 +16,20 @@
1416
class FgemTestCase(BaseTestCase):
1517

1618
def test_fgem(self):
17-
pass
19+
r = self._get_result({})
20+
self.assertIsNotNone(r)
21+
22+
def _egs_test_file_path(self) -> str:
23+
# return self._get_test_file_path('generic-egs-case-2_sam-single-owner-ppa.txt')
24+
return self._get_test_file_path('../examples/example4.txt')
25+
26+
def _get_result(self, _params, file_path=None) -> GeophiresXResult:
27+
if file_path is None:
28+
file_path = self._egs_test_file_path()
29+
30+
return GeophiresXClient().get_geophires_result(
31+
GeophiresInputParameters(
32+
from_file_path=file_path,
33+
params={'Power Plant Type': 10, **_params},
34+
)
35+
)

0 commit comments

Comments
 (0)