• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OGGM / oggm / 5392406468

pending completion
5392406468

Pull #1600

github

web-flow
Merge 2edc8ce4d into 15ff031af
Pull Request #1600: added more flexiblity to dynamic spinup

28 of 39 new or added lines in 1 file covered. (71.79%)

8 existing lines in 4 files now uncovered.

11309 of 13184 relevant lines covered (85.78%)

3.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.25
/oggm/core/inversion.py
1
"""Glacier thickness.
2

3

4
Note for later: the current code is oriented towards a consistent framework
5
for flowline modelling. The major direction shift happens at the
6
flowlines.width_correction step where the widths are computed to follow the
7
altitude area distribution. This must not and should not be the case when
8
the actual objective is to provide a glacier thickness map. For this,
9
the geometrical width and some other criteria such as e.g. "all altitudes
10
*in the current subcatchment* above the considered cross-section are
11
contributing to the flux" might give more interpretable results.
12

13
References:
14

15
Farinotti, D., Huss, M., Bauder, A., Funk, M. and Truffer, M.: A method to
16
    estimate the ice volume and ice-thickness distribution of alpine glaciers,
17
    J. Glaciol., 55(191), 422-430, doi:10.3189/002214309788816759, 2009.
18

19
Huss, M. and Farinotti, D.: Distributed ice thickness and volume of all
20
    glaciers around the globe, J. Geophys. Res. Earth Surf., 117(4), F04010,
21
    doi:10.1029/2012JF002523, 2012.
22

23
Bahr  Pfeffer, W. T., Kaser, G., D. B.: Glacier volume estimation as an
24
    ill-posed boundary value problem, Cryosph. Discuss. Cryosph. Discuss.,
25
    6(6), 5405-5420, doi:10.5194/tcd-6-5405-2012, 2012.
26

27
Adhikari, S., Marshall, J. S.: Parameterization of lateral drag in flowline
28
    models of glacier dynamics, Journal of Glaciology, 58(212), 1119-1132.
29
    doi:10.3189/2012JoG12J018, 2012.
30
"""
31
# Built ins
32
import logging
9✔
33
import warnings
9✔
34

35
# External libs
36
import numpy as np
9✔
37
from scipy.interpolate import griddata
9✔
38
from scipy import optimize
9✔
39

40
# Locals
41
from oggm import utils, cfg
9✔
42
from oggm import entity_task
9✔
43
from oggm.core.gis import gaussian_blur
9✔
44
from oggm.exceptions import InvalidParamsError, InvalidWorkflowError
9✔
45

46
# Module logger
47
log = logging.getLogger(__name__)
9✔
48

49
# arbitrary constant
50
MIN_WIDTH_FOR_INV = 10
9✔
51

52

53
@entity_task(log, writes=['inversion_input'])
9✔
54
def prepare_for_inversion(gdir,
9✔
55
                          invert_with_rectangular=True,
56
                          invert_all_rectangular=False,
57
                          invert_with_trapezoid=True,
58
                          invert_all_trapezoid=False):
59
    """Prepares the data needed for the inversion.
60

61
    Mostly the mass flux and slope angle, the rest (width, height) was already
62
    computed. It is then stored in a list of dicts in order to be faster.
63

64
    Parameters
65
    ----------
66
    gdir : :py:class:`oggm.GlacierDirectory`
67
        the glacier directory to process
68
    """
69

70
    # variables
71
    fls = gdir.read_pickle('inversion_flowlines')
7✔
72

73
    towrite = []
7✔
74
    for fl in fls:
7✔
75

76
        # Distance between two points
77
        dx = fl.dx * gdir.grid.dx
7✔
78

79
        # Widths
80
        widths = fl.widths * gdir.grid.dx
7✔
81

82
        # Heights
83
        hgt = fl.surface_h
7✔
84
        angle = -np.gradient(hgt, dx)  # beware the minus sign
7✔
85

86
        # Flux needs to be in [m3 s-1] (*ice* velocity * surface)
87
        # fl.flux is given in kg m-2 yr-1, rho in kg m-3, so this should be it:
88
        rho = cfg.PARAMS['ice_density']
7✔
89
        
90
        # This might error if usuer didnt compute apparent MB
91
        try:
7✔
92
            flux = fl.flux * (gdir.grid.dx**2) / cfg.SEC_IN_YEAR / rho
7✔
93
        except TypeError:
×
94
            raise InvalidWorkflowError('Flux through flowline unknown. '
×
95
                                       'Did you compute the apparent MB?')
96
        flux_out = fl.flux_out * (gdir.grid.dx**2) / cfg.SEC_IN_YEAR / rho
7✔
97

98
        # Clip flux to 0
99
        if np.any(flux < -0.1):
7✔
100
            log.info('(%s) has negative flux somewhere', gdir.rgi_id)
×
101
        utils.clip_min(flux, 0, out=flux)
7✔
102

103
        if np.sum(flux <= 0) > 1 and len(fls) == 1:
7✔
104
            log.warning("More than one grid point has zero or "
×
105
                        "negative flux: this should not happen.")
106

107
        if fl.flows_to is None and gdir.inversion_calving_rate == 0:
7✔
108
            if not np.allclose(flux_out, 0., atol=0.1):
7✔
109
                # TODO: this test doesn't seem meaningful here
110
                msg = ('({}) flux at terminus should be zero, but is: '
2✔
111
                       '{.4f} m3 ice s-1'.format(gdir.rgi_id, flux_out))
112
                raise RuntimeError(msg)
×
113

114
        # Shape
115
        is_rectangular = fl.is_rectangular
5✔
116
        if not invert_with_rectangular:
5✔
117
            is_rectangular[:] = False
1✔
118
        if invert_all_rectangular:
5✔
119
            is_rectangular[:] = True
2✔
120

121
        # Trapezoid is new - might not be available
122
        is_trapezoid = getattr(fl, 'is_trapezoid', None)
5✔
123
        if is_trapezoid is None:
5✔
124
            is_trapezoid = fl.is_rectangular * False
5✔
125
        if not invert_with_trapezoid:
5✔
126
            is_rectangular[:] = False
1✔
127
        if invert_all_trapezoid:
5✔
128
            is_trapezoid[:] = True
1✔
129

130
        # Optimisation: we need to compute this term of a0 only once
131
        flux_a0 = np.where(is_rectangular, 1, 1.5)
5✔
132
        flux_a0 *= flux / widths
5✔
133

134
        # Add to output
135
        cl_dic = dict(dx=dx, flux_a0=flux_a0, width=widths,
5✔
136
                      slope_angle=angle, is_rectangular=is_rectangular,
137
                      is_trapezoid=is_trapezoid, flux=flux,
138
                      is_last=fl.flows_to is None, hgt=hgt,
139
                      invert_with_trapezoid=invert_with_trapezoid)
140
        towrite.append(cl_dic)
5✔
141

142
    # Write out
