Differences between the “elevation band” and “centerline” flowlines#

In version 1.4, OGGM introduced a new way to compute flowlines: the so-called “elevation-band flowlines” (after Huss & Farinotti, 2012). These elevation bands complement the already available “multiple centerlines” glacier representation.

In OGGM 1.6 and above, the “elevation band” representation is the most commonly used representation for large scale simulations.

This notebook allows you to compare the two representations. It shows that the difference between the two are small for projections of glacier change, but each representation comes with pros and cons when it comes to single glacier simulations.

Tags: beginner, workflow, dynamics, flowlines

from oggm import cfg, utils, workflow, graphics, tasks
import xarray as xr
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
cfg.initialize(logging_level='WARNING')
2026-07-20 13:01:35: oggm.cfg: Reading default parameters from the OGGM `params.cfg` configuration file.
2026-07-20 13:01:35: oggm.cfg: Multiprocessing switched OFF according to the parameter file.
2026-07-20 13:01:35: oggm.cfg: Multiprocessing: using all available processors (N=4)
# Pick the glacier you want! We use Baltoro here
rgi_ids = ['RGI60-14.06794']

Get ready#

In order to open the same glacier on two different glacier directories, we apply a trick: we set a new working directory for each case! This trick is not recommended for real runs: if you have a use case for such a workflow (the same glacier with different flowline types, please get in touch with us).

# Geometrical centerline
# Where to store the data 
cfg.PATHS['working_dir'] = utils.gettempdir(dirname='OGGM-centerlines', reset=True)

# We start from prepro level 3 with all data ready - note the url here
base_url = 'https://cluster.klima.uni-bremen.de/~oggm/gdirs/oggm_v1.6/L3-L5_files/2025.6/centerlines/W5E5/per_glacier_spinup'
gdirs = workflow.init_glacier_directories(rgi_ids, from_prepro_level=3, prepro_border=80, prepro_base_url=base_url)
gdir_cl = gdirs[0]
gdir_cl
2026-07-20 13:01:36: oggm.workflow: init_glacier_directories from prepro level 3 on 1 glaciers.
2026-07-20 13:01:36: oggm.workflow: Execute entity tasks [gdir_from_prepro] on 1 glaciers
<oggm.GlacierDirectory>
  RGI id: RGI60-14.06794
  Region: 14: South Asia West
  Subregion: 14-02: Karakoram                       
  Name: Baltoro Glacier
  Glacier type: Glacier
  Terminus type: Land-terminating
  Status: Glacier or ice cap
  Area: 809.109 km2
  Lon, Lat: (76.4047, 35.7416)
  Grid (nx, ny): (481, 348)
  Grid (dx, dy): (200.0, -200.0)
# Elevation band flowline
# New working directory
cfg.PATHS['working_dir'] = utils.gettempdir(dirname='OGGM-elevbands', reset=True)

# Note the new url
base_url = 'https://cluster.klima.uni-bremen.de/~oggm/gdirs/oggm_v1.6/L3-L5_files/2025.6/elev_bands/W5E5/per_glacier_spinup'
gdirs = workflow.init_glacier_directories(rgi_ids, from_prepro_level=3, prepro_border=80, prepro_base_url=base_url)
gdir_eb = gdirs[0]
gdir_eb
2026-07-20 13:01:58: oggm.workflow: init_glacier_directories from prepro level 3 on 1 glaciers.
2026-07-20 13:01:58: oggm.workflow: Execute entity tasks [gdir_from_prepro] on 1 glaciers
<oggm.GlacierDirectory>
  RGI id: RGI60-14.06794
  Region: 14: South Asia West
  Subregion: 14-02: Karakoram                       
  Name: Baltoro Glacier
  Glacier type: Glacier
  Terminus type: Land-terminating
  Status: Glacier or ice cap
  Area: 809.109 km2
  Lon, Lat: (76.4047, 35.7416)
  Grid (nx, ny): (481, 348)
  Grid (dx, dy): (200.0, -200.0)

Some reading first#

We wrote a bit of information about the differences between these two. First, go to the glacier flowlines documentation where you can find detailed information about the two flowline types and also a guideline when to use which flowline method.

The examples below illustrate these differences, without much text for now because of lack of time:

Glacier length and cross-section#

fls_cl = gdir_cl.read_pickle('model_flowlines')
fls_eb = gdir_eb.read_pickle('model_flowlines')
f, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 14), sharex=True, sharey=True)
graphics.plot_modeloutput_section(fls_cl, ax=ax1)
ax1.set_title('Geometrical centerline')
graphics.plot_modeloutput_section(fls_eb, ax=ax2)
ax2.set_title('Elevation band flowline');
../../_images/765dd1be23cc1439c67fd44c286ada3b92e122a22c09de6252ee566b321a140a.png

Note that the elevation band flowline length is shorter than the geometrical centerline!

Projections: generally small differences in volume, but larger differences in geometry (length and area)#

