gimu 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gimu/__init__.py +0 -0
- gimu/config.py +82 -0
- gimu/easy_date.py +209 -0
- gimu/geo_common.py +222 -0
- gimu/gmf/__init__.py +0 -0
- gimu/gmf/data.py +118 -0
- gimu/gmf/modifier.py +129 -0
- gimu/project_cli.py +56 -0
- gimu/save2incon.py +30 -0
- gimu/t2listingh5.py +375 -0
- gimu/waiwera_copy.py +43 -0
- gimu/waiwera_listing.py +587 -0
- gimu-0.4.0.dist-info/METADATA +86 -0
- gimu-0.4.0.dist-info/RECORD +17 -0
- gimu-0.4.0.dist-info/WHEEL +4 -0
- gimu-0.4.0.dist-info/entry_points.txt +4 -0
- gimu-0.4.0.dist-info/licenses/LICENSE.txt +9 -0
gimu/__init__.py
ADDED
|
File without changes
|
gimu/config.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
""" CONFIG
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Singleton(object):
|
|
6
|
+
__single = None # the one, true Singleton
|
|
7
|
+
|
|
8
|
+
def __new__(classtype, *args, **kwargs):
|
|
9
|
+
# Check to see if a __single exists already for this class
|
|
10
|
+
# Compare class types instead of just looking for None so
|
|
11
|
+
# that subclasses will create their own __single objects
|
|
12
|
+
if classtype != type(classtype.__single):
|
|
13
|
+
classtype.__single = object.__new__(classtype, *args, **kwargs)
|
|
14
|
+
return classtype.__single
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
#class config(Singleton):
|
|
18
|
+
class config(object):
|
|
19
|
+
def __init__(self,filename=''):
|
|
20
|
+
self.empty()
|
|
21
|
+
if filename: self.read_from_file(filename)
|
|
22
|
+
def empty(self):
|
|
23
|
+
self._config_entries = {}
|
|
24
|
+
|
|
25
|
+
def read_from_file(self,filename=None):
|
|
26
|
+
""" read config from a whole file, until [END], if filename
|
|
27
|
+
is not given, user will be prompted for a filename."""
|
|
28
|
+
if filename==None:
|
|
29
|
+
filename=raw_input(' Configuration file (.cfg) name? ')
|
|
30
|
+
print(" Reading file", filename.strip(), "for configurations...")
|
|
31
|
+
|
|
32
|
+
cfgfile=open(filename)
|
|
33
|
+
self._read(cfgfile)
|
|
34
|
+
cfgfile.close()
|
|
35
|
+
|
|
36
|
+
def read_from_file_section(self,file_with_cfg):
|
|
37
|
+
""" only read config entries from the current location,
|
|
38
|
+
file_with_cfg should be already opened, and reading will
|
|
39
|
+
be terminated once [END] is reached, the calling program
|
|
40
|
+
can continue read the rest of the file. """
|
|
41
|
+
self._read(file_with_cfg)
|
|
42
|
+
|
|
43
|
+
def _read(self,cfgfile):
|
|
44
|
+
finished=False
|
|
45
|
+
while not finished:
|
|
46
|
+
line = cfgfile.readline()
|
|
47
|
+
if line:
|
|
48
|
+
if line.strip()=='': continue
|
|
49
|
+
if line.strip()[0]=='!': continue
|
|
50
|
+
if line.strip()[0]=='#': continue
|
|
51
|
+
if line.strip()[0]=='[':
|
|
52
|
+
ikeyend=line.find(']')
|
|
53
|
+
keyword=line[1:ikeyend]
|
|
54
|
+
if keyword=='END': break
|
|
55
|
+
self._config_entries[keyword]=[]
|
|
56
|
+
else:
|
|
57
|
+
self._config_entries[keyword].append(line.rstrip('\n\r'))
|
|
58
|
+
else: finished = True
|
|
59
|
+
def add_value(self,keyword,value):
|
|
60
|
+
""" add value manually, value can be a single value, or a list,
|
|
61
|
+
or whatever object, as long as you know what it is when you
|
|
62
|
+
get it out, if keyword exist, the value wil be appended """
|
|
63
|
+
if keyword in self._config_entries:
|
|
64
|
+
try:
|
|
65
|
+
self._config_entries[keyword].append(value)
|
|
66
|
+
except:
|
|
67
|
+
self._config_entries[keyword] = [self._config_entries[keyword], value]
|
|
68
|
+
else:
|
|
69
|
+
self._config_entries[keyword] = [value]
|
|
70
|
+
|
|
71
|
+
def get_value(self,keyword):
|
|
72
|
+
if len(self._config_entries[keyword]) == 0:
|
|
73
|
+
return ''
|
|
74
|
+
else:
|
|
75
|
+
return self._config_entries[keyword][0]
|
|
76
|
+
|
|
77
|
+
def get_list(self,keyword):
|
|
78
|
+
return self._config_entries[keyword]
|
|
79
|
+
|
|
80
|
+
def check_optional(self,keyword):
|
|
81
|
+
return keyword in self._config_entries.keys()
|
|
82
|
+
|
gimu/easy_date.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
""" Lazy man's easy conversion between date strings and date tuple/date objects.
|
|
2
|
+
|
|
3
|
+
It's actually very easy to code it directly, plus it's better to reduce
|
|
4
|
+
dependency. This module provides very few additional functionality. One being
|
|
5
|
+
able to detect if a list of strings/date tuples are given, and act accordingly.
|
|
6
|
+
Use these few lines of code instead if you are not too lazy:
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
d = datetime.strptime(s, '%Y-%m-%d').date()
|
|
10
|
+
|
|
11
|
+
from datetime import date
|
|
12
|
+
s = str(date((1995,2,3)))
|
|
13
|
+
s = str(date(*tp))
|
|
14
|
+
# tp is your existing tuple, s will be string '1995-02-03'
|
|
15
|
+
"""
|
|
16
|
+
import unittest
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def date_tuple_to_string(tp):
|
|
20
|
+
""" Converts date integer tuple (y,m,d) into string 'yyyy-mm-dd'. Input
|
|
21
|
+
supports both a single date-tuple as well as a list of date-tuples. """
|
|
22
|
+
from datetime import date
|
|
23
|
+
single = False
|
|
24
|
+
if len(tp) == 3:
|
|
25
|
+
if all([isinstance(i, int) for i in tp]):
|
|
26
|
+
single = True
|
|
27
|
+
if single:
|
|
28
|
+
return str(date(*tp))
|
|
29
|
+
else:
|
|
30
|
+
# assuming input is a list of date-tuples
|
|
31
|
+
return [str(date(*d)) for d in tp]
|
|
32
|
+
|
|
33
|
+
def _s2d(s):
|
|
34
|
+
""" Converts a date string of format 'yyyy-mm-dd' or 'dd/mm/yyyy' into a
|
|
35
|
+
python datetime.date object. """
|
|
36
|
+
from datetime import datetime
|
|
37
|
+
if '/' in s:
|
|
38
|
+
frmt = '%d/%m/%Y'
|
|
39
|
+
elif '-' in s:
|
|
40
|
+
frmt = '%Y-%m-%d'
|
|
41
|
+
else:
|
|
42
|
+
raise Exception("Date string format only support 'yyyy-mm-dd' or 'dd/mm/yyyy'")
|
|
43
|
+
return datetime.strptime(s, frmt).date()
|
|
44
|
+
|
|
45
|
+
def date_string_to_tuple(s):
|
|
46
|
+
""" Converts a date string of format 'yyyy-mm-dd' or 'dd/mm/yyyy' into a
|
|
47
|
+
tuple of integers (y,m,d). Input supports both a single string as well as a
|
|
48
|
+
list of strings. """
|
|
49
|
+
def d2t(d):
|
|
50
|
+
return (d.year, d.month, d.day)
|
|
51
|
+
if isinstance(s, list):
|
|
52
|
+
return [d2t(_s2d(sd)) for sd in s]
|
|
53
|
+
else:
|
|
54
|
+
return d2t(_s2d(s))
|
|
55
|
+
|
|
56
|
+
def date_string_to_date(s):
|
|
57
|
+
""" Converts a date string of format 'yyyy-mm-dd' or 'dd/mm/yyyy' into a
|
|
58
|
+
python datetime.date object. Input supports both a single string as well as
|
|
59
|
+
a list of strings. """
|
|
60
|
+
from datetime import date
|
|
61
|
+
if isinstance(s, list):
|
|
62
|
+
return [_s2d(sd) for sd in s]
|
|
63
|
+
else:
|
|
64
|
+
return _s2d(s)
|
|
65
|
+
|
|
66
|
+
def year_fraction_to_date(year_fraction):
|
|
67
|
+
""" Converts a decimal/float year into a python datetime.date object.
|
|
68
|
+
Input supports both a single float as well as a list of floats. """
|
|
69
|
+
from datetime import date, timedelta
|
|
70
|
+
def yf2d(yf):
|
|
71
|
+
year = int(yf)
|
|
72
|
+
fraction = yf - year
|
|
73
|
+
# assuming 365.25 days per year
|
|
74
|
+
day_of_year = int(fraction * 365.25)
|
|
75
|
+
return date(year, 1, 1) + timedelta(days=day_of_year)
|
|
76
|
+
if isinstance(year_fraction, list):
|
|
77
|
+
return [yf2d(yf) for yf in year_fraction]
|
|
78
|
+
else:
|
|
79
|
+
return yf2d(year_fraction)
|
|
80
|
+
|
|
81
|
+
def year_fraction_to_date_str(year_fraction):
|
|
82
|
+
""" Converts a decimal/float year into a string of format 'dd/mm/yyyy'.
|
|
83
|
+
Input supports both a single float as well as a list of floats. """
|
|
84
|
+
dd = year_fraction_to_date(year_fraction)
|
|
85
|
+
if isinstance(dd, list):
|
|
86
|
+
return [d.strftime("%d/%m/%Y") for d in dd]
|
|
87
|
+
else:
|
|
88
|
+
return dd.strftime("%d/%m/%Y")
|
|
89
|
+
|
|
90
|
+
def toYearFraction(date):
|
|
91
|
+
""" converts python datetime objects into decimal/float years
|
|
92
|
+
https://stackoverflow.com/a/6451892/2368167
|
|
93
|
+
"""
|
|
94
|
+
from datetime import datetime as dt
|
|
95
|
+
import time
|
|
96
|
+
def sinceEpoch(date): # returns seconds since epoch
|
|
97
|
+
return time.mktime(date.timetuple())
|
|
98
|
+
s = sinceEpoch
|
|
99
|
+
|
|
100
|
+
year = date.year
|
|
101
|
+
startOfThisYear = dt(year=year, month=1, day=1)
|
|
102
|
+
startOfNextYear = dt(year=year+1, month=1, day=1)
|
|
103
|
+
|
|
104
|
+
yearElapsed = s(date) - s(startOfThisYear)
|
|
105
|
+
yearDuration = s(startOfNextYear) - s(startOfThisYear)
|
|
106
|
+
fraction = yearElapsed/yearDuration
|
|
107
|
+
|
|
108
|
+
return date.year + fraction
|
|
109
|
+
|
|
110
|
+
def toYearFraction2(date):
|
|
111
|
+
""" converts python datetime objects into decimal/float years
|
|
112
|
+
https://stackoverflow.com/a/6451892/2368167
|
|
113
|
+
|
|
114
|
+
fix Epoch issue?
|
|
115
|
+
"""
|
|
116
|
+
from datetime import datetime as dt
|
|
117
|
+
from datetime import date as dd
|
|
118
|
+
import time
|
|
119
|
+
def sinceEpoch(date): # returns seconds since epoch
|
|
120
|
+
return date - dd(1970,1,1)
|
|
121
|
+
s = sinceEpoch
|
|
122
|
+
|
|
123
|
+
year = date.year
|
|
124
|
+
startOfThisYear = dd(year=year, month=1, day=1)
|
|
125
|
+
startOfNextYear = dd(year=year+1, month=1, day=1)
|
|
126
|
+
|
|
127
|
+
yearElapsed = s(date) - s(startOfThisYear)
|
|
128
|
+
yearDuration = s(startOfNextYear) - s(startOfThisYear)
|
|
129
|
+
fraction = yearElapsed.total_seconds()/yearDuration.total_seconds()
|
|
130
|
+
|
|
131
|
+
return date.year + fraction
|
|
132
|
+
|
|
133
|
+
def year_fraction(year, month, day):
|
|
134
|
+
""" https://stackoverflow.com/a/36949905/2368167
|
|
135
|
+
"""
|
|
136
|
+
import datetime
|
|
137
|
+
date = datetime.date(year, month, day)
|
|
138
|
+
start = datetime.date(date.year, 1, 1).toordinal()
|
|
139
|
+
year_length = datetime.date(date.year+1, 1, 1).toordinal() - start
|
|
140
|
+
return date.year + float(date.toordinal() - start) / year_length
|
|
141
|
+
|
|
142
|
+
class TestEasyDate(unittest.TestCase):
|
|
143
|
+
"""docstring for TestEasyDate"""
|
|
144
|
+
def test_year_fraction(self):
|
|
145
|
+
for d,f in [
|
|
146
|
+
((2023, 1, 1), 2023.0),
|
|
147
|
+
((2023, 6, 30), 2023.493),
|
|
148
|
+
((2023, 12, 31), 2023.997),
|
|
149
|
+
((2024, 1, 1), 2024.0),
|
|
150
|
+
]:
|
|
151
|
+
result = year_fraction(*d)
|
|
152
|
+
self.assertAlmostEqual(result, f, places=3)
|
|
153
|
+
|
|
154
|
+
def test_year_fraction_mid_year(self):
|
|
155
|
+
"""Test year_fraction for middle of the year"""
|
|
156
|
+
# Test July 1st (approximately mid-year)
|
|
157
|
+
result = year_fraction(2023, 7, 1)
|
|
158
|
+
self.assertGreater(result, 2023.4)
|
|
159
|
+
self.assertLess(result, 2023.6)
|
|
160
|
+
|
|
161
|
+
def test_year_fraction_to_date(self):
|
|
162
|
+
""" test by starting with dd/mm/yyyy, convert to fraction, then back to dd/mm/yyyy, final one should be the same as the first one """
|
|
163
|
+
ds = ['2011-03-05', '2011-3-6', '04/11/2011', '5/11/2011']
|
|
164
|
+
for d in ds:
|
|
165
|
+
dd = _s2d(d)
|
|
166
|
+
f = toYearFraction2(dd)
|
|
167
|
+
d2 = year_fraction_to_date(f)
|
|
168
|
+
self.assertEqual(dd, d2)
|
|
169
|
+
|
|
170
|
+
ds = ['04/11/2011', '05/01/2011']
|
|
171
|
+
for d in ds:
|
|
172
|
+
dd = _s2d(d)
|
|
173
|
+
f = toYearFraction2(dd)
|
|
174
|
+
d2 = year_fraction_to_date_str(f)
|
|
175
|
+
self.assertEqual(d, d2)
|
|
176
|
+
|
|
177
|
+
def test_tuple_to_string(self):
|
|
178
|
+
s1 = (1995,10,2)
|
|
179
|
+
s2 = [1995,10,2]
|
|
180
|
+
ss = [s1, s2]
|
|
181
|
+
for s in ss:
|
|
182
|
+
self.assertEqual('1995-10-02', date_tuple_to_string(s))
|
|
183
|
+
self.assertEqual(['1995-10-02']*2, date_tuple_to_string(ss))
|
|
184
|
+
|
|
185
|
+
def test_string_to_tuple(self):
|
|
186
|
+
d1 = '2011-03-05'
|
|
187
|
+
d2 = '2011-3-5'
|
|
188
|
+
d3 = '05/03/2011'
|
|
189
|
+
d4 = '5/3/2011'
|
|
190
|
+
ds = [d1,d2,d3,d4]
|
|
191
|
+
for d in ds:
|
|
192
|
+
self.assertEqual((2011,3,5), date_string_to_tuple(d))
|
|
193
|
+
self.assertEqual([(2011,3,5)]*4, date_string_to_tuple(ds))
|
|
194
|
+
|
|
195
|
+
def test_string_to_date(self):
|
|
196
|
+
from datetime import date
|
|
197
|
+
d1 = '2011-03-05'
|
|
198
|
+
d2 = '2011-3-5'
|
|
199
|
+
d3 = '05/03/2011'
|
|
200
|
+
d4 = '5/3/2011'
|
|
201
|
+
ds = [d1,d2,d3,d4]
|
|
202
|
+
for d in ds:
|
|
203
|
+
self.assertEqual(date(2011,3,5), date_string_to_date(d))
|
|
204
|
+
self.assertEqual([date(2011,3,5)]*4, date_string_to_date(ds))
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
if __name__ == '__main__':
|
|
209
|
+
unittest.main(verbosity=2)
|
gimu/geo_common.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from mulgrids import *
|
|
2
|
+
import string
|
|
3
|
+
|
|
4
|
+
def quick_enthalpy(t_or_p,ph='liq'):
|
|
5
|
+
""" Return enthalpy J/kg ('liq', 'vap', or 'dif') of water at specified
|
|
6
|
+
temperature (<=500.0 in degC) or pressu (>500.0 in Pa) """
|
|
7
|
+
import t2thermo
|
|
8
|
+
def enth(t,p,f):
|
|
9
|
+
d,u = f(t,p)
|
|
10
|
+
return u + p/d
|
|
11
|
+
def hlhs(t,p):
|
|
12
|
+
return enth(t,p,t2thermo.cowat), enth(t,p,t2thermo.supst)
|
|
13
|
+
def sat_tp(t_or_p):
|
|
14
|
+
if t_or_p > 500.0:
|
|
15
|
+
return t2thermo.tsat(t_or_p), t_or_p
|
|
16
|
+
else:
|
|
17
|
+
return t_or_p, t2thermo.sat(t_or_p)
|
|
18
|
+
(hl,hs) = hlhs(*sat_tp(t_or_p))
|
|
19
|
+
# xxxx
|
|
20
|
+
return {'liq': hl,'vap': hs,'dif': hs-hl}[ph]
|
|
21
|
+
|
|
22
|
+
class RelativePermeability:
|
|
23
|
+
""" Class to calculate linear relative permeability, this is used in
|
|
24
|
+
TOUGH2 for the linear relative permeability model. The class has a
|
|
25
|
+
single method, which takes the saturation and returns the relative
|
|
26
|
+
permeability. """
|
|
27
|
+
def __init__(self, wj_object=None):
|
|
28
|
+
if wj_object is None:
|
|
29
|
+
wj_object = {
|
|
30
|
+
"type": "linear",
|
|
31
|
+
"liquid": [0.0, 1.0],
|
|
32
|
+
"vapour": [0.0, 1.0]
|
|
33
|
+
}
|
|
34
|
+
self.setting = wj_object
|
|
35
|
+
self.func = {
|
|
36
|
+
"linear": self.linear,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def calc(self, vapour_saturation):
|
|
40
|
+
""" return (kr_liquid, kr_vapour) """
|
|
41
|
+
return self.func[self.setting['type']](vapour_saturation)
|
|
42
|
+
|
|
43
|
+
def linear(self, vapour_saturation):
|
|
44
|
+
liq_limits = self.setting['liquid']
|
|
45
|
+
vap_limits = self.setting['vapour']
|
|
46
|
+
liquid_saturation = 1.0 - vapour_saturation
|
|
47
|
+
kr_liq = np.interp(liquid_saturation, liq_limits, [0., 1.], left=0.0, right=1.0)
|
|
48
|
+
kr_vap = np.interp(vapour_saturation, vap_limits, [0., 1.], left=0.0, right=1.0)
|
|
49
|
+
return kr_liq, kr_vap
|
|
50
|
+
|
|
51
|
+
def flowing_enthalpy(lst, wj, block):
|
|
52
|
+
""" calculate flowing enthalpy of a given block if producing from waiwera h5
|
|
53
|
+
|
|
54
|
+
NOTE use values from waiwera h5 at current time
|
|
55
|
+
"""
|
|
56
|
+
phases = ['liquid', 'vapour']
|
|
57
|
+
rp = RelativePermeability(wj['rock']['relative_permeability'])
|
|
58
|
+
vapour_saturation = lst.element[block][f'fluid_vapour_saturation']
|
|
59
|
+
rel_perm = rp.calc(vapour_saturation)
|
|
60
|
+
fluid = lst.element[block]
|
|
61
|
+
# mobility
|
|
62
|
+
mobility, sum_mobility = {}, 0.0
|
|
63
|
+
for iph, phase in enumerate(phases):
|
|
64
|
+
density = fluid[f'fluid_{phase}_density']
|
|
65
|
+
viscosity = fluid[f'fluid_{phase}_viscosity']
|
|
66
|
+
if viscosity == 0.0:
|
|
67
|
+
mobility[phase] = 0.0
|
|
68
|
+
else:
|
|
69
|
+
mobility[phase] = rel_perm[iph] * density / viscosity
|
|
70
|
+
sum_mobility += mobility[phase]
|
|
71
|
+
# flow fraction
|
|
72
|
+
flow_frac = {}
|
|
73
|
+
for phase in phases:
|
|
74
|
+
flow_frac[phase] = mobility[phase] / sum_mobility
|
|
75
|
+
# enthalpy
|
|
76
|
+
enthalpy = 0.0
|
|
77
|
+
for phase in phases:
|
|
78
|
+
enthalpy += flow_frac[phase] * fluid[f'fluid_{phase}_specific_enthalpy']
|
|
79
|
+
# breakpoint()
|
|
80
|
+
return enthalpy
|
|
81
|
+
|
|
82
|
+
def bottomhole_pressure(depth, temperature=20.0, whp=1.0e5, division=100,
|
|
83
|
+
min_depth_interval=10.0):
|
|
84
|
+
""" Calculate the pressure at the bottom of a well, given the wellhead
|
|
85
|
+
pressure (whp) in pascal, temperature in degC, and division in m. The
|
|
86
|
+
pressure is calculated as: pressure = whp + rho * G * depth where rho is
|
|
87
|
+
the density of water at the given temperature, G is the gravitational
|
|
88
|
+
acceleration (9.81 m/s^2), and depth is the depth of the well divided by
|
|
89
|
+
division.
|
|
90
|
+
|
|
91
|
+
What tis code does is then divide the depth into inv=tervals. Then the
|
|
92
|
+
pressure is accumulated, this allows the density calculation to use the
|
|
93
|
+
actual pressure at the depth. For liquid water, it is quite
|
|
94
|
+
incompressible, so the result won't be that different to the easy
|
|
95
|
+
rho*G*depth method.
|
|
96
|
+
"""
|
|
97
|
+
import t2thermo
|
|
98
|
+
# divide well depth to intervals, use larger of the min interval and division
|
|
99
|
+
interval = max(min_depth_interval, depth/float(division))
|
|
100
|
+
d = 0.0
|
|
101
|
+
pressure = whp
|
|
102
|
+
while d < depth:
|
|
103
|
+
rho, u = t2thermo.cowat(temperature, pressure)
|
|
104
|
+
pressure += rho * 9.81 * interval
|
|
105
|
+
d += interval
|
|
106
|
+
rho, u = t2thermo.cowat(temperature, pressure)
|
|
107
|
+
pressure += rho * 9.81 * (depth - d)
|
|
108
|
+
return pressure
|
|
109
|
+
|
|
110
|
+
def block_depth(block, geo):
|
|
111
|
+
""" Return the depth of a block in a geo object, this is the depth of the
|
|
112
|
+
centre of the block. This also work for Waiwera cell if block is an
|
|
113
|
+
integer number.
|
|
114
|
+
"""
|
|
115
|
+
if isinstance(block, int):
|
|
116
|
+
block = geo.block_name_list(block + geo.num_atmosphere_blocks)
|
|
117
|
+
lay, col = geo.layer_name(block), geo.column_name(block)
|
|
118
|
+
return geo.column[col].surface - geo.block_centre(lay, col)[2]
|
|
119
|
+
|
|
120
|
+
def xyz2fit(fn):
|
|
121
|
+
data = np.fromfile(fn,sep=" ")
|
|
122
|
+
nrow = np.size(data) / 3
|
|
123
|
+
data= data.reshape(nrow,3)
|
|
124
|
+
return data
|
|
125
|
+
|
|
126
|
+
def find_all_cols_below(geo,level,surfer_file=None):
|
|
127
|
+
if surfer_file is not None: outfile = file(surfer_file,'w')
|
|
128
|
+
cols = []
|
|
129
|
+
for col in geo.columnlist:
|
|
130
|
+
surf = col.surface
|
|
131
|
+
if surf < level:
|
|
132
|
+
#print(col.name, str(surf))
|
|
133
|
+
cols.append([col.centre[0], col.centre[1], surf, col.name])
|
|
134
|
+
if surfer_file is not None: outfile.write(
|
|
135
|
+
str(col.centre[0])+' '+str(col.centre[1])+' '+str(surf)+' '+
|
|
136
|
+
col.name + '\n')
|
|
137
|
+
if surfer_file is not None:
|
|
138
|
+
outfile.close()
|
|
139
|
+
print(' -- file: ', surfer_file, ' is written.')
|
|
140
|
+
|
|
141
|
+
def t2_strict_name(n):
|
|
142
|
+
""" convert any name into the common TOUGH style 5 character name """
|
|
143
|
+
import string
|
|
144
|
+
def make_number(c):
|
|
145
|
+
""" change a character into a single number, mostly 'A' or 'a' will
|
|
146
|
+
become a '1' """
|
|
147
|
+
if len(c.strip()) == 0: return ' '
|
|
148
|
+
d = c.lower().strip()[:1]
|
|
149
|
+
i = string.ascii_lowercase.find(d)+1
|
|
150
|
+
if i == 0: return ' '
|
|
151
|
+
else: return str(i%10)
|
|
152
|
+
newn = list(n.strip())
|
|
153
|
+
if len(newn) <= 3:
|
|
154
|
+
return "".join(newn).strip()[:5].ljust(5)
|
|
155
|
+
for i in range(3,len(newn)):
|
|
156
|
+
if newn[i] not in '0123456789':
|
|
157
|
+
newn[i] = make_number(newn[i])
|
|
158
|
+
return "".join(newn).strip()[:5].ljust(5)
|
|
159
|
+
|
|
160
|
+
def is_leap_year(y):
|
|
161
|
+
if int(y)%400 == 0: return True
|
|
162
|
+
elif int(y)%100 == 0: return False
|
|
163
|
+
elif int(y)%4 == 0: return True
|
|
164
|
+
else: return False
|
|
165
|
+
|
|
166
|
+
def days_in_month(month, leap_year=False):
|
|
167
|
+
if leap_year:
|
|
168
|
+
d_month = [ 31 , 29 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 ]
|
|
169
|
+
else:
|
|
170
|
+
d_month = [ 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 ]
|
|
171
|
+
return d_month[month - 1]
|
|
172
|
+
|
|
173
|
+
def date2str(d,m,y):
|
|
174
|
+
ds, ms, ys = str(d), str(m), str(y)
|
|
175
|
+
while len(ds) < 2: ds = '0'+ds
|
|
176
|
+
while len(ms) < 2: ms = '0'+ms
|
|
177
|
+
while len(ys) < 4: ys = '0'+ys
|
|
178
|
+
return ds+'/'+ms+'/'+ys
|
|
179
|
+
|
|
180
|
+
def date2num(enddate):
|
|
181
|
+
|
|
182
|
+
d,m,y = enddate.split('/')
|
|
183
|
+
months = [ '01','02','03','04','05','06','07','08','09','10','11','12' ]
|
|
184
|
+
d_month = [ 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 ]
|
|
185
|
+
d_month_l = [ 31 , 29 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 ]
|
|
186
|
+
acum_ds = [sum(d_month[:i]) for i in range(12)]
|
|
187
|
+
acum_ds_l = [sum(d_month[:i]) for i in range(12)]
|
|
188
|
+
ad_m = dict(zip(months, acum_ds)) # accumulated days before this month
|
|
189
|
+
ad_m_l = dict(zip(months, acum_ds_l)) # accumulated days before this month
|
|
190
|
+
ds_m = dict(zip(months, d_month)) # maximum days in this month
|
|
191
|
+
ds_m_l = dict(zip(months, d_month_l)) # maximum days in this month
|
|
192
|
+
# check and process d and m, this depends on data
|
|
193
|
+
if m not in months:
|
|
194
|
+
print(' Error, unable to convert ', enddate, ' to numeric format. check month.')
|
|
195
|
+
sys.exit()
|
|
196
|
+
if is_leap_year:
|
|
197
|
+
if int(d) not in range(1,ds_m_l[m]+1):
|
|
198
|
+
print(' Error, unable to convert ', enddate, ' to numeric format. check day.')
|
|
199
|
+
sys.exit()
|
|
200
|
+
num = float(y) + ((float(d) + float(ad_m_l[m]))/float(sum(d_month_l)))
|
|
201
|
+
else:
|
|
202
|
+
if int(d) not in range(1,ds_m[m]+1):
|
|
203
|
+
print(' Error, unable to convert ', enddate, ' to numeric format. check day.')
|
|
204
|
+
sys.exit()
|
|
205
|
+
num = float(y) + ((float(d) + float(ad_m[m]))/float(sum(d_month)))
|
|
206
|
+
return num
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def identifier(x, chars='abcdefghijklmnopqrstuvwxyz', width=5):
|
|
210
|
+
""" creates a character-based unique identifier from a given integer,
|
|
211
|
+
both chars and width are customisable, output is space filled and
|
|
212
|
+
right aligned """
|
|
213
|
+
output = []
|
|
214
|
+
base = len(chars)
|
|
215
|
+
while x:
|
|
216
|
+
output.append(chars[x % base])
|
|
217
|
+
x /= base
|
|
218
|
+
final = ''.join(reversed(output))
|
|
219
|
+
if len(final) > width:
|
|
220
|
+
raise Exception('identifier() failed, not enough width.')
|
|
221
|
+
return ('%' + str(width) + 's') % final
|
|
222
|
+
|
gimu/gmf/__init__.py
ADDED
|
File without changes
|
gimu/gmf/data.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
""" Toolbox for accessing GIM/GMF well data
|
|
2
|
+
|
|
3
|
+
Use:
|
|
4
|
+
from gmf_data import data_json
|
|
5
|
+
|
|
6
|
+
data_json.set_data_folder('/path/to/data')
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
from collections import OrderedDict
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class WellData(dict):
|
|
16
|
+
def __init__(self, *args, **kwargs):
|
|
17
|
+
super().__init__(*args, **kwargs)
|
|
18
|
+
|
|
19
|
+
def downhole_temperature(self, date_str):
|
|
20
|
+
""" get direct access to data without fluff """
|
|
21
|
+
try:
|
|
22
|
+
raw_data = self['Downhole_Temperature'][date_str]['Data']
|
|
23
|
+
except KeyError as e:
|
|
24
|
+
print(f"Data '{date_str}' does not exist in {self['Well_Name']}")
|
|
25
|
+
print(f"-> available data set are: {list(self['Downhole_Temperature'].keys())}")
|
|
26
|
+
raise e
|
|
27
|
+
elevs, temps = [], []
|
|
28
|
+
for elev_str, temp in raw_data.items():
|
|
29
|
+
elev = float(elev_str)
|
|
30
|
+
elevs.append(elev)
|
|
31
|
+
temps.append(temp)
|
|
32
|
+
|
|
33
|
+
# TODO takes care of units in GMF
|
|
34
|
+
return elevs, temps
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DataJSON:
|
|
38
|
+
def __init__(self, max_cache_size=10):
|
|
39
|
+
self._data_folders = []
|
|
40
|
+
self._cache = OrderedDict()
|
|
41
|
+
self._max_cache_size = max_cache_size
|
|
42
|
+
|
|
43
|
+
def set_data_folders(self, paths):
|
|
44
|
+
self._data_folders = []
|
|
45
|
+
for path in paths:
|
|
46
|
+
self.add_data_folder(path)
|
|
47
|
+
|
|
48
|
+
def add_data_folder(self, path):
|
|
49
|
+
if not os.path.isdir(path):
|
|
50
|
+
raise ValueError(f"Path '{path}' is not a valid directory.")
|
|
51
|
+
if path not in self._data_folders:
|
|
52
|
+
self._data_folders.append(path)
|
|
53
|
+
|
|
54
|
+
def remove_data_folder(self, path):
|
|
55
|
+
if path in self._data_folders:
|
|
56
|
+
self._data_folders.remove(path)
|
|
57
|
+
|
|
58
|
+
def __getitem__(self, well_name):
|
|
59
|
+
if not self._data_folders:
|
|
60
|
+
raise RuntimeError("Data folder not set. Please call add_data_folder() or set_data_folders() first.")
|
|
61
|
+
|
|
62
|
+
if well_name in self._cache:
|
|
63
|
+
self._cache.move_to_end(well_name)
|
|
64
|
+
return self._cache[well_name]
|
|
65
|
+
|
|
66
|
+
for folder in self._data_folders:
|
|
67
|
+
file_path = os.path.join(folder, f"{well_name}_data.json")
|
|
68
|
+
if os.path.exists(file_path):
|
|
69
|
+
with open(file_path, 'r') as f:
|
|
70
|
+
data = WellData(json.load(f))
|
|
71
|
+
|
|
72
|
+
if len(self._cache) >= self._max_cache_size:
|
|
73
|
+
self._cache.popitem(last=False)
|
|
74
|
+
|
|
75
|
+
self._cache[well_name] = data
|
|
76
|
+
return data
|
|
77
|
+
|
|
78
|
+
msg = '\n'.join([
|
|
79
|
+
f"Well '{well_name}' not found in any of the data folders:",
|
|
80
|
+
] + [f" {f}" for f in self._data_folders])
|
|
81
|
+
raise KeyError(msg)
|
|
82
|
+
|
|
83
|
+
def __contains__(self, well_name):
|
|
84
|
+
if well_name in self._cache:
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
for folder in self._data_folders:
|
|
88
|
+
file_path = os.path.join(folder, f"{well_name}_data.json")
|
|
89
|
+
if os.path.exists(file_path):
|
|
90
|
+
return True
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
def clear_cache(self):
|
|
94
|
+
self._cache.clear()
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def data_folders(self):
|
|
98
|
+
return self._data_folders
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def cached_wells(self):
|
|
102
|
+
return list(self._cache.keys())
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def wells(self):
|
|
106
|
+
""" Returns a list of all available well names from the data folders.
|
|
107
|
+
"""
|
|
108
|
+
well_names = set()
|
|
109
|
+
for folder in self._data_folders:
|
|
110
|
+
for filename in os.listdir(folder):
|
|
111
|
+
if filename.endswith('_data.json'):
|
|
112
|
+
well_name = filename[:-len('_data.json')]
|
|
113
|
+
well_names.add(well_name)
|
|
114
|
+
return sorted(list(well_names))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# Singleton instance
|
|
118
|
+
data_json = DataJSON()
|