diff --git a/compass/landice/tests/ismip7_forcing/fracture/process_excess_melt.py b/compass/landice/tests/ismip7_forcing/fracture/process_excess_melt.py index bc55cebc99..0f291792f0 100644 --- a/compass/landice/tests/ismip7_forcing/fracture/process_excess_melt.py +++ b/compass/landice/tests/ismip7_forcing/fracture/process_excess_melt.py @@ -145,8 +145,8 @@ def run(self): os.remove(f) # Place output in the appropriate directory - output_path = os.path.join(output_base_path, "excess_melt", - f"{model}_{scenario}") + output_path = os.path.join(output_base_path, f"{model}_{scenario}", + "excess_melt") if not os.path.exists(output_path): os.makedirs(output_path) diff --git a/compass/landice/tests/ismip7_forcing/fracture/process_lake_properties.py b/compass/landice/tests/ismip7_forcing/fracture/process_lake_properties.py index d9b3838a14..2b5d5ac1d5 100644 --- a/compass/landice/tests/ismip7_forcing/fracture/process_lake_properties.py +++ b/compass/landice/tests/ismip7_forcing/fracture/process_lake_properties.py @@ -147,8 +147,8 @@ def run(self): os.remove(f) # Place output in the appropriate directory - output_path = os.path.join(output_base_path, "lake_properties", - f"{model}_{scenario}") + output_path = os.path.join(output_base_path, f"{model}_{scenario}", + "lake_properties") if not os.path.exists(output_path): os.makedirs(output_path) diff --git a/compass/landice/tests/ismip7_forcing/fracture/process_shelf_collapse.py b/compass/landice/tests/ismip7_forcing/fracture/process_shelf_collapse.py index 6bf71fd40c..bf6537438e 100644 --- a/compass/landice/tests/ismip7_forcing/fracture/process_shelf_collapse.py +++ b/compass/landice/tests/ismip7_forcing/fracture/process_shelf_collapse.py @@ -125,8 +125,8 @@ def run(self): os.remove(remapped_file) # Place output in the appropriate directory - output_path = os.path.join(output_base_path, "shelf_collapse", - f"{model}_{scenario}") + output_path = os.path.join(output_base_path, f"{model}_{scenario}", + "shelf_collapse") if not os.path.exists(output_path): os.makedirs(output_path) diff --git a/compass/landice/tests/ismip7_run/ismip7_ais/__init__.py b/compass/landice/tests/ismip7_run/ismip7_ais/__init__.py index c6e3a41c94..7e234b6a5c 100644 --- a/compass/landice/tests/ismip7_run/ismip7_ais/__init__.py +++ b/compass/landice/tests/ismip7_run/ismip7_ais/__init__.py @@ -12,12 +12,12 @@ EXPERIMENTS = { 'historical_CESM2-WACCM': { 'scenario': 'historical', 'model': 'CESM2-WACCM', - 'start_time': '2000-01-01_00:00:00', + 'start_time': '2008-01-01_00:00:00', 'stop_time': '2015-01-01_00:00:00', 'is_historical': True}, 'historical_MRI-ESM2-0': { 'scenario': 'historical', 'model': 'MRI-ESM2-0', - 'start_time': '2000-01-01_00:00:00', + 'start_time': '2008-01-01_00:00:00', 'stop_time': '2015-01-01_00:00:00', 'is_historical': True}, 'ssp370_CESM2-WACCM': { @@ -62,7 +62,7 @@ 'is_historical': False}, 'ocx': { 'scenario': 'ocx', 'model': None, - 'start_time': '1990-01-01_00:00:00', + 'start_time': '2008-01-01_00:00:00', 'stop_time': '2026-01-01_00:00:00', 'is_historical': True}, } diff --git a/compass/landice/tests/ismip7_run/ismip7_ais/ismip7_ais.cfg b/compass/landice/tests/ismip7_run/ismip7_ais/ismip7_ais.cfg index 745e4c89f7..bfada238b8 100644 --- a/compass/landice/tests/ismip7_run/ismip7_ais/ismip7_ais.cfg +++ b/compass/landice/tests/ismip7_run/ismip7_ais/ismip7_ais.cfg @@ -59,6 +59,18 @@ calving_method = restore # Only required if calving_method is set to 'von_mises'. von_mises_parameter_path = NotAvailable +# Whether to apply the ISMIP7 Path C ice shelf collapse mask (from +# ismip7_forcing fracture) as hydrofracture-gated mask calving. Expected +# layout: {forcing_basepath}/{model}_{scenario}/shelf_collapse/*.nc +# Not used for historical, ctrl, or ocx experiments. If true and the mask +# file is missing for an experiment that should have one, setup fails. +use_hydrofracture_forcing = true + +# Ice fracture toughness (K_IC, Pa m^0.5) used in the hydrofracture +# vulnerability criterion gating mask calving. Only used when +# use_hydrofracture_forcing is true. +calving_fracture_toughness = 2.0e5 + # True if running coupled MALI-sea level model simulation sea_level_model = false diff --git a/compass/landice/tests/ismip7_run/ismip7_ais/namelist.landice b/compass/landice/tests/ismip7_run/ismip7_ais/namelist.landice index 165f9611f6..f505ef541f 100644 --- a/compass/landice/tests/ismip7_run/ismip7_ais/namelist.landice +++ b/compass/landice/tests/ismip7_run/ismip7_ais/namelist.landice @@ -1,4 +1,5 @@ config_velocity_solver = 'FO' + config_fem_extension_method = 'ismip7-era' config_do_velocity_reconstruction_for_external_dycore = .false. config_unrealistic_velocity = 00.00159 config_nonconvergence_error = .false. diff --git a/compass/landice/tests/ismip7_run/ismip7_ais/set_up_experiment.py b/compass/landice/tests/ismip7_run/ismip7_ais/set_up_experiment.py index 98d59d4c40..7545ba7448 100644 --- a/compass/landice/tests/ismip7_run/ismip7_ais/set_up_experiment.py +++ b/compass/landice/tests/ismip7_run/ismip7_ais/set_up_experiment.py @@ -63,6 +63,10 @@ def setup(self): # noqa: C901 reference_surface_path = section.get('reference_surface_path') reference_surface_fname = os.path.split(reference_surface_path)[-1] calving_method = section.get('calving_method') + use_hydrofracture_forcing = section.getboolean( + 'use_hydrofracture_forcing') + calving_fracture_toughness = section.get( + 'calving_fracture_toughness') sea_level_model = section.getboolean('sea_level_model') exp_info = self.exp_info @@ -229,6 +233,27 @@ def setup(self): # noqa: C901 sys.exit(f"ERROR: Expected 1 TF file at {tf_search}, " f"found {len(tf_list)}: {tf_list}") + # --- Find shelf collapse (calving) mask from ismip7_forcing + # fracture Path C, if requested --- + # historical, ctrl, and ocx experiments never use hydrofracture + # forcing, regardless of use_hydrofracture_forcing. + useCalvingMask = False + if (use_hydrofracture_forcing and + scenario not in ('historical', 'ctrl', 'ocx')): + mask_search = os.path.join(forcing_dir, 'shelf_collapse', + '*ice_shelf_collapse_mask_*.nc') + mask_list = glob.glob(mask_search) + if len(mask_list) == 1: + mask_fname = os.path.split(mask_list[0])[-1] + os.symlink(mask_list[0], + os.path.join(self.work_dir, mask_fname)) + useCalvingMask = True + else: + sys.exit( + f"ERROR: use_hydrofracture_forcing is True but did not " + f"find exactly 1 shelf collapse mask file at " + f"{mask_search}: {mask_list}") + # --- Set up streams --- # Determine forcing interval if scenario == 'ctrl': @@ -266,6 +291,14 @@ def setup(self): # noqa: C901 out_name='streams.landice', template_replacements=stream_replacements) + if useCalvingMask: + mask_stream_replacements = { + 'input_file_calving_mask_forcing_name': mask_fname} + self.add_streams_file( + resource_location, 'streams.mask_calving', + out_name='streams.landice', + template_replacements=mask_stream_replacements) + # --- Set up namelist --- self.add_namelist_file( resource_location, 'namelist.landice', @@ -307,6 +340,19 @@ def setup(self): # noqa: C901 out_name='streams.landice', template_replacements=vM_stream_replacements) + # Mask calving options (ismip7_forcing fracture Path C), gated by + # hydrofracture vulnerability (MALI PR #187) + if useCalvingMask: + options = { + 'config_apply_calving_mask': ".true.", + 'config_restore_calving_front': ".false.", + 'config_require_extensional_stresses_for_mask_calving': + ".true.", + 'config_calving_fracture_toughness': + f'{calving_fracture_toughness}'} + self.add_namelist_options(options=options, + out_name='namelist.landice') + # Sea-level model options if sea_level_model: slm_input_ice = section.get('slm_input_ice') diff --git a/compass/landice/tests/ismip7_run/ismip7_ais/streams.landice.template b/compass/landice/tests/ismip7_run/ismip7_ais/streams.landice.template index 5465926fb8..03de43b3db 100644 --- a/compass/landice/tests/ismip7_run/ismip7_ais/streams.landice.template +++ b/compass/landice/tests/ismip7_run/ismip7_ais/streams.landice.template @@ -108,7 +108,7 @@ filename_template="output/output_2d_$Y.nc" type="output"> - + @@ -128,6 +128,8 @@ + + diff --git a/compass/landice/tests/ismip7_run/ismip7_ais/streams.mask_calving b/compass/landice/tests/ismip7_run/ismip7_ais/streams.mask_calving new file mode 100644 index 0000000000..c885b4da76 --- /dev/null +++ b/compass/landice/tests/ismip7_run/ismip7_ais/streams.mask_calving @@ -0,0 +1,12 @@ + + + + + + + diff --git a/compass/landice/tests/ismip7_run/ismip7_gris/__init__.py b/compass/landice/tests/ismip7_run/ismip7_gris/__init__.py index ab883d15e2..44af20c9df 100644 --- a/compass/landice/tests/ismip7_run/ismip7_gris/__init__.py +++ b/compass/landice/tests/ismip7_run/ismip7_gris/__init__.py @@ -59,7 +59,7 @@ 'is_historical': False}, 'ocx': { 'scenario': 'ocx', 'model': None, - 'start_time': '1990-01-01_00:00:00', + 'start_time': '2007-01-01_00:00:00', 'stop_time': '2026-01-01_00:00:00', 'is_historical': True}, } diff --git a/compass/landice/tests/ismip7_run/ismip7_gris/namelist.landice b/compass/landice/tests/ismip7_run/ismip7_gris/namelist.landice index d29250f7b6..c00c9a5d18 100644 --- a/compass/landice/tests/ismip7_run/ismip7_gris/namelist.landice +++ b/compass/landice/tests/ismip7_run/ismip7_gris/namelist.landice @@ -1,4 +1,5 @@ config_velocity_solver = 'FO' + config_fem_extension_method = 'ismip7-era' config_do_velocity_reconstruction_for_external_dycore = .false. config_unrealistic_velocity = 00.00159 config_nonconvergence_error = .false. diff --git a/docs/developers_guide/landice/test_groups/ismip7_run.rst b/docs/developers_guide/landice/test_groups/ismip7_run.rst index 46b1c81541..edbab77740 100644 --- a/docs/developers_guide/landice/test_groups/ismip7_run.rst +++ b/docs/developers_guide/landice/test_groups/ismip7_run.rst @@ -75,16 +75,27 @@ The ``setup`` method sets up the experiment directory by: and restart frequency. 5. Adding calving-specific streams (face melting, von Mises params) if configured. -6. Creating a restart symlink for projection experiments pointing to +6. If ``use_hydrofracture_forcing`` is true and the experiment's scenario + is not ``historical``, ``ctrl``, or ``ocx``, looking for a single Path C + ``ice_shelf_collapse_mask_*.nc`` file under + ``{forcing_basepath}/{model}_{scenario}/shelf_collapse/`` (from + :ref:`landice_ismip7_forcing_fracture`). If found, it is symlinked in, + the ``streams.mask_calving`` stream is added, and + ``config_apply_calving_mask``, ``config_restore_calving_front``, + ``config_require_extensional_stresses_for_mask_calving``, and + ``config_calving_fracture_toughness`` are set accordingly. If not + found, setup fails with an error, since the mask is required whenever + ``use_hydrofracture_forcing`` is true for that scenario. +7. Creating a restart symlink for projection experiments pointing to the corresponding ESM's historical restart (``../historical_{model}/rst.2015-01-01.nc``). -7. Setting up CTRL2015 experiments with constant-climate forcing +8. Setting up CTRL2015 experiments with constant-climate forcing (``initial_only`` intervals). -8. Setting up the OCX experiment with reanalysis-based forcing. -9. If SLM coupling is enabled, adding a ``CreateSlmMappingFiles`` step - and writing the SLM namelist from the Jinja2 template. -10. Generating a ``graph.info`` file and a SLURM job script. -11. Symlinking the compass load script into the run directory. +9. Setting up the OCX experiment with reanalysis-based forcing. +10. If SLM coupling is enabled, adding a ``CreateSlmMappingFiles`` step + and writing the SLM namelist from the Jinja2 template. +11. Generating a ``graph.info`` file and a SLURM job script. +12. Symlinking the compass load script into the run directory. The ``run`` method executes MALI for the given experiment. diff --git a/docs/users_guide/landice/test_groups/ismip7_forcing.rst b/docs/users_guide/landice/test_groups/ismip7_forcing.rst index 417e3552c0..4a5ca9ea82 100644 --- a/docs/users_guide/landice/test_groups/ismip7_forcing.rst +++ b/docs/users_guide/landice/test_groups/ismip7_forcing.rst @@ -91,6 +91,9 @@ Processed forcing is written under ``output_base_path`` in a layout that the {output_base_path}/{group}/atmosphere/{mesh}_temperature_{source}_{scenario}_{years}.nc {output_base_path}/{group}/atmosphere/{mesh}_runoff_... (and the two gradients) {output_base_path}/{group}/ocean_thermal_forcing/{mesh}_thermal_forcing_{source}_{scenario}_{years}.nc + {output_base_path}/{group}/shelf_collapse/{mesh}_ice_shelf_collapse_mask_*.nc + {output_base_path}/{group}/excess_melt/{mesh}_excess_melt_*.nc + {output_base_path}/{group}/lake_properties/{mesh}_lake_properties_*.nc The ``group`` directory is ``{model}_{scenario}`` for ESM scenarios and ``OCX`` for GrIS OCX. AIS OCX writes one group per selected ocean product, @@ -365,7 +368,7 @@ is useful when only some of the pathway source files are available. (melt + rain after firn air content depletion), matching ``excess_melt_*.nc``. The output variable is ``ismip7ExcessMelt`` (converted from mm w.e. yr-1 to SI units of kg m-2 s-1) and is written to - ``{output_base_path}/excess_melt/{model}_{scenario}/``. Conservative + ``{output_base_path}/{model}_{scenario}/excess_melt/``. Conservative remapping is used by default since this is a flux. This source file has no ``x``/``y`` coordinate variables and its array is flipped along the y axis relative to the other fracture files, so the step reconstructs the source @@ -376,7 +379,7 @@ is useful when only some of the pathway source files are available. depth and area fraction from the Grau et al. (2025) parameterization, matching ``lake_properties_*.nc``. The output variables are ``ismip7LakeDepth`` (m) and ``ismip7LakeAreaFraction`` (unitless), written - to ``{output_base_path}/lake_properties/{model}_{scenario}/``. Bilinear + to ``{output_base_path}/{model}_{scenario}/lake_properties/``. Bilinear remapping is used by default. * **process_shelf_collapse** (Path C): Remaps the annual ice shelf collapse @@ -389,7 +392,7 @@ is useful when only some of the pathway source files are available. that the 0/1 mask values are preserved, and the remapped mask is rounded to 0/1. The output variable is ``calvingMask`` with an accompanying ``xtime`` variable, and the result is written to - ``{output_base_path}/shelf_collapse/{model}_{scenario}/``. + ``{output_base_path}/{model}_{scenario}/shelf_collapse/``. All three pathways produce continuous fields (Paths A and B) or a discrete mask (Path C) with an accompanying ``xtime`` variable. The output variable diff --git a/docs/users_guide/landice/test_groups/ismip7_run.rst b/docs/users_guide/landice/test_groups/ismip7_run.rst index 1d418eb849..97501104d7 100644 --- a/docs/users_guide/landice/test_groups/ismip7_run.rst +++ b/docs/users_guide/landice/test_groups/ismip7_run.rst @@ -113,13 +113,21 @@ Usage ├── CESM2-WACCM_historical/ │ ├── atmosphere/ │ │ └── {mesh}_smb_CESM2-WACCM_historical_*.nc - │ └── ocean_thermal_forcing/ - │ └── {mesh}_thermal_forcing_CESM2-WACCM_historical_*.nc + │ ├── ocean_thermal_forcing/ + │ │ └── {mesh}_thermal_forcing_CESM2-WACCM_historical_*.nc + │ └── shelf_collapse/ + │ └── {mesh}_ice_shelf_collapse_mask_*.nc ├── CESM2-WACCM_ssp585/ │ ├── atmosphere/ - │ └── ocean_thermal_forcing/ + │ ├── ocean_thermal_forcing/ + │ └── shelf_collapse/ └── ... + The ``shelf_collapse`` subdirectory (ISMIP7 Path C ice shelf collapse + mask, produced by :ref:`landice_ismip7_forcing_fracture`) is required + for ``ssp*`` experiments unless ``use_hydrofracture_forcing`` is set to + ``false``; see :ref:`landice_ismip7_run_mask_calving` below. + 3. Create a user config file overriding the ``NotAvailable`` paths. 4. Set up and run:: @@ -168,6 +176,17 @@ All config options should be reviewed and altered as needed. calving_method = restore von_mises_parameter_path = NotAvailable + # Whether to apply the ISMIP7 Path C ice shelf collapse mask as + # hydrofracture-gated mask calving (not used for historical, ctrl, or + # ocx experiments). If true, setup fails when the mask file is missing + # for an experiment that should have one. + use_hydrofracture_forcing = true + + # Ice fracture toughness (Pa m^0.5) for the hydrofracture vulnerability + # criterion gating mask calving, used only when use_hydrofracture_forcing + # is true. + calving_fracture_toughness = 2.0e5 + # Face melting use_face_melting = false @@ -211,6 +230,37 @@ ISMIP7 uses more forcing fields than ISMIP6, at mixed temporal resolutions: For CTRL2015 experiments, all forcing intervals are set to ``initial_only`` (constant climate). +.. _landice_ismip7_run_mask_calving: + +Mask Calving (Path C) +---------------------- + +``ismip7_ais`` can optionally apply the ISMIP7 Path C ice shelf collapse +mask produced by :ref:`landice_ismip7_forcing_fracture`. The mask is looked +up under the same ``forcing_basepath`` used for the atmosphere/ocean +forcing, at +``{forcing_basepath}/{model}_{scenario}/shelf_collapse/*ice_shelf_collapse_mask_*.nc``. +``historical``, ``ctrl``, and ``ocx`` experiments never use this pathway, +regardless of ``use_hydrofracture_forcing``. + +When ``use_hydrofracture_forcing`` is ``true`` (the default) for any other +experiment (e.g. ``ssp126``, ``ssp370``, ``ssp585``), a matching mask file +is required: if it is missing, ``compass setup`` fails with an error rather +than silently skipping mask calving. When found, the mask is symlinked into +the run directory and used to force calving: + +* ``config_apply_calving_mask`` is set to ``.true.`` +* ``config_restore_calving_front`` is set to ``.false.`` +* ``config_require_extensional_stresses_for_mask_calving`` is set to + ``.true.``, so the mask only removes floating ice that is also vulnerable + to hydrofracture per the fracture-toughness criterion of Lai et al. + (2020) / Reynolds and Nowicki (2026). The threshold depends on + ``config_calving_fracture_toughness``. + +Set ``use_hydrofracture_forcing = false`` to disable mask calving and the +hydrofracture gating entirely; experiments then use their normal +``calving_method`` configuration instead. + .. _landice_ismip7_run_ais: ismip7_ais