Skip to content

Commit 2ff0b03

Browse files
Merge pull request #113 from softwareengineerprogrammer/number-of-fractures-per-stimulated-well
Add 'Number of Fractures per Stimulated Well' parameter [v3.10.15] (NREL#426)
2 parents e7add50 + b5eacab commit 2ff0b03

File tree

10 files changed

+174
-13
lines changed

10 files changed

+174
-13
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 3.10.14
2+
current_version = 3.10.15
33
commit = True
44
tag = True
55

.cookiecutterrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ default_context:
5454
sphinx_doctest: "no"
5555
sphinx_theme: "sphinx-py3doc-enhanced-theme"
5656
test_matrix_separate_coverage: "no"
57-
version: 3.10.14
57+
version: 3.10.15
5858
version_manager: "bump2version"
5959
website: "https://github.com/NREL"
6060
year_from: "2023"

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ Free software: `MIT license <LICENSE>`__
5858
:alt: Supported implementations
5959
:target: https://pypi.org/project/geophires-x
6060

61-
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.10.14.svg
61+
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.10.15.svg
6262
:alt: Commits since latest release
63-
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.10.14...main
63+
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.10.15...main
6464

6565
.. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat
6666
:target: https://nrel.github.io/GEOPHIRES-X

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
year = '2025'
1919
author = 'NREL'
2020
copyright = f'{year}, {author}'
21-
version = release = '3.10.14'
21+
version = release = '3.10.15'
2222

2323
pygments_style = 'trac'
2424
templates_path = ['./templates']

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def read(*names, **kwargs):
1313

1414
setup(
1515
name='geophires-x',
16-
version='3.10.14',
16+
version='3.10.15',
1717
license='MIT',
1818
description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.',
1919
long_description='{}\n{}'.format(

src/geophires_x/Reservoir.py

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
from geophires_x.GeoPHIRESUtils import heat_capacity_water_J_per_kg_per_K, quantity, static_pressure_MPa
1515
from geophires_x.GeoPHIRESUtils import density_water_kg_per_m3
1616

17+
_MAX_ALLOWED_FRACTURES = 1_000_000
18+
19+
1720
class Reservoir:
1821
"""
1922
This class is the parent class for modeling the Reservoir.
@@ -284,15 +287,32 @@ def __init__(self, model: Model):
284287
ToolTipText="Width of each fracture"
285288
)
286289

290+
fracnumb_allowable_range = list(range(1, _MAX_ALLOWED_FRACTURES + 1, 1))
287291
self.fracnumb = self.ParameterDict[self.fracnumb.Name] = intParameter(
288292
"Number of Fractures",
289293
DefaultValue=10,
290-
AllowableRange=list(range(1, 100_000, 1)),
294+
AllowableRange=fracnumb_allowable_range,
291295
UnitType=Units.NONE,
292296
ErrMessage="assume default number of fractures (10)",
293297
ToolTipText="Number of identical parallel fractures in EGS fracture-based reservoir model."
294298
)
295299

300+
# Variable is a workaround for the fact that model.economics has not been initialized yet.
301+
model_economics_stimulation_cost_per_production_well_name = \
302+
'Reservoir Stimulation Capital Cost per Production Well'
303+
# noinspection SpellCheckingInspection
304+
self.fracnumb_per_stimulated_well = self.ParameterDict[self.fracnumb_per_stimulated_well.Name] = intParameter(
305+
'Number of Fractures per Stimulated Well',
306+
DefaultValue=20,
307+
AllowableRange=fracnumb_allowable_range,
308+
UnitType=Units.NONE,
309+
ToolTipText=f'Number of identical parallel fractures per stimulated well '
310+
f'in EGS fracture-based reservoir model. '
311+
f'(Note that injection wells are assumed to be stimulated by default; '
312+
f'production wells are assumed to be stimulated if '
313+
f'{model_economics_stimulation_cost_per_production_well_name} is provided.)'
314+
)
315+
296316
self.fracsep = self.ParameterDict[self.fracsep.Name] = floatParameter(
297317
"Fracture Separation",
298318
DefaultValue=50.0,
@@ -617,8 +637,6 @@ def read_parameters(self, model: Model) -> None:
617637

618638
elif ParameterToModify.Name.startswith("Fracture Separation"):
619639
self.fracsepcalc.value = self.fracsep.value
620-
elif ParameterToModify.Name.startswith("Number of Fractures"):
621-
self.fracnumbcalc.value = self.fracnumb.value
622640
elif ParameterToModify.Name.startswith("Fracture Width"):
623641
self.fracwidthcalc.value = self.fracwidth.value
624642
elif ParameterToModify.Name.startswith("Fracture Height"):
@@ -656,6 +674,11 @@ def read_parameters(self, model: Model) -> None:
656674

657675
model.reserv.layerthickness.value[model.reserv.numseg.value-1] = 100_000.0
658676

677+
if self.fracnumb.Provided and self.fracnumb_per_stimulated_well.Provided:
678+
raise ValueError(f'Only one of {self.fracnumb_per_stimulated_well.Name} and {self.fracnumb.Name}'
679+
f'may be provided. '
680+
f'Please provide only one of these parameters.')
681+
659682

660683
model.logger.info(f'complete {str(__class__)}: {sys._getframe().f_code.co_name}')
661684

@@ -676,9 +699,22 @@ def Calculate(self, model: Model) -> None:
676699
# If you choose to subclass this master class, you can also choose to override this method (or not),
677700
# and if you do, do it before or after you call you own version of this method. If you do, you can also
678701
# choose to call this method from you class, which can effectively run the calculations of the superclass,
679-
# making all thr values available to your methods. but you had n better set all the parameters!
702+
# making all the values available to your methods. but you had n better set all the parameters!
703+
704+
# calculate fracture count and geometry
705+
if self.fracnumb_per_stimulated_well.Provided:
706+
stimulated_wells_count = model.wellbores.ninj.value
707+
if model.economics.stimulation_cost_per_production_well.Provided:
708+
# Only injection wells are assumed to be stimulated unless stimulation cost per
709+
# production well parameter is provided (even if provided cost is $0).
710+
stimulated_wells_count += model.wellbores.nprod.value
711+
self.fracnumbcalc.value = self.fracnumb_per_stimulated_well.value * stimulated_wells_count
712+
else:
713+
self.fracnumbcalc.value = self.fracnumb.value
714+
if self.fracnumbcalc.value > _MAX_ALLOWED_FRACTURES:
715+
raise ValueError(f'{self.fracnumb.Name} ({self.fracnumbcalc.value}) must not exceed '
716+
f'{_MAX_ALLOWED_FRACTURES}.')
680717

681-
# calculate fracture geometry
682718
if self.fracshape.value == FractureShape.CIRCULAR_AREA:
683719
self.fracheightcalc.value = math.sqrt(4 / math.pi * self.fracareacalc.value)
684720
self.fracwidthcalc.value = self.fracheightcalc.value

src/geophires_x/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '3.10.14'
1+
__version__ = '3.10.15'

src/geophires_x_schema_generator/geophires-request.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,16 @@
320320
"category": "Reservoir",
321321
"default": 10,
322322
"minimum": 1,
323-
"maximum": 99999
323+
"maximum": 1000000
324+
},
325+
"Number of Fractures per Stimulated Well": {
326+
"description": "Number of identical parallel fractures per stimulated well in EGS fracture-based reservoir model. (Note that injection wells are assumed to be stimulated by default; production wells are assumed to be stimulated if Reservoir Stimulation Capital Cost per Production Well is provided.)",
327+
"type": "integer",
328+
"units": null,
329+
"category": "Reservoir",
330+
"default": 20,
331+
"minimum": 1,
332+
"maximum": 1000000
324333
},
325334
"Fracture Separation": {
326335
"description": "Separation of identical parallel fractures with uniform spatial distribution in EGS fracture-based reservoir",
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
Reservoir Model, 1
2+
Reservoir Volume Option, 1
3+
Reservoir Density, 2800
4+
Reservoir Heat Capacity, 790
5+
Reservoir Thermal Conductivity, 3.05
6+
Reservoir Porosity, 0.0118
7+
Reservoir Impedance, 0.001
8+
9+
Fracture Shape, 4
10+
Fracture Height, 2000
11+
Fracture Width, 10000
12+
Fracture Separation, 30
13+
14+
Number of Segments, 1
15+
16+
Production Well Diameter, 7
17+
Injection Well Diameter, 7
18+
Well Separation, 365 feet
19+
Injection Temperature, 60 degC
20+
Injection Wellbore Temperature Gain, 3
21+
Plant Outlet Pressure, 1000 psi
22+
Ramey Production Wellbore Model, 1
23+
Utilization Factor, .9
24+
Water Loss Fraction, 0.05
25+
Maximum Drawdown, 1
26+
Ambient Temperature, 10 degC
27+
#Surface Temperature, 10 degC
28+
End-Use Option, 1
29+
30+
Plant Lifetime, 25
31+
32+
Circulation Pump Efficiency, 0.80
33+
34+
Economic Model, 3
35+
Starting Electricity Sale Price, 0.15
36+
Ending Electricity Sale Price, 1.00
37+
Electricity Escalation Rate Per Year, 0.004053223
38+
Electricity Escalation Start Year, 1
39+
Fraction of Investment in Bonds, .5
40+
Combined Income Tax Rate, .3
41+
Gross Revenue Tax Rate, 0
42+
Inflated Bond Interest Rate, .05
43+
Inflated Equity Interest Rate, .08
44+
Inflation Rate, .02
45+
Investment Tax Credit Rate, .3, -- https://programs.dsireusa.org/system/program/detail/658
46+
Production Tax Credit Electricity, 0.0275, -- https://programs.dsireusa.org/system/program/detail/734
47+
Inflation Rate During Construction, 0.05
48+
Property Tax Rate, 0
49+
Time steps per year, 10
50+
Maximum Temperature, 500
51+
52+
53+
Print Output to Console, 0
54+
Surface Temperature, 12
55+
Reservoir Depth, 5.4
56+
Gradient 1, 36.7
57+
Power Plant Type, 4
58+
59+
Number of Injection Wells, 54
60+
Number of Production Wells, 54
61+
Production Flow Rate per Well, 80

tests/geophires_x_tests/test_reservoir.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
from geophires_x.GeoPHIRESUtils import static_pressure_MPa
1212
from geophires_x.Model import Model
13+
14+
# noinspection PyProtectedMember
15+
from geophires_x.Reservoir import _MAX_ALLOWED_FRACTURES
1316
from geophires_x.Reservoir import Reservoir
1417
from geophires_x_client import GeophiresInputParameters
1518
from geophires_x_client import GeophiresXClient
@@ -228,3 +231,55 @@ def _get_result() -> GeophiresXResult:
228231

229232
for k, v in expected.items():
230233
self.assertEqual(summary[k], v)
234+
235+
def test_number_of_fractures_per_stimulated_well(self):
236+
def _get_result(
237+
fracs_per_stimulated_well: int | None,
238+
inj_wells: int,
239+
prod_wells: int | None = None,
240+
prod_wells_stimulated: bool = True,
241+
fracs_total: int | None = None,
242+
) -> GeophiresXResult:
243+
if prod_wells is None:
244+
prod_wells = inj_wells
245+
246+
params = {
247+
'Number of Production Wells': prod_wells,
248+
'Number of Injection Wells': inj_wells,
249+
}
250+
251+
if fracs_per_stimulated_well is not None:
252+
params['Number of Fractures per Stimulated Well'] = fracs_per_stimulated_well
253+
254+
if fracs_total is not None:
255+
params['Number of Fractures'] = fracs_total
256+
257+
if prod_wells_stimulated:
258+
# stim cost per production well indicates prod wells are stimulated (cost doesn't matter for this test)
259+
params['Reservoir Stimulation Capital Cost per Production Well'] = 1
260+
261+
return GeophiresXClient().get_geophires_result(
262+
GeophiresInputParameters(
263+
from_file_path=self._get_test_file_path('generic-egs-case-4_no-fractures-specified.txt'),
264+
params=params,
265+
)
266+
)
267+
268+
r_102_per = _get_result(102, 59)
269+
self.assertEqual(12_036, r_102_per.result['RESERVOIR PARAMETERS']['Number of fractures']['value'])
270+
271+
r_102_per_total_equivalent = _get_result(None, 59, fracs_total=12_036)
272+
self.assertEqual(
273+
12_036, r_102_per_total_equivalent.result['RESERVOIR PARAMETERS']['Number of fractures']['value']
274+
)
275+
276+
r_102_per_inj = _get_result(102, 59, prod_wells_stimulated=False)
277+
self.assertEqual(12_036 / 2, r_102_per_inj.result['RESERVOIR PARAMETERS']['Number of fractures']['value'])
278+
279+
with self.assertRaises(RuntimeError) as e:
280+
_get_result(102, 59, fracs_total=12_036)
281+
self.assertIn('provide only one', str(e.exception))
282+
283+
with self.assertRaises(RuntimeError) as e:
284+
_get_result(_MAX_ALLOWED_FRACTURES, 59)
285+
self.assertIn(f'({_MAX_ALLOWED_FRACTURES*59*2}) must not exceed {_MAX_ALLOWED_FRACTURES}', str(e.exception))

0 commit comments

Comments
 (0)