def add_dirichlet(self, g, surface): """ Adds dirichlet boundary conditions to the problem. Args: g (Function, int, or float): The function to apply on the boundary. surface (int or list of int): The index of the boundary to apply the condition to. """ # Explicitly check against False as None should not be caught here. if self._has_boundary is False: raise AttributeError('Cannot add boundary after declaring that ' 'there are no boundaries') norm = FacetNormal(self.mesh) if isinstance(g, (float, int)): g = Constant(g) integrand = -1 * self.v * dot(self.q, norm) if surface == 'all': dbc = DirichletBC(V=self.V, g=g, sub_domain="on_boundary") self.a += integrand * ds else: dbc = DirichletBC(V=self.V, g=g, sub_domain=surface) try: self.a += sum(integrand * ds(s) for s in surface) except TypeError: self.a += integrand * ds(surface) self.bcs.append(dbc) self._has_boundary = True
def mult(self, mat, x, y): # Copy function data into the fivredrake function self.x_fntn.dat.data[:] = x[:] # Transfer the function to meshmode self.meshmode_src_connection.from_firedrake(project( self.x_fntn, dgfspace), out=self.x_mm_fntn) # Restrict to boundary x_mm_fntn_on_bdy = src_bdy_connection(self.x_mm_fntn) # Apply the operation potential_int_mm = self.pyt_op(self.actx, u=x_mm_fntn_on_bdy, k=self.k) grad_potential_int_mm = self.pyt_grad_op(self.actx, u=x_mm_fntn_on_bdy, k=self.k) # Store in firedrake self.potential_int.dat.data[target_indices] = potential_int_mm.get( ) for dim in range(grad_potential_int_mm.shape[0]): self.grad_potential_int.dat.data[ target_indices, dim] = grad_potential_int_mm[dim].get() # Integrate the potential r""" Compute the inner products using firedrake. Note this will be subtracted later, hence appears off by a sign. .. math:: \langle n(x) \cdot \nabla( \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ), v \rangle_\Sigma - \langle i \kappa \cdot \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y), v \rangle_\Sigma """ self.pyt_result = assemble( inner(inner(self.grad_potential_int, self.n), self.v) * ds(outer_bdy_id) - inner(self.potential_int, self.v) * ds(outer_bdy_id)) # y <- Ax - evaluated potential self.A.mult(x, y) with self.pyt_result.dat.vec_ro as ep: y.axpy(-1, ep)
def action(self, **kwargs): r"""Return the action functional that gives the ice shelf diagnostic model as the Euler-Lagrange equations The action functional for the ice shelf diagnostic model is .. math:: E(u) = \int_\Omega\left(\frac{n}{n + 1}hM:\dot\varepsilon - \frac{1}{2}\varrho gh^2\nabla\cdot u\right)dx where :math:`u` is the velocity, :math:`h` is the ice thickness, :math:`\dot\varepsilon` is the strain-rate tensor, and :math:`M` is the membrane stress tensor. Parameters ---------- u : firedrake.Function ice velocity h : firedrake.Function ice thickness Returns ------- E : firedrake.Form the ice shelf action functional Other parameters ---------------- **kwargs All other keyword arguments will be passed on to the viscosity and gravity functionals. The ice fluidity coefficient, for example, is passed as a keyword argument. """ u = kwargs.get('velocity', kwargs.get('u')) mesh = u.ufl_domain() ice_front_ids = tuple(kwargs.pop('ice_front_ids', ())) side_wall_ids = tuple(kwargs.pop('side_wall_ids', ())) viscosity = self.viscosity(**kwargs) * dx gravity = self.gravity(**kwargs) * dx ds_w = ds(domain=mesh, subdomain_id=side_wall_ids) side_friction = self.side_friction(**kwargs) * ds_w penalty = self.penalty(**kwargs) * ds_w ds_t = ds(domain=mesh, subdomain_id=ice_front_ids) terminus = self.terminus(**kwargs) * ds_t return viscosity + side_friction - gravity - terminus + penalty
def action(self, **kwargs): r"""Return the action functional that gives the ice stream diagnostic model as the Euler-Lagrange equations""" u = kwargs["velocity"] mesh = u.ufl_domain() ice_front_ids = tuple(kwargs.pop("ice_front_ids", ())) side_wall_ids = tuple(kwargs.pop("side_wall_ids", ())) metadata = {"quadrature_degree": self.quadrature_degree(**kwargs)} dx = firedrake.dx(metadata=metadata) ds = firedrake.ds(domain=mesh, metadata=metadata) viscosity = self.viscosity(**kwargs) * dx friction = self.friction(**kwargs) * dx gravity = self.gravity(**kwargs) * dx side_friction = self.side_friction(**kwargs) * ds(side_wall_ids) if get_mesh_axes(mesh) == "xy": penalty = self.penalty(**kwargs) * ds(side_wall_ids) else: penalty = 0.0 terminus = self.terminus(**kwargs) * ds(ice_front_ids) return viscosity + friction + side_friction - gravity - terminus + penalty
def test__heat_driven_cavity_with_water(tmpdir): sim = sapphire.simulations.examples.\ heat_driven_cavity_with_water.Simulation( mesh_dimensions = (20, 20), element_degrees = (1, 2, 2), output_directory_path = tmpdir) sim.solution = sim.solve_with_continuation_on_grashof_number() p, u, T = sim.solution.split() fe.tricontourf(T) fe.quiver(u) filepath = tmpdir + "/T_and_u.png" print("Writing plot to {}".format(filepath)) matplotlib.pyplot.savefig(filepath) dot, grad = fe.dot, fe.grad ds = fe.ds(subdomain_id=sim.coldwall_id) nhat = fe.FacetNormal(sim.mesh) p, u, T = fe.split(sim.solution) coldwall_heatflux = fe.assemble(dot(grad(T), nhat) * ds) print("Integrated cold wall heat flux = {}".format(coldwall_heatflux)) assert (round(coldwall_heatflux, 0) == -8.)
def terminus(u, h, s, ice_front_ids=()): r"""Return the terminal stress part of the ice stream action functional The power exerted due to stress at the ice calving terminus :math:`\Gamma` is .. math:: E(u) = \int_\Gamma\left(\frac{1}{2}\rho_Igh^2 - \rho_Wgd^2\right) u\cdot \nu\hspace{2pt}ds where :math:`d` is the water depth at the terminus. We assume that sea level is at :math:`z = 0` for purposes of calculating the water depth. Parameters ---------- u : firedrake.Function ice velocity h : firedrake.Function ice thickness s : firedrake.Function ice surface elevation ice_front_ids : list of int numeric IDs of the parts of the boundary corresponding to the calving front """ from firedrake import conditional, lt d = conditional(lt(s - h, 0), s - h, 0) τ_I = ρ_I * g * h**2 / 2 τ_W = ρ_W * g * d**2 / 2 ν = firedrake.FacetNormal(u.ufl_domain()) return (τ_I - τ_W) * inner(u, ν) * ds(tuple(ice_front_ids))
def __init__(self, *args, meshsize, **kwargs): self.hot_wall_temperature = fe.Constant(1.) self.cold_wall_temperature = fe.Constant(-0.01) self.topwall_heatflux = fe.Constant(0.) super().__init__( *args, mesh=fe.UnitSquareMesh(meshsize, meshsize), initial_values=initial_values, dirichlet_boundary_conditions=dirichlet_boundary_conditions, **kwargs) q = self.topwall_heatflux _, _, psi_T = fe.TestFunctions(self.function_space) ds = fe.ds(domain=self.mesh, subdomain_id=4) self.variational_form_residual += psi_T * q * ds Ra = 3.27e5 Pr = 56.2 self.grashof_number = self.grashof_number.assign(Ra / Pr) self.prandtl_number = self.prandtl_number.assign(Pr) self.stefan_number = self.stefan_number.assign(0.045) self.liquidus_temperature = self.liquidus_temperature.assign(0.)
def test_update_a_multiple_surfaces(self): """ Test a is updated correctly for a with a multiple surfaces. """ T = self.problem.T v = self.problem.v q = self.problem.q self.problem.a = T * dx g = Function(self.V) expected_a = T * dx + -1 * v * dot(q, self.norm) * ds(0) expected_a += -1 * v * dot(q, self.norm) * ds(1) self.problem.add_dirichlet(g, [0, 1]) self.assertEqual(expected_a, self.problem.a)
def test_updates_multiple_surfaces(self): """ Test that a and L are set correctly for two surfaces. """ T = self.problem.T v = self.problem.v self.problem.a = T * dx self.problem.L = T * dx alpha = Function(self.V) g = Function(self.V) expected_a = T * dx + alpha * v * T * ds(0) + alpha * v * T * ds(1) expected_L = T * dx + v * g * ds(0) + v * g * ds(1) self.problem.add_robin(alpha, g, [0, 1]) self.assertEqual(expected_a, self.problem.a) self.assertEqual(expected_L, self.problem.L)
def test_updates_single_surface(self): """ Test that a and L are set correctly for a single surface. """ T = self.problem.T v = self.problem.v self.problem.a = T * dx self.problem.L = T * dx alpha = Function(self.V) g = Function(self.V) expected_a = T * dx + alpha * v * T * ds(0) expected_L = T * dx + v * g * ds(0) self.problem.add_robin(alpha, g, 0) self.assertEqual(expected_a, self.problem.a) self.assertEqual(expected_L, self.problem.L)
def mult(self, mat, x, y): # Perform pytential operation self.x_fntn.dat.data[:] = x[:] self.pyt_op(self.queue, self.potential_int, u=self.x_fntn, k=self.k) self.pyt_grad_op(self.queue, self.grad_potential_int, u=self.x_fntn, k=self.k) # Integrate the potential r""" Compute the inner products using firedrake. Note this will be subtracted later, hence appears off by a sign. .. math:: \langle n(x) \cdot \nabla( \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ), v \rangle_\Sigma - \langle i \kappa \cdot \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y), v \rangle_\Sigma """ self.pyt_result = assemble( inner(inner(self.grad_potential_int, self.n), self.v) * ds(outer_bdy_id) - inner(self.potential_int, self.v) * ds(outer_bdy_id)) # y <- Ax - evaluated potential self.A.mult(x, y) with self.pyt_result.dat.vec_ro as ep: y.axpy(-1, ep)
def action(self, u, h, s, **kwargs): r"""Return the action functional that gives the ice stream diagnostic model as the Euler-Lagrange equations""" mesh = u.ufl_domain() ice_front_ids = tuple(kwargs.pop('ice_front_ids', ())) side_wall_ids = tuple(kwargs.pop('side_wall_ids', ())) viscosity = self.viscosity(u=u, h=h, s=s, **kwargs) * dx friction = self.friction(u=u, h=h, s=s, **kwargs) * dx gravity = self.gravity(u=u, h=h, s=s, **kwargs) * dx ds_w = ds(domain=mesh, subdomain_id=side_wall_ids) side_friction = self.side_friction(u=u, h=h, s=s, **kwargs) * ds_w penalty = self.penalty(u=u, h=h, s=s, **kwargs) * ds_w ds_t = ds(domain=mesh, subdomain_id=ice_front_ids) terminus = self.terminus(u=u, h=h, s=s, **kwargs) * ds_t return (viscosity + friction + side_friction - gravity - terminus + penalty)
def dgls_form(self, problem, mesh, bcs_p): rho = problem.rho mu = problem.mu k = problem.k f = problem.f q, p = fire.TrialFunctions(self._W) w, v = fire.TestFunctions(self._W) n = fire.FacetNormal(mesh) h = fire.CellDiameter(mesh) # Stabilizing parameters has_mesh_characteristic_length = True delta_0 = fire.Constant(1) delta_1 = fire.Constant(-1 / 2) delta_2 = fire.Constant(1 / 2) delta_3 = fire.Constant(1 / 2) eta_p = fire.Constant(100) eta_q = fire.Constant(100) h_avg = (h("+") + h("-")) / 2.0 if has_mesh_characteristic_length: delta_2 = delta_2 * h * h delta_3 = delta_3 * h * h kappa = rho * k / mu inv_kappa = 1.0 / kappa # Classical mixed terms a = (dot(inv_kappa * q, w) - div(w) * p - delta_0 * v * div(q)) * dx L = -delta_0 * f * v * dx # DG terms a += jump(w, n) * avg(p) * dS - avg(v) * jump(q, n) * dS # Edge stabilizing terms a += (eta_q * h_avg) * avg(inv_kappa) * ( jump(q, n) * jump(w, n)) * dS + (eta_p / h_avg) * avg(kappa) * dot( jump(v, n), jump(p, n)) * dS # Add the contributions of the pressure boundary conditions to L for pboundary, iboundary in bcs_p: L -= pboundary * dot(w, n) * ds(iboundary) # Stabilizing terms a += (delta_1 * inner(kappa * (inv_kappa * q + grad(p)), delta_0 * inv_kappa * w + grad(v)) * dx) a += delta_2 * inv_kappa * div(q) * div(w) * dx a += delta_3 * inner(kappa * curl(inv_kappa * q), curl( inv_kappa * w)) * dx L += delta_2 * inv_kappa * f * div(w) * dx return a, L
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 add_robin(self, alpha, g, surface): """ Adds robin boundary conditions to the problem. The Robin condition is a mixed codition and is defined by: dot(grad(T), n) = g - alpha*T Args: g (Function, int, or float): The function to apply on the boundary as per the above formula. alpha (Function, int, or float): The function to apply on the boundary as per the above formula. surface (int or list of int): The index of the boundary to apply the condition to. """ # Explicitly check against False as None should not be caught here. if self._has_boundary is False: raise AttributeError('Cannot add boundary after declaring that ' 'there are no boundaries') if isinstance(g, (float, int)): g = Constant(g) if isinstance(alpha, (float, int)): alpha = Constant(alpha) a_integrand = alpha * self.v * self.T L_integrand = self.v * g if surface == 'all': self.a += a_integrand * ds self.L += L_integrand * ds else: try: self.a += sum(a_integrand * ds(s) for s in surface) self.L += sum(L_integrand * ds(s) for s in surface) except TypeError: self.a += a_integrand * ds(surface) self.L += L_integrand * ds(surface) self._has_boundary = True
def test_update_a_single_surface(self): """ Test a is updated correctly for a with a single surface. """ T = self.problem.T v = self.problem.v q = self.problem.q self.problem.a = T * dx g = Function(self.V) expected_a = T * dx + -1 * v * dot(q, self.norm) * ds(0) self.problem.add_dirichlet(g, 0) self.assertEqual(expected_a, self.problem.a)
def form2indicator(F): """ Multiply throughout in a form and assemble as a cellwise error indicator. :arg F: the form """ mesh = F.ufl_domain() P0 = firedrake.FunctionSpace(mesh, "DG", 0) p0test = firedrake.TestFunction(P0) indicator = firedrake.Function(P0) # Contributions from surface integrals flux_terms = 0 integrals = F.integrals_by_type("exterior_facet") if len(integrals) > 0: for integral in integrals: ds = firedrake.ds(integral.subdomain_id()) flux_terms += p0test * integral.integrand() * ds integrals = F.integrals_by_type("interior_facet") if len(integrals) > 0: for integral in integrals: dS = firedrake.dS(integral.subdomain_id()) flux_terms += p0test("+") * integral.integrand() * dS flux_terms += p0test("-") * integral.integrand() * dS if flux_terms != 0: dx = firedrake.dx mass_term = firedrake.TrialFunction(P0) * p0test * dx sp = { "snes_type": "ksponly", "ksp_type": "preonly", "pc_type": "jacobi", } firedrake.solve(mass_term == flux_terms, indicator, solver_parameters=sp) # Contributions from volume integrals cell_terms = 0 integrals = F.integrals_by_type("cell") if len(integrals) > 0: for integral in integrals: dx = firedrake.dx(integral.subdomain_id()) cell_terms += p0test * integral.integrand() * dx indicator += firedrake.assemble(cell_terms) return indicator
def terminus(u, h, ice_front_ids=()): r"""Return the terminus stress part of the ice shelf action functional The power exerted due to stress at the calving terminus :math:`\Gamma` is .. math:: E(u) = \int_\Gamma\varrho gh^2u\cdot\nu\hspace{2pt}ds We assume that sea level is at :math:`z = 0` for calculating the water depth. """ mesh = u.ufl_domain() ν = firedrake.FacetNormal(mesh) IDs = tuple(ice_front_ids) ρ = ρ_I * (1 - ρ_I / ρ_W) return 0.5 * ρ * g * h**2 * inner(u, ν) * ds(IDs)
def normal_flow_penalty(u, scale=1.0, exponent=None, side_wall_ids=()): r"""Return the penalty for flow normal to the domain boundary For problems where a glacier flows along some boundary, e.g. a fjord wall, the velocity has to be parallel to this boundary. Rather than enforce this boundary condition directly, we add a penalty for normal flow to the action functional. """ mesh = u.ufl_domain() ν = firedrake.FacetNormal(mesh) L = utilities.diameter(mesh) δx = firedrake.CellSize(mesh) d = u.ufl_function_space().ufl_element().degree() exponent = d + 1 if exponent is None else exponent penalty = scale * (L / δx)**exponent return 0.5 * penalty * inner(u, ν)**2 * ds(tuple(side_wall_ids))
def side_friction(u, h, Cs=firedrake.Constant(0), side_wall_ids=()): r"""Return the side wall friction part of the action functional The component of the action functional due to friction along the side walls of the domain is .. math:: E(u) = -\frac{m}{m + 1}\int_\Gamma h\tau(u, C_s)\cdot u\hspace{2pt}ds where :math:`\tau(u, C_s)` is the side wall shear stress, :math:`ds` is the element of surface area and :math:`\Gamma` are the side walls. Side wall friction is relevant for glaciers that flow through a fjord with rock walls on either side. """ mesh = u.ufl_domain() ν = firedrake.FacetNormal(mesh) u_t = u - inner(u, ν) * ν ds_side_walls = ds(domain=mesh, subdomain_id=tuple(side_wall_ids)) return -m / (m + 1) * h * inner(tau(u_t, Cs), u_t) * ds_side_walls
def static_solver(self): def epsilon(u): return 0.5 * (fd.nabla_grad(u) + fd.nabla_grad(u).T) #return sym(nabla_grad(u)) def sigma(u): d = u.geometric_dimension() # space dimension return self.lam * fd.nabla_div(u) * fd.Identity( d) + 2 * self.mu * epsilon(u) # Define variational problem u = fd.TrialFunction(self.V) v = fd.TestFunction(self.V) # f = fd.Constant((0, 0, -self.g)) # body force / rho f = fd.Constant((0, 0, 0)) # body force / rho T = self.surface_force() a = fd.inner(sigma(u), epsilon(v)) * fd.dx L = fd.dot(f, v) * fd.dx + fd.dot(T, v) * fd.ds(1) # Compute solution self.DBC = fd.DirichletBC(self.V, fd.Expression([0., 0., 0.]), self.bottom_id) fd.solve(a == L, self.X, bcs=self.DBC)
def __init__(self, test_space, trial_space, quad_degree=None): """ :arg test_space: the test functionspace :arg trial_space: The trial functionspace test and trial space are only used to determine the the discretisation that's used (ufl_element) not what test and trial functions are actually used (these are provided seperately in residual()) :arg quad_degree: quadrature degree, default is 2*p+1 where p is the polynomial degree of trial_space """ self.test_space = test_space self.trial_space = trial_space self.mesh = trial_space.mesh() p = trial_space.ufl_element().degree() if isinstance(p, int): # isotropic mesh if quad_degree is None: quad_degree = 2 * p + 1 self.ds = firedrake.ds(domain=self.mesh, degree=quad_degree) self.dS = firedrake.dS(domain=self.mesh, degree=quad_degree) else: # extruded mesh p_h, p_v = p if quad_degree is None: quad_degree = 2 * max(p_h, p_v) + 1 self.ds = CombinedSurfaceMeasure(self.mesh, quad_degree) self.dS = firedrake.dS_v(domain=self.mesh, degree=quad_degree) + firedrake.dS_h( domain=self.mesh, degree=quad_degree) self.dx = firedrake.dx(domain=self.mesh, degree=quad_degree) # self._terms stores the actual instances of the BaseTerm-classes in self.terms self._terms = [] for TermClass in self.terms: self._terms.append( TermClass(test_space, trial_space, self.dx, self.ds, self.dS))
def nonlocal_integral_eq( mesh, scatterer_bdy_id, outer_bdy_id, wave_number, options_prefix=None, solver_parameters=None, fspace=None, vfspace=None, true_sol_grad_expr=None, actx=None, dgfspace=None, dgvfspace=None, meshmode_src_connection=None, qbx_kwargs=None, ): r""" see run_method for descriptions of unlisted args args: gamma and beta are used to precondition with the following equation: \Delta u - \kappa^2 \gamma u = 0 (\partial_n - i\kappa\beta) u |_\Sigma = 0 """ # make sure we get outer bdy id as tuple in case it consists of multiple ids if isinstance(outer_bdy_id, int): outer_bdy_id = [outer_bdy_id] outer_bdy_id = tuple(outer_bdy_id) # away from the excluded region, but firedrake and meshmode point # into pyt_inner_normal_sign = -1 ambient_dim = mesh.geometric_dimension() # {{{ Build src and tgt # build connection meshmode near src boundary -> src boundary inside meshmode from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory from meshmode.discretization.connection import make_face_restriction factory = InterpolatoryQuadratureSimplexGroupFactory( dgfspace.finat_element.degree) src_bdy_connection = make_face_restriction(actx, meshmode_src_connection.discr, factory, scatterer_bdy_id) # source is a qbx layer potential from pytential.qbx import QBXLayerPotentialSource disable_refinement = (fspace.mesh().geometric_dimension() == 3) qbx = QBXLayerPotentialSource(src_bdy_connection.to_discr, **qbx_kwargs, _disable_refinement=disable_refinement) # get target indices and point-set target_indices, target = get_target_points_and_indices( fspace, outer_bdy_id) # }}} # build the operations from pytential import bind, sym r""" ..math: x \in \Sigma grad_op(x) = \nabla( \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ) """ grad_op = pyt_inner_normal_sign * sym.grad( ambient_dim, sym.D(HelmholtzKernel(ambient_dim), sym.var("u"), k=sym.var("k"), qbx_forced_limit=None)) r""" ..math: x \in \Sigma op(x) = i \kappa \cdot \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y) """ op = pyt_inner_normal_sign * 1j * sym.var("k") * (sym.D( HelmholtzKernel(ambient_dim), sym.var("u"), k=sym.var("k"), qbx_forced_limit=None)) # bind the operations pyt_grad_op = bind((qbx, target), grad_op) pyt_op = bind((qbx, target), op) # }}} class MatrixFreeB(object): def __init__(self, A, pyt_grad_op, pyt_op, actx, kappa): """ :arg kappa: The wave number """ self.actx = actx self.k = kappa self.pyt_op = pyt_op self.pyt_grad_op = pyt_grad_op self.A = A self.meshmode_src_connection = meshmode_src_connection # {{{ Create some functions needed for multing self.x_fntn = Function(fspace) # CG self.potential_int = Function(fspace) self.potential_int.dat.data[:] = 0.0 self.grad_potential_int = Function(vfspace) self.grad_potential_int.dat.data[:] = 0.0 self.pyt_result = Function(fspace) self.n = FacetNormal(mesh) self.v = TestFunction(fspace) # some meshmode ones self.x_mm_fntn = self.meshmode_src_connection.discr.empty( self.actx, dtype='c') # }}} def mult(self, mat, x, y): # Copy function data into the fivredrake function self.x_fntn.dat.data[:] = x[:] # Transfer the function to meshmode self.meshmode_src_connection.from_firedrake(project( self.x_fntn, dgfspace), out=self.x_mm_fntn) # Restrict to boundary x_mm_fntn_on_bdy = src_bdy_connection(self.x_mm_fntn) # Apply the operation potential_int_mm = self.pyt_op(self.actx, u=x_mm_fntn_on_bdy, k=self.k) grad_potential_int_mm = self.pyt_grad_op(self.actx, u=x_mm_fntn_on_bdy, k=self.k) # Store in firedrake self.potential_int.dat.data[target_indices] = potential_int_mm.get( ) for dim in range(grad_potential_int_mm.shape[0]): self.grad_potential_int.dat.data[ target_indices, dim] = grad_potential_int_mm[dim].get() # Integrate the potential r""" Compute the inner products using firedrake. Note this will be subtracted later, hence appears off by a sign. .. math:: \langle n(x) \cdot \nabla( \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ), v \rangle_\Sigma - \langle i \kappa \cdot \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y), v \rangle_\Sigma """ self.pyt_result = assemble( inner(inner(self.grad_potential_int, self.n), self.v) * ds(outer_bdy_id) - inner(self.potential_int, self.v) * ds(outer_bdy_id)) # y <- Ax - evaluated potential self.A.mult(x, y) with self.pyt_result.dat.vec_ro as ep: y.axpy(-1, ep) # {{{ Compute normal helmholtz operator u = TrialFunction(fspace) v = TestFunction(fspace) r""" .. math:: \langle \nabla u, \nabla v \rangle - \kappa^2 \cdot \langle u, v \rangle - i \kappa \langle u, v \rangle_\Sigma """ a = inner(grad(u), grad(v)) * dx \ - Constant(wave_number**2) * inner(u, v) * dx \ - Constant(1j * wave_number) * inner(u, v) * ds(outer_bdy_id) # get the concrete matrix from a general bilinear form A = assemble(a).M.handle # }}} # {{{ Setup Python matrix B = PETSc.Mat().create() # build matrix context Bctx = MatrixFreeB(A, pyt_grad_op, pyt_op, actx, wave_number) # set up B as same size as A B.setSizes(*A.getSizes()) B.setType(B.Type.PYTHON) B.setPythonContext(Bctx) B.setUp() # }}} # {{{ Create rhs # Remember f is \partial_n(true_sol)|_\Gamma # so we just need to compute \int_\Gamma\partial_n(true_sol) H(x-y) sigma = sym.make_sym_vector("sigma", ambient_dim) r""" ..math: x \in \Sigma grad_op(x) = \nabla( \int_\Gamma( f(y) H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ) """ grad_op = pyt_inner_normal_sign * \ sym.grad(ambient_dim, sym.S(HelmholtzKernel(ambient_dim), sym.n_dot(sigma), k=sym.var("k"), qbx_forced_limit=None)) r""" ..math: x \in \Sigma op(x) = i \kappa \cdot \int_\Gamma( f(y) H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ) """ op = 1j * sym.var("k") * pyt_inner_normal_sign * \ sym.S(HelmholtzKernel(ambient_dim), sym.n_dot(sigma), k=sym.var("k"), qbx_forced_limit=None) rhs_grad_op = bind((qbx, target), grad_op) rhs_op = bind((qbx, target), op) # Transfer to meshmode metadata = {'quadrature_degree': 2 * fspace.ufl_element().degree()} dg_true_sol_grad = project(true_sol_grad_expr, dgvfspace, form_compiler_parameters=metadata) true_sol_grad_mm = meshmode_src_connection.from_firedrake(dg_true_sol_grad, actx=actx) true_sol_grad_mm = src_bdy_connection(true_sol_grad_mm) # Apply the operations f_grad_convoluted_mm = rhs_grad_op(actx, sigma=true_sol_grad_mm, k=wave_number) f_convoluted_mm = rhs_op(actx, sigma=true_sol_grad_mm, k=wave_number) # Transfer function back to firedrake f_grad_convoluted = Function(vfspace) f_convoluted = Function(fspace) f_grad_convoluted.dat.data[:] = 0.0 f_convoluted.dat.data[:] = 0.0 for dim in range(f_grad_convoluted_mm.shape[0]): f_grad_convoluted.dat.data[target_indices, dim] = f_grad_convoluted_mm[dim].get() f_convoluted.dat.data[target_indices] = f_convoluted_mm.get() r""" \langle f, v \rangle_\Gamma + \langle i \kappa \cdot \int_\Gamma( f(y) H_0^{(1)}(\kappa |x - y|) )d\gamma(y), v \rangle_\Sigma - \langle n(x) \cdot \nabla( \int_\Gamma( f(y) H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ), v \rangle_\Sigma """ rhs_form = inner(inner(true_sol_grad_expr, FacetNormal(mesh)), v) * ds(scatterer_bdy_id, metadata=metadata) \ + inner(f_convoluted, v) * ds(outer_bdy_id) \ - inner(inner(f_grad_convoluted, FacetNormal(mesh)), v) * ds(outer_bdy_id) rhs = assemble(rhs_form) # {{{ set up a solver: solution = Function(fspace, name="Computed Solution") # {{{ Used for preconditioning if 'gamma' in solver_parameters or 'beta' in solver_parameters: gamma = complex(solver_parameters.pop('gamma', 1.0)) import cmath beta = complex(solver_parameters.pop('beta', cmath.sqrt(gamma))) p = inner(grad(u), grad(v)) * dx \ - Constant(wave_number**2 * gamma) * inner(u, v) * dx \ - Constant(1j * wave_number * beta) * inner(u, v) * ds(outer_bdy_id) P = assemble(p).M.handle else: P = A # }}} # Set up options to contain solver parameters: ksp = PETSc.KSP().create() if solver_parameters['pc_type'] == 'pyamg': del solver_parameters['pc_type'] # We are using the AMG preconditioner pyamg_tol = solver_parameters.get('pyamg_tol', None) if pyamg_tol is not None: pyamg_tol = float(pyamg_tol) pyamg_maxiter = solver_parameters.get('pyamg_maxiter', None) if pyamg_maxiter is not None: pyamg_maxiter = int(pyamg_maxiter) ksp.setOperators(B) ksp.setUp() pc = ksp.pc pc.setType(pc.Type.PYTHON) pc.setPythonContext( AMGTransmissionPreconditioner(wave_number, fspace, A, tol=pyamg_tol, maxiter=pyamg_maxiter, use_plane_waves=True)) # Otherwise use regular preconditioner else: ksp.setOperators(B, P) options_manager = OptionsManager(solver_parameters, options_prefix) options_manager.set_from_options(ksp) import petsc4py.PETSc petsc4py.PETSc.Sys.popErrorHandler() with rhs.dat.vec_ro as b: with solution.dat.vec as x: ksp.solve(b, x) # }}} return ksp, solution
def heat_exchanger_optimization(mu=0.03, n_iters=1000): output_dir = "2D/" path = os.path.abspath(__file__) dir_path = os.path.dirname(path) mesh = fd.Mesh(f"{dir_path}/2D_mesh.msh") # Perturb the mesh coordinates. Necessary to calculate shape derivatives S = fd.VectorFunctionSpace(mesh, "CG", 1) s = fd.Function(S, name="deform") mesh.coordinates.assign(mesh.coordinates + s) # Initial level set function x, y = fd.SpatialCoordinate(mesh) PHI = fd.FunctionSpace(mesh, "CG", 1) phi_expr = sin(y * pi / 0.2) * cos(x * pi / 0.2) - fd.Constant(0.8) # Avoid recording the operation interpolate into the tape. # Otherwise, the shape derivatives will not be correct with fda.stop_annotating(): phi = fd.interpolate(phi_expr, PHI) phi.rename("LevelSet") fd.File(output_dir + "phi_initial.pvd").write(phi) # Physics mu = fd.Constant(mu) # viscosity alphamin = 1e-12 alphamax = 2.5 / (2e-4) parameters = { "mat_type": "aij", "ksp_type": "preonly", "ksp_converged_reason": None, "pc_type": "lu", "pc_factor_mat_solver_type": "mumps", } stokes_parameters = parameters temperature_parameters = parameters u_inflow = 2e-3 tin1 = fd.Constant(10.0) tin2 = fd.Constant(100.0) P2 = fd.VectorElement("CG", mesh.ufl_cell(), 2) P1 = fd.FiniteElement("CG", mesh.ufl_cell(), 1) TH = P2 * P1 W = fd.FunctionSpace(mesh, TH) U = fd.TrialFunction(W) u, p = fd.split(U) V = fd.TestFunction(W) v, q = fd.split(V) epsilon = fd.Constant(10000.0) def hs(phi, epsilon): return fd.Constant(alphamax) * fd.Constant(1.0) / ( fd.Constant(1.0) + exp(-epsilon * phi)) + fd.Constant(alphamin) def stokes(phi, BLOCK_INLET_MOUTH, BLOCK_OUTLET_MOUTH): a_fluid = mu * inner(grad(u), grad(v)) - div(v) * p - q * div(u) darcy_term = inner(u, v) return (a_fluid * dx + hs(phi, epsilon) * darcy_term * dx(0) + alphamax * darcy_term * (dx(BLOCK_INLET_MOUTH) + dx(BLOCK_OUTLET_MOUTH))) # Dirichlet boundary conditions inflow1 = fd.as_vector([ u_inflow * sin( ((y - (line_sep - (dist_center + inlet_width))) * pi) / inlet_width), 0.0, ]) inflow2 = fd.as_vector([ u_inflow * sin(((y - (line_sep + dist_center)) * pi) / inlet_width), 0.0, ]) noslip = fd.Constant((0.0, 0.0)) # Stokes 1 bcs1_1 = fd.DirichletBC(W.sub(0), noslip, WALLS) bcs1_2 = fd.DirichletBC(W.sub(0), inflow1, INLET1) bcs1_3 = fd.DirichletBC(W.sub(1), fd.Constant(0.0), OUTLET1) bcs1_4 = fd.DirichletBC(W.sub(0), noslip, INLET2) bcs1_5 = fd.DirichletBC(W.sub(0), noslip, OUTLET2) bcs1 = [bcs1_1, bcs1_2, bcs1_3, bcs1_4, bcs1_5] # Stokes 2 bcs2_1 = fd.DirichletBC(W.sub(0), noslip, WALLS) bcs2_2 = fd.DirichletBC(W.sub(0), inflow2, INLET2) bcs2_3 = fd.DirichletBC(W.sub(1), fd.Constant(0.0), OUTLET2) bcs2_4 = fd.DirichletBC(W.sub(0), noslip, INLET1) bcs2_5 = fd.DirichletBC(W.sub(0), noslip, OUTLET1) bcs2 = [bcs2_1, bcs2_2, bcs2_3, bcs2_4, bcs2_5] # Forward problems U1, U2 = fd.Function(W), fd.Function(W) L = inner(fd.Constant((0.0, 0.0, 0.0)), V) * dx problem = fd.LinearVariationalProblem(stokes(-phi, INMOUTH2, OUTMOUTH2), L, U1, bcs=bcs1) solver_stokes1 = fd.LinearVariationalSolver( problem, solver_parameters=stokes_parameters, options_prefix="stokes_1") solver_stokes1.solve() problem = fd.LinearVariationalProblem(stokes(phi, INMOUTH1, OUTMOUTH1), L, U2, bcs=bcs2) solver_stokes2 = fd.LinearVariationalSolver( problem, solver_parameters=stokes_parameters, options_prefix="stokes_2") solver_stokes2.solve() # Convection difussion equation ks = fd.Constant(1e0) cp_value = 5.0e5 cp = fd.Constant(cp_value) T = fd.FunctionSpace(mesh, "DG", 1) t = fd.Function(T, name="Temperature") w = fd.TestFunction(T) # Mesh-related functions n = fd.FacetNormal(mesh) h = fd.CellDiameter(mesh) u1, p1 = fd.split(U1) u2, p2 = fd.split(U2) def upwind(u): return (dot(u, n) + abs(dot(u, n))) / 2.0 u1n = upwind(u1) u2n = upwind(u2) # Penalty term alpha = fd.Constant(500.0) # Bilinear form a_int = dot(grad(w), ks * grad(t) - cp * (u1 + u2) * t) * dx a_fac = (fd.Constant(-1.0) * ks * dot(avg(grad(w)), jump(t, n)) * dS + fd.Constant(-1.0) * ks * dot(jump(w, n), avg(grad(t))) * dS + ks("+") * (alpha("+") / avg(h)) * dot(jump(w, n), jump(t, n)) * dS) a_vel = (dot( jump(w), cp * (u1n("+") + u2n("+")) * t("+") - cp * (u1n("-") + u2n("-")) * t("-"), ) * dS + dot(w, cp * (u1n + u2n) * t) * ds) a_bnd = (dot(w, cp * dot(u1 + u2, n) * t) * (ds(INLET1) + ds(INLET2)) + w * t * (ds(INLET1) + ds(INLET2)) - w * tin1 * ds(INLET1) - w * tin2 * ds(INLET2) + alpha / h * ks * w * t * (ds(INLET1) + ds(INLET2)) - ks * dot(grad(w), t * n) * (ds(INLET1) + ds(INLET2)) - ks * dot(grad(t), w * n) * (ds(INLET1) + ds(INLET2))) aT = a_int + a_fac + a_vel + a_bnd LT_bnd = (alpha / h * ks * tin1 * w * ds(INLET1) + alpha / h * ks * tin2 * w * ds(INLET2) - tin1 * ks * dot(grad(w), n) * ds(INLET1) - tin2 * ks * dot(grad(w), n) * ds(INLET2)) problem = fd.LinearVariationalProblem(derivative(aT, t), LT_bnd, t) solver_temp = fd.LinearVariationalSolver( problem, solver_parameters=temperature_parameters, options_prefix="temperature", ) solver_temp.solve() # fd.solve(eT == 0, t, solver_parameters=temperature_parameters) # Cost function: Flux at the cold outlet scale_factor = 4e-4 Jform = fd.assemble( fd.Constant(-scale_factor * cp_value) * inner(t * u1, n) * ds(OUTLET1)) # Constraints: Pressure drop on each fluid power_drop = 1e-2 Power1 = fd.assemble(p1 / power_drop * ds(INLET1)) Power2 = fd.assemble(p2 / power_drop * ds(INLET2)) phi_pvd = fd.File("phi_evolution.pvd") def deriv_cb(phi): with stop_annotating(): phi_pvd.write(phi[0]) c = fda.Control(s) # Reduced Functionals Jhat = LevelSetFunctional(Jform, c, phi, derivative_cb_pre=deriv_cb) P1hat = LevelSetFunctional(Power1, c, phi) P1control = fda.Control(Power1) P2hat = LevelSetFunctional(Power2, c, phi) P2control = fda.Control(Power2) Jhat_v = Jhat(phi) print("Initial cost function value {:.5f}".format(Jhat_v), flush=True) print("Power drop 1 {:.5f}".format(Power1), flush=True) print("Power drop 2 {:.5f}".format(Power2), flush=True) beta_param = 0.08 # Regularize the shape derivatives only in the domain marked with 0 reg_solver = RegularizationSolver(S, mesh, beta=beta_param, gamma=1e5, dx=dx, design_domain=0) tol = 1e-5 dt = 0.05 params = { "alphaC": 1.0, "debug": 5, "alphaJ": 1.0, "dt": dt, "K": 1e-3, "maxit": n_iters, "maxtrials": 5, "itnormalisation": 10, "tol_merit": 5e-3, # new merit can be within 0.5% of the previous merit # "normalize_tol" : -1, "tol": tol, } solver_parameters = { "reinit_solver": { "h_factor": 2.0, } } # Optimization problem problem = InfDimProblem( Jhat, reg_solver, ineqconstraints=[ Constraint(P1hat, 1.0, P1control), Constraint(P2hat, 1.0, P2control), ], solver_parameters=solver_parameters, ) results = nlspace_solve(problem, params) return results
return kl[i, j] Kinv.dat.data[:, 0, 0] = 1 / \ fix_perm_map(ccenter.dat.data[:, 0], ccenter.dat.data[:, 1]) Kinv.dat.data[:, 1, 1] = 1 / \ fix_perm_map(ccenter.dat.data[:, 0], ccenter.dat.data[:, 1]) # ------- # 3.4) Variational Form # the bilinear and linear forms of the variational problem are defined as: :: a = fd.dot(v, Kinv * u) * fd.dx - fd.div(v) * p * fd.dx + q * fd.div(u) * fd.dx f = fd.Constant(0.0) n = fd.FacetNormal(mesh) L = q * f * fd.dx - fd.Constant(pbar) * fd.inner(v, n) * fd.ds(outlet) # ---- # 3.5) set boundary conditions # The strongly enforced boundary conditions on the BDM space on the top and # bottom of the domain are declared as: :: bc0 = fd.DirichletBC(W.sub(0), fd.Constant(qbar), inlet) bc1 = fd.DirichletBC(W.sub(0), fd.Constant(q0bar), noflow) # ---- # 4) Define and solve the problem # # Now we're ready to solve the variational problem. We define `w` to be a # function to hold the solution on the mixed space. w = fd.Function(W)
def pml(mesh, scatterer_bdy_id, outer_bdy_id, wave_number, options_prefix=None, solver_parameters=None, inner_region=None, fspace=None, tfspace=None, true_sol_grad=None, pml_type=None, delta=None, quad_const=None, speed=None, pml_min=None, pml_max=None): """ For unlisted arg descriptions, see run_method :arg inner_region: boundary id of non-pml region :arg pml_type: Type of pml function, either 'quadratic' or 'bdy_integral' :arg delta: For :arg:`pml_type` of 'bdy_integral', added to denominator to prevent 1 / 0 at edge of boundary :arg quad_const: For :arg:`pml_type` of 'quadratic', a scaling constant :arg speed: Speed of sound :arg pml_min: A list, *pml_min[i]* is where to begin pml layer in direction *i* :arg pml_max: A list, *pml_max[i]* is where to end pml layer in direction *i* """ # Handle defauls if pml_type is None: pml_type = 'bdy_integral' if delta is None: delta = 1e-3 if quad_const is None: quad_const = 1.0 if speed is None: speed = 340.0 pml_types = ['bdy_integral', 'quadratic'] if pml_type not in pml_types: raise ValueError("PML type of %s is not one of %s" % (pml_type, pml_types)) xx = SpatialCoordinate(mesh) # {{{ create sigma functions for PML sigma = None if pml_type == 'bdy_integral': sigma = [ Constant(speed) / (Constant(delta + extent) - abs(coord)) for extent, coord in zip(pml_max, xx) ] elif pml_type == 'quadratic': sigma = [ Constant(quad_const) * (abs(coord) - Constant(min_))**2 for min_, coord in zip(pml_min, xx) ] r""" Here \kappa is the wave number and c is the speed ..math:: \kappa = \frac{ \omega } { c } """ omega = wave_number * speed # {{{ Set up PML functions gamma = [ Constant(1.0) + conditional( abs(real(coord)) >= real(min_), Constant(1j / omega) * sigma_i, Constant(0.0)) for min_, coord, sigma_i in zip(pml_min, xx, sigma) ] kappa = [None] * len(gamma) gamma_prod = 1.0 for i in range(len(gamma)): gamma_prod *= gamma[i] tensor_i = [Constant(0.0) for _ in range(len(gamma))] tensor_i[i] = 1.0 r""" *i*th entry is .. math:: \frac{\prod_{j\neq i} \gamma_j}{ \gamma_i } """ for j in range(len(gamma)): if j != i: tensor_i[i] *= gamma[j] else: tensor_i[i] /= gamma[j] kappa[i] = tensor_i kappa = as_tensor(kappa) # }}} p = TrialFunction(fspace) q = TestFunction(fspace) k = wave_number # Just easier to look at a = (inner(dot(grad(p), kappa), grad(q)) - Constant(k**2) * gamma_prod * inner(p, q)) * dx n = FacetNormal(mesh) L = inner(dot(true_sol_grad, n), q) * ds(scatterer_bdy_id) bc = DirichletBC(fspace, Constant(0), outer_bdy_id) solution = Function(fspace) #solve(a == L, solution, bcs=[bc], options_prefix=options_prefix) # Create a solver and return the KSP object with the solution so that can get # PETSc information # Create problem problem = vs.LinearVariationalProblem(a, L, solution, [bc], None) # Create solver and call solve solver = vs.LinearVariationalSolver(problem, solver_parameters=solver_parameters, options_prefix=options_prefix) solver.solve() return solver.snes, solution
# stability # tau = fd.Constant(1.0) / h + abs(fd.dot(velocity, n)) tau = fd.Constant(5) / h + vn # numerical flux chat = lmbd_h qhat = qh + tau * (ch - chat) * n + velocity * chat # qhat_n = fd.dot(qh, n) + tau*(ch - chat) + chat*vn a_u = ( fd.inner(fd.inv(Diff) * qh, vh) * fd.dx - ch * fd.div(vh) * fd.dx + # internal faces fd.jump(lmbd_h * vh, n) * fd.dS + # other faces lmbd_h * fd.inner(vh, n) * fd.ds(outflow) + lmbd_h * fd.inner(vh, n) * fd.ds(TOP) + lmbd_h * fd.inner(vh, n) * fd.ds(BOTTOM)) # Dirichlet faces L_u = -fd.Constant(cIn) * fd.inner(vh, n) * fd.ds(inlet) a_c = (wh * (ch - c0) / dtc * fd.dx - fd.inner(fd.grad(wh), qh + ch * velocity) * fd.dx + wh("+") * fd.jump(qhat, n) * fd.dS + wh * fd.inner(qhat, n) * fd.ds) L_c = 0 # transmission boundary condition F_q = mu_h("+")*fd.jump(qhat, n)*fd.dS + \ mu_h*fd.inner(qhat, n)*fd.ds(outflow) + \
def lax_friedrichs_boundary_flux(s, s_ex, c, v, boundary_ids): r"""Create the Lax-Friedrichs numerical flux through the domain boundary""" return c * inner(s - s_ex, v) * ds(boundary_ids)
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 sdhm_form(self, problem, mesh, bcs_p, bcs_u): rho = problem.rho mu = problem.mu k = problem.k f = problem.f q, p, lambda_h = fire.split(self.solution) w, v, mu_h = fire.TestFunctions(self._W) n = fire.FacetNormal(mesh) h = fire.CellDiameter(mesh) # Stabilizing parameters has_mesh_characteristic_length = True beta_0 = fire.Constant(1e-15) delta_0 = fire.Constant(1) delta_1 = fire.Constant(-1 / 2) delta_2 = fire.Constant(1 / 2) delta_3 = fire.Constant(1 / 2) # h_avg = (h('+') + h('-')) / 2. beta = beta_0 / h beta_avg = beta_0 / h("+") if has_mesh_characteristic_length: delta_2 = delta_2 * h * h delta_3 = delta_3 * h * h kappa = rho * k / mu inv_kappa = 1.0 / kappa # Classical mixed terms a = (dot(inv_kappa * q, w) - div(w) * p - delta_0 * v * div(q)) * dx L = -delta_0 * f * v * dx # Hybridization terms a += lambda_h("+") * dot(w, n)("+") * dS + mu_h("+") * dot(q, n)("+") * dS a += beta_avg * kappa("+") * (lambda_h("+") - p("+")) * (mu_h("+") - v("+")) * dS # Add the contributions of the pressure boundary conditions to L primal_bc_markers = list(mesh.exterior_facets.unique_markers) for pboundary, iboundary in bcs_p: primal_bc_markers.remove(iboundary) a += (pboundary * dot(w, n) + mu_h * dot(q, n)) * ds(iboundary) a += beta * kappa * (lambda_h - pboundary) * mu_h * ds(iboundary) unprescribed_primal_bc = primal_bc_markers for bc_marker in unprescribed_primal_bc: a += (lambda_h * dot(w, n) + mu_h * dot(q, n)) * ds(bc_marker) a += beta * kappa * lambda_h * mu_h * ds(bc_marker) # Add the (weak) contributions of the velocity boundary conditions to L for uboundary, iboundary, component in bcs_u: if component is not None: dim = mesh.geometric_dimension() bc_array = [] for _ in range(dim): bc_array.append(0.0) bc_array[component] = uboundary bc_as_vector = fire.Constant(bc_array) L += mu_h * dot(bc_as_vector, n) * ds(iboundary) else: L += mu_h * dot(uboundary, n) * ds(iboundary) # Stabilizing terms a += (delta_1 * inner(kappa * (inv_kappa * q + grad(p)), delta_0 * inv_kappa * w + grad(v)) * dx) a += delta_2 * inv_kappa * div(q) * div(w) * dx a += delta_3 * inner(kappa * curl(inv_kappa * q), curl( inv_kappa * w)) * dx L += delta_2 * inv_kappa * f * div(w) * dx return a, L