Esempio n. 1
0
def filter_inversion_output(gdir):
    """Filters the last few grid point whilst conserving total volume.

    The last few grid points sometimes are noisy or can have a negative slope.
    This function filters them while conserving the total volume.

    Parameters
    ----------
    gdir : :py:class:`oggm.GlacierDirectory`
        the glacier directory to process
    """

    if gdir.is_tidewater:
        # No need for filter in tidewater case
        return

    cls = gdir.read_pickle('inversion_output')
    for cl in cls:

        init_vol = np.sum(cl['volume'])
        if init_vol == 0 or not cl['is_last']:
            continue

        w = cl['width']
        out_thick = cl['thick']
        fac = np.where(cl['is_rectangular'], 1, 2./3.)

        # Last thicknesses can be noisy sometimes: interpolate
        out_thick[-4:] = np.NaN
        out_thick = utils.interp_nans(np.append(out_thick, 0))[:-1]
        assert len(out_thick) == len(fac)

        # final volume
        volume = fac * out_thick * w * cl['dx']

        # conserve it
        new_vol = np.nansum(volume)
        if new_vol == 0:
            # Very small glaciers
            return
        volume = init_vol / new_vol * volume
        np.testing.assert_allclose(np.nansum(volume), init_vol)

        # recompute thickness on that base
        out_thick = volume / (fac * w * cl['dx'])

        # output
        cl['thick'] = out_thick
        cl['volume'] = volume

    gdir.write_pickle(cls, 'inversion_output')
Esempio n. 2
0
def filter_inversion_output(gdir):
    """Filters the last few grid point whilst conserving total volume.

    The last few grid points sometimes are noisy or can have a negative slope.
    This function filters them while conserving the total volume.

    Parameters
    ----------
    gdir : :py:class:`oggm.GlacierDirectory`
        the glacier directory to process
    """

    if gdir.is_tidewater:
        # No need for filter in tidewater case
        return

    cls = gdir.read_pickle('inversion_output')
    for cl in cls:

        init_vol = np.sum(cl['volume'])
        if init_vol == 0 or not cl['is_last']:
            continue

        w = cl['width']
        out_thick = cl['thick']
        fac = np.where(cl['is_rectangular'], 1, 2. / 3.)

        # Last thicknesses can be noisy sometimes: interpolate
        out_thick[-4:] = np.NaN
        out_thick = utils.interp_nans(np.append(out_thick, 0))[:-1]
        assert len(out_thick) == len(fac)

        # final volume
        volume = fac * out_thick * w * cl['dx']

        # conserve it
        new_vol = np.nansum(volume)
        if new_vol == 0:
            # Very small glaciers
            return
        volume = init_vol / new_vol * volume
        np.testing.assert_allclose(np.nansum(volume), init_vol)

        # recompute thickness on that base
        out_thick = volume / (fac * w * cl['dx'])

        # output
        cl['thick'] = out_thick
        cl['volume'] = volume

    gdir.write_pickle(cls, 'inversion_output')
Esempio n. 3
0
def filter_inversion_output(gdir):
    """Filters the last few grid point whilst conserving total volume.
    """

    if gdir.is_tidewater:
        # No need for filter in tidewater case
        return

    cls = gdir.read_pickle('inversion_output')
    for cl in cls:

        init_vol = np.sum(cl['volume'])
        if init_vol == 0 or not cl['is_last']:
            continue

        w = cl['width']
        out_thick = cl['thick']
        fac = np.where(cl['is_rectangular'], 1, 2. / 3.)

        # Last thicknesses can be noisy sometimes: interpolate
        out_thick[-4:] = np.NaN
        out_thick = utils.interp_nans(np.append(out_thick, 0))[:-1]
        assert len(out_thick) == len(fac)

        # final volume
        volume = fac * out_thick * w * cl['dx']

        # conserve it
        new_vol = np.nansum(volume)
        assert new_vol != 0
        volume = init_vol / new_vol * volume
        np.testing.assert_allclose(np.nansum(volume), init_vol)

        # recompute thickness on that base
        out_thick = volume / (fac * w * cl['dx'])

        # output
        cl['thick'] = out_thick
        cl['volume'] = volume

    gdir.write_pickle(cls, 'inversion_output')
Esempio n. 4
0
def filter_inversion_output(gdir):
    """Filters the last few grid point whilst conserving total volume.
    """

    for div in gdir.divide_ids:
        cls = gdir.read_pickle('inversion_output', div_id=div)
        for cl in cls:

            init_vol = np.sum(cl['volume'])
            if init_vol == 0 or gdir.is_tidewater or not cl['is_last']:
                continue

            w = cl['width']
            out_thick = cl['thick']
            fac = np.where(cl['is_rectangular'], 1, cfg.TWO_THIRDS)

            # Last thicknesses can be noisy sometimes: interpolate
            out_thick[-4:] = np.NaN
            out_thick = utils.interp_nans(np.append(out_thick, 0))[:-1]
            assert len(out_thick) == len(fac)

            # final volume
            volume = fac * out_thick * w * cl['dx']

            # conserve it
            new_vol = np.nansum(volume)
            assert new_vol != 0
            volume = init_vol / new_vol * volume
            np.testing.assert_allclose(np.nansum(volume), init_vol)

            # recompute thickness on that base
            out_thick = volume / (fac * w * cl['dx'])

            # output
            cl['thick'] = out_thick
            cl['volume'] = volume

        gdir.write_pickle(cls, 'inversion_output', div_id=div)
