Ignore:
Timestamp:
Apr 20, 2026, 3:58:44 PM (4 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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • 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
Note: See TracChangeset for help on using the changeset viewer.