Skip to content

Commit 925a69f

Browse files
committed
Add test for composite rheology
1 parent 9047aa2 commit 925a69f

File tree

1 file changed

+356
-0
lines changed

1 file changed

+356
-0
lines changed

test/composite_rheology_test.py

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
import numpy as np
2+
import sympy
3+
from sympy import lambdify, simplify, diff
4+
import firedrake
5+
from firedrake import (
6+
as_vector,
7+
max_value,
8+
Constant,
9+
Function,
10+
derivative,
11+
NonlinearVariationalProblem,
12+
NonlinearVariationalSolver,
13+
DirichletBC,
14+
)
15+
from icepack2 import constants
16+
from icepack2.model.minimization import (
17+
viscous_power,
18+
friction_power,
19+
calving_terminus,
20+
momentum_balance,
21+
ice_shelf_momentum_balance,
22+
)
23+
24+
25+
sparams = {
26+
"solver_parameters": {
27+
"snes_type": "newtonls",
28+
"snes_max_it": 200,
29+
"snes_linesearch_type": "nleqerr",
30+
"ksp_type": "gmres",
31+
"pc_type": "lu",
32+
"pc_factor_mat_solver_type": "mumps",
33+
},
34+
}
35+
36+
37+
def test_composite_rheology_floating():
38+
lx, ly = 20e3, 20e3
39+
Lx, Ly = Constant(lx), Constant(ly)
40+
h0, dh = Constant(500.0), Constant(100.0)
41+
u_inflow = Constant(100.0)
42+
43+
ρ_I = Constant(constants.ice_density)
44+
ρ_W = Constant(constants.water_density)
45+
g = Constant(constants.gravity)
46+
47+
n_1 = firedrake.Constant(1.0)
48+
n_3 = firedrake.Constant(constants.glen_flow_law)
49+
50+
τ_c = Constant(0.1)
51+
ε_c = Constant(0.01)
52+
A_1 = ε_c / τ_c ** n_1
53+
A_3 = ε_c / τ_c ** n_3
54+
55+
# This is an exact solution for the velocity of a floating ice shelf with
56+
# constant temperature and linearly decreasing thickness. See Greve and
57+
# Blatter for the derivation.
58+
def exact_δu(x, n, A):
59+
ρ = ρ_I * (1 - ρ_I / ρ_W)
60+
h = h0 - dh * x / Lx
61+
P = ρ * g * h / 4
62+
P_0 = ρ * g * h0 / 4
63+
δP = ρ * g * dh / 4
64+
return Lx * A * (P_0 ** (n + 1) - P ** (n + 1)) / ((n + 1) * δP)
65+
66+
degree = 1
67+
errors, mesh_sizes = [], []
68+
k_min, k_max, num_steps = 5 - degree, 8 - degree, 9
69+
for nx in np.logspace(k_min, k_max, num_steps, base=2, dtype=int):
70+
mesh = firedrake.RectangleMesh(nx, nx, lx, ly, diagonal="crossed")
71+
x, y = firedrake.SpatialCoordinate(mesh)
72+
73+
cg = firedrake.FiniteElement("CG", "triangle", degree)
74+
dg = firedrake.FiniteElement("DG", "triangle", degree - 1)
75+
Q = firedrake.FunctionSpace(mesh, cg)
76+
V = firedrake.VectorFunctionSpace(mesh, cg)
77+
Σ = firedrake.TensorFunctionSpace(mesh, dg, symmetry=True)
78+
Z = V * Σ
79+
z = Function(Z)
80+
z.sub(0).assign(Constant(u_inflow, 0))
81+
82+
expr = u_inflow + exact_δu(x, n_1, A_1) + exact_δu(x, n_3, A_3)
83+
u_exact = Function(V).interpolate(as_vector((expr, 0)))
84+
85+
h = Function(Q).interpolate(h0 - dh * x / Lx)
86+
inflow_ids = (1,)
87+
outflow_ids = (2,)
88+
side_wall_ids = (3, 4)
89+
90+
inflow_bc = DirichletBC(Z.sub(0), Constant((u_inflow, 0)), inflow_ids)
91+
side_wall_bc = DirichletBC(Z.sub(0).sub(1), 0, side_wall_ids)
92+
bcs = [inflow_bc, side_wall_bc]
93+
94+
u, M = firedrake.split(z)
95+
fields = {"velocity": u, "membrane_stress": M, "thickness": h}
96+
boundary_ids = {"outflow_ids": outflow_ids}
97+
98+
L = (
99+
viscous_power(**fields, flow_law_exponent=n_1, flow_law_coefficient=A_1) +
100+
viscous_power(**fields, flow_law_exponent=n_3, flow_law_coefficient=A_3) +
101+
ice_shelf_momentum_balance(**fields, **boundary_ids)
102+
)
103+
104+
F = derivative(L, z)
105+
qdegree = max(8, degree ** constants.glen_flow_law)
106+
pparams = {"form_compiler_parameters": {"quadrature_degree": qdegree}}
107+
problem = NonlinearVariationalProblem(F, z, bcs, **pparams)
108+
solver = NonlinearVariationalSolver(problem, **sparams)
109+
110+
num_steps = 5
111+
for exponent in np.linspace(1.0, constants.glen_flow_law, num_steps):
112+
n_3.assign(exponent)
113+
solver.solve()
114+
115+
u, M = z.subfunctions
116+
error = firedrake.norm(u - u_exact) / firedrake.norm(u_exact)
117+
δx = mesh.cell_sizes.dat.data_ro.min()
118+
mesh_sizes.append(δx)
119+
errors.append(error)
120+
print(".", end="", flush=True)
121+
122+
log_mesh_sizes = np.log2(np.array(mesh_sizes))
123+
log_errors = np.log2(np.array(errors))
124+
slope, intercept = np.polyfit(log_mesh_sizes, log_errors, 1)
125+
print(f"degree {degree}: log(error) ~= {slope:g} * log(dx) {intercept:+g}")
126+
assert slope > degree + 0.9
127+
128+
129+
def test_manufactured_solution():
130+
L, ρ_I, ρ_W, g = sympy.symbols("L ρ_I ρ_W g", real=True, positive=True)
131+
A_1, A_3 = sympy.symbols("A_1 A_3", real=True, positive=True)
132+
n_1, n_3 = sympy.symbols("n_1 n_3", integer=True, positive=True)
133+
134+
def strain_rate(M, A_1, A_3, n_1, n_3):
135+
return A_1 * (M / 2)**n_1 + A_3 * (M / 2)**n_3
136+
137+
def sliding_velocity(τ, K_1, K_3, m_1, m_3):
138+
return -K_1 * τ**m_1 - K_3 * τ**m_3
139+
140+
def stress_balance(x, h, s, M, τ):
141+
return diff(h * M, x) + τ - ρ_I * g * h * diff(s, x)
142+
143+
def boundary_condition(x, u, h, s, M):
144+
d = (s - h).subs(x, L)
145+
τ_c = (ρ_I * g * h**2 - ρ_W * g * d**2) / 2
146+
return (h * M - τ_c).subs(x, L)
147+
148+
x = sympy.symbols("x", real=True)
149+
h0, δh = sympy.symbols("h0 δh", real=True, positive=True)
150+
h = h0 - δh * x / L
151+
152+
s0, δs = sympy.symbols("s0 δs", real=True, positive=True)
153+
s = s0 - δs * x / L
154+
155+
h_L = h0 - δh
156+
s_L = s0 - δs
157+
β = δh / δs * (ρ_I * h_L ** 2 - ρ_W * (s_L - h_L) ** 2) / (ρ_I * h_L**2)
158+
159+
ρ = β * ρ_I * δs / δh
160+
P = ρ * g * h / 4
161+
δP = ρ * g * δh / 4
162+
P0 = ρ * g * h0 / 4
163+
164+
M = ρ * g * h / 2
165+
τ = -(1 - β) * ρ_I * g * h * δs / L
166+
167+
u_0 = sympy.symbols("u_0", real=True, positive=True)
168+
δu_1 = L * A_1 * (P0 ** (n_1 + 1) - P ** (n_1 + 1)) / ((n_1 + 1) * δP)
169+
δu_3 = L * A_3 * (P0 ** (n_3 + 1) - P ** (n_3 + 1)) / ((n_3 + 1) * δP)
170+
u = u_0 + δu_1 + δu_3
171+
172+
ε_1 = 0.001
173+
ε_3 = 0.01
174+
τ_c = 0.1
175+
176+
values = {
177+
u_0: 100,
178+
h0: 500,
179+
δh: 100,
180+
s0: 150,
181+
δs: 90,
182+
L: 20e3,
183+
A_1: ε_1 / τ_c,
184+
A_3: ε_3 / τ_c ** constants.glen_flow_law,
185+
# TODO: fix these
186+
ρ_I: constants.ice_density,
187+
ρ_W: constants.water_density,
188+
n_1: 1,
189+
n_3: constants.glen_flow_law,
190+
g: constants.gravity,
191+
}
192+
193+
# Check the momentum conservation law and boundary condition
194+
τ_b = lambdify(x, τ.subs(values), "numpy")
195+
τ_d = lambdify(x, (-ρ_I * g * h * diff(s, x)).subs(values), "numpy")
196+
τ_m = lambdify(x, simplify(diff(h * M, x)).subs(values), "numpy")
197+
xs = np.linspace(0, values[L], 21)
198+
199+
tolerance = 1e-8
200+
assert abs(boundary_condition(x, u, h, s, M).subs(values)) < tolerance
201+
202+
stress_norm = np.max(np.abs(τ_d(xs)))
203+
assert np.max(np.abs(τ_m(xs) + τ_b(xs) + τ_d(xs))) < tolerance * stress_norm
204+
205+
# Check the constitutive relation
206+
ε_u = lambdify(x, simplify(diff(u, x).subs(values)), "numpy")
207+
ε_M = lambdify(
208+
x, simplify(strain_rate(M, A_1, A_3, n_1, n_3).subs(values)), "numpy"
209+
)
210+
assert np.max(np.abs(ε_u(xs) - ε_M(xs))) < tolerance * np.max(np.abs(ε_u(xs)))
211+
212+
# Check the sliding law
213+
α = 0.1
214+
m_1 = 1
215+
m_3 = constants.glen_flow_law
216+
K_1 = α * u / τ ** m_1
217+
K_3 = (u - K_1 * τ ** m_1) / τ**m_3
218+
219+
U = lambdify(x, simplify(u).subs(values), "numpy")
220+
U_b = lambdify(
221+
x, simplify(sliding_velocity(τ, K_1, K_3, m_1, m_3).subs(values)), "numpy"
222+
)
223+
assert np.max(np.abs(U(xs) + U_b(xs))) < tolerance * np.max(np.abs(U(xs)))
224+
225+
226+
def test_composite_rheology_grounded():
227+
lx, ly = 20e3, 20e3
228+
Lx, Ly = Constant(lx), Constant(ly)
229+
h0, dh = Constant(500.0), Constant(100.0)
230+
s0, ds = Constant(150.0), Constant(90.0)
231+
u_inflow = Constant(100.0)
232+
233+
ρ_I = Constant(constants.ice_density)
234+
ρ_W = Constant(constants.water_density)
235+
g = Constant(constants.gravity)
236+
237+
n_1 = firedrake.Constant(1.0)
238+
n_3 = firedrake.Constant(constants.glen_flow_law)
239+
240+
m_1 = firedrake.Constant(1.0)
241+
m_3 = firedrake.Constant(constants.weertman_sliding_law)
242+
243+
τ_c = Constant(0.1)
244+
ε_1 = Constant(0.001)
245+
ε_3 = Constant(0.01)
246+
A_1 = ε_1 / τ_c ** n_1
247+
A_3 = ε_3 / τ_c ** n_3
248+
249+
h_L = h0 - dh
250+
s_L = s0 - ds
251+
β = dh / ds * (ρ_I * h_L**2 - ρ_W * (s_L - h_L)**2) / (ρ_I * h_L**2)
252+
253+
def exact_δu(x, n, A):
254+
ρ = β * ρ_I * ds / dh
255+
h = h0 - dh * x / Lx
256+
P = ρ * g * h / 4
257+
dP = ρ * g * dh / 4
258+
P0 = ρ * g * h0 / 4
259+
du = Lx * A * (P0 ** (n + 1) - P ** (n + 1)) / ((n + 1) * dP)
260+
return du
261+
262+
degree = 1
263+
errors, mesh_sizes = [], []
264+
k_min, k_max, num_steps = 5 - degree, 8 - degree, 9
265+
for nx in np.logspace(k_min, k_max, num_steps, base=2, dtype=int):
266+
mesh = firedrake.RectangleMesh(nx, nx, lx, ly, diagonal="crossed")
267+
x, y = firedrake.SpatialCoordinate(mesh)
268+
269+
cg = firedrake.FiniteElement("CG", "triangle", degree)
270+
dg = firedrake.FiniteElement("DG", "triangle", degree - 1)
271+
Q = firedrake.FunctionSpace(mesh, cg)
272+
V = firedrake.VectorFunctionSpace(mesh, cg)
273+
Σ = firedrake.TensorFunctionSpace(mesh, dg, symmetry=True)
274+
275+
T = firedrake.VectorFunctionSpace(mesh, dg)
276+
Z = V * Σ * T
277+
z = Function(Z)
278+
z.sub(0).assign(Constant((u_inflow, 0)))
279+
280+
u_expr = u_inflow + exact_δu(x, n_1, A_1) + exact_δu(x, n_3, A_3)
281+
u_exact = Function(V).interpolate(as_vector((u_expr, 0)))
282+
283+
h = Function(Q).interpolate(h0 - dh * x / Lx)
284+
s = Function(Q).interpolate(s0 - ds * x / Lx)
285+
286+
α = Constant(0.1)
287+
τ_expr = (1 - β) * ρ_I * g * h * ds / Lx
288+
K_1 = α * u_expr / τ_expr ** m_1
289+
K_3 = (u_expr - K_1 * τ_expr ** m_1) / τ_expr ** m_3
290+
291+
# Create the boundary conditions
292+
inflow_ids = (1,)
293+
outflow_ids = (2,)
294+
side_wall_ids = (3, 4)
295+
296+
inflow_bc = DirichletBC(Z.sub(0), Constant((u_inflow, 0)), inflow_ids)
297+
side_wall_bc = DirichletBC(Z.sub(0).sub(1), 0, side_wall_ids)
298+
bcs = [inflow_bc, side_wall_bc]
299+
300+
u, M, τ = firedrake.split(z)
301+
fields = {
302+
"velocity": u,
303+
"membrane_stress": M,
304+
"basal_stress": τ,
305+
"thickness": h,
306+
"surface": s,
307+
}
308+
309+
rheology_1 = {
310+
"flow_law_exponent": n_1,
311+
"flow_law_coefficient": A_1,
312+
"sliding_exponent": m_1,
313+
"sliding_coefficient": K_1,
314+
}
315+
316+
rheology_3 = {
317+
"flow_law_exponent": n_3,
318+
"flow_law_coefficient": A_3,
319+
"sliding_exponent": m_3,
320+
"sliding_coefficient": K_3,
321+
}
322+
323+
boundary_ids = {"outflow_ids": outflow_ids}
324+
L = (
325+
viscous_power(**fields, **rheology_1) +
326+
viscous_power(**fields, **rheology_3) +
327+
friction_power(**fields, **rheology_1) +
328+
friction_power(**fields, **rheology_3) +
329+
calving_terminus(**fields, **boundary_ids) +
330+
momentum_balance(**fields)
331+
)
332+
333+
F = derivative(L, z)
334+
qdegree = max(8, degree ** constants.glen_flow_law)
335+
pparams = {"form_compiler_parameters": {"quadrature_degree": qdegree}}
336+
problem = NonlinearVariationalProblem(F, z, bcs, **pparams)
337+
solver = NonlinearVariationalSolver(problem, **sparams)
338+
339+
num_steps = 5
340+
for exponent in np.linspace(1.0, constants.glen_flow_law, num_steps):
341+
n_3.assign(exponent)
342+
m_3.assign(exponent)
343+
solver.solve()
344+
345+
u, M, τ = z.subfunctions
346+
error = firedrake.norm(u - u_exact) / firedrake.norm(u_exact)
347+
δx = mesh.cell_sizes.dat.data_ro.min()
348+
mesh_sizes.append(δx)
349+
errors.append(error)
350+
print(".", end="", flush=True)
351+
352+
log_mesh_sizes = np.log2(np.array(mesh_sizes))
353+
log_errors = np.log2(np.array(errors))
354+
slope, intercept = np.polyfit(log_mesh_sizes, log_errors, 1)
355+
print(f"degree {degree}: log(error) ~= {slope:g} * log(dx) {intercept:+g}")
356+
assert slope > degree + 0.9

0 commit comments

Comments
 (0)