Changeset 3771 for trunk/LMDZ.COMMON/libf
- Timestamp:
- May 21, 2025, 4:01:25 PM (4 weeks ago)
- Location:
- trunk/LMDZ.COMMON/libf/evolution
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LMDZ.COMMON/libf/evolution/changelog.txt
r3742 r3771 642 642 == 28/04/2025 == JBC 643 643 Resulting modifications due to changes in the Mars PCM with r3739 and r3741. 644 645 == 21/05/2025 == JBC 646 Revision of the layering structure and algorithm: 647 - 'stratum' components are now expressed in height 648 - deletion of the (redundant) thickness feature of 'stratum' 649 - 'stratif' in the PEM main program is renamed 'deposits' 650 - addition of several procedures to get useful information about a stratum (major component or thickness) 651 - all subsurface layers are now represented in the layering structure by strata with negative top elevation 652 - simplification of the different situations arising from the input tendencies 653 - porosity is determined for the entire stratum (and not anymore for each component) based on dominant component 654 - sublimation of CO2 and H2O ice is now handled simultaneously (more realistic) in a stratum 655 - linking the layering algorithm with the PEM initilization/finalization regarding PCM data and with the PEM stopping criteria 656 - making separate cases for glaciers vs layering management 657 - H2O sublimation flux correction is now handled with the layering when a dust lag layer layer is growing 658 - update of 'h2o_ice_depth' and 'zdqsdif' accordingly at the PEM end for the PCM 659 660 == 21/05/2025 == JBC 661 Following r3770, adaptation and improvements of Python scripts to visualize the layered deposits. -
trunk/LMDZ.COMMON/libf/evolution/deftank/visu_evol_layering.py
r3673 r3771 4 4 5 5 import os 6 import sys 6 7 import numpy as np 7 8 from netCDF4 import Dataset … … 9 10 from scipy import interpolate 10 11 11 ######################### 12 ### Parameters to fill in 13 folder_path = "starts" 14 base_name = "restartpem" 15 dz = 0.1 # Discrization step of the reference grid for the elevation [m] 16 infofile = 'info_PEM.txt' 17 ######################### 18 19 20 ############################################################################################## 12 ### Function to get inputs 13 def get_user_inputs(): 14 folder_path = input("Enter the folder path containing the NetCDF files (press the Enter key for default [starts]): ").strip() 15 if not folder_path: 16 folder_path = "starts" 17 while not os.path.isdir(folder_path): 18 print("Invalid folder path. Please try again.") 19 folder_path = input("Enter the folder path containing the NetCDF files (press the Enter key for default [starts]): ").strip() 20 if not folder_path: 21 folder_path = "starts" 22 23 base_name = input("Enter the base name of the NetCDF files (press the Enter key for default [restartpem]): ").strip() 24 if not base_name: 25 base_name = "restartpem" 26 27 infofile = input("Enter the name of the PEM info file (press the Enter key for default [info_PEM.txt]): ").strip() 28 if not infofile: 29 infofile = "info_PEM.txt" 30 while not os.path.isfile(infofile): 31 print("Invalid file path. Please try again.") 32 infofile = input("Enter the name of the PEM info file (press the Enter key for default [info_PEM.txt]): ").strip() 33 if not infofile: 34 infofile = "info_PEM.txt" 35 36 return folder_path, base_name, infofile 37 21 38 ### Function to read the "startpem.nc" files and process their stratification data 22 39 def process_files(folder_path,base_name): … … 35 52 # Process each file and collect data 36 53 datasets = [] 37 max_top_elevation = 0 54 min_base_elevation = -56943.759374550937 # Base elevation of the deepest subsurface layer 55 max_top_elevation = 0. 38 56 max_nb_str = 0 39 ngrid = Dataset(os.path.join(folder_path,base_name + "1.nc"),'r').dimensions['physical_points'].size # ngrid is the same for all files 40 nslope = Dataset(os.path.join(folder_path,base_name + "1.nc"),'r').dimensions['nslope'].size # nslope is the same for all files 41 longitude = Dataset(os.path.join(folder_path,base_name + "1.nc"),'r').variables['longitude'][:] 42 latitude = Dataset(os.path.join(folder_path,base_name + "1.nc"),'r').variables['latitude'][:] 57 with Dataset(os.path.join(folder_path, base_name + "1.nc"), 'r') as ds: 58 ngrid = ds.dimensions['physical_points'].size # ngrid is the same for all files 59 nslope = ds.dimensions['nslope'].size # nslope is the same for all files 60 longitude = ds.variables['longitude'][:] 61 latitude = ds.variables['latitude'][:] 62 43 63 for i in range(1,nfile + 1): 44 64 file_path = os.path.join(folder_path,base_name + str(i) + ".nc") … … 53 73 for k in range(1,nslope + 1): 54 74 slope_var_name = f"stratif_slope{k:02d}_top_elevation" 75 min_base_elevation = min(min_base_elevation,np.min(ds.variables[slope_var_name][:])) 55 76 max_top_elevation = max(max_top_elevation,np.max(ds.variables[slope_var_name][:])) 56 77 57 print(f"> number of files = {nfile}") 58 print(f"> ngrid = {ngrid}") 59 print(f"> nslope = {nslope}") 60 print(f"> max(nb_str_max) = {max_nb_str}") 61 print(f"> max(top_elevation) = {max_top_elevation}") 78 print(f"> number of files = {nfile}") 79 print(f"> ngrid = {ngrid}") 80 print(f"> nslope = {nslope}") 81 print(f"> max(nb_str_max) = {max_nb_str}") 82 print(f"> min(base_elevation) = {min_base_elevation}") 83 print(f"> max(top_elevation) = {max_top_elevation}") 62 84 63 85 # Concatenate stratif variables with dimension 'nb_str_max' along the "Time" dimension … … 68 90 stratif_dust = np.zeros((ngrid,nfile,nslope,max_nb_str)) 69 91 stratif_pore = np.zeros((ngrid,nfile,nslope,max_nb_str)) 92 stratif_poreice = np.zeros((ngrid,nfile,nslope,max_nb_str)) 70 93 for var_name in datasets[0].variables: 71 94 if 'top_elevation' in var_name: … … 76 99 stratif_heights[i,j,k,:datasets[j].variables[var_name].shape[1]] = datasets[j].variables[var_name][0,:,i] 77 100 print(f"Processed variable: {var_name}") 78 elif ' co2ice_volfrac' in var_name:101 elif 'h_co2ice' in var_name: 79 102 for i in range(0,ngrid): 80 103 for j in range(0,nfile): … … 83 106 stratif_co2ice[i,j,k,:datasets[j].variables[var_name].shape[1]] = datasets[j].variables[var_name][0,:,i] 84 107 print(f"Processed variable: {var_name}") 85 elif 'h 2oice_volfrac' in var_name:108 elif 'h_h2oice' in var_name: 86 109 for i in range(0,ngrid): 87 110 for j in range(0,nfile): … … 90 113 stratif_h2oice[i,j,k,:datasets[j].variables[var_name].shape[1]] = datasets[j].variables[var_name][0,:,i] 91 114 print(f"Processed variable: {var_name}") 92 elif ' dust_volfrac' in var_name:115 elif 'h_dust' in var_name: 93 116 for i in range(0,ngrid): 94 117 for j in range(0,nfile): … … 97 120 stratif_dust[i,j,k,:datasets[j].variables[var_name].shape[1]] = datasets[j].variables[var_name][0,:,i] 98 121 print(f"Processed variable: {var_name}") 99 elif ' pore_volfrac' in var_name:122 elif 'h_pore' in var_name: 100 123 for i in range(0,ngrid): 101 124 for j in range(0,nfile): … … 103 126 if f'slope{k + 1:02d}' in var_name: 104 127 stratif_pore[i,j,k,:datasets[j].variables[var_name].shape[1]] = datasets[j].variables[var_name][0,:,i] 128 print(f"Processed variable: {var_name}") 129 elif 'icepore_volfrac' in var_name: 130 for i in range(0,ngrid): 131 for j in range(0,nfile): 132 for k in range(0,nslope): 133 if f'slope{k + 1:02d}' in var_name: 134 stratif_poreice[i,j,k,:datasets[j].variables[var_name].shape[1]] = datasets[j].variables[var_name][0,:,i] 105 135 print(f"Processed variable: {var_name}") 106 136 … … 110 140 111 141 stratif_data = [stratif_heights,stratif_co2ice,stratif_h2oice,stratif_dust,stratif_pore] 112 return stratif_data, max_top_elevation, longitude, latitude 142 143 while True: 144 try: 145 dz = float(input("Enter the discretization step of the reference grid for the elevation [m]: ").strip()) 146 if dz <= 0: 147 print("Discretization step must be strictly positive!") 148 continue 149 if dz > max_top_elevation: 150 print("Discretization step is higher than the maximum top elevation: please provide a correct value!") 151 continue 152 break 153 except ValueError: 154 print("Invalid value.") 155 return stratif_data, min_base_elevation, max_top_elevation, longitude, latitude, dz 113 156 114 157 ### Function to interpolate the stratification data on a reference grid 115 def interpolate_data(stratif_data,m ax_top_elevation,dz):158 def interpolate_data(stratif_data,min_base_elevation,max_top_elevation,dz): 116 159 # Define the reference ref_grid 117 ref_grid = np.arange( 0,max_top_elevation,dz)160 ref_grid = np.arange(min_base_elevation,max_top_elevation,dz) 118 161 print(f"> Number of ref_grid points = {len(ref_grid)}") 119 162 … … 143 186 date_time = [] 144 187 for line in file: 145 data = list(map( int,line.split()))188 data = list(map(float,line.split())) 146 189 data_lines.append(data) 147 190 date_time.append(data[0]) … … 150 193 151 194 ### Processing 152 stratif_data, max_top_elevation, longitude, latitude = process_files(folder_path,base_name) 153 if dz > max_top_elevation: 154 print('The discretization step is higher than the maximum top elevation: please provide a correct value!') 155 exit() 156 ref_grid, gridded_stratif_data = interpolate_data(stratif_data,max_top_elevation,dz) 195 folder_path, base_name, infofile = get_user_inputs() 196 stratif_data, min_base_elevation, max_top_elevation, longitude, latitude, dz = process_files(folder_path,base_name) 197 ref_grid, gridded_stratif_data = interpolate_data(stratif_data,min_base_elevation,max_top_elevation,dz) 157 198 date_time = read_infofile(infofile) 158 199 … … 168 209 for ax in axes.flat: 169 210 time_mesh, elevation_mesh = np.meshgrid(date_time,ref_grid) 170 #im = ax.imshow(np.transpose(gridded_stratif_data[iprop][ 0,:,0,:]),aspect = 'auto',cmap = 'viridis',origin = 'lower',extent = [date_time[0],date_time[-1],ref_grid[0],ref_grid[-1]],vmin = 0,vmax = 1)211 #im = ax.imshow(np.transpose(gridded_stratif_data[iprop][igr,:,isl,:]),aspect = 'auto',cmap = cmap,origin = 'lower',extent = [date_time[0],date_time[-1],ref_grid[0],ref_grid[-1]],vmin = 0,vmax = 1) 171 212 im = ax.pcolormesh(time_mesh,elevation_mesh,np.transpose(gridded_stratif_data[iprop][igr,:,isl,:]),cmap = cmap,shading = 'auto',vmin = 0,vmax = 1) 172 213 ax.set_title(subtitle[iprop]) -
trunk/LMDZ.COMMON/libf/evolution/deftank/visu_layering.py
r3673 r3771 3 3 #################################################################################### 4 4 5 import os 6 import sys 7 import numpy as np 5 8 import netCDF4 as nc 6 import numpy as np7 9 import matplotlib.pyplot as plt 8 import sys 9 import os.path 10 11 ############################## 12 ### Parameters to fill in 13 filename = 'startpem.nc' #'starts/restartpem226.nc' # File name 14 igrid = 1 # Grid point 15 islope = 1 # Slope number 16 istr = 4 # Stratum number 17 ############################## 18 19 20 #################################################################################### 21 ### Open the NetCDF file 22 if os.path.isfile(filename): 23 nc_file = nc.Dataset(filename,'r') 24 else: 25 sys.exit('The file \"' + filename + '\" does not exist!') 26 27 ### Get the dimensions 28 Time = len(nc_file.dimensions['Time']) 29 ngrid = len(nc_file.dimensions['physical_points']) 30 nslope = len(nc_file.dimensions['nslope']) 31 nb_str_max = len(nc_file.dimensions['nb_str_max']) 32 if igrid > ngrid or igrid < 1: 33 sys.exit('Asked grid point is not possible!') 34 if islope > nslope or islope < 1: 35 sys.exit('Asked slope number is not possible!') 36 if istr > nb_str_max or istr < 1: 37 sys.exit('Asked stratum number is not possible!') 38 39 ### Get the stratification properties 40 stratif_thickness = [] 41 stratif_top_elevation = [] 42 stratif_co2ice_volfrac = [] 43 stratif_h2oice_volfrac = [] 44 stratif_dust_volfrac = [] 45 stratif_pore_volfrac = [] 46 for i in range(1,nslope + 1): 47 stratif_thickness.append(nc_file.variables['stratif_slope' + str(i).zfill(2) + '_thickness'][:]) 48 stratif_top_elevation.append(nc_file.variables['stratif_slope' + str(i).zfill(2) + '_top_elevation'][:]) 49 stratif_co2ice_volfrac.append(nc_file.variables['stratif_slope' + str(i).zfill(2) + '_co2ice_volfrac'][:]) 50 stratif_h2oice_volfrac.append(nc_file.variables['stratif_slope' + str(i).zfill(2) + '_h2oice_volfrac'][:]) 51 stratif_dust_volfrac.append(nc_file.variables['stratif_slope' + str(i).zfill(2) + '_dust_volfrac'][:]) 52 stratif_pore_volfrac.append(nc_file.variables['stratif_slope' + str(i).zfill(2) + '_pore_volfrac'][:]) 53 54 ### Display the data 55 igrid = igrid - 1 56 islope = islope - 1 57 labels = 'CO2 ice', 'H2O ice', 'Dust', 'Pore' 58 contents = np.zeros([4,len(stratif_top_elevation[islope][0,:,igrid]) + 1]) 59 height = np.zeros(len(stratif_top_elevation[islope][0,:,igrid]) + 1) 60 height[0] = stratif_top_elevation[islope][0,0,:] - stratif_thickness[islope][0,0,:] 61 height[1:] = stratif_top_elevation[islope][0,:,igrid] 62 for i in range(len(stratif_top_elevation[islope][0,:,igrid])): 63 contents[0,1 + i] = stratif_co2ice_volfrac[islope][0,i,igrid] 64 contents[1,1 + i] = stratif_h2oice_volfrac[islope][0,i,igrid] 65 contents[2,1 + i] = stratif_dust_volfrac[islope][0,i,igrid] 66 contents[3,1 + i] = stratif_pore_volfrac[islope][0,i,igrid] 67 contents[:,0] = contents[:,1] 68 69 # Simple subplots for a layering 70 fig, (ax1, ax2, ax3, ax4) = plt.subplots(1,4,layout = 'constrained',sharey = True) 71 fig.suptitle('Simple content profiles for the layering') 72 ax1.step(contents[0,:],height,where = 'post') 73 ax2.step(contents[1,:],height,where = 'post') 74 ax3.step(contents[2,:],height,where = 'post') 75 ax4.step(contents[3,:],height,where = 'post') 76 ax1.set_ylabel('Elevation [m]') 77 ax1.set_xlabel('Volume fraction [m3/m3]') 78 ax2.set_xlabel('Volume fraction [m3/m3]') 79 ax3.set_xlabel('Volume fraction [m3/m3]') 80 ax4.set_xlabel('Volume fraction [m3/m3]') 81 ax1.set_title(labels[0]) 82 ax2.set_title(labels[1]) 83 ax3.set_title(labels[2]) 84 ax4.set_title(labels[3]) 85 plt.savefig('layering_simpleprofiles.png') 86 87 # Content profiles for a layering 88 plt.figure() 89 plt.step(contents[0,:],height,where = 'post',color = 'r',label = labels[0]) 90 #plt.plot(contents[0,:],height,'o--',color = 'r',alpha = 0.3) 91 plt.step(contents[1,:],height,where = 'post',color = 'b',label = labels[1]) 92 #plt.plot(contents[1,:],height,'o--',color = 'b',alpha = 0.3) 93 plt.step(contents[2,:],height,where = 'post',color = 'y',label = labels[2]) 94 #plt.plot(contents[2,:],height,'o--',color = 'y',alpha = 0.3) 95 plt.step(contents[3,:],height,where = 'post',color = 'g',label = labels[3]) 96 #plt.plot(contents[3,:],height,'o--',color = 'g',alpha = 0.3) 97 plt.grid(axis='x', color='0.95') 98 plt.grid(axis='y', color='0.95') 99 plt.xlim(0,1) 100 plt.xlabel('Volume fraction [m3/m3]') 101 plt.ylabel('Elevation [m]') 102 plt.title('Content profiles for the layering') 103 plt.savefig('layering_simpleprofiles.png') 104 105 # Stack content profiles for a layering 106 fig, ax = plt.subplots() 107 ax.fill_betweenx(height,0,contents[0,:],label = labels[0],color = 'r',step = 'pre') 108 ax.fill_betweenx(height,contents[0,:],sum(contents[0:2,:]),label = labels[1],color = 'b',step = 'pre') 109 ax.fill_betweenx(height,sum(contents[0:2,:]),sum(contents[0:3,:]),label = labels[2],color = 'y',step = 'pre') 110 ax.fill_betweenx(height,sum(contents[0:3,:]),sum(contents),label = labels[3],color = 'g',step = 'pre') 111 plt.vlines(x = 0.,ymin = height[0],ymax = height[len(height) - 1],color = 'k',linestyle = '-') 112 plt.vlines(x = 1.,ymin = height[0],ymax = height[len(height) - 1],color = 'k',linestyle = '-') 113 for i in range(len(height)): 114 plt.hlines(y = height[i],xmin = 0.0,xmax = 1.0,color = 'k',linestyle = '--') 115 ax.set_title('Stack content profiles for the layering') 116 plt.xlabel('Volume fraction [m3/m3]') 117 plt.ylabel('Elevation [m]') 118 ax.legend(loc = 'center left',bbox_to_anchor = (1,0.5)) 119 plt.savefig('layering_stackprofiles.png') 120 121 plt.show() 122 123 ### Close the NetCDF file 124 nc_file.close() 10 11 12 def get_int_input(prompt: str, min_val: int, max_val: int) -> int: 13 """ 14 Prompt the user for an integer between min_val and max_val (inclusive). 15 If min_val == max_val, return min_val without prompting. 16 """ 17 if min_val == max_val: 18 print(f"{prompt} (only possible value: {min_val})") 19 return min_val 20 21 while True: 22 try: 23 value = int(input(f"{prompt} (integer between {min_val} and {max_val}): ")) 24 if min_val <= value <= max_val: 25 return value 26 print(f"Invalid value! Please enter a number between {min_val} and {max_val}.") 27 except ValueError: 28 print("Invalid input. Please enter a valid integer.") 29 30 31 def get_yes_no_input(prompt: str) -> bool: 32 """ 33 Prompt the user with a yes/no question. Returns True for yes, False for no. 34 """ 35 while True: 36 choice = input(f"{prompt} (y/n): ").strip().lower() 37 if choice in ['y', 'yes']: 38 return True 39 elif choice in ['n', 'no']: 40 return False 41 else: 42 print("Please respond with y or n.") 43 44 45 def load_slope_variables(nc_dataset: nc.Dataset, slope_index: int) -> dict: 46 """ 47 Load all relevant stratification variables for a given slope index (0-based). 48 Returns a dict of NumPy arrays. 49 """ 50 idx_str = str(slope_index + 1).zfill(2) 51 vars_base = { 52 'top_elev': f"stratif_slope{idx_str}_top_elevation", 53 'h_co2': f"stratif_slope{idx_str}_h_co2ice", 54 'h_h2o': f"stratif_slope{idx_str}_h_h2oice", 55 'h_dust': f"stratif_slope{idx_str}_h_dust", 56 'h_air': f"stratif_slope{idx_str}_h_pore", 57 'volfrac': f"stratif_slope{idx_str}_icepore_volfrac", 58 } 59 60 data = {} 61 for key, var_name in vars_base.items(): 62 if var_name not in nc_dataset.variables: 63 sys.exit(f"Error: Variable '{var_name}' not found in the NetCDF file.") 64 data[key] = nc_dataset.variables[var_name][:] 65 return data 66 67 68 def calculate_contents(data: dict, grid_index: int, exclude_subsurface: bool) -> (np.ndarray, np.ndarray, np.ndarray): 69 """ 70 For a given slope's stratification data and a specified grid index (0-based), 71 compute the height array, layer thicknesses, and layer-wise volume fractions. 72 If exclude_subsurface is True, omit layers whose top elevation <= 0. 73 74 Returns: 75 - height: 1D array of elevations (length = number_of_layers + 1) 76 - contents: 2D array of shape (5, number_of_layers + 1), each row for a component 77 - thicknesses: 1D array of layer thicknesses (length = number_of_layers) 78 """ 79 top = data['top_elev'] 80 h_co2 = data['h_co2'] 81 h_h2o = data['h_h2o'] 82 h_dust = data['h_dust'] 83 h_air = data['h_air'] 84 volfrac = data['volfrac'] 85 86 layers = top.shape[1] 87 # Compute raw height and thickness arrays 88 raw_height = np.zeros(layers + 1) 89 raw_thickness = np.zeros(layers) 90 91 total_thickness0 = ( 92 h_co2[0, 0, grid_index] 93 + h_h2o[0, 0, grid_index] 94 + h_dust[0, 0, grid_index] 95 + h_air[0, 0, grid_index] 96 ) 97 raw_height[0] = top[0, 0, grid_index] - total_thickness0 98 raw_height[1:] = top[0, :, grid_index] 99 100 for i in range(layers): 101 raw_thickness[i] = ( 102 h_co2[0, i, grid_index] 103 + h_h2o[0, i, grid_index] 104 + h_dust[0, i, grid_index] 105 + h_air[0, i, grid_index] 106 ) 107 108 include_mask = np.ones(layers, dtype=bool) 109 if exclude_subsurface: 110 include_mask = raw_height[1:] > 0 111 if exclude_subsurface and not include_mask.any() and raw_height[0] <= 0: 112 sys.exit("Error: No layers remain above the surface (elevation > 0).") 113 114 filt_layers = np.where(include_mask)[0] 115 num_filt = len(filt_layers) 116 height = np.zeros(num_filt + 1) 117 thicknesses = np.zeros(num_filt) 118 119 if exclude_subsurface and raw_height[0] <= 0: 120 height[0] = raw_height[filt_layers[0] + 1] - raw_thickness[filt_layers[0]] 121 else: 122 height[0] = raw_height[0] 123 124 for idx, layer_idx in enumerate(filt_layers): 125 height[idx + 1] = raw_height[layer_idx + 1] 126 thicknesses[idx] = raw_thickness[layer_idx] 127 128 contents = np.zeros((5, num_filt + 1)) 129 for idx, layer_idx in enumerate(filt_layers): 130 thickness = raw_thickness[layer_idx] 131 if thickness <= 0: 132 continue 133 co2 = h_co2[0, layer_idx, grid_index] / thickness 134 h2o = h_h2o[0, layer_idx, grid_index] / thickness 135 dust = h_dust[0, layer_idx, grid_index] / thickness 136 air = h_air[0, layer_idx, grid_index] * (1.0 - volfrac[0, layer_idx, grid_index]) / thickness 137 pore = h_air[0, layer_idx, grid_index] * volfrac[0, layer_idx, grid_index] / thickness 138 contents[:, idx + 1] = [co2, h2o, dust, air, pore] 139 140 if num_filt > 0: 141 contents[:, 0] = contents[:, 1] 142 143 return height, contents, thicknesses 144 145 146 def plot_profiles(height: np.ndarray, contents: np.ndarray, thicknesses: np.ndarray, labels: tuple[str, ...]) -> None: 147 """ 148 Create and save plots: 149 1. Simple step profiles (each component in its own subplot) 150 2. Overlaid step profiles (all components on one plot) 151 3. Stacked fill-between profile with relative fractions 152 """ 153 n_components = contents.shape[0] 154 colors = ['r', 'b', 'y', 'violet', 'c'] 155 156 # 1. Simple subplots 157 fig, axes = plt.subplots(1, n_components, sharey=True, constrained_layout=True) 158 fig.suptitle('Simple Content Profiles for Stratification') 159 160 for idx, ax in enumerate(axes): 161 ax.step(contents[idx, :], height, where='post', color=colors[idx]) 162 ax.set_xlabel('Volume Fraction [m^3/m^3]') 163 ax.set_title(labels[idx]) 164 if idx == 0: 165 ax.set_ylabel('Elevation [m]') 166 # Add major and minor grids on y-axis 167 ax.grid(which='major', axis='y', linestyle='--', linewidth=0.5, color='0.8') 168 ax.minorticks_on() 169 ax.grid(which='minor', axis='y', linestyle=':', linewidth=0.3, color='0.9') 170 plt.savefig('layering_simple_profiles.png') 171 172 # 2. Overlaid step profiles 173 plt.figure() 174 for idx, label in enumerate(labels): 175 plt.step(contents[idx, :], height, where='post', color=colors[idx], label=label) 176 plt.grid(axis='x', color='0.95') 177 plt.grid(axis='y', color='0.95') 178 plt.minorticks_on() 179 plt.grid(which='minor', axis='y', linestyle=':', linewidth=0.3, color='0.9') 180 plt.xlim(0, 1) 181 plt.xlabel('Volume Fraction [m^3/m^3]') 182 plt.ylabel('Elevation [m]') 183 plt.title('Content Profiles for Stratification') 184 plt.legend() 185 plt.savefig('layering_overlaid_profiles.png') 186 187 # 3. Stacked fill-between profile with relative fractions 188 plt.figure() 189 cumulative = np.zeros_like(contents[0, :]) 190 for idx, label in enumerate(labels): 191 plt.fill_betweenx( 192 height, 193 cumulative, 194 cumulative + contents[idx, :], 195 step='pre', 196 label=label, 197 color=colors[idx] 198 ) 199 cumulative += contents[idx, :] 200 plt.vlines(x=0., ymin=height[0], ymax=height[-1], color='k', linestyle='-') 201 plt.vlines(x=1., ymin=height[0], ymax=height[-1], color='k', linestyle='-') 202 for h in height: 203 plt.hlines(y=h, xmin=0.0, xmax=1.0, color='k', linestyle='--', linewidth=0.5) 204 plt.xlabel('Volume Fraction [m^3/m^3]') 205 plt.ylabel('Elevation [m]') 206 plt.title('Stacked Content Profiles for Stratification') 207 plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) 208 plt.tight_layout() 209 plt.savefig('layering_stacked_profiles.png') 210 211 212 def plot_stratum_layer(contents: np.ndarray, istr_index: int, labels: tuple[str, ...]) -> None: 213 """ 214 Plot detailed composition of the specified stratum index (0-based) as a bar chart. 215 """ 216 layer_fractions = contents[:, istr_index + 1] 217 plt.figure() 218 x_positions = np.arange(len(labels)) 219 plt.bar(x_positions, layer_fractions, color=['r', 'b', 'y', 'violet', 'c']) 220 plt.xticks(x_positions, labels, rotation=45, ha='right') 221 plt.ylim(0, 1) 222 plt.ylabel('Volume Fraction [m^3/m^3]') 223 plt.title(f'Composition of Stratum {istr_index + 1}') 224 # Add major and minor grids on y-axis for the histogram 225 plt.grid(which='major', axis='y', linestyle='--', linewidth=0.5, color='0.8') 226 plt.minorticks_on() 227 plt.grid(which='minor', axis='y', linestyle=':', linewidth=0.3, color='0.9') 228 plt.tight_layout() 229 plt.savefig(f'stratum_{istr_index+1}_composition.png') 230 231 232 def main(): 233 if len(sys.argv) > 1: 234 filename = sys.argv[1] 235 else: 236 filename = input("Enter the NetCDF file name: ").strip() 237 238 if not os.path.isfile(filename): 239 sys.exit(f"Error: File '{filename}' does not exist.") 240 241 with nc.Dataset(filename, 'r') as dataset: 242 required_dims = ['Time', 'physical_points', 'nslope', 'nb_str_max'] 243 for dim in required_dims: 244 if dim not in dataset.dimensions: 245 sys.exit(f"Error: Missing dimension '{dim}' in file.") 246 247 ngrid = len(dataset.dimensions['physical_points']) 248 nslope = len(dataset.dimensions['nslope']) 249 nb_str_max = len(dataset.dimensions['nb_str_max']) 250 251 print(f"File '{filename}' opened successfully.") 252 print(f"Number of grid points (physical_points): {ngrid}") 253 print(f"Number of slopes: {nslope}") 254 print(f"Maximum number of strata per slope: {nb_str_max}") 255 256 igrid_input = get_int_input( 257 "Enter grid point number", 1, ngrid 258 ) - 1 259 islope_input = get_int_input( 260 "Enter slope number", 1, nslope 261 ) - 1 262 263 show_subsurface = get_yes_no_input("Show subsurface layers?") 264 exclude_sub = not show_subsurface 265 266 # Load data for the chosen slope to determine number of surface strata 267 slope_data = load_slope_variables(dataset, islope_input) 268 269 # Compute raw heights to count strata above surface 270 top = slope_data['top_elev'] 271 layers = top.shape[1] 272 raw_height = np.zeros(layers + 1) 273 # Compute initial subsurface bottom 274 h_co2 = slope_data['h_co2'] 275 h_h2o = slope_data['h_h2o'] 276 h_dust = slope_data['h_dust'] 277 h_air = slope_data['h_air'] 278 total_thickness0 = ( 279 h_co2[0, 0, igrid_input] 280 + h_h2o[0, 0, igrid_input] 281 + h_dust[0, 0, igrid_input] 282 + h_air[0, 0, igrid_input] 283 ) 284 raw_height[0] = top[0, 0, igrid_input] - total_thickness0 285 raw_height[1:] = top[0, :, igrid_input] 286 287 include_mask = np.ones(layers, dtype=bool) 288 if exclude_sub: 289 include_mask = raw_height[1:] > 0 290 # Count number of strata above surface 291 filt_layers = np.where(include_mask)[0] 292 nb_str_surf_max = len(filt_layers) 293 if not show_subsurface: 294 if nb_str_surf_max == 0: 295 print("No stratum layers remain after filtering. Cannot proceed.") 296 return 297 298 # Prompt for stratum index based on surface strata count 299 istr_input = get_int_input( 300 "Enter stratum index for detailed plot", 1, nb_str_surf_max if exclude_sub else nb_str_max 301 ) - 1 302 303 height_arr, contents_arr, thicknesses_arr = calculate_contents( 304 slope_data, igrid_input, exclude_sub 305 ) 306 307 component_labels = ("CO2 Ice", "H2O Ice", "Dust", "Air", "Pore ice") 308 309 plot_profiles(height_arr, contents_arr, thicknesses_arr, component_labels) 310 plot_stratum_layer(contents_arr, istr_input, component_labels) 311 312 # Show all figures at once 313 plt.show() 314 315 316 if __name__ == '__main__': 317 main()
Note: See TracChangeset
for help on using the changeset viewer.