├── LICENSE ├── MANIFEST.in ├── README.rst ├── example ├── example.ipynb ├── reservoir_properties_list.csv └── testGppeval.py ├── gppeval ├── __init__.py ├── __pycache__ │ └── __init__.cpython-310.pyc └── example │ ├── example.ipynb │ └── reservoir_properties_list.csv └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Carlos O. POCASANGRE JIMENEZ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include example/* 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | TOPIC 2 | =============================== 3 | A Python-based stochastic library for assessing geothermal power potential using the volumetric 4 | method in a liquid-dominated reservoir. 5 | 6 | Authors 7 | -------------- 8 | - Carlos Pocasangre Jiménez (carlos.pocasangre@ues.edu.sv) 9 | 10 | - Fidel Ernesto Cortez Torres (ernestocortez.sv@ieee.org) 11 | 12 | - Rubén Alexander Henríquez Miranda (rubenhenriquez@ieee.org) 13 | 14 | ABSTRACT 15 | =============================== 16 | We present a Python-based stochastic library for assessing geothermal power 17 | potential using the volumetric method in a liquid-dominated reservoir. 18 | The specific aims of this study are to use the volumetric method, “heat in 19 | place,” to estimate electrical energy production ability from a geothermal 20 | liquid-dominated reservoir, and to build a Python-based stochastic library 21 | with useful methods for running such simulations. Although licensed 22 | software is available, we selected the open-source programming language 23 | Python for this task. The Geothermal Power Potential Evaluation stochastic 24 | library (*gppeval*) is structured as three essential objects including a 25 | geothermal power plant module, a Monte Carlo simulation module, and a tools 26 | module. 27 | 28 | For testing the application, a **Jupyter Notebook** example has been included in the `example 29 | folder`_. 30 | 31 | *HINT*: **Now, this application is available for Python 3.5** 32 | 33 | Reference 34 | -------------- 35 | Pocasangre, C., & Fujimitsu, Y. (2018). *A Python-based stochastic library for assessing 36 | geothermal power potential using the volumetric method in a liquid-dominated reservoir*. 37 | **Geothermics**, 76, 164-176. 38 | https://doi.org/10.1016/J.GEOTHERMICS.2018.07.009 39 | 40 | J. Lawless. 2010. Geothermal Lexicon For Resources and Reserves Definition 41 | and Reporting. 2nd Edition (2010) Edition. Adelaide, Southern Australia: 42 | Australian Geothermal Reporting Code Committee (AGRCC) 43 | 44 | INSTALLATION 45 | ============ 46 | 47 | Required Packages 48 | ----------------- 49 | 50 | The following packages should be installed automatically (if using 'pip' 51 | or 'easy_install'), otherwise they will need to be installed manually: 52 | 53 | - NumPy_ : Numeric Python 54 | - SciPy_ : Scientific Python 55 | - Matplotlib_ : Python plotting library 56 | - Mcerp_ : Monte Carlo Error Propagation 57 | - Iapws_ : The InternationalAssociation for the Properties of Water and Steam 58 | - Beautifultable_ : Utility package to print visually appealing ASCII tables to terminal 59 | 60 | How to install 61 | -------------- 62 | 63 | You have **several easy, convenient options** to install the 'gppeval' 64 | package (administrative privileges may be required). 65 | 66 | #. Simply copy the unzipped 'gppeval folder' directory to any other location that 67 | python can find it and rename it 'gppeval'. 68 | 69 | #. From the command-line, do one of the following: 70 | 71 | a. Manually download the package files below, unzip to any directory, and 72 | run: 73 | 74 | $ [sudo] python setup.py install 75 | 76 | b. If 'pip' is installed, run the follow command (stable version and internet connection is required) 77 | 78 | $ [sudo] pip install [--upgrade] gppeval 79 | 80 | CHANGES OF NEW ISSUE 81 | ==================== 82 | 83 | #. gppeval (2024.08.04.0.1.dev1). 84 | Fixed bugs. 85 | 86 | #. gppeval (2020.10.1.0.3.dev1). 87 | Added tho-phases reservoir equation. 88 | Fixed bugs. 89 | 90 | #. gppeval (2019.4.17.0.6.dev1). 91 | Python 3.8 92 | Fixed bugs. 93 | 94 | #. gppeval (2019.4.17.0.2.dev1). 95 | Python 3.5 available 96 | 97 | #. gppeval (2018.10.11.0.1.dev1). 98 | The input file csv has been modified. It includes the possibility of using volume as a input 99 | reservoir parameter. Using the word ``none`` is possible to exchange between either to use 100 | **Area** and **Thickness** or to use only **Volume** as a reservoir geometric parameter. 101 | 102 | Example: Using Area and Thickness 103 | 104 | 0,Name,14.00061,-88.73744,ReservoirArea,A,km2,5,6,7,0,0,T 105 | 1,,,,Thickness,h,m,450,500,600,0,0,T 106 | 2,,,,Volume,v,km3,4,6,8.2,0,0,none 107 | 108 | Example: Using only Volume 109 | 110 | 0,Name,14.00061,-88.73744,ReservoirArea,A,km2,5,6,7,0,0,None 111 | 1,,,,Thickness,h,m,450,500,600,0,0,None 112 | 2,,,,Volume,v,km3,4,6,8.2,0,0,T 113 | 114 | #. gppeval (2018.4.6.0.1.dev1). 115 | Original issue after have been upload as a stable. 116 | 117 | #. gppeval (2017.10.1.0.1.dev1). 118 | Original issue. 119 | 120 | CONTACT 121 | ======= 122 | 123 | Please send **feature requests, bug reports, or feedback** to: `Carlos O. POCASANGRE JIMENEZ`_ 124 | 125 | .. _Monte Carlo methods: http://en.wikipedia.org/wiki/Monte_Carlo_method 126 | .. _latin-hypercube sampling: http://en.wikipedia.org/wiki/Latin_hypercube_sampling 127 | .. _error propagation: http://en.wikipedia.org/wiki/Propagation_of_uncertainty 128 | .. _math: http://docs.python.org/library/math.html 129 | .. _NumPy: http://www.numpy.org/ 130 | .. _SciPy: http://scipy.org 131 | .. _Matplotlib: http://matplotlib.org/ 132 | .. _scipy.stats: http://docs.scipy.org/doc/scipy/reference/stats.html 133 | .. _uncertainties: http://pypi.python.org/pypi/uncertainties 134 | .. _Mcerp: http://github.com/tisimst/mcerp 135 | .. _Beautifultable: https://github.com/pri22296/beautifultable 136 | .. _Gppeval: http://github.com/cpocasangre/gppeval 137 | .. _example folder: https://github.com/cpocasangre/gppeval 138 | .. _Carlos O. POCASANGRE JIMENEZ: mailto:carlos.pocasangre@ues.edu.sv 139 | .. _Iapws: https://pypi.org/project/iapws/ 140 | -------------------------------------------------------------------------------- /example/reservoir_properties_list.csv: -------------------------------------------------------------------------------- 1 | #Imput file for the Assessment of Power Energy Generation by using Volumetric Method and Mote Carlo Probabilistic analysis,,,,,,,,,,,, 2 | #Item,Name_Place,Latitude,Longitude,Reservoir_Properties,Symbol,Units,Min,Most_Likely,Max,Mean,Standard_Deviation,Distribution_Type 3 | 0,Fantasy World,27.06,27.97,ReservoirArea,A,km2,8,9,10,0,0,T 4 | 1,,,,Thickness,h,m,500,800,1000,0,0,T 5 | 2,,,,Volume,v,km3,4,6,8.2,0,0,none 6 | 3,,,,ReservoirTemperature,Tr,oC,150,175,200,0,0,T 7 | 4,,,,AbandonTemperature,Ta,oC,0,70,0,0,0,C 8 | 5,,,,Porosity,Phi,%,0.1,0.2,0.3,0,0,T 9 | 6,,,,RockSpecificHeat,Cr,kJ/kg-oC,0,0.91,0,0,0,C 10 | 7,,,,WaterSpecificHeat ,Cf,kJ/kg-oC,0,4.2,0,0,0,C 11 | 8,,,,RockDensity,rho_r,kg/m3,1900,2250,2500,0,0,T 12 | 9,,,,WaterDensity,rho_f,kg/m3,967.34,968.955,970.57,0,0,T 13 | 10,,,,WaterSaturation,Sw,%,0.85,0.95,1.0,0,0,T 14 | 11,,,,RecoveryFactor,Rf,%,0.1,0.175,0.25,0,0,T 15 | 12,,,,ConversionEfficiency,Ce,%,0.1,0.175,0.25,0,0,T 16 | 13,,,,PowerFactor,Pf,%,0.9,0.95,1.0,0,0,T 17 | 14,,,,LifeSpan,t,years,0,25,0,0,0,C 18 | -------------------------------------------------------------------------------- /example/testGppeval.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Wed Mar 11 19:24:11 2020 5 | 6 | @author: Carlos Pocasangre 7 | @email: carlos.pocasangre@ues.edu.sv 8 | """ 9 | import gppeval 10 | 11 | def scenario(pp, x_lim, ite=10000): 12 | tool = gppeval.Tools() 13 | sim = gppeval.MonteCarloSimulation() 14 | sim.set_iterations(iterations=ite) 15 | pp = sim.calc_energy_potential(pp, rtype='tpd') 16 | print(pp) 17 | tool.print_results(pp) 18 | tool.plot_pdf(pp, show=True, x_lim=x_lim, hist_line_width=0.5, hist_type='bar') 19 | 20 | 21 | test = gppeval.Tools().read_file_csv(fn_input='reservoir_properties_list.csv') 22 | 23 | # scenario 1 (25 years) 24 | scenario(test, [20, 120]) 25 | 26 | # scenario 2 (50 years) 27 | test.set_lifespan(most_likely=50.0) 28 | scenario(test, [10, 60]) 29 | -------------------------------------------------------------------------------- /gppeval/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Title: 4 | gppeval (geothermal power potential evaluation) 5 | Description: 6 | A Python-based stochastic library for assessing geothermal power potential 7 | using the volumetric method in a liquid-dominated reservoir. 8 | Author: 9 | Carlos Pocasangre Jiménez 10 | 11 | Fidel Ernesto Cortez Torres 12 | 13 | Rubén Alexander Henríquez Miranda 14 | 15 | Supervisor: 16 | Yasuhiro Fujimitsu 17 | Organization: 18 | 1. Department of Earth Resources Engineering, Kyushu University. 19 | 744 Motooka, Nishi-ku, Fukuoka 819-0395, Japan 20 | 21 | 2. School of Electrical Engineering, University of El Salvador. 22 | At the end of North 25th Avenue "Mártires 30 de julio", San Salvador 23 | Date: 24 | Created on Mon Dec 12th 2017 25 | Last modification: 26 | ( ... Sun Aug 4st 2024) 27 | Version: 28 | 2024.08.04.0.1.dev1 29 | Python_version: 30 | 3.10.12 31 | Abstract: 32 | We present a Python-based stochastic library for assessing geothermal power 33 | potential using the volumetric method in a liquid-dominated and two-phases 34 | reservoir. 35 | The specific aims of this study are to use the volumetric method, “heat in 36 | place,” to estimate electrical energy production ability from a geothermal 37 | liquid-dominated or two-phases reservoir, and to build a Python-based stochastic 38 | library with useful methods for running such simulations. Although licensed 39 | software is available, we selected the open-source programming language 40 | Python for this task. The Geothermal Power Potential Evaluation stochastic 41 | library (gppeval) is structured as three essential objects including a 42 | geothermal power plant module, a Monte Carlo simulation module, and a tools 43 | module. 44 | 45 | Contact: 46 | carlos.pocasangre@ues.edu.sv 47 | 48 | carlos.pocasangre@ieee.org 49 | 50 | ernestocortez.sv@ieee.org 51 | 52 | rubenhenriquez@ieee.org 53 | 54 | Reference: 55 | J. Lawless. 2010. Geothermal Lexicon For Resources and Reserves Definition 56 | and Reporting. 2nd Edition (2010) Edition. Adelaide, Southern Australia: 57 | Australian Geothermal Reporting Code Committee (AGRCC) 58 | 59 | Pocasangre, C., & Fujimitsu, Y. (2018). A Python-based stochastic library 60 | for assessing geothermal power potential using the volumetric method in a 61 | liquid-dominated reservoir. Geothermics, 76, 164–176 62 | 63 | https://doi.org/10.1016/J.GEOTHERMICS.2018.07.009 64 | """ 65 | 66 | import matplotlib.pyplot as plt 67 | from matplotlib.ticker import FormatStrFormatter 68 | import mcerp as mc 69 | import numpy as np 70 | from time import process_time 71 | import scipy.stats as ss 72 | from beautifultable import BeautifulTable 73 | from iapws import IAPWS95 74 | 75 | __version_info__ = (2024, 8, 4, 0, 1, 'dev1') 76 | __version__ = '.'.join(map(str, __version_info__)) 77 | __author__ = 'Carlos O. POCASANGRE JIMENEZ' 78 | __description__ = 'Geothermal Power Potential assessment' 79 | __url__ = 'https://github.com/cpocasangre/gppeval' 80 | __module_name__ = 'gppeval' 81 | __author_email__ = 'carlos.pocasangre@ues.edu.sv' 82 | __license__ = 'MIT License' 83 | __status__ = 'Development release' 84 | 85 | 86 | class Reservoir(object): 87 | """ 88 | **Reservoir abstraction** 89 | 90 | usage: 91 | reservoir_instance = gppeval.Reservoir() 92 | 93 | :param name: name of the reservoir 94 | :param location: dictionary with coordinates of center point in degree 95 | :param address: address of reservoir 96 | :param area: reservoir geometric surface area [km2] 97 | :param thickness: reservoir thickness [m] 98 | :param volume: volume [km3] 99 | 100 | methods: 101 | __init__ 102 | set_values_to_variables 103 | get_name 104 | get_location 105 | get_address 106 | get_area 107 | get_thickness 108 | get_volume 109 | 110 | set_name 111 | set_location 112 | set_address 113 | set_area 114 | set_thickness 115 | set_volume 116 | _str__ 117 | """ 118 | 119 | def __init__(self, **kwargs): 120 | self.name = 'Default name' 121 | self.location = {'lat': 0.0, 'lon': 0.0} 122 | self.address = 'Default address' 123 | 124 | # by default, area, thickness, and volume are set to pdf = None. If the user wants to 125 | # use either V = A * h or V = V, it must be defined before. 126 | 127 | self.area = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 'sd': 1.0, 128 | 'pdf': None} 129 | self.thickness = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 130 | 'sd': 1.0, 'pdf': None} 131 | self.volume = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 132 | 'sd': 1.0, 'pdf': None} 133 | for key in kwargs.keys(): 134 | setattr(self, key, kwargs[key]) 135 | super(Reservoir, self).__init__() 136 | 137 | @staticmethod 138 | def set_values_to_variables(var, values): 139 | """ 140 | helping function for giving values to variables which accept kwargs 141 | 142 | :param var: variable to change dictionary 143 | :param values: dictionary with variable values 144 | """ 145 | for key in values.keys(): 146 | if key in var: 147 | var[key] = values[key] 148 | 149 | def get_name(self): 150 | """ 151 | Get name 152 | 153 | usage: 154 | string = get_name() 155 | 156 | :return name: name of the reservoir (string) 157 | """ 158 | return self.name 159 | 160 | def get_location(self): 161 | """ 162 | Get location dictionary, latitude and longitude 163 | 164 | usage: 165 | dictionary = get_location() 166 | 167 | :return location: a dictionary with latitude and longitude 168 | """ 169 | return self.location 170 | 171 | def get_address(self): 172 | """ 173 | Get address 174 | 175 | usage: 176 | string = get_address() 177 | 178 | :return address: address (string) 179 | """ 180 | return self.address 181 | 182 | def get_area(self): 183 | """ 184 | Get area dictionary with min, most_likely, max, mean, sd, pdf values 185 | 186 | usage: 187 | dictionary = get_area() 188 | 189 | :return area: a dictionary with min, most_likely, max, mean, sd, pdf values 190 | """ 191 | return self.area 192 | 193 | def get_thickness(self): 194 | """ 195 | Get thickness dictionary with min, most_likely, max, mean, sd, pdf values 196 | 197 | usage: 198 | dictionary = get_thickness() 199 | 200 | :return thickness: a dictionary with min, most_likely, max, mean, sd, pdf values 201 | """ 202 | return self.thickness 203 | 204 | def get_volume(self): 205 | """ 206 | Get volume dictionary with min, most_likely, max, mean, sd, pdf values 207 | 208 | usage: 209 | dictionary = get_volume() 210 | 211 | :return volume: a dictionary with min, most_likely, max, mean, sd, pdf values 212 | """ 213 | return self.volume 214 | 215 | def set_name(self, name='string'): 216 | """ 217 | Set name as string 218 | 219 | usage: 220 | set_name('name_of_instance') 221 | """ 222 | self.name = name 223 | 224 | def set_location(self, **kwargs): 225 | """ 226 | Set location values (lat, lon) 227 | 228 | usage: 229 | set_location(lat=10.0, lon=20.0) 230 | """ 231 | self.set_values_to_variables(self.location, kwargs) 232 | 233 | def set_address(self, address='string'): 234 | """ 235 | Set address as string 236 | 237 | usage: 238 | set_address(address='string') 239 | """ 240 | self.address = address 241 | 242 | def set_area(self, **kwargs): 243 | """ 244 | Set area values [km2], use the follow syntax: 245 | 246 | usage: 247 | set_area(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 248 | """ 249 | self.set_values_to_variables(self.area, kwargs) 250 | 251 | def set_thickness(self, **kwargs): 252 | """ 253 | Set thickness values [m], use the follow syntax: 254 | 255 | usage: 256 | set_thickness(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 257 | """ 258 | self.set_values_to_variables(self.thickness, kwargs) 259 | 260 | def set_volume(self, **kwargs): 261 | """ 262 | Set volume values [km3], use the follow syntax: 263 | 264 | usage: 265 | set_volume(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 266 | """ 267 | self.set_values_to_variables(self.volume, kwargs) 268 | 269 | def __str__(self): 270 | """ 271 | Print name, location, address, area, thickness, and volume 272 | """ 273 | if self.volume['pdf'] is not None: 274 | volume = self.volume['most_likely'] 275 | return '{0}, Lat: {1} Lon: {2}, {3}, and {4} km3 '.format(self.name, 276 | str(self.location['lat']), 277 | str(self.location['lon']), 278 | self.address, str(volume)) 279 | 280 | elif self.area['pdf'] is not None and self.thickness['pdf'] is not None: 281 | volume = self.area['most_likely'] * self.thickness['most_likely'] 282 | return '{0}, Lat: {1} Lon: {2}, {3}, and {4} km3 '.format(self.name, 283 | str(self.location[ 284 | 'lat']), 285 | str(self.location[ 286 | 'lon']), 287 | self.address, 288 | str(volume)) 289 | else: 290 | return "Warning: The volume property has not defined." 291 | 292 | 293 | class Thermodynamic(Reservoir): 294 | """ 295 | **Thermodynamics properties abstraction** 296 | 297 | **Physical params (Reservoir Object) 298 | :param name: string 299 | :param location: dictionary lat and lon 300 | :param address: string 301 | :param area: km^2 302 | :param thickness: m 303 | :param volume: km3 304 | 305 | **thermodynamic params 306 | :param reservoir_temp: ºC 307 | :param abandon_temp: ºC 308 | :param porosity: % 309 | :param rock_specific_heat: kJ/kg-ºC 310 | :param fluid_specific_heat: kJ/kg-ºC 311 | :param rock_density: kg/m3 312 | :param fluid_density: kg/m3 313 | 314 | :param reservoir_steam_density: kg/m3 315 | :param reservoir_water_saturation: % 316 | :param reservoir_steam_enthalpy: kJ/kg 317 | :param reservoir_liquid_enthalpy: kJ/kg 318 | :param abandon_liquid_enthalpy: kJ/kg 319 | 320 | **methods 321 | __init__ 322 | get_reservoir_temp 323 | get_abandon_temp 324 | get_porosity 325 | get_rock_specific_heat 326 | get_rock_density 327 | get_fluid_specific_heat 328 | get_fluid_density 329 | 330 | get_reservoir_steam_density 331 | get_reservoir_water_saturation 332 | get_reservoir_steam_enthalpy 333 | get_reservoir_liquid_enthalpy 334 | get_abandon_liquid_enthalpy 335 | 336 | set_reservoir_temp 337 | set_abandon_temp 338 | set_porosity 339 | set_rock_specific_heat 340 | set_rock_density 341 | set_fluid_specific_heat 342 | set_fluid_density 343 | 344 | set_reservoir_steam_density 345 | set_reservoir_water_saturation 346 | set_reservoir_steam_enthalpy 347 | set_reservoir_liquid_enthalpy 348 | set_abandon_liquid_enthalpy 349 | 350 | liquid_dominant_volumetric_energy 351 | two_phase_dominant_volumetric_energy 352 | """ 353 | 354 | def __init__(self, **kwargs): 355 | self.reservoir_temp = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 356 | 'sd': 1.0, 'pdf': 'C'} 357 | self.abandon_temp = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 358 | 'sd': 1.0, 'pdf': 'C'} 359 | self.porosity = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 360 | 'sd': 1.0, 'pdf': 'C'} 361 | self.rock_specific_heat = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 362 | 'sd': 1.0, 'pdf': 'C'} 363 | self.fluid_specific_heat = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 364 | 'mean': 1.0, 'sd': 1.0, 'pdf': 'C'} 365 | self.rock_density = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 366 | 'sd': 1.0, 'pdf': 'C'} 367 | self.fluid_density = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 368 | 'sd': 1.0, 'pdf': 'C'} 369 | # ********* NEW ************* 370 | self.reservoir_steam_density = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 371 | 'sd': 1.0, 'pdf': 'C'} 372 | self.reservoir_water_saturation = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 373 | 'sd': 1.0, 'pdf': 'C'} 374 | self.reservoir_steam_enthalpy = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 375 | 'sd': 1.0, 'pdf': 'C'} 376 | self.reservoir_liquid_enthalpy = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 377 | 'sd': 1.0, 'pdf': 'C'} 378 | self.abandon_liquid_enthalpy = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 'mean': 1.0, 379 | 'sd': 1.0, 'pdf': 'C'} 380 | # ********* FIN NEW ********** 381 | for key in kwargs.keys(): 382 | setattr(self, key, kwargs[key]) 383 | super(Thermodynamic, self).__init__(**kwargs) 384 | 385 | def get_reservoir_temp(self): 386 | """ 387 | To get reservoir_temp dictionary in [ºC] with min, most_likely, max, mean, sd, pdf values 388 | 389 | usage: 390 | dictionary = get_reservoir_temp() 391 | 392 | :return reservoir_temp: a dictionary with min, most_likely, max, mean, sd, pdf values 393 | """ 394 | return self.reservoir_temp 395 | 396 | def get_abandon_temp(self): 397 | """ 398 | To get abandon_temp dictionary in [ºC] with min, most_likely, max, mean, sd, pdf values 399 | 400 | usage: 401 | dictionary = get_abandon_temp() 402 | 403 | :return abandon_temp: a dictionary with min, most_likely, max, mean, sd, pdf values 404 | """ 405 | return self.abandon_temp 406 | 407 | def get_porosity(self): 408 | """ 409 | To get porosity dictionary in [%] with min, most_likely, max, mean, sd, pdf values 410 | 411 | usage: 412 | dictionary = get_porosity() 413 | 414 | :return porosity: a dictionary with min, most_likely, max, mean, sd, pdf values 415 | """ 416 | return self.porosity 417 | 418 | def get_rock_specific_heat(self): 419 | """ 420 | To get rock_specific_heat dictionary in [kJ/kg-ºC] with min, most_likely, max, mean, sd, pdf values 421 | 422 | usage: 423 | dictionary = get_rock_specific_heat() 424 | 425 | :return rock_specific_heat: a dictionary with min, most_likely, max, mean, sd, pdf values 426 | """ 427 | return self.rock_specific_heat 428 | 429 | def get_rock_density(self): 430 | """ 431 | To get rock_density dictionary in [kg/m^3] with min, most_likely, max, mean, sd, pdf values 432 | 433 | usage: 434 | dictionary = get_rock_density() 435 | 436 | :return rock_density: a dictionary with min, most_likely, max, mean, sd, pdf values 437 | """ 438 | return self.rock_density 439 | 440 | def get_fluid_specific_heat(self): 441 | """ 442 | To get fluid_specific_heat dictionary in [kJ/kg-ºC] with min, most_likely, max, mean, sd, pdf values 443 | 444 | usage: 445 | dictionary = get_fluid_specific_heat() 446 | 447 | :return fluid_specific_heat: a dictionary with min, most_likely, max, mean, sd, pdf values 448 | """ 449 | return self.fluid_specific_heat 450 | 451 | def get_fluid_density(self): 452 | """ 453 | To get fluid_density dictionary in [kg/m^3] with min, most_likely, max, mean, sd, pdf values 454 | 455 | usage: 456 | dictionary = get_fluid_density() 457 | 458 | :return fluid_density: a dictionary with min, most_likely, max, mean, sd, pdf values 459 | """ 460 | return self.fluid_density 461 | 462 | # ********* NEW ********* 463 | def get_reservoir_steam_density(self): 464 | """ 465 | To get_reservoir_steam_density dictionary in [kg/m^3] with min, most_likely, max, mean, sd, pdf values 466 | 467 | usage: 468 | dictionary = get_reservoir_steam_density() 469 | 470 | :return reservoir_steam_density: a dictionary with min, most_likely, max, mean, sd, pdf values 471 | """ 472 | return self.reservoir_steam_density 473 | 474 | def get_reservoir_water_saturation(self): 475 | """ 476 | To get_reservoir_water_saturation dictionary in [%] with min, most_likely, max, mean, sd, pdf values 477 | 478 | usage: 479 | dictionary = get_water_saturation() 480 | 481 | :return : water_saturation a dictionary with min, most_likely, max, mean, sd, pdf values 482 | """ 483 | return self.reservoir_water_saturation 484 | 485 | def get_reservoir_steam_enthalpy(self): 486 | """ 487 | To get_reservoir_steam_enthalpy dictionary in [kJ/kg] with min, most_likely, max, mean, sd, pdf values 488 | 489 | usage: 490 | dictionary = get_reservoir_steam_enthalpy() 491 | 492 | :return : reservoir_steam_enthalpy a dictionary with min, most_likely, max, mean, sd, pdf values 493 | """ 494 | return self.reservoir_steam_enthalpy 495 | 496 | def get_reservoir_liquid_enthalpy(self): 497 | """ 498 | To get_reservoir_liquid_enthalpy dictionary in [kJ/kg] with min, most_likely, max, mean, sd, pdf values 499 | 500 | usage: 501 | dictionary = get_reservoir_liquid_enthalpy() 502 | 503 | :return : reservoir_liquid_enthalpy a dictionary with min, most_likely, max, mean, sd, pdf values 504 | """ 505 | return self.reservoir_liquid_enthalpy 506 | 507 | def get_abandon_liquid_enthalpy(self): 508 | """ 509 | To get_abandon_liquid_enthalpy dictionary in [kJ/kg] with min, most_likely, max, mean, sd, pdf values 510 | 511 | usage: 512 | dictionary = get_abandon_liquid_enthalpy() 513 | 514 | :return : abandon_liquid_enthalpy a dictionary with min, most_likely, max, mean, sd, pdf values 515 | """ 516 | return self.abandon_liquid_enthalpy 517 | 518 | # ********* FIN NEW ***** 519 | def set_reservoir_temp(self, **kwargs): 520 | """ 521 | Set reservoir_temp values [ºC], use the follow syntax: 522 | 523 | usage: 524 | set_reservoir_temp(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 525 | """ 526 | self.set_values_to_variables(self.reservoir_temp, kwargs) 527 | 528 | def set_abandon_temp(self, **kwargs): 529 | """ 530 | Set abandon_temp values [ºC], use the follow syntax: 531 | 532 | usage: 533 | set_abandon_temp(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 534 | """ 535 | self.set_values_to_variables(self.abandon_temp, kwargs) 536 | 537 | def set_porosity(self, **kwargs): 538 | """ 539 | Set porosity values [%], use the follow syntax: 540 | 541 | usage: 542 | set_porosity(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 543 | """ 544 | self.set_values_to_variables(self.porosity, kwargs) 545 | 546 | def set_rock_specific_heat(self, **kwargs): 547 | """ 548 | Set rock_specific_heat values [kJ/kg-ºC], use the follow syntax: 549 | 550 | usage: 551 | set_rock_specific_heat(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, 552 | pdf='C') 553 | """ 554 | self.set_values_to_variables(self.rock_specific_heat, kwargs) 555 | 556 | def set_rock_density(self, **kwargs): 557 | """ 558 | Set rock_density values [kg/m^3], use the follow syntax: 559 | 560 | usage: 561 | set_rock_density(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 562 | """ 563 | self.set_values_to_variables(self.rock_density, kwargs) 564 | 565 | def set_fluid_specific_heat(self, **kwargs): 566 | """ 567 | Set fluid_specific_heat values [kJ/kg-ºC], use the follow syntax: 568 | 569 | usage: 570 | set_fluid_specific_heat(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, 571 | pdf='C') 572 | """ 573 | self.set_values_to_variables(self.fluid_specific_heat, kwargs) 574 | 575 | def set_fluid_density(self, **kwargs): 576 | """ 577 | Set fluid_density values [kg/m^3], use the follow syntax: 578 | 579 | usage: 580 | set_fluid_density(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 581 | """ 582 | self.set_values_to_variables(self.fluid_density, kwargs) 583 | 584 | # ********* NEW ********* 585 | def set_reservoir_steam_density(self, **kwargs): 586 | """ 587 | Set reservoir_steam_density dictionary in [kg/m^3] with min, most_likely, max, mean, sd, pdf values 588 | 589 | usage: 590 | set_reservoir_steam_density(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 591 | """ 592 | return self.set_values_to_variables(self.reservoir_steam_density, kwargs) 593 | 594 | def set_reservoir_water_saturation(self, **kwargs): 595 | """ 596 | set reservoir_water_saturation dictionary in [%] with min, most_likely, max, mean, sd, pdf values 597 | 598 | usage: 599 | set_reservoir_water_saturation(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 600 | """ 601 | return self.set_values_to_variables(self.reservoir_water_saturation, kwargs) 602 | 603 | def set_reservoir_steam_enthalpy(self, **kwargs): 604 | """ 605 | Set reservoir_steam_enthalpy dictionary in [kJ/kg] with min, most_likely, max, mean, sd, pdf values 606 | 607 | usage: 608 | set_reservoir_steam_enthalpy(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 609 | 610 | """ 611 | return self.set_values_to_variables(self.reservoir_steam_enthalpy, kwargs) 612 | 613 | def set_reservoir_liquid_enthalpy(self, **kwargs): 614 | """ 615 | Set reservoir_liquid_enthalpy dictionary in [kJ/kg] with min, most_likely, max, mean, sd, pdf values 616 | 617 | usage: 618 | set_reservoir_liquid_enthalpy(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 619 | """ 620 | return self.set_values_to_variables(self.reservoir_liquid_enthalpy, kwargs) 621 | 622 | def set_abandon_liquid_enthalpy(self, **kwargs): 623 | """ 624 | Set abandon_liquid_enthalpy dictionary in [kJ/kg] with min, most_likely, max, mean, sd, pdf values 625 | 626 | usage: 627 | set__abandon_liquid_enthalpy(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 628 | """ 629 | return self.set_values_to_variables(self.abandon_liquid_enthalpy, kwargs) 630 | 631 | # ********* FIN NEW ***** 632 | 633 | @staticmethod 634 | def liquid_dominant_volumetric_energy(tr, ta, phi, cr, cf, rho_r, rho_f, a=None, h=None, v=None): 635 | """ 636 | To calculate energy available in the reservoir by volumetric method in liquid dominated [kJ] 637 | 638 | :param v: volume 639 | :param h: thickness 640 | :param a: area 641 | :param rho_f: fluid density 642 | :param rho_r: rock density 643 | :param cf: fluid-specific heat 644 | :param cr: rock-specific heat 645 | :param phi: porosity 646 | :param ta: abandon temperature 647 | :param tr : reservoir temperature 648 | """ 649 | print('Function LD') 650 | q = 1.0 651 | if v is not None: 652 | q = (rho_r * cr * (1.0 - phi) + rho_f * cf * phi) * (v * 1.0e9) * (tr - ta) 653 | elif a is not None and h is not None: 654 | q = (rho_r * cr * (1.0 - phi) + rho_f * cf * phi) * (a * 1.0e6) * h * (tr - ta) 655 | return q 656 | 657 | @staticmethod 658 | def two_phase_dominant_volumetric_energy(tr, ta, phi, cr, rho_r, rho_f, rho_si, sw, h_si, h_li, h_lf, 659 | a=None, h=None, v=None): 660 | """ 661 | Calculate energy available [kJ] 662 | 663 | :param h_lf: reservoir-abandon enthalpy 664 | :param h_li: reservoir-fluid enthalpy 665 | :param h_si: reservoir-steam enthalpy 666 | :param sw: reservoir-water saturation 667 | :param rho_si: reservoir-steam density 668 | :param v: volume 669 | :param h: thickness 670 | :param a: area 671 | :param rho_f: fluid density 672 | :param rho_r: rock density 673 | :param cr: rock-specific heat 674 | :param phi: porosity 675 | :param ta: abandon temperature 676 | :param tr : reservoir temperature 677 | 678 | HINT: This method has not been implemented yet. It uses the same equation as LQDE 679 | method (Liquid dominated volumetric energy) has. 680 | """ 681 | print('function TPD') 682 | # q = 1.0 683 | 684 | qr = rho_r * cr * (1.0 - phi) * (tr - ta) 685 | qs = rho_si * phi * (1.0 - sw) * (h_si - h_li) 686 | qw = rho_f * phi * sw * (h_li - h_lf) 687 | 688 | q = qr + qs + qw 689 | 690 | if v is not None: 691 | q = q * (v * 1.0e9) 692 | elif a is not None and h is not None: 693 | q = q * (a * 1.0e6) * h 694 | return q 695 | 696 | 697 | class GeothermalPowerPlant(Thermodynamic): 698 | """ 699 | **Class for calculating power assessment of geothermal field** 700 | 701 | General variables: 702 | :param name: string 703 | :param location: dictionary lat and lon 704 | :param address: string 705 | :param area: km^2 706 | :param thickness: m 707 | :param volume: km3 708 | 709 | Thermodynamics properties: 710 | :param reservoir_temp: ºC 711 | :param abandon_temp: ºC 712 | :param porosity: % 713 | :param rock_specific_heat: kJ/kg-ºC 714 | :param fluid_specific_heat: kJ/kg-ºC 715 | :param rock_density: kg/m^3 716 | :param fluid_density: kg/m^3 717 | 718 | :param reservoir_steam_density: kg/m3 719 | :param reservoir_water_saturation: % 720 | :param reservoir_steam_enthalpy: kJ/kg 721 | :param reservoir_liquid_enthalpy: kJ/kg 722 | :param abandon_liquid_enthalpy: kJ/kg 723 | 724 | Power Plant characteristics: 725 | :param recovery_factor: heat recovery factor from heat source to power plant 726 | :param conversion_efficiency: efficiency of electrical conversion 727 | :param plant_net_capacity_factor: plant net capacity factor 728 | :param lifespan: lifespan years 729 | 730 | HINT: variable power_potential has Monte Carlo simulation results: 731 | percentiles list has values from 5% to 95% 732 | 733 | self.power_potential = {'base': 1.0, 'pdf': 1.0, 'iterations': 10000, 'statistics': \ 734 | {'p_base': 1.0, 'mean': 1.0, 'sd': 1.0, 'skew': 1.0, 'kurt': 1.0, 'min': 1.0, 'max': 1.0, \ 735 | 'percentiles': []}} 736 | 737 | **methods 738 | __init__ 739 | get_conversion_efficiency 740 | get_recovery_factor 741 | get_plant_net_capacity_factor 742 | get_lifespan 743 | 744 | set_conversion_efficiency 745 | set_recovery_factor 746 | set_plant_net_capacity_factor 747 | set_lifespan 748 | power_energy 749 | """ 750 | 751 | def __init__(self, **kwargs): 752 | self.conversion_efficiency = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 753 | 'mean': 1.0, 'sd': 1.0, 'pdf': 'C'} 754 | self.recovery_factor = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 755 | 'mean': 1.0, 'sd': 1.0, 'pdf': 'C'} 756 | self.plant_net_capacity_factor = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 757 | 'mean': 1.0, 'sd': 1.0, 'pdf': 'C'} 758 | self.lifespan = {'min': 1.0, 'most_likely': 1.0, 'max': 1.0, 759 | 'mean': 1.0, 'sd': 1.0, 'pdf': 'C'} 760 | self.power_potential = {'base': 1.0, 761 | 'pdf': 1.0, 762 | 'iterations': 10000, 763 | 'statistics': {'p_base': 1.0, 764 | 'mean': 1.0, 765 | 'sd': 1.0, 766 | 'skew': 1.0, 767 | 'kurt': 1.0, 768 | 'min': 1.0, 769 | 'max': 1.0, 770 | 'percentiles': []}} 771 | for key in kwargs.keys(): 772 | setattr(self, key, kwargs[key]) 773 | super(GeothermalPowerPlant, self).__init__(**kwargs) 774 | 775 | def get_conversion_efficiency(self): 776 | """ 777 | To get conversion_efficiency dictionary in [%] with min, most_likely, max, mean, sd, pdf values 778 | 779 | usage: 780 | dictionary = get_conversion_efficiency() 781 | 782 | :return conversion_efficiency: a dictionary with min, most_likely, max, mean, sd, pdf values 783 | """ 784 | return self.conversion_efficiency 785 | 786 | def get_recovery_factor(self): 787 | """ 788 | To get recovery_factor dictionary in [%] with min, most_likely, max, mean, sd, pdf values 789 | 790 | usage: 791 | dictionary = get_recovery_factor() 792 | 793 | :return recovery_factor: a dictionary with min, most_likely, max, mean, sd, pdf values 794 | """ 795 | return self.recovery_factor 796 | 797 | def get_plant_net_capacity_factor(self): 798 | """ 799 | To get plant_net_capacity_factor dictionary in [%] with min, most_likely, max, mean, sd, pdf values 800 | 801 | usage: 802 | dictionary = get_plant_net_capacity_factor() 803 | 804 | :return plant_net_capacity_factor: a dictionary with min, most_likely, max, mean, sd, pdf values 805 | """ 806 | return self.plant_net_capacity_factor 807 | 808 | def get_lifespan(self): 809 | """ 810 | To get lifespan dictionary in [%] with min, most_likely, max, mean, sd, pdf values 811 | 812 | usage: 813 | dictionary = get_lifespan() 814 | 815 | :return lifespan: a dictionary with min, most_likely, max, mean, sd, pdf values 816 | """ 817 | return self.lifespan 818 | 819 | def set_conversion_efficiency(self, **kwargs): 820 | """ 821 | Set conversion_efficiency values [%], use the follow syntax: 822 | 823 | usage: 824 | set_conversion_efficiency(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 825 | """ 826 | self.set_values_to_variables(self.conversion_efficiency, kwargs) 827 | 828 | def set_recovery_factor(self, **kwargs): 829 | """ 830 | Set recovery_factor values [%], use the follow syntax: 831 | 832 | usage: 833 | set_recovery_factor(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 834 | """ 835 | self.set_values_to_variables(self.recovery_factor, kwargs) 836 | 837 | def set_plant_net_capacity_factor(self, **kwargs): 838 | """ 839 | Set plant_net_capacity_factor values [%], use the follow syntax: 840 | 841 | usage: 842 | set_plant_net_capacity_factor(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 843 | """ 844 | self.set_values_to_variables(self.plant_net_capacity_factor, kwargs) 845 | 846 | def set_lifespan(self, **kwargs): 847 | """ 848 | Set lifespan values [years], use the follow syntax: 849 | 850 | usage: 851 | set_lifespan(min=10.0, most_likely=20.0, max=30.0, mean=40.0, sd=50.0, pdf='C') 852 | """ 853 | self.set_values_to_variables(self.lifespan, kwargs) 854 | 855 | def power_energy(self, tr, ta, phi, cr, cf, rho_r, rho_f, rf, ce, pf, t, a, h, v, 856 | rho_si, sw, h_si, h_li, h_lf, rtype='ld'): 857 | """ 858 | **return power energy assessment [We]** 859 | 860 | :param h_lf: reservoir-abandon enthalpy 861 | :param h_li: reservoir-fluid enthalpy 862 | :param h_si: reservoir-steam enthalpy 863 | :param sw: reservoir-water saturation 864 | :param rho_si: reservoir-steam density 865 | 866 | :param t: lifespan [years] 867 | :param pf: plant net capacity factor [%] 868 | :param ce: conversion efficiency [%] 869 | :param rf: recovery factor [%] 870 | :param rho_f: rock density [kg/m^3] 871 | :param rho_r: fluid density [kg/m^3] 872 | :param cf: fluid specific heat [kJ/kg-ºC] 873 | :param cr: rock specific heat [kJ/kg-ºC] 874 | :param phi: porosity [%] 875 | :param h: thickness [m] 876 | :param ta: abandon temperature [ºC] 877 | :param tr: reservoir temperature [ºC] 878 | :param a: area [km^2] 879 | :param v: volume [km^3] 880 | 881 | :param rtype: ld = liquid dominant, tpd = two phase dominant 882 | :return: power energy [We] 883 | """ 884 | q = 1.0 885 | if v is not None: 886 | if rtype == 'ld': 887 | print('LD selected') 888 | a = None 889 | h = None 890 | q = self.liquid_dominant_volumetric_energy(tr, ta, phi, cr, cf, rho_r, rho_f, a, h, v) 891 | elif rtype == 'tpd': 892 | print('TPD selected') 893 | a = None 894 | h = None 895 | # q = self.two_phase_dominant_volumetric_energy(tr, ta, phi, cr, cf, rho_r, 896 | # rho_f, a, h, v) 897 | q = self.two_phase_dominant_volumetric_energy(tr, ta, phi, cr, rho_r, rho_f, rho_si, sw, 898 | h_si, h_li, h_lf, a, h, v) 899 | else: 900 | if rtype == 'ld': 901 | print('LD selected') 902 | v = None 903 | q = self.liquid_dominant_volumetric_energy(tr, ta, phi, cr, cf, rho_r, rho_f, a, h, v) 904 | elif rtype == 'tpd': 905 | print('TPD selected') 906 | v = None 907 | # q = self.two_phase_dominant_volumetric_energy(tr, ta, phi, cr, cf, rho_r, 908 | # rho_f, a, h, v) 909 | q = self.two_phase_dominant_volumetric_energy(tr, ta, phi, cr, rho_r, rho_f, rho_si, sw, 910 | h_si, h_li, h_lf, a, h, v) 911 | 912 | return (q * 1000) * rf * ce / (pf * (t * 31557600)) 913 | 914 | def __str__(self): 915 | """ 916 | To generate a table with all geothermal power plant values 917 | """ 918 | if type(self.power_potential['pdf']) is mc.UncertainFunction: 919 | text = "Most Likely PowerGeneration: {0} [We]\n" \ 920 | "P10%: {1} [We]".format(self.power_potential['base'], 921 | self.power_potential['statistics']['percentiles'][1] 922 | ) 923 | else: 924 | text = 'Reservoir simulation has not been done!' 925 | 926 | def get_values_ordered(val): 927 | keys = ['min', 'most_likely', 'max', 'mean', 'sd', 'pdf'] 928 | return [val[keys[i]] for i in [0, 1, 2, 3, 4, 5]] 929 | 930 | table1 = BeautifulTable(maxwidth=80) 931 | table1.columns.header = ["name", "lat [oC]", "lon [oC]"] 932 | table1.rows.append([self.name, self.location['lat'], self.location['lon']]) 933 | table2 = BeautifulTable(maxwidth=120) 934 | table2.border.top = '=' 935 | table2.columns.header.separator = '=' 936 | table2.border.bottom = '=' 937 | table2.columns.header = ['Item', 'Variable', 'Symbol', 'Units', 'Min', 'Most_Likely', 938 | 'Max', 'Mean', 'SD', 'PDF'] 939 | table2.rows.append([0, 'area', 'A', 'km2'] + get_values_ordered(self.area)) 940 | table2.rows.append([1, 'thickness', 'h', 'm'] + get_values_ordered(self.thickness)) 941 | 942 | table2.rows.append([2, 'volume', 'v', 'km3'] + get_values_ordered(self.volume)) 943 | 944 | table2.rows.append([3, 'reservoir_temp', 'Tr', 'oC'] + 945 | get_values_ordered(self.reservoir_temp)) 946 | table2.rows.append([4, 'abandon_temp', 'Ta', 'oC'] + 947 | get_values_ordered(self.abandon_temp)) 948 | table2.rows.append([5, 'porosity', 'phi', '%'] + get_values_ordered(self.porosity)) 949 | table2.rows.append([6, 'rock_specific_heat', 'Cr', 'kJ/kg-oC'] + 950 | get_values_ordered(self.rock_specific_heat)) 951 | table2.rows.append([7, 'fluid_specific_heat', 'Cf', 'kJ/kg-oC'] + 952 | get_values_ordered(self.fluid_specific_heat)) 953 | table2.rows.append([8, 'rock_density', 'rho_r', 'kg/m3'] + 954 | get_values_ordered(self.rock_density)) 955 | table2.rows.append([9, 'fluid_density', 'rho_f', 'kg/m3'] + 956 | get_values_ordered(self.fluid_density)) 957 | # ****** NEW ***** 958 | table2.rows.append([10, 'reservoir_steam_density', 'rho_si', 'kg/m3'] + 959 | get_values_ordered(self.reservoir_steam_density)) 960 | table2.rows.append([11, 'Water Saturation', 'Sw', '%'] + 961 | get_values_ordered(self.reservoir_water_saturation)) 962 | table2.rows.append([12, 'reservoir_steam_enthalpy', 'h_si', 'kJ/kg'] + 963 | get_values_ordered(self.reservoir_steam_enthalpy)) 964 | table2.rows.append([13, 'reservoir_liquid_enthalpy', 'h_li', 'kJ/kg'] + 965 | get_values_ordered(self.reservoir_liquid_enthalpy)) 966 | table2.rows.append([14, 'abandon_liquid_enthalpy', 'h_lf', 'kJ/kg'] + 967 | get_values_ordered(self.abandon_liquid_enthalpy)) 968 | # ****** FIN NEW **** 969 | table2.rows.append([15, 'recovery_factor', 'RF', '%'] + 970 | get_values_ordered(self.recovery_factor)) 971 | table2.rows.append([16, 'conversion_efficiency', 'Ce', '%'] + 972 | get_values_ordered(self.conversion_efficiency)) 973 | table2.rows.append([17, 'plant_net_capacity_factor', 'Pf', '%'] + 974 | get_values_ordered(self.plant_net_capacity_factor)) 975 | table2.rows.append([18, 'lifespan', 't', 'years'] + get_values_ordered(self.lifespan)) 976 | # return table1.get_string() + '\n' + table2.get_string() + '\n' + text 977 | return str(table1) + '\n' + str(table2) + '\n' + text 978 | 979 | 980 | class MonteCarloSimulation(object): 981 | """ 982 | Run Monte Carlo Simulation for figuring out Geothermal Power Energy 983 | 984 | **methods 985 | __init__ 986 | get_iterations 987 | set_iterations 988 | probability_distribution_function 989 | calc_energy_potential 990 | __str__ 991 | """ 992 | 993 | def __init__(self, **kwargs): 994 | self.iterations = 10000 995 | self.calc_time = 0.0 996 | 997 | for key in kwargs.keys(): 998 | setattr(self, key, kwargs[key]) 999 | 1000 | def get_iterations(self): 1001 | """ 1002 | To get iterations value 1003 | 1004 | usage: 1005 | val = get_iterations() 1006 | """ 1007 | return self.iterations 1008 | 1009 | def set_iterations(self, iterations=10000): 1010 | """ 1011 | To set iterations value 1012 | 1013 | usage: 1014 | set_iterations(10000) 1015 | """ 1016 | self.iterations = iterations 1017 | 1018 | def probability_distribution_function(self, val, lognormal_adjust=False): 1019 | """ 1020 | Probability Distribution Function 1021 | 1022 | :param lognormal_adjust: True return log(lognormal_values), 1023 | False return lognormal_values 1024 | :param val: variable that is a dictionary with {'min': 1.0, 'most_likely': 1.0, 1025 | 'max': 1.0, 'mean': 1.0, 'sd': 1.0, 'pdf': 'C'} 1026 | :return pdf: probability distribution values a collection of trials for every PDF, 1027 | Monte Carlo error propagation type 1028 | """ 1029 | 1030 | def my_log_norm(mu, sigma, tag=None): 1031 | """ 1032 | A Log-Normal random variate 1033 | 1034 | Parameters 1035 | ---------- 1036 | mu : scalar 1037 | The location parameter 1038 | sigma : scalar 1039 | The scale parameter (must be positive and non-zero) 1040 | tag : none 1041 | """ 1042 | assert sigma > 0, 'Log-Normal "sigma" must be positive' 1043 | return mc.uv(ss.lognorm(s=sigma, scale=mc.umath.exp(mu)), tag=tag) 1044 | 1045 | mc.npts = self.iterations 1046 | pdf = val['pdf'] 1047 | 1048 | if pdf == 'C': 1049 | return val['most_likely'] 1050 | elif pdf == 'T': 1051 | return mc.Triangular(val['min'], val['most_likely'], val['max']) 1052 | elif pdf == 'U': 1053 | return mc.Uniform(val['min'], val['max']) 1054 | elif pdf == 'N': 1055 | return mc.Normal(val['mean'], val['sd']) 1056 | elif pdf == 'L': 1057 | if lognormal_adjust: 1058 | return mc.umath.log(my_log_norm(val['mean'], val['sd'])) 1059 | else: 1060 | return my_log_norm(val['mean'], val['sd']) 1061 | 1062 | def calc_energy_potential(self, gpp, rtype='ld'): 1063 | """ 1064 | Calculus of Power Energy Assessment 1065 | 1066 | usage: 1067 | gpp = sim.calc_energy_potential(gpp) 1068 | 1069 | :param rtype: reservoir type 'ld' (liquid-dominant) 'tpd' (two-phases dominant) 1070 | :param gpp: Geothermal power Plant object 1071 | :return: a Geothermal power plant object 1072 | """ 1073 | # start = clock() 1074 | start = process_time() 1075 | if type(gpp) is GeothermalPowerPlant: 1076 | # **** CALCULUS OF MOST LIKELY POWER POTENTIAL ***** 1077 | tr = gpp.reservoir_temp['most_likely'] 1078 | ta = gpp.abandon_temp['most_likely'] 1079 | phi = gpp.porosity['most_likely'] 1080 | cr = gpp.rock_specific_heat['most_likely'] 1081 | cf = gpp.fluid_specific_heat['most_likely'] 1082 | rho_r = gpp.rock_density['most_likely'] 1083 | rho_f = gpp.fluid_density['most_likely'] 1084 | rf = gpp.recovery_factor['most_likely'] 1085 | ce = gpp.conversion_efficiency['most_likely'] 1086 | pf = gpp.plant_net_capacity_factor['most_likely'] 1087 | t = gpp.lifespan['most_likely'] 1088 | 1089 | rho_si = gpp.reservoir_steam_density['most_likely'] 1090 | sw = gpp.reservoir_water_saturation['most_likely'] 1091 | h_si = gpp.reservoir_steam_enthalpy['most_likely'] 1092 | h_li = gpp.reservoir_liquid_enthalpy['most_likely'] 1093 | h_lf = gpp.abandon_liquid_enthalpy['most_likely'] 1094 | 1095 | if gpp.volume['pdf'] is None: 1096 | a = gpp.area['most_likely'] 1097 | h = gpp.thickness['most_likely'] 1098 | v = None 1099 | gpp.power_potential['base'] = gpp.power_energy(tr, ta, phi, cr, cf, rho_r, 1100 | rho_f, rf, ce, pf, t, a, h, v, 1101 | rho_si, sw, h_si, h_li, h_lf, rtype) 1102 | else: 1103 | a = None 1104 | h = None 1105 | v = gpp.volume['most_likely'] 1106 | gpp.power_potential['base'] = gpp.power_energy(tr, ta, phi, cr, cf, rho_r, 1107 | rho_f, rf, ce, pf, t, a, h, v, 1108 | rho_si, sw, h_si, h_li, h_lf, rtype) 1109 | # ******** END OF CALCULUS OF MOST LIKELY POWER POTENTIAL ***** 1110 | 1111 | # ***** STOCHASTIC VARIABLES ************** 1112 | tr = self.probability_distribution_function(gpp.reservoir_temp) 1113 | ta = self.probability_distribution_function(gpp.abandon_temp) 1114 | if gpp.porosity['pdf'] == 'L': 1115 | phi = self.probability_distribution_function(gpp.porosity, 1116 | lognormal_adjust=True) 1117 | else: 1118 | phi = self.probability_distribution_function(gpp.porosity) 1119 | 1120 | cr = self.probability_distribution_function(gpp.rock_specific_heat) 1121 | cf = self.probability_distribution_function(gpp.fluid_specific_heat) 1122 | rho_r = self.probability_distribution_function(gpp.rock_density) 1123 | rho_f = self.probability_distribution_function(gpp.fluid_density) 1124 | rf = self.probability_distribution_function(gpp.recovery_factor) 1125 | ce = self.probability_distribution_function(gpp.conversion_efficiency) 1126 | pf = self.probability_distribution_function(gpp.plant_net_capacity_factor) 1127 | t = self.probability_distribution_function(gpp.lifespan) 1128 | 1129 | rho_si = self.probability_distribution_function(gpp.reservoir_steam_density) 1130 | sw = self.probability_distribution_function(gpp.reservoir_water_saturation) 1131 | h_si = self.probability_distribution_function(gpp.reservoir_steam_enthalpy) 1132 | h_li = self.probability_distribution_function(gpp.reservoir_liquid_enthalpy) 1133 | h_lf = self.probability_distribution_function(gpp.abandon_liquid_enthalpy) 1134 | 1135 | if gpp.volume['pdf'] is None: 1136 | a = self.probability_distribution_function(gpp.area) 1137 | h = self.probability_distribution_function(gpp.thickness) 1138 | v = None 1139 | gpp.power_potential['pdf'] = gpp.power_energy(tr, ta, phi, cr, cf, rho_r, 1140 | rho_f, rf, ce, pf, t, a, h, v, 1141 | rho_si, sw, h_si, h_li, h_lf, rtype) 1142 | else: 1143 | a = None 1144 | h = None 1145 | v = self.probability_distribution_function(gpp.volume) 1146 | gpp.power_potential['pdf'] = gpp.power_energy(tr, ta, phi, cr, cf, rho_r, 1147 | rho_f, rf, ce, pf, t, a, h, v, 1148 | rho_si, sw, h_si, h_li, h_lf, rtype) 1149 | # ***** END OF STOCHASTIC VARIABLES ************** 1150 | gpp.power_potential['iterations'] = self.iterations 1151 | p_base = gpp.power_potential['pdf'] <= gpp.power_potential['base'] 1152 | gpp.power_potential['statistics']['p_base'] = 1.0 - p_base 1153 | gpp.power_potential['statistics']['mean'] = gpp.power_potential['pdf'].mean 1154 | gpp.power_potential['statistics']['sd'] = gpp.power_potential['pdf'].std 1155 | gpp.power_potential['statistics']['skew'] = gpp.power_potential['pdf'].skew 1156 | gpp.power_potential['statistics']['kurt'] = gpp.power_potential['pdf'].kurt 1157 | gpp.power_potential['statistics']['min'] = gpp.power_potential['pdf'].percentile(0) 1158 | gpp.power_potential['statistics']['max'] = gpp.power_potential['pdf'].percentile(1) 1159 | gpp.power_potential['statistics']['percentiles'] = [] 1160 | for i in range(0, 19): 1161 | val = 5 * i + 5 1162 | percentile = gpp.power_potential['pdf'].percentile(val / 100.0) 1163 | gpp.power_potential['statistics']['percentiles'].append(percentile) 1164 | # self.calc_time = clock() - start 1165 | self.calc_time = process_time() - start 1166 | if gpp.volume['pdf'] is not None: 1167 | print('HINT: VOLUME WAS USED.\nSIMULATION ... DONE') 1168 | elif gpp.area['pdf'] is not None and gpp.thickness['pdf'] is not None and gpp.volume['pdf'] is None: 1169 | print('HINT: AREA AND THICKNESS WERE USED.\nSIMULATION ... DONE!') 1170 | else: 1171 | print('ERROR: Distribution function must be valid: T, N, C, U, L\nSIMULATION ... FAILURE !\n') 1172 | 1173 | return gpp 1174 | 1175 | def __str__(self): 1176 | """Print Iterations and calculation time""" 1177 | return 'Iterations: {0}\nCalculation time: {1}s'.format(self.iterations, 1178 | self.calc_time) 1179 | 1180 | 1181 | class Tools(object): 1182 | """ 1183 | Object with helping tools 1184 | 1185 | **methods 1186 | __init__ 1187 | get_num_figures 1188 | get_num_figures_plot 1189 | get_eng_format 1190 | get_hist_bins 1191 | set_num_figures 1192 | set_num_figures_plot 1193 | set_eng_format 1194 | set_hist_bins 1195 | figures_to_present 1196 | eng_fmt 1197 | print_results 1198 | read_file_csv 1199 | write_file_cvs 1200 | set_backend_figure 1201 | plot_pdf 1202 | show_pdf 1203 | """ 1204 | 1205 | def __init__(self, **kwargs): 1206 | self.fn_input = 'input.csv' 1207 | self.fn_output = 'output.csv' 1208 | self.data_base = None 1209 | self.num_figures = 6 1210 | self.num_figures_plot = 3 1211 | self.eng_format = 'M' 1212 | self.hist_bins = 25 1213 | for key in kwargs.keys(): 1214 | setattr(self, key, kwargs[key]) 1215 | 1216 | def get_num_figures(self): 1217 | """ 1218 | To get num_figures 1219 | 1220 | usage: 1221 | val = get_num_figures() 1222 | """ 1223 | return self.num_figures 1224 | 1225 | def get_num_figures_plot(self): 1226 | """ 1227 | To get num_figures_plot 1228 | 1229 | usage: 1230 | val = get_num_figures_plot() 1231 | """ 1232 | return self.num_figures_plot 1233 | 1234 | def get_eng_format(self): 1235 | """ 1236 | To get eng_format 1237 | 1238 | usage: 1239 | val = get_eng_format() 1240 | """ 1241 | return self.eng_format 1242 | 1243 | def get_hist_bins(self): 1244 | """ 1245 | To get hist_bins 1246 | 1247 | usage: 1248 | val = get_hist_bins() 1249 | """ 1250 | return self.hist_bins 1251 | 1252 | def set_num_figures(self, num_figures=6): 1253 | """ 1254 | To set num_figures 1255 | 1256 | usage: 1257 | set_num_figures(6) 1258 | """ 1259 | self.num_figures = num_figures 1260 | 1261 | def set_num_figures_plot(self, num_figures_plot=3): 1262 | """ 1263 | To set num_figures_plot 1264 | 1265 | usage: 1266 | set_num_figures_plot(2) 1267 | """ 1268 | self.num_figures_plot = num_figures_plot 1269 | 1270 | def set_eng_format(self, eng_format='M'): 1271 | """ 1272 | To set eng_format 1273 | 1274 | usage: 1275 | set_eng_format('M') 1276 | """ 1277 | self.eng_format = eng_format 1278 | 1279 | def set_hist_bins(self, hist_bins=25): 1280 | """ 1281 | To set hist_bins 1282 | 1283 | usage: 1284 | set_hist_bins(25) 1285 | """ 1286 | self.hist_bins = hist_bins 1287 | 1288 | @staticmethod 1289 | def figures_to_present(value=1.0, figures=1): 1290 | """ 1291 | present the number by number of figures selected. 1292 | return string 1293 | """ 1294 | fi = '%.' + str(figures) + 'g' 1295 | return fi % value 1296 | 1297 | def eng_fmt(self, value=1.0, option="1", *arg): 1298 | """ 1299 | Present the data as Engineering format 1300 | 1301 | case1: eng_fmt(value=3538, option="k") -> return 3.538 float 1302 | case2: eng_fmt(value=3538, option="k", 2) -> return 3.5 string 1303 | """ 1304 | switcher = {"p": 1e-12, "n": 1e-9, "u": 1e-6, "m": 1e-3, "c": 1e-2, "1": 1.0e0, 1305 | "k": 1.0e3, "M": 1.0e6, "G": 1.0e9, "T": 1.0e12} 1306 | if len(arg): 1307 | figure = arg 1308 | value = value / switcher.get(option, 1.0) 1309 | return self.figures_to_present(value, figure[0]) 1310 | else: 1311 | return value / switcher.get(option, 1.0) 1312 | 1313 | def print_results(self, gpp): 1314 | """ 1315 | Print results 1316 | 1317 | :param gpp: Geothermal power plant object 1318 | :return: nothing 1319 | """ 1320 | if type(gpp.power_potential['pdf']) is not mc.UncertainFunction: 1321 | print('Reservoir simulation has not been done!') 1322 | return None 1323 | 1324 | base = gpp.power_potential['base'] 1325 | e = gpp.power_potential['pdf'] 1326 | ite = str(gpp.power_potential['iterations']) 1327 | x = e <= base 1328 | x = 1.0 - x 1329 | x = self.eng_fmt(x, '1', self.num_figures) 1330 | 1331 | print("\nMAIN INFORMATION:") 1332 | print("Most Likely PowerGeneration [" + self.eng_format + 'We]=' + self.eng_fmt(base, self.eng_format, 1333 | self.num_figures)) 1334 | print("Probability of " + self.eng_fmt(base, self.eng_format, 1335 | self.num_figures) + "[" + self.eng_format + 'We]=' + x) 1336 | print("P10% [" + self.eng_format + 'We]=' + self.eng_fmt(e.percentile(0.1), self.eng_format, self.num_figures)) 1337 | print("\nSTATISTICAL ANALYSIS:") 1338 | print("Iterations=" + ite) 1339 | print("Mean=" + self.eng_fmt(e.mean, self.eng_format, self.num_figures) + "[" + self.eng_format + 'We]') 1340 | print("Median=" + self.eng_fmt(e.percentile(0.5), self.eng_format, 1341 | self.num_figures) + "[" + self.eng_format + 'We]') 1342 | print("Standard Deviation=" + self.eng_fmt(e.std, self.eng_format, 1343 | self.num_figures) + "[" + self.eng_format + 'We]') 1344 | print("Skew=" + self.figures_to_present(e.skew, self.num_figures)) 1345 | print("Kurt=" + self.figures_to_present(e.kurt, self.num_figures)) 1346 | print("Minimum=" + self.eng_fmt(e.percentile(0), self.eng_format, 1347 | self.num_figures) + "[" + self.eng_format + 'We]') 1348 | print("Maximum=" + self.eng_fmt(e.percentile(1), self.eng_format, 1349 | self.num_figures) + "[" + self.eng_format + 'We]') 1350 | for i in range(0, 19): 1351 | val = 5 * i + 5 1352 | print("P" + str(val) + '%=' + self.eng_fmt(e.percentile(val / 100.0), self.eng_format, self.num_figures) + 1353 | "[" + self.eng_format + 'We]') 1354 | print('\nEND') 1355 | 1356 | def read_file_csv(self, fn_input=None): 1357 | """ 1358 | Read File coma separated value *.csv 1359 | 1360 | usage: gpp = tools.read_file_csv('file_name') 1361 | 1362 | :param fn_input: File name string 1363 | :return gpp: Geothermal Power Plant object 1364 | """ 1365 | if fn_input is not None: 1366 | self.fn_input = fn_input 1367 | fil = {'area': 0, 'thickness': 1, 'volume': 2, 'reservoir_temp': 3, 1368 | 'abandon_temp': 4, 'porosity': 5, 'rock_specific_heat': 6, 1369 | 'fluid_specific_heat': 7, 'rock_density': 8, 'fluid_density': 9, 1370 | 'reservoir_water_saturation': 10, 'recovery_factor': 11, 'conversion_efficiency': 12, 1371 | 'plant_net_capacity_factor': 13, 'lifespan': 14, 'name': 0, 'location': 0} 1372 | col = np.dtype([ 1373 | ('item', str, 50), 1374 | ('name', str, 50), 1375 | ('lat', float), 1376 | ('lon', float), 1377 | ('variable', str, 50), 1378 | ('symbol', str, 50), 1379 | ('unit', str, 50), 1380 | ('min', float), 1381 | ('most_likely', float), 1382 | ('max', float), 1383 | ('mean', float), 1384 | ('sd', float), 1385 | ('pdf', str, 50)]) 1386 | self.data_base = np.genfromtxt(self.fn_input, delimiter=',', comments='#', dtype=col) 1387 | gpp = GeothermalPowerPlant() 1388 | gpp.name = self.data_base['name'][fil['name']] 1389 | for key in gpp.location: 1390 | gpp.location[key] = self.data_base[key][fil['location']] 1391 | 1392 | # ------ New --------------- 1393 | for key in gpp.area: 1394 | val = self.data_base[key][fil['area']] 1395 | if val == 'None' or val == 'none': 1396 | gpp.area[key] = None 1397 | else: 1398 | gpp.area[key] = val 1399 | 1400 | for key in gpp.thickness: 1401 | val = self.data_base[key][fil['thickness']] 1402 | if val == 'None' or val == 'none': 1403 | gpp.thickness[key] = None 1404 | else: 1405 | gpp.thickness[key] = val 1406 | 1407 | for key in gpp.volume: 1408 | val = self.data_base[key][fil['volume']] 1409 | if val == 'None' or val == 'none': 1410 | gpp.volume[key] = None 1411 | else: 1412 | gpp.volume[key] = val 1413 | 1414 | # ---- New finished --------------- 1415 | 1416 | for key in gpp.reservoir_temp: 1417 | gpp.reservoir_temp[key] = self.data_base[key][fil['reservoir_temp']] 1418 | for key in gpp.abandon_temp: 1419 | gpp.abandon_temp[key] = self.data_base[key][fil['abandon_temp']] 1420 | for key in gpp.porosity: 1421 | gpp.porosity[key] = self.data_base[key][fil['porosity']] 1422 | for key in gpp.rock_specific_heat: 1423 | gpp.rock_specific_heat[key] = self.data_base[key][fil['rock_specific_heat']] 1424 | for key in gpp.fluid_specific_heat: 1425 | gpp.fluid_specific_heat[key] = self.data_base[key][fil['fluid_specific_heat']] 1426 | for key in gpp.rock_density: 1427 | gpp.rock_density[key] = self.data_base[key][fil['rock_density']] 1428 | for key in gpp.fluid_density: 1429 | gpp.fluid_density[key] = self.data_base[key][fil['fluid_density']] 1430 | 1431 | # ***** NEW RESERVOIR VARIABLES FOR TWO-PHASES RESERVOIR ********** 1432 | for key in gpp.reservoir_water_saturation: 1433 | gpp.reservoir_water_saturation[key] = self.data_base[key][fil['reservoir_water_saturation']] 1434 | 1435 | ti_min = gpp.reservoir_temp['min'] 1436 | ti_mos = gpp.reservoir_temp['most_likely'] 1437 | ti_max = gpp.reservoir_temp['max'] 1438 | gpp.reservoir_steam_enthalpy['min'] = IAPWS95(T=ti_min + 273.15, x=1.0).h 1439 | gpp.reservoir_steam_enthalpy['most_likely'] = IAPWS95(T=ti_mos + 273.15, x=1.0).h 1440 | gpp.reservoir_steam_enthalpy['max'] = IAPWS95(T=ti_max + 273.15, x=1.0).h 1441 | 1442 | gpp.reservoir_liquid_enthalpy['min'] = IAPWS95(T=ti_min + 273.15, x=0.0).h 1443 | gpp.reservoir_liquid_enthalpy['most_likely'] = IAPWS95(T=ti_mos + 273.15, x=0.0).h 1444 | gpp.reservoir_liquid_enthalpy['max'] = IAPWS95(T=ti_max + 273.15, x=0.0).h 1445 | 1446 | gpp.reservoir_steam_density['min'] = IAPWS95(T=ti_min + 273.15, x=1.0).rho 1447 | gpp.reservoir_steam_density['most_likely'] = IAPWS95(T=ti_mos + 273.15, x=1.0).rho 1448 | gpp.reservoir_steam_density['max'] = IAPWS95(T=ti_max + 273.15, x=1.0).rho 1449 | 1450 | if gpp.abandon_temp['pdf'] == 'C': 1451 | tf_min = gpp.abandon_temp['most_likely'] 1452 | tf_mos = gpp.abandon_temp['most_likely'] 1453 | tf_max = gpp.abandon_temp['most_likely'] 1454 | else: 1455 | tf_min = gpp.abandon_temp['min'] 1456 | tf_mos = gpp.abandon_temp['most_likely'] 1457 | tf_max = gpp.abandon_temp['max'] 1458 | gpp.abandon_liquid_enthalpy['min'] = IAPWS95(T=tf_min + 273.15, x=0.0).h 1459 | gpp.abandon_liquid_enthalpy['most_likely'] = IAPWS95(T=tf_mos + 273.15, x=0.0).h 1460 | gpp.abandon_liquid_enthalpy['max'] = IAPWS95(T=tf_max + 273.15, x=0.0).h 1461 | 1462 | # ***** END RESERVOIR VARIABLES FOR TWO-PHASES RESERVOIR ********** 1463 | 1464 | for key in gpp.conversion_efficiency: 1465 | gpp.conversion_efficiency[key] = self.data_base[key][fil['conversion_efficiency']] 1466 | for key in gpp.recovery_factor: 1467 | gpp.recovery_factor[key] = self.data_base[key][fil['recovery_factor']] 1468 | for key in gpp.plant_net_capacity_factor: 1469 | gpp.plant_net_capacity_factor[key] = self.data_base[key][ 1470 | fil['plant_net_capacity_factor']] 1471 | for key in gpp.lifespan: 1472 | gpp.lifespan[key] = self.data_base[key][fil['lifespan']] 1473 | print('READ FILE ... OK') 1474 | return gpp 1475 | 1476 | def write_file_cvs(self, gpp, fn_output=None): 1477 | """ 1478 | Write File coma separated value *.csv 1479 | """ 1480 | if fn_output is not None: 1481 | self.fn_output = fn_output 1482 | output_file = open(self.fn_output, 'w') 1483 | 1484 | def get_values(val): 1485 | keys = ['min', 'most_likely', 'max', 'mean', 'sd', 'pdf'] 1486 | string = '' 1487 | for i in [0, 1, 2, 3, 4, 5]: 1488 | st = str(val[keys[i]]) 1489 | if st == 'nan': 1490 | string += ',' 1491 | else: 1492 | string += ',' + st 1493 | return string + '\n' 1494 | 1495 | text = '#Imput file for the Assessment of Power Energy Generation by using ' \ 1496 | 'Volumetric Method and Monte Carlo Probabilistic analysis,' \ 1497 | ',,,,,,,,,,,\n#Item,Name_Place,Latitude,Longitude,Reservoir_Properties,' \ 1498 | 'Symbol,Units,Min,Most_Likely,Max,Mean,Standard_Deviation,Distribution_Type\n' 1499 | text += '0,' + gpp.name + ',' + str(gpp.location['lat']) + ',' + str(gpp.location['lon']) + \ 1500 | ',area,A,km2' + get_values(gpp.area) 1501 | text += '1,,,,thickness,h,m' + get_values(gpp.thickness) 1502 | 1503 | text += '2,,,,volume,v,km3' + get_values(gpp.volume) 1504 | 1505 | text += '3,,,,reservoir_temp,Tr,oC' + get_values(gpp.reservoir_temp) 1506 | text += '4,,,,abandon_temp,Ta,oC' + get_values(gpp.abandon_temp) 1507 | text += '5,,,,porosity,phi,%' + get_values(gpp.porosity) 1508 | text += '6,,,,rock_specific_heat,Cr,kJ/kg-oC' + get_values(gpp.rock_specific_heat) 1509 | text += '7,,,,fluid_specific_heat,Cf,kJ/kg-oC' + get_values(gpp.fluid_specific_heat) 1510 | text += '8,,,,rock_density,rho_r,kg/m3' + get_values(gpp.rock_density) 1511 | text += '9,,,,fluid_density,rho_f,kg/m3' + get_values(gpp.fluid_density) 1512 | text += '10,,,,WaterSaturation,Sw,%' + get_values(gpp.reservoir_water_saturation) 1513 | text += '11,,,,recovery_factor,Rf,%' + get_values(gpp.recovery_factor) 1514 | text += '12,,,,conversion_efficiency,Ce,%' + get_values(gpp.conversion_efficiency) 1515 | text += '13,,,,plant_net_capacity_factor,Pf,%' + \ 1516 | get_values(gpp.plant_net_capacity_factor) 1517 | text += '14,,,,lifespan,t,years' + get_values(gpp.lifespan) 1518 | output_file.write(text) 1519 | output_file.close() 1520 | print('WRITE TO FILE ... OK') 1521 | 1522 | @staticmethod 1523 | def set_backend_figure(backend='Qt4Agg'): 1524 | """ 1525 | Set matplotlib backend for improving the figure presentation 1526 | By default, python uses TkAgg and ipython uses inline. this function has Qt4Agg. 1527 | Use this function only when you need change backend otherwise refrain to use it. 1528 | at that time to change the backend, restart python kernel. 1529 | """ 1530 | try: 1531 | plt.switch_backend(backend) 1532 | except ValueError: 1533 | print('{0} Not found. Default has set up'.format(backend)) 1534 | except ImportError: 1535 | print('{0} Not found. Default has set up'.format(backend)) 1536 | 1537 | def plot_pdf(self, gpp, type_graph='hist', show=False, x_lim=None, hist_fitted_curve=True, 1538 | hist_edge_color='g', hist_line_width=0, hist_type='stepfilled', fig_dpi=None, 1539 | fig_size=None, axis_label_size=10, tick_params_size=8, text_size=8, 1540 | title_size=10, legend_text_size=10): 1541 | """ 1542 | Plot probability distribution function 1543 | 1544 | :param axis_label_size: axis label size (int) 1545 | :param tick_params_size: tick size (int) 1546 | :param text_size: text size (int) 1547 | :param title_size: title font size (int) 1548 | :param legend_text_size: legend font size (int) 1549 | :param fig_size: figure size, tuple fig_sie=(width, height) 1550 | :param fig_dpi: figure dpi, integer 1551 | :param hist_type: histogram type 1552 | :param hist_line_width: line width in histogram 1553 | :param hist_edge_color: show edge in histogram 1554 | :param hist_fitted_curve: show fitted curve in histogram 1555 | :param type_graph: 'hist' = hist, 'lower' = Cumulative Relative Frequency (lower), 1556 | 'higher' = Cumulative relative frequency (higher), 1557 | 'linear' = linear plot summary 1558 | :param gpp: Geothermal power plant object 1559 | :param show: if True will show figure 1560 | :param x_lim: tuple x_lim=(min,max) 1561 | :return: plot 1562 | """ 1563 | if type_graph is 'hist': 1564 | type_graph = False 1565 | elif type_graph is 'lower': 1566 | type_graph = True 1567 | elif type_graph is 'higher': 1568 | type_graph = -1 1569 | elif type_graph is 'linear': 1570 | pass 1571 | else: 1572 | type_graph = False 1573 | if type(gpp.power_potential['pdf']) is not mc.UncertainFunction: 1574 | print('Reservoir simulation has not been done!') 1575 | return None 1576 | num_div = self.eng_fmt(1, self.eng_format) 1577 | if type_graph is 'linear': 1578 | if fig_size is None: 1579 | fig_size = (10, 1) 1580 | plt.figure(figsize=fig_size, facecolor='w', dpi=fig_dpi) 1581 | plt.tick_params( 1582 | axis='y', 1583 | which='both', 1584 | left='off', 1585 | right='off', 1586 | labelleft='off', 1587 | labelright='off' 1588 | ) 1589 | plt.tick_params( 1590 | axis='x', 1591 | direction='out', 1592 | top='off', 1593 | labelsize=tick_params_size 1594 | ) 1595 | p_base_loc = 100 - gpp.power_potential['statistics']['p_base'] * 100.0 1596 | base = gpp.power_potential['base'] 1597 | y = gpp.power_potential['statistics']['percentiles'] 1598 | plt.barh(1, 5, left=0, color='#228B22', align='center', edgecolor='none') 1599 | plt.barh(1, 5, left=5, color='#228B22', align='center', edgecolor='none') 1600 | plt.barh(1, p_base_loc - 10, left=10, color='#FFA500', align='center', 1601 | edgecolor='none') 1602 | plt.barh(1, 95 - p_base_loc, left=p_base_loc, color='#FF6347', align='center', 1603 | edgecolor='none') 1604 | plt.barh(1, 5, left=95, color='#FF6347', align='center', edgecolor='none') 1605 | x_ticks_label = [self.eng_fmt(y[0], self.eng_format, self.num_figures_plot), 1606 | self.eng_fmt(y[1], self.eng_format, self.num_figures_plot), 1607 | self.eng_fmt(base, self.eng_format, self.num_figures_plot), 1608 | self.eng_fmt(y[18], self.eng_format, self.num_figures_plot)] 1609 | plt.xticks([5, 10, p_base_loc, 95], x_ticks_label) 1610 | plt.xlabel("Power Generation [" + self.eng_format + "We]", 1611 | fontsize=axis_label_size) 1612 | plt.xlim(0, 100) 1613 | plt.text(4, 1, '95%') 1614 | plt.text(9, 1, '90%') 1615 | plt.text(p_base_loc - 2, 1, self.eng_fmt(100 - p_base_loc, '1', 3) + '%') 1616 | plt.text(94, 1, '5%') 1617 | plt.text(4, 0.8, 'Proven reserves') 1618 | plt.text(p_base_loc - 8, 0.8, 'Most likely reserves') 1619 | plt.text(82, 0.8, 'Maximum reserves') 1620 | if show: 1621 | self.show_pdf() 1622 | return None 1623 | 1624 | base = gpp.power_potential['base'] 1625 | e = gpp.power_potential['pdf'] 1626 | ite = gpp.power_potential['iterations'] 1627 | 1628 | fig = plt.figure(facecolor='w', dpi=fig_dpi, figsize=fig_size) 1629 | ax = fig.add_subplot(111) 1630 | 1631 | mcpts_data = [i * num_div for i in e._mcpts] 1632 | 1633 | if x_lim is None: 1634 | r_min = min(mcpts_data) 1635 | r_max = max(mcpts_data) 1636 | else: 1637 | r_min = x_lim[0] 1638 | r_max = x_lim[1] 1639 | if not type_graph: 1640 | # ax.hist(mcpts_data, bins=self.hist_bins, normed=True, label='Estimated', 1641 | ax.hist(mcpts_data, bins=self.hist_bins, density=True, label='Estimated', 1642 | color='#3cb371', cumulative=type_graph, range=(r_min, r_max), 1643 | histtype=hist_type, edgecolor=hist_edge_color, 1644 | linewidth=hist_line_width) # Probability 1645 | if hist_fitted_curve: 1646 | p = ss.kde.gaussian_kde(mcpts_data) 1647 | xp = np.linspace(r_min, r_max, 100) 1648 | ax.plot(xp, p.evaluate(xp), color='r', label='Fitted', linewidth=2) 1649 | else: 1650 | # ax.hist(mcpts_data, bins=self.hist_bins, normed=True, label='Estimated', color='r', 1651 | ax.hist(mcpts_data, bins=self.hist_bins, density=True, label='Estimated', color='r', 1652 | cumulative=type_graph, range=(r_min, r_max), histtype='step') 1653 | figure01_ymin, figure01_ymax = ax.get_ylim() 1654 | ax.tick_params(axis='x', labelsize=tick_params_size) 1655 | ax.tick_params(axis='y', labelsize=tick_params_size) 1656 | ax.yaxis.set_major_formatter(FormatStrFormatter('%.2g')) 1657 | ax.xaxis.set_major_formatter(FormatStrFormatter('%.4g')) 1658 | p05 = e.percentile(0.05) * num_div 1659 | ax.axvline(p05, color='#FFA500', linestyle='--', linewidth=2) 1660 | ax.text(p05, figure01_ymax * 0.9, 1661 | 'P5=' + self.eng_fmt(p05, '1', self.num_figures_plot) + "[" + 1662 | self.eng_format + 'We]', fontsize=text_size, horizontalalignment='center') 1663 | p10 = e.percentile(0.1) * num_div 1664 | ax.axvline(p10, color='r', linestyle='--', linewidth=2) 1665 | ax.text(p10, figure01_ymax * 0.05, 1666 | 'P10=' + self.eng_fmt(p10, '1', self.num_figures_plot) + "[" + 1667 | self.eng_format + 'We]', fontsize=text_size, horizontalalignment='left', 1668 | color='k') 1669 | p95 = e.percentile(0.95) * num_div 1670 | ax.axvline(p95, color='y', linestyle='--', linewidth=2) 1671 | ax.text(p95, figure01_ymax * 0.2, 1672 | 'P95=' + self.eng_fmt(p95, '1', self.num_figures_plot) + "[" + 1673 | self.eng_format + 'We]', fontsize=text_size, horizontalalignment='center') 1674 | p_base = base * num_div 1675 | x = self.eng_fmt(100 - 100 * (e <= base), '1', self.num_figures_plot) 1676 | ax.axvline(p_base, color='b', linestyle='--', linewidth=2) 1677 | ax.text(p_base, figure01_ymax * 0.5, 1678 | 'P(' + self.eng_fmt(p_base, '1', self.num_figures_plot) + self.eng_format + 1679 | 'We)=' + x + '%', fontsize=text_size, horizontalalignment='left') 1680 | ax.set_title("Power Energy Available for " + str(gpp.lifespan['most_likely']) + 1681 | ' years' + '\n' + gpp.name + '. Iterations: ' + str(ite), 1682 | fontsize=title_size) 1683 | ax.set_xlabel("Power Generation [" + self.eng_format + "We]", fontsize=axis_label_size) 1684 | if type_graph == -1: 1685 | ax.set_ylabel("Cumulative Relative Frequency [higher than]", 1686 | fontsize=axis_label_size) 1687 | elif type_graph: 1688 | ax.set_ylabel("Cumulative Relative Frequency [lower than]", 1689 | fontsize=axis_label_size) 1690 | elif not type_graph: 1691 | ax.set_ylabel(r"F(x)=100$\Delta$xFreq", fontsize=axis_label_size) 1692 | ax.grid(linestyle=':') 1693 | ax.legend(loc=1, fontsize=legend_text_size) 1694 | if show: 1695 | self.show_pdf() 1696 | 1697 | @staticmethod 1698 | def show_pdf(): 1699 | plt.show() 1700 | 1701 | """ 1702 | RUN SIMULATION 1703 | To import beta-library from specific folder: 1704 | In[1]: import sys 1705 | In[2]: sys.path.append('/home/user/gppeval_beta') 1706 | In[3]: import gppeval 1707 | In[4]: gppeval.__version__ 1708 | Out[5]: '2024.08.04.0.1.dev1' 1709 | """ 1710 | 1711 | if __name__ == "__main__": 1712 | # Test: Input data manually 1713 | tools = Tools() 1714 | sim = MonteCarloSimulation() 1715 | plant = GeothermalPowerPlant() 1716 | plant.set_name('Nombre de Jesus. El Salvador') 1717 | plant.set_location(lat=14, lon=-88.73) 1718 | plant.set_area(min=5, most_likely=6, max=7, mean=0, sd=0, pdf='T', doc=5) 1719 | plant.set_thickness(min=450, most_likely=500, max=600, mean=0.0, sd=0.0, pdf='T') 1720 | plant.set_volume(pdf=None) 1721 | plant.set_reservoir_temp(min=130, most_likely=160, max=163, mean=0.0, sd=0.0, pdf='T') 1722 | plant.set_abandon_temp(min=0.0, most_likely=80, max=0.0, mean=0.0, sd=0.0, pdf='C') 1723 | plant.set_porosity(min=0.0, most_likely=0.06, max=0.0, mean=0.06, sd=0.02, pdf='L') 1724 | plant.set_rock_specific_heat(min=0.85, most_likely=0.85, max=0.9, mean=0.0, sd=0.0, 1725 | pdf='T') 1726 | plant.set_fluid_specific_heat(min=0, most_likely=5.18, max=0, mean=0.0, sd=0.0, pdf='C') 1727 | plant.set_rock_density(min=0, most_likely=2500, max=0, mean=0.0, sd=0.0, pdf='C') 1728 | plant.set_fluid_density(min=0, most_likely=764.45, max=0, mean=0.0, sd=0.0, pdf='C') 1729 | plant.set_recovery_factor(min=0.0, most_likely=0.23, max=0.0, mean=0.0, sd=0.0, pdf='C') 1730 | plant.set_conversion_efficiency(min=0.1, most_likely=0.12, max=0.2, mean=0.0, sd=0.0, 1731 | pdf='T') 1732 | plant.set_plant_net_capacity_factor(min=0.9, most_likely=0.95, max=1.0, mean=0.0, sd=0.0, 1733 | pdf='T') 1734 | plant.set_lifespan(min=0, most_likely=25, max=0, mean=0.0, sd=0.0, pdf='C') 1735 | 1736 | plant = sim.calc_energy_potential(plant) 1737 | tools.print_results(plant) 1738 | -------------------------------------------------------------------------------- /gppeval/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpocasangre/gppeval/55b08591fbdae615ff2eef670cc69dde07316c18/gppeval/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /gppeval/example/reservoir_properties_list.csv: -------------------------------------------------------------------------------- 1 | #Imput file for the Assessment of Power Energy Generation by using Volumetric Method and Mote Carlo Probabilistic analysis,,,,,,,,,,,, 2 | #Item,Name_Place,Latitude,Longitude,Reservoir_Properties,Symbol,Units,Min,Most_Likely,Max,Mean,Standard_Deviation,Distribution_Type 3 | 0,Nombre de Jesus. Chalatenango,14.00061,-88.73744,ReservoirArea,A,km2,5,6,7,0,0,T 4 | 1,,,,Thickness,h,m,450,500,600,0,0,T 5 | 2,,,,Volume,v,km3,4,6,8.2,0,0,none 6 | 3,,,,ReservoirTemperature,Tr,oC,130,160,163,0,0,T 7 | 4,,,,AbandonTemperature,Ta,oC,0,105.36,0,0,0,C 8 | 5,,,,Porosity,Phi,%,0,0.06,0,0.06,0.02,L 9 | 6,,,,RockSpecificHeat,Cr,kJ/kg-oC,0.85,0.85,0.9,0,0,T 10 | 7,,,,WaterSpecificHeat ,Cf,kJ/kg-oC,0,5.18,0,0,0,C 11 | 8,,,,RockDensity,rho_r,kg/m3,0,2500,0,0,0,C 12 | 9,,,,WaterDensity,rho_f,kg/m3,0,764.45,0,0,0,C 13 | 10,,,,RecoveryFactor,Rf,%,0.08,0.14,0.2,0,0,U 14 | 11,,,,ConversionEfficiency,Ce,%,0,0.25,0,0,0,C 15 | 12,,,,PowerFactor,Pf,%,0.9,0.95,1,0,0,T 16 | 13,,,,LifeSpan,t,years,0,25,0,0,0,C 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | import gppeval 4 | 5 | 6 | def read(file_name): 7 | return open(os.path.join(os.path.dirname(__file__), file_name)).read() 8 | 9 | readme = 'README.rst' 10 | 11 | setup( 12 | name=gppeval.__module_name__, 13 | version=gppeval.__version__, 14 | description=gppeval.__description__, 15 | url=gppeval.__url__, 16 | author=gppeval.__author__, 17 | author_email=gppeval.__author_email__, 18 | license=gppeval.__license__, 19 | long_description=read(readme), 20 | packages=find_packages(exclude=['test*']), 21 | # include_package_data=True, 22 | keywords=['monte carlo latin hypercube geothermal power potential volumetric method geothermal reservoir'], 23 | install_requires=['numpy', 24 | 'scipy', 25 | 'matplotlib', 26 | 'mcerp', 27 | 'beautifultable', 28 | 'iapws'], 29 | classifiers=['Development Status :: 4 - Beta', 30 | 'Environment :: Console', 31 | 'Environment :: MacOS X', 32 | 'Environment :: X11 Applications', 33 | 'Environment :: Win32 (MS Windows)', 34 | 'Natural Language :: English', 35 | 'Operating System :: Microsoft', 36 | 'Operating System :: POSIX :: Linux', 37 | 'Operating System :: MacOS :: MacOS X', 38 | 'License :: OSI Approved :: MIT License', 39 | 'Programming Language :: Python :: 2.7', 40 | 'Programming Language :: Python :: 3.8'], 41 | zip_safe=False, 42 | #package_data={ 43 | # 'gppeval': ['example/example.ipynb', 44 | # 'example/reservoir_properties_list.csv', 45 | # 'example/testGppeval.py'], 46 | #}, 47 | ) 48 | --------------------------------------------------------------------------------