def L2_projection(mesh, iMM, basis, quad_pts, quad_wts, f, U): ''' Performs an L2 projection Inputs: ------- mesh: mesh object iMM: space-time inverse mass matrix basis: basis object quad_pts: quadrature coordinates in reference space quad_wts: quadrature weights f: array of values to be projected from Outputs: -------- U: array of values to be projected to ''' if basis.basis_val.shape[0] != quad_wts.shape[0]: basis.get_basis_val_grads(quad_pts, get_val=True) for elem_ID in range(U.shape[0]): djac, _, _ = basis_tools.element_jacobian(mesh, elem_ID, quad_pts, get_djac=True) rhs = np.matmul(basis.basis_val.transpose(), f[elem_ID, :, :] * quad_wts * djac) # [nb, ns] U[elem_ID, :, :] = np.matmul(iMM[elem_ID], rhs)
def get_geom_data(self, mesh, basis, order): ''' Precomputes the geometric data for the ADER-DG scheme Inputs: ------- mesh: mesh object basis: basis object order: solution order Outputs: -------- self.jac_elems: precomputed Jacobian for each element [num_elems, nb, ndims, ndims] self.ijac_elems: precomputed inverse Jacobian for each element [num_elems, nb, ndims, ndims] self.djac_elems: precomputed determinant of the Jacobian for each element [num_elems, nb, 1] self.x_elems: precomputed coordinates of the nodal points in physical space [num_elems, nb, ndims] ''' ndims = mesh.ndims num_elems = mesh.num_elems nb = basis.nb gbasis = mesh.gbasis tile_basis = basis_defs.LagrangeSeg(order) # Define geometric basis for tiling jac, ijac, and djac xnodes = gbasis.get_nodes(order) nnodes = xnodes.shape[0] tile_xnodes = tile_basis.get_nodes(order) tile_nnodes = tile_xnodes.shape[0] # Allocate self.jac_elems = np.zeros([num_elems, nb, ndims, ndims]) self.ijac_elems = np.zeros([num_elems, nb, ndims, ndims]) self.djac_elems = np.zeros([num_elems, nb, 1]) self.x_elems = np.zeros([num_elems, nb, ndims]) for elem_ID in range(mesh.num_elems): # Jacobian djac, jac, ijac = basis_tools.element_jacobian(mesh, elem_ID, xnodes, get_djac=True, get_jac=True, get_ijac=True) self.jac_elems[elem_ID] = np.tile(jac, (tile_nnodes, 1, 1)) self.ijac_elems[elem_ID] = np.tile(ijac, (tile_nnodes, 1, 1)) self.djac_elems[elem_ID] = np.tile(djac, (tile_nnodes, 1)) # Physical coordinates of nodal points x = mesh_tools.ref_to_phys(mesh, elem_ID, xnodes) # Store self.x_elems[elem_ID] = np.tile(x, (tile_nnodes, 1))
def element_volumes(mesh, solver=None): ''' This function calculates total and per-element volumes Inputs: ------- mesh: mesh object solver: solver object (e.g., DG, ADER-DG, etc.) Outputs: -------- vol_elems: volume of each element [num_elems] domain_vol: total volume of the domain ''' # Check if already calculated if solver is not None: if hasattr(solver.elem_helpers, "domain_vol") \ and hasattr(solver.elem_helpers, "vol_elems"): return solver.elem_helpers.vol_elems, \ solver.elem_helpers.domain_vol # Allocate, unpack vol_elems = np.zeros(mesh.num_elems) gorder = mesh.gorder gbasis = mesh.gbasis # Get quadrature data quad_order = gbasis.get_quadrature_order(mesh, gorder) quad_pts, quad_wts = gbasis.get_quadrature_data(quad_order) # Get element volumes for elem_ID in range(mesh.num_elems): djac, _, _ = basis_tools.element_jacobian(mesh, elem_ID, quad_pts, get_djac=True) vol_elems[elem_ID] = np.sum(quad_wts * djac) # Get domain volume domain_vol = np.sum(vol_elems) return vol_elems, domain_vol # [num_elems], [1]
def get_basis_and_geom_data(self, mesh, basis, order): ''' Precomputes the basis and geometric data for each boundary face Inputs: ------- mesh: mesh object basis: basis object order: solution order Outputs: -------- self.faces_to_basis: basis values evaluated at quadrature points of each face [nfaces_per_elem, nq, nb] self.faces_to_basis_ref_grad: gradient of basis values evaluated at quadrature points of each face for interior element [nfaces_per_elem, nq, nb, ndims] self.faces_to_xref: coordinates of quadrature points of each face converted to element reference space [nfaces_per_elem, nq, ndims] self.normals_bgroups: precomputed normal vectors at each boundary face [num_boundary_faces, nq, ndims] self.x_bgroups: precomputed physical coordinates of the quadrature points [num_boundary_faces, nq, ndims] self.ijac_bgroups: stores the evaluated inverse of the geometric Jacobian for each interior element self.face_lengths_bgroups: stores the precomputed length of each face [num_interior_faces, 1] ''' ndims = mesh.ndims quad_pts = self.quad_pts quad_wts = self.quad_wts nq = quad_pts.shape[0] nb = basis.nb nfaces_per_elem = basis.NFACES # Allocate self.faces_to_basis = np.zeros([nfaces_per_elem, nq, nb]) self.faces_to_xref = np.zeros([nfaces_per_elem, nq, basis.NDIMS]) self.faces_to_basis_ref_grad = np.zeros( [nfaces_per_elem, nq, nb, basis.NDIMS]) # Get values on each face (from interior perspective) for face_ID in range(nfaces_per_elem): self.faces_to_xref[face_ID] = basis.get_elem_ref_from_face_ref( face_ID, quad_pts) basis.get_basis_face_val_grads(mesh, face_ID, quad_pts, get_val=True, get_ref_grad=True) self.faces_to_basis[face_ID] = basis.basis_val self.faces_to_basis_ref_grad[face_ID] = basis.basis_ref_grad # Get boundary information i = 0 for bgroup in mesh.boundary_groups.values(): self.normals_bgroups.append( np.zeros([bgroup.num_boundary_faces, nq, ndims])) self.x_bgroups.append( np.zeros([bgroup.num_boundary_faces, nq, ndims])) self.ijac_bgroups.append( np.zeros([bgroup.num_boundary_faces, nq, ndims, ndims])) self.face_lengths_bgroups.append( np.zeros([bgroup.num_boundary_faces, 1])) normal_bgroup = self.normals_bgroups[i] x_bgroup = self.x_bgroups[i] ijac_bgroup = self.ijac_bgroups[i] face_lengths_bgroup = self.face_lengths_bgroups[i] j = 0 for boundary_face in bgroup.boundary_faces: # Normals normals = mesh.gbasis.calculate_normals( mesh, boundary_face.elem_ID, boundary_face.face_ID, quad_pts) elem_pts = basis.get_elem_ref_from_face_ref( boundary_face.face_ID, quad_pts) djac, jac, ijac = basis_tools.element_jacobian( mesh, boundary_face.elem_ID, elem_pts, get_djac=True, get_jac=True, get_ijac=True) normal_bgroup[j] = normals ijac_bgroup[j] = ijac djac_faces = np.linalg.norm(normals, axis=1) face_lengths_bgroup[j] = mesh_tools.get_face_lengths( djac_faces.reshape([1, nq]), quad_wts) # Physical coordinates of quadrature points x = mesh_tools.ref_to_phys( mesh, boundary_face.elem_ID, self.faces_to_xref[boundary_face.face_ID]) # Store x_bgroup[j] = x # Increment j += 1 i += 1
def get_basis_and_geom_data(self, mesh, basis, order): ''' Precomputes the basis and geometric data for each interior face Inputs: ------- mesh: mesh object basis: basis object order: solution order Outputs: -------- self.faces_to_basisL: basis values evaluated at quadrature points of each face for left element [nfaces_per_elem, nq, nb] self.faces_to_basisR: basis values evaluated at quadrature points of each face for right element [nfaces_per_elem, nq, nb] self.faces_to_basis_ref_gradL: gradient of basis values evaluated at quadrature points of each face for left element [nfaces_per_elem, nq, nb, ndims] self.faces_to_basis_ref_gradR: gradient of basis values evaluated at quadrature points of each face for right element [nfaces_per_elem, nq, nb, ndims] self.normals_int_faces: precomputed normal vectors at each interior face [num_interior_faces, nq, ndims] self.ijacL_elems: stores the evaluated inverse of the geometric Jacobian for each left element [num_interior_faces, nq, ndims, ndims] self.ijacR_elems: stores the evaluated inverse of the geometric Jacobian for each right element [num_interior_faces, nq, ndims, ndims] self.face_lengths: stores the precomputed length of each face [num_interior_faces, 1] Note(s): -------- We separate ndims_basis and ndims to allow for basis and mesh to have different number of dimensions (ex: when using a space-time basis function and only a spatial mesh) ''' ndims_basis = basis.NDIMS ndims = mesh.ndims # unpack quad_pts = self.quad_pts quad_wts = self.quad_wts nq = quad_pts.shape[0] nb = basis.nb nfaces_per_elem = basis.NFACES nfaces = mesh.num_interior_faces # Allocate self.faces_to_basisL = np.zeros([nfaces_per_elem, nq, nb]) self.faces_to_basisR = np.zeros([nfaces_per_elem, nq, nb]) self.faces_to_basis_ref_gradL = np.zeros( [nfaces_per_elem, nq, nb, ndims_basis]) self.faces_to_basis_ref_gradR = np.zeros( [nfaces_per_elem, nq, nb, ndims_basis]) self.ijacL_elems = np.zeros([nfaces, nq, ndims, ndims]) self.ijacR_elems = np.zeros([nfaces, nq, ndims, ndims]) self.normals_int_faces = np.zeros([mesh.num_interior_faces, nq, ndims]) djac_faces = np.zeros([mesh.num_interior_faces, nq]) # Get values on each face (from both left and right perspectives) # for both the basis and the reference gradient of the basis for face_ID in range(nfaces_per_elem): # Left basis.get_basis_face_val_grads(mesh, face_ID, quad_pts, get_val=True, get_ref_grad=True) self.faces_to_basisL[face_ID] = basis.basis_val self.faces_to_basis_ref_gradL[face_ID] = basis.basis_ref_grad # Right basis.get_basis_face_val_grads(mesh, face_ID, quad_pts[::-1], get_val=True, get_ref_grad=True) self.faces_to_basisR[face_ID] = basis.basis_val self.faces_to_basis_ref_gradR[face_ID] = basis.basis_ref_grad # Normals i = 0 for interior_face in mesh.interior_faces: normals = mesh.gbasis.calculate_normals(mesh, interior_face.elemL_ID, interior_face.faceL_ID, quad_pts) self.normals_int_faces[i] = normals # Left state # Convert from face ref space to element ref space elem_pts = basis.get_elem_ref_from_face_ref( interior_face.faceL_ID, quad_pts) _, _, ijacL = basis_tools.element_jacobian(mesh, interior_face.elemL_ID, elem_pts, get_djac=False, get_jac=False, get_ijac=True) # Right state # Convert from face ref space to element ref space elem_pts = basis.get_elem_ref_from_face_ref( interior_face.faceR_ID, quad_pts[::-1]) _, _, ijacR = basis_tools.element_jacobian(mesh, interior_face.elemR_ID, elem_pts, get_djac=False, get_jac=False, get_ijac=True) # Store self.ijacL_elems[i] = ijacL self.ijacR_elems[i] = ijacR # Used for face_length calculations djac_faces[i] = np.linalg.norm(normals, axis=1) i += 1 self.face_lengths = mesh_tools.get_face_lengths(djac_faces, quad_wts)
def get_basis_and_geom_data(self, mesh, basis, order): ''' Precomputes the basis and geometric data for each element Inputs: ------- mesh: mesh object basis: basis object order: solution order Outputs: -------- self.basis_val: precomputed basis value [nq, nb] self.basis_ref_grad: precomputed basis gradient for the reference element [nq, nb, ndims] self.basis_phys_grad_elems: precomputed basis gradient for each physical element [num_elems, nq, nb, ndims] self.jac_elems: precomputed Jacobian for each element [num_elems, nq, ndims, ndims] self.ijac_elems: precomputed inverse Jacobian for each element [num_elems, nq, ndims, ndims] self.djac_elems: precomputed determinant of the Jacobian for each element [num_elems, nq, 1] self.x_elems: precomputed coordinates of the quadrature points in physical space [num_elems, nq, ndims] ''' ndims = mesh.ndims num_elems = mesh.num_elems quad_pts = self.quad_pts nq = quad_pts.shape[0] nb = basis.nb # Allocate self.jac_elems = np.zeros([num_elems, nq, ndims, ndims]) self.ijac_elems = np.zeros([num_elems, nq, ndims, ndims]) self.djac_elems = np.zeros([num_elems, nq, 1]) self.x_elems = np.zeros([num_elems, nq, ndims]) self.basis_phys_grad_elems = np.zeros([num_elems, nq, nb, basis.NDIMS]) self.normals_elems = np.empty([ num_elems, mesh.gbasis.NFACES, self.face_quad_pts.shape[0], ndims ]) # Basis data basis.get_basis_val_grads(self.quad_pts, get_val=True, get_ref_grad=True) self.basis_val = basis.basis_val self.basis_ref_grad = basis.basis_ref_grad for elem_ID in range(mesh.num_elems): # Jacobian djac, jac, ijac = basis_tools.element_jacobian(mesh, elem_ID, quad_pts, get_djac=True, get_jac=True, get_ijac=True) # Store self.jac_elems[elem_ID] = jac self.ijac_elems[elem_ID] = ijac self.djac_elems[elem_ID] = djac # Physical coordinates of quadrature points x = mesh_tools.ref_to_phys(mesh, elem_ID, quad_pts) # Store self.x_elems[elem_ID] = x if self.need_phys_grad: # Physical gradient basis.get_basis_val_grads(quad_pts, get_phys_grad=True, ijac=ijac) self.basis_phys_grad_elems[elem_ID] = basis.basis_phys_grad # [nq, nb, ndims] # Face normals for i in range(mesh.gbasis.NFACES): self.normals_elems[elem_ID, i] = mesh.gbasis.calculate_normals( mesh, elem_ID, i, self.face_quad_pts) # Volumes self.vol_elems, self.domain_vol = mesh_tools.element_volumes(mesh)
def get_error(mesh, physics, solver, var_name, ord=2, print_error=True, normalize_by_volume=True): ''' This function computes the Lp-error, where p is the "ord" input argument. Inputs: ------- mesh: mesh object physics: physics object solver: solver object var_name: name of variable to compute error of ord: order of the error print_error: if True, will print total error normalize_by_volume: if True, will normalize the error by the volume of the domain Outputs: -------- tot_err: total error err_elems: error over each element [num_elems] ''' # Extract info time = solver.time U = solver.state_coeffs basis = solver.basis order = solver.order if physics.exact_soln is None: raise ValueError("No exact solution provided") # Get element volumes if normalize_by_volume: _, tot_vol = mesh_tools.element_volumes(mesh, solver) else: tot_vol = 1. # Allocate, initialize err_elems = np.zeros([mesh.num_elems]) tot_err = 0. # Get quadrature data quad_order = basis.get_quadrature_order(mesh, 2 * np.amax([order, 1]), physics=physics) gbasis = mesh.gbasis quad_pts, quad_wts = gbasis.get_quadrature_data(quad_order) # Get x for each element xphys = np.empty((mesh.num_elems, ) + quad_pts.shape) for elem_ID in range(mesh.num_elems): xphys[elem_ID] = mesh_tools.ref_to_phys(mesh, elem_ID, quad_pts) # Evaluate exact solution at quadrature points u_exact = physics.exact_soln.get_state(physics, x=xphys, t=time) # Interpolate state to quadrature points basis.get_basis_val_grads(quad_pts, True) u = helpers.evaluate_state(U, basis.basis_val) # Computed requested quantity s = physics.compute_variable(var_name, u) s_exact = physics.compute_variable(var_name, u_exact) # Loop through elements for elem_ID in range(mesh.num_elems): # Calculate element-local error djac, _, _ = basis_tools.element_jacobian(mesh, elem_ID, quad_pts, get_djac=True) err = np.sum((s[elem_ID] - s_exact[elem_ID])**ord * quad_wts * djac) err_elems[elem_ID] = err tot_err += err_elems[elem_ID] tot_err = (tot_err / tot_vol)**(1. / ord) # Print if requested if print_error: print("Total error = %.15f" % (tot_err)) return tot_err, err_elems
def get_stiffness_matrix_ader(mesh, basis, basis_st, order, dt, elem_ID, grad_dir, physical_space=False): ''' Calculate the stiffness matrix for ADER-DG prediction step Inputs: ------- mesh: mesh object basis: basis object basis_st: space-time basis object order: solution order dt: time step elem_ID: element index grad_dir: direction of gradient calculation Outputs: -------- SM: stiffness matrix for ADER-DG [nb_st, nb_st] ''' ndims = mesh.ndims quad_order_st = basis_st.get_quadrature_order(mesh, order * 2) quad_order = quad_order_st quad_pts_st, quad_wts_st = basis_st.get_quadrature_data(quad_order_st) quad_pts, quad_wts = basis.get_quadrature_data(quad_order) nq_st = quad_pts_st.shape[0] nq = quad_pts.shape[0] if physical_space: djac, jac, ijac = basis_tools.element_jacobian(mesh, elem_ID, quad_pts_st, get_djac=True, get_ijac=True) ijac_st = np.zeros([nq_st, ndims + 1, ndims + 1]) ijac_st[:, :ndims, :ndims] = ijac # Add the temporal Jacobian in the ndims+1 dimension ijac_st[:, ndims, ndims] = 2. / dt basis_st.get_basis_val_grads(quad_pts_st, get_val=True, get_ref_grad=True) nb_st = basis_st.basis_val.shape[1] basis_st_val = basis_st.basis_val if physical_space: basis_ref_grad = basis_st.basis_ref_grad basis_st_grad = np.transpose( np.matmul(ijac_st.transpose(0, 2, 1), basis_ref_grad.transpose(0, 2, 1)), (0, 2, 1)) else: basis_st_grad = basis_st.basis_ref_grad # ------------------------------------------------------------------- # # Example of ADER Stiffness Matrix calculation using for-loops # ------------------------------------------------------------------- # # # nb_st = basis_st.basis_val.shape[1] # # for i in range(nb_st): # for j in range(nb_st): # a = 0. # for iq in range(nq_st): # a += basis_st_grad[iq, i, grad_dir]*basis_st_val[iq, j] # * quad_wts_st[iq] # SM[i,j] = a # # ------------------------------------------------------------------- # SM = np.matmul(basis_st_grad[:, :, grad_dir].transpose(), basis_st_val * quad_wts_st) return SM # [nb_st, nb_st]
def get_elem_mass_matrix_ader(mesh, basis, order, elem_ID=-1, physical_space=False): ''' Calculate the mass matrix for ADER-DG prediction step Inputs: ------- mesh: mesh object basis: basis object order: solution order elem_ID: [OPTIONAL] element index physical_space: [OPTIONAL] Flag to calc matrix in physical or reference space (default: False {reference space}) Outputs: -------- MM: mass matrix for ADER-DG [nb_st, nb_st] ''' if physical_space: gbasis = mesh.gbasis quad_order = gbasis.get_quadrature_order(mesh, order * 2) else: quad_order = order * 2 quad_pts, quad_wts = basis.get_quadrature_data(quad_order) nq = quad_pts.shape[0] basis.get_basis_val_grads(quad_pts, get_val=True) if physical_space: djac, _, _ = basis_tools.element_jacobian(mesh, elem_ID, quad_pts, get_djac=True) if len(djac) == 1: djac = np.full(nq, djac[0]) else: djac = np.full(nq, 1.).reshape(nq, 1) # ------------------------------------------------------------------- # # Example of ADER Flux Matrix calculation using for-loops # ------------------------------------------------------------------- # # # nb_st = basis.basis_val.shape[1] # basis_val = basis.basis_val # # for i in range(nb_st): # for j in range(nb_st): # a = 0. # for iq in range(nq): # a += basis_val[iq,i]*basis_val[iq,j]*quad_wts[iq]*djac[iq] # MM[i,j] = a # # ------------------------------------------------------------------- # MM = np.matmul(basis.basis_val.transpose(), basis.basis_val * \ quad_wts * djac) # [nb_st, nb_st] return MM # [nb_st, nb_st]