def continuity_form(state, test, q, ibp=IntegrateByParts.ONCE, outflow=False): if outflow and ibp == IntegrateByParts.NEVER: raise ValueError( "outflow is True and ibp is None are incompatible options") Vu = state.spaces("HDiv") dS_ = (dS_v + dS_h) if Vu.extruded else dS ubar = Function(Vu) if ibp == IntegrateByParts.ONCE: L = -inner(grad(test), outer(q, ubar)) * dx else: L = inner(test, div(outer(q, ubar))) * dx if ibp != IntegrateByParts.NEVER: n = FacetNormal(state.mesh) un = 0.5 * (dot(ubar, n) + abs(dot(ubar, n))) L += dot(jump(test), (un('+') * q('+') - un('-') * q('-'))) * dS_ if ibp == IntegrateByParts.TWICE: L -= (inner(test('+'), dot(ubar('+'), n('+')) * q('+')) + inner(test('-'), dot(ubar('-'), n('-')) * q('-'))) * dS_ if outflow: n = FacetNormal(state.mesh) un = 0.5 * (dot(ubar, n) + abs(dot(ubar, n))) L += test * un * q * (ds_v + ds_t + ds_b) form = transporting_velocity(L, ubar) return ibp_label(transport(form, TransportEquationType.conservative), ibp)
def get_flux_form(dS, M): fluxes = (-inner(2 * avg(outer(phi, n)), avg(grad(gamma) * M)) - inner(avg(grad(phi) * M), 2 * avg(outer(gamma, n))) + mu * inner(2 * avg(outer(phi, n)), 2 * avg(outer(gamma, n) * kappa))) * dS return fluxes
def get_flux_form(dS, M): fluxes = ( -inner(2*avg(outer(q, n)), avg(grad(test)*M)) - inner(avg(grad(q)*M), 2*avg(outer(test, n))) + mu*inner(2*avg(outer(q, n)), 2*avg(outer(test, n)*kappa)) )*dS return fluxes
def horizontal_strain(u, s, h): r"""Calculate the horizontal strain rate with corrections for terrain- following coordinates""" ζ = firedrake.SpatialCoordinate(u.ufl_domain())[2] b = s - h v = -((1 - ζ) * grad_2(b) + ζ * grad_2(s)) / h du_dζ = u.dx(2) return sym(grad_2(u)) + 0.5 * (outer(du_dζ, v) + outer(v, du_dζ))
def horizontal_strain_rate(**kwargs): r"""Calculate the horizontal strain rate with corrections for terrain- following coordinates""" u, h, s = itemgetter("velocity", "surface", "thickness")(kwargs) mesh = u.ufl_domain() dim = mesh.geometric_dimension() ζ = firedrake.SpatialCoordinate(mesh)[dim - 1] b = s - h v = -((1 - ζ) * grad(b) + ζ * grad(s)) / h du_dζ = u.dx(dim - 1) return sym_grad(u) + 0.5 * (outer(du_dζ, v) + outer(v, du_dζ))
def _boundary_flux(z, h_ext, q_ext, g, boundary_ids): Z = z.function_space() n = firedrake.FacetNormal(Z.mesh()) φ, v = firedrake.TestFunctions(Z) F_hx, F_qx = _fluxes(h_ext, q_ext, g) h, q = firedrake.split(z) F_h, F_q = _fluxes(h, q, g) return 0.5 * (inner(F_hx, φ * n) + inner(F_qx, outer(v, n)) + inner( F_h, φ * n) + inner(F_q, outer(v, n))) * ds(boundary_ids)
def tensor_jump(v, n): r""" Jump term for vector functions based on the tensor product .. math:: \text{jump}(\mathbf{u}, \mathbf{n}) = (\mathbf{u}^+ \mathbf{n}^+) + (\mathbf{u}^- \mathbf{n}^-) This is the discrete equivalent of grad(u) as opposed to the vectorial UFL jump operator :meth:`ufl.jump` which represents div(u). The equivalent of nabla_grad(u) is given by tensor_jump(n, u). """ return outer(v('+'), n('+')) + outer(v('-'), n('-'))
def residual(self, test, trial, trial_lagged, fields, bcs): u_adv = trial_lagged phi = test n = self.n u = trial F = -dot(u, div(outer(phi, u_adv)))*self.dx for id, bc in bcs.items(): if 'u' in bc: u_in = bc['u'] elif 'un' in bc: u_in = bc['un'] * n # this implies u_t=0 on the inflow else: u_in = zero(self.dim) F += conditional(dot(u_adv, n) < 0, dot(phi, u_in)*dot(u_adv, n), dot(phi, u)*dot(u_adv, n)) * self.ds(id) if not (is_continuous(self.trial_space) and normal_is_continuous(u_adv)): # s=0: u.n(-)<0 => flow goes from '+' to '-' => '+' is upwind # s=1: u.n(-)>0 => flow goes from '-' to '+' => '-' is upwind s = 0.5*(sign(dot(avg(u), n('-'))) + 1.0) u_up = u('-')*s + u('+')*(1-s) F += dot(u_up, (dot(u_adv('+'), n('+'))*phi('+') + dot(u_adv('-'), n('-'))*phi('-'))) * self.dS return -F
def _fluxes(h, q, g): r"""Calculate the flux of mass and momentum for the shallow water equations""" I = firedrake.Identity(2) F_h = q F_q = outer(q, q) / h + 0.5 * g * h**2 * I return F_h, F_q
def advection_term(self, q): if self.continuity: if self.ibp == IntegrateByParts.ONCE: L = -inner(grad(self.test), outer(q, self.ubar)) * dx else: L = inner(self.test, div(outer(q, self.ubar))) * dx else: if self.ibp == IntegrateByParts.ONCE: L = -inner(div(outer(self.test, self.ubar)), q) * dx else: L = inner(outer(self.test, self.ubar), grad(q)) * dx if self.dS is not None and self.ibp != IntegrateByParts.NEVER: n = FacetNormal(self.state.mesh) un = 0.5 * (dot(self.ubar, n) + abs(dot(self.ubar, n))) L += dot(jump(self.test), (un('+') * q('+') - un('-') * q('-'))) * self.dS if self.ibp == IntegrateByParts.TWICE: L -= (inner(self.test('+'), dot(self.ubar('+'), n('+')) * q('+')) + inner(self.test('-'), dot(self.ubar('-'), n('-')) * q('-'))) * self.dS if self.outflow: n = FacetNormal(self.state.mesh) un = 0.5 * (dot(self.ubar, n) + abs(dot(self.ubar, n))) L += self.test * un * q * (ds_v + ds_t + ds_b) if self.vector_manifold: n = FacetNormal(self.state.mesh) w = self.test dS = self.dS u = q L += un('+') * inner(w('-'), n('+') + n('-')) * inner(u('+'), n('+')) * dS L += un('-') * inner(w('+'), n('+') + n('-')) * inner(u('-'), n('-')) * dS return L
def central_facet_flux(F, v): r"""Create the weak form of the central numerical flux through cell facets This numerical flux, by itself, is unstable. The full right-hand side of the problem requires an additional facet flux term for stability. Parameters ---------- F : ufl.Expr A symbolic expression for the flux v : firedrake.TestFunction A test function from the state space Returns ------- f : firedrake.Form A 1-form that discretizes the residual of the flux """ mesh = v.ufl_domain() n = FacetNormal(mesh) return inner(avg(F), outer(v('+'), n('+')) + outer(v('-'), n('-'))) * dS
def __init__(self, state, V, continuity=False): super(DGAdvection, self).__init__(state) element = V.fiat_element assert element.entity_dofs() == element.entity_closure_dofs(), "Provided space is not discontinuous" dt = state.timestepping.dt if V.extruded: surface_measure = (dS_h + dS_v) else: surface_measure = dS phi = TestFunction(V) D = TrialFunction(V) self.D1 = Function(V) self.dD = Function(V) n = FacetNormal(state.mesh) # ( dot(v, n) + |dot(v, n)| )/2.0 un = 0.5*(dot(self.ubar, n) + abs(dot(self.ubar, n))) a_mass = inner(phi,D)*dx if continuity: a_int = -inner(grad(phi), outer(D, self.ubar))*dx else: a_int = -inner(div(outer(phi,self.ubar)),D)*dx a_flux = (dot(jump(phi), un('+')*D('+') - un('-')*D('-')))*surface_measure arhs = a_mass - dt*(a_int + a_flux) DGproblem = LinearVariationalProblem(a_mass, action(arhs,self.D1), self.dD) self.DGsolver = LinearVariationalSolver(DGproblem, solver_parameters={ 'ksp_type':'preonly', 'pc_type':'bjacobi', 'sub_pc_type': 'ilu'}, options_prefix='DGAdvection')
def advection_term(self, q): if self.continuity: if self.ibp == "once": L = -inner(grad(self.test), outer(q, self.ubar)) * dx else: L = inner(self.test, div(outer(q, self.ubar))) * dx else: if self.ibp == "once": L = -inner(div(outer(self.test, self.ubar)), q) * dx else: L = inner(outer(self.test, self.ubar), grad(q)) * dx if self.dS is not None and self.ibp is not None: L += dot(jump(self.test), (self.un('+') * q('+') - self.un('-') * q('-'))) * self.dS if self.ibp == "twice": L -= ( inner(self.test('+'), dot(self.ubar('+'), self.n('+')) * q('+')) + inner(self.test('-'), dot(self.ubar('-'), self.n('-')) * q('-'))) * self.dS if self.outflow: L += self.test * self.un * q * self.ds if self.vector_manifold: un = self.un w = self.test u = q n = self.n dS = self.dS L += un('+') * inner(w('-'), n('+') + n('-')) * inner(u('+'), n('+')) * dS L += un('-') * inner(w('+'), n('+') + n('-')) * inner(u('-'), n('-')) * dS return L
def get_flux_form(dS, M): fluxes = (-inner(2*avg(outer(phi, n)), avg(grad(gamma)*M)) - inner(avg(grad(phi)*M), 2*avg(outer(gamma, n))) + mu*inner(2*avg(outer(phi, n)), 2*avg(outer(gamma, n)*kappa)))*dS return fluxes
def surf_grad(u): return fd.sym(fd.grad(u) - fd.outer(fd.grad(u) * n, n))
def central_inflow_flux(F_in, v, boundary_ids): r"""Create the weak form of the central numerical flux through the domain boundary""" mesh = v.ufl_domain() n = FacetNormal(mesh) return inner(F_in, outer(v, n)) * ds(boundary_ids)
def residual(self, test, trial, trial_lagged, fields, bcs): if 'background_viscosity' in fields: assert('grid_resolution' in fields) mu_background = fields['background_viscosity'] grid_dx = fields['grid_resolution'][0] grid_dz = fields['grid_resolution'][1] mu_h = 0.5*abs(trial[0]) * grid_dx + mu_background mu_v = 0.5*abs(trial[1]) * grid_dz + mu_background print("use redx viscosity") diff_tensor = as_tensor([[mu_h, 0], [0, mu_v]]) else: mu = fields['viscosity'] if len(mu.ufl_shape) == 2: diff_tensor = mu else: diff_tensor = mu * Identity(self.dim) phi = test n = self.n u = trial u_lagged = trial_lagged grad_test = nabla_grad(phi) stress = dot(diff_tensor, nabla_grad(u)) if self.symmetric_stress: stress += dot(diff_tensor, grad(u)) F = 0 F += inner(grad_test, stress)*self.dx # Interior Penalty method # # see https://www.researchgate.net/publication/260085826 for details # on the choice of sigma degree = self.trial_space.ufl_element().degree() if not isinstance(degree, int): degree = max(degree[0], degree[1]) # safety factor: 1.0 is theoretical minimum alpha = fields.get('interior_penalty', 2.0) if degree == 0: # probably only works for orthog. quads and hexes sigma = 1.0 else: nf = self.mesh.ufl_cell().num_facets() family = self.trial_space.ufl_element().family() if family in ['DQ', 'TensorProductElement', 'EnrichedElement']: degree_gradient = degree else: degree_gradient = degree - 1 sigma = alpha * cell_edge_integral_ratio(self.mesh, degree_gradient) * nf # we use (3.23) + (3.20) from https://www.researchgate.net/publication/260085826 # instead of maximum over two adjacent cells + and -, we just sum (which is 2*avg()) # and the for internal facets we have an extra 0.5: # WEIRDNESS: avg(1/CellVolume(mesh)) crashes TSFC - whereas it works in scalar diffusion! - instead just writing out explicitly sigma *= FacetArea(self.mesh)*(1/CellVolume(self.mesh)('-') + 1/CellVolume(self.mesh)('+'))/2 if not is_continuous(self.trial_space): u_tensor_jump = tensor_jump(n, u) if self.symmetric_stress: u_tensor_jump += transpose(u_tensor_jump) F += sigma*inner(tensor_jump(n, phi), dot(avg(diff_tensor), u_tensor_jump))*self.dS F += -inner(avg(dot(diff_tensor, nabla_grad(phi))), u_tensor_jump)*self.dS F += -inner(tensor_jump(n, phi), avg(stress))*self.dS for id, bc in bcs.items(): if 'u' in bc or 'un' in bc: if 'u' in bc: u_tensor_jump = outer(n, u-bc['u']) else: u_tensor_jump = outer(n, n)*(dot(n, u)-bc['un']) if self.symmetric_stress: u_tensor_jump += transpose(u_tensor_jump) # this corresponds to the same 3 terms as the dS integrals for DG above: F += 2*sigma*inner(outer(n, phi), dot(diff_tensor, u_tensor_jump))*self.ds(id) F += -inner(dot(diff_tensor, nabla_grad(phi)), u_tensor_jump)*self.ds(id) if 'u' in bc: F += -inner(outer(n, phi), stress) * self.ds(id) elif 'un' in bc: # we only keep, the normal part of stress, the tangential # part is assumed to be zero stress (i.e. free slip), or prescribed via 'stress' F += -dot(n, phi)*dot(n, dot(stress, n)) * self.ds(id) if 'stress' in bc: # a momentum flux, a.k.a. "force" # here we need only the third term, because we assume jump_u=0 (u_ext=u) # the provided stress = n.(mu.stress_tensor) F += dot(-phi, bc['stress']) * self.ds(id) if 'drag' in bc: # (bottom) drag of the form tau = -C_D u |u| C_D = bc['drag'] if 'coriolis_frequency' in fields and self.dim == 2: assert 'u_velocity' in fields u_vel_component = fields['u_velocity'] unorm = pow(dot(u_lagged, u_lagged) + pow(u_vel_component, 2) + 1e-6, 0.5) else: unorm = pow(dot(u_lagged, u_lagged) + 1e-6, 0.5) F += dot(-phi, -C_D*unorm*u) * self.ds(id) # NOTE 1: unspecified boundaries are equivalent to free stress (i.e. free in all directions) # NOTE 2: 'un' can be combined with 'stress' provided the stress force is tangential (e.g. no-normal flow with wind) if 'u' in bc and 'stress' in bc: raise ValueError("Cannot apply both 'u' and 'stress' bc on same boundary") if 'u' in bc and 'drag' in bc: raise ValueError("Cannot apply both 'u' and 'drag' bc on same boundary") if 'u' in bc and 'un' in bc: raise ValueError("Cannot apply both 'u' and 'un' bc on same boundary") return -F
def __init__(self, mesh: object, kappa: float, comm_space: MPI.Comm, mu: float = 5., *args, **kwargs): """ Constructor :param mesh: spatial domain :param kappa: diffusion coefficient :param mu: penalty weighting function """ super(Diffusion2D, self).__init__(*args, **kwargs) # Spatial domain and function space self.mesh = mesh V = FunctionSpace(self.mesh, "DG", 1) self.function_space = V self.comm_space = comm_space # Placeholder for time step - will be updated in the update method self.dt = Constant(0.) # Things we need for the form gamma = TestFunction(V) phi = TrialFunction(V) self.f = Function(V) n = FacetNormal(mesh) # Set up the rhs and bilinear form of the equation a = (inner(gamma, phi) * dx + self.dt * (inner(grad(gamma), grad(phi) * kappa) * dx - inner(2 * avg(outer(phi, n)), avg(grad(gamma) * kappa)) * dS - inner(avg(grad(phi) * kappa), 2 * avg(outer(gamma, n))) * dS + mu * inner(2 * avg(outer(phi, n)), 2 * avg(outer(gamma, n) * kappa)) * dS)) rhs = inner(gamma, self.f) * dx # Function to hold the solution self.soln = Function(V) # Setup problem and solver prob = LinearVariationalProblem(a, rhs, self.soln) self.solver = NonlinearVariationalSolver(prob) # Set the data structure for any user-defined time point self.vector_template = VectorDiffusion2D(size=len(self.function_space), comm_space=self.comm_space) # Set initial condition: # Setting up a Gaussian blob in the centre of the domain. self.vector_t_start = VectorDiffusion2D(size=len(self.function_space), comm_space=self.comm_space) x = SpatialCoordinate(self.mesh) initial_tracer = exp(-((x[0] - 5)**2 + (x[1] - 5)**2)) tmp = Function(self.function_space) tmp.interpolate(initial_tracer) self.vector_t_start.set_values(np.copy(tmp.dat.data))
fd.inner(f, v)*fd.dx + idt*c_t*p0*q*fd.dx # ------------------------------------------------------------------------- # transport # coefficients K = fd.Constant(K) Dt = fd.Constant(dt) c_mid = 0.5 * (c + c0) # Crank-Nicolson timestepping # advective velocity vel = fd.Function(V, name='velocity') vnorm = fd.sqrt(fd.dot(vel, vel)) # Diffusion tensor Diff = fd.Identity(mesh.geometric_dimension())*(Dm + alphaT*vnorm) + \ (alphaL-alphaT)*fd.outer(vel, vel)/vnorm # source term fc = fd.Constant(s) # weak form (transport) F1 = w * (c - c0) * fd.dx + Dt * (w * fd.dot(vel, fd.grad(c_mid)) + fd.dot( fd.grad(w), Diff * fd.grad(c_mid)) + w * K * c_mid - fc * w) * fd.dx # - Dt*h_n*w*fd.ds(outlet) # strong form R = (c - c0) + Dt * (fd.dot(vel, fd.grad(c_mid)) - fd.div(Diff * fd.grad(c_mid)) + K * c_mid - fc) # *** Adding SUPG stabilizing and shock cap. terms *** # SUPG stabilisation parameters
dt = np.sqrt(tol) dtc = fd.Constant(dt) n = fd.FacetNormal(mesh) h = fd.sqrt(2) * fd.CellVolume(mesh) / fd.CellDiameter(mesh) # advective velocity velocity = fd.Function(vDG1) velocity.interpolate(fd.Constant(v_inlet)) vnorm = fd.sqrt(fd.dot(velocity, velocity)) # Diffusion tensor if d_t < 0: Diff = fd.Identity(mesh.geometric_dimension()) * Dm else: Diff = fd.Identity(mesh.geometric_dimension())*(Dm + d_t*vnorm) + \ fd.Constant(d_l-d_t)*fd.outer(velocity, velocity)/vnorm # upwind ter # vn = 0.5*(fd.dot(velocity, n) + abs(fd.dot(velocity, n))) # cupw = fd.conditional(vn < 0, ch, lmbd_h) # stability tau_d = fd.Constant(max([Dm, tau_e])) / h tau_a = abs(fd.dot(velocity, n)) # tau_a = vn # numerical flux chat = lmbd_h ########################################### qhat = qh + tau_d * (ch - chat) * n + velocity * chat + tau_a * (ch - chat) * n # qhat = qh + tau*(ch - chat)*n + velocity * chat + tau_a*(ch - chat)*n
# 3.3) set boundary conditions t_bc = fd.DirichletBC(X, cIn, inlet) # ====================== # 3.4) Variational Form # coefficients # advective velocity vel = fd.Function(V, name='velocity') vel.interpolate(v_inlet) vnorm = fd.sqrt(fd.dot(vel, vel)) # Diffusion tensor Diff = fd.Identity(mesh.geometric_dimension())*(Dm + d_t*vnorm) + \ fd.Constant(d_l-d_t)*fd.outer(vel, vel)/vnorm Diff = fd.Identity(mesh.geometric_dimension())*Dm Dt = fd.Constant(dt) K = fd.Constant(K) c_mid = 0.5 * (c + c0) # Crank-Nicolson timestepping fc = fd.Constant(s) # weak form (transport) F = w*(c - c0)*fd.dx + Dt*(w*fd.dot(vel, fd.grad(c_mid)) + fd.dot(fd.grad(w), Diff*fd.grad(c_mid)) + w*K*c_mid - fc*w)*fd.dx # - Dt*h_n*w*fd.ds(outlet) # strong form R = (c - c0) + Dt*(fd.dot(vel, fd.grad(c_mid)) -