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,11 @@
1
+ Copyright 2022 Angus Yeh, University of Auckland, New Zealand
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.1
2
+ Name: goPEST
3
+ Version: 0.0.11
4
+ Summary: A TOUGH2/Waiwera interface to PEST
5
+ Author-email: Angus Yeh <a.yeh@auckland.ac.nz>
6
+ License: BSD 3-Clause License
7
+ Keywords: goPEST,PEST,Waiwera,TOUGH2,AUTOUGH2
8
+ Classifier: Development Status :: 2 - Pre-Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: License :: OSI Approved :: BSD License
11
+ Classifier: Topic :: Scientific/Engineering
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=2.7
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: PyTOUGH
17
+ Requires-Dist: numpy
18
+ Requires-Dist: scipy
19
+ Requires-Dist: h5py
20
+ Requires-Dist: pyyaml
21
+ Requires-Dist: tomlkit
22
+ Requires-Dist: xlwt
23
+ Requires-Dist: setuptools-scm
24
+
25
+ # **goPEST**
26
+
27
+ goPEST is a set of utilities used to interface PEST with Waiwera and (AU)TOUGH2 simulators.
28
+
29
+
30
+ ## Install
31
+
32
+ The easiest way to install goPEST is:
33
+
34
+ ```python -m pip install gopest```
35
+
36
+
37
+ ## Input files
38
+
39
+ User needs to prepare a few files for goPEST to work:
40
+
41
+ - `goPESTconfig.toml`, this file contains all settings/configurations related to the running of goPEST. It is written in TOML file format. The file should be placed in the project folder, where user runs gopest commands from. It is possible to let goPEST generate one with default values. The file tries to be self-explanatory with comments.
42
+
43
+ - `goPESTpar.list` is where user specifies model parameters for PEST
44
+
45
+ - `goPESTobs.list` is where user specifies model observations for PEST
46
+
47
+ - `goPESTuser.py` is optional if user wish do perform customised setups when the simulation transit from one stage to the next stage in sequence of simulator runs (eg. usually `ns` and `pr` normally natural state then followed by production history).
48
+
49
+ ## Basic usage
50
+
51
+ The main CLI script `gopest` is to be followed by COMMAND and associated arguments:
52
+
53
+ ```gopest COMMAND [ARGUMENTS]```
54
+
55
+ To get a list of supported COMMANDs, type `gopest help`.
56
+
57
+ The first step is to initialise the working directory:
58
+
59
+ ```gopest init```
60
+
61
+ will setup the current folder. If all goes well, user can simply run the command:
62
+
63
+ ```gopest submit``` or
64
+ ```gopest run```
65
+
66
+ to start the PEST run. Command `submit` is for the NeSI cluster environment using SLURM. Job(s) will be submitted to the cluster queue. PEST master and agents will be launched automatically. On a local machine where user has full access `gopest run` is used.
67
+
68
+ ## How goPEST works
69
+
70
+ Several tasks were performed by the `init` command:
71
+
72
+ - copy user's model files into what goPEST uses internally, these are the `real_model_xxx.*` files, in the current folder, which is also the master folder where PEST is expected to work on.
73
+
74
+ - go through `goPESTpar.list`, extract and set up parameter data in the PEST control file (usually `case.pst`). The corresponding `.tpl` files etc required by PEST will be set up. Note the parameters used in the real model will be extracted and used as the initial parameters in the PEST.
75
+
76
+ - go through `goPESTobs.list`, and set up observation data in the PEST control file. Corresponding PEST instruction file `.ins` will also be set up automatically.
77
+
78
+ ## Development notes
79
+
80
+ - install an editable version of goPEST:
81
+
82
+ ```python -m pip install -e /path/to/repo/root```
83
+
84
+ - run tests at the root of the repo:
85
+
86
+ ```python -m pytest```
87
+
88
+ - generalised model sequence runner? now loads user goPESTuser.py, but internal needs to generalise to have more than two run sequence
89
+
90
+ - in run_ns_pr, code shouldn't worry about nesi/cluster related things, maybe
91
+ not running local vs nesi either
92
+
93
+ - remove obsreref related things, use pest_hp now
94
+
95
+ - I have checked a few PEST related Python libraries. I am looking for something small and pure for basic editing of PEST control file. But these are too big for my liking. I should reconsider about using them.
@@ -0,0 +1,30 @@
1
+ gopest/__init__.py,sha256=sQIeyIA_BvmrEsXURFJskDg6rlPDNJLjFoFJcHn-MkQ,374
2
+ gopest/_version.py,sha256=TLLlZVpoPCYB-ad4nVSzPokzjT6xk0F2KolI_iEJXtA,429
3
+ gopest/check_slaves.py,sha256=PdpDHRjHHl1fJBwhXb8mAhSt6QXK1cJQ03goUXjyumE,13769
4
+ gopest/commands.py,sha256=xXERkb5hb_hbfUZMy1qdD78q1ZakHH5x-1WSyKxzs14,2651
5
+ gopest/common.py,sha256=BT_DqgpqvqnM8obpyt9IOg5OuyqSN1aWuBKqfrUYyu4,7664
6
+ gopest/make_case_pst.py,sha256=DNb1Ta_HE2uYwzlFti9tZRwF80pyvWyavws77LAbSfM,8760
7
+ gopest/obs.py,sha256=rFnWIddecp4Kwx_OHDIbt5uxHTnYI_C3YcaWoznY8cs,10047
8
+ gopest/obs_def.py,sha256=oFYOD8S7HnA1bSRVLRrlKtqBlpwxbcWu_izf88U6ZZo,78308
9
+ gopest/par.py,sha256=lm21mqbwL58I9gLv67mCrVvMC_6BVK7L_4kblTuvgtc,13043
10
+ gopest/par_def.py,sha256=gT-7BTk7zYTYzc3U8WKIRxXAfxIlykTYpvtJbNKBGxM,10841
11
+ gopest/pest_model.py,sha256=KqL1mSrOqP-T3ljyE8UiLsa7FvlEgohnEvai8HiT-Q4,8666
12
+ gopest/rename_latest_files.py,sha256=gZwbZMwni0ZQPjCgOL3C6JG65jO9gFPR9vN9QftqRFY,1095
13
+ gopest/run_beopest.py,sha256=VeuLYI2Wr2leocV92Rowm_s38B_04eJ1dcV1WiMFCe0,7077
14
+ gopest/run_ns_pr.py,sha256=tC5Umidn1gZBHRm2XoM-7FSUlMwWKa9WRxA0C1yfG0U,22721
15
+ gopest/submit_beopest.py,sha256=Q5Z-o6Lz4uuGN8di7NojtiUlRUNHZI6KY_Mmhs30reE,35142
16
+ gopest/data/case.pst,sha256=xGNftyacW3twbqmPUN8V3ZQuNaMyqoGJB0o7Y327fso,2506
17
+ gopest/data/goPESTconfig.aut2.toml,sha256=mrMtwqKy1_4R6qQAPNqLBcMxqVKzrdmSbPpg6rEzD9g,2782
18
+ gopest/data/goPESTconfig.toml,sha256=GbcWFMzStaIEwgStPVyxE7RaIBMVCpZKbMk98r8YdZU,2767
19
+ gopest/data/goPESTobs.list,sha256=aNdlcoP8MQVvsgHaInCnQ_bktFvtiBFCWiPwWI_lkEs,10351
20
+ gopest/data/goPESTpar.list,sha256=Ne-OEsOVh_JtLBVF8HMG5EOmYl8g_SD5mWsbExD2oq4,1852
21
+ gopest/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ gopest/utils/gener_groups.py,sha256=VtbX35VWKE0V0wVBJnUXC0KtcfY-fIz4XrV3OBSYngI,6624
23
+ gopest/utils/t2listingh5.py,sha256=36X-XUdakSkRSn26ZdU8uoAKEfsFydiT0p3xzsobjGk,16791
24
+ gopest/utils/waiwera_listing.py,sha256=c7moQY8DQEH_MqLUuhrinnkEHnDMKWx4srChffXoK6I,27787
25
+ goPEST-0.0.11.dist-info/LICENSE,sha256=ptsyzynWmtAQdzQN1KIqWAkMbJMyKG03PzYniyCa1TE,1490
26
+ goPEST-0.0.11.dist-info/METADATA,sha256=8OBaPfrqRIAxA0-qAuic1YCoTohqWuJemiR4IZL1Zkc,3885
27
+ goPEST-0.0.11.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
28
+ goPEST-0.0.11.dist-info/entry_points.txt,sha256=lQZ0lnYLOBrcoQehLV6TZsLFu1OJPLcw-PCOUs0_SpA,54
29
+ goPEST-0.0.11.dist-info/top_level.txt,sha256=g3FO8XhhpDnQXWy5B9lv4uMKMEilq1TKv8SAk5x8_eU,7
30
+ goPEST-0.0.11.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.2.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ gopest = gopest.commands:gopest_cli
@@ -0,0 +1 @@
1
+ gopest
gopest/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ try:
2
+ # works if calling from an repo or an pip install -e installation
3
+ from setuptools_scm import get_version
4
+ __version__ = get_version()
5
+ except LookupError:
6
+ try:
7
+ # works if calling from other pip install methods
8
+ from ._version import version as __version__
9
+ except ImportError:
10
+ # worst case scenario
11
+ __version__ = '0.0.0'
gopest/_version.py ADDED
@@ -0,0 +1,16 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '0.0.11'
16
+ __version_tuple__ = version_tuple = (0, 0, 11)
gopest/check_slaves.py ADDED
@@ -0,0 +1,402 @@
1
+ from multiprocessing import Pool, cpu_count
2
+ import time
3
+ import json
4
+ import os
5
+ import os.path
6
+ import sys
7
+ import shutil
8
+ import glob
9
+ import subprocess
10
+ import collections.abc
11
+
12
+ import yaml
13
+ import tomlkit
14
+ import xlwt
15
+
16
+ from gopest.common import config
17
+ from gopest.common import runtime
18
+
19
+ def nested_dict_update(d, u):
20
+ """ update a nested dict object with an update dict
21
+ https://stackoverflow.com/a/3233356/2368167
22
+ """
23
+ for k, v in u.items():
24
+ if isinstance(v, collections.abc.Mapping):
25
+ d[k] = nested_dict_update(d.get(k, {}), v)
26
+ else:
27
+ d[k] = v
28
+ return d
29
+
30
+ def unix2dos(fname):
31
+ import sys
32
+ with open(fname, 'rb') as infile:
33
+ instr = infile.read()
34
+ outstr = instr.replace( b"\r\n", b"\n" ).replace( b"\r", b"\n" ).replace( b"\n", b"\r\n" )
35
+ if len(outstr) == len(instr):
36
+ return
37
+ with open(fname, 'wb') as outfile:
38
+ outfile.write( outstr )
39
+
40
+ def fixpcf_noptmax(fpst, noptmax=0):
41
+ """ update PEST case file (.pst) with NOPTMAX, number of optimisation
42
+ iterations
43
+ """
44
+ fpst_bk = fpst + '.backup'
45
+ if not os.path.isfile(fpst):
46
+ print('Error: %s does not exist.' % fpst)
47
+ exit(1)
48
+ if os.path.isfile(fpst_bk):
49
+ os.remove(fpst_bk)
50
+ os.rename(fpst, fpst_bk)
51
+
52
+ try:
53
+ with open(fpst_bk,'r') as fin, open(fpst, 'w') as fout:
54
+ linesep = None # use current file's line ending
55
+ isec = 7 # start with 7, so that it will never be 7 again
56
+ for line in fin:
57
+ # detect current file's line ending first
58
+ if linesep is None:
59
+ if line[-2] == '\r' and line[-1] == '\n':
60
+ linesep = '\r\n'
61
+ elif line[-1] == '\n':
62
+ linesep = '\n'
63
+ # find correct line, replace
64
+ isec += 1
65
+ oline = line
66
+ if line.strip().startswith('*') and 'control data' in line:
67
+ isec = 0
68
+ if isec == 7:
69
+ # replace first item in line (NOPTMAX)
70
+ sps = line.split()
71
+ oline = ' ' + ' '.join([str(noptmax)] + sps[1:] + [linesep])
72
+ fout.write(oline)
73
+ except Exception as e:
74
+ fin.close()
75
+ fout.close()
76
+ print('fixpcf_noptmax() failed to proceed, restoring...')
77
+ os.remove(fpst)
78
+ os.rename(fpst_bk, fpst)
79
+ raise(e)
80
+
81
+ def read_rec(rec_file):
82
+ values = {}
83
+ with open(rec_file, 'r') as f:
84
+ reading = False
85
+ for line in f:
86
+ if reading:
87
+ if empty_line:
88
+ empty_line = False
89
+ continue
90
+ if not line.strip():
91
+ continue
92
+ if '----->' in line:
93
+ break
94
+ sps = line.strip().split('=')
95
+ v = float(sps[-1])
96
+ s = sps[0].strip()
97
+ if s == 'Sum of squared weighted residuals (ie phi)':
98
+ k = 'phi'
99
+ elif s == 'Optimised measurement objective function':
100
+ k = 'phi measured'
101
+ elif s == 'Optimised regularisation objective function':
102
+ k = 'phi regularisation'
103
+ elif '"' in s:
104
+ k = s.split('"')[1]
105
+ else:
106
+ k, v = None, None
107
+ if k is not None:
108
+ values[k] = v
109
+ if line.startswith('Objective function ----->'):
110
+ reading, empty_line = True, True
111
+ return values
112
+
113
+ class message(object):
114
+ """ A Waiwera log (.yaml) message object """
115
+ def __init__(self, msg):
116
+ self._msg = msg
117
+ self.level, self.source, self.event, self.data = msg
118
+
119
+
120
+ def check_sim_ends(spath):
121
+ """ extract summary of waiwera simulation from yaml log """
122
+ def waiwera_ends(fyaml):
123
+ """ return elapsed time in seconds of waiwera simulation time, None if not
124
+ successful """
125
+ try:
126
+ with open(fyaml) as f:
127
+ data = yaml.load(f, Loader=yaml.CSafeLoader)
128
+ r = {''}
129
+ if data[-3][:3] == ['info', 'timestep', 'end']:
130
+ time = data[-3][3]['time']
131
+ size = data[-3][3]['size']
132
+ if data[-1][:3] == ['info', 'simulation', 'destroy']:
133
+ elapsed = data[-1][3]['elapsed_seconds']
134
+ return {'endtime': time, 'stepsize': size, 'elapsed': elapsed}
135
+ except:
136
+ return {}
137
+ cwd = os.getcwd()
138
+ os.chdir(spath)
139
+ print('Working in %s...' % spath)
140
+
141
+ results = {'run': {}}
142
+ sequence = config['model']['sequence']
143
+ input_typ = config['simulator']['input-type']
144
+ flsts = runtime['filename']['lst_seq']
145
+ for i,seq in enumerate(sequence):
146
+ results['run'][seq] = {}
147
+ if input_typ== 'waiwera':
148
+ fyaml = flsts[i].replace('.h5', '.yaml')
149
+ results['run'][seq] = waiwera_ends(fyaml)
150
+ else:
151
+ raise NotImplementedError()
152
+
153
+ os.chdir(cwd)
154
+ # print('Restored directory %s' % cwd)
155
+ return results
156
+
157
+ def check_run_status(spath):
158
+ """ extract summary of waiwera simulation from yaml log """
159
+ cwd = os.getcwd()
160
+ os.chdir(spath)
161
+ print('Working in %s...' % spath)
162
+
163
+ results = {'run': {}}
164
+ sequence = config['model']['sequence']
165
+ input_typ = config['simulator']['input-type']
166
+ flsts = runtime['filename']['lst_seq']
167
+ for i,seq in enumerate(sequence):
168
+ results['run'][seq] = {}
169
+ results['run'][seq]['exists'] = os.path.exists(flsts[i])
170
+
171
+ os.chdir(cwd)
172
+ # print('Restored directory %s' % cwd)
173
+ return results
174
+
175
+ def get_obj_fn(spath):
176
+ """ Return PEST calculated objective function values by modifying config and
177
+ PEST case file to run a dummy run. Then read results from case .rec file.
178
+
179
+ This is more destructive, but I have decided to leave those modification
180
+ in place after running the command for a couple of reasons:
181
+ 1. (lazy) no need to handle undo the mod if exception is raised
182
+ 2. (safe) it's probably safer to leave as modified here, reduce possibility
183
+ for user to wipe out existing model output files, which can be costly to
184
+ run
185
+
186
+ Unfortunately I need to modify the config.toml file, because gopest run-
187
+ pest-model is involked by PEST, not this script directly.
188
+ """
189
+ cwd = os.getcwd()
190
+ os.chdir(spath)
191
+ print('Working in %s...' % spath)
192
+
193
+ # only works if the final real model output file exists
194
+ flst = runtime['filename']['lst_seq'][-1]
195
+ if not os.path.exists(flst):
196
+ print('Real model output file %s is missing, cannot get obj. fn.' % flst)
197
+ os.chdir(cwd)
198
+ return {}
199
+
200
+ # modify config to skip model run
201
+ with open('goPESTconfig.toml', 'r') as f:
202
+ cfg = tomlkit.load(f)
203
+ cfg['model']['skip'] = True
204
+ with open('goPESTconfig.toml', 'w') as f:
205
+ tomlkit.dump(cfg, f)
206
+
207
+ # modify NOPTMAX in control data .pst file
208
+ fpst = config['pest']['case-name'] + '.pst'
209
+ fixpcf_noptmax(fpst, 0)
210
+
211
+ cmd = [
212
+ os.path.join(config['pest']['dir'], config['pest']['executable']),
213
+ config['pest']['case-name'],
214
+ ]
215
+ print('Running: ', cmd)
216
+ results = {}
217
+ subprocess.call(cmd)
218
+ results['obj-fn'] = read_rec('case_reg.rec')
219
+
220
+ os.chdir(cwd)
221
+ # print('Restored directory %s' % cwd)
222
+ return results
223
+
224
+ def export_xls(data):
225
+ phis, regs, others = [], [], []
226
+ for sln,sl in data.items():
227
+ if 'obj-fn' in sl:
228
+ for n in sorted(sl['obj-fn'].keys()):
229
+ if n.startswith('phi'):
230
+ phis.append(n)
231
+ elif n.startswith('regul_'):
232
+ regs.append(n)
233
+ else:
234
+ others.append(n)
235
+ phis, regs, others = sorted(phis), sorted(regs), sorted(others)
236
+ break
237
+ runcs = []
238
+ for sln,sl in data.items():
239
+ if 'run' in sl:
240
+ for rn,r in sl['run'].items():
241
+ runcs += [rn+'.'+c for c in sorted(r.keys())]
242
+ break
243
+ cols = ['slave'] + phis + others + regs + runcs
244
+
245
+ wb = xlwt.Workbook()
246
+ ws = wb.add_sheet('slaves')
247
+
248
+ i = 0
249
+ for j,c in enumerate(cols):
250
+ ws.write(i, j, c)
251
+
252
+ for sln,sl in data.items():
253
+ if 'obj-fn' in sl:
254
+ i += 1
255
+ j = 0
256
+ ws.write(i, j, sln)
257
+ for p in (phis + others + regs):
258
+ j += 1
259
+ if p in sl['obj-fn']:
260
+ ws.write(i, j, sl['obj-fn'][p])
261
+ for r in runcs:
262
+ j += 1
263
+ rn,sn = r.split('.')
264
+ if 'run' in sl and rn in sl['run'] and sn in sl['run'][rn]:
265
+ ws.write(i, j, sl['run'][rn][sn])
266
+ wb.save('goPESTslaves.xls')
267
+
268
+ def init_slave(cfg, rt):
269
+ """ used in multiprocessing Pool to initialise threads with the the updated
270
+ gopest.common.config and gopest.common.runtime
271
+ """
272
+ print('Updating local thread common.config and common.runtime.')
273
+ global config, runtime
274
+ config = cfg
275
+ runtime = rt
276
+
277
+ hlp = '''
278
+ Usage: gopest check-slaves [--help] [--status] [--end-time] [--obj-fn]
279
+ [--dir path_to_slaves] [--pest-exe pest_executable]
280
+ [--export-xls]
281
+
282
+ The check-slaves command searches through slave directories and obtain/collect
283
+ their running status etc. By default, the pest.slave_dirs property from
284
+ goPESTconfig.toml is used. It is possible to specify a different directory by
285
+ using argument "--dir".
286
+
287
+ Extracted information will be dumped into a JSON file "goPESTslaves.json". Note
288
+ that the command will only update/append results into the JSON file if exists.
289
+ It tries not to destroy whatever that is already in the file.
290
+
291
+ "--status" is the fastest option, which only checks if model output files exist
292
+ in the lsave directories. (ie. did the runs produce any output files at all)
293
+
294
+ "--end-time" enables extraction of simulation end time (from output YAML file if
295
+ running waiwera as simulator).
296
+
297
+ "--obj-fn" is more destructive. Within each slave directory, it will run PEST
298
+ (with the NOPTMAX set to 0) once with the internal settings set to skip actual
299
+ model runs. This essentially runs PEST so that observations and objective
300
+ function will be extracted from model outputs within the slave directory.
301
+
302
+ "--pest-exe" is useful with the "--obj-fn" option when user have the slaves
303
+ directories in a different environment than where it was originally run.
304
+ pest_executable here can include the path to the executable if it's not already
305
+ in the system's PATH.
306
+
307
+ "--export-xls" is used to export contents of goPESTslaves.json into spreadsheet
308
+ file goPESTslaves.xls. This command can be used alone as it will load existing
309
+ goPESTslaves.json.
310
+
311
+ This command runs locally, and does not utilise any slurm/srun/queue facilities.
312
+
313
+ This message can be printed with the "--help" argument. It is possible to
314
+ include more than one option at a time.
315
+ '''
316
+
317
+ def check_slaves_cli(argv=[]):
318
+ if '--help' in argv or len(argv) <= 1:
319
+ print(hlp)
320
+ exit(0)
321
+ else:
322
+ # overwrite slave_dirs
323
+ if '--dir' in argv:
324
+ iarg = argv.index('--dir') + 1
325
+ try:
326
+ spath = argv[iarg]
327
+ except IndexError:
328
+ raise Exception('--dir argument needs to be followed by path_to_slaves')
329
+ else:
330
+ spath = config['pest']['slave_dirs']
331
+ # overwrite PEST exe
332
+ if "--pest-exe" in argv:
333
+ iarg = argv.index('--pest-exe') + 1
334
+ try:
335
+ pexe = argv[iarg]
336
+ except IndexError:
337
+ raise Exception('--pest-exe argument needs to be followed by pest_executable')
338
+ # modify the shared module variable
339
+ config['pest']['dir'] = ''
340
+ config['pest']['executable'] = pexe
341
+ else:
342
+ # do nothing, keeps original setting
343
+ pass
344
+ # tasks
345
+ nopts, tasks = 0, []
346
+ if "--status" in argv:
347
+ nopts += 1
348
+ tasks.append('status')
349
+ if "--end-time" in argv:
350
+ nopts += 1
351
+ tasks.append('end-time')
352
+ if "--obj-fn" in argv:
353
+ nopts += 1
354
+ tasks.append('obj-fn')
355
+ xls = False
356
+ if "--export-xls" in argv:
357
+ nopts += 1
358
+ xls = True
359
+ if nopts == 0:
360
+ print(hlp)
361
+ print('Please specify at least one task to perform.')
362
+ exit(0)
363
+
364
+ start_time = time.time()
365
+ if not all([os.path.exists(spath), os.path.isdir(spath)]):
366
+ raise Exception('Specified path_to_slaves needs to be a valid directory: %s' % spath)
367
+
368
+ task_fn = {
369
+ 'end-time': check_sim_ends,
370
+ 'status': check_run_status,
371
+ 'obj-fn': get_obj_fn,
372
+ }
373
+
374
+ slave_paths = sorted([p for p in glob.glob(os.path.join(spath, '*')) if os.path.isdir(p)])
375
+ slave_names = [os.path.basename(sp) for sp in slave_paths]
376
+
377
+ ncpu = max(1, cpu_count() - 1)
378
+ print('Starting %i workers to process %i slaves' % (ncpu, len(slave_paths)))
379
+ pool = Pool(ncpu, initializer=init_slave, initargs=(config, runtime))
380
+
381
+ fout = 'goPESTslaves.json'
382
+ if os.path.exists(fout):
383
+ with open(fout, 'r') as f:
384
+ data = json.load(f)
385
+ else:
386
+ data = {}
387
+
388
+ for task in tasks:
389
+ print('Checking %s...' % task)
390
+ results = pool.map(task_fn[task], slave_paths)
391
+ data = nested_dict_update(data, dict(zip(slave_names, results)))
392
+
393
+ with open(fout, 'w') as f:
394
+ json.dump(data, f, indent=4, sort_keys=True)
395
+
396
+ if xls:
397
+ export_xls(data)
398
+
399
+ print('Finished after %f seconds' % (time.time() - start_time))
400
+
401
+ if __name__ == '__main__':
402
+ check_slaves_cli(sys.argv)
gopest/commands.py ADDED
@@ -0,0 +1,80 @@
1
+ import sys
2
+
3
+ from gopest import __version__
4
+
5
+ title = """
6
+ goPEST - Interfacing PEST with Waiwera and (AU)TOUGH2 simulators
7
+ """
8
+
9
+ version = """Version: (%s)
10
+ """ % __version__
11
+
12
+ hlp = """
13
+ Usage: gopest COMMAND [ARGUMENTS]
14
+
15
+ Supported COMMANDs:
16
+ help
17
+ init [--no-copy][--no-par][--no-obs] (make_case_pst)
18
+ submit (submit_beopest)
19
+ run (run_beopest)
20
+ par (goPESTpar)
21
+ obs (goPESTobs)
22
+ run-pest-model (pest_model)
23
+ run-forward (run_ns_pr)
24
+ save-iter-files (rename_latest_files)
25
+ check-slaves (check_slaves)
26
+
27
+ Important files for goPEST to work:
28
+ goPESTconfig.toml
29
+ This is the main configuration file.
30
+ goPESTpar.list
31
+ This controls model parameters included for PEST.
32
+ goPESTobs.list
33
+ This controls observations for PEST.
34
+
35
+ University of Auckland, 2012, 2022
36
+ """
37
+
38
+ def gopest_cli():
39
+ print(title)
40
+ argc = len(sys.argv)
41
+ if argc < 2:
42
+ print(version + hlp)
43
+ else:
44
+ if sys.argv[1] == 'help':
45
+ print(version + hlp)
46
+ else:
47
+ # NOTE loading gopest.common checks goPESTconfig.toml
48
+ import gopest.common
49
+ import gopest.par
50
+ import gopest.obs
51
+ import gopest.pest_model
52
+ import gopest.run_ns_pr
53
+ import gopest.submit_beopest
54
+ import gopest.run_beopest
55
+ import gopest.make_case_pst
56
+ import gopest.rename_latest_files
57
+ import gopest.check_slaves
58
+ cmds = {
59
+ 'par': gopest.par.goPESTpar,
60
+ 'obs': gopest.obs.goPESTobs,
61
+ 'run-pest-model': gopest.pest_model.main_cli,
62
+ 'run-forward': gopest.run_ns_pr.main_cli,
63
+ 'submit': gopest.submit_beopest.submit_cli,
64
+ 'run': gopest.run_beopest.run_cli,
65
+ 'init': gopest.make_case_pst.make_case_cli,
66
+ 'save-iter-files': gopest.rename_latest_files.rename_latest_files,
67
+ 'check-slaves': gopest.check_slaves.check_slaves_cli,
68
+ }
69
+ if sys.argv[1] not in cmds:
70
+ print(version + hlp)
71
+ print('Error! COMMAND not recognised.')
72
+ exit(1)
73
+ cmds[sys.argv[1]](sys.argv[1:])
74
+
75
+ """
76
+ - NOTE good reference on designing CLI command names:
77
+ https://smallstep.com/blog/the-poetics-of-cli-command-names/
78
+
79
+ - TODO it's probably not necessary to have so many commands
80
+ """