def get_boundary_face_residual(self, bgroup, face_IDs, Uc, resB): # unpack mesh = self.mesh physics = self.physics bgroup_num = bgroup.number bface_helpers = self.bface_helpers elem_helpers = self.elem_helpers fluxes = self.params["ConvFluxSwitch"] quad_wts = bface_helpers.quad_wts normals_bgroups = bface_helpers.normals_bgroups x_bgroups = bface_helpers.x_bgroups ijac_bgroups = bface_helpers.ijac_bgroups basis_val = bface_helpers.faces_to_basis[face_IDs] # [nbf, nq, nb] basis_ref_grad = bface_helpers.faces_to_basis_ref_grad[face_IDs] # Interpolate state at quad points UqI = helpers.evaluate_state(Uc, basis_val) # [nbf, nq, ns] normals = normals_bgroups[bgroup_num] # [nbf, nq, ndims] x = x_bgroups[bgroup_num] # [nbf, nq, ndims] ijac = ijac_bgroups[bgroup_num] BC = physics.BCs[bgroup.name] # Interpolate state at quadrature points UqI = helpers.evaluate_state(Uc, basis_val) # Interpolate gradient of state at quad points gUq_ref = self.evaluate_gradient(Uc, basis_ref_grad) # Make ref gradient of state the physical gradient gUq = self.ref_to_phys_grad(ijac, gUq_ref) # Compute any additional helpers for diffusive flux fcn if physics.diff_flux_fcn: physics.diff_flux_fcn.compute_bface_helpers(self, bgroup_num) if fluxes: # Compute boundary flux Fq, FqB = BC.get_boundary_flux(physics, UqI, normals, x, self.time, gUq=gUq) FqB_phys = self.ref_to_phys_grad(ijac, FqB) # Compute contribution to adjacent element residual resB = solver_tools.calculate_boundary_flux_integral( basis_val, quad_wts, Fq) resB -= self.calculate_boundary_flux_integral_sum( basis_ref_grad, quad_wts, FqB_phys) return resB
def custom_user_function(solver): ''' Provides a custom interface for the model problem into the solver. This particular case saves the physical time and solution in time for the "1D" element. The file written is called 'time_hist.txt'. Inputs: ------- solver: solver object ''' tstart = 0. if solver.time >= tstart: # Unpack Uc = solver.state_coeffs basis_val = solver.elem_helpers.basis_val Uq = helpers.evaluate_state(Uc, basis_val) time_hist = open('time_hist.txt', 'a') # Convert applicable variables to strings s = str(solver.time) s1 = str(Uq[0, 0, 0]) # write to the file at each iteration time_hist.write(s) time_hist.write(' , ') time_hist.write(s1) time_hist.write('\n') time_hist.close()
def average_spacetime_guess(solver, W, U_pred, dt=None): ''' This method calculates the average space-time guess for the ADERDG predictor step. (This is the default approach for the guess) Inputs: ------- solver: solver object W: spatial polynomial solution [ne, nb, ns] U_pred: space-time polynomial coefficients [ne, nb_st, ns] Outputs: -------- U_pred: Space-time predicted polynomial coefficients [ne, nb_st, ns] ''' # Unpack physics = solver.physics basis = solver.basis elem_helpers = solver.elem_helpers quad_wts = elem_helpers.quad_wts basis_val = elem_helpers.basis_val djac_elems = elem_helpers.djac_elems vol_elems = elem_helpers.vol_elems # Calculate the average state for each element in spatial coordinates Wq = helpers.evaluate_state(W, basis_val, skip_interp=basis.skip_interp) W_bar = helpers.get_element_mean(Wq, quad_wts, djac_elems, vol_elems) U_pred[:] = W_bar return U_pred, W_bar # [ne, nb_st, ns]
def get_jacobian_matrix_elems(self, solver, iMM_elems, Uc): ''' Calculates the Jacobian matrix of the source term and its inverse for each element. Definition of 'Jacobian' matrix: A = I - BETA*dt*iMM^{-1}*dRdU Inputs: ------- solver: solver object (e.g., DG, ADERDG, etc...) elem_ID: element ID iMM: inverse mass matrix [ne, nb, nb] Uc: state coefficients [ne, nb, ns] Outputs: -------- A: matrix returned for linear solve [ne, nb, nb, ns, ns] iA: inverse matrix returned for linear solve [ne, nb, nb, ns, ns] ''' mesh = solver.mesh nelem = mesh.num_elems beta = self.BETA dt = solver.stepper.dt physics = solver.physics source_terms = physics.source_terms elem_helpers = solver.elem_helpers basis_val = elem_helpers.basis_val quad_wts = elem_helpers.quad_wts x_elems = elem_helpers.x_elems djac_elems = elem_helpers.djac_elems nq = quad_wts.shape[0] ns = physics.NUM_STATE_VARS nb = basis_val.shape[1] Uq = helpers.evaluate_state( Uc, basis_val, skip_interp=solver.basis.skip_interp) # [ne, nq, ns]) # Evaluate the source term Jacobian [ne, nq, ns, ns] Sjac = np.zeros([nelem, nq, ns, ns]) Sjac = physics.eval_source_term_jacobians(Uq, x_elems, solver.time, Sjac) # Call solver helper to get dRdU (see solver/tools.py) dRdU = solver_tools.calculate_dRdU(elem_helpers, Sjac) # [ne, nb, nb, ns, ns] # Define the identity matrix I = np.expand_dims(np.expand_dims(np.eye(nb), axis=2), axis=3) A = I - beta*dt * \ np.einsum('eij, ejklm -> eiklm', iMM_elems, dRdU) iA = np.zeros_like(A) for i in range(ns): for s in range(ns): iA[:, :, :, i, s] = np.linalg.inv(A[:, :, :, i, s]) return A, iA # [ne, nb, nb, ns, ns]
def get_element_residual(self, Uc, res_elem): # Unpack physics = self.physics ns = physics.NUM_STATE_VARS ndims = physics.NDIMS elem_helpers = self.elem_helpers basis_val = elem_helpers.basis_val basis_phys_grad_elems = elem_helpers.basis_phys_grad_elems quad_wts = elem_helpers.quad_wts djac_elems = elem_helpers.djac_elems ijac_elems = elem_helpers.ijac_elems x_elems = elem_helpers.x_elems nq = quad_wts.shape[0] fluxes = self.params["ConvFluxSwitch"] sources = self.params["SourceSwitch"] # Interpolate state at quad points Uq = helpers.evaluate_state( Uc, basis_val, skip_interp=self.basis.skip_interp) # [ne, nq, ns] # Interpolate gradient of state at quad points gUq = self.evaluate_gradient(Uc, basis_phys_grad_elems) if self.verbose: # Get min and max of state variables for reporting self.get_min_max_state(Uq) if fluxes: # Evaluate the inviscid flux integral Fq = physics.get_conv_flux_interior(Uq)[0] # [ne, nq, ns, ndims] if physics.diff_flux_fcn: # Evaluate the diffusion flux Fq -= physics.get_diff_flux_interior(Uq, gUq) # [ne, nq, ns, ndims] res_elem += solver_tools.calculate_volume_flux_integral( self, elem_helpers, Fq) # [ne, nb, ns] if sources: # Evaluate the source term integral # eval_source_terms is an additive function so source needs to be # initialized to zero for each time step Sq = np.zeros_like(Uq) # [ne, nq, ns] Sq = physics.eval_source_terms(Uq, x_elems, self.time, Sq) # [ne, nq, ns] res_elem += solver_tools.calculate_source_term_integral( elem_helpers, Sq) # [ne, nb, ns] # Add artificial viscosity term if self.params["ArtificialViscosity"]: av_param = self.params["AVParameter"] res_elem -= solver_tools.calculate_artificial_viscosity_integral( physics, elem_helpers, Uc, av_param, self.order) return res_elem # [ne, nb, ns]
def recalculate_jacobian_on(solver, U_pred, dt, Sjac=None): ''' Method to recalculate the jacobian at each subiteration of a nonlinear solver. Note: This has only been useful in very specific applications. Inputs: ------- solver: solver object U_pred: Space-time predicted polynomial coefficients [ne, nb_st, ns] dt: time step size Outputs: -------- Sjac: source term jacobian [nelem, ns, ns] ''' # Unpack physics = solver.physics elem_helpers = solver.elem_helpers elem_helpers_st = solver.elem_helpers_st ns = physics.NUM_STATE_VARS nelem = U_pred.shape[0] quad_wts_st = elem_helpers_st.quad_wts basis_val_st = elem_helpers_st.basis_val nq_t = elem_helpers_st.nq_tile_constant x_elems = elem_helpers.x_elems vol_elems = elem_helpers.vol_elems djac_elems = elem_helpers.djac_elems # Only evaluate Jacobian for stiff sources temp_sources = physics.source_terms.copy() physics.source_terms = physics.implicit_sources.copy() Uq = helpers.evaluate_state(U_pred, basis_val_st) U_bar = helpers.get_element_mean( Uq, quad_wts_st, np.tile(djac_elems, [1, nq_t, 1]) * dt / 2., dt * vol_elems) # Calculate the source term Jacobian using average state Sjac = Sjac.reshape([U_pred.shape[0], 1, ns, ns]) Sjac[:] = 0. # Sjac = np.zeros([U_pred.shape[0], 1, ns, ns]) Sjac = physics.eval_source_term_jacobians(U_bar, x_elems, solver.time, Sjac) Sjac = np.reshape(Sjac, [nelem, ns, ns]) # Set all sources for source_coeffs calculation physics.source_terms = temp_sources.copy()
def get_average_solution(physics, solver, x, basis, var_name): ''' This function evaluates the average numerical solution at a set of points and plots them on the average x location of each element. Inputs: ------- physics: physics object U: state polynomial coefficients [num_elems, num_basis_coeffs, num_state_vars] x: coordinates at which to evaluate solution [num_elems, num_pts, ndims], where num_pts is the number of sample points per element basis: basis object var_name: name of variable to get Outputs: ------- var_numer: values of variable obtained at x [num_elems, num_pts, 1] x_bar: average coordinates for each element [num_elems, 1, 1] ''' mesh = solver.mesh order = solver.order U = solver.state_coeffs if physics.NDIMS > 1: print("Plotting average state not available for 2D") raise NotImplementedError #Additions for Ubar solver.elem_helpers.get_gaussian_quadrature(mesh, physics, basis, 3*order) solver.elem_helpers.get_basis_and_geom_data(mesh, basis, 3*order) elem_helpers = solver.elem_helpers quad_wts = elem_helpers.quad_wts djacs = elem_helpers.djac_elems vols = elem_helpers.vol_elems Uq = helpers.evaluate_state(U, basis.basis_val) Ubar = helpers.get_element_mean(Uq, quad_wts, djacs, vols) var_numer = physics.compute_variable(var_name, Ubar) x_bar = np.sum(x, axis=1).reshape([x.shape[0], 1, 1])/x.shape[1] return var_numer, x_bar
def get_dt_from_cfl(stepper, solver): ''' Calculates dt using a specified CFL number. Updates at everytime step to ensure solution remains within the CFL bound. Inputs: ------- stepper: stepper object (e.g., FE, RK4, etc...) solver: solver object (e.g., DG, ADERDG, etc...) Outputs: -------- dt: time step for the solver ''' mesh = solver.mesh ndims = mesh.ndims physics = solver.physics U = solver.state_coeffs time = solver.time tfinal = solver.params["FinalTime"] stepper.tfinal = tfinal cfl = solver.params["CFL"] elem_helpers = solver.elem_helpers vol_elems = elem_helpers.vol_elems a = np.zeros([mesh.num_elems, U.shape[1], 1]) basis_val = solver.elem_helpers.basis_val # Interpolate state at quad points Uq = helpers.evaluate_state( U, basis_val, skip_interp=solver.basis.skip_interp) # [ne, nq, ns] # Calculate max wavespeed a = physics.compute_variable("MaxWaveSpeed", Uq, flag_non_physical=True) # Calculate the dt for each element dt_elems = cfl * vol_elems**(1. / ndims) / a # take minimum to set appropriate dt dt = np.min(dt_elems) # logic to ensure final time step yields FinalTime if time + dt < tfinal: stepper.num_time_steps += 1 return dt else: return tfinal - time
def take_time_step(self, solver): mesh = solver.mesh elem_helpers = solver.elem_helpers basis_val = elem_helpers.basis_val iMM_elems = elem_helpers.iMM_elems quad_pts = elem_helpers.quad_pts quad_wts = elem_helpers.quad_wts x_elems = elem_helpers.x_elems U = solver.state_coeffs Uq = helpers.evaluate_state( U, basis_val, skip_interp=solver.basis.skip_interp) # [ne, nq, ns]) res = self.res Uq0, t0 = Uq.reshape(-1), solver.time dt = self.dt subiterations = [] # Instantiate ode object r = ode(self.rhs_sources, jac=None) r.set_integrator('lsoda', nsteps=50000, atol=1e-14, rtol=1e-12) r.set_initial_value(Uq0, t0).set_f_params(x_elems, Uq, solver, subiterations) value = r.integrate(r.t + dt).reshape( [res.shape[0], res.shape[1], res.shape[2]]) subiterations = np.unique(subiterations) # Print the number of subiterations for each iteration and # store the total number of ODE subiterations for the solver print("Subiterations:", len(subiterations)) solver.count_evaluations += len(subiterations) # Project onto the basis state from the quadrature points solver_tools.L2_projection(mesh, iMM_elems, solver.basis, quad_pts, quad_wts, value, U) solver.apply_limiter(U) solver.state_coeffs = U return res # [ne, nb, ns]
def project_state_to_new_basis(self, U_old, basis_old, order_old): ''' Projects the state to a different basis and/or order Inputs: ------- U_old: restart files old solution array basis_old: previous basis function order_old: previous polynomial order Outputs: -------- state is modified ''' mesh = self.mesh physics = self.physics basis = self.basis params = self.params iMM_elems = self.elem_helpers.iMM_elems U = self.state_coeffs ns = physics.NUM_STATE_VARS if basis_old.SHAPE_TYPE != basis.SHAPE_TYPE: raise errors.IncompatibleError if not params["L2InitialCondition"]: # Interpolate to solution nodes eval_pts = basis.get_nodes(self.order) else: # Quadrature order = 2 * np.amax([self.order, order_old]) quad_order = basis.get_quadrature_order(mesh, order) quad_pts, quad_wts = basis.get_quadrature_data(quad_order) eval_pts = quad_pts basis_old.get_basis_val_grads(eval_pts, get_val=True) Uq_old = helpers.evaluate_state(U_old, basis_old.basis_val) if not params["L2InitialCondition"]: solver_tools.interpolate_to_nodes(Uq_old, U) else: solver_tools.L2_projection(mesh, iMM_elems, basis, quad_pts, quad_wts, Uq_old, U)
def take_time_step(self, solver): mesh = solver.mesh U = solver.state_coeffs mesh = solver.mesh elem_helpers = solver.elem_helpers basis_val = elem_helpers.basis_val iMM_elems = elem_helpers.iMM_elems quad_pts = elem_helpers.quad_pts quad_wts = elem_helpers.quad_wts x_elems = elem_helpers.x_elems Uq = helpers.evaluate_state( U, basis_val, skip_interp=solver.basis.skip_interp) # [ne, nq, ns]) # Solve the nonlinear system to get the new solution. This is done # for each element and each quadrature point, fully uncoupled. This # is actually only valid for orthogonal bases - for nodal bases this # could incur some error. for i in range(Uq.shape[0]): for j in range(Uq.shape[1]): sol = scipy.optimize.root(self.rhs_sources, Uq[i, j], args=(solver, x_elems[i, j], Uq[i, j])) Uq[i, j] = sol.x res = self.res # Project onto the basis state from the quadrature points solver_tools.L2_projection(mesh, iMM_elems, solver.basis, quad_pts, quad_wts, Uq, U) solver.apply_limiter(U) solver.state_coeffs = U return res # [ne, nb, ns]
def get_numerical_solution(physics, U, x, basis, var_name): ''' This function evaluates the numerical solution at a set of points. Inputs: ------- physics: physics object U: state polynomial coefficients [num_elems, num_basis_coeffs, num_state_vars] x: coordinates at which to evaluate solution [num_elems, num_pts, ndims], where num_pts is the number of sample points per element basis: basis object var_name: name of variable to get Outputs: ------- var_numer: values of variable obtained at x [num_elems, num_pts, 1] ''' Uq = helpers.evaluate_state(U, basis.basis_val) var_numer = physics.compute_variable(var_name, Uq) return var_numer
def compute_variable(solver, Uc, var_name): ''' This function computes the desired variable at the element and face quadrature points, as well as the mean of the variable over the element. ''' # Interpolate state at quadrature points over element and on faces limiter = solver.limiters[0] basis = solver.basis physics = solver.physics U_elem_faces = helpers.evaluate_state(Uc, limiter.basis_val_elem_faces, skip_interp=basis.skip_interp) nq_elem = limiter.quad_wts_elem.shape[0] U_elem = U_elem_faces[:, :nq_elem, :] # Average value of state U_bar = helpers.get_element_mean(U_elem, limiter.quad_wts_elem, limiter.djac_elems, limiter.elem_vols) # Compute variable at quadrature points var_elem_faces = physics.compute_variable(var_name, U_elem_faces) # Compute mean var_bar = physics.compute_variable(var_name, U_bar) return var_elem_faces, var_bar
def spacetime_odeguess(solver, W, U_pred, dt=None): ''' This method sets the space-time guess to the predictor step in the ADER-DG scheme by using a built-in ODE solver from scipy. We solve the stationary ODE in time and use the result to construct our guess to the non-linear solver NOTE: Current implementation only supports ODEs Inputs: ------- solver: solver object W: spatial polynomial solution [ne, nb, ns] U_pred: space-time polynomial coefficients [ne, nb_st, ns] Outputs: -------- U_pred: Space-time predicted polynomial coefficients [ne, nb_st, ns] U_bar: Space-time average value [ne, 1, ns] ''' # Unpack physics = solver.physics ns = physics.NUM_STATE_VARS mesh = solver.mesh basis = solver.basis basis_st = solver.basis_st elem_helpers = solver.elem_helpers elem_helpers_st = solver.elem_helpers_st ader_helpers = solver.ader_helpers iMM_elems = ader_helpers.iMM_elems quad_wts = elem_helpers.quad_wts quad_pts = elem_helpers.quad_pts basis_val = elem_helpers.basis_val basis_val_st = elem_helpers_st.basis_val djac_elems = elem_helpers.djac_elems x_elems = elem_helpers.x_elems nelem = W.shape[0] quad_pts_st = elem_helpers_st.quad_pts quad_wts_st = elem_helpers_st.quad_wts nq_st = quad_wts_st.shape[0] nq_t = elem_helpers_st.nq_tile_constant vol_elems = elem_helpers.vol_elems # Evaluate spatial coeffs on spatial quadrature points Wq = helpers.evaluate_state(W, basis_val, skip_interp=basis.skip_interp) # Allocate memory for the guess at the quadrature points Uq_guess = np.zeros([nelem, nq_st, ns]) Uq_guess = np.tile(Wq, [1, Wq.shape[1], 1]) # Build ref temporal array for space-time element t, elem_helpers_st.basis_time = ref_to_phys_time( mesh, solver.time, dt, quad_pts[:, -1:], elem_helpers_st.basis_time) # Build phys time array for space-time element tphys, elem_helpers_st.basis_time = ref_to_phys_time( mesh, solver.time, dt, ader_helpers.x_elems[0, 0:2, :], elem_helpers_st.basis_time) W0, t0 = Wq.reshape(-1), solver.time def func(t, y, x, Sq_exp): ''' Function for the ode solver to calculate the RHS Inputs: ------- t: time y: solution array x: quadrature points Sq_exp: explicit source term evaluated at quadrature points ''' # Keep track of the number of times func is called tvals.append(t) # Evaluate the source term at the quadrature points Sq = np.zeros([U_pred.shape[0], x.shape[1], ns]) y = y.reshape(Sq.shape) Sq = Sq_exp + physics.eval_source_terms(y, x, t, Sq) # NOTE: This function currently does not include the flux evaluation. # It will need to be added for the guess to be correct for more # complicated systems. Current test cases that require this are # only ODE cases. return Sq.reshape(-1) # Evaluate source terms to be taken explicitly temp_sources = physics.source_terms.copy() physics.source_terms = physics.explicit_sources.copy() Sq_exp = np.zeros([U_pred.shape[0], x_elems.shape[1], ns]) Sq_exp = physics.eval_source_terms(Wq, x_elems, t, Sq_exp) # Set implicit sources only for stiff ODE evaluation physics.source_terms = physics.implicit_sources.copy() # Initialize the integrator r = ode(func, jac=None) r.set_integrator('lsoda', nsteps=50000, atol=1e-14, rtol=1e-12) r.set_initial_value(W0, t0).set_f_params(x_elems, Sq_exp) # Set constants for managing data and begin ODE integration loop i = 0 j = 0 # Run the ODEsolver guess while r.successful() and j < t.shape[0]: # Length of tvals represents number of ODE interations per # timestep between two quadrature points in time tvals = [] # Runs the integrator value = r.integrate(r.t + (t[j] - r.t)) # Populate the data into the guess Uq_guess[:,i:t.shape[0]*j+t.shape[0],:] = \ value.reshape([nelem, t.shape[0], ns]) i += t.shape[0] j += 1 tvals = np.unique(tvals) solver.count_evaluations += len(tvals) # Prints the number of ODE iterations print("Steps/quadrature point: ", len(tvals)) physics.source_terms = temp_sources # Get space-time average from initial guess U_bar = helpers.get_element_mean( Uq_guess, quad_wts_st, np.tile(djac_elems, [1, nq_t, 1]) * dt / 2., dt * vol_elems) # Project the guess at the space-time quadrature points to the # state coefficient's initial guess L2_projection(mesh, iMM_elems, solver.basis_st, quad_pts_st, quad_wts_st, np.tile(djac_elems, [1, nq_t, 1]), Uq_guess, U_pred) return U_pred, U_bar
def get_interior_face_residual(self, faceL_IDs, faceR_IDs, UcL, UcR): # Unpack mesh = self.mesh physics = self.physics fluxes = self.params["ConvFluxSwitch"] int_face_helpers = self.int_face_helpers elem_helpers = self.elem_helpers vol_elems = elem_helpers.vol_elems face_lengths = int_face_helpers.face_lengths quad_wts = int_face_helpers.quad_wts faces_to_basisL = int_face_helpers.faces_to_basisL faces_to_basisR = int_face_helpers.faces_to_basisR faces_to_basis_ref_gradL = int_face_helpers.faces_to_basis_ref_gradL faces_to_basis_ref_gradR = int_face_helpers.faces_to_basis_ref_gradR ijacL_elems = int_face_helpers.ijacL_elems ijacR_elems = int_face_helpers.ijacR_elems normals_int_faces = int_face_helpers.normals_int_faces # [nf, nq, ndims] ns = physics.NUM_STATE_VARS nq = quad_wts.shape[0] # Interpolate state at quad points UqL = helpers.evaluate_state(UcL, faces_to_basisL[faceL_IDs]) # [nf, nq, ns] UqR = helpers.evaluate_state(UcR, faces_to_basisR[faceR_IDs]) # [nf, nq, ns] # Interpolate gradient of state at quad points gUqL_ref = self.evaluate_gradient(UcL, faces_to_basis_ref_gradL[faceL_IDs]) gUqR_ref = self.evaluate_gradient(UcR, faces_to_basis_ref_gradR[faceR_IDs]) # Make gradient the physical gradient at L/R states gUqL = self.ref_to_phys_grad(ijacL_elems, gUqL_ref) gUqR = self.ref_to_phys_grad(ijacR_elems, gUqR_ref) # Allocate resL and resR (needed for operator splitting) nifL = self.int_face_helpers.elemL_IDs.shape[0] nifR = self.int_face_helpers.elemR_IDs.shape[0] resL = np.zeros([nifL, nq, ns]) resR = np.zeros([nifR, nq, ns]) resL_diff = np.zeros([nifL, nq, ns]) resR_diff = np.zeros([nifR, nq, ns]) if physics.diff_flux_fcn: # Calculate diffusion flux helpers physics.diff_flux_fcn.compute_iface_helpers(self) if fluxes: # Compute numerical flux Fq = physics.get_conv_flux_numerical(UqL, UqR, normals_int_faces) # [nf, nq, ns] # Compute diffusion flux Fq_diff, FL, FR = physics.get_diff_flux_numerical( UqL, UqR, gUqL, gUqR, normals_int_faces) # [nf, nq, ns], # [nf, nq, ns, ndims], [nf, nq, ns, ndims] Fq -= Fq_diff FL_phys = self.ref_to_phys_grad(ijacL_elems, FL) FR_phys = self.ref_to_phys_grad(ijacR_elems, FR) # Compute contribution to left and right element residuals resL = solver_tools.calculate_boundary_flux_integral( faces_to_basisL[faceL_IDs], quad_wts, Fq) resR = solver_tools.calculate_boundary_flux_integral( faces_to_basisR[faceR_IDs], quad_wts, Fq) # Compute additional boundary flux integrals for diffusion terms resL_diff = self.calculate_boundary_flux_integral_sum( faces_to_basis_ref_gradL[faceL_IDs], quad_wts, FL_phys) resR_diff = self.calculate_boundary_flux_integral_sum( faces_to_basis_ref_gradR[faceR_IDs], quad_wts, FR_phys) return resL, resR, resL_diff, resR_diff # [nif, nb, ns]
def predictor_elem_explicit(solver, dt, W, U_pred): ''' Calculates the predicted solution state for the ADER-DG method using a nonlinear solve of the weak form of the DG discretization in time. This function treats the source term explicitly. Appropriate for non-stiff systems. Inputs: ------- solver: solver object dt: time step W: previous time step solution in space only [ne, nb, ns] Outputs: -------- U_pred: predicted solution in space-time [ne, nb_st, ns] ''' # Unpack threshold = solver.params["PredictorThreshold"] physics = solver.physics ns = physics.NUM_STATE_VARS mesh = solver.mesh basis = solver.basis basis_st = solver.basis_st elem_helpers = solver.elem_helpers elem_helpers_st = solver.elem_helpers_st ader_helpers = solver.ader_helpers nq_tile_constant = elem_helpers_st.nq_tile_constant basis_ref_grad = elem_helpers.basis_ref_grad basis_ref_grad_st = elem_helpers_st.basis_ref_grad order = solver.order quad_wts = elem_helpers.quad_wts basis_val = elem_helpers.basis_val djac_elems = elem_helpers.djac_elems FTR = ader_helpers.FTR MM = ader_helpers.MM SMS_elems = ader_helpers.SMS_elems iK = ader_helpers.iK # Calculate the average state for each element in spatial coordinates vol_elems = elem_helpers.vol_elems Wq = helpers.evaluate_state(W, basis_val, skip_interp=basis.skip_interp) W_bar = helpers.get_element_mean(Wq, quad_wts, djac_elems, vol_elems) # Initialize space-time coefficients U_pred, U_bar = solver.get_spacetime_guess(solver, W, U_pred, dt=dt) # Calculate the source and flux coefficients with initial guess source_coeffs = solver.source_coefficients(dt, order, basis_st, U_pred) flux_coeffs = solver.flux_coefficients(dt, order, basis_st, U_pred) # Iterate using a discrete Picard nonlinear solve for the # updated space-time coefficients. niter = 100 for i in range(niter): U_pred_new = iK @ ( MM @ source_coeffs - \ smsflux(SMS_elems, flux_coeffs) + FTR @ W ) # We check when the coefficients are no longer changing. # This can lead to differences between NODAL and MODAL solutions. # This could be resolved by evaluating at the quadrature points # and comparing the error between those values. err = U_pred_new - U_pred if np.amax(np.abs(err)) < threshold: U_pred = U_pred_new print("Predictor iterations: ", i) break U_pred = np.copy(U_pred_new) source_coeffs = solver.source_coefficients(dt, order, basis_st, U_pred) flux_coeffs = solver.flux_coefficients(dt, order, basis_st, U_pred) if i == niter - 1: print('Sub-iterations not converging', np.amax(np.abs(err))) raise ValueError('Sub-iterations not converging') return U_pred # [ne, nb_st, ns]
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 predictor_elem_stiffimplicit(solver, dt, W, U_pred): ''' Calculates the predicted solution state for the ADER-DG method using a nonlinear solve of the weak form of the DG discretization in time. This function utilizes scipy's root solver (specifically 'hybr' or a modified Powell method) to converge the nonlinear solver. For this method to be effecient, the user should also select the ODEGuess to provide the initial condition to the nonlinear solver. This is suitable for very stiff systems such as those observed in chemically reacting flows. Inputs: ------- solver: solver object dt: time step W: previous time step solution in space only [ne, nb, ns] Outputs: -------- U_pred: predicted solution in space-time [ne, nb_st, ns] ''' # Unpack threshold = solver.params["PredictorThreshold"] physics = solver.physics ns = physics.NUM_STATE_VARS mesh = solver.mesh basis = solver.basis basis_st = solver.basis_st elem_helpers = solver.elem_helpers ader_helpers = solver.ader_helpers order = solver.order quad_wts = elem_helpers.quad_wts basis_val = elem_helpers.basis_val djac_elems = elem_helpers.djac_elems FTR = ader_helpers.FTR MM = ader_helpers.MM SMS_elems = ader_helpers.SMS_elems iK = ader_helpers.iK # Calculate the average state for each element in spatial coordinates vol_elems = elem_helpers.vol_elems Wq = helpers.evaluate_state(W, basis_val, skip_interp=basis.skip_interp) W_bar = helpers.get_element_mean(Wq, quad_wts, djac_elems, vol_elems) # Initialize space-time coefficients U_pred, U_bar = solver.get_spacetime_guess(solver, W, U_pred, dt=dt) # Calculate the source and flux coefficients with initial guess source_coeffs = solver.source_coefficients(dt, order, basis_st, U_pred) flux_coeffs = solver.flux_coefficients(dt, order, basis_st, U_pred) def rhs_weakform(q): ''' Solves the weak form of the DG discretization while doing integration by parts on the temporal term. Inputs: ------- q: Space-time polynomial coffecients [ne x nb_st x ns] Outputs: -------- zero: The rhs of the nonlinear solver should be zero [ne x nb_st x ns] ''' q = q.reshape([U_pred.shape[0], U_pred.shape[1], U_pred.shape[2]]) source_coeffs = solver.source_coefficients(dt, order, basis_st, q) flux_coeffs = solver.flux_coefficients(dt, order, basis_st, q) zero = np.einsum( 'jk, ikm -> ijm', iK, np.einsum('jk, ikl -> ijl', MM, source_coeffs) - np.einsum('ijkl, ikml -> ijm', SMS_elems, flux_coeffs) + np.einsum('jk, ikm -> ijm', FTR, W)) - q q.reshape(-1) # reshape for the nonlinear solver return zero.reshape(-1) # reshape for the nonlinear solver # Iterate using root function sol = root(rhs_weakform, U_pred.reshape(-1), tol=1e-15, jac=None, method='hybr', options={ 'maxfev': 50000, 'xtol': 1e-15 }) U_pred = np.copy(sol.x.reshape([U_pred.shape[0], U_pred.shape[1], ns])) # Note: Other nonlinear solvers could be more efficient. Further work is # needed to determine the most efficient method. Commented code below # is another approach. # sol = newton_krylov(fun, U_pred.reshape(-1), iter=None, # rdiff=None, method='lgmres', maxiter=100) # U_pred = np.copy(sol.reshape([U_pred.shape[0], U_pred.shape[1], ns])) return U_pred # [ne, nb_st, ns]
def get_interior_face_residual(self, faceL_IDs, faceR_IDs, UcL, UcR): # Unpack mesh = self.mesh physics = self.physics ns = physics.NUM_STATE_VARS time_skip = self.elem_helpers_st.time_skip nq_tile_constant = self.elem_helpers_st.nq_tile_constant int_face_helpers = self.int_face_helpers int_face_helpers_st = self.int_face_helpers_st faceL_id_st = int_face_helpers_st.faceL_IDs_st faceR_id_st = int_face_helpers_st.faceR_IDs_st quad_wts_st = int_face_helpers_st.quad_wts faces_to_basisL = int_face_helpers.faces_to_basisL faces_to_basisR = int_face_helpers.faces_to_basisR faces_to_basisL_st = int_face_helpers_st.faces_to_basisL faces_to_basisR_st = int_face_helpers_st.faces_to_basisR faces_to_basis_ref_gradL = \ int_face_helpers.faces_to_basis_ref_gradL faces_to_basis_ref_gradR = \ int_face_helpers.faces_to_basis_ref_gradR faces_to_basis_ref_gradL_st = \ int_face_helpers_st.faces_to_basis_ref_gradL faces_to_basis_ref_gradR_st = \ int_face_helpers_st.faces_to_basis_ref_gradR basis_valL = faces_to_basisL[faceL_IDs] basis_valR = faces_to_basisR[faceR_IDs] basis_valL_st = faces_to_basisL_st[faceL_id_st] basis_valR_st = faces_to_basisR_st[faceR_id_st] ijacL_elems = int_face_helpers.ijacL_elems ijacR_elems = int_face_helpers.ijacR_elems fluxes = self.params["ConvFluxSwitch"] # Interpolate state at quad points UqL = helpers.evaluate_state(UcL, basis_valL_st) # [nf, nq_st, ns] UqR = helpers.evaluate_state(UcR, basis_valR_st) # [nf, nq_st, ns] # Interpolate gradient of state at quad points gUqL_ref = self.evaluate_gradient( UcL, faces_to_basis_ref_gradL_st[faceL_id_st, :, :, :-1]) gUqR_ref = self.evaluate_gradient( UcR, faces_to_basis_ref_gradR_st[faceR_id_st, :, :, :-1]) ijacL_elems_st = np.tile(ijacL_elems, (1, time_skip, 1, 1)) ijacR_elems_st = np.tile(ijacR_elems, (1, time_skip, 1, 1)) # Make gradient the physical gradient at L/R states gUqL = self.ref_to_phys_grad(ijacL_elems_st, gUqL_ref) gUqR = self.ref_to_phys_grad(ijacR_elems_st, gUqR_ref) normals_int_faces = int_face_helpers.normals_int_faces normals_int_faces = np.tile(normals_int_faces, (normals_int_faces.shape[1], 1)) # Allocate resL/R and resL/R_diff (needed for operator splitting) resL = np.zeros_like(self.stepper.res) resR = np.zeros_like(self.stepper.res) resL_diff = np.zeros_like(resL) resR_diff = np.zeros_like(resR) if physics.diff_flux_fcn: # Calculate diffusion flux helpers physics.diff_flux_fcn.compute_iface_helpers(self) if fluxes: # Compute numerical flux Fq = physics.get_conv_flux_numerical(UqL, UqR, normals_int_faces) # [nf, nq_st, ns] # Compute diffusion flux Fq_diff, FL, FR = physics.get_diff_flux_numerical( UqL, UqR, gUqL, gUqR, normals_int_faces) # [nf, nq, ns], # [nf, nq, ns, ndims], [nf, nq, ns, ndims] Fq -= Fq_diff FL_phys = self.ref_to_phys_grad(ijacL_elems_st, FL) FR_phys = self.ref_to_phys_grad(ijacR_elems_st, FR) # Compute contribution to left and right element residuals resL = solver_tools.calculate_boundary_flux_integral( time_skip, basis_valL, quad_wts_st, Fq) resR = solver_tools.calculate_boundary_flux_integral( time_skip, basis_valR, quad_wts_st, Fq) # Compute additional boundary flux integrals for diffusion terms resL_diff = self.calculate_boundary_flux_integral_sum( time_skip, faces_to_basis_ref_gradL[faceL_IDs], quad_wts_st, FL_phys) resR_diff = self.calculate_boundary_flux_integral_sum( time_skip, faces_to_basis_ref_gradR[faceR_IDs], quad_wts_st, FR_phys) return resL, resR, resL_diff, resR_diff # [nif, nb, ns]
def get_element_residual(self, Uc, res_elem): physics = self.physics mesh = self.mesh ns = physics.NUM_STATE_VARS ndims = physics.NDIMS elem_helpers = self.elem_helpers elem_helpers_st = self.elem_helpers_st nq_tile_constant = elem_helpers_st.nq_tile_constant quad_wts = elem_helpers.quad_wts quad_wts_st = elem_helpers_st.quad_wts quad_pts_st = elem_helpers_st.quad_pts basis_val_st = elem_helpers_st.basis_val basis_phys_grad_elems = elem_helpers.basis_phys_grad_elems basis_phys_grad_elems_st = elem_helpers_st.basis_phys_grad_elems basis_ref_grad_st = elem_helpers_st.basis_ref_grad basis_ref_grad = elem_helpers.basis_ref_grad ijac_elems = elem_helpers.ijac_elems x_elems = elem_helpers.x_elems x_elems_st = elem_helpers_st.x_elems nq = quad_wts.shape[0] nq_st = quad_wts_st.shape[0] fluxes = self.params["ConvFluxSwitch"] sources = self.params["SourceSwitch"] # Interpolate state at quad points Uq = helpers.evaluate_state(Uc, basis_val_st) # [ne, nq_st, ns] # Interpolate gradient of state at quad points # gUq = solver_tools.evaluate_gradient(nq_tile_constant, Uc, # basis_phys_grad_elems) gUq_ref = self.evaluate_gradient(Uc, basis_ref_grad_st[:, :, :-1]) ijac_elems_st = np.tile(ijac_elems, (1, nq_tile_constant, 1, 1)) gUq = self.ref_to_phys_grad(ijac_elems_st, gUq_ref) if self.verbose: # Get min and max of state variables for reporting self.get_min_max_state(Uq) if fluxes: # Evaluate the flux volume integral. Fq = physics.get_conv_flux_interior(Uq)[0] # [ne, nq, ns, ndims] if physics.diff_flux_fcn: # Evaluate the diffusion flux Fq -= physics.get_diff_flux_interior(Uq, gUq) # [ne, nq, ns, ndims] res_elem += solver_tools.calculate_volume_flux_integral( self, elem_helpers, elem_helpers_st, Fq) # [ne, nb, ns] if sources: # Evaluate the source term integral # Get array in physical time from ref time t, elem_helpers_st.basis_time = solver_tools.ref_to_phys_time( mesh, self.time, self.stepper.dt, quad_pts_st[:, -1:], elem_helpers_st.basis_time) # Evaluate the source term at the quadrature points Sq = elem_helpers_st.Sq Sq[:] = 0. # [ne, nq, sr, ndims] Sq = physics.eval_source_terms(Uq, x_elems_st, t, Sq) res_elem += solver_tools.calculate_source_term_integral( elem_helpers, elem_helpers_st, Sq) # [ne, nb, ns] return res_elem # [ne, nb, ns]
def calculate_artificial_viscosity_integral(physics, elem_helpers, Uc, av_param, p): ''' Calculates the artificial viscosity volume integral, given in: Hartmann, R. and Leicht, T, "Higher order and adaptive DG methods for compressible flows", p. 92, 2013. Inputs: ------- physics: physics object elem_helpers: helpers defined in ElemHelpers Uc: state coefficients of each element av_param: artificial viscosity parameter p: solution basis order Outputs: -------- res_elem: artificial viscosity residual array for all elements [ne, nb, ns] ''' # Unpack quad_wts = elem_helpers.quad_wts # [nq, 1] basis_phys_grad_elems = elem_helpers.basis_phys_grad_elems # [ne, nq, nb, dim] basis_val = elem_helpers.basis_val # [nq, nb] djac_elems = elem_helpers.djac_elems # [ne, nq, 1] vol_elems = elem_helpers.vol_elems # [ne] ndims = basis_phys_grad_elems.shape[3] # Evaluate solution at quadrature points Uq = helpers.evaluate_state(Uc, basis_val) # Evaluate solution gradient at quadrature points grad_Uq = np.einsum('ijnl, ink -> ijkl', basis_phys_grad_elems, Uc) # Compute pressure pressure = physics.compute_additional_variable( "Pressure", Uq, flag_non_physical=False)[:, :, 0] # For Euler equations, use pressure as the smoothness variable if physics.PHYSICS_TYPE == general.PhysicsType.Euler: # Compute pressure gradient grad_p = physics.compute_pressure_gradient(Uq, grad_Uq) # Compute its magnitude norm_grad_p = np.linalg.norm(grad_p, axis=2) # Calculate smoothness switch f = norm_grad_p / (pressure + 1e-12) # For everything else, use the first solution variable else: U0 = Uq[:, :, 0] grad_U0 = grad_Uq[:, :, 0] norm_grad_U0 = np.linalg.norm(grad_U0, axis=2) # Calculate smoothness switch f = norm_grad_U0 / (U0 + 1e-12) # Compute s_k s = np.zeros((Uc.shape[0], ndims)) # Loop over dimensions for k in range(ndims): # Loop over number of faces per element for i in range(elem_helpers.normals_elems.shape[1]): # Integrate normals s[:, k] += np.einsum('jx, ij -> i', elem_helpers.face_quad_wts, np.abs(elem_helpers.normals_elems[:, i, :, k])) s[:, k] = 2 * vol_elems / s[:, k] # Compute h_k (the length scale in the kth direction) h = np.empty_like(s) # Loop over dimensions for k in range(ndims): h[:, k] = s[:, k] * (vol_elems / np.prod(s, axis=1))**(1 / 3) # Scale with polynomial order h_tilde = h / (p + 1) # Compute dissipation scaling epsilon = av_param * np.einsum('ij, il -> ijl', f, h_tilde**3) # Calculate integral, with state coeffs factored out integral = np.einsum('ijm, ijpm, ijnm, jx, ijx -> ipn', epsilon, basis_phys_grad_elems, basis_phys_grad_elems, quad_wts, djac_elems) # Calculate residual res_elem = np.einsum('ipn, ipk -> ink', integral, Uc) return res_elem # [ne, nb, ns]
def minmod_shock_indicator(limiter, solver, Uc): ''' TVB modified Minmod calculation used to detect shocks Inputs: ------- limiter: limiter object solver: solver object Uc: state coefficients [ne, nb, ns] Outputs: -------- shock_elems: array with IDs of elements flagged for limiting ''' # Unpack physics = solver.physics mesh = solver.mesh tvb_param = limiter.tvb_param ns = physics.NUM_STATE_VARS elem_helpers = solver.elem_helpers int_face_helpers = solver.int_face_helpers elemP_IDs = limiter.elemP_IDs elemM_IDs = limiter.elemM_IDs djacs = limiter.djac_elems djacP = limiter.djac_elems[elemP_IDs] djacM = limiter.djac_elems[elemM_IDs] UcP = solver.state_coeffs[elemP_IDs] UcM = solver.state_coeffs[elemM_IDs] # Interpolate state at quadrature points over element and on faces U_elem_faces = helpers.evaluate_state(Uc, limiter.basis_val_elem_faces, skip_interp=solver.basis.skip_interp) nq_elem = limiter.quad_wts_elem.shape[0] U_elem = U_elem_faces[:, :nq_elem, :] U_face = U_elem_faces[:, nq_elem:, :] if solver.basis.skip_interp is True: U_face = np.zeros([U_elem.shape[0], 2, ns]) U_face[:, 0, :] = U_elem[:, 0, :] U_face[:, 1, :] = U_elem[:, -1, :] # Average value of states U_bar = helpers.get_element_mean(U_elem, limiter.quad_wts_elem, djacs, limiter.elem_vols) # UcP neighbor evaluated at quadrature points Up_elem = helpers.evaluate_state(UcP, elem_helpers.basis_val, skip_interp=solver.basis.skip_interp) # Average value of state Up_bar = helpers.get_element_mean(Up_elem, limiter.quad_wts_elem, djacP, limiter.elem_vols[elemP_IDs]) # UcM neighbor evaluated at quadrature points Um_elem = helpers.evaluate_state(UcM, elem_helpers.basis_val, skip_interp=solver.basis.skip_interp) # Average value of state Um_bar = helpers.get_element_mean(Um_elem, limiter.quad_wts_elem, djacM, limiter.elem_vols[elemM_IDs]) # Unpack the left and right eigenvector matrices right_eigen = limiter.right_eigen left_eigen = limiter.left_eigen # Store the polynomial coeff values for Up, Um, and U. limiter.U_elem = Uc limiter.Up_elem = UcP limiter.Um_elem = UcM # Store the average values for Up, Um, and U. limiter.U_bar = U_bar limiter.Up_bar = Up_bar limiter.Um_bar = Um_bar U_tilde = (U_face[:, 1, :] - U_bar[:, 0, :]).reshape(U_bar.shape) U_dtilde = (U_bar[:, 0, :] - U_face[:, 0, :]).reshape(U_bar.shape) deltaP_u_bar = Up_bar - U_bar deltaM_u_bar = U_bar - Um_bar aj = np.zeros([U_tilde.shape[0], 3, ns]) aj[:, 0, :] = U_tilde[:, 0, :] aj[:, 1, :] = deltaP_u_bar[:, 0, :] aj[:, 2, :] = deltaM_u_bar[:, 0, :] u_tilde_mod = minmod(aj) tvb = np.where(np.abs(aj[:, 0, :]) <= tvb_param * \ limiter.elem_vols[0]**2)[0] u_tilde_mod[tvb, 0] = aj[tvb, 0, :] aj = np.zeros([U_dtilde.shape[0], 3, ns]) aj[:, 0, :] = U_dtilde[:, 0, :] aj[:, 1, :] = deltaP_u_bar[:, 0, :] aj[:, 2, :] = deltaM_u_bar[:, 0, :] u_dtilde_mod = minmod(aj) tvd = np.where(np.abs(aj[:, 0, :]) <= tvb_param * \ limiter.elem_vols[0]**2)[0] u_dtilde_mod[tvd, 0] = aj[tvd, 0, :] check1 = u_tilde_mod - U_tilde check2 = u_dtilde_mod - U_dtilde shock_elems = np.where((np.abs(check1[:, :, 0]) > 1.e-12) | (np.abs(check2[:, :, 0]) > 1.e-12))[0] # print(shock_elems) return shock_elems
def limit_solution(self, solver, Uc): # Unpack physics = solver.physics elem_helpers = solver.elem_helpers int_face_helpers = solver.int_face_helpers basis = solver.basis djac = self.djac_elems # Interpolate state at quadrature points over element and on faces U_elem_faces = helpers.evaluate_state(Uc, self.basis_val_elem_faces, skip_interp=basis.skip_interp) nq_elem = self.quad_wts_elem.shape[0] U_elem = U_elem_faces[:, :nq_elem, :] # Average value of state U_bar = helpers.get_element_mean(U_elem, self.quad_wts_elem, djac, self.elem_vols) ne = self.elem_vols.shape[0] # Density and pressure from averaged state rho_bar = physics.compute_variable(self.var_name1, U_bar) p_bar = physics.compute_variable(self.var_name2, U_bar) rhoY_bar = physics.compute_variable(self.var_name3, U_bar) if np.any(rho_bar < 0.) or np.any(p_bar < 0.) or np.any(rhoY_bar < 0.): raise errors.NotPhysicalError # Ignore divide-by-zero np.seterr(divide='ignore') ''' Limit density ''' # Compute density rho_elem_faces = physics.compute_variable(self.var_name1, U_elem_faces) # Check if limiting is needed theta = np.abs((rho_bar - POS_TOL) / (rho_bar - rho_elem_faces)) # Truncate theta1; otherwise, can get noticeably different # results across machines, possibly due to poor conditioning in its # calculation theta1 = trunc(np.minimum(1., np.min(theta, axis=1))) irho = physics.get_state_index(self.var_name1) # Get IDs of elements that need limiting elem_IDs = np.where(theta1 < 1.)[0] # Modify density coefficients if basis.MODAL_OR_NODAL == general.ModalOrNodal.Nodal: Uc[elem_IDs, :, irho] = theta1[elem_IDs]*Uc[elem_IDs, :, irho] \ + (1. - theta1[elem_IDs])*rho_bar[elem_IDs, 0] elif basis.MODAL_OR_NODAL == general.ModalOrNodal.Modal: Uc[elem_IDs, :, irho] *= theta1[elem_IDs] Uc[elem_IDs, 0, irho] += (1. - theta1[elem_IDs, 0]) * rho_bar[elem_IDs, 0, 0] else: raise NotImplementedError if np.any(theta1 < 1.): # Intermediate limited solution U_elem_faces = helpers.evaluate_state( Uc, self.basis_val_elem_faces, skip_interp=basis.skip_interp) ''' Limit mass fraction ''' rhoY_elem_faces = physics.compute_variable(self.var_name3, U_elem_faces) theta = np.abs(rhoY_bar / (rhoY_bar - rhoY_elem_faces + POS_TOL)) # Truncate theta2; otherwise, can get noticeably different # results across machines, possibly due to poor conditioning in its # calculation theta2 = trunc(np.minimum(1., np.amin(theta, axis=1))) irhoY = physics.get_state_index(self.var_name3) # Get IDs of elements that need limiting elem_IDs = np.where(theta2 < 1.)[0] # Modify density coefficients if basis.MODAL_OR_NODAL == general.ModalOrNodal.Nodal: Uc[elem_IDs, :, irhoY] = theta2[elem_IDs] * Uc[elem_IDs, :, irhoY] + ( 1. - theta2[elem_IDs]) * rho_bar[elem_IDs, 0] elif basis.MODAL_OR_NODAL == general.ModalOrNodal.Modal: Uc[elem_IDs, :, irhoY] *= theta2[elem_IDs] Uc[elem_IDs, 0, irhoY] += (1. - theta2[elem_IDs, 0]) * rho_bar[elem_IDs, 0, 0] else: raise NotImplementedError if np.any(theta2 < 1.): U_elem_faces = helpers.evaluate_state( Uc, self.basis_val_elem_faces, skip_interp=basis.skip_interp) ''' Limit pressure ''' # Compute pressure at quadrature points p_elem_faces = physics.compute_variable(self.var_name2, U_elem_faces) theta[:] = 1. # Indices where pressure is negative negative_p_indices = np.where(p_elem_faces < 0.) elem_IDs = negative_p_indices[0] i_neg_p = negative_p_indices[1] theta[elem_IDs, i_neg_p] = p_bar[elem_IDs, :, 0] / ( p_bar[elem_IDs, :, 0] - p_elem_faces[elem_IDs, i_neg_p]) # Truncate theta3; otherwise, can get noticeably different # results across machines, possibly due to poor conditioning in its # calculation theta3 = trunc(np.min(theta, axis=1)) # Get IDs of elements that need limiting elem_IDs = np.where(theta3 < 1.)[0] # Modify coefficients if basis.MODAL_OR_NODAL == general.ModalOrNodal.Nodal: Uc[elem_IDs] = np.einsum( 'im, ijk -> ijk', theta3[elem_IDs], Uc[elem_IDs]) + np.einsum( 'im, ijk -> ijk', 1 - theta3[elem_IDs], U_bar[elem_IDs]) elif basis.MODAL_OR_NODAL == general.ModalOrNodal.Modal: Uc[elem_IDs] *= np.expand_dims(theta3[elem_IDs], axis=2) Uc[elem_IDs, 0] += np.einsum('im, ijk -> ik', 1 - theta3[elem_IDs], U_bar[elem_IDs]) else: raise NotImplementedError np.seterr(divide='warn') return Uc # [ne, nq, ns]
def predictor_elem_implicit(solver, dt, W, U_pred): ''' Calculates the predicted solution state for the ADER-DG method using a nonlinear solve of the weak form of the DG discretization in time. This function applies the source term implicitly. Appropriate for stiff systems of equations. The implicit solve utilizes the Sylvester equation of the form: AX + XB = C This is a built-in function via the scipy.linalg library. Inputs: ------- solver: solver object dt: time step W: previous time step solution in space only [ne, nb, ns] Outputs: -------- U_pred: predicted solution in space-time [ne, nb_st, ns] ''' # Unpack threshold = solver.params["PredictorThreshold"] physics = solver.physics source_terms = physics.source_terms ns = physics.NUM_STATE_VARS mesh = solver.mesh basis = solver.basis basis_st = solver.basis_st order = solver.order elem_helpers = solver.elem_helpers ader_helpers = solver.ader_helpers quad_wts = elem_helpers.quad_wts basis_val = elem_helpers.basis_val djac_elems = elem_helpers.djac_elems x_elems = elem_helpers.x_elems FTR = ader_helpers.FTR iMM = ader_helpers.iMM SMS_elems = ader_helpers.SMS_elems K = ader_helpers.K # Initialize space-time coefficients U_pred, U_bar = solver.get_spacetime_guess(solver, W, U_pred, dt=dt) # Get physical average for testing purposes vol_elems = elem_helpers.vol_elems Wq = helpers.evaluate_state(W, basis_val, skip_interp=basis.skip_interp) W_bar = helpers.get_element_mean(Wq, quad_wts, djac_elems, vol_elems) # Only evaluate Jacobian for stiff sources temp_sources = physics.source_terms.copy() physics.source_terms = physics.implicit_sources.copy() # Calculate the source term Jacobian using average state Sjac = np.zeros([U_pred.shape[0], 1, ns, ns]) Sjac = physics.eval_source_term_jacobians(W_bar, x_elems, solver.time, Sjac) Sjac = Sjac[:, 0, :, :] # Set all sources for source_coeffs calculation physics.source_terms = temp_sources.copy() # Calculate the source and flux coefficients with initial guess source_coeffs = solver.source_coefficients(dt, order, basis_st, U_pred) flux_coeffs = solver.flux_coefficients(dt, order, basis_st, U_pred) # Iterate using a nonlinear Sylvester solver for the # updated space-time coefficients. Solves for X in the form: # AX + XB = C # Update: We now transform AX+XB=C into KX=C using kronecker # products. niter = 10000 A = np.matmul(iMM, K) U_pred_new = np.zeros_like(U_pred) for i in range(niter): B = -1.0 * dt * Sjac.transpose(0, 2, 1) Q = np.einsum('jk, ikm -> ijm', FTR, W) - np.einsum( 'ijkl, ikml -> ijm', SMS_elems, flux_coeffs) C = source_coeffs - dt*np.matmul(U_pred[:], Sjac[:].transpose(0, 2, 1)) + \ np.einsum('jk, ikl -> ijl', iMM, Q) # Build identity matrices for kronecker procucts I2 = np.eye(A.shape[1]) I1 = np.eye(B.shape[1]) for ie in range(U_pred.shape[0]): # Conduct kronecker products to transfrom Ax+xB=C system to Ax=b kronecker = np.kron(I1, A) + np.kron(B[ie, :, :].transpose(), I2) U_pred_hold = np.linalg.solve(kronecker, C[ie, :, :].transpose().reshape(-1)) U_pred_new[ie, :, :] = U_pred_hold.reshape( U_pred.shape[2], U_pred.shape[1]).transpose() # Note: Previous implementaion used sylvester solve directly. # This still requires further testing to determine which is # more efficient. # U_pred_new[ie, :, :] = solve_sylvester(A, B[ie, :, :], # C[ie, :, :]) # We check when the coefficients are no longer changing. # This can lead to differences between NODAL and MODAL solutions. # This could be resolved by evaluating at the quadrature points # and comparing the error between those values. err = U_pred_new - U_pred if (np.amax(np.abs(err)) < threshold): print("Predictor iterations: ", i) U_pred = np.copy(U_pred_new) break U_pred = np.copy(U_pred_new) source_coeffs = solver.source_coefficients(dt, order, basis_st, U_pred) flux_coeffs = solver.flux_coefficients(dt, order, basis_st, U_pred) # Recalculate jacobian for subiterations (Default is OFF) solver.recalculate_jacobian(solver, U_pred, dt, Sjac) if i == niter - 1: print('Sub-iterations not converging', np.amax(np.abs(err))) return U_pred #_update # [ne, nb_st, ns]
def get_boundary_face_residual(self, bgroup, face_ID, Uc, resB): # Unpack mesh = self.mesh ndims = mesh.ndims physics = self.physics ns = physics.NUM_STATE_VARS fluxes = self.params["ConvFluxSwitch"] bgroup_num = bgroup.number nq_t = self.elem_helpers_st.nq_tile_constant time_skip = self.elem_helpers_st.time_skip time_tile = self.elem_helpers_st.time_tile bface_helpers = self.bface_helpers bface_helpers_st = self.bface_helpers_st quad_wts_st = bface_helpers_st.quad_wts faces_to_xref_st = bface_helpers_st.faces_to_xref faces_to_basis = bface_helpers.faces_to_basis faces_to_basis_st = bface_helpers_st.faces_to_basis faces_to_basis_ref_grad_st = bface_helpers_st.faces_to_basis_ref_grad faces_to_basis_ref_grad = bface_helpers.faces_to_basis_ref_grad normals_bgroups = bface_helpers.normals_bgroups x_bgroups = bface_helpers.x_bgroups ijac_bgroups = bface_helpers.ijac_bgroups face_ID = bface_helpers.face_IDs[bgroup_num] face_ID_st = bface_helpers_st.face_IDs_st[bgroup_num] basis_val = faces_to_basis[face_ID] basis_val_st = faces_to_basis_st[face_ID_st] basis_ref_grad_st = faces_to_basis_ref_grad_st[face_ID_st] basis_ref_grad = faces_to_basis_ref_grad[face_ID] xref_st = faces_to_xref_st[face_ID_st] ijac = ijac_bgroups[bgroup_num] nq_st = quad_wts_st.shape[0] # Get array in physical time from ref time t, self.elem_helpers_st.basis_time = solver_tools.ref_to_phys_time( mesh, self.time, self.stepper.dt, xref_st[:, :, -1:], self.elem_helpers_st.basis_time) ''' Define time slice to make time arrays one dimensional cases even in the 2D case. ''' # Build tiled time array. Removes unnecessary extra data. time_slice = slice(0, t.shape[1], time_skip) time = t[:, time_slice] time_t = np.tile(time, (1, time_tile, 1)) # Interpolate state at quadrature points UqI = helpers.evaluate_state(Uc, basis_val_st) # [nbf, nq, ns] # Interpolate gradient of state at quad points gUq_ref = self.evaluate_gradient(Uc, basis_ref_grad_st[:, :, :, :-1]) # import code; code.interact(local=locals()) ijac_st = np.tile(ijac, (1, time_skip, 1, 1)) # Make ref gradient of state the physical gradient gUq = self.ref_to_phys_grad(ijac_st, gUq_ref) # Unpack normals and x on boundary faces normals = normals_bgroups[bgroup_num] x = x_bgroups[bgroup_num] # Tile normals and x to prepare for looping over elements in time normals = np.tile(normals, (time_tile, 1)) x = np.tile(x, (time_tile, 1)) # Get boundary state BC = physics.BCs[bgroup.name] nbf = UqI.shape[0] Fq = np.zeros([nbf, nq_st, ns]) FqB = np.zeros([nbf, nq_st, ns, ndims]) # Need to allocate data for gradient when not using diffusion if not physics.diff_flux_fcn: gUq = np.zeros([Uc.shape[0], nq_st, ns, ndims]) # Compute any additional helpers for diffusive flux fcn if physics.diff_flux_fcn: physics.diff_flux_fcn.compute_bface_helpers(self, bgroup_num) if fluxes: # Loop over time to apply BC at each temporal quadrature point for i in range(t.shape[1]): # Need time to be constant not an array to work with # get_boundary_flux appropriately t_ = time_t[:, i][0, 0] x_ = x[:, i].reshape([nbf, 1, ndims]) normals_ = normals[:, i].reshape([nbf, 1, ndims]) Fq_hold, FqB_hold = BC.get_boundary_flux( physics, UqI[:, i, :].reshape([nbf, 1, ns]), normals_, x_, t_, gUq=gUq[:, i, :, :].reshape([nbf, 1, ns, ndims])) if not physics.diff_flux_fcn: FqB_hold = np.zeros([nbf, ns, ndims]) Fq[:, i, :] = Fq_hold.reshape([nbf, ns]) FqB[:, i, :, :] = FqB_hold.reshape([nbf, ns, ndims]) FqB_phys = self.ref_to_phys_grad(ijac_st, FqB) resB = solver_tools.calculate_boundary_flux_integral( time_skip, basis_val, quad_wts_st, Fq) # [nbf, nb, ns] resB -= self.calculate_boundary_flux_integral_sum( time_skip, basis_ref_grad, quad_wts_st, FqB_phys) return resB # [nbf, nb, ns]
def get_boundary_info(solver, mesh, physics, bname, var_name, dot_normal_with_vec=False, vec=0., integrate=True, plot_vs_x=False, plot_vs_y=False, ylabel=None, fmt='k-', legend_label=None, **kwargs): ''' This function integrates and/or plots a given quantity over a specific boundary. Inputs: ------- mesh: mesh object physics: physics object solver: solver object bname: name of boundary var_name: name of variable to compute dot_normal_with_vec: if dot_normal_with_vec is True, will multiply var by the dot product between the outward-pointing unit normal vector and vec vec: vector to dot with normal (see above) [ndims] integrate: if True, will integrate variable over boundary plot_vs_x: if True, will plot variable vs. x plot_vs_y: if True, will plot variable vs. y (lower priority than plot_vs_x) ylabel: y-axis label for figure fmt: format string for plotting, e.g. "bo" for blue circles legend_label: legend label kwargs: keyword arguments (see plot_defs.finalize_plot) ''' if mesh.ndims != 2: raise errors.IncompatibleError # Extract boundary group boundary_group = mesh.boundary_groups[bname] boundary_num = boundary_group.number # Extract helpers bface_helpers = solver.bface_helpers quad_pts = bface_helpers.quad_pts quad_wts = bface_helpers.quad_wts faces_to_basis = bface_helpers.faces_to_basis normals_bgroups = bface_helpers.normals_bgroups x_bgroups = bface_helpers.x_bgroups nq = quad_wts.shape[0] # For plotting plot = True if plot_vs_x: xlabel = "x" d = 0 elif plot_vs_y: xlabel = "y" d = 1 else: plot = False if plot: bvalues = np.zeros([boundary_group.num_boundary_faces, nq]) # [num_boundary_faces, nq] bpoints = x_bgroups[boundary_num][:, :, d].flatten() # [num_boundary_faces, nq, ndims] integ_val = 0. if dot_normal_with_vec: # Convert to numpy array vec = np.array(vec) vec.shape = 1, 2 # Extract elem_ID = bface_helpers.elem_IDs[boundary_num] face_ID = bface_helpers.face_IDs[boundary_num] basis_val = faces_to_basis[face_ID] # Interpolate state at quad points Uq = helpers.evaluate_state(solver.state_coeffs[elem_ID], basis_val) # Get requested variable varq = physics.compute_variable(var_name, Uq) # [nf, nq, 1] # Normals normals = normals_bgroups[boundary_num] # [nf, nq, ndims] jac = np.linalg.norm(normals, axis=2, keepdims=True) # [nf, nq, 1] # If requested, account for normal and dot with input dir if dot_normal_with_vec: varq *= np.sum(normals / jac * vec, axis=2, keepdims=True) # Integrate and sum over faces if integrate: integ_val = np.sum(np.sum(varq * jac * quad_wts, axis=1), axis=0) if plot: bvalues = varq[:, :, 0] if integrate: print("Boundary integral = %g" % (integ_val)) # Plot if requested if plot: plt.figure() bvalues = bvalues.flatten() ylabel = plot_defs.get_ylabel(physics, var_name, ylabel) plot_defs.plot_1D(physics, bpoints, bvalues, ylabel, fmt, legend_label) plot_defs.finalize_plot(xlabel=xlabel, **kwargs)
solution = [] importlib.reload(model_psr) # Run the simulation os.system("quail " + filename) # Access and process the final time step final_file = model_psr.prefix + '_final.pkl' # Read data file solver = readwritedatafiles.read_data_file(final_file) Uc = solver.state_coeffs basis_val = solver.elem_helpers.basis_val Uq = helpers.evaluate_state(Uc, basis_val) solution.append(Uq[0, 0, 0]) # Assumes 0D order = model_psr.order file_out = f'convergence_testing/{scheme_name}/{j}.pkl' write_file(file_out, solution) # text from previous case if j + 1 < dt.shape[0]: text_to_search = f'timestep = {dt[j]}' replacement_text = f'timestep = {dt[j+1]}' search_and_replace(filename, text_to_search, replacement_text) time.sleep(1)
def flux_coefficients(self, dt, order, basis, Up): ''' Calculates the polynomial coefficients for the flux functions in ADER-DG Inputs: ------- dt: time step size order: solution order basis: basis object Up: coefficients of predicted solution [ne, nb_st, ns] Outputs: -------- F: polynomial coefficients of the flux function [ne, nb_st, ns, ndims] ''' # Unpack physics = self.physics mesh = self.mesh ns = physics.NUM_STATE_VARS ndims = physics.NDIMS params = self.params InterpolateFluxADER = params["InterpolateFluxADER"] elem_helpers = self.elem_helpers elem_helpers_st = self.elem_helpers_st djac_elems = elem_helpers.djac_elems basis_ref_grad_st = elem_helpers_st.basis_ref_grad ijac_elems = elem_helpers.ijac_elems ader_helpers = self.ader_helpers ijac_nodes = ader_helpers.ijac_elems nq_t = self.elem_helpers_st.nq_tile_constant # Allocate flux coefficients F = np.zeros( [Up.shape[0], basis.get_num_basis_coeff(order), ns, ndims], dtype=Up.dtype) # Flux coefficient calc from interpolation or L2-projection if InterpolateFluxADER: # Calculate flux Fq = physics.get_conv_flux_interior(Up)[0] if physics.diff_flux_fcn: # Calculate the gradient of the state gUp_ref = solver_tools.get_spacetime_gradient(self, Up) gUp = self.ref_to_phys_grad(ijac_nodes, gUp_ref) Fq -= physics.get_diff_flux_interior(Up, gUp) # Interpolate flux coefficient to nodes dg_tools.interpolate_to_nodes(Fq, F) else: # Unpack for L2-projection basis_val_st = elem_helpers_st.basis_val quad_wts_st = elem_helpers_st.quad_wts quad_wts = elem_helpers.quad_wts quad_pts_st = elem_helpers_st.quad_pts quad_pts = elem_helpers.quad_pts nq_st = quad_wts_st.shape[0] nq = quad_wts.shape[0] iMM_elems = ader_helpers.iMM_elems # Interpolate state at quadrature points Uq = helpers.evaluate_state(Up, basis_val_st) # Interpolate gradient of the state gUq_ref = self.evaluate_gradient(Up, basis_ref_grad_st[:, :, :-1]) ijac_elems_st = np.tile(ijac_elems, (1, nq_t, 1, 1)) gUq = self.ref_to_phys_grad(ijac_elems_st, gUq_ref) # Evaluate the inviscid flux Fq = physics.get_conv_flux_interior(Uq)[0] # Evaluate the diffusive flux if physics.diff_flux_fcn: Fq -= physics.get_diff_flux_interior(Uq, gUq) # Project Fq to the space-time basis coefficients for d in range(ndims): solver_tools.L2_projection(mesh, iMM_elems, basis, quad_pts_st, quad_wts_st, np.tile(djac_elems, (nq_t, 1)), Fq[:, :, :, d], F[:, :, :, d]) return F * dt / 2.0 # [ne, nb_st, ns, ndims]
def limit_solution(self, solver, Uc): # Unpack ns = solver.physics.NUM_STATE_VARS physics = solver.physics mesh = solver.mesh elem_helpers = solver.elem_helpers int_face_helpers = solver.int_face_helpers vols = self.elem_vols basis_phys_grads = elem_helpers.basis_phys_grad_elems quad_wts = elem_helpers.quad_wts elemP_IDs = self.elemP_IDs elemM_IDs = self.elemM_IDs djacs = self.djac_elems djacP = self.djac_elems[elemP_IDs] djacM = self.djac_elems[elemM_IDs] # Interpolate state at quadrature points over element and on faces U_elem_faces = helpers.evaluate_state( Uc, self.basis_val_elem_faces, skip_interp=solver.basis.skip_interp) nq_elem = self.quad_wts_elem.shape[0] U_elem = U_elem_faces[:, :nq_elem, :] U_face = U_elem_faces[:, nq_elem:, :] # Average value of states U_bar = helpers.get_element_mean(U_elem, self.quad_wts_elem, djacs, vols) # Calculate the eigenvectors if available (they are pre-allocated in # precompute_helpers) get_eigenvector_function = getattr(physics, "get_conv_eigenvectors", None) if callable(get_eigenvector_function): self.right_eigen, self.left_eigen = \ physics.get_conv_eigenvectors(U_bar) Vc = np.einsum('elij, elj -> eli', self.left_eigen, Uc) # Determine if the elements requires limiting shock_indicated = self.shock_indicator(self, solver, Uc) # Unpack limiter info from shock indicator p0 = np.einsum('ebij, elj -> eli', self.left_eigen, self.Um_elem) p1 = np.einsum('ebij, elj -> eli', self.left_eigen, self.U_elem) p2 = np.einsum('ebij, elj -> eli', self.left_eigen, self.Up_elem) p0_bar = np.einsum('ebij, elj -> eli', self.left_eigen, self.Um_bar) p1_bar = np.einsum('ebij, elj -> eli', self.left_eigen, self.U_bar) p2_bar = np.einsum('ebij, elj -> eli', self.left_eigen, self.Up_bar) # Check basis type and adjust coefficients to maintain element # p1's average value. if solver.basis.MODAL_OR_NODAL == general.ModalOrNodal.Modal: p0_tilde = p0 p2_tilde = p2 p0_tilde[:, 0, :] = p1_bar[:, 0, :] p2_tilde[:, 0, :] = p1_bar[:, 0, :] else: p0_tilde = p0 - p0_bar + p1_bar p2_tilde = p2 - p2_bar + p1_bar # Currently only implemented up to P2 if solver.order > 2: raise NotImplementedError # Allocate weno_wts weno_wts = np.zeros([Uc.shape[0], 3, ns]) # Calculate non-linear weights weno_wts[:, 0, :] = self.get_nonlinearwts(solver.order, p0_tilde, 0.001, basis_phys_grads, quad_wts, vols, djacM) weno_wts[:, 1, :] = self.get_nonlinearwts(solver.order, p1, 0.998, basis_phys_grads, quad_wts, vols, djacs) weno_wts[:, 2, :] = self.get_nonlinearwts(solver.order, p2_tilde, 0.001, basis_phys_grads, quad_wts, vols, djacP) # Normalize the weights normal_wts = weno_wts / np.sum(weno_wts, axis=1).reshape( [Uc.shape[0], 1, ns]) # Update state_coeffs for indicated elements Vc[shock_indicated] = \ np.einsum('ik, ijk -> ijk', normal_wts[shock_indicated, 0], p0_tilde[shock_indicated]) + \ np.einsum('ik, ijk -> ijk', normal_wts[shock_indicated, 1], p1[shock_indicated]) + \ np.einsum('ik, ijk -> ijk', normal_wts[shock_indicated, 2], p2_tilde[shock_indicated]) # Transform characteristic variables back to physical. Uc[shock_indicated] = np.einsum('ebij, elj -> eli', self.right_eigen[shock_indicated], Vc[shock_indicated]) return Uc # [ne, nq, ns]
def source_coefficients(self, dt, order, basis, Up): ''' Calculates the polynomial coefficients for the source functions in ADER-DG Inputs: ------- elem_ID: element index dt: time step size order: solution order basis: basis object Up: coefficients of predicted solution [ne, nb_st, ns] Outputs: -------- S: polynomical coefficients of the flux function [ne, nb_st, ns] ''' # Unpack mesh = self.mesh ndims = mesh.ndims physics = self.physics ns = physics.NUM_STATE_VARS params = self.params elem_helpers = self.elem_helpers elem_helpers_st = self.elem_helpers_st djac_elems = elem_helpers.djac_elems x_elems = elem_helpers.x_elems nq_t = self.elem_helpers_st.nq_tile_constant ader_helpers = self.ader_helpers x_elems_ader = ader_helpers.x_elems InterpolateFluxADER = params["InterpolateFluxADER"] if InterpolateFluxADER: xnodes = basis.get_nodes(order) nb = xnodes.shape[0] # Get array in physical time from ref time t, elem_helpers_st.basis_time = solver_tools.ref_to_phys_time( mesh, self.time, self.stepper.dt, xnodes[:, -1:], elem_helpers_st.basis_time) # Evaluate the source term at the quadrature points Sq = np.zeros([Up.shape[0], t.shape[0], ns]) S = np.zeros_like(Sq) Sq = physics.eval_source_terms(Up, x_elems_ader, t, Sq) # Interpolate source coefficient to nodes dg_tools.interpolate_to_nodes(Sq, S) else: # Unpack for L2-projection ader_helpers = self.ader_helpers basis_val_st = elem_helpers_st.basis_val nb_st = basis_val_st.shape[1] quad_wts_st = elem_helpers_st.quad_wts quad_wts = elem_helpers.quad_wts quad_pts_st = elem_helpers_st.quad_pts nq_st = quad_wts_st.shape[0] nq = quad_wts.shape[0] iMM_elems = ader_helpers.iMM_elems # Interpolate state at quadrature points Uq = helpers.evaluate_state(Up, basis_val_st) x_elems_st = np.tile(x_elems, [1, nq_t, 1]) # Get array in physical time from ref time t = np.zeros([nq_st, ndims]) t, elem_helpers_st.basis_time = solver_tools.ref_to_phys_time( mesh, self.time, self.stepper.dt, quad_pts_st[:, -1:], elem_helpers_st.basis_time) # Evaluate the source term at the quadrature points Sq = np.zeros_like(Uq) S = np.zeros([Uq.shape[0], nb_st, ns]) Sq = physics.eval_source_terms(Uq, x_elems_st, t, Sq) # [ne, nq, ns, ndims] # Project Sq to the space-time basis coefficients solver_tools.L2_projection(mesh, iMM_elems, basis, quad_pts_st, quad_wts_st, np.tile(djac_elems, (nq_t, 1)), Sq, S) return S * dt / 2.0 # [ne, nb_st, ns]