1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 """
20 Hooks for simulation cases. Two categories of hooks are defined here: (i) base
21 hooks for subclassing and (ii) generic hooks which can be readily installed.
22 """
23
24 -class Hook(object):
25 """
26 Organizer class for hooking subroutines for BaseCase.
27
28 @ivar cse: Case object.
29 @itype cse: BaseCase
30 @ivar info: information output function.
31 @itype info: callable
32 @ivar psteps: the interval number of steps between printing.
33 @itype psteps: int
34 @ivar kws: excessive keywords.
35 @itype kws: dict
36 """
38 """
39 @param cse: Case object.
40 @type cse: BaseCase
41 """
42 from .case import BaseCase
43 assert isinstance(cse, BaseCase)
44 self.cse = cse
45 self.info = cse.info
46 self.psteps = kw.pop('psteps', None)
47 self.ankcls = kw.pop('ankcls', None)
48
49 self.kws = dict(kw)
50 super(Hook, self).__init__()
51
52 - def _makedir(self, dirname, verbose=False):
53 """
54 Make new directory if it does not exist in prior.
55
56 @param dirname: name of directory to be created.
57 @type dirname: str
58 @keyword verbose: flag if print out creation message.
59 @type verbose: bool
60 """
61 import os
62 if not os.path.exists(dirname):
63 os.makedirs(dirname)
64 if verbose:
65 self.info('Created %s' % dirname)
66
67 - def _depend(self, deplst, verbose=False, stop_on_false=True):
68 """
69 Check for dependency to another hook.
70
71 @param deplst: list of depended hook classes.
72 @type deplst: list
73 @keyword verbose: flag print message.
74 @type verbose: bool
75 @keyword stop_on_false: flag stop on false.
76 @type stop_on_false: bool
77 @return: dependency met or not.
78 @rtype: bool
79 """
80 hooks = self.cse.runhooks
81 info = self.info
82
83 metlst = []
84 msglst = []
85 for ahook in deplst:
86 metlst.append(False)
87 for obj in hooks:
88 if isinstance(obj, ahook):
89 metlst[-1] = True
90 break
91 if not metlst[-1]:
92 msglst.append("%s should be enabled for %s." % (
93 ahook.__name__, self.__class__.__name__))
94 if verbose and msglst:
95 info('\n'.join(msglst)+'\n')
96 if stop_on_false and msglst:
97 raise RuntimeError, '\n'.join(msglst)
98
99 for met in metlst:
100 if not met:
101 return False
102 return True
103
104 @staticmethod
106 """
107 Provide the information to instantiate anchor object for a solver. The
108 target object can be a real solver object or a shadow associated to a
109 remote worker object with attached muscle of solver object.
110
111 @param target: the solver or shadow object.
112 @type target: solvcon.solver.Solver or solvcon.rpc.Shadow
113 @param ankcls: type of the anchor to instantiate.
114 @type ankcls: type
115 @param ankkw: keywords to instantiate anchor object.
116 @type ankkw: dict
117 @return: nothing
118 """
119 from .rpc import Shadow
120 if isinstance(target, Shadow):
121 target.drop_anchor(ankcls, ankkw)
122 else:
123 target.runanchors.append(ankcls, **ankkw)
125 """
126 Drop the anchor(s) to the solver object.
127
128 @param svr: the solver object on which the anchor(s) is dropped.
129 @type svr: solvon.solver.BaseSolver
130 @return: nothing
131 """
132 if self.ankcls:
133 self._deliver_anchor(svr, self.ankcls, self.kws)
134
136 """
137 Things to do before the time-marching loop.
138 """
139 pass
140
142 """
143 Things to do before the time march for a specific time step.
144 """
145 pass
146
147 - def postmarch(self):
148 """
149 Things to do after the time march for a specific time step.
150 """
151 pass
152
153 - def postloop(self):
154 """
155 Things to do after the time-marching loop.
156 """
157 pass
158
160 """
161 Hook container and invoker.
162
163 @ivar cse: case object.
164 @itype cse: solvcon.case.BaseCase
165 """
170 """
171 The object to be appended (the first and only argument) should be a
172 Hook object, but this method actually accept either a Hook type or an
173 Anchor type. The method will automatically create the necessary Hook
174 object when detect acceptable type object passed as the first argument.
175
176 All the keywords go to the creation of the Hook object if the first
177 argument is a type. If the first argument is an instantiated Hook
178 object, the method accepts no keywords.
179
180 @param obj: the hook object to be appended.
181 @type obj: solvcon.hook.Hook
182 """
183 from .anchor import Anchor
184 if isinstance(obj, type):
185 if issubclass(obj, Anchor):
186 kw['ankcls'] = obj
187 obj = Hook
188 obj = obj(self.cse, **kw)
189 else:
190 assert len(kw) == 0
191 super(HookList, self).append(obj)
193 """
194 Invoke the specified method for each hook object.
195
196 @param method: name of the method to run.
197 @type method: str
198 """
199 runhooks = self
200 if method == 'postloop':
201 runhooks = reversed(runhooks)
202 for hook in runhooks:
203 getattr(hook, method)()
207
212 """
213 Print simulation progess.
214
215 @ivar linewidth: the maximal width for progress symbol. 50 is upper limit.
216 @itype linewidth: int
217 """
222 istep = self.cse.execution.step_current
223 nsteps = self.cse.execution.steps_run
224 info = self.info
225 info("Steps %d/%d\n" % (istep, nsteps))
226 - def postmarch(self):
227 from time import time
228 istep = self.cse.execution.step_current
229 nsteps = self.cse.execution.steps_run
230 tstart = self.cse.log.time['loop_march'][0]
231 psteps = self.psteps
232 linewidth = self.linewidth
233 info = self.info
234
235 tcurr = time()
236 tleft = (tcurr-tstart) * ((float(nsteps)-float(istep))/float(istep))
237
238 if istep%psteps == 0:
239 info("#")
240 if istep > 0 and istep%(psteps*linewidth) == 0:
241 info("\nStep %d/%d, %.1fs elapsed, %.1fs left\n" % (
242 istep, nsteps, tcurr-tstart, tleft,
243 ))
244 elif istep == nsteps:
245 info("\nStep %d/%d done\n" % (istep, nsteps))
246
251 """
252 Base type for hooks needing a BlockCase.
253 """
258
259 @property
262
263 - def _collect_interior(self, key, tovar=False, inder=False,
264 consider_ghost=True):
265 """
266 @param key: the name of the array to collect in a solver object.
267 @type key: str
268 @keyword tovar: flag to store collect data to case var dict.
269 @type tovar: bool
270 @keyword inder: the array is for derived data.
271 @type inder: bool
272 @keyword consider_ghost: treat the array with the consideration of
273 ghost cells. Default is True.
274 @type consider_ghost: bool
275 @return: the interior array hold by the solver.
276 @rtype: numpy.ndarray
277 """
278 from numpy import empty
279 cse = self.cse
280 ncell = self.blk.ncell
281 ngstcell = self.blk.ngstcell
282 if cse.is_parallel:
283 dom = self.cse.solver.domainobj
284
285 dealer = self.cse.solver.dealer
286 arrs = list()
287 for iblk in range(dom.nblk):
288 dealer[iblk].cmd.pull(key, inder=inder, with_worker=True)
289 arr = dealer[iblk].recv()
290 arrs.append(arr)
291
292 shape = [it for it in arrs[0].shape]
293 shape[0] = ncell
294 arrg = empty(shape, dtype=arrs[0].dtype)
295
296 clmaps = dom.mappers[2]
297 for iblk in range(dom.nblk):
298 slctg = (clmaps[:,1] == iblk)
299 slctl = clmaps[slctg,0]
300 if consider_ghost:
301 slctl += dom.shapes[iblk,6]
302 arrg[slctg] = arrs[iblk][slctl]
303 else:
304 if consider_ghost:
305 start = ngstcell
306 else:
307 start = 0
308 if inder:
309 arrg = cse.solver.solverobj.der[key][start:].copy()
310 else:
311 arrg = getattr(cse.solver.solverobj, key)[start:].copy()
312 if tovar:
313 self.cse.execution.var[key] = arrg
314 return arrg
315
317 """
318 @param arrg: the global array to be spreaded.
319 @type arrg: numpy.ndarray
320 @param key: the name of the array to collect in a solver object.
321 @type key: str
322 @keyword consider_ghost: treat the arrays with the consideration of
323 ghost cells. Default is True.
324 @type consider_ghost: bool
325 @return: the interior array hold by the solver.
326 @rtype: numpy.ndarray
327 """
328 from numpy import empty
329 cse = self.cse
330 ncell = self.blk.ncell
331 ngstcell = self.blk.ngstcell
332 if cse.is_parallel:
333 dom = self.cse.solver.domainobj
334 dealer = self.cse.solver.dealer
335 clmaps = dom.mappers[2]
336 for iblk in range(len(dom)):
337 blk = dom[iblk]
338
339 shape = [it for it in arrg.shape]
340 if consider_ghost:
341 shape[0] = blk.ngstcell+blk.ncell
342 else:
343 shape[0] = blk.ncell
344 arr = empty(shape, dtype=arrg.dtype)
345
346 slctg = (clmaps[:,1] == iblk)
347 slctl = clmaps[slctg,0]
348 if consider_ghost:
349 slctl += blk.ngstcell
350
351 arr[slctl] = arrg[slctg]
352 dealer[iblk].cmd.push(arr, key, start=blk.ngstcell)
353 else:
354 if consider_ghost:
355 start = ngstcell
356 else:
357 start = 0
358 getattr(cse.solver.solverobj, key)[start:] = arrg[:]
359
362 """
363 If keyword psteps is None, postmarch method will not output performance
364 information.
365 """
366 self.show_bclist = kw.pop('show_bclist', False)
367 self.perffn = kw.pop('perffn', None)
368 super(BlockInfoHook, self).__init__(cse, **kw)
370 blk = self.blk
371 self.info("Block information:\n %s\n" % str(blk))
372 if self.show_bclist:
373 for bc in blk.bclist:
374 self.info(" %s\n" % bc)
395 perf = (step_current-step_init)*ncell / time * 1.e-6
396 out('Performance of %s:\n' % self.cse.io.basefn)
397 out(' %g seconds in marching solver.\n' % time)
398 out(' %g seconds/step.\n' % (time/(step_current-step_init)))
399 out(' %g microseconds/cell.\n' % (1./perf))
400 out(' %g Mcells/seconds.\n' % perf)
401 out(' %g Mvariables/seconds.\n' % (perf*neq))
402 if isinstance(self.cse.execution.npart, int):
403 out(' %g Mcells/seconds/computer.\n' % (perf/npart))
404 out(' %g Mvariables/seconds/computer.\n' % (perf*neq/npart))
405 pf.close()
406 - def postmarch(self):
407 istep = self.cse.execution.step_current
408 nsteps = self.cse.execution.steps_run
409 psteps = self.psteps
410 if istep > 0 and psteps and istep%psteps == 0 and istep != nsteps:
411 self._show_performance()
412 - def postloop(self):
414
417 self.varlist = kw.pop('varlist')
418 self.error_on_nan = kw.pop('error_on_nan', False)
419 super(CollectHook, self).__init__(cse, **kw)
420 - def postmarch(self):
421 from numpy import isnan
422 psteps = self.psteps
423 istep = self.cse.execution.step_current
424 if istep%psteps != 0 and istep != self.cse.execution.steps_run:
425 return
426 vstep = self.cse.execution.varstep
427 var = self.cse.execution.var
428
429 if istep != vstep:
430 for key, kw in self.varlist:
431 arr = var[key] = self._collect_interior(key, **kw)
432 nans = isnan(arr)
433 msg = 'nan occurs in %s at step %d' % (key, istep)
434 if nans.any():
435 if self.error_on_nan:
436 raise ValueError(msg)
437 else:
438 self.info(msg+'\n')
439 self.cse.execution.varstep = istep
440 preloop = postmarch
441
446 """
447 Mark each cell with the domain index.
448 """
450 from numpy import zeros
451 from .domain import Collective
452 cse = self.cse
453 dom = cse.solver.domainobj
454 if isinstance(dom, Collective):
455 cse.execution.var['domain'] = dom.part
456 else:
457 cse.execution.var['domain'] = zeros(dom.blk.ncell, dtype='int32')
458
460 """
461 Mark each cell with the group index.
462 """
464 var = self.cse.execution.var
465 var['clgrp'] = self.blk.clgrp.copy()
466
467
468
469
470 -class VtkSave(BlockHook):
471 """
472 Base type for writer for cse with a block.
473
474 @ivar binary: True for BINARY format; False for ASCII.
475 @itype binary: bool
476 @ivar cache_grid: True to cache grid; False to forget grid every time.
477 @itype cache_grid: bool
478 """
480 self.binary = kw.pop('binary', False)
481 self.cache_grid = kw.pop('cache_grid', True)
482 super(VtkSave, self).__init__(cse, **kw)
483
485 """
486 Save the splitted geometry.
487 """
488
490 from math import log10, ceil
491 from .io.vtk import VtkLegacyUstGridWriter
492 cse = self.cse
493 if cse.is_parallel == 0:
494 return
495 basefn = cse.io.basefn
496 dom = cse.solver.domainobj
497 nblk = len(dom)
498
499 vtkfn = basefn + '_decomp'
500 vtksfn_tmpl = basefn + '_decomp' + '_%%0%dd'%int(ceil(log10(nblk))+1)
501 if self.binary:
502 vtkfn += ".bin.vtk"
503 vtksfn_tmpl += ".bin.vtk"
504 else:
505 vtkfn += ".vtk"
506 vtksfn_tmpl += ".vtk"
507
508
509 self.info("Save domain decomposition for visualization (%d parts).\n" \
510 % nblk)
511 VtkLegacyUstGridWriter(dom.blk,
512 binary=self.binary, cache_grid=self.cache_grid).write(vtkfn)
513
514 iblk = 0
515 for blk in dom:
516 writer = VtkLegacyUstGridWriter(blk,
517 binary=self.binary, cache_grid=self.cache_grid).write(
518 vtksfn_tmpl%iblk)
519 iblk += 1
520
522 """
523 Save the geometry and variables in a case when time marching.
524
525 @ivar vtkfn_tmpl: template for output file name(s).
526 @itype vtkfn_tmpl: str
527 """
529 import os
530 from math import log10, ceil
531 super(MarchSave, self).__init__(cse, **kw)
532 nsteps = cse.execution.steps_run
533 basefn = cse.io.basefn
534 vtkfn_tmpl = basefn + "_%%0%dd"%int(ceil(log10(nsteps))+1)
535 if self.binary:
536 vtkfn_tmpl += ".bin.vtk"
537 else:
538 vtkfn_tmpl += ".vtk"
539 self.vtkfn_tmpl = os.path.join(cse.io.basedir,
540 kw.pop('vtkfn_tmpl', vtkfn_tmpl))
541
542 @property
544 """
545 Get dictionaries for scalar and vector data from case.
546
547 @return: dictionaries for scalar and vector.
548 @rtype: tuple
549 """
550 cse = self.cse
551 exe = cse.execution
552 var = exe.var
553
554 sarrnames = []
555 if 'solverobj' in cse.solver:
556 sarrnames = cse.solver.solverobj._solution_array_
557
558 sarrs = dict()
559 varrs = dict()
560 for key in var:
561 if key in sarrnames:
562 continue
563 arr = var[key]
564 if len(arr.shape) == 1:
565 sarrs[key] = arr
566 elif len(arr.shape) == 2:
567 varrs[key] = arr
568 else:
569 raise IndexError, \
570 'the dimensions of case[\'%s\'] is %d > 2' % (
571 key, len(arr.shape))
572
573 soln = var['soln']
574 for i in range(soln.shape[1]):
575 sarrs['soln[%d]'%i] = soln[:,i]
576
577 return sarrs, varrs
578
580 self.writer.scalars, self.writer.vectors = self.data
581 self.writer.write(self.vtkfn_tmpl % istep)
582
593
594 - def postmarch(self):
595 psteps = self.psteps
596 exe = self.cse.execution
597 istep = exe.step_current
598 vstep = exe.varstep
599 if istep%psteps == 0 or istep == self.cse.execution.steps_run:
600 assert istep == vstep
601 self._write(istep)
602
608 """
609 Save the geometry and variables in a case when time marching in parallel
610 VTK XML format.
611
612 @ivar anames: the arrays in der of solvers to be saved. Format is (name,
613 inder, ndim), (name, inder, ndim) ... For ndim > 0 the
614 array is a spatial vector, for ndim == 0 a simple scalar, and ndim < 0
615 a list of scalar.
616 @itype anames: list
617 @ivar compressor: compressor for binary data. Can only be 'gz' or ''.
618 @itype compressor: str
619 @ivar fpdtype: string for floating point data type (in numpy convention).
620 @itype fpdtype: str
621 @ivar altdir: the alternate directory to save the VTK files.
622 @itype altdir: str
623 @ivar altsym: the symbolic link in basedir pointing to the alternate
624 directory to save the VTK files.
625 @itype altsym: str
626 @ivar pextmpl: template for the extension of split VTK file name.
627 @itype pextmpl: str
628 """
630 import os
631 from math import log10, ceil
632 self.anames = kw.pop('anames', list())
633 self.compressor = kw.pop('compressor', 'gz')
634 self.fpdtype = kw.pop('fpdtype', str(cse.execution.fpdtype))
635 self.altdir = kw.pop('altdir', '')
636 self.altsym = kw.pop('altsym', '')
637 super(PMarchSave, self).__init__(cse, **kw)
638
639 nsteps = cse.execution.steps_run
640 basefn = cse.io.basefn
641 if self.altdir:
642 vdir = self.altdir
643 if self.altsym:
644 altsym = os.path.join(cse.io.basedir, self.altsym)
645 if not os.path.exists(altsym):
646 os.symlink(vdir, altsym)
647 else:
648 vdir = cse.io.basedir
649 if not os.path.exists(vdir):
650 os.makedirs(vdir)
651 vtkfn_tmpl = basefn + "_%%0%dd"%int(ceil(log10(nsteps))+1) + '.pvtu'
652 self.vtkfn_tmpl = os.path.join(vdir, kw.pop('vtkfn_tmpl', vtkfn_tmpl))
653
654 npart = cse.execution.npart
655 self.pextmpl = '.p%%0%dd'%int(ceil(log10(npart))+1) if npart else ''
656 self.pextmpl += '.vtu'
658 import os
659 from .anchor import MarchSaveAnchor
660 basefn = os.path.splitext(self.vtkfn_tmpl)[0]
661 anames = dict([(ent[0], ent[1]) for ent in self.anames])
662 ankkw = dict(anames=anames, compressor=self.compressor,
663 fpdtype=self.fpdtype, psteps=self.psteps,
664 vtkfn_tmpl=basefn+self.pextmpl)
665 self._deliver_anchor(svr, MarchSaveAnchor, ankkw)
691 - def postmarch(self):
692 psteps = self.psteps
693 istep = self.cse.execution.step_current
694 if istep%psteps == 0:
695 self._write(istep)
696 - def postloop(self):
697 psteps = self.psteps
698 istep = self.cse.execution.step_current
699 if istep%psteps != 0:
700 self._write(istep)
701
702
703
704
705
706 -class PVtkHook(BlockHook):
707 """
708 Anchor dropper and wrapping PVTP file writer. Note, fpdtype should be set
709 to single precision or parallel VTP file could be in wrong format.
710
711 @ivar name: name of this VTK operation set; there can be multiple operation
712 sets attaching on a Case object; default is None.
713 @itype name: str
714 @ivar anames: the arrays in der of solvers to be saved. Format is (name,
715 inder, ndim), (name, inder, ndim) ... For ndim > 0 the
716 array is a spatial vector, for ndim == 0 a simple scalar, and ndim < 0
717 a list of scalar.
718 @itype anames: list
719 @ivar fpdtype: string for floating point data type (in numpy convention).
720 @itype fpdtype: str
721 @ivar altdir: the alternate directory to save the VTK files.
722 @itype altdir: str
723 @ivar altsym: the symbolic link in basedir pointing to the alternate
724 directory to save the VTK files.
725 @itype altsym: str
726 @ivar pextmpl: template for the extension of split VTK file name.
727 @itype pextmpl: str
728 """
730 import os
731 from math import log10, ceil
732 self.name = kw.pop('name', None)
733 self.anames = kw.pop('anames', list())
734 self.fpdtype = kw.pop('fpdtype', 'float32')
735 self.altdir = kw.pop('altdir', '')
736 self.altsym = kw.pop('altsym', '')
737 self.ankkw = kw.pop('ankkw', dict())
738 super(PVtkHook, self).__init__(cse, **kw)
739
740 nsteps = cse.execution.steps_run
741 if self.altdir:
742 vdir = self.altdir
743 if self.altsym:
744 altsym = os.path.join(cse.io.basedir, self.altsym)
745 if not os.path.exists(altsym):
746 os.symlink(vdir, altsym)
747 else:
748 vdir = cse.io.basedir
749 if not os.path.exists(vdir):
750 os.makedirs(vdir)
751
752 vtkfn_tmpl = cse.io.basefn
753 if self.name is not None:
754 vtkfn_tmpl += '_%s' % self.name
755 vtkfn_tmpl += "_%%0%dd"%int(ceil(log10(nsteps))+1) + '.pvtp'
756 self.vtkfn_tmpl = os.path.join(vdir, kw.pop('vtkfn_tmpl', vtkfn_tmpl))
757
758 npart = cse.execution.npart
759 self.pextmpl = '.p%%0%dd'%int(ceil(log10(npart))+1) if npart else ''
760 self.pextmpl += '.vtp'
762 import os
763 basefn = os.path.splitext(self.vtkfn_tmpl)[0]
764 ankkw = self.ankkw.copy()
765 ankkw.update(dict(anames=self.anames, fpdtype=self.fpdtype,
766 psteps=self.psteps, vtkfn_tmpl=basefn+self.pextmpl))
767 self._deliver_anchor(svr, self.ankcls, ankkw)
769 from .io.vtkxml import PVtkXmlPolyDataWriter
770 if not self.cse.execution.npart:
771 return
772
773 arrs = list()
774 for key, inder, ndim in self.anames:
775 if ndim > 0:
776 arrs.append((key, self.fpdtype, True))
777 elif ndim < 0:
778 for it in range(abs(ndim)):
779 arrs.append(('%s[%d]' % (key, it), self.fpdtype, False))
780 else:
781 arrs.append((key, self.fpdtype, False))
782
783 wtr = PVtkXmlPolyDataWriter(self.blk, fpdtype=self.fpdtype, arrs=arrs,
784 npiece=self.cse.execution.npart, pextmpl=self.pextmpl)
785 vtkfn = self.vtkfn_tmpl % istep
786 self.info('Writing \n %s\n... ' % vtkfn)
787 wtr.write(vtkfn)
788 self.info('done.\n')
791 - def postmarch(self):
792 psteps = self.psteps
793 istep = self.cse.execution.step_current
794 if istep%psteps == 0:
795 self._write(istep)
796 - def postloop(self):
797 psteps = self.psteps
798 istep = self.cse.execution.step_current
799 if istep%psteps != 0:
800 self._write(istep)
801