def __init__(self, dcoll, c, source_f=None, flux_type="upwind", dirichlet_tag=BTAG_ALL, dirichlet_bc_f=0, neumann_tag=BTAG_NONE, radiation_tag=BTAG_NONE): """ :arg c: a thawed (with *actx*) :class:`~meshmode.dof_array.DOFArray` representing the propogation speed of the wave. """ if source_f is None: source_f = lambda actx, dcoll, t: dcoll.zeros(actx) # noqa: E731 actx = c.array_context self.dcoll = dcoll self.c = freeze(c) self.source_f = source_f ones = dcoll.zeros(actx) + 1 self.sign = freeze(actx.np.where(actx.np.greater(c, 0), ones, -ones)) self.dirichlet_tag = dirichlet_tag self.neumann_tag = neumann_tag self.radiation_tag = radiation_tag self.dirichlet_bc_f = dirichlet_bc_f self.flux_type = flux_type
def face_jacobian(self, where): bdry_discr = self.get_discr(where) ((a, ), (b, )) = parametrization_derivative(self._setup_actx, bdry_discr) return freeze((a**2 + b**2)**0.5)
def _area_elements(): result = actx.np.sqrt(pseudoscalar(actx, dcoll, dd=dd).norm_squared()) if _use_geoderiv_connection: result = dcoll._base_to_geoderiv_connection(dd)(result) return freeze(result, actx)
def normal(self, where): bdry_discr = self.get_discr(where) ((a, ), (b, )) = parametrization_derivative(self._setup_actx, bdry_discr) nrm = 1 / (a**2 + b**2)**0.5 return freeze(flat_obj_array(b * nrm, -a * nrm))
def _compute_characteristic_lengthscales(): return freeze( DOFArray( actx, data=tuple( # Scale each group array of geometric factors by the # corresponding group non-geometric factor cng * geo_facts for cng, geo_facts in zip( dt_non_geometric_factors(dcoll), thaw(dt_geometric_factors(dcoll), actx)))))
def normal(self, dd): r"""Get the unit normal to the specified surface discretization, *dd*. :arg dd: a :class:`~grudge.dof_desc.DOFDesc` as the surface discretization. :returns: an object array of frozen :class:`~meshmode.dof_array.DOFArray`\ s. """ from arraycontext import freeze from grudge.geometry import normal return freeze(normal(self._setup_actx, self, dd))
def inverse_parametrization_derivative(self): [a, b], [c, d] = thaw(self.parametrization_derivative(), self._setup_actx) result = np.zeros((2, 2), dtype=object) det = a * d - b * c result[0, 0] = d / det result[0, 1] = -b / det result[1, 0] = -c / det result[1, 1] = a / det return freeze(result)
def _inv_surf_metric_deriv(): if ambient_dim == dim: imd = inverse_metric_derivative(actx, dcoll, rst_axis, xyz_axis, dd=dd) else: inv_form1 = inverse_first_fundamental_form(actx, dcoll, dd=dd) imd = sum( inv_form1[rst_axis, d] * forward_metric_nth_derivative(actx, dcoll, xyz_axis, d, dd=dd) for d in range(dim)) return freeze(imd, actx)
def flat_centers(self): """Return an object array of (interleaved) center coordinates. ``coord_t [ambient_dim][ncenters]`` """ from pytential import bind, sym actx = self._setup_actx centers = bind( self.places, sym.interleaved_expansion_centers( self.ambient_dim, dofdesc=self.source_dd.to_stage1()))(actx) return freeze(flatten(centers, actx, leaf_class=DOFArray), actx)
def flat_expansion_radii(self): """Return an array of radii associated with the (interleaved) expansion centers. ``coord_t [ncenters]`` """ from pytential import bind, sym actx = self._setup_actx radii = bind( self.places, sym.expansion_radii(self.ambient_dim, granularity=sym.GRANULARITY_CENTER, dofdesc=self.source_dd.to_stage1()))(actx) return freeze(flatten(radii, actx), actx)
def _normal(): dim = dcoll.discr_from_dd(dd).dim ambient_dim = dcoll.ambient_dim if dim == ambient_dim: raise ValueError( "may only request normals on domains whose topological " f"dimension ({dim}) differs from " f"their ambient dimension ({ambient_dim})") if dim == ambient_dim - 1: result = rel_mv_normal(actx, dcoll, dd=dd) else: # NOTE: In the case of (d - 2)-dimensional curves, we don't really have # enough information on the face to decide what an "exterior face normal" # is (e.g the "normal" to a 1D curve in 3D space is actually a # "normal plane") # # The trick done here is that we take the surface normal, move it to the # face and then take a cross product with the face tangent to get the # correct exterior face normal vector. assert dim == ambient_dim - 2 from grudge.op import project volm_normal = MultiVector( project( dcoll, dof_desc.DD_VOLUME, dd, rel_mv_normal( actx, dcoll, dd=dof_desc.DD_VOLUME).as_vector(dtype=object))) pder = pseudoscalar(actx, dcoll, dd=dd) mv = -(volm_normal ^ pder) << volm_normal.I.inv() result = mv / actx.np.sqrt(mv.norm_squared()) if _use_geoderiv_connection: result = dcoll._base_to_geoderiv_connection(dd)(result) return freeze(result, actx)
def map_common_subexpression(self, expr): if expr.scope == sym.cse_scope.EXPRESSION: cache = self.bound_expr._get_cache(EvaluationMapperCSECacheKey) elif expr.scope == sym.cse_scope.DISCRETIZATION: cache = self.places._get_cache(EvaluationMapperCSECacheKey) else: return self.rec(expr.child) from numbers import Number try: rec = cache[expr.child] if (expr.scope == sym.cse_scope.DISCRETIZATION and not isinstance(rec, Number)): rec = thaw(rec, self.array_context) except KeyError: cached_rec = rec = self.rec(expr.child) if (expr.scope == sym.cse_scope.DISCRETIZATION and not isinstance(rec, Number)): cached_rec = freeze(cached_rec, self.array_context) cache[expr.child] = cached_rec return rec
def _inv_surf_metric_deriv(): if times_area_element: multiplier = area_element( actx, dcoll, dd=dd, _use_geoderiv_connection=_use_geoderiv_connection) else: multiplier = 1 mat = actx.np.stack([ actx.np.stack([ multiplier * inverse_surface_metric_derivative( actx, dcoll, rst_axis, xyz_axis, dd=dd, _use_geoderiv_connection=_use_geoderiv_connection) for rst_axis in range(dcoll.dim) ]) for xyz_axis in range(dcoll.ambient_dim) ]) return freeze(mat, actx)
def __call__(self, actx: PyOpenCLArrayContext, source_dd, indices: BlockIndexRanges, **kwargs) -> BlockProxyPoints: """Generate proxy points for each block in *indices* with nodes in the discretization *source_dd*. :arg source_dd: a :class:`~pytential.symbolic.primitives.DOFDescriptor` for the discretization on which the proxy points are to be generated. """ from pytential import sym source_dd = sym.as_dofdesc(source_dd) discr = self.places.get_discretization(source_dd.geometry, source_dd.discr_stage) # {{{ get proxy centers and radii sources = flatten(discr.nodes(), actx, leaf_class=DOFArray) knl = self.get_centers_knl(actx) _, (centers_dev, ) = knl(actx.queue, sources=sources, srcindices=indices.indices, srcranges=indices.ranges) knl = self.get_radii_knl(actx) _, (radii_dev, ) = knl(actx.queue, sources=sources, srcindices=indices.indices, srcranges=indices.ranges, radius_factor=self.radius_factor, proxy_centers=centers_dev, **kwargs) # }}} # {{{ build proxy points for each block from arraycontext import to_numpy centers = np.vstack(to_numpy(centers_dev, actx)) radii = to_numpy(radii_dev, actx) nproxy = self.nproxy * indices.nblocks proxies = np.empty((self.ambient_dim, nproxy), dtype=centers.dtype) pxy_nr_base = 0 for i in range(indices.nblocks): points = radii[i] * self.ref_points + centers[:, i].reshape(-1, 1) proxies[:, pxy_nr_base:pxy_nr_base + self.nproxy] = points pxy_nr_base += self.nproxy # }}} pxyindices = np.arange(0, nproxy, dtype=indices.indices.dtype) pxyranges = np.arange(0, nproxy + 1, self.nproxy) from arraycontext import freeze, from_numpy from pytential.linalg import make_block_index_from_array return BlockProxyPoints( lpot_source=self.places.get_geometry(source_dd.geometry), srcindices=indices, indices=make_block_index_from_array(pxyindices, pxyranges), points=freeze(from_numpy(proxies, actx), actx), centers=freeze(centers_dev, actx), radii=freeze(radii_dev, actx), )
def dt_geometric_factors(dcoll: DiscretizationCollection, dd=None) -> DOFArray: r"""Computes a geometric scaling factor for each cell following [Hesthaven_2008]_, section 6.4, defined as the inradius (radius of an inscribed circle/sphere). Specifically, the inradius for each element is computed using the following formula from [Shewchuk_2002]_, Table 1, for simplicial cells (triangles/tetrahedra): .. math:: r_D = \frac{d V}{\sum_{i=1}^{N_{faces}} F_i}, where :math:`d` is the topological dimension, :math:`V` is the cell volume, and :math:`F_i` are the areas of each face of the cell. :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. Defaults to the base volume discretization if not provided. :returns: a frozen :class:`~meshmode.dof_array.DOFArray` containing the geometric factors for each cell and at each nodal location. """ from meshmode.discretization.poly_element import SimplexElementGroupBase if dd is None: dd = DD_VOLUME actx = dcoll._setup_actx volm_discr = dcoll.discr_from_dd(dd) if any(not isinstance(grp, SimplexElementGroupBase) for grp in volm_discr.groups): raise NotImplementedError( "Geometric factors are only implemented for simplex element groups" ) if volm_discr.dim != volm_discr.ambient_dim: from warnings import warn warn( "The geometric factor for the characteristic length scale in " "time step estimation is not necessarily valid for non-volume-" "filling discretizations. Continuing anyway.", stacklevel=3) cell_vols = abs( op.elementwise_integral(dcoll, dd, volm_discr.zeros(actx) + 1.0)) if dcoll.dim == 1: return freeze(cell_vols) dd_face = DOFDesc("all_faces", dd.discretization_tag) face_discr = dcoll.discr_from_dd(dd_face) # To get a single value for the total surface area of a cell, we # take the sum over the averaged face areas on each face. # NOTE: The face areas are the *same* at each face nodal location. # This assumes there are the *same* number of face nodes on each face. surface_areas = abs( op.elementwise_integral(dcoll, dd_face, face_discr.zeros(actx) + 1.0)) surface_areas = DOFArray( actx, data=tuple( actx.einsum("fej->e", face_ae_i.reshape(vgrp.mesh_el_group.nfaces, vgrp.nelements, afgrp.nunit_dofs), tagged=(FirstAxisIsElementsTag(), )) / afgrp.nunit_dofs for vgrp, afgrp, face_ae_i in zip( volm_discr.groups, face_discr.groups, surface_areas))) return freeze( DOFArray(actx, data=tuple( actx.einsum("e,ei->ei", 1 / sae_i, cv_i, tagged=(FirstAxisIsElementsTag(), )) * dcoll.dim for cv_i, sae_i in zip(cell_vols, surface_areas))))
def _force_evaluation(actx, state): if actx is None: return state return thaw(freeze(state, actx), actx)
def _advance_state_stepper_func(rhs, timestepper, state, t_final, dt=0, t=0.0, istep=0, get_timestep=None, pre_step_callback=None, post_step_callback=None, logmgr=None, eos=None, dim=None, actx=None): """Advance state from some time (t) to some time (t_final). Parameters ---------- rhs Function that should return the time derivative of the state. This function should take time and state as arguments, with a call with signature ``rhs(t, state)``. timestepper Function that advances the state from t=time to t=(time+dt), and returns the advanced state. Has a call with signature ``timestepper(state, t, dt, rhs)``. get_timestep Function that should return dt for the next step. This interface allows user-defined adaptive timestepping. A negative return value indicated that the stepper should stop gracefully. Takes only state as an argument. state: numpy.ndarray Agglomerated object array containing at least the state variables that will be advanced by this stepper t_final: float Simulated time at which to stop t: float Time at which to start dt: float Initial timestep size to use, optional if dt is adaptive istep: int Step number from which to start pre_step_callback An optional user-defined function, with signature: ``state, dt = pre_step_callback(step, t, dt, state)``, to be called before the timestepper is called for that particular step. post_step_callback An optional user-defined function, with signature: ``state, dt = post_step_callback(step, t, dt, state)``, to be called after the timestepper is called for that particular step. Returns ------- istep: int the current step number t: float the current time state: numpy.ndarray """ t = np.float64(t) if t_final <= t: return istep, t, state if actx is None: actx = state.array_context compiled_rhs = compile_rhs(actx, rhs, state) while t < t_final: state = thaw(freeze(state, actx), actx) if logmgr: logmgr.tick_before() if pre_step_callback is not None: state, dt = pre_step_callback(state=state, step=istep, t=t, dt=dt) state = timestepper(state=state, t=t, dt=dt, rhs=compiled_rhs) t += dt istep += 1 if post_step_callback is not None: state, dt = post_step_callback(state=state, step=istep, t=t, dt=dt) if logmgr: set_dt(logmgr, dt) set_sim_state(logmgr, dim, state, eos) logmgr.tick_after() return istep, t, state
def parametrization_derivative(self): return freeze( parametrization_derivative(self._setup_actx, self.volume_discr))
def _flat_expansion_radii(dofdesc): radii = bind( bound_expr.places, sym.expansion_radii(self.ambient_dim, dofdesc=dofdesc), )(actx) return freeze(flatten(radii, actx), actx)
def _area_elements(): return freeze( actx.np.sqrt(pseudoscalar(actx, dcoll, dd=dd).norm_squared()), actx)
def main(snapshot_pattern="wave-mpi-{step:04d}-{rank:04d}.pkl", restart_step=None, use_profiling=False, use_logmgr=False, actx_class=PyOpenCLArrayContext): """Drive the example.""" cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) from mpi4py import MPI comm = MPI.COMM_WORLD rank = comm.Get_rank() num_parts = comm.Get_size() logmgr = initialize_logmgr(use_logmgr, filename="wave-mpi.sqlite", mode="wu", mpi_comm=comm) if use_profiling: queue = cl.CommandQueue(cl_ctx, properties=cl.command_queue_properties.PROFILING_ENABLE) actx = actx_class(queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)), logmgr=logmgr) else: queue = cl.CommandQueue(cl_ctx) actx = actx_class(queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue))) if restart_step is None: from meshmode.distributed import MPIMeshDistributor, get_partition_by_pymetis mesh_dist = MPIMeshDistributor(comm) dim = 2 nel_1d = 16 if mesh_dist.is_mananger_rank(): from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh( a=(-0.5,)*dim, b=(0.5,)*dim, nelements_per_axis=(nel_1d,)*dim) print("%d elements" % mesh.nelements) part_per_element = get_partition_by_pymetis(mesh, num_parts) local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) del mesh else: local_mesh = mesh_dist.receive_mesh_part() fields = None else: from mirgecom.restart import read_restart_data restart_data = read_restart_data( actx, snapshot_pattern.format(step=restart_step, rank=rank) ) local_mesh = restart_data["local_mesh"] nel_1d = restart_data["nel_1d"] assert comm.Get_size() == restart_data["num_parts"] order = 3 discr = EagerDGDiscretization(actx, local_mesh, order=order, mpi_communicator=comm) current_cfl = 0.485 wave_speed = 1.0 from grudge.dt_utils import characteristic_lengthscales dt = current_cfl * characteristic_lengthscales(actx, discr) / wave_speed from grudge.op import nodal_min dt = nodal_min(discr, "vol", dt) t_final = 1 if restart_step is None: t = 0 istep = 0 fields = flat_obj_array( bump(actx, discr), [discr.zeros(actx) for i in range(discr.dim)] ) else: t = restart_data["t"] istep = restart_step assert istep == restart_step restart_fields = restart_data["fields"] old_order = restart_data["order"] if old_order != order: old_discr = EagerDGDiscretization(actx, local_mesh, order=old_order, mpi_communicator=comm) from meshmode.discretization.connection import make_same_mesh_connection connection = make_same_mesh_connection(actx, discr.discr_from_dd("vol"), old_discr.discr_from_dd("vol")) fields = connection(restart_fields) else: fields = restart_fields if logmgr: logmgr_add_cl_device_info(logmgr, queue) logmgr_add_device_memory_usage(logmgr, queue) logmgr.add_watches(["step.max", "t_step.max", "t_log.max"]) try: logmgr.add_watches(["memory_usage_python.max", "memory_usage_gpu.max"]) except KeyError: pass if use_profiling: logmgr.add_watches(["multiply_time.max"]) vis_timer = IntervalTimer("t_vis", "Time spent visualizing") logmgr.add_quantity(vis_timer) vis = make_visualizer(discr) def rhs(t, w): return wave_operator(discr, c=wave_speed, w=w) compiled_rhs = actx.compile(rhs) while t < t_final: if logmgr: logmgr.tick_before() # restart must happen at beginning of step if istep % 100 == 0 and ( # Do not overwrite the restart file that we just read. istep != restart_step): from mirgecom.restart import write_restart_file write_restart_file( actx, restart_data={ "local_mesh": local_mesh, "order": order, "fields": fields, "t": t, "step": istep, "nel_1d": nel_1d, "num_parts": num_parts}, filename=snapshot_pattern.format(step=istep, rank=rank), comm=comm ) if istep % 10 == 0: print(istep, t, discr.norm(fields[0])) vis.write_parallel_vtk_file( comm, "fld-wave-mpi-%03d-%04d.vtu" % (rank, istep), [ ("u", fields[0]), ("v", fields[1:]), ], overwrite=True ) fields = thaw(freeze(fields, actx), actx) fields = rk4_step(fields, t, dt, compiled_rhs) t += dt istep += 1 if logmgr: set_dt(logmgr, dt) logmgr.tick_after() final_soln = discr.norm(fields[0]) assert np.abs(final_soln - 0.04409852463947439) < 1e-14
def main(ctx_factory, dim=2, order=3, visualize=False, lazy=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) if lazy: actx = PytatoPyOpenCLArrayContext(queue) else: actx = PyOpenCLArrayContext( queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)), force_device_scalars=True, ) comm = MPI.COMM_WORLD num_parts = comm.Get_size() from meshmode.distributed import MPIMeshDistributor, get_partition_by_pymetis mesh_dist = MPIMeshDistributor(comm) nel_1d = 16 if mesh_dist.is_mananger_rank(): from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, nelements_per_axis=(nel_1d, ) * dim) logger.info("%d elements", mesh.nelements) part_per_element = get_partition_by_pymetis(mesh, num_parts) local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) del mesh else: local_mesh = mesh_dist.receive_mesh_part() dcoll = DiscretizationCollection(actx, local_mesh, order=order, mpi_communicator=comm) fields = WaveState(u=bump(actx, dcoll), v=make_obj_array( [dcoll.zeros(actx) for i in range(dcoll.dim)])) c = 1 dt = actx.to_numpy(0.45 * estimate_rk4_timestep(actx, dcoll, c)) vis = make_visualizer(dcoll) def rhs(t, w): return wave_operator(dcoll, c=c, w=w) compiled_rhs = actx.compile(rhs) if comm.rank == 0: logger.info("dt = %g", dt) import time start = time.time() t = 0 t_final = 3 istep = 0 while t < t_final: if lazy: fields = thaw(freeze(fields, actx), actx) fields = rk4_step(fields, t, dt, compiled_rhs) l2norm = actx.to_numpy(op.norm(dcoll, fields.u, 2)) if istep % 10 == 0: stop = time.time() linfnorm = actx.to_numpy(op.norm(dcoll, fields.u, np.inf)) nodalmax = actx.to_numpy(op.nodal_max(dcoll, "vol", fields.u)) nodalmin = actx.to_numpy(op.nodal_min(dcoll, "vol", fields.u)) if comm.rank == 0: logger.info(f"step: {istep} t: {t} " f"L2: {l2norm} " f"Linf: {linfnorm} " f"sol max: {nodalmax} " f"sol min: {nodalmin} " f"wall: {stop-start} ") if visualize: vis.write_parallel_vtk_file( comm, f"fld-wave-eager-mpi-{{rank:03d}}-{istep:04d}.vtu", [ ("u", fields.u), ("v", fields.v), ]) start = stop t += dt istep += 1 # NOTE: These are here to ensure the solution is bounded for the # time interval specified assert l2norm < 1
def main(use_profiling=False, use_logmgr=False, lazy_eval: bool = False): """Drive the example.""" cl_ctx = cl.create_some_context() logmgr = initialize_logmgr(use_logmgr, filename="wave.sqlite", mode="wu") if use_profiling: if lazy_eval: raise RuntimeError("Cannot run lazy with profiling.") queue = cl.CommandQueue( cl_ctx, properties=cl.command_queue_properties.PROFILING_ENABLE) actx = PyOpenCLProfilingArrayContext( queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue))) else: queue = cl.CommandQueue(cl_ctx) if lazy_eval: actx = PytatoPyOpenCLArrayContext(queue) else: actx = PyOpenCLArrayContext( queue, allocator=cl_tools.MemoryPool( cl_tools.ImmediateAllocator(queue))) dim = 2 nel_1d = 16 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, nelements_per_axis=(nel_1d, ) * dim) order = 3 discr = EagerDGDiscretization(actx, mesh, order=order) current_cfl = 0.485 wave_speed = 1.0 from grudge.dt_utils import characteristic_lengthscales nodal_dt = characteristic_lengthscales(actx, discr) / wave_speed from grudge.op import nodal_min dt = actx.to_numpy(current_cfl * nodal_min(discr, "vol", nodal_dt))[()] print("%d elements" % mesh.nelements) fields = flat_obj_array(bump(actx, discr), [discr.zeros(actx) for i in range(discr.dim)]) if logmgr: logmgr_add_cl_device_info(logmgr, queue) logmgr_add_device_memory_usage(logmgr, queue) logmgr.add_watches(["step.max", "t_step.max", "t_log.max"]) try: logmgr.add_watches( ["memory_usage_python.max", "memory_usage_gpu.max"]) except KeyError: pass if use_profiling: logmgr.add_watches(["multiply_time.max"]) vis_timer = IntervalTimer("t_vis", "Time spent visualizing") logmgr.add_quantity(vis_timer) vis = make_visualizer(discr) def rhs(t, w): return wave_operator(discr, c=wave_speed, w=w) compiled_rhs = actx.compile(rhs) t = 0 t_final = 1 istep = 0 while t < t_final: if logmgr: logmgr.tick_before() fields = thaw(freeze(fields, actx), actx) fields = rk4_step(fields, t, dt, compiled_rhs) if istep % 10 == 0: if use_profiling: print(actx.tabulate_profiling_data()) print(istep, t, actx.to_numpy(discr.norm(fields[0], np.inf))) vis.write_vtk_file("fld-wave-%04d.vtu" % istep, [ ("u", fields[0]), ("v", fields[1:]), ], overwrite=True) t += dt istep += 1 if logmgr: set_dt(logmgr, dt) logmgr.tick_after()
def _flat_nodes(dofdesc): discr = bound_expr.places.get_discretization( dofdesc.geometry, dofdesc.discr_stage) return freeze(flatten(discr.nodes(), actx, leaf_class=DOFArray), actx)
def _advance_state_leap(rhs, timestepper, state, t_final, dt=0, component_id="state", t=0.0, istep=0, get_timestep=None, pre_step_callback=None, post_step_callback=None, logmgr=None, eos=None, dim=None, actx=None): """Advance state from some time *t* to some time *t_final* using :mod:`leap`. Parameters ---------- rhs Function that should return the time derivative of the state. This function should take time and state as arguments, with a call with signature ``rhs(t, state)``. timestepper An instance of :class:`leap.MethodBuilder`. get_timestep Function that should return dt for the next step. This interface allows user-defined adaptive timestepping. A negative return value indicated that the stepper should stop gracefully. state: numpy.ndarray Agglomerated object array containing at least the state variables that will be advanced by this stepper t_final: float Simulated time at which to stop component_id State id (required input for leap method generation) t: float Time at which to start dt: float Initial timestep size to use, optional if dt is adaptive istep: int Step number from which to start pre_step_callback An optional user-defined function, with signature: ``state, dt = pre_step_callback(step, t, dt, state)``, to be called before the timestepper is called for that particular step. post_step_callback An optional user-defined function, with signature: ``state, dt = post_step_callback(step, t, dt, state)``, to be called after the timestepper is called for that particular step. Returns ------- istep: int the current step number t: float the current time state: numpy.ndarray """ if t_final <= t: return istep, t, state if actx is None: actx = state.array_context compiled_rhs = compile_rhs(actx, rhs, state) stepper_cls = generate_singlerate_leap_advancer(timestepper, component_id, compiled_rhs, t, dt, state) while t < t_final: # This is only needed because Leap testing in test/test_time_integrators.py # tests on single scalar values rather than an array-context-ready array # container like a CV. if isinstance(state, np.ndarray): state = thaw(freeze(state, actx), actx) if pre_step_callback is not None: state, dt = pre_step_callback(state=state, step=istep, t=t, dt=dt) stepper_cls.dt = dt # Leap interface here is *a bit* different. for event in stepper_cls.run(t_end=t + dt): if isinstance(event, stepper_cls.StateComputed): state = event.state_component t += dt if post_step_callback is not None: state, dt = post_step_callback(state=state, step=istep, t=t, dt=dt) stepper_cls.dt = dt istep += 1 return istep, t, state
def _flat_centers(dofdesc, qbx_forced_limit): centers = bind(bound_expr.places, sym.expansion_centers( self.ambient_dim, qbx_forced_limit, dofdesc=dofdesc), )(actx) return freeze(flatten(centers, actx, leaf_class=DOFArray), actx)
def vol_jacobian(self): [a, b], [c, d] = thaw(self.parametrization_derivative(), self._setup_actx) return freeze(a * d - b * c)