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:
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 sarrs = dict()
555 varrs = dict()
556 for key in var:
557 if key == 'soln' or key == 'dsoln':
558 continue
559 arr = var[key]
560 if len(arr.shape) == 1:
561 sarrs[key] = arr
562 elif len(arr.shape) == 2:
563 varrs[key] = arr
564 else:
565 raise IndexError, \
566 'the dimensions of case[\'%s\'] is %d > 2' % (
567 key, len(arr.shape))
568
569 soln = var['soln']
570 for i in range(soln.shape[1]):
571 sarrs['soln[%d]'%i] = soln[:,i]
572
573 return sarrs, varrs
574
576 self.writer.scalars, self.writer.vectors = self.data
577 self.writer.write(self.vtkfn_tmpl % istep)
578
589
590 - def postmarch(self):
591 psteps = self.psteps
592 exe = self.cse.execution
593 istep = exe.step_current
594 vstep = exe.varstep
595 if istep%psteps == 0:
596 assert istep == vstep
597 self._write(istep)
598
604 """
605 Save the geometry and variables in a case when time marching in parallel
606 VTK XML format.
607
608 @ivar anames: the arrays in der of solvers to be saved. Format is (name,
609 inder, ndim), (name, inder, ndim) ... For ndim > 0 the
610 array is a spatial vector, for ndim == 0 a simple scalar, and ndim < 0
611 a list of scalar.
612 @itype anames: list
613 @ivar compressor: compressor for binary data. Can only be 'gz' or ''.
614 @itype compressor: str
615 @ivar fpdtype: string for floating point data type (in numpy convention).
616 @itype fpdtype: str
617 @ivar altdir: the alternate directory to save the VTK files.
618 @itype altdir: str
619 @ivar altsym: the symbolic link in basedir pointing to the alternate
620 directory to save the VTK files.
621 @itype altsym: str
622 @ivar pextmpl: template for the extension of split VTK file name.
623 @itype pextmpl: str
624 """
626 import os
627 from math import log10, ceil
628 self.anames = kw.pop('anames', list())
629 self.compressor = kw.pop('compressor', 'gz')
630 self.fpdtype = kw.pop('fpdtype', str(cse.execution.fpdtype))
631 self.altdir = kw.pop('altdir', '')
632 self.altsym = kw.pop('altsym', '')
633 super(PMarchSave, self).__init__(cse, **kw)
634
635 nsteps = cse.execution.steps_run
636 basefn = cse.io.basefn
637 if self.altdir:
638 vdir = self.altdir
639 if self.altsym:
640 altsym = os.path.join(cse.io.basedir, self.altsym)
641 if not os.path.exists(altsym):
642 os.symlink(vdir, altsym)
643 else:
644 vdir = cse.io.basedir
645 if not os.path.exists(vdir):
646 os.makedirs(vdir)
647 vtkfn_tmpl = basefn + "_%%0%dd"%int(ceil(log10(nsteps))+1) + '.pvtu'
648 self.vtkfn_tmpl = os.path.join(vdir, kw.pop('vtkfn_tmpl', vtkfn_tmpl))
649
650 npart = cse.execution.npart
651 self.pextmpl = '.p%%0%dd'%int(ceil(log10(npart))+1) if npart else ''
652 self.pextmpl += '.vtu'
654 import os
655 from .anchor import MarchSaveAnchor
656 basefn = os.path.splitext(self.vtkfn_tmpl)[0]
657 anames = dict([(ent[0], ent[1]) for ent in self.anames])
658 ankkw = dict(anames=anames, compressor=self.compressor,
659 fpdtype=self.fpdtype, psteps=self.psteps,
660 vtkfn_tmpl=basefn+self.pextmpl)
661 self._deliver_anchor(svr, MarchSaveAnchor, ankkw)
687 - def postmarch(self):
688 psteps = self.psteps
689 istep = self.cse.execution.step_current
690 if istep%psteps == 0:
691 self._write(istep)
692 - def postloop(self):
693 psteps = self.psteps
694 istep = self.cse.execution.step_current
695 if istep%psteps != 0:
696 self._write(istep)
697
698
699
700
701
702 -class PVtkHook(BlockHook):
703 """
704 Anchor dropper and wrapping PVTP file writer. Note, fpdtype should be set
705 to single precision or parallel VTP file could be in wrong format.
706
707 @ivar name: name of this VTK operation set; there can be multiple operation
708 sets attaching on a Case object; default is None.
709 @itype name: str
710 @ivar anames: the arrays in der of solvers to be saved. Format is (name,
711 inder, ndim), (name, inder, ndim) ... For ndim > 0 the
712 array is a spatial vector, for ndim == 0 a simple scalar, and ndim < 0
713 a list of scalar.
714 @itype anames: list
715 @ivar fpdtype: string for floating point data type (in numpy convention).
716 @itype fpdtype: str
717 @ivar altdir: the alternate directory to save the VTK files.
718 @itype altdir: str
719 @ivar altsym: the symbolic link in basedir pointing to the alternate
720 directory to save the VTK files.
721 @itype altsym: str
722 @ivar pextmpl: template for the extension of split VTK file name.
723 @itype pextmpl: str
724 """
726 import os
727 from math import log10, ceil
728 self.name = kw.pop('name', None)
729 self.anames = kw.pop('anames', list())
730 self.fpdtype = kw.pop('fpdtype', 'float32')
731 self.altdir = kw.pop('altdir', '')
732 self.altsym = kw.pop('altsym', '')
733 self.ankkw = kw.pop('ankkw', dict())
734 super(PVtkHook, self).__init__(cse, **kw)
735
736 nsteps = cse.execution.steps_run
737 if self.altdir:
738 vdir = self.altdir
739 if self.altsym:
740 altsym = os.path.join(cse.io.basedir, self.altsym)
741 if not os.path.exists(altsym):
742 os.symlink(vdir, altsym)
743 else:
744 vdir = cse.io.basedir
745 if not os.path.exists(vdir):
746 os.makedirs(vdir)
747
748 vtkfn_tmpl = cse.io.basefn
749 if self.name is not None:
750 vtkfn_tmpl += '_%s' % self.name
751 vtkfn_tmpl += "_%%0%dd"%int(ceil(log10(nsteps))+1) + '.pvtp'
752 self.vtkfn_tmpl = os.path.join(vdir, kw.pop('vtkfn_tmpl', vtkfn_tmpl))
753
754 npart = cse.execution.npart
755 self.pextmpl = '.p%%0%dd'%int(ceil(log10(npart))+1) if npart else ''
756 self.pextmpl += '.vtp'
758 import os
759 basefn = os.path.splitext(self.vtkfn_tmpl)[0]
760 ankkw = self.ankkw.copy()
761 ankkw.update(dict(anames=self.anames, fpdtype=self.fpdtype,
762 psteps=self.psteps, vtkfn_tmpl=basefn+self.pextmpl))
763 self._deliver_anchor(svr, self.ankcls, ankkw)
765 from .io.vtkxml import PVtkXmlPolyDataWriter
766 if not self.cse.execution.npart:
767 return
768
769 arrs = list()
770 for key, inder, ndim in self.anames:
771 if ndim > 0:
772 arrs.append((key, self.fpdtype, True))
773 elif ndim < 0:
774 for it in range(abs(ndim)):
775 arrs.append(('%s[%d]' % (key, it), self.fpdtype, False))
776 else:
777 arrs.append((key, self.fpdtype, False))
778
779 wtr = PVtkXmlPolyDataWriter(self.blk, fpdtype=self.fpdtype, arrs=arrs,
780 npiece=self.cse.execution.npart, pextmpl=self.pextmpl)
781 vtkfn = self.vtkfn_tmpl % istep
782 self.info('Writing \n %s\n... ' % vtkfn)
783 wtr.write(vtkfn)
784 self.info('done.\n')
787 - def postmarch(self):
788 psteps = self.psteps
789 istep = self.cse.execution.step_current
790 if istep%psteps == 0:
791 self._write(istep)
792 - def postloop(self):
793 psteps = self.psteps
794 istep = self.cse.execution.step_current
795 if istep%psteps != 0:
796 self._write(istep)
797