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

OGGM / oggm / 4861042853

pending completion
4861042853

push

github

GitHub
add to whats-new (#1569)

11181 of 13110 relevant lines covered (85.29%)

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
                                      smooth_radius=None,
728
                                      dis_from_border_exp=0.25,
729
                                      varname_suffix=''):
730
    """Compute a thickness map by redistributing mass along altitudinal bands.
731

732
    This is a rather cosmetic task, not relevant for OGGM but for ITMIX.
733

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

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

759
    with utils.ncDataset(grids_file) as nc:
5✔
760
        topo_smoothed = nc.variables['topo_smoothed'][:]
5✔
761
        glacier_mask = nc.variables['glacier_mask'][:]
5✔
762
        dis_from_border = nc.variables['dis_from_border'][:]
5✔
763
        if add_slope:
5✔
764
            slope_factor = nc.variables['slope_factor'][:]
5✔
765
        else:
766
            slope_factor = 1.
×
767

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

785
    init_vol = np.sum(vs)
5✔
786

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

809
    # Distance from border (normalized)
810
    dis_from_border = dis_from_border**dis_from_border_exp
5✔
811
    dis_from_border /= np.mean(dis_from_border[glacier_mask == 1])
5✔
812
    thick *= dis_from_border
5✔
813

814
    # Slope
815
    thick *= slope_factor
5✔
816

817
    # Smooth
818
    dx = gdir.grid.dx
5✔
819
    if smooth_radius != 0:
5✔
820
        if smooth_radius is None:
5✔
821
            smooth_radius = np.rint(cfg.PARAMS['smooth_window'] / dx)
5✔
822
        thick = gaussian_blur(thick, int(smooth_radius))
5✔
823
        thick = np.where(glacier_mask, thick, 0.)
5✔
824

825
    # Re-mask
826
    utils.clip_min(thick, 0, out=thick)
5✔
827
    thick[glacier_mask == 0] = np.NaN
5✔
828
    assert np.all(np.isfinite(thick[glacier_mask == 1]))
5✔
829

830
    # Conserve volume
831
    tmp_vol = np.nansum(thick * dx**2)
5✔
832
    thick *= init_vol / tmp_vol
5✔
833

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

845
    return thick
5✔
846

847

848
@entity_task(log, writes=['gridded_data'])
9✔
849
def distribute_thickness_interp(gdir, add_slope=True, smooth_radius=None,
9✔
850
                                varname_suffix=''):
851
    """Compute a thickness map by interpolating between centerlines and border.
852

853
    IMPORTANT: this is NOT what has been used for ITMIX. We used
854
    distribute_thickness_per_altitude for ITMIX and global ITMIX.
855

856
    This is a rather cosmetic task, not relevant for OGGM but for ITMIX.
857

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

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

881
    with utils.ncDataset(grids_file) as nc:
5✔
882
        glacier_mask = nc.variables['glacier_mask'][:]
5✔
883
        glacier_ext = nc.variables['glacier_ext_erosion'][:]
5✔
884
        ice_divides = nc.variables['ice_divides'][:]
5✔
885
        if add_slope:
5✔
886
            slope_factor = nc.variables['slope_factor'][:]
5✔
887
        else:
888
            slope_factor = 1.
×
889

890
    # Thickness to interpolate
891
    thick = glacier_ext * np.NaN
5✔
892
    thick[(glacier_ext-ice_divides) == 1] = 0.
5✔
893
    # TODO: domain border too, for convenience for a start
894
    thick[0, :] = 0.
5✔
895
    thick[-1, :] = 0.
5✔
896
    thick[:, 0] = 0.
5✔
897
    thick[:, -1] = 0.
5✔
898

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

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

918
    # Slope
919
    thick *= slope_factor
5✔
920

921
    # Smooth
922
    dx = gdir.grid.dx
5✔
923
    if smooth_radius != 0:
5✔
924
        if smooth_radius is None:
5✔
925
            smooth_radius = np.rint(cfg.PARAMS['smooth_window'] / dx)
5✔
926
        thick = gaussian_blur(thick, int(smooth_radius))
5✔
927
        thick = np.where(glacier_mask, thick, 0.)
5✔
928

929
    # Re-mask
930
    thick[glacier_mask == 0] = np.NaN
5✔
931
    assert np.all(np.isfinite(thick[glacier_mask == 1]))
5✔
932

933
    # Conserve volume
934
    tmp_vol = np.nansum(thick * dx**2)
5✔
935
    thick *= init_vol / tmp_vol
5✔
936

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

949
    return thick
5✔
950

951

952
def calving_flux_from_depth(gdir, k=None, water_level=None, water_depth=None,
9✔
953
                            thick=None, fixed_water_depth=False):