Thanks to OGGM’s modular workflow, a simulation with each geometry is fairly similar in terms of code. For example, we can process the climate data for both representations with the same command:

gdirs = [gdir_cl, gdir_eb]
from oggm.shop import gcm_climate

# you can choose for example one of these 5 primary ISIMIP3b GCMs:
# 'gfdl-esm4_r1i1p1f1', 'mpi-esm1-2-hr_r1i1p1f1', 'mri-esm2-0_r1i1p1f1' ("low sensitivity" models, within typical ranges from AR6)
# 'ipsl-cm6a-lr_r1i1p1f1', 'ukesm1-0-ll_r1i1p1f2' ("hotter" models, especially ukesm1-0-ll)
member = 'mri-esm2-0_r1i1p1f1' 

for ssp in ['ssp126', 'ssp370','ssp585']:
    # bias correct them
    workflow.execute_entity_task(gcm_climate.process_monthly_isimip_data, gdirs, 
                                 ssp = ssp,
                                 # gcm member -> you can choose another one
                                 member=member,
                                 # recognize the climate file for later
                                 output_filesuffix=f'_ISIMIP3b_{member}_{ssp}'
                                 );
2026-07-20 13:02:20: oggm.workflow: Execute entity tasks [process_monthly_isimip_data] on 2 glaciers
2026-07-20 13:02:22: oggm.workflow: Execute entity tasks [process_monthly_isimip_data] on 2 glaciers
2026-07-20 13:02:24: oggm.workflow: Execute entity tasks [process_monthly_isimip_data] on 2 glaciers

For the ice dynamics simulations, the commands are exactly the same as well. The only difference is that centerlines require the more flexible “FluxBased” numerical model, while the elevation bands can also use the more robust “SemiImplicit” one. The runs are considerably faster with the elevation bands flowlines.

# add additional outputs to default OGGM
cfg.PARAMS['store_model_geometry'] = True  
cfg.PARAMS['store_fl_diagnostics'] = True

for gdir in gdirs:
    if gdir is gdir_cl:
        cfg.PARAMS['evolution_model'] = 'FluxBased'
    else:
        cfg.PARAMS['evolution_model'] = 'SemiImplicit'

    workflow.execute_entity_task(tasks.run_from_climate_data, [gdir],
                                 output_filesuffix='_historical',  
                                )

    for ssp in ['ssp126', 'ssp370', 'ssp585']:
        rid = f'_ISIMIP3b_{member}_{ssp}'

        workflow.execute_entity_task(tasks.run_from_climate_data, [gdir],
                                     climate_filename='gcm_data',  # use gcm_data, not climate_historical
                                     climate_input_filesuffix=rid,  # use the chosen scenario
                                     init_model_filesuffix='_historical',  # this is important! Start from 2020 glacier
                                     output_filesuffix=rid,  # recognize the run for later
                                    );
2026-07-20 13:02:26: oggm.cfg: PARAMS['store_model_geometry'] changed from `False` to `True`.
2026-07-20 13:02:26: oggm.cfg: PARAMS['store_fl_diagnostics'] changed from `False` to `True`.
2026-07-20 13:02:26: oggm.cfg: PARAMS['evolution_model'] changed from `SemiImplicit` to `FluxBased`.
2026-07-20 13:02:26: oggm.workflow: Execute entity tasks [run_from_climate_data] on 1 glaciers
2026-07-20 13:02:42: oggm.workflow: Execute entity tasks [run_from_climate_data] on 1 glaciers
2026-07-20 13:02:44: oggm.core.flowline: InvalidWorkflowError occurred during task run_from_climate_data_ISIMIP3b_mri-esm2-0_r1i1p1f1_ssp126 on RGI60-14.06794: You seem to have calibrated with the GSWP3_W5E5 climate data while this gdir was calibrated with _ISIMIP3b_mri-esm2-0_r1i1p1f1_ssp126_no_OGGM_bias_correction. Set `check_calib_params=False` to ignore this warning.
---------------------------------------------------------------------------
InvalidWorkflowError                      Traceback (most recent call last)
Cell In[10], line 18
     14 
     15     for ssp in ['ssp126', 'ssp370', 'ssp585']:
     16         rid = f'_ISIMIP3b_{member}_{ssp}'
     17 