Esempio n. 5
0
def invert_parabolic_bed(gdir, glen_a=cfg.A, fs=0., write=True):
    """ Compute the glacier thickness along the flowlines

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

    The thickness estimation is computed under the assumption of a parabolic
    bed section.

    It returns (vol, thick) in (m3, m).

    Parameters
    ----------
    gdir : oggm.GlacierDirectory
    glen_a : float
        glen's creep parameter A
    fs : float
        sliding parameter
    write: bool
        default behavior is to compute the thickness and write the
        results in the pickle. Set to False in order to spare time
        during calibration.
    """

    # Check input
    if fs == 0.:
        _inv_function = _inversion_simple
    else:
        _inv_function = _inversion_poly

    # Ice flow params
    fd = 2. / (cfg.N+2) * glen_a
    a3 = fs / fd

    # sometimes the width is small and the flux is big. crop this
    max_ratio = cfg.PARAMS['max_thick_to_width_ratio']
    max_shape = cfg.PARAMS['max_shape_param']
    # sigma of the smoothing window after inversion
    sec_smooth = cfg.PARAMS['section_smoothing']
    # Clip the slope, in degrees
    clip_angle = cfg.PARAMS['min_slope']

    out_volume = 0.
    for div in gdir.divide_ids:
        cls = gdir.read_pickle('inversion_input', div_id=div)
        for cl in cls:
            # Clip slope to avoid negative and small slopes
            slope = cl['slope_angle']
            slope = np.clip(slope, np.deg2rad(clip_angle), np.pi/2.)

            # Parabolic bed rock
            w = cl['width']
            a0s = -(3*cl['flux'])/(2*w*((cfg.RHO*cfg.G*slope)**3)*fd)
            if np.any(~np.isfinite(a0s)):
                raise RuntimeError('{}: something went wrong with '
                                   'inversion'.format(gdir.rgi_id))

            # GO
            out_thick = np.zeros(len(slope))
            for i, (a0, Q) in enumerate(zip(a0s, cl['flux'])):
                if Q > 0.:
                    out_thick[i] = _inv_function(a3, a0)
                else:
                    out_thick[i] = 0.

            # Check for thick to width ratio (should ne be too large)
            ratio = out_thick / w  # there's no 0 width so we're good
            pno = np.where(ratio > max_ratio)
            if len(pno[0]) > 0:
                ratio[pno] = np.NaN
                ratio = utils.interp_nans(ratio, default=max_ratio)
                out_thick = w * ratio

            # TODO: last thicknesses can be noisy sometimes: interpolate?
            if cl['is_last']:
                out_thick[-4:-1] = np.NaN
                out_thick = utils.interp_nans(out_thick)

            # Check for the shape parameter (should not be too large)
            out_shape = (4*out_thick)/(w**2)
            pno = np.where(out_shape > max_shape)
            if len(pno[0]) > 0:
                out_shape[pno] = np.NaN
                out_shape = utils.interp_nans(out_shape, default=max_shape)
                out_thick = (out_shape * w**2) / 4

            # smooth section
            if sec_smooth != 0.:
                section = cfg.TWO_THIRDS * w * out_thick * cl['dx']
                section = gaussian_filter1d(section, sec_smooth)
                out_thick = section / (w * cfg.TWO_THIRDS * cl['dx'])

            # volume
            volume = cfg.TWO_THIRDS * out_thick * w * cl['dx']

            if write:
                cl['thick'] = out_thick
                cl['volume'] = volume
            out_volume += np.nansum(volume)
        if write:
            gdir.write_pickle(cls, 'inversion_output', div_id=div)

    return out_volume, gdir.rgi_area_km2 * 1e6
Esempio n. 6
0
def catchment_width_correction(gdir, div_id=None):
    """Corrects for NaNs and inconsistencies in the geometrical widths.

    Interpolates missing values, ensures consistency of the
    surface-area distribution AND with the geometrical area of the glacier
    polygon, avoiding errors due to gridded representation.

    Updates the 'inversion_flowlines' save file.

    Parameters
    ----------
    gdir : oggm.GlacierDirectory
    """

    # The code below makes of this task a "special" divide task.
    # We keep it as is and remove the divide task decorator
    if div_id is None:
        # This is the original call
        # This time instead of just looping over the divides we add a test
        # to check for the conservation of the shapefile's area.
        area = 0.
        divides = []
        for i in gdir.divide_ids:
            log.info('%s: width correction, divide %d', gdir.rgi_id, i)
            fls = catchment_width_correction(gdir, div_id=i, reset=True)
            for fl in fls:
                area += np.sum(fl.widths) * fl.dx
            divides.append(fls)

        # Final correction - because of the raster, the gridded area of the
        # glacier is not that of the actual geometry. correct for that
        fac = gdir.rgi_area_km2 / (area * gdir.grid.dx**2 * 10**-6)
        log.debug('%s: corrected widths with a factor %.2f', gdir.rgi_id, fac)
        for i in gdir.divide_ids:
            fls = divides[i-1]
            for fl in fls:
                fl.widths *= fac
            # Overwrite centerlines
            gdir.write_pickle(fls, 'inversion_flowlines', div_id=i)
        return None

    # variables
    flowlines = gdir.read_pickle('inversion_flowlines', div_id=div_id)
    catchment_indices = gdir.read_pickle('catchment_indices', div_id=div_id)

    # Topography for altitude-area distribution
    # I take the non-smoothed topography and remove the borders
    fpath = gdir.get_filepath('gridded_data', div_id=div_id)
    with netCDF4.Dataset(fpath) as nc:
        topo = nc.variables['topo'][:]
        ext = nc.variables['glacier_ext'][:]
    topo[np.where(ext==1)] = np.NaN

    # Param
    nmin = int(cfg.PARAMS['min_n_per_bin'])
    smooth_ws = int(cfg.PARAMS['smooth_widths_window_size'])

    # Per flowline (important so that later, the indices can be moved)
    catchment_heights = []
    for ci in catchment_indices:
        _t = topo[tuple(ci.T)][:]
        catchment_heights.append(list(_t[np.isfinite(_t)]))

    # Loop over lines in a reverse order
    for fl, catch_h in zip(flowlines, catchment_heights):

        # Interpolate widths
        widths = utils.interp_nans(fl.widths)
        widths = np.clip(widths, 0.1, np.max(widths))

        # Get topo per catchment and per flowline point
        fhgt = fl.surface_h

        # Sometimes, the centerline does not reach as high as each pix on the
        # glacier. (e.g. RGI40-11.00006)
        catch_h = np.clip(catch_h, 0, np.max(fhgt))

        # Max and mins for the histogram
        maxh = np.max(fhgt)
        if fl.flows_to is None:
            minh = np.min(fhgt)
            catch_h = np.clip(catch_h, minh, np.max(catch_h))
        else:
            minh = np.min(fhgt)  # Min just for flowline (this has reasons)

        # Now decide on a binsize which ensures at least N element per bin
        bsize = cfg.PARAMS['base_binsize']
        while True:
            maxb = utils.nicenumber(maxh, 1)
            minb = utils.nicenumber(minh, 1, lower=True)
            bins = np.arange(minb, maxb+bsize+0.01, bsize)
            minb = np.min(bins)

            # Ignore the topo pixels below the last bin
            tmp_ght = catch_h[np.where(catch_h >= minb)]

            topo_digi = np.digitize(tmp_ght, bins) - 1  # I prefer the left
            fl_digi = np.digitize(fhgt, bins) - 1  # I prefer the left
            if nmin == 1:
                # No need for complicated count
                _c = set(topo_digi)
                _fl = set(fl_digi)
            else:
                # Keep indexes with at least n counts
                _c = Counter(topo_digi.tolist())
                _c = set([k for (k, v) in _c.items() if v >= nmin])
                _fl = Counter(fl_digi.tolist())
                _fl = set([k for (k, v) in _fl.items() if v >= nmin])

            ref_set = set(range(len(bins)-1))
            if (_c == ref_set) and (_fl == ref_set):
                # For each bin, the width(s) have to represent the "real" area
                new_widths = widths.copy()
                for bi in range(len(bins) - 1):
                    bintopoarea = len(np.where(topo_digi == bi)[0])
                    wherewiths = np.where(fl_digi == bi)
                    binflarea = np.sum(new_widths[wherewiths]) * fl.dx
                    new_widths[wherewiths] = (bintopoarea / binflarea) * \
                                             new_widths[wherewiths]
                break
            bsize += 5

            # Add a security for infinite loops
            if bsize > 500:
                nmin -= 1
                bsize = cfg.PARAMS['base_binsize']
                log.warning('%s: reduced min n per bin to %d', gdir.rgi_id,
                            nmin)
                if nmin == 0:
                    raise RuntimeError('NO binsize could be chosen for: '
                                       '{}'.format(gdir.rgi_id))
        if bsize > 150:
            log.warning('%s: chosen binsize %d', gdir.rgi_id, bsize)
        else:
            log.debug('%s: chosen binsize %d', gdir.rgi_id, bsize)

        # Now keep the good topo pixels and send the unattributed ones to the
        # next flowline
        tosend = list(catch_h[np.where(catch_h < minb)])
        if (len(tosend) > 0) and (fl.flows_to is not None):
            ide = flowlines.index(fl.flows_to)
            catchment_heights[ide] = np.append(catchment_heights[ide], tosend)
        if (len(tosend) > 0) and (fl.flows_to is None):
            raise RuntimeError('This should not happen')

        # Now we have a width which is the "best" representation of our
        # tributary according to the altitude area distribution.
        # This sometimes leads to abrupt changes in the widths from one
        # grid point to another. I think it's not too harmful to smooth them
        # here, at the cost of a less perfect altitude area distribution
        if smooth_ws != 0:
            if smooth_ws == 1:
                new_widths = utils.smooth1d(new_widths)
            else:
                new_widths = utils.smooth1d(new_widths, window_size=smooth_ws)

        # Write it
        fl.widths = new_widths

    return flowlines
