submit_tm5_setup_rcfile.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. """
  2. Routine to setup the rcfile of a TM5 run that is actually read by the
  3. executable.
  4. """
  5. def WriteRcfile( rcf ):
  6. """
  7. Reads the settings from the input rcfile, and writes the rcfile that will
  8. be read by the exectuble.
  9. The name of new rcfile is returned.
  10. An import key in the input rcfile is the 'jobstep' number, this is used to
  11. decide whether a restart should be configured rather than an initial run.
  12. If no new run is to be started (for example because the end of the job
  13. chain is reached), the value None is returned.
  14. """
  15. import os
  16. import sys
  17. import shutil
  18. import datetime
  19. import logging
  20. import re
  21. # info ...
  22. logging.info( 'write rcfile (to be read by executable) ...' )
  23. logging.info( ' read time period settings ...' )
  24. # time format in rcfiles:
  25. tfmt = '%Y-%m-%d %H:%M:%S'
  26. # create python datetime objects for overall time range:
  27. if sys.version_info[0:3] <= (2,4,3) :
  28. full_t1 = datetime.datetime( *map(int,rcf.get('timerange.start').replace('-',' ').replace(':',' ').split()) )
  29. full_t2 = datetime.datetime( *map(int,rcf.get('timerange.end' ).replace('-',' ').replace(':',' ').split()) )
  30. else :
  31. full_t1 = datetime.datetime.strptime( rcf.get('timerange.start'), tfmt )
  32. full_t2 = datetime.datetime.strptime( rcf.get('timerange.end' ), tfmt )
  33. logging.info( ' timerange start : %s' % str(full_t1) )
  34. logging.info( ' timerange end : %s' % str(full_t2) )
  35. if full_t2 <= full_t1:
  36. logging.error( 'The start date (%s) is later than the end date (%s), please revise' % (full_t1.strftime(tfmt),full_t2.strftime(tfmt)) )
  37. raise ValueError
  38. # rcfile is valid for the previous step in the chain (or initial);
  39. # read the previous step number (or zero):
  40. prev_step = rcf.get( 'jobstep', 'int' )
  41. if prev_step < 0 :
  42. logging.error( 'step number in chain is %i, but should be 0 (initial) or 1,2,...' % step )
  43. raise Exception
  44. logging.info( ' previous step : %i' % prev_step )
  45. # split name of rcfile in base and extension:
  46. prev_bname,ext = os.path.splitext(rcf.filename)
  47. # new basename is initally the same:
  48. next_bname = prev_bname
  49. # copy settings into new rcfile:
  50. next_rcf = rcf
  51. # start of chain ?
  52. if prev_step == 0 :
  53. # fill first value as end of 'previous' step:
  54. prev_t2 = full_t1
  55. else :
  56. # continuation of chain ...
  57. # base name includes step indication, e.g. mytest_001 ;
  58. # remove the last instance of at least one underscore followed
  59. # by numbers.
  60. next_bname = re.sub("_+[0-9]+$","",next_bname)
  61. # 4D=var mode ?
  62. is_var4d = rcf.get('var4d','bool',default=False)
  63. # get runmode (1 = forward run):
  64. if is_var4d :
  65. # info ...
  66. logging.info( ' script options for 4D-var are enabled ...' )
  67. logging.info( ' read runmode ...' )
  68. # current runmode:
  69. runmode = rcf.get( '4DVAR.runmode', 'int' )
  70. # info ...
  71. logging.info( ' runmode = %i' % runmode )
  72. else :
  73. # forward run:
  74. runmode = 1
  75. # setup for different run modes:
  76. #
  77. if runmode in [1,2,3,4,6,7,8] : # no-iteration modes
  78. #
  79. logging.info( ' setup next forward run ...' )
  80. # create python datetime objects for previous time range:
  81. if sys.version_info[0:3] <= (2,4,3) :
  82. prev_t1 = datetime.datetime( *map(int,rcf.get('jobstep.timerange.start').replace('-',' ').replace(':',' ').split()) )
  83. prev_t2 = datetime.datetime( *map(int,rcf.get('jobstep.timerange.end' ).replace('-',' ').replace(':',' ').split()) )
  84. else :
  85. prev_t1 = datetime.datetime.strptime( rcf.get('jobstep.timerange.start'), tfmt )
  86. prev_t2 = datetime.datetime.strptime( rcf.get('jobstep.timerange.end' ), tfmt )
  87. logging.info( ' previous timerange start : %s' % str(prev_t1) )
  88. logging.info( ' previous timerange end : %s' % str(prev_t2) )
  89. if (prev_t2 < prev_t1) or (prev_t1 < full_t1) or (prev_t2 > full_t2) :
  90. logging.error( 'found strange previous range compared to full range' )
  91. raise Exception
  92. # finished ?
  93. if prev_t2 == full_t2 :
  94. # no new file necessary ...
  95. next_rcf = None
  96. logging.info( ' end of full period reached; finish' )
  97. #
  98. elif runmode == 5 : # 4D-var run
  99. #
  100. logging.info( ' setup next 4D-var run ...' )
  101. # file with 4d-var restart settings is rcfile with extension '.rs' instead of '.rc' :
  102. rsfile = prev_bname+'.rs'
  103. if not os.path.exists(rsfile) :
  104. logging.error( '4D-var restart file "%s" not found ...' % rsfile )
  105. raise Exception
  106. logging.info( ' include settings from restart file in new rcfile:' )
  107. # read file:
  108. f = open(rsfile,'r')
  109. lines = f.readlines()
  110. f.close()
  111. # loop over lines; written in rcfile format
  112. for line in lines :
  113. # remove newlines etc:
  114. line = line.strip()
  115. # skip comment and empty lines:
  116. if line.startswith('!') : continue
  117. if len(line) == 0 : continue
  118. # value definitions, split at ':' :
  119. key,val = line.split(':')
  120. key = key.strip()
  121. val = val.strip()
  122. # this should be a key in the rcfile ...
  123. if not next_rcf.has_key(key) :
  124. logging.error( 'restart file contains key which is not in rc file : %s' % key )
  125. raise Exception
  126. # replace in rcfile:
  127. next_rcf.replace( key, val )
  128. logging.info( ' %s : %s' % (key,val) )
  129. # finished ?
  130. val = next_rcf.get('outer_loop.finished','int')
  131. logging.info( 'check value of outer_loop.finished : %i' % val )
  132. if val == 0 :
  133. logging.info( ' --> VAR4D outer/inner loop to be performed or to be continued; submit next job' )
  134. elif val == 1 :
  135. logging.info( ' --> VAR4D final inner loop finished, final outer loop to be done; submit next job' )
  136. elif val == 2 :
  137. logging.info( ' --> VAR4D final outer loop finished; end of job chain' )
  138. next_rcf = None
  139. else :
  140. logging.error( ' --> unsupported value : %i' % val )
  141. raise Exception
  142. #
  143. else :
  144. #
  145. # info ...
  146. logging.warning( "do not know how to setup next job for 4D-var runmode : %i" % runmode )
  147. logging.warning( "assume end of run" )
  148. # no new file necessary ...
  149. next_rcf = None
  150. # new file to be written ?
  151. if next_rcf != None :
  152. logging.info( ' write new rcfile ...' )
  153. # increase counter:
  154. next_step = prev_step + 1
  155. # replace in rcfile:
  156. next_rcf.replace('jobstep',next_step)
  157. logging.info( ' next jobstep : %i' % next_step )
  158. # get time step
  159. td = next_rcf.get('jobstep.length', default='inf')
  160. logging.info( ' jobstep length : %s' % td )
  161. # single run or splitted ?
  162. if td in ['inf','infinite'] :
  163. # copy time range:
  164. next_t1 = full_t1
  165. next_t2 = full_t2
  166. logging.info( ' job will run complete period ...' )
  167. else :
  168. # start time of this period:
  169. next_t1 = prev_t2
  170. # set end time for this period:
  171. if td == 'month' :
  172. # break at end of this month
  173. if next_t1.month != 12:
  174. next_t2 = datetime.datetime(next_t1.year ,next_t1.month+1,1,0,0)
  175. else:
  176. next_t2 = datetime.datetime(next_t1.year+1, 1,1,0,0)
  177. else :
  178. # assume that the interval specified is the number
  179. # of days to run forward before resubmitting
  180. next_t2 = datetime.datetime(next_t1.year,next_t1.month,next_t1.day) + datetime.timedelta(days=int(td))
  181. # for windowed inversions like CarbonTracker,
  182. # we need to step each job by an amount of time
  183. # which is less than the jobstep length. These
  184. # values are assumed to be in days (no careful
  185. # checking is done). The td (jobstep.length)
  186. # value is also expected to be expressed in days.
  187. jobstep_step = next_rcf.get('jobstep.step',default=0)
  188. if jobstep_step > 0:
  189. if prev_step == 0 :
  190. next_t1 = full_t1
  191. else :
  192. # create python datetime objects for previous time range:
  193. if sys.version_info[0:3] <= (2,4,3) :
  194. prev_t1 = datetime.datetime( *map(int,rcf.get('jobstep.timerange.start').replace('-',' ').replace(':',' ').split()) )
  195. else :
  196. prev_t1 = datetime.datetime.strptime( rcf.get('jobstep.timerange.start'), tfmt )
  197. next_t1 = prev_t1 + datetime.timedelta(days=int(jobstep_step))
  198. next_t2 = next_t1 + datetime.timedelta(days=int(td))
  199. # do not run beyond full range:
  200. if next_t2 > full_t2 : next_t2 = full_t2
  201. logging.info( ' job will run from %s to %s' % (str(next_t1),str(next_t2)) )
  202. # replace time values:
  203. next_rcf.replace( 'jobstep.timerange.start', next_t1.strftime(tfmt) )
  204. next_rcf.replace( 'jobstep.timerange.end' , next_t2.strftime(tfmt) )
  205. # name of previous output directory:
  206. prev_output_dir = rcf.get( 'output.dir' )
  207. # for 2nd and following steps the model might need to read data
  208. # from the previous run ...
  209. if next_step > 1 :
  210. # replace previous output destination:
  211. next_rcf.replace( 'prev.output.dir', prev_output_dir )
  212. # ignore restart? KLUDGE to avoid istart=31 if restart was not
  213. # written in the previous step, which is needed if the model is
  214. # not compiled with HDF4 support
  215. restart_ignore = rcf.get('restart.ignore','bool',default=False)
  216. # take care of restart
  217. if not restart_ignore :
  218. # New istart : 33 if previous run saved restart files,
  219. # else use "save files".
  220. restart_write = rcf.get('restart.write','bool',default=False)
  221. if restart_write :
  222. # restart from a 'restart' file
  223. next_istart = '33'
  224. # name of previous destination directory:
  225. prev_restart_dir = rcf.get( 'restart.write.dir' )
  226. # ensure that new restart file is read from the correct directory:
  227. next_rcf.replace( 'restart.read.dir', prev_restart_dir )
  228. else :
  229. # restart from a 'save' file
  230. next_istart = '31'
  231. # follow the logic of io_save.F90/write_save_file and initexit.F90 to get the savefile name
  232. region_name =rcf.get('my.region1')
  233. f31name = rcf.get('start.3.'+region_name,default='')
  234. if len(f31name)==0:
  235. f31name = prev_output_dir+'/save_'+prev_t2.strftime("%Y%m%d%H")+'_'+region_name+'.hdf'
  236. key31 = 'start.31.'+region_name
  237. logging.info( ' file name for istart=31 : %s' % f31name )
  238. next_rcf.replace(key31,f31name)
  239. logging.info( ' istart value : %s' % next_istart )
  240. # replace rcfile value:
  241. next_rcf.replace( 'istart', next_istart )
  242. # list of output directory extensions (could be empty):
  243. extensions = rcf.get( 'output.dir.extensions' ).split()
  244. # should be extended ?
  245. if len(extensions) > 0 :
  246. # get output directory without extensions:
  247. output_dir = next_rcf.get('output.dir.base')
  248. # loop over sub directories:
  249. for extension in extensions :
  250. # some special values:
  251. if extension == '<jobstep>' :
  252. # 4-digit jobstep:
  253. subdir = '%4.4i' % next_step
  254. elif extension == '<timerange>' :
  255. # format for time: yyyymmdd_hhmnss
  256. fmt = '%Y%m%d_%H%M%S'
  257. # start and end time in short format:
  258. next_t1s = next_t1.strftime(fmt)
  259. next_t2s = next_t2.strftime(fmt)
  260. # fill name of subdirectory:
  261. subdir = '%s__%s' % (next_t1s,next_t2s)
  262. elif extension == '<timestart>' :
  263. # format for time: yyyymmdd
  264. fmt = '%Y%m%d'
  265. # start time in short format:
  266. next_t1s = next_t1.strftime(fmt)
  267. # fill name of subdirectory:
  268. subdir = '%s' % (next_t1s)
  269. elif next_rcf.has_key(extension) :
  270. # read key from rcfile:
  271. subdir = next_rcf.get(extension)
  272. else :
  273. logging.error( 'unsupported output dir extension : %s' % extension )
  274. raise Exception
  275. # extend output path with subdirectory
  276. output_dir = os.path.join( output_dir, subdir )
  277. # replace original value in rcfile:
  278. next_rcf.replace('output.dir',output_dir)
  279. # name of new rcfile including new step number:
  280. next_rcfile = '%s_%3.3i%s' % (next_bname,next_step,ext)
  281. # info ...
  282. logging.info( ' write to %s ...' % next_rcfile )
  283. # write :
  284. next_rcf.WriteFile( next_rcfile )
  285. # for backwards compatibility, write 'tm5_runtime.rc' too ?
  286. flag = next_rcf.get( 'jobstep.runtimerc.write', 'bool', default=False )
  287. if flag : Write_RuntimeRc( next_rcf )
  288. else :
  289. next_rcfile = None
  290. return next_rcfile