---> 18         workflow.execute_entity_task(tasks.run_from_climate_data, [gdir],
     19                                      climate_filename='gcm_data',  # use gcm_data, not climate_historical
     20                                      climate_input_filesuffix=rid,  # use the chosen scenario
     21                                      init_model_filesuffix='_historical',  # this is important! Start from 2020 glacier

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/oggm/workflow.py:208, in execute_entity_task(task, gdirs, **kwargs)
    204     if ng > 3:
    205         log.workflow('WARNING: you are trying to run an entity task on '
    206                      '%d glaciers with multiprocessing turned off. OGGM '
    207                      'will run faster with multiprocessing turned on.', ng)
--> 208     out = [pc(gdir) for gdir in gdirs]
    210 return out

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/oggm/workflow.py:125, in _pickle_copier.__call__(self, arg)
    123 for func in self.call_func:
    124     func, kwargs = func
--> 125     res = self._call_internal(func, arg, kwargs)
    126 return res

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/oggm/workflow.py:119, in _pickle_copier._call_internal(self, call_func, gdir, kwargs)
    116     gdir, gdir_kwargs = gdir
    117     kwargs.update(gdir_kwargs)
--> 119 return call_func(gdir, **kwargs)

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/oggm/utils/_workflow.py:522, in entity_task.__call__.<locals>._entity_task(gdir, reset, print_log, return_value, continue_on_error, add_to_log_file, **kwargs)
    520     signal.alarm(gdir.settings['task_timeout'])
    521 ex_t = time.time()
--> 522 out = task_func(gdir, **kwargs)
    523 ex_t = time.time() - ex_t
    524 if gdir.settings['task_timeout'] > 0:

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/oggm/core/flowline.py:4480, in run_from_climate_data(gdir, settings_filesuffix, ys, ye, min_ys, max_ys, fixed_geometry_spinup_yr, store_monthly_step, store_model_geometry, store_fl_diagnostics, climate_filename, mb_model, mb_model_class, climate_input_filesuffix, output_filesuffix, init_model_filesuffix, init_model_yr, init_model_fls, zero_initial_glacier, bias, temperature_bias, precipitation_factor, mb_diagnostics_filesuffix, save_mb_diagnostics_filesuffix, **kwargs)
   4473         mb_model = MultipleFlowlineMassBalance.load_from_file(
   4474             gdir,
   4475             filesuffix=mb_diagnostics_filesuffix,
   4476             climate_filename=_branch_fn,
   4477             climate_input_filesuffix=_branch_isuf,
   4478         )
   4479     else:
-> 4480         mb_model = MultipleFlowlineMassBalance(
   4481             gdir,
   4482             mb_model_class=mb_model_class,
   4483             filename=climate_filename,
   4484             bias=bias,
   4485             input_filesuffix=climate_input_filesuffix,
   4486             settings_filesuffix=settings_filesuffix,
   4487         )
   4489 if temperature_bias is not None:
   4490     mb_model.temp_bias += temperature_bias

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/oggm/core/massbalance.py:3494, in MultipleFlowlineMassBalance.__init__(self, gdir, settings_filesuffix, fls, mb_model_class, use_inversion_flowlines, flowlines_filesuffix, input_filesuffix, **kwargs)
   3490     if rgi_filesuffix is not None:
   3491         kwargs['input_filesuffix'] = rgi_filesuffix
   3493     self.flowline_mb_models.append(
-> 3494         mb_model_class(
   3495             gdir=gdir,
   3496             settings_filesuffix=settings_filesuffix,
   3497             **kwargs,
   3498         )
   3499     )
   3501 self.valid_bounds = self.flowline_mb_models[-1].valid_bounds
   3502 self.hemisphere = gdir.hemisphere

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/oggm/core/massbalance.py:594, in MonthlyTIModel.__init__(self, gdir, filename, input_filesuffix, settings_filesuffix, fl_id, melt_f, temp_bias, prcp_fac, bias, temp_melt, ys, ye, repeat, check_calib_params, check_climate_data, use_leap_years)
    589     if src != src_calib:
    590         msg = (f'You seem to have calibrated with the {src} '
    591                f"climate data while this gdir was calibrated with "
    592                f"{src_calib}. Set `check_calib_params=False` to "
    593                f"ignore this warning.")
--> 594         raise InvalidWorkflowError(msg)
    596 self.melt_f = melt_f
    597 self.bias = bias

InvalidWorkflowError: You seem to have calibrated with the GSWP3_W5E5 climate data while this gdir was calibrated with _ISIMIP3b_mri-esm2-0_r1i1p1f1_ssp126_no_OGGM_bias_correction. Set `check_calib_params=False` to ignore this warning.
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 4))

# Pick some colors for the lines
color_dict={'ssp126':'blue', 'ssp370':'orange', 'ssp585':'red'}

for ssp in ['ssp126','ssp370', 'ssp585']:
    rid = f'_ISIMIP3b_{member}_{ssp}'
    with xr.open_dataset(gdir_cl.get_filepath('model_diagnostics', filesuffix=rid)) as ds:
        ds.volume_m3.plot(ax=ax1, label=ssp, c=color_dict[ssp])
for ssp in ['ssp126','ssp370', 'ssp585']:
    rid = f'_ISIMIP3b_{member}_{ssp}'
    with xr.open_dataset(gdir_eb.get_filepath('model_diagnostics', filesuffix=rid)) as ds:
        ds.volume_m3.plot(ax=ax1, label=ssp, c=color_dict[ssp], ls='--')
    ax1.set_title('Glacier volume')
    ax1.set_xlim([2020,2100])
    ax1.set_ylim([0, ds.volume_m3.max().max()*1.1])

