-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathbatch.py
executable file
·347 lines (300 loc) · 14.5 KB
/
batch.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Jan 5 15:31:15 2020
@author: levi
"""
import sys, datetime, shutil, os, traceback, numpy, random, string, time
from configparser import ConfigParser
from interf import logger
from main import main
line = "#" * 66
b = 'BATCH: '
nb = '\n'+b
class batMan:
"""This is a class for the manager of batch runs, the BATch MANager."""
def __init__(self,confFile=''):
self.confFile = confFile
Pars = ConfigParser()
Pars.optionxform = str
Pars.read(confFile)
# load main parameters
sec = 'main'
self.name = Pars.get(sec, 'name')
self.mode = Pars.get(sec, 'mode')
self.logMode = Pars.get(sec, 'logMode')
# create a logger for the batch
self.log = logger('batch', runName=self.name, mode=self.logMode)
# TODO: modify the logger object to allow for message "headers" like BATCH
self.log.printL(b+'Running a batch specified in:\n'+self.confFile+'\n')
self.confFile = shutil.copy2(self.confFile, self.log.folderName +
os.sep)
self.log.printL(b+'Saved a copy of the configuration file to:\n'+
self.confFile+'\n')
if self.mode == 'explicit':
sec = 'explicit_mode'
self.NCases = Pars.getint(sec,'NCases')
this_str = Pars.get(sec, 'probs')
self.probList = this_str.split(',\n')
this_str = Pars.get(sec, 'baseFiles')
self.baseFileList = this_str.split(',\n')
# TODO: put a simple test for singleton lists, then replace them for proper
# lists of the same element
elif self.mode == 'variations':
sec = 'variations_mode'
self.baseFile = Pars.get(sec, 'baseFile')
# TODO: actually use this somewhere. It makes a lot of sense for variations
self.initGuesMode = Pars.get(sec, 'initGuesMode')
# Get the variations; NCases goes automatically
varsSets_list = Pars.get(sec, 'vars').split(',\n')
varSets = self.parseVars(varsSets_list)
self.varSets = varSets
self.NCases = len(varSets)+1
self.probList = [Pars.get(sec, 'probName').strip()] * self.NCases
# Generate the files corresponding to the cases to be run:
self.genFiles()
# Reference for successful run
self.isGoodRun = numpy.zeros(self.NCases, dtype=numpy.bool_)
# post processing info
self.postProcInfo = {}
# TODO: put this on the configuration file!!
# this is a list of the attributes for the "sol" object that the user wants
self.postProcKeyList = ['NIterGrad',
'GSStotObjEval',
'GSSavgObjEval',
'timer']
# basic initialization
for key in self.postProcKeyList:
self.postProcInfo[key] = [0] * self.NCases
# show parameters and leave
self.log.printL("\nThese are the parameters for the batch:")
self.log.pprint(self.__dict__)
def runNumbAsStr(self,runNr):
"""Produce the standard run number, starting from 1, zero padded for keeping
alphabetical order in case of more than 9 runs."""
return str(runNr + 1).zfill(int(numpy.floor(numpy.log(self.NCases))))
def parseVars(self,varsSets_list):
"""Parse the variations to be performed, from the input file.
The idea is that the user can put as many variations as desired in each run.
For example, the input:
vars = 'sgra','GSS_PLimCte',1.0e5 | 'accel','acc_max',100 | 'restr','h',2. ,
'sgra','GSS_PLimCte',1.0e10 | 'accel','acc_max',100
produces two runs on top of the base run,
- on the 1st: 'GSS_PLimCte' parameter in 'sgra' section is changed to 1e5;
'acc_max' parameter in 'accel' section is changed to 100;
'h' parameter in 'restr' section is changed to 2.
- on the 2nd: 'GSS_PLimCte' parameter in 'sgra' section is changed to 1e10;
'acc_max' parameter in 'accel' section is changed to 100.
"""
self.log.printL("\n" + b + "Parsing the variations:")
contVarSets = 0; varSets = []
# for each set of variations (i.e., for each new run)
for thisVarSet_str in varsSets_list:
contVarSets += 1
# display this set of variations, by number
self.log.printL("\n\nThis is the variations set #{}:".format(contVarSets))
# get the list by splitting into pieces separated by "|"
thisVarSet_list = thisVarSet_str.split(' | ')
contVars = 0; thisVarSet = []
# for each individual variation:
for var_Str in thisVarSet_list:
contVars += 1
# display variation by number
self.log.printL("\n- This is the variation #{}:".format(contVars))
thisVar = var_Str.split(',')
msg = "\nSection: {}\nParam name: {}\nValue (as string): " \
"{}".format(thisVar[0], thisVar[1], thisVar[2])
self.log.printL(msg)
# assemble dictionary for easy referencing later
var = {'section': thisVar[0].strip(),
'parName': thisVar[1].strip(),
'val': thisVar[2].strip()}
thisVarSet.append(var)
varSets.append(thisVarSet)
return varSets
def genFiles(self):
# generate the different files here, load their names to BM.baseFileList
self.log.printL("\n"+b+"Generating the files for the variations...")
l = len(self.baseFile)
# generate a random code for minimizing the chance of file conflict
# TODO: actually checking for conflicts should not be that hard!
rand = ''.join(random.choice(string.ascii_letters + string.digits)
for _ in range(10))
# base file list starts with the first case, which is the base file
baseFileList = [self.baseFile]
# one file for each of the runs, except the first
for nRun in range(self.NCases-1):
name = self.baseFile[:l-4] + '_' + rand + \
'_var' + self.runNumbAsStr(nRun) + '.its'
self.log.printL("Name of the file for run #{}: {}".format(nRun+1,name))
# Append the name of the file to the list of base files, for running later!
baseFileList.append(name)
# List of Variations for this run
theseVars = self.varSets[nRun]
# List (actually, set) of sections
secList = set()
for var in theseVars:
secList.add(var['section'])
# open the file pointers
targHand = open(name,'w+') # target file
baseHand = open(self.baseFile, 'r') # source file
secName = ''; newLine = ''
for line in baseHand:
# remove spaces, \n and other funny characters
stripLine = line.strip()
# check if this line has to be changed (implement variation) or not
mustChange = False
# get current section name (if it changes)
if stripLine.startswith('['):
# new section found, maybe?
ind = stripLine.find(']')
if ind > -1:
# closing bracket found; new section confirmed
secName = stripLine[1:ind]
else:
# not a new section. Maybe a comment!
if not(stripLine.startswith('#')):
# Not a comment... Now it has to be a variable assignment.
if secName in secList:
# if this section is not even in the list, no need for checking
# get the equal sign for separating variable name
ind = stripLine.find('=')
if ind > -1:
# variable name goes right up to the =
varbName = stripLine[:ind].strip()
# check for matches: the section must be equal to the current
# section and the variable name must match the 'parName'
for var in theseVars:
# DISCLAIMER:
# if the user is crazy enough so that there are two or
# more variations on the same set for the same variable,
# only the last one will be applied.
if var['section'] == secName and \
var['parName'] == varbName:
# Finally! Change the line
mustChange = True
newLine = stripLine[:ind+1] + ' ' + var['val']
# finally, write either the original line or the changed line
if mustChange:
targHand.write(newLine + '\n')
else:
targHand.write(stripLine + '\n')
# close file handlers
targHand.close()
baseHand.close()
self.baseFileList = baseFileList
def getPostProcData(self,sol,runNr:int):
"""Post processing data gathering"""
for key in self.postProcKeyList:
# try...except so that if something goes wrong,
# the whole batch is not wasted
try:
self.postProcInfo[key][runNr] = getattr(sol,key)
except AttributeError:
msg = "Error while retrieving the attribute" \
" {} in run {}".format(key,runNr)
self.log.printL(nb + msg)
self.postProcInfo[key][runNr] = 'ERROR!'
def showPostProcData(self):
"""Post processing data show"""
self.log.printL(nb + "Post-processing results:")
self.log.pprint(BM.postProcInfo)
try:
import xlsxwriter
# declare workbook and worksheet
workbook = xlsxwriter.Workbook(self.log.folderName + os.sep +
'Results_' + self.log.folderName +'.xlsx')
ws = workbook.add_worksheet()
# heading
ws.write(0, 0, 'Run #')
ws.write(0, 1, 'Problem name')
ws.write(0, 2, 'Base file')
ws.write(0, 3, 'Good run?')
col = 4
# write the key names
for key in self.postProcKeyList:
ws.write(0,col,key)
col += 1
# fill in the actual data
for runNr in range(self.NCases):
row = runNr + 1 # because the first row has the header
ws.write(row, 0, runNr)
ws.write(row, 1, self.probList[runNr])
ws.write(row, 2, self.baseFileList[runNr])
ws.write(row, 3, self.isGoodRun[runNr])
col = 4
for key in self.postProcKeyList:
ws.write(row, col, self.postProcInfo[key][runNr])
col += 1
workbook.close()
except ImportError:
self.log.printL(nb+"Error while importing xlsxwriter...")
# this is an attempt to print out the data
titl = "\nRun #\tProbName\tBaseFile\tGood run?"
for key in self.postProcKeyList:
titl += '\t'+key
self.log.printL(titl+'\n')
for runNr in range(self.NCases):
msg = '{}\t{}\t{}\t{}'.format(runNr,self.probList[runNr],
self.baseFileList[runNr],
self.isGoodRun[runNr])
for key in self.postProcKeyList:
msg += '\t{}'.format(self.postProcInfo[key][runNr])
self.log.printL(msg)
def countdown(self,sec=3):
self.log.printL("\nBATCH: Starting in...")
for i in range(sec,0,-1):
self.log.printL("{}...".format(i))
time.sleep(1.)
if __name__ == "__main__":
print('\n'+line)
print('\nRunning batch.py with arguments:')
print(sys.argv)
print(datetime.datetime.now())
args = sys.argv
if len(args) == 1:
# change this line to run this from the editor
confFile = 'defaults' + os.sep + 'testAll.bat'
else:
# if the user runs the program from the command line,
confFile = args[1]
BM = batMan(confFile=confFile)
for runNr in range(BM.NCases):
thisProb, thisFile = BM.probList[runNr], BM.baseFileList[runNr]
BM.log.printL('\n'+line)
msg = nb + 'Running case {} of {}:' \
'\n Problem: {}, file: {}\n'.format(runNr+1,BM.NCases,thisProb,
thisFile)
BM.log.printL(msg)
BM.log.printL(line+'\n')
# set up the string for this run's number
runNrStr = BM.runNumbAsStr(runNr)
# set up the this run's folder, inside the batch folder
folder = BM.log.folderName + os.sep + runNrStr + '_'
try:
BM.countdown()
sol, solInit = main(('',thisProb,thisFile), isManu=False, destFold=folder)
BM.log.printL(nb + "This run was completed successfully.")
BM.log.printL(nb + "Entering post-processing for this run...")
BM.getPostProcData(sol,runNr)
BM.log.printL(nb + "Done. Going for the next run.")
BM.isGoodRun[runNr] = True # good run
except KeyboardInterrupt:
BM.log.printL(nb + "User has stopped the program during this run.")
except Exception:
BM.log.printL(nb + 'Sorry, there was something wrong with this run:')
BM.log.printL(traceback.format_exc())
finally:
if BM.mode == 'variations':
# clean up the generated .its files
if runNr > 0:
file = BM.baseFileList[runNr]
BM.log.printL(nb + "Removing file: " + file)
os.remove(file)
if BM.isGoodRun.all():
BM.log.printL(nb+"* * * Every run was successful! * * *")
else:
BM.log.printL(nb + "* * * WARNING!! NOT ALL RUNS WERE SUCCESSFUL!!! * * *")
BM.showPostProcData()
BM.log.printL(nb+"Execution finished. Terminating now.\n")
BM.log.close()