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
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)
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)
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
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
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
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])