for ssp in ['ssp126','ssp370', 'ssp585']:
    rid = f'_ISIMIP3b_{member}_{ssp}'
    with xr.open_dataset(gdir_cl.get_filepath('model_diagnostics', filesuffix=rid)) as ds:
        ds.length_m.plot(ax=ax2, label=ssp, c=color_dict[ssp])
    ax2.set_ylim([0, ds.length_m.max().max()*1.1])
for ssp in ['ssp126','ssp370', 'ssp585']:
    rid = f'_ISIMIP3b_{member}_{ssp}'
    with xr.open_dataset(gdir_eb.get_filepath('model_diagnostics', filesuffix=rid)) as ds:
        ds.length_m.plot(ax=ax2, label=ssp, c=color_dict[ssp], ls='--');
    ax2.set_title('Glacier length')
    ax2.set_xlim([2020,2100])
    
plt.legend();   
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/file_manager.py:219, in CachingFileManager._acquire_with_cache_info(self, needs_lock)
    218 try:
--> 219     file = self._cache[self._key]
    220 except KeyError:

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/lru_cache.py:56, in LRUCache.__getitem__(self, key)
     55 with self._lock:
---> 56     value = self._cache[key]
     57     self._cache.move_to_end(key)

KeyError: [<class 'netCDF4._netCDF4.Dataset'>, ('/tmp/OGGM/OGGM-centerlines/per_glacier/RGI60-14/RGI60-14.06/RGI60-14.06794/model_diagnostics_ISIMIP3b_mri-esm2-0_r1i1p1f1_ssp126.nc',), 'r', (('clobber', True), ('diskless', False), ('format', 'NETCDF4'), ('persist', False)), '26ab5223-5edc-43a3-bcd0-08aaf345c4dc']

During handling of the above exception, another exception occurred:

FileNotFoundError                         Traceback (most recent call last)
Cell In[11], line 8
      4 color_dict={'ssp126':'blue', 'ssp370':'orange', 'ssp585':'red'}
      5 
      6 for ssp in ['ssp126','ssp370', 'ssp585']:
      7     rid = f'_ISIMIP3b_{member}_{ssp}'
----> 8     with xr.open_dataset(gdir_cl.get_filepath('model_diagnostics', filesuffix=rid)) as ds:
      9         ds.volume_m3.plot(ax=ax1, label=ssp, c=color_dict[ssp])
     10 for ssp in ['ssp126','ssp370', 'ssp585']:
     11     rid = f'_ISIMIP3b_{member}_{ssp}'

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/api.py:607, in open_dataset(filename_or_obj, engine, chunks, cache, decode_cf, mask_and_scale, decode_times, decode_timedelta, use_cftime, concat_characters, decode_coords, drop_variables, create_default_indexes, inline_array, chunked_array_type, from_array_kwargs, backend_kwargs, **kwargs)
    595 decoders = _resolve_decoders_kwargs(
    596     decode_cf,
    597     open_backend_dataset_parameters=backend.open_dataset_parameters,
   (...)    603     decode_coords=decode_coords,
    604 )
    606 overwrite_encoded_chunks = kwargs.pop("overwrite_encoded_chunks", None)
--> 607 backend_ds = backend.open_dataset(
    608     filename_or_obj,
    609     drop_variables=drop_variables,
    610     **decoders,
    611     **kwargs,
    612 )
    613 ds = _dataset_from_backend_dataset(
    614     backend_ds,
    615     filename_or_obj,
   (...)    626     **kwargs,
    627 )
    628 return ds

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:771, in NetCDF4BackendEntrypoint.open_dataset(self, filename_or_obj, mask_and_scale, decode_times, concat_characters, decode_coords, drop_variables, use_cftime, decode_timedelta, group, mode, format, clobber, diskless, persist, auto_complex, lock, autoclose)
    749 def open_dataset(
    750     self,
    751     filename_or_obj: T_PathFileOrDataStore,
   (...)    768     autoclose=False,
    769 ) -> Dataset:
    770     filename_or_obj = _normalize_path(filename_or_obj)
--> 771     store = NetCDF4DataStore.open(
    772         filename_or_obj,
    773         mode=mode,
    774         format=format,
    775         group=group,
    776         clobber=clobber,
    777         diskless=diskless,
    778         persist=persist,
    779         auto_complex=auto_complex,
    780         lock=lock,
    781         autoclose=autoclose,
    782     )
    784     store_entrypoint = StoreBackendEntrypoint()
    785     with close_on_error(store):

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:529, in NetCDF4DataStore.open(cls, filename, mode, format, group, clobber, diskless, persist, auto_complex, lock, lock_maker, autoclose)
    525 else:
    526     manager = CachingFileManager(
    527         netCDF4.Dataset, filename, mode=mode, kwargs=kwargs, lock=lock
    528     )
--> 529 return cls(manager, group=group, mode=mode, lock=lock, autoclose=autoclose)

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:429, in NetCDF4DataStore.__init__(self, manager, group, mode, lock, autoclose)
    427 self._group = group
    428 self._mode = mode