Esempio n. 7
0
def initialize_flowlines(gdir, div_id=None):
    """ Transforms the geometrical Centerlines in the more "physical"
    "Inversion Flowlines".

    This interpolates the centerlines on a regular spacing (i.e. not the
    grid's (i, j) indices. Cuts out the tail of the tributaries to make more
    realistic junctions. Also checks for low and negative slopes and corrects
    them by interpolation.

    Parameters
    ----------
    gdir : oggm.GlacierDirectory
    """

    # variables
    if div_id == 0 and not gdir.has_file('centerlines', div_id=div_id):
        # downstream lines haven't been computed
        return

    cls = gdir.read_pickle('centerlines', div_id=div_id)

    poly = gdir.read_pickle('geometries', div_id=div_id)
    poly = poly['polygon_pix'].buffer(0.5)  # a small buffer around to be sure

    # Initialise the flowlines
    dx = cfg.PARAMS['flowline_dx']
    do_filter = cfg.PARAMS['filter_min_slope']
    lid = int(cfg.PARAMS['flowline_junction_pix'])
    fls = []

    # Topo for heights
    fpath = gdir.get_filepath('gridded_data', div_id=div_id)
    with netCDF4.Dataset(fpath) as nc:
        topo = nc.variables['topo_smoothed'][:]

    # Bilinear interpolation
    # Geometries coordinates are in "pixel centered" convention, i.e
    # (0, 0) is also located in the center of the pixel
    xy = (np.arange(0, gdir.grid.ny-0.1, 1),
          np.arange(0, gdir.grid.nx-0.1, 1))
    interpolator = RegularGridInterpolator(xy, topo)

    # Smooth window
    sw = cfg.PARAMS['flowline_height_smooth']

    for ic, cl in enumerate(cls):
        points = line_interpol(cl.line, dx)

        # For tributaries, remove the tail
        if ic < (len(cls)-1):
            points = points[0:-lid]

        new_line = shpg.LineString(points)

        # Interpolate heights
        xx, yy = new_line.xy
        hgts = interpolator((yy, xx))
        assert len(hgts) >= 5

        # Check where the glacier is and where not
        isglacier = [poly.contains(shpg.Point(x, y)) for x, y in zip(xx, yy)]
        if div_id != 0:
            assert np.all(isglacier)

        # If smoothing, this is the moment
        hgts = gaussian_filter1d(hgts, sw)

        # Check for min slope issues and correct if needed
        if do_filter:
            # Correct only where glacier
            nhgts = _filter_small_slopes(hgts[isglacier], dx*gdir.grid.dx)
            isfin = np.isfinite(nhgts)
            assert np.any(isfin)
            perc_bad = np.sum(~isfin) / len(isfin)
            if perc_bad > 0.8:
                log.warning('{}: more than {:.0%} of the flowline is cropped '
                            'due to negative slopes.'.format(gdir.rgi_id,
                                                             perc_bad))

            hgts[isglacier] = nhgts
            sp = np.min(np.where(np.isfinite(nhgts))[0])
            while len(hgts[sp:]) < 5:
                sp -= 1
            hgts = utils.interp_nans(hgts[sp:])
            isglacier = isglacier[sp:]
            assert np.all(np.isfinite(hgts))
            assert len(hgts) >= 5
            new_line = shpg.LineString(points[sp:])

        l = Centerline(new_line, dx=dx, surface_h=hgts, is_glacier=isglacier)
        l.order = cl.order
        fls.append(l)

    # All objects are initialized, now we can link them.
    for cl, fl in zip(cls, fls):
        if cl.flows_to is None:
            continue
        fl.set_flows_to(fls[cls.index(cl.flows_to)])

    # Write the data
    gdir.write_pickle(fls, 'inversion_flowlines', div_id=div_id)
