Changeset 4192


Ignore:
Timestamp:
Apr 20, 2026, 3:58:44 PM (3 weeks ago)
Author:
jbclement
Message:

PEM:
Codebase for pore ice representation and evolution in the layered deposits:

  • replace single pore ice volume fraction with coefficients of polymial expansion to describe the stratum pore ice profile;
  • add the formation date in the stratum information;
  • make default initialization for 'stratum';
  • centralize the definition of stratum types;
  • merge of similar strata;
  • add interpolation and polynomial fitting tools;
  • remove subsurface strata.

JBC

Location:
trunk/LMDZ.COMMON
Files:
14 edited

Legend:

Unmodified
Added
Removed
  • trunk/LMDZ.COMMON/libf/evolution/backup.F90

    r4189 r4192  
    9696use tracers,          only: build4PCM_tracers, nq
    9797use ice_table,        only: build4PCM_ssice
    98 use climate_rec,   only: write_restart, write_restartfi, write_restartevo
     98use climate_rec,      only: write_restart, write_restartfi, write_restartevo
    9999use layered_deposits, only: layering
    100100
  • trunk/LMDZ.COMMON/libf/evolution/changelog.txt

    r4189 r4192  
    977977- Rename "clim_state_init"/"clim_state_rec" into "climate_init"/"climate_rec".
    978978- Add routines to handle conservative remapping in 1d with slope limiters, positivity and boundedness enforcements.
     979
     980== 20/04/2026 == JBC
     981Codebase for pore ice representation and evolution in the layered deposits:
     982    - replace single pore ice volume fraction with coefficients of polymial expansion to describe the stratum pore ice profile;
     983    - add the formation date in the stratum information;
     984    - make default initialization for 'stratum';
     985    - centralize the definition of stratum types;
     986    - merge of similar strata;
     987    - add interpolation and polynomial fitting tools;
     988    - remove subsurface strata.
  • trunk/LMDZ.COMMON/libf/evolution/climate_init.F90

    r4189 r4192  
    321321use stoppage,           only: stop_clean
    322322use geometry,           only: ngrid, nslope, nsoil, nsoil_PCM
    323 use evolution,          only: dt
     323use evolution,          only: dt, pem_ini_date, n_yr_sim
    324324use physics,            only: mugaz, r, alpha_clap_h2o, beta_clap_h2o
    325325use frost,              only: h2ofrost_PCM, h2o_frost4PCM, co2frost_PCM, co2_frost4PCM
     
    331331use sorption,           only: do_sorption, evolve_regolith_adsorption
    332332use tracers,            only: mmol, iPCM_qh2o
    333 use layered_deposits,   only: layering, do_layering, array2map, ini_layering, add_stratum
     333use layered_deposits,   only: layering, do_layering, array2map, ini_layering, add_stratum, ncoef_poreice
    334334use surf_ice,           only: rho_co2ice, rho_h2oice
    335335use slopes,             only: subslope_dist, def_slope_mean
     
    361361! LOCAL VARIABLES
    362362! ---------------
    363 logical(k4)                               :: here
    364 integer(di)                               :: i, islope, k, nb_str_max, nsoil_startevo
    365 real(dp)                                  :: delta           ! Depth of the interface regolith/breccia, breccia/bedrock [m]
    366 real(dp), dimension(ngrid,nsoil,nslope)   :: TI_startevo     ! Soil thermal inertia saved in the startevo [SI]
    367 real(dp), dimension(ngrid,nsoil,nslope)   :: tsoil_startevo  ! Soil temperature saved in the startevo [K]
    368 real(dp), dimension(:,:,:,:), allocatable :: layerings_array ! Array for layerings
     363logical(k4)                                   :: here
     364integer(di)                                   :: i, islope, k, nb_str_max, nsoil_startevo
     365real(dp)                                      :: delta           ! Depth of the interface regolith/breccia, breccia/bedrock [m]
     366real(dp), dimension(ngrid,nsoil,nslope)       :: TI_startevo     ! Soil thermal inertia saved in the startevo [SI]
     367real(dp), dimension(ngrid,nsoil,nslope)       :: tsoil_startevo  ! Soil temperature saved in the startevo [K]
     368real(dp), dimension(:,:,:,:), allocatable     :: layerings_array ! Array for layerings
     369real(dp), dimension(ncoef_poreice), parameter :: coef_poreice_0 = 0._dp
    369370
    370371! CODE
     
    489490    ! Layering
    490491    if (do_layering) then
    491         call print_msg('layerings_map is initialized with sub-surface strata.',LVL_WRN)
     492        call print_msg('layerings_map is initialized with a surface reference stratum.',LVL_WRN)
    492493        call print_msg("Ice is added with 'h2oice_huge_ini' where 'watercaptag' is true and otherwise with 'perennial_co2ice' found in the PCM.",LVL_WRN)
    493494        do i = 1,ngrid
     
    495496                do islope = 1,nslope
    496497                    call ini_layering(layerings_map(i,islope))
    497                     call add_stratum(layerings_map(i,islope),1.05_dp*h2oice_huge_ini/rho_h2oice,0._dp,h2oice_huge_ini/rho_h2oice,0.05_dp*h2oice_huge_ini/rho_h2oice,0._dp,0._dp)
     498                    call add_stratum(layerings_map(i,islope),1.05_dp*h2oice_huge_ini/rho_h2oice,0._dp,h2oice_huge_ini/rho_h2oice, &
     499                                     0.05_dp*h2oice_huge_ini/rho_h2oice,0._dp,coef_poreice_0,pem_ini_date + n_yr_sim)
    498500                end do
    499501            else
    500502                do islope = 1,nslope
    501503                    call ini_layering(layerings_map(i,islope))
    502                     if (co2_perice_PCM(i,islope) > 0.) call add_stratum(layerings_map(i,islope),1.05_dp*co2_perice_PCM(i,islope)/rho_co2ice,co2_perice_PCM(i,islope)/rho_co2ice,0._dp,0.05_dp*co2_perice_PCM(i,islope)/rho_co2ice,0._dp,0._dp)
     504                    if (co2_perice_PCM(i,islope) > 0.) then
     505                        call add_stratum(layerings_map(i,islope),1.05_dp*co2_perice_PCM(i,islope)/rho_co2ice,co2_perice_PCM(i,islope)/rho_co2ice,0._dp, &
     506                                         0.05_dp*co2_perice_PCM(i,islope)/rho_co2ice,0._dp,coef_poreice_0,pem_ini_date + n_yr_sim)
     507                    end if
    503508                end do
    504509            end if
     
    567572if (do_layering) then
    568573    call get_dim_nc('nb_str_max',nb_str_max)
    569     allocate(layerings_array(ngrid,nslope,nb_str_max,6))
     574    allocate(layerings_array(ngrid,nslope,nb_str_max,6 + ncoef_poreice))
    570575    layerings_array = 0.
    571576    call get_var_nc('stratif_top_elevation',layerings_array(:,:,:,1))
     
    574579    call get_var_nc('stratif_h_dust',layerings_array(:,:,:,4))
    575580    call get_var_nc('stratif_h_pore',layerings_array(:,:,:,5))
    576     call get_var_nc('stratif_poreice_volfrac',layerings_array(:,:,:,6))
     581    call get_var_nc('stratif_poreice_coef1',layerings_array(:,:,:,6))
     582    call get_var_nc('stratif_poreice_coef2',layerings_array(:,:,:,7))
     583    call get_var_nc('stratif_poreice_coef3',layerings_array(:,:,:,8))
     584    call get_var_nc('stratif_poreice_coef4',layerings_array(:,:,:,9))
     585    call get_var_nc('stratif_date',layerings_array(:,:,:,10))
    577586    call array2map(layerings_array,layerings_map)
    578587    deallocate(layerings_array)
  • trunk/LMDZ.COMMON/libf/evolution/climate_rec.F90

    r4189 r4192  
    395395    call check_nc(nf90_put_att(ncid,varid,'title','Layering pore height'),'putting title attribute for stratif_h_pore')
    396396
    397     call check_nc(nf90_def_var(ncid,'stratif_poreice_volfrac',nf90_double,(/dim_ngrid,dim_nb_str,dim_nslope,dim_time/),varid),'defining variable stratif_poreice_volfrac')
    398     call check_nc(nf90_put_att(ncid,varid,'title','Layering ice pore volume fraction'),'putting title attribute for stratif_poreice_volfrac')
     397    call check_nc(nf90_def_var(ncid,'stratif_poreice_coef1',nf90_double,(/dim_ngrid,dim_nb_str,dim_nslope,dim_time/),varid),'defining variable stratif_poreice_coef1')
     398    call check_nc(nf90_put_att(ncid,varid,'title','Layering pore-ice coefficient #1'),'putting title attribute for stratif_poreice_coef1')
     399
     400    call check_nc(nf90_def_var(ncid,'stratif_poreice_coef2',nf90_double,(/dim_ngrid,dim_nb_str,dim_nslope,dim_time/),varid),'defining variable stratif_poreice_coef2')
     401    call check_nc(nf90_put_att(ncid,varid,'title','Layering pore-ice coefficient #2'),'putting title attribute for stratif_poreice_coef2')
     402
     403    call check_nc(nf90_def_var(ncid,'stratif_poreice_coef3',nf90_double,(/dim_ngrid,dim_nb_str,dim_nslope,dim_time/),varid),'defining variable stratif_poreice_coef3')
     404    call check_nc(nf90_put_att(ncid,varid,'title','Layering pore-ice coefficient #3'),'putting title attribute for stratif_poreice_coef3')
     405
     406    call check_nc(nf90_def_var(ncid,'stratif_poreice_coef4',nf90_double,(/dim_ngrid,dim_nb_str,dim_nslope,dim_time/),varid),'defining variable stratif_poreice_coef4')
     407    call check_nc(nf90_put_att(ncid,varid,'title','Layering pore-ice coefficient #4'),'putting title attribute for stratif_poreice_coef4')
     408
     409    call check_nc(nf90_def_var(ncid,'stratif_date',nf90_double,(/dim_ngrid,dim_nb_str,dim_nslope,dim_time/),varid),'defining variable stratif_date')
     410    call check_nc(nf90_put_att(ncid,varid,'title','Layering stratum formation date'),'putting title attribute for stratif_date')
    399411end if
    400412
     
    436448use geometry,         only: ngrid, nslope
    437449use evolution,        only: pem_ini_date, n_yr_sim
    438 use layered_deposits, only: layering, do_layering, map2array, get_nb_str_max
     450use layered_deposits, only: layering, do_layering, map2array, get_nb_str_max, ncoef_poreice
    439451use soil,             only: do_soil, inertiedat
    440452use sorption,         only: do_sorption
     
    493505
    494506if (do_layering) then
    495     allocate(layerings_array(ngrid,nslope,nb_str_max,6))
     507    allocate(layerings_array(ngrid,nslope,nb_str_max,6 + ncoef_poreice))
    496508    call map2array(layerings_map,layerings_array)
    497509    call put_var_nc('stratif_top_elevation',layerings_array(:,:,:,1),itime)
     
    500512    call put_var_nc('stratif_h_dust',layerings_array(:,:,:,4),itime)
    501513    call put_var_nc('stratif_h_pore',layerings_array(:,:,:,5),itime)
    502     call put_var_nc('stratif_poreice_volfrac',layerings_array(:,:,:,6),itime)
     514    call put_var_nc('stratif_poreice_coef1',layerings_array(:,:,:,6),itime)
     515    call put_var_nc('stratif_poreice_coef2',layerings_array(:,:,:,7),itime)
     516    call put_var_nc('stratif_poreice_coef3',layerings_array(:,:,:,8),itime)
     517    call put_var_nc('stratif_poreice_coef4',layerings_array(:,:,:,9),itime)
     518    call put_var_nc('stratif_date',layerings_array(:,:,:,10),itime)
    503519    deallocate(layerings_array)
    504520end if
  • trunk/LMDZ.COMMON/libf/evolution/deftank/visu_layering.py

    r4110 r4192  
    66import os
    77import sys
     8from typing import Optional
    89import numpy as np
    910import netCDF4 as nc
     
    4445
    4546
     47def _get_axis(dim_names: list[str], aliases: tuple[str, ...]) -> Optional[int]:
     48    """Return axis index matching one alias in a list of NetCDF dimension names."""
     49    for i, name in enumerate(dim_names):
     50        lname = name.lower()
     51        for alias in aliases:
     52            if lname == alias or alias in lname:
     53                return i
     54    return None
     55
     56
     57def _read_stratif_var_tsg(nc_dataset: nc.Dataset, var_name: str, slope_index: int) -> Optional[np.ndarray]:
     58    """
     59    Read one stratification variable and reshape it to (time, strata, grid).
     60    If a slope dimension exists, keep only slope_index.
     61    """
     62    if var_name not in nc_dataset.variables:
     63        return None
     64
     65    var = nc_dataset.variables[var_name]
     66    arr = np.asarray(var[:], dtype=np.float64)
     67    dims = list(var.dimensions)
     68
     69    ax_slope = _get_axis(dims, ('nslope', 'slope'))
     70    if ax_slope is not None:
     71        arr = np.take(arr, slope_index, axis=ax_slope)
     72        dims.pop(ax_slope)
     73
     74    if arr.ndim == 2:
     75        return arr[np.newaxis, :, :]
     76    if arr.ndim != 3:
     77        sys.exit(f"Error: Variable '{var_name}' has unsupported shape {arr.shape}.")
     78
     79    ax_time = _get_axis(dims, ('time',))
     80    ax_str = _get_axis(dims, ('nb_str_max', 'nb_str'))
     81    ax_grid = _get_axis(dims, ('physical_points', 'ngrid', 'grid'))
     82
     83    if ax_time is None or ax_str is None or ax_grid is None:
     84        # Fallback to historical layout already used in this script.
     85        return arr
     86
     87    return np.transpose(arr, (ax_time, ax_str, ax_grid))
     88
     89
     90def _poreice_profile_from_coeff(coeff: np.ndarray, z_norm: np.ndarray) -> np.ndarray:
     91    """Evaluate clamped cubic pore-ice profile on normalized depth z in [0,1]."""
     92    profile = coeff[0] + coeff[1] * z_norm + coeff[2] * z_norm**2 + coeff[3] * z_norm**3
     93    return np.clip(profile, 0.0, 1.0)
     94
     95
     96def _mean_poreice_fraction(coeff: np.ndarray, n_profile: int = 33) -> float:
     97    """Compute mean pore-ice volumetric fraction by integrating the cubic profile."""
     98    z_norm = np.linspace(0.0, 1.0, n_profile)
     99    profile = _poreice_profile_from_coeff(coeff, z_norm)
     100    return float(np.clip(np.trapz(profile, z_norm), 0.0, 1.0))
     101
     102
    46103def load_slope_variables(nc_dataset: nc.Dataset, slope_index: int) -> dict:
    47104    """
     
    51108    idx_str = str(slope_index + 1).zfill(2)
    52109    vars_base = {
    53         'top_elev': f"stratif_slope{idx_str}_top_elevation",
    54         'h_co2': f"stratif_slope{idx_str}_h_co2ice",
    55         'h_h2o': f"stratif_slope{idx_str}_h_h2oice",
    56         'h_dust': f"stratif_slope{idx_str}_h_dust",
    57         'h_pore': f"stratif_slope{idx_str}_h_pore",
    58         'poreice_volfrac': f"stratif_slope{idx_str}_poreice_volfrac",
     110        'top_elev': (f"stratif_slope{idx_str}_top_elevation", "stratif_top_elevation"),
     111        'h_co2': (f"stratif_slope{idx_str}_h_co2ice", "stratif_h_co2ice"),
     112        'h_h2o': (f"stratif_slope{idx_str}_h_h2oice", "stratif_h_h2oice"),
     113        'h_dust': (f"stratif_slope{idx_str}_h_dust", "stratif_h_dust"),
     114        'h_pore': (f"stratif_slope{idx_str}_h_pore", "stratif_h_pore"),
    59115    }
    60116
    61117    data = {}
    62     for key, var_name in vars_base.items():
    63         if var_name not in nc_dataset.variables:
    64             sys.exit(f"Error: Variable '{var_name}' not found in the NetCDF file.")
    65         data[key] = nc_dataset.variables[var_name][:]
    66     return data
     118    for key, var_names in vars_base.items():
     119        arr = None
     120        for var_name in var_names:
     121            arr = _read_stratif_var_tsg(nc_dataset, var_name, slope_index)
     122            if arr is not None:
     123                break
     124        if arr is None:
     125            sys.exit(f"Error: Variables {var_names} not found in the NetCDF file.")
     126        data[key] = arr
     127
     128    coeff_arrays = []
     129    for icoef in range(1, 5):
     130        arr = _read_stratif_var_tsg(nc_dataset, f"stratif_slope{idx_str}_poreice_coef{icoef}", slope_index)
     131        if arr is None:
     132            arr = _read_stratif_var_tsg(nc_dataset, f"stratif_poreice_coef{icoef}", slope_index)
     133        if arr is None:
     134            coeff_arrays = []
     135            break
     136        coeff_arrays.append(arr)
     137
     138    if coeff_arrays:
     139        data['poreice_coeff'] = np.stack(coeff_arrays, axis=-1)
     140        return data
     141
     142    sys.exit("Error: Missing pore-ice coefficient variables (expected poreice_coef1..4).")
    67143
    68144
     
    83159    h_dust = data['h_dust']
    84160    h_pore = data['h_pore']
    85     poreice_volfrac = data['poreice_volfrac']
     161    poreice_coeff = data['poreice_coeff']
    86162
    87163    layers = top.shape[1]
     
    135211        h2o = h_h2o[0, layer_idx, grid_index] / thickness
    136212        dust = h_dust[0, layer_idx, grid_index] / thickness
    137         air = h_pore[0, layer_idx, grid_index] * (1.0 - poreice_volfrac[0, layer_idx, grid_index]) / thickness
    138         poreice = h_pore[0, layer_idx, grid_index] * poreice_volfrac[0, layer_idx, grid_index] / thickness
     213        poreice_frac = _mean_poreice_fraction(poreice_coeff[0, layer_idx, grid_index, :])
     214        air = h_pore[0, layer_idx, grid_index] * (1.0 - poreice_frac) / thickness
     215        poreice = h_pore[0, layer_idx, grid_index] * poreice_frac / thickness
    139216        contents[:, idx + 1] = [co2, h2o, dust, air, poreice]
    140217
  • trunk/LMDZ.COMMON/libf/evolution/deftank/visu_layering_evo.py

    r4110 r4192  
    9797
    9898
     99def _get_axis(dim_names, aliases):
     100    """Return axis index matching aliases in a dimension-name list."""
     101    for i, name in enumerate(dim_names):
     102        lname = name.lower()
     103        for alias in aliases:
     104            if lname == alias or alias in lname:
     105                return i
     106    return None
     107
     108
     109def _var_to_tsgi(nc_var):
     110    """
     111    Convert a NetCDF stratification variable to shape (time, slope, strata, grid).
     112    Handles both slope-specific variables and generic variables with an nslope dimension.
     113    """
     114    arr = np.asarray(nc_var[:], dtype=np.float32)
     115    dims = [d.lower() for d in nc_var.dimensions]
     116
     117    ax_time = _get_axis(dims, ('time',))
     118    ax_slope = _get_axis(dims, ('nslope', 'slope'))
     119    ax_str = _get_axis(dims, ('nb_str_max', 'nb_str'))
     120    ax_grid = _get_axis(dims, ('physical_points', 'ngrid', 'grid'))
     121
     122    if arr.ndim == 4 and None not in (ax_time, ax_slope, ax_str, ax_grid):
     123        return np.transpose(arr, (ax_time, ax_slope, ax_str, ax_grid))
     124
     125    if arr.ndim == 3 and None not in (ax_time, ax_str, ax_grid):
     126        arr_tsg = np.transpose(arr, (ax_time, ax_str, ax_grid))
     127        return arr_tsg[:, np.newaxis, :, :]
     128
     129    if arr.ndim == 3 and None not in (ax_slope, ax_str, ax_grid):
     130        arr_ssg = np.transpose(arr, (ax_slope, ax_str, ax_grid))
     131        return arr_ssg[np.newaxis, :, :, :]
     132
     133    if arr.ndim == 3:
     134        # Historical fallback used by this script: (time, strata, grid)
     135        return arr[:, np.newaxis, :, :]
     136
     137    if arr.ndim == 2:
     138        return arr[np.newaxis, np.newaxis, :, :]
     139
     140    raise ValueError(f"Unsupported variable shape {arr.shape} for '{nc_var.name}'")
     141
     142
     143def _slope_index_from_var(vname):
     144    """Return 0-based slope index from a slope-specific variable name, else None."""
     145    if 'slope' not in vname:
     146        return None
     147    try:
     148        return int(vname.split('slope')[1].split('_')[0]) - 1
     149    except (ValueError, IndexError):
     150        return None
     151
     152
    99153def collect_stratification_variables(files, base_name):
    100154    """
     
    114168
    115169    property_markers = {
    116         'heights':   'stratif_slope',    # "..._top_elevation"
     170        'heights':   'top_elevation',
    117171        'co2_ice':   'h_co2ice',
    118172        'h2o_ice':   'h_h2oice',
    119173        'dust':      'h_dust',
    120174        'pore':      'h_pore',
    121         'pore_ice':  'poreice_volfrac'
     175        'poreice_coef1': 'poreice_coef1',
     176        'poreice_coef2': 'poreice_coef2',
     177        'poreice_coef3': 'poreice_coef3',
     178        'poreice_coef4': 'poreice_coef4'
    122179    }
    123180    var_info = {prop: set() for prop in property_markers}
     
    128185                max_nb_str = max(max_nb_str, ds.dimensions['nb_str_max'].size)
    129186
    130             nslope = ds.dimensions['nslope'].size
    131             for k in range(1, nslope + 1):
    132                 var_name = f"stratif_slope{k:02d}_top_elevation"
    133                 if var_name in ds.variables:
    134                     arr = ds.variables[var_name][:]
    135                     min_base_elev = min(min_base_elev, np.min(arr))
    136                     max_top_elev = max(max_top_elev, np.max(arr))
    137                     var_info['heights'].add(var_name)
    138 
    139187            for full_var in ds.variables:
     188                if 'stratif' not in full_var:
     189                    continue
     190
     191                if property_markers['heights'] in full_var:
     192                    arr = np.asarray(ds.variables[full_var][:])
     193                    if arr.size > 0:
     194                        min_base_elev = min(min_base_elev, float(np.nanmin(arr)))
     195                        max_top_elev = max(max_top_elev, float(np.nanmax(arr)))
     196                    var_info['heights'].add(full_var)
     197
    140198                for prop, marker in property_markers.items():
    141                     if (marker in full_var) and prop != 'heights':
     199                    if prop != 'heights' and marker in full_var:
    142200                        var_info[prop].add(full_var)
    143201
     
    174232    for t_idx, ds in enumerate(datasets):
    175233        for var_name in var_info['heights']:
    176             slope_idx = int(var_name.split("slope")[1].split("_")[0]) - 1
    177             if 0 <= slope_idx < nslope:
    178                 raw = ds.variables[var_name][0, :, :]  # (n_strata, ngrid)
    179                 heights_data[t_idx][slope_idx] = raw.T  # (ngrid, n_strata)
     234            if var_name not in ds.variables:
     235                continue
     236            raw_tsgi = _var_to_tsgi(ds.variables[var_name])
     237            raw_sgi = raw_tsgi[0]  # (nslope_var, nstrata, ngrid)
     238
     239            slope_idx = _slope_index_from_var(var_name)
     240            if slope_idx is not None and raw_sgi.shape[0] == 1:
     241                if 0 <= slope_idx < nslope:
     242                    heights_data[t_idx][slope_idx] = raw_sgi[0].T
     243                continue
     244
     245            nsl = min(nslope, raw_sgi.shape[0])
     246            for isl in range(nsl):
     247                heights_data[t_idx][isl] = raw_sgi[isl].T
    180248
    181249    raw_prop_arrays = {}
     
    185253        raw_prop_arrays[prop] = np.zeros((ngrid, ntime, nslope, max_nb_str), dtype=np.float32)
    186254
    187     def slope_index_from_var(vname):
    188         return int(vname.split("slope")[1].split("_")[0]) - 1
    189 
    190255    for prop in raw_prop_arrays:
    191         slope_map = {}
    192         for vname in var_info[prop]:
    193             isl = slope_index_from_var(vname)
    194             if 0 <= isl < nslope:
    195                 slope_map[isl] = vname
    196 
    197256        arr = raw_prop_arrays[prop]
    198257        for t_idx, ds in enumerate(datasets):
    199             for isl, var_name in slope_map.items():
    200                 raw = ds.variables[var_name][0, :, :]  # (n_strata, ngrid)
    201                 n_strata_current = raw.shape[0]
    202                 arr[:, t_idx, isl, :n_strata_current] = raw.T
     258            for var_name in var_info[prop]:
     259                if var_name not in ds.variables:
     260                    continue
     261
     262                raw_tsgi = _var_to_tsgi(ds.variables[var_name])
     263                raw_sgi = raw_tsgi[0]  # (nslope_var, nstrata, ngrid)
     264                slope_idx = _slope_index_from_var(var_name)
     265
     266                if slope_idx is not None and raw_sgi.shape[0] == 1:
     267                    if 0 <= slope_idx < nslope:
     268                        n_strata_current = min(raw_sgi.shape[1], max_nb_str)
     269                        arr[:, t_idx, slope_idx, :n_strata_current] = raw_sgi[0, :n_strata_current, :].T
     270                    continue
     271
     272                nsl = min(nslope, raw_sgi.shape[0])
     273                for isl in range(nsl):
     274                    n_strata_current = min(raw_sgi.shape[1], max_nb_str)
     275                    arr[:, t_idx, isl, :n_strata_current] = raw_sgi[isl, :n_strata_current, :].T
    203276
    204277    return heights_data, raw_prop_arrays, ntime
     
    209282    Given raw_prop_arrays for 'co2_ice', 'h2o_ice', 'dust', 'pore' (in meters),
    210283    normalize each set of strata so that the sum of those four = 1 per cell.
    211     Returns:
    212       - frac_arrays: dict mapping same keys -> 4D arrays of fractions (0..1).
     284        Returns:
     285            - frac_arrays: dict mapping same keys -> 4D arrays of fractions (0..1).
     286            - includes an additional 'pore_ice' fraction derived from pore-ice coefficients.
    213287    """
    214288    co2 = raw_prop_arrays['co2_ice']
     
    224298    frac_dust = np.zeros_like(dust, dtype=np.float32)
    225299    frac_pore = np.zeros_like(pore, dtype=np.float32)
     300    frac_pore_ice = np.zeros_like(pore, dtype=np.float32)
    226301
    227302    frac_co2[mask] = co2[mask] / total[mask]
     
    230305    frac_pore[mask] = pore[mask] / total[mask]
    231306
     307    if all(key in raw_prop_arrays for key in ('poreice_coef1', 'poreice_coef2', 'poreice_coef3', 'poreice_coef4')):
     308        z_norm = np.linspace(0.0, 1.0, 33, dtype=np.float32)
     309        c1 = raw_prop_arrays['poreice_coef1']
     310        c2 = raw_prop_arrays['poreice_coef2']
     311        c3 = raw_prop_arrays['poreice_coef3']
     312        c4 = raw_prop_arrays['poreice_coef4']
     313        profile = (c1[..., None] + c2[..., None] * z_norm + c3[..., None] * z_norm**2 + c4[..., None] * z_norm**3)
     314        profile = np.clip(profile, 0.0, 1.0)
     315        poreice_mean = np.clip(np.trapz(profile, z_norm, axis=-1), 0.0, 1.0).astype(np.float32)
     316    else:
     317        poreice_mean = np.zeros_like(pore, dtype=np.float32)
     318
     319    frac_pore_ice[mask] = pore[mask] * poreice_mean[mask] / total[mask]
     320
    232321    return {
    233322        'co2_ice': frac_co2,
    234323        'h2o_ice': frac_h2o,
    235324        'dust':     frac_dust,
    236         'pore':     frac_pore
     325        'pore':     frac_pore,
     326        'pore_ice': frac_pore_ice
    237327    }
    238328
  • trunk/LMDZ.COMMON/libf/evolution/display.F90

    r4180 r4192  
    3535character(32),  protected, private :: username = ' '    ! User name
    3636character(32),  protected, private :: hostname = ' '    ! Machine/station name
     37character(128), protected, private :: cmd_pgrm = ' '    ! Command used to run the program
     38character(8),   protected, private :: date = ' '        ! Current date (YYYYMMDD)
     39character(10),  protected, private :: time = ' '        ! Current time (hhmmss.sss)
     40character(5),   protected, private :: zone = ' '        ! UTC offset (+/-hhmm)
    3741integer(di),    protected, private :: verbosity_lvl = LVL_NFO
    3842logical(k4),    protected, private :: out2term = .true. ! Flag to output to terminal
     
    125129! LOCAL VARIABLES
    126130! ---------------
    127 character(8)              :: date
    128 character(10)             :: time
    129 character(5)              :: zone
    130131character(100)            :: msg
    131 integer(di), dimension(8) :: values
     132integer(di), dimension(8) :: values ! Date/time components
    132133
    133134! CODE
     
    147148call getlog(username)
    148149call hostnm(hostname)
     150call get_command(cmd_pgrm)
    149151call print_msg('',LVL_NFO)
    150152call print_msg('********* PEM information *********',LVL_NFO)
    151 call print_msg('+ User     : '//trim(username),LVL_NFO)
    152 call print_msg('+ Machine  : '//trim(hostname),LVL_NFO)
    153 call print_msg('+ Directory: '//trim(curr_dir),LVL_NFO)
    154 write(msg,'(a,i2,a,i2,a,i4)') '+ Date     : ',values(3),'/',values(2),'/',values(1)
    155 call print_msg(msg,LVL_NFO)
    156 write(msg,'(a,i2,a,i2,a,i2,a)') '+ Time     : ',values(5),':',values(6),':',values(7)
    157 call print_msg(msg,LVL_NFO)
     153call print_msg('+ User     : '//trim(adjustl(username)),LVL_NFO)
     154call print_msg('+ Machine  : '//trim(adjustl(hostname)),LVL_NFO)
     155call print_msg('+ Directory: '//trim(adjustl(curr_dir)),LVL_NFO)
     156call print_msg('+ Command  : '//trim(adjustl(cmd_pgrm)),LVL_NFO)
     157write(msg,'(a,a4,a,a2,a,a2)') '+ Date     : ',date(1:4),'-',date(5:6),'-',date(7:8)
     158call print_msg(trim(msg),LVL_NFO)
     159write(msg,'(a,a2,a,a2,a,a2,a,a3)') '+ Time     : ',time(1:2),':',time(3:4),':',time(5:6),'.',time(8:10)
     160call print_msg(trim(msg),LVL_NFO)
     161if (zone(1:1) == '+' .or. zone(1:1) == '-') then
     162    write(msg,'(a,a1,a2,a,a2)') '+ Timezone : UTC',zone(1:1),zone(2:3),':',zone(4:5)
     163else
     164    write(msg,'(a,i0,a)') '+ Timezone : unknown (offset=',values(4),' min)'
     165end if
     166call print_msg(trim(msg),LVL_NFO)
    158167call print_msg('',LVL_NFO)
    159168call print_msg('********* Initialization *********',LVL_NFO)
  • trunk/LMDZ.COMMON/libf/evolution/layered_deposits.F90

    r4180 r4192  
    55!
    66! DESCRIPTION
    7 !     Manage layered deposits in the PEM with a linked list data structure.
     7!     Manages layered deposits thanks to a linked list data structure.
    88!     Provides data types and subroutines to manipulate layers of ice and dust.
    99!
    1010! AUTHORS & DATE
    11 !     JB Clement, 2024
     11!     JB Clement, 03/2024
    1212!
    1313! NOTES
     
    1818! ------------
    1919use numerics, only: dp, di, k4, tol, eps
    20 use geometry, only: ngrid, nslope
    2120use surf_ice, only: rho_co2ice, rho_h2oice
    2221
     
    3130
    3231! Physical parameters
    33 logical(k4), protected, private :: impose_dust_ratio            ! Impose dust-to-ice ratio instead of dust tendency (see 'dust2ice_ratio')
    34 real(dp),    protected, private :: dust2ice_ratio               ! Dust-to-ice ratio when ice condenses (10% by default)
    35 real(dp),    protected, private :: d_dust                       ! Tendency of dust [kg/m2/y]
    36 real(dp),    parameter, private :: dry_lag_porosity  = 0.2_dp   ! Porosity of dust lag
    37 real(dp),    parameter, private :: wet_lag_porosity  = 0.15_dp  ! Porosity of water ice lag
    38 real(dp),    parameter, private :: co2ice_porosity   = 0.05_dp  ! Porosity of CO2 ice
    39 real(dp),    parameter, private :: h2oice_porosity   = 0.1_dp   ! Porosity of H2O ice
    40 real(dp),    parameter, private :: rho_dust          = 2500._dp ! Density of dust [kg.m-3]
    41 real(dp),    parameter, private :: h_patchy_dust     = 5.e-4_dp ! Height under which a dust lag is considered patchy [m]
    42 real(dp),    parameter, private :: h_patchy_ice      = 5.e-4_dp ! Height under which a H2O ice lag is considered patchy [m]
     32logical(k4), protected, private :: impose_dust_ratio           ! Impose dust-to-ice ratio instead of dust tendency (see 'dust2ice_ratio')
     33real(dp),    protected, private :: dust2ice_ratio              ! Dust-to-ice ratio when ice condenses (10% by default)
     34real(dp),    protected, private :: d_dust                      ! Tendency of dust [kg/m2/y]
     35real(dp),    parameter, private :: dry_lag_porosity = 0.2_dp   ! Porosity of dust lag
     36real(dp),    parameter, private :: wet_lag_porosity = 0.15_dp  ! Porosity of water ice lag
     37real(dp),    parameter, private :: co2ice_porosity  = 0.05_dp  ! Porosity of CO2 ice
     38real(dp),    parameter, private :: h2oice_porosity  = 0.1_dp   ! Porosity of H2O ice
     39real(dp),    parameter, private :: rho_dust         = 2500._dp ! Density of dust [kg.m-3]
     40real(dp),    parameter, private :: h_patchy_dust    = 5.e-4_dp ! Height under which a dust lag is considered patchy [m]
     41real(dp),    parameter, private :: h_patchy_ice     = 5.e-4_dp ! Height under which a H2O ice lag is considered patchy [m]
     42
     43! Stratum type codes
     44integer(di), parameter, private :: STR_TYPE_EMPTY  = 0_di ! Empty stratum
     45integer(di), parameter, private :: STR_TYPE_CO2ICE = 1_di ! CO2 ice stratum
     46integer(di), parameter, private :: STR_TYPE_H2OICE = 2_di ! H2O ice stratum
     47integer(di), parameter, private :: STR_TYPE_DUST   = 3_di ! Dust stratum
     48integer(di), parameter, private :: STR_TYPE_MIXED  = 4_di ! Mixed stratum (ice and dust)
     49
     50! Parameters for pore ice profile reconstruction
     51integer(di), parameter          :: ncoef_poreice = 4_di    ! Number of cubic coefficients for pore ice profile
     52real(dp),    parameter, private :: dz_poreice    = 0.01_dp ! Target depth step for pore-ice profile reconstruction [m]
     53integer(di), parameter, private :: nmin_poreice  = 4_di    ! Minimal number of points for pore-ice profile reconstruction
     54integer(di), parameter, private :: nmax_poreice  = 64_di   ! Maximal number of points for pore-ice profile reconstruction
    4355
    4456! Parameters for CO2 flux correction (inspired by Levrard et al. 2007)
     
    5062! Stratum = node of the linked list
    5163type :: stratum
    52     real(dp)               :: top_elevation   ! Layer top_elevation (top height from the surface) [m]
    53     real(dp)               :: h_co2ice        ! Height of CO2 ice [m]
    54     real(dp)               :: h_h2oice        ! Height of H2O ice [m]
    55     real(dp)               :: h_dust          ! Height of dust [m]
    56     real(dp)               :: h_pore          ! Height of pores [m]
    57     real(dp)               :: poreice_volfrac ! Pore-filled ice volumetric fraction
    58     type(stratum), pointer :: up => null()    ! Upper stratum (next node)
    59     type(stratum), pointer :: down => null()  ! Lower stratum (previous node)
     64    real(dp)                           :: top_elevation = 1._dp ! Layer top_elevation (top height from the surface) [m]
     65    real(dp)                           :: h_co2ice      = 0._dp ! Height of CO2 ice [m]
     66    real(dp)                           :: h_h2oice      = 0._dp ! Height of H2O ice [m]
     67    real(dp)                           :: h_dust        = 1._dp ! Height of dust [m]
     68    real(dp)                           :: h_pore        = 0._dp ! Height of pores [m]
     69    real(dp), dimension(ncoef_poreice) :: coef_poreice  = 0._dp ! Coefficients for pore ice profile
     70    real(dp)                           :: form_date     = 0._dp ! Stratum formation date [planetary years]
     71    type(stratum), pointer             :: up   => null()        ! Upper stratum (next node)
     72    type(stratum), pointer             :: down => null()        ! Lower stratum (previous node)
    6073end type stratum
    6174
    6275! Layering = linked list
    63 type layering
     76type :: layering
    6477    integer(di)            :: nb_str = 0       ! Number of strata
    65     type(stratum), pointer :: bottom => null() ! First stratum (head of the list)
    66     type(stratum), pointer :: top => null()    ! Last stratum (tail of the list)
    67     !real(dp)               :: h_toplag         ! Height of dust layer at the top [m]
    68     !type(stratum), pointer :: subice => null() ! Shallower stratum containing ice
    69     !type(stratum), pointer :: surf => null()   ! Surface stratum
     78    type(stratum), pointer :: bottom => null() ! First stratum (head of the list) at the very bottom
     79    type(stratum), pointer :: top    => null() ! Last stratum (tail of the list) at the very top
     80    !type(stratum), pointer :: subice => null() ! Shallowest stratum containing compact ice
    7081end type layering
    7182
    7283! Array of pointers to "stratum"
    73 type ptrarray
     84type :: ptrarray
    7485    type(stratum), pointer :: p
    7586end type ptrarray
     
    125136
    126137!=======================================================================
     138FUNCTION get_str_type(str) RESULT(type)
     139!-----------------------------------------------------------------------
     140! NAME
     141!     get_str_type
     142!
     143! DESCRIPTION
     144!     Determine stratum type from composition and pore-ice state.
     145!
     146! AUTHORS & DATE
     147!     JB Clement, 04/2026
     148!
     149! NOTES
     150!
     151!-----------------------------------------------------------------------
     152
     153! DECLARATION
     154! -----------
     155implicit none
     156
     157! ARGUMENTS
     158! ---------
     159type(stratum), intent(in) :: str
     160
     161! LOCAL VARIABLES
     162! ---------------
     163integer(di) :: type
     164
     165! CODE
     166! ----
     167if (str%h_co2ice <= tol .and. str%h_h2oice <= tol .and. str%h_dust <= tol .and. is_poreice_empty(str)) then
     168    type = STR_TYPE_EMPTY
     169else if (str%h_co2ice > str%h_h2oice .and. str%h_co2ice > str%h_dust) then
     170    type = STR_TYPE_CO2ICE
     171else if (str%h_h2oice > str%h_co2ice .and. str%h_h2oice > str%h_dust) then
     172    type = STR_TYPE_H2OICE
     173else if (str%h_dust > str%h_co2ice .and. str%h_dust > str%h_h2oice) then
     174    type = STR_TYPE_DUST
     175else
     176    type = STR_TYPE_MIXED
     177end if
     178
     179END FUNCTION get_str_type
     180!=======================================================================
     181
     182!=======================================================================
     183FUNCTION strtype2txt(type) RESULT(txt_type)
     184!-----------------------------------------------------------------------
     185! NAME
     186!     strtype2txt
     187!
     188! DESCRIPTION
     189!     Convert stratum type integer code into a readable string.
     190!
     191! AUTHORS & DATE
     192!     JB Clement, 04/2026
     193!
     194! NOTES
     195!
     196!-----------------------------------------------------------------------
     197
     198! DECLARATION
     199! -----------
     200implicit none
     201
     202! ARGUMENTS
     203! ---------
     204integer(di), intent(in) :: type
     205
     206! LOCAL VARIABLES
     207! ---------------
     208character(16) :: txt_type
     209
     210! CODE
     211! ----
     212select case (type)
     213    case (STR_TYPE_EMPTY)
     214        txt_type = 'EMPTY'
     215    case (STR_TYPE_CO2ICE)
     216        txt_type = 'CO2_ICE'
     217    case (STR_TYPE_H2OICE)
     218        txt_type = 'H2O_ICE'
     219    case (STR_TYPE_DUST)
     220        txt_type = 'DUST'
     221    case (STR_TYPE_MIXED)
     222        txt_type = 'MIXED'
     223    case default
     224        txt_type = 'UNKNOWN'
     225end select
     226
     227END FUNCTION strtype2txt
     228!=======================================================================
     229
     230!=======================================================================
    127231SUBROUTINE print_stratum(this)
    128232!-----------------------------------------------------------------------
     
    153257type(stratum), intent(in) :: this
    154258
    155 ! CODE
    156 ! ----
    157 call print_msg('Top elevation       [m]: '//real2str(this%top_elevation),LVL_DBG)
    158 call print_msg('Height of CO2 ice   [m]: '//real2str(this%h_co2ice),LVL_DBG)
    159 call print_msg('Height of H2O ice   [m]: '//real2str(this%h_h2oice),LVL_DBG)
    160 call print_msg('Height of dust      [m]: '//real2str(this%h_dust),LVL_DBG)
    161 call print_msg('Height of pores     [m]: '//real2str(this%h_pore),LVL_DBG)
    162 call print_msg('Pore-filled ice [m3/m3]: '//real2str(this%poreice_volfrac),LVL_DBG)
     259! LOCAL VARIABLES
     260! ---------------
     261integer(di)    :: i
     262character(128) :: msg
     263
     264! CODE
     265! ----
     266call print_msg('Nature                     [-]: '//trim(strtype2txt(get_str_type(this))),LVL_DBG)
     267call print_msg('Formation date     [plnt year]: '//real2str(this%form_date),LVL_DBG)
     268call print_msg('Top elevation              [m]: '//real2str(this%top_elevation),LVL_DBG)
     269call print_msg('Height of CO2 ice          [m]: '//real2str(this%h_co2ice),LVL_DBG)
     270call print_msg('Height of H2O ice          [m]: '//real2str(this%h_h2oice),LVL_DBG)
     271call print_msg('Height of dust             [m]: '//real2str(this%h_dust),LVL_DBG)
     272call print_msg('Height of pores            [m]: '//real2str(this%h_pore),LVL_DBG)
     273msg = 'Pore-ice profile coefs [m3/m3]: {'
     274do i = 1, size(this%coef_poreice)
     275    msg = trim(msg)//real2str(this%coef_poreice(i))//', '
     276end do
     277msg = trim(msg)//'}'
     278call print_msg(trim(msg),LVL_DBG)
    163279
    164280END SUBROUTINE print_stratum
     
    166282
    167283!=======================================================================
    168 SUBROUTINE add_stratum(this,top_elevation,co2ice,h2oice,dust,pore,poreice)
     284SUBROUTINE add_stratum(this,top_elevation,co2ice,h2oice,dust,pore,coef_poreice,form_date)
    169285!-----------------------------------------------------------------------
    170286! NAME
     
    187303! ARGUMENTS
    188304! ---------
    189 type(layering), intent(inout)        :: this
    190 real(dp),       intent(in), optional :: top_elevation, co2ice, h2oice, dust, pore, poreice
     305type(layering),               intent(inout)        :: this
     306real(dp),                     intent(in), optional :: top_elevation, co2ice, h2oice, dust, pore
     307real(dp),       dimension(:), intent(in), optional :: coef_poreice
     308real(dp),                     intent(in), optional :: form_date
    191309
    192310! LOCAL VARIABLES
     
    198316! Creation of the stratum
    199317allocate(str)
    200 nullify(str%up,str%down)
    201 str%top_elevation   = 1._dp
    202 str%h_co2ice        = 0._dp
    203 str%h_h2oice        = 0._dp
    204 str%h_dust          = 1._dp
    205 str%h_pore          = 0._dp
    206 str%poreice_volfrac = 0._dp
    207318if (present(top_elevation)) str%top_elevation   = top_elevation
    208319if (present(co2ice))        str%h_co2ice        = co2ice
     
    210321if (present(dust))          str%h_dust          = dust
    211322if (present(pore))          str%h_pore          = pore
    212 if (present(poreice))       str%poreice_volfrac = poreice
     323if (present(coef_poreice))  str%coef_poreice(:) = coef_poreice(:)
     324if (present(form_date))     str%form_date       = form_date
    213325
    214326! Increment the number of strata
     
    229341
    230342!=======================================================================
    231 SUBROUTINE insert_stratum(this,str_prev,top_elevation,co2ice,h2oice,dust,pore,poreice)
     343SUBROUTINE insert_stratum(this,str_prev,top_elevation,co2ice,h2oice,dust,pore,coef_poreice,form_date)
    232344!-----------------------------------------------------------------------
    233345! NAME
     
    252364type(layering),         intent(inout)        ::  this
    253365type(stratum), pointer, intent(inout)        ::  str_prev
    254 real(dp),               intent(in), optional :: top_elevation, co2ice, h2oice, dust, pore, poreice
     366real(dp),               intent(in), optional :: top_elevation, co2ice, h2oice, dust, pore
     367real(dp), dimension(:), intent(in), optional :: coef_poreice
     368real(dp),               intent(in), optional :: form_date
    255369
    256370! LOCAL VARIABLES
     
    261375! ----
    262376if (.not. associated(str_prev%up)) then ! If str_prev is the top stratum of the layering
    263     call add_stratum(this,top_elevation,co2ice,h2oice,dust,pore,poreice)
     377    call add_stratum(this,top_elevation,co2ice,h2oice,dust,pore,coef_poreice,form_date)
    264378else
    265379    ! Creation of the stratum
    266380    allocate(str)
    267     nullify(str%up,str%down)
    268     str%top_elevation   = 1._dp
    269     str%h_co2ice        = 0._dp
    270     str%h_h2oice        = 0._dp
    271     str%h_dust          = 1._dp
    272     str%h_pore          = 0._dp
    273     str%poreice_volfrac = 0._dp
    274381    if (present(top_elevation)) str%top_elevation   = top_elevation
    275382    if (present(co2ice))        str%h_co2ice        = co2ice
     
    277384    if (present(dust))          str%h_dust          = dust
    278385    if (present(pore))          str%h_pore          = pore
    279     if (present(poreice))       str%poreice_volfrac = poreice
     386    if (present(coef_poreice))  str%coef_poreice(:) = coef_poreice(:)
     387    if (present(form_date))     str%form_date       = form_date
    280388
    281389    ! Increment the number of strata
     
    359467
    360468!=======================================================================
     469FUNCTION are_strata_similar(str_lo,str_hi,frac_tol,coef_tol,date_tol) RESULT(similar)
     470!-----------------------------------------------------------------------
     471! NAME
     472!     are_strata_similar
     473!
     474! DESCRIPTION
     475!     Check if two adjacent strata can be merged based on composition,
     476!     pore-ice profile, and optional formation-date proximity.
     477!
     478! AUTHORS & DATE
     479!     JB Clement, 04/2026
     480!
     481! NOTES
     482!     Negative value for date_tol means that formation date is not checked
     483!     for similarity.
     484!-----------------------------------------------------------------------
     485
     486! DECLARATION
     487! -----------
     488implicit none
     489
     490! ARGUMENTS
     491! ---------
     492type(stratum), intent(in) :: str_lo, str_hi
     493real(dp),      intent(in) :: frac_tol, coef_tol, date_tol
     494
     495! LOCAL VARIABLES
     496! ---------------
     497logical(k4)         :: similar
     498real(dp)            :: h_lo, h_hi
     499real(dp)            :: f_lo_co2, f_lo_h2o, f_lo_dust, f_lo_pore
     500real(dp)            :: f_hi_co2, f_hi_h2o, f_hi_dust, f_hi_pore
     501
     502! CODE
     503! ----
     504similar = .false.
     505
     506! Keep the neutral base layer untouched.
     507if (str_lo%top_elevation <= tol) return
     508
     509! Strata must be of the same type to be merged
     510if (get_str_type(str_lo) /= get_str_type(str_hi)) return
     511
     512! Check that the composition fractions are similar within the tolerance
     513h_lo = thickness(str_lo)
     514f_lo_co2  = str_lo%h_co2ice/h_lo
     515f_lo_h2o  = str_lo%h_h2oice/h_lo
     516f_lo_dust = str_lo%h_dust/h_lo
     517f_lo_pore = str_lo%h_pore/h_lo
     518h_hi = thickness(str_hi)
     519f_hi_co2   = str_hi%h_co2ice/h_hi
     520f_hi_h2o   = str_hi%h_h2oice/h_hi
     521f_hi_dust  = str_hi%h_dust/h_hi
     522f_hi_pore  = str_hi%h_pore/h_hi
     523if (abs(f_lo_co2 - f_hi_co2) > frac_tol) return
     524if (abs(f_lo_h2o - f_hi_h2o) > frac_tol) return
     525if (abs(f_lo_dust - f_hi_dust) > frac_tol) return
     526if (abs(f_lo_pore - f_hi_pore) > frac_tol) return
     527if (maxval(abs(str_lo%coef_poreice - str_hi%coef_poreice)) > coef_tol) return
     528
     529! Optionally check that the formation dates are close enough
     530if (date_tol >= 0._dp) then
     531    if (abs(str_lo%form_date - str_hi%form_date) > date_tol) return
     532end if
     533
     534similar = .true.
     535
     536END FUNCTION are_strata_similar
     537!=======================================================================
     538
     539!=======================================================================
     540SUBROUTINE merge_adjacent_strata(this,frac_tol,coef_tol,date_tol,max_pass)
     541!-----------------------------------------------------------------------
     542! NAME
     543!     merge_adjacent_strata
     544!
     545! DESCRIPTION
     546!     Merge adjacent strata with similar properties to limit unnecessary
     547!     stratification growth.
     548!
     549! AUTHORS & DATE
     550!     JB Clement, 04/2026
     551!
     552! NOTES
     553!     Negative value for date_tol means that formation date is not checked
     554!     for similarity.
     555!-----------------------------------------------------------------------
     556
     557! DECLARATION
     558! -----------
     559implicit none
     560
     561! ARGUMENTS
     562! ---------
     563type(layering), intent(inout)        :: this
     564real(dp),       intent(in), optional :: frac_tol, coef_tol, date_tol
     565integer(di),    intent(in), optional :: max_pass
     566
     567! LOCAL VARIABLES
     568! ---------------
     569type(stratum), pointer :: str_lo, str_hi
     570real(dp)               :: h_lo, h_hi, h_tot, w_lo, w_hi
     571real(dp)               :: frac_tol_loc, coef_tol_loc, date_tol_loc
     572logical(k4)            :: merged_in_pass
     573integer(di)            :: ipass, max_pass_loc
     574
     575! CODE
     576! ----
     577if (this%nb_str <= 2) return
     578
     579frac_tol_loc = 1.e-2_dp
     580coef_tol_loc = 1.e-2_dp
     581date_tol_loc = -1._dp
     582if (present(frac_tol)) frac_tol_loc = max(0._dp,frac_tol)
     583if (present(coef_tol)) coef_tol_loc = max(0._dp,coef_tol)
     584if (present(date_tol)) date_tol_loc = date_tol
     585
     586max_pass_loc = this%nb_str
     587if (present(max_pass)) max_pass_loc = max(1_di,max_pass)
     588
     589do ipass = 1,max_pass_loc
     590    merged_in_pass = .false.
     591    str_lo => this%bottom
     592
     593    do while (associated(str_lo) .and. associated(str_lo%up))
     594        str_hi => str_lo%up
     595
     596        if (are_strata_similar(str_lo,str_hi,frac_tol_loc,coef_tol_loc,date_tol_loc)) then
     597            h_lo = thickness(str_lo)
     598            h_hi = thickness(str_hi)
     599            h_tot = h_lo + h_hi
     600            w_lo = h_lo/h_tot
     601            w_hi = h_hi/h_tot
     602
     603            str_lo%top_elevation = str_hi%top_elevation
     604            str_lo%h_co2ice = str_lo%h_co2ice + str_hi%h_co2ice
     605            str_lo%h_h2oice = str_lo%h_h2oice + str_hi%h_h2oice
     606            str_lo%h_dust = str_lo%h_dust + str_hi%h_dust
     607            str_lo%h_pore = str_lo%h_pore + str_hi%h_pore
     608            str_lo%coef_poreice(:) = w_lo*str_lo%coef_poreice(:) + w_hi*str_hi%coef_poreice(:)
     609            str_lo%form_date = w_lo*str_lo%form_date + w_hi*str_hi%form_date
     610
     611            call del_stratum(this,str_hi)
     612            merged_in_pass = .true.
     613            cycle
     614        end if
     615
     616        str_lo => str_lo%up
     617    end do
     618
     619    if (.not. merged_in_pass) exit
     620end do
     621
     622END SUBROUTINE merge_adjacent_strata
     623!=======================================================================
     624
     625!=======================================================================
    361626FUNCTION get_stratum(lay,i) RESULT(str)
    362627!-----------------------------------------------------------------------
     
    409674!-----------------------------------------------------------------------
    410675! NAME
    411 !     del_layering
     676!     ini_layering
    412677!
    413678! DESCRIPTION
     
    421686!-----------------------------------------------------------------------
    422687
    423 ! DEPENDENCIES
    424 ! ------------
    425 use soil,     only: do_soil, layer, index_breccia, index_bedrock, regolith_porosity, breccia_porosity
    426 use geometry, only: nsoil
     688!~ ! DEPENDENCIES
     689!~ ! ------------
     690!~ use soil,     only: do_soil, layer, index_breccia, index_bedrock, regolith_porosity, breccia_porosity
     691!~ use geometry, only: nsoil
    427692
    428693! DECLARATION
     
    436701! LOCAL VARIABLES
    437702! ---------------
    438 integer(di) :: i
    439 real(dp)    :: h_soil, h_pore, h_tot
    440 
    441 ! CODE
    442 ! ----
    443 ! Creation of strata at the bottom of the layering to describe the sub-surface
    444 if (do_soil) then
    445     do i = nsoil,index_bedrock,-1
    446         h_soil = layer(i) - layer(i - 1) ! No porosity
    447         call add_stratum(this,-layer(i - 1),0._dp,0._dp,h_soil,0._dp,0._dp)
    448     end do
    449     do i = index_bedrock - 1,index_breccia,-1
    450         h_tot = layer(i) - layer(i - 1)
    451         h_pore = h_tot*breccia_porosity
    452         h_soil = h_tot - h_pore
    453         call add_stratum(this,-layer(i - 1),0._dp,0._dp,h_soil,h_pore,0._dp)
    454     end do
    455     do i = index_breccia - 1,2,-1
    456         h_tot = layer(i) - layer(i - 1)
    457         h_pore = h_tot*regolith_porosity
    458         h_soil = h_tot - h_pore
    459         call add_stratum(this,-layer(i - 1),0._dp,0._dp,h_soil,h_pore,0._dp)
    460     end do
    461     h_pore = layer(1)*regolith_porosity
    462     h_soil = layer(1) - h_pore
    463     call add_stratum(this,0._dp,0._dp,0._dp,h_soil,h_pore,0._dp)
    464 end if
     703real(dp), dimension(ncoef_poreice), parameter :: coef_poreice_0 = 0._dp
     704!~ integer(di) :: i
     705!~ real(dp)    :: h_soil, h_pore, h_tot
     706
     707! CODE
     708! ----
     709!~ ! Creation of strata at the bottom of the layering to describe the sub-surface
     710!~ if (do_soil) then
     711!~     do i = nsoil,index_bedrock,-1
     712!~         h_soil = layer(i) - layer(i - 1) ! No porosity
     713!~         call add_stratum(this,-layer(i - 1),0._dp,0._dp,h_soil,0._dp,coef_poreice_0)
     714!~     end do
     715!~     do i = index_bedrock - 1,index_breccia,-1
     716!~         h_tot = layer(i) - layer(i - 1)
     717!~         h_pore = h_tot*breccia_porosity
     718!~         h_soil = h_tot - h_pore
     719!~         call add_stratum(this,-layer(i - 1),0._dp,0._dp,h_soil,h_pore,coef_poreice_0)
     720!~     end do
     721!~     do i = index_breccia - 1,2,-1
     722!~         h_tot = layer(i) - layer(i - 1)
     723!~         h_pore = h_tot*regolith_porosity
     724!~         h_soil = h_tot - h_pore
     725!~         call add_stratum(this,-layer(i - 1),0._dp,0._dp,h_soil,h_pore,coef_poreice_0)
     726!~     end do
     727!~     h_pore = layer(1)*regolith_porosity
     728!~     h_soil = layer(1) - h_pore
     729!~     call add_stratum(this,0._dp,0._dp,0._dp,h_soil,h_pore,coef_poreice_0)
     730!~ end if
     731
     732! Creation of a neutral base stratum at the surface
     733call add_stratum(this,0._dp,0._dp,0._dp,0._dp,0._dp,coef_poreice_0)
    465734
    466735END SUBROUTINE ini_layering
     
    648917        k = 1
    649918        do while (associated(current))
    650             layerings_array(ig,islope,k,1) = current%top_elevation
    651             layerings_array(ig,islope,k,2) = current%h_co2ice
    652             layerings_array(ig,islope,k,3) = current%h_h2oice
    653             layerings_array(ig,islope,k,4) = current%h_dust
    654             layerings_array(ig,islope,k,5) = current%h_pore
    655             layerings_array(ig,islope,k,6) = current%poreice_volfrac
     919            layerings_array(ig,islope,k,1)   = current%top_elevation
     920            layerings_array(ig,islope,k,2)   = current%h_co2ice
     921            layerings_array(ig,islope,k,3)   = current%h_h2oice
     922            layerings_array(ig,islope,k,4)   = current%h_dust
     923            layerings_array(ig,islope,k,5)   = current%h_pore
     924            layerings_array(ig,islope,k,6:9) = current%coef_poreice(:)
     925            layerings_array(ig,islope,k,10)  = current%form_date
    656926            current => current%up
    657927            k = k + 1
     
    704974                             layerings_array(ig,islope,k,2),layerings_array(ig,islope,k,3), &
    705975                             layerings_array(ig,islope,k,4),layerings_array(ig,islope,k,5), &
    706                              layerings_array(ig,islope,k,6))
     976                             layerings_array(ig,islope,k,6:9),layerings_array(ig,islope,k,10))
    707977        end do
    708978    end do
     
    7521022do ig = 1,ngrid
    7531023    do islope = 1,nslope
    754         if (is_co2ice_str(layerings_map(ig,islope)%top)) then
     1024        if (get_str_type(layerings_map(ig,islope)%top) == STR_TYPE_CO2ICE) then
    7551025            co2_ice(ig,islope) = layerings_map(ig,islope)%top%h_co2ice*rho_co2ice
    756         else if (is_h2oice_str(layerings_map(ig,islope)%top)) then
     1026        else if (get_str_type(layerings_map(ig,islope)%top) == STR_TYPE_H2OICE) then
    7571027            h2o_ice(ig,islope) = layerings_map(ig,islope)%top%h_h2oice*rho_h2oice
    7581028        else
    759             call get_ssice_layering(layerings_map(ig,islope),h2oice_depth(ig,islope),h2o_ice(ig,islope),co2_ice(ig,islope))
     1029            call get_subice_layering(layerings_map(ig,islope),h2oice_depth(ig,islope),h2o_ice(ig,islope),co2_ice(ig,islope))
    7601030        end if
    7611031    end do
     
    8281098! DEPENDENCIES
    8291099! ------------
    830 use utility, only: int2str
    831 use display, only: print_msg, LVL_DBG
    832 
    833 ! DEPENDENCIES
    834 ! ------------
    8351100use geometry, only: ngrid, nslope
     1101use display,  only: print_msg, LVL_DBG
     1102use utility,  only: int2str
    8361103
    8371104! DECLARATION
     
    8631130
    8641131!=======================================================================
     1132SUBROUTINE define_str_grid(h,dz,nmin,nmax,z)
     1133!-----------------------------------------------------------------------
     1134! NAME
     1135!     define_str_grid
     1136!
     1137! DESCRIPTION
     1138!     Build a normalized-depth grid with adaptive point count.
     1139!
     1140! AUTHORS & DATE
     1141!     JB Clement, 04/2026
     1142!
     1143! NOTES
     1144!
     1145!-----------------------------------------------------------------------
     1146
     1147! DECLARATION
     1148! -----------
     1149implicit none
     1150
     1151! ARGUMENTS
     1152! ---------
     1153real(dp),                            intent(in)  :: h, dz
     1154integer(di),                         intent(in)  :: nmin, nmax
     1155real(dp), dimension(:), allocatable, intent(out) :: z
     1156
     1157! LOCAL VARIABLES
     1158! ---------------
     1159integer(di) :: n, i
     1160
     1161! CODE
     1162! ----
     1163n = nmin
     1164if (dz > eps) n = ceiling(max(0._dp,h)/dz)
     1165n = max(n,nmin)
     1166n = min(n,nmax)
     1167n = max(n,2_di)
     1168
     1169if (allocated(z)) deallocate(z)
     1170allocate(z(n))
     1171do i = 1,n
     1172    z(i) = real(i - 1,dp)/real(n - 1,dp)
     1173end do
     1174
     1175END SUBROUTINE define_str_grid
     1176!=======================================================================
     1177
     1178!=======================================================================
     1179FUNCTION is_poreice_empty(str) RESULT(profile_empty)
     1180!-----------------------------------------------------------------------
     1181! NAME
     1182!     is_poreice_empty
     1183!
     1184! DESCRIPTION
     1185!     Determine if a pore ice profile is empty.
     1186!
     1187! AUTHORS & DATE
     1188!     JB Clement, 04/2026
     1189!
     1190! NOTES
     1191!
     1192!-----------------------------------------------------------------------
     1193
     1194! DECLARATION
     1195! -----------
     1196implicit none
     1197
     1198! ARGUMENTS
     1199! ---------
     1200type(stratum), intent(in) :: str
     1201
     1202! LOCAL VARIABLES
     1203! ---------------
     1204logical(k4) :: profile_empty
     1205integer(di) :: i
     1206
     1207! CODE
     1208! ----
     1209profile_empty = .true.
     1210do i = 1,ncoef_poreice
     1211    if (abs(str%coef_poreice(i)) > tol) then
     1212        profile_empty = .false.
     1213        exit
     1214    end if
     1215end do
     1216
     1217END FUNCTION is_poreice_empty
     1218!=======================================================================
     1219
     1220!=======================================================================
     1221FUNCTION has_compact_ice(str) RESULT(has_ice)
     1222!-----------------------------------------------------------------------
     1223! NAME
     1224!     has_compact_ice
     1225!
     1226! DESCRIPTION
     1227!     Check if a stratum contains compact ice.
     1228!
     1229! AUTHORS & DATE
     1230!     JB Clement, 04/2026
     1231!
     1232! NOTES
     1233!
     1234!-----------------------------------------------------------------------
     1235
     1236! DECLARATION
     1237! -----------
     1238implicit none
     1239
     1240! ARGUMENTS
     1241! ---------
     1242type(stratum), intent(in) :: str
     1243
     1244! LOCAL VARIABLES
     1245! ---------------
     1246logical(k4) :: has_ice
     1247
     1248! CODE
     1249! ----
     1250has_ice = .false.
     1251if (str%h_co2ice > tol .or. str%h_h2oice > tol) has_ice = .true.
     1252
     1253END FUNCTION has_compact_ice
     1254!=======================================================================
     1255
     1256!=======================================================================
     1257FUNCTION is_pure_dust(str) RESULT(pure_dust)
     1258!-----------------------------------------------------------------------
     1259! NAME
     1260!     is_pure_dust
     1261!
     1262! DESCRIPTION
     1263!     Check if a stratum contains only dust (not cemented by ice).
     1264!
     1265! AUTHORS & DATE
     1266!     JB Clement, 04/2026
     1267!
     1268! NOTES
     1269!
     1270!-----------------------------------------------------------------------
     1271
     1272! DECLARATION
     1273! -----------
     1274implicit none
     1275
     1276! ARGUMENTS
     1277! ---------
     1278type(stratum), intent(in) :: str
     1279
     1280! LOCAL VARIABLES
     1281! ---------------
     1282logical(k4) :: pure_dust
     1283
     1284! CODE
     1285! ----
     1286pure_dust = .false.
     1287if (str%h_dust > tol .and. .not. has_compact_ice(str) .and. is_poreice_empty(str)) pure_dust = .true.
     1288
     1289END FUNCTION is_pure_dust
     1290!=======================================================================
     1291
     1292!=======================================================================
     1293FUNCTION is_dust_lag(str) RESULT(dust_lag)
     1294!-----------------------------------------------------------------------
     1295! NAME
     1296!     is_dust_lag
     1297!
     1298! DESCRIPTION
     1299!     Check if a stratum is a dust lag.
     1300!
     1301! AUTHORS & DATE
     1302!     JB Clement, 04/2026
     1303!
     1304! NOTES
     1305!
     1306!-----------------------------------------------------------------------
     1307
     1308! DECLARATION
     1309! -----------
     1310implicit none
     1311
     1312! ARGUMENTS
     1313! ---------
     1314type(stratum), intent(in) :: str
     1315
     1316! LOCAL VARIABLES
     1317! ---------------
     1318logical(k4) :: dust_lag
     1319
     1320! CODE
     1321! ----
     1322dust_lag = .false.
     1323if (str%h_dust > tol .and. .not. has_compact_ice(str)) dust_lag = .true.
     1324
     1325END FUNCTION is_dust_lag
     1326!=======================================================================
     1327
     1328!=======================================================================
     1329SUBROUTINE compute_poreice_dustlag(this)
     1330!-----------------------------------------------------------------------
     1331! NAME
     1332!     compute_poreice_dustlag
     1333!
     1334! DESCRIPTION
     1335!     Update top dust-lag pore-ice coefficients from dynamic pore filling.
     1336!
     1337! AUTHORS & DATE
     1338!     JB Clement, 04/2026
     1339!
     1340! NOTES
     1341!
     1342!-----------------------------------------------------------------------
     1343
     1344! DEPENDENCIES
     1345! ------------
     1346use subsurface_ice, only: dyn_ss_ice_m
     1347
     1348! DECLARATION
     1349! -----------
     1350implicit none
     1351
     1352! ARGUMENTS
     1353! ---------
     1354type(layering), intent(inout) :: this
     1355
     1356! LOCAL VARIABLES
     1357! ---------------
     1358
     1359! CODE
     1360! ----
     1361
     1362! Identify the contiguous top dust-lag column
     1363
     1364! Ensure the top dust-lag column overlays H2O ice
     1365
     1366! Build the grid for each dust-lag stratum
     1367
     1368! If the dust lag is too thin to be resolved by the grid, skip the update
     1369
     1370! Build the full grid and pore-ice profile for the dust-lag column
     1371
     1372! Compute the dynamic grid for the pore filling model, which is finer near the surface to better resolve the expected gradients
     1373
     1374! Interpolate the pore-ice profile onto the dynamic grid, assuming that all pores are filled below the dust lag
     1375
     1376! Get the updated pore-ice profile
     1377
     1378! Interpolate the updated pore-ice profile back onto the stratum-specific grids
     1379
     1380! Update the pore-ice coefficients for each dust-lag stratum
     1381
     1382
     1383END SUBROUTINE compute_poreice_dustlag
     1384!=======================================================================
     1385
     1386!=======================================================================
    8651387FUNCTION thickness(this) RESULT(h_str)
    8661388!-----------------------------------------------------------------------
     
    8981420
    8991421!=======================================================================
    900 FUNCTION is_co2ice_str(str) RESULT(co2ice_str)
    901 !-----------------------------------------------------------------------
    902 ! NAME
    903 !     is_co2ice_str
    904 !
    905 ! DESCRIPTION
    906 !     Check if a stratum can be considered as a CO2 ice layer.
    907 !
    908 ! AUTHORS & DATE
    909 !     JB Clement, 2024
    910 !
    911 ! NOTES
    912 !
    913 !-----------------------------------------------------------------------
    914 
    915 ! DECLARATION
    916 ! -----------
    917 implicit none
    918 
    919 ! ARGUMENTS
    920 ! ---------
    921 type(stratum), intent(in) :: str
    922 
    923 ! LOCAL VARIABLES
    924 ! ---------------
    925 logical(k4) :: co2ice_str
    926 
    927 ! CODE
    928 ! ----
    929 co2ice_str = .false.
    930 if (str%h_co2ice > str%h_h2oice .and. str%h_co2ice > str%h_dust) co2ice_str = .true.
    931 
    932 END FUNCTION is_co2ice_str
    933 !=======================================================================
    934 
    935 !=======================================================================
    936 FUNCTION is_h2oice_str(str) RESULT(h2oice_str)
    937 !-----------------------------------------------------------------------
    938 ! NAME
    939 !     is_h2oice_str
    940 !
    941 ! DESCRIPTION
    942 !     Check if a stratum can be considered as a H2O ice layer.
    943 !
    944 ! AUTHORS & DATE
    945 !     JB Clement, 2024
    946 !
    947 ! NOTES
    948 !
    949 !-----------------------------------------------------------------------
    950 
    951 ! DECLARATION
    952 ! -----------
    953 implicit none
    954 
    955 ! ARGUMENTS
    956 ! ---------
    957 type(stratum), intent(in) :: str
    958 
    959 ! LOCAL VARIABLES
    960 ! ---------------
    961 logical(k4) :: h2oice_str
    962 
    963 ! CODE
    964 ! ----
    965 h2oice_str = .false.
    966 if (str%h_h2oice > str%h_co2ice .and. str%h_h2oice > str%h_dust) h2oice_str = .true.
    967 
    968 END FUNCTION is_h2oice_str
    969 !=======================================================================
    970 
    971 !=======================================================================
    972 FUNCTION is_dust_str(str) RESULT(dust_str)
    973 !-----------------------------------------------------------------------
    974 ! NAME
    975 !     is_dust_str
    976 !
    977 ! DESCRIPTION
    978 !     Check if a stratum can be considered as a dust layer.
    979 !
    980 ! AUTHORS & DATE
    981 !     JB Clement, 2024
    982 !
    983 ! NOTES
    984 !
    985 !-----------------------------------------------------------------------
    986 
    987 ! DECLARATION
    988 ! -----------
    989 implicit none
    990 
    991 ! ARGUMENTS
    992 ! ---------
    993 type(stratum), intent(in) :: str
    994 
    995 ! LOCAL VARIABLES
    996 ! ---------------
    997 logical(k4) :: dust_str
    998 
    999 ! CODE
    1000 ! ----
    1001 dust_str = .false.
    1002 if (str%h_dust > str%h_co2ice .and. str%h_dust > str%h_h2oice) dust_str = .true.
    1003 
    1004 END FUNCTION is_dust_str
    1005 !=======================================================================
    1006 
    1007 !=======================================================================
    1008 FUNCTION is_dust_lag(str) RESULT(dust_lag)
    1009 !-----------------------------------------------------------------------
    1010 ! NAME
    1011 !     is_dust_lag
    1012 !
    1013 ! DESCRIPTION
    1014 !     Check if a stratum is a dust lag (no ice content).
    1015 !
    1016 ! AUTHORS & DATE
    1017 !     JB Clement, 2024
    1018 !
    1019 ! NOTES
    1020 !
    1021 !-----------------------------------------------------------------------
    1022 
    1023 ! DECLARATION
    1024 ! -----------
    1025 implicit none
    1026 
    1027 ! ARGUMENTS
    1028 ! ---------
    1029 type(stratum), intent(in) :: str
    1030 
    1031 ! LOCAL VARIABLES
    1032 ! ---------------
    1033 logical(k4) :: dust_lag
    1034 
    1035 ! CODE
    1036 ! ----
    1037 dust_lag = .false.
    1038 if (str%h_co2ice < tol .and. str%h_h2oice < tol .and. str%poreice_volfrac < tol) dust_lag = .true.
    1039 
    1040 END FUNCTION is_dust_lag
    1041 !=======================================================================
    1042 
    1043 !=======================================================================
    1044 SUBROUTINE get_ssice_layering(this,h_toplag,h2o_ice,co2_ice)
    1045 !-----------------------------------------------------------------------
    1046 ! NAME
    1047 !     get_ssice_layering
     1422SUBROUTINE get_subice_layering(this,h_toplag,h2o_ice,co2_ice)
     1423!-----------------------------------------------------------------------
     1424! NAME
     1425!     get_subice_layering
    10481426!
    10491427! DESCRIPTION
     
    10801458do
    10811459    h_toplag = h_toplag + thickness(current)
    1082     if (.not. associated(current%down)) then ! Bottom of the layering and only dust lag strata
     1460    if (.not. associated(current%down)) then ! Bottom of layering and only dust lag strata
    10831461        h_toplag = 0._dp
    10841462        return
     
    10891467
    10901468! Is the stratum below the dust lag made of ice?
    1091 if (current%h_h2oice > 0._dp .and. current%h_h2oice > current%h_co2ice) then ! Yes, there is more H2O ice than CO2 ice
     1469if (current%h_h2oice > tol .and. current%h_h2oice > current%h_co2ice) then ! Yes, there is more H2O ice than CO2 ice
    10921470    if (h_toplag < h_patchy_dust) then ! But the dust lag is too thin to considered ice as subsurface ice
    10931471        h_toplag = 0._dp
    10941472        h2o_ice = current%h_h2oice*rho_h2oice
    10951473    end if
    1096 else if (current%h_co2ice > 0._dp .and. current%h_co2ice > current%h_h2oice) then ! No, there is more CO2 ice than H2O ice
     1474else if (current%h_co2ice > tol .and. current%h_co2ice > current%h_h2oice) then ! No, there is more CO2 ice than H2O ice
    10971475    if (h_toplag < h_patchy_ice) co2_ice = current%h_co2ice*rho_co2ice ! The dust lag is too thin to considered ice as subsurface ice
    10981476    h_toplag = 0._dp ! Because it matters only for H2O ice
    10991477end if
    11001478
    1101 END SUBROUTINE get_ssice_layering
     1479END SUBROUTINE get_subice_layering
    11021480!=======================================================================
    11031481
     
    11421520
    11431521!=======================================================================
    1144 SUBROUTINE sublimate_ice(this,str,h2subl_co2ice,h2subl_h2oice,new_lag,zlag)
     1522SUBROUTINE sublimate_ice(this,str,h2subl_co2ice,h2subl_h2oice,new_lag,zlag,current_date)
    11451523!-----------------------------------------------------------------------
    11461524! NAME
     
    11681546real(dp),               intent(inout) :: zlag
    11691547real(dp),               intent(in)    :: h2subl_co2ice, h2subl_h2oice
    1170 
    1171 ! LOCAL VARIABLES
    1172 ! ---------------
    1173 real(dp)               :: h2subl, h_ice, h_pore, h_pore_new, h_tot
    1174 real(dp)               :: hlag_dust, hlag_h2oice
    1175 type(stratum), pointer :: current
     1548real(dp),               intent(in)    :: current_date
     1549
     1550! LOCAL VARIABLES
     1551! ---------------
     1552real(dp), dimension(ncoef_poreice), parameter :: coef_poreice_0 = 0._dp
     1553real(dp)                                      :: h2subl, h_ice, h_pore, h_pore_new, h_tot
     1554real(dp)                                      :: hlag_dust, hlag_h2oice
     1555type(stratum), pointer                        :: current
    11761556
    11771557! CODE
     
    12171597! New stratum above the current stratum as a lag layer
    12181598if (new_lag) then
    1219     call insert_stratum(this,str,str%top_elevation + h_tot,0._dp,hlag_h2oice,hlag_dust,h_pore_new,0._dp)
     1599    call insert_stratum(this,str,str%top_elevation + h_tot,0._dp,hlag_h2oice,hlag_dust,h_pore_new,coef_poreice_0,current_date)
    12201600    new_lag = .false.
    12211601else
     
    12681648h_toplag = 0._dp
    12691649currentloc => current%up
    1270 do while (is_dust_lag(currentloc) .and. associated(currentloc%up))
     1650do while (associated(currentloc%up) .and. is_dust_lag(currentloc))
    12711651    h_toplag = h_toplag + thickness(currentloc)
    12721652    currentloc => currentloc%up
     
    13101690! ARGUMENTS
    13111691! ---------
    1312 real(dp),               intent(in)           :: h2oice_depth
    1313 real(dp),               intent(inout)        :: flux_ssice, d_h2oice
    1314 integer(di),            intent(in), optional :: icell, jcell
     1692real(dp),    intent(in)           :: h2oice_depth
     1693real(dp),    intent(inout)        :: flux_ssice, d_h2oice
     1694integer(di), intent(in), optional :: icell, jcell
    13151695
    13161696! LOCAL VARIABLES
     
    13671747
    13681748!=======================================================================
    1369 SUBROUTINE evolve_layering(this,d_co2ice,d_h2oice,h2oice_depth,flux_ssice,new_str,zshift_surf,new_lag,zlag,current,icell,jcell)
     1749SUBROUTINE evolve_layering(this,d_co2ice,d_h2oice,h2oice_depth,flux_ssice,new_str,zshift_surf,new_lag,zlag,current,current_date,icell,jcell)
    13701750!-----------------------------------------------------------------------
    13711751! NAME
     
    13981778! ARGUMENTS
    13991779! ---------
     1780real(dp),               intent(in)           :: h2oice_depth
     1781real(dp),               intent(in)           :: current_date
     1782integer(di),            intent(in), optional :: icell, jcell
    14001783real(dp),               intent(inout)        :: d_co2ice, d_h2oice, flux_ssice
    1401 real(dp),               intent(in)           :: h2oice_depth
    14021784type(layering),         intent(inout)        :: this
    14031785type(stratum), pointer, intent(inout)        :: current
    14041786logical(k4),            intent(inout)        :: new_str, new_lag
    14051787real(dp),               intent(out)          :: zshift_surf, zlag
    1406 integer(di),            intent(in), optional :: icell, jcell
    1407 
    1408 ! LOCAL VARIABLES
    1409 ! ---------------
    1410 real(dp)    :: dh_co2ice, dh_h2oice, dh_dust, dh_dust_co2, dh_dust_h2o
    1411 real(dp)    :: h_co2ice_old, h_h2oice_old
    1412 real(dp)    :: h2subl_co2ice, h2subl_h2oice, h2subl_co2ice_tot, h2subl_h2oice_tot
    1413 real(dp)    :: h2lift, h2lift_tot, h_pore, h_tot!, R_subl
    1414 logical(k4) :: unexpected
     1788
     1789! LOCAL VARIABLES
     1790! ---------------
     1791real(dp), dimension(ncoef_poreice), parameter :: coef_poreice_0 = 0._dp
     1792real(dp)                                      :: dh_co2ice, dh_h2oice, dh_dust, dh_dust_co2, dh_dust_h2o
     1793real(dp)                                      :: h_co2ice_old, h_h2oice_old
     1794real(dp)                                      :: h2subl_co2ice, h2subl_h2oice, h2subl_co2ice_tot, h2subl_h2oice_tot
     1795real(dp)                                      :: h2lift, h2lift_tot, h_pore, h_tot!, R_subl
     1796logical(k4)                                   :: unexpected
    14151797
    14161798! CODE
     
    14561838    ! New stratum at the top of the layering
    14571839    if (new_str) then
    1458         call add_stratum(this,this%top%top_elevation + h_tot,dh_co2ice,dh_h2oice,dh_dust,h_pore,0._dp)
     1840        call add_stratum(this,this%top%top_elevation + h_tot,dh_co2ice,dh_h2oice,dh_dust,h_pore,coef_poreice_0,current_date)
    14591841        new_str = .false.
    14601842    else
     
    14921874            end if
    14931875            ! Ice sublimation
    1494             if (h2subl_co2ice > 0._dp .or. h2subl_h2oice > 0._dp) call sublimate_ice(this,current,h2subl_co2ice,h2subl_h2oice,new_lag,zlag)
     1876            if (h2subl_co2ice > 0._dp .or. h2subl_h2oice > 0._dp) call sublimate_ice(this,current,h2subl_co2ice,h2subl_h2oice,new_lag,zlag,current_date)
    14951877
    14961878            ! Is there dust to lift?
    14971879            h2lift = 0._dp
    1498             if (is_dust_lag(current) .and. h2lift_tot > 0._dp) then
     1880            if (is_pure_dust(current) .and. h2lift_tot > 0._dp) then
    14991881                h2lift = min(h2lift_tot,current%h_dust)
    15001882                h2lift_tot = h2lift_tot - h2lift
     
    15021884                if (h2lift > 0._dp) call lift_dust(this,current,h2lift)
    15031885            else if (associated(current%up)) then
    1504                 if (is_dust_lag(current%up) .and. h2lift_tot > 0._dp) then
     1886                if (is_pure_dust(current%up) .and. h2lift_tot > 0._dp) then
    15051887                    h2lift = min(h2lift_tot,current%up%h_dust)
    15061888                    h2lift_tot = h2lift_tot - h2lift
     
    15111893
    15121894            ! Still ice to be sublimated or dust to be lifted and no more ice in the current stratum so we move to the underlying stratum
    1513             if ((h2subl_co2ice_tot > 0. .or. h2subl_h2oice_tot > 0._dp .or. h2lift_tot > 0._dp) .and. current%h_co2ice < tol .and. current%h_h2oice < tol .and. current%poreice_volfrac < tol) then
     1895            if ((h2subl_co2ice_tot > 0. .or. h2subl_h2oice_tot > 0._dp .or. h2lift_tot > 0._dp) .and. &
     1896                current%h_co2ice < tol .and. current%h_h2oice < tol .and. is_poreice_empty(current)) then
    15141897                current => current%down
    15151898                new_lag = .true.
     
    15501933            end if
    15511934            ! Ice sublimation
    1552             if (h2subl_co2ice > 0._dp) call sublimate_ice(this,current,h2subl_co2ice,0._dp,new_lag,zlag)
     1935            if (h2subl_co2ice > 0._dp) call sublimate_ice(this,current,h2subl_co2ice,0._dp,new_lag,zlag,current_date)
    15531936
    15541937            ! Is there dust to lift?
    15551938            h2lift = 0._dp
    1556             if (is_dust_lag(current) .and. h2lift_tot > 0._dp) then
     1939            if (is_pure_dust(current) .and. h2lift_tot > 0._dp) then
    15571940                h2lift = min(h2lift_tot,current%h_dust)
    15581941                h2lift_tot = h2lift_tot - h2lift
     
    15601943                if (h2lift > 0._dp) call lift_dust(this,current,h2lift)
    15611944            else if (associated(current%up)) then
    1562                 if (is_dust_lag(current%up) .and. h2lift_tot > 0._dp) then
     1945                if (is_pure_dust(current%up) .and. h2lift_tot > 0._dp) then
    15631946                    h2lift = min(h2lift_tot,current%up%h_dust)
    15641947                    h2lift_tot = h2lift_tot - h2lift
     
    15851968        ! New stratum at the top of the layering
    15861969        if (new_str) then
    1587             call add_stratum(this,this%top%top_elevation + h_tot,0._dp,dh_h2oice,dh_dust_h2o,h_pore,0._dp)
     1970            call add_stratum(this,this%top%top_elevation + h_tot,0._dp,dh_h2oice,dh_dust_h2o,h_pore,coef_poreice_0,current_date)
    15881971            new_str = .false.
    15891972        else
     
    15991982        ! New stratum at the top of the layering
    16001983        if (new_str) then
    1601             call add_stratum(this,this%top%top_elevation + h_tot,0._dp,dh_h2oice,dh_dust_h2o,h_pore,0._dp)
     1984            call add_stratum(this,this%top%top_elevation + h_tot,0._dp,dh_h2oice,dh_dust_h2o,h_pore,coef_poreice_0,current_date)
    16021985            new_str = .false.
    16031986        else
     
    16252008            end if
    16262009            ! Ice sublimation
    1627             if (h2subl_co2ice > 0._dp) call sublimate_ice(this,current,h2subl_co2ice,0._dp,new_lag,zlag)
     2010            if (h2subl_co2ice > 0._dp) call sublimate_ice(this,current,h2subl_co2ice,0._dp,new_lag,zlag,current_date)
    16282011
    16292012            ! Is there dust to lift?
    16302013            h2lift = 0._dp
    1631             if (is_dust_lag(current) .and. h2lift_tot > 0._dp) then
     2014            if (is_pure_dust(current) .and. h2lift_tot > 0._dp) then
    16322015                h2lift = min(h2lift_tot,current%h_dust)
    16332016                h2lift_tot = h2lift_tot - h2lift
     
    16352018                if (h2lift > 0._dp) call lift_dust(this,current,h2lift)
    16362019            else if (associated(current%up)) then
    1637                 if (is_dust_lag(current%up) .and. h2lift_tot > 0.) then
     2020                if (is_pure_dust(current%up) .and. h2lift_tot > 0.) then
    16382021                    h2lift = min(h2lift_tot,current%up%h_dust)
    16392022                    h2lift_tot = h2lift_tot - h2lift
    16402023                    ! Dust lifting
    1641                     if (h2lift > 0.) call lift_dust(this,current%up,h2lift)
     2024                    if (h2lift > 0._dp) call lift_dust(this,current%up,h2lift)
    16422025                end if
    16432026            end if
     
    16622045
    16632046if (unexpected) then
    1664     call print_msg('    dh_co2ice [m/y] = '//real2str(dh_co2ice),LVL_ERR)
    1665     call print_msg('    dh_h2oice [m/y] = '//real2str(dh_h2oice),LVL_ERR)
    1666     call print_msg('    dh_dust   [m/y] = '//real2str(dh_dust),LVL_ERR)
     2047    call print_msg('    dh_co2ice  [m/y] = '//real2str(dh_co2ice),LVL_ERR)
     2048    call print_msg('    dh_h2oice  [m/y] = '//real2str(dh_h2oice),LVL_ERR)
     2049    call print_msg('    dh_dust    [m/y] = '//real2str(dh_dust),LVL_ERR)
     2050    call print_msg('    flux_ssice [m/y] = '//real2str(flux_ssice),LVL_ERR)
    16672051    call stop_clean(__FILE__,__LINE__,'situation for the layering construction not managed!',1)
    16682052end if
  • trunk/LMDZ.COMMON/libf/evolution/maths.F90

    r4189 r4192  
    416416
    417417!=======================================================================
     418FUNCTION clamp(x,xmin,xmax) RESULT(y)
     419!-----------------------------------------------------------------------
     420! NAME
     421!     clamp
     422!
     423! DESCRIPTION
     424!     Clamp a scalar value into an admissible interval.
     425!
     426! AUTHORS & DATE
     427!     JB Clement, 04/2026
     428!
     429! NOTES
     430!
     431!-----------------------------------------------------------------------
     432
     433! DECLARATION
     434! -----------
     435implicit none
     436
     437! ARGUMENTS
     438! ---------
     439real(dp), intent(in) :: x
     440real(dp), intent(in) :: xmin, xmax
     441
     442! LOCAL VARIABLES
     443! ---------------
     444real(dp) :: y
     445
     446! CODE
     447! ----
     448if (xmin > xmax) then
     449    y = x
     450else
     451    y = max(xmin,min(xmax,x))
     452end if
     453
     454END FUNCTION clamp
     455!=======================================================================
     456
     457!=======================================================================
     458SUBROUTINE solve_linear_system(a,b,x)
     459!-----------------------------------------------------------------------
     460! NAME
     461!     solve_linear_system
     462!
     463! DESCRIPTION
     464!     Solve a dense linear system by Gaussian elimination.
     465!
     466! AUTHORS & DATE
     467!     JB Clement, 04/2026
     468!
     469! NOTES
     470!     Uses partial pivoting and returns ok = .false. if singular.
     471!-----------------------------------------------------------------------
     472
     473! DEPENDENCIES
     474! ------------
     475use stoppage, only: stop_clean
     476
     477! DECLARATION
     478! -----------
     479implicit none
     480
     481! ARGUMENTS
     482! ---------
     483real(dp), dimension(:,:), intent(in)  :: a
     484real(dp), dimension(:),   intent(in)  :: b
     485real(dp), dimension(:),   intent(out) :: x
     486
     487! LOCAL VARIABLES
     488! ---------------
     489integer(di)                           :: n, i, j, k, ipiv
     490real(dp)                              :: pivot, fac
     491real(dp), dimension(:),   allocatable :: rhs, rowtmp
     492real(dp), dimension(:,:), allocatable :: m
     493
     494! CODE
     495! ----
     496! Initialization
     497n = size(b)
     498x(:) = 0._dp
     499if (size(a,1) /= n .or. size(a,2) /= n) call stop_clean(__FILE__,__LINE__,"size of a must be n x n!",1)
     500if (size(x) /= n) call stop_clean(__FILE__,__LINE__,"size of x must match size of b!",1)
     501
     502allocate(m(n,n),rhs(n),rowtmp(n))
     503m(:,:) = a(:,:)
     504rhs(:) = b(:)
     505
     506! Gaussian elimination with partial pivoting
     507do k = 1,n - 1
     508    ipiv = k
     509    pivot = abs(m(k,k))
     510    do i = k + 1,n
     511        if (abs(m(i,k)) > pivot) then
     512            pivot = abs(m(i,k))
     513            ipiv = i
     514        end if
     515    end do
     516    if (pivot <= eps) call stop_clean(__FILE__,__LINE__,"matrix is singular!",1)
     517
     518    if (ipiv /= k) then
     519        rowtmp(:) = m(k,:)
     520        m(k,:) = m(ipiv,:)
     521        m(ipiv,:) = rowtmp(:)
     522        fac = rhs(k)
     523        rhs(k) = rhs(ipiv)
     524        rhs(ipiv) = fac
     525    end if
     526
     527    do i = k + 1,n
     528        fac = m(i,k)/m(k,k)
     529        m(i,k:n) = m(i,k:n) - fac*m(k,k:n)
     530        rhs(i) = rhs(i) - fac*rhs(k)
     531    end do
     532end do
     533
     534if (abs(m(n,n)) <= eps) call stop_clean(__FILE__,__LINE__,"matrix is singular!",1)
     535
     536do i = n,1,-1
     537    x(i) = rhs(i)
     538    do j = i + 1,n
     539        x(i) = x(i) - m(i,j)*x(j)
     540    end do
     541    if (abs(m(i,i)) <= eps) call stop_clean(__FILE__,__LINE__,"matrix is singular!",1)
     542    x(i) = x(i)/m(i,i)
     543end do
     544
     545END SUBROUTINE solve_linear_system
     546!=======================================================================
     547
     548!=======================================================================
     549SUBROUTINE coef2prof(coef,x,fx)
     550!-----------------------------------------------------------------------
     551! NAME
     552!     coef2prof
     553!
     554! DESCRIPTION
     555!     Evaluate polynomial coefficients on a 1D profile.
     556!
     557! AUTHORS & DATE
     558!     JB Clement, 04/2026
     559!
     560! NOTES
     561!     Coefficients are ordered from degree 0 to degree n-1.
     562!-----------------------------------------------------------------------
     563
     564! DEPENDENCIES
     565! -----------
     566use stoppage, only: stop_clean
     567
     568! DECLARATION
     569! -----------
     570implicit none
     571
     572! ARGUMENTS
     573! ---------
     574real(dp), dimension(:), intent(in)  :: coef
     575real(dp), dimension(:), intent(in)  :: x
     576real(dp), dimension(:), intent(out) :: fx
     577
     578! LOCAL VARIABLES
     579! ---------------
     580integer(di) :: i, j, ncoef
     581real(dp)    :: yloc
     582
     583! CODE
     584! ----
     585! Initialization
     586fx(:) = 0._dp
     587if (size(fx) /= size(x)) call stop_clean(__FILE__,__LINE__,"size of fx must match size of x!",1)
     588
     589ncoef = int(size(coef),di)
     590
     591! Evaluate the polynomial at each point using Horner's method for numerical stability
     592do i = 1,size(x)
     593    yloc = coef(ncoef)
     594    do j = ncoef - 1,1,-1
     595        yloc = yloc*x(i) + coef(j)
     596    end do
     597    fx(i) = yloc
     598end do
     599
     600END SUBROUTINE coef2prof
     601!=======================================================================
     602
     603!=======================================================================
     604SUBROUTINE prof2coef(x,fx,coef)
     605!-----------------------------------------------------------------------
     606! NAME
     607!     prof2coef
     608!
     609! DESCRIPTION
     610!     Fit polynomial coefficients from a 1D profile by least squares.
     611!
     612! AUTHORS & DATE
     613!     JB Clement, 04/2026
     614!
     615! NOTES
     616!     Coefficients are ordered from degree 0 to degree n-1.
     617!-----------------------------------------------------------------------
     618
     619! DEPENDENCIES
     620! ------------
     621use stoppage, only: stop_clean
     622
     623! DECLARATION
     624! -----------
     625implicit none
     626
     627! ARGUMENTS
     628! ---------
     629real(dp), dimension(:), intent(in)  :: x
     630real(dp), dimension(:), intent(in)  :: fx
     631real(dp), dimension(:), intent(out) :: coef
     632
     633! LOCAL VARIABLES
     634! ---------------
     635integer(di)                           :: i, j, k, ncoef
     636real(dp), dimension(:),   allocatable :: rhs, basis
     637real(dp), dimension(:,:), allocatable :: normal
     638
     639! CODE
     640! ----
     641! Initialization
     642coef(:) = 0._dp
     643ncoef = int(size(coef),di)
     644if (size(fx) /= size(x)) call stop_clean(__FILE__,__LINE__,"size of fx must match size of x!",1)
     645if (size(x) < ncoef) call stop_clean(__FILE__,__LINE__,"size of x must be at least as large as the number of coefficients!",1)
     646
     647allocate(rhs(ncoef),basis(ncoef),normal(ncoef,ncoef))
     648normal(:,:) = 0._dp
     649rhs(:) = 0._dp
     650
     651! Set up the normal equations for least squares fitting
     652do i = 1,size(x)
     653    basis(1) = 1._dp
     654    do j = 2,ncoef
     655        basis(j) = basis(j - 1)*x(i)
     656    end do
     657
     658    do j = 1,ncoef
     659        rhs(j) = rhs(j) + basis(j)*fx(i)
     660        do k = 1,ncoef
     661            normal(j,k) = normal(j,k) + basis(j)*basis(k)
     662        end do
     663    end do
     664end do
     665
     666! Solve the linear system
     667call solve_linear_system(normal,rhs,coef)
     668
     669END SUBROUTINE prof2coef
     670!=======================================================================
     671
     672!=======================================================================
     673FUNCTION interp_profile(x_nodes,y_nodes,x_eval) RESULT(y_eval)
     674!-----------------------------------------------------------------------
     675! NAME
     676!     interp_profile
     677!
     678! DESCRIPTION
     679!     Interpolate a profile given at nodes.
     680!
     681! AUTHORS & DATE
     682!     JB Clement, 04/2026
     683!
     684! NOTES
     685!     Performs piecewise linear interpolation.
     686!-----------------------------------------------------------------------
     687
     688! DECLARATION
     689! -----------
     690implicit none
     691
     692! ARGUMENTS
     693! ---------
     694real(dp), dimension(:), intent(in) :: x_nodes, y_nodes
     695real(dp),               intent(in) :: x_eval
     696
     697! LOCAL VARIABLES
     698! ---------------
     699real(dp)    :: y_eval, weight
     700integer(di) :: i, n
     701
     702! CODE
     703! ----
     704! Initialization
     705n = int(size(x_nodes),di)
     706y_eval = 0._dp
     707if (n <= 0_di .or. size(y_nodes) /= n) return
     708
     709! Handle out-of-bounds cases by clamping to the nearest node value
     710if (x_eval <= x_nodes(1)) then
     711    y_eval = y_nodes(1)
     712    return
     713end if
     714if (x_eval >= x_nodes(n)) then
     715    y_eval = y_nodes(n)
     716    return
     717end if
     718
     719! Find the interval containing x_eval and perform linear interpolation
     720do i = 1,n - 1
     721    if (x_eval <= x_nodes(i + 1)) then
     722        if (abs(x_nodes(i + 1) - x_nodes(i)) <= eps) then ! Avoid division by zero if nodes are too close
     723            y_eval = y_nodes(i)
     724        else
     725            weight = (x_eval - x_nodes(i))/(x_nodes(i + 1) - x_nodes(i))
     726            y_eval = weight*(y_nodes(i + 1) - y_nodes(i)) + y_nodes(i)
     727        end if
     728        return
     729    end if
     730end do
     731
     732y_eval = y_nodes(n)
     733
     734END FUNCTION interp_profile
     735!=======================================================================
     736
     737!=======================================================================
    418738FUNCTION limit_slope(dqL,dqR,limiter) RESULT(limited_slope)
    419739!-----------------------------------------------------------------------
     
    426746!
    427747! AUTHORS & DATE
    428 !     JB Clement, 02/s2026
     748!     JB Clement, 02/2026
    429749!
    430750! NOTES
     
    480800!
    481801! AUTHORS & DATE
    482 !     JB Clement, 02/s2026
     802!     JB Clement, 02/2026
    483803!
    484804! NOTES
  • trunk/LMDZ.COMMON/libf/evolution/pem.F90

    r4191 r4192  
    3636use glaciers,           only: h2oice_flow, co2ice_flow, flow_co2glaciers, flow_h2oglaciers
    3737use ice_table,          only: icetable_equilibrium, icetable_dynamic, evolve_ice_table
    38 use layered_deposits,   only: do_layering, del_layering, evolve_layering, ptrarray, layering2surfice, surfice2layering, print_layering
     38use layered_deposits,   only: do_layering, del_layering, evolve_layering, ptrarray, layering2surfice, surfice2layering, &
     39                              print_layering, compute_poreice_dustlag
    3940use maths,              only: pi
    4041use numerics,           only: dp, qp, di, li, k4, eps, eps_qp
     
    238239            do i = 1,ngrid
    239240                call evolve_layering(layerings_map(i,islope),d_co2ice(i,islope),d_h2oice(i,islope),h2oice_depth(i,islope),flux_ssice_avg(i,islope), &
    240                                      new_str(i,islope),zshift_surf(i,islope),new_lag(i,islope),zlag(i,islope),current(i,islope)%p,i,islope)
     241                                     new_str(i,islope),zshift_surf(i,islope),new_lag(i,islope),zlag(i,islope),current(i,islope)%p,                  &
     242                                     pem_ini_date + n_yr_sim + n_yr_run + dt,i,islope)
     243                !if (do_soil .and. icetable_dynamic) then
     244                !    call compute_poreice_dustlag(layerings_map(i,islope))
     245                !end if
    241246                if (is_lvl_enabled(LVL_DBG)) call print_layering(layerings_map(i,islope))
    242247            end do
  • trunk/LMDZ.COMMON/libf/evolution/soil.F90

    r4188 r4192  
    287287if (size(soildepth_PCM_in) /= nsoil_PCM) call stop_clean(__FILE__,__LINE__,'soil size mismatch between "startfi.nc" and geometry dimensions!',1)
    288288
    289 ! Set PCM soil depths read from "startfi.nc" to the mid-layer depth array
     289! Set PCM soil layer depths read from "startfi.nc" to the mid-layer depth array
    290290mlayer(0:nsoil_PCM - 1) = soildepth_PCM_in(:)
    291291
     
    335335! ---------------
    336336integer(di) :: ig, iloop, islope
    337 real(dp)    :: alpha, lay1       ! Coefficients for building layers
    338 real(dp)    :: index_powerlaw    ! Coefficient to match the PEM power law grid to the PCM grid
     337real(dp)    :: alpha, lay1    ! Coefficients to build soil layer depths
     338real(dp)    :: index_powerlaw ! Coefficient to match the PEM power law grid to the PCM grid
    339339
    340340! CODE
  • trunk/LMDZ.COMMON/libf/evolution/subsurface_ice.F90

    r4110 r4192  
    1414!
    1515! NOTES
    16 !    Based on Norbert Schorgofer's code. Updated for PEM.
     16!    Wrapper for Norbert Schorgofer's code that has been slightly updated
     17!    to work with the PEM.
    1718!-----------------------------------------------------------------------
    1819
     
    3940!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    4041
    41 
    42 !=======================================================================
    43 !============================ dyn_ss_ice_m =============================
    44 !=======================================================================
    45 
    46 
     42!=======================================================================
     43!-----------------------------------------------------------------------
     44! NAME
     45!     dyn_ss_ice_m
     46!
     47! DESCRIPTION
     48!     Advance subsurface ice state for one column (NP=1) using the fast
     49!     dynamic method: update pore filling, ice table depth, and soil
     50!     temperatures over a short integration.
     51!
     52! INPUTS
     53!     ssi_depth_in      Initial ice-table depth [m]
     54!     T1                Mean/representative surface temperature [K]
     55!     Tb(nz)            Initial soil temperature profile [K]
     56!     nz                Number of vertical levels
     57!     thIn(1)           Thermal inertia [SI]
     58!     p0(1)             Total atmospheric pressure [Pa]
     59!     pfrost(1)         H2O vapor partial pressure proxy [Pa]
     60!     porefill_in(nz,1) Initial pore-ice filling fraction [0-1]
     61!
     62! OUTPUTS
     63!     ssi_depth         Updated ice-table depth [m]
     64!     porefill(nz,1)    Updated pore-ice filling fraction [0-1]
     65!     Tb(nz)            Updated soil temperature profile [K]
     66!
     67! NOTES
     68!     - This wrapper is hard-coded for NP=1.
     69!     - avrho1 is prescribed as pfrost/T1 in the underlying solver.
     70!-----------------------------------------------------------------------
    4771SUBROUTINE dyn_ss_ice_m(ssi_depth_in,T1,Tb,nz,thIn,p0,pfrost,porefill_in,porefill,ssi_depth)
    48 
    4972!***********************************************************************
    5073! Retreat and growth of subsurface ice on Mars
     
    5376  use orbit, only: sol_len_s
    5477  use maths, only: pi
    55   ! DECLARATION
    56 ! -----------
    57 implicit none
     78  implicit none
    5879  integer, parameter :: NP=1   ! # of sites
    5980  integer nz, i, k, iloop
     
    208229
    209230end subroutine dyn_ss_ice_m
    210 
    211 !=======================================================================
    212 
     231!=======================================================================
     232
     233!=======================================================================
    213234subroutine soilthprop(porosity,fill,rhocobs,tiobs,layertype, &
    214235     &     newrhoc,newti,icefrac)
     
    226247!     output are newti and newrhoc
    227248!***********************************************************************
    228 
    229 ! DEPENDENCIES
    230 ! ------------
    231 use stoppage, only: stop_clean
    232 
    233 ! DECLARATION
    234 ! -----------
    235 implicit none
     249  use stoppage, only: stop_clean
     250  implicit none
    236251  integer, intent(IN) :: layertype
    237252  real(8), intent(IN) :: porosity, fill, rhocobs, tiobs
     
    295310
    296311end subroutine soilthprop
    297 
    298 
    299 !=======================================================================
    300 
     312!=======================================================================
     313
     314!=======================================================================
    301315      real*8 function frostpoint(p)
    302316!     inverse of psv
    303317!     input is partial pressure [Pascal]
    304318!     output is temperature [Kelvin]
    305       ! DECLARATION
    306 ! -----------
    307 implicit none
     319      implicit none
    308320      real*8 p
    309321
     
    326338
    327339      end function frostpoint
    328 
    329 
    330 !=======================================================================
    331 !=========================== fast_subs_mars ============================
    332 !=======================================================================
    333 
    334 
     340!=======================================================================
     341
     342!=======================================================================
    335343!*****************************************************
    336344! Subroutines for fast method
    337345! written by Norbert Schorghofer 2007-2011
    338346!*****************************************************
    339 
    340 
    341347subroutine icelayer_mars(bigstep,nz,NP,thIn,rhoc,z,porosity,pfrost, &
    342348     & Tb,zdepthF,zdepthE,porefill,Tmean1,Tmean3,zdepthG, &
     
    347353! latitude  [degree]
    348354!*************************************************************************
    349   !use ice_table,      only: rho_ice
     355  !use ice_table, only: rho_ice
    350356  !use omp_lib
    351   ! DECLARATION
    352 ! -----------
    353 implicit none
     357  implicit none
    354358  integer, intent(IN) :: nz, NP
    355359  real(8), intent(IN) :: bigstep
     
    453457  !$omp end parallel
    454458end subroutine icelayer_mars
    455 
    456 
    457 
     459!=======================================================================
     460
     461!=======================================================================
    458462subroutine ajsub_mars(typeT, albedo0, pfrost, nz, z, ti, rhocv, &
    459463     &     fracIR, fracDust, patm, avdrho, avdrhoP, avrho1, &
     
    467471!***********************************************************************
    468472  use orbit, only: yr_len_sol, sol_len_s
    469   ! DECLARATION
    470 ! -----------
    471 implicit none
     473  implicit none
    472474  integer, intent(IN) :: nz, typeT
    473475!  real(8), intent(IN) :: latitude  ! in radians
     
    616618
    617619end subroutine ajsub_mars
    618 
    619 
    620 
     620!=======================================================================
     621
     622!=======================================================================
    621623real*8 function tfrostco2(p)
    622624!     the inverse of function psvco2
    623625!     input is pressure in Pascal, output is temperature in Kelvin
    624 ! DECLARATION
    625 ! -----------
    626626implicit none
    627627real*8 p
    628628tfrostco2 = 3182.48/(23.3494+log(100./p))
    629629end function
    630 
    631 !=======================================================================
    632 
     630!=======================================================================
     631
     632!=======================================================================
    633633      real*8 function psv(T)
    634634!     saturation vapor pressure of H2O ice [Pascal]
    635635!     input is temperature [Kelvin]
    636       ! DECLARATION
    637 ! -----------
    638 implicit none
     636      implicit none
    639637      real*8 T
    640638
     
    659657
    660658      end function psv
    661 
    662 
    663 !=======================================================================
    664 !=========================== fast_subs_univ ============================
    665 !=======================================================================
    666 
    667 
     659!=======================================================================
     660
     661!=======================================================================
    668662!*****************************************************
    669663! Commonly used subroutines for fast method
    670664! written by Norbert Schorghofer 2007-2011
    671665!*****************************************************
    672 
    673666pure function zint(y1,y2,z1,z2)
    674667  ! interpolate between two values, y1*y2<0
    675   ! DECLARATION
    676 ! -----------
    677 implicit none
     668  implicit none
    678669  real(8), intent(IN) :: y1,y2,z1,z2
    679670  real(8) zint
    680671  zint = (y1*z2 - y2*z1)/(y1-y2)
    681672end function zint
    682 
    683 
    684 
     673!=======================================================================
     674
     675!=======================================================================
    685676pure function equildepth(nz, z, rhosatav, rhosatav0, avrho1)
    686677!***********************************************************************
     
    688679!  this is not the true (final) equilibrium depth
    689680!***********************************************************************
    690   ! DECLARATION
    691 ! -----------
    692 implicit none
     681  implicit none
    693682  integer, intent(IN) :: nz
    694683  real(8), intent(IN) :: z(nz), rhosatav(nz)
     
    711700  if (equildepth>z(nz)) equildepth=-9999.   ! happens very rarely
    712701end function equildepth
    713 
    714 
    715 
     702!=======================================================================
     703
     704!=======================================================================
    716705subroutine depths_avmeth(typeT, nz, z, rhosatav, rhosatav0, rlow, avrho1,  &
    717706     & porefill, typeF, zdepthF, B, ypp, typeG, zdepthG)
     
    724713!  B = Diff*bigstep/(porosity*icedensity)  [SI units]
    725714!***********************************************************************
    726   use maths,     only: deriv2_simple, deriv1_onesided, deriv1, colint
    727   ! DECLARATION
    728 ! -----------
    729 implicit none
     715  use maths, only: deriv2_simple, deriv1_onesided, deriv1, colint
     716  implicit none
    730717  integer, intent(IN) :: nz, typeT
    731718  real(8), intent(IN), dimension(nz) :: z, rhosatav, porefill
     
    846833  ! if typeG>0, then all ice at and below typeG should be erased
    847834end subroutine depths_avmeth
    848 
    849 
    850 
     835!=======================================================================
     836
     837!=======================================================================
    851838pure subroutine icechanges(nz,z,typeF,avdrho,avdrhoP,ypp, &
    852839     & Diff,porosity,icefrac,bigstep,zdepthT,porefill,typeG)
     
    856843! a crucial subroutine
    857844!***********************************************************
    858   use maths,     only: colint
     845  use maths, only: colint
    859846  !use ice_table, only: rho_ice
    860   ! DECLARATION
    861 ! -----------
    862 implicit none
     847  implicit none
    863848  integer, intent(IN) :: nz, typeF, typeG
    864849  real(8), intent(IN) :: z(nz), ypp(nz), avdrho, avdrhoP
     
    940925  end if
    941926end subroutine icechanges
     927!=======================================================================
    942928
    943929!=======================================================================
  • trunk/LMDZ.COMMON/libf/misc/job.F90

    r4191 r4192  
    99!
    1010! AUTHORS & DATE
    11 !     JB Clement, 07/07/2025
     11!     JB Clement, 10/06/2024
    1212!
    1313! NOTES
     
    3838!
    3939! AUTHORS & DATE
    40 !     JB Clement, 07/07/2025
     40!     JB Clement, 10/06/2024
    4141!
    4242! NOTES
     
    9090!
    9191! AUTHORS & DATE
    92 !     JB Clement, 07/07/2025
     92!     JB Clement, 10/06/2024
    9393!
    9494! NOTES
  • trunk/LMDZ.COMMON/makelmdz_fcm

    r4191 r4192  
    659659!
    660660! AUTHORS & DATE
    661 !     JB Clement, 07/07/2025
     661!     JB Clement, 13/01/2025
    662662!
    663663! NOTES
     
    703703!
    704704! AUTHORS & DATE
    705 !     JB Clement, 07/07/2025
     705!     JB Clement, 13/01/2025
    706706!
    707707! NOTES
Note: See TracChangeset for help on using the changeset viewer.