Source code for species.plot.plot_evolution

"""
Module with functions for plotting the results obtained with the
:class:`~species.fit.fit_evolution.FitEvolution` class.
"""

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt

from beartype import beartype
from beartype.typing import List, Optional, Tuple
from matplotlib.ticker import AutoMinorLocator

from species.read.read_isochrone import ReadIsochrone
from species.util.core_util import print_section


[docs] @beartype def plot_cooling( tag: str, n_samples: int = 50, cooling_param: str = "log_lum", xlim: Optional[Tuple[float, float]] = None, ylim: Optional[Tuple[float, float]] = None, xscale: Optional[str] = "linear", yscale: Optional[str] = "linear", title: Optional[str] = None, offset: Optional[Tuple[float, float]] = None, figsize: Optional[Tuple[float, float]] = (4.0, 2.5), output: Optional[str] = None, ) -> Tuple[mpl.figure.Figure, List[List[List[np.ndarray]]], np.ndarray]: """ Function for plotting samples of cooling tracks that are randomly drawn from the posterior distributions of the age and mass parameters that have been estimated with :class:`~species.fit.fit_evolution.FitEvolution`. Parameters ---------- tag : str Database tag where the samples are stored n_samples : int Number of randomly drawn cooling tracks that will be plotted. cooling_param : str Type of cooling parameter that will be plotted ('log_lum', 'radius', 'teff', or 'logg'). xlim : tuple(float, float), None Limits of the wavelength axis. Automatic limits are used if the argument is set to ``None``. ylim : tuple(float, float), None Limits of the flux axis. Automatic limits are used if the argument is set to ``None``. xscale : str, None Scale of the x axis ('linear' or 'log'). The scale is set to ``'linear'`` if the argument is set to ``None``. yscale : str, None Scale of the x axis ('linear' or 'log'). The scale is set to ``'linear'`` if the argument is set to ``None``. title : str Title to show at the top of the plot. offset : tuple(float, float) Offset for the label of the x- and y-axis. figsize : tuple(float, float) Figure size. output : str, None Output filename for the plot. The plot is shown in an interface window if the argument is set to ``None``. Returns ------- matplotlib.figure.Figure The ``Figure`` object that can be used for further customization of the plot. np.ndarray Array with the cooling tracks. The array contains :math:`L/L_\\odot` or radius as function of time for each companion and sample, so the shape is (n_companions, n_samples, n_ages). np.ndarray Array with the random indices that have been sampled from the posterior distribution. """ print_section("Plot cooling tracks") print(f"Database tag: {tag}") print(f"Number of samples: {n_samples}") print(f"Model parameters: {cooling_param}") plt.close() if cooling_param not in ["log_lum", "luminosity", "radius", "teff", "logg"]: raise ValueError( "The argument of 'cooling_parameter' is " "not valid and should be set to " "'log_lum', 'radius', 'teff', or 'logg'." ) from species.data.database import Database species_db = Database() samples_box = species_db.get_samples(tag) samples = samples_box.samples attr = samples_box.attributes n_param = attr["n_param"] n_planets = attr["n_planets"] model_name = attr["model_name"] log_lum = attr["log_lum"] age_prior = attr["age_prior"] radius_prior = attr["radius_prior"] param_indices = {} for i in range(n_param): param_indices[samples_box.attributes[f"parameter{i}"]] = i if np.isnan(age_prior[0]): param_idx = samples_box.parameters.index("age") age_prior = np.percentile(samples[:, param_idx], [50.0, 16.0, 84.0]) else: # Asymmetric normal prior set in FitEvolution age_prior = [ age_prior[0], age_prior[0] + age_prior[1], age_prior[0] + age_prior[2], ] read_iso = ReadIsochrone(model_name) plt.rcParams["font.family"] = "serif" plt.rcParams["mathtext.fontset"] = "dejavuserif" fig = plt.figure(1, figsize=figsize) gridsp = mpl.gridspec.GridSpec(n_planets, 1) gridsp.update(wspace=0, hspace=0.1, left=0, right=1, bottom=0, top=1) ax = [] for i in range(n_planets): ax.append(plt.subplot(gridsp[i, 0])) if xscale is None: xscale = "linear" if yscale is None: yscale = "linear" cool_tracks = [] for i in range(n_planets): cool_tracks.append([]) for i in range(n_planets): if not isinstance(radius_prior[i], np.ndarray) and np.isnan(radius_prior[i]): param_idx = samples_box.parameters.index(f"radius_{i}") radius_tmp = np.percentile(samples[:, param_idx], [50.0, 16.0, 84.0]) else: radius_tmp = [ radius_prior[i][0], radius_prior[i][0] - radius_prior[i][1], radius_prior[i][0] + radius_prior[i][1], ] param_idx = samples_box.parameters.index(f"teff_{i}") teff_tmp = np.percentile(samples[:, param_idx], [50.0, 16.0, 84.0]) param_idx = samples_box.parameters.index(f"logg_{i}") logg_tmp = np.percentile(samples[:, param_idx], [50.0, 16.0, 84.0]) ax[i].set_xscale(xscale) ax[i].set_yscale(yscale) labelbottom = bool(i == n_planets - 1) ax[i].tick_params( axis="both", which="major", colors="black", labelcolor="black", direction="in", width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=labelbottom, ) ax[i].tick_params( axis="both", which="minor", colors="black", labelcolor="black", direction="in", width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=labelbottom, ) if xscale == "linear": ax[i].xaxis.set_minor_locator(AutoMinorLocator(5)) if i == n_planets - 1: ax[i].set_xlabel("Age (Myr)", fontsize=13) if cooling_param in ["luminosity", "log_lum"]: ax[i].set_ylabel(r"$\log(L/L_\odot)$", fontsize=13) elif cooling_param == "radius": ax[i].set_ylabel(r"Radius ($R_\mathrm{J}$)", fontsize=13) elif cooling_param == "teff": ax[i].set_ylabel(r"$T_\mathrm{eff}$ (K)", fontsize=13) elif cooling_param == "logg": ax[i].set_ylabel(r"$\log\,g$", fontsize=13) if xlim is not None: ax[i].set_xlim(xlim[0], xlim[1]) if ylim is not None: ax[i].set_ylim(ylim[0], ylim[1]) if offset is not None: ax[i].get_xaxis().set_label_coords(0.5, offset[0]) ax[i].get_yaxis().set_label_coords(offset[1], 0.5) ran_indices = np.random.randint(low=0, high=samples.shape[0], size=n_samples) for sample_idx in ran_indices: for planet_idx in range(n_planets): mass = samples[sample_idx, param_indices[f"mass_{planet_idx}"]] if f"s_init_{planet_idx}" in param_indices: s_init = samples[sample_idx, param_indices[f"s_init_{planet_idx}"]] else: s_init = None cool_box = read_iso.get_cooling_track( mass=mass, ages=None, s_init=s_init ) if cooling_param in ["luminosity", "log_lum"]: cool_tracks[planet_idx].append([cool_box.age, cool_box.log_lum]) elif cooling_param == "radius": cool_tracks[planet_idx].append([cool_box.age, cool_box.radius]) elif cooling_param == "teff": cool_tracks[planet_idx].append([cool_box.age, cool_box.teff]) elif cooling_param == "logg": cool_tracks[planet_idx].append([cool_box.age, cool_box.logg]) if cool_tracks[planet_idx][-1][1] is None: raise ValueError( f"The selected parameter, '{cooling_param}', " f"is not part of the '{model_name}' " "evolutionary model grid.") ax[planet_idx].plot( cool_tracks[planet_idx][-1][0], cool_tracks[planet_idx][-1][1], lw=0.5, color="gray", alpha=0.5, ) for i in range(n_planets): if cooling_param in ["luminosity", "log_lum"]: ax[i].errorbar( [age_prior[0]], [log_lum[i][0]], xerr=[ [age_prior[0] - np.abs(age_prior[1])], [age_prior[2] - age_prior[0]], ], yerr=[log_lum[i][1]], color="tab:orange", ) elif cooling_param == "radius": ax[i].errorbar( [age_prior[0]], [radius_tmp[0]], xerr=[ [age_prior[0] - np.abs(age_prior[1])], [age_prior[2] - age_prior[0]], ], yerr=[[radius_tmp[0] - radius_tmp[1]], [radius_tmp[2] - radius_tmp[0]]], color="tab:orange", ) elif cooling_param == "teff": ax[i].errorbar( [age_prior[0]], [teff_tmp[0]], xerr=[ [age_prior[0] - np.abs(age_prior[1])], [age_prior[2] - age_prior[0]], ], yerr=[[teff_tmp[0] - teff_tmp[1]], [teff_tmp[2] - teff_tmp[0]]], color="tab:orange", ) elif cooling_param == "logg": ax[i].errorbar( [age_prior[0]], [logg_tmp[0]], xerr=[ [age_prior[0] - np.abs(age_prior[1])], [age_prior[2] - age_prior[0]], ], yerr=[[logg_tmp[0] - logg_tmp[1]], [logg_tmp[2] - logg_tmp[0]]], color="tab:orange", ) if title is not None: ax[0].set_title(title, fontsize=18.0) if output is None: plt.show() else: print(f"\nOutput: {output}") plt.savefig(output, bbox_inches="tight") return fig, cool_tracks, ran_indices
[docs] @beartype def plot_isochrones( tag: str, n_samples: int = 50, xlim: Optional[Tuple[float, float]] = None, ylim: Optional[Tuple[float, float]] = None, xscale: Optional[str] = "linear", yscale: Optional[str] = "linear", title: Optional[str] = None, offset: Optional[Tuple[float, float]] = None, figsize: Optional[Tuple[float, float]] = (4.0, 2.5), output: Optional[str] = None, ) -> Tuple[mpl.figure.Figure, List[List[List[np.ndarray]]], np.ndarray]: """ Function for plotting samples of isochrones that are randomly drawn from the posterior distributions of the age and mass parameters that have been estimated with :class:`~species.fit.fit_evolution.FitEvolution`. For each isochrone, the parameters from a single posterior sample are used. Parameters ---------- tag : str Database tag where the samples are stored n_samples : int Number of randomly drawn cooling tracks that will be plotted. xlim : tuple(float, float), None Limits of the wavelength axis. Automatic limits are used if the argument is set to ``None``. ylim : tuple(float, float), None Limits of the flux axis. Automatic limits are used if the argument is set to ``None``. xscale : str, None Scale of the x axis ('linear' or 'log'). The scale is set to ``'linear'`` if the argument is set to ``None``. yscale : str, None Scale of the x axis ('linear' or 'log'). The scale is set to ``'linear'`` if the argument is set to ``None``. title : str Title to show at the top of the plot. offset : tuple(float, float) Offset for the label of the x- and y-axis. figsize : tuple(float, float) Figure size. output : str, None Output filename for the plot. The plot is shown in an interface window if the argument is set to ``None``. Returns ------- matplotlib.figure.Figure The ``Figure`` object that can be used for further customization of the plot. np.ndarray Array with the isochrones. The array contains :math:`L/L_\\odot` as function of time for each companions and sample, so the shape is (n_companions, n_samples, n_masses). np.ndarray Array with the random indices that have been sampled from the posterior distribution. """ print_section("Plot isochrones") print(f"Database tag: {tag}") print(f"Number of samples: {n_samples}") plt.close() from species.data.database import Database species_db = Database() samples_box = species_db.get_samples(tag) samples = samples_box.samples attr = samples_box.attributes n_param = samples_box.attributes["n_param"] n_planets = attr["n_planets"] model_name = attr["model_name"] log_lum = attr["log_lum"] age_prior = attr["age_prior"] mass_prior = attr["mass_prior"] param_indices = {} for i in range(n_param): param_indices[samples_box.attributes[f"parameter{i}"]] = i if np.isnan(age_prior[0]): param_idx = samples_box.parameters.index("age") age_prior = np.percentile(samples[:, param_idx], [50.0, 16.0, 84.0]) else: # Asymmetric normal prior set in FitEvolution age_prior = [ age_prior[0], age_prior[0] + age_prior[1], age_prior[0] + age_prior[2], ] read_iso = ReadIsochrone(model_name) plt.rcParams["font.family"] = "serif" plt.rcParams["mathtext.fontset"] = "dejavuserif" fig = plt.figure(1, figsize=figsize) gridsp = mpl.gridspec.GridSpec(n_planets, 1) gridsp.update(wspace=0, hspace=0.1, left=0, right=1, bottom=0, top=1) ax = [] for i in range(n_planets): ax.append(plt.subplot(gridsp[i, 0])) if xscale is None: xscale = "linear" if yscale is None: yscale = "linear" isochrones = [] for i in range(n_planets): isochrones.append([]) for i in range(n_planets): labelbottom = bool(i == n_planets - 1) ax[i].tick_params( axis="both", which="major", colors="black", labelcolor="black", direction="in", width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=labelbottom, ) ax[i].tick_params( axis="both", which="minor", colors="black", labelcolor="black", direction="in", width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=labelbottom, ) if xscale == "linear": ax[i].xaxis.set_minor_locator(AutoMinorLocator(5)) if i == n_planets - 1: ax[i].set_xlabel(r"Mass ($M_\mathrm{J}$)", fontsize=13) ax[i].set_ylabel(r"$\log(L/L_\odot)$", fontsize=13) ax[i].set_xscale(xscale) ax[i].set_yscale(yscale) if xlim is not None: ax[i].set_xlim(xlim[0], xlim[1]) if ylim is not None: ax[i].set_ylim(ylim[0], ylim[1]) if offset is not None: ax[i].get_xaxis().set_label_coords(0.5, offset[0]) ax[i].get_yaxis().set_label_coords(offset[1], 0.5) ran_indices = np.random.randint(low=0, high=samples.shape[0], size=n_samples) for sample_idx in ran_indices: for planet_idx in range(n_planets): age = samples[sample_idx, param_indices["age"]] if f"s_init_{planet_idx}" in param_indices: s_init = samples[sample_idx, param_indices[f"s_init_{planet_idx}"]] else: s_init = None iso_box = read_iso.get_isochrone( age=age, masses=None, s_init=s_init, param_interp=["log_lum"] ) isochrones[planet_idx].append([iso_box.mass, iso_box.log_lum]) ax[planet_idx].plot( isochrones[planet_idx][-1][0], isochrones[planet_idx][-1][1], lw=0.5, color="gray", alpha=0.5, ) for i in range(n_planets): if not isinstance(mass_prior[i], np.ndarray) and np.isnan(mass_prior[i]): param_idx = samples_box.parameters.index(f"mass_{i}") mass_tmp = np.percentile(samples[:, param_idx], [50.0, 16.0, 84.0]) else: mass_tmp = [ mass_prior[i][0], mass_prior[i][0] - mass_prior[i][1], mass_prior[i][0] + mass_prior[i][1], ] ax[i].errorbar( [mass_tmp[0]], [log_lum[i][0]], xerr=[[mass_tmp[0] - mass_tmp[1]], [mass_tmp[2] - mass_tmp[0]]], yerr=[log_lum[i][1]], color="tab:orange", ) if title is not None: ax[0].set_title(title, fontsize=18.0) if output is None: print(f"\nOutput: {output}") plt.show() else: plt.savefig(output, bbox_inches="tight") return fig, isochrones, ran_indices