Esempio n. 8
0
def init_present_time_glacier(gdir):
    """First task after inversion. Merges the data from the various
    preprocessing tasks into a stand-alone dataset ready for run.

    In a first stage we assume that all divides CAN be merged as for HEF,
    so that the concept of divide is not necessary anymore.

    This task is horribly coded and needs work

    Parameters
    ----------
    gdir : oggm.GlacierDirectory
    """

    # Topo for heights
    nc = netCDF4.Dataset(gdir.get_filepath('gridded_data', div_id=0))
    topo = nc.variables['topo_smoothed'][:]
    nc.close()

    # Bilinear interpolation
    # Geometries coordinates are in "pixel centered" convention, i.e
    # (0, 0) is also located in the center of the pixel
    xy = (np.arange(0, gdir.grid.ny - 0.1,
                    1), np.arange(0, gdir.grid.nx - 0.1, 1))
    interpolator = RegularGridInterpolator(xy, topo)

    # Smooth window
    sw = cfg.PARAMS['flowline_height_smooth']

    # Map
    map_dx = gdir.grid.dx

    # OK. Dont try to solve problems you don't know about yet - i.e.
    # rethink about all this when we will have proper divides everywhere.
    # for HEF the following will work, and this is very ugly.
    major_div = gdir.read_pickle('major_divide', div_id=0)
    div_ids = list(gdir.divide_ids)
    div_ids.remove(major_div)
    div_ids = [major_div] + div_ids
    fls_list = []
    fls_per_divide = []
    inversion_per_divide = []
    for div_id in div_ids:
        fls = gdir.read_pickle('inversion_flowlines', div_id=div_id)
        fls_list.extend(fls)
        fls_per_divide.append(fls)
        invs = gdir.read_pickle('inversion_output', div_id=div_id)
        inversion_per_divide.append(invs)

    # Which kind of bed?
    if cfg.PARAMS['bed_shape'] == 'mixed':
        flobject = partial(MixedFlowline,
                           min_shape=cfg.PARAMS['mixed_min_shape'],
                           lambdas=cfg.PARAMS['trapezoid_lambdas'])
    elif cfg.PARAMS['bed_shape'] == 'parabolic':
        flobject = ParabolicFlowline
    else:
        raise NotImplementedError('bed: {}'.format(cfg.PARAMS['bed_shape']))

    max_shape = cfg.PARAMS['max_shape_param']

    # Extend the flowlines with the downstream lines, make a new object
    new_fls = []
    flows_to_ids = []
    major_id = None
    for fls, invs, did in zip(fls_per_divide, inversion_per_divide, div_ids):
        for fl, inv in zip(fls[0:-1], invs[0:-1]):
            bed_h = fl.surface_h - inv['thick']
            w = fl.widths * map_dx
            bed_shape = (4 * inv['thick']) / (w**2)
            bed_shape = bed_shape.clip(0, max_shape)
            bed_shape = np.where(inv['thick'] < 1., np.NaN, bed_shape)
            bed_shape = utils.interp_nans(bed_shape)
            nfl = flobject(fl.line, fl.dx, map_dx, fl.surface_h, bed_h,
                           bed_shape)
            flows_to_ids.append(fls_list.index(fl.flows_to))
            new_fls.append(nfl)

        # The last one is extended with the downstream
        # TODO: copy-paste code smell
        fl = fls[-1]
        inv = invs[-1]
        dline = gdir.read_pickle('downstream_line', div_id=did)
        long_line = oggm.core.preprocessing.geometry._line_extend(
            fl.line, dline, fl.dx)

        # Interpolate heights
        x, y = long_line.xy
        hgts = interpolator((y, x))

        # Inversion stuffs
        bed_h = hgts.copy()
        bed_h[0:len(fl.surface_h)] -= inv['thick']

        # Shapes
        w = fl.widths * map_dx
        bed_shape = (4 * inv['thick']) / (w**2)
        bed_shape = bed_shape.clip(0, max_shape)
        bed_shape = np.where(inv['thick'] < 1., np.NaN, bed_shape)
        bed_shape = utils.interp_nans(bed_shape)

        # But forbid too small shape close to the end
        if cfg.PARAMS['bed_shape'] == 'mixed':
            bed_shape[-4:] = bed_shape[-4:].clip(cfg.PARAMS['mixed_min_shape'])

        # Take the median of the last 30%
        ashape = np.median(
            bed_shape[-np.floor(len(bed_shape) / 3.).astype(np.int64):])

        # But forbid too small shape
        if cfg.PARAMS['bed_shape'] == 'mixed':
            ashape = ashape.clip(cfg.PARAMS['mixed_min_shape'])

        bed_shape = np.append(bed_shape,
                              np.ones(len(bed_h) - len(bed_shape)) * ashape)
        nfl = flobject(long_line, fl.dx, map_dx, hgts, bed_h, bed_shape)

        if major_id is None:
            flid = -1
            major_id = len(fls) - 1
        else:
            flid = major_id
        flows_to_ids.append(flid)
        new_fls.append(nfl)

    # Finalize the linkages
    for fl, fid in zip(new_fls, flows_to_ids):
        if fid == -1:
            continue
        fl.set_flows_to(new_fls[fid])

    # Adds the line level
    for fl in new_fls:
        fl.order = oggm.core.preprocessing.centerlines._line_order(fl)

    # And sort them per order
    fls = []
    for i in np.argsort([fl.order for fl in new_fls]):
        fls.append(new_fls[i])

    # Write the data
    gdir.write_pickle(fls, 'model_flowlines')
