Stellar model grids are typically constructed as a set of evolutionary tracks, where models of stellar evolution are run on grids of initial mass and metallicity, often with some other physical parameter varied as well (e.g., rotation, helium fraction, $\alpha$-abundance, etc.). Each of these evolutionary tracks predicts various physical properties (temperature, luminosity, etc.) of a star with given initial mass and metallicity, as a function of age.
It is also often of interest to re-organize these evolution track grids into "isochrones"---sets of stars at a range of masses, all with the same age. As described in this reference, in order to construct these isochrones, the time axis of each evolution track gets mapped into a new coordinate, called "equivalent evolutionary phase," or EEP. The principle of the EEPs is to first identify physically significant stages in stellar evolution, and then subdivide each of these stages into a number of equal steps. This adaptive sampling enables accurate interpolation between evolution tracks even at ages when stars are evolving quickly, in the post-main sequence phases.
Previous versions of isochrones relied directly on these precomputed isochrone grids and interpolated between grid points in (mass, age, feh)
space. This returned inaccurate results for post-MS stages of stellar evolution, and thus was not reliable for modeling evolved stars. However, beginning with v2.0, isochrones now implements all interpolation using EEPs. In addition, it provides direct access to the evolution track grids, in addition to precomputed isochrone grids. Note that version 2.0 includes only the MIST models; future updates will include more (e.g. PARSEC, YAPSI).
Isochrones provides a simple and direct interface to full grids of stellar models. Upon first access, the grids are downloaded in original form, reorganized, and written to disk in binary format in order to load quickly with subsequent access. The grids are loaded as pandas dataframes with multi-level indexing that reflects the structure of the grids: evolution track grids are indexed by metallicity, initial mass, and EEP; and isochone grids by metallicity, age, and EEP.
In [1]:
from isochrones.mist import MISTEvolutionTrackGrid, MISTIsochroneGrid
track_grid = MISTEvolutionTrackGrid()
track_grid.df.head() # just show first few rows
Out[1]:
In [2]:
iso_grid = MISTIsochroneGrid()
iso_grid.df.head() # just show first few rows
Out[2]:
This generally contains only a subset of the original columns provided by the underlying grid, with standardized names. There are also additional computed columns, such as stellar radius and density. The full, original grids, can be found with the .df_orig
attribute if desired:
In [3]:
iso_grid.df_orig.head() # just show first few rows
Out[3]:
In [4]:
iso_grid.df_orig.columns
Out[4]:
Any property (or properties) of these grids can be interpolated to any value of the index parameters via the .interp
method:
In [5]:
track_grid.interp([-0.12, 1.01, 353.1], ['mass', 'radius', 'logg', 'Teff'])
Out[5]:
Similarly, the .interp_orig
method interpolates any of the original columns by name:
In [6]:
track_grid.interp_orig([-0.12, 1.01, 353.1], ['v_wind_Km_per_s'])
Out[6]:
Note that these interpolations are fast---30-40x faster than the equivalent interpolation in scipy, for evaluating at a single point:
In [7]:
from scipy.interpolate import RegularGridInterpolator
grid = track_grid.interp.grid[:, :, :, 4] # subgrid corresponding to radius
interp = RegularGridInterpolator(track_grid.interp.index_columns, grid)
assert track_grid.interp([-0.12, 1.01, 353.1], ['radius']) == interp([-0.12, 1.01, 353.1])
In [8]:
%timeit interp([-0.12, 1.01, 353.1])
%timeit track_grid.interp([-0.12, 1.01, 353.1], ['radius'])
In order to select a subset of these grids, you can use pandas multi-index magic:
In [9]:
iso_grid.df.xs((9.0, 0.0), level=(0, 1)).head() # just show first few rows
Out[9]:
Just for fun, let's plot a few isochrones:
In [10]:
import hvplot.pandas
# Select two isochrones from the grid
iso_df1 = iso_grid.df.xs((9.0, 0.0), level=(0, 1))
iso_df2 = iso_grid.df.xs((9.5, 0.0), level=(0, 1))
options = dict(invert_xaxis=True, legend_position='bottom_left')
# Isn't hvplot/holoviews great?
plot1 = iso_df1.hvplot.line('logTeff', 'logL', label='Log(age) = 9.0')
plot2 = iso_df2.hvplot.line('logTeff', 'logL', label='Log(age) = 9.5')
(plot1 * plot2).options(**options)
Out[10]: