def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Construct the solution writer basedir = self.cfg.getpath(cfgsect, 'basedir', '.') basename = self.cfg.get(cfgsect, 'basename') self._writer = NativeWriter(intg, self.nvars, basedir, basename, prefix='soln') # Output time step and next output time self.dt_out = self.cfg.getfloat(cfgsect, 'dt-out') self.tout_next = intg.tcurr # Output field names self.fields = intg.system.elementscls.convarmap[self.ndims] # Register our output times with the integrator intg.call_plugin_dt(self.dt_out) # If we're not restarting then write out the initial solution if not intg.isrestart: self(intg) else: self.tout_next += self.dt_out
def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Underlying elements class self.elementscls = intg.system.elementscls # Expressions to time average c = self.cfg.items_as('constants', float) self.exprs = [(k, self.cfg.getexpr(cfgsect, k, subs=c)) for k in self.cfg.items(cfgsect) if k.startswith('avg-')] # Save a reference to the physical solution point locations self.plocs = intg.system.ele_ploc_upts # Output file directory, base name, and writer basedir = self.cfg.getpath(cfgsect, 'basedir', '.') basename = self.cfg.get(cfgsect, 'basename') self._writer = NativeWriter(intg, len(self.exprs), basedir, basename, prefix='tavg') # Time averaging parameters self.dtout = self.cfg.getfloat(cfgsect, 'dt-out') self.nsteps = self.cfg.getint(cfgsect, 'nsteps') self.tout = intg.tcurr + self.dtout # Register our output times with the integrator intg.call_plugin_dt(self.dtout) # Time averaging state self.prevt = intg.tcurr self.prevex = self._eval_exprs(intg) self.accmex = [np.zeros_like(p) for p in self.prevex]
def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Base output directory and file name basedir = self.cfg.getpath(self.cfgsect, 'basedir', '.', abs=True) basename = self.cfg.get(self.cfgsect, 'basename') # Construct the solution writer self._writer = NativeWriter(intg, basedir, basename, 'soln') # Output time step and last output time self.dt_out = self.cfg.getfloat(cfgsect, 'dt-out') self.tout_last = intg.tcurr # Output field names self.fields = intg.system.elementscls.convarmap[self.ndims] # Output data type self.fpdtype = intg.backend.fpdtype # Register our output times with the integrator intg.call_plugin_dt(self.dt_out) # If we're not restarting then make sure we write out the initial # solution when we are called for the first time if not intg.isrestart: self.tout_last -= self.dt_out
class WriterPlugin(BasePlugin): name = 'writer' systems = ['*'] def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Construct the solution writer basedir = self.cfg.getpath(cfgsect, 'basedir', '.') basename = self.cfg.get(cfgsect, 'basename') self._writer = NativeWriter(intg, self.nvars, basedir, basename, prefix='soln') # Output time step and next output time self.dt_out = self.cfg.getfloat(cfgsect, 'dt-out') self.tout_next = intg.tcurr # Output field names self.fields = intg.system.elementscls.convarmap[self.ndims] # Register our output times with the integrator intg.call_plugin_dt(self.dt_out) # If we're not restarting then write out the initial solution if not intg.isrestart: self(intg) else: self.tout_next += self.dt_out def __call__(self, intg): if abs(self.tout_next - intg.tcurr) > intg.dtmin: return stats = Inifile() stats.set('data', 'fields', ','.join(self.fields)) stats.set('data', 'prefix', 'soln') intg.collect_stats(stats) metadata = dict(config=self.cfg.tostr(), stats=stats.tostr(), mesh_uuid=intg.mesh_uuid) self._writer.write(intg.soln, metadata, intg.tcurr) self.tout_next += self.dt_out
def _init_writer_for_region(self, intg, nout, prefix, *, fpdtype=None): # Base output directory and file name basedir = self.cfg.getpath(self.cfgsect, 'basedir', '.', abs=True) basename = self.cfg.get(self.cfgsect, 'basename') # Data type if fpdtype is None: fpdtype = intg.backend.fpdtype elif fpdtype == 'single': fpdtype = np.float32 elif fpdtype == 'double': fpdtype = np.float64 else: raise ValueError('Invalid floating point data type') # Region of interest region = self.cfg.get(self.cfgsect, 'region', '*') # All elements if region == '*': mdata = self._prepare_mdata_all(intg, fpdtype, nout, prefix) self._add_region_data = self._add_region_data_all # All elements inside a box elif '(' in region or '[' in region: box = self.cfg.getliteral(self.cfgsect, 'region') mdata = self._prepare_mdata_box(intg, fpdtype, nout, prefix, *box) self._add_region_data = self._add_region_data_subset # All elements on a boundary else: mdata = self._prepare_mdata_bcs(intg, fpdtype, nout, prefix, region) self._add_region_data = self._add_region_data_subset # Construct the file writer return NativeWriter(intg, mdata, basedir, basename)
class WriterPlugin(BasePlugin): name = 'writer' systems = ['*'] formulations = ['dual', 'std'] def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Construct the solution writer basedir = self.cfg.getpath(cfgsect, 'basedir', '.', abs=True) basename = self.cfg.get(cfgsect, 'basename') self._writer = NativeWriter(intg, self.nvars, basedir, basename, prefix='soln') # Output time step and next output time self.dt_out = self.cfg.getfloat(cfgsect, 'dt-out') self.tout_next = intg.tcurr # Output field names self.fields = intg.system.elementscls.convarmap[self.ndims] # Register our output times with the integrator intg.call_plugin_dt(self.dt_out) # If we're not restarting then write out the initial solution if not intg.isrestart: self(intg) else: self.tout_next += self.dt_out def __call__(self, intg): if abs(self.tout_next - intg.tcurr) > self.tol: return stats = Inifile() stats.set('data', 'fields', ','.join(self.fields)) stats.set('data', 'prefix', 'soln') intg.collect_stats(stats) # Prepare the metadata metadata = dict(intg.cfgmeta, stats=stats.tostr(), mesh_uuid=intg.mesh_uuid) # Write out the file solnfname = self._writer.write(intg.soln, metadata, intg.tcurr) # If a post-action has been registered then invoke it self._invoke_postaction(mesh=intg.system.mesh.fname, soln=solnfname, t=intg.tcurr) # Compute the next output time self.tout_next = intg.tcurr + self.dt_out
def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Underlying elements class self.elementscls = intg.system.elementscls # Expressions to time average c = self.cfg.items_as('constants', float) self.exprs = [(k, self.cfg.getexpr(cfgsect, k, subs=c)) for k in self.cfg.items(cfgsect) if k.startswith('avg-')] # Gradient pre-processing self._init_gradients(intg) # Save a reference to the physical solution point locations self.plocs = intg.system.ele_ploc_upts # Element info and backend data type einfo = zip(intg.system.ele_types, intg.system.ele_shapes) fpdtype = intg.backend.fpdtype # Output metadata mdata = [(f'tavg_{etype}', (nupts, len(self.exprs), neles), fpdtype) for etype, (nupts, _, neles) in einfo] # Output base directory and name basedir = self.cfg.getpath(cfgsect, 'basedir', '.', abs=True) basename = self.cfg.get(cfgsect, 'basename') self._writer = NativeWriter(intg, mdata, basedir, basename) # Time averaging parameters self.dtout = self.cfg.getfloat(cfgsect, 'dt-out') self.nsteps = self.cfg.getint(cfgsect, 'nsteps') self.tout_last = intg.tcurr # Register our output times with the integrator intg.call_plugin_dt(self.dtout) # Time averaging state self.prevt = intg.tcurr self.prevex = self._eval_exprs(intg) self.accmex = [np.zeros_like(p) for p in self.prevex]
def save_solution(self, savedir, basename, t=0): ndims = self.solver.system.ndims nvars = self.solver.system.nvars writer = NativeWriter(self.solver, nvars, savedir, basename, prefix='soln') fields = self.solver.system.elementscls.convarmap[ndims] stats = Inifile() stats.set('data', 'fields', ','.join(fields)) stats.set('data', 'prefix', 'soln') self.solver.collect_stats(stats) stats.set('solver-time-integrator', 'tcurr', str(t)) metadata = dict(self.solver.cfgmeta, stats=stats.tostr(), mesh_uuid=self.solver.mesh_uuid) writer.write(self.solver.soln, metadata, t)
def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Underlying elements class self.elementscls = intg.system.elementscls # Averaging mode self.mode = self.cfg.get(cfgsect, 'mode', 'windowed') if self.mode not in {'continuous', 'windowed'}: raise ValueError('Invalid averaging mode') # Expressions pre-processing self._prepare_exprs() # Output data type fpdtype = self.cfg.get(cfgsect, 'precision', 'single') if fpdtype == 'single': self.fpdtype = np.float32 elif fpdtype == 'double': self.fpdtype = np.float64 else: raise ValueError('Invalid floating point data type') # Base output directory and file name basedir = self.cfg.getpath(self.cfgsect, 'basedir', '.', abs=True) basename = self.cfg.get(self.cfgsect, 'basename') # Construct the file writer self._writer = NativeWriter(intg, basedir, basename, 'tavg') # Gradient pre-processing self._init_gradients(intg) # Time averaging parameters self.tstart = self.cfg.getfloat(cfgsect, 'tstart', 0.0) self.dtout = self.cfg.getfloat(cfgsect, 'dt-out') self.nsteps = self.cfg.getint(cfgsect, 'nsteps') # Register our output times with the integrator intg.call_plugin_dt(self.dtout) # Mark ourselves as not currently averaging self._started = False
def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Output region region = self.cfg.get(cfgsect, 'region', '*') # All elements if region == '*': mdata = self._prepare_mdata_all(intg) self._prepare_data = self._prepare_data_all # All elements inside a box elif ',' in region: box = self.cfg.getliteral(cfgsect, 'region') mdata = self._prepare_mdata_box(intg, *box) self._prepare_data = self._prepare_data_subset # All elements on a boundary else: mdata = self._prepare_mdata_bcs(intg, region) self._prepare_data = self._prepare_data_subset # Construct the solution writer basedir = self.cfg.getpath(cfgsect, 'basedir', '.', abs=True) basename = self.cfg.get(cfgsect, 'basename') self._writer = NativeWriter(intg, mdata, basedir, basename) # Output time step and last output time self.dt_out = self.cfg.getfloat(cfgsect, 'dt-out') self.tout_last = intg.tcurr # Output field names self.fields = intg.system.elementscls.convarmap[self.ndims] # Register our output times with the integrator intg.call_plugin_dt(self.dt_out) # If we're not restarting then write out the initial solution if not intg.isrestart: self.tout_last -= self.dt_out self(intg)
class WriterPlugin(BasePlugin): name = 'writer' systems = ['*'] formulations = ['dual', 'std'] def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Output region region = self.cfg.get(cfgsect, 'region', '*') # All elements if region == '*': mdata = self._prepare_mdata_all(intg) self._prepare_data = self._prepare_data_all # All elements inside a box elif ',' in region: box = self.cfg.getliteral(cfgsect, 'region') mdata = self._prepare_mdata_box(intg, *box) self._prepare_data = self._prepare_data_subset # All elements on a boundary else: mdata = self._prepare_mdata_bcs(intg, region) self._prepare_data = self._prepare_data_subset # Construct the solution writer basedir = self.cfg.getpath(cfgsect, 'basedir', '.', abs=True) basename = self.cfg.get(cfgsect, 'basename') self._writer = NativeWriter(intg, mdata, basedir, basename) # Output time step and last output time self.dt_out = self.cfg.getfloat(cfgsect, 'dt-out') self.tout_last = intg.tcurr # Output field names self.fields = intg.system.elementscls.convarmap[self.ndims] # Register our output times with the integrator intg.call_plugin_dt(self.dt_out) # If we're not restarting then write out the initial solution if not intg.isrestart: self.tout_last -= self.dt_out self(intg) def _prepare_mdata_all(self, intg): # Element info and backend data type einfo = zip(intg.system.ele_types, intg.system.ele_shapes) fpdtype = intg.backend.fpdtype # Output metadata return [(f'soln_{etype}', shape, fpdtype) for etype, shape in einfo] def _prepare_mdata_box(self, intg, x0, x1): eset = {} for etype in intg.system.ele_types: pts = intg.system.mesh[f'spt_{etype}_p{intg.rallocs.prank}'] pts = np.moveaxis(pts, 2, 0) # Determine which points are inside the box inside = np.ones(pts.shape[1:], dtype=np.bool) for l, p, u in zip(x0, pts, x1): inside &= (l <= p) & (p <= u) if np.sum(inside): eset[etype] = np.any(inside, axis=0).nonzero()[0] return self._prepare_eset(intg, eset) def _prepare_mdata_bcs(self, intg, bcname): comm, rank, root = get_comm_rank_root() # Get the mesh and prepare the element set dict mesh = intg.system.mesh eset = defaultdict(list) # Boundary of interest bc = f'bcon_{bcname}_p{intg.rallocs.prank}' # Ensure the boundary exists bcranks = comm.gather(bc in mesh, root=root) if rank == root and not any(bcranks): raise ValueError(f'Boundary {bcname} does not exist') if bc in mesh: # Determine which of our elements are on the boundary for etype, eidx in mesh[bc][['f0', 'f1']].astype('U4,i4'): eset[etype].append(eidx) return self._prepare_eset(intg, eset) def _prepare_eset(self, intg, eset): elemap = intg.system.ele_map mdata, ddata = [], [] for etype, eidxs in sorted(eset.items()): neidx = len(eidxs) shape = (elemap[etype].nupts, elemap[etype].nvars, neidx) mdata.append((f'soln_{etype}', shape, intg.backend.fpdtype)) mdata.append((f'soln_{etype}_idxs', (neidx, ), np.int32)) doff = intg.system.ele_types.index(etype) darr = np.unique(eidxs).astype(np.int32) ddata.append((doff, darr)) # Save ddata for later use self._ddata = ddata return mdata def _prepare_data_all(self, intg): return intg.soln def _prepare_data_subset(self, intg): data = [] for doff, darr in self._ddata: data.append(intg.soln[doff][..., darr]) data.append(darr) return data def __call__(self, intg): if intg.tcurr - self.tout_last < self.dt_out - self.tol: return stats = Inifile() stats.set('data', 'fields', ','.join(self.fields)) stats.set('data', 'prefix', 'soln') intg.collect_stats(stats) # Prepare the metadata metadata = dict(intg.cfgmeta, stats=stats.tostr(), mesh_uuid=intg.mesh_uuid) # Prepare the data itself data = self._prepare_data(intg) # Write out the file solnfname = self._writer.write(data, metadata, intg.tcurr) # If a post-action has been registered then invoke it self._invoke_postaction(mesh=intg.system.mesh.fname, soln=solnfname, t=intg.tcurr) # Update the last output time self.tout_last = intg.tcurr
class TavgPlugin(BasePlugin): name = 'tavg' systems = ['*'] def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Underlying elements class self.elementscls = intg.system.elementscls # Expressions to time average c = self.cfg.items_as('constants', float) self.exprs = [(k, self.cfg.getexpr(cfgsect, k, subs=c)) for k in self.cfg.items(cfgsect) if k.startswith('avg-')] # Save a reference to the physical solution point locations self.plocs = intg.system.ele_ploc_upts # Output file directory, base name, and writer basedir = self.cfg.getpath(cfgsect, 'basedir', '.') basename = self.cfg.get(cfgsect, 'basename') self._writer = NativeWriter(intg, len(self.exprs), basedir, basename, prefix='tavg') # Time averaging parameters self.dtout = self.cfg.getfloat(cfgsect, 'dt-out') self.nsteps = self.cfg.getint(cfgsect, 'nsteps') self.tout = intg.tcurr + self.dtout # Register our output times with the integrator intg.call_plugin_dt(self.dtout) # Time averaging state self.prevt = intg.tcurr self.prevex = self._eval_exprs(intg) self.accmex = [np.zeros_like(p) for p in self.prevex] def _eval_exprs(self, intg): exprs = [] # Iterate over each element type in the simulation for soln, ploc in zip(intg.soln, self.plocs): # Get the primitive variable names and solutions pnames = self.elementscls.privarmap[self.ndims] psolns = self.elementscls.con_to_pri(soln.swapaxes(0, 1), self.cfg) # Prepare the substitutions dictionary ploc = dict(zip('xyz', ploc.swapaxes(0, 1))) subs = dict(zip(pnames, psolns), t=intg.tcurr, **ploc) # Evaluate the expressions exprs.append([npeval(v, subs) for k, v in self.exprs]) # Stack up the expressions for each element type and return return [np.dstack(exs).swapaxes(1, 2) for exs in exprs] def __call__(self, intg): dowrite = abs(self.tout - intg.tcurr) < self.tol doaccum = intg.nacptsteps % self.nsteps == 0 if dowrite or doaccum: # Evaluate the time averaging expressions currex = self._eval_exprs(intg) # Accumulate them; always do this even when just writing for a, p, c in zip(self.accmex, self.prevex, currex): a += 0.5*(intg.tcurr - self.prevt)*(p + c) # Save the time and solution self.prevt = intg.tcurr self.prevex = currex if dowrite: # Normalise accmex = [a / self.dtout for a in self.accmex] stats = Inifile() stats.set('data', 'prefix', 'tavg') stats.set('data', 'fields', ','.join(k for k, v in self.exprs)) stats.set('tavg', 'tstart', intg.tcurr - self.dtout) stats.set('tavg', 'tend', intg.tcurr) intg.collect_stats(stats) metadata = dict(intg.cfgmeta, stats=stats.tostr(), mesh_uuid=intg.mesh_uuid) self._writer.write(accmex, metadata, intg.tcurr) self.tout = intg.tcurr + self.dtout self.accmex = [np.zeros_like(a) for a in accmex]
class TavgPlugin(BasePlugin): name = 'tavg' systems = ['*'] formulations = ['dual', 'std'] def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Underlying elements class self.elementscls = intg.system.elementscls # Expressions to time average c = self.cfg.items_as('constants', float) self.exprs = [(k, self.cfg.getexpr(cfgsect, k, subs=c)) for k in self.cfg.items(cfgsect) if k.startswith('avg-')] # Gradient pre-processing self._init_gradients(intg) # Save a reference to the physical solution point locations self.plocs = intg.system.ele_ploc_upts # Output file directory, base name, and writer basedir = self.cfg.getpath(cfgsect, 'basedir', '.', abs=True) basename = self.cfg.get(cfgsect, 'basename') self._writer = NativeWriter(intg, len(self.exprs), basedir, basename, prefix='tavg') # Time averaging parameters self.dtout = self.cfg.getfloat(cfgsect, 'dt-out') self.nsteps = self.cfg.getint(cfgsect, 'nsteps') self.tout_last = intg.tcurr # Register our output times with the integrator intg.call_plugin_dt(self.dtout) # Time averaging state self.prevt = intg.tcurr self.prevex = self._eval_exprs(intg) self.accmex = [np.zeros_like(p) for p in self.prevex] def _init_gradients(self, intg): # Determine what gradients, if any, are required self._gradpnames = gradpnames = set() for k, ex in self.exprs: gradpnames.update(re.findall(r'\bgrad_(.+?)_[xyz]\b', ex)) # If gradients are required then form the relevant operators if gradpnames: self._gradop, self._rcpjact = [], [] for eles in intg.system.ele_map.values(): self._gradop.append(eles.basis.m4) # Get the smats at the solution points smat = eles.smat_at_np('upts').transpose(2, 0, 1, 3) # Get |J|^-1 at the solution points rcpdjac = eles.rcpdjac_at_np('upts') # Product to give J^-T at the solution points self._rcpjact.append(smat * rcpdjac) def _eval_exprs(self, intg): exprs = [] # Get the primitive variable names pnames = self.elementscls.privarmap[self.ndims] # Iterate over each element type in the simulation for i, (soln, ploc) in enumerate(zip(intg.soln, self.plocs)): # Convert from conservative to primitive variables psolns = self.elementscls.con_to_pri(soln.swapaxes(0, 1), self.cfg) # Prepare the substitutions dictionary subs = dict(zip(pnames, psolns)) subs.update(zip('xyz', ploc.swapaxes(0, 1))) # Compute any required gradients if self._gradpnames: # Gradient operator and J^-T matrix gradop, rcpjact = self._gradop[i], self._rcpjact[i] nupts = gradop.shape[1] for pname in self._gradpnames: psoln = subs[pname] # Compute the transformed gradient tgradpn = gradop @ psoln tgradpn = tgradpn.reshape(self.ndims, nupts, -1) # Untransform this to get the physical gradient gradpn = np.einsum('ijkl,jkl->ikl', rcpjact, tgradpn) gradpn = gradpn.reshape(self.ndims, nupts, -1) for dim, grad in zip('xyz', gradpn): subs['grad_{0}_{1}'.format(pname, dim)] = grad # Evaluate the expressions exprs.append([npeval(v, subs) for k, v in self.exprs]) # Stack up the expressions for each element type and return return [np.dstack(exs).swapaxes(1, 2) for exs in exprs] def __call__(self, intg): tdiff = intg.tcurr - self.tout_last dowrite = tdiff >= self.dtout - self.tol doaccum = intg.nacptsteps % self.nsteps == 0 if dowrite or doaccum: # Evaluate the time averaging expressions currex = self._eval_exprs(intg) # Accumulate them; always do this even when just writing for a, p, c in zip(self.accmex, self.prevex, currex): a += 0.5 * (intg.tcurr - self.prevt) * (p + c) # Save the time and solution self.prevt = intg.tcurr self.prevex = currex if dowrite: # Normalise accmex = [a / tdiff for a in self.accmex] stats = Inifile() stats.set('data', 'prefix', 'tavg') stats.set('data', 'fields', ','.join(k for k, v in self.exprs)) stats.set('tavg', 'tstart', self.tout_last) stats.set('tavg', 'tend', intg.tcurr) intg.collect_stats(stats) metadata = dict(intg.cfgmeta, stats=stats.tostr(), mesh_uuid=intg.mesh_uuid) self._writer.write(accmex, metadata, intg.tcurr) self.tout_last = intg.tcurr self.accmex = [np.zeros_like(a) for a in accmex]
class WriterPlugin(PostactionMixin, RegionMixin, BasePlugin): name = 'writer' systems = ['*'] formulations = ['dual', 'std'] def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Base output directory and file name basedir = self.cfg.getpath(self.cfgsect, 'basedir', '.', abs=True) basename = self.cfg.get(self.cfgsect, 'basename') # Construct the solution writer self._writer = NativeWriter(intg, basedir, basename, 'soln') # Output time step and last output time self.dt_out = self.cfg.getfloat(cfgsect, 'dt-out') self.tout_last = intg.tcurr # Output field names self.fields = intg.system.elementscls.convarmap[self.ndims] # Output data type self.fpdtype = intg.backend.fpdtype # Register our output times with the integrator intg.call_plugin_dt(self.dt_out) # If we're not restarting then make sure we write out the initial # solution when we are called for the first time if not intg.isrestart: self.tout_last -= self.dt_out def __call__(self, intg): if intg.tcurr - self.tout_last < self.dt_out - self.tol: return comm, rank, root = get_comm_rank_root() # If we are the root rank then prepare the metadata if rank == root: stats = Inifile() stats.set('data', 'fields', ','.join(self.fields)) stats.set('data', 'prefix', 'soln') intg.collect_stats(stats) metadata = dict(intg.cfgmeta, stats=stats.tostr(), mesh_uuid=intg.mesh_uuid) else: metadata = None # Fetch data from other plugins and add it to metadata with ad-hoc keys for csh in intg.completed_step_handlers: try: prefix = intg.get_plugin_data_prefix(csh.name, csh.suffix) pdata = csh.serialise(intg) except AttributeError: pdata = {} if rank == root: metadata |= {f'{prefix}/{k}': v for k, v in pdata.items()} # Fetch and (if necessary) subset the solution data = dict(self._ele_region_data) for idx, etype, rgn in self._ele_regions: data[etype] = intg.soln[idx][..., rgn].astype(self.fpdtype) # Write out the file solnfname = self._writer.write(data, intg.tcurr, metadata) # If a post-action has been registered then invoke it self._invoke_postaction(intg=intg, mesh=intg.system.mesh.fname, soln=solnfname, t=intg.tcurr) # Update the last output time self.tout_last = intg.tcurr
class TavgPlugin(PostactionMixin, RegionMixin, BasePlugin): name = 'tavg' systems = ['*'] formulations = ['dual', 'std'] def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Underlying elements class self.elementscls = intg.system.elementscls # Averaging mode self.mode = self.cfg.get(cfgsect, 'mode', 'windowed') if self.mode not in {'continuous', 'windowed'}: raise ValueError('Invalid averaging mode') # Expressions pre-processing self._prepare_exprs() # Output data type fpdtype = self.cfg.get(cfgsect, 'precision', 'single') if fpdtype == 'single': self.fpdtype = np.float32 elif fpdtype == 'double': self.fpdtype = np.float64 else: raise ValueError('Invalid floating point data type') # Base output directory and file name basedir = self.cfg.getpath(self.cfgsect, 'basedir', '.', abs=True) basename = self.cfg.get(self.cfgsect, 'basename') # Construct the file writer self._writer = NativeWriter(intg, basedir, basename, 'tavg') # Gradient pre-processing self._init_gradients(intg) # Time averaging parameters self.tstart = self.cfg.getfloat(cfgsect, 'tstart', 0.0) self.dtout = self.cfg.getfloat(cfgsect, 'dt-out') self.nsteps = self.cfg.getint(cfgsect, 'nsteps') # Register our output times with the integrator intg.call_plugin_dt(self.dtout) # Mark ourselves as not currently averaging self._started = False def _prepare_exprs(self): cfg, cfgsect = self.cfg, self.cfgsect c = self.cfg.items_as('constants', float) self.anames, self.aexprs = [], [] self.outfields, self.fexprs = [], [] # Iterate over accumulation expressions first for k in cfg.items(cfgsect): if k.startswith('avg-'): self.anames.append(k[4:]) self.aexprs.append(cfg.getexpr(cfgsect, k, subs=c)) self.outfields.append(k) # Followed by any functional expressions for k in cfg.items(cfgsect): if k.startswith('fun-avg-'): self.fexprs.append(cfg.getexpr(cfgsect, k, subs=c)) self.outfields.append(k) def _init_gradients(self, intg): # Determine what gradients, if any, are required gradpnames = set() for ex in self.aexprs: gradpnames.update(re.findall(r'\bgrad_(.+?)_[xyz]\b', ex)) privarmap = self.elementscls.privarmap[self.ndims] self._gradpinfo = [(pname, privarmap.index(pname)) for pname in gradpnames] def _init_accumex(self, intg): self.prevt = self.tout_last = intg.tcurr self.prevex = self._eval_acc_exprs(intg) self.accex = [np.zeros_like(p, dtype=np.float64) for p in self.prevex] # Extra state for continuous accumulation if self.mode == 'continuous': self.caccex = [np.zeros_like(a) for a in self.accex] self.tstart_actual = intg.tcurr def _eval_acc_exprs(self, intg): exprs = [] # Get the primitive variable names pnames = self.elementscls.privarmap[self.ndims] # Iterate over each element type in the simulation for idx, etype, rgn in self._ele_regions: soln = intg.soln[idx][..., rgn].swapaxes(0, 1) # Convert from conservative to primitive variables psolns = self.elementscls.con_to_pri(soln, self.cfg) # Prepare the substitutions dictionary subs = dict(zip(pnames, psolns)) # Prepare any required gradients if self._gradpinfo: # Compute the gradients grad_soln = np.rollaxis(intg.grad_soln[idx], 2)[..., rgn] # Transform from conservative to primitive gradients pgrads = self.elementscls.grad_con_to_pri(soln, grad_soln, self.cfg) # Add them to the substitutions dictionary for pname, idx in self._gradpinfo: for dim, grad in zip('xyz', pgrads[idx]): subs[f'grad_{pname}_{dim}'] = grad # Evaluate the expressions exprs.append([npeval(v, subs) for v in self.aexprs]) # Stack up the expressions for each element type and return return [np.dstack(exs).swapaxes(1, 2) for exs in exprs] def _eval_fun_exprs(self, intg, accex): exprs = [] # Iterate over each element type our averaging region for avals in accex: # Prepare the substitution dictionary subs = dict(zip(self.anames, avals.swapaxes(0, 1))) exprs.append([npeval(v, subs) for v in self.fexprs]) # Stack up the expressions for each element type and return return [np.dstack(exs).swapaxes(1, 2) for exs in exprs] def __call__(self, intg): # If we are not supposed to be averaging yet then return if intg.tcurr < self.tstart: return # If necessary, run the start-up routines if not self._started: self._init_accumex(intg) self._started = True # See if we are due to write and/or accumulate this step dowrite = intg.tcurr - self.tout_last >= self.dtout - self.tol doaccum = intg.nacptsteps % self.nsteps == 0 if dowrite or doaccum: # Evaluate the time averaging expressions currex = self._eval_acc_exprs(intg) # Accumulate them; always do this even when just writing for a, p, c in zip(self.accex, self.prevex, currex): a += 0.5*(intg.tcurr - self.prevt)*(p + c) # Save the time and solution self.prevt = intg.tcurr self.prevex = currex if dowrite: comm, rank, root = get_comm_rank_root() if self.mode == 'windowed': accex = self.accex tstart = self.tout_last else: for a, c in zip(self.accex, self.caccex): c += a accex = self.caccex tstart = self.tstart_actual # Normalise the accumulated expressions tavg = [a / (intg.tcurr - tstart) for a in accex] # Evaluate any functional expressions if self.fexprs: funex = self._eval_fun_exprs(intg, tavg) tavg = [np.hstack([a, f]) for a, f in zip(tavg, funex)] # Form the output records to be written to disk data = dict(self._ele_region_data) for (idx, etype, rgn), d in zip(self._ele_regions, tavg): data[etype] = d.astype(self.fpdtype) # If we are the root rank then prepare the metadata if rank == root: stats = Inifile() stats.set('data', 'prefix', 'tavg') stats.set('data', 'fields', ','.join(self.outfields)) stats.set('tavg', 'tstart', tstart) stats.set('tavg', 'tend', intg.tcurr) intg.collect_stats(stats) metadata = dict(intg.cfgmeta, stats=stats.tostr(), mesh_uuid=intg.mesh_uuid) else: metadata = None # Write to disk solnfname = self._writer.write(data, intg.tcurr, metadata) # If a post-action has been registered then invoke it self._invoke_postaction(intg=intg, mesh=intg.system.mesh.fname, soln=solnfname, t=intg.tcurr) # Reset the accumulators for a in self.accex: a.fill(0) self.tout_last = intg.tcurr
class TavgPlugin(BasePlugin): name = 'tavg' systems = ['*'] def __init__(self, intg, cfgsect, suffix=None): super().__init__(intg, cfgsect, suffix) # Underlying elements class self.elementscls = intg.system.elementscls # Expressions to time average c = self.cfg.items_as('constants', float) self.exprs = [(k, self.cfg.getexpr(cfgsect, k, subs=c)) for k in self.cfg.items(cfgsect) if k.startswith('avg-')] # Save a reference to the physical solution point locations self.plocs = intg.system.ele_ploc_upts # Output file directory, base name, and writer basedir = self.cfg.getpath(cfgsect, 'basedir', '.') basename = self.cfg.get(cfgsect, 'basename') self._writer = NativeWriter(intg, len(self.exprs), basedir, basename, prefix='tavg') # Time averaging parameters self.dtout = self.cfg.getfloat(cfgsect, 'dt-out') self.nsteps = self.cfg.getint(cfgsect, 'nsteps') self.tout = intg.tcurr + self.dtout # Register our output times with the integrator intg.call_plugin_dt(self.dtout) # Time averaging state self.prevt = intg.tcurr self.prevex = self._eval_exprs(intg) self.accmex = [np.zeros_like(p) for p in self.prevex] def _eval_exprs(self, intg): exprs = [] # Iterate over each element type in the simulation for soln, ploc in zip(intg.soln, self.plocs): # Get the primitive variable names and solutions pnames = self.elementscls.privarmap[self.ndims] psolns = self.elementscls.conv_to_pri(soln.swapaxes(0, 1), self.cfg) # Prepare the substitutions dictionary ploc = dict(zip('xyz', ploc.swapaxes(0, 1))) subs = dict(zip(pnames, psolns), t=intg.tcurr, **ploc) # Evaluate the expressions exprs.append([npeval(v, subs) for k, v in self.exprs]) # Stack up the expressions for each element type and return return [np.dstack(exs).swapaxes(1, 2) for exs in exprs] def __call__(self, intg): dowrite = abs(self.tout - intg.tcurr) < intg.dtmin doaccum = intg.nacptsteps % self.nsteps == 0 if dowrite or doaccum: # Evaluate the time averaging expressions currex = self._eval_exprs(intg) # Accumulate them; always do this even when just writing for a, p, c in zip(self.accmex, self.prevex, currex): a += 0.5*(intg.tcurr - self.prevt)*(p + c) # Save the time and solution self.prevt = intg.tcurr self.prevex = currex if dowrite: # Normalise accmex = [a / self.dtout for a in self.accmex] stats = Inifile() stats.set('data', 'prefix', 'tavg') stats.set('data', 'fields', ','.join(k for k, v in self.exprs)) stats.set('tavg', 'tstart', intg.tcurr - self.dtout) stats.set('tavg', 'tend', intg.tcurr) intg.collect_stats(stats) metadata = dict(config=self.cfg.tostr(), stats=stats.tostr(), mesh_uuid=intg.mesh_uuid) self._writer.write(accmex, metadata, intg.tcurr) self.tout += self.dtout self.accmex = [np.zeros_like(a) for a in accmex]