Dynamic model initialization using observed thickness data#

This notebook demonstrates the translation of thickness observations into bed topography in ‘flowline-space’ and a dynamic model initialization for projections. Guidelines for adding thickness observations to your glacier directory are provided in the tutorials Ingest gridded products such as ice velocity into OGGM and OGGM-Shop and Glacier Directories in OGGM.

from oggm import utils, workflow, tasks, cfg, graphics
import pandas as pd
import matplotlib.pyplot as plt
import copy
import numpy as np
import xarray as xr
from scipy import optimize

Translate thickness observations into elevation band flowlines#

To convert thickness observations into bed topography, the initial step involves binning the data into elevation bands, as detailed in the tutorial Ingest gridded products such as ice velocity into OGGM. Fortunately, preprocessed directories are available, encompassing all data supported by the OGGM-Shop, already binned to elevation bands. This enables easy initialization of a dynamic flowline using tasks.init_present_time_glacier. Specify the data to be used with use_binned_thickness_data. Here, dynamic flowlines are defined using consensus thickness (Farinotti et al. 2019) and Millan thickness data (Millan et al. 2022).

# initialize our test glacier from the preprocessed directories including the shop data
cfg.initialize(logging_level='WARNING')
cfg.PATHS['working_dir'] = utils.gettempdir('OGGM_thk_dyn_spn', reset=True)
rgi_ids = ['RGI60-11.00897']

# preprocessed directories including shop data and the default oggm dynamic initialisation
## this one would be necessary
# https://cluster.klima.uni-bremen.de/~oggm/gdirs/oggm_v1.6/L3-L5_files/2025.6/elev_bands_w_data/W5E5/per_glacier_spinup/
# tried instead with that one -->TODO: double-check w. Patrick!!
base_url = 'https://cluster.klima.uni-bremen.de/~oggm/gdirs/oggm_v1.6/L3-L5_files/2025.6/elev_bands_w_data/W5E5/per_glacier/'
gdirs = workflow.init_glacier_directories(
    rgi_ids,  # which glaciers?
    prepro_base_url=base_url,  # where to fetch the data?
    from_prepro_level=4,  # what kind of data? 
    prepro_border=80  # how big of a map?
)

gdir = gdirs[0]
2026-07-20 13:27:46: oggm.cfg: Reading default parameters from the OGGM `params.cfg` configuration file.
2026-07-20 13:27:46: oggm.cfg: Multiprocessing switched OFF according to the parameter file.
2026-07-20 13:27:46: oggm.cfg: Multiprocessing: using all available processors (N=4)
2026-07-20 13:27:47: oggm.workflow: init_glacier_directories from prepro level 4 on 1 glaciers.
2026-07-20 13:27:47: oggm.workflow: Execute entity tasks [gdir_from_prepro] on 1 glaciers
# This cell is only needed due to a bug in oggm v1.6.1 which was fixed and can be deleted with updated preprocessed directories
bin_variables = ['consensus_ice_thickness', 'millan_ice_thickness']
workflow.execute_entity_task(tasks.elevation_band_flowline,
                             gdirs,
                             bin_variables=bin_variables)
workflow.execute_entity_task(tasks.fixed_dx_elevation_band_flowline,
                             gdirs,
                             bin_variables=bin_variables);
2026-07-20 13:28:38: oggm.workflow: Execute entity tasks [elevation_band_flowline] on 1 glaciers
2026-07-20 13:28:38: oggm.workflow: Execute entity tasks [fixed_dx_elevation_band_flowline] on 1 glaciers
# create the dynamic flowlines for consensus and millan thickness
tasks.init_present_time_glacier(gdir, filesuffix='_consensus',
                                use_binned_thickness_data='consensus_ice_thickness')
tasks.init_present_time_glacier(gdir, filesuffix='_millan',
                                use_binned_thickness_data='millan_ice_thickness')

# open the default oggm flowline and the two we just created
fls_oggm = gdir.read_pickle('model_flowlines')
fls_consensus = gdir.read_pickle('model_flowlines', filesuffix='_consensus')
fls_millan = gdir.read_pickle('model_flowlines', filesuffix='_millan')