Esempio n. 9
0
def init_present_time_glacier(gdir):
    """First task after inversion. Merges the data from the various
    preprocessing tasks into a stand-alone dataset ready for run.

    In a first stage we assume that all divides CAN be merged as for HEF,
    so that the concept of divide is not necessary anymore.

    This task is horribly coded and needs work

    Parameters
    ----------
    gdir : oggm.GlacierDirectory
    """

    # Topo for heights
    with netCDF4.Dataset(gdir.get_filepath('gridded_data', div_id=0)) as nc:
        topo = nc.variables['topo_smoothed'][:]

    # Bilinear interpolation
    # Geometries coordinates are in "pixel centered" convention, i.e
    # (0, 0) is also located in the center of the pixel
    xy = (np.arange(0, gdir.grid.ny-0.1, 1),
          np.arange(0, gdir.grid.nx-0.1, 1))
    interpolator = RegularGridInterpolator(xy, topo)

    # Smooth window
    sw = cfg.PARAMS['flowline_height_smooth']

    # Map
    map_dx = gdir.grid.dx

    # OK. Dont try to solve problems you don't know about yet - i.e.
    # rethink about all this when we will have proper divides everywhere.
    # for HEF the following will work, and this is very ugly.
    major_div = gdir.read_pickle('major_divide', div_id=0)
    div_ids = list(gdir.divide_ids)
    div_ids.remove(major_div)
    div_ids = [major_div] + div_ids
    fls_list = []
    fls_per_divide = []
    inversion_per_divide = []
    for div_id in div_ids:
        fls = gdir.read_pickle('inversion_flowlines', div_id=div_id)
        fls_list.extend(fls)
        fls_per_divide.append(fls)
        invs = gdir.read_pickle('inversion_output', div_id=div_id)
        inversion_per_divide.append(invs)

    # Which kind of bed?
    if cfg.PARAMS['bed_shape'] == 'mixed':
        flobject = partial(MixedFlowline,
                           min_shape=cfg.PARAMS['mixed_min_shape'],
                           lambdas=cfg.PARAMS['trapezoid_lambdas'])
    elif cfg.PARAMS['bed_shape'] == 'parabolic':
        flobject = ParabolicFlowline
    else:
        raise NotImplementedError('bed: {}'.format(cfg.PARAMS['bed_shape']))

    max_shape = cfg.PARAMS['max_shape_param']

    # Extend the flowlines with the downstream lines, make a new object
    new_fls = []
    flows_to_ids = []
    major_id = None
    for fls, invs, did in zip(fls_per_divide, inversion_per_divide, div_ids):
        for fl, inv in zip(fls[0:-1], invs[0:-1]):
            bed_h = fl.surface_h - inv['thick']
            w = fl.widths * map_dx
            bed_shape = (4*inv['thick'])/(w**2)
            bed_shape = bed_shape.clip(0, max_shape)
            bed_shape = np.where(inv['thick'] < 1., np.NaN, bed_shape)
            bed_shape = utils.interp_nans(bed_shape)
            nfl = flobject(fl.line, fl.dx, map_dx, fl.surface_h,
                                    bed_h, bed_shape)
            flows_to_ids.append(fls_list.index(fl.flows_to))
            new_fls.append(nfl)

        # The last one is extended with the downstream
        # TODO: copy-paste code smell
        fl = fls[-1]
        inv = invs[-1]
        dline = gdir.read_pickle('downstream_line', div_id=did)
        long_line = oggm.core.preprocessing.geometry._line_extend(fl.line, dline, fl.dx)

        # Interpolate heights
        x, y = long_line.xy
        hgts = interpolator((y, x))

        # Inversion stuffs
        bed_h = hgts.copy()
        bed_h[0:len(fl.surface_h)] -= inv['thick']

        # Shapes
        w = fl.widths * map_dx
        bed_shape = (4*inv['thick'])/(w**2)
        bed_shape = bed_shape.clip(0, max_shape)
        bed_shape = np.where(inv['thick'] < 1., np.NaN, bed_shape)
        bed_shape = utils.interp_nans(bed_shape)

        # But forbid too small shape close to the end
        if cfg.PARAMS['bed_shape'] == 'mixed':
            bed_shape[-4:] = bed_shape[-4:].clip(cfg.PARAMS['mixed_min_shape'])

        # Take the median of the last 30%
        ashape = np.median(bed_shape[-np.floor(len(bed_shape)/3.).astype(np.int64):])

        # But forbid too small shape
        if cfg.PARAMS['bed_shape'] == 'mixed':
            ashape = ashape.clip(cfg.PARAMS['mixed_min_shape'])

        bed_shape = np.append(bed_shape, np.ones(len(bed_h)-len(bed_shape))*ashape)
        nfl = flobject(long_line, fl.dx, map_dx, hgts, bed_h, bed_shape)

        if major_id is None:
            flid = -1
            major_id = len(fls)-1
        else:
            flid = major_id
        flows_to_ids.append(flid)
        new_fls.append(nfl)

    # Finalize the linkages
    for fl, fid in zip(new_fls, flows_to_ids):
        if fid == -1:
            continue
        fl.set_flows_to(new_fls[fid])

    # Adds the line level
    for fl in new_fls:
        fl.order = oggm.core.preprocessing.centerlines._line_order(fl)

    # And sort them per order
    fls = []
    for i in np.argsort([fl.order for fl in new_fls]):
        fls.append(new_fls[i])

    # Write the data
    gdir.write_pickle(fls, 'model_flowlines')
Esempio n. 10
0
def catchment_width_correction(gdir, div_id=None):
    """Corrects for NaNs and inconsistencies in the geometrical widths.

    Interpolates missing values, ensures consistency of the
    surface-area distribution AND with the geometrical area of the glacier
    polygon, avoiding errors due to gridded representation.

    Updates the 'inversion_flowlines' save file.

    Parameters
    ----------
    gdir : oggm.GlacierDirectory
    """

    # The code below makes of this task a "special" divide task.
    # We keep it as is and remove the divide task decorator
    if div_id is None:
        # This is the original call
        # This time instead of just looping over the divides we add a test
        # to check for the conservation of the shapefile's area.
        area = 0.
        divides = []
        for i in gdir.divide_ids:
            log.info('%s: width correction, divide %d', gdir.rgi_id, i)
            fls = catchment_width_correction(gdir, div_id=i)
            for fl in fls:
                area += np.sum(fl.widths) * fl.dx
            divides.append(fls)

        # Final correction - because of the raster, the gridded area of the
        # glacier is not that of the actual geometry. correct for that
        fac = gdir.rgi_area_km2 / (area * gdir.grid.dx**2 * 10**-6)
        log.debug('%s: corrected widths with a factor %.2f', gdir.rgi_id, fac)
        for i in gdir.divide_ids:
            fls = divides[i-1]
            for fl in fls:
                fl.widths *= fac
            # Overwrite centerlines
            gdir.write_pickle(fls, 'inversion_flowlines', div_id=i)
        return None

    # variables
    flowlines = gdir.read_pickle('inversion_flowlines', div_id=div_id)
    catchment_indices = gdir.read_pickle('catchment_indices', div_id=div_id)

    # Topography for altitude-area distribution
    # I take the non-smoothed topography and remove the borders
    fpath = gdir.get_filepath('gridded_data', div_id=div_id)
    with netCDF4.Dataset(fpath) as nc:
        topo = nc.variables['topo'][:]
        ext = nc.variables['glacier_ext'][:]
    topo[np.where(ext==1)] = np.NaN

    # Param
    nmin = int(cfg.PARAMS['min_n_per_bin'])

    # Per flowline (important so that later, the indices can be moved)
    catchment_heights = []
    for ci in catchment_indices:
        _t = topo[tuple(ci.T)][:]
        catchment_heights.append(list(_t[np.isfinite(_t)]))

    # Loop over lines in a reverse order
    for fl, catch_h in zip(flowlines, catchment_heights):

        # Interpolate widths
        widths = utils.interp_nans(fl.widths)
        widths = np.clip(widths, 0.1, np.max(widths))

        # Get topo per catchment and per flowline point
        fhgt = fl.surface_h

        # Sometimes, the centerline does not reach as high as each pix on the
        # glacier. (e.g. RGI40-11.00006)
        catch_h = np.clip(catch_h, 0, np.max(fhgt))

        # Max and mins for the histogram
        maxh = np.max(fhgt)
        if fl.flows_to is None:
            minh = np.min(fhgt)
            catch_h = np.clip(catch_h, minh, np.max(catch_h))
        else:
            minh = np.min(fhgt)  # Min just for flowline (this has reasons)

        # Now decide on a binsize which ensures at least N element per bin
        bsize = cfg.PARAMS['base_binsize']
        while True:
            maxb = utils.nicenumber(maxh, 1)
            minb = utils.nicenumber(minh, 1, lower=True)
            bins = np.arange(minb, maxb+bsize+0.01, bsize)
            minb = np.min(bins)

            # Ignore the topo pixels below the last bin
            tmp_ght = catch_h[np.where(catch_h >= minb)]

            topo_digi = np.digitize(tmp_ght, bins) - 1  # I prefer the left
            fl_digi = np.digitize(fhgt, bins) - 1  # I prefer the left
            if nmin == 1:
                # No need for complicated count
                _c = set(topo_digi)
                _fl = set(fl_digi)
            else:
                # Keep indexes with at least n counts
                _c = Counter(topo_digi.tolist())
                _c = set([k for (k, v) in _c.items() if v >= nmin])
                _fl = Counter(fl_digi.tolist())
                _fl = set([k for (k, v) in _fl.items() if v >= nmin])

            ref_set = set(range(len(bins)-1))
            if (_c == ref_set) and (_fl == ref_set):
                # For each bin, the width(s) have to represent the "real" area
                new_widths = widths.copy()
                for bi in range(len(bins) - 1):
                    bintopoarea = len(np.where(topo_digi == bi)[0])
                    wherewiths = np.where(fl_digi == bi)
                    binflarea = np.sum(new_widths[wherewiths]) * fl.dx
                    new_widths[wherewiths] = (bintopoarea / binflarea) * \
                                             new_widths[wherewiths]
                break
                # # TODO: smooth them ?
                # widths = utils.smooth1d(widths)
                # if np.nanmax(_width_change_factor(new_widths)[:-5]) < 2.:
                #     break
            bsize += 5

            # Add a security for infinite loops
            if bsize > 250:
                nmin -= 1
                bsize = cfg.PARAMS['base_binsize']
                log.warning('%s: reduced min n per bin to %d', gdir.rgi_id,
                            nmin)
                if nmin == 0:
                    raise RuntimeError('NO binsize could be chosen for: '
                                       '{}'.format(gdir.rgi_id))
        if bsize > 150:
            log.warning('%s: chosen binsize %d', gdir.rgi_id, bsize)
        else:
            log.debug('%s: chosen binsize %d', gdir.rgi_id, bsize)

        # Now keep the good topo pixels and send the unattributed ones to the
        # next flowline
        tosend = list(catch_h[np.where(catch_h < minb)])
        if (len(tosend) > 0) and (fl.flows_to is not None):
            ide = flowlines.index(fl.flows_to)
            catchment_heights[ide] = np.append(catchment_heights[ide], tosend)
        if (len(tosend) > 0) and (fl.flows_to is None):
            raise RuntimeError('This should not happen')

        # Write it
        fl.widths = new_widths

    return flowlines