143
    gdir.write_pickle(towrite, 'inversion_input')
5✔
144

145

146
def _inversion_poly(a3, a0):
9✔
147
    """Solve for degree 5 polynomial with coefficients a5=1, a3, a0."""
148
    sols = np.roots([1., 0., a3, 0., 0., a0])
4✔
149
    test = (np.isreal(sols)*np.greater(sols, [0]*len(sols)))
4✔
150
    return sols[test][0].real
4✔
151

152

153
def _inversion_simple(a3, a0):
9✔
154
    """Solve for degree 5 polynomial with coefficients a5=1, a3=0., a0."""
155

156
    return (-a0)**(1./5.)
6✔
157

158

159
def _compute_thick(a0s, a3, flux_a0, _inv_function):
9✔
160
    """Content of the original inner loop of the mass-conservation inversion.
161

162
    Put here to avoid code duplication.
163

164
    Parameters
165
    ----------
166
    a0s
167
    a3
168
    flux_a0
169
    _inv_function
170

171
    Returns
172
    -------
173
    the thickness
174
    """
175

176
    if np.any(~np.isfinite(a0s)):
6✔
177
        raise RuntimeError('non-finite coefficients in the polynomial.')
×
178

179
    # Solve the polynomials
180
    try:
6✔
181
        out_thick = np.zeros(len(a0s))
6✔
182
        for i, (a0, Q) in enumerate(zip(a0s, flux_a0)):
5✔
183
            out_thick[i] = _inv_function(a3, a0) if Q > 0 else 0
5✔
184
    except TypeError:
4✔
185
        # Scalar
186
        out_thick = _inv_function(a3, a0s) if flux_a0 > 0 else 0
4✔
187

188
    if np.any(~np.isfinite(out_thick)):
6✔
189
        raise RuntimeError('non-finite coefficients in the polynomial.')
×
190

191
    return out_thick
6✔
192

193

194
def sia_thickness_via_optim(slope, width, flux, shape='rectangular',
9✔
195
                            glen_a=None, fs=None, t_lambda=None):
196
    """Compute the thickness numerically instead of analytically.
197

198
    It's the only way that works for trapezoid shapes.
199

200
    Parameters
201
    ----------
202
    slope : -np.gradient(hgt, dx)
203
    width : section width in m
204
    flux : mass flux in m3 s-1
205
    shape : 'rectangular', 'trapezoid' or 'parabolic'
206
    glen_a : Glen A, defaults to PARAMS
207
    fs : sliding, defaults to PARAMS
208
    t_lambda: the trapezoid lambda, defaults to PARAMS
209

210
    Returns
211
    -------
212
    the ice thickness (in m)
213
    """
214

215
    if len(np.atleast_1d(slope)) > 1:
5✔
216
        shape = utils.tolist(shape, len(slope))
1✔
217
        t_lambda = utils.tolist(t_lambda, len(slope))
1✔
218
        out = []
1✔
219
        for sl, w, f, s, t in zip(slope, width, flux, shape, t_lambda):
1✔
220
            out.append(sia_thickness_via_optim(sl, w, f, shape=s,
1✔
221
                                               glen_a=glen_a, fs=fs,
222
                                               t_lambda=t))
223
        return np.asarray(out)
1✔
224

225
    # Sanity
226
    if flux <= 0:
5✔
227
        return 0
×
228
    if width <= MIN_WIDTH_FOR_INV:
5✔
229
        return 0
1✔
230

231
    if glen_a is None:
5✔
232
        glen_a = cfg.PARAMS['inversion_glen_a']
1✔
233
    if fs is None:
5✔
234
        fs = cfg.PARAMS['inversion_fs']
1✔
235
    if t_lambda is None:
5✔
236
        t_lambda = cfg.PARAMS['trapezoid_lambdas']
3✔
237
    if shape not in ['parabolic', 'rectangular', 'trapezoid']:
5✔
238
        raise InvalidParamsError('shape must be `parabolic`, `trapezoid` '
×
239
                                 'or `rectangular`, not: {}'.format(shape))
240

241
    # Ice flow params
242
    n = cfg.PARAMS['glen_n']
5✔
243
    fd = 2 / (n+2) * glen_a
5✔
244
    rho = cfg.PARAMS['ice_density']
5✔
245
    rhogh = (rho * cfg.G * slope) ** n
5✔
246

247
    # To avoid geometrical inconsistencies
248
    max_h = width / t_lambda if shape == 'trapezoid' else 1e4
5✔
249

250
    def to_minimize(h):
5✔
251
        u = (h ** (n + 1)) * fd * rhogh + (h ** (n - 1)) * fs * rhogh
5✔
252
        if shape == 'parabolic':
5✔
253
            sect = 2./3. * width * h
1✔
254
        elif shape == 'trapezoid':
5✔
255
            w0m = width - t_lambda * h
5✔
256
            sect = (width + w0m) / 2 * h
5✔
257
        else:
258
            sect = width * h
3✔
259
        return sect * u - flux
5✔
260
    out_h, r = optimize.brentq(to_minimize, 0, max_h, full_output=True)
5✔
261
    return out_h
5✔
262

263

264
def sia_thickness(slope, width, flux, shape='rectangular',
9✔
265
                  glen_a=None, fs=None, shape_factor=None):
266
    """Computes the ice thickness from mass-conservation.
267

268
    This is a utility function tested against the true OGGM inversion
269
    function. Useful for teaching and inversion with calving.
270

271
    Parameters
272
    ----------
273
    slope : -np.gradient(hgt, dx) (we don't clip for min slope!)
274
    width : section width in m
275
    flux : mass flux in m3 s-1
276
    shape : 'rectangular' or 'parabolic'
277
    glen_a : Glen A, defaults to PARAMS
278
    fs : sliding, defaults to PARAMS
279
    shape_factor: for lateral drag
280

281
    Returns
282
    -------
283
    the ice thickness (in m)
284
    """
285

286
    if glen_a is None:
5✔
287
        glen_a = cfg.PARAMS['inversion_glen_a']
5✔
288
    if fs is None:
5✔
289
        fs = cfg.PARAMS['inversion_fs']
5✔
290
    if shape not in ['parabolic', 'rectangular']:
5✔
291
        raise InvalidParamsError('shape must be `parabolic` or `rectangular`, '
×
292
                                 'not: {}'.format(shape))
293

294
    _inv_function = _inversion_simple if fs == 0 else _inversion_poly
5✔
295

296
    # Ice flow params
297
    fd = 2. / (cfg.PARAMS['glen_n']+2) * glen_a
5✔
298
    rho = cfg.PARAMS['ice_density']
5✔
299

300
    # Convert the flux to m2 s-1 (averaged to represent the sections center)
301
    flux_a0 = 1 if shape == 'rectangular' else 1.5
5✔
302
    flux_a0 *= flux / width
5✔
303

304
    # With numerically small widths this creates very high thicknesses
305
    try:
