FIX: dirs.html no longer created by new Doxygen versions
[freefoam.git] / bin / freefoam-log.py.in
blob21c54d437a01f40a217f7d619072201041a1e2aa
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:
153 e = sys.exc_info()[1]
154 raise InvalidRegex(l[:-1], i, str(e))
155 allRegex[i] = rc
157 # try to match the regexes and tranfer the succesful ones into `result`
158 result = []
159 keys = list(allRegex.keys())
160 for l in logf:
161 for i in keys:
162 r = allRegex[i]
163 if r.search(l):
164 result.append(r)
165 keys.remove(i)
166 if not len(keys):
167 break
168 dbf.seek(pd, 0)
169 logf.seek(pl, 0)
171 return result
173 def resetCounters(counters):
174 """Reset the sub-iter counters"""
175 for i in counters.keys():
176 counters[i] = 0
178 #-----------------------------
179 # Main
180 #-----------------------------
182 # parse options
183 noTime = False
184 silent = False
185 listOnly = False
186 logName = None
187 caseDir = os.getcwd()
188 args = sys.argv[1:]
189 while len(args) > 0:
190 a = args[0]
191 if a == '-s':
192 silent = True
193 del args[0]
194 elif a == '-n':
195 noTime = True
196 del args[0]
197 elif a == '-l':
198 listOnly = True
199 del args[0]
200 elif a == '-h' or a == '-help':
201 echo(__doc__)
202 sys.exit(0)
203 elif a == '-case':
204 if len(args) < 2:
205 sys.stderr.write('Error: -case requires argument\n')
206 sys.stderr.write(__doc__+'\n')
207 sys.exit(1)
208 caseDir = args[1]
209 del args[:2]
210 elif a[0] == '-':
211 sys.stderr.write('Error: unknown option "%s"\n'%a)
212 sys.stderr.write(__doc__+'\n')
213 sys.exit(1)
214 else:
215 logName = a
216 del args[0]
218 plog = PrintLog(not silent)
220 if not logName:
221 sys.stderr.write('Error: No log file specified')
222 sys.stderr.write(__doc__+'\n')
223 sys.exit(1)
225 if not os.path.isabs(logName):
226 logName = os.path.join(caseDir, logName)
228 if not os.path.isfile(logName):
229 sys.stderr.write('Error: No such file "%s"\n'%logName)
230 sys.exit(1)
232 # find foamLog.db
233 dbName = None
234 for n in (
235 caseDir,
236 os.path.expanduser('~/.FreeFOAM'),
237 os.path.normpath('@FOAM_DATA_DIR@')
239 n = os.path.join(os.path.normpath(n), 'foamLog.db')
240 if os.path.isfile(n):
241 dbName = n
242 break
243 if not dbName:
244 sys.stderr.write('Error: Failed to find foamLog.db\n')
245 sys.exit(1)
247 # open the db and log file
248 logFile = open(logName, 'rt')
249 dbFile = open(dbName, 'rt')
251 # fetch all the regexes
252 regex = getSolvedForRegex(logFile)
253 regex.extend(getDbRegex(logFile, dbFile))
254 dbFile.close()
256 # get all the variable names, create data and counter container
257 vars = []
258 data = {}
259 counters = {}
260 for r in regex:
261 for n in r.groupindex.keys():
262 vars.append(n)
263 data[n] = []
264 counters[n] = 0
265 vars.sort()
267 # check uniqueness
268 if len(vars) != len(set(vars)):
269 cnt = {}
270 for v in vars:
271 if not v in cnt:
272 cnt[v] = 0
273 cnt[v] += 1
274 for v, n in cnt.items():
275 if n > 1:
276 sys.stderr.write(
277 'Error: multiple regular expressions for variable "%s"\n'%v)
278 sys.exit(1)
280 # if -l specified, list variables
281 if listOnly:
282 echo('\n'.join(vars))
283 sys.exit(0)
285 logsDir = os.path.join(caseDir, 'logs')
287 plog('Using:')
288 plog(' log : %s'%logName)
289 plog(' database : %s'%dbName)
290 plog(' files to : %s'%logsDir)
291 plog('')
293 if not os.path.isdir(logsDir):
294 if os.path.exists(logsDir):
295 sys.stderr.write('Error: `%s` exists but is not a directory\n'%logsDir)
296 sys.exit(1)
297 os.mkdir(logsDir)
299 # loop over lines, extract data
300 splitRegex = re.compile(r'\s*Time\s*=\s*(?P<time>\S+)')
301 iteration = 0
302 resetCounters(counters)
303 time = []
304 for l in logFile:
305 # check for splitting regex
306 m = splitRegex.match(l)
307 if m:
308 time.append(m.group('time'))
309 resetCounters(counters)
310 iteration += 1
311 continue
312 for r in regex:
313 # check for data regex
314 m = r.search(l)
315 if m:
316 for n, v in m.groupdict().items():
317 while len(data[n]) <= counters[n]:
318 data[n].append([])
319 data[n][counters[n]].append(v)
320 counters[n] += 1
321 logFile.close()
323 # loop over data and write
324 tLen = max(map(len, time))
325 for n, v in data.items():
326 for i in range(len(v)):
327 f = open(os.path.join(logsDir, '%s_%d'%(n, i)), 'wt')
328 for j in range(min(len(time), len(v[i]))):
329 if not noTime:
330 f.write(('%-'+str(tLen)+'s ')%time[j])
331 f.write('%s\n'%v[i][j])
332 f.close()
333 plog('Generated XY files for:')
334 plog('\n'.join(vars))
336 # ------------------- vim: set sw=3 sts=3 ft=python et: ------------ end-of-file