--> 429 self.format = self.ds.data_model
    430 self._filename = self.ds.filepath()
    431 self.is_remote = is_remote_uri(self._filename)

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:538, in NetCDF4DataStore.ds(self)
    536 @property
    537 def ds(self):
--> 538     return self._acquire()

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:532, in NetCDF4DataStore._acquire(self, needs_lock)
    531 def _acquire(self, needs_lock=True):
--> 532     with self._manager.acquire_context(needs_lock) as root:
    533         ds = _nc4_require_group(root, self._group, self._mode)
    534     return ds

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/contextlib.py:141, in _GeneratorContextManager.__enter__(self)
    139 del self.args, self.kwds, self.func
    140 try:
--> 141     return next(self.gen)
    142 except StopIteration:
    143     raise RuntimeError("generator didn't yield") from None

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/file_manager.py:207, in CachingFileManager.acquire_context(self, needs_lock)
    204 @contextmanager
    205 def acquire_context(self, needs_lock: bool = True) -> Iterator[T_File]:
    206     """Context manager for acquiring a file."""
--> 207     file, cached = self._acquire_with_cache_info(needs_lock)
    208     try:
    209         yield file

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/file_manager.py:225, in CachingFileManager._acquire_with_cache_info(self, needs_lock)
    223     kwargs = kwargs.copy()
    224     kwargs["mode"] = self._mode
--> 225 file = self._opener(*self._args, **kwargs)
    226 if self._mode == "w":
    227     # ensure file doesn't get overridden when opened again
    228     self._mode = "a"

File src/netCDF4/_netCDF4.pyx:2521, in netCDF4._netCDF4.Dataset.__init__()
-> 2521 'Could not get source, probably due dynamically evaluated source code.'

File src/netCDF4/_netCDF4.pyx:2158, in netCDF4._netCDF4._ensure_nc_success()
-> 2158 'Could not get source, probably due dynamically evaluated source code.'

FileNotFoundError: [Errno 2] No such file or directory: '/tmp/OGGM/OGGM-centerlines/per_glacier/RGI60-14/RGI60-14.06/RGI60-14.06794/model_diagnostics_ISIMIP3b_mri-esm2-0_r1i1p1f1_ssp126.nc'
../../_images/4492345bdf5ff18bcc032b6bf957365944d311fe6b5035b3e3558cc2d46f703c.png

As you can see, for this disappearing glacier, the representations create slightly different volume projections. The differences can be quite a bit larger at times, for example for length projections.

Graphical representation: centerlines win by short margin (for now)#

rid = f'_ISIMIP3b_{member}_ssp126'

Both models can be represented with a cross-section, like this:

sel_years = np.linspace(2020, 2100, 17).astype(int)
colors = sns.color_palette('rocket', len(sel_years))
with plt.rc_context({'axes.prop_cycle': plt.cycler(color=colors)}):
    f, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5.5), sharey=True, sharex=True)
    n_lines = len(gdir_cl.read_pickle('model_flowlines'))
    with xr.open_dataset(gdir_cl.get_filepath('fl_diagnostics', filesuffix=rid), group=f'fl_{n_lines-1}') as ds:
        (ds.bed_h + ds.sel(time=sel_years).thickness_m).plot(ax=ax1, hue='time')
        ds.bed_h.plot(ax=ax1, c='k')
        ax1.set_title('Centerlines')
    with xr.open_dataset(gdir_eb.get_filepath('fl_diagnostics', filesuffix=rid), group='fl_0') as ds:
        (ds.bed_h + ds.sel(time=sel_years).thickness_m).plot(ax=ax2, hue='time')
        ds.bed_h.plot(ax=ax2, c='k')
        ax2.set_ylabel('')
        ax2.set_title('Elevation bands')
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/file_manager.py:219, in CachingFileManager._acquire_with_cache_info(self, needs_lock)
    218 try:
--> 219     file = self._cache[self._key]
    220 except KeyError:

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/lru_cache.py:56, in LRUCache.__getitem__(self, key)
     55 with self._lock:
---> 56     value = self._cache[key]
     57     self._cache.move_to_end(key)

KeyError: [<class 'netCDF4._netCDF4.Dataset'>, ('/tmp/OGGM/OGGM-centerlines/per_glacier/RGI60-14/RGI60-14.06/RGI60-14.06794/fl_diagnostics_ISIMIP3b_mri-esm2-0_r1i1p1f1_ssp126.nc',), 'r', (('clobber', True), ('diskless', False), ('format', 'NETCDF4'), ('persist', False)), 'fa103ff4-2af2-4771-b275-b054e3d0fb38']

During handling of the above exception, another exception occurred:

FileNotFoundError                         Traceback (most recent call last)
Cell In[13], line 6
      2 colors = sns.color_palette('rocket', len(sel_years))
      3 with plt.rc_context({'axes.prop_cycle': plt.cycler(color=colors)}):
      4     f, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5.5), sharey=True, sharex=True)
      5     n_lines = len(gdir_cl.read_pickle('model_flowlines'))