5✔
306
        flux_a0[width < MIN_WIDTH_FOR_INV] = 0
5✔
307
    except TypeError:
4✔
308
        if width < MIN_WIDTH_FOR_INV:
4✔
309
            flux_a0 = 0
×
310

311
    # Polynomial factors (a5 = 1)
312
    a0 = - flux_a0 / ((rho * cfg.G * slope) ** 3 * fd)
5✔
313
    a3 = fs / fd
5✔
314

315
    return _compute_thick(a0, a3, flux_a0, _inv_function)
5✔
316

317

318
def find_sia_flux_from_thickness(slope, width, thick, glen_a=None, fs=None,
9✔
319
                                 shape='rectangular'):
320
    """Find the ice flux produced by a given thickness and slope.
321

322
    This can be done analytically but I'm lazy and use optimisation instead.
323
    """
324

325
    def to_minimize(x):
1✔
326
        h = sia_thickness(slope, width, x[0], glen_a=glen_a, fs=fs,
1✔
327
                          shape=shape)
328
        return (thick - h)**2
1✔
329

330
    out = optimize.minimize(to_minimize, [1], bounds=((0, 1e12),))
1✔
331
    flux = out['x'][0]
1✔
332

333
    # Sanity check
334
    minimum = to_minimize([flux])
1✔
335
    if minimum > 1:
1✔
336
        warnings.warn('We did not find a proper flux for this thickness',
×
337
                      RuntimeWarning)
338
    return flux
1✔
339

340

341
def _vol_below_water(surface_h, bed_h, bed_shape, thick, widths,
9✔
342
                     is_rectangular, is_trapezoid, fac, t_lambda,
343
                     dx, water_level):
344
    bsl = (bed_h < water_level) & (thick > 0)
2✔
345
    n_thick = np.copy(thick)
2✔
346
    n_thick[~bsl] = 0
2✔
347
    n_thick[bsl] = utils.clip_max(surface_h[bsl], water_level) - bed_h[bsl]
2✔
348
    with warnings.catch_warnings():
2✔
349
        warnings.filterwarnings("ignore", category=RuntimeWarning)
2✔
350
        n_w = np.sqrt(4 * n_thick / bed_shape)
2✔
351
    n_w[is_rectangular] = widths[is_rectangular]
2✔
352
    out = fac * n_thick * n_w * dx
2✔
353
    # Trap
354
    it = is_trapezoid
2✔
355
    out[it] = (n_w[it] + n_w[it] - t_lambda*n_thick[it]) / 2*n_thick[it]*dx
2✔
356
    return out
2✔
357

358

359
@entity_task(log, writes=['inversion_output'])
9✔
360
def mass_conservation_inversion(gdir, glen_a=None, fs=None, write=True,
9✔
361
                                filesuffix='', water_level=None,
362
                                t_lambda=None):
363
    """ Compute the glacier thickness along the flowlines
364

365
    More or less following Farinotti et al., (2009).
366

367
    Parameters
368
    ----------
369
    gdir : :py:class:`oggm.GlacierDirectory`
370
        the glacier directory to process
371
    glen_a : float
372
        glen's creep parameter A. Defaults to cfg.PARAMS.
373
    fs : float
374
        sliding parameter. Defaults to cfg.PARAMS.
375
    write: bool
376
        default behavior is to compute the thickness and write the
377
        results in the pickle. Set to False in order to spare time
378
        during calibration.
379
    filesuffix : str
380
        add a suffix to the output file
381
    water_level : float
382
        to compute volume below water level - adds an entry to the output dict
383
    t_lambda : float
384
        defining the angle of the trapezoid walls (see documentation). Defaults
385
        to cfg.PARAMS.
386
    """
387

388
    # Defaults
389
    if glen_a is None:
5✔
390
        glen_a = cfg.PARAMS['inversion_glen_a']
4✔
391
    if fs is None:
5✔
392
        fs = cfg.PARAMS['inversion_fs']
4✔
393
    if t_lambda is None:
5✔
394
        t_lambda = cfg.PARAMS['trapezoid_lambdas']
5✔
395

396
    # Check input
397
    _inv_function = _inversion_simple if fs == 0 else _inversion_poly
5✔
398

399
    # Ice flow params
400
    fd = 2. / (cfg.PARAMS['glen_n']+2) * glen_a
5✔
401
    a3 = fs / fd
5✔
402
    rho = cfg.PARAMS['ice_density']
5✔
403

404
    # Clip the slope, in rad
405
    min_slope = 'min_slope_ice_caps' if gdir.is_icecap else 'min_slope'
5✔
406
    min_slope = np.deg2rad(cfg.PARAMS[min_slope])
5✔
407

408
    out_volume = 0.
5✔
409

410
    cls = gdir.read_pickle('inversion_input')
5✔
411
    for cl in cls:
5✔
412
        # Clip slope to avoid negative and small slopes
413
        slope = cl['slope_angle']
5✔
414
        slope = utils.clip_array(slope, min_slope, np.pi/2.)
5✔
415

416
        # Glacier width
417
        w = cl['width']
5✔
418

419
        a0s = - cl['flux_a0'] / ((rho*cfg.G*slope)**3*fd)
5✔
420

421
        out_thick = _compute_thick(a0s, a3, cl['flux_a0'], _inv_function)
5✔
422

423
        # volume
424
        is_rect = cl['is_rectangular']
5✔
425
        fac = np.where(is_rect, 1, 2./3.)
5✔
426
        volume = fac * out_thick * w * cl['dx']
5✔
427

428
        # Now recompute thickness where parabola is too flat
429
        is_trap = cl['is_trapezoid']
5✔
430
        if cl['invert_with_trapezoid']:
5✔
431
            min_shape = cfg.PARAMS['mixed_min_shape']
5✔
432
            bed_shape = 4 * out_thick / w ** 2
5✔
433
            is_trap = ((bed_shape < min_shape) & ~ cl['is_rectangular'] &
5✔
434
                       (cl['flux'] > 0)) | is_trap
435
            for i in np.where(is_trap)[0]:
5✔
436
                try:
5✔
437
                    out_thick[i] = sia_thickness_via_optim(slope[i], w[i],
5✔
438
                                                           cl['flux'][i],
439
                                                           shape='trapezoid',
440
                                                           t_lambda=t_lambda,
441
                                                           glen_a=glen_a,
442
                                                           fs=fs)
443
                    sect = (2*w[i] - t_lambda * out_thick[i]) / 2 * out_thick[i]
5✔
444
                    volume[i] = sect * cl['dx']
5✔
445
                except ValueError:
3✔
446
                    # no solution error - we do with rect
447
                    out_thick[i] = sia_thickness_via_optim(slope[i], w[i],
3✔
448
                                                           cl['flux'][i],
449
                                                           shape='rectangular',
450
                                                           glen_a=glen_a,
451
                                                           fs=fs)
452
                    is_rect[i] = True
