goPEST 0.0.11__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.
- goPEST-0.0.11.dist-info/LICENSE +11 -0
- goPEST-0.0.11.dist-info/METADATA +95 -0
- goPEST-0.0.11.dist-info/RECORD +30 -0
- goPEST-0.0.11.dist-info/WHEEL +5 -0
- goPEST-0.0.11.dist-info/entry_points.txt +2 -0
- goPEST-0.0.11.dist-info/top_level.txt +1 -0
- gopest/__init__.py +11 -0
- gopest/_version.py +16 -0
- gopest/check_slaves.py +402 -0
- gopest/commands.py +80 -0
- gopest/common.py +194 -0
- gopest/data/case.pst +67 -0
- gopest/data/goPESTconfig.aut2.toml +95 -0
- gopest/data/goPESTconfig.toml +94 -0
- gopest/data/goPESTobs.list +793 -0
- gopest/data/goPESTpar.list +95 -0
- gopest/make_case_pst.py +229 -0
- gopest/obs.py +297 -0
- gopest/obs_def.py +2086 -0
- gopest/par.py +332 -0
- gopest/par_def.py +313 -0
- gopest/pest_model.py +245 -0
- gopest/rename_latest_files.py +35 -0
- gopest/run_beopest.py +205 -0
- gopest/run_ns_pr.py +617 -0
- gopest/submit_beopest.py +931 -0
- gopest/utils/__init__.py +0 -0
- gopest/utils/gener_groups.py +192 -0
- gopest/utils/t2listingh5.py +376 -0
- gopest/utils/waiwera_listing.py +587 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# This is an example input file for goPESTpar.py
|
|
2
|
+
#
|
|
3
|
+
# goPESTpar.py uses the content of this file to generate:
|
|
4
|
+
# 1. the * parameter data section for the PEST control file.
|
|
5
|
+
# 2. the template file (.tpl) to be used by PEST
|
|
6
|
+
#
|
|
7
|
+
# In PEST, each line of * parameter data should be:
|
|
8
|
+
# PARNME PARTRANS PARCHGLIM PARVAL1 PARLBND PARUBND PARGP SCALE OFFSET DERCOM PARTIED
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
[Defaults]
|
|
12
|
+
# default names of parameter data can be set by using 'PARNME = '
|
|
13
|
+
# but to be effective, it has to be exactly two character long,
|
|
14
|
+
# otherwise it will be overwritten with the default short alias
|
|
15
|
+
# of the parameter type, which is controlled in goPESTpar_def.py
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
permeability_1_byrock:
|
|
20
|
+
PARTRANS = 'log'
|
|
21
|
+
PARGP = 'k1'
|
|
22
|
+
PARLBND = 0.01e-15
|
|
23
|
+
PARUBND = 4500.0e-15
|
|
24
|
+
|
|
25
|
+
permeability_2_byrock:
|
|
26
|
+
PARTRANS = 'log'
|
|
27
|
+
PARGP = 'k2'
|
|
28
|
+
PARLBND = 0.01e-15
|
|
29
|
+
PARUBND = 4500.0e-15
|
|
30
|
+
|
|
31
|
+
#Vertical permeability
|
|
32
|
+
permeability_3_byrock:
|
|
33
|
+
PARTRANS = 'log'
|
|
34
|
+
PARGP = 'k3'
|
|
35
|
+
PARLBND = 0.01e-15
|
|
36
|
+
PARUBND = 3000.0e-15
|
|
37
|
+
|
|
38
|
+
#Porosity
|
|
39
|
+
porosity_byrock:
|
|
40
|
+
PARTRANS = 'log'
|
|
41
|
+
PARGP = 'por'
|
|
42
|
+
PARLBND = 0.01
|
|
43
|
+
PARUBND = 0.5
|
|
44
|
+
|
|
45
|
+
massgener_rate:
|
|
46
|
+
PARTRANS = 'log'
|
|
47
|
+
PARGP = 'upflow'
|
|
48
|
+
PARLBND = 0.01
|
|
49
|
+
PARUBND = 50.0
|
|
50
|
+
|
|
51
|
+
upflow_rech:
|
|
52
|
+
PARTRANS = 'log'
|
|
53
|
+
PARGP = 'rech'
|
|
54
|
+
PARLBND = 1.0e-8
|
|
55
|
+
PARUBND = 1.0e-5
|
|
56
|
+
|
|
57
|
+
relative_perm_1:
|
|
58
|
+
PARTRANS = 'log'
|
|
59
|
+
PARGP = 'rpcap'
|
|
60
|
+
PARLBND = 0.1
|
|
61
|
+
PARUBND = 0.9
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
[Param]
|
|
65
|
+
ForEach: permeability_1_byrock,permeability_2_byrock,permeability_3_byrock
|
|
66
|
+
ForEach: Rocktype
|
|
67
|
+
'((?!ATMOS).....)'
|
|
68
|
+
# match all rock types but not ATMOS, using look-around
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
[END]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
[Param]
|
|
75
|
+
ForEach: porosity_byrock
|
|
76
|
+
ForEach: Rocktype
|
|
77
|
+
'((?!ATMOS).....)'
|
|
78
|
+
# match all rock types but not ATMOS, using look-around
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
[Param]
|
|
82
|
+
ForEach: massgener_rate
|
|
83
|
+
ForEach: Generator
|
|
84
|
+
'SO...'
|
|
85
|
+
|
|
86
|
+
[Param]
|
|
87
|
+
ForEach: upflow_rech
|
|
88
|
+
ForEach: ConfigRechCoeff
|
|
89
|
+
'.+'
|
|
90
|
+
|
|
91
|
+
[Param]
|
|
92
|
+
ForEach: relative_perm_1
|
|
93
|
+
ForEach: NA
|
|
94
|
+
'rp1'
|
|
95
|
+
|
gopest/make_case_pst.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
from gopest.par import generate_params_and_tpl
|
|
2
|
+
from gopest.obs import generate_obses_and_ins
|
|
3
|
+
|
|
4
|
+
from gopest.common import config
|
|
5
|
+
from gopest.common import runtime
|
|
6
|
+
from gopest.common import check_required
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import importlib.resources as resources
|
|
11
|
+
import shutil
|
|
12
|
+
|
|
13
|
+
def replace_section(begin_sec, end_sec, pcf_text, repl):
|
|
14
|
+
""" returns PEST control file (passed in as a multiline string) with section
|
|
15
|
+
between begin_sec and end_sec replaced by string repl. It does not matter
|
|
16
|
+
if repl has line breaks at the end or start, they will be cleaned up so the
|
|
17
|
+
final file has no empty lines. """
|
|
18
|
+
pattern = re.compile(r'\n *%s *\n.*?\n *%s *\n' % (re.escape(begin_sec), re.escape(end_sec)), flags=re.DOTALL)
|
|
19
|
+
repl = '\n%s\n%s\n%s\n' % (begin_sec, repl.strip("\n"), end_sec)
|
|
20
|
+
result, cnt = pattern.subn(repl, pcf_text)
|
|
21
|
+
if cnt < 1:
|
|
22
|
+
raise Exception("unable to find loaction between section %s and %s" % (begin_sec, end_sec))
|
|
23
|
+
return result
|
|
24
|
+
|
|
25
|
+
def get_lines(filename):
|
|
26
|
+
""" return all non-empty lines froma file as a single string, with a line
|
|
27
|
+
count. """
|
|
28
|
+
f = open(filename, 'r')
|
|
29
|
+
lines, cnt = '', 0
|
|
30
|
+
for line in f:
|
|
31
|
+
if line.strip():
|
|
32
|
+
cnt += 1
|
|
33
|
+
lines += line
|
|
34
|
+
f.close()
|
|
35
|
+
return lines, cnt
|
|
36
|
+
|
|
37
|
+
def replace_nth_line(longstring, i, repl):
|
|
38
|
+
""" replace ith line with repl, if repl is a function, it will be called to
|
|
39
|
+
process the original line. """
|
|
40
|
+
line_i = re.compile(r'([^\n]*\n){%i}' % i, flags=re.MULTILINE)
|
|
41
|
+
m = line_i.match(longstring)
|
|
42
|
+
|
|
43
|
+
if m:
|
|
44
|
+
if isinstance(repl, str):
|
|
45
|
+
real_repl = repl
|
|
46
|
+
elif hasattr(repl, '__call__'):
|
|
47
|
+
real_repl = repl(m.group(1))
|
|
48
|
+
else:
|
|
49
|
+
# if not a string or callable, try convert it into string
|
|
50
|
+
real_repl = str(repl)
|
|
51
|
+
return longstring[:m.start(1)] + real_repl.strip() + '\n' + longstring[m.end(1):]
|
|
52
|
+
else:
|
|
53
|
+
raise Exception("replace_nth_line() failed to replace line number %i" % i)
|
|
54
|
+
|
|
55
|
+
#############################################################################
|
|
56
|
+
|
|
57
|
+
def fixpcf_modelcmd(fpst):
|
|
58
|
+
""" update PEST case file (.pst) with model command line and distribution
|
|
59
|
+
files
|
|
60
|
+
"""
|
|
61
|
+
fsave = runtime['filename']['save']
|
|
62
|
+
fincon = runtime['filename']['incon']
|
|
63
|
+
fdatns = runtime['filename']['dat_seq'][0]
|
|
64
|
+
flstpr = runtime['filename']['lst_seq'][-1]
|
|
65
|
+
|
|
66
|
+
fpst_bk = fpst + '.backup'
|
|
67
|
+
if not os.path.isfile(fpst):
|
|
68
|
+
print('Error: %s does not exist.' % fpst)
|
|
69
|
+
exit(1)
|
|
70
|
+
if os.path.isfile(fpst_bk):
|
|
71
|
+
os.remove(fpst_bk)
|
|
72
|
+
os.rename(fpst, fpst_bk)
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
with open(fpst_bk,'r') as fin:
|
|
76
|
+
pcf_text = fin.read()
|
|
77
|
+
|
|
78
|
+
model_cmd = 'gopest run-pest-model\n'
|
|
79
|
+
model_inout = "\n".join([
|
|
80
|
+
'pest_model.tpl pest_model.dat',
|
|
81
|
+
'pest_model.ins pest_model.obf',
|
|
82
|
+
])
|
|
83
|
+
filedist = "\n".join([
|
|
84
|
+
'2 %s %s %s %s' % (fsave, fincon, fincon, fincon),
|
|
85
|
+
'1 %s %s.999' % (fsave, fincon),
|
|
86
|
+
'1 %s %s.999' % (fdatns, fdatns),
|
|
87
|
+
'1 %s %s.999' % (flstpr, flstpr),
|
|
88
|
+
'1 pest_model.dat pest_model.dat.999',
|
|
89
|
+
'command = "gopest save-iter-model"',
|
|
90
|
+
])
|
|
91
|
+
|
|
92
|
+
pcf_text = replace_section("* model command line", "* model input/output",
|
|
93
|
+
pcf_text, model_cmd)
|
|
94
|
+
pcf_text = replace_section("* model input/output", "* prior information",
|
|
95
|
+
pcf_text, model_inout)
|
|
96
|
+
pcf_text = replace_section("* distribution files", "# end",
|
|
97
|
+
pcf_text, filedist)
|
|
98
|
+
|
|
99
|
+
with open(fpst, 'w') as fout:
|
|
100
|
+
fout.write(pcf_text)
|
|
101
|
+
print('+++ PEST case control file edited, original file saved as %s' % fpst_bk)
|
|
102
|
+
except Exception as e:
|
|
103
|
+
print(e)
|
|
104
|
+
print('update_case_pst.py unable to proceed, restoring.')
|
|
105
|
+
os.rename(fpst_bk, fpst)
|
|
106
|
+
|
|
107
|
+
def fixpcf_parobs(fpst, dopar=True, doobs=True):
|
|
108
|
+
""" update PEST case file (.pst) with parameter and observation data
|
|
109
|
+
"""
|
|
110
|
+
if dopar is False and doobs is False:
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
fpst_bk = fpst + '.backup'
|
|
114
|
+
if not os.path.isfile(fpst):
|
|
115
|
+
print('Error: %s does not exist.' % fpst)
|
|
116
|
+
exit(1)
|
|
117
|
+
if os.path.isfile(fpst_bk):
|
|
118
|
+
os.remove(fpst_bk)
|
|
119
|
+
os.rename(fpst, fpst_bk)
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
with open(fpst_bk,'r') as fin:
|
|
123
|
+
pcf_text = fin.read()
|
|
124
|
+
|
|
125
|
+
if dopar:
|
|
126
|
+
par_data, n_par = get_lines('.pest_par_data')
|
|
127
|
+
pcf_text = replace_section("* parameter data", "* observation groups",
|
|
128
|
+
pcf_text, par_data)
|
|
129
|
+
print('+++ found %i parameters' % n_par)
|
|
130
|
+
|
|
131
|
+
# replace the count of parameters
|
|
132
|
+
def replace_par_cnts(orig):
|
|
133
|
+
""" first two number is n_par and n_obs """
|
|
134
|
+
nums = orig.split()
|
|
135
|
+
return ' '.join([str(n_par)] + nums[1:])
|
|
136
|
+
pcf_text = replace_nth_line(pcf_text, 4, replace_par_cnts)
|
|
137
|
+
|
|
138
|
+
if doobs:
|
|
139
|
+
obs_data, n_obs = get_lines('.pest_obs_data')
|
|
140
|
+
pcf_text = replace_section("* observation data", "* model command line",
|
|
141
|
+
pcf_text, obs_data)
|
|
142
|
+
print('+++ found %i observations' % n_obs)
|
|
143
|
+
|
|
144
|
+
# replace the count of parameters
|
|
145
|
+
def replace_obs_cnts(orig):
|
|
146
|
+
""" first two number is n_par and n_obs """
|
|
147
|
+
nums = orig.split()
|
|
148
|
+
return ' '.join(nums[:1] + [str(n_obs)] + nums[2:])
|
|
149
|
+
pcf_text = replace_nth_line(pcf_text, 4, replace_obs_cnts)
|
|
150
|
+
|
|
151
|
+
with open(fpst, 'w') as fout:
|
|
152
|
+
fout.write(pcf_text)
|
|
153
|
+
print('+++ PEST case control file edited, original file saved as %s' % fpst_bk)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
print(e)
|
|
156
|
+
print('update_case_pst.py unable to proceed, restoring.')
|
|
157
|
+
os.rename(fpst_bk, fpst)
|
|
158
|
+
|
|
159
|
+
def copy_model_files():
|
|
160
|
+
""" user specifies model's original files in [model.original]section
|
|
161
|
+
These files will be copied to the working directory, with goPEST's internal
|
|
162
|
+
naming convention.
|
|
163
|
+
"""
|
|
164
|
+
def copy_to_cwd(filename, newbase):
|
|
165
|
+
""" copy to working dir and rename, but keeping all extention (to lower case) """
|
|
166
|
+
newname = newbase + os.path.splitext(filename)[1].lower()
|
|
167
|
+
print(" copy '%s' -> '%s'" % (filename, newname))
|
|
168
|
+
shutil.copy2(filename, newname)
|
|
169
|
+
|
|
170
|
+
for f in config['model']['original']['geometry-files']:
|
|
171
|
+
copy_to_cwd(f, './g_real_model')
|
|
172
|
+
copy_to_cwd(config['model']['original']['incon-file'], './real_model_incon')
|
|
173
|
+
|
|
174
|
+
# copy input and output files for the sequence of 'ns', 'pr', etc
|
|
175
|
+
sequence = config['model']['sequence']
|
|
176
|
+
for seq in sequence:
|
|
177
|
+
copy_to_cwd(config['model']['original']['%s-input-file' % seq], './real_model_%s' % seq)
|
|
178
|
+
if '%s-output-file' % seq in config['model']['original']:
|
|
179
|
+
copy_to_cwd(config['model']['original']['%s-output-file' % seq], './real_model_%s' % seq)
|
|
180
|
+
# make a copy of first dat to keep as original (handy for goPESTpar etc)
|
|
181
|
+
copy_to_cwd(config['model']['original']['%s-input-file' % sequence[0]], './real_model_original')
|
|
182
|
+
|
|
183
|
+
def make_case_cli(argv=[]):
|
|
184
|
+
""" runs goPEST to set up par and obs entries """
|
|
185
|
+
for a in argv[1:]:
|
|
186
|
+
if a not in ['--no-copy', '--no-par', '--no-obs']:
|
|
187
|
+
raise Exception('Unrecognised option "%s".' % a)
|
|
188
|
+
if '--no-copy' in argv:
|
|
189
|
+
print('+++ use existing model files')
|
|
190
|
+
else:
|
|
191
|
+
print('+++ copy from original model files')
|
|
192
|
+
copy_model_files()
|
|
193
|
+
|
|
194
|
+
fgeo = runtime['filename']['geom']
|
|
195
|
+
fdato = runtime['filename']['dat_orig']
|
|
196
|
+
fdats = runtime['filename']['dat_seq']
|
|
197
|
+
fpst = config['pest']['case-name'] + '.pst'
|
|
198
|
+
|
|
199
|
+
check_required(fpst, 'PEST Case', fdefault='case.pst')
|
|
200
|
+
|
|
201
|
+
dopar = False
|
|
202
|
+
if '--no-par' not in argv:
|
|
203
|
+
check_required('goPESTpar.list', 'Parameter list')
|
|
204
|
+
|
|
205
|
+
print('+++ running goPEST to get par')
|
|
206
|
+
print(' gopestpar', fdato, 'pest_model.tpl', '.pest_par_data')
|
|
207
|
+
generate_params_and_tpl(fdato, 'pest_model.tpl', '.pest_par_data')
|
|
208
|
+
dopar = True
|
|
209
|
+
|
|
210
|
+
doobs = False
|
|
211
|
+
if '--no-obs' not in argv:
|
|
212
|
+
check_required('goPESTobs.list', 'Observation list')
|
|
213
|
+
|
|
214
|
+
print('+++ running goPEST to get obs')
|
|
215
|
+
print(' gopestobs', fgeo, fdats[-1], 'pest_model.ins', '.pest_obs_data')
|
|
216
|
+
generate_obses_and_ins(fgeo, fdats[-1], 'pest_model.ins', '.pest_obs_data')
|
|
217
|
+
doobs = True
|
|
218
|
+
|
|
219
|
+
# unfortunately I need to use 'real_model_original_pr.dat' here because it
|
|
220
|
+
# has many GENERs that may not exist in natural state, while still being
|
|
221
|
+
# needed in observations, eg. Production gener's block, hopefully this is
|
|
222
|
+
# okay because we usually don't need to get any actual values out of the
|
|
223
|
+
# real_model_original_pr.dat model.
|
|
224
|
+
|
|
225
|
+
fixpcf_parobs(fpst, dopar=dopar, doobs=doobs)
|
|
226
|
+
fixpcf_modelcmd(fpst)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
gopest/obs.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import json
|
|
3
|
+
import inspect
|
|
4
|
+
|
|
5
|
+
from mulgrids import *
|
|
6
|
+
from t2data import *
|
|
7
|
+
from t2listing import *
|
|
8
|
+
|
|
9
|
+
from gopest.common import Singleton
|
|
10
|
+
from gopest.common import TwoWayDict
|
|
11
|
+
from gopest.common import readList
|
|
12
|
+
from gopest.common import updateObj
|
|
13
|
+
from gopest.common import merge_dols
|
|
14
|
+
from gopest import obs_def
|
|
15
|
+
|
|
16
|
+
from gopest.utils.waiwera_listing import wlisting
|
|
17
|
+
from gopest.utils.t2listingh5 import t2listingh5
|
|
18
|
+
|
|
19
|
+
OBS_USER_FUNC = dict(inspect.getmembers(obs_def,inspect.isfunction))
|
|
20
|
+
OBS_ALIAS = TwoWayDict(obs_def.shortNames)
|
|
21
|
+
|
|
22
|
+
class PestObsDataName(Singleton):
|
|
23
|
+
""" remembers a list of observation data and observation data """
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.basenames = set([])
|
|
26
|
+
def newName(self,basename):
|
|
27
|
+
apnd = ' 01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
28
|
+
i = 0
|
|
29
|
+
newname = (basename + apnd[i]).strip()
|
|
30
|
+
while newname in self.basenames:
|
|
31
|
+
i += 1
|
|
32
|
+
if i >= len(apnd):
|
|
33
|
+
raise Exception('Unable to find unused obseration name for:' + basename)
|
|
34
|
+
break
|
|
35
|
+
newname = (basename + apnd[i]).strip()
|
|
36
|
+
return newname
|
|
37
|
+
def add(self,newname):
|
|
38
|
+
self.basenames.add(newname)
|
|
39
|
+
|
|
40
|
+
class PestObservData(object):
|
|
41
|
+
def __init__(self,OBSNME='',OBSVAL=0.0,WEIGHT=1.0,OBGNME=''):
|
|
42
|
+
self.OBSNME=OBSNME
|
|
43
|
+
self.OBSVAL=OBSVAL
|
|
44
|
+
self.WEIGHT=WEIGHT
|
|
45
|
+
self.OBGNME=OBGNME
|
|
46
|
+
def __repr__(self):
|
|
47
|
+
return self.OBSNME +' '+ str(self.OBSVAL) +' '+ str(self.WEIGHT) +' '+ self.OBGNME
|
|
48
|
+
|
|
49
|
+
class UserEntryObserv(object):
|
|
50
|
+
"""
|
|
51
|
+
user defined routines
|
|
52
|
+
"""
|
|
53
|
+
def __init__(self,obsType,obsInfo,fieldDataFile,customFilter,
|
|
54
|
+
offsetTime,obsDefault=PestObservData()):
|
|
55
|
+
self.obsType = obsType # a string
|
|
56
|
+
self.obsInfo = obsInfo # a list of anything
|
|
57
|
+
self.fieldDataFile = fieldDataFile # a string
|
|
58
|
+
self.customFilter = customFilter # an expression string
|
|
59
|
+
self.offsetTime = offsetTime # a number to offset model time
|
|
60
|
+
from copy import deepcopy
|
|
61
|
+
self.obsDefault = deepcopy(obsDefault)
|
|
62
|
+
|
|
63
|
+
self.all_obses = None
|
|
64
|
+
self.all_pst_lines = None # should be a list of strings
|
|
65
|
+
self.all_ins_lines = None # should be a list of strings
|
|
66
|
+
self.all_obf_lines = None # should be a list of strings
|
|
67
|
+
|
|
68
|
+
# this is expected to be directly written into self, by custom function
|
|
69
|
+
self.batch_plot_entry = []
|
|
70
|
+
self.coverage = {} # keyed by OBGNME, each group is a list of model blocks with data
|
|
71
|
+
def __repr__(self):
|
|
72
|
+
return '\n'.join([
|
|
73
|
+
'','[Obs]',
|
|
74
|
+
self.obsType,
|
|
75
|
+
';'.join([str(info) for info in self.obsInfo]),
|
|
76
|
+
self.fieldDataFile,
|
|
77
|
+
self.customFilter,
|
|
78
|
+
str(self.obsDefault),
|
|
79
|
+
''])
|
|
80
|
+
def makeObsDataInsLines(self,geo,dat):
|
|
81
|
+
""" generate a tuple of two lists:
|
|
82
|
+
- list of Pest * observation data """
|
|
83
|
+
# maybe + ('%5s' % str(self.obsInfo[0])) with fill?
|
|
84
|
+
if self.obsDefault.OBSNME == '':
|
|
85
|
+
newBaseName = PestObsDataName().newName(OBS_ALIAS[self.obsType])
|
|
86
|
+
PestObsDataName().add(newBaseName)
|
|
87
|
+
# user def routines use this as basename
|
|
88
|
+
self.obsDefault.OBSNME = newBaseName
|
|
89
|
+
# use obsType if not specified by user
|
|
90
|
+
if self.obsDefault.OBGNME == '':
|
|
91
|
+
self.obsDefault.OBGNME = self.obsType
|
|
92
|
+
|
|
93
|
+
### this line does all the work
|
|
94
|
+
self.all_obses = OBS_USER_FUNC[self.obsType+'_fielddata'](geo,dat,self)
|
|
95
|
+
|
|
96
|
+
self.all_pst_lines = []
|
|
97
|
+
self.all_ins_lines = []
|
|
98
|
+
for obs in self.all_obses:
|
|
99
|
+
self.all_ins_lines.append('l1 [%s]21:41' % obs.OBSNME)
|
|
100
|
+
self.all_pst_lines.append(' %-20s %20.13e %12.5e %s' % (obs.OBSNME,
|
|
101
|
+
obs.OBSVAL,obs.WEIGHT,obs.OBGNME))
|
|
102
|
+
|
|
103
|
+
def makeObfLines(self,geo,dat,lst):
|
|
104
|
+
""" generate a list of:
|
|
105
|
+
- values for obf file that PEST requires """
|
|
106
|
+
|
|
107
|
+
### this line does all the work
|
|
108
|
+
obfValues = OBS_USER_FUNC[self.obsType+'_modelresult'](geo,dat,lst,self)
|
|
109
|
+
self.all_obf_lines = []
|
|
110
|
+
for (v,obs) in zip(obfValues,self.all_obses):
|
|
111
|
+
self.all_obf_lines.append('%-20s %20.13e' % (obs.OBSNME,v))
|
|
112
|
+
|
|
113
|
+
def readUserObservation(userListName):
|
|
114
|
+
""" returns a list of UserEntryObserv from reading the file with name
|
|
115
|
+
userObsListName """
|
|
116
|
+
userEntries = []
|
|
117
|
+
f = open(userListName,'r')
|
|
118
|
+
entryName, entry = readList(f)
|
|
119
|
+
f.close()
|
|
120
|
+
# some defaults even if no sections exists:
|
|
121
|
+
obsDefault = PestObservData()
|
|
122
|
+
customFilter = 'True'
|
|
123
|
+
offsetTime = 0.0
|
|
124
|
+
for i,en in enumerate(entryName):
|
|
125
|
+
if en == 'ObservationType':
|
|
126
|
+
obsType = entry[i][0].strip()
|
|
127
|
+
continue
|
|
128
|
+
if en == 'DataFilter':
|
|
129
|
+
if len(entry[i]) == 0:
|
|
130
|
+
# reset
|
|
131
|
+
customFilter = 'True'
|
|
132
|
+
else:
|
|
133
|
+
customFilter = entry[i][0]
|
|
134
|
+
if en == 'DataTimeOffset':
|
|
135
|
+
if len(entry[i]) == 0:
|
|
136
|
+
# reset
|
|
137
|
+
offsetTime = 0.0
|
|
138
|
+
else:
|
|
139
|
+
offsetTime = float(eval(entry[i][0]))
|
|
140
|
+
if en == 'Defaults':
|
|
141
|
+
obsDefault = updateObj(obsDefault,entry[i])
|
|
142
|
+
# if en == 'Obs':
|
|
143
|
+
# if len(entry[i]) < 2: raise Exception
|
|
144
|
+
# if ',' in entry[i][0]:
|
|
145
|
+
# obsInfo = list(eval(entry[i][0]))
|
|
146
|
+
# else:
|
|
147
|
+
# obsInfo = [eval(entry[i][0])]
|
|
148
|
+
# for fieldDataFile in entry[i][1:]:
|
|
149
|
+
# userEntries.append(UserEntryObserv(obsType,obsInfo,
|
|
150
|
+
# fieldDataFile.strip(),customFilter,offsetTime,
|
|
151
|
+
# obsDefault))
|
|
152
|
+
if en == 'Obs':
|
|
153
|
+
if len(entry[i]) < 1:
|
|
154
|
+
raise Exception("An empty [Obs] entry is found in goPESTobs.list")
|
|
155
|
+
# pass the list of lines in, user functions to deal with them
|
|
156
|
+
obsInfo = [s.rstrip('\n') for s in entry[i]]
|
|
157
|
+
fieldDataFile = ''
|
|
158
|
+
userEntries.append(UserEntryObserv(obsType,obsInfo,
|
|
159
|
+
fieldDataFile.strip(),customFilter,offsetTime,
|
|
160
|
+
obsDefault))
|
|
161
|
+
return userEntries
|
|
162
|
+
|
|
163
|
+
def generate_obses_and_ins(fgeo, fdat, insToWrite, fobses, fplts='goPESTobs.json', fcovs='goPESTobs.coverage'):
|
|
164
|
+
""" reads goPESTobs.list and generate observation data lines and instruction
|
|
165
|
+
file for PEST """
|
|
166
|
+
# reset unique obs name
|
|
167
|
+
obs_def.obsBaseNameCount = {}
|
|
168
|
+
geo = mulgrid(fgeo)
|
|
169
|
+
if fdat.endswith('.json'):
|
|
170
|
+
with open(fdat, 'r') as f:
|
|
171
|
+
dat = json.load(f)
|
|
172
|
+
else:
|
|
173
|
+
dat = t2data(fdat)
|
|
174
|
+
|
|
175
|
+
userEntries = readUserObservation('goPESTobs.list')
|
|
176
|
+
pstLines, insLines, plots, coverage = [], [], [], {}
|
|
177
|
+
for ue in userEntries:
|
|
178
|
+
ue.makeObsDataInsLines(geo,dat)
|
|
179
|
+
pstLines = pstLines + ue.all_pst_lines
|
|
180
|
+
insLines = insLines + ue.all_ins_lines
|
|
181
|
+
plots += ue.batch_plot_entry
|
|
182
|
+
coverage = merge_dols(coverage, ue.coverage)
|
|
183
|
+
|
|
184
|
+
f = open(insToWrite, 'w')
|
|
185
|
+
f.write('pif #\n')
|
|
186
|
+
for line in insLines:
|
|
187
|
+
f.write(line+'\n')
|
|
188
|
+
f.close()
|
|
189
|
+
|
|
190
|
+
obs = open(fobses, 'w')
|
|
191
|
+
for line in pstLines:
|
|
192
|
+
obs.write(line + '\n')
|
|
193
|
+
obs.close()
|
|
194
|
+
|
|
195
|
+
plt = open(fplts, 'w')
|
|
196
|
+
json.dump(plots, plt, indent=4, sort_keys=True)
|
|
197
|
+
plt.close()
|
|
198
|
+
|
|
199
|
+
cov = open(fcovs, 'w')
|
|
200
|
+
json.dump(coverage, cov, indent=4, sort_keys=True)
|
|
201
|
+
cov.close()
|
|
202
|
+
|
|
203
|
+
def read_from_real_model(fgeo, fdat, flst, fobf, waiwera=False):
|
|
204
|
+
""" This reads TOUGH2's results and write in appropriate format into obf
|
|
205
|
+
file for PEST """
|
|
206
|
+
# reset unique obs name
|
|
207
|
+
obs_def.obsBaseNameCount = {}
|
|
208
|
+
geo = mulgrid(fgeo)
|
|
209
|
+
if waiwera:
|
|
210
|
+
with open(fdat, 'r') as f:
|
|
211
|
+
dat = json.load(f)
|
|
212
|
+
lst = wlisting(flst, geo, fjson=fdat)
|
|
213
|
+
else:
|
|
214
|
+
dat = t2data(fdat)
|
|
215
|
+
if flst.lower().endswith('.h5'):
|
|
216
|
+
lst = t2listingh5(flst)
|
|
217
|
+
else:
|
|
218
|
+
lst = t2listing(flst)
|
|
219
|
+
|
|
220
|
+
userEntries = readUserObservation('goPESTobs.list')
|
|
221
|
+
obfLines = []
|
|
222
|
+
for ue in userEntries:
|
|
223
|
+
ue.makeObsDataInsLines(geo,dat)
|
|
224
|
+
ue.makeObfLines(geo,dat,lst)
|
|
225
|
+
obfLines = obfLines + ue.all_obf_lines
|
|
226
|
+
|
|
227
|
+
if flst.lower().endswith('.listing'):
|
|
228
|
+
lst.close()
|
|
229
|
+
|
|
230
|
+
f = open(fobf,'w')
|
|
231
|
+
for line in obfLines:
|
|
232
|
+
f.write(line+'\n')
|
|
233
|
+
f.close()
|
|
234
|
+
|
|
235
|
+
def goPESTobs(argv=[]):
|
|
236
|
+
START_TIME = time.time()
|
|
237
|
+
|
|
238
|
+
userlistname = 'goPESTobs.list'
|
|
239
|
+
|
|
240
|
+
if len(argv) not in [4,5]:
|
|
241
|
+
print('to generate PEST .pst observation section and .ins: ')
|
|
242
|
+
print(' gopest obs geo dat newPESTins')
|
|
243
|
+
print('to read Tough2 results and write result file for PEST to read:')
|
|
244
|
+
print(' gopest obs geo dat lst newPESTobf')
|
|
245
|
+
|
|
246
|
+
if len(argv) == 4:
|
|
247
|
+
fgeo = argv[1]
|
|
248
|
+
fdat = argv[2]
|
|
249
|
+
insToWrite = argv[3]
|
|
250
|
+
|
|
251
|
+
fobses = 'pest_obs_data'
|
|
252
|
+
fplts = 'goPESTobs.json'
|
|
253
|
+
fcovs = 'goPESTobs.coverage'
|
|
254
|
+
generate_obses_and_ins(fgeo, fdat, insToWrite,
|
|
255
|
+
fobses, fplts, fcovs)
|
|
256
|
+
|
|
257
|
+
if len(argv) == 5:
|
|
258
|
+
|
|
259
|
+
fgeo = argv[1]
|
|
260
|
+
fdat = argv[2]
|
|
261
|
+
flst = argv[3]
|
|
262
|
+
obfToWrite = argv[4]
|
|
263
|
+
|
|
264
|
+
geo = mulgrid(fgeo)
|
|
265
|
+
if fdat.lower().endswith('.json'):
|
|
266
|
+
with open(fdat, 'r') as f:
|
|
267
|
+
dat = json.load(f)
|
|
268
|
+
else:
|
|
269
|
+
dat = t2data(fdat)
|
|
270
|
+
|
|
271
|
+
if flst.lower().endswith('.listing'):
|
|
272
|
+
lst = t2listing(flst)
|
|
273
|
+
elif flst.lower().endswith('.h5'):
|
|
274
|
+
if fdat.lower().endswith('.json'):
|
|
275
|
+
lst = wlisting(flst, geo, fjson=fdat)
|
|
276
|
+
else:
|
|
277
|
+
lst = t2listingh5(flst)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
userEntries = readUserObservation(userlistname)
|
|
282
|
+
obfLines = []
|
|
283
|
+
for ue in userEntries:
|
|
284
|
+
ue.makeObsDataInsLines(geo,dat)
|
|
285
|
+
ue.makeObfLines(geo,dat,lst)
|
|
286
|
+
obfLines = obfLines + ue.all_obf_lines
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
f = open(obfToWrite,'w')
|
|
290
|
+
for line in obfLines:
|
|
291
|
+
f.write(line+'\n')
|
|
292
|
+
f.close()
|
|
293
|
+
|
|
294
|
+
# print('goPESTobs finished after', (time.time() - START_TIME), 'seconds')
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|