----> 6     with xr.open_dataset(gdir_cl.get_filepath('fl_diagnostics', filesuffix=rid), group=f'fl_{n_lines-1}') as ds:
      7         (ds.bed_h + ds.sel(time=sel_years).thickness_m).plot(ax=ax1, hue='time')
      8         ds.bed_h.plot(ax=ax1, c='k')
      9         ax1.set_title('Centerlines')

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/api.py:607, in open_dataset(filename_or_obj, engine, chunks, cache, decode_cf, mask_and_scale, decode_times, decode_timedelta, use_cftime, concat_characters, decode_coords, drop_variables, create_default_indexes, inline_array, chunked_array_type, from_array_kwargs, backend_kwargs, **kwargs)
    595 decoders = _resolve_decoders_kwargs(
    596     decode_cf,
    597     open_backend_dataset_parameters=backend.open_dataset_parameters,
   (...)    603     decode_coords=decode_coords,
    604 )
    606 overwrite_encoded_chunks = kwargs.pop("overwrite_encoded_chunks", None)
--> 607 backend_ds = backend.open_dataset(
    608     filename_or_obj,
    609     drop_variables=drop_variables,
    610     **decoders,
    611     **kwargs,
    612 )
    613 ds = _dataset_from_backend_dataset(
    614     backend_ds,
    615     filename_or_obj,
   (...)    626     **kwargs,
    627 )
    628 return ds

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:771, in NetCDF4BackendEntrypoint.open_dataset(self, filename_or_obj, mask_and_scale, decode_times, concat_characters, decode_coords, drop_variables, use_cftime, decode_timedelta, group, mode, format, clobber, diskless, persist, auto_complex, lock, autoclose)
    749 def open_dataset(
    750     self,
    751     filename_or_obj: T_PathFileOrDataStore,
   (...)    768     autoclose=False,
    769 ) -> Dataset:
    770     filename_or_obj = _normalize_path(filename_or_obj)
--> 771     store = NetCDF4DataStore.open(
    772         filename_or_obj,
    773         mode=mode,
    774         format=format,
    775         group=group,
    776         clobber=clobber,
    777         diskless=diskless,
    778         persist=persist,
    779         auto_complex=auto_complex,
    780         lock=lock,
    781         autoclose=autoclose,
    782     )
    784     store_entrypoint = StoreBackendEntrypoint()
    785     with close_on_error(store):

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:529, in NetCDF4DataStore.open(cls, filename, mode, format, group, clobber, diskless, persist, auto_complex, lock, lock_maker, autoclose)
    525 else:
    526     manager = CachingFileManager(
    527         netCDF4.Dataset, filename, mode=mode, kwargs=kwargs, lock=lock
    528     )
--> 529 return cls(manager, group=group, mode=mode, lock=lock, autoclose=autoclose)

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:429, in NetCDF4DataStore.__init__(self, manager, group, mode, lock, autoclose)
    427 self._group = group
    428 self._mode = mode
--> 429 self.format = self.ds.data_model
    430 self._filename = self.ds.filepath()
    431 self.is_remote = is_remote_uri(self._filename)

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:538, in NetCDF4DataStore.ds(self)
    536 @property
    537 def ds(self):
--> 538     return self._acquire()

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:532, in NetCDF4DataStore._acquire(self, needs_lock)
    531 def _acquire(self, needs_lock=True):
--> 532     with self._manager.acquire_context(needs_lock) as root:
    533         ds = _nc4_require_group(root, self._group, self._mode)
    534     return ds

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/contextlib.py:141, in _GeneratorContextManager.__enter__(self)
    139 del self.args, self.kwds, self.func
    140 try:
--> 141     return next(self.gen)
    142 except StopIteration:
    143     raise RuntimeError("generator didn't yield") from None

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/file_manager.py:207, in CachingFileManager.acquire_context(self, needs_lock)
    204 @contextmanager
    205 def acquire_context(self, needs_lock: bool = True) -> Iterator[T_File]:
    206     """Context manager for acquiring a file."""
--> 207     file, cached = self._acquire_with_cache_info(needs_lock)
    208     try:
    209         yield file

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/file_manager.py:225, in CachingFileManager._acquire_with_cache_info(self, needs_lock)
    223     kwargs = kwargs.copy()
    224     kwargs["mode"] = self._mode
--> 225 file = self._opener(*self._args, **kwargs)
    226 if self._mode == "w":
    227     # ensure file doesn't get overridden when opened again
    228     self._mode = "a"

File src/netCDF4/_netCDF4.pyx:2521, in netCDF4._netCDF4.Dataset.__init__()
-> 2521 'Could not get source, probably due dynamically evaluated source code.'

File src/netCDF4/_netCDF4.pyx:2158, in netCDF4._netCDF4._ensure_nc_success()
-> 2158 'Could not get source, probably due dynamically evaluated source code.'

