def advective_flux(self, **kwargs): keys = ('energy', 'velocity', 'vertical_velocity', 'thickness', 'energy_inflow', 'energy_surface') keys_alt = ('E', 'u', 'w', 'h', 'E_inflow', 'E_surface') E, u, w, h, E_inflow, E_surface = get_kwargs_alt( kwargs, keys, keys_alt) Q = E.function_space() ψ = firedrake.TestFunction(Q) U = firedrake.as_vector((u[0], u[1], w)) flux_cells = -E * inner(U, grad(ψ)) * h * dx mesh = Q.mesh() ν = facet_normal_2(mesh) outflow = firedrake.max_value(inner(u, ν), 0) inflow = firedrake.min_value(inner(u, ν), 0) flux_outflow = (E * outflow * ψ * h * ds_v + E * firedrake.max_value(-w, 0) * ψ * h * ds_b + E * firedrake.max_value(+w, 0) * ψ * h * ds_t) flux_inflow = (E_inflow * inflow * ψ * h * ds_v + E_surface * firedrake.min_value(-w, 0) * ψ * h * ds_b + E_surface * firedrake.min_value(+w, 0) * ψ * h * ds_t) return flux_cells + flux_outflow + flux_inflow
def Bueler_profile(mesh, R): x, y = firedrake.SpatialCoordinate(mesh) r = firedrake.sqrt(x**2 + y**2) h_divide = (2 * R * (alpha/A0)**(1/n) * (n-1)/n)**(n/(2*n+2)) h_part2 = (n+1)*(r/R) - n*(r/R)**((n+1)/n) + n*(max_value(1-(r/R),0))**((n+1)/n) - 1 h_expr = (h_divide/((n-1)**(n/(2*n+2)))) * (max_value(h_part2,0))**(n/(2*n+2)) return h_expr
def advective_flux(self, **kwargs): keys = ( "energy", "velocity", "vertical_velocity", "thickness", "energy_inflow", "energy_surface", ) E, u, w, h, E_inflow, E_surface = itemgetter(*keys)(kwargs) Q = E.function_space() ψ = firedrake.TestFunction(Q) # NOTE: Be careful here going to xz! You might have to separate this into # the sum of a horizontal and vertical flux if we're shadowing Firedrake's # grad operator with out own specialized one. U = firedrake.as_vector((u[0], u[1], w)) flux_cells = -E * inner(U, grad(ψ)) * h * dx ν = FacetNormal(Q.mesh()) outflow = firedrake.max_value(inner(u, ν), 0) inflow = firedrake.min_value(inner(u, ν), 0) flux_outflow = (E * outflow * ψ * h * ds_v + E * firedrake.max_value(-w, 0) * ψ * h * ds_b + E * firedrake.max_value(+w, 0) * ψ * h * ds_t) flux_inflow = (E_inflow * inflow * ψ * h * ds_v + E_surface * firedrake.min_value(-w, 0) * ψ * h * ds_b + E_surface * firedrake.min_value(+w, 0) * ψ * h * ds_t) return flux_cells + flux_outflow + flux_inflow
def _advect(self, dt, E, u, w, h, s, E_inflow, E_surface): Q = E.function_space() φ, ψ = firedrake.TrialFunction(Q), firedrake.TestFunction(Q) U = firedrake.as_vector((u[0], u[1], w)) flux_cells = -φ * inner(U, grad(ψ)) * h * dx mesh = Q.mesh() ν = facet_normal_2(mesh) outflow = firedrake.max_value(inner(u, ν), 0) inflow = firedrake.min_value(inner(u, ν), 0) flux_outflow = φ * ψ * outflow * h * ds_v + \ φ * ψ * firedrake.max_value(-w, 0) * h * ds_b + \ φ * ψ * firedrake.max_value(+w, 0) * h * ds_t F = φ * ψ * h * dx + dt * (flux_cells + flux_outflow) flux_inflow = -E_inflow * ψ * inflow * h * ds_v \ -E_surface * ψ * firedrake.min_value(-w, 0) * h * ds_b \ -E_surface * ψ * firedrake.min_value(+w, 0) * h * ds_t A = E * ψ * h * dx + dt * flux_inflow solver_parameters = {'ksp_type': 'preonly', 'pc_type': 'lu'} degree_E = E.ufl_element().degree() degree_u = u.ufl_element().degree() degree = (3 * degree_E[0] + degree_u[0], 2 * degree_E[1] + degree_u[1]) form_compiler_parameters = {'quadrature_degree': degree} firedrake.solve(F == A, E, solver_parameters=solver_parameters, form_compiler_parameters=form_compiler_parameters)
def getModelVelocity(baseName, Q, V, minSigma=5, maxSigma=100): """Read in a tiff velocity data set and return firedrake interpolate functions. Parameters ---------- baseName : str baseName should be of the form pattern.*.abc or pattern The wildcard (*) will be filled with the suffixes (vx, vy.) e.g.,pattern.vx.abc.tif, pattern.vy.abc.tif. Q : firedrake function space function space V : firedrake vector space vector space Returns ------- uObs firedrake interp function on V velocity (m/yr) speed firedrake interp function on Q speed in (m) sigmaX firedrake interp function on Q vx error (m) sigmaY firedrake interp function on Q vy error (m) """ # suffixes for products used suffixes = ['vx', 'vy', 'ex', 'ey'] rasters = {} # prep baseName - baseName.*.xyz.tif or baseName.* if '*' not in baseName: baseName += '.*' if '.tif' not in baseName: baseName += '.tif' # read data for suffix in suffixes: myBand = baseName.replace('*', suffix) if not os.path.exists(myBand): u.myerror(f'Velocity/error file - {myBand} - does not exist') rasters[suffix] = rasterio.open(myBand, 'r') # Firedrake interpolators uObs = icepack.interpolate((rasters['vx'], rasters['vy']), V) # force error to be at least 1 to avoid 0 or negatives. sigmaX = icepack.interpolate(rasters['ex'], Q) sigmaX = icepack.interpolate(firedrake.max_value(sigmaX, minSigma), Q) sigmaX = icepack.interpolate(firedrake.min_value(sigmaX, maxSigma), Q) sigmaY = icepack.interpolate(rasters['ey'], Q) sigmaY = icepack.interpolate(firedrake.max_value(sigmaY, minSigma), Q) sigmaY = icepack.interpolate(firedrake.min_value(sigmaY, maxSigma), Q) speed = icepack.interpolate(firedrake.sqrt(inner(uObs, uObs)), Q) # return results return uObs, speed, sigmaX, sigmaY
def test_shallow_ice_prognostic_solve(): R = Constant(500e3) num_refinements = 4 mesh = firedrake.UnitDiskMesh(num_refinements) mesh.coordinates.dat.data[:] *= float(R) T = Constant(254.15) A = icepack.rate_factor(T) Q = firedrake.FunctionSpace(mesh, "CG", 2) V = firedrake.VectorFunctionSpace(mesh, "CG", 2) x, y = firedrake.SpatialCoordinate(mesh) r = firedrake.sqrt(x**2 + y**2) β = Constant(0.5) h_divide = Constant(4e3) h_expr = h_divide * firedrake.max_value(0, 1 - (r / (β * R))**2) h_0 = interpolate(h_expr, Q) h = h_0.copy(deepcopy=True) u = firedrake.Function(V) b = Constant(0.0) s = interpolate(b + h, Q) a = Constant(0.0) model = icepack.models.ShallowIce() solver = icepack.solvers.FlowSolver(model) final_time = 100.0 dt = 1.0 num_steps = int(final_time / dt) for step in range(num_steps): u = solver.diagnostic_solve(velocity=u, thickness=h, surface=s, fluidity=A) h = solver.prognostic_solve(dt, thickness=h, velocity=u, accumulation=a) h.interpolate(firedrake.max_value(0, h)) s.assign(b + h) error = abs(assemble(h * dx) / assemble(h_0 * dx) - 1) assert error < 1 / 2**(num_refinements + 1)
def terminus(u, h, s): r"""Return the terminal stress part of the hybrid model action functional The power exerted due to stress at the calving terminus :math:`\Gamma` is .. math:: E(u) = \int_\Gamma\int_0^1\left(\rho_Ig(1 - \zeta) - \rho_Wg(\zeta_{\text{sl}} - \zeta)_+\right)u\cdot\nu\; h\, d\zeta\; ds where :math:`\zeta_\text{sl}` is the relative depth to sea level and the :math:`(\zeta_\text{sl} - \zeta)_+` denotes only the positive part. 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 """ mesh = u.ufl_domain() zdegree = u.ufl_element().degree()[1] x, y, ζ = firedrake.SpatialCoordinate(mesh) b = s - h ζ_sl = firedrake.max_value(-b, 0) / h p_W = ρ_W * g * h * _pressure_approx(zdegree + 1)(ζ, ζ_sl) p_I = ρ_I * g * h * (1 - ζ) ν = facet_normal_2(mesh) return (p_I - p_W) * inner(u, ν) * h
def solve(self, dt, **kwargs): if not hasattr(self, '_solvers'): self._setup(**kwargs) else: for name, field in kwargs.items(): if isinstance(field, firedrake.Function): self.fields[name].assign(field) δt = self._timestep δt.assign(dt) D = self.fields.get('damage', self.fields.get('D')) solver1, solver2, solver3 = self._solvers D1, D2 = self._stages dD = self._damage_change solver1.solve() D1.assign(D + dD) solver2.solve() D2.assign(3 / 4 * D + 1 / 4 * (D1 + dD)) solver3.solve() D.assign(1 / 3 * D + 2 / 3 * (D2 + dD)) S = self.model.sources(**self.fields) D.project(min_value(max_value(D + δt * S, 0), 1)) return D.copy(deepcopy=True)
def flux(self, **kwargs): keys = ("damage", "velocity", "damage_inflow") D, u, D_inflow = itemgetter(*keys)(kwargs) Q = D.function_space() φ = firedrake.TestFunction(Q) mesh = Q.mesh() n = firedrake.FacetNormal(mesh) u_n = max_value(0, inner(u, n)) f = D * u_n flux_faces = (f("+") - f("-")) * (φ("+") - φ("-")) * dS flux_cells = -D * div(u * φ) * dx flux_out = D * max_value(0, inner(u, n)) * φ * ds flux_in = D_inflow * min_value(0, inner(u, n)) * φ * ds return flux_faces + flux_cells + flux_out + flux_in
def reduceNearGLBeta(s, sOrig, zF, grounded, Q, thresh, limit=False): """Compute beta reduction in area near grounding line where the height above floation is less than thresh. Parameters ---------- s : firedrake function Current surface. sOrig : firedrake function Original surface. zF : firedrake function Flotation height. grounded : firedrake function grounde mask Q : firedrake function space scaler function space for model thresh : float Threshold to determine where to reduce beta (zAbove < thresh) """ # compute original height above flotation for grounded region sAboveOrig = (sOrig - zF) * grounded # avoid negative/zero values sAboveOrig = firedrake.max_value(sAboveOrig, 0.0) # Current height above flotation with negative values zeroed out. sAbove = firedrake.max_value((s - zF) * grounded, 0) # mask so only areas less than thresh but grounded sMask = (sAbove < thresh) * grounded # print(f'{sAbove.dat.data_ro.min()}, {sAbove.dat.data_ro.max()} {thresh}') # Inverse mask sMaskInv = sMask < 1 # scale = fraction of original height above flotation # Use 5 to avoid potentially large ratio at small values. scaleBeta = sAbove / \ firedrake.max_value(firedrake.min_value(thresh, sAboveOrig), 3) if limit: scaleBeta = firedrake.min_value(3, scaleBeta) scaleBeta = scaleBeta * sMask + sMaskInv # scaleBeta = icepack.interpolate(firedrake.min_value(scaleBeta,1.),Q) # sqrt so tau = scale * beta^2 # scaleBeta = icepack.interpolate(firedrake.sqrt(scaleBeta) * grounded, Q) # Removed grounded above because grounded is always applied in friction scaleBeta = icepack.interpolate(firedrake.sqrt(scaleBeta), Q) # print(f'{scaleBeta.dat.data_ro.min()}, {scaleBeta.dat.data_ro.max()}') return scaleBeta
def flux(self, **kwargs): keys = ('damage', 'velocity', 'damage_inflow') keys_alt = ('D', 'u', 'D_inflow') D, u, D_inflow = get_kwargs_alt(kwargs, keys, keys_alt) Q = D.function_space() φ = firedrake.TestFunction(Q) mesh = Q.mesh() n = firedrake.FacetNormal(mesh) u_n = max_value(0, inner(u, n)) f = D * u_n flux_faces = (f('+') - f('-')) * (φ('+') - φ('-')) * dS flux_cells = -D * div(u * φ) * dx flux_out = D * max_value(0, inner(u, n)) * φ * ds flux_in = D_inflow * min_value(0, inner(u, n)) * φ * ds return flux_faces + flux_cells + flux_out + flux_in
def readSMB(SMBfile, Q): ''' Read SMB file an limit values to +/- 6 to avoid no data values Returns water equivalent values. ''' if not os.path.exists: myerror(f'readSMB: SMB file ({SMBfile}) does not exist') SMB = mf.getModelVarFromTiff(SMBfile, Q) # avoid any unreasonably large value SMB = icepack.interpolate( firedrake.max_value(firedrake.min_value(SMB, 6), -6), Q) return SMB
def solve(self, dt, h0, a, u, h_inflow=None): r"""Propagate the thickness forward by one timestep This function uses the implicit Euler timestepping scheme to avoid the stability issues associated to using continuous finite elements for advection-type equations. The implicit Euler scheme is stable for any timestep; you do not need to ensure that the CFL condition is satisfied in order to get an answer. Nonetheless, keeping the timestep within the CFL bound is a good idea for accuracy. Parameters ---------- dt : float Timestep h0 : firedrake.Function Initial ice thickness a : firedrake.Function Sum of accumulation and melt rates u : firedrake.Function Ice velocity h_inflow : firedrake.Function Thickness of the upstream ice that advects into the domain Returns ------- h : firedrake.Function Ice thickness at `t + dt` """ grad, ds = self.grad, self.ds h_inflow = h_inflow if h_inflow is not None else h0 Q = h0.function_space() h, φ = firedrake.TrialFunction(Q), firedrake.TestFunction(Q) n = self.facet_normal(Q.mesh()) outflow = firedrake.max_value(inner(u, n), 0) inflow = firedrake.min_value(inner(u, n), 0) flux_cells = -h * inner(u, grad(φ)) * dx flux_out = h * φ * outflow * ds F = h * φ * dx + dt * (flux_cells + flux_out) accumulation = a * φ * dx flux_in = -h_inflow * φ * inflow * ds A = h0 * φ * dx + dt * (accumulation + flux_in) h = h0.copy(deepcopy=True) solver_parameters = {'ksp_type': 'preonly', 'pc_type': 'lu'} firedrake.solve(F == A, h, solver_parameters=solver_parameters) return h
def reduceNearGLBeta(s, sOrig, zF, grounded, Q, thresh, limit=False): """Compute beta reduction in area near grounding line where the height above floation is less than thresh. Parameters ---------- s : firedrake function Current surface. sOrig : firedrake function Original surface. zF : firedrake function Flotation height. grounded : firedrake function grounde mask Q : firedrake function space scaler function space for model thresh : float Threshold to determine where to reduce beta (zAbove < thresh) """ # compute original height above flotation for grounded region sAboveOrig = (sOrig - zF) * grounded # avoid negative/zero values sAboveOrig = firedrake.max_value(sAboveOrig, 0.001) # Current height above flotation with negative values zeroed out. sAbove = firedrake.max_value((s - zF) * grounded, 0) # mask so only areas less than thresh but grounded sMask = (sAbove <= thresh) * grounded # Inverse mask sMaskInv = sMask < 1 # scale = fraction of of original height above flotation scaleBeta = sAbove / \ firedrake.max_value(firedrake.min_value(thresh, sAboveOrig), 5) if limit: scaleBeta = firedrake.min_value(3, scaleBeta) scaleBeta = scaleBeta * sMask + sMaskInv # scaleBeta = icepack.interpolate(firedrake.min_value(scaleBeta,1.),Q) # sqrt so tau = scale * beta^2 scaleBeta = icepack.interpolate(firedrake.sqrt(scaleBeta) * grounded, Q) return scaleBeta
def friction(**kwargs): keys = ('velocity', 'thickness', 'surface', 'friction') u, h, s, C = map(kwargs.get, keys) p_W = ρ_W * g * max_value(0, -(s - h)) p_I = ρ_I * g * h N = p_I - p_W τ_c = N / 2 u_c = (τ_c / C)**m u_b = sqrt(inner(u, u)) return τ_c * ((u_c**(1 / m + 1) + u_b**(1 / m + 1))**(m / (m + 1)) - u_c)
def solve(self, dt, h0, a, u, h_inflow=None): r"""Propagate the thickness forward by one timestep This function uses an implicit second-order Taylor-Galerkin (also known as Lax-Wendroff) scheme to solve the conservative advection equation for ice thickness. Parameters ---------- dt : float Timestep h0 : firedrake.Function Initial ice thickness a : firedrake.Function Sum of accumulation and melt rates u : firedrake.Function Ice velocity h_inflow : firedrake.Function Thickness of the upstream ice that advects into the domain Returns ------- h : firedrake.Function Ice thickness at `t + dt` """ grad, div, ds = self.grad, self.div, self.ds h_inflow = h_inflow if h_inflow is not None else h0 Q = h0.function_space() h, φ = firedrake.TrialFunction(Q), firedrake.TestFunction(Q) n = self.facet_normal(Q.mesh()) outflow = firedrake.max_value(inner(u, n), 0) inflow = firedrake.min_value(inner(u, n), 0) flux_cells = -h * inner(u, grad(φ)) * dx flux_cells_lax = 0.5 * dt * div(h * u) * inner(u, grad(φ)) * dx flux_out = (h - 0.5 * dt * div(h * u)) * φ * outflow * ds F = h * φ * dx + dt * (flux_cells + flux_cells_lax + flux_out) accumulation = a * φ * dx flux_in = -(h_inflow - 0.5 * dt * div(h0 * u)) * φ * inflow * ds A = h0 * φ * dx + dt * (accumulation + flux_in) h = h0.copy(deepcopy=True) solver_parameters = {'ksp_type': 'preonly', 'pc_type': 'lu'} firedrake.solve(F == A, h, solver_parameters=solver_parameters) return h
def test_mass_transport_solver_convergence(solver_type): Lx, Ly = 1.0, 1.0 u0 = 1.0 h_in, dh = 1.0, 0.2 delta_x, error = [], [] model = icepack.models.IceShelf() for N in range(24, 97, 4): delta_x.append(Lx / N) mesh = firedrake.RectangleMesh(N, N, Lx, Ly) x, y = firedrake.SpatialCoordinate(mesh) degree = 1 V = firedrake.VectorFunctionSpace(mesh, family='CG', degree=degree) Q = firedrake.FunctionSpace(mesh, family='CG', degree=degree) solver = icepack.solvers.FlowSolver( model, prognostic_solver_type=solver_type ) h0 = interpolate(h_in - dh * x / Lx, Q) a = firedrake.Function(Q) u = interpolate(firedrake.as_vector((u0, 0)), V) T = 0.5 δx = 1.0 / N δt = δx / u0 num_timesteps = int(T / δt) h = h0.copy(deepcopy=True) for step in range(num_timesteps): h = solver.prognostic_solve( δt, thickness=h, velocity=u, accumulation=a, thickness_inflow=h0 ) z = x - u0 * num_timesteps * δt h_exact = interpolate(h_in - dh/Lx * firedrake.max_value(0, z), Q) error.append(norm(h - h_exact) / norm(h_exact)) print(delta_x[-1], error[-1]) log_delta_x = np.log2(np.array(delta_x)) log_error = np.log2(np.array(error)) slope, intercept = np.polyfit(log_delta_x, log_error, 1) print('log(error) ~= {:g} * log(dx) + {:g}'.format(slope, intercept)) assert slope > degree - 0.1
def compute_surface(h, b): r"""Return the ice surface elevation consistent with a given thickness and bathymetry If the bathymetry beneath a tidewater glacier is too low, the ice will go afloat. The surface elevation of a floating ice shelf is .. math:: s = (1 - \rho_I / \rho_W)h, provided everything is in hydrostatic balance. """ Q = h.ufl_function_space() s_expr = firedrake.max_value(h + b, (1 - ρ_I / ρ_W) * h) return firedrake.interpolate(s_expr, Q)
def __call__(self, dt, **kwargs): keys = ('thickness', 'velocity', 'accumulation') keys_alt = ('h', 'u', 'a') h, u, a = utilities.get_kwargs_alt(kwargs, keys, keys_alt) h_inflow = kwargs.get('thickness_inflow', kwargs.get('h_inflow', h)) Q = h.function_space() q = firedrake.TestFunction(Q) grad, ds, n = self.grad, self.ds, self.facet_normal(Q.mesh()) u_n = inner(u, n) flux_cells = -inner(h * u, grad(q)) * dx flux_out = h * firedrake.max_value(u_n, 0) * q * ds flux_in = h_inflow * firedrake.min_value(u_n, 0) * q * ds accumulation = a * q * dx return accumulation - (flux_in + flux_out + flux_cells)
def test_converting_fields(): δT = 5.0 T_surface = Tm - δT T_expr = firedrake.min_value(Tm, T_surface + 2 * δT * (1 - ζ)) f_expr = firedrake.max_value(0, 0.0033 * (1 - 2 * ζ)) model = icepack.models.HeatTransport3D() E = firedrake.project(model.energy_density(T_expr, f_expr), Q) f = firedrake.project(model.meltwater_fraction(E), Q) T = firedrake.project(model.temperature(E), Q) avg_meltwater = firedrake.assemble(f * ds_b) / (Lx * Ly) assert avg_meltwater > 0 avg_temp = firedrake.assemble(T * h * dx) / firedrake.assemble(h * dx) assert (avg_temp > T_surface) and (avg_temp < Tm)
def equation(q): Q = q.function_space() φ = firedrake.TestFunction(Q) F = q * u mesh = Q.mesh() n = firedrake.FacetNormal(mesh) c = abs(inner(u, n)) sources = s * φ * dx fluxes = (forms.cell_flux(F, φ) + forms.central_facet_flux(F, φ) + forms.lax_friedrichs_facet_flux(q, c, φ) + q * max_value(0, inner(u, n)) * φ * ds + q_in * min_value(0, inner(u, n)) * φ * ds) return sources - fluxes
def setup(self, **kwargs): r"""Create the internal data structures that help reuse information from past prognostic solves""" for name, field in kwargs.items(): if name in self._fields.keys(): self._fields[name].assign(field) else: if isinstance(field, firedrake.Constant): self._fields[name] = firedrake.Constant(field) elif isinstance(field, firedrake.Function): self._fields[name] = field.copy(deepcopy=True) else: raise TypeError( "Input %s field has type %s, must be Constant or Function!" % (name, type(field)) ) dt = firedrake.Constant(1.0) h = self._fields["thickness"] u = self._fields["velocity"] h_0 = h.copy(deepcopy=True) Q = h.function_space() mesh = Q.mesh() n = FacetNormal(mesh) outflow = firedrake.max_value(0, inner(u, n)) inflow = firedrake.min_value(0, inner(u, n)) # Additional streamlining terms that give 2nd-order accuracy q = firedrake.TestFunction(Q) ds = firedrake.ds if mesh.layers is None else firedrake.ds_v flux_cells = -div(h * u) * inner(u, grad(q)) * dx flux_out = div(h * u) * q * outflow * ds flux_in = div(h_0 * u) * q * inflow * ds d2h_dt2 = flux_cells + flux_out + flux_in dh_dt = self._continuity(dt, **self._fields) F = (h - h_0) * q * dx - dt * (dh_dt + 0.5 * dt * d2h_dt2) problem = firedrake.NonlinearVariationalProblem(F, h) self._solver = firedrake.NonlinearVariationalSolver( problem, solver_parameters=self._solver_parameters ) self._thickness_old = h_0 self._timestep = dt
def __call__(self, dt, **kwargs): keys = ("thickness", "velocity", "accumulation") h, u, a = itemgetter(*keys)(kwargs) h_inflow = kwargs.get("thickness_inflow", h) Q = h.function_space() q = firedrake.TestFunction(Q) mesh = Q.mesh() n = FacetNormal(mesh) ds = firedrake.ds if mesh.layers is None else firedrake.ds_v u_n = inner(u, n) flux_cells = -inner(h * u, grad(q)) * dx flux_out = h * firedrake.max_value(u_n, 0) * q * ds flux_in = h_inflow * firedrake.min_value(u_n, 0) * q * ds accumulation = a * q * dx return accumulation - (flux_in + flux_out + flux_cells)
def compute_surface(**kwargs): r"""Return the ice surface elevation consistent with a given thickness and bathymetry If the bathymetry beneath a tidewater glacier is too low, the ice will go afloat. The surface elevation of a floating ice shelf is .. math:: s = (1 - \rho_I / \rho_W)h, provided everything is in hydrostatic balance. """ # TODO: Remove the 'h' and 'b' arguments once these are deprecated. h = kwargs.get('thickness', kwargs.get('h')) b = kwargs.get('bed', kwargs.get('b')) Q = h.ufl_function_space() s_expr = firedrake.max_value(h + b, (1 - ρ_I / ρ_W) * h) return firedrake.interpolate(s_expr, Q)
def setup(self, **kwargs): r"""Create the internal data structures that help reuse information from past prognostic solves""" for name, field in kwargs.items(): if name in self._fields.keys(): self._fields[name].assign(field) else: if isinstance(field, firedrake.Constant): self._fields[name] = firedrake.Constant(field) elif isinstance(field, firedrake.Function): self._fields[name] = field.copy(deepcopy=True) else: raise TypeError( 'Input fields must be Constant or Function!') dt = firedrake.Constant(1.) h = self._fields.get('thickness', self._fields.get('h')) u = self._fields.get('velocity', self._fields.get('u')) h_0 = h.copy(deepcopy=True) Q = h.function_space() model = self._continuity n = model.facet_normal(Q.mesh()) outflow = firedrake.max_value(0, inner(u, n)) inflow = firedrake.min_value(0, inner(u, n)) # Additional streamlining terms that give 2nd-order accuracy q = firedrake.TestFunction(Q) div, grad, ds = model.div, model.grad, model.ds flux_cells = -div(h * u) * inner(u, grad(q)) * dx flux_out = div(h * u) * q * outflow * ds flux_in = div(h_0 * u) * q * inflow * ds d2h_dt2 = flux_cells + flux_out + flux_in dh_dt = model(dt, **self._fields) F = (h - h_0) * q * dx - dt * (dh_dt + 0.5 * dt * d2h_dt2) problem = firedrake.NonlinearVariationalProblem(F, h) self._solver = firedrake.NonlinearVariationalSolver( problem, solver_parameters=self._solver_parameters) self._thickness_old = h_0 self._timestep = dt
def penalty(**kwargs): r"""Return the penalty of the shallow ice action functional The penalty for the shallow ice action functional is .. math:: E(u) = \frac{1}{2}\int_\Omega l^2\nabla u\cdot \nabla u\; dx Parameters ---------- velocity : firedrake.Function thickness : firedrake.Function Returns ------- firedrake.Form """ u, h = get_kwargs_alt(kwargs, ('velocity', 'thickness'), ('u', 'h')) l = 2 * firedrake.max_value(firedrake.CellDiameter(u.ufl_domain()), 5 * h) return .5 * l**2 * inner(grad(u), grad(u))
def betaInit(s, h, speed, V, Q, Q1, grounded, inversionParams): """Compute intitial beta using 0.5 taud. Parameters ---------- s : firedrake function model surface elevation h : firedrake function model thickness speed : firedrake function modelled speed V : firedrake vector function space vector function space Q : firedrake function space scalar function space grounded : firedrake function Mask with 1s for grounded 0 for floating. """ # Use a result from prior inversion checkFile = inversionParams['initFile'] Quse = Q if inversionParams['initWithDeg1']: checkFile = f'{inversionParams["inversionResult"]}.deg1' Quse = Q1 if checkFile is not None: betaTemp = mf.getCheckPointVars(checkFile, 'betaInv', Quse)['betaInv'] beta1 = icepack.interpolate(betaTemp, Q) return beta1 # No prior result, so use fraction of taud tauD = firedrake.project(-rhoI * g * h * grad(s), V) # stress = firedrake.sqrt(firedrake.inner(tauD, tauD)) Print('stress', firedrake.assemble(stress * firedrake.dx)) fraction = firedrake.Constant(0.95) U = max_value(speed, 1) C = fraction * stress / U**(1/m) if inversionParams['friction'] == 'schoof': mExp = 1/m + 1 U0 = firedrake.Constant(inversionParams['uThresh']) C = C * (m/(m+1)) * (U0**mExp + U**mExp)**(1/(m+1)) beta = firedrake.interpolate(firedrake.sqrt(C) * grounded, Q) return beta
def test_mass_transport_solver_convergence(): Lx, Ly = 1.0, 1.0 u0 = 1.0 h_in, dh = 1.0, 0.2 delta_x, error = [], [] mass_transport = MassTransport() for N in range(16, 97, 4): delta_x.append(Lx / N) mesh = firedrake.RectangleMesh(N, N, Lx, Ly) x, y = firedrake.SpatialCoordinate(mesh) degree = 1 V = firedrake.VectorFunctionSpace(mesh, family='CG', degree=degree) Q = firedrake.FunctionSpace(mesh, family='CG', degree=degree) h = interpolate(h_in - dh * x / Lx, Q) a = firedrake.Function(Q) u = interpolate(firedrake.as_vector((u0, 0)), V) T = 0.5 num_timesteps = int(0.5 * N * u0 * T / Lx) dt = T / num_timesteps for k in range(num_timesteps): h = mass_transport.solve(dt, h0=h, a=a, u=u) z = x - u0 * T h_exact = interpolate(h_in - dh / Lx * firedrake.max_value(0, z), Q) error.append(norm(h - h_exact) / norm(h_exact)) print(delta_x[-1], error[-1]) log_delta_x = np.log2(np.array(delta_x)) log_error = np.log2(np.array(error)) slope, intercept = np.polyfit(log_delta_x, log_error, 1) assert slope > degree - 0.05 print(slope, intercept)
def flotationHeight(zb, Q, rhoI=rhoI, rhoW=rhoW): """Given bed elevation, determine height of flotation for function space Q. Parameters ---------- zb : firedrake interp function bed elevation (m) Q : firedrake function space function space rhoI : [type], optional [description], by default rhoI rhoW : [type], optional [description], by default rhoW Returns ------- zF firedrake interp function Flotation height (m) """ # computation for height above flotation zF = firedrake.interpolate(firedrake.max_value(-zb * (rhoW / rhoI - 1), 0), Q) return zF
def terminus(u, h, s, ice_front_ids=()): r"""Return the terminal stress part of the hybrid model action functional The power exerted due to stress at the calving terminus :math:`\Gamma` is .. math:: E(u) = \int_\Gamma\int_0^1\left(\rho_Ig(1 - \zeta) - \rho_Wg(\zeta_{\text{sl}} - \zeta)_+\right)hd\zeta ds where :math:`\zeta_\text{sl}` is the relative depth to sea level and the :math:`(\zeta - \zeta_\text{sl})` denotes only the positive part. 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 """ xdegree_u, zdegree_u = u.ufl_element().degree() degree_h = h.ufl_element().degree()[0] degree = (xdegree_u + degree_h, 2 * zdegree_u + 1) metadata = {'quadrature_degree': degree} x, y, ζ = firedrake.SpatialCoordinate(u.ufl_domain()) b = s - h ζ_sl = firedrake.max_value(-b, 0) / h p_W = ρ_W * g * h * _pressure_approx(zdegree_u + 1)(ζ, ζ_sl) p_I = ρ_I * g * h * (1 - ζ) ν = facet_normal_2(u.ufl_domain()) dγ = ds_v(tuple(ice_front_ids), metadata=metadata) return (p_I - p_W) * inner(u, ν) * h * dγ