Exemple #1
0
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]
Exemple #2
0
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()
Exemple #3
0
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
Exemple #4
0
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
Exemple #5
0
    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]
Exemple #6
0
    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]
Exemple #7
0
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
Exemple #8
0
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]
Exemple #9
0
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]
Exemple #10
0
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]
Exemple #11
0
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