Ignore:
Timestamp:
Jul 1, 2025, 11:39:58 AM (5 days ago)
Author:
jbclement
Message:

PEM:
Addition of 2 figures in the outputs of "visu_evol_layering.py": (i) the stratification over time colored using RGB ternary mix of H2O ice, CO2 ice and dust; (ii) the dust-to-ice ratio in the stratification over time.
JBC

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/LMDZ.COMMON/libf/evolution/deftank/visu_evol_layering.py

    r3809 r3819  
    1111from netCDF4 import Dataset
    1212import matplotlib.pyplot as plt
     13from mpl_toolkits.axes_grid1.inset_locator import inset_axes
     14from matplotlib.colors import LinearSegmentedColormap, LogNorm
    1315from scipy.interpolate import interp1d
    1416
     
    414416            fig.suptitle(
    415417                f"Content variation over time for (Grid point {ig+1}, Slope {isl+1})",
    416                 fontsize=14
     418                fontsize=14,
     419                fontweight='bold'
    417420            )
    418421
     
    456459
    457460            fig.subplots_adjust(right=0.88)
     461            fig.tight_layout(rect=[0, 0, 0.88, 1.0])
    458462            cbar_ax = fig.add_axes([0.90, 0.15, 0.02, 0.7])
    459463            fig.colorbar(im, cax=cbar_ax, orientation='vertical', label="Content")
    460             fig.tight_layout(rect=[0, 0, 0.88, 1.0])
    461464
    462465            fname = os.path.join(
    463466                output_folder, f"layering_evolution_ig{ig+1}_is{isl+1}.png"
     467            )
     468            fig.savefig(fname, dpi=150)
     469
     470
     471def plot_stratification_rgb_over_time(
     472    gridded_data,
     473    ref_grid,
     474    top_index,
     475    heights_data,
     476    date_time,
     477    exclude_sub=False,
     478    output_folder="."
     479):
     480    """
     481    Plot stratification over time colored using RGB ternary mix of H2O ice (blue), CO2 ice (violet), and dust (orange).
     482    Includes a triangular legend showing the mix proportions.
     483    """
     484
     485    # Define constant colors
     486    violet = np.array([255, 0, 255], dtype=float) / 255
     487    blue   = np.array([0, 0, 255], dtype=float) / 255
     488    orange = np.array([255, 165, 0], dtype=float) / 255
     489
     490    # Prepare elevation mask
     491    mask_elev = (ref_grid >= 0.0) if exclude_sub else np.ones_like(ref_grid, dtype=bool)
     492    elev = ref_grid[mask_elev]
     493
     494    # Generate legend image once
     495    res = 300
     496    u = np.linspace(0, 1, res)
     497    v = np.linspace(0, np.sqrt(3)/2, res)
     498    X, Y = np.meshgrid(u, v)
     499    V_bary = 2 * Y / np.sqrt(3)
     500    U_bary = X - 0.5 * V_bary
     501    W_bary = 1 - U_bary - V_bary
     502    mask_triangle = (U_bary >= 0) & (V_bary >= 0) & (W_bary >= 0)
     503
     504    legend_rgb = (
     505        U_bary[..., None] * violet
     506        + V_bary[..., None] * orange
     507        + W_bary[..., None] * blue
     508    )
     509    legend_rgb = np.clip(legend_rgb, 0.0, 1.0)
     510    legend_rgba = np.zeros((res, res, 4))
     511    legend_rgba[..., :3] = legend_rgb
     512    legend_rgba[..., 3] = mask_triangle.astype(float)
     513
     514    # Loop over grid and slope
     515    h2o = gridded_data['h2o_ice']
     516    co2 = gridded_data['co2_ice']
     517    dust = gridded_data['dust']
     518    ngrid, ntime, nslope, nz = h2o.shape
     519
     520    for ig in range(ngrid):
     521        for isl in range(nslope):
     522            # Compute RGB stratification over time
     523            rgb = np.ones((nz, ntime, 3), dtype=float)
     524            for t in range(ntime):
     525                mask_z = np.arange(nz) < top_index[ig, t, isl]
     526                if not mask_z.any():
     527                    continue
     528                cH2O = np.clip(h2o[ig, t, isl, mask_z], 0, None)
     529                cCO2 = np.clip(co2[ig, t, isl, mask_z], 0, None)
     530                cDust = np.clip(dust[ig, t, isl, mask_z], 0, None)
     531                total = cH2O + cCO2 + cDust
     532                total[total == 0] = 1.0
     533                fH2O = cH2O / total
     534                fCO2 = cCO2 / total
     535                fDust = cDust / total
     536                mix = (
     537                    np.outer(fH2O, blue)
     538                    + np.outer(fCO2, violet)
     539                    + np.outer(fDust, orange)
     540                )
     541                mix = np.clip(mix, 0.0, 1.0)
     542                rgb[mask_z, t, :] = mix
     543
     544            display_rgb = rgb[mask_elev, :, :]
     545
     546            # Create figure with legend
     547            fig, (ax_main, ax_leg) = plt.subplots(
     548                1, 2, figsize=(12, 5), dpi=200,
     549                gridspec_kw={'width_ratios': [5, 1]}
     550            )
     551
     552            # Main stratification panel
     553            ax_main.imshow(
     554                display_rgb,
     555                aspect='auto',
     556                extent=[date_time[0], date_time[-1], elev.min(), elev.max()],
     557                interpolation='nearest',
     558                origin='lower'
     559            )
     560            ax_main.set_facecolor('white')
     561            ax_main.set_title(f"Ternary mix over time (Grid point {ig+1}, Slope {isl+1})", fontweight='bold')
     562            ax_main.set_xlabel("Time (Mars years)")
     563            ax_main.set_ylabel("Elevation (m)")
     564
     565            # Legend panel
     566            ax_leg.imshow(
     567                legend_rgba,
     568                extent=[0, 1, 0, np.sqrt(3)/2],
     569                origin='lower',
     570                interpolation='nearest'
     571            )
     572
     573            # Draw triangle border
     574            triangle = np.array([[0, 0], [1, 0], [0.5, np.sqrt(3)/2], [0, 0]])
     575            ax_leg.plot(triangle[:, 0], triangle[:, 1], 'k-', linewidth=1)
     576
     577            # Dashed gridlines
     578            ticks = np.linspace(0.25, 0.75, 3)
     579            for f in ticks:
     580                ax_leg.plot([1 - f, 0.5 * (1 - f)], [0, (1 - f)*np.sqrt(3)/2], '--', color='k', linewidth=0.5)
     581                ax_leg.plot([f, f + 0.5 * (1 - f)], [0, (1 - f)*np.sqrt(3)/2], '--', color='k', linewidth=0.5)
     582                y = (np.sqrt(3)/2) * f
     583                ax_leg.plot([0.5 * f, 1 - 0.5 * f], [y, y], '--', color='k', linewidth=0.5)
     584
     585            # Legend labels
     586            ax_leg.text(0, -0.05, 'H2O ice', ha='center', va='top', fontsize=8)
     587            ax_leg.text(1, -0.05, 'CO2 ice', ha='center', va='top', fontsize=8)
     588            ax_leg.text(0.5, np.sqrt(3)/2 + 0.05, 'Dust', ha='center', va='bottom', fontsize=8)
     589            ax_leg.axis('off')
     590
     591            plt.tight_layout()
     592
     593            # Save figure
     594            fname = os.path.join(output_folder, f"layering_rgb_evolution_ig{ig+1}_is{isl+1}.png")
     595            fig.savefig(fname, dpi=150, bbox_inches='tight')
     596
     597
     598def plot_dust_to_ice_ratio_over_time(
     599    gridded_data,
     600    ref_grid,
     601    top_index,
     602    heights_data,
     603    date_time,
     604    exclude_sub=False,
     605    output_folder="."
     606):
     607    """
     608    Plot the dust-to-ice ratio in the stratification over time,
     609    using a blue-to-orange colormap:
     610    - blue: ice-dominated (low dust-to-ice ratio)
     611    - orange: dust-dominated (high dust-to-ice ratio)
     612    """
     613    h2o = gridded_data['h2o_ice']
     614    dust = gridded_data['dust']
     615    ngrid, ntime, nslope, nz = h2o.shape
     616
     617    # Elevation mask
     618    if exclude_sub:
     619        elevation_mask = (ref_grid >= 0.0)
     620        elev = ref_grid[elevation_mask]
     621    else:
     622        elevation_mask = np.ones_like(ref_grid, dtype=bool)
     623        elev = ref_grid
     624
     625    # Define custom blue-to-orange colormap
     626    blue = np.array([0, 0, 255], dtype=float) / 255
     627    orange = np.array([255, 165, 0], dtype=float) / 255
     628    custom_cmap = LinearSegmentedColormap.from_list('BlueOrange', [blue, orange], N=256)
     629
     630    # Log‑ratio bounds and small epsilon to avoid log(0)
     631    vmin, vmax = -2, 1
     632    epsilon = 1e-6
     633
     634    # Loop over grids and slopes
     635    for ig in range(ngrid):
     636        for isl in range(nslope):
     637            log_ratio_array = np.full((nz, ntime), np.nan, dtype=np.float32)
     638
     639            # Compute log10(dust/ice) profile at each time step
     640            for t in range(ntime):
     641                zmax = top_index[ig, t, isl]
     642                if zmax <= 0:
     643                    continue
     644
     645                h2o_profile = np.clip(h2o[ig, t, isl, :zmax], 0, None)
     646                dust_profile = np.clip(dust[ig, t, isl, :zmax], 0, None)
     647
     648                with np.errstate(divide='ignore', invalid='ignore'):
     649                    ratio_profile = np.where(
     650                        h2o_profile > 0,
     651                        dust_profile / h2o_profile,
     652                        10**(vmax + 1)
     653                    )
     654                    log_ratio = np.log10(ratio_profile + epsilon)
     655                    log_ratio = np.clip(log_ratio, vmin, vmax)
     656
     657                log_ratio_array[:zmax, t] = log_ratio
     658
     659            # Convert back to linear ratio and apply elevation mask
     660            ratio_array = 10**log_ratio_array
     661            ratio_display = ratio_array[elevation_mask, :]
     662
     663            # Plot
     664            fig, ax = plt.subplots(figsize=(8, 6), dpi=150)
     665            im = ax.imshow(
     666                ratio_display,
     667                aspect='auto',
     668                extent=[date_time[0], date_time[-1], elev.min(), elev.max()],
     669                origin='lower',
     670                interpolation='nearest',
     671                cmap='managua_r',
     672                norm=LogNorm(vmin=10**vmin, vmax=10**vmax)
     673            )
     674
     675            # Add colorbar with simplified ratio labels
     676            cbar = fig.colorbar(im, ax=ax, orientation='vertical')
     677            cbar.set_label('Dust / H₂O ice (ratio)')
     678
     679            # Define custom ticks and labels
     680            ticks = [1e-2, 1e-1, 1, 1e1]
     681            labels = ['1:100', '1:10', '1:1', '10:1']
     682            cbar.set_ticks(ticks)
     683            cbar.set_ticklabels(labels)
     684
     685            # Save figure
     686            plt.tight_layout()
     687            fname = os.path.join(
     688                output_folder,
     689                f"dust_to_ice_ratio_grid{ig+1}_slope{isl+1}.png"
    464690            )
    465691            fig.savefig(fname, dpi=150)
     
    493719            fig.suptitle(
    494720                f"Strata count & total height over time for (Grid point {ig+1}, Slope {isl+1})",
    495                 fontsize=14
     721                fontsize=14,
     722                fontweight='bold'
    496723            )
    497724
     
    549776    # Plot
    550777    fig, axes = plt.subplots(3, 1, figsize=(8, 10), sharex=True)
    551     fig.suptitle("Orbital Parameters vs Simulated Time", fontsize=14)
     778    fig.suptitle("Orbital Parameters vs Simulated Time", fontsize=14, fontweight='bold')
    552779
    553780    axes[0].plot(date_time, obl_interp, 'r+', linestyle='-')
     
    626853        print(f"Warning: {date_time.size} timestamps vs {ntime} NetCDF files.")
    627854
    628     # 13) Plot stratification over time
     855    # 13) Plot stratification data over time
    629856    plot_stratification_over_time(
    630857        gridded_data, ref_grid, top_index, heights_data, date_time,
    631858        exclude_sub=exclude_sub, output_folder="."
    632859    )
    633 
    634     # 14) Plot strata count & total height vs time
     860    plot_stratification_rgb_over_time(
     861        gridded_data, ref_grid, top_index, heights_data, date_time,
     862        exclude_sub=exclude_sub, output_folder="."
     863    )
     864    plot_dust_to_ice_ratio_over_time(
     865        gridded_data, ref_grid, top_index, heights_data, date_time,
     866        exclude_sub=exclude_sub, output_folder="."
     867    )
    635868    plot_strata_count_and_total_height(heights_data, date_time, output_folder=".")
    636869
    637     # 15) Plot orbital parameters
     870    # 14) Plot orbital parameters
    638871    plot_orbital_parameters(infofile, orbfile, date_time, output_folder=".")
    639872
    640     # 16) Show all figures
     873    # 15) Show all figures
    641874    plt.show()
    642875
Note: See TracChangeset for help on using the changeset viewer.