3✔
453
                    is_trap[i] = False
3✔
454
                    volume[i] = out_thick[i] * w[i] * cl['dx']
3✔
455

456
        # Sanity check
457
        if np.any(out_thick <= -1e-2):
5✔
458
            log.warning(f"{gdir.rgi_id} Found negative thickness: "
×
459
                        f"n={(out_thick <= -1e-2).sum()}, "
460
                        f"v={np.min(out_thick)}.")
461

462
        out_thick = utils.clip_min(out_thick, 0)
5✔
463

464
        if write:
5✔
465
            cl['is_trapezoid'] = is_trap
5✔
466
            cl['is_rectangular'] = is_rect
5✔
467
            cl['thick'] = out_thick
5✔
468
            cl['volume'] = volume
5✔
469

470
            # volume below sl
471
            try:
5✔
472
                bed_h = cl['hgt'] - out_thick
5✔
473
                bed_shape = 4 * out_thick / w ** 2
5✔
474
                if np.any(bed_h < 0):
5✔
475
                    cl['volume_bsl'] = _vol_below_water(cl['hgt'], bed_h,
2✔
476
                                                        bed_shape, out_thick,
477
                                                        w,
478
                                                        cl['is_rectangular'],
479
                                                        cl['is_trapezoid'],
480
                                                        fac, t_lambda,
481
                                                        cl['dx'], 0)
482
                if water_level is not None and np.any(bed_h < water_level):
5✔
483
                    cl['volume_bwl'] = _vol_below_water(cl['hgt'], bed_h,
2✔
484
                                                        bed_shape, out_thick,
485
                                                        w,
486
                                                        cl['is_rectangular'],
487
                                                        cl['is_trapezoid'],
488
                                                        fac, t_lambda,
489
                                                        cl['dx'],
490
                                                        water_level)
491
            except KeyError:
×
492
                # cl['hgt'] is not available on old prepro dirs
493
                pass
×
494

495
        out_volume += np.sum(volume)
5✔
496

497
    if write:
5✔
498
        gdir.write_pickle(cls, 'inversion_output', filesuffix=filesuffix)
5✔
499
        gdir.add_to_diagnostics('inversion_glen_a', glen_a)
5✔
500
        gdir.add_to_diagnostics('inversion_fs', fs)
5✔
501

502
    return out_volume
5✔
503

504

505
@entity_task(log, writes=['inversion_output'])
9✔
506
def filter_inversion_output(gdir, n_smoothing=5, min_ice_thick=1.,
9✔
507
                            max_depression=5.):
508
    """Filters the last few grid points after the physically-based inversion.
509

510
    For various reasons (but mostly: the equilibrium assumption), the last few
511
    grid points on a glacier flowline are often noisy and create unphysical
512
    depressions. Here we try to correct for that. It is not volume conserving,
513
    but area conserving. If a parabolic shape factor is getting smaller than
514
    the minimum defined one (cfg.PARAMS['mixed_min_shape']) the grid point is
515
    changed to a trapezoid, similar to what is done during the actual
516
    physically-based inversion.
517

518
    Parameters
519
    ----------
520
    gdir : :py:class:`oggm.GlacierDirectory`
521
        the glacier directory to process
522
    n_smoothing : int
523
        number of grid points which should be smoothed. Default is 5
524
    min_ice_thick : float
525
        the minimum ice thickness after the smoothing. Default is 1 m
526
    max_depression : float
527
        the limit allowed bed depression without smoothing. Default is 5 m
528
    """
529

530
    if gdir.is_tidewater:
5✔
531
        # No need for filter in tidewater case
532
        cls = gdir.read_pickle('inversion_output')
1✔
533
        init_vol = np.sum([np.sum(cl['volume']) for cl in cls])
1✔
534
        return init_vol
1✔
535

536
    if not gdir.has_file('downstream_line'):
5✔
537
        raise InvalidWorkflowError('filter_inversion_output now needs a '
×
538
                                   'previous call to the '
539
                                   'compute_dowstream_line and '
540
                                   'compute_downstream_bedshape tasks')
541

542
    dic_ds = gdir.read_pickle('downstream_line')
5✔
543
    cls = gdir.read_pickle('inversion_output')
5✔
544
    cl = cls[-1]
5✔
545

546
    # convert to negative number for indexing
547
    n_smoothing = -abs(n_smoothing)
5✔
548

549
    cl_sfc_h = cl['hgt'][n_smoothing:]
5✔
550
    cl_thick = cl['thick'][n_smoothing:]
5✔
551
    cl_width = cl['width'][n_smoothing:]
5✔
552
    cl_is_trap = cl['is_trapezoid'][n_smoothing:]
5✔
553
    cl_is_rect = cl['is_rectangular'][n_smoothing:]
5✔
554
    cl_bed_h = cl_sfc_h - cl_thick
5✔
555
    try:
5✔
556
        downstream_sfc_h = dic_ds['surface_h'][:5]
5✔
557
    except KeyError:
×
558
        raise InvalidWorkflowError('Please run compute_downstream_line and '
×
559
                                   'compute_downstream_bedshape for the '
560
                                   'filter.')
561

562
    # we smooth if the depression is larger than max_depression
563
    if downstream_sfc_h[0] - cl_bed_h[-1] > max_depression:
5✔
564
        # force the last grid point height to continue the downstream slope
565
        down_slope_avg = np.average(np.abs(np.diff(downstream_sfc_h)))
5✔
566
        new_last_bed_h = downstream_sfc_h[0] + down_slope_avg
5✔
567

568
        # now smoothly add the change of the bed height over all smoothing grid
569
        # points
570
        all_bed_h_changes = np.linspace(0, new_last_bed_h - cl_bed_h[-1],
5✔
571
                                        -n_smoothing)
572
        cl_bed_h = cl_bed_h + all_bed_h_changes
5✔
573

574
    # define new thick and clip, maximum value needed to avoid geometrical
575
    # inconsistencies with trapezoidal bed shape
576
    new_thick = cl_sfc_h - cl_bed_h
5✔
577
    # max
578
    max_h = np.where(cl_is_trap,
5✔
579
                     # -1. to get w0 > 0 at the end and not w0 = 0
580
                     cl_width / cfg.PARAMS['trapezoid_lambdas'] - 1.,
581
                     1e4)
582
    new_thick = np.where(new_thick > max_h, max_h, new_thick)
5✔
583
    # min
584
    new_thick = np.where(new_thick < min_ice_thick, min_ice_thick, new_thick)
5✔
585

586
    # new trap = (is trap or shape factor small) and not rectangular, we
587
    # conserve all bed shapes
588
    # if new bed shape is smaller than defined minimum it is converted to a
589
    # trapezoidal (as it is done during inversion)
590
    new_bed_shape = 4 * new_thick / cl_width ** 2
