示例#1
0
    def test_specific_mb(self):
        """Compare the specific mass balance to the one computed
        using the OGGM function of the PastMassBalance model.
        """

        # run all needed prepro tasks
        gdir = self._setup_mb_test()

        # instance mb models
        vas_mbmod = vascaling.VAScalingMassBalance(gdir)
        past_mbmod = massbalance.PastMassBalance(gdir)

        # get relevant glacier surface elevation
        min_hgt, max_hgt = vascaling.get_min_max_elevation(gdir)

        # define temporal range
        ys = 1802
        ye = 2003
        years = np.arange(ys, ye + 1)

        # get flow lines
        fls = gdir.read_pickle('inversion_flowlines')

        # create empty container
        past_mb = np.empty(years.size)
        vas_mb = np.empty(years.size)
        # get specific mass balance for all years
        for i, year in enumerate(years):
            past_mb[i] = past_mbmod.get_specific_mb(fls=fls, year=year)
            vas_mb[i] = vas_mbmod.get_specific_mb(min_hgt, max_hgt, year)

        # compute and check correlation
        assert corrcoef(past_mb, vas_mb) >= 0.94

        # relative error of average spec mb
        # TODO: does this even make any sense?!
        assert np.abs(rel_err(past_mb.mean(), vas_mb.mean())) <= 0.36

        # check correlation of positive and negative mb years
        assert corrcoef(np.sign(past_mb), np.sign(vas_mb)) >= 0.72

        # compare to reference mb measurements
        mbs = gdir.get_ref_mb_data()['ANNUAL_BALANCE']
        assert corrcoef(vas_mb[np.in1d(years, mbs.index)], mbs) >= 0.79
示例#2
0
    def test_find_tstars(self):

        hef_file = get_demo_file('Hintereisferner.shp')
        rgidf = gpd.GeoDataFrame.from_file(hef_file)

        # loop because for some reason indexing wont work
        gdirs = []
        for index, entity in rgidf.iterrows():
            gdir = oggm.GlacierDirectory(entity, base_dir=self.testdir)
            gis.define_glacier_region(gdir, entity=entity)
            gis.glacier_masks(gdir)
            centerlines.compute_centerlines(gdir)
            geometry.initialize_flowlines(gdir)
            geometry.catchment_area(gdir)
            geometry.catchment_width_geom(gdir)
            geometry.catchment_width_correction(gdir)
            gdirs.append(gdir)
        climate.distribute_climate_data(gdirs)
        climate.mu_candidates(gdir, div_id=0)

        hef_file = get_demo_file('mbdata_RGI40-11.00897.csv')
        mbdf = pd.read_csv(hef_file).set_index('YEAR')
        t_stars, bias = climate.t_star_from_refmb(gdir, mbdf['ANNUAL_BALANCE'])

        y, t, p = climate.mb_yearly_climate_on_glacier(gdir, div_id=0)

        # which years to look at
        selind = np.searchsorted(y, mbdf.index)
        t = t[selind]
        p = p[selind]

        mu_yr_clim = gdir.read_pickle('mu_candidates', div_id=0)
        for t_s, rmd in zip(t_stars, bias):
            mb_per_mu = p - mu_yr_clim.loc[t_s] * t
            md = utils.md(mbdf['ANNUAL_BALANCE'], mb_per_mu)
            np.testing.assert_allclose(md, rmd)
            self.assertTrue(np.abs(md / np.mean(mbdf['ANNUAL_BALANCE'])) < 0.1)
            r = utils.corrcoef(mbdf['ANNUAL_BALANCE'], mb_per_mu)
            self.assertTrue(r > 0.8)