# plot the created dynamic flowlines, including the oggm default
fig, axs = plt.subplots(2, 2, figsize=(10, 7))
fig.subplots_adjust(wspace=0.6, hspace=0.5)

graphics.plot_modeloutput_section(fls_oggm, ax=axs[0][0], title='OGGM default')
graphics.plot_modeloutput_section(fls_consensus, ax=axs[0][1], title='Consensus')
graphics.plot_modeloutput_section(fls_millan, ax=axs[1][0], title='Millan')

ax_compare = axs[1][1]
ax_compare.plot(fls_oggm[0].surface_h, label='surface height')  # surface height is the same for all flowlines
ax_compare.plot(fls_oggm[0].bed_h, label='OGGM default')
ax_compare.plot(fls_consensus[0].bed_h, label='Consensus')
ax_compare.plot(fls_millan[0].bed_h, label='Millan')
ax_compare.set_title('Direct comparison')
ax_compare.legend();
2026-07-20 13:28:38: oggm.core.flowline: TypeError occurred during task init_present_time_glacier_consensus on RGI60-11.00897: init_present_time_glacier() got an unexpected keyword argument 'filesuffix'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[4], line 2
      1 # create the dynamic flowlines for consensus and millan thickness
----> 2 tasks.init_present_time_glacier(gdir, filesuffix='_consensus',
      3                                 use_binned_thickness_data='consensus_ice_thickness')
      4 tasks.init_present_time_glacier(gdir, filesuffix='_millan',
      5                                 use_binned_thickness_data='millan_ice_thickness')

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:

TypeError: init_present_time_glacier() got an unexpected keyword argument 'filesuffix'

The resulting flowlines exhibit identical surface and area height distributions, defined by the outline and the DEM. Comparing individual bed heights, there is minimal difference between OGGM default and consensus flowline bed heights. This is attributed to OGGM’s past contribution to the consensus estimate and the use of the consensus volume estimate in OGGM inversion. In contrast, the Millan bed height differs significantly.

It’s crucial to note that these newly created flowlines maintain the total glacier volume of the observations. While OGGM default’s glacier volume is regionally calibrated, differences are expected compared to the consensus volume estimate, even though the consensus was used in the inversion (see next section).

The key question now is how to dynamically calibrate and initialize the model with these newly generated flowlines.

Dynamically calibrate and initialise today’s glacier state, using flowlines from thickness observations#

Now, we delve into the process of initializing the model using OGGM’s dynamic calibration methodology, elaborated in the tutorial Dynamic spinup and dynamic melt_f calibration for past simulations. In summary, the approach involves dynamically aligning the RGI area by identifying a suitable glacier state in the past and dynamically adjusting the melt factor of the mass balance to match observed geodetic mass balance. With the glacier bed defined by observations, there is an additional capability to dynamically match for the volume (further details below).

To commence, use the dynamic calibration function run_dynamic_melt_f_calibration in conjunction with the newly created flowlines, which can be passed to init_model_fls. Refer to the designated tutorial Dynamic spinup and dynamic melt_f calibration for past simulations for more details on the other parameters.