5✔
591
    new_is_trapezoid = ((cl_is_trap |
5✔
592
                         (new_bed_shape < cfg.PARAMS['mixed_min_shape'])) &
593
                        ~cl_is_rect)
594

595
    # and calculate new volumes depending on shape
596
    new_volume = np.where(
5✔
597
        new_is_trapezoid,
598
        cl_width * new_thick - cfg.PARAMS['trapezoid_lambdas'] / 2 *
599
        new_thick ** 2,
600
        np.where(cl_is_rect, 1, 2 / 3) * cl_width * new_thick) * cl['dx']
601

602
    # define new values
603
    cl['thick'][n_smoothing:] = new_thick
5✔
604
    cl['is_trapezoid'][n_smoothing:] = new_is_trapezoid
5✔
605
    cl['volume'][n_smoothing:] = new_volume
5✔
606

607
    gdir.write_pickle(cls, 'inversion_output')
5✔
608

609
    # Return volume for convenience
610
    return np.sum([np.sum(cl['volume']) for cl in cls])
5✔
611

612

613
@entity_task(log)
9✔
614
def get_inversion_volume(gdir):
9✔
615
    """Small utility task to get to the volume od all glaciers."""
616
    cls = gdir.read_pickle('inversion_output')
3✔
617
    return np.sum([np.sum(cl['volume']) for cl in cls])
3✔
618

619

620
@entity_task(log, writes=['inversion_output'])
9✔
621
def compute_velocities(*args, **kwargs):
9✔
622
    """Deprecated. Use compute_inversion_velocities instead."""
623
    warnings.warn("`compute_velocities` has been renamed to "
×
624
                  "`compute_inversion_velocities`. Prefer to use the new"
625
                  "name from now on.")
626
    return compute_inversion_velocities(*args, **kwargs)
×
627

628

629
@entity_task(log, writes=['inversion_output'])
9✔
630
def compute_inversion_velocities(gdir, glen_a=None, fs=None, filesuffix='',
9✔
631
                                 with_sliding=None):
632
    """Surface velocities along the flowlines from inverted ice thickness.
633

634
    Computed following the methods described in Cuffey and Paterson (2010)
635
    Eq. 8.35, pp 310:
636

637
        u_s = u_basal + (2A/n+1)* tau^n * H
638

639
    In the case of no sliding (or if with_sliding=False, which is a
640
    justifiable simplification given uncertainties on basal sliding):
641

642
        u_z/u_s = [n+1]/[n+2] (= 0.8 if n = 3).
643

644
    The output is written in 'inversion_output.pkl' in m yr-1
645

646
    Parameters
647
    ----------
648
    gdir : :py:class:`oggm.GlacierDirectory`
649
        the glacier directory to process
650
    with_sliding : bool
651
        if set to True, we will compute velocities the sliding component.
652
        the default is to check if sliding was used for the inversion
653
        (fs != 0)
654
    glen_a : float
655
        Glen's deformation parameter A. Defaults to PARAMS['inversion_glen_a']
656
    fs : float
657
        sliding paramter, defaults to PARAMS['inversion_fs']
658
    filesuffix : str
659
        add a suffix to the output file (optional)
660
    """
661

662
    # Defaults
663
    if glen_a is None:
1✔
664
        glen_a = cfg.PARAMS['inversion_glen_a']
×
665
    if fs is None:
1✔
666
        fs = cfg.PARAMS['inversion_fs']
×
667

668
    rho = cfg.PARAMS['ice_density']
1✔
669
    glen_n = cfg.PARAMS['glen_n']
1✔
670

671
    if with_sliding is None:
1✔
672
        with_sliding = fs != 0
1✔
673

674
    # Getting the data for the main flowline
675
    cls = gdir.read_pickle('inversion_output')
1✔
676

677
    for cl in cls:
1✔
678
        # vol in m3 and dx in m
679
        section = cl['volume'] / cl['dx']
1✔
680

681
        # this flux is in m3 per second
682
        flux = cl['flux']
1✔
683
        angle = cl['slope_angle']
1✔
684
        thick = cl['thick']
1✔
685

686
        if fs > 0 and with_sliding:
1✔
687
            tau = rho * cfg.G * angle * thick
1✔
688

689
            with warnings.catch_warnings():
1✔
690
                # This can trigger a divide by zero Warning
691
                warnings.filterwarnings("ignore", category=RuntimeWarning)
1✔
692
                u_basal = fs * tau ** glen_n / thick
1✔
693

694
            u_basal[~np.isfinite(u_basal)] = 0
1✔
695

696
            u_deformation = (2 * glen_a / (glen_n + 1)) * (tau**glen_n) * thick
1✔
697

698
            u_basal *= cfg.SEC_IN_YEAR
1✔
699
            u_deformation *= cfg.SEC_IN_YEAR
1✔
700
            u_surface = u_basal + u_deformation
1✔
701
            with warnings.catch_warnings():
1✔
702
                warnings.filterwarnings("ignore", category=RuntimeWarning)
1✔
703
                velocity = flux / section
1✔
704
            velocity *= cfg.SEC_IN_YEAR
1✔
705
        else:
706
            # velocity in cross section
707
            fac = (glen_n + 1) / (glen_n + 2)
1✔
708
            with warnings.catch_warnings():
1✔
709
                warnings.filterwarnings("ignore", category=RuntimeWarning)
1✔
710
                velocity = flux / section
1✔
711
            velocity *= cfg.SEC_IN_YEAR
1✔
712
            u_surface = velocity / fac
1✔
713
            u_basal = velocity * np.NaN
1✔
714
            u_deformation = velocity * np.NaN
1✔
715

716
        # output
717
        cl['u_integrated'] = velocity
1✔
718
        cl['u_surface'] = u_surface
1✔
719
        cl['u_basal'] = u_basal
1✔
720
        cl['u_deformation'] = u_deformation
1✔
721

722
    gdir.write_pickle(cls, 'inversion_output', filesuffix=filesuffix)
1✔
723

724

725
@entity_task(log, writes=['gridded_data'])
9✔
726
def distribute_thickness_per_altitude(gdir, add_slope=True,
9✔
727
                                      topo_variable='topo_smoothed',
728
                                      smooth_radius=None,
729
                                      dis_from_border_exp=0.25,
730
                                      varname_suffix=''):
731
    """Compute a thickness map by redistributing mass along altitudinal bands.
732

733
    This is a rather cosmetic task, not relevant for OGGM but for ITMIX or
734
    for visualizations.
735

736
    Parameters
737
    ----------
738
    gdir : :py:class:`oggm.GlacierDirectory`
739
        the glacier directory to process
740
    add_slope : bool
741
        whether a corrective slope factor should be used or not
742
    topo_variable : str
743
        the topography to read from `gridded_data.nc` (could be smoothed, or
744
        smoothed differently).
745
    smooth_radius : int
746
        pixel size of the gaussian smoothing. Default is to use
747
        cfg.PARAMS['smooth_window'] (i.e. a size in meters). Set to zero to
748
        suppress smoothing.
749
    dis_from_border_exp : float
750
        the exponent of the distance from border mask
751
    varname_suffix : str
752
        add a suffix to the variable written in the file (for experiments)
753
    """
