Skip to content

Commit 6281a2a

Browse files
committed
Debugged and extended the Dukowicz slab test case
This commit modifies the run and plot scripts for the Dukowicz slab test case, as described in Section 5 of this paper: J.K. Dukowicz, 2012. Reformulating the full-Stokes ice sheet model for a more efficient computational solution. The Cryosphere, 6, 21-34, https://doi.org/10.5194/tc-6-21-2012. The test case consists of an ice slab of uniform thickness moving down an inclined plane by a combination of sliding and shearing. Analytic Stokes and first-order velocity solutions exist for all values of Glen's exponent n >= 1. The solutions for n = 1 are derived in Dukowicz (2012), and solutions for n > 1 are derived in an unpublished manuscript by Dukowicz (2013). These solutions can be compared to those simulated by CISM. The original scripts, runSlab.py and plotSlab.py, were written by Matt Hoffman with support for n = 1. They came with warnings that the test is not supported. The test is now supported, and the scripts include some new features: * The user may specify any value of n >= 1 (not necessarily an integer). The tests assume which_ho_efvs = 2 (nonlinear viscosity) with flow_law = 0 (constant). * Physics parameters are no longer hard-coded. The user can enter the ice thickness, beta, viscosity coefficient (mu_n), and slope angle (theta) on the command line. * The user can specify time parameters dt (the dynamic time step) and nt (number of steps). The previous version did not support transient runs. * The user can specify a small thickness perturbation dh, which is added to the initial uniform thickness via random sampling from a Gaussian distribution. The perturbation will grow or decay, depending on the solver stability for given dx and dt. For n = 1, the viscosity coefficient mu_1 has a default value of 1.e6 Pa yr in the relation mu = mu_1 * eps((1-n)/n), where eps is the effective strain rate. For n > 1, the user can specify a coefficient mu_n; otherwise the run script computes mu_n such that the basal and surface speeds are nearly the same as for an n = 1 case with the mu_1 = 1.e6 Pa yr and the same values of thickness, beta, and theta. (Note: There is a subtle difference between the Dukowicz and CISM definitions of the effective strain rate; the Dukowicz value is twice as large. Later, it might be helpful to make the Dukowicz convention consistent with CISM.) I modified the plotting script, plotSlab.py, to look in the config and output files for physics parameters that are no longer hardwired. I slightly modified the analytic formulas to give the correct solution for non-integer n. This script creates two plots. The first plot shows excellent agreement between higher-order CISM solutions and the analytic solution for small values of the slope angle theta. For steep slopes, the answers diverge as expected. For the second plot, the extent of the y-axis is wrong. This remains to be fixed. I also added a new script, stabilitySlab.py, to carry out stability tests as described in: Robinson, A., D. Goldberg, and W. H. Lipscomb, A comparison of the performance of depth-integrated ice-dynamics solvers, to be submitted to The Cryosphere. The idea is that for a given set of physics parameters and stress-balance approximation (DIVA, L1L2, etc.), the script launches multiple CISM runs at a range of grid resolutions. At each grid resolution, the script determines the maximum stable time step. A run is deemed stable when the standard deviation of an initial small thickness perturbation is reduced over the course of 100 time steps. A run is unstable if the standard deviation increases or if the model aborts (usually with a CFL violation). I have run the stability script for several solvers (DIVA, L1L2, SIA, SSA) for each of two physical cases: one with thick shearing ice and one with thin sliding ice. Each suite of experiments runs in a few minutes on 4 Macbook cores for solvers other than BP. As expected, DIVA and SSA are much more stable than L1L2 and SIA. This and the previous commit correspond to the CISM code and scripts used for the initial submission by Robinson et al. (2021).
1 parent 303aa19 commit 6281a2a

File tree

4 files changed

+742
-108
lines changed

4 files changed

+742
-108
lines changed

tests/slab/plotSlab.py

+128-39
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
#!/usr/bin/env python2
22

3-
43
"""
54
This script plots the results of an experiment with an ice "slab" on an
65
inclined plane. Test case is described in sections 5.1-2 of:
7-
J.K. Dukoqicz, 2012. Reformulating the full-Stokes ice sheet model for a
6+
J.K. Dukowicz, 2012. Reformulating the full-Stokes ice sheet model for a
87
more efficient computational solution. The Cryosphere, 6, 21-34.
98
www.the-cryosphere.net/6/21/2012/
109
1110
Blatter-Pattyn First-order solution is described in J.K. Dukowicz, manuscript
1211
in preparation.
1312
"""
1413
#FIXME: Manuscript likely not in prep anymore -- JHK, 08/07/2015
14+
# Not published as of July 2021 -- WHL
1515

