FIX: test log file anaylsis was broken
[freefoam.git] / bin / freefoam-log.py.in
blob009a4adb744cc4c4659dcbd10f783147244e5bdc
1 #!@PYTHON_EXECUTABLE@
2 #-------------------------------------------------------------------------------
3 # ______ _ ____ __ __
4 # | ____| _| |_ / __ \ /\ | \/ |
5 # | |__ _ __ ___ ___ / \| | | | / \ | \ / |
6 # | __| '__/ _ \/ _ ( (| |) ) | | |/ /\ \ | |\/| |
7 # | | | | | __/ __/\_ _/| |__| / ____ \| | | |
8 # |_| |_| \___|\___| |_| \____/_/ \_\_| |_|
10 # FreeFOAM: The Cross-Platform CFD Toolkit
12 # Copyright (C) 2008-2010 Michael Wild <themiwi@users.sf.net>
13 # Gerber van der Graaf <gerber_graaf@users.sf.net>
14 #-------------------------------------------------------------------------------
15 # License
16 # This file is part of FreeFOAM.
18 # FreeFOAM is free software; you can redistribute it and/or modify it
19 # under the terms of the GNU General Public License as published by the
20 # Free Software Foundation; either version 2 of the License, or (at your
21 # option) any later version.
23 # FreeFOAM is distributed in the hope that it will be useful, but WITHOUT
24 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
25 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
26 # for more details.
28 # You should have received a copy of the GNU General Public License
29 # along with FreeFOAM; if not, write to the Free Software Foundation,
30 # Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
32 # Script
33 # freefoam-log
35 # Description
36 # Extracts info from log file
38 # Bugs
39 # -solution singularity not handled
40 #------------------------------------------------------------------------------
42 """Usage: freefoam@PY_SCRIPT_SUFFIX@ log [-n] [-s] [-l] [-h, -help] <log>
44 Extracts xy files from Foam logs.
46 Options
47 -------
48 -n Produce single-column data files
49 -s Operate silently
50 -l Only list extracted variables
51 -h, -help Print this help message
52 <log> Log file from which to extract data
54 The default is to extract for all the 'Solved for' variables the initial
55 residual, the final residual and the number of iterations. On top of this a
56 (user editable) database of standard non-solved for variables is used to
57 extract data like Courant number, execution time.
59 The -l option shows all the possible variables but does not extract them.
61 The program writes a set of files, logs/<var>_<subIter>, for every <var>
62 specified, for every occurrence inside a time step.
64 For variables that are 'Solved for' the initial residual name will be <var>,
65 the final residual will get name <var>FinalRes,
67 The files are a simple xy format with the first column Time (default) and the
68 second the extracted values. Option -n creates single column files with the
69 extracted data only.
71 The query database is a simple text format containing Python regular
72 expressions. The regular expression must capture the queried value in a group
73 with the values name (i.e. using (?P<name>...) syntax). Lines where the first
74 non-blank character is a # will be ignored. The database will either be
75 $HOME/.FreeFOAM/foamLog.db or if not found @FOAM_DATA_DIR@/foamLog.db.
77 Option -s suppresses the default information and only prints the extracted
78 variables.
80 """
82 # want to be future proof
83 from FreeFOAM.compat import *
85 import os
86 import os.path
87 import sys
88 import re
90 class PrintLog:
91 def __init__(self, verbose):
92 self.verbose = verbose
93 def __call__(self, *args):
94 if self.verbose:
95 print(*args)
97 class InvalidRegex(Exception):
98 """Raised if a regex fails to compile"""
99 def __init__(self, regex, i, msg):
100 """Initialize with the error message `msg`, thefailed regex `regex` in
101 line `i`"""
102 Exception.__init__(self, regex, i, msg)
104 def __str__(self):
105 return 'Failed to compile regular expression "%s" in line %d:\n %s'%self.args
108 def getSolvedForRegex(logf):
109 """Extracts from the file object `logf` the solved-for variables and
110 generates a list of regular expression objects to extract them or `None`."""
111 p = logf.tell()
112 logf.seek(0, 0)
113 vars = set()
114 for l in logf:
115 m = re.search(r'Solving for\s+(?P<varname>\w+)', l)
116 if m:
117 vars.add(m.group('varname'))
119 result = None
120 if len(vars):
121 result = []
122 for v in vars:
123 result.append(re.compile((''.join([
124 'Solving for\s+%(var)s,\s+',
125 'Initial residual = (?P<%(var)s>\S+),\s+',
126 'Final residual = (?P<%(var)sFinalRes>\S+),\s+',
127 'No Iterations (?P<%(var)sIters>\S+)']))%{'var': v}))
128 logf.seek(p, 0)
129 return result
131 def getDbRegex(logf, dbf):
132 """Extracts from the file object `dbf` the contained regular expression
133 strings and returns a list with regular expression objects that match any of
134 the lines in the file object `logf` at least once."""
135 pl = logf.tell()
136 logf.seek(0, 0)
137 pd = dbf.tell()
138 dbf.seek(0, 0)
140 # read db file
141 allRegex = {}
142 i = 0
143 for l in dbf:
144 i += 1
145 # try to compile the line (discarding empty and comment lines)
146 if not re.match(r'^\s*(#|$)', l):
147 try:
148 rc = re.compile(l[:-1])
149 except re.error, e:
150 raise InvalidRegex(l[:-1], i, str(e))
151 allRegex[i] = rc
153 # try to match the regexes and tranfer the succesful ones into `result`
154 result = []
155 keys = list(allRegex.keys())
156 for l in logf:
157 for i in keys:
158 r = allRegex[i]
159 if r.search(l):
160 result.append(r)
161 keys.remove(i)
162 if not len(keys):
163 break
164 dbf.seek(pd, 0)
165 logf.seek(pl, 0)
167 return result
169 def resetCounters(counters):
170 """Reset the sub-iter counters"""
171 for i in counters.iterkeys():
172 counters[i] = 0
174 #-----------------------------
175 # Main
176 #-----------------------------
178 # parse options
179 noTime = False
180 silent = False
181 listOnly = False
182 logName = None
183 args = sys.argv[1:]
184 while len(args) > 0:
185 a = args[0]
186 if a == '-s':
187 silent = True
188 del args[0]
189 elif a == '-n':
190 noTime = True
191 del args[0]
192 elif a == '-l':
193 listOnly = True
194 del args[0]
195 elif a == '-h' or a == '-help':
196 print(__doc__)
197 sys.exit(0)
198 elif a[0] == '-':
199 sys.stderr.write('Error: unknown option "%s"\n'%a)
200 sys.stderr.write(__doc__+'\n')
201 sys.exit(1)
202 else:
203 logName = a
204 break
206 plog = PrintLog(not silent)
208 if not logName:
209 sys.stderr.write('Error: No log file specified')
210 sys.stderr.write(__doc__+'\n')
211 sys.exit(1)
213 if not os.path.isfile(logName):
214 sys.stderr.write('Error: No such file "%s"\n'%logName)
215 sys.exit(1)
217 # find foamLog.db
218 dbName = None
219 for n in [
220 os.path.expanduser('~/.FreeFOAM'),
221 os.path.normpath('@FOAM_DATA_DIR@')
223 n = os.path.join(os.path.normpath(n), 'foamLog.db')
224 if os.path.isfile(n):
225 dbName = n
226 break
227 if not dbName:
228 sys.stderr.write('Error: Failed to find foamLog.db\n')
229 sys.exit(1)
231 # open the db and log file
232 logFile = open(logName, 'rt')
233 dbFile = open(dbName, 'rt')
235 # fetch all the regexes
236 regex = getSolvedForRegex(logFile)
237 regex.extend(getDbRegex(logFile, dbFile))
238 dbFile.close()
240 # get all the variable names, create data and counter container
241 vars = []
242 data = {}
243 counters = {}
244 for r in regex:
245 for n in r.groupindex.iterkeys():
246 vars.append(n)
247 data[n] = []
248 counters[n] = 0
249 vars.sort()
251 # check uniqueness
252 if len(vars) != len(set(vars)):
253 cnt = {}
254 for v in vars:
255 if not v in cnt:
256 cnt[v] = 0
257 cnt[v] += 1
258 for v, n in cnt.iteritems():
259 if n > 1:
260 sys.stderr.write(
261 'Error: multiple regular expressions for variable "%s"\n'%v)
262 sys.exit(1)
264 # if -l specified, list variables
265 if listOnly:
266 print('\n'.join(vars))
267 sys.exit(0)
269 plog('Using:')
270 plog(' log : %s'%logName)
271 plog(' database : %s'%dbName)
272 plog(' files to : logs/')
273 plog('')
275 if not os.path.isdir('logs'):
276 if os.path.exists('logs'):
277 sys.stderr.write('Error: `logs` exists but is not a directory\n')
278 sys.exit(1)
279 os.mkdir('logs')
281 # loop over lines, extract data
282 splitRegex = re.compile(r'\s*Time\s*=\s*(?P<time>\S+)')
283 iteration = 0
284 resetCounters(counters)
285 time = []
286 for l in logFile:
287 # check for splitting regex
288 m = splitRegex.match(l)
289 if m:
290 time.append(m.group('time'))
291 resetCounters(counters)
292 iteration += 1
293 for r in regex:
294 # check for data regex
295 m = r.search(l)
296 if m:
297 for n, v in m.groupdict().iteritems():
298 while len(data[n]) <= counters[n]:
299 data[n].append([])
300 data[n][counters[n]].append(v)
301 counters[n] += 1
302 logFile.close()
304 # loop over data and write
305 tLen = max(map(len, time))
306 for n, v in data.iteritems():
307 for i in xrange(len(v)):
308 f = open(os.path.join('logs', '%s_%d'%(n, i)), 'wt')
309 for j in xrange(len(time)):
310 if not noTime:
311 f.write(('%-'+str(tLen)+'s ')%time[j])
312 f.write('%s\n'%v[i][j])
313 f.close()
314 plog('Generated XY files for:')
315 plog('\n'.join(vars))
317 # ------------------- vim: set sw=3 sts=3 ft=python et: ------------ end-of-file