754

755
    # Variables
756
    grids_file = gdir.get_filepath('gridded_data')
5✔
757
    # See if we have the masks, else compute them
758
    with utils.ncDataset(grids_file) as nc:
5✔
759
        has_masks = 'glacier_ext_erosion' in nc.variables
5✔
760
    if not has_masks:
5✔
761
        from oggm.core.gis import gridded_attributes
4✔
762
        gridded_attributes(gdir)
4✔
763

764
    with utils.ncDataset(grids_file) as nc:
5✔
765
        topo = nc.variables[topo_variable][:]
5✔
766
        glacier_mask = nc.variables['glacier_mask'][:]
5✔
767
        dis_from_border = nc.variables['dis_from_border'][:]
5✔
768
        if add_slope:
5✔
769
            slope_factor = nc.variables['slope_factor'][:]
5✔
770
        else:
771
            slope_factor = 1.
×
772

773
    # Along the lines
774
    cls = gdir.read_pickle('inversion_output')
5✔
775
    fls = gdir.read_pickle('inversion_flowlines')
5✔
776
    hs, ts, vs, xs, ys = [], [], [], [], []
5✔
777
    for cl, fl in zip(cls, fls):
5✔
778
        hs = np.append(hs, fl.surface_h)
5✔
779
        ts = np.append(ts, cl['thick'])
5✔
780
        vs = np.append(vs, cl['volume'])
5✔
781
        try:
5✔
782
            x, y = fl.line.xy
5✔
783
        except AttributeError:
3✔
784
            # Squeezed flowlines, dummy coords
785
            x = fl.surface_h * 0 - 1
3✔
786
            y = fl.surface_h * 0 - 1
3✔
787
        xs = np.append(xs, x)
5✔
788
        ys = np.append(ys, y)
5✔
789

790
    init_vol = np.sum(vs)
5✔
791

792
    # Assign a first order thickness to the points
793
    # very inefficient inverse distance stuff
794
    thick = glacier_mask * 0.
5✔
795
    yglac, xglac = np.nonzero(glacier_mask == 1)
5✔
796
    for y, x in zip(yglac, xglac):
5✔
797
        phgt = topo[y, x]
5✔
798
        # take the ones in a 100m range
799
        starth = 100.
5✔
800
        while True:
5✔
801
            starth += 10
5✔
802
            pok = np.nonzero(np.abs(phgt - hs) <= starth)[0]
5✔
803
            if len(pok) != 0:
5✔
804
                break
5✔
805
        sqr = np.sqrt((xs[pok]-x)**2 + (ys[pok]-y)**2)
5✔
806
        pzero = np.where(sqr == 0)
5✔
807
        if len(pzero[0]) == 0:
5✔
808
            thick[y, x] = np.average(ts[pok], weights=1 / sqr)
5✔
809
        elif len(pzero[0]) == 1:
5✔
810
            thick[y, x] = ts[pzero]
5✔
811
        else:
812
            raise RuntimeError('We should not be there')
×
813

814
    # Distance from border (normalized)
815
    dis_from_border = dis_from_border**dis_from_border_exp
5✔
816
    dis_from_border /= np.mean(dis_from_border[glacier_mask == 1])
5✔
817
    thick *= dis_from_border
5✔
818

819
    # Slope
820
    thick *= slope_factor
5✔
821

822
    # Smooth
823
    dx = gdir.grid.dx
5✔
824
    if smooth_radius != 0:
5✔
825
        if smooth_radius is None:
5✔
826
            smooth_radius = np.rint(cfg.PARAMS['smooth_window'] / dx)
5✔
827
        thick = gaussian_blur(thick, int(smooth_radius))
5✔
828
        thick = np.where(glacier_mask, thick, 0.)
5✔
829

830
    # Re-mask
831
    utils.clip_min(thick, 0, out=thick)
5✔
832
    thick[glacier_mask == 0] = np.NaN
5✔
833
    assert np.all(np.isfinite(thick[glacier_mask == 1]))
5✔
834

835
    # Conserve volume
836
    tmp_vol = np.nansum(thick * dx**2)
5✔
837
    thick *= init_vol / tmp_vol
5✔
838

839
    # write
840
    with utils.ncDataset(grids_file, 'a') as nc:
5✔
841
        vn = 'distributed_thickness' + varname_suffix
5✔
842
        if vn in nc.variables:
5✔
843
            v = nc.variables[vn]
1✔
844
        else:
845
            v = nc.createVariable(vn, 'f4', ('y', 'x', ), zlib=True)
5✔
846
        v.units = '-'
5✔
847
        v.long_name = 'Distributed ice thickness'
5✔
848
        v[:] = thick
5✔
849

850
    return thick
5✔
851

852

853
@entity_task(log, writes=['gridded_data'])
9✔
854
def distribute_thickness_interp(gdir, add_slope=True, smooth_radius=None,
9✔
855
                                varname_suffix=''):
856
    """Compute a thickness map by interpolating between centerlines and border.
857

858
    IMPORTANT: this is NOT what has been used for ITMIX. We used
859
    distribute_thickness_per_altitude for ITMIX and global ITMIX.
860

861
    This is a rather cosmetic task, not relevant for OGGM but for ITMIX.
862

863
    Parameters
864
    ----------
865
    gdir : :py:class:`oggm.GlacierDirectory`
866
        the glacier directory to process
867
    add_slope : bool
868
        whether a corrective slope factor should be used or not
869
    smooth_radius : int
870
        pixel size of the gaussian smoothing. Default is to use
871
        cfg.PARAMS['smooth_window'] (i.e. a size in meters). Set to zero to
872
        suppress smoothing.
873
    varname_suffix : str
874
        add a suffix to the variable written in the file (for experiments)
875
    """
876

877
    # Variables
878
    grids_file = gdir.get_filepath('gridded_data')
5✔
879
    # See if we have the masks, else compute them
880
    with utils.ncDataset(grids_file) as nc:
5✔
881
        has_masks = 'ice_divides' in nc.variables
5✔
882
    if not has_masks:
5✔
883
        from oggm.core.gis import gridded_attributes
4✔
884
        gridded_attributes(gdir)
4✔
885

886
    with utils.ncDataset(grids_file) as nc:
5✔
887
        glacier_mask = nc.variables['glacier_mask'][:]
5✔
888
        glacier_ext = nc.variables['glacier_ext_erosion'][:]
5✔
889
        ice_divides = nc.variables['ice_divides'][:]
5✔
890
        if add_slope:
5✔
891
            slope_factor = nc.variables['slope_factor'][:]
5✔
892
        else:
893
            slope_factor = 1.
×
894

895
    # Thickness to interpolate
896
    thick = glacier_ext * np.NaN