1616
# Written by Matt Hoffman, Dec. 16, 2013
1717
# Reconfigured by Joseph H Kennedy at ORNL on August 7, 2015 to work with the regression testing
1818
# NOTE: Did not adjust inner workings except where needed.
19-
20-
21-
#NOTE: this script is assuming n=3, but more general solutions are available.
19+
# Revised by William Lipscomb in 2021 to support more options, including general values of Glen's n.
2220

2321
import os
2422
import sys
@@ -28,8 +26,12 @@
2826
import matplotlib.pyplot as plt
2927

3028
from netCDF import *
31-
from math import tan, pi, sin, cos
32-
from runSlab import n, rhoi, grav, theta, beta, efvs, thickness # Get the values used to run the experiment
29+
from math import tan, pi, sin, cos, atan
30+
31+
# Get hard-coded parameters from the run script.
32+
from runSlab import rhoi, grav
33+
34+
import ConfigParser
3335

3436
import argparse
3537
parser = argparse.ArgumentParser(description=__doc__,
@@ -46,16 +48,15 @@
4648
help="The tests output file you would like to plot. If a path is" \
4749
+"passed via this option, the -o/--output-dir option will be ignored.")
4850

51+
parser.add_argument('-c','--config-file',
52+
help="The configure file used to set up the test case and run CISM")
53+
4954

5055
# ===========================================================
5156
# Define some variables and functions used in the main script
5257
# ===========================================================
5358

54-
# Calculate scales from Ducowicz unpub. man.
55-
eta = beta * thickness * efvs**-n * (rhoi * grav * thickness)**(n-1)
56-
velscale = (rhoi * grav * thickness / efvs)**n * thickness
57-
thetar = theta * pi/180.0 # theta in radians
58-
59+
#WHL args.output-file with a hyphen?
5960
def get_in_file():
6061
if args.output_file:
6162
out_d, out_f = os.path.split(args.output_file)
@@ -76,7 +77,7 @@ def get_in_file():
7677
newest = max(matching, key=os.path.getmtime)
7778
print("\nWARNING: MULTIPLE *.out.nc FILES DETECTED!")
7879
print( "==========================================")
79-
print( "Ploting the most recently modified file in the output directory:")
80+
print( "Plotting the most recently modified file in the output directory:")
8081
print( " "+newest)
8182
print( "To plot another file, specify it with the -f/--outfile option.\n")
8283

@@ -94,6 +95,25 @@ def get_in_file():
9495

9596
return filein
9697

98+
def split_file_name(file_name):
99+
"""
100+
Get the root name, size, and number of processors from an out.nc filename.
101+
#WHL - Adapted from plotISMIP_HOM.py
102+
"""
103+
root = ''
104+
size = ''
105+
proc = ''
106+
107+
file_details = file_name.replace('.out.nc','') .split('.')
108+
# print(file_details)
109+
# print('len = ' + str(len(file_details)))
110+
111+
if len(file_details) > 2:
112+
proc = '.'+file_details[2]
113+
size = '.'+file_details[1]
114+
root = file_details[0]
115+
116+
return (root, size, proc)
97117

