Skip to content

Commit c0acc79

Browse files
authored
Merge pull request #43 from NCAR/alli_dev
mpas ensemble generation
2 parents e475cac + 6526828 commit c0acc79

File tree

1 file changed

+318
-0
lines changed

1 file changed

+318
-0
lines changed
Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
#!/usr/bin/python
2+
import argparse
3+
import getopt
4+
import os
5+
import random
6+
import sys
7+
import xml.etree.ElementTree as ET
8+
9+
# ==============================================================================
10+
# create ensembles members from a control case
11+
# ==============================================================================
12+
13+
# generate <num_pick> positive random integers in [0, end-1]
14+
# can't have any duplicates
15+
16+
17+
def random_pick(num_pick, end):
18+
ar = range(0, end)
19+
rand_list = random.sample(ar, num_pick)
20+
return rand_list
21+
22+
23+
# get the pertlim corressponding to the random int
24+
# modified 10/23 to go to 2000 (previously only 1st 900 unique)
25+
# modified 3/24 to go to 3996 (previously 1998 unique)
26+
def get_pertlim_uf(rand_num):
27+
i = rand_num
28+
if i == 0:
29+
ptlim = '0'
30+
elif i > 3996:
31+
print("don't support sizes > 3996")
32+
else: # generate perturbation
33+
if i > 1998:
34+
orig = i
35+
i = orig - 1998
36+
else:
37+
orig = 0
38+
39+
if i <= 1800: # [1 - 1800]
40+
if i <= 900: # [1-900]
41+
j = 2 * int((i - 1) / 100) + 101
42+
elif i <= 1000: # [901 - 1000]
43+
j = 2 * int((i - 1) / 100) + 100
44+
elif i <= 1800: # [1001-1800]
45+
j = 2 * int((i - 1001) / 100) + 102
46+
k = (i - 1) % 100 # this is just last 2 digits of i-1
47+
if i % 2 != 0: # odd
48+
ll = j + int(k / 2) * 18
49+
ippt = str(ll).zfill(3)
50+
ptlim = '0.' + ippt + 'd-13'
51+
else: # even
52+
ll = j + int((k - 1) / 2) * 18
53+
ippt = str(ll).zfill(3)
54+
ptlim = '-0.' + ippt + 'd-13'
55+
else: # [1801 - 2000]
56+
if i <= 1900: # [1801-1900]
57+
j = 1
58+
else: # [1901-2000]
59+
j = 2
60+
k = (i - 1) % 100
61+
if i % 2 != 0: # odd
62+
ll = j + int(k / 2) * 2
63+
ippt = str(ll).zfill(3)
64+
ptlim = '0.' + ippt + 'd-13'
65+
else: # even
66+
ll = j + int((k - 1) / 2) * 2
67+
ippt = str(ll).zfill(3)
68+
ptlim = '-0.' + ippt + 'd-13'
69+
70+
if orig > 0:
71+
# adjust
72+
if i % 2 != 0: # odd
73+
ptlim = '1.' + ippt + 'd-13'
74+
else: # even
75+
ptlim = '-1.' + ippt + 'd-13'
76+
77+
return ptlim
78+
79+
80+
def main(argv):
81+
# directory with the executable (where this script is at the moment)
82+
stat_dir = os.path.dirname(os.path.realpath(__file__))
83+
print('STATUS: stat_dir = ' + stat_dir)
84+
85+
# parse arguments
86+
parser = argparse.ArgumentParser(description='Create and submit mpas ensemble runs.')
87+
parser.add_argument(
88+
'-rd',
89+
'--run_dir',
90+
default=None,
91+
dest='run_dir_root',
92+
help='Directory to create run\
93+
directories in (default: None).',
94+
required=True,
95+
)
96+
parser.add_argument(
97+
'-c',
98+
'--control_run',
99+
default=None,
100+
dest='control_run',
101+
help='Path to the control run\
102+
that will be duplicated for the ensemble members (default = None).',
103+
required=True,
104+
)
105+
parser.add_argument(
106+
'-v',
107+
'--verify',
108+
default=False,
109+
dest='verify',
110+
action='store_true',
111+
help='To create \
112+
random verifications runs (instead of an ensemble).',
113+
)
114+
parser.add_argument(
115+
'-es',
116+
'--ens_size',
117+
default=10,
118+
dest='ens_size',
119+
help='Total number of ensemble \
120+
members (default = 10).',
121+
type=int,
122+
)
123+
parser.add_argument(
124+
'-vs',
125+
'--verify_size',
126+
default=3,
127+
dest='verify_size',
128+
help='Total number of ensemble \
129+
members (default = 3).',
130+
type=int,
131+
)
132+
parser.add_argument(
133+
'-st',
134+
'--start',
135+
default=0,
136+
dest='ens_start',
137+
help='Ensemble run to start with\
138+
(default = 0).',
139+
type=int,
140+
)
141+
parser.add_argument(
142+
'-rs',
143+
'--run_script',
144+
default='chey-run.sh',
145+
dest='run_script',
146+
help='Run script name to\
147+
submit to PBS queue in the control run directory (default: chey-run.sh).',
148+
)
149+
parser.add_argument(
150+
'-s',
151+
'--submit',
152+
default=False,
153+
dest='submit',
154+
action='store_true',
155+
help='Indicates\
156+
that jobs should be submitted to the queue (default: False).',
157+
)
158+
159+
args = parser.parse_args()
160+
print(args)
161+
162+
submit = args.submit
163+
run_script = args.run_script
164+
run_dir_root = args.run_dir_root
165+
control_run = args.control_run
166+
ens_size = args.ens_size
167+
ens_start = args.ens_start
168+
verify = args.verify
169+
verify_size = args.verify_size
170+
171+
if verify:
172+
run_type = 'verify'
173+
run_size = verify_size
174+
else:
175+
run_type = 'ensemble'
176+
run_size = ens_size
177+
178+
# some parameter checking
179+
if run_size > 0:
180+
if run_size > 3996:
181+
print('Error: cannot have an ensemble size greater than 3996.')
182+
sys.exit()
183+
print('STATUS: ensemble size = ' + str(run_size))
184+
else:
185+
print('Error: cannot have an ensemble size less than 1.')
186+
sys.exit()
187+
188+
if not os.path.exists(run_dir_root):
189+
print(
190+
'ERROR: the specified directory for the new runs (',
191+
run_dir_root,
192+
') does not exist. Exiting',
193+
)
194+
sys.exit()
195+
196+
if not os.path.exists(control_run):
197+
print('ERROR: the specified control run (', control_run, ') does not exist. Exiting')
198+
sys.exit()
199+
200+
# generate random pertlim(s) for verify
201+
if run_type == 'verify':
202+
end_range = 200
203+
rand_ints = random_pick(verify_size, end_range)
204+
205+
# assume that the control case has the right run length and output stream settings
206+
# assume it has a submission script
207+
208+
base_dir_name = os.path.basename(os.path.normpath(control_run))
209+
print('STATUS: base_dir_name = ', base_dir_name)
210+
for i in range(ens_start, run_size):
211+
# run dir name
212+
iens = '{0:04d}'.format(i)
213+
214+
new_run = base_dir_name + '.' + iens
215+
216+
# does this exist already?
217+
checkdir = run_dir_root + '/' + new_run
218+
if os.path.exists(checkdir):
219+
print('WARNING: Directory ', checkdir, ' already exists ... skipping ...')
220+
continue
221+
else:
222+
print('STATUS: creating run ' + new_run)
223+
224+
# make a copy of control run in run_dir_root
225+
os.chdir(run_dir_root)
226+
command = 'cp -r ' + control_run + ' ' + new_run
227+
os.system(command)
228+
os.chdir(new_run)
229+
# thisdir = os.getcwd()
230+
231+
# set pertlim in namelist.atmosphere file
232+
if run_type == 'verify':
233+
this_pert = get_pertlim_uf(rand_ints[i])
234+
else: # full ensemble
235+
this_pert = get_pertlim_uf(i)
236+
237+
new_line = 'config_pertlim = ' + str(this_pert) + '\n'
238+
# get old line if i==ens_start
239+
if i == ens_start:
240+
with open('namelist.atmosphere', 'r') as f:
241+
all_lines = f.readlines()
242+
old_line = 'nope'
243+
for line in all_lines:
244+
if line.find('config_pertlim') >= 0:
245+
old_line = line
246+
247+
if old_line == 'nope':
248+
print('Error: no pertlim found!')
249+
sys.exit()
250+
251+
with open('namelist.atmosphere', 'r') as r:
252+
text = r.read().replace(old_line, new_line)
253+
with open('namelist.atmosphere', 'w') as w:
254+
w.write(text)
255+
256+
# set run length and output freq (currently assume correct in the control case)
257+
258+
# modify the run script for derecho
259+
new_name = '#PBS -N mpas.' + iens + '\n'
260+
execute_line_1 = f'mpiexec_mpt {stat_dir}/atmosphere_model'
261+
execute_line_2 = f'mpiexec {stat_dir}/atmosphere_model'
262+
with open(run_script, 'r') as f:
263+
all_lines = f.readlines()
264+
265+
for line in all_lines:
266+
# find job name
267+
if line.find('#PBS -N') >= 0:
268+
old_name = line
269+
270+
# find execute mpt line
271+
if line.find('mpiexec_mpt') >= 0:
272+
old_execute_line = line
273+
execute_line = execute_line_1
274+
275+
# find execute mpt line
276+
elif line.find('mpiexec') >= 0:
277+
old_execute_line = line
278+
execute_line = execute_line_2
279+
280+
with open(run_script, 'r') as r:
281+
text = r.read().replace(old_name, new_name)
282+
text = text.replace(old_execute_line, execute_line)
283+
284+
with open(run_script, 'w') as w:
285+
w.write(text)
286+
287+
# modify the output file name in streams.atmosphere xml file
288+
tree = ET.parse('streams.atmosphere')
289+
root = tree.getroot()
290+
for i in root.iter('stream'):
291+
name = i.attrib['name']
292+
if name == 'output' or name == 'custom_output' or name == 'diagnostics':
293+
# print(i.attrib)
294+
fname = i.attrib['filename_template']
295+
sp = os.path.splitext(fname)
296+
nname = sp[0] + '.' + iens + sp[1]
297+
i.set('filename_template', nname)
298+
# break
299+
300+
tree.write('streams.atmosphere')
301+
302+
# submit the run script?
303+
if submit:
304+
command = 'qsub ' + run_script
305+
os.system(command)
306+
307+
# Final output
308+
if run_type == 'verify':
309+
print('STATUS: ---MPAS-ECT VERIFICATION CASES COMPLETE---')
310+
print('Set up ', run_size, ' cases using the following pertlim values:')
311+
for i in range(0, run_size):
312+
print(' ', get_pertlim_uf(rand_ints[i]))
313+
else:
314+
print('STATUS: --MPAS ENSEMBLE CASES COMPLETE---')
315+
316+
317+
if __name__ == '__main__':
318+
main(sys.argv[1:])

0 commit comments

Comments
 (0)