tasks.run_dynamic_melt_f_calibration
<function oggm.core.dynamic_spinup.run_dynamic_melt_f_calibration(gdir, settings_filesuffix='', observations_filesuffix='', overwrite_observations=False, ref_mb=None, ref_mb_err=None, ref_mb_err_scaling_factor=1, ref_mb_period=None, melt_f_min=None, melt_f_max=None, melt_f_max_step_length_minimum=0.1, maxiter=20, ignore_errors=False, output_filesuffix=None, model_flowlines_filesuffix=None, mb_model_class=None, save_mb_diagnostics_filesuffix=None, ys=None, ye=None, target_yr=None, run_function=<function dynamic_melt_f_run_with_dynamic_spinup at 0x7fa5d55df560>, kwargs_run_function=None, fallback_function=<function dynamic_melt_f_run_with_dynamic_spinup_fallback at 0x7fa5d55df600>, kwargs_fallback_function=None, init_model_filesuffix=None, init_model_yr=None, init_model_fls=None, first_guess_diagnostic_msg='dynamic spinup only')>
### run the "default" one, as our gdir is without _spinup
# todo --> double-check w. Patrick
workflow.execute_entity_task(
    tasks.run_dynamic_melt_f_calibration, gdirs,
    init_model_fls=fls_oggm,
    #err_dmdtda_scaling_factor=err_dmdtda_scaling_factor, <- we use here the default one? todo --> double-check w. Patrick
    ys=1979, ye=2020,
    melt_f_max=cfg.PARAMS['melt_f_max'],
    kwargs_run_function={'minimise_for': 'area',
                         'do_inversion': False},
    ignore_errors=True,
    kwargs_fallback_function={'minimise_for': 'area',
                              'do_inversion': False},
    output_filesuffix='_spinup_historical',
)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[6], line 5
      1 ### run the "default" one, as our gdir is without _spinup
      2 # todo --> double-check w. Patrick
      3 workflow.execute_entity_task(
      4     tasks.run_dynamic_melt_f_calibration, gdirs,
----> 5     init_model_fls=fls_oggm,
      6     #err_dmdtda_scaling_factor=err_dmdtda_scaling_factor, <- we use here the default one? todo --> double-check w. Patrick
      7     ys=1979, ye=2020,
      8     melt_f_max=cfg.PARAMS['melt_f_max'],

NameError: name 'fls_oggm' is not defined
err_dmdtda_scaling_factor = 0.2  # the default value of oggm runs to account for correlated observation errors

# Consensus flowline
workflow.execute_entity_task(
    tasks.run_dynamic_melt_f_calibration, gdirs,
    init_model_fls=fls_consensus,
    err_dmdtda_scaling_factor=err_dmdtda_scaling_factor,
    ys=1979, ye=2020,
    melt_f_max=cfg.PARAMS['melt_f_max'],
    kwargs_run_function={'minimise_for': 'area',
                         'do_inversion': False},
    ignore_errors=True,
    kwargs_fallback_function={'minimise_for': 'area',
                              'do_inversion': False},
    output_filesuffix='_spinup_historical_consensus',
)

# Millan flowlines
workflow.execute_entity_task(
    tasks.run_dynamic_melt_f_calibration, gdirs,
    init_model_fls=fls_millan,
    err_dmdtda_scaling_factor=err_dmdtda_scaling_factor,
    ys=1979, ye=2020,
    melt_f_max=cfg.PARAMS['melt_f_max'],
    kwargs_run_function={'minimise_for': 'area',
                         'do_inversion': False},
    ignore_errors=True,
    kwargs_fallback_function={'minimise_for': 'area',
                              'do_inversion': False},
    output_filesuffix='_spinup_historical_millan',
)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[7], line 6
      2 
      3 # Consensus flowline
      4 workflow.execute_entity_task(
      5     tasks.run_dynamic_melt_f_calibration, gdirs,
----> 6     init_model_fls=fls_consensus,
      7     err_dmdtda_scaling_factor=err_dmdtda_scaling_factor,
      8     ys=1979, ye=2020,
      9     melt_f_max=cfg.PARAMS['melt_f_max'],

NameError: name 'fls_consensus' is not defined
# define a plotting function to compare the dynamic model runs with the observations
def compare_model_to_observations(gdir, filesuffixes):
    
    area_ref = gdir.rgi_area_m2
    area_ref_error = area_ref * 0.01  # 1% is the default of the dynamic spinup
    
    df_dmdtda_ref = utils.get_geodetic_mb_dataframe().loc[gdir.rgi_id]
    sel = df_dmdtda_ref.loc[df_dmdtda_ref['period'] == '2000-01-01_2020-01-01'].iloc[0]
    # reference geodetic mass balance from Hugonnet 2021
    dmdtda_ref = float(sel['dmdtda'])
    # dmdtda: in meters water-equivalent per year -> we convert
    dmdtda_ref *= 1000  # kg m-2 yr-1
    # error of reference geodetic mass balance from Hugonnet 2021
    dmdtda_ref_error = float(sel['err_dmdtda'])
    dmdtda_ref_error *= 1000  # kg m-2 yr-1
    dmdtda_ref_error *= err_dmdtda_scaling_factor
    
    dmdtda_comparison = f'dmdtda reference (with reduced uncertainty): {dmdtda_ref:.1f} +/- {dmdtda_ref_error:.1f} kg m-2 yr-1'

    def get_dmdtda_modelled(volume, area_ref, yr0_ref_mb=2000, yr1_ref_mb=2020):
        return ((volume.loc[yr1_ref_mb].values -
                 volume.loc[yr0_ref_mb].values) /
                area_ref /
                (yr1_ref_mb - yr0_ref_mb) *
                cfg.PARAMS['ice_density'])
    
    
    # get the data of all model runs and plot it
    fig, axs = plt.subplots(2, 1, figsize=(10, 6))
    
    for i, filesuffix in enumerate(filesuffixes):
        if filesuffix == '':
            label = 'OGGM default'
        else:
            label = filesuffix[1:]

        fl = gdir.read_pickle('model_flowlines', filesuffix=filesuffix)[0]
        with xr.open_dataset(
            gdir.get_filepath('model_diagnostics',
                              filesuffix=f'_spinup_historical{filesuffix}')) as ds:
            ds_tmp = ds.load()
        
        # plot volume with reference
        ref_year = gdir.rgi_date + 1
        axs[0].plot(ds_tmp.time.values, ds_tmp.volume_m3.values,
                    c=f'C{i}', label=label)
        axs[0].plot(ref_year, fl.volume_m3,
                    'o', c=f'C{i}', label=f'ref volume {label}')
        

        # plot area with reference
        axs[1].plot(ds_tmp.time, ds_tmp.area_m2,
                    c=f'C{i}', label=label)
        axs[1].plot(ref_year, area_ref,
                    'o', c=f'C{i}', label='ref area')
        axs[1].plot([ref_year, ref_year],
                    [area_ref - area_ref_error,
                     area_ref + area_ref_error],
                    c=f'C{i}')
        
        # add dmdtda as text
        dmdtda_tmp = get_dmdtda_modelled(ds_tmp.volume_m3, area_ref)
        dmdtda_comparison += (f'\n{f"dmdtda {label}:":<44} {dmdtda_tmp:.1f} kg m-2 yr-1, diff. to ref.: '
                              f'{f"{dmdtda_tmp - dmdtda_ref:.1f}":>5} kg m-2 yr-1')
        
    axs[0].legend()
    axs[0].set_ylabel('Volume [m³]')
    axs[1].legend()
    axs[1].set_ylabel('Area [m²]')
    
    print(dmdtda_comparison)
compare_model_to_observations(gdir, ['', '_consensus', '_millan'])
---------------------------------------------------------------------------
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_thk_dyn_spn/per_glacier/RGI60-11/RGI60-11.00/RGI60-11.00897/model_diagnostics_spinup_historical.nc',), 'r', (('clobber', True), ('diskless', False), ('format', 'NETCDF4'), ('persist', False)), '6d868efe-f433-4876-94d2-dd37a8400496']

During handling of the above exception, another exception occurred:

FileNotFoundError                         Traceback (most recent call last)
Cell In[9], line 1
----> 1 compare_model_to_observations(gdir, ['', '_consensus', '_millan'])

Cell In[8], line 38, in compare_model_to_observations(gdir, filesuffixes)
     34         else:
     35             label = filesuffix[1:]
     36 
     37         fl = gdir.read_pickle('model_flowlines', filesuffix=filesuffix)[0]
---> 38         with xr.open_dataset(
     39             gdir.get_filepath('model_diagnostics',
     40                               filesuffix=f'_spinup_historical{filesuffix}')) as ds:
     41             ds_tmp = ds.load()

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_thk_dyn_spn/per_glacier/RGI60-11/RGI60-11.00/RGI60-11.00897/model_diagnostics_spinup_historical.nc'
../../_images/8fa6e71e660803b876a6c457d578ea996181be29f5d19d6886609ea996e6d925.png

In all three simulations, we observe that both the area and geodetic mass balance align within the target boundaries. Nevertheless, the volume is only coincidentally matched for the consensus estimate. This discrepancy arises because the current deformation parameter was calibrated during the inversion for OGGM default initialization, which incorporates an equilibrium assumption (see documentation for more information). However, when defining the glacier bed from thickness observations, it becomes possible/necessary to calibrate the deformation parameter to match the observed volume during initialization. Although there is currently no implemented function for this, the following code should provide an idea of how it can be achieved:

# create another flowline for this, for later comparison
tasks.init_present_time_glacier(gdir, filesuffix='_millan_adapted',
                                use_binned_thickness_data='millan_ice_thickness')

def to_minimize(glen_a):
    ref_volume = fls_millan[0].volume_m3
    workflow.execute_entity_task(
        tasks.run_dynamic_melt_f_calibration, gdirs,
        init_model_fls=gdir.read_pickle('model_flowlines',
                                        filesuffix='_millan_adapted'),
        err_dmdtda_scaling_factor=0.2,
        ys=1979, ye=2020,
        melt_f_max=cfg.PARAMS['melt_f_max'],
        kwargs_run_function={'minimise_for': 'area',
                             'do_inversion': False,
                             'glen_a': glen_a},
        ignore_errors=True,
        kwargs_fallback_function={'minimise_for': 'area',
                                  'do_inversion': False},
        output_filesuffix='_spinup_historical_millan_adapted',
    )
    with xr.open_dataset(gdir.get_filepath('model_diagnostics',
                                       filesuffix='_spinup_historical_millan_adapted')) as ds:
        ds_millan_adapted = ds.load()
    
    return ref_volume - ds_millan_adapted.loc[{'time': gdir.rgi_date + 1}].volume_m3.values.item()


glen_a_millan = optimize.brentq(to_minimize, 1e-22, 1e-24, xtol=1e-25)

# conduct the dynamic initialisation once more to be sure the right one is saved
workflow.execute_entity_task(
    tasks.run_dynamic_melt_f_calibration, gdirs,
    init_model_fls=gdir.read_pickle('model_flowlines', filesuffix='_millan_adapted'),
    err_dmdtda_scaling_factor=0.2,
    ys=1979, ye=2020,
    melt_f_max=cfg.PARAMS['melt_f_max'],
    kwargs_run_function={'minimise_for': 'area',
                         'do_inversion': False,
                         'glen_a': glen_a_millan},
    ignore_errors=True,
    kwargs_fallback_function={'minimise_for': 'area',
                              'do_inversion': False,
                              'glen_a': glen_a_millan},
    output_filesuffix='_spinup_historical_millan_adapted',
)

compare_model_to_observations(gdir, ['', '_consensus', '_millan', '_millan_adapted'])
2026-07-20 13:28:39: oggm.core.flowline: TypeError occurred during task init_present_time_glacier_millan_adapted on RGI60-11.00897: init_present_time_glacier() got an unexpected keyword argument 'filesuffix'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[10], line 2
      1 # create another flowline for this, for later comparison
----> 2 tasks.init_present_time_glacier(gdir, filesuffix='_millan_adapted',
      3                                 use_binned_thickness_data='millan_ice_thickness')
      4 
      5 def to_minimize(glen_a):

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:

TypeError: init_present_time_glacier() got an unexpected keyword argument 'filesuffix'

Now, we achieve dynamic consistency by calibrating the Glen A factor, ensuring a match in area, geodetic mass balance and volume.

glen_a_millan
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[11], line 1
----> 1 glen_a_millan

NameError: name 'glen_a_millan' is not defined

Note that if the optimized Glen A factor falls outside expected boundaries, considering the use of a sliding factor fs is advisable.

Important: To retain the optimized Glen A (and fs) during projections, it must be set in the glacier directory.

# change glen_a in gdir for potential projection runs
gdir.add_to_diagnostics('inversion_glen_a', glen_a_millan)
# gdir.add_to_diagnostics('inversion_fs', fs_optimized)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[12], line 2
      1 # change glen_a in gdir for potential projection runs
----> 2 gdir.add_to_diagnostics('inversion_glen_a', glen_a_millan)
      3 # gdir.add_to_diagnostics('inversion_fs', fs_optimized)

NameError: name 'glen_a_millan' is not defined

With this configuration, you can conduct projections as outlined in the tutorial 10 minutes to… a glacier change projection with GCM data.

If you intend to undertake a comparative study using different thickness datasets, we highly recommend creating an individual glacier directory per thickness data to ensure consistent use of the correct dynamic parameters.

What’s next?#