FAOM API documentation
foam.plot_tools
Plotting functionality
1""" Plotting functionality """ 2 3from pathlib import Path 4 5import matplotlib.patches as patches 6import matplotlib.pyplot as plt 7import numpy as np 8import pandas as pd 9from matplotlib.colors import LinearSegmentedColormap 10from matplotlib.gridspec import GridSpec 11from matplotlib.ticker import AutoMinorLocator 12 13from foam import functions_for_mesa as ffm 14 15 16################################################################################ 17def make_multipanel_plot( 18 nr_panels=1, 19 xlabel="", 20 ylabels=[""], 21 keys=None, 22 title="", 23 label_size=22, 24 xlim=[], 25 left_space=0.1, 26 bottom_space=0.085, 27 right_space=0.978, 28 top_space=0.97, 29 h_space=0.12, 30 figure_size=[12, 8], 31): 32 """ 33 Make a multipanel figure for plots. 34 35 Parameters 36 ---------- 37 nr_panels: int 38 The number of panels to add to the plot 39 xlabel: string 40 Name for x axis. 41 ylabels: list of strings 42 Names for y axes. 43 keys: 44 The keys corresponding to the dictionary entries of the axes 45 title: string 46 Title of the plot, no title if argument not given. 47 left_space, bottom_space, right_space, top_space: float, optional 48 The space that needs to be left open around the figure. 49 h_space: float 50 The size of the space in between both panels. 51 figure_size: list of 2 floats 52 Specify the dimensions of the figure in inch. 53 label_size: float 54 The size of the labels on the axes, and slightly smaller on the ticks 55 xlim: list 56 Lists of length 2, specifying the lower and upper limits of the x axe. 57 Matplotlib sets the limits automatically if the arguments are not given. 58 59 Returns 60 ---------- 61 ax_dict: dict 62 Dictionary of the axes 63 fig: Figure 64 The figure 65 """ 66 if keys == None: # make keys integers 67 keys = range(nr_panels) 68 69 fig = plt.figure(figsize=(figure_size[0], figure_size[1])) 70 gs = GridSpec(nr_panels, 1) # multiple rows, 1 column 71 ax_dict = {} 72 73 for i in range(0, nr_panels): 74 if i == 0: 75 ax = fig.add_subplot(gs[i : i + 1, 0]) 76 if len(xlim) == 2: 77 ax.set_xlim(xlim[0], xlim[1]) 78 79 else: 80 ax = fig.add_subplot(gs[i : i + 1, 0], sharex=ax_dict[keys[0]]) 81 82 ax_dict.update({keys[i]: ax}) 83 84 ax.set_ylabel(ylabels[i], size=label_size) 85 ax.tick_params(labelsize=label_size - 2) 86 87 if i == nr_panels - 1: 88 ax.set_xlabel(xlabel, size=label_size) 89 else: 90 plt.setp(ax.get_xticklabels(), visible=False) 91 92 ax_dict[keys[0]].set_title(title) 93 plt.subplots_adjust(hspace=h_space, left=left_space, right=right_space, top=top_space, bottom=bottom_space) 94 95 return ax_dict, fig 96 97 98################################################################################ 99def corner_plot( 100 merit_values_file, 101 merit_values_file_error_ellipse, 102 fig_title, 103 observations_file, 104 label_size=20, 105 fig_output_dir="figures_correlation/", 106 percentile_to_show=0.5, 107 logg_or_logL="logL", 108 mark_best_model=False, 109 n_sigma_box=3, 110 grid_parameters=None, 111 axis_labels_dict={ 112 "rot": r"$\Omega_{\mathrm{rot}}$ [d$^{-1}$]", 113 "M": r"M$_{\rm ini}$", 114 "Z": r"Z$_{\rm ini}$", 115 "logD": r"log(D$_{\rm env}$)", 116 "aov": r"$\alpha_{\rm CBM}$", 117 "fov": r"f$_{\rm CBM}$", 118 "Xc": r"$\rm X_c$", 119 }, 120): 121 """ 122 Make a plot of all variables vs each other variable, showing the MLE values as colorscale. 123 A kiel/HR diagram is made, depending on if logg_obs or logL_obs is passed as a parameter. 124 The subplots on the diagonal show the distribution of that variable. 125 The list of variables is retrieved from columns of the merit_values_file, 126 where the first column is 'meritValue', which are the MLE values. 127 The resulting figure is saved afterwards in the specified location. 128 129 Parameters 130 ---------- 131 merit_values_file: string 132 Path to the hdf5 file with the merit function values and parameters of the models in the grid. 133 merit_values_file_error_ellipse: string 134 Path to the hdf5 files with the merit function values and parameters of the models in the error ellipse. 135 observations_file: string 136 Path to the tsv file with observations, with a column for each observable and each set of errors. 137 Column names specify the observable, and "_err" suffix denotes that it's the error. 138 fig_title: string 139 Title of the figure and name of the saved png. 140 label_size: int 141 Size of the axis labels. 142 fig_output_dir: string 143 Output directory for the figures. 144 percentile_to_show: float 145 Percentile of models to show in the plots. 146 logg_or_logL: string 147 String 'logg' or 'logL' indicating whether log of surface gravity (g) or luminosity (L) is plot. 148 mark_best_model: boolean 149 Indicate the best model with a marker 150 grid_parameters: list of string 151 List of the parameters in the theoretical grid. 152 axis_labels_dict: dictionary 153 Keys are grid parameters, values are strings how those values should be shown on the axis labels 154 """ 155 # Define custom colormap 156 cdict = { 157 "red": ((0.0, 1.0, 1.0), (0.5, 1.0, 1.0), (0.75, 1.0, 1.0), (1.0, 0.75, 0.75)), 158 "green": ((0.0, 1.0, 1.0), (0.25, 0.5, 0.5), (0.5, 0.0, 0.0), (1.0, 0.0, 0.0)), 159 "blue": ((0.0, 0.0, 0.0), (0.5, 0.0, 0.0), (1.0, 1.0, 1.0)), 160 } 161 CustomCMap = LinearSegmentedColormap("CustomMap", cdict) 162 # theoretical models within the error ellipse 163 dataframe_theory_error_ellipse = pd.read_hdf(merit_values_file_error_ellipse, sep="\s+", header=0) 164 dataframe_theory_error_ellipse = dataframe_theory_error_ellipse.sort_values( 165 "meritValue", ascending=False 166 ) # Order from high to low, to plot lowest values last 167 168 # theoretical models 169 dataframe_theory = pd.read_hdf(merit_values_file) 170 dataframe_theory = dataframe_theory.sort_values( 171 "meritValue", ascending=False 172 ) # Order from high to low, to plot lowest values last 173 dataframe_theory = dataframe_theory.iloc[ 174 int(dataframe_theory.shape[0] * (1 - percentile_to_show)) : 175 ] # only plot the given percentage lowest meritValues 176 177 if ( 178 dataframe_theory.iloc[0]["rot"] 179 == dataframe_theory.iloc[1]["rot"] 180 == dataframe_theory.iloc[2]["rot"] 181 == dataframe_theory.iloc[-1]["rot"] 182 ): # rotation is fixed, don't plot it 183 # make new dataframe with only needed info 184 df_error_ellipse = dataframe_theory_error_ellipse.filter(["meritValue"] + grid_parameters) 185 df = dataframe_theory.filter(["meritValue"] + grid_parameters) 186 # Remove models in the error ellipse from the regular dataframe. 187 df = ( 188 pd.merge( 189 df, 190 df_error_ellipse, 191 indicator=True, 192 how="outer", 193 on=grid_parameters, 194 suffixes=[None, "_remove"], 195 ) 196 .query('_merge=="left_only"') 197 .drop(["meritValue_remove", "_merge"], axis=1) 198 ) 199 200 else: # rotation was varied, include it in the plots 201 # make new dataframe with only needed info 202 df_error_ellipse = dataframe_theory_error_ellipse.filter(["meritValue"] + ["rot"] + grid_parameters) 203 df = dataframe_theory.filter(["meritValue"] + ["rot"] + grid_parameters) 204 # Remove models in the error ellipse from the regular dataframe. 205 df = ( 206 pd.merge( 207 df, 208 df_error_ellipse, 209 indicator=True, 210 how="outer", 211 on=grid_parameters, 212 suffixes=[None, "_remove"], 213 ) 214 .query('_merge=="left_only"') 215 .drop(["meritValue_remove", "rot_remove", "_merge"], axis=1) 216 ) 217 218 ax_dict = {} 219 # dictionary of dictionaries, holding the subplots of the figure, keys indicate position (row, column) of the subplot 220 nr_params = len(df.columns) - 1 221 for i in range(nr_params): 222 ax_dict.update({i: {}}) 223 224 fig = plt.figure(figsize=(10, 8)) 225 gs = GridSpec(nr_params, nr_params) # multiple rows and columns 226 227 if mark_best_model: 228 # get the best model according to the point estimator 229 min_index = df_error_ellipse["meritValue"].idxmin(axis="index", skipna=True) 230 231 for ix in range(0, nr_params): 232 for iy in range(0, nr_params - ix): 233 if iy == 0: 234 share_x = None 235 else: 236 share_x = ax_dict[0][ix] 237 if (ix == 0) or (iy + ix == nr_params - 1): 238 share_y = None 239 else: 240 share_y = ax_dict[iy][0] 241 242 # create subplots and add them to the dictionary 243 ax = fig.add_subplot(gs[nr_params - iy - 1 : nr_params - iy, ix : ix + 1], sharex=share_x, sharey=share_y) 244 ax_dict[iy].update({ix: ax}) 245 246 # manage visibility and size of the labels and ticks 247 ax.tick_params(labelsize=label_size - 4) 248 if ix == 0: 249 ax.set_ylabel(axis_labels_dict[df.columns[iy + 1]], size=label_size) 250 if iy == nr_params - 1: 251 plt.setp(ax.get_yticklabels(), visible=False) 252 else: 253 plt.setp(ax.get_yticklabels(), visible=False) 254 if iy == 0: 255 ax.set_xlabel(axis_labels_dict[df.columns[nr_params - ix]], size=label_size) 256 ax.tick_params(axis="x", rotation=45) 257 else: 258 plt.setp(ax.get_xticklabels(), visible=False) 259 260 if iy + ix == nr_params - 1: # make distribution plots on the diagonal subplots 261 values = sorted(np.unique(df.iloc[:, nr_params - ix])) 262 # determine edges of the bins for the histogram distribution plots 263 if df.columns[nr_params - ix] == "rot": 264 domain = (values[0], values[-1]) 265 ax.hist( 266 df_error_ellipse.iloc[:, nr_params - ix], 267 bins=25, 268 range=domain, 269 density=False, 270 cumulative=False, 271 histtype="step", 272 ) 273 274 else: 275 if len(values) > 1: 276 bin_half_width = (values[0] + values[1]) / 2 - values[0] 277 else: 278 bin_half_width = 1e-3 279 bin_edges = [values[0] - bin_half_width] 280 for i in range(len(values) - 1): 281 bin_edges.extend([(values[i] + values[i + 1]) / 2]) 282 bin_edges.extend([values[-1] + bin_half_width]) 283 ax.hist( 284 df_error_ellipse.iloc[:, nr_params - ix], 285 bins=bin_edges, 286 density=False, 287 cumulative=False, 288 histtype="step", 289 ) 290 291 ax.tick_params(axis="y", left=False) 292 continue 293 294 im = ax.scatter( 295 df.iloc[:, nr_params - ix], 296 df.iloc[:, iy + 1], 297 c=np.log10(df.iloc[:, 0]), 298 cmap="Greys_r", 299 ) 300 im = ax.scatter( 301 df_error_ellipse.iloc[:, nr_params - ix], 302 df_error_ellipse.iloc[:, iy + 1], 303 c=np.log10(dataframe_theory_error_ellipse["meritValue"]), 304 cmap=CustomCMap, 305 ) 306 if mark_best_model: 307 ax.scatter( 308 df_error_ellipse.loc[min_index][nr_params - ix], 309 df.loc[min_index][iy + 1], 310 color="white", 311 marker="x", 312 ) 313 # Adjust x an y limits of subplots 314 limit_adjust = (max(df.iloc[:, iy + 1]) - min(df.iloc[:, iy + 1])) * 0.08 315 if limit_adjust == 0: 316 limit_adjust = 0.1 317 ax.set_ylim(min(df.iloc[:, iy + 1]) - limit_adjust, max(df.iloc[:, iy + 1]) + limit_adjust) 318 limit_adjust = (max(df.iloc[:, nr_params - ix]) - min(df.iloc[:, nr_params - ix])) * 0.08 319 if limit_adjust == 0: 320 limit_adjust = 0.1 321 ax.set_xlim( 322 min(df.iloc[:, nr_params - ix]) - limit_adjust, 323 max(df.iloc[:, nr_params - ix]) + limit_adjust, 324 ) 325 326 fig.align_labels() 327 # add subplot in top right for Kiel or HRD 328 ax_hrd = fig.add_axes([0.508, 0.65, 0.33, 0.33]) # X, Y, width, height 329 330 ax_hrd.set_xlabel(r"log(T$_{\mathrm{eff}}$ [K])", size=label_size) 331 ax_hrd.tick_params(labelsize=label_size - 4) 332 ax_hrd.invert_xaxis() 333 334 # Observations 335 if n_sigma_box != None: 336 obs_dataframe = pd.read_table(observations_file, sep="\s+", header=0, index_col="index") 337 if (("logL" in obs_dataframe.columns) or ("logg" in obs_dataframe.columns)) and ( 338 "Teff" in obs_dataframe.columns 339 ): 340 if "logL" not in obs_dataframe.columns: 341 logg_or_logL = "logg" 342 343 # Observed spectroscopic error bar, only added if observational constraints were provided. 344 # To add the 1 and n-sigma spectro error boxes, calculate their width (so 2 and 2*n sigma wide) 345 width_logTeff_sigma = np.log10( 346 obs_dataframe["Teff"].iloc[0] + obs_dataframe["Teff_err"].iloc[0] 347 ) - np.log10(obs_dataframe["Teff"].iloc[0] - obs_dataframe["Teff_err"].iloc[0]) 348 width_logTeff_nsigma = np.log10( 349 obs_dataframe["Teff"].iloc[0] + n_sigma_box * obs_dataframe["Teff_err"].iloc[0] 350 ) - np.log10(obs_dataframe["Teff"].iloc[0] - n_sigma_box * obs_dataframe["Teff_err"].iloc[0]) 351 errorbox_1s = patches.Rectangle( 352 ( 353 np.log10(obs_dataframe["Teff"].iloc[0] - obs_dataframe["Teff_err"].iloc[0]), 354 obs_dataframe[logg_or_logL].iloc[0] - obs_dataframe[f"{logg_or_logL}_err"].iloc[0], 355 ), 356 width_logTeff_sigma, 357 2 * obs_dataframe[f"{logg_or_logL}_err"].iloc[0], 358 linewidth=1.7, 359 edgecolor="cyan", 360 facecolor="none", 361 zorder=2.1, 362 ) 363 errorbox_ns = patches.Rectangle( 364 ( 365 np.log10(obs_dataframe["Teff"].iloc[0] - n_sigma_box * obs_dataframe["Teff_err"].iloc[0]), 366 obs_dataframe[logg_or_logL].iloc[0] - n_sigma_box * obs_dataframe[f"{logg_or_logL}_err"].iloc[0], 367 ), 368 width_logTeff_nsigma, 369 2 * n_sigma_box * obs_dataframe[f"{logg_or_logL}_err"].iloc[0], 370 linewidth=1.7, 371 edgecolor="cyan", 372 facecolor="none", 373 zorder=2.1, 374 ) 375 ax_hrd.add_patch(errorbox_1s) 376 ax_hrd.add_patch(errorbox_ns) 377 378 if logg_or_logL == "logg": 379 ax_hrd.invert_yaxis() 380 381 im = ax_hrd.scatter( 382 dataframe_theory["logTeff"], 383 dataframe_theory[logg_or_logL], 384 c=np.log10(dataframe_theory["meritValue"]), 385 cmap="Greys_r", 386 ) 387 im_error_ellipse = ax_hrd.scatter( 388 dataframe_theory_error_ellipse["logTeff"], 389 dataframe_theory_error_ellipse[logg_or_logL], 390 c=np.log10(dataframe_theory_error_ellipse["meritValue"]), 391 cmap=CustomCMap, 392 ) 393 ax_hrd.set_ylabel(f"{logg_or_logL[:-1]} {logg_or_logL[-1]}") 394 if logg_or_logL == "logL": 395 ax_hrd.set_ylabel(r"log(L/L$_{\odot}$)", size=label_size) 396 elif logg_or_logL == "logg": 397 ax_hrd.set_ylabel(r"$\log\,g$ [dex]", size=label_size) 398 399 ax_hrd.xaxis.set_minor_locator(AutoMinorLocator(2)) 400 ax_hrd.yaxis.set_minor_locator(AutoMinorLocator(2)) 401 ax_hrd.tick_params(which="major", length=6) 402 ax_hrd.tick_params(which="minor", length=4) 403 404 if mark_best_model: 405 ax_hrd.scatter( 406 dataframe_theory_error_ellipse["logTeff"][min_index], 407 dataframe_theory_error_ellipse[logg_or_logL][min_index], 408 marker="x", 409 color="white", 410 ) 411 412 # Add color bar 413 cax = fig.add_axes([0.856, 0.565, 0.04, 0.415]) # X, Y, width, height 414 cbar = fig.colorbar(im, cax=cax, orientation="vertical") 415 cax2 = fig.add_axes([0.856, 0.137, 0.04, 0.415]) # X, Y, width, height 416 cbar2 = fig.colorbar( 417 im_error_ellipse, 418 cax=cax2, 419 orientation="vertical", 420 ) 421 422 # To prevent messing up colours due to automatic rescaling of colorbar 423 if dataframe_theory_error_ellipse.shape[0] == 1: 424 im_error_ellipse.set_clim( 425 np.log10(dataframe_theory_error_ellipse["meritValue"]), 426 np.log10(dataframe_theory_error_ellipse["meritValue"]) * 1.1, 427 ) 428 429 if "_MD_" in fig_title: 430 cbar.set_label("log(MD)", rotation=90, size=label_size) 431 cbar2.set_label("log(MD)", rotation=90, size=label_size) 432 elif "_CS_" in fig_title: 433 cbar.set_label(r"log($\chi^2$)", rotation=90, size=label_size) 434 cbar2.set_label(r"log($\chi^2$)", rotation=90, size=label_size) 435 else: 436 cbar.set_label("log(merit function value)", rotation=90) 437 cbar.ax.tick_params(labelsize=label_size - 4) 438 cbar2.ax.tick_params(labelsize=label_size - 4) 439 fig.subplots_adjust(left=0.114, right=0.835, bottom=0.137, top=0.99) 440 441 # fig.suptitle(fig_title, horizontalalignment='left', size=20, x=0.28) 442 Path(fig_output_dir).mkdir(parents=True, exist_ok=True) 443 fig.savefig(f"{fig_output_dir}{fig_title}.png", dpi=400) 444 plt.clf() 445 plt.close(fig) 446 447 448################################################################################ 449def plot_mesa_file( 450 profile_file, 451 x_value, 452 y_value, 453 ax=None, 454 label_size=16, 455 colour="", 456 linestyle="solid", 457 alpha=1, 458 legend=True, 459 label=None, 460): 461 """ 462 Plot the requested quantities for the given MESA profile or history file. 463 464 Parameters 465 ---------- 466 profile_file: string 467 The path to the profile file to be used for the plotting. 468 x_value: string 469 The parameter of the profile plotted on the x axis. 470 If x_value is mass or radius, it will be put in units relative to the total mass or radius 471 y_value: string 472 The parameter of the profile plotted on the y axis. 473 ax: Axes 474 Axes object on which the plot will be made. If None: make figure and axis within this function. 475 label_size, alpha: float 476 The size of the labels in the figure, and transparency of the plot 477 colour: string 478 Colour of the plotted data. 479 linestyle: string 480 Linestyle of the plotted data. 481 label: string 482 Label of the plotted data. 483 legend: boolean 484 Flag to enable or disable a legend on the figure 485 """ 486 if ax is None: 487 fig = plt.figure() 488 ax = fig.add_subplot(111) 489 490 header, data = ffm.read_mesa_file(profile_file) 491 # from "data", extract the columns 492 y = np.asarray(data[y_value]) 493 x = np.asarray(data[x_value]) 494 if label == None: # Set the label to be the name of the y variable 495 label = y_value 496 if x_value == "radius" or x_value == "mass": 497 x = x / x[0] # normalized radius/mass coordinates 498 ax.set_xlim(0, 1) 499 # generate the plot, in which colour will not be specified 500 if colour == "": 501 ax.plot(x, y, label=label, linestyle=linestyle, alpha=alpha) 502 # generate the plot, in which colour will be specified 503 else: 504 ax.plot(x, y, label=label, linestyle=linestyle, alpha=alpha, color=colour) 505 if legend is True: 506 ax.legend(loc="best", prop={"size": label_size}) 507 ax.set_xlabel(x_value, size=label_size) 508 ax.set_ylabel(y_value, size=label_size) 509 510 511################################################################################ 512def plot_mesh_histogram( 513 profile_file, 514 x_value="radius", 515 ax=None, 516 label_size=16, 517 colour="", 518 linestyle="solid", 519 alpha=1, 520 legend=True, 521 label=None, 522 bins=200, 523): 524 """ 525 Make a histogram of the mesh points in the MESA profile. 526 527 Parameters 528 ---------- 529 profile_file: string 530 The path to the profile file to be used for the plotting. 531 x_value: string 532 The x value to use for the histogram 533 If x_value is mass or radius, it will be put in units relative to the total mass or radius 534 ax: an axis object 535 Axes object on which the plot will be made. If None: make figure and axis within this function. 536 label_size: float 537 The size of the labels in the figure. 538 alpha: float 539 Transparency of the plot. 540 colour, linestyle, label: float 541 Settings for the plot 542 legend: boolean 543 Flag to enable or disable a legend on the figure. 544 bins: int 545 Number of bins used in the histogram 546 """ 547 if ax is None: 548 fig = plt.figure() 549 ax = fig.add_subplot(111) 550 551 header, data = ffm.read_mesa_file(profile_file) 552 print(f'Total zones of {profile_file} : {header["num_zones"]}') 553 554 # from "data", extract the columns 555 x = np.asarray(data[x_value]) 556 if label == None: # Set the label to be the name of the y variable 557 legend = False 558 if x_value == "radius" or x_value == "mass": 559 x = x / x[0] # normalized radius/mass coordinates 560 ax.set_xlim(0, 1) 561 # generate the plot, in which colour will not be specified 562 if colour == "": 563 ax.hist(x, bins=bins, histtype="step", label=label, alpha=alpha, linestyle=linestyle) 564 # generate the plot, in which colour will be specified 565 else: 566 ax.hist( 567 x, 568 bins=bins, 569 histtype="step", 570 label=label, 571 alpha=alpha, 572 linestyle=linestyle, 573 color=colour, 574 ) 575 # generate a legend if true 576 if legend is True: 577 ax.legend(loc="best", prop={"size": label_size}) 578 ax.set_xlabel(x_value, size=label_size) 579 ax.set_ylabel("Meshpoints", size=label_size) 580 581 582################################################################################ 583def plot_hrd( 584 hist_file, 585 ax=None, 586 colour="blue", 587 linestyle="solid", 588 label="", 589 label_size=16, 590 Xc_marked=None, 591 Teff_logscale=True, 592 start_track_from_Xc=None, 593 diagram="HRD", 594): 595 """ 596 Makes an HRD plot from a provided MESA history file. 597 598 Parameters 599 ---------- 600 hist_file: string 601 The path to the profile file to be used for the plot. 602 ax: Axes 603 Axes object on which the plot will be made. If None: make figure and axis within this function. 604 colour: string 605 Colour of the plotted data. 606 linestyle: string 607 Linestyle of the plotted data. 608 label: string 609 Label of the plotted data. 610 label_size: float 611 The size of the labels in the figure. 612 Xc_marked: list of floats 613 Models with these Xc values are marked with red dots on the plot (listed in increasing value). 614 Teff_logscale: boolean 615 Plot effective temperature in logscale (True), or not (False). 616 start_track_from_Xc: float 617 Only start plotting the track if Xc drops below this value (e.g. to not plot the initial relaxation loop). 618 diagram: string 619 Type of diagram that is plotted. Options are HRD (logL vs logTeff), sHRD (log(Teff^4/g) vs logTeff) or kiel (logg vs logTeff). 620 """ 621 if ax is None: 622 fig = plt.figure() 623 ax = fig.add_subplot(111) 624 625 header, data = ffm.read_mesa_file(hist_file) 626 627 # From "data", extract the required columns as numpy arrays 628 log_L = np.asarray(data["log_L"]) 629 log_Teff = np.asarray(data["log_Teff"]) 630 log_g = np.asarray(data["log_g"]) 631 center_h1 = np.asarray(data["center_h1"]) 632 # Plot the x-axis in log scale 633 if Teff_logscale: 634 T = log_Teff 635 ax.set_xlabel(r"log(T$_{\mathrm{eff}}$)", size=label_size) 636 # Plot the x-axis in linear scale 637 else: 638 T = 10**log_Teff 639 ax.set_xlabel(r"T$_{\mathrm{eff}}$ [K]", size=label_size) 640 641 # Plot HRD 642 if diagram == "HRD": 643 y_axis = log_L 644 ax.set_ylabel(r"log(L/L$_{\odot}$)", size=label_size) 645 # Plot sHRD (log_Teff^4/log_g vs log_Teff) 646 elif diagram == "sHRD": 647 log_Lsun = 10.61 648 y_axis = 4 * log_Teff - log_g - log_Lsun 649 ax.set_ylabel( 650 r"$\log \left(\frac{{T_{\mathrm{eff}}}^4}{g}\right) \ (\mathscr{L}_\odot)$", 651 size=label_size, 652 ) 653 # Plot Kiel diagram (log_g vs log_Teff) 654 elif diagram == "kiel": 655 y_axis = log_g 656 ax.set_ylabel(r"log g [dex]", size=label_size) 657 658 # Start plotting from Xc value 659 if start_track_from_Xc != None: 660 for i in range(len(center_h1)): 661 if center_h1[i] < start_track_from_Xc: 662 T = T[i:] 663 y_axis = y_axis[i:] 664 break 665 666 # Plot the HRD diagram (log_L vs. T) 667 ax.plot(T, y_axis, color=colour, linestyle=linestyle, label=label) 668 669 # Put specific marks on the HRD diagram 670 if Xc_marked is None: 671 return 672 k = 0 673 for i in range(len(center_h1) - 1, -1, -1): 674 if center_h1[i] > Xc_marked[k]: 675 ax.scatter(T[i], log_L[i], marker="o", color="red", lw=2) 676 k += 1 677 if k >= len(Xc_marked): 678 return 679 680 681################################################################################ 682def plot_khd(hist_file, ax=None, number_mix_zones=8, xaxis="model_number"): 683 """ 684 Makes a Kippenhahn plot from a provided MESA history file. 685 686 Parameters 687 ---------- 688 hist_file: string 689 The path to the history file to be used for the plot. 690 ax: Axes 691 Axes object on which the plot will be made. If None: make figure and axis within this function. 692 number_mix_zones: int 693 Number of mixing zones included in the mesa history file. 694 xaxis: string 695 Quantity to put on the x-axis of the plot (e.g. model_number or star_age). 696 """ 697 if ax is None: 698 fig = plt.figure(figsize=(10, 4)) 699 ax = fig.add_subplot(111) 700 701 _, data = ffm.read_mesa_file(hist_file) 702 703 x_values = data[xaxis] 704 m_star = data["star_mass"] 705 m_ini = m_star[0] 706 707 for j in range(number_mix_zones): 708 colours = { 709 "-1": "w", 710 "0": "w", 711 "1": "lightgrey", 712 "2": "b", 713 "7": "g", 714 "3": "cornflowerblue", 715 "8": "red", 716 } 717 if j == number_mix_zones - 1: 718 ax.vlines( 719 x_values, 720 0, 721 data[f"mix_qtop_{number_mix_zones-j}"] * m_star / m_ini, 722 color=[colours[str(x)] for x in data[f"mix_type_{number_mix_zones-j}"]], 723 ) 724 else: 725 ax.vlines( 726 x_values, 727 data[f"mix_qtop_{number_mix_zones-1-j}"] * m_star / m_ini, 728 data[f"mix_qtop_{number_mix_zones-j}"] * m_star / m_ini, 729 color=[colours[str(x)] for x in data[f"mix_type_{number_mix_zones-j}"]], 730 ) 731 732 ax.plot(x_values, m_star / m_ini, lw=1, color="black", label=f"{m_ini:.1f} $M_\odot$") 733 734 ax.set_xlim(min(x_values) * 0.99, max(x_values) * 1.01) 735 ax.set_ylim(0, 1.02) 736 737 # Only to make it appear in the Legend 738 ax.plot([], [], lw=10, color="lightgrey", label=r"Convective") 739 # Only to make it appear in the Legend 740 ax.plot([], [], lw=10, color="cornflowerblue", label=r"CBM") 741 ax.legend( 742 bbox_to_anchor=(0.15, 0.97), 743 fontsize=10, 744 frameon=False, 745 fancybox=False, 746 shadow=False, 747 borderpad=False, 748 ) 749 ax.set_ylabel(r"Relative Mass $\, m/M_\star$") 750 ax.set_xlabel(xaxis) 751 plt.tight_layout() 752 753 return 754 755 756################################################################################
18def make_multipanel_plot( 19 nr_panels=1, 20 xlabel="", 21 ylabels=[""], 22 keys=None, 23 title="", 24 label_size=22, 25 xlim=[], 26 left_space=0.1, 27 bottom_space=0.085, 28 right_space=0.978, 29 top_space=0.97, 30 h_space=0.12, 31 figure_size=[12, 8], 32): 33 """ 34 Make a multipanel figure for plots. 35 36 Parameters 37 ---------- 38 nr_panels: int 39 The number of panels to add to the plot 40 xlabel: string 41 Name for x axis. 42 ylabels: list of strings 43 Names for y axes. 44 keys: 45 The keys corresponding to the dictionary entries of the axes 46 title: string 47 Title of the plot, no title if argument not given. 48 left_space, bottom_space, right_space, top_space: float, optional 49 The space that needs to be left open around the figure. 50 h_space: float 51 The size of the space in between both panels. 52 figure_size: list of 2 floats 53 Specify the dimensions of the figure in inch. 54 label_size: float 55 The size of the labels on the axes, and slightly smaller on the ticks 56 xlim: list 57 Lists of length 2, specifying the lower and upper limits of the x axe. 58 Matplotlib sets the limits automatically if the arguments are not given. 59 60 Returns 61 ---------- 62 ax_dict: dict 63 Dictionary of the axes 64 fig: Figure 65 The figure 66 """ 67 if keys == None: # make keys integers 68 keys = range(nr_panels) 69 70 fig = plt.figure(figsize=(figure_size[0], figure_size[1])) 71 gs = GridSpec(nr_panels, 1) # multiple rows, 1 column 72 ax_dict = {} 73 74 for i in range(0, nr_panels): 75 if i == 0: 76 ax = fig.add_subplot(gs[i : i + 1, 0]) 77 if len(xlim) == 2: 78 ax.set_xlim(xlim[0], xlim[1]) 79 80 else: 81 ax = fig.add_subplot(gs[i : i + 1, 0], sharex=ax_dict[keys[0]]) 82 83 ax_dict.update({keys[i]: ax}) 84 85 ax.set_ylabel(ylabels[i], size=label_size) 86 ax.tick_params(labelsize=label_size - 2) 87 88 if i == nr_panels - 1: 89 ax.set_xlabel(xlabel, size=label_size) 90 else: 91 plt.setp(ax.get_xticklabels(), visible=False) 92 93 ax_dict[keys[0]].set_title(title) 94 plt.subplots_adjust(hspace=h_space, left=left_space, right=right_space, top=top_space, bottom=bottom_space) 95 96 return ax_dict, fig
Make a multipanel figure for plots.
Parameters
- nr_panels (int): The number of panels to add to the plot
- xlabel (string): Name for x axis.
- ylabels (list of strings): Names for y axes.
- keys:: The keys corresponding to the dictionary entries of the axes
- title (string): Title of the plot, no title if argument not given.
- left_space, bottom_space, right_space, top_space (float, optional): The space that needs to be left open around the figure.
- h_space (float): The size of the space in between both panels.
- figure_size (list of 2 floats): Specify the dimensions of the figure in inch.
- label_size (float): The size of the labels on the axes, and slightly smaller on the ticks
- xlim (list): Lists of length 2, specifying the lower and upper limits of the x axe. Matplotlib sets the limits automatically if the arguments are not given.
Returns
- ax_dict (dict): Dictionary of the axes
- fig (Figure): The figure
100def corner_plot( 101 merit_values_file, 102 merit_values_file_error_ellipse, 103 fig_title, 104 observations_file, 105 label_size=20, 106 fig_output_dir="figures_correlation/", 107 percentile_to_show=0.5, 108 logg_or_logL="logL", 109 mark_best_model=False, 110 n_sigma_box=3, 111 grid_parameters=None, 112 axis_labels_dict={ 113 "rot": r"$\Omega_{\mathrm{rot}}$ [d$^{-1}$]", 114 "M": r"M$_{\rm ini}$", 115 "Z": r"Z$_{\rm ini}$", 116 "logD": r"log(D$_{\rm env}$)", 117 "aov": r"$\alpha_{\rm CBM}$", 118 "fov": r"f$_{\rm CBM}$", 119 "Xc": r"$\rm X_c$", 120 }, 121): 122 """ 123 Make a plot of all variables vs each other variable, showing the MLE values as colorscale. 124 A kiel/HR diagram is made, depending on if logg_obs or logL_obs is passed as a parameter. 125 The subplots on the diagonal show the distribution of that variable. 126 The list of variables is retrieved from columns of the merit_values_file, 127 where the first column is 'meritValue', which are the MLE values. 128 The resulting figure is saved afterwards in the specified location. 129 130 Parameters 131 ---------- 132 merit_values_file: string 133 Path to the hdf5 file with the merit function values and parameters of the models in the grid. 134 merit_values_file_error_ellipse: string 135 Path to the hdf5 files with the merit function values and parameters of the models in the error ellipse. 136 observations_file: string 137 Path to the tsv file with observations, with a column for each observable and each set of errors. 138 Column names specify the observable, and "_err" suffix denotes that it's the error. 139 fig_title: string 140 Title of the figure and name of the saved png. 141 label_size: int 142 Size of the axis labels. 143 fig_output_dir: string 144 Output directory for the figures. 145 percentile_to_show: float 146 Percentile of models to show in the plots. 147 logg_or_logL: string 148 String 'logg' or 'logL' indicating whether log of surface gravity (g) or luminosity (L) is plot. 149 mark_best_model: boolean 150 Indicate the best model with a marker 151 grid_parameters: list of string 152 List of the parameters in the theoretical grid. 153 axis_labels_dict: dictionary 154 Keys are grid parameters, values are strings how those values should be shown on the axis labels 155 """ 156 # Define custom colormap 157 cdict = { 158 "red": ((0.0, 1.0, 1.0), (0.5, 1.0, 1.0), (0.75, 1.0, 1.0), (1.0, 0.75, 0.75)), 159 "green": ((0.0, 1.0, 1.0), (0.25, 0.5, 0.5), (0.5, 0.0, 0.0), (1.0, 0.0, 0.0)), 160 "blue": ((0.0, 0.0, 0.0), (0.5, 0.0, 0.0), (1.0, 1.0, 1.0)), 161 } 162 CustomCMap = LinearSegmentedColormap("CustomMap", cdict) 163 # theoretical models within the error ellipse 164 dataframe_theory_error_ellipse = pd.read_hdf(merit_values_file_error_ellipse, sep="\s+", header=0) 165 dataframe_theory_error_ellipse = dataframe_theory_error_ellipse.sort_values( 166 "meritValue", ascending=False 167 ) # Order from high to low, to plot lowest values last 168 169 # theoretical models 170 dataframe_theory = pd.read_hdf(merit_values_file) 171 dataframe_theory = dataframe_theory.sort_values( 172 "meritValue", ascending=False 173 ) # Order from high to low, to plot lowest values last 174 dataframe_theory = dataframe_theory.iloc[ 175 int(dataframe_theory.shape[0] * (1 - percentile_to_show)) : 176 ] # only plot the given percentage lowest meritValues 177 178 if ( 179 dataframe_theory.iloc[0]["rot"] 180 == dataframe_theory.iloc[1]["rot"] 181 == dataframe_theory.iloc[2]["rot"] 182 == dataframe_theory.iloc[-1]["rot"] 183 ): # rotation is fixed, don't plot it 184 # make new dataframe with only needed info 185 df_error_ellipse = dataframe_theory_error_ellipse.filter(["meritValue"] + grid_parameters) 186 df = dataframe_theory.filter(["meritValue"] + grid_parameters) 187 # Remove models in the error ellipse from the regular dataframe. 188 df = ( 189 pd.merge( 190 df, 191 df_error_ellipse, 192 indicator=True, 193 how="outer", 194 on=grid_parameters, 195 suffixes=[None, "_remove"], 196 ) 197 .query('_merge=="left_only"') 198 .drop(["meritValue_remove", "_merge"], axis=1) 199 ) 200 201 else: # rotation was varied, include it in the plots 202 # make new dataframe with only needed info 203 df_error_ellipse = dataframe_theory_error_ellipse.filter(["meritValue"] + ["rot"] + grid_parameters) 204 df = dataframe_theory.filter(["meritValue"] + ["rot"] + grid_parameters) 205 # Remove models in the error ellipse from the regular dataframe. 206 df = ( 207 pd.merge( 208 df, 209 df_error_ellipse, 210 indicator=True, 211 how="outer", 212 on=grid_parameters, 213 suffixes=[None, "_remove"], 214 ) 215 .query('_merge=="left_only"') 216 .drop(["meritValue_remove", "rot_remove", "_merge"], axis=1) 217 ) 218 219 ax_dict = {} 220 # dictionary of dictionaries, holding the subplots of the figure, keys indicate position (row, column) of the subplot 221 nr_params = len(df.columns) - 1 222 for i in range(nr_params): 223 ax_dict.update({i: {}}) 224 225 fig = plt.figure(figsize=(10, 8)) 226 gs = GridSpec(nr_params, nr_params) # multiple rows and columns 227 228 if mark_best_model: 229 # get the best model according to the point estimator 230 min_index = df_error_ellipse["meritValue"].idxmin(axis="index", skipna=True) 231 232 for ix in range(0, nr_params): 233 for iy in range(0, nr_params - ix): 234 if iy == 0: 235 share_x = None 236 else: 237 share_x = ax_dict[0][ix] 238 if (ix == 0) or (iy + ix == nr_params - 1): 239 share_y = None 240 else: 241 share_y = ax_dict[iy][0] 242 243 # create subplots and add them to the dictionary 244 ax = fig.add_subplot(gs[nr_params - iy - 1 : nr_params - iy, ix : ix + 1], sharex=share_x, sharey=share_y) 245 ax_dict[iy].update({ix: ax}) 246 247 # manage visibility and size of the labels and ticks 248 ax.tick_params(labelsize=label_size - 4) 249 if ix == 0: 250 ax.set_ylabel(axis_labels_dict[df.columns[iy + 1]], size=label_size) 251 if iy == nr_params - 1: 252 plt.setp(ax.get_yticklabels(), visible=False) 253 else: 254 plt.setp(ax.get_yticklabels(), visible=False) 255 if iy == 0: 256 ax.set_xlabel(axis_labels_dict[df.columns[nr_params - ix]], size=label_size) 257 ax.tick_params(axis="x", rotation=45) 258 else: 259 plt.setp(ax.get_xticklabels(), visible=False) 260 261 if iy + ix == nr_params - 1: # make distribution plots on the diagonal subplots 262 values = sorted(np.unique(df.iloc[:, nr_params - ix])) 263 # determine edges of the bins for the histogram distribution plots 264 if df.columns[nr_params - ix] == "rot": 265 domain = (values[0], values[-1]) 266 ax.hist( 267 df_error_ellipse.iloc[:, nr_params - ix], 268 bins=25, 269 range=domain, 270 density=False, 271 cumulative=False, 272 histtype="step", 273 ) 274 275 else: 276 if len(values) > 1: 277 bin_half_width = (values[0] + values[1]) / 2 - values[0] 278 else: 279 bin_half_width = 1e-3 280 bin_edges = [values[0] - bin_half_width] 281 for i in range(len(values) - 1): 282 bin_edges.extend([(values[i] + values[i + 1]) / 2]) 283 bin_edges.extend([values[-1] + bin_half_width]) 284 ax.hist( 285 df_error_ellipse.iloc[:, nr_params - ix], 286 bins=bin_edges, 287 density=False, 288 cumulative=False, 289 histtype="step", 290 ) 291 292 ax.tick_params(axis="y", left=False) 293 continue 294 295 im = ax.scatter( 296 df.iloc[:, nr_params - ix], 297 df.iloc[:, iy + 1], 298 c=np.log10(df.iloc[:, 0]), 299 cmap="Greys_r", 300 ) 301 im = ax.scatter( 302 df_error_ellipse.iloc[:, nr_params - ix], 303 df_error_ellipse.iloc[:, iy + 1], 304 c=np.log10(dataframe_theory_error_ellipse["meritValue"]), 305 cmap=CustomCMap, 306 ) 307 if mark_best_model: 308 ax.scatter( 309 df_error_ellipse.loc[min_index][nr_params - ix], 310 df.loc[min_index][iy + 1], 311 color="white", 312 marker="x", 313 ) 314 # Adjust x an y limits of subplots 315 limit_adjust = (max(df.iloc[:, iy + 1]) - min(df.iloc[:, iy + 1])) * 0.08 316 if limit_adjust == 0: 317 limit_adjust = 0.1 318 ax.set_ylim(min(df.iloc[:, iy + 1]) - limit_adjust, max(df.iloc[:, iy + 1]) + limit_adjust) 319 limit_adjust = (max(df.iloc[:, nr_params - ix]) - min(df.iloc[:, nr_params - ix])) * 0.08 320 if limit_adjust == 0: 321 limit_adjust = 0.1 322 ax.set_xlim( 323 min(df.iloc[:, nr_params - ix]) - limit_adjust, 324 max(df.iloc[:, nr_params - ix]) + limit_adjust, 325 ) 326 327 fig.align_labels() 328 # add subplot in top right for Kiel or HRD 329 ax_hrd = fig.add_axes([0.508, 0.65, 0.33, 0.33]) # X, Y, width, height 330 331 ax_hrd.set_xlabel(r"log(T$_{\mathrm{eff}}$ [K])", size=label_size) 332 ax_hrd.tick_params(labelsize=label_size - 4) 333 ax_hrd.invert_xaxis() 334 335 # Observations 336 if n_sigma_box != None: 337 obs_dataframe = pd.read_table(observations_file, sep="\s+", header=0, index_col="index") 338 if (("logL" in obs_dataframe.columns) or ("logg" in obs_dataframe.columns)) and ( 339 "Teff" in obs_dataframe.columns 340 ): 341 if "logL" not in obs_dataframe.columns: 342 logg_or_logL = "logg" 343 344 # Observed spectroscopic error bar, only added if observational constraints were provided. 345 # To add the 1 and n-sigma spectro error boxes, calculate their width (so 2 and 2*n sigma wide) 346 width_logTeff_sigma = np.log10( 347 obs_dataframe["Teff"].iloc[0] + obs_dataframe["Teff_err"].iloc[0] 348 ) - np.log10(obs_dataframe["Teff"].iloc[0] - obs_dataframe["Teff_err"].iloc[0]) 349 width_logTeff_nsigma = np.log10( 350 obs_dataframe["Teff"].iloc[0] + n_sigma_box * obs_dataframe["Teff_err"].iloc[0] 351 ) - np.log10(obs_dataframe["Teff"].iloc[0] - n_sigma_box * obs_dataframe["Teff_err"].iloc[0]) 352 errorbox_1s = patches.Rectangle( 353 ( 354 np.log10(obs_dataframe["Teff"].iloc[0] - obs_dataframe["Teff_err"].iloc[0]), 355 obs_dataframe[logg_or_logL].iloc[0] - obs_dataframe[f"{logg_or_logL}_err"].iloc[0], 356 ), 357 width_logTeff_sigma, 358 2 * obs_dataframe[f"{logg_or_logL}_err"].iloc[0], 359 linewidth=1.7, 360 edgecolor="cyan", 361 facecolor="none", 362 zorder=2.1, 363 ) 364 errorbox_ns = patches.Rectangle( 365 ( 366 np.log10(obs_dataframe["Teff"].iloc[0] - n_sigma_box * obs_dataframe["Teff_err"].iloc[0]), 367 obs_dataframe[logg_or_logL].iloc[0] - n_sigma_box * obs_dataframe[f"{logg_or_logL}_err"].iloc[0], 368 ), 369 width_logTeff_nsigma, 370 2 * n_sigma_box * obs_dataframe[f"{logg_or_logL}_err"].iloc[0], 371 linewidth=1.7, 372 edgecolor="cyan", 373 facecolor="none", 374 zorder=2.1, 375 ) 376 ax_hrd.add_patch(errorbox_1s) 377 ax_hrd.add_patch(errorbox_ns) 378 379 if logg_or_logL == "logg": 380 ax_hrd.invert_yaxis() 381 382 im = ax_hrd.scatter( 383 dataframe_theory["logTeff"], 384 dataframe_theory[logg_or_logL], 385 c=np.log10(dataframe_theory["meritValue"]), 386 cmap="Greys_r", 387 ) 388 im_error_ellipse = ax_hrd.scatter( 389 dataframe_theory_error_ellipse["logTeff"], 390 dataframe_theory_error_ellipse[logg_or_logL], 391 c=np.log10(dataframe_theory_error_ellipse["meritValue"]), 392 cmap=CustomCMap, 393 ) 394 ax_hrd.set_ylabel(f"{logg_or_logL[:-1]} {logg_or_logL[-1]}") 395 if logg_or_logL == "logL": 396 ax_hrd.set_ylabel(r"log(L/L$_{\odot}$)", size=label_size) 397 elif logg_or_logL == "logg": 398 ax_hrd.set_ylabel(r"$\log\,g$ [dex]", size=label_size) 399 400 ax_hrd.xaxis.set_minor_locator(AutoMinorLocator(2)) 401 ax_hrd.yaxis.set_minor_locator(AutoMinorLocator(2)) 402 ax_hrd.tick_params(which="major", length=6) 403 ax_hrd.tick_params(which="minor", length=4) 404 405 if mark_best_model: 406 ax_hrd.scatter( 407 dataframe_theory_error_ellipse["logTeff"][min_index], 408 dataframe_theory_error_ellipse[logg_or_logL][min_index], 409 marker="x", 410 color="white", 411 ) 412 413 # Add color bar 414 cax = fig.add_axes([0.856, 0.565, 0.04, 0.415]) # X, Y, width, height 415 cbar = fig.colorbar(im, cax=cax, orientation="vertical") 416 cax2 = fig.add_axes([0.856, 0.137, 0.04, 0.415]) # X, Y, width, height 417 cbar2 = fig.colorbar( 418 im_error_ellipse, 419 cax=cax2, 420 orientation="vertical", 421 ) 422 423 # To prevent messing up colours due to automatic rescaling of colorbar 424 if dataframe_theory_error_ellipse.shape[0] == 1: 425 im_error_ellipse.set_clim( 426 np.log10(dataframe_theory_error_ellipse["meritValue"]), 427 np.log10(dataframe_theory_error_ellipse["meritValue"]) * 1.1, 428 ) 429 430 if "_MD_" in fig_title: 431 cbar.set_label("log(MD)", rotation=90, size=label_size) 432 cbar2.set_label("log(MD)", rotation=90, size=label_size) 433 elif "_CS_" in fig_title: 434 cbar.set_label(r"log($\chi^2$)", rotation=90, size=label_size) 435 cbar2.set_label(r"log($\chi^2$)", rotation=90, size=label_size) 436 else: 437 cbar.set_label("log(merit function value)", rotation=90) 438 cbar.ax.tick_params(labelsize=label_size - 4) 439 cbar2.ax.tick_params(labelsize=label_size - 4) 440 fig.subplots_adjust(left=0.114, right=0.835, bottom=0.137, top=0.99) 441 442 # fig.suptitle(fig_title, horizontalalignment='left', size=20, x=0.28) 443 Path(fig_output_dir).mkdir(parents=True, exist_ok=True) 444 fig.savefig(f"{fig_output_dir}{fig_title}.png", dpi=400) 445 plt.clf() 446 plt.close(fig)
Make a plot of all variables vs each other variable, showing the MLE values as colorscale. A kiel/HR diagram is made, depending on if logg_obs or logL_obs is passed as a parameter. The subplots on the diagonal show the distribution of that variable. The list of variables is retrieved from columns of the merit_values_file, where the first column is 'meritValue', which are the MLE values. The resulting figure is saved afterwards in the specified location.
Parameters
- merit_values_file (string): Path to the hdf5 file with the merit function values and parameters of the models in the grid.
- merit_values_file_error_ellipse (string): Path to the hdf5 files with the merit function values and parameters of the models in the error ellipse.
- observations_file (string): Path to the tsv file with observations, with a column for each observable and each set of errors. Column names specify the observable, and "_err" suffix denotes that it's the error.
- fig_title (string): Title of the figure and name of the saved png.
- label_size (int): Size of the axis labels.
- fig_output_dir (string): Output directory for the figures.
- percentile_to_show (float): Percentile of models to show in the plots.
- logg_or_logL (string): String 'logg' or 'logL' indicating whether log of surface gravity (g) or luminosity (L) is plot.
- mark_best_model (boolean): Indicate the best model with a marker
- grid_parameters (list of string): List of the parameters in the theoretical grid.
- axis_labels_dict (dictionary): Keys are grid parameters, values are strings how those values should be shown on the axis labels
450def plot_mesa_file( 451 profile_file, 452 x_value, 453 y_value, 454 ax=None, 455 label_size=16, 456 colour="", 457 linestyle="solid", 458 alpha=1, 459 legend=True, 460 label=None, 461): 462 """ 463 Plot the requested quantities for the given MESA profile or history file. 464 465 Parameters 466 ---------- 467 profile_file: string 468 The path to the profile file to be used for the plotting. 469 x_value: string 470 The parameter of the profile plotted on the x axis. 471 If x_value is mass or radius, it will be put in units relative to the total mass or radius 472 y_value: string 473 The parameter of the profile plotted on the y axis. 474 ax: Axes 475 Axes object on which the plot will be made. If None: make figure and axis within this function. 476 label_size, alpha: float 477 The size of the labels in the figure, and transparency of the plot 478 colour: string 479 Colour of the plotted data. 480 linestyle: string 481 Linestyle of the plotted data. 482 label: string 483 Label of the plotted data. 484 legend: boolean 485 Flag to enable or disable a legend on the figure 486 """ 487 if ax is None: 488 fig = plt.figure() 489 ax = fig.add_subplot(111) 490 491 header, data = ffm.read_mesa_file(profile_file) 492 # from "data", extract the columns 493 y = np.asarray(data[y_value]) 494 x = np.asarray(data[x_value]) 495 if label == None: # Set the label to be the name of the y variable 496 label = y_value 497 if x_value == "radius" or x_value == "mass": 498 x = x / x[0] # normalized radius/mass coordinates 499 ax.set_xlim(0, 1) 500 # generate the plot, in which colour will not be specified 501 if colour == "": 502 ax.plot(x, y, label=label, linestyle=linestyle, alpha=alpha) 503 # generate the plot, in which colour will be specified 504 else: 505 ax.plot(x, y, label=label, linestyle=linestyle, alpha=alpha, color=colour) 506 if legend is True: 507 ax.legend(loc="best", prop={"size": label_size}) 508 ax.set_xlabel(x_value, size=label_size) 509 ax.set_ylabel(y_value, size=label_size)
Plot the requested quantities for the given MESA profile or history file.
Parameters
- profile_file (string): The path to the profile file to be used for the plotting.
- x_value (string): The parameter of the profile plotted on the x axis. If x_value is mass or radius, it will be put in units relative to the total mass or radius
- y_value (string): The parameter of the profile plotted on the y axis.
- ax (Axes): Axes object on which the plot will be made. If None: make figure and axis within this function.
- label_size, alpha (float): The size of the labels in the figure, and transparency of the plot
- colour (string): Colour of the plotted data.
- linestyle (string): Linestyle of the plotted data.
- label (string): Label of the plotted data.
- legend (boolean): Flag to enable or disable a legend on the figure
513def plot_mesh_histogram( 514 profile_file, 515 x_value="radius", 516 ax=None, 517 label_size=16, 518 colour="", 519 linestyle="solid", 520 alpha=1, 521 legend=True, 522 label=None, 523 bins=200, 524): 525 """ 526 Make a histogram of the mesh points in the MESA profile. 527 528 Parameters 529 ---------- 530 profile_file: string 531 The path to the profile file to be used for the plotting. 532 x_value: string 533 The x value to use for the histogram 534 If x_value is mass or radius, it will be put in units relative to the total mass or radius 535 ax: an axis object 536 Axes object on which the plot will be made. If None: make figure and axis within this function. 537 label_size: float 538 The size of the labels in the figure. 539 alpha: float 540 Transparency of the plot. 541 colour, linestyle, label: float 542 Settings for the plot 543 legend: boolean 544 Flag to enable or disable a legend on the figure. 545 bins: int 546 Number of bins used in the histogram 547 """ 548 if ax is None: 549 fig = plt.figure() 550 ax = fig.add_subplot(111) 551 552 header, data = ffm.read_mesa_file(profile_file) 553 print(f'Total zones of {profile_file} : {header["num_zones"]}') 554 555 # from "data", extract the columns 556 x = np.asarray(data[x_value]) 557 if label == None: # Set the label to be the name of the y variable 558 legend = False 559 if x_value == "radius" or x_value == "mass": 560 x = x / x[0] # normalized radius/mass coordinates 561 ax.set_xlim(0, 1) 562 # generate the plot, in which colour will not be specified 563 if colour == "": 564 ax.hist(x, bins=bins, histtype="step", label=label, alpha=alpha, linestyle=linestyle) 565 # generate the plot, in which colour will be specified 566 else: 567 ax.hist( 568 x, 569 bins=bins, 570 histtype="step", 571 label=label, 572 alpha=alpha, 573 linestyle=linestyle, 574 color=colour, 575 ) 576 # generate a legend if true 577 if legend is True: 578 ax.legend(loc="best", prop={"size": label_size}) 579 ax.set_xlabel(x_value, size=label_size) 580 ax.set_ylabel("Meshpoints", size=label_size)
Make a histogram of the mesh points in the MESA profile.
Parameters
- profile_file (string): The path to the profile file to be used for the plotting.
- x_value (string): The x value to use for the histogram If x_value is mass or radius, it will be put in units relative to the total mass or radius
- ax (an axis object): Axes object on which the plot will be made. If None: make figure and axis within this function.
- label_size (float): The size of the labels in the figure.
- alpha (float): Transparency of the plot.
- colour, linestyle, label (float): Settings for the plot
- legend (boolean): Flag to enable or disable a legend on the figure.
- bins (int): Number of bins used in the histogram
584def plot_hrd( 585 hist_file, 586 ax=None, 587 colour="blue", 588 linestyle="solid", 589 label="", 590 label_size=16, 591 Xc_marked=None, 592 Teff_logscale=True, 593 start_track_from_Xc=None, 594 diagram="HRD", 595): 596 """ 597 Makes an HRD plot from a provided MESA history file. 598 599 Parameters 600 ---------- 601 hist_file: string 602 The path to the profile file to be used for the plot. 603 ax: Axes 604 Axes object on which the plot will be made. If None: make figure and axis within this function. 605 colour: string 606 Colour of the plotted data. 607 linestyle: string 608 Linestyle of the plotted data. 609 label: string 610 Label of the plotted data. 611 label_size: float 612 The size of the labels in the figure. 613 Xc_marked: list of floats 614 Models with these Xc values are marked with red dots on the plot (listed in increasing value). 615 Teff_logscale: boolean 616 Plot effective temperature in logscale (True), or not (False). 617 start_track_from_Xc: float 618 Only start plotting the track if Xc drops below this value (e.g. to not plot the initial relaxation loop). 619 diagram: string 620 Type of diagram that is plotted. Options are HRD (logL vs logTeff), sHRD (log(Teff^4/g) vs logTeff) or kiel (logg vs logTeff). 621 """ 622 if ax is None: 623 fig = plt.figure() 624 ax = fig.add_subplot(111) 625 626 header, data = ffm.read_mesa_file(hist_file) 627 628 # From "data", extract the required columns as numpy arrays 629 log_L = np.asarray(data["log_L"]) 630 log_Teff = np.asarray(data["log_Teff"]) 631 log_g = np.asarray(data["log_g"]) 632 center_h1 = np.asarray(data["center_h1"]) 633 # Plot the x-axis in log scale 634 if Teff_logscale: 635 T = log_Teff 636 ax.set_xlabel(r"log(T$_{\mathrm{eff}}$)", size=label_size) 637 # Plot the x-axis in linear scale 638 else: 639 T = 10**log_Teff 640 ax.set_xlabel(r"T$_{\mathrm{eff}}$ [K]", size=label_size) 641 642 # Plot HRD 643 if diagram == "HRD": 644 y_axis = log_L 645 ax.set_ylabel(r"log(L/L$_{\odot}$)", size=label_size) 646 # Plot sHRD (log_Teff^4/log_g vs log_Teff) 647 elif diagram == "sHRD": 648 log_Lsun = 10.61 649 y_axis = 4 * log_Teff - log_g - log_Lsun 650 ax.set_ylabel( 651 r"$\log \left(\frac{{T_{\mathrm{eff}}}^4}{g}\right) \ (\mathscr{L}_\odot)$", 652 size=label_size, 653 ) 654 # Plot Kiel diagram (log_g vs log_Teff) 655 elif diagram == "kiel": 656 y_axis = log_g 657 ax.set_ylabel(r"log g [dex]", size=label_size) 658 659 # Start plotting from Xc value 660 if start_track_from_Xc != None: 661 for i in range(len(center_h1)): 662 if center_h1[i] < start_track_from_Xc: 663 T = T[i:] 664 y_axis = y_axis[i:] 665 break 666 667 # Plot the HRD diagram (log_L vs. T) 668 ax.plot(T, y_axis, color=colour, linestyle=linestyle, label=label) 669 670 # Put specific marks on the HRD diagram 671 if Xc_marked is None: 672 return 673 k = 0 674 for i in range(len(center_h1) - 1, -1, -1): 675 if center_h1[i] > Xc_marked[k]: 676 ax.scatter(T[i], log_L[i], marker="o", color="red", lw=2) 677 k += 1 678 if k >= len(Xc_marked): 679 return
Makes an HRD plot from a provided MESA history file.
Parameters
- hist_file (string): The path to the profile file to be used for the plot.
- ax (Axes): Axes object on which the plot will be made. If None: make figure and axis within this function.
- colour (string): Colour of the plotted data.
- linestyle (string): Linestyle of the plotted data.
- label (string): Label of the plotted data.
- label_size (float): The size of the labels in the figure.
- Xc_marked (list of floats): Models with these Xc values are marked with red dots on the plot (listed in increasing value).
- Teff_logscale (boolean): Plot effective temperature in logscale (True), or not (False).
- start_track_from_Xc (float): Only start plotting the track if Xc drops below this value (e.g. to not plot the initial relaxation loop).
- diagram (string): Type of diagram that is plotted. Options are HRD (logL vs logTeff), sHRD (log(Teff^4/g) vs logTeff) or kiel (logg vs logTeff).
683def plot_khd(hist_file, ax=None, number_mix_zones=8, xaxis="model_number"): 684 """ 685 Makes a Kippenhahn plot from a provided MESA history file. 686 687 Parameters 688 ---------- 689 hist_file: string 690 The path to the history file to be used for the plot. 691 ax: Axes 692 Axes object on which the plot will be made. If None: make figure and axis within this function. 693 number_mix_zones: int 694 Number of mixing zones included in the mesa history file. 695 xaxis: string 696 Quantity to put on the x-axis of the plot (e.g. model_number or star_age). 697 """ 698 if ax is None: 699 fig = plt.figure(figsize=(10, 4)) 700 ax = fig.add_subplot(111) 701 702 _, data = ffm.read_mesa_file(hist_file) 703 704 x_values = data[xaxis] 705 m_star = data["star_mass"] 706 m_ini = m_star[0] 707 708 for j in range(number_mix_zones): 709 colours = { 710 "-1": "w", 711 "0": "w", 712 "1": "lightgrey", 713 "2": "b", 714 "7": "g", 715 "3": "cornflowerblue", 716 "8": "red", 717 } 718 if j == number_mix_zones - 1: 719 ax.vlines( 720 x_values, 721 0, 722 data[f"mix_qtop_{number_mix_zones-j}"] * m_star / m_ini, 723 color=[colours[str(x)] for x in data[f"mix_type_{number_mix_zones-j}"]], 724 ) 725 else: 726 ax.vlines( 727 x_values, 728 data[f"mix_qtop_{number_mix_zones-1-j}"] * m_star / m_ini, 729 data[f"mix_qtop_{number_mix_zones-j}"] * m_star / m_ini, 730 color=[colours[str(x)] for x in data[f"mix_type_{number_mix_zones-j}"]], 731 ) 732 733 ax.plot(x_values, m_star / m_ini, lw=1, color="black", label=f"{m_ini:.1f} $M_\odot$") 734 735 ax.set_xlim(min(x_values) * 0.99, max(x_values) * 1.01) 736 ax.set_ylim(0, 1.02) 737 738 # Only to make it appear in the Legend 739 ax.plot([], [], lw=10, color="lightgrey", label=r"Convective") 740 # Only to make it appear in the Legend 741 ax.plot([], [], lw=10, color="cornflowerblue", label=r"CBM") 742 ax.legend( 743 bbox_to_anchor=(0.15, 0.97), 744 fontsize=10, 745 frameon=False, 746 fancybox=False, 747 shadow=False, 748 borderpad=False, 749 ) 750 ax.set_ylabel(r"Relative Mass $\, m/M_\star$") 751 ax.set_xlabel(xaxis) 752 plt.tight_layout() 753 754 return
Makes a Kippenhahn plot from a provided MESA history file.
Parameters
- hist_file (string): The path to the history file to be used for the plot.
- ax (Axes): Axes object on which the plot will be made. If None: make figure and axis within this function.
- number_mix_zones (int): Number of mixing zones included in the mesa history file.
- xaxis (string): Quantity to put on the x-axis of the plot (e.g. model_number or star_age).