示例#3
0
    def test_find_tstars(self):

        hef_file = get_demo_file('Hintereisferner.shp')
        rgidf = gpd.GeoDataFrame.from_file(hef_file)

        # loop because for some reason indexing wont work
        gdirs = []
        for index, entity in rgidf.iterrows():
            gdir = cfg.GlacierDir(entity, base_dir=self.testdir)
            gis.define_glacier_region(gdir, entity)
            gis.glacier_masks(gdir)
            centerlines.compute_centerlines(gdir)
            geometry.initialize_flowlines(gdir)
            geometry.catchment_area(gdir)
            geometry.catchment_width_geom(gdir)
            geometry.catchment_width_correction(gdir)
            gdirs.append(gdir)
        climate.distribute_climate_data(gdirs)
        climate.mu_candidates(gdir, div_id=0)

        hef_file = get_demo_file('mbdata_RGI40-11.00897.csv')
        mbdf = pd.read_csv(hef_file).set_index('YEAR')
        t_stars, bias = climate.t_star_from_refmb(gdir, mbdf['ANNUAL_BALANCE'])

        y, t, p = climate.mb_yearly_climate_on_glacier(gdir, div_id=0)

        # which years to look at
        selind = np.searchsorted(y, mbdf.index)
        t = t[selind]
        p = p[selind]

        mu_yr_clim = gdir.read_pickle('mu_candidates', div_id=0)
        for t_s, rmd in zip(t_stars, bias):
            mb_per_mu = p - mu_yr_clim.loc[t_s] * t
            md = utils.md(mbdf['ANNUAL_BALANCE'], mb_per_mu)
            np.testing.assert_allclose(md, rmd)
            self.assertTrue(np.abs(md/np.mean(mbdf['ANNUAL_BALANCE'])) < 0.1)
            r = utils.corrcoef(mbdf['ANNUAL_BALANCE'], mb_per_mu)
            self.assertTrue(r > 0.8)
示例#4
0
    def test_process_isimip_data_monthly(self, gdir):
        cfg.PARAMS['hydro_month_nh'] = 1
        ssp = 'ssp126'

        process_w5e5_data(gdir,
                          temporal_resol='monthly',
                          climate_type='WFDE5_CRU')

        process_isimip_data(gdir,
                            ensemble=ensemble,
                            ssp=ssp,
                            climate_historical_filesuffix='_monthly_WFDE5_CRU')
        process_isimip_data(gdir,
                            ensemble=ensemble,
                            ssp=ssp,
                            correct=False,
                            climate_historical_filesuffix='_monthly_WFDE5_CRU')

        fh = gdir.get_filepath('climate_historical',
                               filesuffix='_monthly_WFDE5_CRU')
        fgcm = gdir.get_filepath('gcm_data',
                                 filesuffix='_monthly_ISIMIP3b_{}_{}'.format(
                                     ensemble, ssp))

        fgcm_nc = gdir.get_filepath(
            'gcm_data',
            filesuffix='_monthly_ISIMIP3b_{}_{}_no_correction'.format(
                ensemble, ssp))

        with xr.open_dataset(fh) as clim, xr.open_dataset(fgcm) as gcm, \
                xr.open_dataset(fgcm_nc) as gcm_nc:
            # Let's do some basic checks
            sclim = clim.sel(time=slice('1979', '2014'))
            # print(sclim.temp)
            sgcm = gcm.load().isel(time=((gcm['time.year'] >= 1979)
                                         & (gcm['time.year'] <= 2014)))
            #sgcm_nc = gcm_nc.load().isel(time=((gcm['time.year'] >= 1979) &
            #                                   (gcm['time.year'] <= 2014)))

            # Climate during the chosen period should be the same
            # print(sclim.temp.mean(), sgcm.temp.mean(), sgcm_nc.temp.mean())
            np.testing.assert_allclose(sclim.temp.mean(),
                                       sgcm.temp.mean(),
                                       rtol=1e-3)
            np.testing.assert_allclose(sclim.temp_std.mean(),
                                       sgcm.temp_std.mean(),
                                       rtol=1e-3)
            np.testing.assert_allclose(sclim.prcp.mean(),
                                       sgcm.prcp.mean(),
                                       rtol=1e-3)

            # Here no std dev!
            # do not look at the lapse rate gradient here, because this is set constant
            # for gcms (for clim it varies, but when using 'var_an_cycle', only the mean
            # annual lapse rate cycle is applied anyway
            _sclim = sclim.groupby('time.month').std(dim='time')
            _sgcm = sgcm.groupby('time.month').std(dim='time')
            # need higher tolerance here:
            np.testing.assert_allclose(_sclim.temp, _sgcm.temp,
                                       rtol=0.05)  # 1e-3
            np.testing.assert_allclose(_sclim.temp_std,
                                       _sgcm.temp_std,
                                       rtol=0.01)
            # not done for precipitation!

            # check the gradient
            np.testing.assert_allclose(
                sclim.gradient.groupby('time.month').mean(),
                sgcm.gradient.groupby('time.month').mean(),
                rtol=1e-5)
            np.testing.assert_allclose(
                sgcm.gradient.groupby('time.month').std(), 0, atol=1e-6)

            # And also the annual cycle
            sclim = sclim.groupby('time.month').mean(dim='time')
            sgcm = sgcm.groupby('time.month').mean(dim='time')
            np.testing.assert_allclose(sclim.temp, sgcm.temp, rtol=1e-3)
            np.testing.assert_allclose(sclim.temp_std,
                                       sgcm.temp_std,
                                       rtol=1e-3)
            np.testing.assert_allclose(sclim.prcp, sgcm.prcp, rtol=1e-3)

            # How did the annual cycle change with time?
            sgcm1 = gcm.load().isel(time=((gcm['time.year'] >= 1979)
                                          & (gcm['time.year'] <= 2018)))
            sgcm2 = gcm.isel(time=((gcm['time.year'] >= 2060)
                                   & (gcm['time.year'] <= 2100)))

            _sgcm1_std = sgcm1.groupby('time.month').mean(dim='time').std()
            _sgcm2_std = sgcm2.groupby('time.month').mean(dim='time').std()
            # the mean standard deviation over the year between the months
            # should be different for the time periods
            assert not np.allclose(_sgcm1_std.temp, _sgcm2_std.temp, rtol=1e-2)

            sgcm1 = sgcm1.groupby('time.month').mean(dim='time')
            sgcm2 = sgcm2.groupby('time.month').mean(dim='time')
            # It has warmed at least 1 degree for each scenario???
            assert sgcm1.temp.mean() < (sgcm2.temp.mean() - 1)
            # N more than 30%? (silly test)
            np.testing.assert_allclose(sgcm1.prcp, sgcm2.prcp, rtol=0.3)

            # Check that temp still correlate a lot between non corrected
            # and corrected gcm:
            n = 36 * 12 + 1
            ss1 = gcm.temp.rolling(time=n, min_periods=1, center=True).std()
            ss2 = gcm_nc.temp.rolling(time=n, min_periods=1, center=True).std()
            assert utils.corrcoef(ss1, ss2) > 0.9