5✔
897
    thick[(glacier_ext-ice_divides) == 1] = 0.
5✔
898
    # TODO: domain border too, for convenience for a start
899
    thick[0, :] = 0.
5✔
900
    thick[-1, :] = 0.
5✔
901
    thick[:, 0] = 0.
5✔
902
    thick[:, -1] = 0.
5✔
903

904
    # Along the lines
905
    cls = gdir.read_pickle('inversion_output')
5✔
906
    fls = gdir.read_pickle('inversion_flowlines')
5✔
907
    vs = []
5✔
908
    for cl, fl in zip(cls, fls):
5✔
909
        vs.extend(cl['volume'])
5✔
910
        x, y = utils.tuple2int(fl.line.xy)
5✔
911
        thick[y, x] = cl['thick']
5✔
912
    init_vol = np.sum(vs)
5✔
913

914
    # Interpolate
915
    xx, yy = gdir.grid.ij_coordinates
5✔
916
    pnan = np.nonzero(~ np.isfinite(thick))
5✔
917
    pok = np.nonzero(np.isfinite(thick))
5✔
918
    points = np.array((np.ravel(yy[pok]), np.ravel(xx[pok]))).T
5✔
919
    inter = np.array((np.ravel(yy[pnan]), np.ravel(xx[pnan]))).T
5✔
920
    thick[pnan] = griddata(points, np.ravel(thick[pok]), inter, method='cubic')
5✔
921
    utils.clip_min(thick, 0, out=thick)
5✔
922

923
    # Slope
924
    thick *= slope_factor
5✔
925

926
    # Smooth
927
    dx = gdir.grid.dx
5✔
928
    if smooth_radius != 0:
5✔
929
        if smooth_radius is None:
5✔
930
            smooth_radius = np.rint(cfg.PARAMS['smooth_window'] / dx)
5✔
931
        thick = gaussian_blur(thick, int(smooth_radius))
5✔
932
        thick = np.where(glacier_mask, thick, 0.)
5✔
933

934
    # Re-mask
935
    thick[glacier_mask == 0] = np.NaN
5✔
936
    assert np.all(np.isfinite(thick[glacier_mask == 1]))
5✔
937

938
    # Conserve volume
939
    tmp_vol = np.nansum(thick * dx**2)
5✔
940
    thick *= init_vol / tmp_vol
5✔
941

942
    # write
943
    grids_file = gdir.get_filepath('gridded_data')
5✔
944
    with utils.ncDataset(grids_file, 'a') as nc:
5✔
945
        vn = 'distributed_thickness' + varname_suffix
5✔
946
        if vn in nc.variables:
5✔
947
            v = nc.variables[vn]
×
948
        else:
949
            v = nc.createVariable(vn, 'f4', ('y', 'x', ), zlib=True)
5✔
950
        v.units = '-'
5✔
951
        v.long_name = 'Distributed ice thickness'
5✔
952
        v[:] = thick
5✔
953

954
    return thick
5✔
955

956

957
def calving_flux_from_depth(gdir, k=None, water_level=None, water_depth=None,
9✔
958
                            thick=None, fixed_water_depth=False):
959
    """Finds a calving flux from the calving front thickness.
960

961
    Approach based on Huss and Hock, (2015) and Oerlemans and Nick (2005).
962
    We take the initial output of the model and surface elevation data
963
    to calculate the water depth of the calving front.
964

965
    Parameters
966
    ----------
967
    gdir : GlacierDirectory
968
    k : float
969
        calving constant
970
    water_level : float
971
        in case water is not at 0 m a.s.l
972
    water_depth : float (mandatory)
973
        the default is to compute the water_depth from ice thickness
974
        at the terminus and altitude. Set this to force the water depth
975
        to a certain value
976
    thick :
977
        Set this to force the ice thickness to a certain value (for
978
        sensitivity experiments).
979
    fixed_water_depth :
980
        If we have water depth from Bathymetry we fix the water depth
981
        and forget about the free-board
982

983
    Returns
984
    -------
985
    A dictionary containing:
986
    - the calving flux in [km3 yr-1]
987
    - the frontal width in m
988
    - the frontal thickness in m
989
    - the frontal water depth in m
990
    - the frontal free board in m
991
    """
992

993
    # Defaults
994
    if k is None:
3✔
995
        k = cfg.PARAMS['inversion_calving_k']
3✔
996

997
    # Read necessary data
998
    fl = gdir.read_pickle('inversion_flowlines')[-1]
3✔
999

1000
    # Altitude at the terminus and frontal width
1001
    free_board = utils.clip_min(fl.surface_h[-1], 0) - water_level
3✔
1002
    width = fl.widths[-1] * gdir.grid.dx
3✔
1003

1004
    # Calving formula
1005
    if thick is None:
3✔
1006
        cl = gdir.read_pickle('inversion_output')[-1]
3✔
1007
        thick = cl['thick'][-1]
3✔
1008
    if water_depth is None:
3✔
1009
        water_depth = thick - free_board
3✔
1010
    elif not fixed_water_depth:
3✔
1011
        # Correct thickness with prescribed water depth
1012
        # If fixed_water_depth=True then we forget about t_altitude
1013
        thick = water_depth + free_board
3✔
1014

1015
    flux = k * thick * water_depth * width / 1e9
3✔
1016

1017
    if fixed_water_depth:
3✔
1018
        # Recompute free board before returning
1019
        free_board = thick - water_depth
×
1020

1021
    return {'flux': utils.clip_min(flux, 0),
3✔
1022
            'width': width,
1023
            'thick': thick,
1024
            'inversion_calving_k': k,
1025
            'water_depth': water_depth,
1026
            'water_level': water_level,
1027
            'free_board': free_board}
1028

1029

1030
@entity_task(log, writes=['diagnostics'])
9✔
1031
def find_inversion_calving_from_any_mb(gdir, mb_model=None, mb_years=None,
9✔
1032
                                       water_level=None,
1033
                                       glen_a=None, fs=None):
1034
    """Optimized search for a calving flux compatible with the bed inversion.
1035

1036
    See Recinos et al. (2019) for details. This task is an update to
1037
    `find_inversion_calving` but acting upon the MB residual (i.e. a shift)
1038
    instead of the model temperature sensitivity.
1039

1040
    Parameters
1041
    ----------
1042
    mb_model : :py:class:`oggm.core.massbalance.MassBalanceModel`
1043
        the mass balance model to use
1044
    mb_years : array
1045
        the array of years from which you want to average the MB for (for
1046
        mb_model only).
1047
    water_level : float
1048
        the water level. It should be zero m a.s.l, but:
1049
        - sometimes the frontal elevation is unrealistically high (or low).
1050
        - lake terminating glaciers
1051
        - other uncertainties
1052
        With this parameter, you can produce more realistic values. The default
1053
        is to infer the water level from PARAMS['free_board_lake_terminating']
1054
        and PARAMS['free_board_marine_terminating']
1055
    glen_a : float, optional
1056
    fs : float, optional
1057
    """
