ENH: Update FreeFOAM contributions to GPL v3
[freefoam.git] / bin / freefoam-log.py.in
blob1bb50cd6c1fdd7ffec1d3b2ae4fcdcc353e59d5f
1 #!@PYTHON_EXECUTABLE@
2 #-------------------------------------------------------------------------------
3 # ______ _ ____ __ __
4 # | ____| _| |_ / __ \ /\ | \/ |
5 # | |__ _ __ ___ ___ / \| | | | / \ | \ / |
6 # | __| '__/ _ \/ _ ( (| |) ) | | |/ /\ \ | |\/| |
7 # | | | | | __/ __/\_ _/| |__| / ____ \| | | |
8 # |_| |_| \___|\___| |_| \____/_/ \_\_| |_|
10 # FreeFOAM: The Cross-Platform CFD Toolkit
12 # Copyright (C) 2008-2012 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 3 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, see <http://www.gnu.org/licenses/>.
31 # Script
32 # freefoam-log
34 # Description
35 # Extracts info from log file
37 # Bugs
38 # -solution singularity not handled
39 #------------------------------------------------------------------------------
41 """Usage: freefoam@PY_SCRIPT_SUFFIX@ log [options] <log>
43 Extracts xy files from Foam logs.
45 Options
46 -------
47 -case <case_dir> Case directory (defaults to $PWD)
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. If <log> is not an
53 absolute path, it is relative to the specified case directory.
55 The default is to extract for all the 'Solved for' variables the initial
56 residual, the final residual and the number of iterations. On top of this a
57 (user editable) database of standard non-solved for variables is used to
58 extract data like Courant number, execution time.
60 The -l option shows all the possible variables but does not extract them.
62 The program writes a set of files, <case_dir>/logs/<var>_<subIter>, for every
63 <var> specified, for every occurrence inside a time step.
65 For variables that are 'Solved for' the initial residual name will be <var>,
66 the final residual will get name <var>FinalRes,
68 The files are a simple xy format with the first column Time (default) and the
69 second the extracted values. Option -n creates single column files with the
70 extracted data only.
72 The query database is a simple text format containing Python regular
73 expressions. The regular expression must capture the queried value in a group
74 with the values name (i.e. using (?P<name>...) syntax). Lines where the first
75 non-blank character is a # will be ignored. The database will either be
76 <case_dir>/foamLog.db, $HOME/.FreeFOAM/foamLog.db or
77 @FOAM_DATA_DIR@/foamLog.db, whichever is found first.
79 Option -s suppresses the default information and only prints the extracted
80 variables.
82 """
85 import os
86 import os.path
87 import sys
88 import re
89 # want to be future proof
90 sys.path.insert(0, '@FOAM_PYTHON_DIR@')
91 from FreeFOAM.compat import *
93 class PrintLog:
94 def __init__(self, verbose):
95 self.verbose = verbose
96 def __call__(self, *args):
97 if self.verbose:
98 echo(*args)
100 class InvalidRegex(Exception):
101 """Raised if a regex fails to compile"""
102 def __init__(self, regex, i, msg):
103 """Initialize with the error message `msg`, thefailed regex `regex` in
104 line `i`"""
105 Exception.__init__(self, regex, i, msg)
107 def __str__(self):
108 return 'Failed to compile regular expression "%s" in line %d:\n %s'%self.args
111 def getSolvedForRegex(logf):
112 """Extracts from the file object `logf` the solved-for variables and
113 generates a list of regular expression objects to extract them or `None`."""
114 p = logf.tell()
115 logf.seek(0, 0)
116 vars = set()
117 for l in logf:
118 m = re.search(r'Solving for\s+(?P<varname>\w+)', l)
119 if m:
120 vars.add(m.group('varname'))
122 result = None
123 if len(vars):
124 result = []
125 for v in vars:
126 result.append(re.compile((''.join([
127 'Solving for\s+%(var)s,\s+',
128 'Initial residual = (?P<%(var)s>\S+),\s+',
129 'Final residual = (?P<%(var)sFinalRes>\S+),\s+',
130 'No Iterations (?P<%(var)sIters>\S+)']))%{'var': v}))
131 logf.seek(p, 0)
132 return result
134 def getDbRegex(logf, dbf):
135 """Extracts from the file object `dbf` the contained regular expression
136 strings and returns a list with regular expression objects that match any of
137 the lines in the file object `logf` at least once."""
138 pl = logf.tell()
139 logf.seek(0, 0)
140 pd = dbf.tell()
141 dbf.seek(0, 0)
143 # read db file
144 allRegex = {}
145 i = 0
146 for l in dbf:
147 i += 1
148 # try to compile the line (discarding empty and comment lines)
149 if not re.match(r'^\s*(#|$)', l):
150 try:
151 rc = re.compile(l[:-1])
152 except re.error, e:
153 raise InvalidRegex(l[:-1], i, str(e))
154 allRegex[i] = rc
156 # try to match the regexes and tranfer the succesful ones into `result`
157 result = []
158 keys = list(allRegex.keys())
159 for l in logf:
160 for i in keys:
161 r = allRegex[i]
162 if r.search(l):
163 result.append(r)
164 keys.remove(i)
165 if not len(keys):
166 break
167 dbf.seek(pd, 0)
168 logf.seek(pl, 0)
170 return result
172 def resetCounters(counters):
173 """Reset the sub-iter counters"""
174 for i in counters.iterkeys():
175 counters[i] = 0
177 #-----------------------------
178 # Main
179 #-----------------------------
181 # parse options
182 noTime = False
183 silent = False
184 listOnly = False
185 logName = None
186 caseDir = os.getcwd()
187 args = sys.argv[1:]
188 while len(args) > 0:
189 a = args[0]
190 if a == '-s':
191 silent = True
192 del args[0]
193 elif a == '-n':
194 noTime = True
195 del args[0]
196 elif a == '-l':
197 listOnly = True
198 del args[0]
199 elif a == '-h' or a == '-help':
200 echo(__doc__)
201 sys.exit(0)
202 elif a == '-case':
203 if len(args) < 2:
204 sys.stderr.write('Error: -case requires argument\n')
205 sys.stderr.write(__doc__+'\n')
206 sys.exit(1)
207 caseDir = args[1]
208 del args[:2]
209 elif a[0] == '-':
210 sys.stderr.write('Error: unknown option "%s"\n'%a)
211 sys.stderr.write(__doc__+'\n')
212 sys.exit(1)
213 else:
214 logName = a
215 del args[0]
217 plog = PrintLog(not silent)
219 if not logName:
220 sys.stderr.write('Error: No log file specified')
221 sys.stderr.write(__doc__+'\n')
222 sys.exit(1)
224 if not os.path.isabs(logName):
225 logName = os.path.join(caseDir, logName)
227 if not os.path.isfile(logName):
228 sys.stderr.write('Error: No such file "%s"\n'%logName)
229 sys.exit(1)
231 # find foamLog.db
232 dbName = None
233 for n in (
234 caseDir,
235 os.path.expanduser('~/.FreeFOAM'),
236 os.path.normpath('@FOAM_DATA_DIR@')
238 n = os.path.join(os.path.normpath(n), 'foamLog.db')
239 if os.path.isfile(n):
240 dbName = n
241 break
242 if not dbName:
243 sys.stderr.write('Error: Failed to find foamLog.db\n')
244 sys.exit(1)
246 # open the db and log file
247 logFile = open(logName, 'rt')
248 dbFile = open(dbName, 'rt')
250 # fetch all the regexes
251 regex = getSolvedForRegex(logFile)
252 regex.extend(getDbRegex(logFile, dbFile))
253 dbFile.close()
255 # get all the variable names, create data and counter container
256 vars = []
257 data = {}
258 counters = {}
259 for r in regex:
260 for n in r.groupindex.iterkeys():
261 vars.append(n)
262 data[n] = []
263 counters[n] = 0
264 vars.sort()
266 # check uniqueness
267 if len(vars) != len(set(vars)):
268 cnt = {}
269 for v in vars:
270 if not v in cnt:
271 cnt[v] = 0
272 cnt[v] += 1
273 for v, n in cnt.iteritems():
274 if n > 1:
275 sys.stderr.write(
276 'Error: multiple regular expressions for variable "%s"\n'%v)
277 sys.exit(1)
279 # if -l specified, list variables
280 if listOnly:
281 echo('\n'.join(vars))
282 sys.exit(0)
284 plog('Using:')
285 plog(' log : %s'%logName)
286 plog(' database : %s'%dbName)
287 plog(' files to : %s/logs/'%caseDir)
288 plog('')
290 logsDir = os.path.join(caseDir, 'logs')
291 if not os.path.isdir(logsDir):
292 if os.path.exists(logsDir):
293 sys.stderr.write('Error: `%s` exists but is not a directory\n'%logsDir)
294 sys.exit(1)
295 os.mkdir('logs')
297 # loop over lines, extract data
298 splitRegex = re.compile(r'\s*Time\s*=\s*(?P<time>\S+)')
299 iteration = 0
300 resetCounters(counters)
301 time = []
302 for l in logFile:
303 # check for splitting regex
304 m = splitRegex.match(l)
305 if m:
306 time.append(m.group('time'))
307 resetCounters(counters)
308 iteration += 1
309 for r in regex:
310 # check for data regex
311 m = r.search(l)
312 if m:
313 for n, v in m.groupdict().iteritems():
314 while len(data[n]) <= counters[n]:
315 data[n].append([])
316 data[n][counters[n]].append(v)
317 counters[n] += 1
318 logFile.close()
320 # loop over data and write
321 tLen = max(map(len, time))
322 for n, v in data.iteritems():
323 for i in xrange(len(v)):
324 f = open(os.path.join(logsDir, '%s_%d'%(n, i)), 'wt')
325 for j in xrange(len(time)):
326 if not noTime:
327 f.write(('%-'+str(tLen)+'s ')%time[j])
328 f.write('%s\n'%v[i][j])
329 f.close()
330 plog('Generated XY files for:')
331 plog('\n'.join(vars))
333 # ------------------- vim: set sw=3 sts=3 ft=python et: ------------ end-of-file