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/pest_model.py ADDED
@@ -0,0 +1,245 @@
1
+ """ This is the actual code that wraps TOUGH2 model run with pre- and post-
2
+ processing, so all PEST's model/batch file call this.
3
+
4
+ Use with optiona flags:
5
+ python pest_model.py [--svda] [--obsreref]
6
+
7
+ Run with "--svda" flag with additional command of parcalc for SVDassist runs.
8
+ Flag "--obsreref" is for observation re-referencing which reset save to incon
9
+ and overwrites the master's incon, which will be used by all subsequent model
10
+ runs. Flag "--test-update" will cause each slave to save a unique
11
+ real_model.save file back to master directory. This allows "--obsreref" to
12
+ later select the best save/incon to start with.
13
+
14
+ To generate/overwrite/fix the model/batch files, use:
15
+ python make_batch_files.py
16
+ """
17
+
18
+ import time
19
+ import sys
20
+ import glob
21
+ import os
22
+ from os import devnull, system, remove, sep, path
23
+ from shutil import copy2
24
+ from shutil import Error
25
+ from time import sleep
26
+
27
+ from numpy.testing import assert_approx_equal
28
+
29
+ from gopest.run_ns_pr import run_ns_pr
30
+ from gopest.par import generate_real_model
31
+ from gopest.obs import read_from_real_model
32
+
33
+ from gopest.common import config
34
+ from gopest.common import runtime
35
+
36
+ def get_master_dir():
37
+ # if config['mode'] != 'local':
38
+ with open('_master_dir', 'r') as f:
39
+ line = f.readlines()[0].strip()
40
+ return line
41
+
42
+ def get_pest_dir():
43
+ with open('_pest_dir', 'r') as f:
44
+ line = f.readlines()[0].strip()
45
+ return line
46
+
47
+ def get_t2():
48
+ with open('_tough2', 'r') as f:
49
+ line = f.readlines()[0].strip()
50
+ return line
51
+
52
+ def get_slave_id():
53
+ try:
54
+ with open('_procid', 'r') as f:
55
+ line = f.readlines()[0].strip()
56
+ return line
57
+ except:
58
+ return '0'
59
+
60
+ def par_match(pf1, pf2):
61
+ matched = False
62
+ with open(pf1,'r') as a:
63
+ with open(pf2,'r') as b:
64
+ # once open successfully, assume equal, until something fails
65
+ matched = True
66
+ try:
67
+ for aa,bb in zip(a,b):
68
+ ax, bx = float(aa.split(',')[0]), float(bb.split(',')[0])
69
+ assert_approx_equal(ax, bx, significant=7)
70
+ except AssertionError:
71
+ matched = False
72
+ return matched
73
+
74
+ def main(obsreref, svda, testup, local, skiprun, useobf, sendbad, skippr, hdf5, waiwera):
75
+ fgeo = runtime['filename']['geom']
76
+ fsave = runtime['filename']['save']
77
+ fincon = runtime['filename']['incon']
78
+ fdato = runtime['filename']['dat_orig']
79
+ fdats = runtime['filename']['dat_seq']
80
+ flsts = runtime['filename']['lst_seq']
81
+
82
+ print("----- Running " + " ".join(sys.argv[1:]))
83
+ if local:
84
+ master_dir = '.'
85
+ else:
86
+ master_dir = get_master_dir()
87
+ print(" --- Clean up pest_model.obf")
88
+ if path.isfile('pest_model.obf'):
89
+ remove('pest_model.obf')
90
+
91
+ ### skips everything if use obf directly
92
+ if useobf:
93
+ raise Exception('Should use pest_hp file distribution 17/12/2022')
94
+ if path.isfile('pest_model.obf.use'):
95
+ copy2(master_dir + sep + 'pest_model.obf.use', 'pest_model.obf')
96
+ print("Found pest_model.obf.use , skips everything.")
97
+ return
98
+ else:
99
+ print("Error, cannot find existing pest_model.obf.use file")
100
+
101
+ ### SVD-assist
102
+ if svda:
103
+ print(" --- PARCALC")
104
+ try:
105
+ remove('pest_model.dat')
106
+ except:
107
+ print("pest_model.dat probably does not exist")
108
+ PARCALC = path.join(get_pest_dir(), 'parcalc')
109
+ system(PARCALC + ' > ' + devnull)
110
+
111
+ ### goPESTpar
112
+ if not skiprun:
113
+ print(" --- goPESTpar")
114
+ generate_real_model(fdato, 'pest_model.dat', fdats[0])
115
+ # sleep(30) # just in case shared file system slow
116
+
117
+ if obsreref:
118
+ if path.isfile('pest_model.obf'):
119
+ remove('pest_model.obf')
120
+ # get matching incon, if exist
121
+ for parf in glob.glob(master_dir + sep + 'pest_model.dat.*'):
122
+ if par_match(parf, 'pest_model.dat'):
123
+ matchname = path.splitext(parf)[1]
124
+ print(" --- found matched incon/pars %s from master dir, overwrite Master INCON" % matchname)
125
+ copy2(master_dir + sep + fincon + matchname, master_dir + sep + fincon)
126
+ copy2(master_dir + sep + 'pest_model.obf' + matchname, 'pest_model.obf')
127
+ break
128
+ # print(" --- remove all pairs from labmda tests after searching")
129
+ # for f in glob.glob(master_dir + sep + 'pest_model.obf.*'):
130
+ # remove(f)
131
+ # for f in glob.glob(master_dir + sep + 'pest_model.dat.*'):
132
+ # remove(f)
133
+ # for f in glob.glob(master_dir + sep + 'real_model.incon.*'):
134
+ # remove(f)
135
+ if path.isfile('pest_model.obf'):
136
+ print(" --- use obf, skip actual model run")
137
+ return
138
+ else:
139
+ print(" --- could not find matching pars, obsreref continue with normal run")
140
+
141
+ ### RUN TOUGH2 model
142
+ if skiprun:
143
+ print(" --- skip actual TOUGH2 run")
144
+ else:
145
+ if not local:
146
+ print(" --- use master INCON")
147
+ try:
148
+ copy2(master_dir + sep + fincon, fincon)
149
+ except Error as e:
150
+ # OK if src and dst are the same file, simply skip.
151
+ print(e)
152
+
153
+ START_TIME = time.time()
154
+ print(" --- run_ns_pr()")
155
+ runok = run_ns_pr()
156
+ if obsreref:
157
+ if not local:
158
+ print(" --- reset Master INCON")
159
+ copy2('real_model.incon', master_dir + sep + 'real_model.incon')
160
+ else:
161
+ print(" --- .save file written as .incon")
162
+ if sendbad:
163
+ for f in glob.glob('bad_model_*'):
164
+ copy2(f, master_dir + sep + 'bad_model_slave' + get_slave_id() + '_' + f)
165
+ if not runok:
166
+ print(" --- run_ns_pr failed, skip goPESTobs, no obf, make sure lamforgive/derforgive is used.")
167
+ return
168
+ print(' --- run_ns_pr() complete after', (time.time() - START_TIME), 'seconds')
169
+
170
+ ### goPESTobs
171
+ # sleep(30) # just in case shared file system slow
172
+ print(" --- goPESTobs")
173
+ read_from_real_model(fgeo, fdats[-1], flsts[-1], 'pest_model.obf', waiwera=waiwera)
174
+
175
+ if testup:
176
+ print(" --- store lambda test (save,obf,pars) pair:" + get_slave_id())
177
+ copy2(fsave, master_dir + sep + fincon + '.' + get_slave_id())
178
+ copy2('pest_model.dat', master_dir + sep + 'pest_model.dat.' + get_slave_id())
179
+ copy2('pest_model.obf', master_dir + sep + 'pest_model.obf.' + get_slave_id())
180
+
181
+ def main_cli(argv=[]):
182
+ """ the main purpose of run-pest-model is to be called by PEST.
183
+
184
+ PEST needs:
185
+ - pest_model.tpl
186
+ - pest_model.ins
187
+
188
+ When PEST wants a forward run, PEST
189
+ 1. uses pest_model.tpl to generate: pest_model.dat
190
+ 2. runs the command: gopest run-pest-model
191
+ 3. uses pest_model.ins to read data from: pest_model.obf
192
+
193
+ under the hood, run-pest-model's job is to:
194
+ a. get parameters from pest_model.dat to construct real_model.dat
195
+ b. run actual simulation, which generates real_model.h5
196
+ c. converts real_model.h5 into pest_model.obf
197
+
198
+ a. is done by goPESTpar
199
+ b. is done by run-forward
200
+ c. is done by goPESTobs
201
+ """
202
+ obsreref = False
203
+ svda = False
204
+ testup = False
205
+ skiprun = False
206
+ useobf = False
207
+ sendbad = True
208
+ skippr = False
209
+ waiwera = False
210
+ hdf5 = False
211
+
212
+ if config['simulator']['output-type'] == 'h5':
213
+ hdf5 = True
214
+ else:
215
+ hdf5 = False
216
+
217
+ if config['simulator']['input-type'] == 'waiwera':
218
+ waiwera = True
219
+ hdf5 = True
220
+ else:
221
+ waiwera = False
222
+
223
+ # probably should be invoked from within gopest?
224
+ # local = config['pest']['mode'] == 'local'
225
+ local = False
226
+
227
+ skiprun = config['model']['skip']
228
+ skippr = config['model']['skip-pr']
229
+
230
+ print('pest_model.py running at ', os.getcwd())
231
+
232
+ if len(argv) > 1:
233
+ if '--test-update' in argv[1:]:
234
+ testup = True
235
+ if '--obsreref' in argv[1:]:
236
+ obsreref = True
237
+ if '--svda' in argv[1:]:
238
+ svda = True
239
+ if '--use-obf' in argv[1:]:
240
+ # requires existing pest_model.obf.use (PEST will remove
241
+ # pest_model.obf, so use different name)
242
+ useobf = True
243
+ if '--local' in argv[1:]:
244
+ local = True
245
+ main(obsreref, svda, testup, local, skiprun, useobf, sendbad, skippr, hdf5, waiwera)
@@ -0,0 +1,35 @@
1
+ import glob
2
+ import os
3
+ from shutil import copy2
4
+
5
+ from gopest.common import config
6
+
7
+ def current_iteration():
8
+ its = [int(f.split('.')[-1]) for f in glob.glob('%s.jco.*' % config['pest']['case-name'])]
9
+ its.append(0)
10
+ return max(its)
11
+
12
+ def rename_latest_files(argv=[]):
13
+ """ PEST_HP's file distribution will copy files from the best update slave
14
+ to here. They will be named as '*.999'. This script renames these to the
15
+ latest iteration number.
16
+ """
17
+ print('gopest save-iter-model (rename_latest_files.py):')
18
+
19
+ # try:
20
+ # copy2('real_model.incon.999', 'real_model.incon')
21
+ # print(" real_model.incon.999 -> real_model.incon (copy)")
22
+ # except Exception as e:
23
+ # print(" ", e)
24
+ # print(" failed to copy incon file for next iteration!")
25
+
26
+ ii = current_iteration()
27
+ print(' Current iteration is %i' % ii)
28
+
29
+ for f in glob.glob('*.999'):
30
+ newname = f.replace('.999', '.%i' % ii)
31
+ print(' %s -> %s' % (f, newname))
32
+ try:
33
+ os.rename(f, newname)
34
+ except Exception as e:
35
+ print(e)
gopest/run_beopest.py ADDED
@@ -0,0 +1,205 @@
1
+ import os
2
+ import sys
3
+ import shutil
4
+ import time
5
+ from subprocess import Popen, DEVNULL
6
+ from multiprocessing import cpu_count
7
+
8
+ from gopest.common import config as cfg
9
+ from gopest.common import runtime
10
+
11
+ NUM_SLAVES = cfg['pest']['num_slaves']
12
+ SILENT_SLAVES = cfg['pest']['silent_slaves']
13
+ PST_NAME = cfg['pest']['case-name']
14
+ PORT = cfg['pest']['port']
15
+ SWITCHES = " ".join(cfg['pest']['switches'])
16
+
17
+ TOUGH2 = cfg['simulator']['executable']
18
+ BEOPEST = cfg['pest']['executable']
19
+ PESTDIR = cfg['pest']['dir']
20
+
21
+ """
22
+ This script helps to launch BeoPEST on the case in current directory (either
23
+ 'case.pst' or 'case_svda.pst'). Use:
24
+
25
+ python run_beopest.py
26
+
27
+ Requires:
28
+ - Python 2.6 or 2.7, NumPy
29
+ - PEST and TOUGH2 executables, see settings above.
30
+
31
+ Most of the important settings are listed above. Instead of letting PEST runs
32
+ TOUGH2 models directly, actual model runs were controlled by the pest_model.py
33
+ script, which uses the goPEST interface and run_ns_pr.py. (goPEST is a little
34
+ like PLPROC)
35
+
36
+ In the actual workflow, there are only a couple files that need attention:
37
+
38
+ - goPESTpar.list a list of parameters (goPESTpar.py translates this into
39
+ actual PEST parameter data)
40
+ - goPESTobs.list a list of observations (goPESTobs.py translates this
41
+ into actual PEST observation data)
42
+
43
+ Brief workflow:
44
+
45
+ 1. edit goPESTpar.list and goPESTobs.list
46
+
47
+ 2. run 'python make_case_pst.py', which processed two lists above and
48
+ updates case.pst. This also initialise the parameter values from the values
49
+ in the real_model_original.dat (.pdat).
50
+
51
+ 3. inspect/modify/check case.pst
52
+
53
+ 4. run 'python run_beopest.py'
54
+
55
+ 5. to obtain the model with optimised parameters run 'python
56
+ make_real_model.py' or 'python make_real_model_svda.py', now
57
+ real_model.dat is the updated model, run_ns_pr.py can be used to run this
58
+ model.
59
+
60
+ 6. occasionally run 'make_batch_files.py', eg. after svdaprep, so that model
61
+ batch files are correct. This should be run after moving the folder between
62
+ platform as well.
63
+
64
+ To update/change a TOUGH2 model, the following files needs to be updated:
65
+
66
+ - g_real_model.dat (model's mulgrid/geometry file)
67
+ - real_model_original.dat (and .pdat)
68
+ - real_model_original_pr.dat (and .pdat, rocktypes, geners not as important,
69
+ they are processed from the natural state real_model_ogirinal.dat)
70
+ - real_model.incon
71
+ - and of course goPESTpar.list and goPESTobs.list
72
+ - make sure to run 'python make_case_pst.py'
73
+
74
+ """
75
+
76
+ if NUM_SLAVES is None:
77
+ NUM_SLAVES = max(cpu_count() - 2, 1)
78
+ if PESTDIR:
79
+ BEOPEST = os.path.join(PESTDIR,BEOPEST)
80
+
81
+ def ignore_dirs(folder, files):
82
+ """ for shutil.copytree, ignores all subdirectories, ie. copy only files
83
+ """
84
+ ignore_list = []
85
+ for f in files:
86
+ full_path = os.path.join(folder, f)
87
+ if os.path.isdir(full_path):
88
+ ignore_list.append(f)
89
+ return ignore_list
90
+
91
+ def run_cli(argv=[]):
92
+ """ generate master and slave commands, copy files into slave directories,
93
+ and launch them as process, wait until all finished.
94
+ """
95
+ if "pest_hp" in BEOPEST.lower():
96
+ MASTER = BEOPEST
97
+ if PESTDIR:
98
+ AGENT = os.path.join(PESTDIR, cfg['pest']['executable_agent'])
99
+ else:
100
+ AGENT = cfg['pest']['executable_agent']
101
+ else:
102
+ MASTER = BEOPEST
103
+ AGENT = BEOPEST
104
+
105
+ generate_comm_files()
106
+ print('Running BeoPEST with silent=', SILENT_SLAVES)
107
+
108
+ # each command is a tuple (args, other options), see subproces.Popen()
109
+ master = ([MASTER, PST_NAME, SWITCHES, '/h :%s' % PORT], {} )
110
+ slaves, slaves_outs = [], []
111
+ for i in range(NUM_SLAVES):
112
+ s_dir = 'slave%i' % (i+1)
113
+ if os.path.exists(s_dir):
114
+ shutil.rmtree(s_dir)
115
+ shutil.copytree('.', s_dir, ignore=ignore_dirs)
116
+ if SILENT_SLAVES:
117
+ # stdout_file = DEVNULL
118
+ stdout_file = open(s_dir + '/stdout.txt', 'w')
119
+ slaves_outs.append(stdout_file)
120
+ else:
121
+ stdout_file = None
122
+ slaves.append((
123
+ [AGENT, PST_NAME, '/h localhost:%s' % PORT],
124
+ {
125
+ 'cwd': s_dir,
126
+ 'stdout': stdout_file,
127
+ 'stderr': stdout_file,
128
+ }))
129
+
130
+ # start master, sleep a bit, then start all slaves
131
+ ps = []
132
+ ps.append(Popen(master[0], **master[1]))
133
+ time.sleep(0.1)
134
+ for s in slaves:
135
+ ps.append(Popen(s[0], **s[1]))
136
+ for p in ps:
137
+ p.wait()
138
+ for f in slaves_outs:
139
+ f.close()
140
+
141
+ def gen_run_management_file(nslave, slaves, wait=0.2, parlam=1, runtime=3600):
142
+ """ generate run management file required by ppest/jactest. I am
143
+ implementing the simplified version here, single template and instruction
144
+ file only. TODO extend to be more flexible, which may require more info
145
+ from pest control file. Each slaves is a list of tuple (SLAVNAME,SLAVDIR),
146
+ should have the same length as nslaves.
147
+ """
148
+ ifletyp = 0 # for now
149
+ lines = []
150
+ lines += [
151
+ 'prf',
152
+ '%i %i %f %i' % (nslave, ifletyp, wait, parlam),
153
+ ]
154
+ for i in range(nslave):
155
+ slavname, slavdir = slaves[i]
156
+ if " " in slavname or "'" in slavname:
157
+ slavname = slavname.replace("'", "''")
158
+ slavname = "'" + slavname + "'"
159
+ lines.append(slavname + ' ' + slavdir)
160
+ lines.append(' '.join([str(runtime)] * nslave))
161
+ # TODO: support non-zero IFLETYP
162
+ return '\n'.join(lines)
163
+
164
+ def run_pslaves(master_command):
165
+ """ generate master and slave commands, copy files into slave directories,
166
+ and launch them as process, wait until all finished. master_command should
167
+ be a list of strings [cmd, arg1, arg2, ...].
168
+ """
169
+ # each command is a tuple (args, other options), see subproces.Popen()
170
+ master = (master_command, {} )
171
+ slaves = []
172
+ for i in range(NUM_SLAVES):
173
+ s_dir = 'slave%i' % (i+1)
174
+ if os.path.exists(s_dir):
175
+ shutil.rmtree(s_dir)
176
+ shutil.copytree('.', s_dir, ignore=ignore_dirs)
177
+ slaves.append((
178
+ [BEOPEST, PST_NAME, '/h localhost:%s' % PORT],
179
+ {
180
+ 'cwd': s_dir,
181
+ }))
182
+
183
+ # start master, sleep a bit, then start all slaves
184
+ ps = []
185
+ ps.append(Popen(master[0], **master[1]))
186
+ time.sleep(0.1)
187
+ for s in slaves:
188
+ ps.append(Popen(s[0], **s[1]))
189
+ for p in ps:
190
+ p.wait()
191
+
192
+ def generate_comm_files():
193
+ """ generate some files contain information so slaves know where things are.
194
+ TODO: to improve, there should be better ways, partially to fit with my
195
+ original NeSI system.
196
+ """
197
+ def write_to(filename, line):
198
+ with open(filename, 'w') as f:
199
+ f.write(line)
200
+ write_to('_master_dir', os.getcwd())
201
+ write_to('_pest_dir', PESTDIR)
202
+ write_to('_tough2', TOUGH2)
203
+
204
+ if __name__ == '__main__':
205
+ run_cli()