def steady_state(u, **kwargs): z = firedrake.Function(u.function_space()) J_diffusive = -k * S**2 / 2 * ln(1 - inner(grad(z), grad(z)) / S**2) * dx J_uplift = u * z * dx J = J_diffusive - J_uplift return _newton_solve(z, J, J_diffusive, **kwargs)
def hankel_function(expr, n=None): """ Returns a :mod:`firedrake` expression approximation a hankel function of the first kind and order 0 evaluated at :arg:`expr` by using the taylor series, expanded out to :arg:`n` terms. """ if n is None: warn( "Default n to %s, this may cause errors." "If it bugs out on you, try setting n to something more reasonable" % MAX_N) n = MAX_N j_0 = 0 for i in range(n): j_0 += (-1)**i * (1 / 4 * expr**2)**i / factorial(i)**2 g = Constant(0.57721566490153286) y_0 = (ln(expr / 2) + g) * j_0 h_n = 0 for i in range(n): h_n += 1 / (i + 1) y_0 += (-1)**(i) * h_n * (expr**2 / 4)**(i + 1) / (factorial(i + 1))**2 y_0 *= Constant(2 / pi) imag_unit = Constant((np.zeros(1, dtype=np.complex128) + 1j)[0]) h_0 = j_0 + imag_unit * y_0 return h_0
def solve(dt, z0, u, **kwargs): z = z0.copy(deepcopy=True) J_diffusive = -k * S**2 / 2 * ln(1 - inner(grad(z), grad(z)) / S**2) * dx J_uplift = u * z * dx J = J_diffusive - J_uplift E = 0.5 * (z - z0)**2 * dx + firedrake.Constant(dt) * J scale = 0.5 * z**2 * dx + firedrake.Constant(dt) * J_diffusive return _newton_solve(z, E, scale, **kwargs)
def __init__(self): super().__init__() X = self.X # volume and boundary integrals self.name = "LevelSet Example 2" self.J = (sin(X[0]) * cos(X[1]) * dx + pow(1.3 + X[0], 4.2) * pow(1.4 + X[1], 3.3) * dx + exp(sin(X[0]) + cos(X[1])) * dx + ln(5 + sin(X[0]) + cos(X[1])) * ds) self.set_quadrature(8)
def __init__(self): super().__init__() X = self.X # volume and boundary integrals self.name = "LevelSet Example 3" V = FunctionSpace(self.mesh, "CG", 1) u = interpolate(sin(X[0]) * cos(X[1])**2, V) n = FacetNormal(self.mesh) self.J = (sin(X[0]) * cos(X[1]) * dx + pow(1.3 + X[0], 4.2) * pow(1.4 + X[1], 3.3) * dx + exp(sin(X[0]) + cos(X[1])) * dx + ln(5 + sin(X[0]) + cos(X[1])) * ds + inner(grad(u), n) * ds) self.set_quadrature(8)
def T_dew(parameters, p, r_v): """ Returns the dewpoint temperature as a function of temperature and the water vapour mixing ratio. :arg parameters: a CompressibleParameters object. :arg T: the temperature in K. :arg r_v: the water vapour mixing ratio. """ R_d = parameters.R_d R_v = parameters.R_v T_0 = parameters.T_0 e = p * r_v / (r_v + R_d / R_v) return 243.5 / ((17.67 / ln(e / 611.2)) - 1) + T_0
def eval_hankel_function(pt, n=MAX_N): """ Evaluate a hankel function of the first kind of order 0 at point :arg:`pt` using the Taylor series eptpanded out to degree :arg:`n` """ j_0 = 0 for i in range(n): j_0 += (-1)**i * (1 / 4 * e**2)**i / factorial(i)**2 g = 0.57721566490153286 y_0 = (ln(e / 2) + g) * j_0 h_n = 0 for i in range(n): h_n += 1 / (i + 1) y_0 += (-1)**(i) * h_n * (e**2 / 4)**(i + 1) / (factorial(i + 1))**2 y_0 *= 2 / pi imag_unit = (np.zeros(1, dtype=np.complept128) + 1j)[0] h_0 = j_0 + imag_unit * y_0 return h_0
def hankel_function(expr, n): """ Returns a :mod:`firedrake` expression approximation a hankel function of the first kind and order 0 evaluated at :arg:`expr` by using the taylor series, expanded out to :arg:`n` terms. """ j_0 = 0 for i in range(n): j_0 += (-1)**i * (1 / 4 * expr**2)**i / factorial(i)**2 g = Constant(0.57721566490153286) y_0 = (ln(expr / 2) + g) * j_0 h_n = 0 for i in range(n): h_n += 1 / (i + 1) y_0 += (-1)**(i) * h_n * (expr**2 / 4)**(i + 1) / (factorial(i + 1))**2 y_0 *= Constant(2 / pi) imag_unit = Constant((np.zeros(1, dtype=np.complex128) + 1j)[0]) h_0 = j_0 + imag_unit * y_0 return h_0
def thetaInit(Ainit, Q, Q1, grounded, floating, inversionParams): """Compute intitial theta on the ice shelf (not grounded). Parameters ---------- Ainit : firedrake function A Glens flow law A Q : firedrake function space scalar function space Q1 : firedrake function space 1 deg scalar function space used with 'initWithDeg1' grounded : firedrake function Mask with 1s for grounded 0 for floating. floating : firedrake function Mask with 1s for floating 0 for grounded. Returns ------- theta : firedrake function theta for floating ice """ # Get initial file if there is one to init inversion checkFile = inversionParams['initFile'] Quse = Q # Use degree 1 solution if prompted if inversionParams['initWithDeg1']: checkFile = f'{inversionParams["inversionResult"]}.deg1' Quse = Q1 # Now check if there is a file specificed, and if so, init with that if checkFile is not None: Print(f'Init. with theta: {checkFile}') thetaTemp = mf.getCheckPointVars(checkFile, 'thetaInv', Quse)['thetaInv'] thetaInit = icepack.interpolate(thetaTemp, Q) return thetaInit # No initial theta, so use initial A to init inversion Atheta = mf.firedrakeSmooth(Ainit, alpha=1000) theta = firedrake.ln(Atheta) theta = firedrake.interpolate(theta, Q) return theta
def __init__(self, salinity, temperature, pressure_perturbation, z, GammaTfunc=None, velocity=None, ice_heat_flux=True, HJ99Gamma=False, f=None): super().__init__(salinity, temperature, pressure_perturbation, z) if velocity is None: # Use constant turbulent thermal and salinity exchange values given in Holland and Jenkins 1999. gammaT = self.gammaT gammaS = self.gammaS else: u = velocity if HJ99Gamma: # Holland and Jenkins 1999 turbulent heat/salt exchange velocity as a function # of friction velocity. This should be the same as in MITgcm. # Holland, D.M. and Jenkins, A., 1999. Modeling thermodynamic ice–ocean interactions at # the base of an ice shelf. Journal of Physical Oceanography, 29(8), pp.1787-1800. # Calculate friction velocity if isinstance(u, float): print("Input velocity:", u) u_bounded = max(u, 1e-3) print("Bounded velocity:", u_bounded) else: u_bounded = conditional(u > 1e-3, u, 1e-3) u_star = pow(self.C_d * pow(u_bounded, 2), 0.5) # Calculate turbulent component of thermal and salinity exchange velocity (Eq 15) # N.b MITgcm sets etastar = 1 so that the first part of Gamma_turb term below is constant. # eta_star = (1 + (zeta_N * u_star) / (f * L0 * Rc))^-1/2 (Eq 18) # In H&J99 eta_star is set to 1 when the Obukhov length is negative (i.e the buoyancy flux # is destabilising. This occurs during freezing. (Melting is stabilising) # So it does seem a bit odd because then eta_star is tuned to freezing conditions # need to work this out...? Gamma_Turb = 1.0 / (2.0 * self.zeta_N * self.eta_star) - 1.0 / self.k if f is not None: # Add extra term if using coriolis term # Calculate viscous sublayer thickness (Eq 17) h_nu = 5.0 * self.nu / u_star Gamma_Turb += ln( u_star * self.zeta_N * pow(self.eta_star, 2) / (abs(f) * h_nu)) / self.k # Calculate molecular components of thermal exchange velocity (Eq 16) GammaT_Mole = 12.5 * pow(self.Pr, 2.0 / 3.0) - 6.0 # Calculate molecular component of salinity exchange velocity (Eq 16) GammaS_Mole = 12.5 * pow(self.Sc, 2.0 / 3.0) - 6.0 # Calculate thermal and salinity exchange velocity. (Eq 14) # Do we need to catch -ve gamma? could have -ve Gamma_Turb? gammaT = u_star / (Gamma_Turb + GammaT_Mole) gammaS = u_star / (Gamma_Turb + GammaS_Mole) # print exchange velocities if testing when input velocity is a float. if isinstance(gammaT, float) or isinstance(gammaS, float): print("gammaT = ", gammaT) print("gammaS = ", gammaS) else: # ISOMIP+ based on Jenkins et al 2010. Measurement of basal rates beneath Ronne Ice Shelf u_tidal = 0.01 u_star = pow(self.C_d * (pow(u, 2) + pow(u_tidal, 2)), 0.5) if GammaTfunc is None: gammaT = self.GammaT * u_star gammaS = self.GammaS * u_star else: gammaT = GammaTfunc * u_star gammaS = (GammaTfunc / 35.0) * u_star # print exchange velocities if testing when input velocity is a float. if isinstance(gammaT, float) or isinstance(gammaS, float): print("gammaT = ", gammaT) print("gammaS = ", gammaS) b_plus_cPb = (self.b + self.c * self.P_full ) # save calculating this each time... # Calculate coefficients in quadratic equation for salinity at ice-ocean boundary. # Aa.Sb^2 + Bb.Sb + Cc = 0 Aa = self.c_p_m * gammaT * self.a Bb = -gammaS * self.Lf Bb -= self.c_p_m * gammaT * self.T Bb += self.c_p_m * gammaT * b_plus_cPb Cc = gammaS * self.S * self.Lf if ice_heat_flux: Aa -= gammaS * self.c_p_i * self.a Bb += gammaS * self.S * self.c_p_i * self.a Bb -= gammaS * self.c_p_i * b_plus_cPb Bb += gammaS * self.c_p_i * self.T_ice Cc += gammaS * self.S * self.c_p_i * b_plus_cPb Cc -= gammaS * self.S * self.c_p_i * self.T_ice S1 = (-Bb + pow(Bb**2 - 4.0 * Aa * Cc, 0.5)) / (2.0 * Aa) S2 = (-Bb - pow(Bb**2 - 4.0 * Aa * Cc, 0.5)) / (2.0 * Aa) print(type(S1)) print(S1) if isinstance(S1, (float, sympy.core.numbers.Float)): # Print statements for testing print("S1 = ", S1) print("S2 = ", S2) if S1 > 0: self.Sb = S1 print("Choose S1") else: self.Sb = S2 print("Choose S2") elif isinstance(S1, sympy.core.add.Add): self.Sb = S2 else: self.Sb = conditional(S1 > 0.0, S1, S2) self.Tb = self.a * self.Sb + self.b + self.c * self.P_full self.wb = gammaS * (self.S - self.Sb) / self.Sb if ice_heat_flux: self.Q_ice = -self.rho0 * (self.T_ice - self.Tb) * self.c_p_i * self.wb else: if isinstance(S1, float): self.Q_ice = 0.0 else: self.Q_ice = Constant(0.0) self.Q_mixed = -self.rho0 * self.c_p_m * gammaT * (self.Tb - self.T) self.Q_latent = self.Q_ice - self.Q_mixed self.QS_mixed = -self.rho0 * gammaS * (self.Sb - self.S) self.T_flux_bc = -(self.wb + gammaT) * (self.Tb - self.T) self.S_flux_bc = -(self.wb + gammaS) * (self.Sb - self.S)
m = fd.Mesh('meshes/circle.msh') V = fd.FunctionSpace(m, 'DG', degree) Vdim = fd.VectorFunctionSpace(m, 'DG', degree) mesh_analog = fd2mm.MeshAnalog(m) fspace_analog = fd2mm.FunctionSpaceAnalog(cl_ctx, mesh_analog, V) xx = fd.SpatialCoordinate(m) r""" ..math: \ln(\sqrt{(x+1)^2 + (y+1)^2}) i.e. a shift of the fundamental solution """ expr = fd.ln(fd.sqrt((xx[0] + 2)**2 + (xx[1] + 2)**2)) f = fd.Function(V).interpolate(expr) gradf = fd.Function(Vdim).interpolate(fd.grad(expr)) # Let's create an operator which plugs in f, \partial_n f # to Green's formula sigma = sym.make_sym_vector("sigma", 2) op = -(sym.D(LaplaceKernel(2), sym.var("u"), qbx_forced_limit=None) - sym.S(LaplaceKernel(2), sym.n_dot(sigma), qbx_forced_limit=None)) from meshmode.mesh import BTAG_ALL
def test_ice_shelf_inverse_with_noise(solver_type): import icepack Nx, Ny = 32, 32 Lx, Ly = 20e3, 20e3 u0 = 100 h0, δh = 500, 100 T0, δT = 254.15, 5.0 A0 = icepack.rate_factor(T0) δA = icepack.rate_factor(T0 + δT) - A0 def exact_u(x): ρ = ρ_I * (1 - ρ_I / ρ_W) Z = icepack.rate_factor(T0) * (ρ * g * h0 / 4)**n q = 1 - (1 - (δh / h0) * (x / Lx))**(n + 1) δu = Z * q * Lx * (h0 / δh) / (n + 1) return u0 + δu mesh = firedrake.RectangleMesh(Nx, Ny, Lx, Ly) degree = 2 V = firedrake.VectorFunctionSpace(mesh, "CG", degree) Q = firedrake.FunctionSpace(mesh, "CG", degree) x, y = firedrake.SpatialCoordinate(mesh) u_initial = interpolate(as_vector((exact_u(x), 0)), V) q_initial = interpolate(firedrake.Constant(0), Q) h = interpolate(h0 - δh * x / Lx, Q) def viscosity(**kwargs): u = kwargs["velocity"] h = kwargs["thickness"] q = kwargs["log_fluidity"] A = A0 * firedrake.exp(-q / n) return icepack.models.viscosity.viscosity_depth_averaged(velocity=u, thickness=h, fluidity=A) model = icepack.models.IceShelf(viscosity=viscosity) dirichlet_ids = [1, 3, 4] flow_solver = icepack.solvers.FlowSolver(model, dirichlet_ids=dirichlet_ids) r = firedrake.sqrt((x / Lx - 1 / 2)**2 + (y / Ly - 1 / 2)**2) R = 1 / 4 expr = firedrake.max_value(0, δA * (1 - (r / R)**2)) q_true = firedrake.interpolate(-n * firedrake.ln(1 + expr / A0), Q) u_true = flow_solver.diagnostic_solve(velocity=u_initial, thickness=h, log_fluidity=q_true) # Make the noise equal to 1% of the signal area = firedrake.assemble(firedrake.Constant(1) * dx(mesh)) σ = 0.001 * icepack.norm(u_true) / np.sqrt(area) print(σ) u_obs = u_true.copy(deepcopy=True) shape = u_obs.dat.data_ro.shape u_obs.dat.data[:] += σ * random.standard_normal(shape) / np.sqrt(2) def callback(inverse_solver): E, R = inverse_solver.objective, inverse_solver.regularization misfit = firedrake.assemble(E) / area regularization = firedrake.assemble(R) / area q = inverse_solver.parameter error = firedrake.norm(q - q_true) / np.sqrt(area) print(misfit, regularization, error, flush=True) L = firedrake.Constant(0.25 * Lx) problem = icepack.inverse.InverseProblem( model=model, objective=lambda u: 0.5 * ((u - u_obs) / σ)**2 * dx, regularization=lambda q: 0.5 * L**2 * inner(grad(q), grad(q)) * dx, state_name="velocity", state=u_initial, parameter_name="log_fluidity", parameter=q_initial, solver_kwargs={"dirichlet_ids": dirichlet_ids}, diagnostic_solve_kwargs={"thickness": h}, ) solver = solver_type(problem, callback) max_iterations = 100 iterations = solver.solve(rtol=1e-2, atol=0, max_iterations=max_iterations) print(f"Number of iterations: {iterations}") assert iterations < max_iterations q = solver.parameter assert firedrake.norm(q - q_true) / firedrake.norm(q_initial - q_true) < 1 / 3
def test_ice_shelf_inverse_with_noise(solver_type): import icepack Nx, Ny = 32, 32 Lx, Ly = 20e3, 20e3 u0 = 100 h0, δh = 500, 100 T0, δT = 254.15, 5.0 A0 = icepack.rate_factor(T0) δA = icepack.rate_factor(T0 + δT) - A0 def exact_u(x): ρ = ρ_I * (1 - ρ_I / ρ_W) Z = icepack.rate_factor(T0) * (ρ * g * h0 / 4)**n q = 1 - (1 - (δh / h0) * (x / Lx))**(n + 1) δu = Z * q * Lx * (h0 / δh) / (n + 1) return u0 + δu mesh = firedrake.RectangleMesh(Nx, Ny, Lx, Ly) degree = 2 V = firedrake.VectorFunctionSpace(mesh, 'CG', degree) Q = firedrake.FunctionSpace(mesh, 'CG', degree) x, y = firedrake.SpatialCoordinate(mesh) u_initial = interpolate(as_vector((exact_u(x), 0)), V) q_initial = interpolate(firedrake.Constant(0), Q) h = interpolate(h0 - δh * x / Lx, Q) def viscosity(u, h, q): A = A0 * firedrake.exp(-q / n) return icepack.models.viscosity.viscosity_depth_averaged(u, h, A) ice_shelf = icepack.models.IceShelf(viscosity=viscosity) dirichlet_ids = [1, 3, 4] tol = 1e-12 r = firedrake.sqrt((x / Lx - 1 / 2)**2 + (y / Ly - 1 / 2)**2) R = 1 / 4 expr = firedrake.max_value(0, δA * (1 - (r / R)**2)) q_true = firedrake.interpolate(-n * firedrake.ln(1 + expr / A0), Q) u_true = ice_shelf.diagnostic_solve(h=h, q=q_true, u0=u_initial, dirichlet_ids=dirichlet_ids, tol=tol) # Make the noise equal to 1% of the signal area = firedrake.assemble(firedrake.Constant(1) * dx(mesh)) σ = 0.001 * icepack.norm(u_true) / np.sqrt(area) print(σ) u_obs = u_true.copy(deepcopy=True) shape = u_obs.dat.data_ro.shape u_obs.dat.data[:] += σ * random.standard_normal(shape) / np.sqrt(2) def callback(inverse_solver): E, R = inverse_solver.objective, inverse_solver.regularization misfit = firedrake.assemble(E) / area regularization = firedrake.assemble(R) / area q = inverse_solver.parameter error = firedrake.norm(q - q_true) / np.sqrt(area) print(misfit, regularization, error, flush=True) L = 0.25 * Lx regularization = L**2 / 2 * inner(grad(q_initial), grad(q_initial)) * dx problem = icepack.inverse.InverseProblem( model=ice_shelf, method=icepack.models.IceShelf.diagnostic_solve, objective=lambda u: 0.5 * ((u - u_obs) / σ)**2 * dx, regularization=lambda q: 0.5 * L**2 * inner(grad(q), grad(q)) * dx, state_name='u', state=u_initial, parameter_name='q', parameter=q_initial, model_args={ 'h': h, 'u0': u_initial, 'tol': tol }, dirichlet_ids=dirichlet_ids) solver = solver_type(problem, callback) max_iterations = 100 iterations = solver.solve(rtol=1e-2, atol=0, max_iterations=max_iterations) print('Number of iterations: {}'.format(iterations)) assert iterations < max_iterations q = solver.parameter assert firedrake.norm(q - q_true) / firedrake.norm(q_initial - q_true) < 1 / 3
def test_ice_shelf_inverse(solver_type): import icepack Nx, Ny = 32, 32 Lx, Ly = 20e3, 20e3 u0 = 100 h0, δh = 500, 100 T0, δT = 254.15, 5.0 A0 = icepack.rate_factor(T0) δA = icepack.rate_factor(T0 + δT) - A0 def exact_u(x): ρ = ρ_I * (1 - ρ_I / ρ_W) Z = icepack.rate_factor(T0) * (ρ * g * h0 / 4)**n q = 1 - (1 - (δh / h0) * (x / Lx))**(n + 1) δu = Z * q * Lx * (h0 / δh) / (n + 1) return u0 + δu mesh = firedrake.RectangleMesh(Nx, Ny, Lx, Ly) degree = 2 V = firedrake.VectorFunctionSpace(mesh, 'CG', degree) Q = firedrake.FunctionSpace(mesh, 'CG', degree) x, y = firedrake.SpatialCoordinate(mesh) u_initial = interpolate(as_vector((exact_u(x), 0)), V) q_initial = interpolate(firedrake.Constant(0), Q) h = interpolate(h0 - δh * x / Lx, Q) def viscosity(u, h, q): A = A0 * firedrake.exp(-q / n) return icepack.models.viscosity.viscosity_depth_averaged(u, h, A) ice_shelf = icepack.models.IceShelf(viscosity=viscosity) dirichlet_ids = [1, 3, 4] tol = 1e-12 r = firedrake.sqrt((x / Lx - 1 / 2)**2 + (y / Ly - 1 / 2)**2) R = 1 / 4 expr = firedrake.max_value(0, δA * (1 - (r / R)**2)) q_true = firedrake.interpolate(-n * firedrake.ln(1 + expr / A0), Q) u_true = ice_shelf.diagnostic_solve(h=h, q=q_true, u0=u_initial, dirichlet_ids=dirichlet_ids, tol=tol) area = firedrake.assemble(firedrake.Constant(1) * dx(mesh)) def callback(inverse_solver): E, R = inverse_solver.objective, inverse_solver.regularization misfit = firedrake.assemble(E) / area regularization = firedrake.assemble(R) / area q = inverse_solver.parameter error = firedrake.norm(q - q_true) / np.sqrt(area) print(misfit, regularization, error) L = 1e-4 * Lx problem = icepack.inverse.InverseProblem( model=ice_shelf, method=icepack.models.IceShelf.diagnostic_solve, objective=lambda u: 0.5 * (u - u_true)**2 * dx, regularization=lambda q: 0.5 * L**2 * inner(grad(q), grad(q)) * dx, state_name='u', state=u_initial, parameter_name='q', parameter=q_initial, model_args={ 'h': h, 'u0': u_initial, 'tol': tol }, dirichlet_ids=dirichlet_ids) solver = solver_type(problem, callback) # Set an absolute tolerance so that we stop whenever the RMS velocity # errors are less than 0.1 m/yr area = firedrake.assemble(firedrake.Constant(1) * dx(mesh)) atol = 0.5 * 0.01 * area max_iterations = 100 iterations = solver.solve(rtol=0, atol=atol, max_iterations=max_iterations) print('Number of iterations: {}'.format(iterations)) assert iterations < max_iterations q = solver.parameter assert firedrake.norm(q - q_true) / firedrake.norm(q_initial - q_true) < 1 / 4
def test_greens_formula(degree, family, ambient_dim): fine_order = 4 * degree # Parameter to tune accuracy of pytential fmm_order = 5 # This should be (order of convergence = qbx_order + 1) qbx_order = degree with_refinement = True qbx_kwargs = { 'fine_order': fine_order, 'fmm_order': fmm_order, 'qbx_order': qbx_order } if ambient_dim == 2: mesh = mesh2d r""" ..math: \ln(\sqrt{(x+1)^2 + (y+1)^2}) i.e. a shift of the fundamental solution """ x, y = fd.SpatialCoordinate(mesh) expr = fd.ln(fd.sqrt((x + 2)**2 + (y + 2)**2)) elif ambient_dim == 3: mesh = mesh3d x, y, z = fd.SpatialCoordinate(mesh) r""" ..math: \f{1}{4\pi \sqrt{(x-2)^2 + (y-2)^2 + (z-2)^2)}} i.e. a shift of the fundamental solution """ expr = fd.Constant(1 / 4 / fd.pi) * 1 / fd.sqrt((x - 2)**2 + (y - 2)**2 + (z - 2)**2) else: raise ValueError("Ambient dimension must be 2 or 3, not %s" % ambient_dim) # Let's compute some layer potentials! V = fd.FunctionSpace(mesh, family, degree) Vdim = fd.VectorFunctionSpace(mesh, family, degree) mesh_analog = fd2mm.MeshAnalog(mesh) fspace_analog = fd2mm.FunctionSpaceAnalog(cl_ctx, mesh_analog, V) true_sol = fd.Function(V).interpolate(expr) grad_true_sol = fd.Function(Vdim).interpolate(fd.grad(expr)) # Let's create an operator which plugs in f, \partial_n f # to Green's formula sigma = sym.make_sym_vector("sigma", ambient_dim) op = -(sym.D( LaplaceKernel(ambient_dim), sym.var("u"), qbx_forced_limit=None) - sym.S(LaplaceKernel(ambient_dim), sym.n_dot(sigma), qbx_forced_limit=None)) from meshmode.mesh import BTAG_ALL outer_bdy_id = BTAG_ALL # Think of this like :mod:`pytential`'s :function:`bind` pyt_op = fd2mm.fd_bind(cl_ctx, fspace_analog, op, source=(V, outer_bdy_id), target=V, qbx_kwargs=qbx_kwargs, with_refinement=with_refinement) # Compute the operation and store in result result = fd.Function(V) pyt_op(queue, u=true_sol, sigma=grad_true_sol, result_function=result) # Compare with f fnorm = fd.sqrt(fd.assemble(fd.inner(true_sol, true_sol) * fd.dx)) l2_err = fd.sqrt( fd.assemble(fd.inner(true_sol - result, true_sol - result) * fd.dx)) rel_l2_err = l2_err / fnorm # TODO: Make this more strict assert rel_l2_err < 0.09