Home Installation Walkthrough Pipeline modules Pipeline configuration Plotting tools Community guidelines

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################################################################################
def make_multipanel_plot( nr_panels=1, xlabel='', ylabels=[''], keys=None, title='', label_size=22, xlim=[], left_space=0.1, bottom_space=0.085, right_space=0.978, top_space=0.97, h_space=0.12, figure_size=[12, 8]):
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
def corner_plot( merit_values_file, merit_values_file_error_ellipse, fig_title, observations_file, label_size=20, fig_output_dir='figures_correlation/', percentile_to_show=0.5, logg_or_logL='logL', mark_best_model=False, n_sigma_box=3, grid_parameters=None, axis_labels_dict={'rot': '$\\Omega_{\\mathrm{rot}}$ [d$^{-1}$]', 'M': 'M$_{\\rm ini}$', 'Z': 'Z$_{\\rm ini}$', 'logD': 'log(D$_{\\rm env}$)', 'aov': '$\\alpha_{\\rm CBM}$', 'fov': 'f$_{\\rm CBM}$', 'Xc': '$\\rm X_c$'}):
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
def plot_mesa_file( profile_file, x_value, y_value, ax=None, label_size=16, colour='', linestyle='solid', alpha=1, legend=True, label=None):
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
def plot_mesh_histogram( profile_file, x_value='radius', ax=None, label_size=16, colour='', linestyle='solid', alpha=1, legend=True, label=None, bins=200):
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
def plot_hrd( hist_file, ax=None, colour='blue', linestyle='solid', label='', label_size=16, Xc_marked=None, Teff_logscale=True, start_track_from_Xc=None, diagram='HRD'):
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).
def plot_khd(hist_file, ax=None, number_mix_zones=8, xaxis='model_number'):
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).