Esempio n. 11
0
def initialize_flowlines(gdir, div_id=None):
    """ Transforms the geometrical Centerlines in the more "physical"
    "Inversion Flowlines".

    This interpolates the centerlines on a regular spacing (i.e. not the
    grid's (i, j) indices. Cuts out the tail of the tributaries to make more
    realistic junctions. It also checks for low and negatve slopes and corrects
    them by interpolation.

    Parameters
    ----------
    gdir : oggm.GlacierDirectory
    """

    # variables
    cls = gdir.read_pickle('centerlines', div_id=div_id)

    # Initialise the flowlines
    dx = cfg.PARAMS['flowline_dx']
    ms = cfg.PARAMS['min_slope']
    do_filter = cfg.PARAMS['filter_min_slope']
    lid = int(cfg.PARAMS['flowline_junction_pix'])
    fls = []

    # Topo for heights
    fpath = gdir.get_filepath('gridded_data', div_id=div_id)
    with netCDF4.Dataset(fpath) as nc:
        topo = nc.variables['topo_smoothed'][:]

    # Bilinear interpolation
    # Geometries coordinates are in "pixel centered" convention, i.e
    # (0, 0) is also located in the center of the pixel
    xy = (np.arange(0, gdir.grid.ny-0.1, 1),
          np.arange(0, gdir.grid.nx-0.1, 1))
    interpolator = RegularGridInterpolator(xy, topo)

    # Smooth window
    sw = cfg.PARAMS['flowline_height_smooth']

    for ic, cl in enumerate(cls):
        points = _line_interpol(cl.line, dx)

        # For tributaries, remove the tail
        if ic < (len(cls)-1):
            points = points[0:-lid]

        new_line = shpg.LineString(points)

        # Interpolate heights
        x, y = new_line.xy
        hgts = interpolator((y, x))

        # If smoothing, this is the moment
        hgts = gaussian_filter1d(hgts, sw)

        # Check for min slope issues and correct if needed
        if do_filter:
            hgts = _filter_small_slopes(hgts, dx*gdir.grid.dx, min_slope=ms)
            isfin = np.isfinite(hgts)
            assert np.any(isfin)
            perc_bad = np.sum(~isfin) / len(isfin)
            if perc_bad > 0.1:
                log.warning('{}: more than {:.0%} of the flowline is cropped '
                            'due to negative slopes.'.format(gdir.rgi_id,
                                                             perc_bad))
            sp = np.min(np.where(isfin)[0])
            hgts = utils.interp_nans(hgts[sp:])
            assert np.all(np.isfinite(hgts))
            assert len(hgts) > 5
            new_line = shpg.LineString(points[sp:])

        l = Centerline(new_line, dx=dx, surface_h=hgts)
        l.order = cl.order
        fls.append(l)

    # All objects are initialized, now we can link them.
    for cl, fl in zip(cls, fls):
        if cl.flows_to is None:
            continue
        fl.set_flows_to(fls[cls.index(cl.flows_to)])

    # Write the data
    gdir.write_pickle(fls, 'inversion_flowlines', div_id=div_id)
