def div_operator(discr, dd_vol, dd_faces, v, flux): r"""Compute a DG divergence of vector-valued function *v* with flux given by *flux*. Parameters ---------- discr: grudge.eager.EagerDGDiscretization the discretization to use dd_vol: grudge.dof_desc.DOFDesc the degree-of-freedom tag associated with the volume discrezation. This determines the type of quadrature to be used. dd_faces: grudge.dof_desc.DOFDesc the degree-of-freedom tag associated with the surface discrezation. This determines the type of quadrature to be used. v: numpy.ndarray obj array of :class:`~meshmode.dof_array.DOFArray` (or container of such) representing the vector-valued functions for which divergence is to be calculated flux: numpy.ndarray the boundary flux for each function in v Returns ------- meshmode.dof_array.DOFArray or numpy.ndarray the dg divergence operator applied to vector-valued function(s) *v*. """ return -discr.inverse_mass( op.weak_local_div(discr, dd_vol, v) - op.face_mass(discr, dd_faces, flux))
def wave_operator(dcoll, c, w): u = w[0] v = w[1:] dir_u = op.project(dcoll, "vol", BTAG_ALL, u) dir_v = op.project(dcoll, "vol", BTAG_ALL, v) dir_bval = flat_obj_array(dir_u, dir_v) dir_bc = flat_obj_array(-dir_u, dir_v) dd_quad = DOFDesc("vol", DISCR_TAG_QUAD) c_quad = op.project(dcoll, "vol", dd_quad, c) w_quad = op.project(dcoll, "vol", dd_quad, w) u_quad = w_quad[0] v_quad = w_quad[1:] dd_allfaces_quad = DOFDesc("all_faces", DISCR_TAG_QUAD) return (op.inverse_mass( dcoll, flat_obj_array(-op.weak_local_div(dcoll, dd_quad, c_quad * v_quad), -op.weak_local_grad(dcoll, dd_quad, c_quad * u_quad)) + # noqa: W504 op.face_mass( dcoll, dd_allfaces_quad, wave_flux(dcoll, c=c, w_tpair=op.interior_trace_pair(dcoll, w)) + wave_flux(dcoll, c=c, w_tpair=TracePair( BTAG_ALL, interior=dir_bval, exterior=dir_bc)))))
def operator(self, t, u): from grudge.dof_desc import DOFDesc, DD_VOLUME, DTAG_VOLUME_ALL from meshmode.discretization.connection import FACE_RESTR_ALL face_dd = DOFDesc(FACE_RESTR_ALL, self.quad_tag) quad_dd = DOFDesc(DTAG_VOLUME_ALL, self.quad_tag) dcoll = self.dcoll def flux(tpair): return op.project(dcoll, tpair.dd, face_dd, self.flux(tpair)) def to_quad(arg): return op.project(dcoll, DD_VOLUME, quad_dd, arg) quad_v = to_quad(self.v) quad_u = to_quad(u) return (op.inverse_mass( dcoll, sum( op.weak_local_d_dx(dcoll, quad_dd, d, quad_u * quad_v[d]) for d in range(dcoll.ambient_dim)) - op.face_mass( dcoll, face_dd, sum( flux(quad_tpair) for quad_tpair in to_quad_int_tpairs( dcoll, u, self.quad_tag)))))
def grad_operator(discr, dd_vol, dd_faces, u, flux): r"""Compute a DG gradient for the input *u* with flux given by *flux*. Parameters ---------- discr: grudge.eager.EagerDGDiscretization the discretization to use dd_vol: grudge.dof_desc.DOFDesc the degree-of-freedom tag associated with the volume discrezation. This determines the type of quadrature to be used. dd_faces: grudge.dof_desc.DOFDesc the degree-of-freedom tag associated with the surface discrezation. This determines the type of quadrature to be used. u: meshmode.dof_array.DOFArray or numpy.ndarray the function (or container of functions) for which gradient is to be calculated flux: numpy.ndarray the boundary flux across the faces of the element for each component of *u* Returns ------- meshmode.dof_array.DOFArray or numpy.ndarray the dg gradient operator applied to *u* """ return -discr.inverse_mass( op.weak_local_grad(discr, dd_vol, u) - op.face_mass(discr, dd_faces, flux))
def operator(self, t, w): dcoll = self.dcoll u = w[0] v = w[1:] actx = u.array_context # boundary conditions ------------------------------------------------- # dirichlet BCs ------------------------------------------------------- dir_u = op.project(dcoll, "vol", self.dirichlet_tag, u) dir_v = op.project(dcoll, "vol", self.dirichlet_tag, v) if self.dirichlet_bc_f: # FIXME from warnings import warn warn("Inhomogeneous Dirichlet conditions on the wave equation " "are still having issues.") dir_g = self.dirichlet_bc_f dir_bc = flat_obj_array(2 * dir_g - dir_u, dir_v) else: dir_bc = flat_obj_array(-dir_u, dir_v) # neumann BCs --------------------------------------------------------- neu_u = op.project(dcoll, "vol", self.neumann_tag, u) neu_v = op.project(dcoll, "vol", self.neumann_tag, v) neu_bc = flat_obj_array(neu_u, -neu_v) # radiation BCs ------------------------------------------------------- rad_normal = thaw(dcoll.normal(dd=self.radiation_tag), actx) rad_u = op.project(dcoll, "vol", self.radiation_tag, u) rad_v = op.project(dcoll, "vol", self.radiation_tag, v) rad_bc = flat_obj_array( 0.5 * (rad_u - self.sign * np.dot(rad_normal, rad_v)), 0.5 * rad_normal * (np.dot(rad_normal, rad_v) - self.sign * rad_u)) # entire operator ----------------------------------------------------- def flux(tpair): return op.project(dcoll, tpair.dd, "all_faces", self.flux(tpair)) result = (op.inverse_mass( dcoll, flat_obj_array(-self.c * op.weak_local_div(dcoll, v), -self.c * op.weak_local_grad(dcoll, u)) - op.face_mass( dcoll, sum( flux(tpair) for tpair in op.interior_trace_pairs(dcoll, w)) + flux(op.bv_trace_pair(dcoll, self.dirichlet_tag, w, dir_bc)) + flux(op.bv_trace_pair(dcoll, self.neumann_tag, w, neu_bc)) + flux(op.bv_trace_pair(dcoll, self.radiation_tag, w, rad_bc))))) result[0] = result[0] + self.source_f(actx, dcoll, t) return result
def operator(self, t, u): from grudge.dof_desc import DOFDesc, DD_VOLUME, DTAG_VOLUME_ALL from meshmode.mesh import BTAG_ALL from meshmode.discretization.connection import FACE_RESTR_ALL face_dd = DOFDesc(FACE_RESTR_ALL, self.quad_tag) boundary_dd = DOFDesc(BTAG_ALL, self.quad_tag) quad_dd = DOFDesc(DTAG_VOLUME_ALL, self.quad_tag) dcoll = self.dcoll def flux(tpair): return op.project(dcoll, tpair.dd, face_dd, self.flux(tpair)) def to_quad(arg): return op.project(dcoll, DD_VOLUME, quad_dd, arg) if self.inflow_u is not None: inflow_flux = flux( op.bv_trace_pair(dcoll, boundary_dd, interior=u, exterior=self.inflow_u(t))) else: inflow_flux = 0 quad_v = to_quad(self.v) quad_u = to_quad(u) return (op.inverse_mass( dcoll, sum( op.weak_local_d_dx(dcoll, quad_dd, d, quad_u * quad_v[d]) for d in range(dcoll.ambient_dim)) - op.face_mass( dcoll, face_dd, sum( flux(quad_tpair) for quad_tpair in to_quad_int_tpairs( dcoll, u, self.quad_tag)) + inflow_flux # FIXME: Add support for inflow/outflow tags # + flux(op.bv_trace_pair(dcoll, # self.inflow_tag, # interior=u, # exterior=bc_in)) # + flux(op.bv_trace_pair(dcoll, # self.outflow_tag, # interior=u, # exterior=bc_out)) )))
def wave_operator(dcoll, c, w): u = w[0] v = w[1:] dir_u = op.project(dcoll, "vol", BTAG_ALL, u) dir_v = op.project(dcoll, "vol", BTAG_ALL, v) dir_bval = flat_obj_array(dir_u, dir_v) dir_bc = flat_obj_array(-dir_u, dir_v) return (op.inverse_mass( dcoll, flat_obj_array(-c * op.weak_local_div(dcoll, v), -c * op.weak_local_grad(dcoll, u)) + # noqa: W504 op.face_mass( dcoll, wave_flux(dcoll, c=c, w_tpair=op.interior_trace_pair(dcoll, w)) + wave_flux(dcoll, c=c, w_tpair=TracePair( BTAG_ALL, interior=dir_bval, exterior=dir_bc)))))
def wave_operator(dcoll, c, w): u = w[0] v = w[1:] dir_u = op.project(dcoll, "vol", BTAG_ALL, u) dir_v = op.project(dcoll, "vol", BTAG_ALL, v) dir_bval = flat_obj_array(dir_u, dir_v) dir_bc = flat_obj_array(-dir_u, dir_v) dd_quad = DOFDesc("vol", DISCR_TAG_QUAD) c_quad = op.project(dcoll, "vol", dd_quad, c) w_quad = op.project(dcoll, "vol", dd_quad, w) u_quad = w_quad[0] v_quad = w_quad[1:] dd_allfaces_quad = DOFDesc("all_faces", DISCR_TAG_QUAD) return ( op.inverse_mass( dcoll, flat_obj_array( -op.weak_local_div(dcoll, dd_quad, c_quad*v_quad), -op.weak_local_grad(dcoll, dd_quad, c_quad*u_quad) \ # pylint: disable=invalid-unary-operand-type ) + op.face_mass( dcoll, dd_allfaces_quad, wave_flux( dcoll, c=c, w_tpair=op.bdry_trace_pair(dcoll, BTAG_ALL, interior=dir_bval, exterior=dir_bc) ) + sum( wave_flux(dcoll, c=c, w_tpair=tpair) for tpair in op.interior_trace_pairs(dcoll, w) ) ) ) )
def operator(self, t, w): """The full operator template - the high level description of the Maxwell operator. Combines the relevant operator templates for spatial derivatives, flux, boundary conditions etc. """ from grudge.tools import count_subset elec_components = count_subset(self.get_eh_subset()[0:3]) mag_components = count_subset(self.get_eh_subset()[3:6]) if self.fixed_material: # need to check this material_divisor = ([self.epsilon] * elec_components + [self.mu] * mag_components) tags_and_bcs = [ (self.pec_tag, self.pec_bc(w)), (self.pmc_tag, self.pmc_bc(w)), (self.absorb_tag, self.absorbing_bc(w)), (self.incident_tag, self.incident_bc(w)), ] dcoll = self.dcoll def flux(pair): return op.project(dcoll, pair.dd, "all_faces", self.flux(pair)) return (-self.local_derivatives(w) - op.inverse_mass( dcoll, op.face_mass( dcoll, sum( flux(tpair) for tpair in op.interior_trace_pairs(dcoll, w)) + sum( flux(op.bv_trace_pair(dcoll, tag, w, bc)) for tag, bc in tags_and_bcs)))) / material_divisor
def wave_operator(dcoll, c, w): u = w.u v = w.v dir_w = op.project(dcoll, "vol", BTAG_ALL, w) dir_u = dir_w.u dir_v = dir_w.v dir_bval = WaveState(u=dir_u, v=dir_v) dir_bc = WaveState(u=-dir_u, v=dir_v) return (op.inverse_mass( dcoll, WaveState(u=-c * op.weak_local_div(dcoll, v), v=-c * op.weak_local_grad(dcoll, u)) + op.face_mass( dcoll, wave_flux(dcoll, c=c, w_tpair=op.bdry_trace_pair( dcoll, BTAG_ALL, interior=dir_bval, exterior=dir_bc)) + sum( wave_flux(dcoll, c=c, w_tpair=tpair) for tpair in op.interior_trace_pairs(dcoll, w)))))
def operator(self, t, u): from meshmode.mesh import BTAG_ALL dcoll = self.dcoll def flux(tpair): return op.project(dcoll, tpair.dd, "all_faces", self.flux(tpair)) if self.inflow_u is not None: inflow_flux = flux( op.bv_trace_pair(dcoll, BTAG_ALL, interior=u, exterior=self.inflow_u(t))) else: inflow_flux = 0 return (op.inverse_mass( dcoll, np.dot(self.v, op.weak_local_grad(dcoll, u)) - op.face_mass( dcoll, sum( flux(tpair) for tpair in op.interior_trace_pairs(dcoll, u)) + inflow_flux # FIXME: Add support for inflow/outflow tags # + flux(op.bv_trace_pair(dcoll, # self.inflow_tag, # interior=u, # exterior=bc_in)) # + flux(op.bv_trace_pair(dcoll, # self.outflow_tag, # interior=u, # exterior=bc_out)) )))
dd=right_bndry, interior=uh, exterior=op.project(dcoll, "vol", right_bndry, uh)) # extract the trace pairs on the interior faces interior_tpair = op.interior_trace_pair(dcoll, uh) Su = op.weak_local_grad(dcoll, uh) lift = op.face_mass(dcoll, # left boundary weak-flux terms op.project(dcoll, left_bndry, "all_faces", flux(dcoll, lbnd_tpair)) # right boundary weak-flux terms + op.project(dcoll, right_bndry, "all_faces", flux(dcoll, rbnd_tpair)) # interior weak-flux terms + op.project(dcoll, FACE_RESTR_INTERIOR, "all_faces", flux(dcoll, interior_tpair))) duh_by_dt = op.inverse_mass(dcoll, np.dot([2 * np.pi], Su) - lift) # forward euler time step uh = uh + dt * duh_by_dt t += dt # ENDEXAMPLE
def face_mass(self, *args): return op.face_mass(self, *args)
def test_surface_divergence_theorem(actx_factory, mesh_name, visualize=False): r"""Check the surface divergence theorem. .. math:: \int_Sigma \phi \nabla_i f_i = \int_\Sigma \nabla_i \phi f_i + \int_\Sigma \kappa \phi f_i n_i + \int_{\partial \Sigma} \phi f_i m_i where :math:`n_i` is the surface normal and :class:`m_i` is the face normal (which should be orthogonal to both the surface normal and the face tangent). """ actx = actx_factory() # {{{ cases if mesh_name == "2-1-ellipse": from mesh_data import EllipseMeshBuilder builder = EllipseMeshBuilder(radius=3.1, aspect_ratio=2.0) elif mesh_name == "spheroid": from mesh_data import SpheroidMeshBuilder builder = SpheroidMeshBuilder() elif mesh_name == "circle": from mesh_data import EllipseMeshBuilder builder = EllipseMeshBuilder(radius=1.0, aspect_ratio=1.0) elif mesh_name == "starfish": from mesh_data import StarfishMeshBuilder builder = StarfishMeshBuilder() elif mesh_name == "sphere": from mesh_data import SphereMeshBuilder builder = SphereMeshBuilder(radius=1.0, mesh_order=16) else: raise ValueError("unknown mesh name: %s" % mesh_name) # }}} # {{{ convergence def f(x): return flat_obj_array( actx.np.sin(3 * x[1]) + actx.np.cos(3 * x[0]) + 1.0, actx.np.sin(2 * x[0]) + actx.np.cos(x[1]), 3.0 * actx.np.cos(x[0] / 2) + actx.np.cos(x[1]), )[:ambient_dim] from pytools.convergence import EOCRecorder eoc_global = EOCRecorder() eoc_local = EOCRecorder() theta = np.pi / 3.33 ambient_dim = builder.ambient_dim if ambient_dim == 2: mesh_rotation = np.array([ [np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)], ]) else: mesh_rotation = np.array([ [1.0, 0.0, 0.0], [0.0, np.cos(theta), -np.sin(theta)], [0.0, np.sin(theta), np.cos(theta)], ]) mesh_offset = np.array([0.33, -0.21, 0.0])[:ambient_dim] for i, resolution in enumerate(builder.resolutions): from meshmode.mesh.processing import affine_map from meshmode.discretization.connection import FACE_RESTR_ALL mesh = builder.get_mesh(resolution, builder.mesh_order) mesh = affine_map(mesh, A=mesh_rotation, b=mesh_offset) from meshmode.discretization.poly_element import \ QuadratureSimplexGroupFactory qtag = dof_desc.DISCR_TAG_QUAD dcoll = DiscretizationCollection(actx, mesh, order=builder.order, discr_tag_to_group_factory={ qtag: QuadratureSimplexGroupFactory( 2 * builder.order) }) volume = dcoll.discr_from_dd(dof_desc.DD_VOLUME) logger.info("ndofs: %d", volume.ndofs) logger.info("nelements: %d", volume.mesh.nelements) dd = dof_desc.DD_VOLUME dq = dd.with_discr_tag(qtag) df = dof_desc.as_dofdesc(FACE_RESTR_ALL) ambient_dim = dcoll.ambient_dim # variables f_num = f(thaw(dcoll.nodes(dd=dd), actx)) f_quad_num = f(thaw(dcoll.nodes(dd=dq), actx)) from grudge.geometry import normal, summed_curvature kappa = summed_curvature(actx, dcoll, dd=dq) normal = normal(actx, dcoll, dd=dq) face_normal = thaw(dcoll.normal(df), actx) face_f = op.project(dcoll, dd, df, f_num) # operators stiff = op.mass( dcoll, sum( op.local_d_dx(dcoll, i, f_num_i) for i, f_num_i in enumerate(f_num))) stiff_t = sum( op.weak_local_d_dx(dcoll, i, f_num_i) for i, f_num_i in enumerate(f_num)) kterm = op.mass(dcoll, dq, kappa * f_quad_num.dot(normal)) flux = op.face_mass(dcoll, face_f.dot(face_normal)) # sum everything up op_global = op.nodal_sum(dcoll, dd, stiff - (stiff_t + kterm)) op_local = op.elementwise_sum(dcoll, dd, stiff - (stiff_t + kterm + flux)) err_global = abs(op_global) err_local = op.norm(dcoll, op_local, np.inf) logger.info("errors: global %.5e local %.5e", err_global, err_local) # compute max element size from grudge.dt_utils import h_max_from_volume h_max = h_max_from_volume(dcoll) eoc_global.add_data_point(h_max, actx.to_numpy(err_global)) eoc_local.add_data_point(h_max, err_local) if visualize: from grudge.shortcuts import make_visualizer vis = make_visualizer(dcoll) filename = f"surface_divergence_theorem_{mesh_name}_{i:04d}.vtu" vis.write_vtk_file(filename, [("r", actx.np.log10(op_local))], overwrite=True) # }}} order = min(builder.order, builder.mesh_order) - 0.5 logger.info("\n%s", str(eoc_global)) logger.info("\n%s", str(eoc_local)) assert eoc_global.max_error() < 1.0e-12 \ or eoc_global.order_estimate() > order - 0.5 assert eoc_local.max_error() < 1.0e-12 \ or eoc_local.order_estimate() > order - 0.5
def test_gradient(actx_factory, form, dim, order, vectorize, nested, visualize=False): actx = actx_factory() from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for n in [4, 6, 8]: mesh = mgen.generate_regular_rect_mesh(a=(-1, ) * dim, b=(1, ) * dim, nelements_per_axis=(n, ) * dim) dcoll = DiscretizationCollection(actx, mesh, order=order) def f(x): result = dcoll.zeros(actx) + 1 for i in range(dim - 1): result = result * actx.np.sin(np.pi * x[i]) result = result * actx.np.cos(np.pi / 2 * x[dim - 1]) return result def grad_f(x): result = make_obj_array( [dcoll.zeros(actx) + 1 for _ in range(dim)]) for i in range(dim - 1): for j in range(i): result[i] = result[i] * actx.np.sin(np.pi * x[j]) result[i] = result[i] * np.pi * actx.np.cos(np.pi * x[i]) for j in range(i + 1, dim - 1): result[i] = result[i] * actx.np.sin(np.pi * x[j]) result[i] = result[i] * actx.np.cos(np.pi / 2 * x[dim - 1]) for j in range(dim - 1): result[dim - 1] = result[dim - 1] * actx.np.sin(np.pi * x[j]) result[dim - 1] = result[dim - 1] * (-np.pi / 2 * actx.np.sin(np.pi / 2 * x[dim - 1])) return result x = thaw(dcoll.nodes(), actx) if vectorize: u = make_obj_array([(i + 1) * f(x) for i in range(dim)]) else: u = f(x) def get_flux(u_tpair): dd = u_tpair.dd dd_allfaces = dd.with_dtag("all_faces") normal = thaw(dcoll.normal(dd), actx) u_avg = u_tpair.avg if vectorize: if nested: flux = make_obj_array( [u_avg_i * normal for u_avg_i in u_avg]) else: flux = np.outer(u_avg, normal) else: flux = u_avg * normal return op.project(dcoll, dd, dd_allfaces, flux) dd_allfaces = DOFDesc("all_faces") if form == "strong": grad_u = ( op.local_grad(dcoll, u, nested=nested) # No flux terms because u doesn't have inter-el jumps ) elif form == "weak": grad_u = op.inverse_mass( dcoll, -op.weak_local_grad(dcoll, u, nested=nested) # pylint: disable=E1130 + # noqa: W504 op.face_mass( dcoll, dd_allfaces, # Note: no boundary flux terms here because u_ext == u_int == 0 sum( get_flux(utpair) for utpair in op.interior_trace_pairs(dcoll, u)))) else: raise ValueError("Invalid form argument.") if vectorize: expected_grad_u = make_obj_array([(i + 1) * grad_f(x) for i in range(dim)]) if not nested: expected_grad_u = np.stack(expected_grad_u, axis=0) else: expected_grad_u = grad_f(x) if visualize: from grudge.shortcuts import make_visualizer vis = make_visualizer(dcoll, vis_order=order if dim == 3 else dim + 3) filename = ( f"test_gradient_{form}_{dim}_{order}" f"{'_vec' if vectorize else ''}{'_nested' if nested else ''}.vtu" ) vis.write_vtk_file(filename, [ ("u", u), ("grad_u", grad_u), ("expected_grad_u", expected_grad_u), ], overwrite=True) rel_linf_err = actx.to_numpy( op.norm(dcoll, grad_u - expected_grad_u, np.inf) / op.norm(dcoll, expected_grad_u, np.inf)) eoc_rec.add_data_point(1. / n, rel_linf_err) print("L^inf error:") print(eoc_rec) assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1e-11)