FileNotFoundError: [Errno 2] No such file or directory: '/tmp/OGGM/OGGM-centerlines/per_glacier/RGI60-14/RGI60-14.06/RGI60-14.06794/fl_diagnostics_ISIMIP3b_mri-esm2-0_r1i1p1f1_ssp126.nc'
../../_images/9b7085f4f93d83f50e5eb688407e9c27cbaa9e018b72f352e13d55581f3346f8.png

However, only centerlines can be plotted as a map:

# this can take some time
# if you want to see more thickness differences you can use ssp585 instead of ssp126
# by uncomment the following line
# rid = f'_ISIMIP3b_{member}_ssp585'
f, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(14, 6))
# let's have the same colorbar for every subplot for better comparability
graphics.plot_modeloutput_map(gdir_cl, filesuffix=rid, modelyr=2020, ax=ax1, vmax=600) 
graphics.plot_modeloutput_map(gdir_cl, filesuffix=rid, modelyr=2050, ax=ax2, vmax=600)
graphics.plot_modeloutput_map(gdir_cl, filesuffix=rid, modelyr=2100, ax=ax3, vmax=600)
plt.tight_layout();
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/file_manager.py:219, in CachingFileManager._acquire_with_cache_info(self, needs_lock)
    218 try:
--> 219     file = self._cache[self._key]
    220 except KeyError:

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/lru_cache.py:56, in LRUCache.__getitem__(self, key)
     55 with self._lock:
---> 56     value = self._cache[key]
     57     self._cache.move_to_end(key)

KeyError: [<class 'netCDF4._netCDF4.Dataset'>, ('/tmp/OGGM/OGGM-centerlines/per_glacier/RGI60-14/RGI60-14.06/RGI60-14.06794/model_geometry_ISIMIP3b_mri-esm2-0_r1i1p1f1_ssp126.nc',), 'r', (('clobber', True), ('diskless', False), ('format', 'NETCDF4'), ('persist', False)), 'ff615fe3-776d-440a-972d-ae79290e37f6']

During handling of the above exception, another exception occurred:

FileNotFoundError                         Traceback (most recent call last)
Cell In[14], line 7
      3 # by uncomment the following line
      4 # rid = f'_ISIMIP3b_{member}_ssp585'
      5 f, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(14, 6))
      6 # let's have the same colorbar for every subplot for better comparability
----> 7 graphics.plot_modeloutput_map(gdir_cl, filesuffix=rid, modelyr=2020, ax=ax1, vmax=600)
      8 graphics.plot_modeloutput_map(gdir_cl, filesuffix=rid, modelyr=2050, ax=ax2, vmax=600)
      9 graphics.plot_modeloutput_map(gdir_cl, filesuffix=rid, modelyr=2100, ax=ax3, vmax=600)
     10 plt.tight_layout();

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/oggm/graphics.py:158, in _plot_map.<locals>.newplotfunc(gdirs, ax, smap, add_colorbar, title, title_comment, horizontal_colorbar, lonlat_contours_kwargs, cbar_ax, autosave, add_scalebar, figsize, savefig, savefig_kwargs, extend_plot_limit, **kwargs)
    156 if add_scalebar:
    157     mp.set_scale_bar()
--> 158 out = plotfunc(gdirs, ax=ax, smap=mp, **kwargs)
    160 if add_colorbar and 'cbar_label' in out:
    161     cbprim = out.get('cbar_primitive', mp)

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/oggm/graphics.py:710, in plot_modeloutput_map(gdirs, ax, smap, model, vmax, linewidth, filesuffix, modelyr, plotting_var)
    708 models = []
    709 for gdir in gdirs:
--> 710     model = FileModel(gdir.get_filepath('model_geometry',
    711                                         filesuffix=filesuffix))
    712     model.run_until(modelyr)
    713     models.append(model)

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/oggm/core/flowline.py:3109, in FileModel.__init__(self, path)
   3106 def __init__(self, path):
   3107     """ Instantiate."""
-> 3109     self.fls = glacier_from_netcdf(path)
   3111     fl_tss = []
   3112     for flid, fl in enumerate(self.fls):

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/oggm/core/flowline.py:3610, in glacier_from_netcdf(path)
   3607 def glacier_from_netcdf(path):
   3608     """Instantiates a list of flowlines from an xarray Dataset."""
-> 3610     with xr.open_dataset(path) as ds:
   3611         fls = []
   3612         for flid in ds['flowlines'].values:

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/api.py:607, in open_dataset(filename_or_obj, engine, chunks, cache, decode_cf, mask_and_scale, decode_times, decode_timedelta, use_cftime, concat_characters, decode_coords, drop_variables, create_default_indexes, inline_array, chunked_array_type, from_array_kwargs, backend_kwargs, **kwargs)
    595 decoders = _resolve_decoders_kwargs(
    596     decode_cf,
    597     open_backend_dataset_parameters=backend.open_dataset_parameters,
   (...)    603     decode_coords=decode_coords,
    604 )
    606 overwrite_encoded_chunks = kwargs.pop("overwrite_encoded_chunks", None)
