cmflow 0.3.3__tar.gz → 0.3.3.dev2__tar.gz
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.
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/PKG-INFO +1 -1
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/conceptual_models.py +0 -194
- cmflow-0.3.3/cmflow/name_combo.py +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/.github/workflows/pypi.yml +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/.github/workflows/unittest.yml +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/.gitignore +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/README.md +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/__init__.py +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/conceptual_models.py.bak +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/geom_3dface_utils.py +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/geom_3dface_utils.py.bak +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/geom_surface_utils.py +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/geom_surface_utils.py.bak +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/t2data_utils.py +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/t2data_utils.py.bak +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/test_conceptual_models.py +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/test_conceptual_models.py.bak +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/test_geom_3dface_utils.py +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/test_geom_3dface_utils_ex.ts +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/cmflow/test_geom_surface_utils.py +0 -0
- {cmflow-0.3.3 → cmflow-0.3.3.dev2}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cmflow
|
|
3
|
-
Version: 0.3.3
|
|
3
|
+
Version: 0.3.3.dev2
|
|
4
4
|
Summary: Building TOUGH2/Waiwera models from layers of conceptual models
|
|
5
5
|
Project-URL: Homepage, https://github.com/cyeh015/cmflow
|
|
6
6
|
Project-URL: Source Code, https://github.com/cyeh015/cmflow
|
|
@@ -14,8 +14,6 @@ from cmflow.geom_surface_utils import geo_column_polygon
|
|
|
14
14
|
|
|
15
15
|
import json
|
|
16
16
|
import time
|
|
17
|
-
import string
|
|
18
|
-
import re
|
|
19
17
|
|
|
20
18
|
START = [time.time()]
|
|
21
19
|
def print_wall_time(msg, loop_stop=False, total=False):
|
|
@@ -27,34 +25,6 @@ def print_wall_time(msg, loop_stop=False, total=False):
|
|
|
27
25
|
if loop_stop:
|
|
28
26
|
START.append(t)
|
|
29
27
|
|
|
30
|
-
def assign_chars(names, chars=string.ascii_uppercase):
|
|
31
|
-
""" Assign unique characters to each name.
|
|
32
|
-
|
|
33
|
-
Returns
|
|
34
|
-
-------
|
|
35
|
-
tuple(dict, str)
|
|
36
|
-
A mapping from name to assigned character, and the remaining
|
|
37
|
-
unused characters.
|
|
38
|
-
"""
|
|
39
|
-
if len(names) > len(chars):
|
|
40
|
-
raise Exception("Not enough characters for names")
|
|
41
|
-
name_index = {n: chars[i] for i, n in enumerate(names)}
|
|
42
|
-
remaining_chars = chars[len(names):]
|
|
43
|
-
return name_index, remaining_chars
|
|
44
|
-
|
|
45
|
-
def natural_key(text):
|
|
46
|
-
""" Convert text chunks and number chunks into a tuple of string
|
|
47
|
-
and int, useful for sorting strings naturally. eg.
|
|
48
|
-
|
|
49
|
-
data = ["F24", "F8", "F2", "F10"]
|
|
50
|
-
sorted_data = sorted(data, key=natural_key)
|
|
51
|
-
print(sorted_data) # Output: ['F2', 'F8', 'F10', 'F24']
|
|
52
|
-
"""
|
|
53
|
-
return [
|
|
54
|
-
int(c) if c.isdigit() else c.lower()
|
|
55
|
-
for c in re.split(r'(\d+)', text)
|
|
56
|
-
]
|
|
57
|
-
|
|
58
28
|
class LeapfrogGM(object):
|
|
59
29
|
def __init__(self, geometry=''):
|
|
60
30
|
super(LeapfrogGM, self).__init__()
|
|
@@ -99,170 +69,6 @@ class LeapfrogGM(object):
|
|
|
99
69
|
print('%8s Lithology found.' % len(self.litholist))
|
|
100
70
|
print('%8s Blocks allocated.' % len(self.blocklitho))
|
|
101
71
|
|
|
102
|
-
def parse_lithology_names(self):
|
|
103
|
-
""" parse Leapfrog's Lithology names and determine the faults and pure
|
|
104
|
-
lithos
|
|
105
|
-
|
|
106
|
-
Returns a list of lithology and a list of faults.
|
|
107
|
-
|
|
108
|
-
NOTE Leapfrog remove spaces from fault names, and then truncated into
|
|
109
|
-
7 characters. (Lithology names remain.)
|
|
110
|
-
|
|
111
|
-
Observation:
|
|
112
|
-
- a string can have two parts separated by ', '
|
|
113
|
-
- if there is only a single part, then it can be pure litho OR faults
|
|
114
|
-
- if there are two parts, then they are faults and pure litho
|
|
115
|
-
- faults are one or more faults joined by '+'
|
|
116
|
-
- fault names are shorteded by removing spaces
|
|
117
|
-
"""
|
|
118
|
-
if len(self.litholist) == 0:
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
faults, rocks = [], []
|
|
122
|
-
self.litho_fault_combos = []
|
|
123
|
-
for lf_litho in self.litholist:
|
|
124
|
-
parts = lf_litho.split(', ')
|
|
125
|
-
if len(parts) == 1:
|
|
126
|
-
if '+' in parts[0]:
|
|
127
|
-
fault_parts = parts[0].split('+')
|
|
128
|
-
for fault in fault_parts:
|
|
129
|
-
if fault not in faults:
|
|
130
|
-
faults.append(fault)
|
|
131
|
-
self.litho_fault_combos.append(fault_parts)
|
|
132
|
-
else:
|
|
133
|
-
if parts[0] not in rocks:
|
|
134
|
-
rocks.append(parts[0])
|
|
135
|
-
self.litho_fault_combos.append([])
|
|
136
|
-
|
|
137
|
-
elif len(parts) == 2:
|
|
138
|
-
fault_parts = parts[0].split('+')
|
|
139
|
-
for fault in fault_parts:
|
|
140
|
-
if fault not in faults:
|
|
141
|
-
faults.append(fault)
|
|
142
|
-
self.litho_fault_combos.append(fault_parts)
|
|
143
|
-
|
|
144
|
-
if parts[1] not in rocks:
|
|
145
|
-
rocks.append(parts[1])
|
|
146
|
-
else:
|
|
147
|
-
raise Exception("Unexpected Lithology name format: '%s'" % lf_litho)
|
|
148
|
-
|
|
149
|
-
for f in faults:
|
|
150
|
-
if f in rocks:
|
|
151
|
-
raise Exception(f"Fault {f} also found in pure litho list")
|
|
152
|
-
for r in rocks:
|
|
153
|
-
if r in faults:
|
|
154
|
-
raise Exception(f"Pure litho {r} also found in fault list")
|
|
155
|
-
|
|
156
|
-
self._lf_rocks = sorted(rocks)
|
|
157
|
-
self._lf_faults = sorted(faults)
|
|
158
|
-
return self._lf_rocks, self._lf_faults
|
|
159
|
-
|
|
160
|
-
def gmf_litho_rocktype_1L(self, litho_codes={}, chars=None, report=False):
|
|
161
|
-
""" Generates GMF style rocktype names (length of 1) based on lithologies.
|
|
162
|
-
Returns a dict of mapping from Leapfrog lithology name to GMF rocktype name.
|
|
163
|
-
|
|
164
|
-
litho_codes is a user-defined dict with keys and values a single char.
|
|
165
|
-
The order is important, will affect how the final names are sorted.
|
|
166
|
-
If not specified, the names will be sorted *naturally*.
|
|
167
|
-
"""
|
|
168
|
-
if not chars:
|
|
169
|
-
chars = string.ascii_uppercase + string.ascii_lowercase + string.digits[1:]
|
|
170
|
-
|
|
171
|
-
if litho_codes:
|
|
172
|
-
# some checks and remove used characters
|
|
173
|
-
for l,c in litho_codes.items():
|
|
174
|
-
if len(c) != 1:
|
|
175
|
-
raise Exception(f"Lithology code for {l} must be a single character")
|
|
176
|
-
if l not in self._lf_rocks:
|
|
177
|
-
raise Exception(f"Lithology {l} not found in Leapfrog lithologies list")
|
|
178
|
-
chars = chars.replace(c, '')
|
|
179
|
-
# tolerate if user only specifies some of the lithologies
|
|
180
|
-
remaining_lf_rocks = [r for r in self._lf_rocks if r not in litho_codes]
|
|
181
|
-
new_litho_codes, chars = assign_chars(sorted(remaining_lf_rocks, key=natural_key), chars)
|
|
182
|
-
litho_codes.update(new_litho_codes)
|
|
183
|
-
else:
|
|
184
|
-
litho_codes, chars = assign_chars(sorted(self._lf_rocks, key=natural_key), chars)
|
|
185
|
-
|
|
186
|
-
if report:
|
|
187
|
-
print(f"{len(self.litholist):>8} Leapfrog LithoCode\n"
|
|
188
|
-
f"{len(litho_codes):>8} Lithology\n")
|
|
189
|
-
print()
|
|
190
|
-
for name, code in litho_codes.items():
|
|
191
|
-
print(f"{code} -> {name}")
|
|
192
|
-
print()
|
|
193
|
-
print(litho_codes)
|
|
194
|
-
|
|
195
|
-
return litho_codes
|
|
196
|
-
|
|
197
|
-
def gmf_fault_rocktype_2L(self, fault_codes={}, chars=None, report=False):
|
|
198
|
-
""" Generates GMF style rocktype names (length of 2) based on faults.
|
|
199
|
-
Returns a dict of mapping from tuple of faults to GMF rocktype name.
|
|
200
|
-
|
|
201
|
-
fault_code is a user-defined dict with keys and values a single char.
|
|
202
|
-
The order is important, will affect how the final names are sorted.
|
|
203
|
-
If not specified, the names will be sorted *naturally*.
|
|
204
|
-
|
|
205
|
-
GMF uses:
|
|
206
|
-
- 2nd char (3rd char is zero) single fault
|
|
207
|
-
- 3rd char (combined with 2nd char as unique combination of faults)
|
|
208
|
-
"""
|
|
209
|
-
if not chars:
|
|
210
|
-
chars = string.ascii_uppercase + string.ascii_lowercase
|
|
211
|
-
chars += string.digits[1:] # take out zero because it means no fault
|
|
212
|
-
|
|
213
|
-
if fault_codes:
|
|
214
|
-
# some checks and remove used characters
|
|
215
|
-
for f,c in fault_codes.items():
|
|
216
|
-
if len(c) != 1:
|
|
217
|
-
raise Exception(f"Fault code for {f} must be a single character")
|
|
218
|
-
if f not in fault_codes:
|
|
219
|
-
raise Exception(f"Fault {f} not found in Leapfrog faults list")
|
|
220
|
-
chars = chars.replace(c, '')
|
|
221
|
-
# tolerate if user only specifies some of the faults
|
|
222
|
-
remaining_lf_faults = [f for f in self._lf_faults if f not in fault_codes]
|
|
223
|
-
new_fault_codes, chars = assign_chars(sorted(remaining_lf_faults, key=natural_key), chars=chars)
|
|
224
|
-
fault_codes.update(new_fault_codes)
|
|
225
|
-
else:
|
|
226
|
-
fault_codes, chars = assign_chars(sorted(self._lf_faults, key=natural_key), chars=chars)
|
|
227
|
-
|
|
228
|
-
fault_rank = {name: idx for idx, name in enumerate(fault_codes)}
|
|
229
|
-
|
|
230
|
-
# sort combinations: by num of faults, then by ranking of 1st fault, then 2nd...
|
|
231
|
-
def combo_id(items):
|
|
232
|
-
# id (a tuple of faults) is also ordered by rank
|
|
233
|
-
return tuple(sorted(items, key=lambda x: fault_rank.get(x, 0)))
|
|
234
|
-
combinations = list({combo_id(c) for c in self.litho_fault_combos if c})
|
|
235
|
-
combinations = sorted(combinations, key=lambda x: (len(x), tuple(fault_rank[y] for y in x)))
|
|
236
|
-
if report:
|
|
237
|
-
print(f"{len(self.litho_fault_combos):>8} Leapfrog LithoCode\n"
|
|
238
|
-
f"{len(self._lf_faults):>8} Faults\n"
|
|
239
|
-
f"{len(combinations):>8} Fault combinations\n")
|
|
240
|
-
|
|
241
|
-
final_codes = {}
|
|
242
|
-
for ci,fid in enumerate(combinations):
|
|
243
|
-
if len(fid) == 1:
|
|
244
|
-
# single item ones first -> fault code + '0'
|
|
245
|
-
final_codes[fid] = fault_codes[fid[0]] + '0'
|
|
246
|
-
elif len(fid) == 2:
|
|
247
|
-
# two items combo -> first + second
|
|
248
|
-
final_codes[fid] = ''
|
|
249
|
-
for fault in fid:
|
|
250
|
-
final_codes[fid] += fault_codes[fault]
|
|
251
|
-
else:
|
|
252
|
-
# three or more -> first fault code + nunique code
|
|
253
|
-
if not chars:
|
|
254
|
-
need_more = len(combinations) - ci
|
|
255
|
-
raise Exception(f"Not enough characters for remaining fault combinations,"
|
|
256
|
-
f" need {need_more} more.")
|
|
257
|
-
code = chars[0]
|
|
258
|
-
chars = chars[1:]
|
|
259
|
-
final_codes[fid] = fault_codes[fid[0]] + code
|
|
260
|
-
if report:
|
|
261
|
-
# print(f"{fid} -> {final_codes[fid]}")
|
|
262
|
-
print(f"{final_codes[fid]} -> ({len(fid)}) {fid}")
|
|
263
|
-
|
|
264
|
-
return final_codes
|
|
265
|
-
|
|
266
72
|
def write(self, filename):
|
|
267
73
|
with open(filename, 'w') as f:
|
|
268
74
|
data = {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|