Esempio n. 12
0
def filter_inversion_output(gdir):
    """Overwrites the inversion output with filtered one.
    
    This conserves the total volume.
    """

    # sometimes the width is small and the flux is big. crop this
    max_ratio = cfg.PARAMS['max_thick_to_width_ratio']
    max_shape = cfg.PARAMS['max_shape_param']
    # sigma of the smoothing window after inversion
    sec_smooth = cfg.PARAMS['section_smoothing']

    for div in gdir.divide_ids:
        cls = gdir.read_pickle('inversion_output', div_id=div)
        for cl in cls:
            # this filtering stuff below is not explained in Farinotti's
            # paper. I did this because it looks better, but I'm not sure
            # (yet) that this is a good idea
            fac = np.where(cl['is_rectangular'], 1, cfg.TWO_THIRDS)
            init_vol = np.sum(cl['volume'])
            if init_vol == 0:
                # this can happen
                continue
            w = cl['width']
            out_thick = cl['thick']

            # However for tidewater we have to be carefull at the tongue
            if gdir.is_tidewater and cl['is_last']:
                # store it to restore it later
                tongue_thick = out_thick[-5:]

            # Check for thick to width ratio (should ne be too large)
            ratio = out_thick / w  # there's no 0 width so we're good
            pno = np.where((~ cl['is_rectangular']) & (ratio > max_ratio))
            if len(pno[0]) > 0:
                ratio[pno] = np.NaN
                ratio = utils.interp_nans(ratio, default=max_ratio)
                out_thick[pno] = w[pno] * ratio[pno]

            # TODO: last thicknesses can be noisy sometimes: interpolate?
            if cl['is_last']:
                out_thick[-4:-1] = np.NaN
                out_thick = utils.interp_nans(out_thick)

            # Check for the shape parameter (should not be too large)
            out_shape = (4 * out_thick) / (w ** 2)
            pno = np.where((~ cl['is_rectangular']) & (out_shape > max_shape))
            if len(pno[0]) > 0:
                out_shape[pno] = np.NaN
                out_shape = utils.interp_nans(out_shape, default=max_shape)
                out_thick[pno] = (out_shape[pno] * w[pno] ** 2) / 4

            # smooth section
            if sec_smooth != 0.:
                section = out_thick * fac * w * cl['dx']
                section = gaussian_filter1d(section, sec_smooth)
                out_thick = section / (fac * w * cl['dx'])

            if gdir.is_tidewater and cl['is_last']:
                # restore the last thicknesses
                out_thick[-5:] = tongue_thick

            # final volume
            volume = fac * out_thick * w * cl['dx']

            # conserve it
            new_vol = np.nansum(volume)
            volume = init_vol / new_vol * volume
            np.testing.assert_allclose(np.nansum(volume), init_vol)

            # recompute thickness on that base
            out_thick = volume / (fac * w * cl['dx'])

            # output
            cl['thick'] = out_thick
            cl['volume'] = volume

        gdir.write_pickle(cls, 'inversion_output', div_id=div)
Esempio n. 13
0
def _parabolic_bed_from_topo(gdir, idl, interpolator):
    """this returns the parabolic bedhape for all points on idl"""

    # Volume area scaling formula for the probable ice thickness
    h_mean = 0.034 * gdir.rgi_area_km2**0.375 * 1000
    gnx, gny = gdir.grid.nx, gdir.grid.ny

    # Far Factor
    r = 40
    # number of points
    cs_n = 20

    # normals
    ns = [i[0] for i in idl.normals]
    cs = []
    donot_compute = []

    for pcoords, n, isgl in zip(idl.line.coords, ns, idl.is_glacier):
        xi, yi = pcoords
        vx, vy = n
        modul = np.sqrt(vx**2 + vy**2)
        ci = []
        _isborder = False
        for ro in np.linspace(0, r / 2.0, cs_n):
            t = ro / modul
            cp1 = HashablePoint(xi + t * vx, yi + t * vy)
            cp2 = HashablePoint(xi - t * vx, yi - t * vy)

            # check if out of the frame
            if not (0 < cp2.y < gny - 1) or \
                    not (0 < cp2.x < gnx - 1) or \
                    not (0 < cp1.y < gny - 1) or \
                    not (0 < cp1.x < gnx - 1):
                _isborder = True

            ci.append((cp1, ro))
            ci.append((cp2, -ro))

        ci = list(set(ci))
        cs.append(ci)
        donot_compute.append(_isborder or isgl)

    bed = []
    for ic, (cc, dontcomp) in enumerate(zip(cs, donot_compute)):

        if dontcomp:
            bed.append(np.NaN)
            continue

        z = []
        ro = []
        for i in cc:
            z.append(interpolator((i[0].y, i[0].x)))
            ro.append(i[1])
        aso = np.argsort(ro)
        ro, z = np.array(ro)[aso], np.array(z)[aso]

        # find top of parabola
        roHead = ro[np.argmin(z)]
        zero = np.argmin(z)  # it is index of roHead/zHead
        zHead = np.amin(z)

        dsts = abs(h_mean + zHead - z)

        # find local minima in set of distances
        extr = scipy.signal.argrelextrema(dsts, np.less, mode='wrap')
        if len(extr[0]) == 0:
            bed.append(np.NaN)
            continue

        # from local minima find that with the minimum |x|
        idx = extr[0][np.argmin(abs(ro[extr]))]

        # x<0 => x=0
        # (|x|+x)/2
        roN = ro[int((abs(zero - abs(zero - idx)) + zero - abs(zero - idx)) /
                     2):zero + abs(zero - idx) + 1]
        zN = z[int((abs(zero - abs(zero - idx)) + zero - abs(zero - idx)) /
                   2):zero + abs(zero - idx) + 1]
        roNx = roN - roHead
        # zN=zN-zHead#

        p = _approx_parabola(roNx, zN, y0=zHead)

        # shift parabola to the ds-line
        p2 = np.copy(p)
        p2[2] = z[ro == 0]

        err = _parabola_error(roN, zN, p2) * 100

        # The original implementation of @anton-ub stored all three parabola
        # params. We just keep the one important here for now
        if err < 1.5:
            bed.append(p2[0])
        else:
            bed.append(np.NaN)

    bed = np.asarray(bed)
    assert len(bed) == idl.nx
    pvalid = np.sum(np.isfinite(bed)) / len(bed) * 100
    log.debug('%s: percentage of valid parabolas total: %d', gdir.rgi_id,
              int(pvalid))

    bedg = bed[~idl.is_glacier]
    if len(bedg) > 0:
        pvalid = np.sum(np.isfinite(bedg)) / len(bedg) * 100
        log.debug('%s: percentage of valid parabolas out glacier: %d',
                  gdir.rgi_id, int(pvalid))
        if pvalid < 10:
            log.warning('{}: {}% of valid bedshapes.'.format(
                gdir.rgi_id, int(pvalid)))

    # interpolation, filling the gaps
    default = cfg.PARAMS['default_parabolic_bedshape']
    bed_int = interp_nans(bed, default=default)

    # Scale for dx (we worked in grid coords but need meters)
    bed_int = bed_int / gdir.grid.dx**2

    # Smoothing
    bed_ma = pdSeries(bed_int)
    bed_ma = bed_ma.rolling(window=5, center=True, min_periods=1).mean()
    return bed_ma.values