--> 607 backend_ds = backend.open_dataset(
    608     filename_or_obj,
    609     drop_variables=drop_variables,
    610     **decoders,
    611     **kwargs,
    612 )
    613 ds = _dataset_from_backend_dataset(
    614     backend_ds,
    615     filename_or_obj,
   (...)    626     **kwargs,
    627 )
    628 return ds

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:771, in NetCDF4BackendEntrypoint.open_dataset(self, filename_or_obj, mask_and_scale, decode_times, concat_characters, decode_coords, drop_variables, use_cftime, decode_timedelta, group, mode, format, clobber, diskless, persist, auto_complex, lock, autoclose)
    749 def open_dataset(
    750     self,
    751     filename_or_obj: T_PathFileOrDataStore,
   (...)    768     autoclose=False,
    769 ) -> Dataset:
    770     filename_or_obj = _normalize_path(filename_or_obj)
--> 771     store = NetCDF4DataStore.open(
    772         filename_or_obj,
    773         mode=mode,
    774         format=format,
    775         group=group,
    776         clobber=clobber,
    777         diskless=diskless,
    778         persist=persist,
    779         auto_complex=auto_complex,
    780         lock=lock,
    781         autoclose=autoclose,
    782     )
    784     store_entrypoint = StoreBackendEntrypoint()
    785     with close_on_error(store):

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:529, in NetCDF4DataStore.open(cls, filename, mode, format, group, clobber, diskless, persist, auto_complex, lock, lock_maker, autoclose)
    525 else:
    526     manager = CachingFileManager(
    527         netCDF4.Dataset, filename, mode=mode, kwargs=kwargs, lock=lock
    528     )
--> 529 return cls(manager, group=group, mode=mode, lock=lock, autoclose=autoclose)

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:429, in NetCDF4DataStore.__init__(self, manager, group, mode, lock, autoclose)
    427 self._group = group
    428 self._mode = mode
--> 429 self.format = self.ds.data_model
    430 self._filename = self.ds.filepath()
    431 self.is_remote = is_remote_uri(self._filename)

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:538, in NetCDF4DataStore.ds(self)
    536 @property
    537 def ds(self):
--> 538     return self._acquire()

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/netCDF4_.py:532, in NetCDF4DataStore._acquire(self, needs_lock)
    531 def _acquire(self, needs_lock=True):
--> 532     with self._manager.acquire_context(needs_lock) as root:
    533         ds = _nc4_require_group(root, self._group, self._mode)
    534     return ds

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/contextlib.py:141, in _GeneratorContextManager.__enter__(self)
    139 del self.args, self.kwds, self.func
    140 try:
--> 141     return next(self.gen)
    142 except StopIteration:
    143     raise RuntimeError("generator didn't yield") from None

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/file_manager.py:207, in CachingFileManager.acquire_context(self, needs_lock)
    204 @contextmanager
    205 def acquire_context(self, needs_lock: bool = True) -> Iterator[T_File]:
    206     """Context manager for acquiring a file."""
--> 207     file, cached = self._acquire_with_cache_info(needs_lock)
    208     try:
    209         yield file

File /usr/local/pyenv/versions/3.13.13/lib/python3.13/site-packages/xarray/backends/file_manager.py:225, in CachingFileManager._acquire_with_cache_info(self, needs_lock)
    223     kwargs = kwargs.copy()
    224     kwargs["mode"] = self._mode
--> 225 file = self._opener(*self._args, **kwargs)
    226 if self._mode == "w":
    227     # ensure file doesn't get overridden when opened again
    228     self._mode = "a"

File src/netCDF4/_netCDF4.pyx:2521, in netCDF4._netCDF4.Dataset.__init__()
-> 2521 'Could not get source, probably due dynamically evaluated source code.'

File src/netCDF4/_netCDF4.pyx:2158, in netCDF4._netCDF4._ensure_nc_success()
-> 2158 'Could not get source, probably due dynamically evaluated source code.'

FileNotFoundError: [Errno 2] No such file or directory: '/tmp/OGGM/OGGM-centerlines/per_glacier/RGI60-14/RGI60-14.06/RGI60-14.06794/model_geometry_ISIMIP3b_mri-esm2-0_r1i1p1f1_ssp126.nc'
../../_images/1b9e1e6df8c2299c2dfca669a569b926962e1cf4d0e030ca81d10f98f12fabe2.png

We are however working on a better representation of retreating glaciers for outreach. Have a look at this tutorial!

Take home messages#

  • in the absence of additional data to better calibrate the mass balance model, using multiple centerlines is considered not useful: indeed, the distributed representation offers little advantages if the mass balance is only a function of elevation.

  • elevation band flowlines are now the default of most OGGM applications. It is faster, much cheaper, and more robust to use these simplified glaciers.

  • elevation band flowlines cannot be represented on a map “out of the box”. We have however developed a tool to display the changes by redistributing them on a map: have a look at this tutorial!

  • multiple centerlines can be useful for growing glacier cases and use cases where geometry plays an important role (e.g. lakes, paleo applications).

What’s next?#