示例#5
0
    def test_run_until_and_store(self):
        """Test the volume/area scaling model against the oggm.FluxBasedModel.

        Both models run the Hintereisferner over the entire HistAlp climate
        period, initialized with the 2003 RGI outline without spin up.

        The following two parameters for length, area and volume are tested:
            - correlation coefficient
            - relative RMSE, i.e. RMSE/mean(OGGM). Whereby the results from the
                VAS model are offset with the average differences to the OGGM
                results.
       """

        # read the Hintereisferner DEM
        hef_file = get_demo_file('Hintereisferner_RGI5.shp')
        entity = gpd.read_file(hef_file).iloc[0]

        # initialize the GlacierDirectory
        gdir = oggm.GlacierDirectory(entity, base_dir=self.testdir)
        # define the local grid and glacier mask
        gis.define_glacier_region(gdir, entity=entity)
        gis.glacier_masks(gdir)

        # process the given climate file
        climate.process_custom_climate_data(gdir)

        # run center line preprocessing tasks
        centerlines.compute_centerlines(gdir)
        centerlines.initialize_flowlines(gdir)
        centerlines.compute_downstream_line(gdir)
        centerlines.compute_downstream_bedshape(gdir)
        centerlines.catchment_area(gdir)
        centerlines.catchment_intersections(gdir)
        centerlines.catchment_width_geom(gdir)
        centerlines.catchment_width_correction(gdir)

        # read reference glacier mass balance data
        mbdf = gdir.get_ref_mb_data()
        # compute the reference t* for the glacier
        # given the reference of mass balance measurements
        res = climate.t_star_from_refmb(gdir, mbdf=mbdf['ANNUAL_BALANCE'])
        t_star, bias = res['t_star'], res['bias']

        # --------------------
        #  SCALING MODEL
        # --------------------

        # compute local t* and the corresponding mu*
        vascaling.local_t_star(gdir, tstar=t_star, bias=bias)

        # instance the mass balance models
        vas_mbmod = vascaling.VAScalingMassBalance(gdir)

        # get reference area
        a0 = gdir.rgi_area_m2
        # get reference year
        y0 = gdir.read_json('climate_info')['baseline_hydro_yr_0']
        # get min and max glacier surface elevation
        h0, h1 = vascaling.get_min_max_elevation(gdir)

        vas_model = vascaling.VAScalingModel(year_0=y0, area_m2_0=a0,
                                             min_hgt=h0, max_hgt=h1,
                                             mb_model=vas_mbmod)

        # let model run over entire HistAlp climate period
        vas_ds = vas_model.run_until_and_store(2003)

        # ------
        #  OGGM
        # ------

        # compute local t* and the corresponding mu*
        climate.local_t_star(gdir, tstar=t_star, bias=bias)
        climate.mu_star_calibration(gdir)

        # instance the mass balance models
        mb_mod = massbalance.PastMassBalance(gdir)

        # perform ice thickness inversion
        inversion.prepare_for_inversion(gdir)
        inversion.mass_conservation_inversion(gdir)
        inversion.filter_inversion_output(gdir)

        # initialize present time glacier
        flowline.init_present_time_glacier(gdir)

        # instance flowline model
        fls = gdir.read_pickle('model_flowlines')
        y0 = gdir.read_json('climate_info')['baseline_hydro_yr_0']
        fl_mod = flowline.FluxBasedModel(flowlines=fls, mb_model=mb_mod, y0=y0)

        # run model and store output as xarray data set
        _, oggm_ds = fl_mod.run_until_and_store(2003)

        # temporal indices must be equal
        assert (vas_ds.time == oggm_ds.time).all()

        # specify which parameters to compare and their respective correlation
        # coefficients and rmsd values
        params = ['length_m', 'area_m2', 'volume_m3']
        corr_coeffs = np.array([0.96, 0.90, 0.93])
        rmsds = np.array([0.43e3, 0.14e6, 0.03e9])

        # compare given parameters
        for param, cc, rmsd in zip(params, corr_coeffs, rmsds):
            # correlation coefficient
            assert corrcoef(oggm_ds[param].values, vas_ds[param].values) >= cc
            # root mean squared deviation
            rmsd_an = rmsd_bc(oggm_ds[param].values, vas_ds[param].values)
            assert rmsd_an <= rmsd