98118
# =========================
99119
# Actual script starts here
@@ -103,10 +123,7 @@ def main():
103123
Plot the slab test results.
104124
"""
105125

106-
print("WARNING: THIS TEST CASE IS IN DEVELOPMENT. USE AT YOUR OWN RISK!")
107-
108-
109-
filein = get_in_file()
126+
filein = get_in_file()
110127

111128
# Get needed variables from the output file
112129
x1 = filein.variables['x1'][:]
@@ -120,28 +137,96 @@ def main():
120137
# use integer floor division operator to get an index close to the center
121138
xp = len(x0)//2
122139
yp = len(y0)//2
123-
#yp = 15
124-
#xp = 15
125140
# =====================================================================
126-
print 'Using x index of '+str(xp)+'='+str(x0[xp])
127-
print 'Using y index of '+str(yp)+'='+str(y0[yp])
141+
142+
print('Using x index of '+str(xp)+'='+str(x0[xp]))
143+
print('Using y index of '+str(yp)+'='+str(y0[yp]))
128144

129145
thk = filein.variables['thk'][:]
130146
if netCDF_module == 'Scientific.IO.NetCDF':
131-
thk = thk * filein.variables['thk'].scale_factor
147+
thk = thk * filein.variables['thk'].scale_factor
132148
topg = filein.variables['topg'][:]
133149
if netCDF_module == 'Scientific.IO.NetCDF':
134-
topg = topg * filein.variables['topg'].scale_factor
150+
topg = topg * filein.variables['topg'].scale_factor
135151
uvel = filein.variables['uvel'][:]
136152
if netCDF_module == 'Scientific.IO.NetCDF':
137-
uvel = uvel * filein.variables['uvel'].scale_factor
138-
153+
uvel = uvel * filein.variables['uvel'].scale_factor
154+
beta_2d = filein.variables['beta'][:]
155+
if netCDF_module == 'Scientific.IO.NetCDF':
156+
beta_2d = beta_2d * filein.variables['beta'].scale_factor
157+
158+
# Get the name of the config file
159+
# If not entered on the command line, then construct from the output filename
160+
161+
if not args.config_file:
162+
root, size, proc = split_file_name(args.output_file)
163+
args.config_file = root + size + proc + '.config'
164+
165+
configpath = os.path.join(args.output_dir, args.config_file)
166+
print('configpath = ' + configpath)
167+
168+
# Get gn and default_flwa from the config file
169+
170+
try:
171+
config_parser = ConfigParser.SafeConfigParser()
172+
config_parser.read( configpath )
173+
174+
gn = float(config_parser.get('parameters','n_glen'))
175+
flwa = float(config_parser.get('parameters', 'default_flwa'))
176+
177+
except ConfigParser.Error as error:
178+
print("Error parsing " + args.config )
179+
print(" "),
180+
print(error)
181+
sys.exit(1)
182+
183+
# Derive the viscosity constant mu_n from flwa
184+
# This expression is derived in the comments on flwa in runSlab.py.
185+
mu_n = 1.0 / (2.0**((1.0+gn)/(2.0*gn)) * flwa**(1.0/gn))
186+
187+
# Get the ice thickness from the output file.
188+
# If thickness = constant (i.e., the optional perturbation dh = 0), it does not matter where we sample.
189+
# Note: In general, this thickness will differ from the baseline 'thk' that is used in runSlab.py
190+
# to create the input file.
191+
# This is because the baseline value is measured perpendicular to the sloped bed,
192+
# whereas the CISM value is in the vertical direction, which is not perpendicular to the bed.
193+
thickness = thk[0,yp,xp]
194+
195+
# Get beta from the output file.
196+
# Since beta = constant, it does not matter where we sample.
197+
beta = beta_2d[0,yp,xp]
198+
199+
# Derive theta from the output file as atan(slope(topg))
200+
# Since the slope is constant, it does not matter where we sample.
201+
slope = (topg[0,yp,xp] - topg[0,yp,xp+1]) / (x0[xp+1] - x0[xp])
202+
thetar = atan(slope)
203+
theta = thetar * 180.0/pi
204+
205+
# Compute the dimensionless parameter eta and the velocity scale,
206+
# which appear in the scaled velocity solution.
207+
eta = (beta * thickness / mu_n**gn) * (rhoi * grav * thickness)**(gn-1)
208+
velscale = (rhoi * grav * thickness / mu_n)**gn * thickness
209+
210+
print('gn = ' + str(gn))
211+
print('rhoi = ' + str(rhoi))
212+
print('grav = ' + str(grav))
213+
print('thck = ' + str(thickness))
214+
print('mu_n = ' + str(mu_n))
215+
print('flwa = ' + str(flwa))
216+
print('beta = ' + str(beta))
217+
print('eta = ' + str(eta))
218+
print('theta= ' + str(theta))
219+
print('velscale = ' + str(velscale))
139220

140221
# === Plot the results at the given location ===
141222
# Note we are not plotting like in Fig 3 of paper.
142223
# That figure plotted a profile against zprime.
143224
# It seemed more accurate to plot a profile against z to avoid interpolating model results (analytic solution can be calculated anywhere).
144-
# Also, the analytic solution calculates the bed-parallel u velocity, but CISM calculates u as parallel to the geoid, so we need to transform the analytic solution to the CISM coordinate system.
225+
# Also, the analytic solution calculates the bed-parallel u velocity, but CISM calculates u as parallel to the geoid,
226+
# so we need to transform the analytic solution to the CISM coordinate system.
227+
228+
#WHL - I think the analytic solution is actually for u(z'), which is not bed-parallel.
229+
# The bed-parallel solution would be u'(z'), with w'(z') = 0.
145230

146231
fig = plt.figure(1, facecolor='w', figsize=(12, 6))
147232

@@ -151,24 +236,23 @@ def main():
151236
x = (x0-x0[xp]) / thickness
152237
# calculate rotated zprime coordinates for this column (we assume the solution truly is spatially uniform)
153238
zprime = x[xp] * sin(thetar) + z * cos(thetar)
154-
#print 'zprime', zprime
155239

156240
# Calculate analytic solution for x-component of velocity (eq. 39 in paper) for the CISM-column
157-
#uvelStokesAnalyticScaled = sin(theta * pi/180.0) * cos(theta * pi/180.0) * (0.5 * zprime**2 - zprime - 1.0/eta)
158-
uvelStokesAnalyticScaled = (-1)**n * 2**((1.0-n)/2.0) * sin(thetar)**n * cos(thetar) / (n+1) \
159-
* ( (zprime - 1.0)**(n+1) - (-1.0)**(n+1) ) + sin(thetar) * cos(thetar) / eta
241+
uvelStokesAnalyticScaled = sin(thetar) * cos(thetar) / eta \
242+
- 2**((1.0-gn)/2.0) * sin(thetar)**gn * cos(thetar) / (gn+1) * ( (1.0 - zprime)**(gn+1) - 1.0 )
160243

161-
# Calculate the BP FO solution for x-component of velocity (Ducowicz, in prep. paper, Eq.30, n=3)
162-
#uvelFOAnalyticScaled = (tan(theta * pi/180.0))**3 / (8.0 * (1.0 + 3.0 * (sin(theta * pi/180.0)**2))**2) \
163-
uvelFOAnalyticScaled = (-1)**n * 2**((1.0-n)/2.0) * tan(thetar)**n / \
164-
( (n + 1) * (1.0 + 3.0 * sin(thetar)**2)**((n+1.0)/2.0) ) \
165-
* ( (zprime - 1.0)**(n+1) - (-1.0)**(n+1) ) + tan(thetar) / eta
244+
# Calculate the BP FO solution for x-component of velocity (Dukowicz, in prep. paper, Eq.30, n=3)
245+
uvelFOAnalyticScaled = + tan(thetar) / eta \
246+
- 2**((1.0-gn)/2.0) * tan(thetar)**gn / \
247+
( (gn + 1) * (1.0 + 3.0 * sin(thetar)**2)**((gn+1.0)/2.0) ) \
248+
* ( (1.0 - zprime)**(gn+1) - 1.0 )
166249

167250
### 1. Plot as nondimensional variables
168251
# Plot analytic solution
169252
fig.add_subplot(1,2,1)
170253
plt.plot(uvelStokesAnalyticScaled, z, '-kx', label='Analytic Stokes')
171254
plt.plot(uvelFOAnalyticScaled, z, '-ko', label='Analytic FO')
255+
172256
# Plot model results
173257
plt.plot(uvel[0,:,yp,xp] / velscale, z, '--ro', label='CISM')
174258
plt.ylim((-0.05, 1.05))
@@ -191,7 +275,16 @@ def main():
191275
plt.title('Velocity profile at x=' + str(x0[xp]) + ' m, y=' + str(y0[yp]) + ' m\n(Unscaled coordinates)')
192276

193277
#################
278+
# print('y0_min:')
279+
# print(y0.min())
280+
# print('y0_max:')
281+
# print(y0.max())
282+
194283
# Now plot maps to show if the velocities vary over the domain (they should not)
284+
# For some reason, the y-axis has a greater extent than the range (y0.min, y0.max).
285+
#TODO - Fix the y-axis extent. Currently, the extent is too large for small values of ny.
286+
#TODO - Plot the thickness relative to the initial thickness.
287+
195288
fig = plt.figure(2, facecolor='w', figsize=(12, 6))
196289
fig.add_subplot(1,2,1)
197290
uvelDiff = uvel[0,0,:,:] - uvel[0,0,yp,xp]
@@ -224,14 +317,11 @@ def main():
224317
#plt.plot(level, tan(thetar)**3 / (8.0 * (1.0 + 3.0 * sin(thetar)**2)**2) * (1.0 - (level-1.0)**4 ) + tan(thetar)/eta, 'b--' , label='nonlinear fo')
225318
#plt.ylim((0.0, 0.04)); plt.xlabel("z'"); plt.ylabel('u'); plt.legend()
226319

227-
228320
plt.draw()
229321
plt.show()
230322

231323
filein.close()
232324

233-
print("WARNING: THIS TEST CASE IS IN DEVELOPMENT. USE AT YOUR OWN RISK!")
234-
235325
# Run only if this is being run as a script.
236326
if __name__=='__main__':
237327

@@ -240,4 +330,3 @@ def main():
240330

241331
# run the script
242332
sys.exit(main())
243-

0 commit comments

Comments
 (0)