Esempio n. 14
0
def invert_parabolic_bed(gdir, glen_a=cfg.A, fs=0., write=True):
    """ Compute the glacier thickness along the flowlines

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

    The thickness estimation is computed under the assumption of a parabolic
    bed section.

    It returns (vol, thick) in (m3, m).

    Parameters
    ----------
    gdir : oggm.GlacierDirectory
    glen_a : float
        glen's creep parameter A
    fs : float
        sliding parameter
    write: bool
        default behavior is to compute the thickness and write the
        results in the pickle. Set to False in order to spare time
        during calibration.
    """

    # Check input
    if fs == 0.:
        _inv_function = _inversion_simple
    else:
        _inv_function = _inversion_poly

    # Ice flow params
    fd = 2. / (cfg.N + 2) * glen_a
    a3 = fs / fd

    # sometimes the width is small and the flux is big. crop this
    max_ratio = cfg.PARAMS['max_thick_to_width_ratio']
    max_shape = cfg.PARAMS['max_shape_param']
    # sigma of the smoothing window after inversion
    sec_smooth = cfg.PARAMS['section_smoothing']
    # Clip the slope, in degrees
    clip_angle = cfg.PARAMS['min_slope']

    out_volume = 0.
    for div in gdir.divide_ids:
        cls = gdir.read_pickle('inversion_input', div_id=div)
        for cl in cls:
            # Clip slope to avoid negative and small slopes
            slope = cl['slope_angle']
            slope = np.clip(slope, np.deg2rad(clip_angle), np.pi / 2.)

            # Parabolic bed rock
            w = cl['width']
            a0s = -(3 * cl['flux']) / (2 * w *
                                       ((cfg.RHO * cfg.G * slope)**3) * fd)
            if np.any(~np.isfinite(a0s)):
                raise RuntimeError('{}: something went wrong with '
                                   'inversion'.format(gdir.rgi_id))

            # GO
            out_thick = np.zeros(len(slope))
            for i, (a0, Q) in enumerate(zip(a0s, cl['flux'])):
                if Q > 0.:
                    out_thick[i] = _inv_function(a3, a0)
                else:
                    out_thick[i] = 0.

            if gdir.terminus_type == 'Land-terminating':

                # this filtering stuff below is not explained in Farinotti's
                # paper. I did this because it looks better, but I'm not sure
                # (yet) that this is a good idea

                # Check for thick to width ratio (should ne be too large)
                ratio = out_thick / w  # there's no 0 width so we're good
                pno = np.where(ratio > max_ratio)
                if len(pno[0]) > 0:
                    ratio[pno] = np.NaN
                    ratio = utils.interp_nans(ratio, default=max_ratio)
                    out_thick = w * ratio

                # TODO: last thicknesses can be noisy sometimes: interpolate?
                if cl['is_last']:
                    out_thick[-4:-1] = np.NaN
                    out_thick = utils.interp_nans(out_thick)

                # Check for the shape parameter (should not be too large)
                out_shape = (4 * out_thick) / (w**2)
                pno = np.where(out_shape > max_shape)
                if len(pno[0]) > 0:
                    out_shape[pno] = np.NaN
                    out_shape = utils.interp_nans(out_shape, default=max_shape)
                    out_thick = (out_shape * w**2) / 4

            # smooth section
            if sec_smooth != 0.:
                section = cfg.TWO_THIRDS * w * out_thick * cl['dx']
                section = gaussian_filter1d(section, sec_smooth)
                out_thick = section / (w * cfg.TWO_THIRDS * cl['dx'])

            # volume
            volume = cfg.TWO_THIRDS * out_thick * w * cl['dx']

            if write:
                cl['thick'] = out_thick
                cl['volume'] = volume
            out_volume += np.nansum(volume)
        if write:
            gdir.write_pickle(cls, 'inversion_output', div_id=div)

    return out_volume, gdir.rgi_area_km2 * 1e6
Esempio n. 15
0
def inversion_parabolic_point_slope(gdir,
                                    fs=5.7e-20,
                                    fd=1.9e-24,
                                    write=True):
    """ Compute thickness and bed topography

    Parameters
    ----------
    gdir: GlacierDir object
    fs: sliding param
    fd: deformation param
    write: default behavior is to compute the thickness and write the
           results in the pickle. Set to False in order to spare time
           during calibration.

    Returns
    -------
    glacier volume (m^3), glacier area (m^2)
    """

    # Check input
    if fs == 0.:
        _inv = _inversion_simple
    else:
        _inv = _inversion_poly

    a3 = fs / fd

    # sometimes the width is small and the flux is big. crop this too
    max_ratio = cfg.params['max_thick_to_width_ratio']
    max_shape = cfg.params['max_shape_param']
    # sigma of the smoothing window after inversion
    sec_smooth = cfg.params['section_smoothing']
    # Clip the slope, in degrees
    clip_angle = cfg.params['min_slope']

    out_volume = 0.
    for div in gdir.divide_ids:
        cls = gdir.read_pickle('inversion_input', div_id=div)
        for cl in cls:
            # Clip slope to avoid negative and small slopes
            slope = cl['slope_angle']
            slope = np.clip(slope, np.deg2rad(clip_angle), np.pi/2.)

            # Parabolic bed rock
            w = cl['width']
            a0s = -(3*cl['flux'])/(2*w*((rho*g*slope)**3)*fd)
            assert np.all(np.isfinite(a0s))

            # GO
            out_thick = np.zeros(len(slope))
            for i, (a0, Q) in enumerate(zip(a0s, cl['flux'])):
                if Q > 0.:
                    out_thick[i] = _inv(a3, a0)
                else:
                    out_thick[i] = 0.

            # Check for thick to width ratio (should ne be too large)
            ratio = out_thick / w  # there's no 0 width so we're good
            pno = np.where(ratio > max_ratio)

            # TODO: investigate this
            if gdir.rgi_id != 'RGI40-11.03002':
                if len(pno[0]) > 0:
                    ratio[pno] = np.NaN
                    ratio = utils.interp_nans(ratio)
                    out_thick = w * ratio

            # Check for the shape parameter (should ne be too large)
            out_shape = (4*out_thick)/(w**2)
            pno = np.where(out_shape > max_shape)
            if len(pno[0]) > 0 and (len(pno[0]) < (len(out_shape)/1.2)):
                out_shape[pno] = np.NaN
                out_shape = utils.interp_nans(out_shape)
                out_thick = 0.25 * out_shape * w**2

            # smooth section
            section = twothirds * w * out_thick
            section = gaussian_filter1d(section, sec_smooth)
            out_thick = section / (w * twothirds)

            # volume
            volume = twothirds * out_thick * w * cl['dx']

            if write:
                cl['thick'] = out_thick
                out_shape = (4*out_thick)/(w**2)
                out_shape = np.where(out_thick == 0., np.NaN, out_shape)
                cl['shape'] = out_shape
                cl['volume'] = volume
            out_volume += np.nansum(volume)
        if write:
            gdir.write_pickle(cls, 'inversion_output', div_id=div)

    return out_volume, gdir.glacier_area * 1e6