示例#6
0
    def test_yearly_mb_temp_prcp(self):
        """Test the routine which returns the yearly mass balance relevant
        climate parameters, i.e. positive melting temperature and solid
        precipitation. The testing target is the output of the corresponding
        OGGM routine `get_yearly_mb_climate_on_glacier(gdir)`.
        """

        # read the Hintereisferner DEM
        hef_file = get_demo_file('Hintereisferner_RGI5.shp')
        entity = gpd.read_file(hef_file).iloc[0]

        # initialize the GlacierDirectory
        gdir = oggm.GlacierDirectory(entity, base_dir=self.testdir)
        # define the local grid and glacier mask
        gis.define_glacier_region(gdir, entity=entity)
        gis.glacier_masks(gdir)
        # run centerline prepro tasks
        centerlines.compute_centerlines(gdir)
        centerlines.initialize_flowlines(gdir)
        centerlines.catchment_area(gdir)
        centerlines.catchment_intersections(gdir)
        centerlines.catchment_width_geom(gdir)
        centerlines.catchment_width_correction(gdir)
        # process the given climate file
        climate.process_custom_climate_data(gdir)

        # get yearly sums of terminus temperature and solid precipitation
        years, temp, prcp = vascaling.get_yearly_mb_temp_prcp(gdir)

        # use the OGGM methode to get the mass balance
        # relevant climate parameters
        years_oggm, temp_oggm, prcp_oggm = \
            climate.mb_yearly_climate_on_glacier(gdir)

        # the energy input at the glacier terminus must be greater than (or
        # equal to) the glacier wide average, since the air temperature drops
        # with elevation, i.e. the mean deviation must be positive, using the
        # OGGM data as reference
        assert md(temp_oggm, temp) >= 0
        # consequentially, the average mass input must be less than (or equal
        # to) the mass input integrated over the whole glacier surface, i.e.
        # the mean deviation must be negative, using the OGGM data as reference
        # TODO: does it actually?! And if so, why?! @ASK
        assert md(prcp_oggm, prcp) <= 0

        # correlation must be higher than set threshold
        assert corrcoef(temp, temp_oggm) >= 0.94
        assert corrcoef(prcp, prcp_oggm) >= 0.98

        # get terminus temperature using the OGGM routine
        fpath = gdir.get_filepath('gridded_data')
        with ncDataset(fpath) as nc:
            mask = nc.variables['glacier_mask'][:]
            topo = nc.variables['topo'][:]
        heights = np.array([np.min(topo[np.where(mask == 1)])])
        years_height, temp_height, _ = \
            climate.mb_yearly_climate_on_height(gdir, heights, flatten=False)
        temp_height = temp_height[0]
        # both time series must be equal
        np.testing.assert_array_equal(temp, temp_height)

        # get solid precipitation averaged over the glacier
        # (not weighted with widths)
        fls = gdir.read_pickle('inversion_flowlines')
        heights = np.array([])
        for fl in fls:
            heights = np.append(heights, fl.surface_h)
        years_height, _, prcp_height = \
            climate.mb_yearly_climate_on_height(gdir, heights, flatten=True)
        # correlation must be higher than set threshold
        assert corrcoef(prcp, prcp_height) >= 0.99

        # TODO: assert absolute values (or differences) of precipitation @ASK

        # test exception handling of out of bounds time/year range
        with self.assertRaises(climate.MassBalanceCalibrationError):
            # start year out of bounds
            year_range = [1500, 1980]
            _, _, _ = vascaling.get_yearly_mb_temp_prcp(gdir,
                                                        year_range=year_range)
        with self.assertRaises(climate.MassBalanceCalibrationError):
            # end year oud of bounds
            year_range = [1980, 3000]
            _, _, _ = vascaling.get_yearly_mb_temp_prcp(gdir,
                                                        year_range=year_range)
        with self.assertRaises(ValueError):
            # get not N full years
            t0 = datetime.datetime(1980, 1, 1)
            t1 = datetime.datetime(1980, 3, 1)
            time_range = [t0, t1]
            _, _, _ = vascaling.get_yearly_mb_temp_prcp(gdir,
                                                        time_range=time_range)

        # TODO: assert gradient in climate file?!

        pass
