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.
@@ -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
+
@@ -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
+