def _get_optimal_scaling_factor(ref_gdirs): """Get the precipitation scaling factor that minimizes the std dev error. """ from scipy import optimize as optimization def to_optimize(sf): abs_std = [] for gdir in ref_gdirs: # all possible mus mu_candidates(gdir, prcp_sf=sf) # list of mus compatibles with refmb mbdf = gdir.get_ref_mb_data()['ANNUAL_BALANCE'] res = t_star_from_refmb(gdir, mbdf) abs_std.append(np.mean(res['std_bias'])) return np.mean(abs_std)**2 with utils.DisableLogger(): opti = optimization.minimize(to_optimize, [1.], bounds=((0.01, 10),), tol=1) fac = opti['x'][0] log.info('Optimal prcp factor: {:.2f}'.format(fac)) return fac
def crossval_t_stars(gdirs): """Cross-validate the interpolation of tstar to each individual glacier. This is a thorough check (redoes many calculations) because it recomputes the chosen tstars at each time. You should use quick_crossval_t_stars instead. Parameters ---------- gdirs: list of oggm.GlacierDirectory objects """ log.info('Cross-validate the t* and mu* determination') full_ref_df = pd.read_csv(os.path.join(cfg.PATHS['working_dir'], 'ref_tstars.csv'), index_col=0) rgdirs = utils.get_ref_mb_glaciers(gdirs) n = len(full_ref_df) for i, rid in enumerate(full_ref_df.index): log.info('Cross-validation iteration {} of {}'.format(i + 1, n)) # the glacier to look at gdir = [g for g in rgdirs if g.rgi_id == rid][0] # the reference glaciers ref_gdirs = [g for g in rgdirs if g.rgi_id != rid] # redo the computations with utils.DisableLogger(): compute_ref_t_stars(ref_gdirs) distribute_t_stars([gdir]) # store rdf = pd.read_csv(gdir.get_filepath('local_mustar')) full_ref_df.loc[rid, 'cv_tstar'] = int(rdf['t_star'].values[0]) full_ref_df.loc[rid, 'cv_mustar'] = rdf['mu_star'].values[0] full_ref_df.loc[rid, 'cv_prcp_fac'] = rdf['prcp_fac'].values[0] full_ref_df.loc[rid, 'cv_bias'] = rdf['bias'].values[0] # write file = os.path.join(cfg.PATHS['working_dir'], 'crossval_tstars.csv') full_ref_df.to_csv(file)
def calibration(gdirs, xval, major=0): # Climate tasks if mbcfg.PARAMS['histalp']: cfg.PATHS['climate_file'] = mbcfg.PATHS['histalpfile'] execute_entity_task(tasks.process_custom_climate_data, gdirs) else: execute_entity_task(tasks.process_cru_data, gdirs) with utils.DisableLogger(): tasks.compute_ref_t_stars(gdirs) tasks.distribute_t_stars(gdirs) execute_entity_task(tasks.apparent_mb, gdirs) # do the crossvalidation xval = quick_crossval(gdirs, xval, major=major) return xval
def crossval_t_stars(gdirs): """Cross-validate the interpolation of tstar to each individual glacier. Parameters ---------- gdirs: list of oggm.GlacierDirectory objects """ log.info('Cross-validate the t* and mu* determination') full_ref_df = pd.read_csv(os.path.join(cfg.PATHS['working_dir'], 'ref_tstars.csv'), index_col=0) rgdirs = _get_ref_glaciers(gdirs) for rid in full_ref_df.index: # the glacier to look at gdir = [g for g in rgdirs if g.rgi_id == rid][0] # the reference glaciers ref_gdirs = [g for g in rgdirs if g.rgi_id != rid] # redo the computations with utils.DisableLogger(): compute_ref_t_stars(ref_gdirs) distribute_t_stars([gdir], compute_apparent_mb=True) # store rdf = pd.read_csv(gdir.get_filepath('local_mustar')) full_ref_df.loc[rid, 'cv_tstar'] = int(rdf['t_star'].values[0]) full_ref_df.loc[rid, 'cv_mustar'] = rdf['mu_star'].values[0] full_ref_df.loc[rid, 'cv_prcp_fac'] = rdf['prcp_fac'].values[0] full_ref_df.loc[rid, 'cv_bias'] = rdf['bias'].values[0] # write file = os.path.join(cfg.PATHS['working_dir'], 'crossval_tstars.csv') full_ref_df.to_csv(file)
def quick_crossval_t_stars(gdirs): """Cross-validate the interpolation of tstar to each individual glacier. This version does NOT recompute the precipitation scaling factor at each round (this quite OK to do so) Parameters ---------- gdirs: list of oggm.GlacierDirectory objects """ log.info('Cross-validate the t* and mu* determination') rgdirs = utils.get_ref_mb_glaciers(gdirs) # This might be redundant but we redo the calc here with utils.DisableLogger(): compute_ref_t_stars(rgdirs) full_ref_df = pd.read_csv(os.path.join(cfg.PATHS['working_dir'], 'ref_tstars.csv'), index_col=0) with utils.DisableLogger(): distribute_t_stars(rgdirs) n = len(full_ref_df) for i, rid in enumerate(full_ref_df.index): # log.info('Cross-validation iteration {} of {}'.format(i+1, n)) # the glacier to look at gdir = [g for g in rgdirs if g.rgi_id == rid][0] # the reference glaciers tmp_ref_df = full_ref_df.loc[full_ref_df.index != rid] # before the cross-val we can get the info about "real" mustar rdf = pd.read_csv(gdir.get_filepath('local_mustar')) full_ref_df.loc[rid, 'mustar'] = rdf['mu_star'].values[0] # redo the computations with utils.DisableLogger(): distribute_t_stars([gdir], ref_df=tmp_ref_df) # store rdf = pd.read_csv(gdir.get_filepath('local_mustar')) full_ref_df.loc[rid, 'cv_tstar'] = int(rdf['t_star'].values[0]) full_ref_df.loc[rid, 'cv_mustar'] = rdf['mu_star'].values[0] full_ref_df.loc[rid, 'cv_prcp_fac'] = rdf['prcp_fac'].values[0] full_ref_df.loc[rid, 'cv_bias'] = rdf['bias'].values[0] # Reproduce Ben's figure for i, rid in enumerate(full_ref_df.index): # the glacier to look at gdir = full_ref_df.loc[full_ref_df.index == rid] # the reference glaciers tmp_ref_df = full_ref_df.loc[full_ref_df.index != rid] # Compute the distance distances = utils.haversine(gdir.lon.values[0], gdir.lat.values[0], tmp_ref_df.lon, tmp_ref_df.lat) # Take the 10 closests aso = np.argsort(distances)[0:9] amin = tmp_ref_df.iloc[aso] distances = distances[aso]**2 interp = np.average(amin.mustar, weights=1. / distances) full_ref_df.loc[rid, 'interp_mustar'] = interp # write file = os.path.join(cfg.PATHS['working_dir'], 'crossval_tstars.csv') full_ref_df.to_csv(file)
def quick_crossval(gdirs, xval, major=0): # following climate.quick_crossval_t_stars # but minimized for performance full_ref_df = pd.read_csv(os.path.join(cfg.PATHS['working_dir'], 'ref_tstars.csv'), index_col=0) tmpdf = pd.DataFrame( [], columns=['std_oggm', 'std_ref', 'rmse', 'core', 'bias']) for i, rid in enumerate(full_ref_df.index): # the glacier to look at gdir = [g for g in gdirs if g.rgi_id == rid][0] # the reference glaciers tmp_ref_df = full_ref_df.loc[full_ref_df.index != rid] # select reference glacier directories # Only necessary if tasks.compute_ref_t_stars is uncommented below # ref_gdirs = [g for g in gdirs if g.rgi_id != rid] # before the cross-val store the info about "real" mustar rdf = pd.read_csv(gdir.get_filepath('local_mustar')) full_ref_df.loc[rid, 'mustar'] = rdf['mu_star'].values[0] # redistribute t_star with utils.DisableLogger(): # compute_ref_t_stars should be done again for # every crossvalidation step # This will/might have an influence if one of the 10 surrounding # glaciers of the current glacier has more than one t_star # If so, the currently crossvalidated glacier was probably # used to select one t_star for this surrounding glacier. # # But: compute_ref_t_stars is very time consuming. And the # influence is probably very small. Also only 40 out of the 253 # reference glaciers do have more than one possible t_star. # # tasks.compute_ref_t_stars(ref_gdirs) tasks.distribute_t_stars([gdir], ref_df=tmp_ref_df) # read crossvalidated values rdf = pd.read_csv(gdir.get_filepath('local_mustar')) # ---- # --- MASS-BALANCE MODEL heights, widths = gdir.get_inversion_flowline_hw() mb_mod = PastMassBalance(gdir, mu_star=rdf['mu_star'].values[0], bias=rdf['bias'].values[0], prcp_fac=rdf['prcp_fac'].values[0]) # Mass-blaance timeseries, observed and simulated refmb = gdir.get_ref_mb_data().copy() refmb['OGGM'] = mb_mod.get_specific_mb(heights, widths, year=refmb.index) # store single glacier results bias = refmb.OGGM.mean() - refmb.ANNUAL_BALANCE.mean() rmse = np.sqrt(np.mean(refmb.OGGM - refmb.ANNUAL_BALANCE)**2) rcor = np.corrcoef(refmb.OGGM, refmb.ANNUAL_BALANCE)[0, 1] ref_std = refmb.ANNUAL_BALANCE.std() # unclear how to treat this best if ref_std == 0: ref_std = refmb.OGGM.std() rcor = 1 tmpdf.loc[len(tmpdf.index)] = { 'std_oggm': refmb.OGGM.std(), 'std_ref': ref_std, 'bias': bias, 'rmse': rmse, 'core': rcor } if not major: # store cross validated values full_ref_df.loc[rid, 'cv_tstar'] = int(rdf['t_star'].values[0]) full_ref_df.loc[rid, 'cv_mustar'] = rdf['mu_star'].values[0] full_ref_df.loc[rid, 'cv_bias'] = rdf['bias'].values[0] full_ref_df.loc[rid, 'cv_prcp_fac'] = rdf['prcp_fac'].values[0] # and store mean values std_quot = np.mean(tmpdf.std_oggm / tmpdf.std_ref) xval.loc[len(xval.index)] = { 'prcpsf': cfg.PARAMS['prcp_scaling_factor'], 'tliq': cfg.PARAMS['temp_all_liq'], 'tmelt': cfg.PARAMS['temp_melt'], 'tgrad': cfg.PARAMS['temp_default_gradient'], 'std_quot': std_quot, 'bias': tmpdf['bias'].mean(), 'rmse': tmpdf['rmse'].mean(), 'core': tmpdf['core'].mean() } if major: return xval else: for i, rid in enumerate(full_ref_df.index): # the glacier to look at gdir = full_ref_df.loc[full_ref_df.index == rid] # the reference glaciers tmp_ref_df = full_ref_df.loc[full_ref_df.index != rid] # Compute the distance distances = utils.haversine(gdir.lon.values[0], gdir.lat.values[0], tmp_ref_df.lon, tmp_ref_df.lat) # Take the 10 closests aso = np.argsort(distances)[0:9] amin = tmp_ref_df.iloc[aso] distances = distances[aso]**2 interp = np.average(amin.mustar, weights=1. / distances) full_ref_df.loc[rid, 'interp_mustar'] = interp # write file = os.path.join(cfg.PATHS['working_dir'], 'crossval_tstars.csv') full_ref_df.to_csv(file) # alternative: do not write csv file, but store the needed values # within xval_minor_statistics return xval
def find_inversion_calving(gdir, water_level=None, fixed_water_depth=None): """Optimized search for a calving flux compatible with the bed inversion. See Recinos et al 2019 for details. Parameters ---------- water_level : float the water level. It should be zero m a.s.l, but: - sometimes the frontal elevation is unrealistically high (or low). - lake terminating glaciers - other uncertainties With this parameter, you can produce more realistic values. The default is to infer the water level from PARAMS['free_board_lake_terminating'] and PARAMS['free_board_marine_terminating'] fixed_water_depth : float fix the water depth to an observed value and let the free board vary instead. """ from oggm.core import climate from oggm.exceptions import MassBalanceCalibrationError if not gdir.is_tidewater or not cfg.PARAMS['use_kcalving_for_inversion']: # Do nothing return # Let's start from a fresh state gdir.inversion_calving_rate = 0 with utils.DisableLogger(): climate.local_t_star(gdir) climate.mu_star_calibration(gdir) prepare_for_inversion(gdir) v_ref = mass_conservation_inversion(gdir, water_level=water_level) # Store for statistics gdir.add_to_diagnostics('volume_before_calving', v_ref) # Get the relevant variables cls = gdir.read_pickle('inversion_input')[-1] slope = cls['slope_angle'][-1] width = cls['width'][-1] # Check that water level is within given bounds if water_level is None: th = cls['hgt'][-1] if gdir.is_lake_terminating: water_level = th - cfg.PARAMS['free_board_lake_terminating'] else: vmin, vmax = cfg.PARAMS['free_board_marine_terminating'] water_level = utils.clip_scalar(0, th - vmax, th - vmin) # The functions all have the same shape: they decrease, then increase # We seek the absolute minimum first def to_minimize(h): if fixed_water_depth is not None: fl = calving_flux_from_depth(gdir, thick=h, water_level=water_level, water_depth=fixed_water_depth, fixed_water_depth=True) else: fl = calving_flux_from_depth(gdir, water_level=water_level, water_depth=h) flux = fl['flux'] * 1e9 / cfg.SEC_IN_YEAR sia_thick = sia_thickness(slope, width, flux) return fl['thick'] - sia_thick abs_min = optimize.minimize(to_minimize, [1], bounds=((1e-4, 1e4), ), tol=1e-1) if not abs_min['success']: raise RuntimeError('Could not find the absolute minimum in calving ' 'flux optimization: {}'.format(abs_min)) if abs_min['fun'] > 0: # This happens, and means that this glacier simply can't calve # This is an indicator for physics not matching, often a unrealistic # slope of free-board df = gdir.read_json('local_mustar') out = calving_flux_from_depth(gdir, water_level=water_level) odf = dict() odf['calving_flux'] = 0 odf['calving_mu_star'] = df['mu_star_glacierwide'] odf['calving_law_flux'] = out['flux'] odf['calving_water_level'] = out['water_level'] odf['calving_inversion_k'] = out['inversion_calving_k'] odf['calving_front_slope'] = slope odf['calving_front_water_depth'] = out['water_depth'] odf['calving_front_free_board'] = out['free_board'] odf['calving_front_thick'] = out['thick'] odf['calving_front_width'] = out['width'] for k, v in odf.items(): gdir.add_to_diagnostics(k, v) return # OK, we now find the zero between abs min and an arbitrary high front abs_min = abs_min['x'][0] opt = optimize.brentq(to_minimize, abs_min, 1e4) # This is the thick guaranteeing OGGM Flux = Calving Law Flux # Let's see if it results in a meaningful mu_star # Give the flux to the inversion and recompute if fixed_water_depth is not None: out = calving_flux_from_depth(gdir, water_level=water_level, thick=opt, water_depth=fixed_water_depth, fixed_water_depth=True) f_calving = out['flux'] else: out = calving_flux_from_depth(gdir, water_level=water_level, water_depth=opt) f_calving = out['flux'] gdir.inversion_calving_rate = f_calving with utils.DisableLogger(): # We accept values down to zero before stopping cfg.PARAMS['min_mu_star'] = 0 cfg.PARAMS['clip_mu_star'] = False # At this step we might raise a MassBalanceCalibrationError try: climate.local_t_star(gdir) df = gdir.read_json('local_mustar') except MassBalanceCalibrationError as e: assert 'mu* out of specified bounds' in str(e) # When this happens we clip mu* to zero cfg.PARAMS['clip_mu_star'] = True climate.local_t_star(gdir) df = gdir.read_json('local_mustar') climate.mu_star_calibration(gdir) prepare_for_inversion(gdir) mass_conservation_inversion(gdir, water_level=water_level) if fixed_water_depth is not None: out = calving_flux_from_depth(gdir, water_level=water_level, water_depth=fixed_water_depth, fixed_water_depth=True) else: out = calving_flux_from_depth(gdir, water_level=water_level) fl = gdir.read_pickle('inversion_flowlines')[-1] f_calving = (fl.flux[-1] * (gdir.grid.dx**2) * 1e-9 / cfg.PARAMS['ice_density']) # Store results odf = dict() odf['calving_flux'] = f_calving odf['calving_mu_star'] = df['mu_star_glacierwide'] odf['calving_law_flux'] = out['flux'] odf['calving_water_level'] = out['water_level'] odf['calving_inversion_k'] = out['inversion_calving_k'] odf['calving_front_slope'] = slope odf['calving_front_water_depth'] = out['water_depth'] odf['calving_front_free_board'] = out['free_board'] odf['calving_front_thick'] = out['thick'] odf['calving_front_width'] = out['width'] for k, v in odf.items(): gdir.add_to_diagnostics(k, v) # Restore defaults with utils.DisableLogger(): cfg.PARAMS['min_mu_star'] = 1. cfg.PARAMS['clip_mu_star'] = False return odf