def __init__(self, V, fixed_dims=[], direct_solve=False): if isinstance(fixed_dims, int): fixed_dims = [fixed_dims] self.V = V self.fixed_dims = fixed_dims self.direct_solve = direct_solve self.zero = fd.Constant(V.mesh().topological_dimension() * (0, )) u = fd.TrialFunction(V) v = fd.TestFunction(V) self.zero_fun = fd.Function(V) self.a = 1e-2 * \ fd.inner(u, v) * fd.dx + fd.inner(fd.sym(fd.grad(u)), fd.sym(fd.grad(v))) * fd.dx self.bc_fun = fd.Function(V) if len(self.fixed_dims) == 0: bcs = [fd.DirichletBC(self.V, self.bc_fun, "on_boundary")] else: bcs = [] for i in range(self.V.mesh().topological_dimension()): if i in self.fixed_dims: bcs.append(fd.DirichletBC(self.V.sub(i), 0, "on_boundary")) else: bcs.append( fd.DirichletBC(self.V.sub(i), self.bc_fun.sub(i), "on_boundary")) self.A_ext = fd.assemble(self.a, bcs=bcs, mat_type="aij") self.ls_ext = fd.LinearSolver(self.A_ext, solver_parameters=self.get_params()) self.A_adj = fd.assemble(self.a, bcs=fd.DirichletBC(self.V, self.zero, "on_boundary"), mat_type="aij") self.ls_adj = fd.LinearSolver(self.A_adj, solver_parameters=self.get_params())
def sym_grad(u): r"""Compute the symmetric gradient of a vector field""" axes = get_mesh_axes(u.ufl_domain()) if axes == "xy": return firedrake.sym(firedrake.grad(u)) if axes == "xyz": return firedrake.sym(firedrake.as_tensor((u.dx(0), u.dx(1)))) return u.dx(0)
def get_weak_form(self, V): """ mu is a spatially varying coefficient in the weak form of the elasticity equations. The idea is to make the mesh stiff near the boundary that is being deformed. """ if self.fixed_bids is not None and len(self.fixed_bids) > 0: mu = self.get_mu(V) else: mu = fd.Constant(1.0) u = fd.TrialFunction(V) v = fd.TestFunction(V) return mu * fd.inner(fd.sym(fd.grad(u)), fd.sym(fd.grad(v))) * fd.dx
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 value_form(self): f = self.f def norm(u): return fd.inner(u, u) * fd.dx val = self.l2_reg * norm(f) val += self.sym_grad_reg * norm(fd.sym(fd.grad(f))) val += self.skew_grad_reg * norm(fd.skew(fd.grad(f))) return val
def test_damage_transport(): nx, ny = 32, 32 Lx, Ly = 20e3, 20e3 mesh = firedrake.RectangleMesh(nx, ny, Lx, Ly) x, y = firedrake.SpatialCoordinate(mesh) V = firedrake.VectorFunctionSpace(mesh, "CG", 2) Q = firedrake.FunctionSpace(mesh, "CG", 2) u0 = 100.0 h0, dh = 500.0, 100.0 T = 268.0 ρ = ρ_I * (1 - ρ_I / ρ_W) Z = icepack.rate_factor(T) * (ρ * g * h0 / 4)**n q = 1 - (1 - (dh / h0) * (x / Lx))**(n + 1) du = Z * q * Lx * (h0 / dh) / (n + 1) u = interpolate(as_vector((u0 + du, 0)), V) h = interpolate(h0 - dh * x / Lx, Q) A = firedrake.Constant(icepack.rate_factor(T)) S = firedrake.TensorFunctionSpace(mesh, "DG", 1) ε = firedrake.project(sym(grad(u)), S) M = firedrake.project(membrane_stress(strain_rate=ε, fluidity=A), S) degree = 1 Δ = firedrake.FunctionSpace(mesh, "DG", degree) D_inflow = firedrake.Constant(0.0) D = firedrake.Function(Δ) damage_model = icepack.models.DamageTransport() damage_solver = icepack.solvers.DamageSolver(damage_model) final_time = Lx / u0 max_speed = u.at((Lx - 1.0, Ly / 2), tolerance=1e-10)[0] δx = Lx / nx timestep = δx / max_speed / (2 * degree + 1) num_steps = int(final_time / timestep) dt = final_time / num_steps for step in range(num_steps): D = damage_solver.solve( dt, damage=D, velocity=u, strain_rate=ε, membrane_stress=M, damage_inflow=D_inflow, ) Dmax = D.dat.data_ro[:].max() assert 0 < Dmax < 1
def viscosity_depth_averaged(u=None, h=None, A=None, **kwargs): r"""Return the viscous part of the action for depth-averaged models The viscous component of the action for depth-averaged ice flow is .. math:: E(u) = \frac{n}{n+1}\int_\Omega h\cdot M(\dot\varepsilon, A):\dot\varepsilon\; dx where :math:`M(\dot\varepsilon, A)` is the membrane stress tensor .. math:: M(\dot\varepsilon, A) = A^{-1/n}|\dot\varepsilon|^{1/n - 1} (\dot\varepsilon + \text{tr}\dot\varepsilon\cdot I). This form assumes that we're using the fluidity parameter instead the rheology parameter, the temperature, etc. To use a different variable, you can implement your own viscosity functional and pass it as an argument when initializing model objects to use your functional instead. Parameters ---------- velocity : firedrake.Function thickness : firedrake.Function fluidity : firedrake.Function Returns ------- firedrake.Form """ # NOTE: This mess is for backwards-compatibility, so users can still pass # in the velocity, thickness, and fluidity as positional arguments if they # are still using old code. if (u is not None) or (h is not None) or (A is not None): warnings.warn("Abbreviated names (u, h, A) have been deprecated, use " "full names (velocity, thickness, fluidity) instead.", FutureWarning) if u is None: u = kwargs['velocity'] if h is None: h = kwargs['thickness'] if A is None: A = kwargs['fluidity'] ε = sym(grad(u)) M = membrane_stress(ε, A) return n / (n + 1) * h * inner(M, ε)
def test_eigenvalues(): nx, ny = 32, 32 mesh = firedrake.UnitSquareMesh(nx, ny) x, y = firedrake.SpatialCoordinate(mesh) V = firedrake.VectorFunctionSpace(mesh, family='CG', degree=2) u = interpolate(as_vector((x, 0)), V) Q = firedrake.FunctionSpace(mesh, family='DG', degree=2) ε = sym(grad(u)) Λ1, Λ2 = eigenvalues(ε) λ1 = firedrake.project(Λ1, Q) λ2 = firedrake.project(Λ2, Q) assert norm(λ1 - Constant(1)) < norm(u) / (nx * ny) assert norm(λ2) < norm(u) / (nx * ny)
def test_solver_no_flow_region(): mesh = fd.Mesh("./2D_mesh.msh") no_flow = [2] no_flow_markers = [1] mesh = mark_no_flow_regions(mesh, no_flow, no_flow_markers) P2 = fd.VectorElement("CG", mesh.ufl_cell(), 1) P1 = fd.FiniteElement("CG", mesh.ufl_cell(), 1) TH = P2 * P1 W = fd.FunctionSpace(mesh, TH) (v, q) = fd.TestFunctions(W) # Stokes 1 w_sol1 = fd.Function(W) nu = fd.Constant(0.05) F = NavierStokesBrinkmannForm(W, w_sol1, nu, beta_gls=2.0) x, y = fd.SpatialCoordinate(mesh) u_mms = fd.as_vector( [sin(2.0 * pi * x) * sin(pi * y), sin(pi * x) * sin(2.0 * pi * y)]) p_mms = -0.5 * (u_mms[0]**2 + u_mms[1]**2) f_mms_u = (grad(u_mms) * u_mms + grad(p_mms) - 2.0 * nu * div(sym(grad(u_mms)))) f_mms_p = div(u_mms) F += -inner(f_mms_u, v) * dx - f_mms_p * q * dx bc1 = fd.DirichletBC(W.sub(0), u_mms, "on_boundary") bc2 = fd.DirichletBC(W.sub(1), p_mms, "on_boundary") bc_no_flow = InteriorBC(W.sub(0), fd.Constant((0.0, 0.0)), no_flow_markers) solver_parameters = {"ksp_max_it": 500, "ksp_monitor": None} problem1 = fd.NonlinearVariationalProblem(F, w_sol1, bcs=[bc1, bc2, bc_no_flow]) solver1 = NavierStokesBrinkmannSolver( problem1, options_prefix="navier_stokes", solver_parameters=solver_parameters, ) solver1.solve() u_sol, _ = w_sol1.split() u_mms_func = fd.interpolate(u_mms, W.sub(0)) error = fd.errornorm(u_sol, u_mms_func) assert error < 0.07
def run_solver(r): mesh = fd.UnitSquareMesh(2**r, 2**r) P2 = fd.VectorElement("CG", mesh.ufl_cell(), 1) P1 = fd.FiniteElement("CG", mesh.ufl_cell(), 1) TH = P2 * P1 W = fd.FunctionSpace(mesh, TH) (v, q) = fd.TestFunctions(W) # Stokes 1 w_sol1 = fd.Function(W) nu = fd.Constant(0.05) F = NavierStokesBrinkmannForm(W, w_sol1, nu, beta_gls=2.0) from firedrake import sin, grad, pi, sym, div, inner x, y = fd.SpatialCoordinate(mesh) u_mms = fd.as_vector( [sin(2.0 * pi * x) * sin(pi * y), sin(pi * x) * sin(2.0 * pi * y)]) p_mms = -0.5 * (u_mms[0]**2 + u_mms[1]**2) f_mms_u = (grad(u_mms) * u_mms + grad(p_mms) - 2.0 * nu * div(sym(grad(u_mms)))) f_mms_p = div(u_mms) F += -inner(f_mms_u, v) * dx - f_mms_p * q * dx bc1 = fd.DirichletBC(W.sub(0), u_mms, "on_boundary") bc2 = fd.DirichletBC(W.sub(1), p_mms, "on_boundary") solver_parameters = {"ksp_max_it": 200} problem1 = fd.NonlinearVariationalProblem(F, w_sol1, bcs=[bc1, bc2]) solver1 = NavierStokesBrinkmannSolver( problem1, options_prefix="navier_stokes", solver_parameters=solver_parameters, ) solver1.solve() u_sol, _ = w_sol1.split() fd.File("test_u_sol.pvd").write(u_sol) u_mms_func = fd.interpolate(u_mms, W.sub(0)) error = fd.errornorm(u_sol, u_mms_func) print(f"Error: {error}") return error
def sources(self, **kwargs): keys = ('damage', 'velocity', 'fluidity') keys_alt = ('D', 'u', 'A') D, u, A = get_kwargs_alt(kwargs, keys, keys_alt) # Increase/decrease damage depending on stress and strain rates ε = sym(grad(u)) ε_1 = eigenvalues(ε)[0] σ = M(ε, A) σ_e = sqrt(inner(σ, σ) - det(σ)) ε_h = firedrake.Constant(self.healing_strain_rate) σ_d = firedrake.Constant(self.damage_stress) γ_h = firedrake.Constant(self.healing_rate) γ_d = firedrake.Constant(self.damage_rate) healing = γ_h * min_value(ε_1 - ε_h, 0) fracture = γ_d * conditional(σ_e - σ_d > 0, ε_1, 0.) * (1 - D) return healing + fracture
def surf_grad(u): return fd.sym(fd.grad(u) - fd.outer(fd.grad(u) * n, n))
def solve(self, dt, D0, u, A, D_inflow=None, **kwargs): r"""Propogate the damage forward by one timestep This function uses a Runge-Kutta scheme to upwind damage (limiting damage diffusion) while sourcing and sinking damage assocaited with crevasse opening/crevasse healing Parameters ---------- dt : float Timestep D0 : firedrake.Function initial damage feild should be discontinuous u : firedrake.Function Ice velocity A : firedrake.Function fluidity parameter D_inflow : firedrake.Function Damage of the upstream ice that advects into the domain Returns ------- D : firedrake.Function Ice damage at `t + dt` """ D_inflow = D_inflow if D_inflow is not None else D0 Q = D0.function_space() dD, φ = firedrake.TrialFunction(Q), firedrake.TestFunction(Q) d = φ * dD * dx D = D0.copy(deepcopy=True) n = firedrake.FacetNormal(Q.mesh()) un = 0.5 * (inner(u, n) + abs(inner(u, n))) L1 = dt * (D * div(φ * u) * dx - φ * max_value(inner(u, n), 0) * D * ds - φ * min_value(inner(u, n), 0) * D_inflow * ds - (φ('+') - φ('-')) * (un('+') * D('+') - un('-') * D('-')) * dS) D1 = firedrake.Function(Q) D2 = firedrake.Function(Q) L2 = firedrake.replace(L1, {D: D1}) L3 = firedrake.replace(L1, {D: D2}) dq = firedrake.Function(Q) # Three-stage strong structure-preserving Runge Kutta (SSPRK3) method params = { 'ksp_type': 'preonly', 'pc_type': 'bjacobi', 'sub_pc_type': 'ilu' } prob1 = firedrake.LinearVariationalProblem(d, L1, dq) solv1 = firedrake.LinearVariationalSolver(prob1, solver_parameters=params) prob2 = firedrake.LinearVariationalProblem(d, L2, dq) solv2 = firedrake.LinearVariationalSolver(prob2, solver_parameters=params) prob3 = firedrake.LinearVariationalProblem(d, L3, dq) solv3 = firedrake.LinearVariationalSolver(prob3, solver_parameters=params) solv1.solve() D1.assign(D + dq) solv2.solve() D2.assign(0.75 * D + 0.25 * (D1 + dq)) solv3.solve() D.assign((1.0 / 3.0) * D + (2.0 / 3.0) * (D2 + dq)) # Increase/decrease damage depending on stress and strain rates ε = sym(grad(u)) ε_1 = eigenvalues(ε)[0] σ = M(ε, A) σ_e = sqrt(inner(σ, σ) - det(σ)) ε_h = firedrake.Constant(self.healing_strain_rate) σ_d = firedrake.Constant(self.damage_stress) γ_h = firedrake.Constant(self.healing_rate) γ_d = firedrake.Constant(self.damage_rate) healing = γ_h * min_value(ε_1 - ε_h, 0) fracture = γ_d * conditional(σ_e - σ_d > 0, ε_1, 0.) * (1 - D) # Clamp damage field to [0, 1] D.project(min_value(max_value(D + dt * (healing + fracture), 0), 1)) return D
def ε(u): r"""Calculate the strain rate for a given flow velocity""" return sym(grad(u))
def D(x): return 2*fd.sym(fd.grad(x))
def ε(u): return sym(grad(u))
def epsilon(v): return sym(nabla_grad(v))
h = h0.copy(deepcopy=True) u = flow_solver.diagnostic_solve( velocity=u0, thickness=h, fluidity=A, damage=D ) final_time = args.final_time num_timesteps = args.num_steps dt = final_time / num_timesteps a = firedrake.Constant(0.0) for step in tqdm.trange(num_timesteps): h = flow_solver.prognostic_solve( dt, thickness=h, accumulation=a, velocity=u, thickness_inflow=h0 ) if args.damage: ε = firedrake.project(sym(grad(u)), S) M = firedrake.project((1 - D) * membrane_stress(ε, A), S) D = damage_solver.solve( dt, damage=D, velocity=u, strain_rate=ε, membrane_stress=M, damage_inflow=D_inflow ) u = flow_solver.diagnostic_solve( velocity=u, thickness=h, fluidity=A, damage=D ) output_name = os.path.splitext(args.output)[0]
def epsilon(u): return sym(nabla_grad(u))
Q, fixed_bids=fixed_bids, direct_solve=True) # import IPython; IPython.embed() res = [0, 1, 10, 50, 100, 150, 200, 250, 300, 400, 499, 625, 750, 875, 999] res = [r for r in res if r <= optre - 1] if res[-1] != optre - 1: res.append(optre - 1) results = run_solver(solver, res, args) # import sys; sys.exit() u, _ = fd.split(solver.z) nu = solver.nu objective_form = nu * fd.inner(fd.sym(fd.grad(u)), fd.sym(fd.grad(u))) * fd.dx solver.setup_adjoint(objective_form) solver.solver_adjoint.solve() # import sys; sys.exit() class Constraint(fs.PdeConstraint): def solve(self): super().solve() solver.solve(optre) class Objective(fs.ShapeObjective): def __init__(self, *args_, **kwargs_): super().__init__(*args_, **kwargs_)