954
    """Finds a calving flux from the calving front thickness.
955

956
    Approach based on Huss and Hock, (2015) and Oerlemans and Nick (2005).
957
    We take the initial output of the model and surface elevation data
958
    to calculate the water depth of the calving front.
959

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

978
    Returns
979
    -------
980
    A dictionary containing:
981
    - the calving flux in [km3 yr-1]
982
    - the frontal width in m
983
    - the frontal thickness in m
984
    - the frontal water depth in m
985
    - the frontal free board in m
986
    """
987

988
    # Defaults
989
    if k is None:
3✔
990
        k = cfg.PARAMS['inversion_calving_k']
3✔
991

992
    # Read necessary data
993
    fl = gdir.read_pickle('inversion_flowlines')[-1]
3✔
994

995
    # Altitude at the terminus and frontal width
996
    free_board = utils.clip_min(fl.surface_h[-1], 0) - water_level
3✔
997
    width = fl.widths[-1] * gdir.grid.dx
3✔
998

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

1010
    flux = k * thick * water_depth * width / 1e9
3✔
1011

1012
    if fixed_water_depth:
3✔
1013
        # Recompute free board before returning
1014
        free_board = thick - water_depth
×
1015

1016
    return {'flux': utils.clip_min(flux, 0),
3✔
1017
            'width': width,
1018
            'thick': thick,
1019
            'inversion_calving_k': k,
1020
            'water_depth': water_depth,
1021
            'water_level': water_level,
1022
            'free_board': free_board}
1023

1024

1025
@entity_task(log, writes=['diagnostics'])
9✔
1026
def find_inversion_calving_from_any_mb(gdir, mb_model=None, mb_years=None,
9✔
1027
                                       water_level=None,
1028
                                       glen_a=None, fs=None):
1029
    """Optimized search for a calving flux compatible with the bed inversion.
1030

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

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

1055
    if not gdir.is_tidewater or not cfg.PARAMS['use_kcalving_for_inversion']:
3✔
1056
        # Do nothing
1057
        return
×
1058

1059
    # Let's start from a fresh state
1060
    gdir.inversion_calving_rate = 0
3✔
1061
    with utils.DisableLogger():
3✔
1062
        massbalance.apparent_mb_from_any_mb(gdir, mb_model=mb_model,
3✔
1063
                                            mb_years=mb_years)
1064
        prepare_for_inversion(gdir)
3✔
1065
        v_ref = mass_conservation_inversion(gdir, water_level=water_level,
3✔
1066
                                            glen_a=glen_a, fs=fs)
1067

1068
    # Store for statistics
1069
    gdir.add_to_diagnostics('volume_before_calving', v_ref)
3✔
1070

1071
    # Get the relevant variables
1072
    cls = gdir.read_pickle('inversion_input')[-1]
3✔
1073
    slope = cls['slope_angle'][-1]
3✔
1074
    width = cls['width'][-1]
3✔
1075

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

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

1091
    # The functions all have the same shape: they decrease, then increase
1092
    # We seek the absolute minimum first
1093
    def to_minimize(h):
3✔
1094
        fl = calving_flux_from_depth(gdir, water_level=water_level,
3✔
1095
                                     water_depth=h)
1096

1097
        flux = fl['flux'] * 1e9 / cfg.SEC_IN_YEAR
3✔
1098
        sia_thick = sia_thickness(slope, width, flux, glen_a=glen_a, fs=fs)
3✔
1099
        return fl['thick'] - sia_thick
3✔
1100

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

1112
        log.warning('({}) find_inversion_calving_from_any_mb: could not find '
1✔
1113
                    'calving flux.'.format(gdir.rgi_id))
1114

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

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

1134
    # Give the flux to the inversion and recompute
1135
    # This is the thick guaranteeing OGGM Flux = Calving Law Flux
1136
    out = calving_flux_from_depth(gdir, water_level=water_level,
2✔
1137
                                  water_depth=opt)
1138
    f_calving = out['flux']
2✔
1139

1140
    log.info('({}) find_inversion_calving_from_any_mb: found calving flux of '
2✔
1141
             '{:.03f} km3 yr-1'.format(gdir.rgi_id, f_calving))
1142
    gdir.inversion_calving_rate = f_calving
2✔
1143

1144
    with utils.DisableLogger():
2✔
1145
        massbalance.apparent_mb_from_any_mb(gdir, mb_model=mb_model,
2✔
1146
                                            mb_years=mb_years)
1147
        prepare_for_inversion(gdir)
2✔
1148
        mass_conservation_inversion(gdir, water_level=water_level,
2✔
1149
                                    glen_a=glen_a, fs=fs)
1150

1151
    out = calving_flux_from_depth(gdir, water_level=water_level)
2✔
1152

1153
    fl = gdir.read_pickle('inversion_flowlines')[-1]
2✔
1154
    f_calving = (fl.flux[-1] * (gdir.grid.dx ** 2) * 1e-9 /
2✔
1155
                 cfg.PARAMS['ice_density'])
1156

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

1172
    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