示例#7
0
文件: test_prepro.py 项目: OGGM/oggm
    def test_find_tstars_prcp_fac(self):

        hef_file = get_demo_file("Hintereisferner.shp")
        entity = gpd.GeoDataFrame.from_file(hef_file).iloc[0]

        mb_file = get_demo_file("RGI_WGMS_oetztal.csv")
        mb_file = os.path.join(os.path.dirname(mb_file), "mbdata", "mbdata_RGI40-11.00897.csv")

        cfg.PARAMS["prcp_auto_scaling_factor"] = True

        gdirs = []
        gdir = oggm.GlacierDirectory(entity, base_dir=self.testdir)
        gis.define_glacier_region(gdir, entity=entity)
        gis.glacier_masks(gdir)
        centerlines.compute_centerlines(gdir)
        geometry.initialize_flowlines(gdir)
        geometry.catchment_area(gdir)
        geometry.catchment_width_geom(gdir)
        geometry.catchment_width_correction(gdir)
        gdirs.append(gdir)
        climate.process_histalp_nonparallel(gdirs)
        climate.mu_candidates(gdir, div_id=0)

        mbdf = pd.read_csv(mb_file).set_index("YEAR")["ANNUAL_BALANCE"]
        t_stars, bias, prcp_fac = climate.t_star_from_refmb(gdir, mbdf)

        y, t, p = climate.mb_yearly_climate_on_glacier(gdir, prcp_fac, div_id=0)

        # which years to look at
        selind = np.searchsorted(y, mbdf.index)
        t = t[selind]
        p = p[selind]

        dffac = gdir.read_pickle("prcp_fac_optim").loc[prcp_fac]
        np.testing.assert_allclose(dffac["avg_bias"], np.mean(bias))
        mu_yr_clim = gdir.read_pickle("mu_candidates", div_id=0)[prcp_fac]
        std_bias = []
        for t_s, rmd in zip(t_stars, bias):
            mb_per_mu = p - mu_yr_clim.loc[t_s] * t
            md = utils.md(mbdf, mb_per_mu)
            np.testing.assert_allclose(md, rmd, rtol=1e-4)
            self.assertTrue(np.abs(md / np.mean(mbdf)) < 0.1)
            r = utils.corrcoef(mbdf, mb_per_mu)
            self.assertTrue(r > 0.8)
            std_bias.append(np.std(mb_per_mu) - np.std(mbdf))

        np.testing.assert_allclose(dffac["avg_std_bias"], np.mean(std_bias), rtol=1e-4)

        # test crop years
        cfg.PARAMS["tstar_search_window"] = [1902, 0]
        climate.mu_candidates(gdir, div_id=0)
        t_stars, bias, prcp_fac = climate.t_star_from_refmb(gdir, mbdf)
        mu_yr_clim = gdir.read_pickle("mu_candidates", div_id=0)[prcp_fac]
        y, t, p = climate.mb_yearly_climate_on_glacier(gdir, prcp_fac, div_id=0)
        selind = np.searchsorted(y, mbdf.index)
        t = t[selind]
        p = p[selind]
        for t_s, rmd in zip(t_stars, bias):
            mb_per_mu = p - mu_yr_clim.loc[t_s] * t
            md = utils.md(mbdf, mb_per_mu)
            np.testing.assert_allclose(md, rmd, rtol=1e-4)
            self.assertTrue(np.abs(md / np.mean(mbdf)) < 0.1)
            r = utils.corrcoef(mbdf, mb_per_mu)
            self.assertTrue(r > 0.8)
            self.assertTrue(t_s >= 1902)

        # test distribute
        cfg.PATHS["wgms_rgi_links"] = get_demo_file("RGI_WGMS_oetztal.csv")
        climate.compute_ref_t_stars(gdirs)
        climate.distribute_t_stars(gdirs)
        cfg.PARAMS["tstar_search_window"] = [0, 0]

        df = pd.read_csv(gdir.get_filepath("local_mustar"))
        np.testing.assert_allclose(df["t_star"], t_s)
        np.testing.assert_allclose(df["bias"], rmd)
        np.testing.assert_allclose(df["prcp_fac"], prcp_fac)

        cfg.PARAMS["prcp_auto_scaling_factor"] = False
    def plot_both(vas_df, oggm_df, ref=None, correct_bias=False,
                  title='', ylabel='', file_path='', exp=0):
        """ Plot geometric parameters of both models.
        If a `file_path` is given, the figure will be saved.

        :param vas_df: (pandas.Series) geometric glacier parameter of the VAS model
        :param oggm_df: (pandas.Series) geometric glacier parameter of the OGGM
        :param ref: (pandas.Series) measured glacier parameter, optional
        :param title: (string) figure title, optional
        :param ylabel: (string) label for y-axis, optional
        :param file_path: (string) where to store the figure, optional
        :param exp: (int) exponent for labels in scientific notation, optional
        """
        beamer = True
        if beamer:
            mpl.rc('axes', titlesize=18)
            mpl.rc('axes', labelsize=14)
            mpl.rc('xtick', labelsize=14)
            mpl.rc('ytick', labelsize=14)
            mpl.rc('legend', fontsize=10)
        # create figure and first axes
        fig = plt.figure(figsize=[6, 4])
        ax = fig.add_axes([0.15, 0.1, 0.8, 0.8])

        # define colors
        c1 = 'C0'
        c2 = 'C1'
        c3 = 'C3'
        # plot vas and OGGM parameters
        ax.plot(oggm_df.index, oggm_df.values, c=c2, label='OGGM')
        ax.plot(vas_df.index, vas_df.values, c=c1, label='VAS')
        if ref:
            # plot reference parameter if given
            ax.plot(ref.index, ref.values, c=c3, label='measurements')
        if correct_bias:
            # plot bias corrected vas
            df_ = pd.DataFrame([oggm_df, vas_df]).T
            bias = vas_df.values - df_.mean().diff().iloc[1]
            ax.plot(vas_df.index, bias, c=c1, ls='--',
                    label='VAS, bias corrected')
            # add RMSD as text
            ax.text(0.05, 0.05,
                    'RMSD: {:.1e}'.format(utils.rmsd(oggm_df, bias)),
                    transform=plt.gca().transAxes)

        # add correlation coefficient as text
        ax.text(0.05, 0.11, 'Corr. Coef.: {:.2f}'.format(
            utils.corrcoef(oggm_df, vas_df)),
                    transform=plt.gca().transAxes)

        # add title, labels, legend
        ax.set_title(title)
        ax.set_ylabel(ylabel)
        ax.legend()

        import matplotlib.ticker

        class OOMFormatter(matplotlib.ticker.ScalarFormatter):
            def __init__(self, order=0, fformat="%1.1f", offset=False, mathText=False):
                self.oom = order
                self.fformat = fformat
                matplotlib.ticker.ScalarFormatter.__init__(self, useOffset=offset, useMathText=mathText)

            def _set_orderOfMagnitude(self, nothing):
                self.orderOfMagnitude = self.oom

            def _set_format(self, vmin, vmax):
                self.format = self.fformat
                if self._useMathText:
                    self.format = '$%s$' % matplotlib.ticker._mathdefault(self.format)

        # use scientific notation with fixed exponent according
        ax.yaxis.set_major_formatter(OOMFormatter(exp, "%1.2f"))

        # store to file
        if file_path:
            plt.savefig(file_path, bbox_inches='tight',
                        format=file_path.split('.')[-1])