1058
    from oggm.core import massbalance
3✔
1059

1060
    if not gdir.is_tidewater or not cfg.PARAMS['use_kcalving_for_inversion']:
3✔
1061
        # Do nothing
1062
        return
×
1063

1064
    # Let's start from a fresh state
1065
    gdir.inversion_calving_rate = 0
3✔
1066
    with utils.DisableLogger():
3✔
1067
        massbalance.apparent_mb_from_any_mb(gdir, mb_model=mb_model,
3✔
1068
                                            mb_years=mb_years)
1069
        prepare_for_inversion(gdir)
3✔
1070
        v_ref = mass_conservation_inversion(gdir, water_level=water_level,
3✔
1071
                                            glen_a=glen_a, fs=fs)
1072

1073
    # Store for statistics
1074
    gdir.add_to_diagnostics('volume_before_calving', v_ref)
3✔
1075

1076
    # Get the relevant variables
1077
    cls = gdir.read_pickle('inversion_input')[-1]
3✔
1078
    slope = cls['slope_angle'][-1]
3✔
1079
    width = cls['width'][-1]
3✔
1080

1081
    # Stupidly enough the slope is clipped in the OGGM inversion, not
1082
    # in inversion prepro - clip here
1083
    min_slope = 'min_slope_ice_caps' if gdir.is_icecap else 'min_slope'
3✔
1084
    min_slope = np.deg2rad(cfg.PARAMS[min_slope])
3✔
1085
    slope = utils.clip_array(slope, min_slope, np.pi / 2.)
3✔
1086

1087
    # Check that water level is within given bounds
1088
    if water_level is None:
3✔
1089
        th = cls['hgt'][-1]
3✔
1090
        if gdir.is_lake_terminating:
3✔
UNCOV
1091
            water_level = th - cfg.PARAMS['free_board_lake_terminating']
×
1092
        else:
1093
            vmin, vmax = cfg.PARAMS['free_board_marine_terminating']
3✔
1094
            water_level = utils.clip_scalar(0, th - vmax, th - vmin)
3✔
1095

1096
    # The functions all have the same shape: they decrease, then increase
1097
    # We seek the absolute minimum first
1098
    def to_minimize(h):
3✔
1099
        fl = calving_flux_from_depth(gdir, water_level=water_level,
3✔
1100
                                     water_depth=h)
1101

1102
        flux = fl['flux'] * 1e9 / cfg.SEC_IN_YEAR
3✔
1103
        sia_thick = sia_thickness(slope, width, flux, glen_a=glen_a, fs=fs)
3✔
1104
        return fl['thick'] - sia_thick
3✔
1105

1106
    abs_min = optimize.minimize(to_minimize, [1], bounds=((1e-4, 1e4), ),
3✔
1107
                                tol=1e-1)
1108
    if not abs_min['success']:
3✔
1109
        raise RuntimeError('Could not find the absolute minimum in calving '
×
1110
                           'flux optimization: {}'.format(abs_min))
1111
    if abs_min['fun'] > 0:
3✔
1112
        # This happens, and means that this glacier simply can't calve
1113
        # This is an indicator for physics not matching, often an unrealistic
1114
        # slope of free-board
1115
        out = calving_flux_from_depth(gdir, water_level=water_level)
1✔
1116

1117
        log.warning('({}) find_inversion_calving_from_any_mb: could not find '
1✔
1118
                    'calving flux.'.format(gdir.rgi_id))
1119

1120
        odf = dict()
1✔
1121
        odf['calving_flux'] = 0
1✔
1122
        odf['calving_rate_myr'] = 0
1✔
1123
        odf['calving_law_flux'] = out['flux']
1✔
1124
        odf['calving_water_level'] = out['water_level']
1✔
1125
        odf['calving_inversion_k'] = out['inversion_calving_k']
1✔
1126
        odf['calving_front_slope'] = slope
1✔
1127
        odf['calving_front_water_depth'] = out['water_depth']
1✔
1128
        odf['calving_front_free_board'] = out['free_board']
1✔
1129
        odf['calving_front_thick'] = out['thick']
1✔
1130
        odf['calving_front_width'] = out['width']
1✔
1131
        for k, v in odf.items():
1✔
1132
            gdir.add_to_diagnostics(k, v)
1✔
1133
        return odf
1✔
1134

1135
    # OK, we now find the zero between abs min and an arbitrary high front
1136
    abs_min = abs_min['x'][0]
2✔
1137
    opt = optimize.brentq(to_minimize, abs_min, 1e4)
2✔
1138

1139
    # Give the flux to the inversion and recompute
1140
    # This is the thick guaranteeing OGGM Flux = Calving Law Flux
1141
    out = calving_flux_from_depth(gdir, water_level=water_level,
2✔
1142
                                  water_depth=opt)
1143
    f_calving = out['flux']
2✔
1144

1145
    log.info('({}) find_inversion_calving_from_any_mb: found calving flux of '
2✔
1146
             '{:.03f} km3 yr-1'.format(gdir.rgi_id, f_calving))
1147
    gdir.inversion_calving_rate = f_calving
2✔
1148

1149
    with utils.DisableLogger():
2✔
1150
        massbalance.apparent_mb_from_any_mb(gdir, mb_model=mb_model,
2✔
1151
                                            mb_years=mb_years)
1152
        prepare_for_inversion(gdir)
2✔
1153
        mass_conservation_inversion(gdir, water_level=water_level,
2✔
1154
                                    glen_a=glen_a, fs=fs)
1155

1156
    out = calving_flux_from_depth(gdir, water_level=water_level)
2✔
1157

1158
    fl = gdir.read_pickle('inversion_flowlines')[-1]
2✔
1159
    f_calving = (fl.flux[-1] * (gdir.grid.dx ** 2) * 1e-9 /
2✔
1160
                 cfg.PARAMS['ice_density'])
1161

1162
    # Store results
1163
    odf = dict()
2✔
1164
    odf['calving_flux'] = f_calving
2✔
1165
    odf['calving_rate_myr'] = f_calving * 1e9 / (out['thick'] * out['width'])
2✔
1166
    odf['calving_law_flux'] = out['flux']
2✔
1167
    odf['calving_water_level'] = out['water_level']
2✔
1168
    odf['calving_inversion_k'] = out['inversion_calving_k']
2✔
1169
    odf['calving_front_slope'] = slope
2✔
1170
    odf['calving_front_water_depth'] = out['water_depth']
2✔
1171
    odf['calving_front_free_board'] = out['free_board']
2✔
1172
    odf['calving_front_thick'] = out['thick']
2✔
1173
    odf['calving_front_width'] = out['width']
2✔
1174
    for k, v in odf.items():
2✔
1175
        gdir.add_to_diagnostics(k, v)
2✔
1176

1177
    return odf
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc